Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
243433d9d3 | ||
|
|
7a69a0335c | ||
|
|
16da928df8 | ||
|
|
4583162eda | ||
|
|
b20fea7c4f | ||
|
|
d9029c7f98 | ||
|
|
362ff69e65 | ||
|
|
ba43183f1b | ||
|
|
af959582d7 | ||
|
|
46b73173b5 | ||
|
|
8b66976c96 | ||
|
|
36e6261164 | ||
|
|
a629fdac0d | ||
|
|
3c7cc9ab84 | ||
|
|
c7f67bc52a | ||
|
|
dffd8c59ad | ||
|
|
fa9a78f0dc | ||
|
|
82cf622e9b | ||
|
|
5c29d76bca | ||
|
|
088ef2fbe8 | ||
|
|
498ac849fb | ||
|
|
d4c2436521 | ||
|
|
0f9b717c03 | ||
|
|
295d275d13 | ||
|
|
4c1f4451e2 | ||
|
|
0f1584c0fa | ||
|
|
0a42d7aa3c | ||
|
|
31dcbfb8de | ||
|
|
49fa59d3a9 | ||
|
|
c06a44c2fd | ||
|
|
4e8fb9915b | ||
|
|
cf5f57f07a | ||
|
|
fc03c8b43d | ||
|
|
b32f6ef51f | ||
|
|
9b3d851a9c | ||
|
|
8df803471c | ||
|
|
d55afa44c6 | ||
|
|
a061528731 | ||
|
|
9e030a6102 | ||
|
|
7c5ce0ce74 | ||
|
|
73340a466d | ||
|
|
94cc204e4f | ||
|
|
2ae2b88ea8 | ||
|
|
b3d203712a | ||
|
|
486cb79fab | ||
|
|
fe13173aaf | ||
|
|
d8a070d145 | ||
|
|
8a6fddd477 | ||
|
|
57a09f25eb | ||
|
|
3e320fd10c | ||
|
|
97bc7a3727 | ||
|
|
252886c2dc | ||
|
|
909db1170c | ||
|
|
6a68bef35a | ||
|
|
b04351f625 | ||
|
|
624f781c07 | ||
|
|
5cd8de0493 | ||
|
|
8742673da2 | ||
|
|
2fcbf077d0 | ||
|
|
2aee6bdf2d | ||
|
|
51f2530f79 | ||
|
|
1e2f170e95 | ||
|
|
9ed5936f9a | ||
|
|
bf5431a105 | ||
|
|
ab8da88aad | ||
|
|
670822ce63 | ||
|
|
d731e8ca4a | ||
|
|
a18dbf8704 | ||
|
|
bc53005f98 | ||
|
|
d538bbbd22 | ||
|
|
82716f12cb | ||
|
|
f84800a9d3 | ||
|
|
3eb509fdd2 | ||
|
|
71de5f0524 | ||
|
|
c2076c79df | ||
|
|
f0ab87fc1e | ||
|
|
b1cb6801c7 | ||
|
|
f249eab01e | ||
|
|
ced5a4386b | ||
|
|
e11285007e | ||
|
|
0db1f7279b | ||
|
|
f1b81fd726 | ||
|
|
b8d16dae6f | ||
|
|
b25d5f27d7 | ||
|
|
b9e5e490cd | ||
|
|
956773ee7e | ||
|
|
401ad781c3 | ||
|
|
4227d94603 | ||
|
|
bf745b352e | ||
|
|
3de677607c | ||
|
|
f178d999b3 | ||
|
|
bce07f3c27 | ||
|
|
c7f83ccda3 | ||
|
|
e174cbfbfe | ||
|
|
703a7d833f | ||
|
|
29f76a106c | ||
|
|
ba70744f14 | ||
|
|
54d72121c3 | ||
|
|
1b19c9a7bd | ||
|
|
13abbeb1b2 | ||
|
|
f97b3cea18 | ||
|
|
9a7bdc158a | ||
|
|
ed0eff4d1d | ||
|
|
b8f05b2b37 | ||
|
|
b9600fb6c4 | ||
|
|
5e26cd01b6 | ||
|
|
775803be1a | ||
|
|
9f16178898 | ||
|
|
7ddab53707 | ||
|
|
05e694f7e2 | ||
|
|
768ede8a1c | ||
|
|
2984771883 | ||
|
|
875d1007a9 | ||
|
|
1c8993695d | ||
|
|
71837bfbf0 | ||
|
|
df8a33b16a | ||
|
|
600c8022ba | ||
|
|
5c967ace04 | ||
|
|
87ab504e06 | ||
|
|
36ceb48e21 | ||
|
|
678fafe7c1 | ||
|
|
b5eb2dbd3e | ||
|
|
44bcc00fce | ||
|
|
2016b4b236 | ||
|
|
bd67df4d8c | ||
|
|
69ba46ec3e | ||
|
|
3cd1a7d1ce | ||
|
|
224f5e47fe | ||
|
|
0f75b92b25 | ||
|
|
7b7b08c49e | ||
|
|
46a293d6f5 | ||
|
|
166217a105 | ||
|
|
bddb614927 | ||
|
|
80866e3544 | ||
|
|
131db13763 |
@@ -4,6 +4,7 @@ notifications:
|
||||
irc: "irc.freenode.org#elixir-lang"
|
||||
recipients:
|
||||
- jose.valim@plataformatec.com.br
|
||||
- yrashk@gmail.com
|
||||
otp_release:
|
||||
- R15B02
|
||||
- R15B01
|
||||
|
||||
+31
-4
@@ -1,3 +1,30 @@
|
||||
# v0.7.2 (2012-12-04)
|
||||
|
||||
* enhancements
|
||||
* [CLI] `--debug-info` is now true by default
|
||||
* [ExUnit] Make ExUnit exit happen in two steps allowing developers to add custom `at_exit` hooks
|
||||
* [IEx] Many improvements to helpers functions `h/1`, `s/1` and others
|
||||
* [Kernel] Functions defined with `fn` can now handle many clauses
|
||||
* [Kernel] Raise an error if clauses with different arities are defined in the same function
|
||||
* [Kernel] `function` macro now accepts arguments in `M.f/a` and `f/a` formats
|
||||
* [Macro] Improvements to `Macro.to_binary`
|
||||
* [Mix] Mix now echoes the output as it comes when executing external commands such as git or rebar
|
||||
* [Mix] Mix now validates `application` callback's values
|
||||
* [Record] Record accessors are now optimized and can be up to 6x faster in some cases
|
||||
* [String] Support `\xXX` and `\x{HEX}` escape sequences in strings, char lists and regexes
|
||||
|
||||
* bug fix
|
||||
* [Bootstrap] Compiling Elixir source no longer fails if environment variables contain utf-8 entries
|
||||
* [IEx] IEx will now wait for all command line options to be processed before starting
|
||||
* [Kernel] Ensure proper stacktraces when showing deprecations
|
||||
|
||||
* deprecations
|
||||
* [Enum] `Enum.qsort` is deprecated in favor of `Enum.sort`
|
||||
* [ExUnit] `assert left in right` is deprecated in favor of `assert left inlist right`
|
||||
* [List] `List.sort` and `List.uniq` have been deprecated in favor of their `Enum` counterparts
|
||||
* [Record] Default-based generated functions are deprecated
|
||||
* [Typespec] Enhancements and deprecations to the `@spec/@callback` and the fun type syntax
|
||||
|
||||
# v0.7.1 (2012-11-18)
|
||||
|
||||
* enhancements
|
||||
@@ -25,9 +52,9 @@
|
||||
* [File] File is opened by default on binary mode instead of utf-8
|
||||
|
||||
* deprecations
|
||||
* [Behaviour] `defcallback/1` was deprecated in favor of `defcallback/2` which matches erlang `@callbacks`
|
||||
* [Enum] `Enum.times` was deprecated in favor of using ranges
|
||||
* [System] `halt` was moved to System module
|
||||
* [Behaviour] `defcallback/1` is deprecated in favor of `defcallback/2` which matches erlang `@callbacks`
|
||||
* [Enum] `Enum.times` is deprecated in favor of using ranges
|
||||
* [System] `halt` moved to `System` module
|
||||
|
||||
# v0.7.0 (2012-10-20)
|
||||
|
||||
@@ -89,7 +116,7 @@
|
||||
|
||||
* deprecations
|
||||
* [Access] The semantics of the access protocol were reduced from a broad query API to simple data structure key-based access
|
||||
* [ExUnit] Some assertions are deprecated in favor of simply using `assert()`
|
||||
* [ExUnit] Some assertions are deprecated in favor of simply using `assert()`
|
||||
* [File] `File.read_info` is deprecated in favor of `File.stat`
|
||||
* [IO] `IO.print` is deprecated in favor of `IO.write`
|
||||
* [Kernel] Deprecate `__LINE__` and `__FUNCTION__` in favor of `__ENV__.line` and `__ENV__.function`
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
REBAR:=$(shell echo `pwd`/rebar)
|
||||
ELIXIRC:=bin/elixirc --debug-info --ignore-module-conflict $(ELIXIRC_OPTS)
|
||||
ERLC:=erlc -I lib/elixir/include
|
||||
ERL:=erl -I lib/elixir/include -noshell -env ERL_LIBS $ERL_LIBS:lib
|
||||
VERSION:=0.7.1
|
||||
RELEASE_FLAG:=.release
|
||||
REBAR := $(shell echo `pwd`/rebar)
|
||||
ELIXIRC := bin/elixirc --ignore-module-conflict $(ELIXIRC_OPTS)
|
||||
ERLC := erlc -I lib/elixir/include
|
||||
ERL := erl -I lib/elixir/include -noshell -env ERL_LIBS $ERL_LIBS:lib
|
||||
VERSION := 0.7.2
|
||||
RELEASE_FLAG := .release
|
||||
INSTALL_PATH := /usr/local
|
||||
|
||||
.PHONY: 1
|
||||
.NOTPARALLEL: compile
|
||||
@@ -14,7 +15,7 @@ define APP_TEMPLATE
|
||||
$(1): lib/$(1)/ebin/Elixir-$(2).beam lib/$(1)/ebin/$(1).app
|
||||
|
||||
lib/$(1)/ebin/$(1).app:
|
||||
@ cd lib/$(1) && ../../bin/elixir ../../bin/mix compile.app
|
||||
@ cd lib/$(1) && ../../bin/mix compile.app
|
||||
|
||||
lib/$(1)/ebin/Elixir-$(2).beam: $(wildcard lib/$(1)/lib/*.ex) $(wildcard lib/$(1)/lib/*/*.ex) $(wildcard lib/$(1)/lib/*/*/*.ex)
|
||||
@ echo "==> $(1) (compile)"
|
||||
@@ -68,6 +69,17 @@ $(eval $(call APP_TEMPLATE,eex,EEx))
|
||||
$(eval $(call APP_TEMPLATE,mix,Mix))
|
||||
$(eval $(call APP_TEMPLATE,iex,IEx))
|
||||
|
||||
install: compile
|
||||
@ echo "==> elixir (install)"
|
||||
for dir in lib/*; do \
|
||||
install -m755 -d $(INSTALL_PATH)/lib/elixir/$$dir/ebin; \
|
||||
install -m644 $$dir/ebin/* $(INSTALL_PATH)/lib/elixir/$$dir/ebin; \
|
||||
done
|
||||
install -m755 -d $(INSTALL_PATH)/lib/elixir/bin
|
||||
install -m755 $(filter-out %.bat, $(wildcard bin/*)) $(INSTALL_PATH)/lib/elixir/bin
|
||||
install -m755 -d $(INSTALL_PATH)/bin
|
||||
ln -sf $(INSTALL_PATH)/lib/elixir/bin/* $(INSTALL_PATH)/bin
|
||||
|
||||
clean:
|
||||
@ cd lib/elixir && $(REBAR) clean
|
||||
rm -rf $(RELEASE_FLAG)
|
||||
@@ -77,15 +89,11 @@ clean:
|
||||
rm -rf lib/mix/test/fixtures/git_repo
|
||||
rm -rf lib/mix/tmp
|
||||
|
||||
#==> Release tasks (modules compiled with --debug-info and --docs)
|
||||
#==> Release tasks
|
||||
|
||||
$(RELEASE_FLAG): compile
|
||||
touch $(RELEASE_FLAG)
|
||||
|
||||
zip: $(RELEASE_FLAG)
|
||||
rm -rf v$(VERSION).zip
|
||||
zip -9 -r v$(VERSION).zip bin CHANGELOG.md LEGAL lib/*/ebin LICENSE README.md rel
|
||||
|
||||
docs: $(RELEASE_FLAG)
|
||||
mkdir -p ebin
|
||||
rm -rf docs
|
||||
@@ -93,6 +101,10 @@ docs: $(RELEASE_FLAG)
|
||||
bin/elixir ../exdoc/bin/exdoc
|
||||
rm -rf ebin
|
||||
|
||||
release_zip: $(RELEASE_FLAG)
|
||||
rm -rf v$(VERSION).zip
|
||||
zip -9 -r v$(VERSION).zip bin CHANGELOG.md LEGAL lib/*/ebin LICENSE README.md rel
|
||||
|
||||
release_docs: docs
|
||||
cd ../elixir-lang.github.com && git checkout master
|
||||
rm -rf ../elixir-lang.github.com/docs/master
|
||||
|
||||
@@ -23,6 +23,7 @@ This document simply outlines the release process:
|
||||
## Places where version is mentioned
|
||||
|
||||
* src/elixir.app.src
|
||||
* lib/elixir/src/elixir.app.src
|
||||
* lib/elixir/lib/system.ex
|
||||
* rel/reltool.config
|
||||
* Makefile
|
||||
|
||||
+3
-3
@@ -2,9 +2,9 @@
|
||||
if [ $# -eq 0 ]; then
|
||||
echo "Usage: `basename $0` [elixir switches] [compiler switches] [.ex files]
|
||||
|
||||
-o The directory to output compiled files
|
||||
--no-docs Do not attach documentation with compiled code
|
||||
--debug-info Attach debug info to compiled modules
|
||||
-o The directory to output compiled files
|
||||
--no-docs Do not attach documentation to compiled modules
|
||||
--no-debug-info Do not attach debug info to compiled modules
|
||||
--ignore-module-conflict
|
||||
|
||||
** Options given after -- are passed down to the executed code
|
||||
|
||||
+4
-7
@@ -5,14 +5,11 @@ if "%*" == "" (
|
||||
goto run
|
||||
)
|
||||
:documentation
|
||||
echo Usage: %~nx0 [switches] [.ex files]
|
||||
echo Usage: %~nx0 [elixir switches] [compiler switches] [.ex files]
|
||||
echo.
|
||||
echo -v Prints version and exit
|
||||
echo -o The directory to output compiled files
|
||||
echo -pa path Prepend the given path to Erlang code path (*)
|
||||
echo -pz path Append the given path to Erlang code path (*)
|
||||
echo --no-docs Do not attach documentation with compiled code
|
||||
echo --debug-info Attach debug info to compiled modules
|
||||
echo -o The directory to output compiled files
|
||||
echo --no-docs Do not attach documentation to compiled modules
|
||||
echo --no-debug-info Do not attach debug info to compiled modules
|
||||
echo --ignore-module-conflict
|
||||
echo.
|
||||
echo ** Options marked with (*) can be given more than once
|
||||
|
||||
@@ -11,4 +11,4 @@ readlink_f () {
|
||||
|
||||
SELF=$(readlink_f "$0")
|
||||
SCRIPT_PATH=$(dirname "$SELF")
|
||||
exec "$SCRIPT_PATH"/elixir --no-halt -e "IEx.cli" "$@"
|
||||
exec "$SCRIPT_PATH"/elixir --no-halt --erl "-user Elixir-IEx-CLI" "$@"
|
||||
+1
-1
@@ -1,2 +1,2 @@
|
||||
@echo off
|
||||
call "%~dp0\elixir.bat" --no-halt -e "IEx.cli" %*
|
||||
call "%~dp0\elixir.bat" --no-halt -e "IEx.start" %*
|
||||
@@ -1,3 +1,17 @@
|
||||
#!/usr/bin/env elixir
|
||||
Mix.start
|
||||
Mix.CLI.run
|
||||
#!/bin/sh
|
||||
readlink_f () {
|
||||
cd "$(dirname "$1")" > /dev/null
|
||||
local filename="$(basename "$1")"
|
||||
if [ -h "$filename" ]; then
|
||||
readlink_f "$(readlink "$filename")"
|
||||
else
|
||||
echo "`pwd -P`/$filename"
|
||||
fi
|
||||
}
|
||||
|
||||
SELF=$(readlink_f "$0")
|
||||
SCRIPT_PATH=$(dirname "$SELF")
|
||||
EXECUTABLE="elixir"
|
||||
|
||||
if [ "$1" = "iex" ]; then EXECUTABLE="iex"; fi
|
||||
exec "$SCRIPT_PATH"/$EXECUTABLE -e "Mix.start; Mix.CLI.run" -- "$@"
|
||||
+9
-1
@@ -1,2 +1,10 @@
|
||||
@echo off
|
||||
call "%~dp0\elixir.bat" "%~dp0\mix" %*
|
||||
if "%1" == "iex" (
|
||||
goto iex
|
||||
) else (
|
||||
goto elixir
|
||||
)
|
||||
:iex
|
||||
call "%~dp0\iex.bat" -e "Mix.start" -e "Mix.CLI.run" -- %*
|
||||
:elixir
|
||||
call "%~dp0\elixir.bat" -e "Mix.start" -e "Mix.CLI.run" -- %*
|
||||
@@ -68,7 +68,7 @@ defmodule EEx.Compiler do
|
||||
else
|
||||
key = length(state.dict)
|
||||
placeholder = '__EEX__(' ++ integer_to_list(key) ++ ');'
|
||||
{ current ++ placeholder ++ new_lines ++ chars, state.prepend_dict([{key, buffer}]) }
|
||||
{ current ++ placeholder ++ new_lines ++ chars, state.update_dict([{key, buffer}|&1]) }
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -16,10 +16,8 @@
|
||||
name_args=false, %% when true, it means arguments should be named
|
||||
module=nil, %% the current module
|
||||
function=nil, %% the current function
|
||||
recur=nil, %% the current loop function to be recurred
|
||||
vars=[], %% a dict of defined variables and their alias
|
||||
temp_vars=[], %% a dict of all variables defined in a particular assign
|
||||
quote_vars=[], %% a dict of all quoted variables
|
||||
clause_vars=nil, %% a dict of all variables defined in a particular clause
|
||||
extra_guards=nil, %% extra guards from args expansion
|
||||
counter=[], %% a counter for the variables defined
|
||||
|
||||
+25
-41
@@ -12,10 +12,10 @@ defmodule Behaviour do
|
||||
use Behaviour
|
||||
|
||||
@doc "Parses the given URL"
|
||||
defcallback parse(uri_info :: URI.Info.t), do: URI.Info.t
|
||||
defcallback parse(uri_info :: URI.Info.t) :: URI.Info.t
|
||||
|
||||
@doc "Defines a default port"
|
||||
defcallback default_port(), do: integer
|
||||
defcallback default_port() :: integer
|
||||
end
|
||||
|
||||
And then a specific module may use it as:
|
||||
@@ -46,10 +46,28 @@ defmodule Behaviour do
|
||||
Defines a callback according to the given type specification.
|
||||
"""
|
||||
defmacro defcallback(fun, do: return) do
|
||||
{ name, args } = :elixir_clauses.extract_args(fun)
|
||||
IO.write "[WARNING] defcallback(fun, do: return) is deprecated, please use defcallback(fun :: return) instead\n#{Exception.env_stacktrace(__CALLER__)}"
|
||||
do_defcallback(fun, return, __CALLER__)
|
||||
end
|
||||
|
||||
defmacro defcallback({ :::, _, [fun, return] }) do
|
||||
do_defcallback(fun, return, __CALLER__)
|
||||
end
|
||||
|
||||
defmacro defcallback(fun) do
|
||||
do_defcallback(fun, quote(do: term), __CALLER__)
|
||||
end
|
||||
|
||||
defp do_defcallback(fun, return, caller) do
|
||||
case Macro.extract_args(fun) do
|
||||
{ name, args } -> :ok
|
||||
:error ->
|
||||
raise ArgumentError, message: "invalid syntax in defcallback #{Macro.to_binary(fun)}"
|
||||
end
|
||||
|
||||
arity = length(args)
|
||||
|
||||
Enum.each args, (function do
|
||||
Enum.each args, fn
|
||||
{ :::, _, [left, right] } ->
|
||||
ensure_not_default(left)
|
||||
ensure_not_default(right)
|
||||
@@ -57,11 +75,11 @@ defmodule Behaviour do
|
||||
other ->
|
||||
ensure_not_default(other)
|
||||
other
|
||||
end)
|
||||
end
|
||||
|
||||
quote do
|
||||
@callback unquote(name)(unquote_splicing(args)), do: unquote(return)
|
||||
Behaviour.store_docs __MODULE__, unquote(__CALLER__.line), unquote(name), unquote(arity)
|
||||
@callback unquote(name)(unquote_splicing(args)) :: unquote(return)
|
||||
Behaviour.store_docs __MODULE__, unquote(caller.line), unquote(name), unquote(arity)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -78,36 +96,9 @@ defmodule Behaviour do
|
||||
Module.put_attribute module, :__behaviour_docs, { { name, arity }, line, doc }
|
||||
end
|
||||
|
||||
@doc false
|
||||
defmacro defcallback(fun) do
|
||||
IO.write "[WARNING] Behaviour.defcallback/1 is deprecated, please use defcallback/2 instead\n#{Exception.formatted_stacktrace}"
|
||||
|
||||
{ name, args } = :elixir_clauses.extract_args(fun)
|
||||
length = length(args)
|
||||
|
||||
{ args, defaults } = Enum.map_reduce(args, 0, function do
|
||||
{ ://, _, [expr, _] }, acc ->
|
||||
{ expr, acc + 1 }
|
||||
expr, acc ->
|
||||
{ expr, acc }
|
||||
end)
|
||||
|
||||
attributes = Enum.map (length - defaults)..length, fn(i) ->
|
||||
quote do
|
||||
@__behaviour_callbacks { unquote(name), unquote(i) }
|
||||
end
|
||||
end
|
||||
|
||||
quote do
|
||||
__block__ unquote(attributes)
|
||||
def unquote(name)(unquote_splicing(args))
|
||||
end
|
||||
end
|
||||
|
||||
@doc false
|
||||
defmacro __using__(_) do
|
||||
quote do
|
||||
Module.register_attribute(__MODULE__, :__behaviour_callbacks, accumulate: true)
|
||||
Module.register_attribute(__MODULE__, :__behaviour_docs, accumulate: true)
|
||||
@before_compile unquote(__MODULE__)
|
||||
import unquote(__MODULE__)
|
||||
@@ -117,13 +108,6 @@ defmodule Behaviour do
|
||||
@doc false
|
||||
defmacro __before_compile__(_) do
|
||||
quote do
|
||||
if @__behaviour_callbacks != [] do
|
||||
@doc false
|
||||
def behaviour_info(:callbacks) do
|
||||
@__behaviour_callbacks
|
||||
end
|
||||
end
|
||||
|
||||
@doc false
|
||||
def __behaviour__(:callbacks) do
|
||||
__MODULE__.behaviour_info(:callbacks)
|
||||
|
||||
+26
-26
@@ -32,17 +32,17 @@ defmodule Dict do
|
||||
@type value :: any
|
||||
@type t :: tuple
|
||||
|
||||
defcallback delete(t, key), do: t
|
||||
defcallback empty(t), do: t
|
||||
defcallback get(t, key, value), do: value
|
||||
defcallback has_key?(t, key), do: boolean
|
||||
defcallback keys(t), do: list(key)
|
||||
defcallback merge(t, t, fun(key, value, value, do: value)), do: t
|
||||
defcallback put(t, key, value), do: t
|
||||
defcallback size(t), do: non_neg_integer()
|
||||
defcallback to_list(t), do: list()
|
||||
defcallback update(t, key, fun(value, do: value)), do: t
|
||||
defcallback values(t), do: list(value)
|
||||
defcallback delete(t, key) :: t
|
||||
defcallback empty(t) :: t
|
||||
defcallback get(t, key, value) :: value
|
||||
defcallback has_key?(t, key) :: boolean
|
||||
defcallback keys(t) :: list(key)
|
||||
defcallback merge(t, t, (key, value, value -> value)) :: t
|
||||
defcallback put(t, key, value) :: t
|
||||
defcallback size(t) :: non_neg_integer()
|
||||
defcallback to_list(t) :: list()
|
||||
defcallback update(t, key, (value -> value)) :: t
|
||||
defcallback values(t) :: list(value)
|
||||
|
||||
@doc """
|
||||
Returns a list containing all dict's keys.
|
||||
@@ -55,7 +55,7 @@ defmodule Dict do
|
||||
Dict.keys d #=> [:a,:b]
|
||||
|
||||
"""
|
||||
@spec keys(t), do: [key]
|
||||
@spec keys(t) :: [key]
|
||||
def keys(dict) do
|
||||
elem(dict, 0).keys(dict)
|
||||
end
|
||||
@@ -69,7 +69,7 @@ defmodule Dict do
|
||||
Dict.values d #=> [1,2]
|
||||
|
||||
"""
|
||||
@spec values(t), do: [value]
|
||||
@spec values(t) :: [value]
|
||||
def values(dict) do
|
||||
elem(dict, 0).values(dict)
|
||||
end
|
||||
@@ -83,7 +83,7 @@ defmodule Dict do
|
||||
Dict.size d #=> 2
|
||||
|
||||
"""
|
||||
@spec size(t), do: non_neg_integer
|
||||
@spec size(t) :: non_neg_integer
|
||||
def size(dict) do
|
||||
elem(dict, 0).size(dict)
|
||||
end
|
||||
@@ -98,7 +98,7 @@ defmodule Dict do
|
||||
Dict.has_key?(d, :b) #=> false
|
||||
|
||||
"""
|
||||
@spec has_key?(t, key), do: boolean
|
||||
@spec has_key?(t, key) :: boolean
|
||||
def has_key?(dict, key) do
|
||||
elem(dict, 0).has_key?(dict, key)
|
||||
end
|
||||
@@ -115,8 +115,8 @@ defmodule Dict do
|
||||
Dict.get d, :b, 3 #=> 3
|
||||
|
||||
"""
|
||||
@spec get(t, key), do: value | nil
|
||||
@spec get(t, key, value), do: value
|
||||
@spec get(t, key) :: value | nil
|
||||
@spec get(t, key, value) :: value
|
||||
def get(dict, key, default // nil) do
|
||||
elem(dict, 0).get(dict, key, default)
|
||||
end
|
||||
@@ -132,7 +132,7 @@ defmodule Dict do
|
||||
Dict.get d, :b #=> raises KeyError[key: :b]
|
||||
|
||||
"""
|
||||
@spec get!(t, key), do: value | no_return
|
||||
@spec get!(t, key) :: value | no_return
|
||||
def get!(dict, key) do
|
||||
elem(dict, 0).get!(dict, key)
|
||||
end
|
||||
@@ -148,7 +148,7 @@ defmodule Dict do
|
||||
#=> [a: 3, b: 2]
|
||||
|
||||
"""
|
||||
@spec put(t, key, value), do: t
|
||||
@spec put(t, key, value) :: t
|
||||
def put(dict, key, val) do
|
||||
elem(dict, 0).put(dict, key, val)
|
||||
end
|
||||
@@ -166,7 +166,7 @@ defmodule Dict do
|
||||
Dict.delete d, :a #=> [b: 2]
|
||||
|
||||
"""
|
||||
@spec delete(t, key), do: t
|
||||
@spec delete(t, key) :: t
|
||||
def delete(dict, key) do
|
||||
elem(dict, 0).delete(dict, key)
|
||||
end
|
||||
@@ -185,7 +185,7 @@ defmodule Dict do
|
||||
#=> [a: 3, b: 2, d: 4]
|
||||
|
||||
"""
|
||||
@spec merge(t, t), do: t
|
||||
@spec merge(t, t) :: t
|
||||
def merge(dict1, dict2) do
|
||||
merge(dict1, dict2, fn(_k, _v1, v2) -> v2 end)
|
||||
end
|
||||
@@ -204,7 +204,7 @@ defmodule Dict do
|
||||
#=> [a: 4, b: 2, d: 4]
|
||||
|
||||
"""
|
||||
@spec merge(t, t, fun(key, value, value, do: value)), do: t
|
||||
@spec merge(t, t, (key, value, value -> value)) :: t
|
||||
def merge(dict1, dict2, fun) do
|
||||
elem(dict1, 0).merge(dict1, dict2, fun)
|
||||
end
|
||||
@@ -220,7 +220,7 @@ defmodule Dict do
|
||||
#=> [a: -1, b: 2]
|
||||
|
||||
"""
|
||||
@spec update(t, key, fun(value, do: value)), do: t
|
||||
@spec update(t, key, (value -> value)) :: t
|
||||
def update(dict, key, fun) do
|
||||
elem(dict, 0).update(dict, key, fun)
|
||||
end
|
||||
@@ -237,7 +237,7 @@ defmodule Dict do
|
||||
#=> [a: 1, b: 2, c: 3]
|
||||
|
||||
"""
|
||||
@spec update(t, key, value, fun(value, do: value)), do: t
|
||||
@spec update(t, key, value, (value -> value)) :: t
|
||||
def update(dict, key, initial, fun) do
|
||||
elem(dict, 0).update(dict, key, initial, fun)
|
||||
end
|
||||
@@ -245,7 +245,7 @@ defmodule Dict do
|
||||
@doc """
|
||||
Returns an empty dict of the same type as `dict`.
|
||||
"""
|
||||
@spec empty(t), do: t
|
||||
@spec empty(t) :: t
|
||||
def empty(dict) do
|
||||
elem(dict, 0).empty(dict)
|
||||
end
|
||||
@@ -254,7 +254,7 @@ defmodule Dict do
|
||||
Returns a list of key-value pairs stored in `dict`.
|
||||
No particular order is enforced.
|
||||
"""
|
||||
@spec to_list(t), do: list
|
||||
@spec to_list(t) :: list
|
||||
def to_list(dict) do
|
||||
elem(dict, 0).to_list(dict)
|
||||
end
|
||||
|
||||
+423
-166
@@ -91,8 +91,8 @@ defmodule Enum do
|
||||
Enum.all? [1,nil,3] #=> false
|
||||
|
||||
"""
|
||||
@spec all?(t), do: boolean
|
||||
@spec all?(t, fun(element, do: boolean)), do: boolean
|
||||
@spec all?(t) :: boolean
|
||||
@spec all?(t, (element -> boolean)) :: boolean
|
||||
|
||||
def all?(collection, fun // fn(x) -> x end)
|
||||
|
||||
@@ -128,8 +128,8 @@ defmodule Enum do
|
||||
Enum.any? [false,true,false] #=> true
|
||||
|
||||
"""
|
||||
@spec any?(t), do: boolean
|
||||
@spec any?(t, fun(element, do: boolean)), do: boolean
|
||||
@spec any?(t) :: boolean
|
||||
@spec any?(t, (element -> boolean)) :: boolean
|
||||
|
||||
def any?(collection, fun // fn(x) -> x end)
|
||||
|
||||
@@ -160,7 +160,7 @@ defmodule Enum do
|
||||
Enum.at! [2,4,6], 4 #=> raises Enum.OutOfBoundsError
|
||||
|
||||
"""
|
||||
@spec at!(t, non_neg_integer), do: element | no_return
|
||||
@spec at!(t, non_neg_integer) :: element | no_return
|
||||
def at!(collection, n) when is_list(collection) and n >= 0 do
|
||||
do_at!(collection, n)
|
||||
end
|
||||
@@ -182,7 +182,7 @@ defmodule Enum do
|
||||
Enum.count [1,2,3] #=> 3
|
||||
|
||||
"""
|
||||
@spec count(t), do: non_neg_integer
|
||||
@spec count(t) :: non_neg_integer
|
||||
def count(collection) do
|
||||
I.count(collection)
|
||||
end
|
||||
@@ -190,7 +190,7 @@ defmodule Enum do
|
||||
@doc """
|
||||
Counts for how many items the function returns true.
|
||||
"""
|
||||
@spec count(t, fun(element, do: boolean)), do: non_neg_integer
|
||||
@spec count(t, (element -> boolean)) :: non_neg_integer
|
||||
def count(collection, fun) when is_list(collection) do
|
||||
do_count(collection, fun)
|
||||
end
|
||||
@@ -205,8 +205,8 @@ defmodule Enum do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Drops the first `count` items from the collection. Expects an ordered
|
||||
collection.
|
||||
Drops the first `count` items from the collection.
|
||||
Expects an ordered collection.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -215,9 +215,23 @@ defmodule Enum do
|
||||
Enum.drop [1,2,3], 0 #=> [1,2,3]
|
||||
|
||||
"""
|
||||
@spec drop(t, non_neg_integer), do: t
|
||||
def drop(collection, count) do
|
||||
elem split(collection, count), 1
|
||||
@spec drop(t, integer) :: list
|
||||
def drop(collection, count) when is_list(collection) and count >= 0 do
|
||||
do_drop(collection, count)
|
||||
end
|
||||
|
||||
def drop(collection, count) when count >= 0 do
|
||||
case I.iterator(collection) do
|
||||
{ iterator, pointer } ->
|
||||
do_drop(pointer, iterator, count)
|
||||
list when is_list(list) ->
|
||||
do_drop(list, count)
|
||||
end
|
||||
end
|
||||
|
||||
def drop(collection, count) when count < 0 do
|
||||
{ list, count } = iterate_and_count(collection, count)
|
||||
drop(list, count)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -229,7 +243,7 @@ defmodule Enum do
|
||||
Enum.drop_while [1,2,3,4,5], fn(x) -> x < 3 end
|
||||
#=> [3,4,5]
|
||||
"""
|
||||
@spec drop_while(t, fun(element, do: boolean)), do: t
|
||||
@spec drop_while(t, (element -> boolean)) :: list
|
||||
def drop_while(collection, fun) when is_list(collection) do
|
||||
do_drop_while(collection, fun)
|
||||
end
|
||||
@@ -252,7 +266,7 @@ defmodule Enum do
|
||||
Enum.each ['some', 'example'], fn(x) -> IO.puts x end
|
||||
|
||||
"""
|
||||
@spec each(t, fun(element, do: any)), do: :ok
|
||||
@spec each(t, (element -> any)) :: :ok
|
||||
def each(collection, fun) when is_list(collection) do
|
||||
:lists.foreach(fun, collection)
|
||||
:ok
|
||||
@@ -277,7 +291,7 @@ defmodule Enum do
|
||||
Enum.empty? [1,2,3] #=> false
|
||||
|
||||
"""
|
||||
@spec empty?(t), do: boolean
|
||||
@spec empty?(t) :: boolean
|
||||
def empty?(collection) when is_list(collection) do
|
||||
collection == []
|
||||
end
|
||||
@@ -299,7 +313,7 @@ defmodule Enum do
|
||||
#=> [2]
|
||||
|
||||
"""
|
||||
@spec filter(t, fun(element, do: boolean)), do: t
|
||||
@spec filter(t, (element -> boolean)) :: list
|
||||
def filter(collection, fun) when is_list(collection) do
|
||||
lc item inlist collection, fun.(item), do: item
|
||||
end
|
||||
@@ -322,7 +336,7 @@ defmodule Enum do
|
||||
#=> [4]
|
||||
|
||||
"""
|
||||
@spec filter_map(t, fun(element, do: boolean), fun(element, do: element)), do: t
|
||||
@spec filter_map(t, (element -> boolean), (element -> element)) :: list
|
||||
def filter_map(collection, filter, mapper) when is_list(collection) do
|
||||
lc item inlist collection, filter.(item), do: mapper.(item)
|
||||
end
|
||||
@@ -352,8 +366,8 @@ defmodule Enum do
|
||||
#=> 3
|
||||
|
||||
"""
|
||||
@spec find(t, fun(element, do: any)), do: element | :nil
|
||||
@spec find(t, any, fun(element, do: any)), do: element | :nil
|
||||
@spec find(t, (element -> any)) :: element | :nil
|
||||
@spec find(t, any, (element -> any)) :: element | :nil
|
||||
|
||||
def find(collection, ifnone // nil, fun)
|
||||
|
||||
@@ -383,8 +397,8 @@ defmodule Enum do
|
||||
#=> true
|
||||
|
||||
"""
|
||||
@spec find_value(t, fun(element, do: any)), do: any | :nil
|
||||
@spec find_value(t, any, fun(element, do: any)), do: any | :nil
|
||||
@spec find_value(t, (element -> any)) :: any | :nil
|
||||
@spec find_value(t, any, (element -> any)) :: any | :nil
|
||||
|
||||
def find_value(collection, ifnone // nil, fun)
|
||||
|
||||
@@ -416,7 +430,7 @@ defmodule Enum do
|
||||
#=> 2
|
||||
|
||||
"""
|
||||
@spec find_index(t, fun(element, do: any)), do: non_neg_integer | :nil
|
||||
@spec find_index(t, (element -> any)) :: non_neg_integer | :nil
|
||||
def find_index(collection, fun) when is_list(collection) do
|
||||
do_find_index(collection, 0, fun)
|
||||
end
|
||||
@@ -439,7 +453,7 @@ defmodule Enum do
|
||||
Enum.first [1,2,3] #=> 1
|
||||
|
||||
"""
|
||||
@spec first(t), do: :nil | element
|
||||
@spec first(t) :: :nil | element
|
||||
def first([]), do: nil
|
||||
def first([h|_]), do: h
|
||||
|
||||
@@ -468,8 +482,8 @@ defmodule Enum do
|
||||
Enum.join([1,2,3], ' = ') #=> '1 = 2 = 3'
|
||||
|
||||
"""
|
||||
@spec join(t), do: String.t
|
||||
@spec join(t, String.t | char_list), do: String.t | char_list
|
||||
@spec join(t) :: String.t
|
||||
@spec join(t, String.t | char_list) :: String.t | char_list
|
||||
def join(collection, joiner // "")
|
||||
|
||||
def join(collection, joiner) when is_list(joiner) do
|
||||
@@ -503,7 +517,7 @@ defmodule Enum do
|
||||
#=> [a: -1, b: -2]
|
||||
|
||||
"""
|
||||
@spec map(t, fun(element, do: any)), do: t
|
||||
@spec map(t, (element -> any)) :: list
|
||||
def map(collection, fun) when is_list(collection) do
|
||||
lc item inlist collection, do: fun.(item)
|
||||
end
|
||||
@@ -534,8 +548,8 @@ defmodule Enum do
|
||||
Enum.map_join([1,2,3], ' = ', &1 * 2) #=> '2 = 4 = 6'
|
||||
|
||||
"""
|
||||
@spec map_join(t, fun(element, do: any)), do: String.t
|
||||
@spec map_join(t, String.t | char_list, fun(element, do: any)), do: String.t | char_list
|
||||
@spec map_join(t, (element -> any)) :: String.t
|
||||
@spec map_join(t, String.t | char_list, (element -> any)) :: String.t | char_list
|
||||
def map_join(collection, joiner // "", mapper)
|
||||
|
||||
def map_join(collection, joiner, mapper) when is_list(joiner) do
|
||||
@@ -570,7 +584,7 @@ defmodule Enum do
|
||||
#=> { [2, 4, 6], 6 }
|
||||
|
||||
"""
|
||||
@spec map_reduce(t, any, fun(element, any, do: any)), do: any
|
||||
@spec map_reduce(t, any, (element, any -> any)) :: any
|
||||
def map_reduce(collection, acc, f) when is_list(collection) do
|
||||
:lists.mapfoldl(f, acc, collection)
|
||||
end
|
||||
@@ -595,7 +609,7 @@ defmodule Enum do
|
||||
#=> { [2], [1,3] }
|
||||
|
||||
"""
|
||||
@spec partition(t, fun(element, do: any)), do: {t, t}
|
||||
@spec partition(t, (element -> any)) :: {list, list}
|
||||
def partition(collection, fun) when is_list(collection) do
|
||||
do_partition(collection, fun, [], [])
|
||||
end
|
||||
@@ -620,7 +634,7 @@ defmodule Enum do
|
||||
#=> 6
|
||||
|
||||
"""
|
||||
@spec reduce(t, any, fun(element, any, do: any)), do: any
|
||||
@spec reduce(t, any, (element, any -> any)) :: any
|
||||
def reduce(collection, acc, fun) when is_list(collection) do
|
||||
:lists.foldl(fun, acc, collection)
|
||||
end
|
||||
@@ -643,7 +657,7 @@ defmodule Enum do
|
||||
#=> [3, 2, 1]
|
||||
|
||||
"""
|
||||
@spec reverse(t), do: t
|
||||
@spec reverse(t) :: list
|
||||
def reverse(collection) when is_list(collection) do
|
||||
:lists.reverse(collection)
|
||||
end
|
||||
@@ -655,22 +669,17 @@ defmodule Enum do
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Sorts the collection according to the quick sort algorithm.
|
||||
|
||||
## Examples
|
||||
|
||||
Enum.qsort [3,2,1] #=> [1,2,3]
|
||||
|
||||
"""
|
||||
@spec qsort(t), do: t
|
||||
@doc false
|
||||
@spec qsort(t) :: list
|
||||
def qsort(collection) when is_list(collection) do
|
||||
IO.write "[WARNING] Enum.qsort is deprecated, please use Enum.sort instead\n#{Exception.formatted_stacktrace}"
|
||||
do_list_qsort(collection, [])
|
||||
end
|
||||
|
||||
def qsort(collection) do
|
||||
case I.iterator(collection) do
|
||||
{ iterator, pointer } ->
|
||||
IO.write "[WARNING] Enum.qsort is deprecated, please use Enum.sort instead\n#{Exception.formatted_stacktrace}"
|
||||
do_qsort(pointer, iterator, [])
|
||||
list when is_list(list) ->
|
||||
qsort(list)
|
||||
@@ -678,11 +687,58 @@ defmodule Enum do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Splits the enumerable into two collections, leaving `count` elements in the
|
||||
first one. If `count` is a negative number, it starts couting from the back
|
||||
to the beginning of the collection. Be aware that a negative `count`
|
||||
implies in an iteration through the whole collection.
|
||||
Expects an ordered collection.
|
||||
Sorts the collection using the merge sort algorithm.
|
||||
|
||||
## Examples
|
||||
|
||||
Enum.sort [3,2,1] #=> [1,2,3]
|
||||
|
||||
"""
|
||||
@spec sort(t) :: list
|
||||
def sort(collection) when is_list(collection) do
|
||||
:lists.sort(collection)
|
||||
end
|
||||
|
||||
def sort(collection) do
|
||||
case I.iterator(collection) do
|
||||
{ iterator, pointer } ->
|
||||
do_sort(pointer, iterator, &1 <= &2)
|
||||
list when is_list(list) ->
|
||||
sort(list)
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Sorts the collection using the merge sort algorithm.
|
||||
|
||||
## Examples
|
||||
|
||||
Enum.sort [3,2,1], &1 > &2 #=> [1,2,3]
|
||||
|
||||
"""
|
||||
@spec sort(t, (element, element -> boolean)) :: list
|
||||
def sort(collection, fun) when is_list(collection) do
|
||||
:lists.sort(fun, collection)
|
||||
end
|
||||
|
||||
def sort(collection, fun) do
|
||||
case I.iterator(collection) do
|
||||
{ iterator, pointer } ->
|
||||
do_sort(pointer, iterator, fun)
|
||||
list when is_list(list) ->
|
||||
sort(list, fun)
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Splits the enumerable into two collections, leaving `count`
|
||||
elements in the first one. If `count` is a negative number,
|
||||
it starts couting from the back to the beginning of the
|
||||
collection.
|
||||
|
||||
Be aware that a negative `count` implies the collection
|
||||
will be iterate twice. One to calculate the position and
|
||||
another one to do the actual splitting.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -693,7 +749,7 @@ defmodule Enum do
|
||||
Enum.split [1,2,3], -5 #=> { [], [1,2,3] }
|
||||
|
||||
"""
|
||||
@spec split(t, integer), do: {t, t}
|
||||
@spec split(t, integer) :: {list, list}
|
||||
def split(collection, count) when is_list(collection) and count >= 0 do
|
||||
do_split(collection, count, [])
|
||||
end
|
||||
@@ -708,10 +764,8 @@ defmodule Enum do
|
||||
end
|
||||
|
||||
def split(collection, count) when count < 0 do
|
||||
reducer = fn(x, acc) -> {x, acc + 1} end
|
||||
{ list , total_items } = Enum.map_reduce(collection, 0, reducer)
|
||||
real_count = max(0, total_items - abs(count))
|
||||
split(list, real_count)
|
||||
{ list, count } = iterate_and_count(collection, count)
|
||||
split(list, count)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -723,7 +777,7 @@ defmodule Enum do
|
||||
Enum.split_while [1,2,3,4], fn x -> x == 2 end
|
||||
#=> { [1], [2, 3, 4] }
|
||||
"""
|
||||
@spec split_while(t, fun(element, do: boolean)), do: {t, t}
|
||||
@spec split_while(t, (element -> boolean)) :: {list, list}
|
||||
def split_while(collection, fun) when is_list(collection) do
|
||||
do_split_while(collection, fun, [])
|
||||
end
|
||||
@@ -748,9 +802,23 @@ defmodule Enum do
|
||||
Enum.take [1,2,3], 0 #=> []
|
||||
|
||||
"""
|
||||
@spec take(t, non_neg_integer), do: t
|
||||
def take(collection, count) do
|
||||
elem split(collection, count), 0
|
||||
@spec take(t, integer) :: list
|
||||
def take(collection, count) when is_list(collection) and count >= 0 do
|
||||
do_take(collection, count)
|
||||
end
|
||||
|
||||
def take(collection, count) when count >= 0 do
|
||||
case I.iterator(collection) do
|
||||
{ iterator, pointer } ->
|
||||
do_take(pointer, iterator, count)
|
||||
list when is_list(list) ->
|
||||
do_take(list, count)
|
||||
end
|
||||
end
|
||||
|
||||
def take(collection, count) when count < 0 do
|
||||
{ list, count } = iterate_and_count(collection, count)
|
||||
take(list, count)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -763,7 +831,7 @@ defmodule Enum do
|
||||
#=> [1, 2]
|
||||
|
||||
"""
|
||||
@spec take_while(t, fun(element, do: boolean)), do: t
|
||||
@spec take_while(t, (element -> boolean)) :: list
|
||||
def take_while(collection, fun) when is_list(collection) do
|
||||
do_take_while(collection, fun)
|
||||
end
|
||||
@@ -777,22 +845,27 @@ defmodule Enum do
|
||||
end
|
||||
end
|
||||
|
||||
@doc false
|
||||
def times(times, function) when times >= 0 do
|
||||
IO.write "[WARNING] Enum.times/2 is deprecated, please use ranges instead\n#{Exception.formatted_stacktrace}"
|
||||
case is_function(function, 0) do
|
||||
true ->
|
||||
do_times_0(times, 1, function)
|
||||
_ ->
|
||||
do_times_1(times, 1, function)
|
||||
end
|
||||
times
|
||||
@doc """
|
||||
Iterates the enumerable removing all duplicated items.
|
||||
|
||||
## Examples
|
||||
|
||||
Enum.uniq [1,2,3,2,1]
|
||||
#=> [1, 2, 3]
|
||||
|
||||
"""
|
||||
@spec uniq(t) :: list
|
||||
def uniq(collection) when is_list(collection) do
|
||||
do_uniq(collection, [])
|
||||
end
|
||||
|
||||
@doc false
|
||||
def times(times, acc, function) when times >= 0 do
|
||||
IO.write "[WARNING] Enum.times/3 is deprecated, please use ranges instead\n#{Exception.formatted_stacktrace}"
|
||||
do_times_2(times, 1, function, acc)
|
||||
def uniq(collection) do
|
||||
case I.iterator(collection) do
|
||||
{ iterator, pointer } ->
|
||||
do_uniq(pointer, iterator, [])
|
||||
list when is_list(list) ->
|
||||
do_uniq(list, [])
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -801,7 +874,7 @@ defmodule Enum do
|
||||
dictated by the first enum. In case the second list is shorter,
|
||||
values are filled with nil.
|
||||
"""
|
||||
@spec zip(t, t), do: [{any, any}]
|
||||
@spec zip(t, t) :: [{any, any}]
|
||||
def zip(coll1, coll2) when is_list(coll1) do
|
||||
do_zip(coll1, iterator(coll2))
|
||||
end
|
||||
@@ -828,6 +901,25 @@ defmodule Enum do
|
||||
[]
|
||||
end
|
||||
|
||||
defp iterate_and_count(collection, count) do
|
||||
{ list, total_items } = do_iterate_and_count(collection)
|
||||
{ list, max(0, total_items - abs(count)) }
|
||||
end
|
||||
|
||||
defp do_iterate_and_count(collection) when is_list(collection) do
|
||||
{ collection, length(collection) }
|
||||
end
|
||||
|
||||
defp do_iterate_and_count(collection) do
|
||||
case I.iterator(collection) do
|
||||
{ iterator, pointer } ->
|
||||
reducer = fn(x, acc) -> { x, acc + 1 } end
|
||||
do_map_reduce(pointer, iterator, [], 0, reducer)
|
||||
list when is_list(list) ->
|
||||
do_iterate_and_count(list)
|
||||
end
|
||||
end
|
||||
|
||||
## Implementations
|
||||
|
||||
## all?
|
||||
@@ -918,6 +1010,32 @@ defmodule Enum do
|
||||
0
|
||||
end
|
||||
|
||||
## drop
|
||||
|
||||
defp do_drop([_|t], counter) when counter > 0 do
|
||||
do_drop(t, counter - 1)
|
||||
end
|
||||
|
||||
defp do_drop(list, 0) do
|
||||
list
|
||||
end
|
||||
|
||||
defp do_drop([], _) do
|
||||
[]
|
||||
end
|
||||
|
||||
defp do_drop({ _, next }, iterator, counter) when counter > 0 do
|
||||
do_drop(iterator.(next), iterator, counter - 1)
|
||||
end
|
||||
|
||||
defp do_drop(extra, iterator, 0) do
|
||||
to_list(extra, iterator)
|
||||
end
|
||||
|
||||
defp do_drop(:stop, _, _) do
|
||||
[]
|
||||
end
|
||||
|
||||
## drop_while
|
||||
|
||||
defp do_drop_while([h|t], fun) do
|
||||
@@ -970,24 +1088,6 @@ defmodule Enum do
|
||||
ifnone
|
||||
end
|
||||
|
||||
## find_value
|
||||
|
||||
defp do_find_value([h|t], ifnone, fun) do
|
||||
fun.(h) || do_find_value(t, ifnone, fun)
|
||||
end
|
||||
|
||||
defp do_find_value([], ifnone, _) do
|
||||
ifnone
|
||||
end
|
||||
|
||||
defp do_find_value({ h, next }, iterator, ifnone, fun) do
|
||||
fun.(h) || do_find_value(iterator.(next), iterator, ifnone, fun)
|
||||
end
|
||||
|
||||
defp do_find_value(:stop, _, ifnone, _) do
|
||||
ifnone
|
||||
end
|
||||
|
||||
## find_index
|
||||
|
||||
defp do_find_index([h|t], counter, fun) do
|
||||
@@ -1014,6 +1114,24 @@ defmodule Enum do
|
||||
nil
|
||||
end
|
||||
|
||||
## find_value
|
||||
|
||||
defp do_find_value([h|t], ifnone, fun) do
|
||||
fun.(h) || do_find_value(t, ifnone, fun)
|
||||
end
|
||||
|
||||
defp do_find_value([], ifnone, _) do
|
||||
ifnone
|
||||
end
|
||||
|
||||
defp do_find_value({ h, next }, iterator, ifnone, fun) do
|
||||
fun.(h) || do_find_value(iterator.(next), iterator, ifnone, fun)
|
||||
end
|
||||
|
||||
defp do_find_value(:stop, _, ifnone, _) do
|
||||
ifnone
|
||||
end
|
||||
|
||||
## each
|
||||
|
||||
defp do_each({ h, next }, iterator, fun) do
|
||||
@@ -1053,42 +1171,6 @@ defmodule Enum do
|
||||
[]
|
||||
end
|
||||
|
||||
## reduce
|
||||
|
||||
defp do_reduce({ h, next }, iterator, acc, fun) do
|
||||
do_reduce(iterator.(next), iterator, fun.(h, acc), fun)
|
||||
end
|
||||
|
||||
defp do_reduce(:stop, _, acc, _) do
|
||||
acc
|
||||
end
|
||||
|
||||
## split_while
|
||||
|
||||
defp do_split_while([h|t], fun, acc) do
|
||||
if fun.(h) do
|
||||
do_split_while(t, fun, [h|acc])
|
||||
else
|
||||
{ :lists.reverse(acc), [h|t] }
|
||||
end
|
||||
end
|
||||
|
||||
defp do_split_while([], _, acc) do
|
||||
{ :lists.reverse(acc), [] }
|
||||
end
|
||||
|
||||
defp do_split_while({ h, next } = extra, iterator, fun, acc) do
|
||||
if fun.(h) do
|
||||
do_split_while(iterator.(next), iterator, fun, [h|acc])
|
||||
else
|
||||
{ :lists.reverse(acc), to_list(extra, iterator) }
|
||||
end
|
||||
end
|
||||
|
||||
defp do_split_while(:stop, _, _, acc) do
|
||||
{ :lists.reverse(acc), [] }
|
||||
end
|
||||
|
||||
## join
|
||||
|
||||
defp do_join([h|t], joiner, nil) do
|
||||
@@ -1117,6 +1199,16 @@ defmodule Enum do
|
||||
acc || ""
|
||||
end
|
||||
|
||||
## map
|
||||
|
||||
defp do_map({ h, next }, iterator, fun) do
|
||||
[fun.(h)|do_map(iterator.(next), iterator, fun)]
|
||||
end
|
||||
|
||||
defp do_map(:stop, _, _) do
|
||||
[]
|
||||
end
|
||||
|
||||
## map join
|
||||
|
||||
defp do_map_join([h|t], mapper, joiner, nil) do
|
||||
@@ -1145,16 +1237,6 @@ defmodule Enum do
|
||||
acc || ""
|
||||
end
|
||||
|
||||
## map
|
||||
|
||||
defp do_map({ h, next }, iterator, fun) do
|
||||
[fun.(h)|do_map(iterator.(next), iterator, fun)]
|
||||
end
|
||||
|
||||
defp do_map(:stop, _, _) do
|
||||
[]
|
||||
end
|
||||
|
||||
## map_reduce
|
||||
|
||||
defp do_map_reduce({ h, next }, iterator, list_acc, acc, f) do
|
||||
@@ -1192,6 +1274,16 @@ defmodule Enum do
|
||||
{ :lists.reverse(acc1), :lists.reverse(acc2) }
|
||||
end
|
||||
|
||||
## reduce
|
||||
|
||||
defp do_reduce({ h, next }, iterator, acc, fun) do
|
||||
do_reduce(iterator.(next), iterator, fun.(h, acc), fun)
|
||||
end
|
||||
|
||||
defp do_reduce(:stop, _, acc, _) do
|
||||
acc
|
||||
end
|
||||
|
||||
## reverse
|
||||
|
||||
defp do_reverse({ h, next }, iterator, acc) do
|
||||
@@ -1202,6 +1294,174 @@ defmodule Enum do
|
||||
acc
|
||||
end
|
||||
|
||||
## sort
|
||||
|
||||
defp do_sort(extra, iterator, fun) do
|
||||
case sort_take(extra, iterator, 2, []) do
|
||||
{ [y, x], next } -> sort_split(y, x, next, iterator, fun, [], [], fun.(x, y))
|
||||
{ other, _ } -> other
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
defp sort_take({ h, next }, iterator, counter, acc) when counter > 0 do
|
||||
sort_take(iterator.(next), iterator, counter - 1, [h|acc])
|
||||
end
|
||||
|
||||
defp sort_take(extra, _iterator, 0, acc) do
|
||||
{ acc, extra }
|
||||
end
|
||||
|
||||
defp sort_take(:stop, _, _, acc) do
|
||||
{ acc, :stop }
|
||||
end
|
||||
|
||||
|
||||
defp sort_split(y, x, { z, next }, iterator, fun, r, rs, bool) do
|
||||
cond do
|
||||
fun.(y, z) == bool ->
|
||||
sort_split(z, y, iterator.(next), iterator, fun, [x | r], rs, bool)
|
||||
fun.(x, z) == bool ->
|
||||
sort_split(y, z, iterator.(next), iterator, fun, [x | r], rs, bool)
|
||||
r == [] ->
|
||||
sort_split(y, x, iterator.(next), iterator, fun, [z], rs, bool)
|
||||
true ->
|
||||
sort_split_pivot(y, x, iterator.(next), iterator, fun, r, rs, z, bool)
|
||||
end
|
||||
end
|
||||
|
||||
defp sort_split(y, x, :stop, _iterator, fun, r, rs, bool) do
|
||||
sort_merge([[y, x | r] | rs], fun, bool)
|
||||
end
|
||||
|
||||
defp sort_split_pivot(y, x, { z, next }, iterator, fun, r, rs, s, bool) do
|
||||
cond do
|
||||
fun.(y, z) == bool ->
|
||||
sort_split_pivot(z, y, iterator.(next), iterator, fun, [x | r], rs, s, bool)
|
||||
fun.(x, z) == bool ->
|
||||
sort_split_pivot(y, z, iterator.(next), iterator, fun, [x | r], rs, s, bool)
|
||||
fun.(s, z) == bool ->
|
||||
sort_split(z, s, iterator.(next), iterator, fun, [], [[y, x | r] | rs], bool)
|
||||
true ->
|
||||
sort_split(s, z, iterator.(next), iterator, fun, [], [[y, x | r] | rs], bool)
|
||||
end
|
||||
end
|
||||
|
||||
defp sort_split_pivot(y, x, :stop, _iterator, fun, r, rs, s, bool) do
|
||||
sort_merge([[s], [[y, x | r] | rs]], fun, bool)
|
||||
end
|
||||
|
||||
|
||||
defp sort_merge(list, fun, true), do:
|
||||
reverse_sort_merge(list, [], fun, true)
|
||||
|
||||
defp sort_merge(list, fun, false), do:
|
||||
sort_merge(list, [], fun, false)
|
||||
|
||||
|
||||
defp sort_merge([t1, [h2 | t2] | l], acc, fun, true), do:
|
||||
sort_merge(l, [sort_merge_1(t1, h2, t2, [], fun, false) | acc], fun, true)
|
||||
|
||||
defp sort_merge([[h2 | t2], t1 | l], acc, fun, false), do:
|
||||
sort_merge(l, [sort_merge_1(t1, h2, t2, [], fun, false) | acc], fun, false)
|
||||
|
||||
defp sort_merge([l], [], _fun, _bool), do: l
|
||||
|
||||
defp sort_merge([l], acc, fun, bool), do:
|
||||
reverse_sort_merge([:lists.reverse(l, []) | acc], [], fun, bool)
|
||||
|
||||
defp sort_merge([], acc, fun, bool), do:
|
||||
reverse_sort_merge(acc, [], fun, bool)
|
||||
|
||||
|
||||
defp reverse_sort_merge([[h2 | t2], t1 | l], acc, fun, true), do:
|
||||
reverse_sort_merge(l, [sort_merge_1(t1, h2, t2, [], fun, true) | acc], fun, true)
|
||||
|
||||
defp reverse_sort_merge([t1, [h2 | t2] | l], acc, fun, false), do:
|
||||
reverse_sort_merge(l, [sort_merge_1(t1, h2, t2, [], fun, true) | acc], fun, false)
|
||||
|
||||
defp reverse_sort_merge([l], acc, fun, bool), do:
|
||||
sort_merge([:lists.reverse(l, []) | acc], [], fun, bool)
|
||||
|
||||
defp reverse_sort_merge([], acc, fun, bool), do:
|
||||
sort_merge(acc, [], fun, bool)
|
||||
|
||||
|
||||
defp sort_merge_1([h1 | t1], h2, t2, m, fun, bool) do
|
||||
if fun.(h1, h2) == bool do
|
||||
sort_merge_2(h1, t1, t2, [h2 | m], fun, bool)
|
||||
else
|
||||
sort_merge_1(t1, h2, t2, [h1 | m], fun, bool)
|
||||
end
|
||||
end
|
||||
|
||||
defp sort_merge_1([], h2, t2, m, _fun, _bool), do:
|
||||
:lists.reverse(t2, [h2 | m])
|
||||
|
||||
|
||||
defp sort_merge_2(h1, t1, [h2 | t2], m, fun, bool) do
|
||||
if fun.(h1, h2) == bool do
|
||||
sort_merge_2(h1, t1, t2, [h2 | m], fun, bool)
|
||||
else
|
||||
sort_merge_1(t1, h2, t2, [h1 | m], fun, bool)
|
||||
end
|
||||
end
|
||||
|
||||
defp sort_merge_2(h1, t1, [], m, _fun, _bool), do:
|
||||
:lists.reverse(t1, [h1 | m])
|
||||
|
||||
## split
|
||||
|
||||
defp do_split([h|t], counter, acc) when counter > 0 do
|
||||
do_split(t, counter - 1, [h|acc])
|
||||
end
|
||||
|
||||
defp do_split(list, 0, acc) do
|
||||
{ :lists.reverse(acc), list }
|
||||
end
|
||||
|
||||
defp do_split([], _, acc) do
|
||||
{ :lists.reverse(acc), [] }
|
||||
end
|
||||
|
||||
defp do_split({ h, next }, iterator, counter, acc) when counter > 0 do
|
||||
do_split(iterator.(next), iterator, counter - 1, [h|acc])
|
||||
end
|
||||
|
||||
defp do_split(extra, iterator, 0, acc) do
|
||||
{ :lists.reverse(acc), to_list(extra, iterator) }
|
||||
end
|
||||
|
||||
defp do_split(:stop, _, _, acc) do
|
||||
{ :lists.reverse(acc), [] }
|
||||
end
|
||||
|
||||
## split_while
|
||||
|
||||
defp do_split_while([h|t], fun, acc) do
|
||||
if fun.(h) do
|
||||
do_split_while(t, fun, [h|acc])
|
||||
else
|
||||
{ :lists.reverse(acc), [h|t] }
|
||||
end
|
||||
end
|
||||
|
||||
defp do_split_while([], _, acc) do
|
||||
{ :lists.reverse(acc), [] }
|
||||
end
|
||||
|
||||
defp do_split_while({ h, next } = extra, iterator, fun, acc) do
|
||||
if fun.(h) do
|
||||
do_split_while(iterator.(next), iterator, fun, [h|acc])
|
||||
else
|
||||
{ :lists.reverse(acc), to_list(extra, iterator) }
|
||||
end
|
||||
end
|
||||
|
||||
defp do_split_while(:stop, _, _, acc) do
|
||||
{ :lists.reverse(acc), [] }
|
||||
end
|
||||
|
||||
## qsort (lists)
|
||||
|
||||
defp do_list_qsort([], acc) do
|
||||
@@ -1252,30 +1512,30 @@ defmodule Enum do
|
||||
end
|
||||
end
|
||||
|
||||
## split
|
||||
## take
|
||||
|
||||
defp do_split([h|t], counter, acc) when counter > 0 do
|
||||
do_split(t, counter - 1, [h|acc])
|
||||
defp do_take([h|t], counter) when counter > 0 do
|
||||
[h|do_take(t, counter - 1)]
|
||||
end
|
||||
|
||||
defp do_split(list, 0, acc) do
|
||||
{ :lists.reverse(acc), list }
|
||||
defp do_take(_list, 0) do
|
||||
[]
|
||||
end
|
||||
|
||||
defp do_split([], _, acc) do
|
||||
{ :lists.reverse(acc), [] }
|
||||
defp do_take([], _) do
|
||||
[]
|
||||
end
|
||||
|
||||
defp do_split({ h, next }, iterator, counter, acc) when counter > 0 do
|
||||
do_split(iterator.(next), iterator, counter - 1, [h|acc])
|
||||
defp do_take({ h, next }, iterator, counter) when counter > 0 do
|
||||
[h|do_take(iterator.(next), iterator, counter - 1)]
|
||||
end
|
||||
|
||||
defp do_split(extra, iterator, 0, acc) do
|
||||
{ :lists.reverse(acc), to_list(extra, iterator) }
|
||||
defp do_take(_extra, _iterator, 0) do
|
||||
[]
|
||||
end
|
||||
|
||||
defp do_split(:stop, _, _, acc) do
|
||||
{ :lists.reverse(acc), [] }
|
||||
defp do_take(:stop, _, _) do
|
||||
[]
|
||||
end
|
||||
|
||||
## take_while
|
||||
@@ -1304,31 +1564,28 @@ defmodule Enum do
|
||||
[]
|
||||
end
|
||||
|
||||
## times
|
||||
## uniq
|
||||
|
||||
defp do_times_0(limit, counter, _function) when counter >= limit do
|
||||
defp do_uniq([h|t], acc) do
|
||||
case :lists.member(h, acc) do
|
||||
true -> do_uniq(t, acc)
|
||||
false -> [h|do_uniq(t, [h|acc])]
|
||||
end
|
||||
end
|
||||
|
||||
defp do_times_0(limit, counter, function) do
|
||||
function.()
|
||||
do_times_0(limit, 1 + counter, function)
|
||||
defp do_uniq([], _acc) do
|
||||
[]
|
||||
end
|
||||
|
||||
defp do_times_1(limit, counter, _function) when counter >= limit do
|
||||
defp do_uniq({ h, next }, iterator, acc) do
|
||||
case :lists.member(h, acc) do
|
||||
true -> do_uniq(iterator.(next), iterator, acc)
|
||||
false -> [h|do_uniq(iterator.(next), iterator, [h|acc])]
|
||||
end
|
||||
end
|
||||
|
||||
defp do_times_1(limit, counter, function) do
|
||||
function.(counter)
|
||||
do_times_1(limit, 1 + counter, function)
|
||||
end
|
||||
|
||||
defp do_times_2(limit, counter, _function, acc) when counter >= limit do
|
||||
acc
|
||||
end
|
||||
|
||||
defp do_times_2(limit, counter, function, acc) do
|
||||
new_acc = function.(counter, acc)
|
||||
do_times_2(limit, 1 + counter, function, new_acc)
|
||||
defp do_uniq(:stop, _, _acc) do
|
||||
[]
|
||||
end
|
||||
|
||||
## zip
|
||||
|
||||
@@ -86,13 +86,32 @@ defmodule Exception do
|
||||
@doc """
|
||||
Returns the stacktrace as a binary formatted as per `format_stacktrace/1`.
|
||||
"""
|
||||
def formatted_stacktrace(trace // System.stacktrace) do
|
||||
def formatted_stacktrace(trace // nil) do
|
||||
trace = trace || try do
|
||||
throw(:stacktrace)
|
||||
catch
|
||||
:stacktrace -> Enum.drop(System.stacktrace, 1)
|
||||
end
|
||||
|
||||
case trace do
|
||||
[] -> ""
|
||||
s -> " " <> Enum.map_join(s, "\n ", format_stacktrace(&1)) <> "\n"
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a formatted stacktrace from the environment.
|
||||
"""
|
||||
def env_stacktrace(env) do
|
||||
rest =
|
||||
case env.function do
|
||||
{ name, arity } -> format_module_fun_arity(env.module, name, arity)
|
||||
nil -> "#{inspect env.module} (body)"
|
||||
end
|
||||
|
||||
" #{format_file_line(env.location)}#{rest}\n"
|
||||
end
|
||||
|
||||
@doc """
|
||||
Formats each line in the stacktrace.
|
||||
"""
|
||||
|
||||
+54
-12
@@ -1152,10 +1152,12 @@ defmodule File do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Convert the file device into an iterator that can be
|
||||
Converts the file device into an iterator that can be
|
||||
passed into `Enum`. The device is iterated line
|
||||
by line lazily, at the end of iteration the file is
|
||||
closed.
|
||||
by line, at the end of iteration the file is closed.
|
||||
|
||||
This reads the file as utf-8. CHeck out `File.biniterator`
|
||||
to handle the file as a raw binary.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -1187,21 +1189,13 @@ defmodule File do
|
||||
{ :error, reason } ->
|
||||
raise File.IteratorError, reason: reason
|
||||
data ->
|
||||
{ strip_ending(data), :ok }
|
||||
{ data, :ok }
|
||||
end
|
||||
end
|
||||
{ function, function.(:start) }
|
||||
end
|
||||
end
|
||||
|
||||
defp strip_ending("\r\n"), do: <<>>
|
||||
defp strip_ending("\n"), do: <<>>
|
||||
defp strip_ending(""), do: <<>>
|
||||
|
||||
defp strip_ending(<< h, t :: binary >>) do
|
||||
<< h, strip_ending(t) :: binary >>
|
||||
end
|
||||
|
||||
@doc """
|
||||
Opens the given `file` with the given `mode` and
|
||||
returns its iterator. Fails for the same reasons
|
||||
@@ -1222,6 +1216,54 @@ defmodule File do
|
||||
open!(file, mode) /> iterator
|
||||
end
|
||||
|
||||
@doc """
|
||||
Converts the file device into an iterator that can
|
||||
be passed into `Enum` to iterate line by line as a
|
||||
binary. Check `iterator/1` for more information.
|
||||
"""
|
||||
def biniterator(device)
|
||||
|
||||
def biniterator(file) when is_binary(file) or is_list(file) do
|
||||
biniterator(file, [])
|
||||
end
|
||||
|
||||
def biniterator(device) do
|
||||
fn ->
|
||||
function = fn(_) ->
|
||||
case :file.read_line(device) do
|
||||
:eof ->
|
||||
close(device)
|
||||
:stop
|
||||
{ :error, reason } ->
|
||||
raise File.IteratorError, reason: reason
|
||||
{ :ok, data } ->
|
||||
{ data, :ok }
|
||||
end
|
||||
end
|
||||
{ function, function.(:start) }
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Opens the given `file` with the given `mode` and
|
||||
returns its biniterator. Fails for the same reasons
|
||||
as `File.open`.
|
||||
"""
|
||||
def biniterator(file, mode) do
|
||||
case open(file, mode) do
|
||||
{ :ok, device } -> { :ok, biniterator(device) }
|
||||
error -> error
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Same as `biniterator/2` but raises if the file
|
||||
cannot be opened.
|
||||
"""
|
||||
def biniterator!(file, mode // []) do
|
||||
open!(file, mode) /> biniterator
|
||||
end
|
||||
|
||||
## Helpers
|
||||
|
||||
# Normalize the given path by removing "..".
|
||||
|
||||
+202
-137
@@ -299,7 +299,7 @@ defmodule Kernel do
|
||||
abs(-3.33) #=> 3.33
|
||||
abs(-3) #=> 3
|
||||
"""
|
||||
@spec abs(number), do: number
|
||||
@spec abs(number) :: number
|
||||
def abs(number) do
|
||||
:erlang.abs(number)
|
||||
end
|
||||
@@ -313,7 +313,7 @@ defmodule Kernel do
|
||||
#=> 4
|
||||
|
||||
"""
|
||||
@spec apply(fun, list), do: term
|
||||
@spec apply((... -> any), list) :: term
|
||||
def apply(fun, args) do
|
||||
:erlang.apply(fun, args)
|
||||
end
|
||||
@@ -327,7 +327,7 @@ defmodule Kernel do
|
||||
#=> [3,2,1]
|
||||
|
||||
"""
|
||||
@spec apply(atom, atom, list), do: term
|
||||
@spec apply(atom, atom, list) :: term
|
||||
def apply(module, fun, args) do
|
||||
:erlang.apply(module, fun, args)
|
||||
end
|
||||
@@ -344,7 +344,7 @@ defmodule Kernel do
|
||||
atom_to_binary(:elixir, :utf8) #=> "elixir"
|
||||
|
||||
"""
|
||||
@spec atom_to_binary(atom, :utf8 | :unicode | :latin1), do: binary
|
||||
@spec atom_to_binary(atom, :utf8 | :unicode | :latin1) :: binary
|
||||
def atom_to_binary(atom, encoding) do
|
||||
:erlang.atom_to_binary(atom, encoding)
|
||||
end
|
||||
@@ -357,7 +357,7 @@ defmodule Kernel do
|
||||
atom_to_list(:elixir) #=> 'elixir'
|
||||
|
||||
"""
|
||||
@spec atom_to_list(atom), do: list
|
||||
@spec atom_to_list(atom) :: list
|
||||
def atom_to_list(atom) do
|
||||
:erlang.atom_to_list(atom)
|
||||
end
|
||||
@@ -380,7 +380,7 @@ defmodule Kernel do
|
||||
binary_part "foo", 3, -1 #=> 1
|
||||
|
||||
"""
|
||||
@spec binary_part(binary, pos_integer, integer), do: binary
|
||||
@spec binary_part(binary, pos_integer, integer) :: binary
|
||||
def binary_part(binary, start, length) do
|
||||
:erlang.binary_part(binary, start, length)
|
||||
end
|
||||
@@ -396,7 +396,7 @@ defmodule Kernel do
|
||||
binary_to_atom("elixir", :utf8) #=> :elixir
|
||||
|
||||
"""
|
||||
@spec binary_to_atom(binary, :utf8 | :unicode | :latin1), do: atom
|
||||
@spec binary_to_atom(binary, :utf8 | :unicode | :latin1) :: atom
|
||||
def binary_to_atom(binary, encoding) do
|
||||
:erlang.binary_to_atom(binary, encoding)
|
||||
end
|
||||
@@ -404,7 +404,7 @@ defmodule Kernel do
|
||||
@doc """
|
||||
Works like `binary_to_atom/2`, but the atom must already exist.
|
||||
"""
|
||||
@spec binary_to_existing_atom(binary, :utf8 | :unicode | :latin1), do: atom
|
||||
@spec binary_to_existing_atom(binary, :utf8 | :unicode | :latin1) :: atom
|
||||
def binary_to_existing_atom(binary, encoding) do
|
||||
:erlang.binary_to_existing_atom(binary, encoding)
|
||||
end
|
||||
@@ -412,7 +412,7 @@ defmodule Kernel do
|
||||
@doc """
|
||||
Returns a list of integers which correspond to the bytes of `binary`.
|
||||
"""
|
||||
@spec binary_to_list(binary), do: list
|
||||
@spec binary_to_list(binary) :: list
|
||||
def binary_to_list(binary) do
|
||||
:erlang.binary_to_list(binary)
|
||||
end
|
||||
@@ -422,7 +422,7 @@ defmodule Kernel do
|
||||
from position `start` to position `stop` in `binary`. Positions in the binary
|
||||
are numbered starting from 1.
|
||||
"""
|
||||
@spec binary_to_list(binary, pos_integer, pos_integer), do: list
|
||||
@spec binary_to_list(binary, pos_integer, pos_integer) :: list
|
||||
def binary_to_list(binary, start, stop) do
|
||||
:erlang.binary_to_list(binary, start, stop)
|
||||
end
|
||||
@@ -437,7 +437,7 @@ defmodule Kernel do
|
||||
binary_to_term(term_to_binary("foo")) #=> "foo"
|
||||
|
||||
"""
|
||||
@spec binary_to_term(binary), do: term
|
||||
@spec binary_to_term(binary) :: term
|
||||
def binary_to_term(binary) do
|
||||
:erlang.binary_to_term(binary)
|
||||
end
|
||||
@@ -461,7 +461,7 @@ defmodule Kernel do
|
||||
binary_to_term(term_to_binary("foo"), [:safe])
|
||||
|
||||
"""
|
||||
@spec binary_to_term(binary, [] | [:safe]), do: term
|
||||
@spec binary_to_term(binary, [] | [:safe]) :: term
|
||||
def binary_to_term(binary, options) do
|
||||
:erlang.binary_to_term(binary, options)
|
||||
end
|
||||
@@ -477,7 +477,7 @@ defmodule Kernel do
|
||||
bit_size(<<1,2,3>>) #=> 24
|
||||
|
||||
"""
|
||||
@spec bit_size(bitstring), do: non_neg_integer
|
||||
@spec bit_size(bitstring) :: non_neg_integer
|
||||
def bit_size(bitstring) do
|
||||
:erlang.bit_size(bitstring)
|
||||
end
|
||||
@@ -487,7 +487,7 @@ defmodule Kernel do
|
||||
number of bits in the binary is not divisible by 8, the last element of the list will
|
||||
be a bitstring containing the remaining bits (1 up to 7 bits).
|
||||
"""
|
||||
@spec bitstring_to_list(bitstring), do: list
|
||||
@spec bitstring_to_list(bitstring) :: list
|
||||
def bitstring_to_list(bitstring) do
|
||||
:erlang.bitstring_to_list(bitstring)
|
||||
end
|
||||
@@ -505,7 +505,7 @@ defmodule Kernel do
|
||||
byte_size(<<1,2,3>>) #=> 3
|
||||
|
||||
"""
|
||||
@spec byte_size(bitstring), do: non_neg_integer
|
||||
@spec byte_size(bitstring) :: non_neg_integer
|
||||
def byte_size(bitstring) do
|
||||
:erlang.byte_size(bitstring)
|
||||
end
|
||||
@@ -521,7 +521,7 @@ defmodule Kernel do
|
||||
exit(:seems_bad)
|
||||
|
||||
"""
|
||||
@spec exit(term), do: no_return
|
||||
@spec exit(term) :: no_return
|
||||
def exit(reason) do
|
||||
:erlang.exit(reason)
|
||||
end
|
||||
@@ -529,7 +529,7 @@ defmodule Kernel do
|
||||
@doc """
|
||||
Converts the given number to a float. Allowed in guard clauses.
|
||||
"""
|
||||
@spec float(number), do: float
|
||||
@spec float(number) :: float
|
||||
def float(number) do
|
||||
:erlang.float(number)
|
||||
end
|
||||
@@ -543,7 +543,7 @@ defmodule Kernel do
|
||||
#=> '7.00000000000000000000e+00'
|
||||
|
||||
"""
|
||||
@spec float_to_list(number), do: char_list
|
||||
@spec float_to_list(number) :: char_list
|
||||
def float_to_list(number) do
|
||||
:erlang.float_to_list(number)
|
||||
end
|
||||
@@ -551,7 +551,7 @@ defmodule Kernel do
|
||||
@doc """
|
||||
Returns the head of a list, raises badarg if the list is empty.
|
||||
"""
|
||||
@spec hd(list), do: term
|
||||
@spec hd(list) :: term
|
||||
def hd(list) do
|
||||
:erlang.hd(list)
|
||||
end
|
||||
@@ -565,7 +565,7 @@ defmodule Kernel do
|
||||
#=> '7'
|
||||
|
||||
"""
|
||||
@spec integer_to_list(integer), do: char_list
|
||||
@spec integer_to_list(integer) :: char_list
|
||||
def integer_to_list(number) do
|
||||
:erlang.integer_to_list(number)
|
||||
end
|
||||
@@ -580,7 +580,7 @@ defmodule Kernel do
|
||||
#=> "3FF"
|
||||
|
||||
"""
|
||||
@spec integer_to_list(integer, pos_integer), do: char_list
|
||||
@spec integer_to_list(integer, pos_integer) :: char_list
|
||||
def integer_to_list(number, base) do
|
||||
:erlang.integer_to_list(number, base)
|
||||
end
|
||||
@@ -594,7 +594,7 @@ defmodule Kernel do
|
||||
#=> 4
|
||||
|
||||
"""
|
||||
@spec iolist_size(iolist), do: non_neg_integer
|
||||
@spec iolist_size(iolist) :: non_neg_integer
|
||||
def iolist_size(item) do
|
||||
:erlang.iolist_size(item)
|
||||
end
|
||||
@@ -612,7 +612,7 @@ defmodule Kernel do
|
||||
#=> <<1,2,3,1,2,3,4,5,4,6>>
|
||||
|
||||
"""
|
||||
@spec iolist_to_binary(iolist), do: binary
|
||||
@spec iolist_to_binary(iolist) :: binary
|
||||
def iolist_to_binary(item) do
|
||||
:erlang.iolist_to_binary(item)
|
||||
end
|
||||
@@ -621,7 +621,7 @@ defmodule Kernel do
|
||||
Returns true if the local node is alive; that is,
|
||||
if the node can be part of a distributed system.
|
||||
"""
|
||||
@spec is_alive, do: boolean
|
||||
@spec is_alive :: boolean
|
||||
def is_alive do
|
||||
:erlang.is_alive
|
||||
end
|
||||
@@ -631,7 +631,7 @@ defmodule Kernel do
|
||||
|
||||
Allowed in guard tests.
|
||||
"""
|
||||
@spec is_atom(term), do: boolean
|
||||
@spec is_atom(term) :: boolean
|
||||
def is_atom(term) do
|
||||
:erlang.is_atom(term)
|
||||
end
|
||||
@@ -643,7 +643,7 @@ defmodule Kernel do
|
||||
|
||||
Allowed in guard tests.
|
||||
"""
|
||||
@spec is_binary(term), do: boolean
|
||||
@spec is_binary(term) :: boolean
|
||||
def is_binary(term) do
|
||||
:erlang.is_binary(term)
|
||||
end
|
||||
@@ -653,7 +653,7 @@ defmodule Kernel do
|
||||
|
||||
Allowed in guard tests.
|
||||
"""
|
||||
@spec is_bitstring(term), do: boolean
|
||||
@spec is_bitstring(term) :: boolean
|
||||
def is_bitstring(term) do
|
||||
:erlang.is_bitstring(term)
|
||||
end
|
||||
@@ -664,7 +664,7 @@ defmodule Kernel do
|
||||
|
||||
Allowed in guard tests.
|
||||
"""
|
||||
@spec is_boolean(term), do: boolean
|
||||
@spec is_boolean(term) :: boolean
|
||||
def is_boolean(term) do
|
||||
:erlang.is_boolean(term)
|
||||
end
|
||||
@@ -674,7 +674,7 @@ defmodule Kernel do
|
||||
|
||||
Allowed in guard tests.
|
||||
"""
|
||||
@spec is_float(term), do: boolean
|
||||
@spec is_float(term) :: boolean
|
||||
def is_float(term) do
|
||||
:erlang.is_float(term)
|
||||
end
|
||||
@@ -684,7 +684,7 @@ defmodule Kernel do
|
||||
|
||||
Allowed in guard tests.
|
||||
"""
|
||||
@spec is_function(term), do: boolean
|
||||
@spec is_function(term) :: boolean
|
||||
def is_function(term) do
|
||||
:erlang.is_function(term)
|
||||
end
|
||||
@@ -695,7 +695,7 @@ defmodule Kernel do
|
||||
|
||||
Allowed in guard tests.
|
||||
"""
|
||||
@spec is_function(term, non_neg_integer), do: boolean
|
||||
@spec is_function(term, non_neg_integer) :: boolean
|
||||
def is_function(term, arity) do
|
||||
:erlang.is_function(term, arity)
|
||||
end
|
||||
@@ -705,7 +705,7 @@ defmodule Kernel do
|
||||
|
||||
Allowed in guard tests.
|
||||
"""
|
||||
@spec is_integer(term), do: boolean
|
||||
@spec is_integer(term) :: boolean
|
||||
def is_integer(term) do
|
||||
:erlang.is_integer(term)
|
||||
end
|
||||
@@ -715,7 +715,7 @@ defmodule Kernel do
|
||||
|
||||
Allowed in guard tests.
|
||||
"""
|
||||
@spec is_list(term), do: boolean
|
||||
@spec is_list(term) :: boolean
|
||||
def is_list(term) do
|
||||
:erlang.is_list(term)
|
||||
end
|
||||
@@ -726,7 +726,7 @@ defmodule Kernel do
|
||||
|
||||
Allowed in guard tests.
|
||||
"""
|
||||
@spec is_number(term), do: boolean
|
||||
@spec is_number(term) :: boolean
|
||||
def is_number(term) do
|
||||
:erlang.is_number(term)
|
||||
end
|
||||
@@ -736,7 +736,7 @@ defmodule Kernel do
|
||||
|
||||
Allowed in guard tests.
|
||||
"""
|
||||
@spec is_pid(term), do: boolean
|
||||
@spec is_pid(term) :: boolean
|
||||
def is_pid(term) do
|
||||
:erlang.is_pid(term)
|
||||
end
|
||||
@@ -746,7 +746,7 @@ defmodule Kernel do
|
||||
|
||||
Allowed in guard tests.
|
||||
"""
|
||||
@spec is_port(term), do: boolean
|
||||
@spec is_port(term) :: boolean
|
||||
def is_port(term) do
|
||||
:erlang.is_port(term)
|
||||
end
|
||||
@@ -756,7 +756,7 @@ defmodule Kernel do
|
||||
|
||||
Allowed in guard tests.
|
||||
"""
|
||||
@spec is_reference(term), do: boolean
|
||||
@spec is_reference(term) :: boolean
|
||||
def is_reference(term) do
|
||||
:erlang.is_reference(term)
|
||||
end
|
||||
@@ -766,7 +766,7 @@ defmodule Kernel do
|
||||
|
||||
Allowed in guard tests.
|
||||
"""
|
||||
@spec is_tuple(term), do: boolean
|
||||
@spec is_tuple(term) :: boolean
|
||||
def is_tuple(term) do
|
||||
:erlang.is_tuple(term)
|
||||
end
|
||||
@@ -780,7 +780,7 @@ defmodule Kernel do
|
||||
|
||||
length([1,2,3,4,5,6,7,8,9]) #=> 9
|
||||
"""
|
||||
@spec length(list), do: non_neg_integer
|
||||
@spec length(list) :: non_neg_integer
|
||||
def length(list) do
|
||||
:erlang.length(list)
|
||||
end
|
||||
@@ -792,7 +792,7 @@ defmodule Kernel do
|
||||
|
||||
list_to_atom('elixir') #=> :elixir
|
||||
"""
|
||||
@spec list_to_atom(char_list), do: atom
|
||||
@spec list_to_atom(char_list) :: atom
|
||||
def list_to_atom(char_list) do
|
||||
:erlang.list_to_atom(char_list)
|
||||
end
|
||||
@@ -804,7 +804,7 @@ defmodule Kernel do
|
||||
|
||||
list_to_binary('Elixir') #=> "Elixir"
|
||||
"""
|
||||
@spec list_to_binary(iolist), do: binary
|
||||
@spec list_to_binary(iolist) :: binary
|
||||
def list_to_binary(char_list) do
|
||||
:erlang.list_to_binary(char_list)
|
||||
end
|
||||
@@ -823,7 +823,7 @@ defmodule Kernel do
|
||||
#=> <<1,2,3,1,2,3,4,5,4,6,7|4>>
|
||||
|
||||
"""
|
||||
@spec list_to_bitstring(maybe_improper_list(char | binary | iolist | bitstring, binary | bitstring | [])), do: bitstring
|
||||
@spec list_to_bitstring(maybe_improper_list(char | binary | iolist | bitstring, binary | bitstring | [])) :: bitstring
|
||||
def list_to_bitstring(bitstring_list) do
|
||||
:erlang.list_to_bitstring(bitstring_list)
|
||||
end
|
||||
@@ -832,7 +832,7 @@ defmodule Kernel do
|
||||
Returns the atom whose text representation is `char_list`, but only if there already
|
||||
exists such atom.
|
||||
"""
|
||||
@spec list_to_existing_atom(char_list), do: atom
|
||||
@spec list_to_existing_atom(char_list) :: atom
|
||||
def list_to_existing_atom(char_list) do
|
||||
:erlang.list_to_existing_atom(char_list)
|
||||
end
|
||||
@@ -844,7 +844,7 @@ defmodule Kernel do
|
||||
|
||||
list_to_float('2.2017764e+0') #=> 2.2017764
|
||||
"""
|
||||
@spec list_to_float(char_list), do: float
|
||||
@spec list_to_float(char_list) :: float
|
||||
def list_to_float(char_list) do
|
||||
:erlang.list_to_float(char_list)
|
||||
end
|
||||
@@ -856,7 +856,7 @@ defmodule Kernel do
|
||||
|
||||
list_to_integer('123') #=> 123
|
||||
"""
|
||||
@spec list_to_integer(char_list), do: integer
|
||||
@spec list_to_integer(char_list) :: integer
|
||||
def list_to_integer(char_list) do
|
||||
:erlang.list_to_integer(char_list)
|
||||
end
|
||||
@@ -868,7 +868,7 @@ defmodule Kernel do
|
||||
|
||||
list_to_integer('3FF', 16) #=> 1023
|
||||
"""
|
||||
@spec list_to_integer(char_list, non_neg_integer), do: integer
|
||||
@spec list_to_integer(char_list, non_neg_integer) :: integer
|
||||
def list_to_integer(char_list, base) do
|
||||
:erlang.list_to_integer(char_list, base)
|
||||
end
|
||||
@@ -886,7 +886,7 @@ defmodule Kernel do
|
||||
## Examples
|
||||
list_to_pid('<0.41>') #=> <0.4.1>
|
||||
"""
|
||||
@spec list_to_pid(char_list), do: pid
|
||||
@spec list_to_pid(char_list) :: pid
|
||||
def list_to_pid(char_list) do
|
||||
:erlang.list_to_pid(char_list)
|
||||
end
|
||||
@@ -898,7 +898,7 @@ defmodule Kernel do
|
||||
|
||||
list_to_tuple([:share, [:elixir, 163]]). #=> {:share, [:elixir, 163]}
|
||||
"""
|
||||
@spec list_to_tuple(list), do: tuple
|
||||
@spec list_to_tuple(list) :: tuple
|
||||
def list_to_tuple(list) do
|
||||
:erlang.list_to_tuple(list)
|
||||
end
|
||||
@@ -915,7 +915,7 @@ defmodule Kernel do
|
||||
#=> #Ref<0.0.0.135>
|
||||
|
||||
"""
|
||||
@spec make_ref(), do: reference
|
||||
@spec make_ref() :: reference
|
||||
def make_ref() do
|
||||
:erlang.make_ref()
|
||||
end
|
||||
@@ -930,7 +930,7 @@ defmodule Kernel do
|
||||
max(1, 2) #=> 2
|
||||
|
||||
"""
|
||||
@spec max(term, term), do: term
|
||||
@spec max(term, term) :: term
|
||||
def max(first, second) do
|
||||
:erlang.max(first, second)
|
||||
end
|
||||
@@ -945,7 +945,7 @@ defmodule Kernel do
|
||||
min(1, 2) #=> 1
|
||||
|
||||
"""
|
||||
@spec min(term, term), do: term
|
||||
@spec min(term, term) :: term
|
||||
def min(first, second) do
|
||||
:erlang.min(first, second)
|
||||
end
|
||||
@@ -956,7 +956,7 @@ defmodule Kernel do
|
||||
|
||||
Allowed in guard tests.
|
||||
"""
|
||||
@spec node(), do: node
|
||||
@spec node() :: node
|
||||
def node do
|
||||
:erlang.node
|
||||
end
|
||||
@@ -968,7 +968,7 @@ defmodule Kernel do
|
||||
|
||||
Allowed in guard tests.
|
||||
"""
|
||||
@spec node(pid|reference|port), do: node
|
||||
@spec node(pid|reference|port) :: node
|
||||
def node(arg) do
|
||||
:erlang.node(arg)
|
||||
end
|
||||
@@ -985,7 +985,7 @@ defmodule Kernel do
|
||||
|
||||
It should not be used in application programs.
|
||||
"""
|
||||
@spec pid_to_list(pid), do: list
|
||||
@spec pid_to_list(pid) :: list
|
||||
def pid_to_list(pid) do
|
||||
:erlang.pid_to_list(pid)
|
||||
end
|
||||
@@ -999,7 +999,7 @@ defmodule Kernel do
|
||||
round(5.5) #=> 6
|
||||
|
||||
"""
|
||||
@spec round(number), do: integer
|
||||
@spec round(number) :: integer
|
||||
def round(number) do
|
||||
:erlang.round(number)
|
||||
end
|
||||
@@ -1008,7 +1008,7 @@ defmodule Kernel do
|
||||
Returns the pid (process identifier) of the calling process.
|
||||
Allowed in guard clauses.
|
||||
"""
|
||||
@spec self(), do: pid
|
||||
@spec self() :: pid
|
||||
def self() do
|
||||
:erlang.self()
|
||||
end
|
||||
@@ -1017,7 +1017,7 @@ defmodule Kernel do
|
||||
Returns the size of the given argument, which must be a tuple
|
||||
or a binary. If possible, please use tuple_size or binary_size.
|
||||
"""
|
||||
@spec size(tuple|binary), do: non_neg_integer
|
||||
@spec size(tuple|binary) :: non_neg_integer
|
||||
def size(arg) do
|
||||
:erlang.size(arg)
|
||||
end
|
||||
@@ -1038,7 +1038,7 @@ defmodule Kernel do
|
||||
end
|
||||
|
||||
"""
|
||||
@spec spawn(fun), do: pid
|
||||
@spec spawn((() -> any)) :: pid
|
||||
def spawn(fun) do
|
||||
:erlang.spawn(fun)
|
||||
end
|
||||
@@ -1055,7 +1055,7 @@ defmodule Kernel do
|
||||
spawn(SomeModule, :function, [1,2,3])
|
||||
|
||||
"""
|
||||
@spec spawn(module, atom, list), do: pid
|
||||
@spec spawn(module, atom, list) :: pid
|
||||
def spawn(module, fun, args) do
|
||||
:erlang.spawn(module, fun, args)
|
||||
end
|
||||
@@ -1077,7 +1077,7 @@ defmodule Kernel do
|
||||
end
|
||||
|
||||
"""
|
||||
@spec spawn_link(fun), do: pid
|
||||
@spec spawn_link((() -> any)) :: pid
|
||||
def spawn_link(fun) do
|
||||
:erlang.spawn_link(fun)
|
||||
end
|
||||
@@ -1094,7 +1094,7 @@ defmodule Kernel do
|
||||
spawn_link(SomeModule, :function, [1,2,3])
|
||||
|
||||
"""
|
||||
@spec spawn_link(module, atom, list), do: pid
|
||||
@spec spawn_link(module, atom, list) :: pid
|
||||
def spawn_link(module, fun, args) do
|
||||
:erlang.spawn_link(module, fun, args)
|
||||
end
|
||||
@@ -1107,7 +1107,7 @@ defmodule Kernel do
|
||||
to a file in an efficient way, or sending an Erlang term to some type
|
||||
of communications channel not supported by distributed :
|
||||
"""
|
||||
@spec term_to_binary(term), do: binary
|
||||
@spec term_to_binary(term) :: binary
|
||||
def term_to_binary(term) do
|
||||
:erlang.term_to_binary(term)
|
||||
end
|
||||
@@ -1121,7 +1121,7 @@ defmodule Kernel do
|
||||
for more details
|
||||
|
||||
"""
|
||||
@spec term_to_binary(term, list({:compressed, 0..9}|{:minor_version, 0}|{:minor_version, 1})), do: binary
|
||||
@spec term_to_binary(term, list({:compressed, 0..9}|{:minor_version, 0}|{:minor_version, 1})) :: binary
|
||||
def term_to_binary(term, opts) do
|
||||
:erlang.term_to_binary(term, opts)
|
||||
end
|
||||
@@ -1129,7 +1129,7 @@ defmodule Kernel do
|
||||
@doc """
|
||||
A non-local return from a function. Check try/2 for more information.
|
||||
"""
|
||||
@spec throw(term), do: no_return
|
||||
@spec throw(term) :: no_return
|
||||
def throw(term) do
|
||||
:erlang.throw(term)
|
||||
end
|
||||
@@ -1137,7 +1137,7 @@ defmodule Kernel do
|
||||
@doc """
|
||||
Returns the tail of a list. Raises ArgumentError if the list is empty.
|
||||
"""
|
||||
@spec tl(maybe_improper_list), do: maybe_improper_list
|
||||
@spec tl(maybe_improper_list) :: maybe_improper_list
|
||||
def tl(list) do
|
||||
:erlang.tl(list)
|
||||
end
|
||||
@@ -1151,7 +1151,7 @@ defmodule Kernel do
|
||||
trunc(5.5) #=> 5
|
||||
|
||||
"""
|
||||
@spec trunc(number), do: integer
|
||||
@spec trunc(number) :: integer
|
||||
def trunc(number) do
|
||||
:erlang.trunc(number)
|
||||
end
|
||||
@@ -1159,7 +1159,7 @@ defmodule Kernel do
|
||||
@doc """
|
||||
Returns the size of a tuple.
|
||||
"""
|
||||
@spec tuple_size(tuple), do: non_neg_integer
|
||||
@spec tuple_size(tuple) :: non_neg_integer
|
||||
def tuple_size(tuple) do
|
||||
:erlang.tuple_size(tuple)
|
||||
end
|
||||
@@ -1167,7 +1167,7 @@ defmodule Kernel do
|
||||
@doc """
|
||||
Converts a tuple to a list.
|
||||
"""
|
||||
@spec tuple_to_list(tuple), do: list
|
||||
@spec tuple_to_list(tuple) :: list
|
||||
def tuple_to_list(tuple) do
|
||||
:erlang.tuple_to_list(tuple)
|
||||
end
|
||||
@@ -1354,7 +1354,7 @@ defmodule Kernel do
|
||||
|
||||
## Examples
|
||||
|
||||
defrecord FileInfo, atime: nil, mtime: nil
|
||||
defrecord FileInfo, atime: nil, accesses: 0
|
||||
|
||||
The line above will define a module named `FileInfo` which
|
||||
contains a function named `new` that returns a new record
|
||||
@@ -1371,36 +1371,50 @@ defmodule Kernel do
|
||||
inspect FileInfo.new, raw: true
|
||||
#=> { FileInfo, nil, nil }
|
||||
|
||||
## Extensions
|
||||
|
||||
Besides defining readers and writers for each attribute. Elixir will
|
||||
define extensions functions for each attribute. By default, it will
|
||||
define an `update_#{attribute}` function to update the value. Such
|
||||
Besides defining readers and writers for each attribute, Elixir also
|
||||
defines an `update_#{attribute}` function to update the value. Such
|
||||
functions expect a function as argument that receives the current
|
||||
value and must return the new one:
|
||||
value and must return the new one. For example, every time the file
|
||||
is accessed, the accesses counter can be incremented with:
|
||||
|
||||
file_info.update_atime(fn(_old) -> now() end) #=> Updates the value of atime
|
||||
file_info.update_accesses(fn(old) -> old + 1 end)
|
||||
|
||||
Besides, Elixir may define new functions depending on the default value.
|
||||
For example, ExUnit defines a record which keeps track of how many tests
|
||||
were executed and the failures that happened. The record definition is
|
||||
similar to:
|
||||
Which can be also written as:
|
||||
|
||||
defrecord Config, counter: 0, failures: []
|
||||
file_info.update_accesses(&1 + 1)
|
||||
|
||||
Since `counter` is an integer, Elixir automatically defines a helper
|
||||
named `increment_counter` that will increase the counter value:
|
||||
## Access syntax
|
||||
|
||||
Config.new.increment_counter.counter #=> 1
|
||||
Records in Elixir can be expanded at compilation time to provide
|
||||
pattern matching and faster operations. For example, the clause
|
||||
below will only match if a `FileInfo` is given and the number of
|
||||
accesses is zero:
|
||||
|
||||
`increment_counter` also accepts a number of increment as argument:
|
||||
def enforce_no_access(FileInfo[accesses: 0]), do: :ok
|
||||
|
||||
Config.new.increment_counter(10).counter #=> 10
|
||||
The clause above will expand to:
|
||||
|
||||
Besides, if the default is a list, Elixir will define two helpers:
|
||||
def enforce_no_access({ FileInfo, _, 0 }), do: :ok
|
||||
|
||||
* `merge_field` - Receives keywords and merge it into the current value;
|
||||
* `prepend_field` - Receives another list and prepend its values;
|
||||
The downside of using such syntax is that, every time the record
|
||||
changes, your code now needs to be recompiled (which is usually
|
||||
not a concern since Elixir build tools by default recompiles the
|
||||
whole project whenever there is a change).
|
||||
|
||||
Finally, keep in mind that Elixir triggers some optimizations whenever
|
||||
the access syntax is used. For example:
|
||||
|
||||
def no_access?(FileInfo[] = file_info) do
|
||||
file_info.accesses == 0
|
||||
end
|
||||
|
||||
Is translated to:
|
||||
|
||||
def no_access?({ FileInfo, _, _ } = file_info) do
|
||||
elem(file_info, 1) == 0
|
||||
end
|
||||
|
||||
Which provides faster get and set times for record operations.
|
||||
|
||||
## Documentation
|
||||
|
||||
@@ -1412,7 +1426,7 @@ defmodule Kernel do
|
||||
For example, assuming the `Config` record defined above, it could be used
|
||||
in typespecs as follow:
|
||||
|
||||
@spec handle_config(Config.t), do: boolean()
|
||||
@spec handle_config(Config.t) :: boolean()
|
||||
|
||||
Inside the record definition, a developer can define his own types too:
|
||||
|
||||
@@ -1483,6 +1497,7 @@ defmodule Kernel do
|
||||
differences:
|
||||
|
||||
1) Differently from records, exceptions are documented by default;
|
||||
|
||||
2) Exceptions **must** implement `message/1` as API that return a
|
||||
binary as result;
|
||||
|
||||
@@ -1519,12 +1534,14 @@ defmodule Kernel do
|
||||
case __CALLER__.in_guard? do
|
||||
true ->
|
||||
quote do
|
||||
is_tuple(unquote(thing)) and :erlang.element(2, unquote(thing)) == :__exception__
|
||||
is_tuple(unquote(thing)) and tuple_size(unquote(thing)) > 1 and
|
||||
:erlang.element(2, unquote(thing)) == :__exception__
|
||||
end
|
||||
false ->
|
||||
quote do
|
||||
result = unquote(thing)
|
||||
is_tuple(result) and :erlang.element(2, result) == :__exception__
|
||||
is_tuple(result) and tuple_size(result) > 1 and
|
||||
:erlang.element(2, result) == :__exception__
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1681,7 +1698,7 @@ defmodule Kernel do
|
||||
As in records, defining a protocol automatically defines a type named `t`,
|
||||
which can be used as:
|
||||
|
||||
@spec present?(Blank.t), do: boolean
|
||||
@spec present?(Blank.t) :: boolean
|
||||
def present?(blank) do
|
||||
not Blank.blank?(blank)
|
||||
end
|
||||
@@ -1965,26 +1982,26 @@ defmodule Kernel do
|
||||
`do/end` always matches the furthest call, if we used the `function`
|
||||
macro as below:
|
||||
|
||||
Enum.map [1,2,3], function(x) do
|
||||
x * 2
|
||||
Enum.map [1,2,3], function do
|
||||
x -> x * 2
|
||||
end
|
||||
|
||||
It would be parsed as:
|
||||
|
||||
Enum.map([1,2,3], function(x)) do
|
||||
x * 2
|
||||
Enum.map([1,2,3], function) do
|
||||
x -> x * 2
|
||||
end
|
||||
|
||||
The stab shortcut syntax has the proper precedence:
|
||||
|
||||
Enum.map [1,2,3], fn x ->
|
||||
x * 2
|
||||
Enum.map [1,2,3], fn
|
||||
x -> x * 2
|
||||
end
|
||||
|
||||
Which is handled as:
|
||||
|
||||
Enum.map([1,2,3], fn x ->
|
||||
x * 2
|
||||
Enum.map([1,2,3], fn
|
||||
x -> x * 2
|
||||
end)
|
||||
|
||||
## Function retrieval
|
||||
@@ -2067,20 +2084,19 @@ defmodule Kernel do
|
||||
IO.puts "This is printed regardless if it failed or succeed"
|
||||
end
|
||||
|
||||
The rescue clause is used to handle errors, while the catch clause
|
||||
can be used to catch throw values. Both catch and rescue clauses
|
||||
accepts the same pattern matching rules as match.
|
||||
The rescue clause is used to handle exceptions, while the catch
|
||||
clause can be used to catch thrown values. Both catch and rescue
|
||||
clauses work based on pattern matching.
|
||||
|
||||
Note that calls inside `try` are not tail recursive since the VM
|
||||
needs to keep the stacktrace in case an exception happens.
|
||||
|
||||
## Rescue clauses
|
||||
|
||||
Besides accepting the same pattern matching rules as `match`
|
||||
clauses, rescue provides some conveniences around exceptions
|
||||
that allows one to rescue an exception by its name and not by
|
||||
its internal contents. All the following formats are valid
|
||||
rescue expressions:
|
||||
Besides relying on pattern matching, rescue clauses provides some
|
||||
conveniences around exceptions that allows one to rescue an
|
||||
exception by its name. All the following formats are valid rescue
|
||||
expressions:
|
||||
|
||||
try do
|
||||
UndefinedModule.undefined_function
|
||||
@@ -2108,29 +2124,9 @@ defmodule Kernel do
|
||||
x -> nil
|
||||
end
|
||||
|
||||
## Variable visibility
|
||||
|
||||
Since an expression inside `try` may not have been evaluted
|
||||
due to an exception, any variable created inside `try` cannot
|
||||
be accessed externaly.
|
||||
For instance:
|
||||
|
||||
try do
|
||||
x = 1
|
||||
do_something_that_may_fail(same_arg)
|
||||
:ok
|
||||
catch
|
||||
_ | _ -> :failed
|
||||
end
|
||||
|
||||
x #=> Cannot access `x`
|
||||
|
||||
In the example above, `x` cannot be accessed since it was defined
|
||||
inside the `try` clause.
|
||||
|
||||
## Catching exits and Erlang errors
|
||||
|
||||
The catch clause works exactly the same as in : Therefore,
|
||||
The catch clause works exactly the same as in erlang. Therefore,
|
||||
one can also handle exits/errors coming from Erlang as below:
|
||||
|
||||
try do
|
||||
@@ -2148,6 +2144,36 @@ defmodule Kernel do
|
||||
|
||||
Although the second form should be avoided in favor of raise/rescue
|
||||
control mechanisms.
|
||||
|
||||
## Variable visibility
|
||||
|
||||
Since an expression inside `try` may not have been evaluted
|
||||
due to an exception, any variable created inside `try` cannot
|
||||
be accessed externaly. For instance:
|
||||
|
||||
try do
|
||||
x = 1
|
||||
do_something_that_may_fail(same_arg)
|
||||
:ok
|
||||
catch
|
||||
_, _ -> :failed
|
||||
end
|
||||
|
||||
x #=> Cannot access `x`
|
||||
|
||||
In the example above, `x` cannot be accessed since it was defined
|
||||
inside the `try` clause. A common practice to address this issue
|
||||
is to return the variables defined inside `try`:
|
||||
|
||||
x =
|
||||
try do
|
||||
x = 1
|
||||
do_something_that_may_fail(same_arg)
|
||||
x
|
||||
catch
|
||||
_, _ -> :failed
|
||||
end
|
||||
|
||||
"""
|
||||
defmacro try(args)
|
||||
|
||||
@@ -2672,6 +2698,21 @@ defmodule Kernel do
|
||||
|
||||
Enum.map(List.flatten([1,[2],3]), &1 * 2)
|
||||
|
||||
Please be aware of operator precendence, when using
|
||||
this operator. For example, the following expression:
|
||||
|
||||
String.graphemes "Hello" /> Enum.reverse
|
||||
|
||||
Is translated to:
|
||||
|
||||
String.graphemes("Hello" /> Enum.reverse)
|
||||
|
||||
Which will result in an error as Enum.Iterator protocol
|
||||
is not defined for binaries. Adding explicit parenthesis
|
||||
is recommended:
|
||||
|
||||
String.graphemes("Hello") /> Enum.reverse
|
||||
|
||||
"""
|
||||
defmacro left /> right do
|
||||
pipeline_op(left, right)
|
||||
@@ -2716,9 +2757,9 @@ defmodule Kernel do
|
||||
end
|
||||
|
||||
"""
|
||||
@spec raise(binary | atom | tuple), do: no_return
|
||||
@spec raise(binary | atom | tuple) :: no_return
|
||||
def raise(msg) when is_binary(msg) do
|
||||
:erlang.error RuntimeError.new(message: msg)
|
||||
:erlang.error RuntimeError[message: msg]
|
||||
end
|
||||
|
||||
def raise(exception) do
|
||||
@@ -2735,14 +2776,23 @@ defmodule Kernel do
|
||||
Any module defined via `defexception` automatically
|
||||
defines `exception(args)` that returns a new instance
|
||||
of the record and a `exception(args, current)` that
|
||||
works as no-op.
|
||||
updates the current exception.
|
||||
|
||||
Re-raising an exception will retrieve the previous
|
||||
stacktrace so it keps the properties of the original
|
||||
exception.
|
||||
|
||||
## Examples
|
||||
|
||||
raise ArgumentError, message: "Sample"
|
||||
|
||||
"""
|
||||
@spec raise(tuple | atom, list), do: no_return
|
||||
@spec raise(tuple | atom, list) :: no_return
|
||||
def raise(exception, args) when is_tuple(exception) and tuple_size(exception) > 1 and
|
||||
:erlang.element(2, exception) == :__exception__ do
|
||||
:erlang.raise(:error, exception.exception(args), :erlang.get_stacktrace)
|
||||
end
|
||||
|
||||
def raise(exception, args) do
|
||||
:erlang.error exception.exception(args)
|
||||
end
|
||||
@@ -2758,7 +2808,7 @@ defmodule Kernel do
|
||||
it is not loaded. Check `Code.ensure_loaded/1` for more
|
||||
information.
|
||||
"""
|
||||
@spec function_exported?(atom | tuple, atom, integer), do: boolean
|
||||
@spec function_exported?(atom | tuple, atom, integer) :: boolean
|
||||
def function_exported?(module, function, arity) do
|
||||
case is_tuple(module) do
|
||||
true ->
|
||||
@@ -2768,6 +2818,19 @@ defmodule Kernel do
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns true if the `module` is loaded and contains a
|
||||
public `macro` with the given `arity`, otherwise false.
|
||||
|
||||
Notice that this function does not load the module in case
|
||||
it is not loaded. Check `Code.ensure_loaded/1` for more
|
||||
information.
|
||||
"""
|
||||
@spec macro_exported?(atom, atom, integer) :: boolean
|
||||
def macro_exported?(module, macro, arity) do
|
||||
:lists.member({macro, arity}, module.__info__(:macros))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Access the given element using the qualifier according
|
||||
to the `Access` protocol. All calls in the form `foo[bar]`
|
||||
@@ -2927,7 +2990,10 @@ defmodule Kernel do
|
||||
append_first = Keyword.get(opts, :append_first, false)
|
||||
|
||||
lc fun inlist funs do
|
||||
{ name, args } = :elixir_clauses.extract_args(fun)
|
||||
case Macro.extract_args(fun) do
|
||||
{ name, args } -> :ok
|
||||
:error -> raise ArgumentError, message: "invalid syntax in defdelegate #{Macro.to_binary(fun)}"
|
||||
end
|
||||
|
||||
actual_args =
|
||||
case append_first and args != [] do
|
||||
@@ -3017,7 +3083,6 @@ defmodule Kernel do
|
||||
Regex.match? %r(foo), "foo" #=> true
|
||||
|
||||
"""
|
||||
|
||||
defmacro __r__({ :<<>>, _line, [string] }, options) when is_binary(string) do
|
||||
binary = Macro.unescape_binary(string, Regex.unescape_map(&1))
|
||||
regex = Regex.compile!(binary, options)
|
||||
|
||||
@@ -1,27 +1,45 @@
|
||||
defrecord Kernel.CLI.Config, commands: [], output: ".",
|
||||
compile: [], halt: true, compiler_options: []
|
||||
|
||||
defmodule Kernel.CLI do
|
||||
@moduledoc false
|
||||
|
||||
@doc """
|
||||
Invoked directly from erlang boot process. It parses all argv
|
||||
options and execute them in the order they are specified.
|
||||
@moduledoc """
|
||||
Module responsible for controlling Elixir's CLI
|
||||
"""
|
||||
def process_argv(options) do
|
||||
{ config, argv } = process_options(options, Kernel.CLI.Config.new)
|
||||
|
||||
defrecord Config, commands: [], output: ".",
|
||||
compile: [], halt: true, compiler_options: []
|
||||
|
||||
# This is the API invoked by Elixir boot process.
|
||||
@doc false
|
||||
def main(options) do
|
||||
{ config, argv } = process_argv(options, Kernel.CLI.Config.new)
|
||||
|
||||
argv = lc arg inlist argv, do: list_to_binary(arg)
|
||||
:gen_server.call(:elixir_code_server, { :argv, argv })
|
||||
|
||||
all_commands = Enum.reverse(config.commands)
|
||||
|
||||
run fn -> Enum.map all_commands, process_command(&1, config) end, config.halt
|
||||
run fn ->
|
||||
Enum.map Enum.reverse(config.commands), process_command(&1, config)
|
||||
:gen_server.cast(:elixir_code_server, :finished)
|
||||
end, config.halt
|
||||
end
|
||||
|
||||
@doc """
|
||||
Runs the given function and wraps any exception or
|
||||
error in messages useful for the command line.
|
||||
Wait until the CLI finishes procesing options.
|
||||
"""
|
||||
def wait_until_finished do
|
||||
case :gen_server.call(:elixir_code_server, { :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
|
||||
also invoked before exiting.
|
||||
|
||||
This function is used by Elixir's CLI and also
|
||||
by escripts generated by Elixir.
|
||||
"""
|
||||
def run(fun, halt // true) do
|
||||
try do
|
||||
@@ -56,7 +74,8 @@ defmodule Kernel.CLI do
|
||||
## Private
|
||||
|
||||
defp at_exit(status) do
|
||||
hooks = :gen_server.call(:elixir_code_server, :at_exit)
|
||||
hooks = :gen_server.call(:elixir_code_server, :flush_at_exit)
|
||||
|
||||
lc hook inlist hooks do
|
||||
try do
|
||||
hook.(status)
|
||||
@@ -72,6 +91,10 @@ defmodule Kernel.CLI do
|
||||
IO.puts Exception.formatted_stacktrace(trace)
|
||||
end
|
||||
end
|
||||
|
||||
# If an at_exit callback adds a
|
||||
# new hook we need to invoke it.
|
||||
unless hooks == [], do: at_exit(status)
|
||||
end
|
||||
|
||||
defp invalid_option(option) do
|
||||
@@ -100,7 +123,7 @@ defmodule Kernel.CLI do
|
||||
end
|
||||
|
||||
defp process_shared(['-e',h|t], config) do
|
||||
process_shared t, config.prepend_commands [eval: h]
|
||||
process_shared t, config.update_commands [{:eval,h}|&1]
|
||||
end
|
||||
|
||||
defp process_shared(['-pa',h|t], config) do
|
||||
@@ -116,14 +139,14 @@ defmodule Kernel.CLI do
|
||||
defp process_shared(['-r',h|t], config) do
|
||||
h = list_to_binary(h)
|
||||
config = Enum.reduce File.wildcard(h), config, fn path, config ->
|
||||
config.prepend_commands [require: path]
|
||||
config.update_commands [{:require,path}|&1]
|
||||
end
|
||||
process_shared t, config
|
||||
end
|
||||
|
||||
defp process_shared(['-pr',h|t], config) do
|
||||
h = list_to_binary(h)
|
||||
process_shared t, config.prepend_commands [parallel_require: h]
|
||||
process_shared t, config.update_commands [{:parallel_require,h}|&1]
|
||||
end
|
||||
|
||||
defp process_shared([erl,_|t], config) when erl in ['--erl', '--sname', '--remsh', '--name'] do
|
||||
@@ -136,35 +159,36 @@ defmodule Kernel.CLI do
|
||||
|
||||
# Process init options
|
||||
|
||||
def process_options(['--'|t], config) do
|
||||
defp process_argv(['--'|t], config) do
|
||||
{ config, t }
|
||||
end
|
||||
|
||||
def process_options(['--compile'|t], config) do
|
||||
defp process_argv(['--compile'|t], config) do
|
||||
process_compiler t, config
|
||||
end
|
||||
|
||||
def process_options(['-S',h|t], config) do
|
||||
defp process_argv(['-S',h|t], config) do
|
||||
exec = System.find_executable(h)
|
||||
if exec do
|
||||
{ config.prepend_commands([require: list_to_binary(exec)]), t }
|
||||
bin = list_to_binary(exec)
|
||||
{ config.update_commands([{:require,bin}|&1]), t }
|
||||
else
|
||||
IO.puts(:stderr, "Could not find executable #{h}")
|
||||
System.halt(1)
|
||||
end
|
||||
end
|
||||
|
||||
def process_options([h|t] = list, config) do
|
||||
defp process_argv([h|t] = list, config) do
|
||||
case h do
|
||||
'-' ++ _ ->
|
||||
shared_option? list, config, process_options(&1, &2)
|
||||
shared_option? list, config, process_argv(&1, &2)
|
||||
_ ->
|
||||
h = list_to_binary(h)
|
||||
{ config.prepend_commands([require: h]), t }
|
||||
bin = list_to_binary(h)
|
||||
{ config.update_commands([{:require,bin}|&1]), t }
|
||||
end
|
||||
end
|
||||
|
||||
def process_options([], config) do
|
||||
defp process_argv([], config) do
|
||||
{ config, [] }
|
||||
end
|
||||
|
||||
@@ -179,15 +203,15 @@ defmodule Kernel.CLI do
|
||||
end
|
||||
|
||||
defp process_compiler(['--no-docs'|t], config) do
|
||||
process_compiler t, config.merge_compiler_options(docs: false)
|
||||
process_compiler t, config.update_compiler_options([{:docs,false}|&1])
|
||||
end
|
||||
|
||||
defp process_compiler(['--debug-info'|t], config) do
|
||||
process_compiler t, config.merge_compiler_options(debug_info: true)
|
||||
defp process_compiler(['--no-debug-info'|t], config) do
|
||||
process_compiler t, config.update_compiler_options([{:debug_info,false}|&1])
|
||||
end
|
||||
|
||||
defp process_compiler(['--ignore-module-conflict'|t], config) do
|
||||
process_compiler t, config.merge_compiler_options(ignore_module_conflict: true)
|
||||
process_compiler t, config.update_compiler_options([{:ignore_module_conflict,true}|&1])
|
||||
end
|
||||
|
||||
defp process_compiler([h|t] = list, config) do
|
||||
@@ -197,12 +221,12 @@ defmodule Kernel.CLI do
|
||||
_ ->
|
||||
h = list_to_binary(h)
|
||||
pattern = if File.dir?(h), do: "#{h}/**/*.ex", else: h
|
||||
process_compiler t, config.prepend_compile [pattern]
|
||||
process_compiler t, config.update_compile [pattern|&1]
|
||||
end
|
||||
end
|
||||
|
||||
defp process_compiler([], config) do
|
||||
{ config.prepend_commands([compile: config.compile]), [] }
|
||||
{ config.update_commands([{:compile,config.compile}|&1]), [] }
|
||||
end
|
||||
|
||||
# Process commands
|
||||
@@ -217,7 +241,7 @@ defmodule Kernel.CLI do
|
||||
|
||||
defp process_command({:parallel_require, pattern}, _config) when is_binary(pattern) do
|
||||
files = File.wildcard(pattern)
|
||||
files = List.uniq(files)
|
||||
files = Enum.uniq(files)
|
||||
files = Enum.filter files, File.regular?(&1)
|
||||
Kernel.ParallelRequire.files(files)
|
||||
end
|
||||
@@ -226,7 +250,7 @@ defmodule Kernel.CLI do
|
||||
File.mkdir_p(config.output)
|
||||
|
||||
files = Enum.map patterns, File.wildcard(&1)
|
||||
files = List.uniq(List.concat(files))
|
||||
files = Enum.uniq(List.concat(files))
|
||||
files = Enum.filter files, File.regular?(&1)
|
||||
|
||||
Code.compiler_options(config.compiler_options)
|
||||
|
||||
@@ -0,0 +1,374 @@
|
||||
# This is an optimization Elixir runs on function clauses.
|
||||
# Whenever a variables matches against a record (a tagged
|
||||
# tuple), this information is stored in order to optimize
|
||||
# record calls.
|
||||
defmodule Kernel.RecordRewriter do
|
||||
@moduledoc false
|
||||
|
||||
def optimize_clause(clause) do
|
||||
optimize_clause(clause, :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, [])
|
||||
{ { :clause, line, args, guards, body }, dict, res }
|
||||
end
|
||||
|
||||
defp optimize_args(args, dict) do
|
||||
Enum.map_reduce args, dict, fn(arg, acc) ->
|
||||
{ new_arg, new_acc, _res } = optimize_expr(arg, acc)
|
||||
{ new_arg, new_acc }
|
||||
end
|
||||
end
|
||||
|
||||
defp optimize_body([], dict, _acc) do
|
||||
{ [], dict, nil }
|
||||
end
|
||||
|
||||
defp optimize_body([h], dict, acc) do
|
||||
{ new_expr, new_dict, new_res } = optimize_expr(h, 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])
|
||||
end
|
||||
|
||||
## Record helpers
|
||||
|
||||
defp record_fields(record) do
|
||||
if Code.ensure_loaded?(record) && function_exported?(record, :__record__, 1) do
|
||||
try do
|
||||
fields = lc { k, _ } inlist record.__record__(:fields), do: k
|
||||
optimizable = record.__record__(:optimizable)
|
||||
{ fields, optimizable }
|
||||
rescue
|
||||
[UndefinedFunctionError, FunctionClauseError] -> nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp record_field_info(function) do
|
||||
case atom_to_list(function) do
|
||||
'update_' ++ field -> { :update, list_to_atom(field) }
|
||||
_ -> { :accessor, function }
|
||||
end
|
||||
end
|
||||
|
||||
defp optimize_call(line, { record, _ } = res, left, { :atom, _, function }, args) do
|
||||
case record_fields(record) do
|
||||
{ fields, optimizable } ->
|
||||
if List.member?(optimizable, { function, length(args) + 1 }) do
|
||||
{ kind, field } = record_field_info(function)
|
||||
if index = Enum.find_index(fields, field == &1) do
|
||||
optimize_call(line, res, kind, field, index, left, args)
|
||||
end
|
||||
end
|
||||
nil -> nil
|
||||
end
|
||||
end
|
||||
|
||||
defp optimize_call(_line, _res, _left, _right, _args) do
|
||||
nil
|
||||
end
|
||||
|
||||
defp optimize_call(line, _res, :accessor, _field, index, left, []) do
|
||||
call = { :call, line,
|
||||
{ :remote, line, { :atom, 0, :erlang }, { :atom, 0, :element } },
|
||||
[{ :integer, 0, index + 2 }, left]
|
||||
}
|
||||
{ call, nil }
|
||||
end
|
||||
|
||||
defp optimize_call(line, res, :accessor, _field, index, left, [arg]) do
|
||||
call = { :call, line,
|
||||
{ :remote, line, { :atom, 0, :erlang }, { :atom, 0, :setelement } },
|
||||
[{ :integer, 0, index + 2 }, left, arg]
|
||||
}
|
||||
{ call, res }
|
||||
end
|
||||
|
||||
defp optimize_call(line, res, :update, field, _index, left, args) do
|
||||
call = { :call, line,
|
||||
{ :remote, line, left, { :atom, 0, :"update_#{field}" } },
|
||||
args
|
||||
}
|
||||
{ call, res }
|
||||
end
|
||||
|
||||
## 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)
|
||||
|
||||
case optimize_call(call_line, res, left, right, args) do
|
||||
{ call, call_res } ->
|
||||
{ call, dict, call_res }
|
||||
nil ->
|
||||
{ { :call, call_line, { :remote, line, left, right }, args }, dict, nil }
|
||||
end
|
||||
end
|
||||
|
||||
defp optimize_expr({ :call, line, expr, args }, dict) do
|
||||
{ expr, dict, _ } = optimize_expr(expr, dict)
|
||||
{ args, dict } = optimize_args(args, 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)
|
||||
|
||||
match = { :match, line, left, right }
|
||||
|
||||
if left_res do
|
||||
dict = assign_vars(extract_vars(right, []), dict, left_res)
|
||||
end
|
||||
|
||||
if right_res do
|
||||
dict = assign_vars(extract_vars(left, []), dict, right_res)
|
||||
end
|
||||
|
||||
{ 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)
|
||||
{ { :op, line, op, left, right }, dict, nil }
|
||||
end
|
||||
|
||||
defp optimize_expr({ :op, line, op, expr }, dict) do
|
||||
{ expr, dict, _ } = optimize_expr(expr, dict)
|
||||
{ { :op, line, op, expr }, dict, nil }
|
||||
end
|
||||
|
||||
defp optimize_expr({ :bin, line, elements }, dict) do
|
||||
{ elements, dict } = optimize_args(elements, dict)
|
||||
{ { :bin, line, elements }, dict, nil }
|
||||
end
|
||||
|
||||
defp optimize_expr({ :bin_element, line, expr, type1, type2 }, dict) do
|
||||
{ expr, dict, _ } = optimize_expr(expr, 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)
|
||||
{ { :cons, line, left, right }, dict, nil }
|
||||
end
|
||||
|
||||
defp optimize_expr({ :block, line, args }, dict) do
|
||||
{ args, dict, res } = optimize_body(args, dict, [])
|
||||
{ { :block, line, args }, dict, res }
|
||||
end
|
||||
|
||||
defp optimize_expr({ :tuple, line, args }, dict) do
|
||||
{ args, dict, args_res } = optimize_tuple_args(args, dict)
|
||||
args_res = if Enum.any?(args_res), do: args_res, else: nil
|
||||
|
||||
res =
|
||||
case args do
|
||||
[{ :atom, _, atom }|t] -> atom
|
||||
_ -> nil
|
||||
end
|
||||
|
||||
{ { :tuple, line, args }, dict, { res, args_res } }
|
||||
end
|
||||
|
||||
defp optimize_expr({ :var, _, name } = var, 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)
|
||||
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)
|
||||
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)
|
||||
clauses = lc { clause, _, _ } inlist tuples, do: clause
|
||||
|
||||
{ after_key, dict, _ } = optimize_expr(after_key, dict)
|
||||
{ after_value, dict, res } = optimize_body(after_value, dict, [])
|
||||
|
||||
dict = join_dict(tuples, dict)
|
||||
res = join_result(tuples, res)
|
||||
|
||||
{ { :receive, line, clauses, after_key, after_value }, dict, res }
|
||||
end
|
||||
|
||||
defp optimize_expr({ :try, line, body, [], clauses, try_after }, dict) do
|
||||
tuples = lc clause inlist clauses, do: optimize_clause(clause, dict)
|
||||
clauses = lc { clause, _, _ } inlist tuples, do: clause
|
||||
|
||||
{ body, _, res } = optimize_body(body, dict, [])
|
||||
res = join_result(tuples, res)
|
||||
|
||||
{ try_after, _, _ } = optimize_body(try_after, dict, [])
|
||||
{ { :try, line, body, [], clauses, 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 }
|
||||
end
|
||||
|
||||
defp optimize_expr({ :fun, line, { :clauses, clauses } }, dict) do
|
||||
clauses = lc clause inlist clauses do
|
||||
{ clause, _, _ } = optimize_clause(clause, 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)
|
||||
{ { 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)
|
||||
{ { generate, line, left, right }, dict, nil }
|
||||
end
|
||||
|
||||
defp optimize_expr(other, 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
|
||||
{ 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|acc_res] } }
|
||||
end
|
||||
|
||||
{ final_args, final_dict, Enum.reverse(final_acc) }
|
||||
end
|
||||
|
||||
defp assign_vars([key|t], dict, { _, value } = res) when is_list(key) and is_list(value) and length(key) == length(value) do
|
||||
assign_vars t, assign_nested_vars(key, dict, value), res
|
||||
end
|
||||
|
||||
defp assign_vars([key|t], dict, { value, _ } = res) when is_atom(key) and value != nil do
|
||||
dict =
|
||||
case :orddict.find(key, dict) do
|
||||
{ :ok, ^res } ->
|
||||
dict
|
||||
{ :ok, { ^value, _ } } ->
|
||||
:orddict.store(key, { value, nil }, dict)
|
||||
{ :ok, _ } ->
|
||||
# We are overriding a type of an existing variable,
|
||||
# which means the source code is invalid.
|
||||
:orddict.store(key, nil, dict)
|
||||
:error ->
|
||||
:orddict.store(key, res, dict)
|
||||
end
|
||||
|
||||
assign_vars t, dict, res
|
||||
end
|
||||
|
||||
defp assign_vars([_|t], dict, res) do
|
||||
assign_vars t, dict, res
|
||||
end
|
||||
|
||||
defp assign_vars([], dict, _res) do
|
||||
dict
|
||||
end
|
||||
|
||||
defp assign_nested_vars([vars|vt], dict, [res|rt]) do
|
||||
assign_nested_vars(vt, assign_vars(vars, dict, res), rt)
|
||||
end
|
||||
|
||||
defp assign_nested_vars([], dict, []) do
|
||||
dict
|
||||
end
|
||||
|
||||
defp extract_vars({ :match, _, left, right }, vars) do
|
||||
vars = extract_vars(right, vars)
|
||||
extract_vars(left, vars)
|
||||
end
|
||||
|
||||
defp extract_vars({ :var, _, name }, vars) do
|
||||
[name|vars]
|
||||
end
|
||||
|
||||
defp extract_vars({ :tuple, _, args }, vars) do
|
||||
[Enum.map(args, extract_vars(&1, []))|vars]
|
||||
end
|
||||
|
||||
defp extract_vars(_, vars) do
|
||||
vars
|
||||
end
|
||||
|
||||
defp join_dict([{ _, dict, _ }|t]) do
|
||||
join_dict(t, dict)
|
||||
end
|
||||
|
||||
defp join_dict([{ _, dict, _ }|t], other) do
|
||||
other = Enum.reduce other, other, fn
|
||||
{ key, { value, _ } = res }, acc ->
|
||||
case :orddict.find(key, dict) do
|
||||
{ :ok, ^res } -> acc
|
||||
{ :ok, { ^value, _ } } -> :orddict.store(key, { value, nil }, acc)
|
||||
{ :ok, _ } -> :orddict.store(key, nil, acc)
|
||||
:error -> :orddict.erase(key, acc)
|
||||
end
|
||||
end
|
||||
|
||||
join_dict(t, other)
|
||||
end
|
||||
|
||||
defp join_dict([], other) do
|
||||
other
|
||||
end
|
||||
|
||||
defp join_result([{ _, _, res }|t]) do
|
||||
join_result(t, res)
|
||||
end
|
||||
|
||||
defp join_result([{ _, _, res }|t], res) do
|
||||
join_result(t, res)
|
||||
end
|
||||
|
||||
defp join_result([{ _, _, { res, _ } }|t], { res, _ }) do
|
||||
join_result(t, { res, nil })
|
||||
end
|
||||
|
||||
defp join_result([{ _, _, _ }|_], _res) do
|
||||
nil
|
||||
end
|
||||
|
||||
defp join_result([], res) do
|
||||
res
|
||||
end
|
||||
end
|
||||
@@ -56,7 +56,7 @@ defmodule Kernel.SpecialForms do
|
||||
alias MyKeyword, as: Keyword
|
||||
end
|
||||
|
||||
In the example above, we have set up `MyOrdict` to be alias
|
||||
In the example above, we have set up `MyKeyword` to be alias
|
||||
as `Keyword`. So now, any reference to `Keyword` will be
|
||||
automatically replaced by `MyKeyword`.
|
||||
|
||||
@@ -276,7 +276,7 @@ defmodule Kernel.SpecialForms do
|
||||
|
||||
a = 10
|
||||
NoHygiene.interference
|
||||
a #=> 11
|
||||
a #=> 1
|
||||
|
||||
Notice that aliases are not hygienic in Elixir, ambiguity
|
||||
must be solved by prepending Elixir:
|
||||
|
||||
@@ -6,6 +6,81 @@ defmodule Kernel.Typespec do
|
||||
`@callback` available in modules are handled by the equivalent
|
||||
macros defined by this module.
|
||||
|
||||
## Defining a type
|
||||
|
||||
@type type_name :: type
|
||||
@typep type_name :: type
|
||||
@opaque type_name :: type
|
||||
|
||||
For more details, see documentation for deftype, deftypep and defopaque in
|
||||
Kernel.Typespec
|
||||
|
||||
## Defining a specification
|
||||
|
||||
@spec function_name(type, type) :: type
|
||||
@callback function_name(type, type) :: type
|
||||
|
||||
For more details, see documentation for defspec and defcallback in
|
||||
Kernel.Typespec
|
||||
|
||||
## Types
|
||||
|
||||
The type syntax provided by Elixir is fairly similar to the one
|
||||
in Erlang.
|
||||
|
||||
Most of the built-in types provided in Erlang (for example, `pid()`)
|
||||
are expressed the same way: `pid()` or simply `pid`. Parametrized types
|
||||
are also supported: `list(integer())` and so are remote types: `Enum.t`.
|
||||
|
||||
Certain data type shortcuts ([...], <<>> and {...}) are supported as well.
|
||||
|
||||
Main differences lie in how bit strings and functions are defined:
|
||||
|
||||
### Bit Strings
|
||||
|
||||
Bit string with a base size of 3:
|
||||
|
||||
<<_ :: 3>>
|
||||
|
||||
Bit string with a unit size of 8:
|
||||
|
||||
<<_ :: _ * 8>>
|
||||
|
||||
### Functions
|
||||
|
||||
Any function:
|
||||
|
||||
(fun(...) -> any)
|
||||
or
|
||||
((...) -> any)
|
||||
or
|
||||
(... -> any)
|
||||
|
||||
Function with arity of zero:
|
||||
|
||||
(fun() -> type)
|
||||
or
|
||||
(() -> type)
|
||||
|
||||
Function with some arity:
|
||||
|
||||
(fun(type, type) -> type)
|
||||
or
|
||||
((type, type) -> type)
|
||||
or
|
||||
(type, type -> type)
|
||||
|
||||
## Notes
|
||||
|
||||
Elixir discourages the use of type `string()` as it might be confused
|
||||
with binaries which are referred to as "strings" in Elixir (as opposed to
|
||||
character lists). In order to use the type that is called `string()` in Erlang,
|
||||
one has to use the `char_list()` type which is a synonym to `string()`. If yu
|
||||
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
|
||||
String module), use `String.t()` type instead.
|
||||
|
||||
See http://www.erlang.org/doc/reference_manual/typespec.html
|
||||
for more information.
|
||||
"""
|
||||
@@ -55,19 +130,34 @@ defmodule Kernel.Typespec do
|
||||
end
|
||||
end
|
||||
|
||||
@doc false
|
||||
defmacro defspec(spec, [do: block]) do
|
||||
IO.write "[WARNING] @spec f(...), do: type is deprecated, use @spec f(...) :: type\n#{Exception.env_stacktrace(__CALLER__)}"
|
||||
quote do
|
||||
Kernel.Typespec.defspec(:spec, (quote line: :keep, do: unquote(spec) :: unquote(block)), __ENV__)
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Defines a spec.
|
||||
This macro is the one responsible to handle the attribute @spec.
|
||||
|
||||
## Examples
|
||||
|
||||
@spec add(number, number), do: number
|
||||
@spec add(number, number) :: number
|
||||
|
||||
"""
|
||||
defmacro defspec(spec, block) do
|
||||
defmacro defspec(spec) do
|
||||
quote do
|
||||
Kernel.Typespec.defspec(:spec, (quote line: :keep, do: unquote spec),
|
||||
(quote line: :keep, do: unquote block), __ENV__)
|
||||
Kernel.Typespec.defspec(:spec, (quote line: :keep, do: unquote spec), __ENV__)
|
||||
end
|
||||
end
|
||||
|
||||
@doc false
|
||||
defmacro defcallback(spec, [do: block]) do
|
||||
IO.write "[WARNING] @callback f(...), do: type is deprecated, use @callback f(...) :: type\n#{Exception.env_stacktrace(__CALLER__)}"
|
||||
quote do
|
||||
Kernel.Typespec.defspec(:callback, (quote line: :keep, do: unquote(spec) :: unquote(block)), __ENV__)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -77,13 +167,12 @@ defmodule Kernel.Typespec do
|
||||
|
||||
## Examples
|
||||
|
||||
@callback add(number, number), do: number
|
||||
@callback add(number, number) :: number
|
||||
|
||||
"""
|
||||
defmacro defcallback(spec, block) do
|
||||
defmacro defcallback(spec) do
|
||||
quote do
|
||||
Kernel.Typespec.defspec(:callback, (quote line: :keep, do: unquote spec),
|
||||
(quote line: :keep, do: unquote block), __ENV__)
|
||||
Kernel.Typespec.defspec(:callback, (quote line: :keep, do: unquote(spec)), __ENV__)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -101,11 +190,8 @@ defmodule Kernel.Typespec do
|
||||
end
|
||||
|
||||
Module.compile_typespec module, kind, type
|
||||
|
||||
if export do
|
||||
if export, do:
|
||||
Module.compile_typespec module, :export_type, [{ name, length(vars) }]
|
||||
end
|
||||
|
||||
type
|
||||
end
|
||||
|
||||
@@ -154,15 +240,26 @@ defmodule Kernel.Typespec do
|
||||
|
||||
@doc """
|
||||
Converts a spec clause back to Elixir AST.
|
||||
Returns a 2-items tuple with the spec arguments and return result.
|
||||
"""
|
||||
def spec_to_ast({ :type, _line, :fun, [{:type, _, :product, args},result] }) do
|
||||
def spec_to_ast(name, { :type, line, :fun, [{:type, _, :product, args},result] }) do
|
||||
args = lc arg inlist args, do: typespec_to_ast(arg)
|
||||
{ args, typespec_to_ast(result) }
|
||||
{ :::, line, [{ name, line, args }, typespec_to_ast(result)] }
|
||||
end
|
||||
|
||||
def spec_to_ast({ :type, _, :fun, [] }) do
|
||||
{ [], quote do: term }
|
||||
def spec_to_ast(name, { :type, line, :fun, [] }) do
|
||||
{ :::, line, [{ name, line, [] }, quote(do: term)] }
|
||||
end
|
||||
|
||||
def spec_to_ast(name, { :type, line, :bounded_fun, [{ :type, _, :fun, [{ :type, _, :product, args }, result] }, constraints] }) do
|
||||
[h|t] =
|
||||
lc {:type, line, :constraint, [{:atom, _, :is_subtype}, [var, type]]} inlist constraints do
|
||||
{ :is_subtype, line, [typespec_to_ast(var), typespec_to_ast(type)] }
|
||||
end
|
||||
|
||||
args = lc arg inlist args, do: typespec_to_ast(arg)
|
||||
guards = Enum.reduce t, h, fn(x, acc) -> { :and, line, [acc, x] } end
|
||||
|
||||
{ :::, line, [{ :when, line, [{ name, line, args }, guards] }, typespec_to_ast(result)] }
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -174,6 +271,7 @@ defmodule Kernel.Typespec do
|
||||
type = { :{}, 0, [record|fields] }
|
||||
quote do: unquote(record)(unquote_splicing(args)) :: unquote(type)
|
||||
end
|
||||
|
||||
def type_to_ast({ name, type, args }) do
|
||||
args = lc arg inlist args, do: typespec_to_ast(arg)
|
||||
quote do: unquote(name)(unquote_splicing(args)) :: unquote(typespec_to_ast(type))
|
||||
@@ -290,14 +388,33 @@ defmodule Kernel.Typespec do
|
||||
end
|
||||
|
||||
@doc false
|
||||
def defspec(type, { name, line, args }, [do: return], caller) do
|
||||
def defspec(type, {:::, _, [{ :when, _, [{ name, line, args }, constraints_guard] }, return] }, caller) do
|
||||
if is_atom(args), do: args = []
|
||||
spec = { :type, line, :fun, fn_args(line, args, return, [], caller) }
|
||||
code = { { name, Kernel.length(args) }, spec }
|
||||
constraints = guard_to_constraints(constraints_guard, caller)
|
||||
spec = { :type, line, :fun, fn_args(line, args, return, Keyword.keys(constraints), caller) }
|
||||
spec = { :type, line, :bounded_fun, [spec, Keyword.values(constraints)] }
|
||||
code = { { name, Kernel.length(args) }, spec }
|
||||
Module.compile_typespec(caller.module, type, code)
|
||||
code
|
||||
end
|
||||
|
||||
def defspec(type, {:::, _, [{ name, line, args }, return]}, caller) do
|
||||
if is_atom(args), do: args = []
|
||||
spec = { :type, line, :fun, fn_args(line, args, return, [], caller) }
|
||||
code = { { name, Kernel.length(args) }, spec }
|
||||
Module.compile_typespec(caller.module, type, code)
|
||||
code
|
||||
end
|
||||
|
||||
defp guard_to_constraints({ :is_subtype, line, [{ name, _, _ }, type] }, caller) do
|
||||
contraints = [{ :atom, line, :is_subtype }, [{:var, line, name}, typespec(type, [], caller)]]
|
||||
[{ name, { :type, line, :constraint, contraints } }]
|
||||
end
|
||||
|
||||
defp guard_to_constraints({ :and, _, [left, right] }, caller) do
|
||||
guard_to_constraints(left, caller) ++ guard_to_constraints(right, caller)
|
||||
end
|
||||
|
||||
## To AST conversion
|
||||
|
||||
defp typespec_to_ast({ :type, line, :tuple, :any }) do
|
||||
@@ -329,11 +446,15 @@ defmodule Kernel.Typespec do
|
||||
|
||||
defp typespec_to_ast({ :type, line, :fun, [{:type, _, :product, args},result] }) do
|
||||
args = lc arg inlist args, do: typespec_to_ast(arg)
|
||||
{ :fun, line, args ++ [[do: typespec_to_ast(result)]] }
|
||||
{ :"->", line, [{args, typespec_to_ast(result)}] }
|
||||
end
|
||||
|
||||
defp typespec_to_ast({ :type, line, :fun, [args, result] }) do
|
||||
{ :"->", line, [{[typespec_to_ast(args)], typespec_to_ast(result)}] }
|
||||
end
|
||||
|
||||
defp typespec_to_ast({ :type, line, :fun, [] }) do
|
||||
{ :fun, line, [] }
|
||||
typespec_to_ast({ :type, line, :fun, [{:type, line, :any}, {:type,line,:any, []} ] })
|
||||
end
|
||||
|
||||
defp typespec_to_ast({ :type, line, name, args }) do
|
||||
@@ -342,9 +463,21 @@ defmodule Kernel.Typespec do
|
||||
end
|
||||
|
||||
defp typespec_to_ast({ :var, line, var }) do
|
||||
var =
|
||||
case atom_to_binary(var) do
|
||||
<<"_", c :: [binary, size(1)], rest :: binary>> ->
|
||||
binary_to_atom("_#{String.downcase(c)}#{rest}")
|
||||
<<c :: [binary, size(1)], rest :: binary>> ->
|
||||
binary_to_atom("#{String.downcase(c)}#{rest}")
|
||||
end
|
||||
{ var, line, nil }
|
||||
end
|
||||
|
||||
# special shortcut(s)
|
||||
defp typespec_to_ast({ :remote_type, line, [{:atom, _, :elixir}, {:atom, _, :char_list}, []] }) do
|
||||
typespec_to_ast({:type, line, :char_list, []})
|
||||
end
|
||||
|
||||
defp typespec_to_ast({ :remote_type, line, [mod, name, args] }) do
|
||||
args = lc arg inlist args, do: typespec_to_ast(arg)
|
||||
dot = { :., line, [typespec_to_ast(mod), typespec_to_ast(name)] }
|
||||
@@ -361,11 +494,20 @@ defmodule Kernel.Typespec do
|
||||
typespec_to_ast({ :ann_type, line, [{ :var, line1, name }, type] })
|
||||
end
|
||||
|
||||
defp typespec_to_ast({:type, _, :any}) do
|
||||
quote do: ...
|
||||
end
|
||||
|
||||
defp typespec_to_ast({:paren_type, _, [type]}) do
|
||||
typespec_to_ast(type)
|
||||
end
|
||||
|
||||
defp typespec_to_ast({ t, _line, atom }) when is_atom(t) do
|
||||
atom
|
||||
end
|
||||
|
||||
defp typespec_to_ast(other), do: other
|
||||
|
||||
## From AST conversion
|
||||
|
||||
# Handle unions
|
||||
@@ -403,6 +545,19 @@ defmodule Kernel.Typespec do
|
||||
typespec(atom, vars, caller)
|
||||
end
|
||||
|
||||
# Handle funs
|
||||
defp typespec({:fun, line, args}, vars, caller) when is_list(args) do
|
||||
IO.write "[WARNING] fun() type is deprecated, use (... -> type) instead\n#{Exception.env_stacktrace(caller)}"
|
||||
typespec({:"->", line, [{args, quote do: any}]}, vars, caller)
|
||||
end
|
||||
defp typespec({:"->", line, [{[{:fun, _, arguments}], return}]}, vars, caller) when is_list(arguments) do
|
||||
typespec({:"->", line, [{arguments, return}]}, vars, caller)
|
||||
end
|
||||
defp typespec({:"->", line, [{arguments, return}]}, vars, caller) when is_list(arguments) do
|
||||
args = fn_args(line, arguments, return, vars, caller)
|
||||
{ :type, line, :fun, args }
|
||||
end
|
||||
|
||||
# Handle type operator
|
||||
defp typespec({:"::", line, [var, expr] }, vars, caller) do
|
||||
left = typespec(var, [elem(var, 0)|vars], caller)
|
||||
@@ -425,7 +580,7 @@ defmodule Kernel.Typespec do
|
||||
# Handle remote calls
|
||||
defp typespec({{:., line, [remote, name]}, _, args}, vars, caller) do
|
||||
remote = Macro.expand remote, caller
|
||||
unless is_atom(remote), do: raise(ArgumentError, message: "Invalid remote in typespec")
|
||||
unless is_atom(remote), do: raise ArgumentError, message: "invalid remote in typespec"
|
||||
remote_type({typespec(remote, vars, caller), line, typespec(name, vars, caller), args}, vars, caller)
|
||||
end
|
||||
|
||||
@@ -443,18 +598,6 @@ defmodule Kernel.Typespec do
|
||||
{ :type, line, :tuple, args }
|
||||
end
|
||||
|
||||
# Handle funs
|
||||
defp typespec({:fun, line, arguments}, vars, caller) when is_list(arguments) do
|
||||
args =
|
||||
case :lists.reverse(arguments) do
|
||||
[[{:do,h}]|t] -> fn_args(line, :lists.reverse(t), h, vars, caller)
|
||||
[] -> []
|
||||
_ -> [fn_args(line, arguments, vars, caller)]
|
||||
end
|
||||
|
||||
{ :type, line, :fun, args }
|
||||
end
|
||||
|
||||
# Handle variables or local calls
|
||||
defp typespec({name, line, atom}, vars, caller) when is_atom(atom) do
|
||||
if List.member?(vars, name) do
|
||||
@@ -466,7 +609,8 @@ defmodule Kernel.Typespec do
|
||||
|
||||
# Handle local calls
|
||||
defp typespec({:string, line, arguments}, vars, caller) do
|
||||
IO.write "#{caller.file}:#{caller.line}: warning: string() type use is discouraged. For character lists, use char_list() type, for strings, String.t()\n"
|
||||
IO.write "warning: string() type use is discouraged. For character lists, use " <>
|
||||
"char_list() type, for strings, String.t()\n#{Exception.env_stacktrace(caller)}"
|
||||
arguments = lc arg inlist arguments, do: typespec(arg, vars, caller)
|
||||
{ :type, line, :string, arguments }
|
||||
end
|
||||
@@ -517,7 +661,10 @@ defmodule Kernel.Typespec do
|
||||
defp collect_union(v), do: [v]
|
||||
|
||||
defp fn_args(line, args, return, vars, caller) do
|
||||
[fn_args(line, args, vars, caller), typespec(return, vars, caller)]
|
||||
case [fn_args(line, args, vars, caller), typespec(return, vars, caller)] do
|
||||
[{:type,_,:any},{:type,_,:any,[]}] -> []
|
||||
x -> x
|
||||
end
|
||||
end
|
||||
|
||||
defp fn_args(line, [{:"...", _, _}], _vars, _caller) do
|
||||
|
||||
+19
-19
@@ -22,7 +22,7 @@ defmodule Keyword do
|
||||
which behaves as a dict, `Keyword.from_enum` do not remove
|
||||
duplicated entries.
|
||||
"""
|
||||
@spec from_enum(Enum.t), do: t
|
||||
@spec from_enum(Enum.t) :: t
|
||||
def from_enum(enum) when is_list(enum) do
|
||||
enum
|
||||
end
|
||||
@@ -34,7 +34,7 @@ defmodule Keyword do
|
||||
@doc """
|
||||
Checks if the given argument is a keywords list or not
|
||||
"""
|
||||
@spec keyword?(term), do: boolean
|
||||
@spec keyword?(term) :: boolean
|
||||
def keyword?([{ key, _value } | rest]) when is_atom(key) do
|
||||
case atom_to_list(key) do
|
||||
'Elixir-' ++ _ -> false
|
||||
@@ -48,7 +48,7 @@ defmodule Keyword do
|
||||
@doc """
|
||||
Returns an empty keyword list, i.e. an empty list.
|
||||
"""
|
||||
@spec new, do: t
|
||||
@spec new :: t
|
||||
def new do
|
||||
[]
|
||||
end
|
||||
@@ -63,7 +63,7 @@ defmodule Keyword do
|
||||
#=> [a: 2, b: 1]
|
||||
|
||||
"""
|
||||
@spec new(Enum.t), do: t
|
||||
@spec new(Enum.t) :: t
|
||||
def new(pairs) do
|
||||
Enum.reduce pairs, [], fn { k, v }, keywords ->
|
||||
put(keywords, k, v)
|
||||
@@ -81,7 +81,7 @@ defmodule Keyword do
|
||||
#=> [a: :a, b: :b]
|
||||
|
||||
"""
|
||||
@spec new(Enum.t, fun({key, value}, do: {key, value})), do: t
|
||||
@spec new(Enum.t, ({key, value} -> {key, value})) :: t
|
||||
def new(pairs, transform) do
|
||||
Enum.reduce pairs, [], fn i, keywords ->
|
||||
{ k, v } = transform.(i)
|
||||
@@ -105,8 +105,8 @@ defmodule Keyword do
|
||||
Keyword.get [a: 1], :b, 3 #=> 3
|
||||
|
||||
"""
|
||||
@spec get(t, key), do: value
|
||||
@spec get(t, key, value), do: value
|
||||
@spec get(t, key) :: value
|
||||
@spec get(t, key, value) :: value
|
||||
def get(keywords, key, default // nil) when is_atom(key) do
|
||||
case :lists.keyfind(key, 1, keywords) do
|
||||
{ ^key, value } -> value
|
||||
@@ -124,7 +124,7 @@ defmodule Keyword do
|
||||
Keyword.get! [a: 1], :b #=> raises KeyError[key: :b]
|
||||
|
||||
"""
|
||||
@spec get!(t, key), do: value | no_return
|
||||
@spec get!(t, key) :: value | no_return
|
||||
def get!(keywords, key) when is_atom(key) do
|
||||
case :lists.keyfind(key, 1, keywords) do
|
||||
{ ^key, value } -> value
|
||||
@@ -141,7 +141,7 @@ defmodule Keyword do
|
||||
#=> [1,2]
|
||||
|
||||
"""
|
||||
@spec get_values(t, key), do: [value]
|
||||
@spec get_values(t, key) :: [value]
|
||||
def get_values(keywords, key) when is_atom(key) do
|
||||
lc { k, v } inlist keywords, key == k, do: v
|
||||
end
|
||||
@@ -155,7 +155,7 @@ defmodule Keyword do
|
||||
Keyword.keys [a: 1, b: 2] #=> [:a,:b]
|
||||
|
||||
"""
|
||||
@spec keys(t), do: [key]
|
||||
@spec keys(t) :: [key]
|
||||
def keys(keywords) do
|
||||
lc { key, _ } inlist keywords, do: key
|
||||
end
|
||||
@@ -168,7 +168,7 @@ defmodule Keyword do
|
||||
Keyword.values [a: 1, b: 2] #=> [1,2]
|
||||
|
||||
"""
|
||||
@spec values(t), do: [value]
|
||||
@spec values(t) :: [value]
|
||||
def values(keywords) do
|
||||
lc { _, value } inlist keywords, do: value
|
||||
end
|
||||
@@ -185,7 +185,7 @@ defmodule Keyword do
|
||||
Keyword.delete [b: 2], :a #=> [b: 2]
|
||||
|
||||
"""
|
||||
@spec delete(t, key), do: t
|
||||
@spec delete(t, key) :: t
|
||||
def delete(keywords, key) when is_atom(key) do
|
||||
lc { k, _ } = tuple inlist keywords, key != k, do: tuple
|
||||
end
|
||||
@@ -202,7 +202,7 @@ defmodule Keyword do
|
||||
#=> [a: 3, b: 2]
|
||||
|
||||
"""
|
||||
@spec put(t, key, value), do: t
|
||||
@spec put(t, key, value) :: t
|
||||
def put(keywords, key, value) when is_atom(key) do
|
||||
[{key, value}|delete(keywords, key)]
|
||||
end
|
||||
@@ -217,7 +217,7 @@ defmodule Keyword do
|
||||
#=> true
|
||||
|
||||
"""
|
||||
@spec equal?(t, t), do: boolean
|
||||
@spec equal?(t, t) :: boolean
|
||||
def equal?(left, right) do
|
||||
:lists.sort(left) == :lists.sort(right)
|
||||
end
|
||||
@@ -232,7 +232,7 @@ defmodule Keyword do
|
||||
#=> [a:3, b:2, d: 4]
|
||||
|
||||
"""
|
||||
@spec merge(t, t), do: t
|
||||
@spec merge(t, t) :: t
|
||||
def merge(d1, d2) do
|
||||
d2 ++ lc({ k, _ } = tuple inlist d1, not has_key?(d2, k), do: tuple)
|
||||
end
|
||||
@@ -249,7 +249,7 @@ defmodule Keyword do
|
||||
#=> [a:4, b:2, d: 4]
|
||||
|
||||
"""
|
||||
@spec merge(t, t, fun(key, value, value, do: value)), do: t
|
||||
@spec merge(t, t, (key, value, value -> value)) :: t
|
||||
def merge(d1, d2, fun) do
|
||||
do_merge(d2, d1, fun)
|
||||
end
|
||||
@@ -273,7 +273,7 @@ defmodule Keyword do
|
||||
#=> false
|
||||
|
||||
"""
|
||||
@spec has_key?(t, key), do: boolean
|
||||
@spec has_key?(t, key) :: boolean
|
||||
def has_key?(keywords, key) when is_atom(key) do
|
||||
:lists.keymember(key, 1, keywords)
|
||||
end
|
||||
@@ -290,7 +290,7 @@ defmodule Keyword do
|
||||
#=> KeyError
|
||||
|
||||
"""
|
||||
@spec update(t, key, fun(value, do: value)), do: t | no_return
|
||||
@spec update(t, key, (value -> value)) :: t | no_return
|
||||
def update([{key, value}|keywords], key, fun) do
|
||||
[{key, fun.(value)}|delete(keywords, key)]
|
||||
end
|
||||
@@ -315,7 +315,7 @@ defmodule Keyword do
|
||||
#=> [a: 1, b: 11]
|
||||
|
||||
"""
|
||||
@spec update(t, key, value, fun(value, do: value)), do: t
|
||||
@spec update(t, key, value, (value -> value)) :: t
|
||||
def update([{key, value}|keywords], key, _initial, fun) do
|
||||
[{key, fun.(value)}|delete(keywords, key)]
|
||||
end
|
||||
|
||||
+13
-53
@@ -252,25 +252,11 @@ defmodule List do
|
||||
:lists.keydelete(key, position + 1, list)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a list of integers in the given range (both ends included when
|
||||
possible). An optional step can be provided as well (defaults to 1).
|
||||
|
||||
If first > last and no step is provided, the numbers will be in descending
|
||||
order.
|
||||
|
||||
## Examples
|
||||
|
||||
List.range 1, 3 #=> [1,2,3]
|
||||
List.range 1, 8, 2 #=> [1,3,5,7]
|
||||
List.range 1, 0 #=> []
|
||||
List.range 3, 1 #=> [3,2,1]
|
||||
List.range 5, 1, -2 #=> [5, 3, 1]
|
||||
|
||||
"""
|
||||
@doc false
|
||||
def range(first, last, step // nil)
|
||||
|
||||
def range(first, last, step) when is_integer(first) and is_integer(last) and first <= last do
|
||||
IO.write "[WARNING] List.range is deprecated, please use ranges instead\n#{Exception.formatted_stacktrace}"
|
||||
case step do
|
||||
nil ->
|
||||
:lists.seq(first, last, 1)
|
||||
@@ -282,6 +268,7 @@ defmodule List do
|
||||
end
|
||||
|
||||
def range(first, last, step) when is_integer(first) and is_integer(last) and first > last do
|
||||
IO.write "[WARNING] List.range is deprecated, please use ranges instead\n#{Exception.formatted_stacktrace}"
|
||||
case step do
|
||||
nil ->
|
||||
:lists.seq(first, last, -1)
|
||||
@@ -292,44 +279,21 @@ defmodule List do
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Sorts the list by comparing each term. For an alternative
|
||||
sorting algorithm, check `Enum.qsort`.
|
||||
|
||||
## Examples
|
||||
|
||||
List.sort [3, 4, 2, 1, 7]
|
||||
#=> [1, 2, 3, 4, 7]
|
||||
|
||||
"""
|
||||
@doc false
|
||||
def sort(list) do
|
||||
IO.write "[WARNING] List.sort is deprecated, please use Enum.sort instead\n#{Exception.formatted_stacktrace}"
|
||||
:lists.sort list
|
||||
end
|
||||
|
||||
@doc """
|
||||
Sorts the list according to an ordering function. fun(a, b) should
|
||||
return true if `a` compares less than or equal to `b`, `false` otherwise.
|
||||
|
||||
## Examples
|
||||
|
||||
List.sort [3, 4, 2, 1, 7], fn a, b -> b <= a end
|
||||
#=> [7, 4, 3, 2, 1]
|
||||
|
||||
"""
|
||||
@doc false
|
||||
def sort(list, fun) do
|
||||
IO.write "[WARNING] List.sort is deprecated, please use Enum.sort instead\n#{Exception.formatted_stacktrace}"
|
||||
:lists.sort fun, list
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a list without duplicated items.
|
||||
|
||||
## Examples
|
||||
|
||||
List.uniq [1,2,3,2,1]
|
||||
#=> [1,2,3]
|
||||
|
||||
"""
|
||||
@doc false
|
||||
def uniq(list) when is_list(list) do
|
||||
IO.write "[WARNING] List.uniq is deprecated, please use Enum.uniq instead\n#{Exception.formatted_stacktrace}"
|
||||
do_uniq(list, [])
|
||||
end
|
||||
|
||||
@@ -395,10 +359,8 @@ defmodule List do
|
||||
|
||||
defp do_uniq([h|t], acc) do
|
||||
case :lists.member(h, acc) do
|
||||
true ->
|
||||
do_uniq(t, acc)
|
||||
false ->
|
||||
[h|do_uniq(t, [h|acc])]
|
||||
true -> do_uniq(t, acc)
|
||||
false -> [h|do_uniq(t, [h|acc])]
|
||||
end
|
||||
end
|
||||
|
||||
@@ -413,10 +375,8 @@ defmodule List do
|
||||
{mlist, heads} = :lists.mapfoldl converter, [], list
|
||||
|
||||
case heads do
|
||||
nil ->
|
||||
:lists.reverse acc
|
||||
_ ->
|
||||
do_zip mlist, [list_to_tuple(:lists.reverse(heads))|acc]
|
||||
nil -> :lists.reverse acc
|
||||
_ -> do_zip mlist, [list_to_tuple(:lists.reverse(heads))|acc]
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
+65
-4
@@ -29,6 +29,28 @@ defmodule Macro do
|
||||
[:!, :@, :^, :not, :+, :-]
|
||||
end
|
||||
|
||||
@doc """
|
||||
Receives an expresion representing a possible definition
|
||||
and extracts its arguments. It returns a tuple with the
|
||||
function name and the arguments list or `:error` if not
|
||||
a valid call syntax.
|
||||
|
||||
This is useful for macros that want to provide the same
|
||||
arguments syntax available in def/defp/defmacro and friends.
|
||||
|
||||
## Examples
|
||||
|
||||
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
|
||||
|
||||
"""
|
||||
def extract_args(expr) do
|
||||
:elixir_clauses.extract_args(expr)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Recursively escapes the given value so it can be inserted
|
||||
into a syntax tree. Structures that are valid syntax nodes
|
||||
@@ -111,6 +133,11 @@ defmodule Macro do
|
||||
Octals will by default be escaped unless the map function
|
||||
returns false for ?0.
|
||||
|
||||
## Hex
|
||||
|
||||
Octals will by default be escaped unless the map function
|
||||
returns false for ?x.
|
||||
|
||||
## Examples
|
||||
|
||||
Using the unescape_map defined above is easy:
|
||||
@@ -126,7 +153,7 @@ defmodule Macro do
|
||||
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 method is useful
|
||||
unescaped, all others are ignored. This function is useful
|
||||
when implementing your own sigils. Check the implementation
|
||||
of `Kernel.__b__` for examples.
|
||||
"""
|
||||
@@ -189,21 +216,40 @@ defmodule Macro do
|
||||
end
|
||||
|
||||
# Fn keyword
|
||||
def to_binary({ :fn, _, [[do: block]] }) do
|
||||
def to_binary({ :fn, _, [[do: { :->, _, [{_,tuple}] } = arrow]] })
|
||||
when not is_tuple(tuple) or elem(tuple, 0) != :__block__ do
|
||||
"fn " <> arrow_to_binary(arrow) <> " end"
|
||||
end
|
||||
|
||||
def to_binary({ :fn, _, [[do: { :->, _, [_] } = block]] }) do
|
||||
"fn " <> block_to_binary(block) <> "\nend"
|
||||
end
|
||||
|
||||
def to_binary({ :fn, _, [[do: block]] }) do
|
||||
block = adjust_new_lines block_to_binary(block), "\n "
|
||||
"fn\n " <> block <> "\nend"
|
||||
end
|
||||
|
||||
# Partial call
|
||||
def to_binary({ :&, _, [num] }) do
|
||||
"&#{num}"
|
||||
end
|
||||
|
||||
# left -> right
|
||||
def to_binary({ :->, _, _ } = arrow) do
|
||||
"(" <> arrow_to_binary(arrow, true) <> ")"
|
||||
end
|
||||
|
||||
# Binary ops
|
||||
def to_binary({ op, _, [left, right] }) when op in binary_ops do
|
||||
op_to_binary(left) <> " #{op} " <> op_to_binary(right)
|
||||
end
|
||||
|
||||
# Unary ops
|
||||
def to_binary({ :not, _, [arg] }) do
|
||||
"not " <> to_binary(arg)
|
||||
end
|
||||
|
||||
def to_binary({ op, _, [arg] }) when op in unary_ops do
|
||||
atom_to_binary(op, :utf8) <> to_binary(arg)
|
||||
end
|
||||
@@ -267,8 +313,8 @@ defmodule Macro do
|
||||
|
||||
defp block_to_binary({ :->, _, exprs }) do
|
||||
Enum.map_join(exprs, "\n", fn({ left, right }) ->
|
||||
left = Enum.map_join(left, ", ", to_binary(&1))
|
||||
left <> " ->\n " <> adjust_new_lines block_to_binary(right), "\n "
|
||||
left = comma_join_or_empty_paren(left, false)
|
||||
left <> "->\n " <> adjust_new_lines block_to_binary(right), "\n "
|
||||
end)
|
||||
end
|
||||
|
||||
@@ -284,6 +330,20 @@ defmodule Macro do
|
||||
|
||||
defp op_to_binary(expr), do: to_binary(expr)
|
||||
|
||||
defp arrow_to_binary({ :->, _, pairs }, paren // false) do
|
||||
Enum.map_join(pairs, "; ", fn({ left, right }) ->
|
||||
left = comma_join_or_empty_paren(left, paren)
|
||||
left <> "-> " <> to_binary(right)
|
||||
end)
|
||||
end
|
||||
|
||||
defp comma_join_or_empty_paren([], true), do: "() "
|
||||
defp comma_join_or_empty_paren([], false), do: ""
|
||||
|
||||
defp comma_join_or_empty_paren(left, _) do
|
||||
Enum.map_join(left, ", ", to_binary(&1)) <> " "
|
||||
end
|
||||
|
||||
defp adjust_new_lines(block, replacement) do
|
||||
bc <<x>> inbits block do
|
||||
<< case x == ?\n do
|
||||
@@ -300,6 +360,7 @@ defmodule Macro do
|
||||
* Macros (local or remote);
|
||||
* Aliases are expanded (if possible) and return atoms;
|
||||
* All pseudo-variables (__FILE__, __MODULE__, etc);
|
||||
* Module attributes reader (@foo);
|
||||
|
||||
In case the expression cannot be expanded, it returns the expression itself.
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ defmodule Macro.Env do
|
||||
functions: functions, macros: macros]
|
||||
|
||||
Record.deffunctions(fields, __MODULE__)
|
||||
Record.deftypes(fields, types, __ENV__)
|
||||
Record.deftypes(fields, types, __MODULE__)
|
||||
|
||||
@moduledoc """
|
||||
A record that contains compile time environment information,
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
defmodule Module do
|
||||
require :ets, as: ETS
|
||||
|
||||
defmacrop is_env(env) do
|
||||
quote do
|
||||
is_tuple(unquote(env)) and size(unquote(env)) > 1 and elem(unquote(env), 0) == Macro.Env
|
||||
end
|
||||
end
|
||||
|
||||
@moduledoc """
|
||||
This module provides many functions to deal with modules during
|
||||
compilation time. It allows a developer to dynamically attach
|
||||
@@ -51,11 +57,11 @@ defmodule Module do
|
||||
"""
|
||||
def eval_quoted(module, quoted, binding // [], opts // [])
|
||||
|
||||
def eval_quoted(Macro.Env[module: module] = env, quoted, binding, opts) do
|
||||
eval_quoted(module, quoted, binding, Keyword.merge(env.to_keywords, opts))
|
||||
def eval_quoted(env, quoted, binding, opts) when is_env(env) do
|
||||
eval_quoted(env.module, quoted, binding, Keyword.merge(env.to_keywords, opts))
|
||||
end
|
||||
|
||||
def eval_quoted(module, quoted, binding, Macro.Env[] = env) do
|
||||
def eval_quoted(module, quoted, binding, env) when is_env(env) do
|
||||
eval_quoted(module, quoted, binding, env.to_keywords)
|
||||
end
|
||||
|
||||
@@ -95,12 +101,12 @@ defmodule Module do
|
||||
"""
|
||||
def create(module, quoted, opts // [])
|
||||
|
||||
def create(module, quoted, Macro.Env[] = env) do
|
||||
def create(module, quoted, env) when is_env(env) do
|
||||
create(module, quoted, env.to_keywords)
|
||||
end
|
||||
|
||||
def create(module, quoted, opts) when is_atom(module) do
|
||||
line = opts[:line] || 1
|
||||
line = Keyword.get(opts, :line, 1)
|
||||
:elixir_module.compile(line, module, quoted, [], :elixir.scope_for_eval(opts))
|
||||
end
|
||||
|
||||
@@ -578,7 +584,7 @@ defmodule Module do
|
||||
atom
|
||||
end
|
||||
|
||||
defp normalize_attribute(:file, Macro.Env[file: file, line: line]), do: { binary_to_list(file), line}
|
||||
defp normalize_attribute(:file, env) when is_env(env), do: { binary_to_list(env.file), env.line}
|
||||
defp normalize_attribute(:file, { binary, line }) when is_binary(binary), do: { binary_to_list(binary), line }
|
||||
defp normalize_attribute(:file, other) when not is_tuple(other), do: normalize_attribute(:file, { other, 1 })
|
||||
|
||||
|
||||
@@ -213,7 +213,7 @@ defmodule Protocol do
|
||||
{ List, :is_list, quote do: list },
|
||||
{ BitString, :is_bitstring, quote do: <<>> },
|
||||
{ Number, :is_number, quote do: number },
|
||||
{ Function, :is_function, quote do: fun },
|
||||
{ Function, :is_function, quote do: (... -> any) },
|
||||
{ PID, :is_pid, quote do: pid },
|
||||
{ Port, :is_port, quote do: port },
|
||||
{ Reference, :is_reference, quote do: reference },
|
||||
@@ -393,7 +393,7 @@ defmodule Protocol.DSL do
|
||||
# Convert the spec to callback if possible,
|
||||
# otherwise generate a dummy callback
|
||||
Protocol.DSL.callback_from_spec(__MODULE__, name, arity) ||
|
||||
@callback unquote(name)(unquote_splicing(type_args)), do: term
|
||||
@callback unquote(name)(unquote_splicing(type_args)) :: term
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
+113
-120
@@ -65,27 +65,26 @@ defmodule Record do
|
||||
end
|
||||
|
||||
"""
|
||||
def deffunctions(values, opts // [], env) do
|
||||
values = lc value inlist values, do: convert_value(value)
|
||||
escaped = Macro.escape(values)
|
||||
extensions = Keyword.get(opts, :extensions, Record.Extensions)
|
||||
def deffunctions(values, _opts // [], env) do
|
||||
values = lc value inlist values, do: convert_value(value)
|
||||
escaped = Macro.escape(values)
|
||||
|
||||
contents = [
|
||||
reflection(escaped),
|
||||
initializer(escaped),
|
||||
indexes(escaped),
|
||||
readers(values, 1, []),
|
||||
conversions(values),
|
||||
writers(values, 1, []),
|
||||
updater(values),
|
||||
extensions(values, 1, [], extensions)
|
||||
extensions(values, 1, [], Record.Extensions),
|
||||
accessors(values),
|
||||
]
|
||||
|
||||
contents = [quote(do: @__record__ unquote(escaped))|contents]
|
||||
|
||||
# Special case for bootstraping purposes
|
||||
if env == Macro.Env do
|
||||
:elixir_module.eval_quoted(env, contents, [], [])
|
||||
Module.eval_quoted(env, contents, [], [])
|
||||
else
|
||||
contents = [quote(do: @__record__ unquote(escaped))|contents]
|
||||
Module.eval_quoted(env.module, contents, [], env.location)
|
||||
end
|
||||
end
|
||||
@@ -93,23 +92,21 @@ defmodule Record do
|
||||
@doc """
|
||||
Defines types and specs for the record.
|
||||
"""
|
||||
def deftypes(values, types, opts // [], env) do
|
||||
def deftypes(values, types, _opts // [], env) do
|
||||
types = types || []
|
||||
values = lc value inlist values do
|
||||
{ name, default } = convert_value(value)
|
||||
{ name, default, find_spec(types, name) }
|
||||
end
|
||||
|
||||
extensions = Keyword.get(opts, :extensions, Record.Extensions)
|
||||
|
||||
contents = [
|
||||
core_specs(values),
|
||||
accessor_specs(values, []),
|
||||
extensions_specs(values, [], extensions)
|
||||
accessor_specs(values, 1, [])
|
||||
]
|
||||
|
||||
# Special case for bootstraping purposes
|
||||
if :erlang.function_exported(Module, :eval_quoted, 2) do
|
||||
if env == Macro.Env do
|
||||
Module.eval_quoted(env, contents, [], [])
|
||||
else
|
||||
Module.eval_quoted(env.module, contents, [], env.location)
|
||||
end
|
||||
end
|
||||
@@ -157,6 +154,34 @@ defmodule Record do
|
||||
Module.eval_quoted(env.module, contents, [], env.location)
|
||||
end
|
||||
|
||||
## Callbacks
|
||||
|
||||
# Store all optimizable fields in the record as well
|
||||
@doc false
|
||||
defmacro __before_compile__(_) do
|
||||
quote do
|
||||
def __record__(:optimizable), do: @record_optimizable
|
||||
end
|
||||
end
|
||||
|
||||
# Store fields that can be optimized and that cannot be
|
||||
# optimized as they are overriden
|
||||
@doc false
|
||||
def __on_definition__(env, kind, name, args, _guards, _body) do
|
||||
tuple = { name, length(args) }
|
||||
module = env.module
|
||||
functions = Module.get_attribute(module, :record_optimizable)
|
||||
|
||||
functions =
|
||||
if kind in [:def] and Module.get_attribute(module, :record_optimized) do
|
||||
[tuple|functions]
|
||||
else
|
||||
List.delete(functions, tuple)
|
||||
end
|
||||
|
||||
Module.put_attribute(module, :record_optimizable, functions)
|
||||
end
|
||||
|
||||
# Implements the access macro used by records.
|
||||
# It returns a quoted expression that defines
|
||||
# a record or a match in case the record is
|
||||
@@ -389,7 +414,7 @@ defmodule Record do
|
||||
end
|
||||
end
|
||||
|
||||
# Implement readers. For a declaration like:
|
||||
# Implement accessors. For a declaration like:
|
||||
#
|
||||
# defrecord FileInfo, atime: nil, mtime: nil
|
||||
#
|
||||
@@ -403,24 +428,6 @@ defmodule Record do
|
||||
# elem(record, 2)
|
||||
# end
|
||||
#
|
||||
defp readers([{ key, _default }|t], i, acc) do
|
||||
contents = quote do
|
||||
def unquote(key).(record) do
|
||||
:erlang.element(unquote(i + 1), record)
|
||||
end
|
||||
end
|
||||
|
||||
readers(t, i + 1, [contents | acc])
|
||||
end
|
||||
|
||||
defp readers([], _i, acc), do: acc
|
||||
|
||||
# Implement writers. For a declaration like:
|
||||
#
|
||||
# defrecord FileInfo, atime: nil, mtime: nil
|
||||
#
|
||||
# It will define four methods:
|
||||
#
|
||||
# def :atime.(value, record) do
|
||||
# setelem(record, 1, value)
|
||||
# end
|
||||
@@ -429,17 +436,51 @@ defmodule Record do
|
||||
# setelem(record, 2, value)
|
||||
# end
|
||||
#
|
||||
defp writers([{ key, _default }|t], i, acc) do
|
||||
# def :atime.(callback, record) do
|
||||
# setelem(record, 1, callback.(elem(record, 1)))
|
||||
# end
|
||||
#
|
||||
# def :mtime.(callback, record) do
|
||||
# setelem(record, 2, callback.(elem(record, 2)))
|
||||
# end
|
||||
#
|
||||
defp accessors(values) do
|
||||
[ quote do
|
||||
@record_optimized true
|
||||
@record_optimizable []
|
||||
@before_compile { unquote(__MODULE__), :__before_compile__ }
|
||||
@on_definition { unquote(__MODULE__), :__on_definition__ }
|
||||
end | accessors(values, 1) ]
|
||||
end
|
||||
|
||||
defp accessors([{ :__exception__, _ }|t], 1) do
|
||||
accessors(t, 2)
|
||||
end
|
||||
|
||||
defp accessors([{ key, _default }|t], i) do
|
||||
update = binary_to_atom "update_" <> atom_to_binary(key)
|
||||
|
||||
contents = quote do
|
||||
def unquote(key).(record) do
|
||||
:erlang.element(unquote(i + 1), record)
|
||||
end
|
||||
|
||||
def unquote(key).(value, record) do
|
||||
:erlang.setelement(unquote(i + 1), record, value)
|
||||
end
|
||||
|
||||
def unquote(update).(function, record) do
|
||||
:erlang.setelement(unquote(i + 1), record,
|
||||
function.(:erlang.element(unquote(i + 1), record)))
|
||||
end
|
||||
end
|
||||
|
||||
writers(t, i + 1, [contents | acc])
|
||||
[contents|accessors(t, i + 1)]
|
||||
end
|
||||
|
||||
defp writers([], _i, acc), do: acc
|
||||
defp accessors([], _i) do
|
||||
[quote do: @record_optimized false]
|
||||
end
|
||||
|
||||
# Define an updater method that receives a
|
||||
# keyword list and updates the record.
|
||||
@@ -483,30 +524,31 @@ defmodule Record do
|
||||
@type t :: { __MODULE__, unquote_splicing(types) }
|
||||
end
|
||||
|
||||
@spec new, do: t
|
||||
@spec new(Keyword.t | tuple), do: t
|
||||
@spec to_keywords(t), do: Keyword.t
|
||||
@spec update(Keyword.t, t), do: t
|
||||
@spec __index__(atom), do: non_neg_integer | nil
|
||||
@spec new :: t
|
||||
@spec new(Keyword.t | tuple) :: t
|
||||
@spec to_keywords(t) :: Keyword.t
|
||||
@spec update(Keyword.t, t) :: t
|
||||
@spec __index__(atom) :: non_neg_integer | nil
|
||||
end
|
||||
end
|
||||
|
||||
defp accessor_specs([{ key, _default, spec }|t], acc) do
|
||||
defp accessor_specs([{ :__exception__, _, _ }|t], 1, acc) do
|
||||
accessor_specs(t, 2, acc)
|
||||
end
|
||||
|
||||
defp accessor_specs([{ key, _default, spec }|t], i, acc) do
|
||||
update = binary_to_atom "update_" <> atom_to_binary(key)
|
||||
|
||||
contents = quote do
|
||||
@spec unquote(key)(t), do: unquote(spec)
|
||||
@spec unquote(key)(unquote(spec), t), do: t
|
||||
@spec unquote(key)(t) :: unquote(spec)
|
||||
@spec unquote(key)(unquote(spec), t) :: t
|
||||
@spec unquote(update)((unquote(spec) -> unquote(spec)), t) :: t
|
||||
end
|
||||
accessor_specs(t, [contents | acc])
|
||||
|
||||
accessor_specs(t, i + 1, [contents | acc])
|
||||
end
|
||||
|
||||
defp accessor_specs([], acc), do: acc
|
||||
|
||||
defp extensions_specs([{ key, default, spec }|t], acc, extensions) do
|
||||
specs = extensions.specs_for(key, default, spec)
|
||||
extensions_specs(t, [specs | acc], extensions)
|
||||
end
|
||||
|
||||
defp extensions_specs([], acc, _), do: acc
|
||||
defp accessor_specs([], _i, acc), do: acc
|
||||
|
||||
## Helpers
|
||||
|
||||
@@ -517,7 +559,17 @@ defmodule Record do
|
||||
defp is_keyword_tuple(_), do: false
|
||||
|
||||
defp convert_value(atom) when is_atom(atom), do: { atom, nil }
|
||||
defp convert_value(other), do: other
|
||||
|
||||
defp convert_value({ atom, other }) when is_atom(atom) and is_function(other), do:
|
||||
raise ArgumentError, message: "record field default value #{inspect atom} cannot be a function"
|
||||
|
||||
defp convert_value({ atom, other }) when is_atom(atom) and (is_reference(other) or is_pid(other) or is_port(other)), do:
|
||||
raise ArgumentError, message: "record field default value #{inspect atom} cannot be a reference, pid or port"
|
||||
|
||||
defp convert_value({ atom, _ } = tuple) when is_atom(atom), do: tuple
|
||||
|
||||
defp convert_value({ field, _ }), do:
|
||||
raise ArgumentError, message: "record field name has to be an atom, got #{inspect field}"
|
||||
|
||||
defp find_index([{ k, _ }|_], k, i), do: i
|
||||
defp find_index([{ _, _ }|t], k, i), do: find_index(t, k, i + 1)
|
||||
@@ -552,23 +604,7 @@ defmodule Record.Extensions do
|
||||
# Function definition
|
||||
|
||||
def functions_for(key, default, i) do
|
||||
[ default_for(key, default, i),
|
||||
extension_for(key, default, i) ]
|
||||
end
|
||||
|
||||
defp default_for(:__exception__, _default, _i) do
|
||||
nil
|
||||
end
|
||||
|
||||
defp default_for(key, _default, i) do
|
||||
update = prefix("update_", key)
|
||||
|
||||
quote do
|
||||
def unquote(update).(function, record) do
|
||||
current = :erlang.element(unquote(i + 1), record)
|
||||
:erlang.setelement(unquote(i + 1), record, function.(current))
|
||||
end
|
||||
end
|
||||
extension_for(key, default, i)
|
||||
end
|
||||
|
||||
defp extension_for(key, default, i) when is_list(default) do
|
||||
@@ -577,11 +613,13 @@ defmodule Record.Extensions do
|
||||
|
||||
quote do
|
||||
def unquote(prepend).(value, record) do
|
||||
IO.write "[WARNING] record default-based generated function #{unquote(prepend)} is deprecated\n#{Exception.formatted_stacktrace}"
|
||||
current = :erlang.element(unquote(i + 1), record)
|
||||
:erlang.setelement(unquote(i + 1), record, value ++ current)
|
||||
end
|
||||
|
||||
def unquote(merge).(value, record) do
|
||||
IO.write "[WARNING] record default-based generated function #{unquote(merge)} is deprecated\n#{Exception.formatted_stacktrace}"
|
||||
current = :erlang.element(unquote(i + 1), record)
|
||||
:erlang.setelement(unquote(i + 1), record, Keyword.merge(current, value))
|
||||
end
|
||||
@@ -593,6 +631,7 @@ defmodule Record.Extensions do
|
||||
|
||||
quote do
|
||||
def unquote(increment).(value // 1, record) do
|
||||
IO.write "[WARNING] record default-based generated function #{unquote(increment)} is deprecated\n#{Exception.formatted_stacktrace}"
|
||||
current = :erlang.element(unquote(i + 1), record)
|
||||
:erlang.setelement(unquote(i + 1), record, current + value)
|
||||
end
|
||||
@@ -604,6 +643,7 @@ defmodule Record.Extensions do
|
||||
|
||||
quote do
|
||||
def unquote(toggle).(record) do
|
||||
IO.write "[WARNING] record default-based generated function #{unquote(toggle)} is deprecated\n#{Exception.formatted_stacktrace}"
|
||||
current = :erlang.element(unquote(i + 1), record)
|
||||
:erlang.setelement(unquote(i + 1), record, not current)
|
||||
end
|
||||
@@ -612,53 +652,6 @@ defmodule Record.Extensions do
|
||||
|
||||
defp extension_for(_, _, _), do: nil
|
||||
|
||||
# Specs definition
|
||||
|
||||
def specs_for(key, default, spec) do
|
||||
[ default_specs_for(key, default, spec),
|
||||
extension_specs_for(key, default, spec) ]
|
||||
end
|
||||
|
||||
defp default_specs_for(:__exception__, _default, _spec) do
|
||||
nil
|
||||
end
|
||||
|
||||
defp default_specs_for(key, _default, spec) do
|
||||
update = prefix("update_", key)
|
||||
|
||||
quote do
|
||||
@spec unquote(update)(fun(unquote(spec), do: unquote(spec)), t), do: t
|
||||
end
|
||||
end
|
||||
|
||||
defp extension_specs_for(key, default, _spec) when is_list(default) do
|
||||
prepend = prefix("prepend_", key)
|
||||
merge = prefix("merge_", key)
|
||||
|
||||
quote do
|
||||
@spec unquote(prepend)(list, t), do: t
|
||||
@spec unquote(merge)(Keyword.t, t), do: t
|
||||
end
|
||||
end
|
||||
|
||||
defp extension_specs_for(key, default, _spec) when is_number(default) do
|
||||
increment = prefix("increment_", key)
|
||||
|
||||
quote do
|
||||
@spec unquote(increment)(number, t), do: t
|
||||
end
|
||||
end
|
||||
|
||||
defp extension_specs_for(key, default, _spec) when is_boolean(default) do
|
||||
toggle = prefix("toggle_", key)
|
||||
|
||||
quote do
|
||||
@spec unquote(toggle)(t), do: t
|
||||
end
|
||||
end
|
||||
|
||||
defp extension_specs_for(_, _, _), do: nil
|
||||
|
||||
# Helpers
|
||||
|
||||
defp prefix(prefix, key) do
|
||||
|
||||
@@ -21,7 +21,7 @@ defmodule Record.Extractor do
|
||||
|
||||
case :code.lib_dir(to_char_list(app)) do
|
||||
{ :error, _ } ->
|
||||
raise ArgumentError, "Lib file #{to_binary(file)} could not be found"
|
||||
raise ArgumentError, "lib file #{to_binary(file)} could not be found"
|
||||
libpath ->
|
||||
retrieve_record name, :filename.join([libpath|path])
|
||||
end
|
||||
@@ -33,7 +33,7 @@ defmodule Record.Extractor do
|
||||
if record = List.keyfind(records, name, 0) do
|
||||
parse_record(record)
|
||||
else
|
||||
raise ArgumentError, "No record #{name} found at #{to_binary(file)}"
|
||||
raise ArgumentError, "no record #{name} found at #{to_binary(file)}"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -50,13 +50,13 @@ defmodule Record.Extractor do
|
||||
{ :ok, form } ->
|
||||
form
|
||||
other ->
|
||||
raise "Error parsing file #{to_binary(file)}, got: #{inspect(other)}"
|
||||
raise "error parsing file #{to_binary(file)}, got: #{inspect(other)}"
|
||||
end
|
||||
end
|
||||
|
||||
# Parse a tuple with name and fields and returns a
|
||||
# list of second order tuples where the first element
|
||||
# is the field and the second is its default value.
|
||||
# list of tuples where the first element is the field
|
||||
# and the second is its default value.
|
||||
defp parse_record({ _name, fields }) do
|
||||
cons = List.foldr fields, { nil, 0 }, fn f, acc ->
|
||||
{ :cons, 0, parse_field(f), acc }
|
||||
|
||||
+10
-10
@@ -16,7 +16,7 @@ defmodule Regex do
|
||||
The re module provides several options, the one available in Elixir, followed by
|
||||
their shortcut in parenthesis, are:
|
||||
|
||||
* unicode (u) - used when you want to match against specific unicode characters
|
||||
* unicode (u) - enable unicode specific patterns like \p
|
||||
* caseless (i) - add case insensitivity
|
||||
* dotall (s) - causes dot to match newlines and also set newline to anycrlf.
|
||||
The new line setting can be overwritten by setting `(*CR)` or `(*LF)` or
|
||||
@@ -149,7 +149,7 @@ defmodule Regex do
|
||||
def captures(regex(groups: groups) = regex, string, options // []) do
|
||||
unless captures = Keyword.get(options, :capture) do
|
||||
captures = if groups do
|
||||
List.sort(groups)
|
||||
Enum.sort(groups)
|
||||
else
|
||||
raise "Regex was not compiled with g"
|
||||
end
|
||||
@@ -218,7 +218,7 @@ defmodule Regex do
|
||||
def scan(regex, string, options // [])
|
||||
|
||||
def scan(regex(re_pattern: compiled), string, options) do
|
||||
return = options[:return] || return_for(string)
|
||||
return = Keyword.get(options, :return, return_for(string))
|
||||
options = [{ :capture, :all, return }, :global]
|
||||
case :re.run(string, compiled, options) do
|
||||
:nomatch -> []
|
||||
@@ -236,13 +236,13 @@ defmodule Regex do
|
||||
def split(regex(re_pattern: compiled), string, options) do
|
||||
parts =
|
||||
cond do
|
||||
options[:global] == false -> 2
|
||||
p = options[:parts] -> p
|
||||
true -> :infinity
|
||||
Keyword.get(options, :global) == false -> 2
|
||||
p = Keyword.get(options, :parts) -> p
|
||||
true -> :infinity
|
||||
end
|
||||
|
||||
return = options[:return] || return_for(string)
|
||||
opts = [{ :return, return }, { :parts, parts }]
|
||||
return = Keyword.get(options, :return, return_for(string))
|
||||
opts = [return: return, parts: parts]
|
||||
:re.split(string, compiled, opts)
|
||||
end
|
||||
|
||||
@@ -265,8 +265,8 @@ defmodule Regex do
|
||||
|
||||
"""
|
||||
def replace(regex(re_pattern: compiled), string, replacement, options // []) do
|
||||
opts = if options[:global] != false, do: [:global], else: []
|
||||
return = options[:return] || return_for(string)
|
||||
opts = if Keyword.get(options, :global) != false, do: [:global], else: []
|
||||
return = Keyword.get(options, :return, return_for(string))
|
||||
opts = [{ :return, return }|opts]
|
||||
:re.replace(string, compiled, replacement, opts)
|
||||
end
|
||||
|
||||
+71
-40
@@ -27,7 +27,7 @@ defmodule String do
|
||||
String.printable?("abc") #=> true
|
||||
|
||||
"""
|
||||
@spec printable?(t), do: boolean
|
||||
@spec printable?(t) :: boolean
|
||||
# Allow basic ascii chars
|
||||
def printable?(<<c, t :: binary>>) when c in ?\s..?~ do
|
||||
printable?(t)
|
||||
@@ -125,9 +125,9 @@ defmodule String do
|
||||
String.split("a,b", %r{\.}) #=> ["a,b"]
|
||||
|
||||
"""
|
||||
@spec split(t), do: [t]
|
||||
@spec split(t, t | [t] | Regex.t), do: [t]
|
||||
@spec split(t, t | [t] | Regex.t, Keyword.t), do: [t]
|
||||
@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 // [])
|
||||
|
||||
def split(binary, pattern, options) when is_regex(pattern) do
|
||||
@@ -153,7 +153,7 @@ defmodule String do
|
||||
String.upcase("josé") #=> "JOSÉ"
|
||||
|
||||
"""
|
||||
@spec upcase(t), do: t
|
||||
@spec upcase(t) :: t
|
||||
defdelegate upcase(binary), to: String.Unicode
|
||||
|
||||
@doc """
|
||||
@@ -170,22 +170,30 @@ defmodule String do
|
||||
String.downcase("JOSÉ") #=> "josé"
|
||||
|
||||
"""
|
||||
@spec downcase(t), do: t
|
||||
@spec downcase(t) :: t
|
||||
defdelegate downcase(binary), to: String.Unicode
|
||||
|
||||
@doc """
|
||||
Returns a string where trailing char have been
|
||||
removed. If no `char` is passed `space`is used.
|
||||
Returns a string where trailing whitespace characters
|
||||
and new line have been removed.
|
||||
|
||||
## Examples
|
||||
|
||||
String.rstrip(" abc ") #=> " abc"
|
||||
|
||||
"""
|
||||
@spec rstrip(t) :: t
|
||||
defdelegate rstrip(binary), to: String.Unicode
|
||||
|
||||
@doc """
|
||||
Returns a string where trailing `char` have been removed.
|
||||
|
||||
## Examples
|
||||
|
||||
String.rstrip(" abc _", ?_) #=> " abc "
|
||||
|
||||
"""
|
||||
@spec rstrip(t), do: t
|
||||
@spec rstrip(t, char), do: t
|
||||
def rstrip(string, char // ?\s)
|
||||
@spec rstrip(t, char) :: t
|
||||
|
||||
def rstrip("", _char), do: ""
|
||||
|
||||
@@ -194,37 +202,45 @@ defmodule String do
|
||||
# does not traverse the whole binary).
|
||||
def rstrip(string, char) do
|
||||
if :binary.last(string) == char do
|
||||
rstrip(string, "", char)
|
||||
do_rstrip(string, "", char)
|
||||
else
|
||||
string
|
||||
end
|
||||
end
|
||||
|
||||
defp rstrip(<<char, string :: binary>>, buffer, char) do
|
||||
rstrip(string, <<char, buffer :: binary>>, char)
|
||||
defp do_rstrip(<<char, string :: binary>>, buffer, char) do
|
||||
do_rstrip(string, <<char, buffer :: binary>>, char)
|
||||
end
|
||||
|
||||
defp rstrip(<<char, string :: binary>>, buffer, another_char) do
|
||||
<<buffer :: binary, char, rstrip(string, "", another_char) :: binary>>
|
||||
defp do_rstrip(<<char, string :: binary>>, buffer, another_char) do
|
||||
<<buffer :: binary, char, do_rstrip(string, "", another_char) :: binary>>
|
||||
end
|
||||
|
||||
defp rstrip(<<>>, _, _) do
|
||||
defp do_rstrip(<<>>, _, _) do
|
||||
<<>>
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a string where leading char have been
|
||||
removed. If no `char` is passed `space`is used.
|
||||
Returns a string where leading whitespace characters
|
||||
have been removed.
|
||||
|
||||
## Examples
|
||||
|
||||
String.lstrip(" abc ") #=> "abc "
|
||||
|
||||
"""
|
||||
defdelegate lstrip(binary), to: String.Unicode
|
||||
|
||||
@doc """
|
||||
Returns a string where leading `char` have been removed.
|
||||
|
||||
## Examples
|
||||
|
||||
String.lstrip("_ abc _", ?_) #=> " abc _"
|
||||
|
||||
"""
|
||||
@spec lstrip(t), do: t
|
||||
@spec lstrip(t, char), do: t
|
||||
def lstrip(string, char // ?\s)
|
||||
|
||||
@spec lstrip(t, char) :: t
|
||||
|
||||
def lstrip(<<char, rest :: binary>>, char) do
|
||||
<<lstrip(rest, char) :: binary>>
|
||||
@@ -235,18 +251,32 @@ defmodule String do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a string where leading/trailing char have been
|
||||
removed. If no `char` is passed `space`is used.
|
||||
Returns a string where leading/trailing whitespace
|
||||
and new line characters have been removed.
|
||||
|
||||
## Examples
|
||||
|
||||
String.strip(" abc ") #=> "abc"
|
||||
|
||||
"""
|
||||
@spec strip(t) :: t
|
||||
|
||||
def strip(string) do
|
||||
rstrip(lstrip(string))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a string where leading/trailing `char` have been
|
||||
removed.
|
||||
|
||||
## Examples
|
||||
|
||||
String.strip("a abc a", ?a) #=> " abc "
|
||||
|
||||
"""
|
||||
@spec strip(t), do: t
|
||||
@spec strip(t, char), do: t
|
||||
def strip(string, char // ?\s) do
|
||||
@spec strip(t, char) :: t
|
||||
|
||||
def strip(string, char) do
|
||||
rstrip(lstrip(string, char), char)
|
||||
end
|
||||
|
||||
@@ -269,8 +299,9 @@ defmodule String do
|
||||
String.replace("a,b,c", ",", "[]", insert_replaced: [1,1]) #=> "a[,,]b[,,]c"
|
||||
|
||||
"""
|
||||
@spec replace(t, t, t), do: t
|
||||
@spec replace(t, t, t, Keyword.t), do: t
|
||||
@spec replace(t, t, t) :: t
|
||||
@spec replace(t, t, t, Keyword.t) :: t
|
||||
|
||||
def replace(subject, pattern, replacement, options // []) do
|
||||
opts = translate_replace_options(options)
|
||||
:binary.replace(subject, pattern, replacement, opts)
|
||||
@@ -295,7 +326,7 @@ defmodule String do
|
||||
String.duplicate("abc", 2) #=> "abcabc"
|
||||
|
||||
"""
|
||||
@spec duplicate(t, pos_integer), do: t
|
||||
@spec duplicate(t, pos_integer) :: t
|
||||
def duplicate(subject, n) when is_integer(n) and n > 0 do
|
||||
:binary.copy(subject, n)
|
||||
end
|
||||
@@ -310,7 +341,7 @@ defmodule String do
|
||||
String.codepoints("ἅἪῼ") #=> ["ἅ","Ἢ","ῼ"]
|
||||
|
||||
"""
|
||||
@spec codepoints(t), do: [codepoint]
|
||||
@spec codepoints(t) :: [codepoint]
|
||||
defdelegate codepoints(string), to: String.Unicode
|
||||
|
||||
@doc """
|
||||
@@ -325,7 +356,7 @@ defmodule String do
|
||||
String.next_codepoint("josé") #=> { "j", "osé" }
|
||||
|
||||
"""
|
||||
@spec next_codepoint(t), do: codepoint | :no_codepoint
|
||||
@spec next_codepoint(t) :: codepoint | :no_codepoint
|
||||
defdelegate next_codepoint(string), to: String.Unicode
|
||||
|
||||
@doc """
|
||||
@@ -335,7 +366,7 @@ defmodule String do
|
||||
String.graphemes("Ā̀stute") # => ["Ā̀","s","t","u","t","e"]
|
||||
|
||||
"""
|
||||
@spec graphemes(t), do: [grapheme]
|
||||
@spec graphemes(t) :: [grapheme]
|
||||
defdelegate graphemes(string), to: String.Unicode
|
||||
|
||||
@doc """
|
||||
@@ -350,7 +381,7 @@ defmodule String do
|
||||
String.next_grapheme("josé") #=> { "j", "osé" }
|
||||
|
||||
"""
|
||||
@spec next_grapheme(t), do: grapheme | :no_grapheme
|
||||
@spec next_grapheme(t) :: grapheme | :no_grapheme
|
||||
defdelegate next_grapheme(string), to: String.Unicode
|
||||
|
||||
@doc """
|
||||
@@ -362,7 +393,7 @@ defmodule String do
|
||||
String.first("եոգլի") #=> "ե"
|
||||
|
||||
"""
|
||||
@spec first(t), do: grapheme
|
||||
@spec first(t) :: grapheme
|
||||
def first(string) do
|
||||
case next_grapheme(string) do
|
||||
{ char, _ } -> char
|
||||
@@ -379,7 +410,7 @@ defmodule String do
|
||||
String.last("եոգլի") #=> "ի"
|
||||
|
||||
"""
|
||||
@spec last(t), do: grapheme
|
||||
@spec last(t) :: grapheme
|
||||
def last(string) do
|
||||
do_last(next_grapheme(string), "")
|
||||
end
|
||||
@@ -399,7 +430,7 @@ defmodule String do
|
||||
String.length("եոգլի") #=> 5
|
||||
|
||||
"""
|
||||
@spec length(t), do: non_neg_integer
|
||||
@spec length(t) :: non_neg_integer
|
||||
def length(string) do
|
||||
do_length(next_grapheme(string))
|
||||
end
|
||||
@@ -416,14 +447,14 @@ defmodule String do
|
||||
|
||||
## Examples
|
||||
|
||||
String.at("elixir", 0) #=> "1"
|
||||
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
|
||||
|
||||
"""
|
||||
@spec at(t, integer), do: grapheme | nil
|
||||
@spec at(t, integer) :: grapheme | nil
|
||||
def at(string, position) when position >= 0 do
|
||||
do_at(next_grapheme(string), position, 0)
|
||||
end
|
||||
@@ -432,7 +463,7 @@ defmodule String do
|
||||
real_pos = do_length(next_grapheme(string)) - abs(position)
|
||||
case real_pos >= 0 do
|
||||
true -> do_at(next_grapheme(string), real_pos, 0)
|
||||
false -> ""
|
||||
false -> nil
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -8,7 +8,8 @@ defmodule System do
|
||||
# Tries to run `git describe --always --tags`. In case of success
|
||||
# returns the most recent tag, otherwise returns an empty string.
|
||||
defmacrop get_describe do
|
||||
if :os.find_executable('git') do
|
||||
dotgit = File.join(File.cwd!, ".git")
|
||||
if :os.find_executable('git') && File.exists?(dotgit) do
|
||||
data = :os.cmd('git describe --always --tags')
|
||||
Regex.replace %r/\n/, to_binary(data), ""
|
||||
else
|
||||
@@ -24,11 +25,13 @@ defmodule System do
|
||||
@doc """
|
||||
Returns Elixir's version as binary.
|
||||
"""
|
||||
def version, do: "0.7.1"
|
||||
@spec version() :: String.t
|
||||
def version, do: "0.7.2"
|
||||
|
||||
@doc """
|
||||
Returns a keywords list with version, git tag info and date.
|
||||
"""
|
||||
@spec build_info() :: Keyword.t
|
||||
def build_info do
|
||||
[version: version, tag: get_describe, date: get_date]
|
||||
end
|
||||
@@ -36,6 +39,7 @@ defmodule System do
|
||||
@doc """
|
||||
Returns the list of command-line arguments passed to the program.
|
||||
"""
|
||||
@spec argv() :: [String.t]
|
||||
def argv do
|
||||
:gen_server.call(:elixir_code_server, :argv)
|
||||
end
|
||||
@@ -60,6 +64,8 @@ defmodule System do
|
||||
If `command` is a char list, a char list is returned.
|
||||
Returns a binary otherwise.
|
||||
"""
|
||||
@spec cmd(char_list) :: char_list
|
||||
@spec cmd(String.t) :: String.t
|
||||
def cmd(command) when is_list(command) do
|
||||
:os.cmd(command)
|
||||
end
|
||||
@@ -76,6 +82,8 @@ defmodule System do
|
||||
If `command` is a char list, a char list is returned.
|
||||
Returns a binary otherwise.
|
||||
"""
|
||||
@spec find_executable(char_list) :: char_list | nil
|
||||
@spec find_executable(String.t) :: String.t | nil
|
||||
def find_executable(command) when is_list(command) do
|
||||
:os.find_executable(command) || nil
|
||||
end
|
||||
@@ -92,8 +100,9 @@ defmodule System do
|
||||
given as a single string of the format "VarName=Value", where VarName is the
|
||||
name of the variable and Value its value.
|
||||
"""
|
||||
@spec get_env() :: [{String.t, String.t}]
|
||||
def get_env do
|
||||
Enum.map :os.getenv, list_to_binary &1
|
||||
Enum.map :os.getenv, :unicode.characters_to_binary &1
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -101,10 +110,11 @@ defmodule System do
|
||||
`varname` as a binary, or nil if the environment
|
||||
variable is undefined.
|
||||
"""
|
||||
@spec get_env(String.t) :: String.t | nil
|
||||
def get_env(varname) do
|
||||
case :os.getenv(to_char_list(varname)) do
|
||||
false -> nil
|
||||
other -> list_to_binary(other)
|
||||
other -> :unicode.characters_to_binary(other)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -114,19 +124,23 @@ defmodule System do
|
||||
|
||||
See http://www.erlang.org/doc/man/os.html#getpid-0 for more info.
|
||||
"""
|
||||
@spec get_pid() :: String.t
|
||||
def get_pid, do: list_to_binary(:os.getpid)
|
||||
|
||||
@doc """
|
||||
Sets a new `value` for the environment variable `varname`.
|
||||
"""
|
||||
def put_env(varname, value) do
|
||||
:os.putenv to_char_list(varname), to_char_list(value)
|
||||
@spec put_env(String.t, String.t | char_list) :: :ok
|
||||
def put_env(varname, value) when is_binary(value) or is_list(value) do
|
||||
:os.putenv to_char_list(varname), :unicode.characters_to_list(value)
|
||||
:ok
|
||||
end
|
||||
|
||||
@doc """
|
||||
Sets a new value for each environment variable corresponding
|
||||
to each key in `dict`.
|
||||
"""
|
||||
@spec put_env(Dict.t) :: :ok
|
||||
def put_env(dict) do
|
||||
Enum.each dict, fn {key, val} -> put_env key, val end
|
||||
end
|
||||
@@ -168,7 +182,9 @@ defmodule System do
|
||||
System.halt(:abort)
|
||||
|
||||
"""
|
||||
@spec halt(non_neg_integer | List.Chars.t | :abort, [] | [flush: false]), do: no_return
|
||||
@spec halt() :: no_return
|
||||
@spec halt(non_neg_integer | List.Chars.t | :abort) :: no_return
|
||||
@spec halt(non_neg_integer | List.Chars.t | :abort, [] | [flush: false]) :: no_return
|
||||
def halt(status // 0, options // [])
|
||||
|
||||
def halt(status, options) when is_integer(status) or status == :abort do
|
||||
|
||||
@@ -81,14 +81,9 @@ defmodule URI do
|
||||
<<c>>
|
||||
end
|
||||
|
||||
defp percent(c), do: escape_byte(c)
|
||||
|
||||
defp escape_byte(c), do: "%" <> hex(c)
|
||||
defp percent(c), do: "%" <> hex(bsr(c, 4)) <> hex(band(c, 15))
|
||||
|
||||
defp hex(n) when n <= 9, do: <<n + ?0>>
|
||||
defp hex(n) when n > 15 do
|
||||
hex(bsr(n, 4)) <> hex(band(n, 15))
|
||||
end
|
||||
defp hex(n), do: <<n + ?A - 10>>
|
||||
|
||||
@doc """
|
||||
|
||||
@@ -9,10 +9,10 @@ defmodule URI.Parser do
|
||||
@doc """
|
||||
Responsible for parsing extra URL information.
|
||||
"""
|
||||
defcallback parse(uri_info :: URI.Info.t), do: URI.Info.t
|
||||
defcallback parse(uri_info :: URI.Info.t) :: URI.Info.t
|
||||
|
||||
@doc """
|
||||
Responsible for returning the default port.
|
||||
"""
|
||||
defcallback default_port(), do: integer
|
||||
defcallback default_port() :: integer
|
||||
end
|
||||
|
||||
+46
-12
@@ -1,4 +1,4 @@
|
||||
# This file has its compilation step because
|
||||
# This file has its own compilation step because
|
||||
# it needs to parse String.Unicode data and
|
||||
# compile a digested module.
|
||||
defmodule String.Unicode do
|
||||
@@ -12,28 +12,32 @@ defmodule String.Unicode do
|
||||
|
||||
data_path = File.expand_path("../UnicodeData.txt", __FILE__)
|
||||
|
||||
codes = Enum.reduce File.iterator!(data_path), [], fn(line, acc) ->
|
||||
{ codes, whitespace } = Enum.reduce File.iterator!(data_path), { [], [] }, fn(line, { cacc, wacc }) ->
|
||||
[ codepoint, _name, _category,
|
||||
_class, _bidi, _decomposition,
|
||||
_class, bidi, _decomposition,
|
||||
_numeric_1, _numeric_2, _numeric_3,
|
||||
_bidi_mirror, _unicode_1, _iso,
|
||||
upper, lower, _title ] = :binary.split(line, ";", [:global])
|
||||
|
||||
if upper != "" or lower != "" do
|
||||
[{ to_binary.(codepoint), upper, lower }|acc]
|
||||
else
|
||||
acc
|
||||
cond do
|
||||
upper != "" or lower != "" ->
|
||||
{ [{ to_binary.(codepoint), upper, lower } | cacc], wacc }
|
||||
bidi in ["B", "S", "WS"] ->
|
||||
{ cacc, [to_binary.(codepoint) | wacc] }
|
||||
true ->
|
||||
{ cacc, wacc }
|
||||
end
|
||||
end
|
||||
|
||||
seqs_path = File.expand_path("../NamedSequences.txt", __FILE__)
|
||||
|
||||
seqs = Enum.map(File.iterator!(seqs_path), fn(line) ->
|
||||
seqs = Enum.map File.iterator!(seqs_path), fn(line) ->
|
||||
[ _name, codepoints ] = :binary.split(line, ";", [:global])
|
||||
codepoints = Enum.filter(:binary.split(codepoints, " ", [:global, :trim]),
|
||||
fn(x) -> size(x) > 0 end)
|
||||
Enum.map(codepoints, fn(x) -> to_binary.(x) end)
|
||||
end)
|
||||
codepoints = :binary.split(codepoints, " ", [:global])
|
||||
codepoints = Enum.map codepoints, Regex.replace(%r/\s+/, &1, "")
|
||||
codepoints = Enum.filter codepoints, fn(x) -> size(x) > 0 end
|
||||
Enum.map codepoints, to_binary.(&1)
|
||||
end
|
||||
|
||||
# Downcase
|
||||
|
||||
@@ -69,6 +73,36 @@ defmodule String.Unicode do
|
||||
<< >>
|
||||
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
|
||||
end
|
||||
|
||||
def lstrip(other), do: other
|
||||
|
||||
def rstrip(""), do: ""
|
||||
|
||||
def rstrip(string) 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
|
||||
end
|
||||
|
||||
defp do_rstrip(<< char, string :: binary >>, buffer) do
|
||||
<< buffer :: binary, char, do_rstrip(string, "") :: binary >>
|
||||
end
|
||||
|
||||
defp do_rstrip(<<>>, _), do: <<>>
|
||||
|
||||
# Graphemes
|
||||
|
||||
lc codepoints inlist seqs do
|
||||
|
||||
+14
-22
@@ -1,6 +1,6 @@
|
||||
-module(elixir).
|
||||
-behaviour(application).
|
||||
-export([main/1, start_cli/0, start_app/0,
|
||||
-export([main/1, start_cli/0,
|
||||
scope_for_eval/1, eval/2, eval/3, eval/4,
|
||||
eval_quoted/2, eval_quoted/3, eval_quoted/4,
|
||||
eval_forms/3]).
|
||||
@@ -16,9 +16,12 @@
|
||||
-export([start/2, stop/1, config_change/3]).
|
||||
|
||||
start(_Type, _Args) ->
|
||||
%% Set the shell to unicode so printing inside files work
|
||||
io:setopts(standard_io, [{encoding,unicode}]),
|
||||
io:setopts(standard_error, [{encoding,unicode}]),
|
||||
%% Set the shell to unicode so printing inside scripts work
|
||||
%% Those can take a while, so let's do it in a new process
|
||||
spawn(fun() ->
|
||||
io:setopts(standard_io, [{encoding,unicode}]),
|
||||
io:setopts(standard_error, [{encoding,unicode}])
|
||||
end),
|
||||
elixir_sup:start_link([]).
|
||||
|
||||
stop(_S) ->
|
||||
@@ -30,25 +33,14 @@ config_change(_Changed, _New, _Remove) ->
|
||||
%% escript entry point
|
||||
|
||||
main(Args) ->
|
||||
start_app(),
|
||||
'Elixir.Kernel.CLI':process_argv(Args).
|
||||
|
||||
%% ELIXIR ENTRY POINTS
|
||||
|
||||
% Start the Elixir app. This is the proper way to boot Elixir from
|
||||
% inside an Erlang process.
|
||||
|
||||
start_app() ->
|
||||
case lists:keyfind(?MODULE, 1, application:loaded_applications()) of
|
||||
false -> application:start(?MODULE);
|
||||
_ -> ok
|
||||
end.
|
||||
application:start(?MODULE),
|
||||
'Elixir.Kernel.CLI':main(Args).
|
||||
|
||||
% Boot and process given options. Invoked by Elixir's script.
|
||||
|
||||
start_cli() ->
|
||||
start_app(),
|
||||
'Elixir.Kernel.CLI':process_argv(init:get_plain_arguments()).
|
||||
application:start(?MODULE),
|
||||
'Elixir.Kernel.CLI':main(init:get_plain_arguments()).
|
||||
|
||||
%% EVAL HOOKS
|
||||
|
||||
@@ -125,7 +117,6 @@ eval_forms(Tree, Binding, RawScope) ->
|
||||
Scope = RawScope#elixir_scope{
|
||||
vars=binding_dict(Binding),
|
||||
temp_vars=[],
|
||||
quote_vars=[],
|
||||
clause_vars=nil,
|
||||
counter=[]
|
||||
},
|
||||
@@ -143,7 +134,8 @@ to_binary(Bin) when is_binary(Bin) -> Bin;
|
||||
to_binary(List) when is_list(List) -> list_to_binary(List).
|
||||
|
||||
binding_dict(List) -> binding_dict(List, orddict:new()).
|
||||
binding_dict([{H,_}|T], Dict) -> binding_dict(T, orddict:store(H, H, Dict));
|
||||
binding_dict([{{H,Kind},_}|T], Dict) -> binding_dict(T, orddict:store({ H, Kind }, H, Dict));
|
||||
binding_dict([{H,_}|T], Dict) -> binding_dict(T, orddict:store({ H, nil }, H, Dict));
|
||||
binding_dict([], Dict) -> Dict.
|
||||
|
||||
final_binding(Binding, Vars) -> final_binding(Binding, [], Binding, Vars).
|
||||
@@ -152,7 +144,7 @@ final_binding([{Var,_}|T], Acc, Binding, Vars) ->
|
||||
true ->
|
||||
final_binding(T, Acc, Binding, Vars);
|
||||
false ->
|
||||
RealName = orddict:fetch(Var, Vars),
|
||||
RealName = orddict:fetch({ Var, nil }, Vars),
|
||||
RealValue = proplists:get_value(RealName, Binding, nil),
|
||||
final_binding(T, [{Var, RealValue}|Acc], Binding, Vars)
|
||||
end;
|
||||
|
||||
@@ -75,7 +75,8 @@ extract_or_clauses(Term, Acc) -> [Term|Acc].
|
||||
|
||||
extract_args({ { '.', _, [Name] }, _, Args }) when is_atom(Name), is_list(Args) -> { Name, Args };
|
||||
extract_args({ Name, _, Args }) when is_atom(Name), is_atom(Args) -> { Name, [] };
|
||||
extract_args({ Name, _, Args }) when is_atom(Name), is_list(Args) -> { Name, Args }.
|
||||
extract_args({ Name, _, Args }) when is_atom(Name), is_list(Args) -> { Name, Args };
|
||||
extract_args(_) -> error.
|
||||
|
||||
% Extract guards when it is in the last element of the args
|
||||
|
||||
@@ -109,9 +110,11 @@ do_match(Line, DecoupledClauses, S) ->
|
||||
|
||||
% Now get all the variables defined inside each clause
|
||||
CV = lists:reverse(RawCV),
|
||||
NewVars = lists:umerge([lists:sort(orddict:fetch_keys(X)) || X <- CV]),
|
||||
|
||||
case NewVars of
|
||||
AllVars = lists:umerge([orddict:fetch_keys(X) || X <- CV]),
|
||||
SharedVars = ordsets:intersection(CV),
|
||||
|
||||
case AllVars of
|
||||
[] -> { TClauses, TS };
|
||||
_ ->
|
||||
% Create a new scope that contains a list of all variables
|
||||
@@ -119,7 +122,7 @@ do_match(Line, DecoupledClauses, S) ->
|
||||
% 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 normalize_vars/2, TS, NewVars),
|
||||
{ FinalVars, FS } = lists:mapfoldl(fun(X, Acc) -> normalize_vars(X, SharedVars, Acc) end, TS, AllVars),
|
||||
|
||||
% Defines a tuple that will be used as left side of the match operator
|
||||
LeftVars = [{var, Line, NewValue} || { _, _, NewValue, _ } <- FinalVars],
|
||||
@@ -131,7 +134,8 @@ do_match(Line, DecoupledClauses, S) ->
|
||||
end.
|
||||
|
||||
expand_clauses(Line, [Clause|T], [ClauseVars|V], LeftVars, FinalVars, Acc, S) ->
|
||||
RightVars = [normalize_clause_var(Var, Kind, OldValue, ClauseVars) || { Var, Kind, _, OldValue } <- FinalVars],
|
||||
RightVars = [normalize_clause_var(Var, Kind, OldValue, ClauseVars) ||
|
||||
{ Var, Kind, _, OldValue } <- FinalVars],
|
||||
|
||||
AssignExpr = generate_match(Line, LeftVars, RightVars),
|
||||
ClauseExprs = element(5, Clause),
|
||||
@@ -202,29 +206,30 @@ has_match_tuple(_) -> false.
|
||||
|
||||
% Normalize the given var checking its existence in the scope var dictionary.
|
||||
|
||||
normalize_vars({ Var, quoted }, S) ->
|
||||
normalize_vars(Var, quoted, #elixir_scope.quote_vars, S);
|
||||
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 == quoted) 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,
|
||||
|
||||
normalize_vars({ Var, Kind }, S) ->
|
||||
normalize_vars(Var, Kind, #elixir_scope.vars, S).
|
||||
S2 = S1#elixir_scope{
|
||||
vars=orddict:store(Key, NewValue, Vars),
|
||||
clause_vars=orddict:store(Key, NewValue, ClauseVars)
|
||||
},
|
||||
|
||||
normalize_vars(Var, Kind, Index, #elixir_scope{clause_vars=ClauseVars} = S) ->
|
||||
Vars = element(Index, S),
|
||||
|
||||
{ { _, _, NewValue }, S1 } = if
|
||||
(Kind == quoted) or (S#elixir_scope.noname) -> elixir_scope:build_erl_var(0, S);
|
||||
true -> elixir_scope:build_erl_var(0, Var, "_@" ++ atom_to_list(Var), S)
|
||||
end,
|
||||
|
||||
S2 = setelement(Index, S1, orddict:store(Var, NewValue, Vars)),
|
||||
S3 = S2#elixir_scope{clause_vars=orddict:store({ Var, Kind }, NewValue, ClauseVars)},
|
||||
|
||||
Expr = case orddict:find(Var, Vars) of
|
||||
Expr = case orddict:find(Key, Vars) of
|
||||
{ ok, OldValue } -> { var, 0, OldValue };
|
||||
error -> { atom, 0, nil }
|
||||
end,
|
||||
|
||||
{ { Var, Kind, NewValue, Expr }, S3 }.
|
||||
{ { Var, Kind, NewValue, Expr }, S2 }.
|
||||
|
||||
% Normalize a var by checking if it was defined in the clause.
|
||||
% If so, use it, otherwise use from main scope.
|
||||
|
||||
@@ -8,16 +8,23 @@
|
||||
at_exit=[],
|
||||
pool=[],
|
||||
counter=0,
|
||||
compiler_options=[{docs,true}]
|
||||
compiler_options=[{docs,true},{debug_info,true}],
|
||||
waiting=[]
|
||||
}).
|
||||
|
||||
start_link() ->
|
||||
{ ok, _ } = gen_server:start_link({local, elixir_code_server}, ?MODULE, [], []).
|
||||
|
||||
init(_args) ->
|
||||
process_flag(trap_exit, true),
|
||||
{ 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
|
||||
@@ -47,6 +54,9 @@ handle_call(loaded, _From, Config) ->
|
||||
handle_call(at_exit, _From, Config) ->
|
||||
{ reply, Config#elixir_code_server.at_exit, Config };
|
||||
|
||||
handle_call(flush_at_exit, _From, Config) ->
|
||||
{ reply, Config#elixir_code_server.at_exit, Config#elixir_code_server{at_exit=[]} };
|
||||
|
||||
handle_call(argv, _From, Config) ->
|
||||
{ reply, Config#elixir_code_server.argv, Config };
|
||||
|
||||
@@ -65,6 +75,11 @@ handle_call(retrieve_module_name, _From, Config) ->
|
||||
handle_call(_Request, _From, Config) ->
|
||||
{ reply, undef, Config }.
|
||||
|
||||
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
|
||||
@@ -93,9 +108,7 @@ handle_cast(_Request, Config) ->
|
||||
handle_info(_Request, Config) ->
|
||||
{ noreply, Config }.
|
||||
|
||||
terminate(Reason, Config) ->
|
||||
io:format("[FATAL] ~p crashed:\n~p~n", [?MODULE, Reason]),
|
||||
io:format("[FATAL] ~p snapshot:\n~p~n", [?MODULE, Config]),
|
||||
terminate(_Reason, _Config) ->
|
||||
ok.
|
||||
|
||||
code_change(_Old, Config, _Extra) ->
|
||||
|
||||
@@ -27,7 +27,7 @@ string(Contents, File) when is_list(Contents), is_binary(File) ->
|
||||
try
|
||||
put(elixir_compiled, []),
|
||||
Forms = elixir_translator:'forms!'(Contents, 1, File, []),
|
||||
eval_forms(Forms, 1, File, [], elixir:scope_for_eval([{file,File}])),
|
||||
eval_forms(Forms, 1, [], elixir:scope_for_eval([{file,File}])),
|
||||
lists:reverse(get(elixir_compiled))
|
||||
after
|
||||
put(elixir_compiled, Previous)
|
||||
@@ -49,32 +49,17 @@ file_to_path(File, Path) when is_binary(File), is_binary(Path) ->
|
||||
|
||||
%% Evaluates the contents/forms by compiling them to an Erlang module.
|
||||
|
||||
eval_forms(Forms, Line, Vars, #elixir_scope{module=nil} = S) ->
|
||||
eval_forms(Forms, Line, nil, Vars, S);
|
||||
|
||||
eval_forms(Forms, Line, Vars, #elixir_scope{module=Value} = S) ->
|
||||
eval_forms(Forms, Line, Value, Vars, S).
|
||||
|
||||
eval_forms(Forms, Line, Value, Vars, S) ->
|
||||
case (Value == nil) andalso allows_fast_compilation(Forms) of
|
||||
true -> eval_compilation(Forms, Vars, S);
|
||||
false -> code_loading_compilation(Forms, Line, Value, Vars, S)
|
||||
end.
|
||||
|
||||
eval_compilation(Forms, Vars, S) ->
|
||||
Binding = [{ Var, Value } || { _, Var, Value } <- Vars],
|
||||
{ Result, _Binding, FS } = elixir:eval_forms(Forms, [{'_@MODULE',nil}|Binding], S),
|
||||
{ Result, FS }.
|
||||
|
||||
code_loading_compilation(Forms, Line, Value, Vars, S) ->
|
||||
eval_forms(Forms, Line, Vars, S) ->
|
||||
{ Module, I } = retrieve_module_name(),
|
||||
{ Exprs, FS } = elixir_translator:translate(Forms, S),
|
||||
ModuleForm = module_form(Exprs, Line, S#elixir_scope.file, Module, Vars),
|
||||
|
||||
Args = [X || { _, _, X } <- Vars],
|
||||
Args = [X || { _, _, _, X } <- Vars],
|
||||
|
||||
{ module(ModuleForm, S#elixir_scope.file, [], true, fun(_, _) ->
|
||||
Res = Module:'BOOTSTRAP'(Value, Args),
|
||||
%% Pass { native, false } to speed up bootstrap
|
||||
%% process when native is set to true
|
||||
{ module(ModuleForm, S#elixir_scope.file, [{native,false}], true, fun(_, _) ->
|
||||
Res = Module:'BOOTSTRAP'(S#elixir_scope.module, Args),
|
||||
code:delete(Module),
|
||||
case code:soft_purge(Module) of
|
||||
true -> return_module_name(I);
|
||||
@@ -112,8 +97,8 @@ module(Forms, File, Options, Bootstrap, Callback) when
|
||||
%% Invoked from the Makefile.
|
||||
|
||||
core() ->
|
||||
elixir:start_app(),
|
||||
gen_server:call(elixir_code_server, { compiler_options, [{docs,false},{internal,true},{debug_info,true}] }),
|
||||
application:start(elixir),
|
||||
gen_server:call(elixir_code_server, { compiler_options, [{docs,false},{internal,true}] }),
|
||||
[core_file(File) || File <- core_main()].
|
||||
|
||||
%% HELPERS
|
||||
@@ -125,7 +110,7 @@ no_auto_import() ->
|
||||
module_form(Exprs, Line, File, Module, Vars) when
|
||||
is_binary(File), is_list(Exprs), is_integer(Line), is_atom(Module) ->
|
||||
|
||||
Cons = lists:foldr(fun({ _, Var, _ }, Acc) ->
|
||||
Cons = lists:foldr(fun({ _, _, Var, _ }, Acc) ->
|
||||
{ cons, Line, { var, Line, Var }, Acc }
|
||||
end, { nil, Line }, Vars),
|
||||
|
||||
@@ -140,12 +125,6 @@ module_form(Exprs, Line, File, Module, Vars) when
|
||||
] }
|
||||
].
|
||||
|
||||
%% Fast compilation is available?
|
||||
|
||||
allows_fast_compilation([{defmodule,_,_}|T]) -> allows_fast_compilation(T);
|
||||
allows_fast_compilation([]) -> true;
|
||||
allows_fast_compilation(_) -> false.
|
||||
|
||||
%% Generate module names from code server.
|
||||
|
||||
retrieve_module_name() ->
|
||||
@@ -180,11 +159,11 @@ core_main() ->
|
||||
"lib/elixir/lib/keyword.ex",
|
||||
"lib/elixir/lib/list.ex",
|
||||
"lib/elixir/lib/kernel/typespec.ex",
|
||||
"lib/elixir/lib/module.ex",
|
||||
"lib/elixir/lib/record.ex",
|
||||
"lib/elixir/lib/record/extractor.ex",
|
||||
"lib/elixir/lib/macro.ex",
|
||||
"lib/elixir/lib/macro/env.ex",
|
||||
"lib/elixir/lib/module.ex",
|
||||
"lib/elixir/lib/code.ex",
|
||||
"lib/elixir/lib/protocol.ex",
|
||||
"lib/elixir/lib/enum.ex",
|
||||
@@ -199,7 +178,8 @@ core_main() ->
|
||||
"lib/elixir/lib/system.ex",
|
||||
"lib/elixir/lib/kernel/cli.ex",
|
||||
"lib/elixir/lib/kernel/error_handler.ex",
|
||||
"lib/elixir/lib/kernel/parallel_compiler.ex"
|
||||
"lib/elixir/lib/kernel/parallel_compiler.ex",
|
||||
"lib/elixir/lib/kernel/record_rewriter.ex"
|
||||
].
|
||||
|
||||
%% ERROR HANDLING
|
||||
|
||||
@@ -93,7 +93,7 @@ store_definition(Kind, Line, Module, Name, Args, Guards, Body, RawS) ->
|
||||
end,
|
||||
|
||||
[store_each(false, Kind, File, Location, Table, 0,
|
||||
function_for_default(Kind, Name, Default)) || Default <- Defaults],
|
||||
default_function_for(Kind, Name, Default)) || Default <- Defaults],
|
||||
|
||||
{ Name, Arity }.
|
||||
|
||||
@@ -241,11 +241,11 @@ function_for_stored_definition({{Name, Arity}, _, Line, _, _, Location, _, Claus
|
||||
]
|
||||
}.
|
||||
|
||||
function_for_default(Kind, Name, { clause, Line, Args, _Guards, _Exprs } = Clause)
|
||||
default_function_for(Kind, Name, { clause, Line, Args, _Guards, _Exprs } = Clause)
|
||||
when Kind == defmacro; Kind == defmacrop ->
|
||||
{ function, Line, Name, length(Args) - 1, [Clause] };
|
||||
|
||||
function_for_default(_, Name, { clause, Line, Args, _Guards, _Exprs } = Clause) ->
|
||||
default_function_for(_, Name, { clause, Line, Args, _Guards, _Exprs } = Clause) ->
|
||||
{ function, Line, Name, length(Args), [Clause] }.
|
||||
|
||||
%% Store each definition in the table.
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
-module(elixir_interpolation).
|
||||
-export([extract/5, unescape_chars/1, unescape_chars/2,
|
||||
unescape_tokens/1, unescape_tokens/2, unescape_map/1]).
|
||||
-define(is_octal(S), S >= $0 andalso S =< $7).
|
||||
-include("elixir.hrl").
|
||||
-compile({parse_transform, elixir_transform}).
|
||||
|
||||
@@ -75,42 +74,61 @@ unescape_token(Other, _Map) -> Other.
|
||||
|
||||
% Unescape chars. For instance, "\" "n" (two chars) needs to be converted to "\n" (one char).
|
||||
|
||||
-define(to_octal(List),
|
||||
|
||||
).
|
||||
-define(is_octal(S), S >= $0 andalso S =< $7).
|
||||
-define(is_hex(S), (S >= $0 andalso S =< $9) orelse (S >= $A andalso S =< $F)).
|
||||
|
||||
unescape_chars(String) ->
|
||||
unescape_chars(String, fun unescape_map/1).
|
||||
|
||||
unescape_chars(String, Map) ->
|
||||
Octals = case Map($0) of
|
||||
false -> false;
|
||||
_ -> true
|
||||
end,
|
||||
unescape_chars(String, Map, Octals, <<>>).
|
||||
Octals = Map($0) /= false,
|
||||
Hex = Map($x) /= false,
|
||||
unescape_chars(String, Map, Octals, Hex, <<>>).
|
||||
|
||||
unescape_chars(<<$\\,A,B,C,Rest/binary>>, Map, true, Acc) when ?is_octal(A), ?is_octal(B), ?is_octal(C) ->
|
||||
to_octal(Rest, Map, [A,B,C], Acc);
|
||||
unescape_chars(<<$\\,A,B,C,Rest/binary>>, Map, true, Hex, Acc) when ?is_octal(A), ?is_octal(B), ?is_octal(C) ->
|
||||
append_escaped(Rest, Map, [A,B,C], true, Hex, Acc, 8);
|
||||
|
||||
unescape_chars(<<$\\,A,B,Rest/binary>>, Map, true, Acc) when ?is_octal(A), ?is_octal(B) ->
|
||||
to_octal(Rest, Map, [A,B], Acc);
|
||||
unescape_chars(<<$\\,A,B,Rest/binary>>, Map, true, Hex, Acc) when ?is_octal(A), ?is_octal(B) ->
|
||||
append_escaped(Rest, Map, [A,B], true, Hex, Acc, 8);
|
||||
|
||||
unescape_chars(<<$\\,A,Rest/binary>>, Map, true, Acc) when ?is_octal(A) ->
|
||||
to_octal(Rest, Map, [A], Acc);
|
||||
unescape_chars(<<$\\,A,Rest/binary>>, Map, true, Hex, Acc) when ?is_octal(A) ->
|
||||
append_escaped(Rest, Map, [A], true, Hex, Acc, 8);
|
||||
|
||||
unescape_chars(<<$\\,Escaped,Rest/binary>>, Map, Octals, Acc) ->
|
||||
unescape_chars(<<$\\,$x,A,B,Rest/binary>>, Map, Octal, true, Acc) when ?is_hex(A), ?is_hex(B) ->
|
||||
append_escaped(Rest, Map, [A,B], Octal, true, Acc, 16);
|
||||
|
||||
unescape_chars(<<$\\,$x,${,A,$},Rest/binary>>, Map, Octal, true, Acc) when ?is_hex(A) ->
|
||||
append_escaped(Rest, Map, [A], Octal, true, Acc, 16);
|
||||
|
||||
unescape_chars(<<$\\,$x,${,A,B,$},Rest/binary>>, Map, Octal, true, Acc) when ?is_hex(A), ?is_hex(B) ->
|
||||
append_escaped(Rest, Map, [A,B], Octal, true, Acc, 16);
|
||||
|
||||
unescape_chars(<<$\\,$x,${,A,B,C,$},Rest/binary>>, Map, Octal, true, Acc) when ?is_hex(A), ?is_hex(B), ?is_hex(C) ->
|
||||
append_escaped(Rest, Map, [A,B,C], Octal, true, Acc, 16);
|
||||
|
||||
unescape_chars(<<$\\,$x,${,A,B,C,D,$},Rest/binary>>, Map, Octal, true, Acc) when ?is_hex(A), ?is_hex(B), ?is_hex(C), ?is_hex(D) ->
|
||||
append_escaped(Rest, Map, [A,B,C,D], Octal, true, Acc, 16);
|
||||
|
||||
unescape_chars(<<$\\,$x,${,A,B,C,D,E,$},Rest/binary>>, Map, Octal, true, Acc) when ?is_hex(A), ?is_hex(B), ?is_hex(C), ?is_hex(D), ?is_hex(E) ->
|
||||
append_escaped(Rest, Map, [A,B,C,D,E], Octal, true, Acc, 16);
|
||||
|
||||
unescape_chars(<<$\\,$x,${,A,B,C,D,E,F,$},Rest/binary>>, Map, Octal, true, Acc) when ?is_hex(A), ?is_hex(B), ?is_hex(C), ?is_hex(D), ?is_hex(E), ?is_hex(F) ->
|
||||
append_escaped(Rest, Map, [A,B,C,D,E,F], Octal, true, Acc, 16);
|
||||
|
||||
unescape_chars(<<$\\,Escaped,Rest/binary>>, Map, Octals, Hex, Acc) ->
|
||||
case Map(Escaped) of
|
||||
false -> unescape_chars(Rest, Map, Octals, <<Acc/binary, $\\, Escaped>>);
|
||||
Other -> unescape_chars(Rest, Map, Octals, <<Acc/binary, Other>>)
|
||||
false -> unescape_chars(Rest, Map, Octals, Hex, <<Acc/binary, $\\, Escaped>>);
|
||||
Other -> unescape_chars(Rest, Map, Octals, Hex, <<Acc/binary, Other>>)
|
||||
end;
|
||||
|
||||
unescape_chars(<<Char, Rest/binary>>, Map, Octals, Acc) ->
|
||||
unescape_chars(Rest, Map, Octals, <<Acc/binary, Char>>);
|
||||
unescape_chars(<<Char, Rest/binary>>, Map, Octals, Hex, Acc) ->
|
||||
unescape_chars(Rest, Map, Octals, Hex, <<Acc/binary, Char>>);
|
||||
|
||||
unescape_chars(<<>>, _Map, _Octals, Acc) -> Acc.
|
||||
unescape_chars(<<>>, _Map, _Octals, _Hex, Acc) -> Acc.
|
||||
|
||||
to_octal(Rest, Map, Octal, Acc) ->
|
||||
unescape_chars(Rest, Map, true, <<Acc/binary, (list_to_integer(Octal, 8))/integer>>).
|
||||
append_escaped(Rest, Map, List, Octal, Hex, Acc, Base) ->
|
||||
Binary = unicode:characters_to_binary([list_to_integer(List, Base)]),
|
||||
unescape_chars(Rest, Map, Octal, Hex, <<Acc/binary, Binary/binary>>).
|
||||
|
||||
% Unescape Helpers
|
||||
|
||||
|
||||
@@ -56,6 +56,12 @@ translate({ function, Line, [[{do,{ '->',_,Pairs}}]] }, S) ->
|
||||
assert_no_assign_or_guard_scope(Line, 'function', S),
|
||||
elixir_translator:translate_fn(Line, Pairs, S);
|
||||
|
||||
translate({ function, Line, [{ '/', _, [{{ '.', _ ,[M, F] }, _ , [] }, A]}] }, S) ->
|
||||
translate({ function, Line, [M, F, A] }, S);
|
||||
|
||||
translate({ function, Line, [{ '/', _, [{F, _, Q}, A]}] }, S) when is_atom(Q) ->
|
||||
translate({ function, Line, [F, A] }, S);
|
||||
|
||||
translate({ function, Line, [_] }, S) ->
|
||||
assert_no_assign_or_guard_scope(Line, 'function', S),
|
||||
syntax_error(Line, S#elixir_scope.file, "invalid args for function");
|
||||
@@ -215,13 +221,21 @@ translate({Kind, Line, [Call]}, S) when ?FUNS(Kind) ->
|
||||
translate({Kind, Line, [Call, Expr]}, S) when ?FUNS(Kind) ->
|
||||
assert_module_scope(Line, Kind, S),
|
||||
assert_no_function_scope(Line, Kind, S),
|
||||
|
||||
{ TCall, Guards } = elixir_clauses:extract_guards(Call),
|
||||
{ Name, Args } = elixir_clauses:extract_args(TCall),
|
||||
{ Name, Args } = case elixir_clauses:extract_args(TCall) of
|
||||
error -> syntax_error(Line, S#elixir_scope.file,
|
||||
"invalid syntax in ~s ~s", [Kind, 'Elixir.Macro':to_binary(TCall)]);
|
||||
Tuple -> Tuple
|
||||
end,
|
||||
|
||||
assert_no_aliases_name(Line, 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, Line, TName, TArgs, TGuards, TExpr, S), S };
|
||||
|
||||
translate({Kind, Line, [Name, Args, Guards, Expr]}, S) when ?FUNS(Kind) ->
|
||||
@@ -277,6 +291,10 @@ translate_in(Line, Left, Right, S) ->
|
||||
lists:foldr(fun(X, Acc) ->
|
||||
{ op, Line, 'orelse', { op, Line, '==', Var, X }, Acc }
|
||||
end, { op, Line, '==', Var, H }, T);
|
||||
{ string, _, [H|T] } ->
|
||||
lists:foldl(fun(X, Acc) ->
|
||||
{ op, Line, 'orelse', { op, Line, '==', Var, { integer, Line, X } }, Acc }
|
||||
end, { op, Line, '==', Var, { integer, Line, H } }, T);
|
||||
{ tuple, _, [{ atom, _, 'Elixir.Range' }, Start, End] } ->
|
||||
case { Start, End } of
|
||||
{ { K1, _, StartInt }, { K2, _, EndInt } } when ?IN_TYPES(K1), ?IN_TYPES(K2), StartInt =< EndInt ->
|
||||
|
||||
@@ -40,18 +40,10 @@ docs_table(Module) ->
|
||||
%% will be passed to the invoked function.
|
||||
|
||||
translate(Line, Ref, Block, S) ->
|
||||
MetaBlock = elixir_tree_helpers:abstract_syntax(Block),
|
||||
MetaS = elixir_scope:serialize(S),
|
||||
MetaBlock = elixir_tree_helpers:abstract_syntax(Block),
|
||||
{ MetaS, Vars } = elixir_scope:serialize_with_vars(Line, S),
|
||||
|
||||
Vars = orddict:fold(fun(Key, Value, { Acc, Counter }) ->
|
||||
{ { cons, Line, { tuple, Line, [
|
||||
{ atom, Line, Key },
|
||||
{ atom, Line, ?ELIXIR_ATOM_CONCAT(["_@", Counter]) },
|
||||
{ var, Line, Value }
|
||||
] }, Acc }, Counter + 1 }
|
||||
end, { { nil, Line }, 0 }, S#elixir_scope.vars),
|
||||
|
||||
Args = [{integer, Line, Line}, Ref, MetaBlock, element(1, Vars), MetaS],
|
||||
Args = [{integer, Line, Line}, Ref, MetaBlock, Vars, MetaS],
|
||||
?ELIXIR_WRAP_CALL(Line, ?MODULE, compile, Args).
|
||||
|
||||
%% The compilation hook.
|
||||
@@ -92,8 +84,8 @@ compile(Line, Other, _Block, _Vars, #elixir_scope{file=File}) ->
|
||||
elixir_errors:form_error(Line, File, ?MODULE, { invalid_module, Other });
|
||||
|
||||
compile(Line, Module, Block, Vars, RawS) ->
|
||||
Dict = [{ X, Y } || { X, Y, _ } <- Vars],
|
||||
S = elixir_scope:deserialize(RawS, Dict),
|
||||
Dict = [{ { Name, Kind }, Value } || { Name, Kind, Value, _ } <- Vars],
|
||||
S = elixir_scope:deserialize_with_vars(RawS, Dict),
|
||||
compile(Line, Module, Block, Vars, S).
|
||||
|
||||
%% Hook that builds both attribute and functions and set up common hooks.
|
||||
@@ -144,7 +136,12 @@ eval_form(Line, Module, Block, Vars, RawS) ->
|
||||
Value.
|
||||
|
||||
%% Return the form with exports and function declarations.
|
||||
functions_form(Line, File, Module, Export, Private, Def, Defmacro, Defmacrop, Functions, C) ->
|
||||
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)
|
||||
end,
|
||||
|
||||
{ FinalExport, FinalFunctions } =
|
||||
add_info_function(Line, File, Module, Export, Functions, Def, Defmacro, C),
|
||||
|
||||
@@ -155,6 +152,17 @@ functions_form(Line, File, Module, Export, Private, Def, Defmacro, Defmacrop, Fu
|
||||
{attribute, Line, export, lists:sort(FinalExport)} | FinalFunctions
|
||||
] }.
|
||||
|
||||
record_rewrite_functions(Functions) ->
|
||||
lists:map(fun
|
||||
({ function, Line, Name, Arity, Clauses }) ->
|
||||
Rewriten = [begin
|
||||
{ C, _, _ } = 'Elixir.Kernel.RecordRewriter':optimize_clause(Clause),
|
||||
C
|
||||
end || Clause <- Clauses],
|
||||
{ function, Line, Name, Arity, Rewriten };
|
||||
(Other) -> Other
|
||||
end, Functions).
|
||||
|
||||
%% Add attributes handling to the form
|
||||
|
||||
attributes_form(Line, _File, Module, Current) ->
|
||||
@@ -196,7 +204,9 @@ specs_form(Line, Module, Defmacro, DefmacropWithLine, Forms, C) ->
|
||||
end.
|
||||
|
||||
specs_attributes(Line, Type, Forms, Specs) ->
|
||||
Keys = lists:ukeysort(1, Specs),
|
||||
Keys = lists:foldl(fun({ Tuple, Value }, Acc) ->
|
||||
lists:keystore(Tuple, 1, Acc, { Tuple, Value } )
|
||||
end, [], Specs),
|
||||
lists:foldl(fun({ Tuple, _ }, Acc) ->
|
||||
Values = [V || { K, V } <- Specs, K == Tuple],
|
||||
[{ attribute, Line, Type, { Tuple, Values } }|Acc]
|
||||
@@ -262,50 +272,50 @@ add_info_function(Line, File, Module, Export, Functions, Def, Defmacro, C) ->
|
||||
true -> elixir_errors:form_error(Line, File, ?MODULE, {internal_function_overridden, Pair});
|
||||
false ->
|
||||
Docs = elixir_compiler:get_opt(docs, C),
|
||||
Contents = { function, Line, '__info__', 1, [
|
||||
functions_clause(Line, Def),
|
||||
macros_clause(Line, Module, Def, Defmacro),
|
||||
docs_clause(Line, Module, Docs),
|
||||
Contents = { function, 0, '__info__', 1, [
|
||||
functions_clause(Def),
|
||||
macros_clause(Module, Def, Defmacro),
|
||||
docs_clause(Module, Docs),
|
||||
moduledoc_clause(Line, Module, Docs),
|
||||
module_clause(Line, Module),
|
||||
else_clause(Line)
|
||||
module_clause(Module),
|
||||
else_clause()
|
||||
] },
|
||||
{ [Pair|Export], [Contents|Functions] }
|
||||
end.
|
||||
|
||||
functions_clause(Line, Def) ->
|
||||
functions_clause(Def) ->
|
||||
All = ordsets:add_element({'__info__',1}, Def),
|
||||
{ clause, Line, [{ atom, Line, functions }], [], [elixir_tree_helpers:abstract_syntax(All)] }.
|
||||
{ clause, 0, [{ atom, 0, functions }], [], [elixir_tree_helpers:abstract_syntax(All)] }.
|
||||
|
||||
macros_clause(Line, Module, Def, Defmacro) ->
|
||||
macros_clause(Module, Def, Defmacro) ->
|
||||
All = handle_builtin_macros(Module, Def, Defmacro),
|
||||
{ clause, Line, [{ atom, Line, macros }], [], [elixir_tree_helpers:abstract_syntax(All)] }.
|
||||
{ clause, 0, [{ atom, 0, macros }], [], [elixir_tree_helpers:abstract_syntax(All)] }.
|
||||
|
||||
handle_builtin_macros('Elixir.Kernel', Def, Defmacro) ->
|
||||
ordsets:subtract(ordsets:union(Defmacro,
|
||||
elixir_dispatch:in_erlang_macros()), Def);
|
||||
handle_builtin_macros(_, _Def, Defmacro) -> Defmacro.
|
||||
|
||||
module_clause(Line, Module) ->
|
||||
{ clause, Line, [{ atom, Line, module }], [], [{ atom, Line, Module }] }.
|
||||
module_clause(Module) ->
|
||||
{ clause, 0, [{ atom, 0, module }], [], [{ atom, 0, Module }] }.
|
||||
|
||||
docs_clause(Line, Module, true) ->
|
||||
docs_clause(Module, true) ->
|
||||
Docs = ordsets:from_list(ets:tab2list(docs_table(Module))),
|
||||
{ clause, Line, [{ atom, Line, docs }], [], [elixir_tree_helpers:abstract_syntax(Docs)] };
|
||||
{ clause, 0, [{ atom, 0, docs }], [], [elixir_tree_helpers:abstract_syntax(Docs)] };
|
||||
|
||||
docs_clause(Line, _Module, _) ->
|
||||
{ clause, Line, [{ atom, Line, docs }], [], [{ atom, Line, nil }] }.
|
||||
docs_clause(_Module, _) ->
|
||||
{ clause, 0, [{ atom, 0, docs }], [], [{ atom, 0, nil }] }.
|
||||
|
||||
moduledoc_clause(Line, Module, true) ->
|
||||
Docs = 'Elixir.Module':get_attribute(Module, moduledoc),
|
||||
{ clause, Line, [{ atom, Line, moduledoc }], [], [elixir_tree_helpers:abstract_syntax({ Line, Docs })] };
|
||||
{ clause, 0, [{ atom, 0, moduledoc }], [], [elixir_tree_helpers:abstract_syntax({ Line, Docs })] };
|
||||
|
||||
moduledoc_clause(Line, _Module, _) ->
|
||||
{ clause, Line, [{ atom, Line, moduledoc }], [], [{ atom, Line, nil }] }.
|
||||
moduledoc_clause(_Line, _Module, _) ->
|
||||
{ clause, 0, [{ atom, 0, moduledoc }], [], [{ atom, 0, nil }] }.
|
||||
|
||||
else_clause(Line) ->
|
||||
Info = { call, Line, { atom, Line, module_info }, [{ var, Line, atom }] },
|
||||
{ clause, Line, [{ var, Line, atom }], [], [Info] }.
|
||||
else_clause() ->
|
||||
Info = { call, 0, { atom, 0, module_info }, [{ var, 0, atom }] },
|
||||
{ clause, 0, [{ var, 0, atom }], [], [Info] }.
|
||||
|
||||
% HELPERS
|
||||
|
||||
|
||||
@@ -3,12 +3,12 @@
|
||||
|
||||
Nonterminals
|
||||
grammar expr_list
|
||||
expr block_expr fn_expr bracket_expr call_expr bracket_at_expr max_expr
|
||||
expr paren_expr block_expr fn_expr bracket_expr call_expr bracket_at_expr max_expr
|
||||
base_expr matched_expr matched_op_expr unmatched_expr op_expr
|
||||
add_op mult_op unary_op two_op pipeline_op bin_concat_op
|
||||
match_op send_op default_op when_op pipe_op in_op inc_op stab_op range_op
|
||||
andand_op oror_op and_op or_op comp_expr_op colon_colon_op three_op at_op
|
||||
open_paren close_paren
|
||||
open_paren close_paren empty_paren
|
||||
open_bracket close_bracket
|
||||
open_curly close_curly
|
||||
open_bit close_bit
|
||||
@@ -20,13 +20,13 @@ Nonterminals
|
||||
dot_op dot_ref dot_identifier dot_op_identifier dot_do_identifier
|
||||
dot_paren_identifier dot_punctuated_identifier dot_bracket_identifier
|
||||
var list bracket_access bit_string tuple
|
||||
fn_block do_block do_eol end_eol block_eol block_item block_list
|
||||
do_block fn_eol do_eol end_eol block_eol block_item block_list
|
||||
.
|
||||
|
||||
Terminals
|
||||
identifier kw_identifier punctuated_identifier
|
||||
bracket_identifier paren_identifier do_identifier block_identifier
|
||||
fn fn_paren 'end' '__aliases__'
|
||||
fn 'end' '__aliases__'
|
||||
number signed_number atom bin_string list_string sigil
|
||||
dot_call_op comp_op op_identifier
|
||||
'not' 'and' 'or' 'xor' 'when' 'in' 'inlist' 'inbits' 'do'
|
||||
@@ -43,8 +43,8 @@ Rootsymbol grammar.
|
||||
Left 5 do.
|
||||
Right 10 '->'.
|
||||
Left 20 ','. % Solve nested call_args conflicts
|
||||
Right 30 when_op.
|
||||
Right 40 colon_colon_op.
|
||||
Right 30 colon_colon_op.
|
||||
Right 40 when_op.
|
||||
Right 50 default_op.
|
||||
Left 60 pipe_op.
|
||||
Left 70 inc_op.
|
||||
@@ -80,9 +80,12 @@ grammar -> '$empty' : [nil].
|
||||
|
||||
% Note expressions are on reverse order
|
||||
expr_list -> expr : ['$1'].
|
||||
expr_list -> open_paren ')' : [nil].
|
||||
expr_list -> empty_paren : [nil].
|
||||
expr_list -> expr_list eol expr : ['$3'|'$1'].
|
||||
|
||||
paren_expr -> empty_paren : nil.
|
||||
paren_expr -> expr : '$1'.
|
||||
|
||||
expr -> matched_expr : '$1'.
|
||||
expr -> unmatched_expr : '$1'.
|
||||
|
||||
@@ -146,10 +149,8 @@ block_expr -> dot_punctuated_identifier call_args_no_parens do_block : build_ide
|
||||
block_expr -> dot_do_identifier do_block : build_identifier('$1', '$2').
|
||||
block_expr -> dot_identifier call_args_no_parens do_block : build_identifier('$1', '$2' ++ '$3').
|
||||
|
||||
fn_expr -> fn_paren call_args_parens fn_block : build_fn('$1', '$2', '$3').
|
||||
fn_expr -> fn call_args_no_parens fn_block : build_fn('$1', '$2', '$3').
|
||||
fn_expr -> fn call_args_parens_not_one fn_block : build_fn('$1', '$2', '$3').
|
||||
fn_expr -> fn fn_block : build_fn('$1', [], '$2').
|
||||
fn_expr -> fn_eol stab_expr_list end_eol : build_fn('$1', build_stab(lists:reverse('$2'))).
|
||||
fn_expr -> fn_eol '->' grammar 'end' : build_fn('$1', { '->', ?line('$2'), [{ [], build_block('$3') }] }).
|
||||
fn_expr -> call_expr : '$1'.
|
||||
|
||||
call_expr -> dot_punctuated_identifier call_args_no_parens : build_identifier('$1', '$2').
|
||||
@@ -165,7 +166,7 @@ max_expr -> parens_call call_args_parens : build_identifier('$1', '$2').
|
||||
max_expr -> parens_call call_args_parens call_args_parens : { build_identifier('$1', '$2'), ?line('$1'), '$3' }.
|
||||
max_expr -> dot_ref : '$1'.
|
||||
max_expr -> base_expr : '$1'.
|
||||
max_expr -> open_paren expr_list close_paren : build_block('$2').
|
||||
max_expr -> open_paren stab_expr_list close_paren : build_stab(lists:reverse('$2')).
|
||||
|
||||
bracket_expr -> dot_bracket_identifier bracket_access : build_access(build_identifier('$1', nil), '$2').
|
||||
bracket_expr -> max_expr bracket_access : build_access('$1', '$2').
|
||||
@@ -192,13 +193,14 @@ base_expr -> sigil : build_sigil('$1').
|
||||
|
||||
%% Blocks
|
||||
|
||||
fn_block -> '->' grammar 'end' : build_block('$2', false).
|
||||
|
||||
do_block -> do_eol 'end' : [[{do,nil}]].
|
||||
do_block -> do_eol stab_expr_list end_eol : [[{ do, build_stab(lists:reverse('$2')) }]].
|
||||
do_block -> do_eol block_list 'end' : [[{ do, nil }|'$2']].
|
||||
do_block -> do_eol stab_expr_list eol block_list 'end' : [[{ do, build_stab(lists:reverse('$2')) }|'$4']].
|
||||
|
||||
fn_eol -> 'fn' : '$1'.
|
||||
fn_eol -> 'fn' eol : '$1'.
|
||||
|
||||
do_eol -> 'do' : '$1'.
|
||||
do_eol -> 'do' eol : '$1'.
|
||||
|
||||
@@ -230,6 +232,8 @@ open_paren -> '(' eol : '$1'.
|
||||
close_paren -> ')' : '$1'.
|
||||
close_paren -> eol ')' : '$2'.
|
||||
|
||||
empty_paren -> open_paren ')' : nil.
|
||||
|
||||
open_bracket -> '[' : '$1'.
|
||||
open_bracket -> '[' eol : '$1'.
|
||||
close_bracket -> ']' : '$1'.
|
||||
@@ -385,32 +389,32 @@ call_args_no_parens -> matched_comma_expr : lists:reverse('$1').
|
||||
call_args_no_parens -> matched_kw_base : ['$1'].
|
||||
call_args_no_parens -> matched_comma_expr ',' matched_kw_base : lists:reverse(['$3'|'$1']).
|
||||
|
||||
base_comma_expr -> expr ',' : ['$1'].
|
||||
base_comma_expr -> base_comma_expr expr ',' : ['$2'|'$1'].
|
||||
call_args_parens_not_one -> open_paren ')' : [].
|
||||
call_args_parens_not_one -> open_paren matched_kw_base close_paren : ['$2'].
|
||||
call_args_parens_not_one -> open_paren matched_expr ',' call_args_no_parens close_paren : ['$2'|'$4'].
|
||||
|
||||
comma_expr -> expr : ['$1'].
|
||||
base_comma_expr -> paren_expr ',' : ['$1'].
|
||||
base_comma_expr -> base_comma_expr paren_expr ',' : ['$2'|'$1'].
|
||||
|
||||
comma_expr -> paren_expr : ['$1'].
|
||||
comma_expr -> kw_base : ['$1'].
|
||||
comma_expr -> base_comma_expr : '$1'.
|
||||
comma_expr -> base_comma_expr expr : ['$2'|'$1'].
|
||||
comma_expr -> base_comma_expr paren_expr : ['$2'|'$1'].
|
||||
comma_expr -> base_comma_expr kw_base : ['$2'|'$1'].
|
||||
|
||||
optional_comma_expr -> expr : '$1'.
|
||||
optional_comma_expr -> expr ',' : '$1'.
|
||||
optional_comma_expr -> paren_expr : '$1'.
|
||||
optional_comma_expr -> paren_expr ',' : '$1'.
|
||||
|
||||
call_args -> comma_expr : lists:reverse('$1').
|
||||
|
||||
call_args_parens -> open_paren ')' : [].
|
||||
call_args_parens -> open_paren call_args close_paren : '$2'.
|
||||
|
||||
call_args_parens_not_one -> open_paren ')' : [].
|
||||
call_args_parens_not_one -> open_paren kw_base close_paren : ['$2'].
|
||||
call_args_parens_not_one -> open_paren expr ',' call_args close_paren : ['$2'|'$4'].
|
||||
|
||||
% KV
|
||||
|
||||
kw_eol -> kw_identifier : '$1'.
|
||||
kw_eol -> kw_identifier eol : '$1'.
|
||||
kw_expr -> kw_eol expr : { ?exprs('$1'),'$2' }.
|
||||
kw_expr -> kw_eol paren_expr : { ?exprs('$1'),'$2' }.
|
||||
|
||||
kw_comma -> kw_expr ',' : ['$1'].
|
||||
kw_comma -> kw_comma kw_expr ',' : ['$2'|'$1'].
|
||||
@@ -420,6 +424,7 @@ kw_base -> kw_comma : lists:reverse('$1').
|
||||
kw_base -> kw_comma kw_expr : lists:reverse(['$2'|'$1']).
|
||||
|
||||
matched_kw_expr -> kw_eol matched_expr : {?exprs('$1'),'$2'}.
|
||||
matched_kw_expr -> kw_eol empty_paren : {?exprs('$1'),nil}.
|
||||
matched_kw_comma -> matched_kw_expr : ['$1'].
|
||||
matched_kw_comma -> matched_kw_expr ',' matched_kw_comma : ['$1'|'$3'].
|
||||
matched_kw_base -> matched_kw_comma : '$1'.
|
||||
@@ -433,13 +438,13 @@ bracket_access -> open_bracket kw_base close_bracket : { '$2', ?line('$1') }.
|
||||
list -> open_bracket ']' : [].
|
||||
list -> open_bracket kw_base close_bracket : '$2'.
|
||||
list -> open_bracket optional_comma_expr close_bracket : ['$2'].
|
||||
list -> open_bracket expr ',' call_args close_bracket : ['$2'|'$4'].
|
||||
list -> open_bracket paren_expr ',' call_args close_bracket : ['$2'|'$4'].
|
||||
|
||||
% Tuple
|
||||
|
||||
tuple -> open_curly '}' : build_tuple('$1', []).
|
||||
tuple -> open_curly optional_comma_expr close_curly : build_tuple('$1', ['$2']).
|
||||
tuple -> open_curly expr ',' call_args close_curly : build_tuple('$1', ['$2'|'$4']).
|
||||
tuple -> open_curly paren_expr ',' call_args close_curly : build_tuple('$1', ['$2'|'$4']).
|
||||
|
||||
% Bitstrings
|
||||
|
||||
@@ -481,13 +486,10 @@ build_tuple(Marker, Args) ->
|
||||
|
||||
%% Blocks
|
||||
|
||||
build_block(Exprs) -> build_block(Exprs, true).
|
||||
|
||||
build_block([nil], _) -> { '__block__', 0, [nil] };
|
||||
build_block([{Op,_,[_]}]=Exprs, _) when ?rearrange_uop(Op) -> { '__block__', 0, Exprs };
|
||||
build_block([Expr], _) when not is_list(Expr) -> Expr;
|
||||
build_block(Exprs, true) -> { '__block__', 0, lists:reverse(Exprs) };
|
||||
build_block(Exprs, false) -> { '__block__', 0, Exprs }.
|
||||
build_block([nil]) -> { '__block__', 0, [nil] };
|
||||
build_block([{Op,_,[_]}]=Exprs) when ?rearrange_uop(Op) -> { '__block__', 0, Exprs };
|
||||
build_block([Expr]) when not is_list(Expr) -> Expr;
|
||||
build_block(Exprs) -> { '__block__', 0, Exprs }.
|
||||
|
||||
%% Dots
|
||||
|
||||
@@ -509,7 +511,7 @@ build_identifier({ '.', Line, _ } = Dot, Args) ->
|
||||
end,
|
||||
{ Dot, Line, FArgs };
|
||||
|
||||
build_identifier({ Keyword, Line }, Args) when Keyword == fn; Keyword == fn_paren ->
|
||||
build_identifier({ Keyword, Line }, Args) when Keyword == fn ->
|
||||
{ fn, Line, Args };
|
||||
|
||||
build_identifier({ op_identifier, Line, Identifier }, Args) ->
|
||||
@@ -527,10 +529,8 @@ extract_identifier(Other) -> Other.
|
||||
|
||||
%% Fn
|
||||
|
||||
build_fn(Op, Args, Expr) ->
|
||||
Line = ?line(Op),
|
||||
Stab = [{do, { '->', Line, [{ Args, Expr }] }}],
|
||||
{ fn, Line, [Stab] }.
|
||||
build_fn(Op, Stab) ->
|
||||
{ fn, ?line(Op), [[{ do, Stab }]] }.
|
||||
|
||||
%% Access
|
||||
|
||||
@@ -558,15 +558,15 @@ build_stab([{ '->', Line, [Left, Right] }|T]) ->
|
||||
{ '->', Line, build_stab(T, Left, [Right], []) };
|
||||
|
||||
build_stab(Else) ->
|
||||
build_block(Else, false).
|
||||
build_block(Else).
|
||||
|
||||
build_stab([{ '->', _, [Left, Right] }|T], Marker, Temp, Acc) ->
|
||||
H = { Marker, build_block(Temp) },
|
||||
H = { Marker, build_block(lists:reverse(Temp)) },
|
||||
build_stab(T, Left, [Right], [H|Acc]);
|
||||
|
||||
build_stab([H|T], Marker, Temp, Acc) ->
|
||||
build_stab(T, Marker, [H|Temp], Acc);
|
||||
|
||||
build_stab([], Marker, Temp, Acc) ->
|
||||
H = { Marker, build_block(Temp) },
|
||||
H = { Marker, build_block(lists:reverse(Temp)) },
|
||||
lists:reverse([H|Acc]).
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
%% Convenience functions used to manipulate scope
|
||||
%% and its variables.
|
||||
-module(elixir_scope).
|
||||
@@ -6,7 +5,8 @@
|
||||
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, deserialize/2,
|
||||
serialize/1, deserialize/1,
|
||||
serialize_with_vars/2, deserialize_with_vars/2,
|
||||
to_erl_env/1, to_ex_env/1, filename/1,
|
||||
umergev/2, umergec/2, merge_clause_vars/2
|
||||
]).
|
||||
@@ -23,20 +23,20 @@ translate_var(Line, Name, Kind, S) ->
|
||||
case S#elixir_scope.context of
|
||||
assign ->
|
||||
TempVars = S#elixir_scope.temp_vars,
|
||||
case { orddict:is_key(Name, Vars), orddict:find(Name, TempVars) } of
|
||||
case { orddict:is_key({ Name, Kind }, Vars), orddict:find(Name, TempVars) } of
|
||||
{ true, { ok, Kind } } ->
|
||||
{ {var, Line, orddict:fetch(Name, Vars) }, S };
|
||||
{ { var, Line, orddict:fetch({ Name, Kind }, Vars) }, S };
|
||||
{ Else, _ } ->
|
||||
{ NewVar, NS } = if
|
||||
Kind == quoted -> build_erl_var(Line, S);
|
||||
Else -> build_erl_var(Line, Name, S);
|
||||
S#elixir_scope.noname -> build_erl_var(Line, Name, S);
|
||||
true -> { {var, Line, Name}, S }
|
||||
true -> { { var, Line, Name }, S }
|
||||
end,
|
||||
RealName = element(3, NewVar),
|
||||
ClauseVars = S#elixir_scope.clause_vars,
|
||||
{ NewVar, NS#elixir_scope{
|
||||
vars=orddict:store(Name, RealName, Vars),
|
||||
vars=orddict:store({ Name, Kind }, RealName, Vars),
|
||||
temp_vars=orddict:store(Name, Kind, TempVars),
|
||||
clause_vars=if
|
||||
ClauseVars == nil -> nil;
|
||||
@@ -45,9 +45,9 @@ translate_var(Line, Name, Kind, S) ->
|
||||
} }
|
||||
end;
|
||||
_ ->
|
||||
case orddict:is_key(Name, Vars) of
|
||||
false -> elixir_translator:translate_each({Name, Line, []}, S);
|
||||
true -> { {var, Line, orddict:fetch(Name, Vars) }, S }
|
||||
case orddict:find({ Name, Kind }, Vars) of
|
||||
{ ok, VarName } -> { { var, Line, VarName }, S };
|
||||
error -> elixir_translator:translate_each({ Name, Line, [] }, S)
|
||||
end
|
||||
end
|
||||
end.
|
||||
@@ -98,11 +98,22 @@ serialize(S) ->
|
||||
S#elixir_scope.requires, S#elixir_scope.macros, S#elixir_scope.aliases, S#elixir_scope.scheduled }
|
||||
).
|
||||
|
||||
serialize_with_vars(Line, S) ->
|
||||
{ Vars, _ } = orddict:fold(fun({ Key, Kind }, Value, { Acc, Counter }) ->
|
||||
{ { cons, Line, { tuple, Line, [
|
||||
{ atom, Line, Key },
|
||||
{ atom, Line, Kind },
|
||||
{ atom, Line, ?ELIXIR_ATOM_CONCAT(["_@", Counter]) },
|
||||
{ var, Line, Value }
|
||||
] }, Acc }, Counter + 1 }
|
||||
end, { { nil, Line }, 0 }, S#elixir_scope.vars),
|
||||
{ serialize(S), Vars }.
|
||||
|
||||
% Fill in the scope with the variables serialization set in serialize_scope.
|
||||
|
||||
deserialize(Tuple) -> deserialize(Tuple, []).
|
||||
deserialize(Tuple) -> deserialize_with_vars(Tuple, []).
|
||||
|
||||
deserialize({ File, Functions, CheckClauses, Requires, Macros, Aliases, Scheduled }, Vars) ->
|
||||
deserialize_with_vars({ File, Functions, CheckClauses, Requires, Macros, Aliases, Scheduled }, Vars) ->
|
||||
#elixir_scope{
|
||||
file=File,
|
||||
functions=Functions,
|
||||
@@ -121,13 +132,10 @@ deserialize({ File, Functions, CheckClauses, Requires, Macros, Aliases, Schedule
|
||||
umergev(S1, S2) ->
|
||||
V1 = S1#elixir_scope.vars,
|
||||
V2 = S2#elixir_scope.vars,
|
||||
Q1 = S1#elixir_scope.quote_vars,
|
||||
Q2 = S2#elixir_scope.quote_vars,
|
||||
C1 = S1#elixir_scope.clause_vars,
|
||||
C2 = S2#elixir_scope.clause_vars,
|
||||
S2#elixir_scope{
|
||||
vars=orddict:merge(fun var_merger/3, V1, V2),
|
||||
quote_vars=orddict:merge(fun var_merger/3, Q1, Q2),
|
||||
clause_vars=merge_clause_vars(C1, C2)
|
||||
}.
|
||||
|
||||
@@ -148,13 +156,10 @@ umergec(S1, S2) ->
|
||||
merge_clause_vars(nil, _C2) -> nil;
|
||||
merge_clause_vars(_C1, nil) -> nil;
|
||||
merge_clause_vars(C1, C2) ->
|
||||
orddict:merge(fun clause_var_merger/3, C1, C2).
|
||||
orddict:merge(fun var_merger/3, C1, C2).
|
||||
|
||||
clause_var_merger({ Var, _ }, K1, K2) ->
|
||||
var_merger(Var, K1, K2).
|
||||
|
||||
var_merger(Var, Var, K2) -> K2;
|
||||
var_merger(Var, K1, Var) -> K1;
|
||||
var_merger({ Var, _ }, Var, K2) -> K2;
|
||||
var_merger({ Var, _ }, K1, Var) -> K1;
|
||||
var_merger(_Var, K1, K2) ->
|
||||
V1 = var_number(atom_to_list(K1), []),
|
||||
V2 = var_number(atom_to_list(K2), []),
|
||||
|
||||
@@ -269,10 +269,10 @@ tokenize([$:,T|Rest], Line, Scope, Tokens) when ?comp1(T); ?op1(T); T == $&; T =
|
||||
% End of line
|
||||
|
||||
tokenize(";" ++ Rest, Line, Scope, []) ->
|
||||
tokenize(Rest, Line, Scope, eol(Line, $;, []));
|
||||
tokenize(Rest, Line, Scope, eol(Line, ';', []));
|
||||
|
||||
tokenize(";" ++ Rest, Line, Scope, [Top|Tokens]) when element(1, Top) /= eol ->
|
||||
tokenize(Rest, Line, Scope, eol(Line, $;, [Top|Tokens]));
|
||||
tokenize(Rest, Line, Scope, eol(Line, ';', [Top|Tokens]));
|
||||
|
||||
tokenize("\\\n" ++ Rest, Line, Scope, Tokens) ->
|
||||
tokenize(Rest, Line + 1, Scope, Tokens);
|
||||
@@ -281,10 +281,10 @@ tokenize("\\\r\n" ++ Rest, Line, Scope, Tokens) ->
|
||||
tokenize(Rest, Line + 1, Scope, Tokens);
|
||||
|
||||
tokenize("\n" ++ Rest, Line, Scope, Tokens) ->
|
||||
tokenize(Rest, Line + 1, Scope, eol(Line, $\n, Tokens));
|
||||
tokenize(Rest, Line + 1, Scope, eol(Line, newline, Tokens));
|
||||
|
||||
tokenize("\r\n" ++ Rest, Line, Scope, Tokens) ->
|
||||
tokenize(Rest, Line + 1, Scope, eol(Line, $\n, Tokens));
|
||||
tokenize(Rest, Line + 1, Scope, eol(Line, newline, Tokens));
|
||||
|
||||
% Stand-alone tokens
|
||||
|
||||
@@ -702,7 +702,7 @@ next_is_block([$d,$o|_]) ->
|
||||
next_is_block(_) ->
|
||||
false.
|
||||
|
||||
add_token_with_nl(Left, [{eol,_,$\n}|T]) -> [Left|T];
|
||||
add_token_with_nl(Left, [{eol,_,newline}|T]) -> [Left|T];
|
||||
add_token_with_nl(Left, T) -> [Left|T].
|
||||
|
||||
% Error handling
|
||||
@@ -729,7 +729,7 @@ handle_terminator(Token, #scope{terminators=Terminators} = Scope) ->
|
||||
New -> Scope#scope{terminators=New}
|
||||
end.
|
||||
|
||||
check_terminator({ S, Line }, Terminators) when S == 'fn'; S == 'fn_paren' ->
|
||||
check_terminator({ S, Line }, Terminators) when S == 'fn' ->
|
||||
[{ fn, Line }|Terminators];
|
||||
|
||||
check_terminator({ S, _ } = New, Terminators) when
|
||||
@@ -770,9 +770,6 @@ terminator('{') -> '}';
|
||||
terminator('<<') -> '>>'.
|
||||
|
||||
% Keywords check
|
||||
handle_keyword(Line, paren_identifier, fn, Tokens) ->
|
||||
[{ 'fn_paren', Line }|Tokens];
|
||||
|
||||
handle_keyword(Line, Identifier, Atom, [{ '.', _ }|_] = Tokens) ->
|
||||
[{ Identifier, Line, Atom }|Tokens];
|
||||
|
||||
|
||||
@@ -77,9 +77,9 @@ translate_each({ '__op__', Line, [Op, Left, Right] }, S) when is_atom(Op) ->
|
||||
{ { op, Line, convert_op(Op), TLeft, TRight }, NS };
|
||||
|
||||
translate_each({ '__ambiguousop__', Line, [Var, H|T] }, S) ->
|
||||
{ Name, _, _ } = Var,
|
||||
{ Name, _, Kind } = Var,
|
||||
|
||||
case orddict:find(Name, S#elixir_scope.vars) of
|
||||
case orddict:find({ Name, Kind }, S#elixir_scope.vars) of
|
||||
error -> translate_each({ Name, Line, [H|T] }, S);
|
||||
_ ->
|
||||
case T of
|
||||
@@ -103,7 +103,7 @@ translate_each({ alias, Line, [Ref, KV] }, S) ->
|
||||
{ atom, _, Old } ->
|
||||
{ New, SF } = case lists:keyfind(as, 1, KV) of
|
||||
Opt when Opt == { as, true }; Opt == false ->
|
||||
{ elixir_aliases:last(Old), SR };
|
||||
{ elixir_aliases:last(Old), SR };
|
||||
{ as, false } ->
|
||||
{ Old, SR };
|
||||
{ as, Other } ->
|
||||
@@ -284,7 +284,7 @@ translate_each({ quote, GivenLine, [T] }, S) when is_list(T) ->
|
||||
|
||||
translate_each({ quote, GivenLine, [_] }, S) ->
|
||||
syntax_error(GivenLine, S#elixir_scope.file, "invalid args for quote");
|
||||
|
||||
|
||||
%% Functions
|
||||
|
||||
translate_each({ fn, Line, [[{do, { '->', _, Pairs }}]] }, S) ->
|
||||
@@ -330,41 +330,23 @@ translate_each({ 'super?', Line, [] }, S) ->
|
||||
|
||||
%% Variables
|
||||
|
||||
translate_each({ '^', Line, [ { Name, _, Args } ] }, S) ->
|
||||
Dict = case Args of
|
||||
nil -> S#elixir_scope.vars;
|
||||
quoted -> S#elixir_scope.quote_vars;
|
||||
_ ->
|
||||
syntax_error(Line, S#elixir_scope.file, "cannot use ^ with expression at ^~s, ^ must be used only with variables", [Name])
|
||||
end,
|
||||
translate_each({ '^', Line, [ { Name, _, Kind } ] }, S) when is_list(Kind) ->
|
||||
syntax_error(Line, S#elixir_scope.file, "cannot use ^ with expression at ^~s, ^ must be used only with variables", [Name]);
|
||||
|
||||
Result = case S#elixir_scope.context of
|
||||
assign ->
|
||||
case orddict:find(Name, Dict) of
|
||||
error -> "unbound variable ^~s";
|
||||
{ ok, Value } -> { {var, Line, Value}, S }
|
||||
end;
|
||||
_ -> "cannot access variable ^~s outside of assignment"
|
||||
end,
|
||||
|
||||
case is_list(Result) of
|
||||
true ->
|
||||
syntax_error(Line, S#elixir_scope.file, Result, [Name]);
|
||||
false ->
|
||||
Result
|
||||
translate_each({ '^', Line, [ { Name, _, Kind } ] }, #elixir_scope{context=assign} = S) when is_atom(Kind) ->
|
||||
case orddict:find({ Name, Kind }, S#elixir_scope.vars) of
|
||||
{ ok, Value } ->
|
||||
{ { var, Line, Value }, S };
|
||||
error ->
|
||||
syntax_error(Line, S#elixir_scope.file, "unbound variable ^~s", [Name])
|
||||
end;
|
||||
|
||||
translate_each({ Name, Line, quoted }, S) when is_atom(Name) ->
|
||||
NewS = S#elixir_scope{vars=S#elixir_scope.quote_vars,noname=true},
|
||||
{ TVar, VS } = elixir_scope:translate_var(Line, Name, quoted, NewS),
|
||||
{ TVar, VS#elixir_scope{
|
||||
quote_vars=VS#elixir_scope.vars,
|
||||
noname=S#elixir_scope.noname,
|
||||
vars=S#elixir_scope.vars
|
||||
} };
|
||||
translate_each({ '^', Line, [ { Name, _, Kind } ] }, S) when is_atom(Kind) ->
|
||||
syntax_error(Line, S#elixir_scope.file,
|
||||
"cannot access variable ^~s outside of assignment", [Name]);
|
||||
|
||||
translate_each({ Name, Line, nil }, S) when is_atom(Name) ->
|
||||
elixir_scope:translate_var(Line, Name, nil, S);
|
||||
translate_each({ Name, Line, Kind }, S) when is_atom(Name), (Kind == nil orelse Kind == quoted) ->
|
||||
elixir_scope:translate_var(Line, Name, Kind, S);
|
||||
|
||||
%% Local calls
|
||||
|
||||
@@ -462,7 +444,14 @@ translate_fn(Line, Clauses, S) ->
|
||||
end,
|
||||
|
||||
{ TClauses, NS } = lists:mapfoldl(Transformer, S, Clauses),
|
||||
{ { 'fun', Line, {clauses, TClauses} }, umergec(S, NS) }.
|
||||
Arities = [length(Args) || { clause, _Line, Args, _Guards, _Exprs } <- TClauses],
|
||||
|
||||
case length(lists:usort(Arities)) of
|
||||
1 ->
|
||||
{ { 'fun', Line, { clauses, TClauses } }, umergec(S, NS) };
|
||||
_ ->
|
||||
syntax_error(Line, S#elixir_scope.file, "cannot mix clauses with different arities in function definition")
|
||||
end.
|
||||
|
||||
translate_local(Line, Name, Args, #elixir_scope{local=nil} = S) ->
|
||||
{ TArgs, NS } = translate_args(Args, S),
|
||||
@@ -511,11 +500,9 @@ translate_apply(Line, TLeft, TRight, Args, S, SL, SR) ->
|
||||
Optimize = case (Args == []) orelse lists:last(Args) of
|
||||
{ '|', _, _ } -> false;
|
||||
_ ->
|
||||
case { TLeft, TRight } of
|
||||
{ { Kind, _, _ }, { atom, _, _ } } when Kind == var; Kind == tuple; Kind == atom ->
|
||||
true;
|
||||
_ ->
|
||||
false
|
||||
case TRight of
|
||||
{ atom, _, _ } -> true;
|
||||
_ -> false
|
||||
end
|
||||
end,
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
-module(elixir_try).
|
||||
-export([clauses/3]).
|
||||
-export([clauses/3, format_error/1]).
|
||||
-import(elixir_scope, [umergec/2]).
|
||||
-include("elixir.hrl").
|
||||
-compile({parse_transform, elixir_transform}).
|
||||
@@ -86,6 +86,16 @@ normalize_rescue(_, { in, Line, [Left, Right] }, S) ->
|
||||
end
|
||||
end;
|
||||
|
||||
normalize_rescue(_, { '=', Line, [{ '__aliases__', _, _ } = Alias, { Name, _, Atom } = Var] }, S)
|
||||
when is_atom(Name) and is_atom(Atom) ->
|
||||
elixir_errors:handle_file_warning(S#elixir_scope.file, { Line, ?MODULE, { rescue_no_match, Var, Alias } }),
|
||||
false;
|
||||
|
||||
normalize_rescue(_, { '=', Line, [{ Name, _, Atom } = Var, { '__aliases__', _, _ } = Alias] }, S)
|
||||
when is_atom(Name) and is_atom(Atom) ->
|
||||
elixir_errors:handle_file_warning(S#elixir_scope.file, { Line, ?MODULE, { rescue_no_match, Var, Alias } }),
|
||||
false;
|
||||
|
||||
normalize_rescue(Line, Condition, S) ->
|
||||
case elixir_translator:translate_each(Condition, S#elixir_scope{context=assign}) of
|
||||
{ { atom, _, Atom }, _ } ->
|
||||
@@ -94,7 +104,6 @@ normalize_rescue(Line, Condition, S) ->
|
||||
false
|
||||
end.
|
||||
|
||||
|
||||
%% Convert rescue clauses into guards.
|
||||
rescue_guards(_, Var, nil, _) -> { Var, false };
|
||||
|
||||
@@ -254,6 +263,12 @@ validate_rescue_access(_, _, _) -> [].
|
||||
|
||||
%% Helpers
|
||||
|
||||
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] ?",
|
||||
io_lib:format(Message, [AliasBinary, VarBinary, VarBinary, AliasBinary]).
|
||||
|
||||
is_var({ Name, _, Atom }) when is_atom(Name), is_atom(Atom) -> true;
|
||||
is_var(_) -> false.
|
||||
|
||||
|
||||
@@ -7,14 +7,14 @@ defmodule BehaviourTest do
|
||||
use Behaviour
|
||||
|
||||
@doc "Foo"
|
||||
defcallback foo(atom, binary), do: binary
|
||||
defcallback foo(atom, binary) :: binary
|
||||
|
||||
@doc "Bar"
|
||||
defcallback bar(External.hello, my_var :: binary), do: binary
|
||||
defcallback bar(External.hello, my_var :: binary) :: binary
|
||||
end
|
||||
|
||||
test :docs do
|
||||
docs = Enum.qsort(Sample.__behaviour__(:docs))
|
||||
docs = Enum.sort(Sample.__behaviour__(:docs))
|
||||
assert docs == [
|
||||
{{:bar,2},13,"Bar"},
|
||||
{{:foo,2},10,"Foo"}
|
||||
@@ -33,14 +33,14 @@ defmodule BehaviourTest do
|
||||
assert_raise ArgumentError, fn ->
|
||||
defmodule WithDefault do
|
||||
use Behaviour
|
||||
defcallback hello(num // 0 :: integer), do: integer
|
||||
defcallback hello(num // 0 :: integer) :: integer
|
||||
end
|
||||
end
|
||||
|
||||
assert_raise ArgumentError, fn ->
|
||||
defmodule WithDefault do
|
||||
use Behaviour
|
||||
defcallback hello(num :: integer // 0), do: integer
|
||||
defcallback hello(num :: integer // 0) :: integer
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -52,6 +52,14 @@ bar
|
||||
assert "\18" == <<1,?8>>
|
||||
end
|
||||
|
||||
test :hex do
|
||||
assert "\xE9" == "é"
|
||||
assert "\xFF" == "ÿ"
|
||||
assert "\x{A}"== "\n"
|
||||
assert "\x{E9}"== "é"
|
||||
assert "\x{10FFFF}" == <<244,143,191,191>>
|
||||
end
|
||||
|
||||
test :match do
|
||||
assert is_match?("ab", ?a)
|
||||
assert not is_match?("cd", ?a)
|
||||
|
||||
@@ -35,21 +35,21 @@ defmodule CodeTest do
|
||||
|
||||
test :require do
|
||||
Code.require_file fixture_path("code_sample.exs")
|
||||
assert fixture_path("code_sample.exs") in Code.loaded_files
|
||||
assert fixture_path("code_sample.exs") inlist Code.loaded_files
|
||||
assert Code.require_file(fixture_path("code_sample.exs")) == nil
|
||||
|
||||
Code.unload_files [fixture_path("code_sample.exs")]
|
||||
refute fixture_path("code_sample.exs") in Code.loaded_files
|
||||
refute fixture_path("code_sample.exs") inlist Code.loaded_files
|
||||
assert Code.require_file(fixture_path("code_sample.exs")) != nil
|
||||
end
|
||||
|
||||
test :path_manipulation do
|
||||
path = File.expand_path("../binary", __FILE__)
|
||||
Code.prepend_path path
|
||||
assert binary_to_list(path) in :code.get_path
|
||||
assert binary_to_list(path) inlist :code.get_path
|
||||
|
||||
Code.delete_path path
|
||||
refute binary_to_list(path) in :code.get_path
|
||||
refute binary_to_list(path) inlist :code.get_path
|
||||
end
|
||||
|
||||
test :file do
|
||||
|
||||
@@ -15,16 +15,16 @@ defmodule DictTest.Common do
|
||||
dict = new_dict [{"first_key", 1}, {"second_key", 2}]
|
||||
assert 2 == Dict.size dict
|
||||
|
||||
assert ["first_key", "second_key"] == List.sort Dict.keys dict
|
||||
assert [1, 2] == List.sort Dict.values dict
|
||||
assert ["first_key", "second_key"] == Enum.sort Dict.keys dict
|
||||
assert [1, 2] == Enum.sort Dict.values dict
|
||||
end
|
||||
|
||||
test :new_pairs_with_transform do
|
||||
dict = new_dict [{1}, {2}, {3}], fn {x} -> { <<x + 64>>, x } end
|
||||
assert 3 == Dict.size dict
|
||||
|
||||
assert ["A", "B", "C"] == List.sort Dict.keys dict
|
||||
assert [1, 2, 3] == List.sort Dict.values dict
|
||||
assert ["A", "B", "C"] == Enum.sort Dict.keys dict
|
||||
assert [1, 2, 3] == Enum.sort Dict.values dict
|
||||
end
|
||||
|
||||
test :get do
|
||||
@@ -49,12 +49,12 @@ defmodule DictTest.Common do
|
||||
end
|
||||
|
||||
test :keys do
|
||||
assert ["first_key", "second_key"] == List.sort Dict.keys new_dict
|
||||
assert ["first_key", "second_key"] == Enum.sort Dict.keys new_dict
|
||||
assert [] == Dict.keys empty_dict
|
||||
end
|
||||
|
||||
test :values do
|
||||
assert [1, 2] ==List.sort Dict.values(new_dict)
|
||||
assert [1, 2] ==Enum.sort Dict.values(new_dict)
|
||||
assert [] == Dict.values empty_dict
|
||||
end
|
||||
|
||||
|
||||
@@ -60,6 +60,7 @@ defmodule EnumTest.List do
|
||||
assert Enum.drop([1,2,3], 2) == [3]
|
||||
assert Enum.drop([1,2,3], 3) == []
|
||||
assert Enum.drop([1,2,3], 4) == []
|
||||
assert Enum.drop([1,2,3], -1) == [3]
|
||||
assert Enum.drop([], 3) == []
|
||||
end
|
||||
|
||||
@@ -173,8 +174,9 @@ defmodule EnumTest.List do
|
||||
assert Enum.reverse([1,2,3]) == [3,2,1]
|
||||
end
|
||||
|
||||
test :qsort do
|
||||
assert Enum.qsort([5,3,2,4,1]) == [1,2,3,4,5]
|
||||
test :sort do
|
||||
assert Enum.sort([5,3,2,4,1]) == [1,2,3,4,5]
|
||||
assert Enum.sort([5,3,2,4,1], &1 > &2) == [5,4,3,2,1]
|
||||
end
|
||||
|
||||
test :split do
|
||||
@@ -205,6 +207,7 @@ defmodule EnumTest.List do
|
||||
assert Enum.take([1,2,3], 2) == [1,2]
|
||||
assert Enum.take([1,2,3], 3) == [1,2,3]
|
||||
assert Enum.take([1,2,3], 4) == [1,2,3]
|
||||
assert Enum.take([1,2,3], -1) == [1,2]
|
||||
assert Enum.take([], 3) == []
|
||||
end
|
||||
|
||||
@@ -215,6 +218,10 @@ defmodule EnumTest.List do
|
||||
assert Enum.take_while([], fn(_) -> true end) == []
|
||||
end
|
||||
|
||||
test :uniq do
|
||||
assert Enum.uniq([1,2,3,2,1]) == [1,2,3]
|
||||
end
|
||||
|
||||
test :zip do
|
||||
assert Enum.zip([:a, :b], [1, 2]) == [{:a, 1}, {:b, 2}]
|
||||
assert Enum.zip([:a, :b], [1, 2, 3, 4]) == [{:a, 1}, {:b, 2}]
|
||||
@@ -359,9 +366,9 @@ defmodule EnumTest.Dict.Common do
|
||||
assert { all, [] } == Enum.partition(dict, fn({_k, v}) -> v < 10 end)
|
||||
end
|
||||
|
||||
test :qsort do
|
||||
test :sort do
|
||||
dict = unquote(module).new [{:b,2},{:c,3},{:a,1},{:d,4}]
|
||||
assert Enum.qsort(dict) == [a: 1, b: 2, c: 3, d: 4]
|
||||
assert Enum.sort(dict) == [a: 1, b: 2, c: 3, d: 4]
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -458,10 +465,10 @@ defmodule EnumTest.Range do
|
||||
assert_raise Enum.OutOfBoundsError, fn ->
|
||||
assert Enum.at!(2..6, 8)
|
||||
end
|
||||
|
||||
assert_raise Enum.OutOfBoundsError, fn ->
|
||||
assert Enum.at!(-2..-6, 8)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
test :count do
|
||||
@@ -642,6 +649,16 @@ defmodule EnumTest.Range do
|
||||
assert Enum.partition(range, fn(x) -> rem(x, 2) == 0 end) == { [2], [1,3] }
|
||||
end
|
||||
|
||||
test :sort do
|
||||
assert Enum.sort(Range.new(first: 3, last: 1)) == [1,2,3]
|
||||
assert Enum.sort(Range.new(first: 2, last: 1)) == [1,2]
|
||||
assert Enum.sort(Range.new(first: 1, last: 1)) == [1]
|
||||
|
||||
assert Enum.sort(Range.new(first: 3, last: 1), &1 > &2) == [3,2,1]
|
||||
assert Enum.sort(Range.new(first: 2, last: 1), &1 > &2) == [2,1]
|
||||
assert Enum.sort(Range.new(first: 1, last: 1), &1 > &2) == [1]
|
||||
end
|
||||
|
||||
test :split do
|
||||
range = Range.new(first: 1, last: 3)
|
||||
assert Enum.split(range, 0) == { [], [1,2,3] }
|
||||
@@ -690,6 +707,10 @@ defmodule EnumTest.Range do
|
||||
assert Enum.take_while([], fn(_) -> true end) == []
|
||||
end
|
||||
|
||||
test :uniq do
|
||||
assert Enum.uniq(1..3) == [1,2,3]
|
||||
end
|
||||
|
||||
test :zip do
|
||||
assert Enum.zip([:a, :b], 1..2) == [{:a, 1}, {:b, 2}]
|
||||
assert Enum.zip([:a, :b], 1..4) == [{:a, 1}, {:b, 2}]
|
||||
|
||||
@@ -8,6 +8,13 @@ defmodule Kernel.ExceptionTest do
|
||||
refute is_exception({ :foo, :bar })
|
||||
end
|
||||
|
||||
test :format_environment do
|
||||
env = __ENV__.file("foo").line(12)
|
||||
assert Exception.env_stacktrace(env) == " foo:12: Kernel.ExceptionTest.test_format_environment/0\n"
|
||||
env = env.function(nil)
|
||||
assert Exception.env_stacktrace(env) == " foo:12: Kernel.ExceptionTest (body)\n"
|
||||
end
|
||||
|
||||
test :format_stacktrace_with_no_file_or_line do
|
||||
assert Exception.format_stacktrace({Foo, :bar, [1, 2, 3], []}) == "Foo.bar(1, 2, 3)"
|
||||
assert Exception.format_stacktrace({Foo, :bar, [], []}) == "Foo.bar()"
|
||||
|
||||
@@ -189,9 +189,9 @@ defmodule FileTest do
|
||||
|
||||
{ :ok, files } = File.cp_r(src, dest)
|
||||
assert length(files) == 8
|
||||
assert tmp_path("tmp/cp_r/a") in files
|
||||
assert tmp_path("tmp/cp_r/c") in files
|
||||
assert tmp_path("tmp/cp_r/a/1.txt") in files
|
||||
assert tmp_path("tmp/cp_r/a") inlist files
|
||||
assert tmp_path("tmp/cp_r/c") inlist files
|
||||
assert tmp_path("tmp/cp_r/a/1.txt") inlist files
|
||||
|
||||
assert { :ok, 'certainly/invalid' } = :file.read_link(tmp_path("tmp/cp_r/c"))
|
||||
|
||||
@@ -878,8 +878,8 @@ defmodule FileTest do
|
||||
|
||||
{ :ok, files } = File.rm_rf(fixture)
|
||||
assert length(files) == 8
|
||||
assert fixture in files
|
||||
assert tmp_path("tmp/a/1.txt") in files
|
||||
assert fixture inlist files
|
||||
assert tmp_path("tmp/a/1.txt") inlist files
|
||||
|
||||
refute File.exists?(tmp_path("tmp/a/1.txt"))
|
||||
refute File.exists?(tmp_path("tmp/a/a/2.txt"))
|
||||
@@ -912,8 +912,8 @@ defmodule FileTest do
|
||||
|
||||
{ :ok, files } = File.rm_rf(fixture)
|
||||
assert length(files) == 8
|
||||
assert fixture in files
|
||||
assert (tmp_path("tmp/a/1.txt") /> to_char_list) in files
|
||||
assert fixture inlist files
|
||||
assert (tmp_path("tmp/a/1.txt") /> to_char_list) inlist files
|
||||
|
||||
refute File.exists?(tmp_path("tmp/a/1.txt"))
|
||||
refute File.exists?(tmp_path("tmp/a/a/2.txt"))
|
||||
@@ -948,8 +948,8 @@ defmodule FileTest do
|
||||
|
||||
files = File.rm_rf!(fixture)
|
||||
assert length(files) == 8
|
||||
assert fixture in files
|
||||
assert tmp_path("tmp/a/1.txt") in files
|
||||
assert fixture inlist files
|
||||
assert tmp_path("tmp/a/1.txt") inlist files
|
||||
|
||||
refute File.exists?(tmp_path("tmp/a/1.txt"))
|
||||
refute File.exists?(tmp_path("tmp/a/a/2.txt"))
|
||||
@@ -992,7 +992,7 @@ defmodule FileTest do
|
||||
iterator = File.iterator(src)
|
||||
File.open dest, [:write], fn(target) ->
|
||||
Enum.each iterator, fn(line) ->
|
||||
IO.puts target, Regex.replace(%r/"/, line, "'")
|
||||
IO.write target, Regex.replace(%r/"/, line, "'")
|
||||
end
|
||||
end
|
||||
assert File.read(dest) == { :ok, "FOO\n" }
|
||||
@@ -1009,7 +1009,7 @@ defmodule FileTest do
|
||||
{ :ok, iterator } = File.iterator(src)
|
||||
File.open dest, [:write], fn(target) ->
|
||||
Enum.each iterator, fn(line) ->
|
||||
IO.puts target, Regex.replace(%r/"/, line, "'")
|
||||
IO.write target, Regex.replace(%r/"/, line, "'")
|
||||
end
|
||||
end
|
||||
assert File.read(dest) == { :ok, "FOO\n" }
|
||||
@@ -1026,7 +1026,58 @@ defmodule FileTest do
|
||||
iterator = File.iterator!(src)
|
||||
File.open dest, [:write], fn(target) ->
|
||||
Enum.each iterator, fn(line) ->
|
||||
IO.puts target, Regex.replace(%r/"/, line, "'")
|
||||
IO.write target, Regex.replace(%r/"/, line, "'")
|
||||
end
|
||||
end
|
||||
assert File.read(dest) == { :ok, "FOO\n" }
|
||||
after
|
||||
File.rm(dest)
|
||||
end
|
||||
end
|
||||
|
||||
test :biniterator do
|
||||
src = File.open! fixture_path("foo.txt")
|
||||
dest = tmp_path("tmp_test.txt")
|
||||
|
||||
try do
|
||||
iterator = File.biniterator(src)
|
||||
File.open dest, [:write], fn(target) ->
|
||||
Enum.each iterator, fn(line) ->
|
||||
IO.write target, Regex.replace(%r/"/, line, "'")
|
||||
end
|
||||
end
|
||||
assert File.read(dest) == { :ok, "FOO\n" }
|
||||
after
|
||||
File.rm(dest)
|
||||
end
|
||||
end
|
||||
|
||||
test :biniterator_with_path do
|
||||
src = fixture_path("foo.txt")
|
||||
dest = tmp_path("tmp_test.txt")
|
||||
|
||||
try do
|
||||
{ :ok, iterator } = File.biniterator(src)
|
||||
File.open dest, [:write], fn(target) ->
|
||||
Enum.each iterator, fn(line) ->
|
||||
IO.write target, Regex.replace(%r/"/, line, "'")
|
||||
end
|
||||
end
|
||||
assert File.read(dest) == { :ok, "FOO\n" }
|
||||
after
|
||||
File.rm(dest)
|
||||
end
|
||||
end
|
||||
|
||||
test :biniterator! do
|
||||
src = fixture_path("foo.txt")
|
||||
dest = tmp_path("tmp_test.txt")
|
||||
|
||||
try do
|
||||
iterator = File.biniterator!(src)
|
||||
File.open dest, [:write], fn(target) ->
|
||||
Enum.each iterator, fn(line) ->
|
||||
IO.write target, Regex.replace(%r/"/, line, "'")
|
||||
end
|
||||
end
|
||||
assert File.read(dest) == { :ok, "FOO\n" }
|
||||
|
||||
@@ -3,6 +3,10 @@ Code.require_file "../../test_helper.exs", __FILE__
|
||||
defmodule Kernel.CaseTest do
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
test :inline_case do
|
||||
assert (case 1, do: (1 -> :ok; 2 -> :wrong)) == :ok
|
||||
end
|
||||
|
||||
test :nested_case do
|
||||
assert get_case == 2
|
||||
end
|
||||
@@ -31,8 +35,8 @@ defmodule Kernel.CaseTest do
|
||||
test :in_operator_outside_case do
|
||||
x = 1
|
||||
y = 4
|
||||
assert x in [1,2,3]
|
||||
assert not y in [1, 2, 3]
|
||||
assert x in [1,2,3], "in assertion"
|
||||
assert not y in [1, 2, 3], "not in assertion"
|
||||
end
|
||||
|
||||
test :in_operator_in_function_definition do
|
||||
|
||||
@@ -22,18 +22,12 @@ defmodule Kernel.CLI.OptionParsingTest do
|
||||
{ path, _ } = Code.eval list, []
|
||||
|
||||
# pa
|
||||
assert File.expand_path('ebin', root) in path
|
||||
assert File.expand_path('lib', root) in path
|
||||
assert File.expand_path('src', root) in path
|
||||
assert File.expand_path('ebin', root) inlist path
|
||||
assert File.expand_path('lib', root) inlist path
|
||||
assert File.expand_path('src', root) inlist path
|
||||
|
||||
# pz
|
||||
assert File.expand_path('lib/list', root) in path
|
||||
end
|
||||
|
||||
test :require do
|
||||
options = ['-r', fixture_path('../../../lib/list/*') /> to_char_list, '-r', '/never/gonna/*/up']
|
||||
{ config, _argv } = Kernel.CLI.process_options(options, Kernel.CLI.Config.new)
|
||||
assert {:require, fixture_path "../../../lib/list/chars.ex"} in config.commands
|
||||
assert File.expand_path('lib/list', root) inlist path
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -167,6 +167,11 @@ defmodule Kernel.ErrorsTest do
|
||||
format_rescue 'defmodule Foo, do: (defmodule Elixir.Foo, do: true)'
|
||||
end
|
||||
|
||||
test :invalid_definition do
|
||||
assert "nofile:1: invalid syntax in def 1.(hello)" ==
|
||||
format_rescue 'defmodule Foo, do: (def 1.(hello), do: true)'
|
||||
end
|
||||
|
||||
test :duplicated_bitstring_size do
|
||||
assert "nofile:1: duplicated size definition for bitstring" == format_rescue '<<1 :: [size(12), size(13)]>>'
|
||||
end
|
||||
@@ -234,6 +239,16 @@ defmodule Kernel.ErrorsTest do
|
||||
format_rescue 'cond do _ -> true end'
|
||||
end
|
||||
|
||||
test :fun_different_arities do
|
||||
assert "nofile:1: cannot mix clauses with different arities in function definition" ==
|
||||
format_rescue 'fn x -> x; x, y -> x + y end'
|
||||
end
|
||||
|
||||
test :new_line_error do
|
||||
assert "nofile:3: syntax error before: newline" ==
|
||||
format_rescue 'if true do\n foo = [],\n baz\nend'
|
||||
end
|
||||
|
||||
test :macros_error_stacktrace do
|
||||
assert [{:erlang,:+,[1,:foo],_},{Foo,:sample,1,_}|_] =
|
||||
rescue_stacktrace("defmodule Foo do\ndefmacro sample(num), do: num + :foo\ndef other, do: sample(1)\nend")
|
||||
|
||||
@@ -147,7 +147,7 @@ defmodule Kernel.OverridableTest do
|
||||
end
|
||||
|
||||
test "overridable definitions are private" do
|
||||
refute {:"with_super (overridable 0)",0} in Overridable.__info__(:exports)
|
||||
refute {:"with_super (overridable 0)",0} inlist Overridable.__info__(:exports)
|
||||
end
|
||||
|
||||
test "invalid super call" do
|
||||
|
||||
@@ -0,0 +1,257 @@
|
||||
Code.require_file "../../test_helper.exs", __FILE__
|
||||
|
||||
defrecord BadRange, first: 0, last: 0 do
|
||||
defoverridable [first: 1]
|
||||
|
||||
def first(_) do
|
||||
:not_optimizable
|
||||
end
|
||||
end
|
||||
|
||||
defmodule Kernel.RecordRewriterTest do
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
## Helpers
|
||||
|
||||
defmacrop clause(expr) do
|
||||
quote do: extract_clause(unquote(Macro.escape(expr)))
|
||||
end
|
||||
|
||||
defp extract_clause(fun) do
|
||||
{ { :fun, _, { :clauses, clauses } }, _ } =
|
||||
:elixir_translator.translate_each(fun, :elixir.scope_for_eval([]))
|
||||
hd(clauses)
|
||||
end
|
||||
|
||||
defp optimize_clause(clause) do
|
||||
{ clause, dict, res } = Kernel.RecordRewriter.optimize_clause(clause)
|
||||
dict = Enum.map dict, fn { k, { v, _ } } -> { k, v }; other -> other; end
|
||||
{ clause, dict, res }
|
||||
end
|
||||
|
||||
## Inference tests
|
||||
|
||||
test "simple atom" do
|
||||
clause = clause(fn -> :foo end)
|
||||
assert optimize_clause(clause) == { clause, [], nil }
|
||||
end
|
||||
|
||||
test "with left-side arg match" do
|
||||
clause = clause(fn(x = Macro.Env[]) -> :foo end)
|
||||
assert optimize_clause(clause) == { clause, [x: Macro.Env], nil }
|
||||
end
|
||||
|
||||
test "with right-side arg match" do
|
||||
clause = clause(fn(Macro.Env[] = x) -> :foo end)
|
||||
assert optimize_clause(clause) == { clause, [x: Macro.Env], nil }
|
||||
end
|
||||
|
||||
test "with nested left-side arg match" do
|
||||
clause = clause(fn(x = y = Macro.Env[]) -> :foo end)
|
||||
assert optimize_clause(clause) == { clause, [x: Macro.Env, y: Macro.Env], nil }
|
||||
end
|
||||
|
||||
test "with nested right-side arg match" do
|
||||
clause = clause(fn(Macro.Env[] = x = y) -> :foo end)
|
||||
assert optimize_clause(clause) == { clause, [x: Macro.Env, y: Macro.Env], nil }
|
||||
end
|
||||
|
||||
test "with deep left-side arg match" do
|
||||
clause = clause(fn({ x, y = Macro.Env[] }) -> :foo end)
|
||||
assert optimize_clause(clause) == { clause, [y: Macro.Env], nil }
|
||||
end
|
||||
|
||||
test "with deep right-side arg match" do
|
||||
clause = clause(fn({ x, Macro.Env[] = y }) -> :foo end)
|
||||
assert optimize_clause(clause) == { clause, [y: Macro.Env], nil }
|
||||
end
|
||||
|
||||
test "with tuple match" do
|
||||
clause = clause(fn({ x, y } = { Macro.Env[], Range[] }) -> :foo end)
|
||||
assert optimize_clause(clause) == { clause, [x: Macro.Env, y: Range], nil }
|
||||
|
||||
clause = clause(fn({ Macro.Env[], y } = { x, Range[] }) -> :foo end)
|
||||
assert optimize_clause(clause) == { clause, [x: Macro.Env, y: Range], nil }
|
||||
end
|
||||
|
||||
test "inside body" do
|
||||
clause = clause(fn(x = Macro.Env[]) -> y = x end)
|
||||
assert optimize_clause(clause) == { clause, [x: Macro.Env, y: Macro.Env], { Macro.Env, nil } }
|
||||
end
|
||||
|
||||
test "inside body with variable overridden" do
|
||||
clause = clause(fn(x = Macro.Env[], y = Range[]) -> y = x end)
|
||||
assert optimize_clause(clause) == { clause, [x: Macro.Env, y: Range, "y@1": Macro.Env], { Macro.Env, nil } }
|
||||
end
|
||||
|
||||
test "inside body with nested tuple" do
|
||||
clause = clause(fn(x = Range[]) -> ^x = Range[first: { :hello, 2 }] end)
|
||||
assert optimize_clause(clause) == { clause, [x: Range], { Range, [nil, { :hello, nil }, nil] } }
|
||||
end
|
||||
|
||||
test "conflicting definition" do
|
||||
clause = clause(fn(x = Macro.Env[]) -> ^x = Range[]; :foo end)
|
||||
assert optimize_clause(clause) == { clause, [x: nil], nil }
|
||||
end
|
||||
|
||||
test "inside list" do
|
||||
clause = clause(fn -> [x = Macro.Env[]]; :foo end)
|
||||
assert optimize_clause(clause) == { clause, [x: Macro.Env], nil }
|
||||
end
|
||||
|
||||
test "inside tuple" do
|
||||
clause = clause(fn -> { x = Macro.Env[] }; :foo end)
|
||||
assert optimize_clause(clause) == { clause, [x: Macro.Env], nil }
|
||||
end
|
||||
|
||||
test "inside bin" do
|
||||
clause = clause(fn -> << x = Macro.Env[] >>; :foo end)
|
||||
assert optimize_clause(clause) == { clause, [x: Macro.Env], nil }
|
||||
end
|
||||
|
||||
test "inside operator" do
|
||||
clause = clause(fn -> 1 + (x = Macro.Env[]); :foo end)
|
||||
assert optimize_clause(clause) == { clause, [x: Macro.Env], nil }
|
||||
end
|
||||
|
||||
test "inside block" do
|
||||
clause = clause(fn -> :foo; (x = Macro.Env[]; x) end)
|
||||
assert optimize_clause(clause) == { clause, [x: Macro.Env], { Macro.Env, nil } }
|
||||
end
|
||||
|
||||
test "inside local call" do
|
||||
clause = clause(fn -> (x = Macro.Env[]).(y = Range[]) end)
|
||||
assert optimize_clause(clause) == { clause, [x: Macro.Env, y: Range], nil }
|
||||
end
|
||||
|
||||
test "inside remote call" do
|
||||
clause = clause(fn -> (x = Macro.Env[]).call(y = Range[]) end)
|
||||
assert optimize_clause(clause) == { clause, [x: Macro.Env, y: Range], nil }
|
||||
end
|
||||
|
||||
test "inside list comprehension" do
|
||||
clause = clause(fn -> lc x = Macro.Env[] inlist sample, do: x end)
|
||||
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 }
|
||||
end
|
||||
|
||||
test "inside anonymous function" do
|
||||
clause = clause(fn -> fn (x = Macro.Env[]) -> x end end)
|
||||
assert optimize_clause(clause) == { clause, [], nil }
|
||||
end
|
||||
|
||||
test "inside case" do
|
||||
clause = clause(fn -> case x = Macro.Env[] do _ -> x end end)
|
||||
assert optimize_clause(clause) == { clause, [x: Macro.Env], { Macro.Env, nil } }
|
||||
|
||||
clause = clause(fn -> case something do x = Macro.Env[] -> x; Macro.Env[] = x -> x end end)
|
||||
assert optimize_clause(clause) == { clause, [x: Macro.Env], { Macro.Env, nil } }
|
||||
|
||||
clause = clause(fn -> case something do x = Macro.Env[] -> x; Macro.Env[] = y -> y end end)
|
||||
assert optimize_clause(clause) == { clause, [], { Macro.Env, nil } }
|
||||
|
||||
clause = clause(fn -> case something do 1 -> x = Macro.Env[]; 2 -> x = Macro.Env[] end end)
|
||||
assert optimize_clause(clause) == { clause, [x: Macro.Env], { Macro.Env, nil } }
|
||||
|
||||
clause = clause(fn -> case something do 1 -> x = Macro.Env[]; 2 -> x = Range[] end end)
|
||||
assert optimize_clause(clause) == { clause, [x: nil], nil }
|
||||
end
|
||||
|
||||
test "inside case with nested tuple" do
|
||||
clause = clause(fn -> case something do x = Range[first: { :foo, 2 }] -> x; Range[] = x -> x end end)
|
||||
assert optimize_clause(clause) == { clause, [x: Range], { Range, nil } }
|
||||
|
||||
clause = clause(fn -> case something do x = Range[first: { :foo, 2 }] -> x; Range[first: { :foo, 2 }] = x -> x end end)
|
||||
assert optimize_clause(clause) == { clause, [x: Range], { Range, [nil, { :foo, nil }, nil] } }
|
||||
end
|
||||
|
||||
test "inside receive" do
|
||||
clause = clause(fn -> receive do x = Macro.Env[] -> x; Macro.Env[] = x -> x end end)
|
||||
assert optimize_clause(clause) == { clause, [x: Macro.Env], { Macro.Env, nil } }
|
||||
|
||||
clause = clause(fn -> receive do x = Macro.Env[] -> x; Macro.Env[] = y -> y end end)
|
||||
assert optimize_clause(clause) == { clause, [], { Macro.Env, nil } }
|
||||
|
||||
clause = clause(fn -> receive do 1 -> x = Macro.Env[]; 2 -> x = Macro.Env[] end end)
|
||||
assert optimize_clause(clause) == { clause, [x: Macro.Env], { Macro.Env, nil } }
|
||||
|
||||
clause = clause(fn -> receive do 1 -> x = Macro.Env[]; 2 -> x = Range[] end end)
|
||||
assert optimize_clause(clause) == { clause, [x: nil], nil }
|
||||
end
|
||||
|
||||
test "inside receive with after" do
|
||||
clause = clause(fn -> receive do 1 -> x = Macro.Env[]; after 2 -> x = Macro.Env[]; end end)
|
||||
assert optimize_clause(clause) == { clause, [x: Macro.Env], { Macro.Env, nil } }
|
||||
|
||||
clause = clause(fn -> receive do 1 -> x = Macro.Env[]; after 2 -> x = Range[]; end end)
|
||||
assert optimize_clause(clause) == { clause, [x: nil], nil }
|
||||
end
|
||||
|
||||
test "inside try" do
|
||||
clause = clause(fn -> try do x = Macro.Env[]; x end end)
|
||||
assert optimize_clause(clause) == { clause, [], { Macro.Env, nil } }
|
||||
|
||||
clause = clause(fn -> try do x = Macro.Env[]; x; catch :oops -> x = Macro.Env[]; end end)
|
||||
assert optimize_clause(clause) == { clause, [], { Macro.Env, nil } }
|
||||
|
||||
clause = clause(fn -> try do x = Macro.Env[]; x; after x = Macro.Env[]; end end)
|
||||
assert optimize_clause(clause) == { clause, [], { Macro.Env, nil } }
|
||||
end
|
||||
|
||||
## Rewrite tests
|
||||
|
||||
test "getter call is rewriten" do
|
||||
{ clause, rewriten } =
|
||||
{ clause(fn(x = Range[]) -> x.first end), clause(fn(x = Range[]) -> :erlang.element(2, x) end) }
|
||||
|
||||
assert optimize_clause(clause) == { rewriten, [x: Range], nil }
|
||||
end
|
||||
|
||||
test "setter call is rewriten" do
|
||||
{ clause, rewriten } =
|
||||
{ clause(fn(x = Range[]) -> x.first(:first) end), clause(fn(x = Range[]) -> :erlang.setelement(2, x, :first) end) }
|
||||
|
||||
assert optimize_clause(clause) == { rewriten, [x: Range], { Range, nil } }
|
||||
end
|
||||
|
||||
test "nested setter call is rewriten" do
|
||||
{ clause, rewriten } =
|
||||
{ clause(fn(x = Range[]) -> x.first(:first).last(:last) end), clause(fn(x = Range[]) -> :erlang.setelement(3, :erlang.setelement(2, x, :first), :last) end) }
|
||||
|
||||
assert optimize_clause(clause) == { rewriten, [x: Range], { Range, nil } }
|
||||
end
|
||||
|
||||
test "updater call is no-op (for now)" do
|
||||
clause = clause(fn(x = Range[]) -> x.update_first(&1 + 1) end)
|
||||
assert optimize_clause(clause) == { clause, [x: Range], { Range, nil } }
|
||||
end
|
||||
|
||||
test "noop for unknown fields" do
|
||||
clause = clause(fn(x = Range[]) -> x.unknown end)
|
||||
assert optimize_clause(clause) == { clause, [x: Range], nil }
|
||||
end
|
||||
|
||||
test "noop for not records fields" do
|
||||
clause = clause(fn(x = { :not_a_record, _ }) -> x.unknown end)
|
||||
assert optimize_clause(clause) == { clause, [x: :not_a_record], nil }
|
||||
end
|
||||
|
||||
test "noop for rewriten fields" do
|
||||
clause = clause(fn(x = BadRange[]) -> x.first end)
|
||||
assert optimize_clause(clause) == { clause, [x: BadRange], nil }
|
||||
end
|
||||
|
||||
test "noop for conflicting inference" do
|
||||
clause = clause(fn(x = Macro.Env[]) -> ^x = Range[]; x.first end)
|
||||
assert optimize_clause(clause) == { clause, [x: nil], nil }
|
||||
end
|
||||
end
|
||||
@@ -20,8 +20,28 @@ defmodule KernelTest do
|
||||
refute x([])
|
||||
end
|
||||
|
||||
test :paren do
|
||||
assert nil?(())
|
||||
assert [ 1, (), 3 ] == [1, nil, 3 ]
|
||||
assert [do: ()] == [do: nil]
|
||||
assert { 1, (), 3 } == { 1, nil, 3 }
|
||||
end
|
||||
|
||||
test :__info__ do
|
||||
assert { :in, 2 } in Kernel.__info__(:macros)
|
||||
assert { :in, 2 } inlist Kernel.__info__(:macros)
|
||||
end
|
||||
|
||||
test :macro_exported? do
|
||||
assert macro_exported?(Kernel, :in, 2) == true
|
||||
assert macro_exported?(Kernel, :def, 1) == true
|
||||
assert macro_exported?(Kernel, :def, 2) == true
|
||||
assert macro_exported?(Kernel, :def, 3) == false
|
||||
assert macro_exported?(Kernel, :def, 4) == true
|
||||
assert macro_exported?(Kernel, :no_such_macro, 2) == false
|
||||
end
|
||||
|
||||
test :debug_info do
|
||||
assert :debug_info inlist Kernel.__info__(:compile)[:options]
|
||||
end
|
||||
|
||||
defp x(value) when value in [1,2,3], do: true
|
||||
@@ -96,12 +116,22 @@ defmodule KernelTest do
|
||||
assert function(:erlang, :atom_to_list, 1).(:a) == 'a'
|
||||
end
|
||||
|
||||
test :remote_syntax_function do
|
||||
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}
|
||||
|
||||
@@ -53,36 +53,12 @@ defmodule ListTest do
|
||||
assert List.foldr([1,2,3,4], 0, fn x,y -> x - y end) == -2
|
||||
end
|
||||
|
||||
def test_member? do
|
||||
test :member? do
|
||||
assert List.member? [1,2,3], 1
|
||||
refute List.member? [1,2,3], 0
|
||||
refute List.member? [], 0
|
||||
end
|
||||
|
||||
test :range do
|
||||
assert List.range(1,3) == [1,2,3]
|
||||
assert List.range(1, 1) == [1]
|
||||
assert List.range(5, 0) == [5,4,3,2,1,0]
|
||||
assert List.range(1, 0, -1) == [1,0]
|
||||
assert List.range(1,8,2) == [1,3,5,7]
|
||||
assert List.range(7,-1,-3) == [7,4,1]
|
||||
assert List.range(2,1,1) == []
|
||||
assert List.range(8,1,1) == []
|
||||
assert List.range(1,8,-1) == []
|
||||
assert List.range(1,1,-1) == []
|
||||
end
|
||||
|
||||
test :sort do
|
||||
assert List.sort([3, 5, 1, 2, 4]) == [1,2,3,4,5]
|
||||
assert List.sort([3, 5, 1, 2, 4], &2 <= &1) == [5,4,3,2,1]
|
||||
assert List.sort(['2', '3', '0', '11', '10']) == ['0', '10', '11', '2', '3']
|
||||
assert ['0', '2', '3', '10', '11'] == List.sort ['2', '3', '0', '11', '10'], fn a, b ->
|
||||
{na, _} = :string.to_integer a
|
||||
{nb, _} = :string.to_integer b
|
||||
na <= nb
|
||||
end
|
||||
end
|
||||
|
||||
test :concat_1 do
|
||||
assert List.concat([[1,[2],3], [4], [5,6]]) == [1,[2],3,4,5,6]
|
||||
end
|
||||
@@ -95,10 +71,6 @@ defmodule ListTest do
|
||||
assert Enum.reverse([1,2,3]) == [3,2,1]
|
||||
end
|
||||
|
||||
test :uniq do
|
||||
assert List.uniq([1,2,3,2,1]) == [1,2,3]
|
||||
end
|
||||
|
||||
test :duplicate do
|
||||
assert List.duplicate(1, 3) == [1,1,1]
|
||||
assert List.duplicate([1], 1) == [[1]]
|
||||
|
||||
@@ -141,11 +141,19 @@ defmodule MacroTest do
|
||||
assert Macro.to_binary(quote do: foo.bar.([1, 2, 3])) == "foo.bar().([1, 2, 3])"
|
||||
end
|
||||
|
||||
test :atom_call_to_binary do
|
||||
assert Macro.to_binary(quote do: :foo.(1, 2, 3)) == ":foo.(1, 2, 3)"
|
||||
end
|
||||
|
||||
test :aliases_call_to_binary do
|
||||
assert Macro.to_binary(quote do: Foo.Bar.baz(1, 2, 3)) == "Foo.Bar.baz(1, 2, 3)"
|
||||
assert Macro.to_binary(quote do: Foo.Bar.baz([1, 2, 3])) == "Foo.Bar.baz([1, 2, 3])"
|
||||
end
|
||||
|
||||
test :arrow_to_binary do
|
||||
assert Macro.to_binary(quote do: foo(1, (2 -> 3))) == "foo(1, (2 -> 3))"
|
||||
end
|
||||
|
||||
test :blocks_to_binary do
|
||||
assert Macro.to_binary(quote do: (1; 2; (:foo; :bar); 3)) <> "\n" == """
|
||||
(
|
||||
@@ -183,9 +191,24 @@ defmodule MacroTest do
|
||||
end
|
||||
|
||||
test :fn_to_binary do
|
||||
assert Macro.to_binary(quote do: (fn(x) -> x + 1 end)) <> "\n" == """
|
||||
assert Macro.to_binary(quote do: (() -> x)) == "(() -> x)"
|
||||
assert Macro.to_binary(quote do: (fn -> 1 + 2 end)) == "fn -> 1 + 2 end"
|
||||
assert Macro.to_binary(quote do: (fn(x) -> x + 1 end)) == "fn x -> x + 1 end"
|
||||
|
||||
assert Macro.to_binary(quote do: (fn(x) -> y = x + 1; y end)) <> "\n" == """
|
||||
fn x ->
|
||||
x + 1
|
||||
y = (x + 1)
|
||||
y
|
||||
end
|
||||
"""
|
||||
|
||||
assert Macro.to_binary(quote do: (fn(x) -> y = x + 1; y; (z) -> z end)) <> "\n" == """
|
||||
fn
|
||||
x ->
|
||||
y = (x + 1)
|
||||
y
|
||||
z ->
|
||||
z
|
||||
end
|
||||
"""
|
||||
end
|
||||
@@ -222,6 +245,8 @@ defmodule MacroTest do
|
||||
end
|
||||
|
||||
test :unary_ops_to_binary do
|
||||
assert Macro.to_binary(quote do: not 1) == "not 1"
|
||||
assert Macro.to_binary(quote do: not foo) == "not foo"
|
||||
assert Macro.to_binary(quote do: -1) == "-1"
|
||||
assert Macro.to_binary(quote do: @foo(bar)) == "@foo(bar)"
|
||||
end
|
||||
@@ -248,4 +273,14 @@ defmodule MacroTest do
|
||||
assert Macro.safe_term(quote do: [1+1]) == { :unsafe, quote do: 1 + 1 }
|
||||
assert Macro.safe_term(quote do: {1+1}) == { :unsafe, quote do: 1 + 1 }
|
||||
end
|
||||
|
||||
## extract_args
|
||||
|
||||
test :extract_args do
|
||||
assert Macro.extract_args(quote do: foo) == { :foo, [] }
|
||||
assert Macro.extract_args(quote do: foo()) == { :foo, [] }
|
||||
assert Macro.extract_args(quote do: :foo.()) == { :foo, [] }
|
||||
assert Macro.extract_args(quote do: foo(1,2,3)) == { :foo, [1,2,3] }
|
||||
assert Macro.extract_args(quote do: 1.(1,2,3)) == :error
|
||||
end
|
||||
end
|
||||
@@ -8,7 +8,7 @@ end
|
||||
|
||||
defprotocol ProtocolTest.WithExcept do
|
||||
@except [Atom, Number, List]
|
||||
@spec blank(t), do: boolean
|
||||
@spec blank(t) :: boolean
|
||||
def blank(thing)
|
||||
end
|
||||
|
||||
|
||||
@@ -17,12 +17,12 @@ defmodule RangeTest do
|
||||
end
|
||||
|
||||
test :in do
|
||||
refute 0 in 1..3, "raw range assertion"
|
||||
assert 1 in 1..3, "raw range assertion"
|
||||
assert 2 in 1..3, "raw range assertion"
|
||||
assert 3 in 1..3, "raw range assertion"
|
||||
refute 4 in 1..3, "raw range assertion"
|
||||
assert -3 in -1..-3, "raw range assertion"
|
||||
refute 0 in 1..3, "in range assertion"
|
||||
assert 1 in 1..3, "in range assertion"
|
||||
assert 2 in 1..3, "in range assertion"
|
||||
assert 3 in 1..3, "in range assertion"
|
||||
refute 4 in 1..3, "in range assertion"
|
||||
assert -3 in -1..-3, "in range assertion"
|
||||
end
|
||||
|
||||
test :is_range do
|
||||
|
||||
@@ -11,6 +11,12 @@ defrecord name, a: 0, b: 1 do
|
||||
def get_a(RecordTest.DynamicName[a: a]) do
|
||||
a
|
||||
end
|
||||
|
||||
defoverridable [update_b: 2]
|
||||
|
||||
def update_b(_, _) do
|
||||
:not_optimizable
|
||||
end
|
||||
end
|
||||
|
||||
## With types
|
||||
@@ -152,6 +158,13 @@ defmodule RecordTest do
|
||||
assert RecordTest.SomeRecord.b(record.update(b: 2)) == 2
|
||||
end
|
||||
|
||||
test :optimizable do
|
||||
assert { :b, 1 } inlist RecordTest.SomeRecord.__record__(:optimizable)
|
||||
assert { :b, 2 } inlist RecordTest.SomeRecord.__record__(:optimizable)
|
||||
assert { :update_b, 2 } inlist RecordTest.SomeRecord.__record__(:optimizable)
|
||||
refute { :update_b, 2 } inlist RecordTest.DynamicName.__record__(:optimizable)
|
||||
end
|
||||
|
||||
defp file_info do
|
||||
{ :ok, file_info } = :file.read_file_info(__FILE__)
|
||||
file_info
|
||||
|
||||
@@ -35,6 +35,10 @@ defmodule Regex.BinaryTest do
|
||||
assert Regex.opts(Regex.compile!("foo", "u")) == "u"
|
||||
end
|
||||
|
||||
test :unicode do
|
||||
assert ("josé" =~ %r"\p{Latin}$"u) == 3
|
||||
end
|
||||
|
||||
test :groups do
|
||||
assert Regex.groups(%r/(?<FOO>foo)/g) == [:FOO]
|
||||
assert Regex.groups(Regex.compile!("foo")) == nil
|
||||
|
||||
@@ -45,6 +45,10 @@ defmodule StringTest do
|
||||
assert String.rstrip("") == ""
|
||||
assert String.rstrip(" abc ") == " abc"
|
||||
assert String.rstrip(" abc a") == " abc a"
|
||||
assert String.rstrip("a abc a\n\n") == "a abc a"
|
||||
assert String.rstrip("a abc a\t\n\v\f\r\s") == "a abc a"
|
||||
assert String.rstrip("a abc a " <> <<31>>) == "a abc a"
|
||||
assert String.rstrip("a abc a" <> <<194,133>>) == "a abc a"
|
||||
assert String.rstrip(" abc aa", ?a) == " abc "
|
||||
assert String.rstrip(" abc __", ?_) == " abc "
|
||||
end
|
||||
@@ -53,12 +57,18 @@ defmodule StringTest do
|
||||
assert String.lstrip("") == ""
|
||||
assert String.lstrip(" abc ") == "abc "
|
||||
assert String.lstrip("a abc a") == "a abc a"
|
||||
assert String.lstrip("\n\na abc a") == "a abc a"
|
||||
assert String.lstrip("\t\n\v\f\r\sa abc a") == "a abc a"
|
||||
assert String.lstrip(<<31>> <> " a abc a") == "a abc a"
|
||||
assert String.lstrip(<<194,133>> <> "a abc a") == "a abc a"
|
||||
assert String.lstrip("__ abc _", ?_) == " abc _"
|
||||
end
|
||||
|
||||
test :strip do
|
||||
assert String.strip("") == ""
|
||||
assert String.strip(" abc ") == "abc"
|
||||
assert String.strip("a abc a\n\n") == "a abc a"
|
||||
assert String.strip("a abc a\t\n\v\f\r\s") == "a abc a"
|
||||
assert String.strip("___ abc ___", ?_) == " abc "
|
||||
end
|
||||
|
||||
@@ -152,7 +162,7 @@ defmodule StringTest do
|
||||
assert String.at("л", 10) == nil
|
||||
assert String.at("elixir", -1) == "r"
|
||||
assert String.at("がガちゃ", -2) == "ち"
|
||||
assert String.at("л", -3) == ""
|
||||
assert String.at("л", -3) == nil
|
||||
assert String.at("Ā̀stute", 1) == "s"
|
||||
assert String.at("elixir",6) == nil
|
||||
end
|
||||
|
||||
@@ -24,9 +24,9 @@ defmodule SystemTest do
|
||||
end
|
||||
|
||||
test :env do
|
||||
assert System.get_env("SYSTEM_ENV_TEST_VAR") == nil
|
||||
System.put_env('SYSTEM_ENV_TEST_VAR', 'SAMPLE')
|
||||
assert System.get_env("SYSTEM_ENV_TEST_VAR") == "SAMPLE"
|
||||
assert System.get_env("SYSTEM_ELIXIR_ENV_TEST_VAR") == nil
|
||||
System.put_env('SYSTEM_ELIXIR_ENV_TEST_VAR', 'SAMPLE')
|
||||
assert System.get_env("SYSTEM_ELIXIR_ENV_TEST_VAR") == "SAMPLE"
|
||||
end
|
||||
|
||||
test :cmd do
|
||||
|
||||
@@ -123,23 +123,28 @@ defmodule Typespec.Test.Type do
|
||||
|
||||
test "@type with a fun" do
|
||||
spec = test_module do
|
||||
@type mytype :: fun
|
||||
@type mytype :: (... -> any)
|
||||
end
|
||||
assert {:mytype,{:type,_,:fun, []},[]} = spec
|
||||
end
|
||||
|
||||
test "@type with a fun with arguments and return type" do
|
||||
spec = test_module do
|
||||
@type mytype :: fun(integer, integer, do: integer)
|
||||
{spec1, spec2} = test_module do
|
||||
t1 = @type mytype :: (integer, integer -> integer)
|
||||
t2 = @type mytype2 :: (fun(integer, integer) -> integer)
|
||||
{t1, t2}
|
||||
end
|
||||
assert {:mytype,{:type,_,:fun, [{:type, _, :product,
|
||||
[{:type, _, :integer, []}, {:type, _, :integer, []}]},
|
||||
{:type, _, :integer, []}]},[]} = spec
|
||||
{:type, _, :integer, []}]},[]} = spec1
|
||||
assert {:mytype2,{:type,_,:fun, [{:type, _, :product,
|
||||
[{:type, _, :integer, []}, {:type, _, :integer, []}]},
|
||||
{:type, _, :integer, []}]},[]} = spec2
|
||||
end
|
||||
|
||||
test "@type with a fun with no arguments and return type" do
|
||||
spec = test_module do
|
||||
@type mytype :: fun(do: integer)
|
||||
@type mytype :: (() -> integer)
|
||||
end
|
||||
assert {:mytype,{:type,_,:fun, [{:type, _, :product, []},
|
||||
{:type, _, :integer, []}]}, []} = spec
|
||||
@@ -147,7 +152,7 @@ defmodule Typespec.Test.Type do
|
||||
|
||||
test "@type with a fun with any arity and return type" do
|
||||
spec = test_module do
|
||||
@type mytype :: fun(..., do: integer)
|
||||
@type mytype :: (... -> integer)
|
||||
end
|
||||
assert {:mytype,{:type,_,:fun, [{:type, _, :any},
|
||||
{:type, _, :integer, []}]}, []} = spec
|
||||
@@ -186,7 +191,7 @@ defmodule Typespec.Test.Type do
|
||||
test "@type with annotations" do
|
||||
{spec1, spec2} = test_module do
|
||||
t1 = @type mytype :: named :: integer
|
||||
t2 = @type mytype1 :: fun(a :: integer, do: integer)
|
||||
t2 = @type mytype1 :: (a :: integer -> integer)
|
||||
{t1,t2}
|
||||
end
|
||||
assert {:mytype, {:ann_type, _, [{:var, _, :named}, {:type, _, :integer, []}]}, []} = spec1
|
||||
@@ -224,9 +229,9 @@ defmodule Typespec.Test.Type do
|
||||
def myfun(x), do: x
|
||||
def myfun(), do: :ok
|
||||
def myfun(x,y), do: {x,y}
|
||||
t1 = @spec myfun(integer), do: integer
|
||||
t2 = @spec myfun(), do: integer
|
||||
t3 = @spec myfun(integer, integer), do: {integer, integer}
|
||||
t1 = @spec myfun(integer) :: integer
|
||||
t2 = @spec myfun() :: integer
|
||||
t3 = @spec myfun(integer, integer) :: {integer, integer}
|
||||
{t1,t2,t3}
|
||||
end
|
||||
assert {{:myfun,1},{:type,_,:fun,[{:type,_,:product,[{:type,_,:integer,[]}]},{:type,_,:integer,[]}]}} = spec1
|
||||
@@ -234,11 +239,19 @@ defmodule Typespec.Test.Type do
|
||||
assert {{:myfun,2},{:type,_,:fun,[{:type,_,:product,[{:type,_,:integer,[]},{:type,_,:integer,[]}]},{:type,_,:tuple,[{:type,_,:integer,[]},{:type,_,:integer,[]}]}]}} = spec3
|
||||
end
|
||||
|
||||
test "@spec(spec) with guards" do
|
||||
spec1 = test_module do
|
||||
def myfun(x), do: x
|
||||
@spec myfun(x) when is_subtype(x, integer) :: boolean
|
||||
end
|
||||
assert {{:myfun,1},{:type,_,:bounded_fun,[{:type,_,:fun,[{:type,_,:product,[{:var,_,:x}]},{:type,_,:boolean,[]}]},[{:type,_,:constraint,[{:atom,_,:is_subtype},[{:var,_,:x},{:type,_,:integer,[]}]]}]]}} = spec1
|
||||
end
|
||||
|
||||
test "@callback(callback)" do
|
||||
{spec1, spec2, spec3} = test_module do
|
||||
t1 = @callback myfun(integer), do: integer
|
||||
t2 = @callback myfun(), do: integer
|
||||
t3 = @callback myfun(integer, integer), do: {integer, integer}
|
||||
t1 = @callback myfun(integer) :: integer
|
||||
t2 = @callback myfun() :: integer
|
||||
t3 = @callback myfun(integer, integer) :: {integer, integer}
|
||||
{t1,t2,t3}
|
||||
end
|
||||
assert {{:myfun,1},{:type,_,:fun,[{:type,_,:product,[{:type,_,:integer,[]}]},{:type,_,:integer,[]}]}} = spec1
|
||||
@@ -249,22 +262,22 @@ defmodule Typespec.Test.Type do
|
||||
test "@spec + @callback" do
|
||||
{ specs, callbacks } = test_module do
|
||||
def myfun(x), do: x
|
||||
@spec myfun(integer), do: integer
|
||||
@spec myfun(char_list), do: char_list
|
||||
@callback cb(integer), do: integer
|
||||
@spec myfun(integer) :: integer
|
||||
@spec myfun(char_list) :: char_list
|
||||
@callback cb(integer) :: integer
|
||||
{ @spec, @callback }
|
||||
end
|
||||
|
||||
assert [
|
||||
{ {:cb, 1}, {:type,_,:fun,[{:type,_,:product,[{:type,_,:integer,[]}]},{:type,_,:integer,[]}]} }
|
||||
] = List.sort(callbacks)
|
||||
] = Enum.sort(callbacks)
|
||||
|
||||
assert [
|
||||
{ {:myfun,1}, {:type,_,:fun,[{:type,_,:product,[{:type,_,:integer,[]}]},{:type,_,:integer,[]}]} },
|
||||
{ {:myfun,1}, {:type,_,:fun,[{:type,_,:product,[
|
||||
{:remote_type, _, [{:atom, _, :elixir},{:atom, _, :char_list}, []]}]},
|
||||
{:remote_type, _, [{:atom, _, :elixir},{:atom, _, :char_list}, []]}]} }
|
||||
] = List.sort(specs)
|
||||
] = Enum.sort(specs)
|
||||
end
|
||||
|
||||
# Conversion to AST
|
||||
@@ -282,7 +295,9 @@ defmodule Typespec.Test.Type do
|
||||
(quote do: @type binary_type2() :: <<_ :: 3 * 8>>),
|
||||
(quote do: @type binary_type3() :: <<_ :: 3>>),
|
||||
(quote do: @type tuple_type() :: {integer()}),
|
||||
(quote do: @type ftype() :: fun() | fun(do: integer()) | fun(integer(), do: integer())),
|
||||
(quote do: @type ftype() :: (() -> any()) | (() -> integer()) | ((integer() -> integer()))),
|
||||
(quote do: @type cl() :: char_list()),
|
||||
(quote do: @type vaf() :: (... -> any())),
|
||||
]
|
||||
|
||||
types = test_module do
|
||||
@@ -309,13 +324,21 @@ defmodule Typespec.Test.Type do
|
||||
{ :type, 0, :integer, [] } },
|
||||
],
|
||||
[]}
|
||||
assert Kernel.Typespec.type_to_ast(record_type) == quote hygiene: false, do: my_record() :: {:my_record, field1 :: atom(), field2 :: integer() }
|
||||
assert Kernel.Typespec.type_to_ast(record_type) ==
|
||||
quote(hygiene: false, do: my_record() :: {:my_record, field1 :: atom(), field2 :: integer() })
|
||||
end
|
||||
|
||||
test "type_to_ast for paren_type" do
|
||||
type = {:my_type, {:paren_type, 0, [{:type, 0, :integer, []}]}, []}
|
||||
assert Kernel.Typespec.type_to_ast(type) ==
|
||||
quote(hygiene: false, do: my_type() :: integer())
|
||||
end
|
||||
|
||||
test "spec_to_ast" do
|
||||
specs = [
|
||||
(quote do: @spec a(), do: integer()),
|
||||
(quote do: @spec a(atom()), do: integer()),
|
||||
(quote do: @spec a() :: integer()),
|
||||
(quote do: @spec a(atom()) :: integer()),
|
||||
(quote do: @spec a(b) when is_subtype(b, integer()) :: integer()),
|
||||
]
|
||||
|
||||
compiled = test_module do
|
||||
@@ -325,9 +348,8 @@ defmodule Typespec.Test.Type do
|
||||
Enum.reverse @spec
|
||||
end
|
||||
|
||||
lc { { { name, _ }, spec }, definition } inlist Enum.zip(compiled, specs) do
|
||||
{ args, result } = Kernel.Typespec.spec_to_ast(spec)
|
||||
quoted = quote do: @spec unquote(name)(unquote_splicing(args)), do: unquote(result)
|
||||
lc { { { _, _ }, spec }, definition } inlist Enum.zip(compiled, specs) do
|
||||
quoted = quote do: @spec unquote(Kernel.Typespec.spec_to_ast(:a, spec))
|
||||
assert Macro.to_binary(quoted) == Macro.to_binary(definition)
|
||||
end
|
||||
end
|
||||
@@ -338,8 +360,8 @@ defmodule Typespec.Test.Type do
|
||||
Code.compiler_options debug_info: true
|
||||
|
||||
{ :module, T, binary, _ } = defmodule T do
|
||||
@spec a, do: any
|
||||
def a, do: nil
|
||||
@spec a :: any
|
||||
def a, do: nil
|
||||
end
|
||||
|
||||
:code.delete(T)
|
||||
@@ -356,7 +378,7 @@ defmodule Typespec.Test.Type do
|
||||
{ :module, T, binary, _ } = defmodule T do
|
||||
@type a :: any
|
||||
@typep b :: any
|
||||
@spec t(b), do: b
|
||||
@spec t(b) :: b
|
||||
def t(b), do: b
|
||||
@opaque c :: any
|
||||
end
|
||||
|
||||
@@ -4,8 +4,8 @@ defmodule URITest do
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
test :encode_with_binary do
|
||||
raw = <<38,60,62,34,32,227,130,134,227,130,147,227,130,134,227,130,147>>
|
||||
expected = "%26%3C%3E%22+%E3%82%86%E3%82%93%E3%82%86%E3%82%93"
|
||||
raw = <<13,10,38,60,62,34,32,227,130,134,227,130,147,227,130,134,227,130,147>>
|
||||
expected = "%0D%0A%26%3C%3E%22+%E3%82%86%E3%82%93%E3%82%86%E3%82%93"
|
||||
assert URI.encode(raw) == expected
|
||||
end
|
||||
|
||||
|
||||
@@ -14,6 +14,16 @@ function_stab_end_test() ->
|
||||
{_, [{a, Fun}]} = eval("a = fn -> 1 + 2 end"),
|
||||
3 = Fun().
|
||||
|
||||
function_stab_many_test() ->
|
||||
{_, [{a, Fun}]} = eval("a = fn\n{ :foo, x } -> x\n{ :bar, x } -> x\nend"),
|
||||
1 = Fun({ foo, 1 }),
|
||||
2 = Fun({ bar, 2 }).
|
||||
|
||||
function_stab_inline_test() ->
|
||||
{_, [{a, Fun}]} = eval("a = fn { :foo, x } -> x; { :bar, x } -> x end"),
|
||||
1 = Fun({ foo, 1 }),
|
||||
2 = Fun({ bar, 2 }).
|
||||
|
||||
function_with_args_test() ->
|
||||
{Fun, _} = eval("fn(a, b) -> a + b end"),
|
||||
3 = Fun(1,2).
|
||||
|
||||
@@ -37,34 +37,3 @@ record_new_selective_test() ->
|
||||
{ 20, _ } = eval("Foo.new(b: 20).b")
|
||||
end,
|
||||
test_helper:run_and_remove(F, ['Elixir.Foo']).
|
||||
|
||||
record_prepend_test() ->
|
||||
F = fun() ->
|
||||
eval("defrecord Foo, a: 1, b: [3], c: 3"),
|
||||
{ { 'Elixir.Foo', 1, [1,2,3], 3 }, _ } = eval("Foo.new.prepend_b [1,2]")
|
||||
end,
|
||||
test_helper:run_and_remove(F, ['Elixir.Foo']).
|
||||
|
||||
record_merge_test() ->
|
||||
F = fun() ->
|
||||
eval("defrecord Foo, a: [foo: :bar]"),
|
||||
{ { 'Elixir.Foo', [{foo,baz}, {a,1}] }, _ } = eval("Foo.new.merge_a [foo: :baz, a: 1]")
|
||||
end,
|
||||
test_helper:run_and_remove(F, ['Elixir.Foo']).
|
||||
|
||||
record_increment_test() ->
|
||||
F = fun() ->
|
||||
eval("defrecord Foo, a: 0"),
|
||||
{ { 'Elixir.Foo', 1 }, _ } = eval("Foo.new.increment_a"),
|
||||
{ { 'Elixir.Foo', 10 }, _ } = eval("Foo.new.increment_a 10"),
|
||||
{ { 'Elixir.Foo', -2 }, _ } = eval("Foo.new.increment_a -2")
|
||||
end,
|
||||
test_helper:run_and_remove(F, ['Elixir.Foo']).
|
||||
|
||||
record_toggle_test() ->
|
||||
F = fun() ->
|
||||
eval("defrecord Foo, a: false, b: true"),
|
||||
{ { 'Elixir.Foo', true, true }, _ } = eval("Foo.new.toggle_a"),
|
||||
{ { 'Elixir.Foo', false, false }, _ } = eval("Foo.new.toggle_b")
|
||||
end,
|
||||
test_helper:run_and_remove(F, ['Elixir.Foo']).
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
-export([test/0, run_and_remove/2, throw_elixir/1, throw_erlang/1]).
|
||||
|
||||
test() ->
|
||||
elixir:start_app(),
|
||||
application:start(elixir),
|
||||
eunit:test([
|
||||
atom_test,
|
||||
conditionals_test,
|
||||
|
||||
@@ -45,18 +45,18 @@ kw_test() ->
|
||||
|
||||
integer_test() ->
|
||||
[{number, 1, 123}] = tokenize("123"),
|
||||
[{number, 1, 123},{eol, 1, $;}] = tokenize("123;"),
|
||||
[{eol, 1, $\n}, {number, 3, 123}] = tokenize("\n\n123"),
|
||||
[{number, 1, 123},{eol, 1, ';'}] = tokenize("123;"),
|
||||
[{eol, 1, newline}, {number, 3, 123}] = tokenize("\n\n123"),
|
||||
[{number, 1, 123}, {number, 1, 234}] = tokenize(" 123 234 ").
|
||||
|
||||
float_test() ->
|
||||
[{number, 1, 12.3}] = tokenize("12.3"),
|
||||
[{number, 1, 12.3},{eol, 1, $;}] = tokenize("12.3;"),
|
||||
[{eol, 1, $\n}, {number, 3, 12.3}] = tokenize("\n\n12.3"),
|
||||
[{number, 1, 12.3},{eol, 1, ';'}] = tokenize("12.3;"),
|
||||
[{eol, 1, newline}, {number, 3, 12.3}] = tokenize("\n\n12.3"),
|
||||
[{number, 1, 12.3}, {number, 1, 23.4}] = tokenize(" 12.3 23.4 ").
|
||||
|
||||
comments_test() ->
|
||||
[{number, 1, 1},{eol, 1, $\n},{number,2,2}] = tokenize("1 # Comment\n2").
|
||||
[{number, 1, 1},{eol, 1, newline},{number,2,2}] = tokenize("1 # Comment\n2").
|
||||
|
||||
identifier_test() ->
|
||||
[{identifier,1,abc}] = tokenize("abc "),
|
||||
|
||||
@@ -65,12 +65,20 @@ defmodule ExUnit do
|
||||
options = Keyword.merge(user_options, options)
|
||||
ExUnit.Server.start_link
|
||||
configure(options)
|
||||
System.at_exit fn status ->
|
||||
if status == 0, do: ExUnit.run
|
||||
System.at_exit fn
|
||||
0 ->
|
||||
failures = ExUnit.run
|
||||
System.at_exit fn _ ->
|
||||
if failures > 0, do: System.halt(1), else: System.halt(0)
|
||||
end
|
||||
_ ->
|
||||
:ok
|
||||
end
|
||||
end
|
||||
|
||||
@doc false
|
||||
@doc """
|
||||
Returns the configured user options.
|
||||
"""
|
||||
def user_options(user_config // nil) do
|
||||
user_config = user_config ||
|
||||
System.get_env("EXUNIT_CONFIG") ||
|
||||
@@ -113,8 +121,6 @@ defmodule ExUnit do
|
||||
need to call it directly.
|
||||
"""
|
||||
def run do
|
||||
config = ExUnit.Runner.Config.new ExUnit.Server.options
|
||||
failures = ExUnit.Runner.run config
|
||||
if failures > 0, do: System.halt(1), else: System.halt(0)
|
||||
ExUnit.Runner.run ExUnit.Server.options
|
||||
end
|
||||
end
|
||||
|
||||
@@ -110,6 +110,7 @@ defmodule ExUnit.Assertions do
|
||||
end
|
||||
|
||||
defp translate_assertion({ :in, _, [left, right] }, _else) do
|
||||
IO.write "[WARNING] assert(left in right) is deprecated, please use assert(left inlist right)\n#{Exception.formatted_stacktrace}"
|
||||
quote do
|
||||
left = unquote(left)
|
||||
right = unquote(right)
|
||||
@@ -117,6 +118,14 @@ defmodule ExUnit.Assertions do
|
||||
end
|
||||
end
|
||||
|
||||
defp translate_assertion({ :inlist, _, [left, right] }, _else) do
|
||||
quote do
|
||||
left = unquote(left)
|
||||
right = unquote(right)
|
||||
assert(List.member?(right, left), "Expected #{inspect left} to be in #{inspect right}")
|
||||
end
|
||||
end
|
||||
|
||||
## Negative versions
|
||||
|
||||
defp translate_assertion({ op, _, [{ :=, _, [expected, received] }] }, _else) when negation?(op) do
|
||||
@@ -139,6 +148,8 @@ defmodule ExUnit.Assertions do
|
||||
end
|
||||
|
||||
defp translate_assertion({ op, _, [{ :in, _, [left, right] }] }, _else) when negation?(op) do
|
||||
IO.write "[WARNING] refute(left in right) is deprecated, please use refute(left inlist right)\n#{Exception.formatted_stacktrace}"
|
||||
|
||||
quote do
|
||||
left = unquote(left)
|
||||
right = unquote(right)
|
||||
@@ -146,6 +157,14 @@ defmodule ExUnit.Assertions do
|
||||
end
|
||||
end
|
||||
|
||||
defp translate_assertion({ op, _, [{ :inlist, _, [left, right] }] }, _else) when negation?(op) do
|
||||
quote do
|
||||
left = unquote(left)
|
||||
right = unquote(right)
|
||||
assert(!List.member?(right, left), "Expected #{inspect left} to not be in #{inspect right}")
|
||||
end
|
||||
end
|
||||
|
||||
## Fallback
|
||||
|
||||
defp translate_assertion(_expected, fallback) do
|
||||
@@ -311,7 +330,7 @@ defmodule ExUnit.Assertions do
|
||||
unquote(expr)
|
||||
flunk "Expected to catch #{unquote(kind)}, got nothing"
|
||||
rescue
|
||||
ExUnit.AssertionError = e -> raise(e)
|
||||
e in [ExUnit.AssertionError] -> raise(e)
|
||||
catch
|
||||
unquote(kind), what_we_got -> what_we_got
|
||||
end
|
||||
@@ -380,8 +399,8 @@ defmodule ExUnit.Assertions do
|
||||
flunk "This should raise an error"
|
||||
|
||||
"""
|
||||
@spec flunk, do: no_return
|
||||
@spec flunk(String.t), do: no_return
|
||||
@spec flunk :: no_return
|
||||
@spec flunk(String.t) :: no_return
|
||||
def flunk(message // "Epic Fail!") do
|
||||
raise ExUnit.AssertionError, message: message
|
||||
end
|
||||
|
||||
@@ -56,13 +56,13 @@ defmodule ExUnit.CLIFormatter do
|
||||
|
||||
def handle_cast({ :test_finished, _test_case, _test, nil }, config) do
|
||||
IO.write "."
|
||||
{ :noreply, config.increment_counter }
|
||||
{ :noreply, config.update_counter(&1 + 1) }
|
||||
end
|
||||
|
||||
def handle_cast({ :test_finished, test_case, test, failure }, config) do
|
||||
IO.write "F"
|
||||
{ :noreply, config.increment_counter.
|
||||
prepend_failures([{test_case, test, failure}]) }
|
||||
{ :noreply, config.update_counter(&1 + 1).
|
||||
update_failures([{test_case, test, failure}|&1]) }
|
||||
end
|
||||
|
||||
def handle_cast(_, _) do
|
||||
|
||||
@@ -10,12 +10,12 @@ defmodule ExUnit.Formatter do
|
||||
@type test :: atom
|
||||
@type result :: { kind :: atom, reason :: term, stacktrace :: list } | nil
|
||||
|
||||
defcallback suite_started(), do: any
|
||||
defcallback suite_finished(), do: non_neg_integer
|
||||
defcallback suite_started() :: any
|
||||
defcallback suite_finished() :: non_neg_integer
|
||||
|
||||
defcallback case_started(test_case), do: any
|
||||
defcallback case_finished(test_case), do: any
|
||||
defcallback case_started(test_case) :: any
|
||||
defcallback case_finished(test_case) :: any
|
||||
|
||||
defcallback test_started(test_case, test), do: any
|
||||
defcallback test_finished(test_case, test, result), do: any
|
||||
defcallback test_started(test_case, test) :: any
|
||||
defcallback test_finished(test_case, test, result) :: any
|
||||
end
|
||||
|
||||
@@ -3,7 +3,8 @@ defmodule ExUnit.Runner do
|
||||
|
||||
defrecord Config, formatter: ExUnit.CLIFormatter, max_cases: 4, taken_cases: 0
|
||||
|
||||
def run(config) do
|
||||
def run(opts) do
|
||||
config = ExUnit.Runner.Config.new opts
|
||||
config.formatter.suite_started
|
||||
loop config
|
||||
end
|
||||
@@ -44,13 +45,13 @@ defmodule ExUnit.Runner do
|
||||
wait_until_available config
|
||||
{ pid, :case_finished, test_case } ->
|
||||
config.formatter.case_finished(test_case)
|
||||
loop config.increment_taken_cases(-1)
|
||||
loop config.update_taken_cases(&1-1)
|
||||
end
|
||||
end
|
||||
|
||||
defp spawn_cases(config, cases) do
|
||||
Enum.each cases, spawn_case(config, &1)
|
||||
loop config.increment_taken_cases(length(cases))
|
||||
loop config.update_taken_cases(&1+length(cases))
|
||||
end
|
||||
|
||||
defp spawn_case(config, test_case) do
|
||||
|
||||
@@ -82,19 +82,19 @@ defmodule ExUnit.Server do
|
||||
end
|
||||
|
||||
def handle_cast({:add_async_case, name}, config) do
|
||||
{ :noreply, config.prepend_async_cases [name] }
|
||||
{ :noreply, config.update_async_cases [name|&1] }
|
||||
end
|
||||
|
||||
def handle_cast({:add_sync_case, name}, config) do
|
||||
{ :noreply, config.prepend_sync_cases [name] }
|
||||
{ :noreply, config.update_sync_cases [name|&1] }
|
||||
end
|
||||
|
||||
def handle_cast({:add_after_spawn, fun}, config) do
|
||||
{ :noreply, config.prepend_after_spawn [fun] }
|
||||
{ :noreply, config.update_after_spawn [fun|&1] }
|
||||
end
|
||||
|
||||
def handle_cast({:merge_options, options}, config) do
|
||||
{ :noreply, config.merge_options(options) }
|
||||
{ :noreply, config.update_options(&1 /> Keyword.merge(options)) }
|
||||
end
|
||||
|
||||
def handle_cast(request, config) do
|
||||
|
||||
@@ -103,26 +103,26 @@ defmodule ExUnit.AssertionsTest do
|
||||
end
|
||||
end
|
||||
|
||||
test :assert_in_when_is_member do
|
||||
true = assert 'foo' in ['foo', 'bar']
|
||||
test :assert_inlist_when_is_member do
|
||||
true = assert 'foo' inlist ['foo', 'bar']
|
||||
end
|
||||
|
||||
test :assert_in_when_is_not_member do
|
||||
test :assert_inlist_when_is_not_member do
|
||||
try do
|
||||
"This should never be tested" = assert 'foo' in 'bar'
|
||||
"This should never be tested" = assert 'foo' inlist 'bar'
|
||||
rescue
|
||||
error in [ExUnit.AssertionError] ->
|
||||
"Expected 'foo' to be in 'bar'" = error.message
|
||||
end
|
||||
end
|
||||
|
||||
test :refute_in_when_is_not_member do
|
||||
false = refute 'baz' in ['foo', 'bar']
|
||||
test :refute_inlist_when_is_not_member do
|
||||
false = refute 'baz' inlist ['foo', 'bar']
|
||||
end
|
||||
|
||||
test :refute_in_when_is_member do
|
||||
test :refute_inlist_when_is_member do
|
||||
try do
|
||||
"This should never be tested" = refute 'foo' in ['foo', 'bar']
|
||||
"This should never be tested" = refute 'foo' inlist ['foo', 'bar']
|
||||
rescue
|
||||
error in [ExUnit.AssertionError] ->
|
||||
"Expected 'foo' to not be in ['foo','bar']" = error.message
|
||||
|
||||
+41
-130
@@ -2,31 +2,22 @@ defrecord IEx.Config, binding: nil, cache: '', counter: 1, scope: nil, result: n
|
||||
|
||||
defmodule IEx do
|
||||
@moduledoc """
|
||||
This module implements interactive Elixir. It provides a main
|
||||
function, `start` which will either delegate to `tty` or `simple`.
|
||||
The former is meant for systems where tty is available and relies
|
||||
on it in order to work properly. This makes all control commands
|
||||
available in tty available to the developer.
|
||||
This module implements Interactive Elixir.
|
||||
|
||||
In case `tty` is not available (for example, Windows), a
|
||||
developer may invoke `simple` which starts a stripped
|
||||
down version.
|
||||
The interactive elixir needs to be set as the
|
||||
proper `-user` when starting the Erlang VM and
|
||||
so can be done with the help of IEx.CLI.
|
||||
|
||||
If possible, Elixir will start a tty (smart terminal)
|
||||
which makes all control commands available in tty
|
||||
available to the developer.
|
||||
|
||||
In case `tty` is not available (for example, Windows),
|
||||
a dumb terminal version is started instead.
|
||||
"""
|
||||
|
||||
@doc """
|
||||
Simply loads IEx application. Most of the times it is
|
||||
preloaded on demand, but if you want to pre-configure
|
||||
IEx, you need to preload it.
|
||||
"""
|
||||
def preload do
|
||||
:application.start(:iex)
|
||||
__MODULE__
|
||||
end
|
||||
|
||||
@doc """
|
||||
Registers a function to be invoked after IEx
|
||||
process is spawned. Requires IEx.preload to
|
||||
be invoked.
|
||||
Registers a function to be invoked after IEx process is spawned.
|
||||
"""
|
||||
def after_spawn(fun) when is_function(fun) do
|
||||
:application.set_env(:iex, :after_spawn, [fun|after_spawn])
|
||||
@@ -36,10 +27,15 @@ defmodule IEx do
|
||||
Returns registered after spawn callbacks.
|
||||
"""
|
||||
def after_spawn do
|
||||
case :application.get_env(:iex, :after_spawn) do
|
||||
{ :ok, list } -> list
|
||||
:undefined -> []
|
||||
end
|
||||
{ :ok, list } = :application.get_env(:iex, :after_spawn)
|
||||
list
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns true if IEx was properly started.
|
||||
"""
|
||||
def started? do
|
||||
match?({ :ok, true }, :application.get_env(:iex, :started))
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -53,107 +49,20 @@ defmodule IEx do
|
||||
Returns currently registered inspect options.
|
||||
"""
|
||||
def inspect_opts do
|
||||
case :application.get_env(:iex, :inspect_opts) do
|
||||
{ :ok, list } -> list
|
||||
:undefined -> [limit: 50]
|
||||
end
|
||||
{ :ok, opts } = :application.get_env(:iex, :inspect_opts)
|
||||
opts
|
||||
end
|
||||
|
||||
@doc """
|
||||
Interface to start IEx from CLI.
|
||||
"""
|
||||
def cli do
|
||||
run([remsh: get_remsh(:init.get_plain_arguments)])
|
||||
end
|
||||
|
||||
defp get_remsh(['--remsh',h|_]), do: list_to_binary(h)
|
||||
defp get_remsh([_|t]), do: get_remsh(t)
|
||||
defp get_remsh([]), do: nil
|
||||
|
||||
@doc """
|
||||
Runs IEx checking if tty is available or not.
|
||||
If so, invoke tty, otherwise go with the simple iex.
|
||||
"""
|
||||
def run(opts // []) when is_list(opts) do
|
||||
if tty_works? do
|
||||
tty(opts)
|
||||
else
|
||||
IO.puts "Warning: could not run smart terminal, falling back to dumb one"
|
||||
simple(opts)
|
||||
end
|
||||
end
|
||||
|
||||
# Check if tty works. If it does not, we fall back to the
|
||||
# simple/dumb terminal. This is starting the linked in
|
||||
# driver twice, it would be nice and appropriate if we had
|
||||
# to do it just once.
|
||||
defp tty_works? do
|
||||
# Dettack the error logger since we are spawning a new one
|
||||
:error_logger.tty(false)
|
||||
|
||||
try do
|
||||
port = Port.open { :spawn, :"tty_sl -c -e" }, [:eof]
|
||||
Port.close(port)
|
||||
catch
|
||||
_, _ -> false
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Starts IEx using a tty server.
|
||||
"""
|
||||
def tty(opts // []) when is_list(opts) do
|
||||
config = boot_config(opts)
|
||||
|
||||
remote =
|
||||
if remsh = opts[:remsh] do
|
||||
unless is_alive do
|
||||
raise ArgumentError, message: "In order to use --remsh, you need to name the current node"
|
||||
end
|
||||
if is_atom(remsh), do: remsh, else: binary_to_atom(remsh)
|
||||
end
|
||||
|
||||
function = fn ->
|
||||
# We are inside the new tty and in a new process,
|
||||
# reattach it the error logger.
|
||||
:error_logger.tty(true)
|
||||
start config
|
||||
end
|
||||
|
||||
# Dettach the error logger because we are going to unregister
|
||||
# the user process and start a new tty which will get control
|
||||
# over the standardio. Dettaching it here allows us to get rid
|
||||
# of warnings. We reattach it again when we get the new tty.
|
||||
:error_logger.tty(false)
|
||||
|
||||
# Unregister the user process, user_drv command below
|
||||
# will register the new one.
|
||||
unregister_user_process
|
||||
|
||||
args =
|
||||
if remote do
|
||||
{ remote, :erlang, :apply, [function, []] }
|
||||
else
|
||||
{ :erlang, :apply, [function, []] }
|
||||
end
|
||||
|
||||
:user_drv.start([:"tty_sl -c -e", args])
|
||||
end
|
||||
|
||||
@doc """
|
||||
Starts IEx simply using the current stdio.
|
||||
"""
|
||||
def simple(opts // []) when is_list(opts) do
|
||||
start boot_config(opts)
|
||||
end
|
||||
|
||||
# This is a callback invoked by Erlang shell utilities.
|
||||
# This is a callback invoked by Erlang shell utilities
|
||||
# when someone press Ctrl+G and adds 's Elixir-IEx'.
|
||||
@doc false
|
||||
def start(config // nil) do
|
||||
preload
|
||||
|
||||
def start(config // [], callback // fn -> end) do
|
||||
spawn fn ->
|
||||
config = config || boot_config([])
|
||||
config =
|
||||
case config do
|
||||
IEx.Config[] -> config
|
||||
opts -> boot_config(opts)
|
||||
end
|
||||
|
||||
case :init.notify_when_started(self()) do
|
||||
:started -> :ok
|
||||
@@ -162,18 +71,24 @@ defmodule IEx do
|
||||
|
||||
Process.flag(:trap_exit, true)
|
||||
|
||||
start_iex()
|
||||
callback.()
|
||||
|
||||
set_expand_fun()
|
||||
run_after_spawn()
|
||||
IEx.Loop.start(config)
|
||||
IEx.Server.start(config)
|
||||
end
|
||||
end
|
||||
|
||||
## Boot Helpers
|
||||
|
||||
defp boot_config(opts) do
|
||||
IO.puts "Interactive Elixir (#{System.version}) - press Ctrl+C to exit (type h() ENTER for help)"
|
||||
defp start_iex do
|
||||
:application.start(:elixir)
|
||||
:application.start(:iex)
|
||||
end
|
||||
|
||||
scope = :elixir.scope_for_eval(
|
||||
defp boot_config(opts) do
|
||||
scope = :elixir.scope_for_eval(
|
||||
file: "iex",
|
||||
delegate_locals_to: IEx.Helpers
|
||||
)
|
||||
@@ -188,10 +103,6 @@ defmodule IEx do
|
||||
]
|
||||
end
|
||||
|
||||
defp unregister_user_process do
|
||||
if is_pid(Process.whereis(:user)), do: Process.unregister :user
|
||||
end
|
||||
|
||||
defp set_expand_fun do
|
||||
gl = Process.group_leader
|
||||
glnode = node gl
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
defmodule IEx.CLI do
|
||||
@moduledoc false
|
||||
|
||||
@doc """
|
||||
Starts IEx checking if tty is available or not.
|
||||
If so, invoke tty, otherwise go with the simple iex.
|
||||
"""
|
||||
def start do
|
||||
if tty_works? do
|
||||
tty
|
||||
else
|
||||
:user.start
|
||||
IO.puts "Warning: could not run smart terminal, falling back to dumb one"
|
||||
IEx.start
|
||||
end
|
||||
end
|
||||
|
||||
# Check if tty works. If it does not, we fall back to the
|
||||
# simple/dumb terminal. This is starting the linked in
|
||||
# driver twice, it would be nice and appropriate if we had
|
||||
# to do it just once.
|
||||
defp tty_works? do
|
||||
try do
|
||||
port = Port.open { :spawn, :"tty_sl -c -e" }, [:eof]
|
||||
Port.close(port)
|
||||
catch
|
||||
_, _ -> false
|
||||
end
|
||||
end
|
||||
|
||||
defp tty do
|
||||
remote =
|
||||
if remsh = get_remsh(:init.get_plain_arguments) do
|
||||
unless is_alive do
|
||||
raise ArgumentError, message: "In order to use --remsh, you need to name the current node"
|
||||
end
|
||||
if is_atom(remsh), do: remsh, else: binary_to_atom(remsh)
|
||||
end
|
||||
|
||||
function = fn ->
|
||||
IEx.start([], fn -> Kernel.CLI.wait_until_finished end)
|
||||
end
|
||||
|
||||
args =
|
||||
if remote do
|
||||
{ remote, :erlang, :apply, [function, []] }
|
||||
else
|
||||
{ :erlang, :apply, [function, []] }
|
||||
end
|
||||
|
||||
:user_drv.start([:"tty_sl -c -e", args])
|
||||
end
|
||||
|
||||
defp get_remsh(['--remsh',h|_]), do: list_to_binary(h)
|
||||
defp get_remsh([_|t]), do: get_remsh(t)
|
||||
defp get_remsh([]), do: nil
|
||||
end
|
||||
+145
-54
@@ -12,13 +12,15 @@ defmodule IEx.Helpers do
|
||||
There are many other helpers available:
|
||||
|
||||
* `c/2` - compiles a file in the given path
|
||||
* `h/0`,`h/1`, `h/2` - prints help/documentation
|
||||
* `t/1`, `t/3` — prints type information
|
||||
* `s/1`, `s/3` — prints spec information
|
||||
* `h/0`, `h/1` - prints help/documentation
|
||||
* `t/1` — prints type information
|
||||
* `s/1` — prints spec information
|
||||
* `m/0` - prints loaded modules
|
||||
* `r/0` - recompiles and reloads the given module's source file
|
||||
* `r/0`, `r/1` - recompiles and reloads the given module's source file
|
||||
* `l/1` - loads given module beam code by purging the current version
|
||||
* `v/0` - prints all commands and values
|
||||
* `v/1` - retrieves nth value from console
|
||||
* `flush/0` — flush all messages sent to the shell
|
||||
|
||||
Help for functions in this module can be consulted
|
||||
directly from the command line, as an example, try:
|
||||
@@ -54,7 +56,7 @@ defmodule IEx.Helpers do
|
||||
"""
|
||||
def m do
|
||||
all = Enum.map :code.all_loaded, fn { mod, file } -> { inspect(mod), file } end
|
||||
sorted = List.sort(all)
|
||||
sorted = Enum.sort all
|
||||
size = Enum.reduce sorted, 0, fn({ mod, _ }, acc) -> max(byte_size(mod), acc) end
|
||||
format = "~-#{size}s ~s~n"
|
||||
|
||||
@@ -79,7 +81,7 @@ defmodule IEx.Helpers do
|
||||
Shows the documentation for IEx.Helpers.
|
||||
"""
|
||||
def h() do
|
||||
h(IEx.Helpers, :moduledoc)
|
||||
h(IEx.Helpers, [])
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -96,52 +98,55 @@ defmodule IEx.Helpers do
|
||||
|
||||
h receive/1
|
||||
h Enum.all?/2
|
||||
h Enum.all?
|
||||
|
||||
"""
|
||||
defmacro h({ :/, _, [{ fun, _, nil }, arity] }) do
|
||||
quote do
|
||||
h(unquote(fun), unquote(arity))
|
||||
end
|
||||
end
|
||||
|
||||
defmacro h({ :/, _, [{ { :., _, [mod, fun] }, _, [] }, arity] }) do
|
||||
quote do
|
||||
h(unquote(mod), unquote(fun), unquote(arity))
|
||||
end
|
||||
end
|
||||
|
||||
defmacro h(other) do
|
||||
defmacro h({ { :., _, [mod, fun] }, _, [] }) do
|
||||
quote do
|
||||
h(unquote(other), :moduledoc)
|
||||
h(unquote(mod), unquote(fun))
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Prints the documentation for the given function and arity.
|
||||
defmacro h({ :/, _, [{ fun, _, args }, arity] }) when args == [] or is_atom(args) do
|
||||
quote do
|
||||
h(unquote(fun), unquote(arity))
|
||||
end
|
||||
end
|
||||
|
||||
The function may either be a function defined inside `IEx.Helpers`
|
||||
or in `Kernel`. To see functions from other module, use
|
||||
`h/3` instead.
|
||||
defmacro h({ name, _, args }) when args == [] or is_atom(args) do
|
||||
quote do
|
||||
h(unquote(__MODULE__), unquote(name))
|
||||
h(Kernel, unquote(name))
|
||||
end
|
||||
end
|
||||
|
||||
## Examples
|
||||
defmacro h(other) do
|
||||
quote do
|
||||
h(unquote(other), [])
|
||||
end
|
||||
end
|
||||
|
||||
h(:h, 2)
|
||||
#=> Prints documentation for this function
|
||||
|
||||
"""
|
||||
@doc false
|
||||
def h(:h, 1) do
|
||||
h(__MODULE__, :h, 1)
|
||||
end
|
||||
|
||||
def h(function, arity) when is_atom(function) and is_integer(arity) do
|
||||
if function_exported?(__MODULE__, function, arity) do
|
||||
if function_exported?(__MODULE__, function, arity) or
|
||||
macro_exported?(__MODULE__, function, arity) do
|
||||
h(__MODULE__, function, arity)
|
||||
else
|
||||
h(Kernel, function, arity)
|
||||
end
|
||||
end
|
||||
|
||||
def h(module, :moduledoc) when is_atom(module) do
|
||||
def h(module, []) when is_atom(module) do
|
||||
case Code.ensure_loaded(module) do
|
||||
{ :module, _ } ->
|
||||
case module.__info__(:moduledoc) do
|
||||
@@ -158,9 +163,20 @@ defmodule IEx.Helpers do
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Shows the documentation for the `function/arity` in `module`.
|
||||
"""
|
||||
def h(module, function) when is_atom(module) and is_atom(function) do
|
||||
lc {{f, arity}, _line, _type, _args, doc } inlist module.__info__(:docs),
|
||||
f == function and doc != false do
|
||||
h(module, function, arity)
|
||||
end
|
||||
:ok
|
||||
end
|
||||
|
||||
def h(_, _) do
|
||||
IO.puts "Invalid h helper argument"
|
||||
h()
|
||||
end
|
||||
|
||||
@doc false
|
||||
def h(module, function, arity) when is_atom(module) and is_atom(function) and is_integer(arity) do
|
||||
if docs = module.__info__(:docs) do
|
||||
doc =
|
||||
@@ -223,6 +239,7 @@ defmodule IEx.Helpers do
|
||||
|
||||
t(Enum)
|
||||
t(Enum.t/0)
|
||||
t(Enum.t)
|
||||
|
||||
"""
|
||||
defmacro t({ :/, _, [{ { :., _, [mod, fun] }, _, [] }, arity] }) do
|
||||
@@ -231,15 +248,34 @@ defmodule IEx.Helpers do
|
||||
end
|
||||
end
|
||||
|
||||
defmacro t(module) do
|
||||
defmacro t({ { :., _, [mod, fun] }, _, [] }) do
|
||||
quote do
|
||||
t(unquote(module), :all)
|
||||
t(unquote(mod), unquote(fun))
|
||||
end
|
||||
end
|
||||
|
||||
defmacro t(module) do
|
||||
quote do
|
||||
t(unquote(module), [])
|
||||
end
|
||||
end
|
||||
|
||||
@doc false
|
||||
def t(module, :all) do
|
||||
def t(module, type) when is_atom(type) do
|
||||
types = lc {_, {t, _, _args}} = typespec inlist Kernel.Typespec.beam_types(module),
|
||||
t == type do
|
||||
print_type(typespec)
|
||||
typespec
|
||||
end
|
||||
|
||||
if types == [] do
|
||||
IO.puts "No types for #{inspect module}.#{type} have been found"
|
||||
end
|
||||
|
||||
:ok
|
||||
end
|
||||
|
||||
def t(module, []) do
|
||||
types = lc type inlist Kernel.Typespec.beam_types(module), do: print_type(type)
|
||||
|
||||
if types == [] do
|
||||
@@ -270,6 +306,10 @@ defmodule IEx.Helpers do
|
||||
## Examples
|
||||
|
||||
s(Enum)
|
||||
s(Enum.all?)
|
||||
s(Enum.all?/2)
|
||||
s(list_to_atom)
|
||||
s(list_to_atom/1)
|
||||
|
||||
"""
|
||||
defmacro s({ :/, _, [{ { :., _, [mod, fun] }, _, [] }, arity] }) do
|
||||
@@ -278,15 +318,47 @@ defmodule IEx.Helpers do
|
||||
end
|
||||
end
|
||||
|
||||
defmacro s({ { :., _, [mod, fun] }, _, [] }) do
|
||||
quote do
|
||||
s(unquote(mod), unquote(fun))
|
||||
end
|
||||
end
|
||||
|
||||
defmacro s({ fun, _, args }) when args == [] or is_atom(args) do
|
||||
quote do
|
||||
s(Kernel, unquote(fun))
|
||||
end
|
||||
end
|
||||
|
||||
defmacro s({ :/, _, [{ fun, _, args }, arity] }) when args == [] or is_atom(args) do
|
||||
quote do
|
||||
s(Kernel, unquote(fun), unquote(arity))
|
||||
end
|
||||
end
|
||||
|
||||
defmacro s(module) do
|
||||
quote do
|
||||
s(unquote(module), :all)
|
||||
s(unquote(module), [])
|
||||
end
|
||||
end
|
||||
|
||||
@doc false
|
||||
def s(module, :all) do
|
||||
specs = lc spec inlist Kernel.Typespec.beam_specs(module), do: print_spec(spec)
|
||||
def s(module, function) when is_atom(function) do
|
||||
specs = lc {_kind, {{f, _arity}, _spec}} = spec inlist beam_specs(module),
|
||||
f == function do
|
||||
print_spec(spec)
|
||||
spec
|
||||
end
|
||||
|
||||
if specs == [] do
|
||||
IO.puts "No specs for #{inspect module}.#{function} have been found"
|
||||
end
|
||||
|
||||
:ok
|
||||
end
|
||||
|
||||
def s(module, []) do
|
||||
specs = lc spec inlist beam_specs(module), do: print_spec(spec)
|
||||
|
||||
if specs == [] do
|
||||
IO.puts "No specs for #{inspect module} have been found"
|
||||
@@ -295,39 +367,37 @@ defmodule IEx.Helpers do
|
||||
:ok
|
||||
end
|
||||
|
||||
@doc """
|
||||
Prints the specs for a given function.
|
||||
|
||||
## Examples
|
||||
|
||||
s(Enum.all?/2)
|
||||
s(Enum.t/0)
|
||||
|
||||
"""
|
||||
@doc false
|
||||
def s(module, function, arity) do
|
||||
spec = List.keyfind(Kernel.Typespec.beam_specs(module), { function, arity }, 0)
|
||||
|
||||
if spec do
|
||||
specs = lc {_kind, {{f, a}, _spec}} = spec inlist beam_specs(module),
|
||||
f == function and a == arity do
|
||||
print_spec(spec)
|
||||
else
|
||||
IO.puts "No specs for #{inspect module}.#{function}/#{arity} have been found"
|
||||
spec
|
||||
end
|
||||
|
||||
if specs == [] do
|
||||
IO.puts "No specs for #{inspect module}.#{function} have been found"
|
||||
end
|
||||
|
||||
:ok
|
||||
end
|
||||
|
||||
defp beam_specs(module) do
|
||||
specs = Enum.map(Kernel.Typespec.beam_specs(module), {:spec, &1})
|
||||
callbacks = Enum.map(Kernel.Typespec.beam_callbacks(module), {:callback, &1})
|
||||
List.concat(specs, callbacks)
|
||||
end
|
||||
|
||||
defp print_type({ kind, type }) do
|
||||
ast = Kernel.Typespec.type_to_ast(type)
|
||||
IO.puts "@#{kind} #{Macro.to_binary(ast)}"
|
||||
true
|
||||
end
|
||||
|
||||
defp print_spec({ { name, _arity }, specs }) do
|
||||
defp print_spec({kind, { { name, _arity }, specs }}) do
|
||||
Enum.each specs, fn(spec) ->
|
||||
{ args, result } = Kernel.Typespec.spec_to_ast(spec)
|
||||
bin_args = Macro.to_binary { name, 0, args }
|
||||
bin_result = Macro.to_binary result
|
||||
IO.puts "@spec #{bin_args}, do: #{bin_result}"
|
||||
binary = Macro.to_binary Kernel.Typespec.spec_to_ast(name, spec)
|
||||
IO.puts "@#{kind} #{binary}"
|
||||
end
|
||||
true
|
||||
end
|
||||
@@ -370,6 +440,27 @@ defmodule IEx.Helpers do
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Purges and reloads specified module
|
||||
"""
|
||||
def l(module) do
|
||||
:code.purge(module)
|
||||
:code.load_file(module)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Flushes all messages sent to the shell and prints them out
|
||||
"""
|
||||
def flush do
|
||||
receive do
|
||||
msg ->
|
||||
IO.inspect(msg)
|
||||
flush
|
||||
after 0 ->
|
||||
:ok
|
||||
end
|
||||
end
|
||||
|
||||
defp iex_reloaded do
|
||||
Process.get(:iex_reloaded) || :ordsets.new
|
||||
end
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
defmodule IEx.Loop do
|
||||
defmodule IEx.Server do
|
||||
@moduledoc false
|
||||
|
||||
def start(config) do
|
||||
IO.puts "Interactive Elixir (#{System.version}) - press Ctrl+C to exit (type h() ENTER for help)"
|
||||
Process.put :iex_history, []
|
||||
{ _, _, scope } = :elixir.eval('import IEx.Helpers', [], 0, config.scope)
|
||||
do_loop(config.scope(scope))
|
||||
@@ -21,7 +22,7 @@ defmodule IEx.Loop do
|
||||
|
||||
config = config.result(result)
|
||||
update_history(config.cache(code).scope(nil))
|
||||
config.increment_counter.cache('').binding(new_binding).scope(scope)
|
||||
config.update_counter(&1+1).cache('').binding(new_binding).scope(scope)
|
||||
rescue
|
||||
TokenMissingError ->
|
||||
config.cache(code)
|
||||
+5
-1
@@ -6,6 +6,10 @@ defmodule IEx.Mixfile do
|
||||
end
|
||||
|
||||
def application do
|
||||
[]
|
||||
[env: [
|
||||
after_spawn: [],
|
||||
inspect_opts: [limit: 50],
|
||||
started: true
|
||||
]]
|
||||
end
|
||||
end
|
||||
@@ -67,8 +67,8 @@ defmodule IEx.AutocompleteTest do
|
||||
test :elixir_root_completion do
|
||||
{:yes, '', list} = expand('')
|
||||
assert is_list(list)
|
||||
assert 'h/1' in list
|
||||
assert 'Elixir' in list
|
||||
assert 'h/1' inlist list
|
||||
assert 'Elixir' inlist list
|
||||
end
|
||||
|
||||
test :elixir_kernel_completion do
|
||||
@@ -77,13 +77,13 @@ defmodule IEx.AutocompleteTest do
|
||||
|
||||
test :elixir_proxy do
|
||||
{:yes, '', list} = expand('E')
|
||||
assert 'Elixir' in list
|
||||
assert 'Elixir' inlist list
|
||||
end
|
||||
|
||||
test :elixir_erlang_module_root_completion do
|
||||
{:yes, '', list} = expand(':')
|
||||
assert is_list(list)
|
||||
assert 'lists' in list
|
||||
assert 'lists' inlist list
|
||||
end
|
||||
|
||||
test :completion_inside_expression do
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
IEx.preload
|
||||
:application.start(:iex)
|
||||
ExUnit.start []
|
||||
@@ -1,10 +1,6 @@
|
||||
defmodule Mix.CLI do
|
||||
@moduledoc false
|
||||
|
||||
defmacrop exceptions do
|
||||
[Mix.Error, Mix.NoTaskError, Mix.InvalidTaskError, Mix.NoProjectError, Mix.OutOfDateDepsError]
|
||||
end
|
||||
|
||||
@doc """
|
||||
Runs Mix according to the command line arguments.
|
||||
"""
|
||||
@@ -47,11 +43,13 @@ defmodule Mix.CLI do
|
||||
rescue
|
||||
# We only rescue exceptions in the mix namespace, all
|
||||
# others pass through and will explode on the users face
|
||||
exception in exceptions ->
|
||||
if msg = exception.message do
|
||||
Mix.shell.error msg
|
||||
exception ->
|
||||
if function_exported?(exception, :mix_error, 0) do
|
||||
if msg = exception.message, do: Mix.shell.error "** (Mix) #{msg}"
|
||||
else
|
||||
raise(exception)
|
||||
end
|
||||
exit(1)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -92,7 +92,7 @@ defmodule Mix.Deps do
|
||||
do: "the dependency is not available, run `mix deps.get`"
|
||||
|
||||
defp inspect_kw(list) do
|
||||
middle = lc { key, value } inlist Enum.qsort(list), do: "#{key}: #{inspect value, raw: true}"
|
||||
middle = lc { key, value } inlist Enum.sort(list), do: "#{key}: #{inspect value, raw: true}"
|
||||
"[ " <> Enum.join(middle, ",\n ") <> " ]"
|
||||
end
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ defmodule Mix.Deps.Lock do
|
||||
Receives a keyword list and writes it to the disk.
|
||||
"""
|
||||
def write(file // lockfile, dict) do
|
||||
lines = lc { app, rev } inlist Enum.qsort(dict), rev != nil do
|
||||
lines = lc { app, rev } inlist Enum.sort(dict), rev != nil do
|
||||
%b("#{app}": #{inspect rev, raw: true})
|
||||
end
|
||||
|
||||
|
||||
@@ -58,7 +58,7 @@ defmodule Mix.Deps.Project do
|
||||
|
||||
defp with_scm_and_status(other, _scms) do
|
||||
raise Mix.Error, message: %b(dependency specified in the wrong format: #{inspect other}, ) <>
|
||||
%b(expected { "app", "requirement", scm: "location" })
|
||||
%b(expected { :app, scm: "location" } | { :app, "requirement", scm: "location" })
|
||||
end
|
||||
|
||||
defp status(scm, app, req, opts) do
|
||||
|
||||
@@ -1,22 +1,21 @@
|
||||
defexception Mix.NoTaskError, task: nil do
|
||||
defexception Mix.NoTaskError, task: nil, mix_error: true do
|
||||
def message(exception) do
|
||||
"The task #{task(exception)} could not be found"
|
||||
end
|
||||
end
|
||||
|
||||
defexception Mix.InvalidTaskError, task: nil do
|
||||
defexception Mix.InvalidTaskError, task: nil, mix_error: true do
|
||||
def message(exception) do
|
||||
"The task #{task(exception)} does not respond to run/1"
|
||||
end
|
||||
end
|
||||
|
||||
defexception Mix.NoProjectError,
|
||||
defexception Mix.NoProjectError, mix_error: true,
|
||||
message: "Could not find a Mix.Project"
|
||||
|
||||
defexception Mix.Error,
|
||||
message: nil
|
||||
defexception Mix.Error, mix_error: true, message: nil
|
||||
|
||||
defexception Mix.OutOfDateDepsError, env: nil do
|
||||
defexception Mix.OutOfDateDepsError, mix_error: true, env: nil do
|
||||
def message(exception) do
|
||||
"Some dependencies are out of date, please run `MIX_ENV=#{exception.env} mix deps.get` to proceed"
|
||||
end
|
||||
|
||||
@@ -13,13 +13,13 @@ defmodule Mix.SCM do
|
||||
Returns an Elixir term that contains relevant SCM
|
||||
information for printing.
|
||||
"""
|
||||
defcallback format(opts), do: term
|
||||
defcallback format(opts) :: term
|
||||
|
||||
@doc """
|
||||
Returns an Elixir term that contains relevant SCM
|
||||
lock information for printing.
|
||||
"""
|
||||
defcallback format_lock(lock), do: term
|
||||
defcallback format_lock(lock) :: term
|
||||
|
||||
@doc """
|
||||
This behavior function receives a keyword list of `opts`
|
||||
@@ -34,13 +34,13 @@ defmodule Mix.SCM do
|
||||
sense for the Git SCM, it will return an update list of options
|
||||
while other SCMs would simply return nil.
|
||||
"""
|
||||
defcallback accepts_options(opts), do: opts | nil
|
||||
defcallback accepts_options(opts) :: opts | nil
|
||||
|
||||
@doc """
|
||||
This behavior function returns a boolean if the
|
||||
dependency is available.
|
||||
"""
|
||||
defcallback checked_out?(opts), do: boolean
|
||||
defcallback checked_out?(opts) :: boolean
|
||||
|
||||
@doc """
|
||||
This behavior function checks out dependencies.
|
||||
@@ -50,7 +50,7 @@ defmodule Mix.SCM do
|
||||
no lock is given and the repository can be checked out
|
||||
to the latest version.
|
||||
"""
|
||||
defcallback checkout(opts), do: any
|
||||
defcallback checkout(opts) :: any
|
||||
|
||||
@doc """
|
||||
This behavior function updates dependencies. It may be
|
||||
@@ -62,7 +62,7 @@ defmodule Mix.SCM do
|
||||
|
||||
It must return the current lock.
|
||||
"""
|
||||
defcallback update(opts), do: any
|
||||
defcallback update(opts) :: any
|
||||
|
||||
@doc """
|
||||
This behavior function checks if the dependency is locked and
|
||||
@@ -70,19 +70,19 @@ defmodule Mix.SCM do
|
||||
SCMs do not require a lock, for such, this function can simply
|
||||
return true.
|
||||
"""
|
||||
defcallback matches_lock?(opts), do: boolean
|
||||
defcallback matches_lock?(opts) :: boolean
|
||||
|
||||
@doc """
|
||||
Receives two options and must return true if the refer to the
|
||||
same repository. The options are guaranteed to belong to the
|
||||
same SCM.
|
||||
"""
|
||||
defcallback equals?(opts1 :: opts, opts2 :: opts), do: boolean
|
||||
defcallback equals?(opts1 :: opts, opts2 :: opts) :: boolean
|
||||
|
||||
@doc """
|
||||
This behavior function should clean the given dependency.
|
||||
"""
|
||||
defcallback clean(opts), do: any
|
||||
defcallback clean(opts) :: any
|
||||
|
||||
@doc """
|
||||
Returns all available SCM.
|
||||
|
||||
+14
-10
@@ -38,20 +38,20 @@ defmodule Mix.SCM.Git do
|
||||
def checkout(opts) do
|
||||
path = opts[:path]
|
||||
location = opts[:git]
|
||||
maybe_error System.cmd(%b[git clone --quiet --no-checkout "#{location}" "#{path}"])
|
||||
command = %b[git clone --no-checkout "#{location}" "#{path}"]
|
||||
|
||||
if checked_out?(opts) do
|
||||
File.cd! path, fn -> do_checkout(opts) end
|
||||
end
|
||||
run_cmd_or_raise(command)
|
||||
File.cd! path, fn -> do_checkout(opts) end
|
||||
end
|
||||
|
||||
def update(opts) do
|
||||
File.cd! opts[:path], fn ->
|
||||
command = "git fetch --force --quiet"
|
||||
command = "git fetch --force"
|
||||
if opts[:tag] do
|
||||
command = command <> " --tags"
|
||||
end
|
||||
maybe_error System.cmd(command)
|
||||
|
||||
run_cmd_or_raise(command)
|
||||
do_checkout(opts)
|
||||
end
|
||||
end
|
||||
@@ -64,10 +64,10 @@ defmodule Mix.SCM.Git do
|
||||
|
||||
defp do_checkout(opts) do
|
||||
ref = get_lock_rev(opts[:lock]) || get_opts_rev(opts)
|
||||
maybe_error System.cmd("git checkout --quiet #{ref}")
|
||||
run_cmd_or_raise "git checkout --quiet #{ref}"
|
||||
|
||||
if opts[:submodules] do
|
||||
maybe_error System.cmd("git submodule update --init --recursive")
|
||||
run_cmd_or_raise "git submodule update --init --recursive"
|
||||
end
|
||||
|
||||
get_lock(opts, true)
|
||||
@@ -120,6 +120,10 @@ defmodule Mix.SCM.Git do
|
||||
nil
|
||||
end
|
||||
|
||||
defp maybe_error(""), do: :ok
|
||||
defp maybe_error(other), do: Mix.shell.error(other)
|
||||
defp run_cmd_or_raise(command) do
|
||||
if Mix.shell.cmd(command) != 0 do
|
||||
raise Mix.Error, message: "command `#{command}` failed"
|
||||
end
|
||||
true
|
||||
end
|
||||
end
|
||||
@@ -92,11 +92,11 @@ defmodule Mix.Server do
|
||||
|
||||
def handle_cast({ :push_project, name, project }, config) do
|
||||
project = Keyword.merge(project, config.post_config)
|
||||
{ :noreply, config.post_config([]).prepend_projects [ { name, project } ] }
|
||||
{ :noreply, config.post_config([]).update_projects [{ name, project }|&1] }
|
||||
end
|
||||
|
||||
def handle_cast({ :post_config, value }, config) do
|
||||
{ :noreply, config.merge_post_config value }
|
||||
{ :noreply, config.update_post_config(&1 /> Keyword.merge(value)) }
|
||||
end
|
||||
|
||||
def handle_cast({ :add_scm, mod }, config) do
|
||||
@@ -104,7 +104,7 @@ defmodule Mix.Server do
|
||||
end
|
||||
|
||||
def handle_cast({ :mixfile_cache, app, new }, config) do
|
||||
{ :noreply, config.merge_mixfile([{ app, new }]) }
|
||||
{ :noreply, config.update_mixfile(&1 /> Keyword.merge([{ app, new }])) }
|
||||
end
|
||||
|
||||
def handle_cast(:clear_mixfile_cache, config) do
|
||||
|
||||
@@ -8,15 +8,41 @@ defmodule Mix.Shell do
|
||||
@doc """
|
||||
Informs the given message.
|
||||
"""
|
||||
defcallback info(message :: binary), do: any
|
||||
defcallback info(message :: binary) :: any
|
||||
|
||||
@doc """
|
||||
Warns about the given error message.
|
||||
"""
|
||||
defcallback error(message :: binary), do: any
|
||||
defcallback error(message :: binary) :: any
|
||||
|
||||
@doc """
|
||||
Asks the user for confirmation.
|
||||
"""
|
||||
defcallback yes?(message :: binary), do: any
|
||||
defcallback yes?(message :: binary) :: any
|
||||
|
||||
@doc """
|
||||
Executes the given command and returns
|
||||
its exit status.
|
||||
"""
|
||||
defcallback cmd(command :: binary) :: integer
|
||||
|
||||
@doc """
|
||||
An implementation of the command callback that
|
||||
is shared accross different shells.
|
||||
"""
|
||||
def cmd(command, callback) do
|
||||
port = Port.open({ :spawn, to_char_list(command) },
|
||||
[:stream, :binary, :exit_status, :hide])
|
||||
do_cmd(port, callback)
|
||||
end
|
||||
|
||||
defp do_cmd(port, callback) do
|
||||
receive do
|
||||
{ ^port, { :data, data } } ->
|
||||
callback.(data)
|
||||
do_cmd(port, callback)
|
||||
{ ^port, { :exit_status, status } } ->
|
||||
status
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -4,6 +4,16 @@ defmodule Mix.Shell.IO do
|
||||
It simply prints messages to stdio and stderr.
|
||||
"""
|
||||
|
||||
@behaviour Mix.Shell
|
||||
|
||||
@doc """
|
||||
Executes the given command and prints its output
|
||||
to stdout as it comes.
|
||||
"""
|
||||
def cmd(command) do
|
||||
Mix.Shell.cmd(command, IO.write(&1))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Writes a message to the shell followed by new line.
|
||||
"""
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user