Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8cede65b0f | ||
|
|
bb2e1d5d7c | ||
|
|
e4f5f9d9ed | ||
|
|
e801cbd380 | ||
|
|
7894ba0fc1 | ||
|
|
ad37d5f67a | ||
|
|
40efeb48d7 | ||
|
|
d03d647560 | ||
|
|
c3b58eef16 | ||
|
|
32764b3370 | ||
|
|
2104b292c1 | ||
|
|
e0bbd47d7b | ||
|
|
743213d8c1 | ||
|
|
2db5b9c76c | ||
|
|
ba6a23a1a4 | ||
|
|
b9692fc2fe | ||
|
|
8a5f3f3a53 | ||
|
|
c0a42c95e0 | ||
|
|
9cad7eb1e2 | ||
|
|
fcb1a610de | ||
|
|
04d96f7f18 | ||
|
|
d117dbf04c | ||
|
|
ce489eae2b | ||
|
|
02e63e4849 | ||
|
|
cae328112c | ||
|
|
b2931135d0 | ||
|
|
bddaed1284 | ||
|
|
f78433c892 | ||
|
|
f1bbd3f37e | ||
|
|
b159b1c6bf | ||
|
|
e6c75bbe98 | ||
|
|
c6a012602c | ||
|
|
cc89cf942f | ||
|
|
73d7de7bc9 | ||
|
|
1ba159ddd7 | ||
|
|
8c3d8fc676 | ||
|
|
8ac3844955 | ||
|
|
1b15972d07 | ||
|
|
4a2d773e34 | ||
|
|
8185ff7333 | ||
|
|
2263d44ccf | ||
|
|
46f3696799 | ||
|
|
9b276f52d9 | ||
|
|
175a2dc1eb | ||
|
|
aba27a27e3 | ||
|
|
1f56b673bd | ||
|
|
41dbbbf714 | ||
|
|
1331f23a27 | ||
|
|
52a5f5c9c9 | ||
|
|
1ae30a56e2 | ||
|
|
30bfd4736d | ||
|
|
e9334fb7dc | ||
|
|
bd8648d490 | ||
|
|
ec68b7dd8a | ||
|
|
a5539beb37 | ||
|
|
47ce7e3fd8 | ||
|
|
1113d3cfc9 | ||
|
|
f160f3ee6f | ||
|
|
1bf06bbc1a | ||
|
|
8b981efecd | ||
|
|
f95e4512b5 | ||
|
|
12d14edab5 | ||
|
|
caa50368a7 | ||
|
|
a144d27886 | ||
|
|
413fcfdd65 | ||
|
|
78c6c7201f | ||
|
|
76cf3e5bba | ||
|
|
5d2336d23e | ||
|
|
8280de5fa9 | ||
|
|
8cadba5ea4 | ||
|
|
3750bcc8d1 | ||
|
|
3fac73ea17 | ||
|
|
01f9518899 | ||
|
|
11eb2d6d3c | ||
|
|
c00381780f | ||
|
|
b325e29d23 | ||
|
|
e2e1747acb | ||
|
|
84501090ce | ||
|
|
c9dead1fc1 | ||
|
|
4906fac3cf | ||
|
|
1c827af937 | ||
|
|
06d9b26f94 | ||
|
|
aecb1b3716 | ||
|
|
a322635971 | ||
|
|
88fdb20eb0 | ||
|
|
6d5c2e1d22 | ||
|
|
437ef403c4 | ||
|
|
d5b6340977 | ||
|
|
99befc6715 | ||
|
|
e97e2b310e | ||
|
|
d305c96762 | ||
|
|
e99f623b03 | ||
|
|
562e7e391e | ||
|
|
69bccec7ae | ||
|
|
e65b18b203 | ||
|
|
3b33478cc0 | ||
|
|
4d427f9f50 | ||
|
|
ea3775ecf0 | ||
|
|
7607051224 | ||
|
|
4fdec2c780 | ||
|
|
335ba59712 | ||
|
|
84e04b6a8b | ||
|
|
06d1b47cc2 | ||
|
|
c21c00d42f | ||
|
|
097a427cb7 | ||
|
|
8c317e3d43 | ||
|
|
f7490bd5af | ||
|
|
6aec85fae5 | ||
|
|
cc22787c65 | ||
|
|
c547d70c5f | ||
|
|
a35b3f8ac1 | ||
|
|
856cd70d86 | ||
|
|
beac96cd55 | ||
|
|
edf34edf2a | ||
|
|
f4daf0a39d | ||
|
|
a67df523ce | ||
|
|
154e73b0d4 | ||
|
|
18fd04010e | ||
|
|
a6d86d9f44 | ||
|
|
a2b79e4331 | ||
|
|
6a9a3b3efe | ||
|
|
591b95bfb9 | ||
|
|
833a153b33 | ||
|
|
fdc2657af8 | ||
|
|
f2a284e268 | ||
|
|
d75fb11dc4 | ||
|
|
6edab89372 | ||
|
|
078da93886 | ||
|
|
ee380fe79c | ||
|
|
ce26312803 | ||
|
|
7c78f22a95 | ||
|
|
925ab81ca2 | ||
|
|
4301d14555 | ||
|
|
523170228a | ||
|
|
2b9d67dd9d | ||
|
|
7eae5a5e8e | ||
|
|
87391a5832 | ||
|
|
b9dbd56f3a | ||
|
|
215a28ceed | ||
|
|
f95529b7b3 | ||
|
|
21d2fec7aa | ||
|
|
6afb2684c6 | ||
|
|
93f426fcff | ||
|
|
94b4831709 | ||
|
|
b497632540 | ||
|
|
d1cbefdbe0 | ||
|
|
2704ba1a07 | ||
|
|
3c15bcc36f | ||
|
|
40cda34a23 | ||
|
|
95bea1a4c7 | ||
|
|
85fce7aaef | ||
|
|
fc76de175c | ||
|
|
bae135c0ef | ||
|
|
e653dece8d | ||
|
|
71368b6590 | ||
|
|
50960e0e88 | ||
|
|
03bb0831e5 | ||
|
|
194647ef5a | ||
|
|
1514ffbabc | ||
|
|
c3da51f32c | ||
|
|
91b43f244c | ||
|
|
fa10442606 | ||
|
|
35fd0bf896 | ||
|
|
7a8fa34020 | ||
|
|
9a949702f1 | ||
|
|
e8ea3e834c | ||
|
|
2f3289aaef | ||
|
|
2de1102544 | ||
|
|
445398b7bc | ||
|
|
e2e6e5d0d4 | ||
|
|
d33eff2aec | ||
|
|
9a2ae1bac7 | ||
|
|
31e1965ea2 | ||
|
|
dc169dc783 | ||
|
|
a617628848 | ||
|
|
431c8739cc | ||
|
|
96c84370e3 | ||
|
|
4717c9a10b | ||
|
|
78230df154 | ||
|
|
b1e130cace | ||
|
|
354be2383d | ||
|
|
3b63ce2ab1 | ||
|
|
d220618f12 | ||
|
|
5c4a5db828 | ||
|
|
146dddf0b5 | ||
|
|
b2c8669c4c | ||
|
|
318454c092 | ||
|
|
0aa4c7227e | ||
|
|
a17140ea50 | ||
|
|
e03bf4d7d8 | ||
|
|
85a9d70544 | ||
|
|
1cfdb00c4d | ||
|
|
b94def32d2 | ||
|
|
f438630e82 | ||
|
|
293044c168 | ||
|
|
e975336d5d | ||
|
|
43b8a26b36 | ||
|
|
26d35eead1 |
+4
-2
@@ -1,5 +1,5 @@
|
||||
/.eunit/*
|
||||
/.full
|
||||
/.release
|
||||
/lib/*/ebin/*
|
||||
/lib/*/tmp
|
||||
/lib/*/test/tmp
|
||||
@@ -10,4 +10,6 @@
|
||||
/deps/*
|
||||
/ebin
|
||||
/rel/elixir
|
||||
erl_crash.dump
|
||||
erl_crash.dump
|
||||
.dialyzer_plt
|
||||
.dialyzer.base_plt
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
language: erlang
|
||||
script: "make compile && make .full test"
|
||||
script: "make compile && make .release test"
|
||||
notifications:
|
||||
irc: "irc.freenode.org#elixir-lang"
|
||||
recipients:
|
||||
|
||||
@@ -1,3 +1,34 @@
|
||||
# v0.7.1 (2012-11-18)
|
||||
|
||||
* enhancements
|
||||
* [IEx] Only show documented functions and also show docs for default generated functions
|
||||
* [IO] Add `IO.binread`, `IO.binwrite` and `IO.binreadline` to handle raw binary file operations
|
||||
* [ExUnit] Add support for user configuration at `HOME/.ex_unit.exs`
|
||||
* [ExUnit] Add support for custom formatters via a well-defined behaviour
|
||||
* [Kernel] Add support for `defrecordp`
|
||||
* [Kernel] Improved dialyzer support
|
||||
* [Kernel] Improved error messages when creating functions with aliases names
|
||||
* [Mix] Improve SCM behaviour to allow more robust integration
|
||||
* [Mix] Changing deps information on `mix.exs` forces users to fetch new dependencies
|
||||
* [Mix] Support (parallel) requires on mix run
|
||||
* [Mix] Support `-q` when running tests to compile only changed files
|
||||
* [String] Support `String.downcase` and `String.upcase` according to Unicode 6.2.0
|
||||
* [String] Add support for graphemes in `String.length`, `String.at` and others
|
||||
* [Typespec] Support `@opaque` as attribute
|
||||
* [Typespec] Define a default type `t` for protocols and records
|
||||
* [Typespec] Add support for the access protocol in typespecs
|
||||
|
||||
* bug fix
|
||||
* [Kernel] Fix an issue where variables inside clauses remained unassigned
|
||||
* [Kernel] Ensure `defoverridable` functions can be referred in many clauses
|
||||
* [Kernel] Allow keywords as function names when following a dot (useful when integrating with erlang libraries)
|
||||
* [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
|
||||
|
||||
# v0.7.0 (2012-10-20)
|
||||
|
||||
* enhancements
|
||||
|
||||
@@ -1,21 +1,22 @@
|
||||
REBAR:=$(shell echo `pwd`/rebar)
|
||||
ELIXIRC:=bin/elixirc --ignore-module-conflict $(ELIXIRC_OPTS)
|
||||
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
|
||||
FULLFLAG:=.full
|
||||
VERSION:=0.7.0
|
||||
VERSION:=0.7.1
|
||||
RELEASE_FLAG:=.release
|
||||
|
||||
.PHONY: 1
|
||||
.NOTPARALLEL: compile
|
||||
|
||||
#==> Templates
|
||||
define TASK_TEMPLATE
|
||||
|
||||
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
|
||||
|
||||
lib/$(1)/ebin/Elixir-$(2).beam: $(wildcard lib/$(1)/lib/*.ex) $(wildcard lib/$(1)/lib/*/*.ex) $(wildcard lib/$(1)/lib/*/*/*.ex) $$(FORCE)
|
||||
lib/$(1)/ebin/Elixir-$(2).beam: $(wildcard lib/$(1)/lib/*.ex) $(wildcard lib/$(1)/lib/*/*.ex) $(wildcard lib/$(1)/lib/*/*/*.ex)
|
||||
@ echo "==> $(1) (compile)"
|
||||
@ $$(ELIXIRC) "lib/$(1)/lib/**/*.ex" -o lib/$(1)/ebin
|
||||
|
||||
@@ -25,7 +26,11 @@ test_$(1): $(1)
|
||||
endef
|
||||
|
||||
#==> Compilation tasks
|
||||
|
||||
KERNEL:=lib/elixir/ebin/Elixir-Kernel.beam
|
||||
UNICODE:=lib/elixir/ebin/Elixir-String-Unicode.beam
|
||||
|
||||
default: compile
|
||||
|
||||
compile: lib/elixir/src/elixir.app.src erlang elixir
|
||||
|
||||
@@ -36,46 +41,52 @@ lib/elixir/src/elixir.app.src: src/elixir.app.src
|
||||
erlang:
|
||||
@ cd lib/elixir && $(REBAR) compile
|
||||
|
||||
# We need to compile only EEx (without the app)
|
||||
# file so we can compile Mix
|
||||
elixir: kernel lib/eex/ebin/Elixir-EEx.beam mix ex_unit eex iex
|
||||
# Since Mix depends on EEx and EEx depends on
|
||||
# Mix, we first compile EEx without the .app
|
||||
# file, then mix and then compile eex fully
|
||||
elixir: kernel unicode lib/eex/ebin/Elixir-EEx.beam mix ex_unit eex iex
|
||||
|
||||
kernel: $(KERNEL)
|
||||
$(KERNEL): lib/elixir/lib/*.ex lib/elixir/lib/*/*.ex $(FORCE)
|
||||
@ if [ -f $(KERNEL) ]; then \
|
||||
echo "==> kernel (compile)"; \
|
||||
$(ELIXIRC) "lib/elixir/lib/**/*.ex" -o lib/elixir/ebin; \
|
||||
else \
|
||||
echo "==> bootstrap (compile)"; \
|
||||
$(ERL) -s elixir_compiler core -s erlang halt; \
|
||||
$(KERNEL): lib/elixir/lib/*.ex lib/elixir/lib/*/*.ex
|
||||
@ if [ ! -f $(KERNEL) ]; then \
|
||||
echo "==> bootstrap (compile)"; \
|
||||
$(ERL) -s elixir_compiler core -s erlang halt; \
|
||||
fi
|
||||
@ echo "==> kernel (compile)";
|
||||
@ $(ELIXIRC) "lib/elixir/lib/**/*.ex" -o lib/elixir/ebin;
|
||||
@ rm -rf lib/elixir/ebin/elixir.app
|
||||
@ cd lib/elixir && $(REBAR) compile
|
||||
|
||||
$(eval $(call TASK_TEMPLATE,ex_unit,ExUnit))
|
||||
$(eval $(call TASK_TEMPLATE,eex,EEx))
|
||||
$(eval $(call TASK_TEMPLATE,mix,Mix))
|
||||
$(eval $(call TASK_TEMPLATE,iex,IEx))
|
||||
unicode: $(UNICODE)
|
||||
$(UNICODE): lib/elixir/priv/unicode.ex lib/elixir/priv/UnicodeData.txt lib/elixir/priv/NamedSequences.txt
|
||||
@ echo "==> unicode (compile)";
|
||||
@ echo "This step can take up to a minute to compile in order to embed the Unicode database"
|
||||
@ $(ELIXIRC) lib/elixir/priv/unicode.ex -o lib/elixir/ebin;
|
||||
|
||||
$(eval $(call APP_TEMPLATE,ex_unit,ExUnit))
|
||||
$(eval $(call APP_TEMPLATE,eex,EEx))
|
||||
$(eval $(call APP_TEMPLATE,mix,Mix))
|
||||
$(eval $(call APP_TEMPLATE,iex,IEx))
|
||||
|
||||
clean:
|
||||
@ cd lib/elixir && $(REBAR) clean
|
||||
rm -rf .full
|
||||
rm -rf $(RELEASE_FLAG)
|
||||
rm -rf ebin
|
||||
rm -rf lib/*/ebin
|
||||
rm -rf lib/*/test/tmp
|
||||
rm -rf lib/mix/test/fixtures/git_repo
|
||||
rm -rf lib/mix/tmp
|
||||
|
||||
#==> Release tasks
|
||||
$(FULLFLAG): $(wildcard lib/*/ebin/*)
|
||||
make ELIXIRC_OPTS="--debug-info" FORCE=1
|
||||
touch $(FULLFLAG)
|
||||
#==> Release tasks (modules compiled with --debug-info and --docs)
|
||||
|
||||
zip: $(FULLFLAG)
|
||||
$(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: $(FULLFLAG)
|
||||
docs: $(RELEASE_FLAG)
|
||||
mkdir -p ebin
|
||||
rm -rf docs
|
||||
cp -R -f lib/*/ebin/*.beam ./ebin
|
||||
@@ -87,11 +98,12 @@ release_docs: docs
|
||||
rm -rf ../elixir-lang.github.com/docs/master
|
||||
mv output ../elixir-lang.github.com/docs/master
|
||||
|
||||
release_erl: $(FULLFLAG)
|
||||
release_erl: $(RELEASE_FLAG)
|
||||
@ rm -rf rel/elixir
|
||||
@ cd rel && ../rebar generate
|
||||
|
||||
#==> Tests tasks
|
||||
|
||||
test: test_erlang test_elixir
|
||||
|
||||
test_erlang: compile
|
||||
@@ -106,3 +118,15 @@ test_elixir: test_kernel test_mix test_ex_unit test_eex test_iex
|
||||
test_kernel: compile
|
||||
@ echo "==> kernel (exunit)"
|
||||
@ cd lib/elixir && time ../../bin/elixir -r "test/elixir/test_helper.exs" -pr "test/elixir/**/*_test.exs";
|
||||
|
||||
.dialyzer.base_plt:
|
||||
@ echo "==> Adding Erlang/OTP basic applications to a new base PLT"
|
||||
@ dialyzer --output_plt .dialyzer.base_plt --build_plt --apps erts kernel stdlib compiler syntax_tools inets crypto ssl
|
||||
|
||||
dialyze: $(RELEASE_FLAG) .dialyzer.base_plt
|
||||
@ rm -f .dialyzer_plt
|
||||
@ cp .dialyzer.base_plt .dialyzer_plt
|
||||
@ echo "==> Adding Elixir to PLT..."
|
||||
@ dialyzer --plt .dialyzer_plt --add_to_plt -r lib/elixir/ebin lib/ex_unit/ebin lib/mix/ebin lib/iex/ebin lib/eex/ebin
|
||||
@ echo "==> Dialyzing Elixir..."
|
||||
@ dialyzer --plt .dialyzer_plt -r lib/elixir/ebin lib/ex_unit/ebin lib/mix/ebin lib/iex/ebin lib/eex/ebin
|
||||
|
||||
@@ -30,6 +30,10 @@ If you want to contribute, Elixir code is divided in applications inside the `li
|
||||
|
||||
* `ex_unit` - Simple test framework that ships with Elixir;
|
||||
|
||||
* `iex` — IEx, Elixir's interactive shell
|
||||
|
||||
* `mix` — Elixir's build tool
|
||||
|
||||
We usually keep a list of features and bugs [in the issue tracker][2].
|
||||
|
||||
# Important links
|
||||
|
||||
+7
-4
@@ -4,19 +4,21 @@ This document simply outlines the release process:
|
||||
|
||||
1) Remove .dev extension from current versions
|
||||
|
||||
2) Run `make clean test` to ensure all tests pass from scratch
|
||||
2) Run `make clean test` to ensure all tests pass from scratch and the CI is green
|
||||
|
||||
3) Ensure CHANGELOG is updated and tag release version with timestamp in it
|
||||
|
||||
4) Commit changes above and update stable branch
|
||||
|
||||
5) Create tag from stable branch
|
||||
5) Create tag from master branch
|
||||
|
||||
6) Release new docs, update elixir-lang.org
|
||||
|
||||
7) Push new zip to Elixir's downloads page
|
||||
|
||||
8) After release, bump versions and add .dev back
|
||||
8) Push package to expm
|
||||
|
||||
9) After release, bump versions and add .dev back
|
||||
|
||||
## Places where version is mentioned
|
||||
|
||||
@@ -24,4 +26,5 @@ This document simply outlines the release process:
|
||||
* lib/elixir/lib/system.ex
|
||||
* rel/reltool.config
|
||||
* Makefile
|
||||
* CHANGELOG
|
||||
* CHANGELOG
|
||||
* package.exs
|
||||
@@ -14,23 +14,22 @@
|
||||
super=false, %% when true, it means super was invoked
|
||||
caller=false, %% when true, it means caller was invoked
|
||||
name_args=false, %% when true, it means arguments should be named
|
||||
macro=[], %% a stack with macros nesting
|
||||
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
|
||||
clause_vars=[], %% a dict of all variables defined in a particular clause
|
||||
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=0, %% a counter for the variables defined
|
||||
file=(<<"nofile">>), %% the current scope filename
|
||||
counter=[], %% a counter for the variables defined
|
||||
local=nil, %% the scope to evaluate local functions against
|
||||
aliases=[], %% an orddict with aliases by new -> old names
|
||||
requires=elixir_dispatch:default_requires(), %% a set with modules required
|
||||
macros=elixir_dispatch:default_macros(), %% a list with macros imported by module
|
||||
functions=elixir_dispatch:default_functions(), %% a list with functions imported by module
|
||||
scheduled=[]}). %% scheduled modules to be loaded
|
||||
scheduled=[], %% scheduled modules to be loaded
|
||||
file, %% the current scope filename
|
||||
aliases, %% an orddict with aliases by new -> old names
|
||||
requires, %% a set with modules required
|
||||
macros, %% a list with macros imported by module
|
||||
functions}). %% a list with functions imported by module
|
||||
|
||||
-record(elixir_quote, {
|
||||
line=0,
|
||||
|
||||
@@ -5,11 +5,10 @@ defprotocol Access do
|
||||
The Access protocol is the underlying protocol invoked
|
||||
when the brackets syntax is used. For instance, `foo[bar]`
|
||||
is translated to `access foo, bar` which, by default,
|
||||
invokes `Access.access` protocol.
|
||||
invokes the `Access.access` protocol.
|
||||
|
||||
This protocol is limited and is implemented only for the
|
||||
following built-in types: keywords, records, atoms and
|
||||
functions.
|
||||
following built-in types: keywords, records and functions.
|
||||
"""
|
||||
|
||||
@only [List, Function, Record, Atom]
|
||||
@@ -17,7 +16,7 @@ defprotocol Access do
|
||||
@doc """
|
||||
Receives the element being accessed and the access item.
|
||||
"""
|
||||
def access(element, qualifier)
|
||||
def access(container, key)
|
||||
end
|
||||
|
||||
defimpl Access, for: List do
|
||||
|
||||
+62
-14
@@ -12,13 +12,13 @@ defmodule Behaviour do
|
||||
use Behaviour
|
||||
|
||||
@doc "Parses the given URL"
|
||||
defcallback parse(arg)
|
||||
defcallback parse(uri_info :: URI.Info.t), do: URI.Info.t
|
||||
|
||||
@doc "Defines a default port"
|
||||
defcallback default_port()
|
||||
defcallback default_port(), do: integer
|
||||
end
|
||||
|
||||
And then a specific protocol may use it as:
|
||||
And then a specific module may use it as:
|
||||
|
||||
defmodule URI.HTTP do
|
||||
@behaviour URI.Parser
|
||||
@@ -32,20 +32,56 @@ defmodule Behaviour do
|
||||
|
||||
## Implementation
|
||||
|
||||
Internally, Erlang call `behaviour_info(:callbacks)`
|
||||
to obtain all functions that a behaviour should
|
||||
implemented. Therefore, all this module does is
|
||||
to define `behaviour_info(:callbacks)` with the
|
||||
`defcallback` definitions.
|
||||
Behaviours since Erlang R15 must be defined via
|
||||
`@callback` attributes. `defcallback` is a simple
|
||||
mechanism that defines the `@callback` attribute
|
||||
according to the type specification and also allows
|
||||
docs and defines a custom function signature.
|
||||
|
||||
The callbacks and their documentation can be retrieved
|
||||
via the `__behaviour__` callback function.
|
||||
"""
|
||||
|
||||
@doc """
|
||||
Annotates the given function is a callback. `defcallback` is
|
||||
slightly different than simple using `def` because, even if
|
||||
`defcallback` contains default values, a default function
|
||||
won't be generated, which would happen with `def`.
|
||||
Defines a callback according to the given type specification.
|
||||
"""
|
||||
defmacro defcallback(fun, do: return) do
|
||||
{ name, args } = :elixir_clauses.extract_args(fun)
|
||||
arity = length(args)
|
||||
|
||||
Enum.each args, (function do
|
||||
{ :::, _, [left, right] } ->
|
||||
ensure_not_default(left)
|
||||
ensure_not_default(right)
|
||||
left
|
||||
other ->
|
||||
ensure_not_default(other)
|
||||
other
|
||||
end)
|
||||
|
||||
quote do
|
||||
@callback unquote(name)(unquote_splicing(args)), do: unquote(return)
|
||||
Behaviour.store_docs __MODULE__, unquote(__CALLER__.line), unquote(name), unquote(arity)
|
||||
end
|
||||
end
|
||||
|
||||
defp ensure_not_default({ ://, _, [_, _] }) do
|
||||
raise ArgumentError, message: "default arguments // not supported in defcallback"
|
||||
end
|
||||
|
||||
defp ensure_not_default(_), do: :ok
|
||||
|
||||
@doc false
|
||||
def store_docs(module, line, name, arity) do
|
||||
doc = Module.get_attribute module, :doc
|
||||
Module.delete_attribute module, :doc
|
||||
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)
|
||||
|
||||
@@ -72,6 +108,7 @@ defmodule Behaviour do
|
||||
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__)
|
||||
end
|
||||
@@ -80,9 +117,20 @@ 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_info(:callbacks) do
|
||||
@__behaviour_callbacks
|
||||
def __behaviour__(:callbacks) do
|
||||
__MODULE__.behaviour_info(:callbacks)
|
||||
end
|
||||
|
||||
def __behaviour__(:docs) do
|
||||
@__behaviour_docs
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -38,6 +38,13 @@ defmodule Binary.Dict do
|
||||
end
|
||||
end
|
||||
|
||||
def get!(dict(data), key) do
|
||||
case :orddict.find(to_binary(key), data) do
|
||||
{:ok, value} -> value
|
||||
:error -> raise(KeyError, key: key)
|
||||
end
|
||||
end
|
||||
|
||||
def put(dict(data), key, value) do
|
||||
dict(:orddict.store to_binary(key), value, data)
|
||||
end
|
||||
|
||||
@@ -128,8 +128,7 @@ defimpl Binary.Inspect, for: Atom do
|
||||
valid_atom_identifier?(binary) ->
|
||||
":" <> binary
|
||||
valid_ref_identifier?(binary) ->
|
||||
"Elixir-" <> rest = binary
|
||||
bc <<r>> inbits rest, do: <<to_dot(r)>>
|
||||
Module.to_binary(atom)
|
||||
atom in Macro.binary_ops or atom in Macro.unary_ops ->
|
||||
":" <> binary
|
||||
true ->
|
||||
@@ -139,9 +138,6 @@ defimpl Binary.Inspect, for: Atom do
|
||||
|
||||
# Detect if atom is an atom alias (Elixir-Foo-Bar-Baz)
|
||||
|
||||
defp to_dot(?-), do: ?.
|
||||
defp to_dot(l), do: l
|
||||
|
||||
defp valid_ref_identifier?("Elixir" <> rest) do
|
||||
valid_ref_piece?(rest)
|
||||
end
|
||||
@@ -265,20 +261,13 @@ defimpl Binary.Inspect, for: List do
|
||||
cond do
|
||||
printable?(thing) ->
|
||||
escape(list_to_binary(thing), ?')
|
||||
keywords?(thing) ->
|
||||
Keyword.keyword?(thing) ->
|
||||
"[" <> join_keywords(thing, opts) <> "]"
|
||||
true ->
|
||||
container_join(thing, "[", "]", opts)
|
||||
end
|
||||
end
|
||||
|
||||
## keywords?
|
||||
defp keywords?([]), do: true
|
||||
defp keywords?([{ key, _value } | rest]) when is_atom(key) do
|
||||
keywords?(rest)
|
||||
end
|
||||
defp keywords?(_other), do: false
|
||||
|
||||
defp join_keywords(thing, opts) do
|
||||
Enum.join(lc {key, value} inlist thing do
|
||||
key_to_binary(key, opts) <> ": " <> Binary.Inspect.inspect(value, opts)
|
||||
@@ -334,9 +323,7 @@ defimpl Binary.Inspect, for: Tuple do
|
||||
name = elem(tuple, 0)
|
||||
|
||||
if is_atom(name) and match?("Elixir-" <> _, atom_to_binary(name)) do
|
||||
if name in [BitString, List, Tuple, Atom, Number, Any] do
|
||||
record_inspect(tuple, opts)
|
||||
else
|
||||
unless name in [BitString, List, Tuple, Atom, Number, Any] do
|
||||
try do
|
||||
target = Module.concat(Binary.Inspect, name)
|
||||
target.inspect(tuple, opts)
|
||||
@@ -348,19 +335,16 @@ defimpl Binary.Inspect, for: Tuple do
|
||||
end
|
||||
end
|
||||
|
||||
defp record_inspect(exception, opts) when is_exception(exception) do
|
||||
list = tuple_to_list(exception)
|
||||
[name,_|tail] = list
|
||||
[_|fields] = name.__record__(:fields)
|
||||
record_join(name, fields, tail, opts)
|
||||
end
|
||||
|
||||
defp record_inspect(record, opts) do
|
||||
list = tuple_to_list(record)
|
||||
[name|tail] = list
|
||||
|
||||
if (fields = record_fields(name)) && (length(fields) == size(record) - 1) do
|
||||
record_join(name, fields, tail, opts)
|
||||
if Enum.first(tail) == :__exception__ do
|
||||
record_join(name, tl(fields), tl(tail), opts)
|
||||
else
|
||||
record_join(name, fields, tail, opts)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -422,8 +406,12 @@ defimpl Binary.Inspect, for: Regex do
|
||||
|
||||
"""
|
||||
|
||||
def inspect(thing, _) do
|
||||
"%r" <> Binary.Inspect.inspect(Regex.source(thing), []) <> Regex.opts(thing)
|
||||
def inspect(regex, _opts) when size(regex) == 5 do
|
||||
"%r" <> Binary.Inspect.inspect(Regex.source(regex), []) <> Regex.opts(regex)
|
||||
end
|
||||
|
||||
def inspect(other, opts) do
|
||||
Binary.Inspect.inspect other, Keyword.put(opts, :raw, true)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
+28
-28
@@ -52,28 +52,41 @@ defmodule Code do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Evalutes the contents given by string. The second argument is the binding
|
||||
(which should be a Keyword) followed by a keyword list of options. The
|
||||
options can be:
|
||||
Evalutes the contents given by string. The second argument is the
|
||||
binding (which should be a keyword) followed by a keyword list of
|
||||
environment options. Those options can be:
|
||||
|
||||
* `:file` - the file to be considered in the evaluation
|
||||
* `:line` - the line the script starts
|
||||
* `:aliases` - a list of tuples with the alias and its target
|
||||
* `:requires` - a list of modules required
|
||||
* `:functions` - a list of tuples where the first element is a module
|
||||
and the second a list of imported function names and arity
|
||||
* `:macros` - a list of tuples where the first element is a module
|
||||
and the second a list of imported macro names and arity
|
||||
* `:delegate_locals_to` - delegate local calls to the given module,
|
||||
otherwise functions are evaluated inside Erlang's default scope.
|
||||
the default is to not delegate
|
||||
|
||||
## Examples
|
||||
|
||||
Code.eval "a + b", [a: 1, b: 2], file: __ENV__.file, line: __ENV__.line
|
||||
#=> { 3, [ {:a, 1}, {:b, 2} ] }
|
||||
|
||||
When passing the __ENV__'s file and line, we could simply get
|
||||
the location which already returns both fields as a keyword list:
|
||||
For convenience, you can my pass `__ENV__` as argument and
|
||||
all imports, requires and aliases will be automatically carried
|
||||
over:
|
||||
|
||||
Code.eval "a + b", [a: 1, b: 2], __ENV__.location
|
||||
Code.eval "a + b", [a: 1, b: 2], __ENV__
|
||||
#=> { 3, [ {:a, 1}, {:b, 2} ] }
|
||||
|
||||
"""
|
||||
def eval(string, binding // [], opts // []) do
|
||||
def eval(string, binding // [], opts // [])
|
||||
|
||||
def eval(string, binding, Macro.Env[] = env) do
|
||||
eval(string, binding, env.to_keywords)
|
||||
end
|
||||
|
||||
def eval(string, binding, opts) do
|
||||
{ value, binding, _scope } =
|
||||
:elixir.eval :unicode.characters_to_list(string), binding, opts
|
||||
{ value, binding }
|
||||
@@ -82,15 +95,8 @@ defmodule Code do
|
||||
@doc """
|
||||
Evalutes the quoted contents.
|
||||
|
||||
## Options
|
||||
|
||||
This function accepts a list of options. The supported
|
||||
options are:
|
||||
|
||||
* `:file` - The filename to be used in stacktraces
|
||||
and the file reported in the __ENV__ variable.
|
||||
|
||||
* `:line` - The line reported in the __ENV__ variable.
|
||||
This function accepts a list of environment options.
|
||||
Check `Code.eval` for more information.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -99,8 +105,8 @@ defmodule Code do
|
||||
Code.eval_quoted contents, [a: 1, b: 2], file: __ENV__.file, line: __ENV__.line
|
||||
#=> { 3, [ {:a, 1}, {:b, 2} ] }
|
||||
|
||||
For convenience, you can also pass the current __ENV__ and
|
||||
the proper information will be extracted:
|
||||
For convenience, you can my pass `__ENV__` as argument and
|
||||
all options will be automatically extracted from the environment:
|
||||
|
||||
Code.eval_quoted contents, [a: 1, b: 2], __ENV__
|
||||
#=> { 3, [ {:a, 1}, {:b, 2} ] }
|
||||
@@ -109,7 +115,7 @@ defmodule Code do
|
||||
def eval_quoted(quoted, binding // [], opts // [])
|
||||
|
||||
def eval_quoted(quoted, binding, Macro.Env[] = env) do
|
||||
eval_quoted(quoted, binding, env.location)
|
||||
eval_quoted(quoted, binding, env.to_keywords)
|
||||
end
|
||||
|
||||
def eval_quoted(quoted, binding, opts) do
|
||||
@@ -319,7 +325,7 @@ defmodule Code do
|
||||
:undefined -> error
|
||||
_ ->
|
||||
try do
|
||||
module.__info__(:self)
|
||||
module.__info__(:module)
|
||||
{ :module, module }
|
||||
rescue
|
||||
UndefinedFunctionError -> error
|
||||
@@ -353,13 +359,7 @@ defmodule Code do
|
||||
if File.regular?(file) do
|
||||
file
|
||||
else
|
||||
prefix = "#{file}.exs"
|
||||
if File.regular?(prefix) do
|
||||
IO.write "[WARNING] Passing a file without .exs extension to Code.load_file or Code.require_file is deprecated, please pass the full name instead\n#{Exception.formatted_stacktrace}"
|
||||
prefix
|
||||
else
|
||||
raise ArgumentError, message: "could not load #{file}"
|
||||
end
|
||||
raise ArgumentError, message: "could not load #{file}"
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
+47
-6
@@ -26,12 +26,23 @@ defmodule Dict do
|
||||
|
||||
"""
|
||||
|
||||
@doc false
|
||||
def behaviour_info(:callbacks) do
|
||||
[ delete: 2, empty: 1, get: 3, has_key?: 2,
|
||||
keys: 1, merge: 3, put: 3, size: 1, to_list: 1,
|
||||
update: 3, update: 4, values: 1 ]
|
||||
end
|
||||
use Behaviour
|
||||
|
||||
@type key :: any
|
||||
@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)
|
||||
|
||||
@doc """
|
||||
Returns a list containing all dict's keys.
|
||||
@@ -44,6 +55,7 @@ defmodule Dict do
|
||||
Dict.keys d #=> [:a,:b]
|
||||
|
||||
"""
|
||||
@spec keys(t), do: [key]
|
||||
def keys(dict) do
|
||||
elem(dict, 0).keys(dict)
|
||||
end
|
||||
@@ -57,6 +69,7 @@ defmodule Dict do
|
||||
Dict.values d #=> [1,2]
|
||||
|
||||
"""
|
||||
@spec values(t), do: [value]
|
||||
def values(dict) do
|
||||
elem(dict, 0).values(dict)
|
||||
end
|
||||
@@ -70,6 +83,7 @@ defmodule Dict do
|
||||
Dict.size d #=> 2
|
||||
|
||||
"""
|
||||
@spec size(t), do: non_neg_integer
|
||||
def size(dict) do
|
||||
elem(dict, 0).size(dict)
|
||||
end
|
||||
@@ -84,6 +98,7 @@ defmodule Dict do
|
||||
Dict.has_key?(d, :b) #=> false
|
||||
|
||||
"""
|
||||
@spec has_key?(t, key), do: boolean
|
||||
def has_key?(dict, key) do
|
||||
elem(dict, 0).has_key?(dict, key)
|
||||
end
|
||||
@@ -100,10 +115,28 @@ defmodule Dict do
|
||||
Dict.get d, :b, 3 #=> 3
|
||||
|
||||
"""
|
||||
@spec get(t, key), do: value | nil
|
||||
@spec get(t, key, value), do: value
|
||||
def get(dict, key, default // nil) do
|
||||
elem(dict, 0).get(dict, key, default)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the value associated with `key` in `dict`. If `dict` does not
|
||||
contain `key`, it raises `KeyError`.
|
||||
|
||||
## Examples
|
||||
|
||||
d = new [a: 1]
|
||||
Dict.get d, :a #=> 1
|
||||
Dict.get d, :b #=> raises KeyError[key: :b]
|
||||
|
||||
"""
|
||||
@spec get!(t, key), do: value | no_return
|
||||
def get!(dict, key) do
|
||||
elem(dict, 0).get!(dict, key)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Stores the given `value` under `key` in `dict`.
|
||||
If `dict` already has `key`, the stored value is replaced by the new one.
|
||||
@@ -115,6 +148,7 @@ defmodule Dict do
|
||||
#=> [a: 3, b: 2]
|
||||
|
||||
"""
|
||||
@spec put(t, key, value), do: t
|
||||
def put(dict, key, val) do
|
||||
elem(dict, 0).put(dict, key, val)
|
||||
end
|
||||
@@ -132,6 +166,7 @@ defmodule Dict do
|
||||
Dict.delete d, :a #=> [b: 2]
|
||||
|
||||
"""
|
||||
@spec delete(t, key), do: t
|
||||
def delete(dict, key) do
|
||||
elem(dict, 0).delete(dict, key)
|
||||
end
|
||||
@@ -150,6 +185,7 @@ defmodule Dict do
|
||||
#=> [a: 3, b: 2, d: 4]
|
||||
|
||||
"""
|
||||
@spec merge(t, t), do: t
|
||||
def merge(dict1, dict2) do
|
||||
merge(dict1, dict2, fn(_k, _v1, v2) -> v2 end)
|
||||
end
|
||||
@@ -168,6 +204,7 @@ defmodule Dict do
|
||||
#=> [a: 4, b: 2, d: 4]
|
||||
|
||||
"""
|
||||
@spec merge(t, t, fun(key, value, value, do: value)), do: t
|
||||
def merge(dict1, dict2, fun) do
|
||||
elem(dict1, 0).merge(dict1, dict2, fun)
|
||||
end
|
||||
@@ -183,6 +220,7 @@ defmodule Dict do
|
||||
#=> [a: -1, b: 2]
|
||||
|
||||
"""
|
||||
@spec update(t, key, fun(value, do: value)), do: t
|
||||
def update(dict, key, fun) do
|
||||
elem(dict, 0).update(dict, key, fun)
|
||||
end
|
||||
@@ -199,6 +237,7 @@ defmodule Dict do
|
||||
#=> [a: 1, b: 2, c: 3]
|
||||
|
||||
"""
|
||||
@spec update(t, key, value, fun(value, do: value)), do: t
|
||||
def update(dict, key, initial, fun) do
|
||||
elem(dict, 0).update(dict, key, initial, fun)
|
||||
end
|
||||
@@ -206,6 +245,7 @@ defmodule Dict do
|
||||
@doc """
|
||||
Returns an empty dict of the same type as `dict`.
|
||||
"""
|
||||
@spec empty(t), do: t
|
||||
def empty(dict) do
|
||||
elem(dict, 0).empty(dict)
|
||||
end
|
||||
@@ -214,6 +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
|
||||
def to_list(dict) do
|
||||
elem(dict, 0).to_list(dict)
|
||||
end
|
||||
|
||||
+48
-34
@@ -68,9 +68,12 @@ defmodule Enum do
|
||||
`{ key, value }` tuple.
|
||||
"""
|
||||
|
||||
@type t :: Enum.Iterator.t
|
||||
@type element :: any
|
||||
|
||||
@doc """
|
||||
Invokes the given `fun` for each item in the `collection` and returns true if
|
||||
each invocation returns true as well, otherwise it shirt-circuits and returns
|
||||
each invocation returns true as well, otherwise it short-circuits and returns
|
||||
false.
|
||||
|
||||
## Examples
|
||||
@@ -88,6 +91,9 @@ defmodule Enum do
|
||||
Enum.all? [1,nil,3] #=> false
|
||||
|
||||
"""
|
||||
@spec all?(t), do: boolean
|
||||
@spec all?(t, fun(element, do: boolean)), do: boolean
|
||||
|
||||
def all?(collection, fun // fn(x) -> x end)
|
||||
|
||||
def all?(collection, fun) when is_list(collection) do
|
||||
@@ -122,6 +128,9 @@ defmodule Enum do
|
||||
Enum.any? [false,true,false] #=> true
|
||||
|
||||
"""
|
||||
@spec any?(t), do: boolean
|
||||
@spec any?(t, fun(element, do: boolean)), do: boolean
|
||||
|
||||
def any?(collection, fun // fn(x) -> x end)
|
||||
|
||||
def any?(collection, fun) when is_list(collection) do
|
||||
@@ -151,6 +160,7 @@ defmodule Enum do
|
||||
Enum.at! [2,4,6], 4 #=> raises Enum.OutOfBoundsError
|
||||
|
||||
"""
|
||||
@spec at!(t, non_neg_integer), do: element | no_return
|
||||
def at!(collection, n) when is_list(collection) and n >= 0 do
|
||||
do_at!(collection, n)
|
||||
end
|
||||
@@ -172,6 +182,7 @@ defmodule Enum do
|
||||
Enum.count [1,2,3] #=> 3
|
||||
|
||||
"""
|
||||
@spec count(t), do: non_neg_integer
|
||||
def count(collection) do
|
||||
I.count(collection)
|
||||
end
|
||||
@@ -179,6 +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
|
||||
def count(collection, fun) when is_list(collection) do
|
||||
do_count(collection, fun)
|
||||
end
|
||||
@@ -203,6 +215,7 @@ 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
|
||||
end
|
||||
@@ -216,6 +229,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
|
||||
def drop_while(collection, fun) when is_list(collection) do
|
||||
do_drop_while(collection, fun)
|
||||
end
|
||||
@@ -238,6 +252,7 @@ defmodule Enum do
|
||||
Enum.each ['some', 'example'], fn(x) -> IO.puts x end
|
||||
|
||||
"""
|
||||
@spec each(t, fun(element, do: any)), do: :ok
|
||||
def each(collection, fun) when is_list(collection) do
|
||||
:lists.foreach(fun, collection)
|
||||
:ok
|
||||
@@ -262,6 +277,7 @@ defmodule Enum do
|
||||
Enum.empty? [1,2,3] #=> false
|
||||
|
||||
"""
|
||||
@spec empty?(t), do: boolean
|
||||
def empty?(collection) when is_list(collection) do
|
||||
collection == []
|
||||
end
|
||||
@@ -283,6 +299,7 @@ defmodule Enum do
|
||||
#=> [2]
|
||||
|
||||
"""
|
||||
@spec filter(t, fun(element, do: boolean)), do: t
|
||||
def filter(collection, fun) when is_list(collection) do
|
||||
lc item inlist collection, fun.(item), do: item
|
||||
end
|
||||
@@ -305,6 +322,7 @@ defmodule Enum do
|
||||
#=> [4]
|
||||
|
||||
"""
|
||||
@spec filter_map(t, fun(element, do: boolean), fun(element, do: element)), do: t
|
||||
def filter_map(collection, filter, mapper) when is_list(collection) do
|
||||
lc item inlist collection, filter.(item), do: mapper.(item)
|
||||
end
|
||||
@@ -334,6 +352,9 @@ 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
|
||||
|
||||
def find(collection, ifnone // nil, fun)
|
||||
|
||||
def find(collection, ifnone, fun) when is_list(collection) do
|
||||
@@ -362,6 +383,9 @@ 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
|
||||
|
||||
def find_value(collection, ifnone // nil, fun)
|
||||
|
||||
def find_value(collection, ifnone, fun) when is_list(collection) do
|
||||
@@ -392,6 +416,7 @@ defmodule Enum do
|
||||
#=> 2
|
||||
|
||||
"""
|
||||
@spec find_index(t, fun(element, do: any)), do: non_neg_integer | :nil
|
||||
def find_index(collection, fun) when is_list(collection) do
|
||||
do_find_index(collection, 0, fun)
|
||||
end
|
||||
@@ -414,6 +439,7 @@ defmodule Enum do
|
||||
Enum.first [1,2,3] #=> 1
|
||||
|
||||
"""
|
||||
@spec first(t), do: :nil | element
|
||||
def first([]), do: nil
|
||||
def first([h|_]), do: h
|
||||
|
||||
@@ -442,6 +468,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
|
||||
def join(collection, joiner // "")
|
||||
|
||||
def join(collection, joiner) when is_list(joiner) do
|
||||
@@ -475,6 +503,7 @@ defmodule Enum do
|
||||
#=> [a: -1, b: -2]
|
||||
|
||||
"""
|
||||
@spec map(t, fun(element, do: any)), do: t
|
||||
def map(collection, fun) when is_list(collection) do
|
||||
lc item inlist collection, do: fun.(item)
|
||||
end
|
||||
@@ -501,10 +530,12 @@ defmodule Enum do
|
||||
## Examples
|
||||
|
||||
Enum.map_join([1,2,3], &1 * 2) #=> "246"
|
||||
Enum.map_join([1,2,3], &1 * 2, " = ") #=> "2 = 4 = 6"
|
||||
Enum.map_join([1,2,3], &1 * 2, ' = ') #=> '2 = 4 = 6'
|
||||
Enum.map_join([1,2,3], " = ", &1 * 2) #=> "2 = 4 = 6"
|
||||
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
|
||||
def map_join(collection, joiner // "", mapper)
|
||||
|
||||
def map_join(collection, joiner, mapper) when is_list(joiner) do
|
||||
@@ -539,6 +570,7 @@ defmodule Enum do
|
||||
#=> { [2, 4, 6], 6 }
|
||||
|
||||
"""
|
||||
@spec map_reduce(t, any, fun(element, any, do: any)), do: any
|
||||
def map_reduce(collection, acc, f) when is_list(collection) do
|
||||
:lists.mapfoldl(f, acc, collection)
|
||||
end
|
||||
@@ -563,6 +595,7 @@ defmodule Enum do
|
||||
#=> { [2], [1,3] }
|
||||
|
||||
"""
|
||||
@spec partition(t, fun(element, do: any)), do: {t, t}
|
||||
def partition(collection, fun) when is_list(collection) do
|
||||
do_partition(collection, fun, [], [])
|
||||
end
|
||||
@@ -587,6 +620,7 @@ defmodule Enum do
|
||||
#=> 6
|
||||
|
||||
"""
|
||||
@spec reduce(t, any, fun(element, any, do: any)), do: any
|
||||
def reduce(collection, acc, fun) when is_list(collection) do
|
||||
:lists.foldl(fun, acc, collection)
|
||||
end
|
||||
@@ -603,14 +637,13 @@ defmodule Enum do
|
||||
@doc """
|
||||
Reverses the collection.
|
||||
|
||||
Expects an ordered collection.
|
||||
|
||||
## Examples
|
||||
|
||||
Enum.reverse [1, 2, 3]
|
||||
#=> [3, 2, 1]
|
||||
|
||||
"""
|
||||
@spec reverse(t), do: t
|
||||
def reverse(collection) when is_list(collection) do
|
||||
:lists.reverse(collection)
|
||||
end
|
||||
@@ -630,6 +663,7 @@ defmodule Enum do
|
||||
Enum.qsort [3,2,1] #=> [1,2,3]
|
||||
|
||||
"""
|
||||
@spec qsort(t), do: t
|
||||
def qsort(collection) when is_list(collection) do
|
||||
do_list_qsort(collection, [])
|
||||
end
|
||||
@@ -659,6 +693,7 @@ defmodule Enum do
|
||||
Enum.split [1,2,3], -5 #=> { [], [1,2,3] }
|
||||
|
||||
"""
|
||||
@spec split(t, integer), do: {t, t}
|
||||
def split(collection, count) when is_list(collection) and count >= 0 do
|
||||
do_split(collection, count, [])
|
||||
end
|
||||
@@ -679,12 +714,6 @@ defmodule Enum do
|
||||
split(list, real_count)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def split_with(collection, fun) do
|
||||
IO.write "[WARNING] Enum.split_with is deprecated, please use split_while instead\n#{Exception.formatted_stacktrace}"
|
||||
split_while(collection, fun)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Splits `collection` at the first element, for which `fun` returns true.
|
||||
Expects an ordered collection.
|
||||
@@ -694,6 +723,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}
|
||||
def split_while(collection, fun) when is_list(collection) do
|
||||
do_split_while(collection, fun, [])
|
||||
end
|
||||
@@ -718,6 +748,7 @@ 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
|
||||
end
|
||||
@@ -732,6 +763,7 @@ defmodule Enum do
|
||||
#=> [1, 2]
|
||||
|
||||
"""
|
||||
@spec take_while(t, fun(element, do: boolean)), do: t
|
||||
def take_while(collection, fun) when is_list(collection) do
|
||||
do_take_while(collection, fun)
|
||||
end
|
||||
@@ -745,19 +777,9 @@ defmodule Enum do
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Iterates the given function n times, passing values
|
||||
from 0 to n - 1.
|
||||
|
||||
## Examples
|
||||
|
||||
Enum.times 3, fn(x) -> IO.inspect x end
|
||||
0
|
||||
1
|
||||
2
|
||||
|
||||
"""
|
||||
@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)
|
||||
@@ -767,18 +789,9 @@ defmodule Enum do
|
||||
times
|
||||
end
|
||||
|
||||
@doc """
|
||||
Iterates the given function n times, passing values from 1
|
||||
to n. Also has an accumulator similar to reduce to store the
|
||||
value between computations.
|
||||
|
||||
## Examples
|
||||
|
||||
Enum.times 5, 0, fn(x, acc) -> acc + x end
|
||||
#=> 15
|
||||
|
||||
"""
|
||||
@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)
|
||||
end
|
||||
|
||||
@@ -788,6 +801,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}]
|
||||
def zip(coll1, coll2) when is_list(coll1) do
|
||||
do_zip(coll1, iterator(coll2))
|
||||
end
|
||||
|
||||
@@ -56,7 +56,7 @@ defmodule Exception do
|
||||
ErlangError.new original: other
|
||||
end
|
||||
|
||||
# Check the given module is a valid record.
|
||||
# Check the given module is a valid exception record.
|
||||
@doc false
|
||||
def check!(module) do
|
||||
unless :erlang.function_exported(module, :message, 1) do
|
||||
@@ -217,7 +217,7 @@ defexception ErlangError, [original: nil] do
|
||||
end
|
||||
end
|
||||
|
||||
defexception Keyword.KeyError, key: nil do
|
||||
defexception KeyError, key: nil do
|
||||
def message(exception) do
|
||||
"key not found: #{inspect exception.key}"
|
||||
end
|
||||
|
||||
+26
-16
@@ -59,8 +59,16 @@ defmodule File do
|
||||
that interact with the filesystem have their naming
|
||||
based on its UNIX variants. For example, deleting a
|
||||
file is done with `File.rm`. Getting its stats with
|
||||
`File.stat`. If you want to read or write to a file
|
||||
in chunks, check the IO module.
|
||||
`File.stat`.
|
||||
|
||||
In order to write and read files, one must use the
|
||||
functions in the IO module. By default, a file is
|
||||
opened on binary mode which requires the functions
|
||||
`IO.binread`, `IO.binwrite` and `IO.binreadline` to
|
||||
interact with the file. A developer may pass `:utf8`
|
||||
as an option when opening the file and then all other
|
||||
functions from IO are available, since they work directly
|
||||
with Unicode data.
|
||||
|
||||
Most of the functions in this module return `:ok`
|
||||
or `{ :ok, result }` in case of success, `{ :error, reason }`
|
||||
@@ -974,6 +982,12 @@ defmodule File do
|
||||
Note that the file size obtained with `stat/1` will most probably not
|
||||
match the number of bytes that can be read from a compressed file.
|
||||
|
||||
* `:utf8` - This option denotes how data is actually stored in the disk file and
|
||||
makes the file perform automatic translation of characters to and from utf-8.
|
||||
If data is sent to a file in a format that cannot be converted to the utf-8
|
||||
or if data is read by a function that returns data in a format that cannot cope
|
||||
with the character range of the data, an error occurs and the file will be closed.
|
||||
|
||||
If a function is given to modes (instead of a list), it dispatches to `open/3`.
|
||||
|
||||
Check `http://www.erlang.org/doc/man/file.html#open-2` for more information about
|
||||
@@ -1000,7 +1014,7 @@ defmodule File do
|
||||
def open(path, modes // [])
|
||||
|
||||
def open(path, modes) when is_list(modes) do
|
||||
F.open(path, open_defaults(modes, true, true))
|
||||
F.open(path, open_defaults(modes, true))
|
||||
end
|
||||
|
||||
def open(path, function) when is_function(function) do
|
||||
@@ -1166,7 +1180,7 @@ defmodule File do
|
||||
def iterator(device) do
|
||||
fn ->
|
||||
function = fn(_) ->
|
||||
case :io.get_line(device, "") do
|
||||
case :io.get_line(device, '') do
|
||||
:eof ->
|
||||
close(device)
|
||||
:stop
|
||||
@@ -1229,22 +1243,18 @@ defmodule File do
|
||||
join Enum.reverse(acc)
|
||||
end
|
||||
|
||||
defp open_defaults([:charlist|t], add_encoding, _add_binary) do
|
||||
open_defaults(t, add_encoding, false)
|
||||
defp open_defaults([:charlist|t], _add_binary) do
|
||||
open_defaults(t, false)
|
||||
end
|
||||
|
||||
defp open_defaults([{:encoding, _} = h|t], _add_encoding, add_binary) do
|
||||
[h|open_defaults(t, false, add_binary)]
|
||||
defp open_defaults([:utf8|t], add_binary) do
|
||||
open_defaults([{ :encoding, :utf8 }|t], add_binary)
|
||||
end
|
||||
|
||||
defp open_defaults([h|t], add_encoding, add_binary) do
|
||||
[h|open_defaults(t, add_encoding, add_binary)]
|
||||
defp open_defaults([h|t], add_binary) do
|
||||
[h|open_defaults(t, add_binary)]
|
||||
end
|
||||
|
||||
defp open_defaults([], add_encoding, add_binary) do
|
||||
options = []
|
||||
if add_encoding, do: options = [{:encoding, :unicode}|options]
|
||||
if add_binary, do: options = [:binary|options]
|
||||
options
|
||||
end
|
||||
defp open_defaults([], true), do: [:binary]
|
||||
defp open_defaults([], false), do: []
|
||||
end
|
||||
|
||||
@@ -1,14 +1,3 @@
|
||||
defmodule GenServer.Behavior do
|
||||
@moduledoc false
|
||||
|
||||
defmacro __using__(_) do
|
||||
IO.puts "Using GenServer.Behavior is deprecated, use GenServer.Behaviour instead"
|
||||
quote do
|
||||
use GenServer.Behaviour
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defmodule GenServer.Behaviour do
|
||||
@moduledoc """
|
||||
By using this module, you get default GenServer callbacks
|
||||
|
||||
@@ -46,6 +46,14 @@ defmodule HashDict do
|
||||
end
|
||||
end
|
||||
|
||||
@doc false
|
||||
def get!(dict(data), key) do
|
||||
case :dict.find(key, data) do
|
||||
{:ok, value} -> value
|
||||
:error -> raise(KeyError, key: key)
|
||||
end
|
||||
end
|
||||
|
||||
@doc false
|
||||
def put(dict(data), key, value) do
|
||||
dict(:dict.store key, value, data)
|
||||
|
||||
+38
-2
@@ -3,7 +3,7 @@ defmodule IO do
|
||||
Module responsible for doing IO. Many functions in this
|
||||
module expects an IO device and an io data encoded in UTF-8.
|
||||
|
||||
An IO device must be a pid is an atom representing a process.
|
||||
An IO device must be a pid or an atom representing a process.
|
||||
For convenience, Elixir provides `:stdio` and `:stderr` as
|
||||
shortcut to Erlang's `:standard_io` and `:standard_error`.
|
||||
|
||||
@@ -39,7 +39,20 @@ defmodule IO do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Read a line from the IO device. It returns:
|
||||
Reads `count` bytes from the IO device as binary,
|
||||
no unicode conversion happens.
|
||||
|
||||
Check `read/2` for more information.
|
||||
"""
|
||||
def binread(device // :stdio, count) do
|
||||
case :file.read(map_dev(device), count) do
|
||||
{ :ok, data } -> data
|
||||
other -> other
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Reads a line from the IO device. It returns:
|
||||
|
||||
* `data` - The input characters.
|
||||
|
||||
@@ -56,6 +69,19 @@ defmodule IO do
|
||||
:io.get_line(map_dev(device), "")
|
||||
end
|
||||
|
||||
@doc """
|
||||
Reads a line from the IO device as binary,
|
||||
no unicode conversion happens.
|
||||
|
||||
Check `readline/1` for more information.
|
||||
"""
|
||||
def binreadline(device // :stdio) do
|
||||
case :file.read_line(map_dev(device)) do
|
||||
{ :ok, data } -> data
|
||||
other -> other
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Writes the given argument to the given device.
|
||||
By default the device is the standard output.
|
||||
@@ -77,6 +103,16 @@ defmodule IO do
|
||||
:io.put_chars map_dev(device), to_iodata(item)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Writes the given argument to the given device
|
||||
as a binary, no unicode conversion happens.
|
||||
|
||||
Check `write/2` for more information.
|
||||
"""
|
||||
def binwrite(device // :stdio, item) do
|
||||
:file.write map_dev(device), to_iodata(item)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Writes the argument to the device, similarly to write
|
||||
but adds a new line at the end. The argument is expected
|
||||
|
||||
+117
-84
@@ -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: non_neg_integer
|
||||
@spec bitstring_to_list(bitstring), do: list
|
||||
def bitstring_to_list(bitstring) do
|
||||
:erlang.bitstring_to_list(bitstring)
|
||||
end
|
||||
@@ -543,59 +543,11 @@ defmodule Kernel do
|
||||
#=> '7.00000000000000000000e+00'
|
||||
|
||||
"""
|
||||
@spec float_to_list(number), do: string
|
||||
@spec float_to_list(number), do: char_list
|
||||
def float_to_list(number) do
|
||||
:erlang.float_to_list(number)
|
||||
end
|
||||
|
||||
@doc """
|
||||
The same as halt(0, []).
|
||||
"""
|
||||
@spec halt(), do: no_return
|
||||
def halt() do
|
||||
:erlang.halt()
|
||||
end
|
||||
|
||||
@doc """
|
||||
The same as halt(status, []).
|
||||
"""
|
||||
@spec halt(non_neg_integer | string | :abort), do: no_return
|
||||
def halt(status) do
|
||||
:erlang.halt(status)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Halts the Erlang runtime system where the first argument status must be a
|
||||
non-negative integer, a char list, or the atom `:abort`.
|
||||
|
||||
* If an integer, the runtime system exits with the integer value which
|
||||
is returned to the Operating System;
|
||||
|
||||
* If a char list, an erlang crash dump is produced with status as slogan,
|
||||
and then the runtime system exits with status code 1;
|
||||
|
||||
* If `:abort`, the runtime system aborts producing a core dump, if that is
|
||||
enabled in the operating system.
|
||||
|
||||
Note that on many platforms, only the status codes 0-255 are supported
|
||||
by the operating system.
|
||||
|
||||
For integer status, Erlang runtime system closes all ports and allows async
|
||||
threads to finish their operations before exiting. To exit without such
|
||||
flushing, pass options [flush: false] instead.
|
||||
|
||||
## Examples
|
||||
|
||||
halt(0)
|
||||
halt(1, flush: false)
|
||||
halt(:abort)
|
||||
|
||||
"""
|
||||
@spec halt(non_neg_integer | string | :abort, [] | [flush: false]), do: no_return
|
||||
def halt(status, options) do
|
||||
:erlang.halt(status, options)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the head of a list, raises badarg if the list is empty.
|
||||
"""
|
||||
@@ -613,7 +565,7 @@ defmodule Kernel do
|
||||
#=> '7'
|
||||
|
||||
"""
|
||||
@spec integer_to_list(integer), do: string
|
||||
@spec integer_to_list(integer), do: char_list
|
||||
def integer_to_list(number) do
|
||||
:erlang.integer_to_list(number)
|
||||
end
|
||||
@@ -628,7 +580,7 @@ defmodule Kernel do
|
||||
#=> "3FF"
|
||||
|
||||
"""
|
||||
@spec integer_to_list(integer, pos_integer), do: string
|
||||
@spec integer_to_list(integer, pos_integer), do: char_list
|
||||
def integer_to_list(number, base) do
|
||||
:erlang.integer_to_list(number, base)
|
||||
end
|
||||
@@ -840,7 +792,7 @@ defmodule Kernel do
|
||||
|
||||
list_to_atom('elixir') #=> :elixir
|
||||
"""
|
||||
@spec list_to_atom(string), do: atom
|
||||
@spec list_to_atom(char_list), do: atom
|
||||
def list_to_atom(char_list) do
|
||||
:erlang.list_to_atom(char_list)
|
||||
end
|
||||
@@ -880,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(string), do: atom
|
||||
@spec list_to_existing_atom(char_list), do: atom
|
||||
def list_to_existing_atom(char_list) do
|
||||
:erlang.list_to_existing_atom(char_list)
|
||||
end
|
||||
@@ -892,7 +844,7 @@ defmodule Kernel do
|
||||
|
||||
list_to_float('2.2017764e+0') #=> 2.2017764
|
||||
"""
|
||||
@spec list_to_float(string), do: float
|
||||
@spec list_to_float(char_list), do: float
|
||||
def list_to_float(char_list) do
|
||||
:erlang.list_to_float(char_list)
|
||||
end
|
||||
@@ -904,7 +856,7 @@ defmodule Kernel do
|
||||
|
||||
list_to_integer('123') #=> 123
|
||||
"""
|
||||
@spec list_to_integer(string), do: integer
|
||||
@spec list_to_integer(char_list), do: integer
|
||||
def list_to_integer(char_list) do
|
||||
:erlang.list_to_integer(char_list)
|
||||
end
|
||||
@@ -916,7 +868,7 @@ defmodule Kernel do
|
||||
|
||||
list_to_integer('3FF', 16) #=> 1023
|
||||
"""
|
||||
@spec list_to_integer(string, non_neg_integer), do: integer
|
||||
@spec list_to_integer(char_list, non_neg_integer), do: integer
|
||||
def list_to_integer(char_list, base) do
|
||||
:erlang.list_to_integer(char_list, base)
|
||||
end
|
||||
@@ -934,7 +886,7 @@ defmodule Kernel do
|
||||
## Examples
|
||||
list_to_pid('<0.41>') #=> <0.4.1>
|
||||
"""
|
||||
@spec list_to_pid(string), do: pid
|
||||
@spec list_to_pid(char_list), do: pid
|
||||
def list_to_pid(char_list) do
|
||||
:erlang.list_to_pid(char_list)
|
||||
end
|
||||
@@ -1393,7 +1345,12 @@ defmodule Kernel do
|
||||
defmacro defp(name, args, guards, do: contents)
|
||||
|
||||
@doc %B"""
|
||||
Define a record given by name and values.
|
||||
Defines a record.
|
||||
|
||||
A record is a tagged tuple which contains one or more elements
|
||||
and the first element is a module. This macro defines a module
|
||||
that generates accessors to manipulate the record at both
|
||||
compilation and runtime.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -1402,25 +1359,25 @@ defmodule Kernel do
|
||||
The line above will define a module named `FileInfo` which
|
||||
contains a function named `new` that returns a new record
|
||||
and other functions to read and set the values in the
|
||||
record. Therefore, we can do:
|
||||
record:
|
||||
|
||||
file_info = FileInfo.new(atime: now())
|
||||
file_info.atime #=> Returns the value of atime
|
||||
file_info.atime(now()) #=> Updates the value of atime
|
||||
|
||||
Internally, a record is simply a tuple where the first element is
|
||||
the record module name. This can be noticed if we print the record:
|
||||
A record is simply a tuple where the first element is the record
|
||||
module name. We can get the record raw representation as follow:
|
||||
|
||||
IO.inspect FileInfo.new
|
||||
{ FileInfo, nil, nil }
|
||||
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
|
||||
functions expect a function as argument that receives the current value
|
||||
and must return the new one:
|
||||
functions expect a function as argument that receives the current
|
||||
value and must return the new one:
|
||||
|
||||
file_info.update_atime(fn(_old) -> now() end) #=> Updates the value of atime
|
||||
|
||||
@@ -1443,20 +1400,79 @@ defmodule Kernel do
|
||||
Besides, if the default is a list, Elixir will define two helpers:
|
||||
|
||||
* `merge_field` - Receives keywords and merge it into the current value;
|
||||
* `prepend_field` - Receives another list and prepend its values
|
||||
|
||||
You can define your own extensions or disable them using the except
|
||||
option:
|
||||
|
||||
defrecord Config, [counter: 0, failures: []], except: [:extensions]
|
||||
* `prepend_field` - Receives another list and prepend its values;
|
||||
|
||||
## Documentation
|
||||
|
||||
By default records are not documented and have @moduledoc set to false.
|
||||
By default records are not documented and have `@moduledoc` set to false.
|
||||
|
||||
## Types
|
||||
|
||||
Every record defines a type named `t` that can be accessed in typespecs.
|
||||
For example, assuming the `Config` record defined above, it could be used
|
||||
in typespecs as follow:
|
||||
|
||||
@spec handle_config(Config.t), do: boolean()
|
||||
|
||||
Inside the record definition, a developer can define his own types too:
|
||||
|
||||
defrecord Config, counter: 0, failures: [] do
|
||||
@type kind :: term
|
||||
record_type counter: integer, failures: [kind]
|
||||
end
|
||||
|
||||
When defining a type, all the fields not mentioned in the type are
|
||||
assumed to have type `term`.
|
||||
"""
|
||||
defmacro defrecord(name, fields, opts // [], do_block // []) do
|
||||
Record.defrecord(name, fields, Keyword.merge(opts, do_block))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Defines a record with a set of private macros to manipulate it.
|
||||
|
||||
A record is a tagged tuple which contains one or more elements
|
||||
and the first element is a module. This macro defines a set of
|
||||
macros private to the current module to manipulate the record
|
||||
exclusively at compilation time.
|
||||
|
||||
`defrecordp` must be used instead of `defrecord` when there is
|
||||
no interest in exposing the record as a whole. In many ways,
|
||||
it is similar to Erlang records, since it is only available at
|
||||
compilation time.
|
||||
|
||||
## Examples
|
||||
|
||||
defmodule User do
|
||||
defrecordp :user, [name: "José", age: "25"]
|
||||
end
|
||||
|
||||
In the example above, a set of macros named `user` but with different
|
||||
arities will be defined to manipulate the underlying record:
|
||||
|
||||
# To create records
|
||||
user() #=> { User, "José", 25 }
|
||||
user(age: 26) #=> { User, "José", 26 }
|
||||
|
||||
# To get a field from the record
|
||||
user(record, :name) #=> "José"
|
||||
|
||||
# To get many fields from the record
|
||||
user(record, [:name, :age]) #=> ["José", 25]
|
||||
|
||||
# To update the record
|
||||
user(record, age: 26) #=> { User, "José", 26 }
|
||||
|
||||
# To convert the record to keywords
|
||||
user(record) #=> [name: "José", age: 25]
|
||||
|
||||
# To match against the record
|
||||
user(name: name) = record
|
||||
name #=> "José"
|
||||
|
||||
"""
|
||||
defmacro defrecord(name, values, opts // [], do_block // []) do
|
||||
Record.defrecord(name, values, Keyword.merge(opts, do_block))
|
||||
defmacro defrecordp(name, fields) when is_atom(name) do
|
||||
Record.defrecordp(name, fields)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -1467,21 +1483,21 @@ defmodule Kernel do
|
||||
differences:
|
||||
|
||||
1) Differently from records, exceptions are documented by default;
|
||||
2) Exceptions **must** implement `message/1` as API and return a
|
||||
2) Exceptions **must** implement `message/1` as API that return a
|
||||
binary as result;
|
||||
|
||||
"""
|
||||
defmacro defexception(name, values, opts // [], do_block // []) do
|
||||
defmacro defexception(name, fields, opts // [], do_block // []) do
|
||||
opts = Keyword.merge(opts, do_block)
|
||||
opts = Keyword.put(opts, :do, quote do
|
||||
@moduledoc nil
|
||||
unquote(Keyword.get opts, :do)
|
||||
def exception(args), do: new(args)
|
||||
def exception(args, self), do: self
|
||||
def exception(args, self), do: update(args, self)
|
||||
end)
|
||||
|
||||
values = [{ :__exception__, :__exception__ }|values]
|
||||
record = Record.defrecord(name, values, opts)
|
||||
fields = [{ :__exception__, :__exception__ }|fields]
|
||||
record = Record.defrecord(name, fields, opts)
|
||||
|
||||
check = quote do
|
||||
Exception.check! Module.concat(__MODULE__, unquote(name))
|
||||
@@ -1621,7 +1637,7 @@ defmodule Kernel do
|
||||
Reference are never going to be blank, it would be easier if we
|
||||
could simply provide a default implementation.
|
||||
|
||||
This can be achieved with Elixir as follows:
|
||||
This can be achieved in Elixir as follows:
|
||||
|
||||
defprotocol Blank do
|
||||
@only [Atom, Tuple, List, BitString, Any]
|
||||
@@ -1659,6 +1675,19 @@ defmodule Kernel do
|
||||
|
||||
Finally, since records are simply tuples, one can add a default protocol
|
||||
implementation to any record by defining a default implementation for tuples.
|
||||
|
||||
## Types
|
||||
|
||||
As in records, defining a protocol automatically defines a type named `t`,
|
||||
which can be used as:
|
||||
|
||||
@spec present?(Blank.t), do: boolean
|
||||
def present?(blank) do
|
||||
not Blank.blank?(blank)
|
||||
end
|
||||
|
||||
The `@spec` above expresses that all types allowed to implement the
|
||||
given protocol are valid argument types for the given function.
|
||||
"""
|
||||
defmacro defprotocol(name, [do: block]) do
|
||||
Protocol.defprotocol(name, [do: block])
|
||||
@@ -2299,7 +2328,11 @@ defmodule Kernel do
|
||||
and documentation.
|
||||
"""
|
||||
defmacro unless(clause, options) do
|
||||
quote do: if(!unquote(clause), unquote(options))
|
||||
do_clause = Keyword.get(options, :do, nil)
|
||||
else_clause = Keyword.get(options, :else, nil)
|
||||
quote do
|
||||
if(unquote(clause), do: unquote(else_clause), else: unquote(do_clause))
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -2828,7 +2861,7 @@ defmodule Kernel do
|
||||
end
|
||||
end
|
||||
|
||||
Record.access(caller, atom, fields, args)
|
||||
Record.access(atom, fields, args, caller)
|
||||
false ->
|
||||
case caller.in_match? do
|
||||
true -> raise "invalid usage of access protocol in signature"
|
||||
@@ -2872,7 +2905,7 @@ defmodule Kernel do
|
||||
defdelegate other_reverse(list), to: :lists, as: :reverse
|
||||
end
|
||||
|
||||
My:lists.reverse([1,2,3])
|
||||
MyList.reverse([1,2,3])
|
||||
#=> [3,2,1]
|
||||
|
||||
MyList.other_reverse([1,2,3])
|
||||
|
||||
@@ -4,8 +4,10 @@ defrecord Kernel.CLI.Config, commands: [], output: ".",
|
||||
defmodule Kernel.CLI do
|
||||
@moduledoc false
|
||||
|
||||
# Invoked directly from erlang boot process. It parses all argv
|
||||
# options and execute them in the order they are specified.
|
||||
@doc """
|
||||
Invoked directly from erlang boot process. It parses all argv
|
||||
options and execute them in the order they are specified.
|
||||
"""
|
||||
def process_argv(options) do
|
||||
{ config, argv } = process_options(options, Kernel.CLI.Config.new)
|
||||
|
||||
@@ -14,11 +16,19 @@ defmodule Kernel.CLI do
|
||||
|
||||
all_commands = Enum.reverse(config.commands)
|
||||
|
||||
run fn -> Enum.map all_commands, process_command(&1, config) end, config.halt
|
||||
end
|
||||
|
||||
@doc """
|
||||
Runs the given function and wraps any exception or
|
||||
error in messages useful for the command line.
|
||||
"""
|
||||
def run(fun, halt // true) do
|
||||
try do
|
||||
Enum.map all_commands, process_command(&1, config)
|
||||
if config.halt do
|
||||
fun.()
|
||||
if halt do
|
||||
at_exit(0)
|
||||
halt(0)
|
||||
System.halt(0)
|
||||
end
|
||||
rescue
|
||||
exception ->
|
||||
@@ -26,20 +36,20 @@ defmodule Kernel.CLI do
|
||||
trace = System.stacktrace
|
||||
IO.puts :stderr, "** (#{inspect exception.__record__(:name)}) #{exception.message}"
|
||||
IO.puts Exception.formatted_stacktrace(trace)
|
||||
halt(1)
|
||||
System.halt(1)
|
||||
catch
|
||||
:exit, reason when is_integer(reason) ->
|
||||
at_exit(reason)
|
||||
halt(reason)
|
||||
System.halt(reason)
|
||||
:exit, :normal ->
|
||||
at_exit(0)
|
||||
halt(0)
|
||||
System.halt(0)
|
||||
kind, reason ->
|
||||
at_exit(1)
|
||||
trace = System.stacktrace
|
||||
IO.puts :stderr, "** (#{kind}) #{inspect(reason)}"
|
||||
IO.puts Exception.formatted_stacktrace(trace)
|
||||
halt(1)
|
||||
System.halt(1)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -66,7 +76,7 @@ defmodule Kernel.CLI do
|
||||
|
||||
defp invalid_option(option) do
|
||||
IO.puts(:stderr, "Unknown option #{list_to_binary(option)}")
|
||||
halt(1)
|
||||
System.halt(1)
|
||||
end
|
||||
|
||||
defp shared_option?(list, config, callback) do
|
||||
@@ -140,7 +150,7 @@ defmodule Kernel.CLI do
|
||||
{ config.prepend_commands([require: list_to_binary(exec)]), t }
|
||||
else
|
||||
IO.puts(:stderr, "Could not find executable #{h}")
|
||||
halt(1)
|
||||
System.halt(1)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ defmodule Kernel.ParallelCompiler do
|
||||
with its name can be optionally given as argument.
|
||||
"""
|
||||
def files(files, callback // default_callback) do
|
||||
files_to_path(files, nil, callback)
|
||||
spawn_compilers(files, nil, callback)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -27,6 +27,10 @@ defmodule Kernel.ParallelCompiler do
|
||||
Read files/2 for more information.
|
||||
"""
|
||||
def files_to_path(files, path, callback // default_callback) when is_binary(path) do
|
||||
spawn_compilers(files, path, callback)
|
||||
end
|
||||
|
||||
defp spawn_compilers(files, path, callback) do
|
||||
Code.ensure_loaded(Kernel.ErrorHandler)
|
||||
spawn_compilers(files, path, callback, [], [], [])
|
||||
end
|
||||
@@ -42,9 +46,9 @@ defmodule Kernel.ParallelCompiler do
|
||||
parent = self()
|
||||
|
||||
child = spawn_link fn ->
|
||||
Process.put(:elixir_compiler_pid, parent)
|
||||
Process.put(:elixir_ensure_compiled, true)
|
||||
Process.flag(:error_handler, Kernel.ErrorHandler)
|
||||
:erlang.put(:elixir_compiler_pid, parent)
|
||||
:erlang.put(:elixir_ensure_compiled, true)
|
||||
:erlang.process_flag(:error_handler, Kernel.ErrorHandler)
|
||||
|
||||
try do
|
||||
if output do
|
||||
|
||||
@@ -27,10 +27,9 @@ defmodule Kernel.ParallelRequire do
|
||||
|
||||
child = spawn_link fn ->
|
||||
try do
|
||||
new = Code.require_file(h)
|
||||
result = (new || []) ++ result
|
||||
new = Code.require_file(h)
|
||||
callback.(h)
|
||||
parent <- { :required, self }
|
||||
parent <- { :required, self, new }
|
||||
catch
|
||||
kind, reason ->
|
||||
parent <- { :failure, self, kind, reason, System.stacktrace }
|
||||
@@ -42,8 +41,8 @@ defmodule Kernel.ParallelRequire do
|
||||
|
||||
defp wait_for_messages(files, waiting, callback, result) do
|
||||
receive do
|
||||
{ :required, child } ->
|
||||
spawn_requires(files, List.delete(waiting, child), callback, result)
|
||||
{ :required, child, new } ->
|
||||
spawn_requires(files, List.delete(waiting, child), callback, (new || []) ++ result)
|
||||
{ :failure, _child, kind, reason, stacktrace } ->
|
||||
:erlang.raise(kind, reason, stacktrace)
|
||||
end
|
||||
|
||||
@@ -139,8 +139,8 @@ defmodule Kernel.SpecialForms do
|
||||
|
||||
import :all, List
|
||||
|
||||
It can also be customized to import only functions or only
|
||||
macros:
|
||||
It can also be customized to import only all functions or
|
||||
all macros:
|
||||
|
||||
import :functions, List
|
||||
import :macros, List
|
||||
@@ -440,7 +440,7 @@ defmodule Kernel.SpecialForms do
|
||||
it belonged to another file.
|
||||
|
||||
quote location: :keep, do: 1
|
||||
#=> { :__scope__, 1,[[file: "iex"], 1] }
|
||||
#=> { :__scope__, 1,[[file: "iex"],[do: 1]] }
|
||||
|
||||
Check `quote/1` for more information.
|
||||
"""
|
||||
@@ -448,11 +448,39 @@ defmodule Kernel.SpecialForms do
|
||||
|
||||
@doc """
|
||||
This is the special form used to hold aliases information.
|
||||
At compilation time, it is usually compiled to an atom:
|
||||
It is usually compiled to an atom:
|
||||
|
||||
quote do: Foo.Bar
|
||||
{ :__aliases__, 0, [:Foo,:Bar] }
|
||||
|
||||
Elixir represents `Foo.Bar` as `__aliases__` so calls can be
|
||||
unambiguously identified by the operator `:.`. For example:
|
||||
|
||||
quote do: Foo.bar
|
||||
{{:.,0,[{:__aliases__,0,[:Foo]},:bar]},0,[]}
|
||||
|
||||
Whenever an expression iterator sees a `:.` as the tuple key,
|
||||
it can be sure that it represents a call and the second element
|
||||
of the arguments list is an atom.
|
||||
|
||||
On the other hand, aliases holds some properties:
|
||||
|
||||
1) The head element of aliases can be any term;
|
||||
|
||||
2) The tail elements of aliases are guaranteed to always be atoms;
|
||||
|
||||
3) When the head element of aliases is the atom :Elixir, no expansion happen;
|
||||
|
||||
4) When the head element of aliases is not an atom, it is expanded at runtime:
|
||||
|
||||
quote do: some_var.Foo
|
||||
{:__aliases__,0,[{:some_var,0,:quoted},:Bar]}
|
||||
|
||||
Since `some_var` is not available at compilation time, the compiler
|
||||
expands such expression to:
|
||||
|
||||
Module.concat [some_var, Foo]
|
||||
|
||||
"""
|
||||
defmacro __aliases__(args)
|
||||
end
|
||||
@@ -1,54 +1,376 @@
|
||||
defmodule Kernel.Typespec do
|
||||
@moduledoc """
|
||||
This is the module that converts Elixir typespecs
|
||||
to Erlang typespecs syntax. Everytime @spec, @type
|
||||
and @typep are used they proxy to the functions
|
||||
in this module.
|
||||
Holds macros and functions for working with typespecs.
|
||||
|
||||
The attributes `@type`, `@opaque`, `@typep`, `@spec` and
|
||||
`@callback` available in modules are handled by the equivalent
|
||||
macros defined by this module.
|
||||
|
||||
See http://www.erlang.org/doc/reference_manual/typespec.html
|
||||
for more information.
|
||||
"""
|
||||
|
||||
defmacro deftype(name, options // []) do
|
||||
_deftype(name, true, __CALLER__, options)
|
||||
@doc """
|
||||
Defines a type.
|
||||
This macro is the one responsible to handle the attribute @type.
|
||||
|
||||
## Examples
|
||||
|
||||
@type my_type :: atom
|
||||
|
||||
"""
|
||||
defmacro deftype(type) do
|
||||
quote do
|
||||
Kernel.Typespec.deftype(:type, (quote line: :keep, do: unquote(type)), __ENV__)
|
||||
end
|
||||
end
|
||||
|
||||
defmacro deftypep(name) do
|
||||
_deftype(name, false, __CALLER__, [])
|
||||
@doc """
|
||||
Defines an opaque type.
|
||||
This macro is the one responsible to handle the attribute @opaque.
|
||||
|
||||
## Examples
|
||||
|
||||
@opaque my_type :: atom
|
||||
|
||||
"""
|
||||
defmacro defopaque(type) do
|
||||
quote do
|
||||
Kernel.Typespec.deftype(:opaque, (quote line: :keep, do: unquote(type)), __ENV__)
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Defines a private type.
|
||||
This macro is the one responsible to handle the attribute @typep.
|
||||
|
||||
## Examples
|
||||
|
||||
@typep my_type :: atom
|
||||
|
||||
"""
|
||||
defmacro deftypep(type) do
|
||||
quote do
|
||||
Kernel.Typespec.deftype(:typep, (quote line: :keep, do: unquote(type)), __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
|
||||
|
||||
"""
|
||||
defmacro defspec(spec, block) do
|
||||
_defspec(:spec, __CALLER__, spec, block)
|
||||
quote do
|
||||
Kernel.Typespec.defspec(:spec, (quote line: :keep, do: unquote spec),
|
||||
(quote line: :keep, do: unquote block), __ENV__)
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Defines a callback.
|
||||
This macro is the one responsible to handle the attribute @callback.
|
||||
|
||||
## Examples
|
||||
|
||||
@callback add(number, number), do: number
|
||||
|
||||
"""
|
||||
defmacro defcallback(spec, block) do
|
||||
_defspec(:callback, __CALLER__, spec, block)
|
||||
quote do
|
||||
Kernel.Typespec.defspec(:callback, (quote line: :keep, do: unquote spec),
|
||||
(quote line: :keep, do: unquote block), __ENV__)
|
||||
end
|
||||
end
|
||||
|
||||
## Helpers
|
||||
|
||||
@doc """
|
||||
Defines a `type`, `typep` or `opaque` by receiving Erlang's typespec.
|
||||
"""
|
||||
def define_type(module, kind, { name, _, vars } = type) when kind in [:type, :typep, :opaque] do
|
||||
{ kind, export } =
|
||||
case kind do
|
||||
:type -> { :type, true }
|
||||
:typep -> { :type, false }
|
||||
:opaque -> { :opaque, true }
|
||||
end
|
||||
|
||||
Module.compile_typespec module, kind, type
|
||||
|
||||
if export do
|
||||
Module.compile_typespec module, :export_type, [{ name, length(vars) }]
|
||||
end
|
||||
|
||||
type
|
||||
end
|
||||
|
||||
@doc """
|
||||
Get the types defined for the given module. This function
|
||||
is only available for modules being compiled. If the module
|
||||
was already compiled, you need to loop its attributes
|
||||
to get such information.
|
||||
Defines a `spec` by receiving Erlang's typespec.
|
||||
"""
|
||||
def get_types(module) do
|
||||
Module.get_attribute(module, :type) ++ Module.get_attribute(module, :opaque)
|
||||
def define_spec(module, tuple, definition) do
|
||||
Module.compile_typespec module, :spec, { tuple, definition }
|
||||
end
|
||||
|
||||
@doc """
|
||||
Get the specs defined for the given module. This function
|
||||
is only available for modules being compiled. If the module
|
||||
was already compiled, you need to loop its attributes
|
||||
to get such information.
|
||||
Defines a `callback` by receiving Erlang's typespec.
|
||||
"""
|
||||
def get_specs(module) do
|
||||
specs = :ets.tab2list(spec_table_for(module))
|
||||
keys = :lists.ukeysort(1, specs)
|
||||
lc { k, _ } inlist keys, do: { k, :proplists.append_values(k, specs) }
|
||||
def define_callback(module, tuple, definition) do
|
||||
Module.compile_typespec module, :callback, { tuple, definition }
|
||||
end
|
||||
|
||||
## Typespec conversion
|
||||
@doc """
|
||||
Returns true if the current module defines a given type
|
||||
(private, opaque or not). This function is only available
|
||||
for modules being compiled.
|
||||
"""
|
||||
def defines_type?(module, name, arity) do
|
||||
finder = match?({ ^name, _, vars } when length(vars) == arity, &1)
|
||||
:lists.any(finder, Module.get_attribute(module, :type)) or
|
||||
:lists.any(finder, Module.get_attribute(module, :opaque))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns true if the current module defines a given spec.
|
||||
This function is only available for modules being compiled.
|
||||
"""
|
||||
def defines_spec?(module, name, arity) do
|
||||
tuple = { name, arity }
|
||||
:lists.any(match?(^tuple, &1), Module.get_attribute(module, :spec))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns true if the current module defines a callback.
|
||||
This function is only available for modules being compiled.
|
||||
"""
|
||||
def defines_callback?(module, name, arity) do
|
||||
tuple = { name, arity }
|
||||
:lists.any(match?(^tuple, &1), Module.get_attribute(module, :callback))
|
||||
end
|
||||
|
||||
@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
|
||||
args = lc arg inlist args, do: typespec_to_ast(arg)
|
||||
{ args, typespec_to_ast(result) }
|
||||
end
|
||||
|
||||
def spec_to_ast({ :type, _, :fun, [] }) do
|
||||
{ [], quote do: term }
|
||||
end
|
||||
|
||||
@doc """
|
||||
Converts a type clause back to Elixir AST.
|
||||
"""
|
||||
def type_to_ast({ { :record, record }, fields, args }) when is_atom(record) do
|
||||
fields = lc field inlist fields, do: typespec_to_ast(field)
|
||||
args = lc arg inlist args, do: typespec_to_ast(arg)
|
||||
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))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns all types available from the beam.
|
||||
|
||||
It is returned as a list of tuples where the first
|
||||
element is the type (`:typep`, `:type` and `:opaque`).
|
||||
|
||||
The module has to have a corresponding beam file
|
||||
on the file system.
|
||||
"""
|
||||
def beam_types(module) do
|
||||
case abstract_code(module) do
|
||||
{ :ok, abstract_code } ->
|
||||
exported_types = lc { :attribute, _, :export_type, types } inlist abstract_code, do: types
|
||||
exported_types = List.flatten(exported_types)
|
||||
|
||||
lc { :attribute, _, kind, { name, _, args } = type } inlist abstract_code, kind in [:opaque, :type] do
|
||||
cond do
|
||||
kind == :opaque -> { :opaque, type }
|
||||
List.member?(exported_types, { name, length(args) }) -> { :type, type }
|
||||
true -> { :typep, type }
|
||||
end
|
||||
end
|
||||
_ ->
|
||||
[]
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns all specs available from the beam.
|
||||
|
||||
It is returned as a list of tuples where the first
|
||||
element is spec name and arity and the second is the spec.
|
||||
|
||||
The module has to have a corresponding beam file
|
||||
on the file system.
|
||||
"""
|
||||
def beam_specs(module) do
|
||||
from_abstract_code(module, :spec)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns all callbacks available from the beam.
|
||||
|
||||
It is returned as a list of tuples where the first
|
||||
element is spec name and arity and the second is the spec.
|
||||
|
||||
The module has to have a corresponding beam file
|
||||
on the file system.
|
||||
"""
|
||||
def beam_callbacks(module) do
|
||||
from_abstract_code(module, :callback)
|
||||
end
|
||||
|
||||
defp from_abstract_code(module, kind) do
|
||||
case abstract_code(module) do
|
||||
{ :ok, abstract_code } ->
|
||||
lc { :attribute, _, abs_kind, value } inlist abstract_code, kind == abs_kind, do: value
|
||||
_ ->
|
||||
[]
|
||||
end
|
||||
end
|
||||
|
||||
defp abstract_code(module) do
|
||||
case :beam_lib.chunks(abstract_code_beam(module), [:abstract_code]) do
|
||||
{:ok, { _, [{ :abstract_code, { raw_abstract_v1, abstract_code } }] } } ->
|
||||
{ :ok, abstract_code }
|
||||
_ ->
|
||||
[]
|
||||
end
|
||||
end
|
||||
|
||||
defp abstract_code_beam(module) when is_atom(module) do
|
||||
case :code.which(module) do
|
||||
:non_existing -> module
|
||||
file -> file
|
||||
end
|
||||
end
|
||||
|
||||
defp abstract_code_beam(binary) when is_binary(binary) do
|
||||
binary
|
||||
end
|
||||
|
||||
## Macro callbacks
|
||||
|
||||
@doc false
|
||||
def deftype(kind, { :::, _, [type, definition] }, caller) do
|
||||
do_deftype(kind, type, definition, caller)
|
||||
end
|
||||
|
||||
def deftype(kind, type, caller) do
|
||||
do_deftype(kind, type, { :term, caller.line, nil }, caller)
|
||||
end
|
||||
|
||||
defp do_deftype(kind, { name, _, args }, definition, caller) do
|
||||
args =
|
||||
if is_atom(args) do
|
||||
[]
|
||||
else
|
||||
lc(arg inlist args, do: variable(arg))
|
||||
end
|
||||
|
||||
vars = lc { :var, _, var } inlist args, do: var
|
||||
spec = typespec(definition, vars, caller)
|
||||
|
||||
vars = lc { :var, _, _ } = var inlist args, do: var
|
||||
type = { name, spec, vars }
|
||||
|
||||
define_type(caller.module, kind, type)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def defspec(type, { name, line, args }, [do: 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
|
||||
|
||||
## To AST conversion
|
||||
|
||||
defp typespec_to_ast({ :type, line, :tuple, :any }) do
|
||||
typespec_to_ast({:type, line, :tuple, []})
|
||||
end
|
||||
|
||||
defp typespec_to_ast({ :type, line, :tuple, args }) do
|
||||
args = lc arg inlist args, do: typespec_to_ast(arg)
|
||||
{ :{}, line, args }
|
||||
end
|
||||
|
||||
defp typespec_to_ast({ :type, line, :binary, [arg1, arg2] }) do
|
||||
[arg1, arg2] = lc arg inlist [arg1, arg2], do: typespec_to_ast(arg)
|
||||
cond do
|
||||
arg2 == 0 ->
|
||||
quote line: line, do: <<_ :: unquote(arg1)>>
|
||||
arg1 == 0 ->
|
||||
quote line: line, do: <<_ :: _ * unquote(arg2)>>
|
||||
true ->
|
||||
quote line: line, do: <<_ :: unquote(arg1) * unquote(arg2)>>
|
||||
end
|
||||
end
|
||||
|
||||
defp typespec_to_ast({ :type, line, :union, args }) do
|
||||
args = lc arg inlist args, do: typespec_to_ast(arg)
|
||||
Enum.reduce tl(args), hd(args),
|
||||
fn(arg, expr) -> { :|, line, [expr, arg] } end
|
||||
end
|
||||
|
||||
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)]] }
|
||||
end
|
||||
|
||||
defp typespec_to_ast({ :type, line, :fun, [] }) do
|
||||
{ :fun, line, [] }
|
||||
end
|
||||
|
||||
defp typespec_to_ast({ :type, line, name, args }) do
|
||||
args = lc arg inlist args, do: typespec_to_ast(arg)
|
||||
{ name, line, args }
|
||||
end
|
||||
|
||||
defp typespec_to_ast({ :var, line, var }) do
|
||||
{ var, line, nil }
|
||||
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)] }
|
||||
{ dot, line, args }
|
||||
end
|
||||
|
||||
defp typespec_to_ast({ :ann_type, line, [var, type] }) do
|
||||
{ :::, line, [typespec_to_ast(var), typespec_to_ast(type)] }
|
||||
end
|
||||
|
||||
defp typespec_to_ast({ :typed_record_field,
|
||||
{ :record_field, line, { :atom, line1, name }},
|
||||
type }) do
|
||||
typespec_to_ast({ :ann_type, line, [{ :var, line1, name }, type] })
|
||||
end
|
||||
|
||||
|
||||
defp typespec_to_ast({ t, _line, atom }) when is_atom(t) do
|
||||
atom
|
||||
end
|
||||
|
||||
## From AST conversion
|
||||
|
||||
# Handle unions
|
||||
defp typespec({ :|, line, [_,_] } = exprs, vars, caller) do
|
||||
exprs = Enum.reverse(collect_union(exprs))
|
||||
exprs = :lists.reverse(collect_union(exprs))
|
||||
union = lc e inlist exprs, do: typespec(e, vars, caller)
|
||||
{ :type, line, :union, union }
|
||||
end
|
||||
@@ -58,11 +380,11 @@ defmodule Kernel.Typespec do
|
||||
{:type, line, :binary, [{:integer, line, 0}, {:integer, line, 0}]}
|
||||
end
|
||||
|
||||
defp typespec({:<<>>, line, [{:|, _, [{:_, line1, atom}, {:*, _, [{:_, line2, atom}, unit]}]}]}, _, _) when is_atom(atom) do
|
||||
defp typespec({:<<>>, line, [{:::, _, [{:_, line1, atom}, {:*, _, [{:_, line2, atom}, unit]}]}]}, _, _) when is_atom(atom) do
|
||||
{:type, line, :binary, [{:integer, line1, 0}, {:integer, line2, unit}]}
|
||||
end
|
||||
|
||||
defp typespec({:<<>>, line, [{:|, line1, [{:_, line2, atom}, base]}]}, _, _) when is_atom(atom) do
|
||||
defp typespec({:<<>>, line, [{:::, line1, [{:_, line2, atom}, base]}]}, _, _) when is_atom(atom) do
|
||||
{:type, line, :binary, [{:integer, line1, base}, {:integer, line2, 0}]}
|
||||
end
|
||||
|
||||
@@ -71,7 +393,11 @@ defmodule Kernel.Typespec do
|
||||
typespec({:range, line, args}, vars, caller)
|
||||
end
|
||||
|
||||
# Handle aliases
|
||||
# Handle special forms
|
||||
defp typespec({:__MODULE__, _, atom}, vars, caller) when is_atom(atom) do
|
||||
typespec(caller.module, vars, caller)
|
||||
end
|
||||
|
||||
defp typespec({:__aliases__, _, _} = alias, vars, caller) do
|
||||
atom = Macro.expand alias, caller
|
||||
typespec(atom, vars, caller)
|
||||
@@ -89,6 +415,13 @@ defmodule Kernel.Typespec do
|
||||
{ :op, line, op, {:integer, line, integer} }
|
||||
end
|
||||
|
||||
# Handle access macro
|
||||
defp typespec({{:., line, [Kernel, :access]}, line1, [target, args]}, vars, caller) do
|
||||
access = {{:., line, [Kernel, :access]}, line1,
|
||||
[target, args ++ [_: (quote hygiene: false, do: any)]]}
|
||||
typespec(Macro.expand(access, caller), vars, caller)
|
||||
end
|
||||
|
||||
# Handle remote calls
|
||||
defp typespec({{:., line, [remote, name]}, _, args}, vars, caller) do
|
||||
remote = Macro.expand remote, caller
|
||||
@@ -113,8 +446,8 @@ defmodule Kernel.Typespec do
|
||||
# Handle funs
|
||||
defp typespec({:fun, line, arguments}, vars, caller) when is_list(arguments) do
|
||||
args =
|
||||
case Enum.reverse(arguments) do
|
||||
[[{:do,h}]|t] -> fn_args(line, Enum.reverse(t), h, vars, caller)
|
||||
case :lists.reverse(arguments) do
|
||||
[[{:do,h}]|t] -> fn_args(line, :lists.reverse(t), h, vars, caller)
|
||||
[] -> []
|
||||
_ -> [fn_args(line, arguments, vars, caller)]
|
||||
end
|
||||
@@ -125,13 +458,23 @@ defmodule Kernel.Typespec do
|
||||
# Handle variables or local calls
|
||||
defp typespec({name, line, atom}, vars, caller) when is_atom(atom) do
|
||||
if List.member?(vars, name) do
|
||||
{ :var, line, name }
|
||||
{ :var, line, name }
|
||||
else
|
||||
typespec({name, line, []}, vars, caller)
|
||||
end
|
||||
end
|
||||
|
||||
# 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"
|
||||
arguments = lc arg inlist arguments, do: typespec(arg, vars, caller)
|
||||
{ :type, line, :string, arguments }
|
||||
end
|
||||
|
||||
defp typespec({:char_list, _line, arguments}, vars, caller) do
|
||||
typespec((quote do: :elixir.char_list(unquote_splicing(arguments))), vars, caller)
|
||||
end
|
||||
|
||||
defp typespec({name, line, arguments}, vars, caller) do
|
||||
arguments = lc arg inlist arguments, do: typespec(arg, vars, caller)
|
||||
{ :type, line, name, arguments }
|
||||
@@ -155,7 +498,7 @@ defmodule Kernel.Typespec do
|
||||
end
|
||||
|
||||
defp typespec(l, _, _) when is_list(l) do
|
||||
raise(ArgumentError, message: "Unexpected list #{inspect l}")
|
||||
raise ArgumentError, message: "Unexpected list #{inspect l}"
|
||||
end
|
||||
|
||||
defp typespec(t, vars, caller) when is_tuple(t) do
|
||||
@@ -186,59 +529,6 @@ defmodule Kernel.Typespec do
|
||||
{ :type, line, :product, args }
|
||||
end
|
||||
|
||||
def _deftype({:"::", _, [name, definition]}, export, caller, opts) do
|
||||
_deftype(name, definition, export, caller, opts)
|
||||
end
|
||||
|
||||
def _deftype(name, export, caller, opts) do
|
||||
_deftype(name, { :term, caller.line, nil }, export, caller, opts)
|
||||
end
|
||||
|
||||
defp _deftype({name, _, args}, definition, export, caller, options) do
|
||||
args = if is_atom(args), do: [], else: lc(arg inlist args, do: variable(arg))
|
||||
vars = lc {:var, _, var} inlist args, do: var
|
||||
spec = typespec(definition, vars, caller)
|
||||
|
||||
vars = lc ({:var, _, _} = var) inlist args, do: var
|
||||
attr = if options[:opaque], do: :opaque, else: :type
|
||||
|
||||
export = if export do
|
||||
quote do: Module.compile_type(__MODULE__, :export_type, [{name, length(vars)}])
|
||||
else
|
||||
nil
|
||||
end
|
||||
|
||||
quote do
|
||||
name = unquote(name)
|
||||
spec = unquote(Macro.escape(spec))
|
||||
vars = unquote(Macro.escape(vars))
|
||||
type = { name, spec, vars }
|
||||
Module.compile_type __MODULE__, unquote(attr), type
|
||||
unquote(export)
|
||||
{ unquote(attr), type }
|
||||
end
|
||||
end
|
||||
|
||||
defp _defspec(type, caller, {name, line, args},[{:do,return}]) do
|
||||
if is_atom(args), do: args = []
|
||||
spec = { :type, line, :fun, fn_args(line, args, return, [], caller) }
|
||||
code = Macro.escape { {type, { name, length(args) }}, [spec] }
|
||||
table = spec_table_for(caller.module)
|
||||
|
||||
quote do
|
||||
code = unquote(code)
|
||||
:ets.insert(unquote(table), code)
|
||||
code
|
||||
end
|
||||
end
|
||||
|
||||
defp spec_table_for(module) do
|
||||
table = list_to_atom :lists.concat([:s, module])
|
||||
unless table == :ets.info(table, :name), do:
|
||||
raise(ArgumentError, message: "cannot manage specs for #{inspect module} because it was already compiled")
|
||||
table
|
||||
end
|
||||
|
||||
defp variable({name, line, _}) do
|
||||
{:var, line, name}
|
||||
end
|
||||
|
||||
+47
-17
@@ -13,11 +13,16 @@ defmodule Keyword do
|
||||
entries for a given key are removed when invoked.
|
||||
"""
|
||||
|
||||
@type key :: atom
|
||||
@type value :: any
|
||||
@type t :: [{key, value}]
|
||||
|
||||
@doc """
|
||||
Creates a Keyword from enum. Differently from `Keyword.new`
|
||||
which behaves as a dict, `Keyword.from_enum` do not remove
|
||||
duplicated entries.
|
||||
"""
|
||||
@spec from_enum(Enum.t), do: t
|
||||
def from_enum(enum) when is_list(enum) do
|
||||
enum
|
||||
end
|
||||
@@ -26,9 +31,24 @@ defmodule Keyword do
|
||||
Enum.map(enum, fn(x) -> x end)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Checks if the given argument is a keywords list or not
|
||||
"""
|
||||
@spec keyword?(term), do: boolean
|
||||
def keyword?([{ key, _value } | rest]) when is_atom(key) do
|
||||
case atom_to_list(key) do
|
||||
'Elixir-' ++ _ -> false
|
||||
_ -> keyword?(rest)
|
||||
end
|
||||
end
|
||||
|
||||
def keyword?([]), do: true
|
||||
def keyword?(_other), do: false
|
||||
|
||||
@doc """
|
||||
Returns an empty keyword list, i.e. an empty list.
|
||||
"""
|
||||
@spec new, do: t
|
||||
def new do
|
||||
[]
|
||||
end
|
||||
@@ -43,6 +63,7 @@ defmodule Keyword do
|
||||
#=> [a: 2, b: 1]
|
||||
|
||||
"""
|
||||
@spec new(Enum.t), do: t
|
||||
def new(pairs) do
|
||||
Enum.reduce pairs, [], fn { k, v }, keywords ->
|
||||
put(keywords, k, v)
|
||||
@@ -60,6 +81,7 @@ defmodule Keyword do
|
||||
#=> [a: :a, b: :b]
|
||||
|
||||
"""
|
||||
@spec new(Enum.t, fun({key, value}, do: {key, value})), do: t
|
||||
def new(pairs, transform) do
|
||||
Enum.reduce pairs, [], fn i, keywords ->
|
||||
{ k, v } = transform.(i)
|
||||
@@ -83,7 +105,9 @@ defmodule Keyword do
|
||||
Keyword.get [a: 1], :b, 3 #=> 3
|
||||
|
||||
"""
|
||||
def get(keywords, key, default // nil) do
|
||||
@spec get(t, key), do: value
|
||||
@spec get(t, key, value), do: value
|
||||
def get(keywords, key, default // nil) when is_atom(key) do
|
||||
case :lists.keyfind(key, 1, keywords) do
|
||||
{ ^key, value } -> value
|
||||
false -> default
|
||||
@@ -100,10 +124,11 @@ defmodule Keyword do
|
||||
Keyword.get! [a: 1], :b #=> raises KeyError[key: :b]
|
||||
|
||||
"""
|
||||
def get!(keywords, key) do
|
||||
@spec get!(t, key), do: value | no_return
|
||||
def get!(keywords, key) when is_atom(key) do
|
||||
case :lists.keyfind(key, 1, keywords) do
|
||||
{ ^key, value } -> value
|
||||
false -> raise(Keyword.KeyError, key: key)
|
||||
false -> raise(KeyError, key: key)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -116,7 +141,8 @@ defmodule Keyword do
|
||||
#=> [1,2]
|
||||
|
||||
"""
|
||||
def get_values(keywords, key) do
|
||||
@spec get_values(t, key), do: [value]
|
||||
def get_values(keywords, key) when is_atom(key) do
|
||||
lc { k, v } inlist keywords, key == k, do: v
|
||||
end
|
||||
|
||||
@@ -129,6 +155,7 @@ defmodule Keyword do
|
||||
Keyword.keys [a: 1, b: 2] #=> [:a,:b]
|
||||
|
||||
"""
|
||||
@spec keys(t), do: [key]
|
||||
def keys(keywords) do
|
||||
lc { key, _ } inlist keywords, do: key
|
||||
end
|
||||
@@ -141,6 +168,7 @@ defmodule Keyword do
|
||||
Keyword.values [a: 1, b: 2] #=> [1,2]
|
||||
|
||||
"""
|
||||
@spec values(t), do: [value]
|
||||
def values(keywords) do
|
||||
lc { _, value } inlist keywords, do: value
|
||||
end
|
||||
@@ -157,7 +185,8 @@ defmodule Keyword do
|
||||
Keyword.delete [b: 2], :a #=> [b: 2]
|
||||
|
||||
"""
|
||||
def delete(keywords, key) do
|
||||
@spec delete(t, key), do: t
|
||||
def delete(keywords, key) when is_atom(key) do
|
||||
lc { k, _ } = tuple inlist keywords, key != k, do: tuple
|
||||
end
|
||||
|
||||
@@ -173,8 +202,9 @@ defmodule Keyword do
|
||||
#=> [a: 3, b: 2]
|
||||
|
||||
"""
|
||||
def put(list, key, value) do
|
||||
[{key, value}|delete(list, key)]
|
||||
@spec put(t, key, value), do: t
|
||||
def put(keywords, key, value) when is_atom(key) do
|
||||
[{key, value}|delete(keywords, key)]
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -187,6 +217,7 @@ defmodule Keyword do
|
||||
#=> true
|
||||
|
||||
"""
|
||||
@spec equal?(t, t), do: boolean
|
||||
def equal?(left, right) do
|
||||
:lists.sort(left) == :lists.sort(right)
|
||||
end
|
||||
@@ -201,6 +232,7 @@ defmodule Keyword do
|
||||
#=> [a:3, b:2, d: 4]
|
||||
|
||||
"""
|
||||
@spec merge(t, t), do: t
|
||||
def merge(d1, d2) do
|
||||
d2 ++ lc({ k, _ } = tuple inlist d1, not has_key?(d2, k), do: tuple)
|
||||
end
|
||||
@@ -217,6 +249,7 @@ defmodule Keyword do
|
||||
#=> [a:4, b:2, d: 4]
|
||||
|
||||
"""
|
||||
@spec merge(t, t, fun(key, value, value, do: value)), do: t
|
||||
def merge(d1, d2, fun) do
|
||||
do_merge(d2, d1, fun)
|
||||
end
|
||||
@@ -229,12 +262,6 @@ defmodule Keyword do
|
||||
acc
|
||||
end
|
||||
|
||||
@doc false
|
||||
def key?(list, key) do
|
||||
IO.write "[WARNING] Keyword.key? is deprecated, please use Keyword.has_key? instead\n#{Exception.formatted_stacktrace}"
|
||||
has_key?(list, key)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns whether a given key exists in the given keywords.
|
||||
|
||||
@@ -246,22 +273,24 @@ defmodule Keyword do
|
||||
#=> false
|
||||
|
||||
"""
|
||||
def has_key?(keywords, key) do
|
||||
@spec has_key?(t, key), do: boolean
|
||||
def has_key?(keywords, key) when is_atom(key) do
|
||||
:lists.keymember(key, 1, keywords)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Updates the key with the given function. If the key does
|
||||
not exist, raises `Keyword.KeyError`.
|
||||
not exist, raises `KeyError`.
|
||||
|
||||
## Examples
|
||||
|
||||
Keyword.update([a: 1], :a, &1 * 2)
|
||||
#=> [a: 2]
|
||||
Keyword.update([a: 1], :b, &1 * 2)
|
||||
#=> Keyword.KeyError
|
||||
#=> KeyError
|
||||
|
||||
"""
|
||||
@spec update(t, key, fun(value, do: value)), do: t | no_return
|
||||
def update([{key, value}|keywords], key, fun) do
|
||||
[{key, fun.(value)}|delete(keywords, key)]
|
||||
end
|
||||
@@ -271,7 +300,7 @@ defmodule Keyword do
|
||||
end
|
||||
|
||||
def update([], key, _fun) when is_atom(key) do
|
||||
raise(Keyword.KeyError, key: key)
|
||||
raise(KeyError, key: key)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -286,6 +315,7 @@ defmodule Keyword do
|
||||
#=> [a: 1, b: 11]
|
||||
|
||||
"""
|
||||
@spec update(t, key, value, fun(value, do: value)), do: t
|
||||
def update([{key, value}|keywords], key, _initial, fun) do
|
||||
[{key, fun.(value)}|delete(keywords, key)]
|
||||
end
|
||||
|
||||
+2
-22
@@ -123,12 +123,6 @@ defmodule List do
|
||||
:lists.foldr(function, acc, list)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def reverse(list) do
|
||||
IO.write "[WARNING] List.reverse/1 is deprecated, please use Enum.reverse/1 instead\n#{Exception.formatted_stacktrace}"
|
||||
:lists.reverse(list)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the last element in `list` or nil if the `list` is empty.
|
||||
|
||||
@@ -277,7 +271,7 @@ defmodule List do
|
||||
def range(first, last, step // nil)
|
||||
|
||||
def range(first, last, step) when is_integer(first) and is_integer(last) and first <= last do
|
||||
step = case step do
|
||||
case step do
|
||||
nil ->
|
||||
:lists.seq(first, last, 1)
|
||||
x when x < 0 ->
|
||||
@@ -288,7 +282,7 @@ defmodule List do
|
||||
end
|
||||
|
||||
def range(first, last, step) when is_integer(first) and is_integer(last) and first > last do
|
||||
step = case step do
|
||||
case step do
|
||||
nil ->
|
||||
:lists.seq(first, last, -1)
|
||||
x when x > 0 ->
|
||||
@@ -361,12 +355,6 @@ defmodule List do
|
||||
[other]
|
||||
end
|
||||
|
||||
@doc false
|
||||
def zip(item1, item2) do
|
||||
IO.write "[WARNING] List.zip/2 is deprecated, please use Enum.zip/2 instead\n#{Exception.formatted_stacktrace}"
|
||||
do_zip(to_list(item1), to_list(item2), [])
|
||||
end
|
||||
|
||||
@doc """
|
||||
Zips corresponding elements from each list in `list_of_lists`.
|
||||
|
||||
@@ -420,14 +408,6 @@ defmodule List do
|
||||
|
||||
# zip
|
||||
|
||||
defp do_zip([h1|t1], [h2|t2], acc) do
|
||||
do_zip t1, t2, [{h1, h2}|acc]
|
||||
end
|
||||
|
||||
defp do_zip(_, _, acc) do
|
||||
:lists.reverse acc
|
||||
end
|
||||
|
||||
defp do_zip(list, acc) do
|
||||
converter = fn x, acc -> do_zip_each(to_list(x), acc) end
|
||||
{mlist, heads} = :lists.mapfoldl converter, [], list
|
||||
|
||||
@@ -236,11 +236,14 @@ defmodule Macro do
|
||||
defp is_kw_blocks?([_|_] = kw) do
|
||||
Enum.all?(kw, match?({x, _} when x in kw_keywords, &1))
|
||||
end
|
||||
defp is_kw_blocks?(_), do: false
|
||||
defp is_kw_blocks?(_), do: false
|
||||
|
||||
defp module_to_binary(atom) when is_atom(atom), do: Binary.Inspect.inspect(atom, raw: true)
|
||||
defp module_to_binary(other), do: call_to_binary(other)
|
||||
|
||||
defp call_to_binary(atom) when is_atom(atom), do: atom_to_binary(atom, :utf8)
|
||||
defp call_to_binary({ :., _, [arg] }), do: call_to_binary(arg) <> "."
|
||||
defp call_to_binary({ :., _, [left, right] }), do: call_to_binary(left) <> "." <> call_to_binary(right)
|
||||
defp call_to_binary({ :., _, [arg] }), do: module_to_binary(arg) <> "."
|
||||
defp call_to_binary({ :., _, [left, right] }), do: module_to_binary(left) <> "." <> call_to_binary(right)
|
||||
defp call_to_binary(other), do: to_binary(other)
|
||||
|
||||
defp call_to_binary_with_args(target, args) do
|
||||
|
||||
@@ -1,6 +1,22 @@
|
||||
defmodule Macro.Env do
|
||||
Record.deffunctions([:module, :file, :line, :function,
|
||||
:aliases, :context, :requires, :macros], __MODULE__)
|
||||
@type name_arity :: { atom, non_neg_integer }
|
||||
@type file :: binary
|
||||
@type line :: non_neg_integer
|
||||
@type aliases :: [{ module, module }]
|
||||
@type context :: :assign | :guard | nil
|
||||
@type requires :: [module]
|
||||
@type functions :: [{ module, [name_arity] }]
|
||||
@type macros :: [{ module, [name_arity] }]
|
||||
|
||||
fields = [:module, :file, :line, :function,
|
||||
:aliases, :context, :requires, :functions, :macros]
|
||||
|
||||
types = quote do: [module: module, file: file, line: line,
|
||||
function: name_arity, aliases: aliases, requires: requires,
|
||||
functions: functions, macros: macros]
|
||||
|
||||
Record.deffunctions(fields, __MODULE__)
|
||||
Record.deftypes(fields, types, __ENV__)
|
||||
|
||||
@moduledoc """
|
||||
A record that contains compile time environment information,
|
||||
@@ -46,6 +62,11 @@ defmodule Macro.Env do
|
||||
"""
|
||||
def requires(record)
|
||||
|
||||
@doc """
|
||||
Returns a list of functions imported from each module.
|
||||
"""
|
||||
def functions(record)
|
||||
|
||||
@doc """
|
||||
Returns a list of macros imported from each module.
|
||||
"""
|
||||
|
||||
+39
-50
@@ -23,43 +23,40 @@ defmodule Module do
|
||||
|
||||
@doc """
|
||||
Evalutes the quotes contents in the given module context.
|
||||
|
||||
A list of environment options can also be given as argument.
|
||||
Check `Code.eval` for more information.
|
||||
|
||||
Raises an error if the module was already compiled.
|
||||
|
||||
## Options
|
||||
|
||||
This function accepts a list of options. The supported
|
||||
options are:
|
||||
|
||||
* `:file` - The filename to be used in stacktraces
|
||||
and the file reported in the __ENV__ variable.
|
||||
|
||||
* `:line` - The line reported in the __ENV__ variable.
|
||||
|
||||
## Examples
|
||||
|
||||
defmodule Foo do
|
||||
contents = quote do: (def sum(a, b), do: a + b)
|
||||
Module.eval_quoted __MODULE__, contents, []
|
||||
Module.eval_quoted __MODULE__, contents
|
||||
end
|
||||
|
||||
Foo.sum(1, 2) #=> 3
|
||||
|
||||
This function also accepts a `Macro.Env` as first argument. This
|
||||
is useful to evalute the quoted contents inside an existing
|
||||
environment (considering the environemnt module, line and file):
|
||||
For convenience, you can my pass `__ENV__` as argument and
|
||||
all options will be automatically extracted from the environment:
|
||||
|
||||
defmodule Foo do
|
||||
contents = quote do: (def sum(a, b), do: a + b)
|
||||
Module.eval_quoted __ENV__, contents, []
|
||||
Module.eval_quoted __MODULE__, contents, [], __ENV__
|
||||
end
|
||||
|
||||
Foo.sum(1, 2) #=> 3
|
||||
|
||||
"""
|
||||
def eval_quoted(env, quoted, binding // [], opts // [])
|
||||
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.location, opts))
|
||||
eval_quoted(module, quoted, binding, Keyword.merge(env.to_keywords, opts))
|
||||
end
|
||||
|
||||
def eval_quoted(module, quoted, binding, Macro.Env[] = env) do
|
||||
eval_quoted(module, quoted, binding, env.to_keywords)
|
||||
end
|
||||
|
||||
def eval_quoted(module, quoted, binding, opts) do
|
||||
@@ -79,7 +76,7 @@ defmodule Module do
|
||||
def world, do: true
|
||||
end
|
||||
|
||||
Module.create(Hello, contents, __ENV__)
|
||||
Module.create(Hello, contents, __ENV__.location)
|
||||
|
||||
Hello.world #=> true
|
||||
|
||||
@@ -91,16 +88,15 @@ defmodule Module do
|
||||
function is preferred when the module body is given
|
||||
by a quoted expression.
|
||||
|
||||
Another important distinction is that `defmodule`
|
||||
blends into the scope it is invoked, allowing you
|
||||
to access all variables, imports and requires from
|
||||
the module. `Module.create`, on the other hand, creates
|
||||
a new scope so imports, requires, etc are not inherited.
|
||||
Another important distinction is that `Module.create`
|
||||
allows you to control the environment variables used
|
||||
when defining the module, while `defmodule` automatically
|
||||
shares the same environment.
|
||||
"""
|
||||
def create(module, quoted, opts // [])
|
||||
|
||||
def create(module, quoted, Macro.Env[] = env) do
|
||||
create(module, quoted, env.location)
|
||||
create(module, quoted, env.to_keywords)
|
||||
end
|
||||
|
||||
def create(module, quoted, opts) when is_atom(module) do
|
||||
@@ -196,7 +192,7 @@ defmodule Module do
|
||||
end
|
||||
|
||||
def add_doc(module, line, kind, tuple, signature, doc) when
|
||||
kind in [:def, :defmacro, :defcallback] and (is_binary(doc) or is_boolean(doc) or doc == nil) do
|
||||
kind in [:def, :defmacro] and (is_binary(doc) or is_boolean(doc) or doc == nil) do
|
||||
assert_not_compiled!(:add_doc, module)
|
||||
table = docs_table_for(module)
|
||||
|
||||
@@ -212,7 +208,7 @@ defmodule Module do
|
||||
ETS.insert(table, {
|
||||
tuple,
|
||||
line,
|
||||
merge_kind(old_kind, kind),
|
||||
kind,
|
||||
merge_signatures(old_sign, signature, 1),
|
||||
doc || old_doc
|
||||
})
|
||||
@@ -249,10 +245,6 @@ defmodule Module do
|
||||
|
||||
# Merge
|
||||
|
||||
defp merge_kind(:defcallback, new), do: new
|
||||
defp merge_kind(old, :defcallback), do: old
|
||||
defp merge_kind(_old, new), do: new
|
||||
|
||||
defp merge_signatures([h1|t1], [h2|t2], i) do
|
||||
[merge_signature(h1, h2, i)|merge_signatures(t1, t2, i + 1)]
|
||||
end
|
||||
@@ -371,8 +363,8 @@ defmodule Module do
|
||||
ETS.delete(table, tuple)
|
||||
|
||||
old = get_attribute(module, :__overridable)
|
||||
new = [ { tuple, { 1, [clause] } } ]
|
||||
merged = :orddict.merge(fn(_k, { count, v1 }, _v2) -> { count + 1, [clause|v1] } end, old, new)
|
||||
new = [ { tuple, { 1, clause, false } } ]
|
||||
merged = :orddict.merge(fn(_k, { count, _, _ }, _v2) -> { count + 1, clause, false } end, old, new)
|
||||
|
||||
put_attribute(module, :__overridable, merged)
|
||||
_ ->
|
||||
@@ -386,8 +378,7 @@ defmodule Module do
|
||||
Returns true if the given tuple in module is marked as overridable.
|
||||
"""
|
||||
def overridable?(module, tuple) do
|
||||
key = List.keyfind(get_attribute(module, :__overridable), tuple, 0)
|
||||
match? { _, { _, [_|_] } }, key
|
||||
!! List.keyfind(get_attribute(module, :__overridable), tuple, 0)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -421,12 +412,6 @@ defmodule Module do
|
||||
ETS.insert(table, { key, new })
|
||||
end
|
||||
|
||||
@doc false
|
||||
def add_attribute(module, key, value) when is_atom(key) do
|
||||
IO.write "[WARNING] Module.add_attribute is deprecated, please use Module.put_attribute instead\n#{Exception.formatted_stacktrace}"
|
||||
put_attribute(module, key, value)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets the given attribute from a module. If the attribute
|
||||
was marked as accumulate with `Module.register_attribute`,
|
||||
@@ -456,12 +441,6 @@ defmodule Module do
|
||||
end
|
||||
end
|
||||
|
||||
@doc false
|
||||
def read_attribute(module, key) when is_atom(key) do
|
||||
IO.write "[WARNING] Module.read_attribute is deprecated, please use Module.get_attribute instead\n#{Exception.formatted_stacktrace}"
|
||||
get_attribute(module, key)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Deletes all attributes that matches the given key.
|
||||
|
||||
@@ -536,19 +515,29 @@ defmodule Module do
|
||||
|
||||
"""
|
||||
def split(module) do
|
||||
tl(String.split(to_binary(module), "-"))
|
||||
tl(String.split(Binary.Chars.to_binary(module), "-"))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Convert a module name to binary without the Elixir prefix.
|
||||
"""
|
||||
def to_binary(module) do
|
||||
"Elixir-" <> rest = Binary.Chars.to_binary(module)
|
||||
bc <<r>> inbits rest, do: <<to_dot(r)>>
|
||||
end
|
||||
|
||||
defp to_dot(?-), do: ?.
|
||||
defp to_dot(l), do: l
|
||||
|
||||
@doc false
|
||||
# Used internally to compile documentation. This function
|
||||
# is private and must be used only internally.
|
||||
def compile_doc(env, kind, name, args, _guards, body) do
|
||||
def compile_doc(env, kind, name, args, _guards, _body) do
|
||||
module = env.module
|
||||
line = env.line
|
||||
arity = length(args)
|
||||
pair = { name, arity }
|
||||
doc = get_attribute(module, :doc)
|
||||
kind = if kind == :def and body == nil and module != Kernel, do: :defcallback, else: kind
|
||||
|
||||
case add_doc(module, line, kind, pair, args, doc) do
|
||||
:ok ->
|
||||
@@ -565,7 +554,7 @@ defmodule Module do
|
||||
@doc false
|
||||
# Used internally to compile types. This function
|
||||
# is private and must be used only internally.
|
||||
def compile_type(module, key, value) when is_atom(key) do
|
||||
def compile_typespec(module, key, value) when is_atom(key) do
|
||||
assert_not_compiled!(:put_attribute, module)
|
||||
table = data_table_for(module)
|
||||
|
||||
|
||||
+30
-7
@@ -73,23 +73,46 @@ defmodule Node do
|
||||
Returns the pid of a new process started by the application of `fun`
|
||||
on `node`. If `node` does not exist, a useless pid is returned.
|
||||
|
||||
Check http://www.erlang.org/doc/man/erlang.html#spawn_opt-4 for
|
||||
Check http://www.erlang.org/doc/man/erlang.html#spawn-2 for
|
||||
the list of available options.
|
||||
"""
|
||||
def spawn(node, fun, opts // []) do
|
||||
def spawn(node, fun) do
|
||||
:erlang.spawn(node, fun)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the pid of a new process started by the application of `fun`
|
||||
on `node`. If `node` does not exist, a useless pid is returned.
|
||||
|
||||
Check http://www.erlang.org/doc/man/erlang.html#spawn_opt-3 for
|
||||
the list of available options.
|
||||
"""
|
||||
def spawn(node, fun, opts) do
|
||||
:erlang.spawn_opt(node, fun, opts)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the pid of a new process started by the application of
|
||||
`module.function(args)` on `node`. If `node` does not exists, a useless
|
||||
pid is returned.
|
||||
`module.function(args)` on `node`. If `node` does not exists,
|
||||
a useless pid is returned.
|
||||
|
||||
Check http://www.erlang.org/doc/man/erlang.html#spawn_opt-4 for
|
||||
Check http://www.erlang.org/doc/man/erlang.html#spawn-4 for
|
||||
the list of available options.
|
||||
"""
|
||||
def spawn(node, module, fun, args, opts // []) do
|
||||
:erlang.spawn(node, module, fun, args, opts)
|
||||
def spawn(node, module, fun, args) do
|
||||
:erlang.spawn(node, module, fun, args)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the pid of a new process started by the application of
|
||||
`module.function(args)` on `node`. If `node` does not exists,
|
||||
a useless pid is returned.
|
||||
|
||||
Check http://www.erlang.org/doc/man/erlang.html#spawn_opt-5 for
|
||||
the list of available options.
|
||||
"""
|
||||
def spawn(node, module, fun, args, opts) do
|
||||
:erlang.spawn_opt(node, module, fun, args, opts)
|
||||
end
|
||||
|
||||
@doc """
|
||||
|
||||
@@ -44,8 +44,7 @@ defmodule OptionParser do
|
||||
def parse(argv, opts // []) when is_list(argv) and is_list(opts) do
|
||||
aliases = opts[:aliases] || []
|
||||
flags = opts[:flags] || []
|
||||
dict = Keyword.new(flags, fn(k) -> { k, false } end)
|
||||
parse(argv, aliases, flags, dict, [], true)
|
||||
parse(argv, aliases, flags, true)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -63,12 +62,15 @@ defmodule OptionParser do
|
||||
def parse_head(argv, opts // []) when is_list(argv) and is_list(opts) do
|
||||
aliases = opts[:aliases] || []
|
||||
flags = opts[:flags] || []
|
||||
dict = Keyword.new(flags, fn(k) -> { k, false } end)
|
||||
parse(argv, aliases, flags, dict, [], false)
|
||||
parse(argv, aliases, flags, false)
|
||||
end
|
||||
|
||||
## Helpers
|
||||
|
||||
defp parse(argv, aliases, flags, all) do
|
||||
parse(argv, aliases, flags, [], [], all)
|
||||
end
|
||||
|
||||
defp parse(["-" <> option|t], aliases, flags, dict, args, all) do
|
||||
{ option, value } = normalize_option(option, aliases)
|
||||
|
||||
@@ -84,16 +86,16 @@ defmodule OptionParser do
|
||||
parse(t, aliases, flags, dict, args, all)
|
||||
end
|
||||
|
||||
defp parse([], _, _, dict, args, true) do
|
||||
{ dict, Enum.reverse(args) }
|
||||
defp parse([], _, flags, dict, args, true) do
|
||||
{ reverse_dict(dict, flags), Enum.reverse(args) }
|
||||
end
|
||||
|
||||
defp parse([h|t], aliases, flags, dict, args, true) do
|
||||
parse(t, aliases, flags, dict, [h|args], true)
|
||||
end
|
||||
|
||||
defp parse(value, _, _, dict, _args, false) do
|
||||
{ dict, value }
|
||||
defp parse(value, _, flags, dict, _args, false) do
|
||||
{ reverse_dict(dict, flags), value }
|
||||
end
|
||||
|
||||
defp flag_from_tail([h|t]) when h in ["false", "true"], do: { h, t }
|
||||
@@ -108,7 +110,12 @@ defmodule OptionParser do
|
||||
end
|
||||
|
||||
defp store_option(dict, option, value) do
|
||||
Keyword.put dict, option, value
|
||||
[{ option, value }|dict]
|
||||
end
|
||||
|
||||
defp reverse_dict(dict, flags) do
|
||||
flags = lc k inlist flags, not Keyword.has_key?(dict, k), do: { k, false }
|
||||
Enum.reverse flags ++ dict
|
||||
end
|
||||
|
||||
defp normalize_option(<<?-, option :: binary>>, aliases) do
|
||||
|
||||
@@ -44,6 +44,14 @@ defmodule OrdDict do
|
||||
end
|
||||
end
|
||||
|
||||
@doc false
|
||||
def get!(dict(data), key) do
|
||||
case :orddict.find(key, data) do
|
||||
{:ok, value} -> value
|
||||
:error -> raise(KeyError, key: key)
|
||||
end
|
||||
end
|
||||
|
||||
@doc false
|
||||
def put(dict(data), key, value) do
|
||||
dict(:orddict.store key, value, data)
|
||||
|
||||
@@ -103,12 +103,19 @@ defmodule Process do
|
||||
|
||||
@doc """
|
||||
Returns the pid of a new process started by the application of `fun`.
|
||||
|
||||
It behaves exactly the same as the `Kernel.spawn/1` function except
|
||||
it also accepts extra options, for the list of available options
|
||||
check http://www.erlang.org/doc/man/erlang.html#spawn_opt-4
|
||||
It behaves exactly the same as `Kernel.spawn/1`.
|
||||
"""
|
||||
def spawn(fun, opts // []) do
|
||||
def spawn(fun) do
|
||||
:erlang.spawn(fun)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the pid of a new process started by the application of `fun`.
|
||||
|
||||
It also accepts extra options, for the list of available options
|
||||
check http://www.erlang.org/doc/man/erlang.html#spawn_opt-2
|
||||
"""
|
||||
def spawn(fun, opts) do
|
||||
:erlang.spawn_opt(fun, opts)
|
||||
end
|
||||
|
||||
@@ -117,12 +124,22 @@ defmodule Process do
|
||||
`module.function(args)`. The new process created will be placed in the system
|
||||
scheduler queue and be run some time later.
|
||||
|
||||
It behaves exactly the same as the `Kernel.spawn/3` function except
|
||||
it also accepts extra options, for the list of available options
|
||||
It behaves exactly the same as the `Kernel.spawn/3` function.
|
||||
"""
|
||||
def spawn(mod, fun, args) do
|
||||
:erlang.spawn(mod, fun, args)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the pid of a new process started by the application of
|
||||
`module.function(args)`. The new process created will be placed in the system
|
||||
scheduler queue and be run some time later.
|
||||
|
||||
It also accepts extra options, for the list of available options
|
||||
check http://www.erlang.org/doc/man/erlang.html#spawn_opt-4
|
||||
|
||||
"""
|
||||
def spawn(mod, fun, args, opts // []) do
|
||||
def spawn(mod, fun, args, opts) do
|
||||
:erlang.spawn_opt(mod, fun, args, opts)
|
||||
end
|
||||
|
||||
|
||||
+207
-129
@@ -23,13 +23,14 @@ defmodule Protocol do
|
||||
defmodule unquote(name) do
|
||||
# We don't allow function definition inside protocols
|
||||
import Kernel, except: [
|
||||
defmacrop: 1, defmacrop: 2, defmacrop: 4,
|
||||
defmacro: 1, defmacro: 2, defmacro: 4,
|
||||
defp: 1, defp: 2, defp: 4,
|
||||
def: 1, def: 2, def: 4
|
||||
]
|
||||
|
||||
# Import the new dsl that holds the new def
|
||||
import Protocol.DSL
|
||||
import :macros, Protocol.DSL
|
||||
|
||||
# Set up a clear slate to store defined functions
|
||||
@functions []
|
||||
@@ -38,9 +39,9 @@ defmodule Protocol do
|
||||
unquote(block)
|
||||
|
||||
# Define callbacks and meta information
|
||||
{ conversions, fallback } = Protocol.conversions_for(__MODULE__, @only, @except)
|
||||
Protocol.impl_for(conversions, fallback, __ENV__)
|
||||
Protocol.meta(@functions, fallback, __ENV__)
|
||||
{ conversions, fallback, returns_nil } = Protocol.conversions_for(__MODULE__, @only, @except)
|
||||
Protocol.impl_for(conversions, fallback, returns_nil, __ENV__)
|
||||
Protocol.meta(@functions, conversions, fallback, returns_nil, __ENV__)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -74,10 +75,9 @@ defmodule Protocol do
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Check if the given module is a protocol. Raises an error
|
||||
if not loaded or not a protocol.
|
||||
"""
|
||||
# Check if the given module is a protocol. Raises an error
|
||||
# if not loaded or not a protocol.
|
||||
@doc false
|
||||
def assert_protocol(module) do
|
||||
case Code.ensure_compiled(module) do
|
||||
{ :module, ^module } -> nil
|
||||
@@ -92,60 +92,16 @@ defmodule Protocol do
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Defines meta information about the protocol and internal callbacks.
|
||||
"""
|
||||
def meta(functions, fallback, env) do
|
||||
contents = quote do
|
||||
def __protocol__(:name), do: __MODULE__
|
||||
def __protocol__(:functions), do: unquote(:lists.sort(functions))
|
||||
|
||||
@doc false
|
||||
def behaviour_info(:callbacks), do: [{ :__impl__, 0 }|__protocol__(:functions)]
|
||||
|
||||
def __impl_for__(arg) do
|
||||
case __raw_impl__(arg) do
|
||||
__MODULE__.Record ->
|
||||
target = Module.concat(__MODULE__, :erlang.element(1, arg))
|
||||
try do
|
||||
target.__impl__
|
||||
target
|
||||
catch
|
||||
:error, :undef, [[{ ^target, :__impl__, [], _ }|_]|_] ->
|
||||
__fallback__
|
||||
end
|
||||
other ->
|
||||
other
|
||||
end
|
||||
end
|
||||
|
||||
def __impl_for__!(arg) do
|
||||
if module = __impl_for__(arg) do
|
||||
module
|
||||
else
|
||||
raise Protocol.UndefinedError, protocol: __MODULE__, structure: arg
|
||||
end
|
||||
end
|
||||
|
||||
defp __fallback__, do: unquote(fallback)
|
||||
end
|
||||
|
||||
Module.eval_quoted env, contents
|
||||
end
|
||||
|
||||
@doc """
|
||||
Implements the function that detects the protocol and returns
|
||||
the module to dispatch to. Returns module.Record for records
|
||||
which should be properly handled by the dispatching function.
|
||||
"""
|
||||
def impl_for(conversions, fallback, env) do
|
||||
# Implements the function that detects the protocol and returns
|
||||
# the module to dispatch to. Returns module.Record for records
|
||||
# which should be properly handled by the dispatching function.
|
||||
@doc false
|
||||
def impl_for(conversions, fallback, returns_nil, env) do
|
||||
contents = lc kind inlist conversions do
|
||||
each_impl_for(kind, if fallback, do: conversions)
|
||||
each_impl_for(kind, conversions, fallback)
|
||||
end
|
||||
|
||||
# If we don't implement all protocols and any is not in the
|
||||
# list, we need to add a final clause that returns nil.
|
||||
if !L.keyfind(Any, 1, conversions) && length(conversions) < 10 do
|
||||
if returns_nil do
|
||||
contents = contents ++ [quote do
|
||||
defp __raw_impl__(_) do
|
||||
nil
|
||||
@@ -153,13 +109,65 @@ defmodule Protocol do
|
||||
end]
|
||||
end
|
||||
|
||||
Module.eval_quoted env, contents
|
||||
Module.eval_quoted env.module, contents, [], env.location
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the default conversions according to the given
|
||||
only/except options.
|
||||
"""
|
||||
# Defines meta information about the protocol and internal callbacks.
|
||||
@doc false
|
||||
def meta(functions, conversions, fallback, returns_nil, env) do
|
||||
any = L.keyfind(Any, 1, conversions) != false
|
||||
|
||||
meta = quote do
|
||||
unless Kernel.Typespec.defines_type?(__MODULE__, :t, 0) do
|
||||
@type t :: unquote(generate_type(conversions, any))
|
||||
end
|
||||
|
||||
def __protocol__(:name), do: __MODULE__
|
||||
def __protocol__(:functions), do: unquote(:lists.sort(functions))
|
||||
end
|
||||
|
||||
impl_for = if L.keyfind(Record, 1, conversions) do
|
||||
quote do
|
||||
def __impl_for__(arg) do
|
||||
case __raw_impl__(arg) do
|
||||
__MODULE__.Record ->
|
||||
target = Module.concat(__MODULE__, :erlang.element(1, arg))
|
||||
try do
|
||||
target.__impl__
|
||||
target
|
||||
catch
|
||||
:error, :undef, [[{ ^target, :__impl__, [], _ }|_]|_] ->
|
||||
unquote(fallback)
|
||||
end
|
||||
other ->
|
||||
other
|
||||
end
|
||||
end
|
||||
end
|
||||
else
|
||||
quote do
|
||||
def __impl_for__(arg), do: __raw_impl__(arg)
|
||||
end
|
||||
end
|
||||
|
||||
impl_bang = if returns_nil do
|
||||
quote do
|
||||
def __impl_for__!(arg) do
|
||||
__impl_for__(arg) || raise(Protocol.UndefinedError, protocol: __MODULE__, structure: arg)
|
||||
end
|
||||
end
|
||||
else
|
||||
quote do
|
||||
def __impl_for__!(arg), do: __impl_for__(arg)
|
||||
end
|
||||
end
|
||||
|
||||
Module.eval_quoted env.module, [meta, impl_for, impl_bang], [], env.location
|
||||
end
|
||||
|
||||
# Returns the default conversions according to the given
|
||||
# only/except options.
|
||||
@doc false
|
||||
def conversions_for(module, only, except) do
|
||||
kinds = all_types
|
||||
|
||||
@@ -180,65 +188,63 @@ defmodule Protocol do
|
||||
nil
|
||||
end
|
||||
|
||||
{ conversions, fallback }
|
||||
# If any is not in the list and we don't implement all
|
||||
# protocols, we need to handle nil cases.
|
||||
returns_nil = L.keyfind(Any, 1, conversions) == false and length(conversions) < 10
|
||||
{ conversions, fallback, returns_nil }
|
||||
end
|
||||
|
||||
## Helpers
|
||||
|
||||
defp generate_type(_conversions, true) do
|
||||
quote(do: any)
|
||||
end
|
||||
|
||||
defp generate_type(conversions, false) do
|
||||
or_function = fn({ _, _, x }, acc) -> { :|, 0, [acc, x] } end
|
||||
{ _, _, first } = hd(conversions)
|
||||
:lists.foldl(or_function, first, tl(conversions))
|
||||
end
|
||||
|
||||
defp all_types do
|
||||
[
|
||||
{ Record, :is_record },
|
||||
{ Tuple, :is_tuple },
|
||||
{ Atom, :is_atom },
|
||||
{ List, :is_list },
|
||||
{ BitString, :is_bitstring },
|
||||
{ Number, :is_number },
|
||||
{ Function, :is_function },
|
||||
{ PID, :is_pid },
|
||||
{ Port, :is_port },
|
||||
{ Reference, :is_reference },
|
||||
{ Any, :is_any }
|
||||
]
|
||||
[ { Record, :is_record, quote do: tuple },
|
||||
{ Tuple, :is_tuple, quote do: tuple },
|
||||
{ Atom, :is_atom, quote do: atom },
|
||||
{ List, :is_list, quote do: list },
|
||||
{ BitString, :is_bitstring, quote do: <<>> },
|
||||
{ Number, :is_number, quote do: number },
|
||||
{ Function, :is_function, quote do: fun },
|
||||
{ PID, :is_pid, quote do: pid },
|
||||
{ Port, :is_port, quote do: port },
|
||||
{ Reference, :is_reference, quote do: reference },
|
||||
{ Any, :is_any, quote do: any } ]
|
||||
end
|
||||
|
||||
# Returns a quoted expression that allows to check
|
||||
# if the first item in the tuple is a built-in or not.
|
||||
defp is_builtin?([{h,_}]) do
|
||||
defp is_builtin?([{h,_,_}]) do
|
||||
quote do
|
||||
first == unquote(h)
|
||||
end
|
||||
end
|
||||
|
||||
defp is_builtin?([{h,_}|t]) do
|
||||
defp is_builtin?([{h,_,_}|t]) do
|
||||
quote do
|
||||
first == unquote(h) or unquote(is_builtin?(t))
|
||||
end
|
||||
end
|
||||
|
||||
# Handle records when we don't have fallbacks.
|
||||
# It simply gets the first element of the tuple.
|
||||
# This case assumes that, whenever a tuple is given
|
||||
# it is meant to be a record, so we don't need extra
|
||||
# checks.
|
||||
defp each_impl_for({ _, :is_record }, nil) do
|
||||
quote do
|
||||
defp __raw_impl__(arg) when is_tuple(arg) and is_atom(:erlang.element(1, arg)) do
|
||||
__MODULE__.Record
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Specially handle records in the case we have fallbacks.
|
||||
defp each_impl_for({ _, :is_record }, conversions) do
|
||||
defp each_impl_for({ _, :is_record, _ }, conversions, fallback) do
|
||||
quote do
|
||||
defp __raw_impl__(arg) when is_tuple(arg) and is_atom(:erlang.element(1, arg)) do
|
||||
first = :erlang.element(1, arg)
|
||||
case unquote(is_builtin?(conversions)) do
|
||||
true -> __fallback__
|
||||
true -> unquote(fallback)
|
||||
false ->
|
||||
case atom_to_list(first) do
|
||||
'Elixir-' ++ _ -> __MODULE__.Record
|
||||
_ -> __fallback__
|
||||
_ -> unquote(fallback)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -246,7 +252,7 @@ defmodule Protocol do
|
||||
end
|
||||
|
||||
# Special case any as we don't need to generate a guard.
|
||||
defp each_impl_for({ _, :is_any }, _) do
|
||||
defp each_impl_for({ _, :is_any, _ }, _, _) do
|
||||
quote do
|
||||
defp __raw_impl__(_) do
|
||||
__MODULE__.Any
|
||||
@@ -255,10 +261,10 @@ defmodule Protocol do
|
||||
end
|
||||
|
||||
# Generate all others protocols.
|
||||
defp each_impl_for({ kind, fun }, _) do
|
||||
defp each_impl_for({ kind, fun, _ }, _, _) do
|
||||
quote do
|
||||
defp __raw_impl__(arg) when unquote(fun).(arg) do
|
||||
Module.concat __MODULE__, unquote(kind)
|
||||
__MODULE__.unquote(kind)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -267,6 +273,95 @@ end
|
||||
defmodule Protocol.DSL do
|
||||
@moduledoc false
|
||||
|
||||
# We need to use :lists because Enum is not available yet
|
||||
require :lists, as: L
|
||||
|
||||
@doc false
|
||||
def args_and_body(module, name, arity) do
|
||||
# Generate arguments according the arity. The arguments
|
||||
# are named xa, xb and so forth. We cannot use string
|
||||
# interpolation to generate the arguments because of compile
|
||||
# dependencies, so we use the <<>> instead.
|
||||
args = lc i inlist :lists.seq(1, arity) do
|
||||
{ binary_to_atom(<<?x, i + 64>>), 0, :quoted }
|
||||
end
|
||||
|
||||
{ conversions, fallback, returns_nil } = conversions_for(module)
|
||||
clauses = [default_clause(name, args)]
|
||||
|
||||
if returns_nil do
|
||||
clauses = [nil_clause()|clauses]
|
||||
end
|
||||
|
||||
if L.keyfind(Record, 1, conversions) do
|
||||
clauses = [record_clause(name, args, fallback)|clauses]
|
||||
end
|
||||
|
||||
body =
|
||||
quote do
|
||||
case __raw_impl__(xA), do: unquote({ :->, 0, clauses })
|
||||
end
|
||||
|
||||
{ args, body }
|
||||
end
|
||||
|
||||
@doc false
|
||||
def callback_from_spec(module, name, arity) do
|
||||
tuple = { name, arity }
|
||||
specs = Module.get_attribute(module, :spec)
|
||||
|
||||
found = lc { k, v } inlist specs, k == tuple do
|
||||
Kernel.Typespec.define_callback(module, tuple, v)
|
||||
true
|
||||
end
|
||||
|
||||
found != []
|
||||
end
|
||||
|
||||
defp conversions_for(module) do
|
||||
only = Module.get_attribute(module, :only)
|
||||
except = Module.get_attribute(module, :except)
|
||||
Protocol.conversions_for(module, only, except)
|
||||
end
|
||||
|
||||
defp default_clause(name, args) do
|
||||
quote do: { [other], apply(other, unquote(name), [unquote_splicing(args)]) }
|
||||
end
|
||||
|
||||
defp nil_clause() do
|
||||
quote do
|
||||
{ [nil], raise(Protocol.UndefinedError, protocol: __MODULE__, structure: xA) }
|
||||
end
|
||||
end
|
||||
|
||||
defp record_clause(name, args, fallback) do
|
||||
arity = length(args)
|
||||
|
||||
quote do
|
||||
{ [__MODULE__.Record],
|
||||
(target = Module.concat(__MODULE__, :erlang.element(1, xA))
|
||||
try do
|
||||
target.unquote(name)(unquote_splicing(args))
|
||||
catch
|
||||
:error, :undef, [[{ ^target, name, args, _ }|_]|_] when
|
||||
name == unquote(name) and length(args) == unquote(arity) ->
|
||||
unquote(catch_clause(args, fallback))
|
||||
end) }
|
||||
end
|
||||
end
|
||||
|
||||
defp catch_clause(args, fallback) do
|
||||
if fallback do
|
||||
quote do
|
||||
apply unquote(fallback), name, [unquote_splicing(args)]
|
||||
end
|
||||
else
|
||||
quote do
|
||||
raise Protocol.UndefinedError, protocol: __MODULE__, structure: xA
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defmacro def(expression) do
|
||||
case expression do
|
||||
{ _, _, args } when args == [] or is_atom(args) ->
|
||||
@@ -279,43 +374,26 @@ defmodule Protocol.DSL do
|
||||
|
||||
arity = length(args)
|
||||
|
||||
# Generate arguments according the arity. The arguments
|
||||
# are named xa, xb and so forth. We cannot use string
|
||||
# interpolation to generate the arguments because of compile
|
||||
# dependencies, so we use the <<>> instead.
|
||||
generated = lc i inlist :lists.seq(1, arity) do
|
||||
{ binary_to_atom(<<?x, i + 64>>), 0, :quoted }
|
||||
end
|
||||
type_args = lc _ inlist :lists.seq(2, arity), do: quote(do: term)
|
||||
type_args = [quote(do: t) | type_args]
|
||||
|
||||
quote do
|
||||
# Append new function to the list
|
||||
@functions [unquote({name, arity})|@functions]
|
||||
name = unquote(name)
|
||||
arity = unquote(arity)
|
||||
|
||||
@functions [{name, arity}|@functions]
|
||||
|
||||
# Generate a fake definition with the user
|
||||
# signature that will be used by docs
|
||||
Kernel.def unquote(name).(unquote_splicing(args))
|
||||
|
||||
Kernel.def unquote(name).(unquote_splicing(generated)) do
|
||||
case __raw_impl__(xA) do
|
||||
__MODULE__.Record ->
|
||||
target = Module.concat(__MODULE__, :erlang.element(1, xA))
|
||||
try do
|
||||
target.unquote(name)(unquote_splicing(generated))
|
||||
catch
|
||||
:error, :undef, [[{ ^target, unquote(name), args, _ }|_]|_] when length(args) == unquote(arity) ->
|
||||
case __fallback__ do
|
||||
nil ->
|
||||
raise Protocol.UndefinedError, protocol: __MODULE__, structure: xA
|
||||
other ->
|
||||
apply other, unquote(name), [unquote_splicing(generated)]
|
||||
end
|
||||
end
|
||||
nil ->
|
||||
raise Protocol.UndefinedError, protocol: __MODULE__, structure: xA
|
||||
other ->
|
||||
apply other, unquote(name), [unquote_splicing(generated)]
|
||||
end
|
||||
end
|
||||
{ args, body } = Protocol.DSL.args_and_body(__MODULE__, name, arity)
|
||||
Kernel.def unquote(name), args, [], do: body
|
||||
|
||||
# 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
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
+220
-168
@@ -18,7 +18,7 @@ defmodule Record do
|
||||
|
||||
@doc """
|
||||
Main entry point for records definition. It defines a module
|
||||
with the given `name` and the fields specified in `modules`.
|
||||
with the given `name` and the fields specified in `values`.
|
||||
This is invoked directly by `Kernel.defrecord`, so check it
|
||||
for more information and documentation.
|
||||
"""
|
||||
@@ -29,22 +29,39 @@ defmodule Record do
|
||||
quote do
|
||||
defmodule unquote(name) do
|
||||
@moduledoc false
|
||||
Record.deffunctions(unquote(values), unquote(opts), __ENV__)
|
||||
import Record.DSL
|
||||
|
||||
values = unquote(values)
|
||||
opts = unquote(opts)
|
||||
Record.deffunctions(values, opts, __ENV__)
|
||||
unquote(block)
|
||||
Record.deftypes(values, @record_type, opts, __ENV__)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Main entry point for private records definition. It defines
|
||||
a set of macros with the given `name` and the fields specified
|
||||
in `values`. This is invoked directly by `Kernel.defrecordp`,
|
||||
so check it for more information and documentation.
|
||||
"""
|
||||
def defrecordp(name, fields) do
|
||||
quote do
|
||||
Record.defmacros(unquote(name), unquote(fields), __ENV__)
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Defines record functions skipping the module definition.
|
||||
This is called directly by `defrecord`. It expects the
|
||||
module environment, the module values and a keyword list
|
||||
of options.
|
||||
This is called directly by `defrecord`. It expects the record
|
||||
values, a set of options and the module environment.
|
||||
|
||||
## Examples
|
||||
|
||||
defmodule CustomRecord do
|
||||
Record.deffunctions [:name, :age], __ENV__
|
||||
Record.deftypes [:name, :age], __ENV__
|
||||
end
|
||||
|
||||
"""
|
||||
@@ -69,85 +86,83 @@ defmodule Record do
|
||||
:elixir_module.eval_quoted(env, contents, [], [])
|
||||
else
|
||||
contents = [quote(do: @__record__ unquote(escaped))|contents]
|
||||
Module.eval_quoted(env, contents)
|
||||
Module.eval_quoted(env.module, contents, [], env.location)
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Defines three macros for reading and writing records values.
|
||||
These macros are private to the current module and are
|
||||
basically a simple mechanism for manipulating tuples when
|
||||
there isn't an interest in exposing the record as a whole.
|
||||
In some ways, it is similar to Erlang records, since it is
|
||||
only available at compilation time.
|
||||
Defines types and specs for the record.
|
||||
"""
|
||||
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)
|
||||
]
|
||||
|
||||
# Special case for bootstraping purposes
|
||||
if :erlang.function_exported(Module, :eval_quoted, 2) do
|
||||
Module.eval_quoted(env.module, contents, [], env.location)
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Defines macros for manipulating records. This is called
|
||||
directly by `defrecordp`. It expects the macro name, the
|
||||
record values and the environment.
|
||||
|
||||
## Examples
|
||||
|
||||
defmodule CustomModule do
|
||||
Record.defmacros :_user, [:name, :age], __ENV__
|
||||
|
||||
def new(name, age) do
|
||||
_user(name: name, age: age)
|
||||
end
|
||||
|
||||
def name(user, name) do
|
||||
_user(user, name: name)
|
||||
end
|
||||
|
||||
def age(user) do
|
||||
_user(user, :age)
|
||||
end
|
||||
|
||||
def to_keywords(user) do
|
||||
_user(user)
|
||||
end
|
||||
|
||||
def name_and_age(user) do
|
||||
_user(user, [:name, :age])
|
||||
end
|
||||
|
||||
def age_and_name(user) do
|
||||
_user(user, [:age, :name])
|
||||
end
|
||||
defmodule CustomRecord do
|
||||
Record.defmacros :user, [:name, :age], __ENV__
|
||||
end
|
||||
|
||||
"""
|
||||
def defmacros(name, values, env) do
|
||||
escaped = lc value inlist values, do: Macro.escape(convert_value(value))
|
||||
escaped = lc value inlist values do
|
||||
{ key, value } = convert_value(value)
|
||||
{ key, Macro.escape(value) }
|
||||
end
|
||||
|
||||
contents = quote do
|
||||
defmacrop unquote(name).() do
|
||||
Record.access(__MODULE__, unquote(escaped), [], __CALLER__)
|
||||
end
|
||||
|
||||
defmacrop unquote(name).(record) when is_tuple(record) do
|
||||
Record.to_keywords(__CALLER__, __MODULE__, unquote(escaped), record)
|
||||
Record.to_keywords(__MODULE__, unquote(escaped), record)
|
||||
end
|
||||
|
||||
defmacrop unquote(name).(args) do
|
||||
Record.access(__CALLER__, __MODULE__, unquote(escaped), args)
|
||||
Record.access(__MODULE__, unquote(escaped), args, __CALLER__)
|
||||
end
|
||||
|
||||
defmacrop unquote(name).(record, key) when is_atom(key) do
|
||||
Record.get(__CALLER__, __MODULE__, unquote(escaped), record, key)
|
||||
Record.get(__MODULE__, unquote(escaped), record, key)
|
||||
end
|
||||
|
||||
defmacrop unquote(name).(record, args) do
|
||||
Record.dispatch(__CALLER__, __MODULE__, unquote(escaped), record, args)
|
||||
Record.dispatch(__MODULE__, unquote(escaped), record, args, __CALLER__)
|
||||
end
|
||||
end
|
||||
|
||||
Module.eval_quoted(env, contents)
|
||||
Module.eval_quoted(env.module, contents, [], env.location)
|
||||
end
|
||||
|
||||
defp convert_value(atom) when is_atom(atom) do
|
||||
{ atom, nil }
|
||||
end
|
||||
|
||||
defp convert_value(other), do: other
|
||||
|
||||
# Implements the access macro used by records.
|
||||
# It returns a quoted expression that defines
|
||||
# a record or a match in case the record is
|
||||
# inside a match.
|
||||
@doc false
|
||||
def access(caller, atom, fields, keyword) do
|
||||
def access(atom, fields, keyword, caller) do
|
||||
unless is_keyword(keyword) do
|
||||
raise "expected contents inside brackets to be a Keyword"
|
||||
end
|
||||
@@ -184,11 +199,11 @@ defmodule Record do
|
||||
|
||||
# Dispatch the call to either update or to_list depending on the args given.
|
||||
@doc false
|
||||
def dispatch(caller, atom, fields, record, args) do
|
||||
def dispatch(atom, fields, record, args, caller) do
|
||||
if is_keyword(args) do
|
||||
update(caller, atom, fields, record, args)
|
||||
update(atom, fields, record, args, caller)
|
||||
else
|
||||
to_list(caller, atom, fields, record, args)
|
||||
to_list(atom, fields, record, args)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -196,7 +211,7 @@ defmodule Record do
|
||||
# It returns a quoted expression that represents
|
||||
# the access given by the keywords.
|
||||
@doc false
|
||||
defp update(caller, atom, fields, var, keyword) do
|
||||
defp update(atom, fields, var, keyword, caller) do
|
||||
unless is_keyword(keyword) do
|
||||
raise "expected contents inside brackets to be a Keyword"
|
||||
end
|
||||
@@ -221,7 +236,7 @@ defmodule Record do
|
||||
# It returns a quoted expression that represents
|
||||
# getting the value of a given field.
|
||||
@doc false
|
||||
def get(_caller, atom, fields, var, key) do
|
||||
def get(atom, fields, var, key) do
|
||||
index = find_index(fields, key, 0)
|
||||
if index do
|
||||
quote do
|
||||
@@ -236,12 +251,12 @@ defmodule Record do
|
||||
# It returns a quoted expression that represents
|
||||
# converting record to keywords list.
|
||||
@doc false
|
||||
def to_keywords(_caller, _atom, fields, record) do
|
||||
Enum.map Keyword.from_enum(fields),
|
||||
fn({key, _default}) ->
|
||||
def to_keywords(_atom, fields, record) do
|
||||
Enum.map fields,
|
||||
fn { key, _default } ->
|
||||
index = find_index(fields, key, 0)
|
||||
quote do
|
||||
{unquote(key), :erlang.element(unquote(index + 2), unquote(record))}
|
||||
{ unquote(key), :erlang.element(unquote(index + 2), unquote(record)) }
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -250,7 +265,7 @@ defmodule Record do
|
||||
# It returns a quoted expression that represents
|
||||
# extracting given fields from record.
|
||||
@doc false
|
||||
defp to_list(_caller, atom, fields, record, keys) do
|
||||
defp to_list(atom, fields, record, keys) do
|
||||
Enum.map keys,
|
||||
fn(key) ->
|
||||
index = find_index(fields, key, 0)
|
||||
@@ -262,11 +277,7 @@ defmodule Record do
|
||||
end
|
||||
end
|
||||
|
||||
defp is_keyword(list) when is_list(list), do: :lists.all(is_keyword_tuple(&1), list)
|
||||
defp is_keyword(_), do: false
|
||||
|
||||
defp is_keyword_tuple({ x, _ }) when is_atom(x), do: true
|
||||
defp is_keyword_tuple(_), do: false
|
||||
## Function generation
|
||||
|
||||
# Define __record__/1 and __record__/2 as reflection functions
|
||||
# that returns the record names and fields.
|
||||
@@ -378,10 +389,6 @@ defmodule Record do
|
||||
end
|
||||
end
|
||||
|
||||
defp find_index([{ k, _ }|_], k, i), do: i
|
||||
defp find_index([{ _, _ }|t], k, i), do: find_index(t, k, i + 1)
|
||||
defp find_index([], _k, _i), do: nil
|
||||
|
||||
# Implement readers. For a declaration like:
|
||||
#
|
||||
# defrecord FileInfo, atime: nil, mtime: nil
|
||||
@@ -434,18 +441,24 @@ defmodule Record do
|
||||
|
||||
defp writers([], _i, acc), do: acc
|
||||
|
||||
# Defines update/2
|
||||
# Define an updater method that receives a
|
||||
# keyword list and updates the record.
|
||||
defp updater(values) do
|
||||
fields =
|
||||
lc {key, _default} inlist values do
|
||||
index = find_index(values, key, 1)
|
||||
quote do
|
||||
Keyword.get(keywords,
|
||||
unquote(key),
|
||||
elem(record, unquote(find_index(values, key, 1))))
|
||||
Keyword.get(keywords, unquote(key), elem(record, unquote(index)))
|
||||
end
|
||||
end
|
||||
contents = {:{}, 0, [(quote do: __MODULE__)|fields]}
|
||||
|
||||
contents = { :{}, 0, [(quote do: __MODULE__)|fields] }
|
||||
|
||||
quote do
|
||||
def update([], record) do
|
||||
record
|
||||
end
|
||||
|
||||
def update(keywords, record) do
|
||||
unquote(contents)
|
||||
end
|
||||
@@ -459,106 +472,96 @@ defmodule Record do
|
||||
end
|
||||
|
||||
defp extensions([], _i, acc, _), do: acc
|
||||
|
||||
## Types/specs generation
|
||||
|
||||
defp core_specs(values) do
|
||||
types = lc { _, _, spec } inlist values, do: spec
|
||||
|
||||
quote do
|
||||
unless Kernel.Typespec.defines_type?(__MODULE__, :t, 0) 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
|
||||
end
|
||||
end
|
||||
|
||||
defp accessor_specs([{ key, _default, spec }|t], acc) do
|
||||
contents = quote do
|
||||
@spec unquote(key)(t), do: unquote(spec)
|
||||
@spec unquote(key)(unquote(spec), t), do: t
|
||||
end
|
||||
accessor_specs(t, [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
|
||||
|
||||
## Helpers
|
||||
|
||||
defp is_keyword(list) when is_list(list), do: :lists.all(is_keyword_tuple(&1), list)
|
||||
defp is_keyword(_), do: false
|
||||
|
||||
defp is_keyword_tuple({ x, _ }) when is_atom(x), do: true
|
||||
defp is_keyword_tuple(_), do: false
|
||||
|
||||
defp convert_value(atom) when is_atom(atom), do: { atom, nil }
|
||||
defp convert_value(other), do: other
|
||||
|
||||
defp find_index([{ k, _ }|_], k, i), do: i
|
||||
defp find_index([{ _, _ }|t], k, i), do: find_index(t, k, i + 1)
|
||||
defp find_index([], _k, _i), do: nil
|
||||
|
||||
defp find_spec(types, name) do
|
||||
matches = lc { k, v } inlist types, name == k, do: v
|
||||
case matches do
|
||||
[h|_] -> h
|
||||
_ -> quote do: term
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defmodule Record.Extractor do
|
||||
defmodule Record.DSL do
|
||||
@moduledoc false
|
||||
|
||||
# Retrieve a record definition from an Erlang file using
|
||||
# the same lookup as the *include* attribute from Erlang modules.
|
||||
def retrieve(name, from: string) do
|
||||
file = to_char_list(string)
|
||||
|
||||
case :code.where_is_file(file) do
|
||||
:non_existing -> realfile = file
|
||||
realfile -> nil
|
||||
@doc """
|
||||
Defines the type for each field in the record.
|
||||
Expects a keyword list.
|
||||
"""
|
||||
defmacro record_type(opts) when is_list(opts) do
|
||||
quote do
|
||||
@record_type quote do: unquote(opts)
|
||||
end
|
||||
|
||||
retrieve_record(name, realfile)
|
||||
end
|
||||
|
||||
# Retrieve a record definition from an Erlang file using
|
||||
# the same lookup as the *include_lib* attribute from Erlang modules.
|
||||
def retrieve(name, from_lib: file) do
|
||||
[app|path] = :filename.split(to_char_list(file))
|
||||
case :code.lib_dir(to_char_list(app)) do
|
||||
{ :error, _ } ->
|
||||
raise ArgumentError, "Lib file #{to_binary(file)} could not be found"
|
||||
libpath ->
|
||||
retrieve_record name, :filename.join([libpath|path])
|
||||
end
|
||||
end
|
||||
|
||||
# Retrieve the record with the given name from the given file
|
||||
defp retrieve_record(name, file) do
|
||||
records = retrieve_from_file(file)
|
||||
if record = List.keyfind(records, name, 0) do
|
||||
parse_record(record)
|
||||
else
|
||||
raise ArgumentError, "No record #{name} found at #{to_binary(file)}"
|
||||
end
|
||||
end
|
||||
|
||||
# Parse the given file and retrieve all existent records.
|
||||
defp retrieve_from_file(file) do
|
||||
lc { :attribute, _, :record, record } inlist read_file(file), do: record
|
||||
end
|
||||
|
||||
# Read a file and return its abstract syntax form that also
|
||||
# includes record and other preprocessor modules. This is done
|
||||
# by using Erlang's epp_dodger.
|
||||
defp read_file(file) do
|
||||
case :epp_dodger.quick_parse_file(file) do
|
||||
{ :ok, form } ->
|
||||
form
|
||||
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.
|
||||
defp parse_record({ _name, fields }) do
|
||||
cons = List.foldr fields, { nil, 0 }, fn f, acc ->
|
||||
{ :cons, 0, parse_field(f), acc }
|
||||
end
|
||||
{ :value, list, _ } = :erl_eval.expr(cons, [])
|
||||
list
|
||||
end
|
||||
|
||||
defp parse_field({ :typed_record_field, record_field, _type }) do
|
||||
parse_field(record_field)
|
||||
end
|
||||
|
||||
defp parse_field({ :record_field, _, key }) do
|
||||
{ :tuple, 0, [key, {:atom, 0, :nil}] }
|
||||
end
|
||||
|
||||
defp parse_field({ :record_field, _, key, value }) do
|
||||
{ :tuple, 0, [key, value] }
|
||||
end
|
||||
end
|
||||
|
||||
defmodule Record.Extensions do
|
||||
@moduledoc false
|
||||
|
||||
# Main entry point. It defines both default functions
|
||||
# via `default_for` and extensions via `extension_for`.
|
||||
# Function definition
|
||||
|
||||
def functions_for(key, default, i) do
|
||||
[ default_for(key, default, i),
|
||||
extension_for(key, default, i) ]
|
||||
end
|
||||
|
||||
# Skip the __exception__ for defexception.
|
||||
def default_for(:__exception__, _default, _i) do
|
||||
defp default_for(:__exception__, _default, _i) do
|
||||
nil
|
||||
end
|
||||
|
||||
# Define the default functions for each field.
|
||||
def default_for(key, _default, i) do
|
||||
bin_update = "update_" <> atom_to_binary(key)
|
||||
update = binary_to_atom(bin_update)
|
||||
defp default_for(key, _default, i) do
|
||||
update = prefix("update_", key)
|
||||
|
||||
quote do
|
||||
def unquote(update).(function, record) do
|
||||
@@ -568,11 +571,9 @@ defmodule Record.Extensions do
|
||||
end
|
||||
end
|
||||
|
||||
# Define extensions based on the default type.
|
||||
def extension_for(key, default, i) when is_list(default) do
|
||||
bin_key = atom_to_binary(key)
|
||||
prepend = :"prepend_#{bin_key}"
|
||||
merge = :"merge_#{bin_key}"
|
||||
defp extension_for(key, default, i) when is_list(default) do
|
||||
prepend = prefix("prepend_", key)
|
||||
merge = prefix("merge_", key)
|
||||
|
||||
quote do
|
||||
def unquote(prepend).(value, record) do
|
||||
@@ -587,9 +588,8 @@ defmodule Record.Extensions do
|
||||
end
|
||||
end
|
||||
|
||||
def extension_for(key, default, i) when is_number(default) do
|
||||
bin_key = atom_to_binary(key)
|
||||
increment = :"increment_#{bin_key}"
|
||||
defp extension_for(key, default, i) when is_number(default) do
|
||||
increment = prefix("increment_", key)
|
||||
|
||||
quote do
|
||||
def unquote(increment).(value // 1, record) do
|
||||
@@ -599,9 +599,8 @@ defmodule Record.Extensions do
|
||||
end
|
||||
end
|
||||
|
||||
def extension_for(key, default, i) when is_boolean(default) do
|
||||
bin_key = atom_to_binary(key)
|
||||
toggle = :"toggle_#{bin_key}"
|
||||
defp extension_for(key, default, i) when is_boolean(default) do
|
||||
toggle = prefix("toggle_", key)
|
||||
|
||||
quote do
|
||||
def unquote(toggle).(record) do
|
||||
@@ -611,5 +610,58 @@ defmodule Record.Extensions do
|
||||
end
|
||||
end
|
||||
|
||||
def extension_for(_, _, _), do: nil
|
||||
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
|
||||
binary_to_atom prefix <> atom_to_binary(key)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
defmodule Record.Extractor do
|
||||
@moduledoc false
|
||||
|
||||
# Retrieve a record definition from an Erlang file using
|
||||
# the same lookup as the *include* attribute from Erlang modules.
|
||||
def retrieve(name, from: string) do
|
||||
file = to_char_list(string)
|
||||
|
||||
case :code.where_is_file(file) do
|
||||
:non_existing -> realfile = file
|
||||
realfile -> nil
|
||||
end
|
||||
|
||||
retrieve_record(name, realfile)
|
||||
end
|
||||
|
||||
# Retrieve a record definition from an Erlang file using
|
||||
# the same lookup as the *include_lib* attribute from Erlang modules.
|
||||
def retrieve(name, from_lib: file) do
|
||||
[app|path] = :filename.split(to_char_list(file))
|
||||
|
||||
case :code.lib_dir(to_char_list(app)) do
|
||||
{ :error, _ } ->
|
||||
raise ArgumentError, "Lib file #{to_binary(file)} could not be found"
|
||||
libpath ->
|
||||
retrieve_record name, :filename.join([libpath|path])
|
||||
end
|
||||
end
|
||||
|
||||
# Retrieve the record with the given name from the given file
|
||||
defp retrieve_record(name, file) do
|
||||
records = retrieve_from_file(file)
|
||||
if record = List.keyfind(records, name, 0) do
|
||||
parse_record(record)
|
||||
else
|
||||
raise ArgumentError, "No record #{name} found at #{to_binary(file)}"
|
||||
end
|
||||
end
|
||||
|
||||
# Parse the given file and retrieve all existent records.
|
||||
defp retrieve_from_file(file) do
|
||||
lc { :attribute, _, :record, record } inlist read_file(file), do: record
|
||||
end
|
||||
|
||||
# Read a file and return its abstract syntax form that also
|
||||
# includes record and other preprocessor modules. This is done
|
||||
# by using Erlang's epp_dodger.
|
||||
defp read_file(file) do
|
||||
case :epp_dodger.quick_parse_file(file) do
|
||||
{ :ok, form } ->
|
||||
form
|
||||
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.
|
||||
defp parse_record({ _name, fields }) do
|
||||
cons = List.foldr fields, { nil, 0 }, fn f, acc ->
|
||||
{ :cons, 0, parse_field(f), acc }
|
||||
end
|
||||
{ :value, list, _ } = :erl_eval.expr(cons, [])
|
||||
list
|
||||
end
|
||||
|
||||
defp parse_field({ :typed_record_field, record_field, _type }) do
|
||||
parse_field(record_field)
|
||||
end
|
||||
|
||||
defp parse_field({ :record_field, _, key }) do
|
||||
{ :tuple, 0, [key, {:atom, 0, :undefined}] }
|
||||
end
|
||||
|
||||
defp parse_field({ :record_field, _, key, value }) do
|
||||
{ :tuple, 0, [key, value] }
|
||||
end
|
||||
end
|
||||
+21
-46
@@ -43,6 +43,9 @@ defmodule Regex do
|
||||
a binary, a char list will return a char list).
|
||||
"""
|
||||
|
||||
defrecordp :regex, [:re_pattern, :source, :options, :groups]
|
||||
@type t :: { Regex, term, term, term, term }
|
||||
|
||||
defexception CompileError, message: "regex could not be compiled"
|
||||
|
||||
@doc """
|
||||
@@ -57,9 +60,10 @@ defmodule Regex do
|
||||
opts = translate_options(options)
|
||||
re_opts = opts -- [:groups]
|
||||
groups = if opts != re_opts, do: parse_groups(source)
|
||||
|
||||
case :re.compile(source, re_opts) do
|
||||
{ :ok, compiled } ->
|
||||
{ :ok, { Regex, compiled, source, options, groups } }
|
||||
{ :ok, re_pattern } ->
|
||||
{ :ok, regex(re_pattern: re_pattern, source: source, options: options, groups: groups) }
|
||||
error ->
|
||||
error
|
||||
end
|
||||
@@ -87,7 +91,7 @@ defmodule Regex do
|
||||
Regex.index %r/e/, "abcd" #=> nil
|
||||
|
||||
"""
|
||||
def index({ Regex, compiled, _, _, _ }, string) do
|
||||
def index(regex(re_pattern: compiled), string) do
|
||||
case :re.run(string, compiled, [{ :capture, :first, :index }]) do
|
||||
:nomatch -> nil
|
||||
{ :match, [{index,_}] } -> index
|
||||
@@ -103,7 +107,7 @@ defmodule Regex do
|
||||
Regex.match? %r/foo/, "bar" #=> false
|
||||
|
||||
"""
|
||||
def match?({ Regex, compiled, _, _, _ }, string) do
|
||||
def match?(regex(re_pattern: compiled), string) do
|
||||
:re.run(string, compiled, [{ :capture, :none }]) == :match
|
||||
end
|
||||
|
||||
@@ -118,12 +122,13 @@ defmodule Regex do
|
||||
|
||||
"""
|
||||
def run(regex, string, options // [])
|
||||
def run({ Regex, compiled, _, _, groups }, string, options) do
|
||||
|
||||
def run(regex(re_pattern: compiled, groups: groups), string, options) do
|
||||
return = Keyword.get(options, :return, return_for(string))
|
||||
|
||||
captures =
|
||||
case Keyword.get(options, :capture, :all) do
|
||||
:groups -> groups || raise "Regex was not compiled with g"
|
||||
:groups -> groups || raise "regex was not compiled with g"
|
||||
others -> others
|
||||
end
|
||||
|
||||
@@ -141,7 +146,7 @@ defmodule Regex do
|
||||
Regex.captures %r/c(?<foo>d)/g, "abcd" #=> [{:foo, ["d"]}]
|
||||
|
||||
"""
|
||||
def captures({ Regex, _, _, _, groups } = regex, string, options // []) do
|
||||
def captures(regex(groups: groups) = regex, string, options // []) do
|
||||
unless captures = Keyword.get(options, :capture) do
|
||||
captures = if groups do
|
||||
List.sort(groups)
|
||||
@@ -154,30 +159,10 @@ defmodule Regex do
|
||||
if results, do: Enum.zip captures, results
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a list with the match indexes in the given string.
|
||||
The matches are tuples where the first element is the index
|
||||
(zero indexed) the match happened and the second is the length
|
||||
of the match.
|
||||
|
||||
## Examples
|
||||
|
||||
Regex.indexes %r/c(d)/, "abcd" #=> [{2,2},{3,1}]
|
||||
Regex.indexes %r/e/, "abcd" #=> nil
|
||||
|
||||
"""
|
||||
def indexes({ Regex, compiled, _, _, _ }, string) do
|
||||
IO.write "[WARNING] Regex.indexes is deprecated, please use Regex.run with return: :index as option instead\n#{Exception.formatted_stacktrace}"
|
||||
case :re.run(string, compiled, [{ :capture, :all, :index }]) do
|
||||
:nomatch -> nil
|
||||
{ :match, results } -> results
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the underlying re_pattern in the regular expression.
|
||||
"""
|
||||
def re_pattern({ Regex, compiled, _, _, _ }) do
|
||||
def re_pattern(regex(re_pattern: compiled)) do
|
||||
compiled
|
||||
end
|
||||
|
||||
@@ -189,7 +174,7 @@ defmodule Regex do
|
||||
Regex.source %r(foo) #=> "foo"
|
||||
|
||||
"""
|
||||
def source({ Regex, _, source, _, _ }) do
|
||||
def source(regex(source: source)) do
|
||||
source
|
||||
end
|
||||
|
||||
@@ -201,8 +186,8 @@ defmodule Regex do
|
||||
Regex.opts %r(foo)m #=> 'm'
|
||||
|
||||
"""
|
||||
def opts({ Regex, _, _, opts, _ }) do
|
||||
opts
|
||||
def opts(regex(options: options)) do
|
||||
options
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -213,7 +198,7 @@ defmodule Regex do
|
||||
Regex.groups %r/(?<foo>foo)/g #=> ["foo"]
|
||||
|
||||
"""
|
||||
def groups({ Regex, _, _, _, groups }) do
|
||||
def groups(regex(groups: groups)) do
|
||||
groups
|
||||
end
|
||||
|
||||
@@ -231,7 +216,8 @@ defmodule Regex do
|
||||
|
||||
"""
|
||||
def scan(regex, string, options // [])
|
||||
def scan({ Regex, compiled, _, _, _ }, string, options) do
|
||||
|
||||
def scan(regex(re_pattern: compiled), string, options) do
|
||||
return = options[:return] || return_for(string)
|
||||
options = [{ :capture, :all, return }, :global]
|
||||
case :re.run(string, compiled, options) do
|
||||
@@ -247,12 +233,7 @@ defmodule Regex do
|
||||
|
||||
def split(regex, string, options // [])
|
||||
|
||||
def split(regex, string, options) when is_integer(options) or is_atom(options) do
|
||||
IO.write "[WARNING] Passing an integer or atom to Regex.split/3 is deprecated, pass a :parts option instead\n#{Exception.formatted_stacktrace}"
|
||||
split(regex, string, parts: options)
|
||||
end
|
||||
|
||||
def split({ Regex, compiled, _, _, _ }, string, options) do
|
||||
def split(regex(re_pattern: compiled), string, options) do
|
||||
parts =
|
||||
cond do
|
||||
options[:global] == false -> 2
|
||||
@@ -283,19 +264,13 @@ defmodule Regex do
|
||||
Regex.replace(%r/(b)/, "abc", "[\\1]") #=> "a[b]c"
|
||||
|
||||
"""
|
||||
def replace({ Regex, compiled, _, _, _ }, string, replacement, options // []) 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 = [{ :return, return }|opts]
|
||||
:re.replace(string, compiled, replacement, opts)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def replace_all({ Regex, compiled, _, _, _ }, string, replacement) do
|
||||
IO.write "[WARNING] Regex.replace_all is deprecated, simply use Regex.replace instead\n#{Exception.formatted_stacktrace}"
|
||||
:re.replace(string, compiled, replacement, [{ :return, return_for(string) }, :global])
|
||||
end
|
||||
|
||||
# Helpers
|
||||
|
||||
@doc false
|
||||
|
||||
+105
-82
@@ -1,13 +1,23 @@
|
||||
defmodule String do
|
||||
@moduledoc """
|
||||
A string in Elixir is a utf-8 binary. This module
|
||||
contains function to work with utf-8 data and its
|
||||
codepoints.
|
||||
contains function to work with utf-8 data, its
|
||||
codepoints and graphemes.
|
||||
|
||||
Notice that graphemes is a superset of UTF-8 codepoints
|
||||
which also contains named sequences as defined per
|
||||
http://www.unicode.org/reports/tr34/. In short, graphemes
|
||||
also contain multiple characters that are "perceived as
|
||||
a single character" by readers.
|
||||
|
||||
For working with raw binaries, use Erlang's :binary
|
||||
module.
|
||||
"""
|
||||
|
||||
@type t :: binary
|
||||
@type codepoint :: t
|
||||
@type grapheme :: t
|
||||
|
||||
@doc """
|
||||
Checks if a string is printable considering it is encoded
|
||||
as UTF-8. Returns true if so, false otherwise.
|
||||
@@ -17,7 +27,7 @@ defmodule String do
|
||||
String.printable?("abc") #=> true
|
||||
|
||||
"""
|
||||
|
||||
@spec printable?(t), do: boolean
|
||||
# Allow basic ascii chars
|
||||
def printable?(<<c, t :: binary>>) when c in ?\s..?~ do
|
||||
printable?(t)
|
||||
@@ -90,7 +100,6 @@ defmodule String do
|
||||
def printable?(<<>>), do: true
|
||||
def printable?(_), do: false
|
||||
|
||||
|
||||
@doc """
|
||||
Divides a string into sub string based on a pattern,
|
||||
returning a list of these sub string. The pattern can
|
||||
@@ -116,6 +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]
|
||||
def split(binary, pattern // " ", options // [])
|
||||
|
||||
def split(binary, pattern, options) when is_regex(pattern) do
|
||||
@@ -128,7 +140,11 @@ defmodule String do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Convert all characters on the given string to upper case.
|
||||
Convert all characters on the given string to upcase.
|
||||
|
||||
This function relies on the simple uppercase mapping
|
||||
available in Unicode 6.2.0, check http://unicode.org/reports/tr44/
|
||||
for more information.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -137,22 +153,15 @@ defmodule String do
|
||||
String.upcase("josé") #=> "JOSÉ"
|
||||
|
||||
"""
|
||||
def upcase(<<>>), do: <<>>
|
||||
|
||||
def upcase(<<195, c, t :: binary>>) when c in 160..191 do
|
||||
<<195, c - 32, upcase(t) :: binary>>
|
||||
end
|
||||
|
||||
def upcase(<<c, t :: binary>>) when c in ?a..?z do
|
||||
<<c - 32, upcase(t) :: binary>>
|
||||
end
|
||||
|
||||
def upcase(<<c, t :: binary>>) do
|
||||
<<c , upcase(t) :: binary>>
|
||||
end
|
||||
@spec upcase(t), do: t
|
||||
defdelegate upcase(binary), to: String.Unicode
|
||||
|
||||
@doc """
|
||||
Convert all characters on the given string to down case.
|
||||
Convert all characters on the given string to downcase.
|
||||
|
||||
This function relies on the simple lowercase mapping
|
||||
available in Unicode 6.2.0, check http://unicode.org/reports/tr44/
|
||||
for more information.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -161,19 +170,8 @@ defmodule String do
|
||||
String.downcase("JOSÉ") #=> "josé"
|
||||
|
||||
"""
|
||||
def downcase(<<>>), do: <<>>
|
||||
|
||||
def downcase(<<195, c, t :: binary>>) when c in 128..159 do
|
||||
<<195, c + 32, downcase(t) :: binary>>
|
||||
end
|
||||
|
||||
def downcase(<<c, t :: binary>>) when c in ?A..?Z do
|
||||
<<c + 32, downcase(t) :: binary>>
|
||||
end
|
||||
|
||||
def downcase(<<c, t :: binary>>) do
|
||||
<<c , downcase(t) :: binary>>
|
||||
end
|
||||
@spec downcase(t), do: t
|
||||
defdelegate downcase(binary), to: String.Unicode
|
||||
|
||||
@doc """
|
||||
Returns a string where trailing char have been
|
||||
@@ -185,8 +183,12 @@ defmodule String do
|
||||
String.rstrip(" abc _", ?_) #=> " abc "
|
||||
|
||||
"""
|
||||
@spec rstrip(t), do: t
|
||||
@spec rstrip(t, char), do: t
|
||||
def rstrip(string, char // ?\s)
|
||||
|
||||
def rstrip("", _char), do: ""
|
||||
|
||||
# Do a quick check before we traverse the whole
|
||||
# binary. :binary.last is a fast operation (it
|
||||
# does not traverse the whole binary).
|
||||
@@ -220,6 +222,8 @@ defmodule String do
|
||||
String.lstrip("_ abc _", ?_) #=> " abc _"
|
||||
|
||||
"""
|
||||
@spec lstrip(t), do: t
|
||||
@spec lstrip(t, char), do: t
|
||||
def lstrip(string, char // ?\s)
|
||||
|
||||
def lstrip(<<char, rest :: binary>>, char) do
|
||||
@@ -240,6 +244,8 @@ defmodule String do
|
||||
String.strip("a abc a", ?a) #=> " abc "
|
||||
|
||||
"""
|
||||
@spec strip(t), do: t
|
||||
@spec strip(t, char), do: t
|
||||
def strip(string, char // ?\s) do
|
||||
rstrip(lstrip(string, char), char)
|
||||
end
|
||||
@@ -263,6 +269,8 @@ 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
|
||||
def replace(subject, pattern, replacement, options // []) do
|
||||
opts = translate_replace_options(options)
|
||||
:binary.replace(subject, pattern, replacement, opts)
|
||||
@@ -287,6 +295,7 @@ defmodule String do
|
||||
String.duplicate("abc", 2) #=> "abcabc"
|
||||
|
||||
"""
|
||||
@spec duplicate(t, pos_integer), do: t
|
||||
def duplicate(subject, n) when is_integer(n) and n > 0 do
|
||||
:binary.copy(subject, n)
|
||||
end
|
||||
@@ -301,18 +310,51 @@ defmodule String do
|
||||
String.codepoints("ἅἪῼ") #=> ["ἅ","Ἢ","ῼ"]
|
||||
|
||||
"""
|
||||
def codepoints(string) do
|
||||
do_codepoints(codepoint(string))
|
||||
end
|
||||
|
||||
defp do_codepoints({char, rest}) do
|
||||
[char|do_codepoints(codepoint(rest))]
|
||||
end
|
||||
|
||||
defp do_codepoints(:no_codepoint), do: []
|
||||
@spec codepoints(t), do: [codepoint]
|
||||
defdelegate codepoints(string), to: String.Unicode
|
||||
|
||||
@doc """
|
||||
Returns the first codepoint from an utf8 string.
|
||||
Returns the next codepoint in a String.
|
||||
|
||||
The result is a tuple with the codepoint and the
|
||||
remaining of the string or `:no_codepoint` in case
|
||||
the String reached its end.
|
||||
|
||||
## Examples
|
||||
|
||||
String.next_codepoint("josé") #=> { "j", "osé" }
|
||||
|
||||
"""
|
||||
@spec next_codepoint(t), do: codepoint | :no_codepoint
|
||||
defdelegate next_codepoint(string), to: String.Unicode
|
||||
|
||||
@doc """
|
||||
Returns unicode graphemes in the string
|
||||
|
||||
## Examples
|
||||
String.graphemes("Ā̀stute") # => ["Ā̀","s","t","u","t","e"]
|
||||
|
||||
"""
|
||||
@spec graphemes(t), do: [grapheme]
|
||||
defdelegate graphemes(string), to: String.Unicode
|
||||
|
||||
@doc """
|
||||
Returns the next grapheme in a String.
|
||||
|
||||
The result is a tuple with the grapheme and the
|
||||
remaining of the string or `:no_grapheme` in case
|
||||
the String reached its end.
|
||||
|
||||
## Examples
|
||||
|
||||
String.next_grapheme("josé") #=> { "j", "osé" }
|
||||
|
||||
"""
|
||||
@spec next_grapheme(t), do: grapheme | :no_grapheme
|
||||
defdelegate next_grapheme(string), to: String.Unicode
|
||||
|
||||
@doc """
|
||||
Returns the first grapheme from an utf8 string.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -320,15 +362,16 @@ defmodule String do
|
||||
String.first("եոգլի") #=> "ե"
|
||||
|
||||
"""
|
||||
@spec first(t), do: grapheme
|
||||
def first(string) do
|
||||
case codepoint(string) do
|
||||
case next_grapheme(string) do
|
||||
{ char, _ } -> char
|
||||
:no_codepoint -> ""
|
||||
:no_grapheme -> ""
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the last codepoint from an utf8 string.
|
||||
Returns the last grapheme from an utf8 string.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -336,18 +379,19 @@ defmodule String do
|
||||
String.last("եոգլի") #=> "ի"
|
||||
|
||||
"""
|
||||
@spec last(t), do: grapheme
|
||||
def last(string) do
|
||||
do_last(codepoint(string), "")
|
||||
do_last(next_grapheme(string), "")
|
||||
end
|
||||
|
||||
defp do_last({char, rest}, _) do
|
||||
do_last(codepoint(rest), char)
|
||||
do_last(next_grapheme(rest), char)
|
||||
end
|
||||
|
||||
defp do_last(:no_codepoint, last_char), do: last_char
|
||||
defp do_last(:no_grapheme, last_char), do: last_char
|
||||
|
||||
@doc """
|
||||
Returns the number of codepoint in an utf8 string.
|
||||
Returns the number of unicode graphemes in an utf8 string.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -355,18 +399,19 @@ defmodule String do
|
||||
String.length("եոգլի") #=> 5
|
||||
|
||||
"""
|
||||
@spec length(t), do: non_neg_integer
|
||||
def length(string) do
|
||||
do_length(codepoint(string))
|
||||
do_length(next_grapheme(string))
|
||||
end
|
||||
|
||||
defp do_length({_, rest}) do
|
||||
1 + do_length(codepoint(rest))
|
||||
1 + do_length(next_grapheme(rest))
|
||||
end
|
||||
|
||||
defp do_length(:no_codepoint), do: 0
|
||||
defp do_length(:no_grapheme), do: 0
|
||||
|
||||
@doc """
|
||||
Returns the codepoint in the `position` of the given utf8 `string`.
|
||||
Returns the grapheme in the `position` of the given utf8 `string`.
|
||||
If `position` is greater than `string` length, than it returns `nil`.
|
||||
|
||||
## Examples
|
||||
@@ -375,51 +420,29 @@ defmodule String do
|
||||
String.at("elixir", 1) #=> "l"
|
||||
String.at("elixir", 10) #=> nil
|
||||
String.at("elixir", -1) #=> "r"
|
||||
String.at("elixir", -10) #=> "nil"
|
||||
String.at("elixir", -10) #=> nil
|
||||
|
||||
"""
|
||||
@spec at(t, integer), do: grapheme | nil
|
||||
def at(string, position) when position >= 0 do
|
||||
do_at(codepoint(string), position, 0)
|
||||
do_at(next_grapheme(string), position, 0)
|
||||
end
|
||||
|
||||
def at(string, position) when position < 0 do
|
||||
real_pos = do_length(codepoint(string)) - abs(position)
|
||||
real_pos = do_length(next_grapheme(string)) - abs(position)
|
||||
case real_pos >= 0 do
|
||||
true -> do_at(codepoint(string), real_pos, 0)
|
||||
true -> do_at(next_grapheme(string), real_pos, 0)
|
||||
false -> ""
|
||||
end
|
||||
end
|
||||
|
||||
defp do_at({_ , rest}, desired_pos, current_pos) when desired_pos > current_pos do
|
||||
do_at(codepoint(rest), desired_pos, current_pos + 1)
|
||||
do_at(next_grapheme(rest), desired_pos, current_pos + 1)
|
||||
end
|
||||
|
||||
defp do_at({char, _}, desired_pos, current_pos) when desired_pos == current_pos do
|
||||
char
|
||||
end
|
||||
|
||||
defp do_at(:no_codepoint, _, _), do: ""
|
||||
|
||||
# Private implementation which returns the first codepoint
|
||||
# of any given utf8 string and the rest of it
|
||||
# If an empty string is given, :no_codepoint is returned.
|
||||
defp codepoint(<<194, char, rest :: binary>>)
|
||||
when char in 161..191,
|
||||
do: { <<194, char>>, rest }
|
||||
|
||||
defp codepoint(<<first, char, rest :: binary>>)
|
||||
when first in 195..223 and char in 128..191,
|
||||
do: { <<first, char>>, rest }
|
||||
|
||||
defp codepoint(<<first, second, char, rest :: binary>>)
|
||||
when first == 224 and second in 160..191 and char in 128..191,
|
||||
do: { <<first, second, char>>, rest }
|
||||
|
||||
defp codepoint(<<first, second, char, rest :: binary>>)
|
||||
when first in 225..239 and second in 128..191 and char in 128..191,
|
||||
do: { <<first, second, char>>, rest }
|
||||
|
||||
defp codepoint(<<other, rest :: binary>>), do: { <<other>>, rest }
|
||||
|
||||
defp codepoint(<<>>), do: :no_codepoint
|
||||
defp do_at(:no_grapheme, _, _), do: nil
|
||||
end
|
||||
|
||||
@@ -24,7 +24,7 @@ defmodule System do
|
||||
@doc """
|
||||
Returns Elixir's version as binary.
|
||||
"""
|
||||
def version, do: "0.7.0"
|
||||
def version, do: "0.7.1"
|
||||
|
||||
@doc """
|
||||
Returns a keywords list with version, git tag info and date.
|
||||
@@ -138,6 +138,55 @@ defmodule System do
|
||||
filter_stacktrace :erlang.get_stacktrace
|
||||
end
|
||||
|
||||
@doc """
|
||||
Halts the Erlang runtime system where the first argument status must be a
|
||||
non-negative integer, the atom `:abort` or any type that can be converted
|
||||
to a char list.
|
||||
|
||||
* If an integer, the runtime system exits with the integer value which
|
||||
is returned to the Operating System;
|
||||
|
||||
* If `:abort`, the runtime system aborts producing a core dump, if that is
|
||||
enabled in the operating system;
|
||||
|
||||
* If a char list, an erlang crash dump is produced with status as slogan,
|
||||
and then the runtime system exits with status code 1;
|
||||
|
||||
Note that on many platforms, only the status codes 0-255 are supported
|
||||
by the operating system.
|
||||
|
||||
For integer status, Erlang runtime system closes all ports and allows async
|
||||
threads to finish their operations before exiting. To exit without such
|
||||
flushing, pass options [flush: false] instead.
|
||||
|
||||
For more information, check: http://www.erlang.org/doc/man/erlang.html#halt-2
|
||||
|
||||
## Examples
|
||||
|
||||
System.halt(0)
|
||||
System.halt(1, flush: false)
|
||||
System.halt(:abort)
|
||||
|
||||
"""
|
||||
@spec halt(non_neg_integer | List.Chars.t | :abort, [] | [flush: false]), do: no_return
|
||||
def halt(status // 0, options // [])
|
||||
|
||||
def halt(status, options) when is_integer(status) or status == :abort do
|
||||
do_halt(status, options)
|
||||
end
|
||||
|
||||
def halt(status, options) do
|
||||
do_halt(to_char_list(status), options)
|
||||
end
|
||||
|
||||
# Support R15B
|
||||
if List.member?(:erlang.module_info(:exports), { :halt, 2 }) do
|
||||
defp do_halt(status, options), do: :erlang.halt(status, options)
|
||||
else
|
||||
IO.puts "Using limited halt support. Upgrade to R15B01 or later is recommended."
|
||||
defp do_halt(status, _options), do: :erlang.halt(status)
|
||||
end
|
||||
|
||||
## Helpers
|
||||
|
||||
# Filter stacktrace by removing internal BOOTSTRAP calls.
|
||||
|
||||
@@ -9,10 +9,10 @@ defmodule URI.Parser do
|
||||
@doc """
|
||||
Responsible for parsing extra URL information.
|
||||
"""
|
||||
defcallback parse(uri_info)
|
||||
defcallback parse(uri_info :: URI.Info.t), do: URI.Info.t
|
||||
|
||||
@doc """
|
||||
Responsible for returning the default port.
|
||||
"""
|
||||
defcallback default_port()
|
||||
defcallback default_port(), do: integer
|
||||
end
|
||||
|
||||
@@ -0,0 +1,420 @@
|
||||
LATIN CAPITAL LETTER A WITH MACRON AND GRAVE;0100 0300
|
||||
LATIN SMALL LETTER A WITH MACRON AND GRAVE;0101 0300
|
||||
LATIN CAPITAL LETTER E WITH VERTICAL LINE BELOW;0045 0329
|
||||
LATIN SMALL LETTER E WITH VERTICAL LINE BELOW;0065 0329
|
||||
LATIN CAPITAL LETTER E WITH VERTICAL LINE BELOW AND GRAVE;00C8 0329
|
||||
LATIN SMALL LETTER E WITH VERTICAL LINE BELOW AND GRAVE;00E8 0329
|
||||
LATIN CAPITAL LETTER E WITH VERTICAL LINE BELOW AND ACUTE;00C9 0329
|
||||
LATIN SMALL LETTER E WITH VERTICAL LINE BELOW AND ACUTE;00E9 0329
|
||||
LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND MACRON;00CA 0304
|
||||
LATIN SMALL LETTER E WITH CIRCUMFLEX AND MACRON;00EA 0304
|
||||
LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND CARON;00CA 030C
|
||||
LATIN SMALL LETTER E WITH CIRCUMFLEX AND CARON;00EA 030C
|
||||
LATIN CAPITAL LETTER I WITH MACRON AND GRAVE;012A 0300
|
||||
LATIN SMALL LETTER I WITH MACRON AND GRAVE;012B 0300
|
||||
LATIN SMALL LETTER I WITH DOT ABOVE AND ACUTE;0069 0307 0301
|
||||
LATIN SMALL LETTER NG WITH TILDE ABOVE;006E 0360 0067
|
||||
LATIN CAPITAL LETTER O WITH VERTICAL LINE BELOW;004F 0329
|
||||
LATIN SMALL LETTER O WITH VERTICAL LINE BELOW;006F 0329
|
||||
LATIN CAPITAL LETTER O WITH VERTICAL LINE BELOW AND GRAVE;00D2 0329
|
||||
LATIN SMALL LETTER O WITH VERTICAL LINE BELOW AND GRAVE;00F2 0329
|
||||
LATIN CAPITAL LETTER O WITH VERTICAL LINE BELOW AND ACUTE;00D3 0329
|
||||
LATIN SMALL LETTER O WITH VERTICAL LINE BELOW AND ACUTE;00F3 0329
|
||||
LATIN CAPITAL LETTER S WITH VERTICAL LINE BELOW;0053 0329
|
||||
LATIN SMALL LETTER S WITH VERTICAL LINE BELOW;0073 0329
|
||||
LATIN CAPITAL LETTER U WITH MACRON AND GRAVE;016A 0300
|
||||
LATIN SMALL LETTER U WITH MACRON AND GRAVE;016B 0300
|
||||
LATIN CAPITAL LETTER A WITH OGONEK AND ACUTE;0104 0301
|
||||
LATIN SMALL LETTER A WITH OGONEK AND ACUTE;0105 0301
|
||||
LATIN CAPITAL LETTER A WITH OGONEK AND TILDE;0104 0303
|
||||
LATIN SMALL LETTER A WITH OGONEK AND TILDE;0105 0303
|
||||
LATIN CAPITAL LETTER E WITH OGONEK AND ACUTE;0118 0301
|
||||
LATIN SMALL LETTER E WITH OGONEK AND ACUTE;0119 0301
|
||||
LATIN CAPITAL LETTER E WITH OGONEK AND TILDE;0118 0303
|
||||
LATIN SMALL LETTER E WITH OGONEK AND TILDE;0119 0303
|
||||
LATIN CAPITAL LETTER E WITH DOT ABOVE AND ACUTE;0116 0301
|
||||
LATIN SMALL LETTER E WITH DOT ABOVE AND ACUTE;0117 0301
|
||||
LATIN CAPITAL LETTER E WITH DOT ABOVE AND TILDE;0116 0303
|
||||
LATIN SMALL LETTER E WITH DOT ABOVE AND TILDE;0117 0303
|
||||
LATIN SMALL LETTER I WITH DOT ABOVE AND GRAVE;0069 0307 0300
|
||||
LATIN SMALL LETTER I WITH DOT ABOVE AND TILDE;0069 0307 0303
|
||||
LATIN CAPITAL LETTER I WITH OGONEK AND ACUTE;012E 0301
|
||||
LATIN SMALL LETTER I WITH OGONEK AND DOT ABOVE AND ACUTE;012F 0307 0301
|
||||
LATIN CAPITAL LETTER I WITH OGONEK AND TILDE;012E 0303
|
||||
LATIN SMALL LETTER I WITH OGONEK AND DOT ABOVE AND TILDE;012F 0307 0303
|
||||
LATIN CAPITAL LETTER J WITH TILDE;004A 0303
|
||||
LATIN SMALL LETTER J WITH DOT ABOVE AND TILDE;006A 0307 0303
|
||||
LATIN CAPITAL LETTER L WITH TILDE;004C 0303
|
||||
LATIN SMALL LETTER L WITH TILDE;006C 0303
|
||||
LATIN CAPITAL LETTER M WITH TILDE;004D 0303
|
||||
LATIN SMALL LETTER M WITH TILDE;006D 0303
|
||||
LATIN CAPITAL LETTER R WITH TILDE;0052 0303
|
||||
LATIN SMALL LETTER R WITH TILDE;0072 0303
|
||||
LATIN CAPITAL LETTER U WITH OGONEK AND ACUTE;0172 0301
|
||||
LATIN SMALL LETTER U WITH OGONEK AND ACUTE;0173 0301
|
||||
LATIN CAPITAL LETTER U WITH OGONEK AND TILDE;0172 0303
|
||||
LATIN SMALL LETTER U WITH OGONEK AND TILDE;0173 0303
|
||||
LATIN CAPITAL LETTER U WITH MACRON AND ACUTE;016A 0301
|
||||
LATIN SMALL LETTER U WITH MACRON AND ACUTE;016B 0301
|
||||
LATIN CAPITAL LETTER U WITH MACRON AND TILDE;016A 0303
|
||||
LATIN SMALL LETTER U WITH MACRON AND TILDE;016B 0303
|
||||
LATIN SMALL LETTER AE WITH GRAVE;00E6 0300
|
||||
LATIN SMALL LETTER OPEN O WITH GRAVE;0254 0300
|
||||
LATIN SMALL LETTER OPEN O WITH ACUTE;0254 0301
|
||||
LATIN SMALL LETTER TURNED V WITH GRAVE;028C 0300
|
||||
LATIN SMALL LETTER TURNED V WITH ACUTE;028C 0301
|
||||
LATIN SMALL LETTER SCHWA WITH GRAVE;0259 0300
|
||||
LATIN SMALL LETTER SCHWA WITH ACUTE;0259 0301
|
||||
LATIN SMALL LETTER HOOKED SCHWA WITH GRAVE;025A 0300
|
||||
LATIN SMALL LETTER HOOKED SCHWA WITH ACUTE;025A 0301
|
||||
BENGALI LETTER KHINYA;0995 09CD 09B7
|
||||
TAMIL CONSONANT K; 0B95 0BCD
|
||||
TAMIL CONSONANT NG; 0B99 0BCD
|
||||
TAMIL CONSONANT C; 0B9A 0BCD
|
||||
TAMIL CONSONANT NY; 0B9E 0BCD
|
||||
TAMIL CONSONANT TT; 0B9F 0BCD
|
||||
TAMIL CONSONANT NN; 0BA3 0BCD
|
||||
TAMIL CONSONANT T; 0BA4 0BCD
|
||||
TAMIL CONSONANT N; 0BA8 0BCD
|
||||
TAMIL CONSONANT P; 0BAA 0BCD
|
||||
TAMIL CONSONANT M; 0BAE 0BCD
|
||||
TAMIL CONSONANT Y; 0BAF 0BCD
|
||||
TAMIL CONSONANT R; 0BB0 0BCD
|
||||
TAMIL CONSONANT L; 0BB2 0BCD
|
||||
TAMIL CONSONANT V; 0BB5 0BCD
|
||||
TAMIL CONSONANT LLL;0BB4 0BCD
|
||||
TAMIL CONSONANT LL; 0BB3 0BCD
|
||||
TAMIL CONSONANT RR; 0BB1 0BCD
|
||||
TAMIL CONSONANT NNN;0BA9 0BCD
|
||||
TAMIL CONSONANT J; 0B9C 0BCD
|
||||
TAMIL CONSONANT SH; 0BB6 0BCD
|
||||
TAMIL CONSONANT SS; 0BB7 0BCD
|
||||
TAMIL CONSONANT S; 0BB8 0BCD
|
||||
TAMIL CONSONANT H; 0BB9 0BCD
|
||||
TAMIL CONSONANT KSS;0B95 0BCD 0BB7 0BCD
|
||||
TAMIL SYLLABLE KAA; 0B95 0BBE
|
||||
TAMIL SYLLABLE KI; 0B95 0BBF
|
||||
TAMIL SYLLABLE KII; 0B95 0BC0
|
||||
TAMIL SYLLABLE KU; 0B95 0BC1
|
||||
TAMIL SYLLABLE KUU; 0B95 0BC2
|
||||
TAMIL SYLLABLE KE; 0B95 0BC6
|
||||
TAMIL SYLLABLE KEE; 0B95 0BC7
|
||||
TAMIL SYLLABLE KAI; 0B95 0BC8
|
||||
TAMIL SYLLABLE KO; 0B95 0BCA
|
||||
TAMIL SYLLABLE KOO; 0B95 0BCB
|
||||
TAMIL SYLLABLE KAU; 0B95 0BCC
|
||||
TAMIL SYLLABLE NGAA; 0B99 0BBE
|
||||
TAMIL SYLLABLE NGI; 0B99 0BBF
|
||||
TAMIL SYLLABLE NGII; 0B99 0BC0
|
||||
TAMIL SYLLABLE NGU; 0B99 0BC1
|
||||
TAMIL SYLLABLE NGUU; 0B99 0BC2
|
||||
TAMIL SYLLABLE NGE; 0B99 0BC6
|
||||
TAMIL SYLLABLE NGEE; 0B99 0BC7
|
||||
TAMIL SYLLABLE NGAI; 0B99 0BC8
|
||||
TAMIL SYLLABLE NGO; 0B99 0BCA
|
||||
TAMIL SYLLABLE NGOO; 0B99 0BCB
|
||||
TAMIL SYLLABLE NGAU; 0B99 0BCC
|
||||
TAMIL SYLLABLE CI; 0B9A 0BBF
|
||||
TAMIL SYLLABLE CII; 0B9A 0BC0
|
||||
TAMIL SYLLABLE CU; 0B9A 0BC1
|
||||
TAMIL SYLLABLE CUU; 0B9A 0BC2
|
||||
TAMIL SYLLABLE CE; 0B9A 0BC6
|
||||
TAMIL SYLLABLE CEE; 0B9A 0BC7
|
||||
TAMIL SYLLABLE CAI; 0B9A 0BC8
|
||||
TAMIL SYLLABLE CO; 0B9A 0BCA
|
||||
TAMIL SYLLABLE COO; 0B9A 0BCB
|
||||
TAMIL SYLLABLE CAU; 0B9A 0BCC
|
||||
TAMIL SYLLABLE NYAA; 0B9E 0BBE
|
||||
TAMIL SYLLABLE NYI; 0B9E 0BBF
|
||||
TAMIL SYLLABLE NYII; 0B9E 0BC0
|
||||
TAMIL SYLLABLE NYU; 0B9E 0BC1
|
||||
TAMIL SYLLABLE NYUU; 0B9E 0BC2
|
||||
TAMIL SYLLABLE NYE; 0B9E 0BC6
|
||||
TAMIL SYLLABLE NYEE; 0B9E 0BC7
|
||||
TAMIL SYLLABLE NYAI; 0B9E 0BC8
|
||||
TAMIL SYLLABLE NYO; 0B9E 0BCA
|
||||
TAMIL SYLLABLE NYOO; 0B9E 0BCB
|
||||
TAMIL SYLLABLE NYAU; 0B9E 0BCC
|
||||
TAMIL SYLLABLE TTAA; 0B9F 0BBE
|
||||
TAMIL SYLLABLE TTI; 0B9F 0BBF
|
||||
TAMIL SYLLABLE TTII; 0B9F 0BC0
|
||||
TAMIL SYLLABLE TTU; 0B9F 0BC1
|
||||
TAMIL SYLLABLE TTUU; 0B9F 0BC2
|
||||
TAMIL SYLLABLE TTE; 0B9F 0BC6
|
||||
TAMIL SYLLABLE TTEE; 0B9F 0BC7
|
||||
TAMIL SYLLABLE TTAI; 0B9F 0BC8
|
||||
TAMIL SYLLABLE TTO; 0B9F 0BCA
|
||||
TAMIL SYLLABLE TTOO; 0B9F 0BCB
|
||||
TAMIL SYLLABLE TTAU; 0B9F 0BCC
|
||||
TAMIL SYLLABLE NNAA; 0BA3 0BBE
|
||||
TAMIL SYLLABLE NNI; 0BA3 0BBF
|
||||
TAMIL SYLLABLE NNII; 0BA3 0BC0
|
||||
TAMIL SYLLABLE NNU; 0BA3 0BC1
|
||||
TAMIL SYLLABLE NNUU; 0BA3 0BC2
|
||||
TAMIL SYLLABLE NNE; 0BA3 0BC6
|
||||
TAMIL SYLLABLE NNEE; 0BA3 0BC7
|
||||
TAMIL SYLLABLE NNAI; 0BA3 0BC8
|
||||
TAMIL SYLLABLE NNO; 0BA3 0BCA
|
||||
TAMIL SYLLABLE NNOO; 0BA3 0BCB
|
||||
TAMIL SYLLABLE NNAU; 0BA3 0BCC
|
||||
TAMIL SYLLABLE TAA; 0BA4 0BBE
|
||||
TAMIL SYLLABLE TI; 0BA4 0BBF
|
||||
TAMIL SYLLABLE TII; 0BA4 0BC0
|
||||
TAMIL SYLLABLE TU; 0BA4 0BC1
|
||||
TAMIL SYLLABLE TUU; 0BA4 0BC2
|
||||
TAMIL SYLLABLE TE; 0BA4 0BC6
|
||||
TAMIL SYLLABLE TEE; 0BA4 0BC7
|
||||
TAMIL SYLLABLE TAI; 0BA4 0BC8
|
||||
TAMIL SYLLABLE TO; 0BA4 0BCA
|
||||
TAMIL SYLLABLE TOO; 0BA4 0BCB
|
||||
TAMIL SYLLABLE TAU; 0BA4 0BCC
|
||||
TAMIL SYLLABLE NAA; 0BA8 0BBE
|
||||
TAMIL SYLLABLE NI; 0BA8 0BBF
|
||||
TAMIL SYLLABLE NII; 0BA8 0BC0
|
||||
TAMIL SYLLABLE NU; 0BA8 0BC1
|
||||
TAMIL SYLLABLE NUU; 0BA8 0BC2
|
||||
TAMIL SYLLABLE NE; 0BA8 0BC6
|
||||
TAMIL SYLLABLE NEE; 0BA8 0BC7
|
||||
TAMIL SYLLABLE NAI; 0BA8 0BC8
|
||||
TAMIL SYLLABLE NO; 0BA8 0BCA
|
||||
TAMIL SYLLABLE NOO; 0BA8 0BCB
|
||||
TAMIL SYLLABLE NAU; 0BA8 0BCC
|
||||
TAMIL SYLLABLE PAA; 0BAA 0BBE
|
||||
TAMIL SYLLABLE PI; 0BAA 0BBF
|
||||
TAMIL SYLLABLE PII; 0BAA 0BC0
|
||||
TAMIL SYLLABLE PU; 0BAA 0BC1
|
||||
TAMIL SYLLABLE PUU; 0BAA 0BC2
|
||||
TAMIL SYLLABLE PE; 0BAA 0BC6
|
||||
TAMIL SYLLABLE PEE; 0BAA 0BC7
|
||||
TAMIL SYLLABLE PAI; 0BAA 0BC8
|
||||
TAMIL SYLLABLE PO; 0BAA 0BCA
|
||||
TAMIL SYLLABLE POO; 0BAA 0BCB
|
||||
TAMIL SYLLABLE PAU; 0BAA 0BCC
|
||||
TAMIL SYLLABLE MAA; 0BAE 0BBE
|
||||
TAMIL SYLLABLE MI; 0BAE 0BBF
|
||||
TAMIL SYLLABLE MII; 0BAE 0BC0
|
||||
TAMIL SYLLABLE MU; 0BAE 0BC1
|
||||
TAMIL SYLLABLE MUU; 0BAE 0BC2
|
||||
TAMIL SYLLABLE ME; 0BAE 0BC6
|
||||
TAMIL SYLLABLE MEE; 0BAE 0BC7
|
||||
TAMIL SYLLABLE MAI; 0BAE 0BC8
|
||||
TAMIL SYLLABLE MO; 0BAE 0BCA
|
||||
TAMIL SYLLABLE MOO; 0BAE 0BCB
|
||||
TAMIL SYLLABLE MAU; 0BAE 0BCC
|
||||
TAMIL SYLLABLE YAA; 0BAF 0BBE
|
||||
TAMIL SYLLABLE YI; 0BAF 0BBF
|
||||
TAMIL SYLLABLE YII; 0BAF 0BC0
|
||||
TAMIL SYLLABLE YU; 0BAF 0BC1
|
||||
TAMIL SYLLABLE YUU; 0BAF 0BC2
|
||||
TAMIL SYLLABLE YE; 0BAF 0BC6
|
||||
TAMIL SYLLABLE YEE; 0BAF 0BC7
|
||||
TAMIL SYLLABLE YAI; 0BAF 0BC8
|
||||
TAMIL SYLLABLE YO; 0BAF 0BCA
|
||||
TAMIL SYLLABLE YOO; 0BAF 0BCB
|
||||
TAMIL SYLLABLE YAU; 0BAF 0BCC
|
||||
TAMIL SYLLABLE RAA; 0BB0 0BBE
|
||||
TAMIL SYLLABLE RI; 0BB0 0BBF
|
||||
TAMIL SYLLABLE RII; 0BB0 0BC0
|
||||
TAMIL SYLLABLE RU; 0BB0 0BC1
|
||||
TAMIL SYLLABLE RUU; 0BB0 0BC2
|
||||
TAMIL SYLLABLE RE; 0BB0 0BC6
|
||||
TAMIL SYLLABLE REE; 0BB0 0BC7
|
||||
TAMIL SYLLABLE RAI; 0BB0 0BC8
|
||||
TAMIL SYLLABLE RO; 0BB0 0BCA
|
||||
TAMIL SYLLABLE ROO; 0BB0 0BCB
|
||||
TAMIL SYLLABLE RAU; 0BB0 0BCC
|
||||
TAMIL SYLLABLE LAA; 0BB2 0BBE
|
||||
TAMIL SYLLABLE LI; 0BB2 0BBF
|
||||
TAMIL SYLLABLE LII; 0BB2 0BC0
|
||||
TAMIL SYLLABLE LU; 0BB2 0BC1
|
||||
TAMIL SYLLABLE LUU; 0BB2 0BC2
|
||||
TAMIL SYLLABLE LE; 0BB2 0BC6
|
||||
TAMIL SYLLABLE LEE; 0BB2 0BC7
|
||||
TAMIL SYLLABLE LAI; 0BB2 0BC8
|
||||
TAMIL SYLLABLE LO; 0BB2 0BCA
|
||||
TAMIL SYLLABLE LOO; 0BB2 0BCB
|
||||
TAMIL SYLLABLE LAU; 0BB2 0BCC
|
||||
TAMIL SYLLABLE VAA; 0BB5 0BBE
|
||||
TAMIL SYLLABLE VI; 0BB5 0BBF
|
||||
TAMIL SYLLABLE VII; 0BB5 0BC0
|
||||
TAMIL SYLLABLE VU; 0BB5 0BC1
|
||||
TAMIL SYLLABLE VUU; 0BB5 0BC2
|
||||
TAMIL SYLLABLE VE; 0BB5 0BC6
|
||||
TAMIL SYLLABLE VEE; 0BB5 0BC7
|
||||
TAMIL SYLLABLE VAI; 0BB5 0BC8
|
||||
TAMIL SYLLABLE VO; 0BB5 0BCA
|
||||
TAMIL SYLLABLE VOO; 0BB5 0BCB
|
||||
TAMIL SYLLABLE VAU; 0BB5 0BCC
|
||||
TAMIL SYLLABLE LLLAA; 0BB4 0BBE
|
||||
TAMIL SYLLABLE LLLI; 0BB4 0BBF
|
||||
TAMIL SYLLABLE LLLII; 0BB4 0BC0
|
||||
TAMIL SYLLABLE LLLU; 0BB4 0BC1
|
||||
TAMIL SYLLABLE LLLUU; 0BB4 0BC2
|
||||
TAMIL SYLLABLE LLLE; 0BB4 0BC6
|
||||
TAMIL SYLLABLE LLLEE; 0BB4 0BC7
|
||||
TAMIL SYLLABLE LLLAI; 0BB4 0BC8
|
||||
TAMIL SYLLABLE LLLO; 0BB4 0BCA
|
||||
TAMIL SYLLABLE LLLOO; 0BB4 0BCB
|
||||
TAMIL SYLLABLE LLLAU; 0BB4 0BCC
|
||||
TAMIL SYLLABLE LLAA; 0BB3 0BBE
|
||||
TAMIL SYLLABLE LLI; 0BB3 0BBF
|
||||
TAMIL SYLLABLE LLII; 0BB3 0BC0
|
||||
TAMIL SYLLABLE LLU; 0BB3 0BC1
|
||||
TAMIL SYLLABLE LLUU; 0BB3 0BC2
|
||||
TAMIL SYLLABLE LLE; 0BB3 0BC6
|
||||
TAMIL SYLLABLE LLEE; 0BB3 0BC7
|
||||
TAMIL SYLLABLE LLAI; 0BB3 0BC8
|
||||
TAMIL SYLLABLE LLO; 0BB3 0BCA
|
||||
TAMIL SYLLABLE LLOO; 0BB3 0BCB
|
||||
TAMIL SYLLABLE LLAU; 0BB3 0BCC
|
||||
TAMIL SYLLABLE RRAA; 0BB1 0BBE
|
||||
TAMIL SYLLABLE RRI; 0BB1 0BBF
|
||||
TAMIL SYLLABLE RRII; 0BB1 0BC0
|
||||
TAMIL SYLLABLE RRU; 0BB1 0BC1
|
||||
TAMIL SYLLABLE RRUU; 0BB1 0BC2
|
||||
TAMIL SYLLABLE RRE; 0BB1 0BC6
|
||||
TAMIL SYLLABLE RREE; 0BB1 0BC7
|
||||
TAMIL SYLLABLE RRAI; 0BB1 0BC8
|
||||
TAMIL SYLLABLE RRO; 0BB1 0BCA
|
||||
TAMIL SYLLABLE RROO; 0BB1 0BCB
|
||||
TAMIL SYLLABLE RRAU; 0BB1 0BCC
|
||||
TAMIL SYLLABLE NNNAA; 0BA9 0BBE
|
||||
TAMIL SYLLABLE NNNI; 0BA9 0BBF
|
||||
TAMIL SYLLABLE NNNII; 0BA9 0BC0
|
||||
TAMIL SYLLABLE NNNU; 0BA9 0BC1
|
||||
TAMIL SYLLABLE NNNUU; 0BA9 0BC2
|
||||
TAMIL SYLLABLE NNNE; 0BA9 0BC6
|
||||
TAMIL SYLLABLE NNNEE; 0BA9 0BC7
|
||||
TAMIL SYLLABLE NNNAI; 0BA9 0BC8
|
||||
TAMIL SYLLABLE NNNO; 0BA9 0BCA
|
||||
TAMIL SYLLABLE NNNOO; 0BA9 0BCB
|
||||
TAMIL SYLLABLE NNNAU; 0BA9 0BCC
|
||||
TAMIL SYLLABLE JAA; 0B9C 0BBE
|
||||
TAMIL SYLLABLE JI; 0B9C 0BBF
|
||||
TAMIL SYLLABLE JII; 0B9C 0BC0
|
||||
TAMIL SYLLABLE JU; 0B9C 0BC1
|
||||
TAMIL SYLLABLE JUU; 0B9C 0BC2
|
||||
TAMIL SYLLABLE JE; 0B9C 0BC6
|
||||
TAMIL SYLLABLE JEE; 0B9C 0BC7
|
||||
TAMIL SYLLABLE JAI; 0B9C 0BC8
|
||||
TAMIL SYLLABLE JO; 0B9C 0BCA
|
||||
TAMIL SYLLABLE JOO; 0B9C 0BCB
|
||||
TAMIL SYLLABLE JAU; 0B9C 0BCC
|
||||
TAMIL SYLLABLE SHAA; 0BB6 0BBE
|
||||
TAMIL SYLLABLE SHI; 0BB6 0BBF
|
||||
TAMIL SYLLABLE SHII; 0BB6 0BC0
|
||||
TAMIL SYLLABLE SHU; 0BB6 0BC1
|
||||
TAMIL SYLLABLE SHUU; 0BB6 0BC2
|
||||
TAMIL SYLLABLE SHE; 0BB6 0BC6
|
||||
TAMIL SYLLABLE SHEE; 0BB6 0BC7
|
||||
TAMIL SYLLABLE SHAI; 0BB6 0BC8
|
||||
TAMIL SYLLABLE SHO; 0BB6 0BCA
|
||||
TAMIL SYLLABLE SHOO; 0BB6 0BCB
|
||||
TAMIL SYLLABLE SHAU; 0BB6 0BCC
|
||||
TAMIL SYLLABLE SSAA; 0BB7 0BBE
|
||||
TAMIL SYLLABLE SSI; 0BB7 0BBF
|
||||
TAMIL SYLLABLE SSII; 0BB7 0BC0
|
||||
TAMIL SYLLABLE SSU; 0BB7 0BC1
|
||||
TAMIL SYLLABLE SSUU; 0BB7 0BC2
|
||||
TAMIL SYLLABLE SSE; 0BB7 0BC6
|
||||
TAMIL SYLLABLE SSEE; 0BB7 0BC7
|
||||
TAMIL SYLLABLE SSAI; 0BB7 0BC8
|
||||
TAMIL SYLLABLE SSO; 0BB7 0BCA
|
||||
TAMIL SYLLABLE SSOO; 0BB7 0BCB
|
||||
TAMIL SYLLABLE SSAU; 0BB7 0BCC
|
||||
TAMIL SYLLABLE SAA; 0BB8 0BBE
|
||||
TAMIL SYLLABLE SI; 0BB8 0BBF
|
||||
TAMIL SYLLABLE SII; 0BB8 0BC0
|
||||
TAMIL SYLLABLE SU; 0BB8 0BC1
|
||||
TAMIL SYLLABLE SUU; 0BB8 0BC2
|
||||
TAMIL SYLLABLE SE; 0BB8 0BC6
|
||||
TAMIL SYLLABLE SEE; 0BB8 0BC7
|
||||
TAMIL SYLLABLE SAI; 0BB8 0BC8
|
||||
TAMIL SYLLABLE SO; 0BB8 0BCA
|
||||
TAMIL SYLLABLE SOO; 0BB8 0BCB
|
||||
TAMIL SYLLABLE SAU; 0BB8 0BCC
|
||||
TAMIL SYLLABLE HAA; 0BB9 0BBE
|
||||
TAMIL SYLLABLE HI; 0BB9 0BBF
|
||||
TAMIL SYLLABLE HII; 0BB9 0BC0
|
||||
TAMIL SYLLABLE HU; 0BB9 0BC1
|
||||
TAMIL SYLLABLE HUU; 0BB9 0BC2
|
||||
TAMIL SYLLABLE HE; 0BB9 0BC6
|
||||
TAMIL SYLLABLE HEE; 0BB9 0BC7
|
||||
TAMIL SYLLABLE HAI; 0BB9 0BC8
|
||||
TAMIL SYLLABLE HO; 0BB9 0BCA
|
||||
TAMIL SYLLABLE HOO; 0BB9 0BCB
|
||||
TAMIL SYLLABLE HAU; 0BB9 0BCC
|
||||
TAMIL SYLLABLE KSSA; 0B95 0BCD 0BB7
|
||||
TAMIL SYLLABLE KSSAA; 0B95 0BCD 0BB7 0BBE
|
||||
TAMIL SYLLABLE KSSI; 0B95 0BCD 0BB7 0BBF
|
||||
TAMIL SYLLABLE KSSII; 0B95 0BCD 0BB7 0BC0
|
||||
TAMIL SYLLABLE KSSU; 0B95 0BCD 0BB7 0BC1
|
||||
TAMIL SYLLABLE KSSUU; 0B95 0BCD 0BB7 0BC2
|
||||
TAMIL SYLLABLE KSSE; 0B95 0BCD 0BB7 0BC6
|
||||
TAMIL SYLLABLE KSSEE; 0B95 0BCD 0BB7 0BC7
|
||||
TAMIL SYLLABLE KSSAI; 0B95 0BCD 0BB7 0BC8
|
||||
TAMIL SYLLABLE KSSO; 0B95 0BCD 0BB7 0BCA
|
||||
TAMIL SYLLABLE KSSOO; 0B95 0BCD 0BB7 0BCB
|
||||
TAMIL SYLLABLE KSSAU; 0B95 0BCD 0BB7 0BCC
|
||||
TAMIL SYLLABLE SHRII; 0BB6 0BCD 0BB0 0BC0
|
||||
SINHALA CONSONANT SIGN YANSAYA;0DCA 200D 0DBA
|
||||
SINHALA CONSONANT SIGN RAKAARAANSAYA;0DCA 200D 0DBB
|
||||
SINHALA CONSONANT SIGN REPAYA;0DBB 0DCA 200D
|
||||
GEORGIAN LETTER U-BRJGU;10E3 0302
|
||||
KHMER CONSONANT SIGN COENG KA;17D2 1780
|
||||
KHMER CONSONANT SIGN COENG KHA;17D2 1781
|
||||
KHMER CONSONANT SIGN COENG KO;17D2 1782
|
||||
KHMER CONSONANT SIGN COENG KHO;17D2 1783
|
||||
KHMER CONSONANT SIGN COENG NGO;17D2 1784
|
||||
KHMER CONSONANT SIGN COENG CA;17D2 1785
|
||||
KHMER CONSONANT SIGN COENG CHA;17D2 1786
|
||||
KHMER CONSONANT SIGN COENG CO;17D2 1787
|
||||
KHMER CONSONANT SIGN COENG CHO;17D2 1788
|
||||
KHMER CONSONANT SIGN COENG NYO;17D2 1789
|
||||
KHMER CONSONANT SIGN COENG DA;17D2 178A
|
||||
KHMER CONSONANT SIGN COENG TTHA;17D2 178B
|
||||
KHMER CONSONANT SIGN COENG DO;17D2 178C
|
||||
KHMER CONSONANT SIGN COENG TTHO;17D2 178D
|
||||
KHMER CONSONANT SIGN COENG NA;17D2 178E
|
||||
KHMER CONSONANT SIGN COENG TA;17D2 178F
|
||||
KHMER CONSONANT SIGN COENG THA;17D2 1790
|
||||
KHMER CONSONANT SIGN COENG TO;17D2 1791
|
||||
KHMER CONSONANT SIGN COENG THO;17D2 1792
|
||||
KHMER CONSONANT SIGN COENG NO;17D2 1793
|
||||
KHMER CONSONANT SIGN COENG BA;17D2 1794
|
||||
KHMER CONSONANT SIGN COENG PHA;17D2 1795
|
||||
KHMER CONSONANT SIGN COENG PO;17D2 1796
|
||||
KHMER CONSONANT SIGN COENG PHO;17D2 1797
|
||||
KHMER CONSONANT SIGN COENG MO;17D2 1798
|
||||
KHMER CONSONANT SIGN COENG YO;17D2 1799
|
||||
KHMER CONSONANT SIGN COENG RO;17D2 179A
|
||||
KHMER CONSONANT SIGN COENG LO;17D2 179B
|
||||
KHMER CONSONANT SIGN COENG VO;17D2 179C
|
||||
KHMER CONSONANT SIGN COENG SHA;17D2 179D
|
||||
KHMER CONSONANT SIGN COENG SSA;17D2 179E
|
||||
KHMER CONSONANT SIGN COENG SA;17D2 179F
|
||||
KHMER CONSONANT SIGN COENG HA;17D2 17A0
|
||||
KHMER CONSONANT SIGN COENG LA;17D2 17A1
|
||||
KHMER VOWEL SIGN COENG QA;17D2 17A2
|
||||
KHMER INDEPENDENT VOWEL SIGN COENG QU;17D2 17A7
|
||||
KHMER INDEPENDENT VOWEL SIGN COENG RY;17D2 17AB
|
||||
KHMER INDEPENDENT VOWEL SIGN COENG RYY;17D2 17AC
|
||||
KHMER INDEPENDENT VOWEL SIGN COENG QE;17D2 17AF
|
||||
KHMER VOWEL SIGN OM;17BB 17C6
|
||||
KHMER VOWEL SIGN AAM;17B6 17C6
|
||||
HIRAGANA LETTER BIDAKUON NGA;304B 309A
|
||||
HIRAGANA LETTER BIDAKUON NGI;304D 309A
|
||||
HIRAGANA LETTER BIDAKUON NGU;304F 309A
|
||||
HIRAGANA LETTER BIDAKUON NGE;3051 309A
|
||||
HIRAGANA LETTER BIDAKUON NGO;3053 309A
|
||||
KATAKANA LETTER BIDAKUON NGA;30AB 309A
|
||||
KATAKANA LETTER BIDAKUON NGI;30AD 309A
|
||||
KATAKANA LETTER BIDAKUON NGU;30AF 309A
|
||||
KATAKANA LETTER BIDAKUON NGE;30B1 309A
|
||||
KATAKANA LETTER BIDAKUON NGO;30B3 309A
|
||||
KATAKANA LETTER AINU CE;30BB 309A
|
||||
KATAKANA LETTER AINU TU;30C4 309A
|
||||
KATAKANA LETTER AINU TO;30C8 309A
|
||||
KATAKANA LETTER AINU P;31F7 309A
|
||||
MODIFIER LETTER EXTRA-HIGH EXTRA-LOW CONTOUR TONE BAR;02E5 02E9
|
||||
MODIFIER LETTER EXTRA-LOW EXTRA-HIGH CONTOUR TONE BAR;02E9 02E5
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,136 @@
|
||||
# This file has its compilation step because
|
||||
# it needs to parse String.Unicode data and
|
||||
# compile a digested module.
|
||||
defmodule String.Unicode do
|
||||
@moduledoc false
|
||||
|
||||
def version, do: {6,2,0}
|
||||
|
||||
to_binary = fn(codepoint) ->
|
||||
:unicode.characters_to_binary([binary_to_integer(codepoint, 16)])
|
||||
end
|
||||
|
||||
data_path = File.expand_path("../UnicodeData.txt", __FILE__)
|
||||
|
||||
codes = Enum.reduce File.iterator!(data_path), [], fn(line, acc) ->
|
||||
[ codepoint, _name, _category,
|
||||
_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
|
||||
end
|
||||
end
|
||||
|
||||
seqs_path = File.expand_path("../NamedSequences.txt", __FILE__)
|
||||
|
||||
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)
|
||||
|
||||
# Downcase
|
||||
|
||||
lc { codepoint, _upper, lower } inlist codes, lower != "" do
|
||||
lower = to_binary.(lower)
|
||||
args = quote do: [unquote(codepoint) <> t]
|
||||
code = quote do: unquote(lower) <> downcase(t)
|
||||
def :downcase, args, [], do: code
|
||||
end
|
||||
|
||||
def downcase(<< h, t :: binary >>) do
|
||||
<< h >> <> downcase(t)
|
||||
end
|
||||
|
||||
def downcase(<< >>) do
|
||||
<< >>
|
||||
end
|
||||
|
||||
# Upcase
|
||||
|
||||
lc { codepoint, upper, _lower } inlist codes, upper != "" do
|
||||
upper = to_binary.(upper)
|
||||
args = quote do: [unquote(codepoint) <> t]
|
||||
code = quote do: unquote(upper) <> upcase(t)
|
||||
def :upcase, args, [], do: code
|
||||
end
|
||||
|
||||
def upcase(<< h, t :: binary >>) do
|
||||
<< h >> <> upcase(t)
|
||||
end
|
||||
|
||||
def upcase(<< >>) do
|
||||
<< >>
|
||||
end
|
||||
|
||||
# Graphemes
|
||||
|
||||
lc codepoints inlist seqs do
|
||||
seq_args = quote do: [<< unquote_splicing(codepoints), t :: binary >>]
|
||||
seq_code = quote do: {<< unquote_splicing(codepoints) >>, t}
|
||||
def :next_grapheme, seq_args, [], do: seq_code
|
||||
end
|
||||
|
||||
def next_grapheme(<<>>) do
|
||||
:no_grapheme
|
||||
end
|
||||
|
||||
def next_grapheme(binary) when is_binary(binary) do
|
||||
case next_codepoint(binary) do
|
||||
:no_codepoint -> :no_grapheme
|
||||
other -> other
|
||||
end
|
||||
end
|
||||
|
||||
def graphemes(binary) when is_binary(binary) do
|
||||
do_graphemes(next_grapheme(binary))
|
||||
end
|
||||
|
||||
defp do_graphemes({ c, rest }) do
|
||||
[c|do_graphemes(next_grapheme(rest))]
|
||||
end
|
||||
|
||||
defp do_graphemes(:no_grapheme) do
|
||||
[]
|
||||
end
|
||||
|
||||
# Codepoints
|
||||
|
||||
def next_codepoint(<<194, char, rest :: binary>>)
|
||||
when char in 161..191,
|
||||
do: { <<194, char>>, rest }
|
||||
|
||||
def next_codepoint(<<first, char, rest :: binary>>)
|
||||
when first in 195..223 and char in 128..191,
|
||||
do: { <<first, char>>, rest }
|
||||
|
||||
def next_codepoint(<<first, second, char, rest :: binary>>)
|
||||
when first == 224 and second in 160..191 and char in 128..191,
|
||||
do: { <<first, second, char>>, rest }
|
||||
|
||||
def next_codepoint(<<first, second, char, rest :: binary>>)
|
||||
when first in 225..239 and second in 128..191 and char in 128..191,
|
||||
do: { <<first, second, char>>, rest }
|
||||
|
||||
def next_codepoint(<<other, rest :: binary>>), do: { <<other>>, rest }
|
||||
|
||||
def next_codepoint(<<>>), do: :no_codepoint
|
||||
|
||||
def codepoints(binary) when is_binary(binary) do
|
||||
do_codepoints(next_codepoint(binary))
|
||||
end
|
||||
|
||||
defp do_codepoints({ c, rest }) do
|
||||
[c|do_codepoints(next_codepoint(rest))]
|
||||
end
|
||||
|
||||
defp do_codepoints(:no_codepoint) do
|
||||
[]
|
||||
end
|
||||
end
|
||||
@@ -16,7 +16,7 @@
|
||||
%% warn_missing_spec,
|
||||
%% warn_untyped_record,
|
||||
%% warnings_as_errors,
|
||||
no_debug_info
|
||||
debug_info
|
||||
]}.
|
||||
|
||||
{yrl_opts, [
|
||||
|
||||
+39
-12
@@ -7,6 +7,9 @@
|
||||
-include("elixir.hrl").
|
||||
-compile({parse_transform, elixir_transform}).
|
||||
|
||||
%% Top level types
|
||||
-export_type([char_list/0]).
|
||||
-type char_list() :: string().
|
||||
|
||||
% OTP APPLICATION API
|
||||
|
||||
@@ -50,17 +53,40 @@ start_cli() ->
|
||||
%% EVAL HOOKS
|
||||
|
||||
scope_for_eval(Opts) ->
|
||||
File = case lists:keyfind(file, 1, Opts) of
|
||||
{ file, F } -> to_binary(F);
|
||||
false -> <<"nofile">>
|
||||
case lists:keyfind(file, 1, Opts) of
|
||||
{ file, RawFile } -> File = to_binary(RawFile);
|
||||
false -> File = <<"nofile">>
|
||||
end,
|
||||
|
||||
Local = case lists:keyfind(delegate_locals_to, 1, Opts) of
|
||||
{ delegate_locals_to, L } -> L;
|
||||
false -> nil
|
||||
case lists:keyfind(delegate_locals_to, 1, Opts) of
|
||||
{ delegate_locals_to, Local } -> Local;
|
||||
false -> Local = nil
|
||||
end,
|
||||
|
||||
#elixir_scope{file=File,local=Local}.
|
||||
case lists:keyfind(aliases, 1, Opts) of
|
||||
{ aliases, Aliases } -> Aliases;
|
||||
false -> Aliases = []
|
||||
end,
|
||||
|
||||
case lists:keyfind(requires, 1, Opts) of
|
||||
{ requires, Requires } -> Requires;
|
||||
false -> Requires = elixir_dispatch:default_requires()
|
||||
end,
|
||||
|
||||
case lists:keyfind(functions, 1, Opts) of
|
||||
{ functions, Functions } -> Functions;
|
||||
false -> Functions = elixir_dispatch:default_functions()
|
||||
end,
|
||||
|
||||
case lists:keyfind(macros, 1, Opts) of
|
||||
{ macros, Macros } -> Macros;
|
||||
false -> Macros = elixir_dispatch:default_macros()
|
||||
end,
|
||||
|
||||
#elixir_scope{
|
||||
file=File, local=Local,
|
||||
macros=Macros, functions=Functions,
|
||||
requires=Requires, aliases=Aliases }.
|
||||
|
||||
%% String evaluation
|
||||
|
||||
@@ -100,8 +126,8 @@ eval_forms(Tree, Binding, RawScope) ->
|
||||
vars=binding_dict(Binding),
|
||||
temp_vars=[],
|
||||
quote_vars=[],
|
||||
clause_vars=[],
|
||||
counter=0
|
||||
clause_vars=nil,
|
||||
counter=[]
|
||||
},
|
||||
{ ParseTree, NewScope } = elixir_translator:translate(Tree, Scope),
|
||||
case ParseTree of
|
||||
@@ -122,9 +148,10 @@ binding_dict([], Dict) -> Dict.
|
||||
|
||||
final_binding(Binding, Vars) -> final_binding(Binding, [], Binding, Vars).
|
||||
final_binding([{Var,_}|T], Acc, Binding, Vars) ->
|
||||
case atom_to_list(Var) of
|
||||
"_@" ++ _ -> final_binding(T, Acc, Binding, Vars);
|
||||
_ ->
|
||||
case lists:member($@, atom_to_list(Var)) of
|
||||
true ->
|
||||
final_binding(T, Acc, Binding, Vars);
|
||||
false ->
|
||||
RealName = orddict:fetch(Var, Vars),
|
||||
RealValue = proplists:get_value(RealName, Binding, nil),
|
||||
final_binding(T, [{Var, RealValue}|Acc], Binding, Vars)
|
||||
|
||||
@@ -87,9 +87,12 @@ extract_last_guards(Args) ->
|
||||
|
||||
% Function for translating macros with match style like case and receive.
|
||||
|
||||
match(Line, DecoupledClauses, RawS) ->
|
||||
S = RawS#elixir_scope{clause_vars=orddict:new()},
|
||||
match(Line, Clauses, #elixir_scope{clause_vars=C1} = S) ->
|
||||
{ TC, TS } = do_match(Line, Clauses, S#elixir_scope{clause_vars=orddict:new()}),
|
||||
C2 = TS#elixir_scope.clause_vars,
|
||||
{ TC, TS#elixir_scope{clause_vars=elixir_scope:merge_clause_vars(C1, C2)} }.
|
||||
|
||||
do_match(Line, DecoupledClauses, S) ->
|
||||
case DecoupledClauses of
|
||||
[DecoupledClause] ->
|
||||
{ TDecoupledClause, TS } = each_clause(Line, DecoupledClause, S),
|
||||
@@ -207,7 +210,11 @@ normalize_vars({ Var, Kind }, S) ->
|
||||
|
||||
normalize_vars(Var, Kind, Index, #elixir_scope{clause_vars=ClauseVars} = S) ->
|
||||
Vars = element(Index, S),
|
||||
{ { _, _, NewValue }, S1 } = elixir_scope:build_erl_var(0, 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)},
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
argv=[],
|
||||
loaded=[],
|
||||
at_exit=[],
|
||||
pool=[],
|
||||
counter=0,
|
||||
compiler_options=[{docs,true}]
|
||||
}).
|
||||
|
||||
@@ -51,6 +53,15 @@ handle_call(argv, _From, Config) ->
|
||||
handle_call(compiler_options, _From, Config) ->
|
||||
{ reply, Config#elixir_code_server.compiler_options, Config };
|
||||
|
||||
handle_call(retrieve_module_name, _From, Config) ->
|
||||
case Config#elixir_code_server.pool of
|
||||
[H|T] ->
|
||||
{ reply, module_tuple(H), Config#elixir_code_server{pool=T} };
|
||||
[] ->
|
||||
Counter = Config#elixir_code_server.counter,
|
||||
{ reply, module_tuple(Counter), Config#elixir_code_server{counter=Counter+1} }
|
||||
end;
|
||||
|
||||
handle_call(_Request, _From, Config) ->
|
||||
{ reply, undef, Config }.
|
||||
|
||||
@@ -73,6 +84,9 @@ handle_cast({ unload_files, Files }, Config) ->
|
||||
Unloaded = lists:foldl(fun(File, Acc) -> orddict:erase(File, Acc) end, Current, Files),
|
||||
{ noreply, Config#elixir_code_server{loaded=Unloaded} };
|
||||
|
||||
handle_cast({ return_module_name, I }, #elixir_code_server{pool=Pool} = Config) ->
|
||||
{ noreply, Config#elixir_code_server{pool=[I|Pool]} };
|
||||
|
||||
handle_cast(_Request, Config) ->
|
||||
{ noreply, Config }.
|
||||
|
||||
@@ -85,4 +99,6 @@ terminate(Reason, Config) ->
|
||||
ok.
|
||||
|
||||
code_change(_Old, Config, _Extra) ->
|
||||
{ ok, Config }.
|
||||
{ ok, Config }.
|
||||
|
||||
module_tuple(I) -> { list_to_atom("elixir_compiler_" ++ integer_to_list(I)), I }.
|
||||
@@ -1,6 +1,6 @@
|
||||
-module(elixir_compiler).
|
||||
-export([get_opts/0, get_opt/1, get_opt/2, string/2, file/1, file_to_path/2]).
|
||||
-export([core/0, module/3, eval_forms/5]).
|
||||
-export([core/0, module/3, eval_forms/4]).
|
||||
-include("elixir.hrl").
|
||||
-compile({parse_transform, elixir_transform}).
|
||||
|
||||
@@ -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{file=File}),
|
||||
eval_forms(Forms, 1, File, [], elixir:scope_for_eval([{file,File}])),
|
||||
lists:reverse(get(elixir_compiled))
|
||||
after
|
||||
put(elixir_compiled, Previous)
|
||||
@@ -49,16 +49,16 @@ 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, Module, Vars, #elixir_scope{module=nil} = S) ->
|
||||
eval_forms(Forms, Line, Module, nil, Vars, S);
|
||||
eval_forms(Forms, Line, Vars, #elixir_scope{module=nil} = S) ->
|
||||
eval_forms(Forms, Line, nil, Vars, S);
|
||||
|
||||
eval_forms(Forms, Line, Module, Vars, #elixir_scope{module=Value} = S) ->
|
||||
eval_forms(Forms, Line, Module, Value, Vars, S).
|
||||
eval_forms(Forms, Line, Vars, #elixir_scope{module=Value} = S) ->
|
||||
eval_forms(Forms, Line, Value, Vars, S).
|
||||
|
||||
eval_forms(Forms, Line, RawModule, 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, RawModule, Value, Vars, S)
|
||||
false -> code_loading_compilation(Forms, Line, Value, Vars, S)
|
||||
end.
|
||||
|
||||
eval_compilation(Forms, Vars, S) ->
|
||||
@@ -66,17 +66,20 @@ eval_compilation(Forms, Vars, S) ->
|
||||
{ Result, _Binding, FS } = elixir:eval_forms(Forms, [{'_@MODULE',nil}|Binding], S),
|
||||
{ Result, FS }.
|
||||
|
||||
code_loading_compilation(Forms, Line, RawModule, Value, Vars, S) ->
|
||||
Module = escape_module(RawModule),
|
||||
code_loading_compilation(Forms, Line, Value, 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],
|
||||
|
||||
{ module(ModuleForm, S#elixir_scope.file, [], true, fun(Mod, _) ->
|
||||
Res = Mod:'BOOTSTRAP'(Value, Args),
|
||||
code:purge(Module),
|
||||
{ module(ModuleForm, S#elixir_scope.file, [], true, fun(_, _) ->
|
||||
Res = Module:'BOOTSTRAP'(Value, Args),
|
||||
code:delete(Module),
|
||||
case code:soft_purge(Module) of
|
||||
true -> return_module_name(I);
|
||||
false -> ok
|
||||
end,
|
||||
Res
|
||||
end), FS }.
|
||||
|
||||
@@ -110,17 +113,14 @@ module(Forms, File, Options, Bootstrap, Callback) when
|
||||
|
||||
core() ->
|
||||
elixir:start_app(),
|
||||
gen_server:call(elixir_code_server, { compiler_options, [{docs,false},{internal,true}] }),
|
||||
[core_file(File) || File <- core_main()],
|
||||
AllLists = [filelib:wildcard(Wildcard) || Wildcard <- core_list()],
|
||||
Files = lists:append(AllLists) -- core_main(),
|
||||
[core_file(File) || File <- 'Elixir.List':uniq(Files)].
|
||||
gen_server:call(elixir_code_server, { compiler_options, [{docs,false},{internal,true},{debug_info,true}] }),
|
||||
[core_file(File) || File <- core_main()].
|
||||
|
||||
%% HELPERS
|
||||
|
||||
no_auto_import() ->
|
||||
{ attribute, 0, compile, {
|
||||
no_auto_import, erlang:module_info(exports) } }.
|
||||
Bifs = [{ Name, Arity } || { Name, Arity } <- erlang:module_info(exports), erl_internal:bif(Name, Arity)],
|
||||
{ attribute, 0, compile, { no_auto_import, Bifs } }.
|
||||
|
||||
module_form(Exprs, Line, File, Module, Vars) when
|
||||
is_binary(File), is_list(Exprs), is_integer(Line), is_atom(Module) ->
|
||||
@@ -146,25 +146,13 @@ allows_fast_compilation([{defmodule,_,_}|T]) -> allows_fast_compilation(T);
|
||||
allows_fast_compilation([]) -> true;
|
||||
allows_fast_compilation(_) -> false.
|
||||
|
||||
%% Escape the module name, removing slashes, dots,
|
||||
%% so it can be loaded by Erlang.
|
||||
%% Generate module names from code server.
|
||||
|
||||
escape_module(Module) when is_atom(Module) ->
|
||||
escape_module(atom_to_list(Module));
|
||||
retrieve_module_name() ->
|
||||
gen_server:call(elixir_code_server, retrieve_module_name).
|
||||
|
||||
escape_module(Module) when is_binary(Module) ->
|
||||
escape_module(binary_to_list(Module));
|
||||
|
||||
escape_module(Module) when is_list(Module) ->
|
||||
list_to_atom(escape_each(Module)).
|
||||
|
||||
escape_each([H|T]) when H >= $A, H =< $Z; H >= $a, H =< $z; H >= $0, H =< $9; H == $- ->
|
||||
[H|escape_each(T)];
|
||||
|
||||
escape_each([_|T]) ->
|
||||
[$_|escape_each(T)];
|
||||
|
||||
escape_each([]) -> [].
|
||||
return_module_name(I) ->
|
||||
gen_server:cast(elixir_code_server, { return_module_name, I }).
|
||||
|
||||
%% Receives a module Binary and outputs it in the given path.
|
||||
|
||||
@@ -186,29 +174,17 @@ core_file(File) ->
|
||||
exit(1)
|
||||
end.
|
||||
|
||||
core_list() ->
|
||||
[
|
||||
"lib/elixir/lib/range.ex",
|
||||
"lib/elixir/lib/behaviour.ex",
|
||||
"lib/elixir/lib/uri/parser.ex",
|
||||
"lib/elixir/lib/elixir/formatter.ex",
|
||||
"lib/elixir/lib/dict.ex",
|
||||
"lib/elixir/lib/dict/common.ex",
|
||||
"lib/elixir/lib/access.ex",
|
||||
"lib/elixir/lib/range.ex",
|
||||
"lib/elixir/lib/*/*.ex",
|
||||
"lib/elixir/lib/*.ex"
|
||||
].
|
||||
|
||||
core_main() ->
|
||||
[
|
||||
"lib/elixir/lib/kernel.ex",
|
||||
"lib/elixir/lib/keyword.ex",
|
||||
"lib/elixir/lib/list.ex",
|
||||
"lib/elixir/lib/kernel/typespec.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/list.ex",
|
||||
"lib/elixir/lib/code.ex",
|
||||
"lib/elixir/lib/protocol.ex",
|
||||
"lib/elixir/lib/enum.ex",
|
||||
@@ -216,7 +192,14 @@ core_main() ->
|
||||
"lib/elixir/lib/binary/inspect.ex",
|
||||
"lib/elixir/lib/binary/chars.ex",
|
||||
"lib/elixir/lib/list/chars.ex",
|
||||
"lib/elixir/lib/gen_server/behaviour.ex"
|
||||
"lib/elixir/lib/io.ex",
|
||||
"lib/elixir/lib/file.ex",
|
||||
"lib/elixir/lib/access.ex",
|
||||
"lib/elixir/lib/regex.ex",
|
||||
"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"
|
||||
].
|
||||
|
||||
%% ERROR HANDLING
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
reset_last/1,
|
||||
wrap_definition/7,
|
||||
store_definition/8,
|
||||
store_each/8,
|
||||
store_each/7,
|
||||
unwrap_stored_definitions/1,
|
||||
format_error/1]).
|
||||
-include("elixir.hrl").
|
||||
@@ -19,7 +19,7 @@ table(Module) -> ?ELIXIR_ATOM_CONCAT([f, Module]).
|
||||
build_table(Module) ->
|
||||
FunctionTable = table(Module),
|
||||
ets:new(FunctionTable, [set, named_table, public]),
|
||||
ets:insert(FunctionTable, { last, [] }),
|
||||
reset_last(Module),
|
||||
FunctionTable.
|
||||
|
||||
delete_table(Module) ->
|
||||
@@ -81,7 +81,6 @@ store_definition(Kind, Line, Module, Name, Args, Guards, Body, RawS) ->
|
||||
|
||||
File = TS#elixir_scope.file,
|
||||
Table = table(Module),
|
||||
Stack = TS#elixir_scope.macro,
|
||||
|
||||
%% Store function
|
||||
if
|
||||
@@ -90,10 +89,10 @@ store_definition(Kind, Line, Module, Name, Args, Guards, Body, RawS) ->
|
||||
compile_super(Module, TS),
|
||||
CheckClauses = S#elixir_scope.check_clauses,
|
||||
store_each(CheckClauses, Kind, File, Location,
|
||||
Stack, Table, length(Defaults), Function)
|
||||
Table, length(Defaults), Function)
|
||||
end,
|
||||
|
||||
[store_each(false, Kind, File, Location, Stack, Table, 0,
|
||||
[store_each(false, Kind, File, Location, Table, 0,
|
||||
function_for_default(Kind, Name, Default)) || Default <- Defaults],
|
||||
|
||||
{ Name, Arity }.
|
||||
@@ -215,9 +214,10 @@ unwrap_stored_definition([Fun|T], Exports, Private, Def, Defmacro, Defmacrop, Fu
|
||||
);
|
||||
|
||||
unwrap_stored_definition([Fun|T], Exports, Private, Def, Defmacro, Defmacrop, Functions) when element(2, Fun) == defmacrop ->
|
||||
Tuple = element(1, Fun),
|
||||
unwrap_stored_definition(
|
||||
T, Exports, [element(1, Fun)|Private], Def, Defmacro,
|
||||
[{ element(1, Fun), element(3, Fun) }|Defmacrop], Functions
|
||||
T, Exports, [Tuple|Private], Def, Defmacro,
|
||||
[{ Tuple, element(3, Fun), element(5, Fun) }|Defmacrop], Functions
|
||||
);
|
||||
|
||||
unwrap_stored_definition([], Exports, Private, Def, Defmacro, Defmacrop, {Functions,Tail}) ->
|
||||
@@ -226,13 +226,13 @@ unwrap_stored_definition([], Exports, Private, Def, Defmacro, Defmacrop, {Functi
|
||||
|
||||
%% Helpers
|
||||
|
||||
function_for_stored_definition({{Name, Arity}, _, Line, _, [], _, _, Clauses}, {Functions,Tail}) ->
|
||||
function_for_stored_definition({{Name, Arity}, _, Line, _, _, [], _, Clauses}, {Functions,Tail}) ->
|
||||
{
|
||||
[{ function, Line, Name, Arity, lists:reverse(Clauses) }|Functions],
|
||||
Tail
|
||||
};
|
||||
|
||||
function_for_stored_definition({{Name, Arity}, _, Line, _, Location, _, _, Clauses}, {Functions,Tail}) ->
|
||||
function_for_stored_definition({{Name, Arity}, _, Line, _, _, Location, _, Clauses}, {Functions,Tail}) ->
|
||||
{
|
||||
Functions,
|
||||
[
|
||||
@@ -252,22 +252,22 @@ function_for_default(_, Name, { clause, Line, Args, _Guards, _Exprs } = Clause)
|
||||
%% This function also checks and emit warnings in case
|
||||
%% the kind, of the visibility of the function changes.
|
||||
|
||||
store_each(Check, Kind, File, Location, Stack, Table, Defaults, {function, Line, Name, Arity, Clauses}) ->
|
||||
store_each(Check, Kind, File, Location, Table, Defaults, {function, Line, Name, Arity, Clauses}) ->
|
||||
case ets:lookup(Table, {Name, Arity}) of
|
||||
[{{Name, Arity}, StoredKind, _, _, StoredLocation, StoredStack, StoredDefaults, StoredClauses}] ->
|
||||
[{{Name, Arity}, StoredKind, _, _, StoredCheck, StoredLocation, StoredDefaults, StoredClauses}] ->
|
||||
FinalLocation = StoredLocation,
|
||||
FinalDefaults = Defaults + StoredDefaults,
|
||||
FinalClauses = Clauses ++ StoredClauses,
|
||||
check_valid_kind(Line, File, Name, Arity, Kind, StoredKind),
|
||||
check_valid_defaults(Line, File, Name, Arity, FinalDefaults),
|
||||
Check andalso (Stack == StoredStack) andalso check_valid_clause(Line, File, Name, Arity, Table);
|
||||
(Check and StoredCheck) andalso check_valid_clause(Line, File, Name, Arity, Table);
|
||||
[] ->
|
||||
FinalLocation = Location,
|
||||
FinalDefaults = Defaults,
|
||||
FinalClauses = Clauses,
|
||||
Check andalso ets:insert(Table, { last, { Name, Arity } })
|
||||
end,
|
||||
ets:insert(Table, {{Name, Arity}, Kind, Line, File, FinalLocation, Stack, FinalDefaults, FinalClauses}).
|
||||
ets:insert(Table, {{Name, Arity}, Kind, Line, File, Check, FinalLocation, FinalDefaults, FinalClauses}).
|
||||
|
||||
%% Validations
|
||||
|
||||
|
||||
@@ -15,8 +15,7 @@ unpack_each(Kind, Name, [{'//', Line, [Expr, _]}|T] = List, Acc, Clauses, S) ->
|
||||
Base = build_match(Acc, Line, []),
|
||||
{ Args, Invoke } = extract_defaults(List, Line, length(Base), [], []),
|
||||
|
||||
SM = S#elixir_scope{counter=length(Base)},
|
||||
{ DefArgs, SA } = elixir_clauses:assigns(fun elixir_translator:translate/2, Base ++ Args, SM),
|
||||
{ DefArgs, SA } = elixir_clauses:assigns(fun elixir_translator:translate/2, Base ++ Args, S),
|
||||
{ InvokeArgs, _ } = elixir_translator:translate_args(Base ++ Invoke, SA),
|
||||
|
||||
Call = { call, Line,
|
||||
@@ -39,7 +38,7 @@ extract_defaults([{'//', _, [_Expr, Default]}|T], Line, Counter, NewArgs, NewInv
|
||||
extract_defaults(T, Line, Counter, NewArgs, [Default|NewInvoke]);
|
||||
|
||||
extract_defaults([_|T], Line, Counter, NewArgs, NewInvoke) ->
|
||||
H = { ?ELIXIR_ATOM_CONCAT(["_@", Counter]), Line, nil },
|
||||
H = { ?ELIXIR_ATOM_CONCAT(["_@D", Counter]), Line, nil },
|
||||
extract_defaults(T, Line, Counter + 1, [H|NewArgs], [H|NewInvoke]);
|
||||
|
||||
extract_defaults([], _Line, _Counter, NewArgs, NewInvoke) ->
|
||||
@@ -50,7 +49,7 @@ extract_defaults([], _Line, _Counter, NewArgs, NewInvoke) ->
|
||||
build_match([], _Line, Acc) -> Acc;
|
||||
|
||||
build_match([_|T], Line, Acc) ->
|
||||
Var = { ?ELIXIR_ATOM_CONCAT(["_@", length(T)]), Line, nil },
|
||||
Var = { ?ELIXIR_ATOM_CONCAT(["_@D", length(T)]), Line, nil },
|
||||
build_match(T, Line, [Var|Acc]).
|
||||
|
||||
% Given the invoked function name based on the kind
|
||||
|
||||
@@ -69,9 +69,10 @@ rewrite_clause(Else, _) -> Else.
|
||||
|
||||
%% Error handling
|
||||
|
||||
check_unused_local_macros(File, Recorded, PMacros) ->
|
||||
check_unused_local_macros(File, Recorded, Defmacrop) ->
|
||||
[elixir_errors:handle_file_warning(File,
|
||||
{ Line, ?MODULE, { unused_macro, Fun } }) || { Fun, Line } <- PMacros, not lists:member(Fun, Recorded)].
|
||||
{ Line, ?MODULE, { unused_macro, Fun } }) || { Fun, Line, Check } <- Defmacrop,
|
||||
Check, not lists:member(Fun, Recorded)].
|
||||
|
||||
format_error({unused_macro,{Name, Arity}}) ->
|
||||
io_lib:format("macro ~s/~B is unused", [Name, Arity]).
|
||||
@@ -16,7 +16,7 @@ overridable(Module, Value) ->
|
||||
is_defined(Module, Tuple) ->
|
||||
Overridable = overridable(Module),
|
||||
case orddict:find(Tuple, Overridable) of
|
||||
{ ok, { _, [_|_] } } -> true;
|
||||
{ ok, { _, _, _ } } -> true;
|
||||
_ -> false
|
||||
end.
|
||||
|
||||
@@ -53,37 +53,39 @@ name(Module, Function) ->
|
||||
name(Module, Function, overridable(Module)).
|
||||
|
||||
name(_Module, { Name, _ } = Function, Overridable) ->
|
||||
{ Count, _ } = orddict:fetch(Function, Overridable),
|
||||
{ Count, _, _ } = orddict:fetch(Function, Overridable),
|
||||
?ELIXIR_ATOM_CONCAT([Name, " (overridable ", Count, ")"]).
|
||||
|
||||
%% Store
|
||||
|
||||
store(Module, Function, GenerateName) ->
|
||||
Overridable = overridable(Module),
|
||||
{ Count, [H|T] } = orddict:fetch(Function, Overridable),
|
||||
overridable(Module, orddict:store(Function, { Count, T }, Overridable)),
|
||||
case orddict:fetch(Function, Overridable) of
|
||||
{ _Count, _Clause, true } -> ok;
|
||||
{ Count, Clause, false } ->
|
||||
overridable(Module, orddict:store(Function, { Count, Clause, true }, Overridable)),
|
||||
{ { Name, Arity }, Kind, Line, File, _Check, Location, Defaults, Clauses } = Clause,
|
||||
|
||||
{ { Name, Arity }, Kind, Line, File, Location, Stack, Defaults, Clauses } = H,
|
||||
{ FinalKind, FinalName } = case GenerateName of
|
||||
true -> { defp, name(Module, Function, Overridable) };
|
||||
false -> { Kind, Name }
|
||||
end,
|
||||
|
||||
{ FinalKind, FinalName } = case GenerateName of
|
||||
true -> { defp, name(Module, Function, Overridable) };
|
||||
false -> { Kind, Name }
|
||||
end,
|
||||
|
||||
Def = { function, Line, FinalName, Arity, Clauses },
|
||||
elixir_def:store_each(false, FinalKind, File, Location,
|
||||
Stack, elixir_def:table(Module), Defaults, Def).
|
||||
Def = { function, Line, FinalName, Arity, Clauses },
|
||||
elixir_def:store_each(false, FinalKind, File, Location,
|
||||
elixir_def:table(Module), Defaults, Def)
|
||||
end.
|
||||
|
||||
%% Store pending declarations that were not manually made concrete.
|
||||
|
||||
store_pending(Module) ->
|
||||
[store(Module, X, false) || { X, { _, [_|_] } } <- overridable(Module),
|
||||
[store(Module, X, false) || { X, { _, _, false } } <- overridable(Module),
|
||||
not 'Elixir.Module':'defines?'(Module, X)].
|
||||
|
||||
%% Error handling
|
||||
|
||||
format_error({ no_super, Module, { Name, Arity } }) ->
|
||||
Bins = [ format_fa(X) || { X, { _, [_|_] } } <- overridable(Module)],
|
||||
Bins = [ format_fa(X) || { X, { _, _, _ } } <- overridable(Module)],
|
||||
Joined = 'Elixir.Enum':join(Bins, <<", ">>),
|
||||
io_lib:format("no super defined for ~s/~B in module ~s. Overridable functions available are: ~s",
|
||||
[Name, Arity, elixir_errors:inspect(Module), Joined]).
|
||||
|
||||
@@ -59,8 +59,8 @@ dispatch_import(Line, Name, Args, S, Callback) ->
|
||||
{ error, internal } ->
|
||||
elixir_import:record(import, Tuple, ?BUILTIN, Module),
|
||||
elixir_macros:translate({ Name, Line, Args }, S);
|
||||
{ ok, Receiver, Tree } ->
|
||||
translate_expansion(Line, Tree, Receiver, Name, Arity, S)
|
||||
{ ok, _Receiver, Tree } ->
|
||||
translate_expansion(Line, Tree, S)
|
||||
end;
|
||||
Receiver ->
|
||||
elixir_import:record(import, Tuple, Receiver, Module),
|
||||
@@ -87,7 +87,7 @@ dispatch_require(Line, Receiver, Name, Args, S, Callback) ->
|
||||
{ error, internal } ->
|
||||
elixir_macros:translate({ Name, Line, Args }, S);
|
||||
{ ok, Tree } ->
|
||||
translate_expansion(Line, Tree, Receiver, Name, Arity, S)
|
||||
translate_expansion(Line, Tree, S)
|
||||
end
|
||||
end.
|
||||
|
||||
@@ -167,10 +167,12 @@ expand_macro_named(Line, Receiver, Name, Arity, Args, Module, Requires, SEnv) ->
|
||||
Fun = fun Receiver:ProperName/ProperArity,
|
||||
expand_macro_fun(Line, Fun, Receiver, Name, Args, Module, Requires, SEnv).
|
||||
|
||||
translate_expansion(Line, Tree, Receiver, Name, Arity, S) ->
|
||||
NewS = S#elixir_scope{macro=[{Line,Receiver,Name,Arity}|S#elixir_scope.macro]},
|
||||
{ TTree, TS } = elixir_translator:translate_each(elixir_quote:linify(Line, Tree), NewS),
|
||||
{ TTree, TS#elixir_scope{macro=S#elixir_scope.macro} }.
|
||||
translate_expansion(Line, Tree, S) ->
|
||||
{ TR, TS } = elixir_translator:translate_each(
|
||||
elixir_quote:linify(Line, Tree),
|
||||
S#elixir_scope{check_clauses=false}
|
||||
),
|
||||
{ TR, TS#elixir_scope{check_clauses=S#elixir_scope.check_clauses} }.
|
||||
|
||||
%% Helpers
|
||||
|
||||
@@ -268,9 +270,6 @@ in_erlang_functions() ->
|
||||
{ exit, 1 },
|
||||
{ float, 1 },
|
||||
{ float_to_list, 1 },
|
||||
{ halt, 0 },
|
||||
{ halt, 1 },
|
||||
{ halt, 2 },
|
||||
{ hd, 1 },
|
||||
{ integer_to_list, 1 },
|
||||
{ integer_to_list, 2 },
|
||||
|
||||
@@ -26,6 +26,9 @@ to_dot(L) -> L.
|
||||
|
||||
%% Raised during macros translation.
|
||||
|
||||
-spec syntax_error(non_neg_integer(), file:filename(), binary() | string()) -> no_return().
|
||||
-spec syntax_error(non_neg_integer(), file:filename(), binary() | string(), list()) -> no_return().
|
||||
|
||||
syntax_error(Line, File, Message) when is_list(Message) ->
|
||||
syntax_error(Line, File, iolist_to_binary(Message));
|
||||
|
||||
@@ -38,6 +41,8 @@ syntax_error(Line, File, Format, Args) ->
|
||||
|
||||
%% Raised on tokenizing/parsing
|
||||
|
||||
-spec parse_error(non_neg_integer(), file:filename(), iolist() | atom(), [] | iolist()) -> no_return().
|
||||
|
||||
parse_error(Line, File, Error, []) ->
|
||||
Message = case Error of
|
||||
"syntax error before: " -> <<"syntax error: expression is incomplete">>;
|
||||
@@ -64,6 +69,8 @@ parse_error(Line, File, Error, Token) ->
|
||||
|
||||
%% Raised during compilation
|
||||
|
||||
-spec form_error(non_neg_integer(), file:filename(), module(), any()) -> no_return().
|
||||
|
||||
form_error(Line, File, Module, Desc) ->
|
||||
Message = iolist_to_binary(format_error(Module, Desc)),
|
||||
raise(Line, File, 'Elixir.CompileError', Message).
|
||||
@@ -95,13 +102,19 @@ handle_file_warning(_, File, {Line,erl_lint,{undefined_behaviour_func,{Fun,Arity
|
||||
io:format(file_format(Line, File, Message));
|
||||
|
||||
handle_file_warning(_, File, {Line,erl_lint,{undefined_behaviour,Module}}) ->
|
||||
Raw = io_lib:format("behaviour ~s undefined", [inspect(Module)]),
|
||||
case elixir_compiler:get_opt(internal) of
|
||||
true -> [];
|
||||
false ->
|
||||
Message = io_lib:format("behaviour ~s undefined", [inspect(Module)]),
|
||||
io:format(file_format(Line, File, Message))
|
||||
end;
|
||||
|
||||
Message = case erlang:function_exported(Module, behavior_info, 1) of
|
||||
true -> Raw ++ " (maybe you meant behaviour_info instead of behavior_info?)";
|
||||
false -> Raw
|
||||
end,
|
||||
handle_file_warning(_, File, {Line,erl_lint,{unused_var,Var}}) ->
|
||||
Message = format_error(erl_lint, { unused_var, format_var(Var) }),
|
||||
io:format(file_format(Line, File, Message));
|
||||
|
||||
handle_file_warning(_, File, {Line,erl_lint,{shadowed_var,Var,Where}}) ->
|
||||
Message = format_error(erl_lint, { shadowed_var, format_var(Var), Where }),
|
||||
io:format(file_format(Line, File, Message));
|
||||
|
||||
%% Default behavior
|
||||
@@ -112,6 +125,8 @@ handle_file_warning(_, File, {Line,Module,Desc}) ->
|
||||
handle_file_warning(File, Desc) ->
|
||||
handle_file_warning(false, File, Desc).
|
||||
|
||||
-spec handle_file_error(file:filename(), {non_neg_integer(), module(), any()}) -> no_return().
|
||||
|
||||
handle_file_error(File, {Line,Module,Desc}) ->
|
||||
form_error(Line, File, Module, Desc).
|
||||
|
||||
@@ -151,6 +166,9 @@ raise(Line, File, Kind, Message) ->
|
||||
file_format(Line, File, Message) ->
|
||||
io_lib:format("~ts:~w: ~ts~n", [File, Line, Message]).
|
||||
|
||||
format_var(Var) ->
|
||||
list_to_atom(lists:takewhile(fun(X) -> X /= $@ end, atom_to_list(Var))).
|
||||
|
||||
format_error([], Desc) ->
|
||||
io_lib:format("~p", [Desc]);
|
||||
|
||||
|
||||
@@ -236,10 +236,10 @@ format_error({import_conflict,{Receiver, Name, Arity}}) ->
|
||||
[elixir_errors:inspect(Receiver), Name, Arity]);
|
||||
|
||||
format_error({local_conflict,{_, Name, Arity}}) ->
|
||||
io_lib:format("cannot define local ~s/~B because it conflicts with Elixir internal macros", [Name, Arity]);
|
||||
io_lib:format("cannot define local ~s/~B because it conflicts with Elixir special forms", [Name, Arity]);
|
||||
|
||||
format_error({internal_conflict,{Receiver, Name, Arity}}) ->
|
||||
io_lib:format("cannot import ~s.~s/~B because it conflicts with Elixir internal macros",
|
||||
io_lib:format("cannot import ~s.~s/~B because it conflicts with Elixir special forms",
|
||||
[elixir_errors:inspect(Receiver), Name, Arity]);
|
||||
|
||||
format_error({ no_macros, Module }) ->
|
||||
|
||||
@@ -114,28 +114,6 @@ build_bitstr_each(Fun, [{'::',_,[H,V]}|T], Line, S, Acc) ->
|
||||
{ Size, Types } = extract_bit_values(Line, V, ES),
|
||||
build_bitstr_each(Fun, T, Line, NS, [{ bin_element, Line, Expr, Size, Types }|Acc]);
|
||||
|
||||
%% Deprecated syntax
|
||||
build_bitstr_each(Fun, [{'|',_,[H,V]}|T], Line, S, Acc) ->
|
||||
{ Expr, NS } = Fun(H, S),
|
||||
|
||||
%% Just variables defined outside the binary can be accounted on subparts
|
||||
case NS of
|
||||
{ ES, _ } -> [];
|
||||
ES -> []
|
||||
end,
|
||||
|
||||
elixir_errors:deprecation(Line, ES#elixir_scope.file, "Old bitstring syntax with | is deprecated, use the new one with :: instead"),
|
||||
|
||||
%% Assigns can be made in subparts
|
||||
{ Int, Types } = extract_bin_values(Line, V, default, [], ES#elixir_scope{context=nil}),
|
||||
|
||||
Final = case Types of
|
||||
[] -> default;
|
||||
_ -> lists:reverse(Types)
|
||||
end,
|
||||
|
||||
build_bitstr_each(Fun, T, Line, NS, [{ bin_element, Line, Expr, Int, Final }|Acc]);
|
||||
|
||||
build_bitstr_each(Fun, [H|T], Line, S, Acc) ->
|
||||
{ Expr, NS } = Fun(H, S),
|
||||
build_bitstr_each(Fun, T, Line, NS, [{ bin_element, Line, Expr, default, default }|Acc]).
|
||||
@@ -221,25 +199,3 @@ handle_unknown_specifier(Line, [H|T], Size, Types, S) ->
|
||||
join_expansion({ '__block__', _, [Expanded] }, Tail) -> join_expansion(Expanded, Tail);
|
||||
join_expansion(Expanded, Tail) when is_list(Expanded) -> Expanded ++ Tail;
|
||||
join_expansion(Expanded, Tail) -> [Expanded|Tail].
|
||||
|
||||
%% Deprecated specifiers
|
||||
|
||||
extract_bin_values(Line, { '-', _Line, [Left, Right] }, Int, Types, S) ->
|
||||
{ LInt, LTypes } = extract_bin_values(Line, Left, Int, Types, S),
|
||||
extract_bin_values(Line, Right, LInt, LTypes, S);
|
||||
|
||||
extract_bin_values(Line, Value, default, Types, _S) when is_integer(Value) ->
|
||||
{ { integer, Line, Value }, Types };
|
||||
|
||||
extract_bin_values(_Line, { Value, _, Atom } = Expr, default, Types, S) when is_atom(Value), is_atom(Atom) ->
|
||||
Translated = element(1, elixir_translator:translate_each(Expr, S)),
|
||||
{ Translated, Types };
|
||||
|
||||
extract_bin_values(Line, Value, _Int, _Types, S) when is_integer(Value) ->
|
||||
elixir_errors:syntax_error(Line, S#elixir_scope.file, "duplicated size specifier ~p in <<>>", [Value]);
|
||||
|
||||
extract_bin_values(_Line, Value, Int, Types, _S) when is_atom(Value); is_tuple(Value), tuple_size(Value) == 2 ->
|
||||
{ Int, [Value|Types] };
|
||||
|
||||
extract_bin_values(Line, _Value, _Int, _Types, S) ->
|
||||
elixir_errors:syntax_error(Line, S#elixir_scope.file, "invalid specifier for <<>>").
|
||||
@@ -8,7 +8,9 @@
|
||||
assert_no_function_scope/3, assert_module_scope/3, assert_no_assign_or_guard_scope/3]).
|
||||
-include("elixir.hrl").
|
||||
|
||||
-define(FUNS(), Kind == def; Kind == defp; Kind == defmacro; Kind == defmacrop).
|
||||
-define(FUNS(Kind), Kind == def; Kind == defp; Kind == defmacro; Kind == defmacrop).
|
||||
-define(IN_TYPES(Kind), Kind == atom orelse Kind == integer orelse Kind == float).
|
||||
|
||||
-compile({parse_transform, elixir_transform}).
|
||||
|
||||
%% Operators
|
||||
@@ -34,11 +36,11 @@ translate({ Op, Line, Exprs }, S) when is_list(Exprs),
|
||||
|
||||
translate({ '!', Line, [{ '!', _, [Expr] }] }, S) ->
|
||||
{ TExpr, SE } = translate_each(Expr, S),
|
||||
{ elixir_tree_helpers:convert_to_boolean(Line, TExpr, true, S#elixir_scope.context == guard), SE };
|
||||
elixir_tree_helpers:convert_to_boolean(Line, TExpr, true, S#elixir_scope.context == guard, SE);
|
||||
|
||||
translate({ '!', Line, [Expr] }, S) ->
|
||||
{ TExpr, SE } = translate_each(Expr, S),
|
||||
{ elixir_tree_helpers:convert_to_boolean(Line, TExpr, false, S#elixir_scope.context == guard), SE };
|
||||
elixir_tree_helpers:convert_to_boolean(Line, TExpr, false, S#elixir_scope.context == guard, SE);
|
||||
|
||||
translate({ in, Line, [Left, Right] }, #elixir_scope{extra_guards=nil} = S) ->
|
||||
{ _, TExpr, TS } = translate_in(Line, Left, Right, S),
|
||||
@@ -78,7 +80,7 @@ translate({ function, Line, [_,_,_] = Args }, S) when is_list(Args) ->
|
||||
|
||||
%% @
|
||||
|
||||
translate({'@', Line, [{ Name, _, Args }]}, S) when Name == typep; Name == type; Name == spec; Name == callback ->
|
||||
translate({'@', Line, [{ Name, _, Args }]}, S) when is_list(Args) andalso (Name == typep orelse Name == type orelse Name == spec orelse Name == callback orelse Name == opaque) ->
|
||||
case elixir_compiler:get_opt(internal) of
|
||||
true -> { { nil, Line }, S };
|
||||
false ->
|
||||
@@ -205,23 +207,24 @@ translate({defmodule, Line, [Ref, KV]}, S) ->
|
||||
{ TRef, S }
|
||||
end,
|
||||
|
||||
{ elixir_module:translate(Line, FRef, Block, S), FS };
|
||||
{ elixir_module:translate(Line, FRef, Block, S#elixir_scope{check_clauses=true}), FS };
|
||||
|
||||
translate({Kind, Line, [Call]}, S) when ?FUNS() ->
|
||||
translate({Kind, Line, [Call]}, S) when ?FUNS(Kind) ->
|
||||
translate({Kind, Line, [Call, nil]}, S);
|
||||
|
||||
translate({Kind, Line, [Call, Expr]}, S) when ?FUNS() ->
|
||||
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),
|
||||
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() ->
|
||||
translate({Kind, Line, [Name, Args, Guards, Expr]}, S) when ?FUNS(Kind) ->
|
||||
assert_module_scope(Line, Kind, S),
|
||||
assert_no_function_scope(Line, Kind, S),
|
||||
{ TName, NS } = translate_each(Name, S),
|
||||
@@ -271,22 +274,24 @@ translate_in(Line, Left, Right, S) ->
|
||||
Expr = case TRight of
|
||||
{ cons, _, _, _ } ->
|
||||
[H|T] = elixir_tree_helpers:cons_to_list(TRight),
|
||||
lists:foldl(fun(X, Acc) ->
|
||||
{ op, Line, 'orelse', Acc, { op, Line, '==', Var, X } }
|
||||
lists:foldr(fun(X, Acc) ->
|
||||
{ op, Line, 'orelse', { op, Line, '==', Var, X }, Acc }
|
||||
end, { op, Line, '==', Var, H }, T);
|
||||
{ tuple, _, [{ atom, _, 'Elixir.Range' }, Start, End] } ->
|
||||
{ op, Line, 'orelse',
|
||||
{ op, Line, 'andalso',
|
||||
{ op, Line, '=<', Start, End},
|
||||
{ op, Line, 'andalso',
|
||||
{ op, Line, '>=', Var, Start },
|
||||
{ op, Line, '=<', Var, End } } },
|
||||
{ op, Line, 'andalso',
|
||||
{ op, Line, '<', End, Start},
|
||||
{ op, Line, 'andalso',
|
||||
{ op, Line, '=<', Var, Start },
|
||||
{ op, Line, '>=', Var, End } } }
|
||||
};
|
||||
case { Start, End } of
|
||||
{ { K1, _, StartInt }, { K2, _, EndInt } } when ?IN_TYPES(K1), ?IN_TYPES(K2), StartInt =< EndInt ->
|
||||
increasing_compare(Line, Var, Start, End);
|
||||
{ { K1, _, _ }, { K2, _, _ } } when ?IN_TYPES(K1), ?IN_TYPES(K2) ->
|
||||
decreasing_compare(Line, Var, Start, End);
|
||||
_ ->
|
||||
{ op, Line, 'orelse',
|
||||
{ op, Line, 'andalso',
|
||||
{ op, Line, '=<', Start, End},
|
||||
increasing_compare(Line, Var, Start, End) },
|
||||
{ op, Line, 'andalso',
|
||||
{ op, Line, '<', End, Start},
|
||||
decreasing_compare(Line, Var, Start, End) } }
|
||||
end;
|
||||
_ ->
|
||||
syntax_error(Line, S#elixir_scope.file, "invalid args for operator in")
|
||||
end,
|
||||
@@ -296,6 +301,16 @@ translate_in(Line, Left, Right, S) ->
|
||||
false -> { Var, Expr, SV }
|
||||
end.
|
||||
|
||||
increasing_compare(Line, Var, Start, End) ->
|
||||
{ op, Line, 'andalso',
|
||||
{ op, Line, '>=', Var, Start },
|
||||
{ op, Line, '=<', Var, End } }.
|
||||
|
||||
decreasing_compare(Line, Var, Start, End) ->
|
||||
{ op, Line, 'andalso',
|
||||
{ op, Line, '=<', Var, Start },
|
||||
{ op, Line, '>=', Var, End } }.
|
||||
|
||||
rewrite_case_clauses([{do,[{in,_,[{'_',_,_},[false,nil]]}],False},{do,[{'_',_,_}],True}]) ->
|
||||
[{do,[false],False},{do,[true],True}];
|
||||
|
||||
@@ -317,9 +332,17 @@ is_reserved_data(_) -> false.
|
||||
|
||||
spec_to_macro(type) -> deftype;
|
||||
spec_to_macro(typep) -> deftypep;
|
||||
spec_to_macro(opaque) -> defopaque;
|
||||
spec_to_macro(spec) -> defspec;
|
||||
spec_to_macro(callback) -> defcallback.
|
||||
|
||||
% Unpack a list of expressions from a block.
|
||||
unpack([{ '__block__', _, Exprs }]) -> Exprs;
|
||||
unpack(Exprs) -> Exprs.
|
||||
|
||||
assert_no_aliases_name(Line, '__aliases__', [Atom], #elixir_scope{file=File}) when is_atom(Atom) ->
|
||||
Message = "function names should start with lowercase characters or underscore, invalid name ~s",
|
||||
syntax_error(Line, File, Message, [atom_to_binary(Atom, utf8)]);
|
||||
|
||||
assert_no_aliases_name(_Line, _Aliases, _Args, _S) ->
|
||||
ok.
|
||||
@@ -14,7 +14,7 @@ eval_quoted(Module, Quoted, RawBinding, Opts) ->
|
||||
false -> Line = 1
|
||||
end,
|
||||
|
||||
{ Value, FinalBinding, _Scope } = elixir:eval_quoted([Quoted], Binding, Line, Scope),
|
||||
{ Value, FinalBinding, _Scope } = elixir:eval_quoted([Quoted], Binding, Line, Scope#elixir_scope{check_clauses=false}),
|
||||
{ Value, FinalBinding }.
|
||||
|
||||
scope_for_eval(Module, #elixir_scope{} = S) ->
|
||||
@@ -25,7 +25,6 @@ scope_for_eval(Module, Opts) ->
|
||||
|
||||
binding_for_eval(Module, Binding) -> [{'_@MODULE',Module}|Binding].
|
||||
|
||||
|
||||
%% TABLE METHODS
|
||||
|
||||
data_table(Module) ->
|
||||
@@ -34,9 +33,6 @@ data_table(Module) ->
|
||||
docs_table(Module) ->
|
||||
?ELIXIR_ATOM_CONCAT([o, Module]).
|
||||
|
||||
spec_table(Module) ->
|
||||
?ELIXIR_ATOM_CONCAT([s, Module]).
|
||||
|
||||
%% TRANSFORMATION FUNCTIONS
|
||||
|
||||
%% Transformation of args and scope into a compiled erlang call.
|
||||
@@ -72,15 +68,15 @@ compile(Line, Module, Block, Vars, #elixir_scope{} = S) when is_atom(Module) ->
|
||||
{ Export, Private, Def, Defmacro, Defmacrop, Functions } = elixir_def:unwrap_stored_definitions(Module),
|
||||
|
||||
{ All, Forms0 } = functions_form(Line, File, Module, Export, Private, Def, Defmacro, Defmacrop, Functions, C),
|
||||
Forms1 = attributes_form(Line, File, Module, Forms0),
|
||||
Forms2 = specs_form(Line, Module, Defmacro, Defmacrop, Forms1),
|
||||
Forms1 = specs_form(Line, Module, Defmacro, Defmacrop, Forms0, C),
|
||||
Forms2 = attributes_form(Line, File, Module, Forms1),
|
||||
|
||||
elixir_import:ensure_no_local_conflict(Line, File, Module, All),
|
||||
elixir_import:ensure_no_import_conflict(Line, File, Module, All),
|
||||
|
||||
Final = [
|
||||
{attribute, Line, file, {File,Line}},
|
||||
{attribute, Line, module, Module} | Forms2
|
||||
{ attribute, Line, file, { binary_to_list(File), Line } },
|
||||
{ attribute, Line, module, Module } | Forms2
|
||||
],
|
||||
|
||||
Binary = load_form(Line, Final, S),
|
||||
@@ -88,7 +84,6 @@ compile(Line, Module, Block, Vars, #elixir_scope{} = S) when is_atom(Module) ->
|
||||
after
|
||||
ets:delete(data_table(Module)),
|
||||
ets:delete(docs_table(Module)),
|
||||
ets:delete(spec_table(Module)),
|
||||
elixir_def:delete_table(Module),
|
||||
elixir_import:delete_table(Module)
|
||||
end;
|
||||
@@ -131,10 +126,6 @@ build(Line, File, Module) ->
|
||||
DocsTable = docs_table(Module),
|
||||
ets:new(DocsTable, [ordered_set, named_table, public]),
|
||||
|
||||
%% Holds specs in a format it is easier to query.
|
||||
SpecTable = spec_table(Module),
|
||||
ets:new(SpecTable, [duplicate_bag, named_table, public]),
|
||||
|
||||
%% We keep a separated table for function definitions
|
||||
%% and another one for imports. We keep them in different
|
||||
%% tables for organization and speed purpose (since the
|
||||
@@ -145,11 +136,11 @@ build(Line, File, Module) ->
|
||||
%% Receives the module representation and evaluates it.
|
||||
|
||||
eval_form(Line, Module, Block, Vars, RawS) ->
|
||||
Temp = ?ELIXIR_ATOM_CONCAT(["COMPILE-",Module]),
|
||||
S = scope_for_eval(Module, RawS),
|
||||
{ Value, NewS } = elixir_compiler:eval_forms([Block], Line, Temp, Vars, S),
|
||||
{ Value, NewS } = elixir_compiler:eval_forms([Block], Line, Vars, S),
|
||||
elixir_def_overridable:store_pending(Module),
|
||||
eval_callbacks(Line, Module, before_compile, [Module], NewS),
|
||||
elixir_def_overridable:store_pending(Module),
|
||||
Value.
|
||||
|
||||
%% Return the form with exports and function declarations.
|
||||
@@ -188,36 +179,41 @@ attributes_form(Line, _File, Module, Current) ->
|
||||
|
||||
%% Specs
|
||||
|
||||
specs_form(Line, Module, Defmacro, Defmacrop, Forms) ->
|
||||
{ Specs, Callbacks } = ets:foldl(fun(X, Acc) ->
|
||||
translate_spec(X, Defmacro, Defmacrop, Acc)
|
||||
end, { [], [] }, spec_table(Module)),
|
||||
specs_form(Line, Module, Defmacro, DefmacropWithLine, Forms, C) ->
|
||||
Defmacrop = [Tuple || { Tuple, _, _ } <- DefmacropWithLine],
|
||||
case elixir_compiler:get_opt(internal, C) of
|
||||
true -> Forms;
|
||||
_ ->
|
||||
Callbacks = 'Elixir.Module':get_attribute(Module, callback),
|
||||
Specs = [translate_spec(Spec, Defmacro, Defmacrop) ||
|
||||
Spec <- 'Elixir.Module':get_attribute(Module, spec)],
|
||||
|
||||
Temp = specs_attributes(Line, spec, Forms, Specs),
|
||||
specs_attributes(Line, callback, Temp, Callbacks).
|
||||
'Elixir.Module':delete_attribute(Module, spec),
|
||||
'Elixir.Module':delete_attribute(Module, callback),
|
||||
|
||||
Temp = specs_attributes(Line, spec, Forms, Specs),
|
||||
specs_attributes(Line, callback, Temp, Callbacks)
|
||||
end.
|
||||
|
||||
specs_attributes(Line, Type, Forms, Specs) ->
|
||||
Keys = lists:ukeysort(1, Specs),
|
||||
lists:foldl(fun({ Tuple, _ }, Acc) ->
|
||||
[{ attribute, Line, Type, { Tuple, proplists:append_values(Tuple, Specs) } }|Acc]
|
||||
Values = [V || { K, V } <- Specs, K == Tuple],
|
||||
[{ attribute, Line, Type, { Tuple, Values } }|Acc]
|
||||
end, Forms, Keys).
|
||||
|
||||
translate_spec({ { spec, Spec }, Rest }, Defmacro, Defmacrop, { Specs, Callbacks } = Acc) ->
|
||||
translate_spec({ Spec, Rest }, Defmacro, Defmacrop) ->
|
||||
case ordsets:is_element(Spec, Defmacrop) of
|
||||
true -> Acc;
|
||||
true -> { Spec, Rest };
|
||||
false ->
|
||||
case ordsets:is_element(Spec, Defmacro) of
|
||||
true ->
|
||||
{ Name, Arity } = Spec,
|
||||
New = { { ?ELIXIR_MACRO(Name), Arity + 1 }, [spec_for_macro(X) || X <- Rest] },
|
||||
{ [New|Specs], Callbacks };
|
||||
{ { ?ELIXIR_MACRO(Name), Arity + 1 }, spec_for_macro(Rest) };
|
||||
false ->
|
||||
{ [{ Spec, Rest }|Specs], Callbacks }
|
||||
{ Spec, Rest }
|
||||
end
|
||||
end;
|
||||
|
||||
translate_spec({ { callback, Spec }, Rest }, _, _, { Specs, Callbacks }) ->
|
||||
{ Specs, [{ Spec, Rest }|Callbacks] }.
|
||||
end.
|
||||
|
||||
spec_for_macro({ type, Line, 'fun', [{ type, _, product, Args }|T] }) ->
|
||||
NewArgs = [{type,Line,term,[]}|Args],
|
||||
@@ -271,8 +267,7 @@ add_info_function(Line, File, Module, Export, Functions, Def, Defmacro, C) ->
|
||||
macros_clause(Line, Module, Def, Defmacro),
|
||||
docs_clause(Line, Module, Docs),
|
||||
moduledoc_clause(Line, Module, Docs),
|
||||
compile_clause(Line),
|
||||
self_clause(Line, Module),
|
||||
module_clause(Line, Module),
|
||||
else_clause(Line)
|
||||
] },
|
||||
{ [Pair|Export], [Contents|Functions] }
|
||||
@@ -291,8 +286,8 @@ handle_builtin_macros('Elixir.Kernel', Def, Defmacro) ->
|
||||
elixir_dispatch:in_erlang_macros()), Def);
|
||||
handle_builtin_macros(_, _Def, Defmacro) -> Defmacro.
|
||||
|
||||
self_clause(Line, Module) ->
|
||||
{ clause, Line, [{ atom, Line, self }], [], [{ atom, Line, Module }] }.
|
||||
module_clause(Line, Module) ->
|
||||
{ clause, Line, [{ atom, Line, module }], [], [{ atom, Line, Module }] }.
|
||||
|
||||
docs_clause(Line, Module, true) ->
|
||||
Docs = ordsets:from_list(ets:tab2list(docs_table(Module))),
|
||||
@@ -308,11 +303,6 @@ moduledoc_clause(Line, Module, true) ->
|
||||
moduledoc_clause(Line, _Module, _) ->
|
||||
{ clause, Line, [{ atom, Line, moduledoc }], [], [{ atom, Line, nil }] }.
|
||||
|
||||
compile_clause(Line) ->
|
||||
Info = { call, Line, { atom, Line, module_info }, [{ atom, Line, compile }] },
|
||||
WrappedInfo = ?ELIXIR_WRAP_CALL(Line, 'Elixir.Keyword', 'from_enum', [Info]),
|
||||
{ clause, Line, [{ atom, Line, compile }], [], [WrappedInfo] }.
|
||||
|
||||
else_clause(Line) ->
|
||||
Info = { call, Line, { atom, Line, module_info }, [{ var, Line, atom }] },
|
||||
{ clause, Line, [{ var, Line, atom }], [], [Info] }.
|
||||
|
||||
@@ -13,7 +13,7 @@ Nonterminals
|
||||
open_curly close_curly
|
||||
open_bit close_bit
|
||||
base_comma_expr comma_expr optional_comma_expr matched_comma_expr
|
||||
call_args call_args_parens call_args_no_parens parens_call
|
||||
call_args call_args_parens call_args_parens_not_one call_args_no_parens parens_call
|
||||
stab_expr stab_expr_list
|
||||
kw_eol kw_expr kw_comma kw_base
|
||||
matched_kw_expr matched_kw_comma matched_kw_base
|
||||
@@ -80,6 +80,7 @@ grammar -> '$empty' : [nil].
|
||||
|
||||
% Note expressions are on reverse order
|
||||
expr_list -> expr : ['$1'].
|
||||
expr_list -> open_paren ')' : [nil].
|
||||
expr_list -> expr_list eol expr : ['$3'|'$1'].
|
||||
|
||||
expr -> matched_expr : '$1'.
|
||||
@@ -147,6 +148,7 @@ block_expr -> dot_identifier call_args_no_parens do_block : build_identifier('$1
|
||||
|
||||
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 -> call_expr : '$1'.
|
||||
|
||||
@@ -163,7 +165,6 @@ 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 ')' : build_block([]).
|
||||
max_expr -> open_paren expr_list close_paren : build_block('$2').
|
||||
|
||||
bracket_expr -> dot_bracket_identifier bracket_access : build_access(build_identifier('$1', nil), '$2').
|
||||
@@ -211,7 +212,8 @@ stab_expr_list -> stab_expr : ['$1'].
|
||||
stab_expr_list -> stab_expr_list eol stab_expr : ['$3'|'$1'].
|
||||
|
||||
stab_expr -> expr : '$1'.
|
||||
stab_expr -> comma_expr stab_op expr : build_op('$2', lists:reverse('$1'), '$3').
|
||||
stab_expr -> call_args_no_parens stab_op expr : build_op('$2', '$1', '$3').
|
||||
stab_expr -> call_args_parens_not_one stab_op expr : build_op('$2', '$1', '$3').
|
||||
|
||||
block_item -> block_eol stab_expr_list eol : { ?exprs('$1'), build_stab(lists:reverse('$2')) }.
|
||||
block_item -> block_eol : { ?exprs('$1'), nil }.
|
||||
@@ -400,6 +402,10 @@ 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'.
|
||||
@@ -477,7 +483,6 @@ build_tuple(Marker, Args) ->
|
||||
|
||||
build_block(Exprs) -> build_block(Exprs, true).
|
||||
|
||||
build_block([], _) -> nil;
|
||||
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;
|
||||
@@ -530,7 +535,8 @@ build_fn(Op, Args, Expr) ->
|
||||
%% Access
|
||||
|
||||
build_access(Expr, Access) ->
|
||||
{ access, ?line(Access), [ Expr, ?op(Access) ] }.
|
||||
Line = ?line(Access),
|
||||
{ { '.', Line, ['Elixir-Kernel', access] }, ?line(Access), [ Expr, ?op(Access) ] }.
|
||||
|
||||
%% Interpolation aware
|
||||
|
||||
@@ -541,10 +547,10 @@ build_bin_string({ bin_string, _Line, [H] }) when is_binary(H) -> H;
|
||||
build_bin_string({ bin_string, Line, Args }) -> { '<<>>', Line, Args }.
|
||||
|
||||
build_list_string({ list_string, _Line, [H] }) when is_binary(H) -> binary_to_list(H);
|
||||
build_list_string({ list_string, Line, Args }) -> { binary_to_list, Line, [{ '<<>>', Line, Args}] }.
|
||||
build_list_string({ list_string, Line, Args }) -> { { '.', Line, [erlang, binary_to_list] }, Line, [{ '<<>>', Line, Args}] }.
|
||||
|
||||
build_atom({ atom, _Line, Atom }) when is_atom(Atom) -> Atom;
|
||||
build_atom({ atom, Line, Args }) -> { binary_to_atom, Line, [{ '<<>>', Line, Args }, utf8] }.
|
||||
build_atom({ atom, Line, Args }) -> { { '.', Line, [erlang, binary_to_atom] }, Line, [{ '<<>>', Line, Args }, utf8] }.
|
||||
|
||||
%% Keywords
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
-module(elixir_quote).
|
||||
-export([quote/3, linify/2]).
|
||||
-export([quote/3, linify/2, join_quoted/5]).
|
||||
-include("elixir.hrl").
|
||||
|
||||
%% Apply the line from site call on quoted contents.
|
||||
@@ -15,9 +15,28 @@ linify(Line, List) when is_list(List) ->
|
||||
|
||||
linify(_, Else) -> Else.
|
||||
|
||||
%% Join quoted checks for quote arguments at runtime
|
||||
%% in order to properly insert them into the tree
|
||||
join_quoted(Line, Left, { '__aliases__', _, Args }, nil, _File) ->
|
||||
{ '__aliases__', Line, [Left|Args] };
|
||||
|
||||
join_quoted(Line, Left, Right, nil, _File) when is_atom(Right) ->
|
||||
case atom_to_list(Right) of
|
||||
"Elixir-" ++ _ ->
|
||||
{ '__aliases__', Line, [Left, Right] };
|
||||
_ ->
|
||||
{ { '.', Line, [Left, Right] }, Line, [] }
|
||||
end;
|
||||
|
||||
join_quoted(Line, Left, Right, Args, _File) when is_atom(Right) ->
|
||||
{ { '.', Line, [Left, Right] }, Line, Args };
|
||||
|
||||
join_quoted(Line, _Left, _Right, _Args, File) ->
|
||||
elixir_errors:syntax_error(Line, File, "expected unquote after dot to return an atom or an alias").
|
||||
|
||||
%% Translation
|
||||
|
||||
quote({ 'unquote_splicing', Line, _ } = Expr, Q, S) ->
|
||||
quote({ 'unquote_splicing', Line, _ } = Expr, #elixir_quote{unquote=true} = Q, S) ->
|
||||
do_quote({ '__block__', Line, [Expr] }, Q, S);
|
||||
|
||||
quote(Else, Q, S) ->
|
||||
@@ -26,13 +45,15 @@ quote(Else, Q, S) ->
|
||||
do_quote({ unquote, _Line, [Expr] }, #elixir_quote{unquote=true}, S) ->
|
||||
elixir_translator:translate_each(Expr, S);
|
||||
|
||||
do_quote({ { { '.', Line, [Left, unquote] }, _Line, [Expr] }, CallLine, Args }, Q, S) ->
|
||||
Rewritten = { { '.', Line, [Left, { unquote, Line, [Expr] }] }, CallLine, Args },
|
||||
do_quote(Rewritten, Q, S);
|
||||
do_quote({ { { '.', Line, [Left, unquote] }, _, [Expr] }, _, Args }, #elixir_quote{unquote=true} = Q, S) ->
|
||||
All = [Left, { unquote, Line, [Expr] }, Args, S#elixir_scope.file],
|
||||
{ TAll, TS } = lists:mapfoldl(fun(X, Acc) -> do_quote(X, Q, Acc) end, S, All),
|
||||
{ ?ELIXIR_WRAP_CALL(Line, elixir_quote, join_quoted, [line(Line, Q)|TAll]), TS };
|
||||
|
||||
do_quote({ { '.', Line, [Left, unquote] }, _Line, [Expr] }, Q, S) ->
|
||||
Rewritten = { { '.', Line, [Left, { unquote, Line, [Expr] }] }, Line, [] },
|
||||
do_quote(Rewritten, Q, S);
|
||||
do_quote({ { '.', Line, [Left, unquote] }, _, [Expr] }, #elixir_quote{unquote=true} = Q, S) ->
|
||||
All = [Left, { unquote, Line, [Expr] }, nil, S#elixir_scope.file],
|
||||
{ TAll, TS } = lists:mapfoldl(fun(X, Acc) -> do_quote(X, Q, Acc) end, S, All),
|
||||
{ ?ELIXIR_WRAP_CALL(Line, elixir_quote, join_quoted, [line(Line, Q)|TAll]), TS };
|
||||
|
||||
do_quote({ Left, Line, nil }, Q, S) when is_atom(Left) ->
|
||||
Tuple = { tuple, Line, [
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
|
||||
%% Convenience functions used to manipulate scope
|
||||
%% and its variables.
|
||||
-module(elixir_scope).
|
||||
-export([translate_var/4,
|
||||
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,
|
||||
to_erl_env/1, to_ex_env/1, filename/1,
|
||||
umergev/2, umergec/2
|
||||
umergev/2, umergec/2, merge_clause_vars/2
|
||||
]).
|
||||
-include("elixir.hrl").
|
||||
-compile({parse_transform, elixir_transform}).
|
||||
@@ -14,7 +17,8 @@ translate_var(Line, Name, Kind, S) ->
|
||||
Vars = S#elixir_scope.vars,
|
||||
|
||||
case Name of
|
||||
'_' -> { {var, Line, Name}, S };
|
||||
'_' ->
|
||||
{ { var, Line, Name }, S };
|
||||
_ ->
|
||||
case S#elixir_scope.context of
|
||||
assign ->
|
||||
@@ -23,16 +27,21 @@ translate_var(Line, Name, Kind, S) ->
|
||||
{ true, { ok, Kind } } ->
|
||||
{ {var, Line, orddict:fetch(Name, Vars) }, S };
|
||||
{ Else, _ } ->
|
||||
{ NewVar, NS } = case Else or S#elixir_scope.noname of
|
||||
true -> build_erl_var(Line, S);
|
||||
false -> { {var, Line, Name}, S }
|
||||
{ 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 }
|
||||
end,
|
||||
RealName = element(3, NewVar),
|
||||
ClauseVars = S#elixir_scope.clause_vars,
|
||||
{ NewVar, NS#elixir_scope{
|
||||
vars=orddict:store(Name, RealName, Vars),
|
||||
temp_vars=orddict:store(Name, Kind, TempVars),
|
||||
clause_vars=orddict:store({ Name, Kind }, RealName, ClauseVars)
|
||||
clause_vars=if
|
||||
ClauseVars == nil -> nil;
|
||||
true -> orddict:store({ Name, Kind }, RealName, ClauseVars)
|
||||
end
|
||||
} }
|
||||
end;
|
||||
_ ->
|
||||
@@ -45,14 +54,24 @@ translate_var(Line, Name, Kind, S) ->
|
||||
|
||||
% Handle variables translation
|
||||
|
||||
build_erl_var(Line, #elixir_scope{counter=Counter} = S) ->
|
||||
NS = S#elixir_scope{counter=Counter+1},
|
||||
Var = { var, Line, ?ELIXIR_ATOM_CONCAT(["_@", Counter]) },
|
||||
build_ex_var(Line, S) -> build_ex_var(Line, '', "_", S).
|
||||
build_erl_var(Line, S) -> build_erl_var(Line, '', "_", S).
|
||||
|
||||
build_ex_var(Line, Key, S) -> build_ex_var(Line, Key, Key, S).
|
||||
build_erl_var(Line, Key, S) -> build_erl_var(Line, Key, Key, S).
|
||||
|
||||
build_var_counter(Key, #elixir_scope{counter=Counter} = S) ->
|
||||
New = orddict:update_counter(Key, 1, Counter),
|
||||
{ orddict:fetch(Key, New), S#elixir_scope{counter=New} }.
|
||||
|
||||
build_erl_var(Line, Key, Name, S) ->
|
||||
{ Counter, NS } = build_var_counter(Key, S),
|
||||
Var = { var, Line, ?ELIXIR_ATOM_CONCAT([Name, "@", Counter]) },
|
||||
{ Var, NS }.
|
||||
|
||||
build_ex_var(Line, #elixir_scope{counter=Counter} = S) ->
|
||||
NS = S#elixir_scope{counter=Counter+1},
|
||||
Var = { ?ELIXIR_ATOM_CONCAT(["_@", Counter]), Line, nil },
|
||||
build_ex_var(Line, Key, Name, S) ->
|
||||
{ Counter, NS } = build_var_counter(Key, S),
|
||||
Var = { ?ELIXIR_ATOM_CONCAT([Name, "@", Counter]), Line, quoted },
|
||||
{ Var, NS }.
|
||||
|
||||
% Handle Macro.Env conversion
|
||||
@@ -65,8 +84,8 @@ to_ex_env({ Line, Tuple }) when element(1, Tuple) == 'Elixir.Macro.Env' ->
|
||||
|
||||
to_ex_env({ Line, #elixir_scope{module=Module,file=File,
|
||||
function=Function,aliases=Aliases,context=Context,
|
||||
requires=Requires,macros=Macros} }) ->
|
||||
{ 'Elixir.Macro.Env', Module, File, Line, Function, Aliases, Context, Requires, Macros }.
|
||||
requires=Requires,macros=Macros,functions=Functions} }) ->
|
||||
{ 'Elixir.Macro.Env', Module, File, Line, Function, Aliases, Context, Requires, Functions, Macros }.
|
||||
|
||||
filename(#elixir_scope{file=File}) -> File;
|
||||
filename(Other) -> element(3, Other).
|
||||
@@ -75,7 +94,7 @@ filename(Other) -> element(3, Other).
|
||||
|
||||
serialize(S) ->
|
||||
elixir_tree_helpers:abstract_syntax(
|
||||
{ S#elixir_scope.file, S#elixir_scope.functions, S#elixir_scope.check_clauses, S#elixir_scope.macro,
|
||||
{ S#elixir_scope.file, S#elixir_scope.functions, S#elixir_scope.check_clauses,
|
||||
S#elixir_scope.requires, S#elixir_scope.macros, S#elixir_scope.aliases, S#elixir_scope.scheduled }
|
||||
).
|
||||
|
||||
@@ -83,18 +102,17 @@ serialize(S) ->
|
||||
|
||||
deserialize(Tuple) -> deserialize(Tuple, []).
|
||||
|
||||
deserialize({ File, Functions, CheckClauses, Macro, Requires, Macros, Aliases, Scheduled }, Vars) ->
|
||||
deserialize({ File, Functions, CheckClauses, Requires, Macros, Aliases, Scheduled }, Vars) ->
|
||||
#elixir_scope{
|
||||
file=File,
|
||||
functions=Functions,
|
||||
check_clauses=CheckClauses,
|
||||
macro=Macro,
|
||||
requires=Requires,
|
||||
macros=Macros,
|
||||
aliases=Aliases,
|
||||
scheduled=Scheduled,
|
||||
vars=orddict:from_list(Vars),
|
||||
counter=length(Vars)
|
||||
counter=[{'',length(Vars)}]
|
||||
}.
|
||||
|
||||
% Receives two scopes and return a new scope based on the second
|
||||
@@ -110,7 +128,7 @@ umergev(S1, S2) ->
|
||||
S2#elixir_scope{
|
||||
vars=orddict:merge(fun var_merger/3, V1, V2),
|
||||
quote_vars=orddict:merge(fun var_merger/3, Q1, Q2),
|
||||
clause_vars=orddict:merge(fun clause_var_merger/3, C1, C2)
|
||||
clause_vars=merge_clause_vars(C1, C2)
|
||||
}.
|
||||
|
||||
% Receives two scopes and return a new scope based on the first
|
||||
@@ -127,16 +145,24 @@ umergec(S1, S2) ->
|
||||
|
||||
% Merge variables trying to find the most recently created.
|
||||
|
||||
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).
|
||||
|
||||
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, K1, K2) ->
|
||||
V1 = var_number(atom_to_list(K1)),
|
||||
V2 = var_number(atom_to_list(K2)),
|
||||
if V1 > V2 -> K1;
|
||||
true -> K2
|
||||
V1 = var_number(atom_to_list(K1), []),
|
||||
V2 = var_number(atom_to_list(K2), []),
|
||||
case V1 > V2 of
|
||||
true -> K1;
|
||||
false -> K2
|
||||
end.
|
||||
|
||||
var_number([_,_|T]) -> list_to_integer(T).
|
||||
var_number([$@|T], _Acc) -> var_number(T, []);
|
||||
var_number([H|T], Acc) -> var_number(T, [H|Acc]);
|
||||
var_number([], Acc) -> list_to_integer(lists:reverse(Acc)).
|
||||
|
||||
@@ -773,13 +773,12 @@ terminator('<<') -> '>>'.
|
||||
handle_keyword(Line, paren_identifier, fn, Tokens) ->
|
||||
[{ 'fn_paren', Line }|Tokens];
|
||||
|
||||
handle_keyword(Line, Identifier, Atom, Tokens) when Identifier == bracket_identifier; Identifier == paren_identifier ->
|
||||
case keyword(Atom) of
|
||||
true -> [{ Atom, Line }|Tokens];
|
||||
_ -> [{ Identifier, Line, Atom }|Tokens]
|
||||
end;
|
||||
handle_keyword(Line, Identifier, Atom, [{ '.', _ }|_] = Tokens) ->
|
||||
[{ Identifier, Line, Atom }|Tokens];
|
||||
|
||||
handle_keyword(Line, Identifier, Atom, Tokens) when Identifier == identifier; Identifier == do_identifier ->
|
||||
handle_keyword(Line, Identifier, Atom, Tokens) when
|
||||
Identifier == identifier; Identifier == do_identifier;
|
||||
Identifier == bracket_identifier; Identifier == paren_identifier ->
|
||||
case keyword(Atom) of
|
||||
true -> [{ Atom, Line }|Tokens];
|
||||
op -> add_token_with_nl({ Atom, Line }, Tokens);
|
||||
|
||||
@@ -61,7 +61,7 @@ translate_each({ '__block__', Line, Args }, S) when is_list(Args) ->
|
||||
{ TArgs, NS } = translate(Args, S),
|
||||
{ { block, Line, TArgs }, NS };
|
||||
|
||||
translate_each({ '__scope__', _Line, [[{file,File}],Expr] }, S) ->
|
||||
translate_each({ '__scope__', _Line, [[{file,File}],[{do,Expr}]] }, S) ->
|
||||
Old = S#elixir_scope.file,
|
||||
{ TExpr, TS } = translate_each(Expr, S#elixir_scope{file=File}),
|
||||
{ TExpr, TS#elixir_scope{file=Old} };
|
||||
@@ -259,7 +259,7 @@ translate_each({ quote, GivenLine, [T] }, S) when is_list(T) ->
|
||||
|
||||
{ DefaultLine, WrappedExprs } = case lists:keyfind(location, 1, T) of
|
||||
{ location, keep } ->
|
||||
Scoped = { '__scope__', GivenLine, [[{file,S#elixir_scope.file}],Exprs] },
|
||||
Scoped = { '__scope__', GivenLine, [[{file,S#elixir_scope.file}],[{do,Exprs}]] },
|
||||
{ keep, Scoped };
|
||||
_ ->
|
||||
{ 0, Exprs}
|
||||
@@ -408,14 +408,6 @@ translate_each({ { '.', _, [Left, Right] }, Line, Args } = Original, S) when is_
|
||||
Callback = fun() -> translate_apply(Line, TLeft, TRight, Args, S, SL, SR) end,
|
||||
|
||||
case TLeft of
|
||||
{ atom, _, 'Elixir.Erlang' } ->
|
||||
elixir_errors:deprecation(Line, S#elixir_scope.file, "Erlang. syntax is deprecated"),
|
||||
case Args of
|
||||
[] -> { { atom, Line, Right }, S };
|
||||
_ ->
|
||||
Message = "invalid args for Erlang.~s expression",
|
||||
syntax_error(Line, S#elixir_scope.file, Message, [Right])
|
||||
end;
|
||||
{ atom, _, Receiver } ->
|
||||
elixir_dispatch:dispatch_require(Line, Receiver, Right, Args, umergev(SL, SR), Callback);
|
||||
_ ->
|
||||
@@ -565,7 +557,7 @@ rellocate_ambiguous_op({ Call, Line, [H|T] }, Var) ->
|
||||
translate_comprehension(Line, Kind, Args, S) ->
|
||||
case elixir_tree_helpers:split_last(Args) of
|
||||
{ Cases, [{do,Expr}] } ->
|
||||
{ TCases, SC } = lists:mapfoldl(fun translate_comprehension_clause/2, S, Cases),
|
||||
{ TCases, SC } = lists:mapfoldl(fun(C, Acc) -> translate_comprehension_clause(Line, C, Acc) end, S, Cases),
|
||||
{ TExpr, SE } = translate_comprehension_do(Line, Kind, Expr, SC),
|
||||
{ { Kind, Line, TExpr, TCases }, umergec(S, SE) };
|
||||
_ ->
|
||||
@@ -581,20 +573,16 @@ translate_comprehension_do(Line, bc, _Expr, S) ->
|
||||
translate_comprehension_do(_Line, _Kind, Expr, S) ->
|
||||
translate_each(Expr, S).
|
||||
|
||||
translate_comprehension_clause({inbits, Line, [Left, Right]}, S) ->
|
||||
translate_comprehension_clause(_Line, {inbits, Line, [Left, Right]}, S) ->
|
||||
{ TRight, SR } = translate_each(Right, S),
|
||||
{ TLeft, SL } = elixir_clauses:assigns(fun elixir_translator:translate_each/2, Left, SR),
|
||||
{ { b_generate, Line, TLeft, TRight }, SL };
|
||||
|
||||
translate_comprehension_clause({inlist, Line, [Left, Right]}, S) ->
|
||||
translate_comprehension_clause(_Line, {inlist, Line, [Left, Right]}, S) ->
|
||||
{ TRight, SR } = translate_each(Right, S),
|
||||
{ TLeft, SL } = elixir_clauses:assigns(fun elixir_translator:translate_each/2, Left, SR),
|
||||
{ { generate, Line, TLeft, TRight }, SL };
|
||||
|
||||
translate_comprehension_clause(X, S) ->
|
||||
translate_comprehension_clause(Line, X, S) ->
|
||||
{ TX, TS } = translate_each(X, S),
|
||||
Line = case X of
|
||||
{ _, L, _ } -> L;
|
||||
_ -> 0
|
||||
end,
|
||||
{ elixir_tree_helpers:convert_to_boolean(Line, TX, true, false), TS }.
|
||||
elixir_tree_helpers:convert_to_boolean(Line, TX, true, false, TS).
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
-module(elixir_tree_helpers).
|
||||
-compile({parse_transform, elixir_transform}).
|
||||
-export([abstract_syntax/1, split_last/1, cons_to_list/1,
|
||||
convert_to_boolean/4, returns_boolean/1,
|
||||
convert_to_boolean/5, returns_boolean/1,
|
||||
build_list/4, build_list/5, build_simple_list/2,
|
||||
build_reverse_list/4, build_reverse_list/5, build_simple_reverse_list/2]).
|
||||
-include("elixir.hrl").
|
||||
@@ -84,34 +84,47 @@ returns_boolean({ call, _, { remote, _, { atom, _, erlang }, { atom, _, Fun } },
|
||||
returns_boolean({ call, _, { remote, _, { atom, _, erlang }, { atom, _, Fun } }, [_,_] }) when
|
||||
Fun == is_function -> true;
|
||||
|
||||
returns_boolean({ call, _, { remote, _, { atom, _, erlang }, { atom, _, Fun } }, [_,_,_] }) when
|
||||
Fun == function_exported -> true;
|
||||
|
||||
returns_boolean({ atom, _, Bool }) when Bool == true; Bool == false -> true;
|
||||
|
||||
returns_boolean({ 'case', _, _, Clauses }) ->
|
||||
lists:all(fun
|
||||
({clause,_,_,_,[Expr]}) -> returns_boolean(Expr);
|
||||
(_) -> false
|
||||
end, Clauses);
|
||||
|
||||
returns_boolean(_) -> false.
|
||||
|
||||
convert_to_boolean(Line, Expr, Bool, InGuard) ->
|
||||
convert_to_boolean(Line, Expr, Bool, InGuard, S) ->
|
||||
case { returns_boolean(Expr), Bool } of
|
||||
{ true, true } -> Expr;
|
||||
{ true, false } -> { op, Line, 'not', Expr };
|
||||
_ -> do_convert_to_boolean(Line, Expr, Bool, InGuard)
|
||||
{ true, true } -> { Expr, S };
|
||||
{ true, false } -> { { op, Line, 'not', Expr }, S };
|
||||
_ -> do_convert_to_boolean(Line, Expr, Bool, InGuard, S)
|
||||
end.
|
||||
|
||||
do_convert_to_boolean(Line, Expr, Bool, false) ->
|
||||
Any = [{var, Line, '_'}],
|
||||
False = [{atom,Line,false}],
|
||||
Nil = [{atom,Line,nil}],
|
||||
%% Notice we use a temporary var and bundle nil
|
||||
%% and false checks in the same clause since
|
||||
%% it makes dialyzer happy.
|
||||
do_convert_to_boolean(Line, Expr, Bool, false, S) ->
|
||||
Any = { var, Line, '_' },
|
||||
{ Var, TS } = elixir_scope:build_erl_var(Line, S),
|
||||
OrElse = do_guarded_convert_to_boolean(Line, Var, 'orelse', '=='),
|
||||
|
||||
FalseResult = [{atom,Line,not Bool}],
|
||||
TrueResult = [{atom,Line,Bool}],
|
||||
FalseResult = { atom,Line,not Bool },
|
||||
TrueResult = { atom,Line,Bool },
|
||||
|
||||
{ 'case', Line, Expr, [
|
||||
{ clause, Line, False, [], FalseResult },
|
||||
{ clause, Line, Nil, [], FalseResult },
|
||||
{ clause, Line, Any, [], TrueResult }
|
||||
] };
|
||||
{ { 'case', Line, Expr, [
|
||||
{ clause, Line, [Var], [[OrElse]], [FalseResult] },
|
||||
{ clause, Line, [Any], [], [TrueResult] }
|
||||
] }, TS };
|
||||
|
||||
do_convert_to_boolean(Line, Expr, true, true) ->
|
||||
do_guarded_convert_to_boolean(Line, Expr, 'andalso', '/=');
|
||||
do_convert_to_boolean(Line, Expr, true, true, S) ->
|
||||
{ do_guarded_convert_to_boolean(Line, Expr, 'andalso', '/='), S };
|
||||
|
||||
do_convert_to_boolean(Line, Expr, false, true) ->
|
||||
do_guarded_convert_to_boolean(Line, Expr, 'orelse', '==').
|
||||
do_convert_to_boolean(Line, Expr, false, true, S) ->
|
||||
{ do_guarded_convert_to_boolean(Line, Expr, 'orelse', '=='), S }.
|
||||
|
||||
do_guarded_convert_to_boolean(Line, Expr, Op, Comp) ->
|
||||
Left = { op, Line, Comp, Expr, { atom, Line, false } },
|
||||
|
||||
@@ -240,7 +240,7 @@ validate_rescue_access(Line, { '=', _, [Left, Right] }, S) ->
|
||||
validate_rescue_access(Line, Left, S),
|
||||
validate_rescue_access(Line, Right, S);
|
||||
|
||||
validate_rescue_access(Line, { 'access', _, [Element, _] }, S) ->
|
||||
validate_rescue_access(Line, { { '.', _, ['Elixir.Kernel', 'access'] }, _, [Element, _] }, S) ->
|
||||
case elixir_translator:translate_each(Element, S) of
|
||||
{ { atom, _, Atom }, _ } ->
|
||||
case lists:member(Atom, erlang_rescues()) of
|
||||
|
||||
@@ -6,16 +6,42 @@ defmodule BehaviourTest do
|
||||
defmodule Sample do
|
||||
use Behaviour
|
||||
|
||||
@doc "Doc"
|
||||
defcallback foo(foo, bar // [], bat // [])
|
||||
@doc "Foo"
|
||||
defcallback foo(atom, binary), do: binary
|
||||
|
||||
@doc "Bar"
|
||||
defcallback bar(External.hello, my_var :: binary), do: binary
|
||||
end
|
||||
|
||||
test :docs_are_defined do
|
||||
assert [_, _] = Sample.__info__(:docs)
|
||||
test :docs do
|
||||
docs = Enum.qsort(Sample.__behaviour__(:docs))
|
||||
assert docs == [
|
||||
{{:bar,2},13,"Bar"},
|
||||
{{:foo,2},10,"Foo"}
|
||||
]
|
||||
end
|
||||
|
||||
test :callbacks do
|
||||
assert [{:foo,3},{:foo,2},{:foo,1}] = Sample.behaviour_info(:callbacks)
|
||||
assert Sample.__behaviour__(:callbacks) == [foo: 2, bar: 2]
|
||||
end
|
||||
|
||||
test :specs do
|
||||
assert length(Keyword.get_values(Sample.module_info[:attributes], :callback)) == 2
|
||||
end
|
||||
|
||||
test :default_is_not_supported do
|
||||
assert_raise ArgumentError, fn ->
|
||||
defmodule WithDefault do
|
||||
use Behaviour
|
||||
defcallback hello(num // 0 :: integer), do: integer
|
||||
end
|
||||
end
|
||||
|
||||
assert_raise ArgumentError, fn ->
|
||||
defmodule WithDefault do
|
||||
use Behaviour
|
||||
defcallback hello(num :: integer // 0), do: integer
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -110,6 +110,10 @@ defmodule Binary.Inspect.TupleTest do
|
||||
assert inspect({ List, 1 }) == "{List,1}"
|
||||
end
|
||||
|
||||
test :with_record_like_pseudo_exception do
|
||||
assert inspect({ Other, :__exception__, 1 }) == "{Other,:__exception__,1}"
|
||||
end
|
||||
|
||||
defrecord Config, a: 1, b: []
|
||||
|
||||
test :with_record do
|
||||
@@ -155,6 +159,10 @@ defmodule Binary.Inspect.ListTest do
|
||||
assert inspect(["123": 1]) == %b(["123": 1])
|
||||
end
|
||||
|
||||
test :non_keyword do
|
||||
assert inspect([{ Regex, 1 }]) == "[{Regex,1}]"
|
||||
end
|
||||
|
||||
test :non_printable do
|
||||
assert inspect([{:b, 1},{:a,1}]) == "[b: 1, a: 1]"
|
||||
end
|
||||
|
||||
@@ -59,7 +59,7 @@ bar
|
||||
|
||||
test :pattern_match do
|
||||
s = 16
|
||||
assert <<a, b :: size(s)>> = "foo"
|
||||
assert <<_a, _b :: size(s)>> = "foo"
|
||||
end
|
||||
|
||||
test :partial_application do
|
||||
|
||||
@@ -28,6 +28,11 @@ defmodule CodeTest do
|
||||
assert CodeTest.Sample.eval_quoted_info() == { CodeTest.Sample, "sample.ex", 13 }
|
||||
end
|
||||
|
||||
test :eval_quoted_with_env do
|
||||
alias :lists, as: MyList
|
||||
assert Code.eval_quoted(quote(do: MyList.flatten [[1,2,3]]), [], __ENV__) == { [1,2,3],[] }
|
||||
end
|
||||
|
||||
test :require do
|
||||
Code.require_file fixture_path("code_sample.exs")
|
||||
assert fixture_path("code_sample.exs") in Code.loaded_files
|
||||
|
||||
@@ -34,6 +34,14 @@ defmodule DictTest.Common do
|
||||
assert "default" == Dict.get(empty_dict, "first_key", "default")
|
||||
end
|
||||
|
||||
test :get! do
|
||||
assert 1 == Dict.get!(new_dict, "first_key")
|
||||
assert 2 == Dict.get!(new_dict, "second_key")
|
||||
assert_raise KeyError, fn ->
|
||||
Dict.get!(new_dict, "other_key")
|
||||
end
|
||||
end
|
||||
|
||||
test :put do
|
||||
dict = Dict.put(new_dict, "first_key", {1})
|
||||
assert {1} == Dict.get dict, "first_key"
|
||||
|
||||
@@ -3,31 +3,6 @@ Code.require_file "../test_helper.exs", __FILE__
|
||||
defmodule EnumTest do
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
test :times do
|
||||
try do
|
||||
Process.put(:times_with_arity, nil)
|
||||
assert Enum.times(0, fn -> Process.put(:times_with_arity, :ok) end) == 0
|
||||
assert Process.get(:times_with_arity) == nil
|
||||
assert Enum.times(3, fn -> Process.put(:times_with_arity, :ok) end) == 3
|
||||
assert Process.get(:times_with_arity) == :ok
|
||||
after
|
||||
Process.delete(:times_with_arity)
|
||||
end
|
||||
end
|
||||
|
||||
test :times_with_iterator do
|
||||
try do
|
||||
assert Enum.times(5, fn x -> Process.put(:times_with_arity, x) end) == 5
|
||||
assert Process.get(:times_with_arity) == 4
|
||||
after
|
||||
Process.delete(:times_with_arity)
|
||||
end
|
||||
end
|
||||
|
||||
test :times_with_iterator_and_acumulator do
|
||||
assert Enum.times(5, 0, fn acc, x -> acc + x end) == 10
|
||||
end
|
||||
|
||||
test :reverse do
|
||||
assert Enum.reverse(URI.query_decoder("foo=bar&baz=bat")) ==
|
||||
[{ "baz", "bat" }, { "foo", "bar" }]
|
||||
@@ -712,8 +687,6 @@ defmodule EnumTest.Range do
|
||||
assert Enum.take_while(range, fn(x) -> x > 3 end) == []
|
||||
assert Enum.take_while(range, fn(x) -> x <= 1 end) == [1]
|
||||
assert Enum.take_while(range, fn(x) -> x <= 3 end) == [1,2,3]
|
||||
|
||||
range = Range.new(first: 1, last: 0)
|
||||
assert Enum.take_while([], fn(_) -> true end) == []
|
||||
end
|
||||
|
||||
|
||||
@@ -589,7 +589,7 @@ defmodule FileTest do
|
||||
end
|
||||
|
||||
test :open_utf8_by_default do
|
||||
{ :ok, file } = File.open(fixture_path("utf8.txt"))
|
||||
{ :ok, file } = File.open(fixture_path("utf8.txt"), [:utf8])
|
||||
assert IO.gets(file, "") == "Русский\n"
|
||||
assert File.close(file) == :ok
|
||||
end
|
||||
@@ -612,8 +612,20 @@ defmodule FileTest do
|
||||
end
|
||||
end
|
||||
|
||||
test :open_with_binwrite_permission do
|
||||
fixture = tmp_path("tmp_text.txt")
|
||||
try do
|
||||
{ :ok, file } = File.open(fixture, [:write])
|
||||
assert IO.binwrite(file, "Русский") == :ok
|
||||
assert File.close(file) == :ok
|
||||
assert File.read(fixture) == { :ok, "Русский" }
|
||||
after
|
||||
File.rm(fixture)
|
||||
end
|
||||
end
|
||||
|
||||
test :open_utf8_and_charlist do
|
||||
{ :ok, file } = File.open(fixture_path("utf8.txt"), [:charlist])
|
||||
{ :ok, file } = File.open(fixture_path("utf8.txt"), [:charlist, :utf8])
|
||||
assert IO.gets(file, "") == [1056,1091,1089,1089,1082,1080,1081,10]
|
||||
assert File.close(file) == :ok
|
||||
end
|
||||
|
||||
@@ -10,11 +10,17 @@ defmodule IOTest do
|
||||
end
|
||||
|
||||
test :read_with_utf8_and_binary do
|
||||
{ :ok, file } = File.open(File.expand_path('../fixtures/utf8.txt', __FILE__))
|
||||
{ :ok, file } = File.open(File.expand_path('../fixtures/utf8.txt', __FILE__), [:utf8])
|
||||
assert "Русский" == IO.read(file, 7)
|
||||
assert File.close(file) == :ok
|
||||
end
|
||||
|
||||
test :binread do
|
||||
{ :ok, file } = File.open(File.expand_path('../fixtures/utf8.txt', __FILE__))
|
||||
assert "Русский" == IO.binread(file, 14)
|
||||
assert File.close(file) == :ok
|
||||
end
|
||||
|
||||
test :getb do
|
||||
{ :ok, file } = File.open(File.expand_path('../fixtures/foo.txt', __FILE__))
|
||||
assert "F" == IO.getb(file, "")
|
||||
@@ -32,7 +38,7 @@ defmodule IOTest do
|
||||
end
|
||||
|
||||
test :getb_with_utf8_and_binary do
|
||||
{ :ok, file } = File.open(File.expand_path('../fixtures/utf8.txt', __FILE__))
|
||||
{ :ok, file } = File.open(File.expand_path('../fixtures/utf8.txt', __FILE__), [:utf8])
|
||||
assert "Русский" == IO.getb(file, "", 7)
|
||||
assert File.close(file) == :ok
|
||||
end
|
||||
@@ -45,7 +51,7 @@ defmodule IOTest do
|
||||
end
|
||||
|
||||
test :gets_with_utf8_and_binary do
|
||||
{ :ok, file } = File.open(File.expand_path('../fixtures/utf8.txt', __FILE__))
|
||||
{ :ok, file } = File.open(File.expand_path('../fixtures/utf8.txt', __FILE__), [:utf8])
|
||||
assert "Русский\n" == IO.gets(file, "")
|
||||
assert "日\n" == IO.gets(file, "")
|
||||
assert File.close(file) == :ok
|
||||
@@ -59,9 +65,16 @@ defmodule IOTest do
|
||||
end
|
||||
|
||||
test :readline_with_utf8_and_binary do
|
||||
{ :ok, file } = File.open(File.expand_path('../fixtures/utf8.txt', __FILE__))
|
||||
{ :ok, file } = File.open(File.expand_path('../fixtures/utf8.txt', __FILE__), [:utf8])
|
||||
assert "Русский\n" == IO.readline(file)
|
||||
assert "日\n" == IO.readline(file)
|
||||
assert File.close(file) == :ok
|
||||
end
|
||||
|
||||
test :binreadline do
|
||||
{ :ok, file } = File.open(File.expand_path('../fixtures/utf8.txt', __FILE__))
|
||||
assert "Русский\n" == IO.binreadline(file)
|
||||
assert "日\n" == IO.binreadline(file)
|
||||
assert File.close(file) == :ok
|
||||
end
|
||||
end
|
||||
|
||||
@@ -77,7 +77,7 @@ defmodule Kernel.CLI.CompileTest do
|
||||
import PathHelpers
|
||||
|
||||
test :compile_code do
|
||||
fixture = fixture_path "compile_sample.exs"
|
||||
fixture = fixture_path "compile_sample.ex"
|
||||
assert elixirc('#{fixture} -o #{tmp_path}') ==
|
||||
'Compiled #{fixture}\n'
|
||||
assert File.regular?(tmp_path "Elixir-CompileSample.beam")
|
||||
@@ -88,6 +88,12 @@ defmodule Kernel.CLI.ParallelCompilerTest do
|
||||
use ExUnit.Case, async: true
|
||||
import PathHelpers
|
||||
|
||||
test :files do
|
||||
fixtures = [fixture_path("compile_sample.ex")]
|
||||
assert [{ CompileSample, binary }] = Kernel.ParallelCompiler.files fixtures
|
||||
assert is_binary(binary)
|
||||
end
|
||||
|
||||
test :compile_code do
|
||||
output = elixirc('#{fixture_path("parallel_compiler")} -o #{tmp_path}')
|
||||
assert :string.str(output, 'message_from_foo') > 0,
|
||||
|
||||
@@ -17,8 +17,8 @@ defmodule Kernel.DocTest do
|
||||
assert CompiledWithDocs.__info__(:docs) == expected
|
||||
assert CompiledWithDocs.__info__(:moduledoc) == { 1, "moduledoc" }
|
||||
after
|
||||
:code.purge CompiledWithDocs
|
||||
:code.delete CompiledWithDocs
|
||||
:code.purge CompiledWithDocs
|
||||
end
|
||||
end
|
||||
|
||||
@@ -33,8 +33,8 @@ defmodule Kernel.DocTest do
|
||||
assert CompiledWithDocs.__info__(:moduledoc) == nil
|
||||
after
|
||||
Code.compiler_options(docs: true)
|
||||
:code.purge CompiledWithDocs
|
||||
:code.delete CompiledWithDocs
|
||||
:code.purge CompiledWithDocs
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -103,6 +103,11 @@ defmodule Kernel.ErrorsTest do
|
||||
format_rescue 'defmodule Foo do\ndefmacrop foo, do: 1\ndefmacro bar, do: __MODULE__.foo\ndefmacro baz, do: bar\nend'
|
||||
end
|
||||
|
||||
test :function_definition_with_alias do
|
||||
assert "nofile:1: function names should start with lowercase characters or underscore, invalid name Bar" ==
|
||||
format_rescue 'defmodule Foo do def Bar do :baz end\nend\n'
|
||||
end
|
||||
|
||||
test :erlang_function_conflict do
|
||||
assert "nofile:1: function exit/1 already imported from Kernel" ==
|
||||
format_rescue 'defmodule Foo do import Kernel.ErrorsTest.UnproperMacro, only: [exit: 1]\nend'
|
||||
@@ -153,7 +158,7 @@ defmodule Kernel.ErrorsTest do
|
||||
end
|
||||
|
||||
test :cant_define_local_due_to_in_erlang_macros_conflict do
|
||||
assert "nofile:1: cannot define local quote/1 because it conflicts with Elixir internal macros" ==
|
||||
assert "nofile:1: cannot define local quote/1 because it conflicts with Elixir special forms" ==
|
||||
format_rescue 'defmodule Foo do\ndef quote(x), do: x\ndef bar(x), do: quote(do: x)\nend'
|
||||
end
|
||||
|
||||
@@ -273,7 +278,7 @@ defmodule Kernel.ErrorsTest do
|
||||
:elixir.eval(to_char_list(expr), [])
|
||||
nil
|
||||
rescue
|
||||
error -> System.stacktrace
|
||||
_ -> System.stacktrace
|
||||
end
|
||||
|
||||
result || raise(ExUnit.AssertionError, message: "Expected function given to format_rescue to fail")
|
||||
|
||||
@@ -27,7 +27,7 @@ defmodule Kernel.Overridable do
|
||||
{ super, super?, 1 }
|
||||
end
|
||||
|
||||
false = Module.overridable? __MODULE__, { :explicit_nested_super, 0 }
|
||||
true = Module.overridable? __MODULE__, { :explicit_nested_super, 0 }
|
||||
|
||||
defoverridable [explicit_nested_super: 0]
|
||||
|
||||
@@ -94,6 +94,10 @@ defmodule Kernel.Overridable do
|
||||
17
|
||||
end
|
||||
|
||||
def many_clauses(3) do
|
||||
super(0) + super(1)
|
||||
end
|
||||
|
||||
def many_clauses(_) do
|
||||
super
|
||||
end
|
||||
@@ -139,6 +143,7 @@ defmodule Kernel.OverridableTest do
|
||||
assert Overridable.many_clauses(0) == 11
|
||||
assert Overridable.many_clauses(1) == 13
|
||||
assert Overridable.many_clauses(2) == 17
|
||||
assert Overridable.many_clauses(3) == 24
|
||||
end
|
||||
|
||||
test "overridable definitions are private" do
|
||||
|
||||
@@ -69,7 +69,7 @@ defmodule Kernel.QuoteTest do
|
||||
67,
|
||||
[
|
||||
[file: __FILE__],
|
||||
{ :bar, 67, [1,2,3] }
|
||||
[do: { :bar, 67, [1,2,3] }]
|
||||
]
|
||||
}
|
||||
end
|
||||
@@ -87,6 +87,13 @@ defmodule Kernel.QuoteTest do
|
||||
assert quote(do: foo.unquote(:bar)) == quote(do: foo.bar)
|
||||
assert quote(do: foo.unquote(:bar)(1)) == quote(do: foo.bar(1))
|
||||
assert quote(do: foo.unquote(:bar)(1) do 2 + 3 end) == quote(do: foo.bar(1) do 2 + 3 end)
|
||||
|
||||
assert Code.eval_quoted(quote(do: Foo.unquote(Bar))) == { Foo.Bar, [] }
|
||||
assert Code.eval_quoted(quote(do: Foo.unquote(quote do: Bar))) == { Foo.Bar, [] }
|
||||
|
||||
assert_raise SyntaxError, fn ->
|
||||
quote(do: foo.unquote(1))
|
||||
end
|
||||
end
|
||||
|
||||
test :splice_on_root do
|
||||
|
||||
@@ -31,7 +31,7 @@ defmodule Kernel.RescueTest do
|
||||
rescue
|
||||
RuntimeError -> true
|
||||
catch
|
||||
:error, value -> false
|
||||
:error, _ -> false
|
||||
end
|
||||
|
||||
assert result
|
||||
|
||||
@@ -204,6 +204,54 @@ defmodule KernelTest do
|
||||
defp bang(_), do: :truthy
|
||||
end
|
||||
|
||||
defmodule IfScope do
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
test :variables_on_nested_if do
|
||||
if true do
|
||||
a = 1
|
||||
if true do
|
||||
b = 2
|
||||
end
|
||||
end
|
||||
|
||||
assert a == 1
|
||||
assert b == 2
|
||||
end
|
||||
|
||||
test :variables_on_siblings_if do
|
||||
if true do
|
||||
a = 1
|
||||
|
||||
if true do
|
||||
b = 2
|
||||
end
|
||||
|
||||
if true do
|
||||
c = 3
|
||||
end
|
||||
end
|
||||
|
||||
assert a == 1
|
||||
assert b == 2
|
||||
assert c == 3
|
||||
end
|
||||
|
||||
test :variables_counter_on_nested_if do
|
||||
r = (fn() -> 3 end).() # supresses warning at (if r < 0...)
|
||||
r = r - 1
|
||||
r = r - 1
|
||||
r = r - 1
|
||||
|
||||
if true do
|
||||
r = r - 1
|
||||
if r < 0, do: r = 0
|
||||
end
|
||||
|
||||
assert r == 0
|
||||
end
|
||||
end
|
||||
|
||||
defmodule Destructure do
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
|
||||
@@ -28,6 +28,13 @@ defmodule KeywordTest do
|
||||
assert Keyword.from_enum(dict) == [a: 1, b: 2, c: 3]
|
||||
end
|
||||
|
||||
test :keyword? do
|
||||
assert Keyword.keyword?([])
|
||||
assert Keyword.keyword?([a: 1])
|
||||
refute Keyword.keyword?([{}])
|
||||
refute Keyword.keyword?(<<>>)
|
||||
end
|
||||
|
||||
test :new do
|
||||
assert Keyword.new == []
|
||||
end
|
||||
@@ -50,7 +57,7 @@ defmodule KeywordTest do
|
||||
test :get! do
|
||||
assert Keyword.get!(create_keywords, :first_key) == 1
|
||||
|
||||
error = assert_raise Keyword.KeyError, fn ->
|
||||
error = assert_raise KeyError, fn ->
|
||||
Keyword.get!(create_keywords, :unknown)
|
||||
end
|
||||
|
||||
@@ -99,7 +106,7 @@ defmodule KeywordTest do
|
||||
|
||||
test :update do
|
||||
assert Keyword.update([a: 1], :a, &1 * 2) == [a: 2]
|
||||
assert_raise Keyword.KeyError, fn ->
|
||||
assert_raise KeyError, fn ->
|
||||
assert Keyword.update([a: 1], :b, &1 * 2)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -128,6 +128,14 @@ defmodule MacroTest do
|
||||
assert Macro.to_binary(quote do: foo.bar([1, 2, 3])) == "foo.bar([1, 2, 3])"
|
||||
end
|
||||
|
||||
test :low_atom_remote_call_to_binary do
|
||||
assert Macro.to_binary(quote do: :foo.bar(1, 2, 3)) == ":foo.bar(1, 2, 3)"
|
||||
end
|
||||
|
||||
test :big_atom_remote_call_to_binary do
|
||||
assert Macro.to_binary(quote do: Foo.Bar.bar(1, 2, 3)) == "Foo.Bar.bar(1, 2, 3)"
|
||||
end
|
||||
|
||||
test :remote_and_fun_call_to_binary do
|
||||
assert Macro.to_binary(quote do: foo.bar.(1, 2, 3)) == "foo.bar().(1, 2, 3)"
|
||||
assert Macro.to_binary(quote do: foo.bar.([1, 2, 3])) == "foo.bar().([1, 2, 3])"
|
||||
|
||||
@@ -170,6 +170,16 @@ defmodule ModuleTest do
|
||||
end)
|
||||
end
|
||||
|
||||
def __on_definition__(env, kind, name, args, guards, expr) do
|
||||
Process.put(env.module, :called)
|
||||
assert env.module == ModuleTest.OnDefinition
|
||||
assert kind == :def
|
||||
assert name == :hello
|
||||
assert [{ :foo, _, _ }, { :bar, _ ,_ }] = args
|
||||
assert [] = guards
|
||||
assert [do: { :+, _, [{ :foo, _, _ }, { :bar, _, _ }] }] = expr
|
||||
end
|
||||
|
||||
test :on_definition do
|
||||
defmodule OnDefinition do
|
||||
@on_definition ModuleTest
|
||||
@@ -182,16 +192,6 @@ defmodule ModuleTest do
|
||||
assert Process.get(ModuleTest.OnDefinition) == :called
|
||||
end
|
||||
|
||||
def __on_definition__(env, kind, name, args, guards, expr) do
|
||||
Process.put(env.module, :called)
|
||||
assert env.module == ModuleTest.OnDefinition
|
||||
assert kind == :def
|
||||
assert name == :hello
|
||||
assert [{ :foo, _, _ }, { :bar, _ ,_ }] = args
|
||||
assert [] = guards
|
||||
assert [do: { :+, _, [{ :foo, _, _ }, { :bar, _, _ }] }] = expr
|
||||
end
|
||||
|
||||
test :create do
|
||||
contents =
|
||||
quote do
|
||||
@@ -201,4 +201,22 @@ defmodule ModuleTest do
|
||||
Module.create(ModuleCreateSample, contents, __ENV__)
|
||||
assert ModuleCreateSample.world
|
||||
end
|
||||
|
||||
defmacro __before_compile__(_) do
|
||||
quote do
|
||||
def constant, do: 1
|
||||
defoverridable constant: 0
|
||||
end
|
||||
end
|
||||
|
||||
test :overridable_inside_before_compile do
|
||||
defmodule OverridableWithBeforeCompile do
|
||||
@before_compile ModuleTest
|
||||
end
|
||||
assert OverridableWithBeforeCompile.constant == 1
|
||||
end
|
||||
|
||||
test :to_binary do
|
||||
assert Module.to_binary(Hello.World) == "Hello.World"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -16,11 +16,11 @@ defmodule OptionParserTest do
|
||||
end
|
||||
|
||||
test "parses more than one boolean option" do
|
||||
assert OptionParser.parse(["--docs", "--compile"]) == { [compile: true, docs: true], [] }
|
||||
assert OptionParser.parse(["--docs", "--compile"]) == { [docs: true, compile: true], [] }
|
||||
end
|
||||
|
||||
test "parses more than one boolean options as the alias" do
|
||||
assert OptionParser.parse(["--d", "--compile"], aliases: [d: :docs]) == { [compile: true, docs: true], [] }
|
||||
assert OptionParser.parse(["--d", "--compile"], aliases: [d: :docs]) == { [docs: true, compile: true], [] }
|
||||
end
|
||||
|
||||
test "parses key/value option" do
|
||||
@@ -57,7 +57,7 @@ defmodule OptionParserTest do
|
||||
assert OptionParser.parse(["--docs", "foo"], flags: [:docs]) == { [docs: true], ["foo"] }
|
||||
end
|
||||
|
||||
test "flags default to false even when not given" do
|
||||
test "not parsed flags defaults to false" do
|
||||
assert OptionParser.parse(["foo"], flags: [:docs]) == { [docs: false], ["foo"] }
|
||||
end
|
||||
|
||||
@@ -71,27 +71,25 @@ defmodule OptionParserTest do
|
||||
end
|
||||
|
||||
test "parses more than one key/value options" do
|
||||
options = OptionParser.parse(["--source", "from_docs/", "--docs", "false"])
|
||||
assert_equal options, { [docs: false, source: "from_docs/"], [] }
|
||||
assert OptionParser.parse(["--source", "from_docs/", "--docs", "false"]) == { [source: "from_docs/", docs: false], [] }
|
||||
end
|
||||
|
||||
test "accumulates options accumulators" do
|
||||
assert OptionParser.parse(["--require", "foo", "--require", "bar", "baz"]) == { [require: "foo", require: "bar"], ["baz"] }
|
||||
end
|
||||
|
||||
test "parses mixed options" do
|
||||
options = OptionParser.parse(["--source", "from_docs/", "--docs", "false", "--compile", "-x"])
|
||||
assert_equal options, { :orddict.from_list([docs: false, source: "from_docs/", compile: true, x: true]), [] }
|
||||
assert options == { [source: "from_docs/", docs: false, compile: true, x: true], [] }
|
||||
end
|
||||
|
||||
test "stops on first non option arguments" do
|
||||
options = OptionParser.parse_head(["--source", "from_docs/", "test/enum_test.exs", "--verbose"])
|
||||
assert_equal options, { [source: "from_docs/"], ["test/enum_test.exs", "--verbose"] }
|
||||
assert options == { [source: "from_docs/"], ["test/enum_test.exs", "--verbose"] }
|
||||
end
|
||||
|
||||
test "goes beyond the first non option arguments" do
|
||||
options = OptionParser.parse(["--source", "from_docs/", "test/enum_test.exs", "--verbose"])
|
||||
assert_equal options, { [source: "from_docs/", verbose: true], ["test/enum_test.exs"] }
|
||||
end
|
||||
|
||||
defp assert_equal({ opts1, tail1 }, { opts2, tail2 }) do
|
||||
assert Keyword.equal?(opts1, opts2)
|
||||
assert tail1 == tail2
|
||||
assert options == { [source: "from_docs/", verbose: true], ["test/enum_test.exs"] }
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
Code.require_file "../test_helper.exs", __FILE__
|
||||
|
||||
defprotocol ProtocolTest.WithAll do
|
||||
@type t :: any
|
||||
@doc "Blank"
|
||||
def blank(thing)
|
||||
end
|
||||
|
||||
defprotocol ProtocolTest.WithExcept do
|
||||
@except [Atom, Number, List]
|
||||
@spec blank(t), do: boolean
|
||||
def blank(thing)
|
||||
end
|
||||
|
||||
@@ -140,6 +142,19 @@ defmodule ProtocolTest do
|
||||
assert catch_error(ProtocolTest.Multi.test("a")) == :undef
|
||||
end
|
||||
|
||||
test :protocol_callback do
|
||||
assert get_callbacks(ProtocolTest.WithOnly, :blank, 1) ==
|
||||
[{:type,17,:fun,[{:type,17,:product,[{:type,17,:t,[]}]},{:type,17,:term,[]}]}]
|
||||
|
||||
assert get_callbacks(ProtocolTest.WithExcept, :blank, 1) ==
|
||||
[{:type,11,:fun,[{:type,11,:product,[{:type,11,:t,[]}]},{:type,11,:boolean,[]}]}]
|
||||
end
|
||||
|
||||
defp get_callbacks(module, name, arity) do
|
||||
callbacks = lc { :callback, info } inlist module.__info__(:attributes), do: hd(info)
|
||||
List.keyfind(callbacks, { name, arity }, 0) /> elem(1)
|
||||
end
|
||||
|
||||
# Assert that the given protocol is going to be dispatched.
|
||||
defp assert_protocol_for(target, impl, thing) do
|
||||
joined = Module.concat(target, impl)
|
||||
|
||||
@@ -3,6 +3,9 @@ Code.require_file "../test_helper.exs", __FILE__
|
||||
defrecord RecordTest.FileInfo,
|
||||
Record.extract(:file_info, from_lib: "kernel/include/file.hrl")
|
||||
|
||||
defrecord RecordTest.SomeRecord, a: 0, b: 1
|
||||
defrecord RecordTest.WithNoField, []
|
||||
|
||||
name = RecordTest.DynamicName
|
||||
defrecord name, a: 0, b: 1 do
|
||||
def get_a(RecordTest.DynamicName[a: a]) do
|
||||
@@ -10,12 +13,22 @@ defrecord name, a: 0, b: 1 do
|
||||
end
|
||||
end
|
||||
|
||||
defrecord RecordTest.SomeRecord, a: 0, b: 1
|
||||
## With types
|
||||
|
||||
defrecord RecordTest.WithNoField, []
|
||||
defrecord RecordTest.WithTypeOverriden, a: 0, b: 1 do
|
||||
@type t :: __MODULE__[a: integer, b: any]
|
||||
end
|
||||
|
||||
defrecord RecordTest.WithRecordType, a: 0, b: 1 do
|
||||
record_type a: integer
|
||||
end
|
||||
|
||||
defmodule RecordTest.Macros do
|
||||
Record.defmacros :_user, [:name, :age], __ENV__
|
||||
defrecordp :_user, name: "José", age: 25
|
||||
|
||||
def new() do
|
||||
_user()
|
||||
end
|
||||
|
||||
def new(name, age) do
|
||||
_user(name: name, age: age)
|
||||
@@ -58,7 +71,7 @@ defmodule RecordTest do
|
||||
test :record_constructor_with_dict do
|
||||
record = RecordTest.FileInfo.new(type: :regular)
|
||||
assert record.type == :regular
|
||||
assert record.access == nil
|
||||
assert record.access == :undefined
|
||||
end
|
||||
|
||||
test :record_accessors do
|
||||
@@ -115,6 +128,9 @@ defmodule RecordTest do
|
||||
end
|
||||
|
||||
test :record_macros do
|
||||
record = RecordTest.Macros.new
|
||||
assert record.name == "José"
|
||||
|
||||
record = RecordTest.Macros.new("Foo", 25)
|
||||
assert record.name == "Foo"
|
||||
|
||||
|
||||
@@ -42,6 +42,7 @@ defmodule StringTest do
|
||||
end
|
||||
|
||||
test :rstrip do
|
||||
assert String.rstrip("") == ""
|
||||
assert String.rstrip(" abc ") == " abc"
|
||||
assert String.rstrip(" abc a") == " abc a"
|
||||
assert String.rstrip(" abc aa", ?a) == " abc "
|
||||
@@ -49,12 +50,14 @@ defmodule StringTest do
|
||||
end
|
||||
|
||||
test :lstrip do
|
||||
assert String.lstrip("") == ""
|
||||
assert String.lstrip(" abc ") == "abc "
|
||||
assert String.lstrip("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("___ abc ___", ?_) == " abc "
|
||||
end
|
||||
@@ -97,6 +100,15 @@ defmodule StringTest do
|
||||
assert String.codepoints("ϖͲϥЫݎߟΈټϘለДШव׆ש؇؊صلټܗݎޥޘ߉ऌ૫ሏᶆ℆ℙℱ ⅚Ⅷ↠∈⌘①ffi") == ["ϖ","Ͳ","ϥ","Ы","ݎ","ߟ","Έ","ټ","Ϙ","ለ","Д","Ш","व","׆","ש","؇","؊","ص","ل","ټ","ܗ","ݎ","ޥ","ޘ","߉","ऌ","૫","ሏ","ᶆ","℆","ℙ","ℱ"," ","⅚","Ⅷ","↠","∈","⌘","①","ffi"]
|
||||
end
|
||||
|
||||
test :graphemes do
|
||||
assert String.graphemes("Ā̀stute") == ["Ā̀","s","t","u","t","e"]
|
||||
end
|
||||
|
||||
test :next_grapheme do
|
||||
assert String.next_grapheme("Ā̀stute") == {"Ā̀","stute"}
|
||||
assert String.next_grapheme("") == :no_grapheme
|
||||
end
|
||||
|
||||
test :first do
|
||||
assert String.first("elixir") == "e"
|
||||
assert String.first("íelixr") == "í"
|
||||
@@ -105,6 +117,7 @@ defmodule StringTest do
|
||||
assert String.first("ελιξήριο") == "ε"
|
||||
assert String.first("סם חיים") == "ס"
|
||||
assert String.first("がガちゃ") == "が"
|
||||
assert String.first("Ā̀stute") == "Ā̀"
|
||||
assert String.first("") == ""
|
||||
end
|
||||
|
||||
@@ -117,6 +130,7 @@ defmodule StringTest do
|
||||
assert String.last("סם ייםח") == "ח"
|
||||
assert String.last("がガちゃ") == "ゃ"
|
||||
assert String.last("") == ""
|
||||
assert String.last("Ā̀") == "Ā̀"
|
||||
end
|
||||
|
||||
test :length do
|
||||
@@ -127,6 +141,7 @@ defmodule StringTest do
|
||||
assert String.length("ειξήριολ") == 8
|
||||
assert String.length("סם ייםח") == 7
|
||||
assert String.length("がガちゃ") == 4
|
||||
assert String.length("Ā̀stute") == 6
|
||||
assert String.length("") == 0
|
||||
end
|
||||
|
||||
@@ -134,10 +149,12 @@ defmodule StringTest do
|
||||
assert String.at("л", 0) == "л"
|
||||
assert String.at("elixir", 1) == "l"
|
||||
assert String.at("がガちゃ", 2) == "ち"
|
||||
assert String.at("л", 10) == ""
|
||||
assert String.at("л", 10) == nil
|
||||
assert String.at("elixir", -1) == "r"
|
||||
assert String.at("がガちゃ", -2) == "ち"
|
||||
assert String.at("л", -3) == ""
|
||||
assert String.at("Ā̀stute", 1) == "s"
|
||||
assert String.at("elixir",6) == nil
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@@ -6,7 +6,7 @@ defmodule Typespec.Test.Type do
|
||||
# This macro allows us to focus on the result of the
|
||||
# definition and not on the hassles of handling test
|
||||
# module
|
||||
defmacro test_module([{:do, block}]) do
|
||||
defmacrop test_module([{:do, block}]) do
|
||||
quote do
|
||||
{ :module, T, _binary, result } = defmodule T do
|
||||
unquote(block)
|
||||
@@ -21,42 +21,42 @@ defmodule Typespec.Test.Type do
|
||||
spec = test_module do
|
||||
@type mytype
|
||||
end
|
||||
assert {:type,{:mytype,{:type,_,:term,[]},[]}} = spec
|
||||
assert {:mytype,{:type,_,:term,[]},[]} = spec
|
||||
end
|
||||
|
||||
test "@type with a single type" do
|
||||
spec = test_module do
|
||||
@type mytype :: term
|
||||
end
|
||||
assert {:type,{:mytype,{:type,_,:term,[]},[]}} = spec
|
||||
assert {:mytype,{:type,_,:term,[]},[]} = spec
|
||||
end
|
||||
|
||||
test "@type with an atom" do
|
||||
spec = test_module do
|
||||
@type mytype :: :atom
|
||||
end
|
||||
assert {:type,{:mytype,{:atom,_,:atom},[]}} = spec
|
||||
assert {:mytype,{:atom,_,:atom},[]} = spec
|
||||
end
|
||||
|
||||
test "@type with an atom alias" do
|
||||
spec = test_module do
|
||||
@type mytype :: Atom
|
||||
end
|
||||
assert {:type,{:mytype,{:atom,_,Atom},[]}} = spec
|
||||
assert {:mytype,{:atom,_,Atom},[]} = spec
|
||||
end
|
||||
|
||||
test "@type with an integer" do
|
||||
spec = test_module do
|
||||
@type mytype :: 10
|
||||
end
|
||||
assert {:type,{:mytype,{:integer,_,10},[]}} = spec
|
||||
assert {:mytype,{:integer,_,10},[]} = spec
|
||||
end
|
||||
|
||||
test "@type with a negative integer" do
|
||||
spec = test_module do
|
||||
@type mytype :: -10
|
||||
end
|
||||
assert {:type,{:mytype,{:op, _, :-, {:integer,_,10}},[]}} = spec
|
||||
assert {:mytype,{:op, _, :-, {:integer,_,10}},[]} = spec
|
||||
end
|
||||
|
||||
test "@type with a remote type" do
|
||||
@@ -65,50 +65,50 @@ defmodule Typespec.Test.Type do
|
||||
t2 = @type mytype_arg :: Remote.type(integer)
|
||||
{t1, t2}
|
||||
end
|
||||
assert {:type,{:mytype,{:remote_type, _, [{:atom, _, Remote.Some},{:atom, _, :type}, []]},[]}} = spec1
|
||||
assert {:type,{:mytype_arg,{:remote_type, _, [{:atom, _, Remote},{:atom, _, :type},[{:type,_, :integer,[]}]]},[]}} = spec2
|
||||
assert {:mytype,{:remote_type, _, [{:atom, _, Remote.Some},{:atom, _, :type}, []]},[]} = spec1
|
||||
assert {:mytype_arg,{:remote_type, _, [{:atom, _, Remote},{:atom, _, :type},[{:type,_, :integer,[]}]]},[]} = spec2
|
||||
end
|
||||
|
||||
test "@type with a binary" do
|
||||
spec = test_module do
|
||||
@type mytype :: binary
|
||||
end
|
||||
assert {:type,{:mytype,{:type,_,:binary, []},[]}} = spec
|
||||
assert {:mytype,{:type,_,:binary, []},[]} = spec
|
||||
end
|
||||
|
||||
test "@type with an empty binary" do
|
||||
spec = test_module do
|
||||
@type mytype :: <<>>
|
||||
end
|
||||
assert {:type,{:mytype,{:type,_,:binary, [{:integer, _, 0},{:integer, _, 0}]},[]}} = spec
|
||||
assert {:mytype,{:type,_,:binary, [{:integer, _, 0},{:integer, _, 0}]},[]} = spec
|
||||
end
|
||||
|
||||
test "@type with a binary with a base size" do
|
||||
spec = test_module do
|
||||
@type mytype :: <<_|3>>
|
||||
@type mytype :: <<_ :: 3>>
|
||||
end
|
||||
assert {:type,{:mytype,{:type,_,:binary, [{:integer, _, 3},{:integer, _, 0}]},[]}} = spec
|
||||
assert {:mytype,{:type,_,:binary, [{:integer, _, 3},{:integer, _, 0}]},[]} = spec
|
||||
end
|
||||
|
||||
test "@type with a binary with a unit size" do
|
||||
spec = test_module do
|
||||
@type mytype :: <<_|_ * 8>>
|
||||
@type mytype :: <<_ :: _ * 8>>
|
||||
end
|
||||
assert {:type,{:mytype,{:type,_,:binary, [{:integer, _, 0},{:integer, _, 8}]},[]}} = spec
|
||||
assert {:mytype,{:type,_,:binary, [{:integer, _, 0},{:integer, _, 8}]},[]} = spec
|
||||
end
|
||||
|
||||
test "@type with a range" do
|
||||
spec = test_module do
|
||||
@type mytype :: range(1,10)
|
||||
end
|
||||
assert {:type,{:mytype,{:type,_,:range, [{:integer, _, 1},{:integer, _, 10}]},[]}} = spec
|
||||
assert {:mytype,{:type,_,:range, [{:integer, _, 1},{:integer, _, 10}]},[]} = spec
|
||||
end
|
||||
|
||||
test "@type with a range op" do
|
||||
spec = test_module do
|
||||
@type mytype :: 1..10
|
||||
end
|
||||
assert {:type,{:mytype,{:type,_,:range, [{:integer, _, 1},{:integer, _, 10}]},[]}} = spec
|
||||
assert {:mytype,{:type,_,:range, [{:integer, _, 1},{:integer, _, 10}]},[]} = spec
|
||||
end
|
||||
|
||||
test "@type with a tuple" do
|
||||
@@ -117,50 +117,58 @@ defmodule Typespec.Test.Type do
|
||||
t2 = @type mytype1 :: {}
|
||||
{t1, t2}
|
||||
end
|
||||
assert {:type,{:mytype,{:type,_,:tuple, :any},[]}} = spec1
|
||||
assert {:type,{:mytype1,{:type,_,:tuple, :any},[]}} = spec2
|
||||
assert {:mytype,{:type,_,:tuple, :any},[]} = spec1
|
||||
assert {:mytype1,{:type,_,:tuple, :any},[]} = spec2
|
||||
end
|
||||
|
||||
test "@type with a fun" do
|
||||
spec = test_module do
|
||||
@type mytype :: fun
|
||||
end
|
||||
assert {:type,{:mytype,{:type,_,:fun, []},[]}} = spec
|
||||
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)
|
||||
end
|
||||
assert {:type,{:mytype,{:type,_,:fun, [{:type, _, :product,
|
||||
[{:type, _, :integer, []}, {:type, _, :integer, []}]},
|
||||
{:type, _, :integer, []}]},[]}} = spec
|
||||
assert {:mytype,{:type,_,:fun, [{:type, _, :product,
|
||||
[{:type, _, :integer, []}, {:type, _, :integer, []}]},
|
||||
{:type, _, :integer, []}]},[]} = spec
|
||||
end
|
||||
|
||||
test "@type with a fun with no arguments and return type" do
|
||||
spec = test_module do
|
||||
@type mytype :: fun(do: integer)
|
||||
end
|
||||
assert {:type,{:mytype,{:type,_,:fun, [{:type, _, :product, []},
|
||||
{:type, _, :integer, []}]}, []}} = spec
|
||||
assert {:mytype,{:type,_,:fun, [{:type, _, :product, []},
|
||||
{:type, _, :integer, []}]}, []} = spec
|
||||
end
|
||||
|
||||
test "@type with a fun with any arity and return type" do
|
||||
spec = test_module do
|
||||
@type mytype :: fun(..., do: integer)
|
||||
end
|
||||
assert {:type,{:mytype,{:type,_,:fun, [{:type, _, :any},
|
||||
{:type, _, :integer, []}]}, []}} = spec
|
||||
assert {:mytype,{:type,_,:fun, [{:type, _, :any},
|
||||
{:type, _, :integer, []}]}, []} = spec
|
||||
end
|
||||
|
||||
test "@type with a union" do
|
||||
spec = test_module do
|
||||
@type mytype :: integer | string
|
||||
@type mytype :: integer | char_list
|
||||
| atom
|
||||
end
|
||||
assert {:type,{:mytype,{:type,_,:union, [{:type, _, :integer, []},
|
||||
{:type, _, :string, []},
|
||||
{:type, _, :atom, []}]}, []}} = spec
|
||||
assert {:mytype,{:type,_,:union, [{:type, _, :integer, []},
|
||||
{:remote_type, _, [{:atom, _, :elixir},{:atom, _, :char_list}, []]},
|
||||
{:type, _, :atom, []}]}, []} = spec
|
||||
end
|
||||
|
||||
test "@type with an access macro" do
|
||||
spec = test_module do
|
||||
@type mytype :: Range[first: integer]
|
||||
end
|
||||
assert {:mytype,{:type, _, :tuple,
|
||||
[{:atom, _, Range}, {:type, _, :integer, []}, {:type, _, :any, []}]}, []} = spec
|
||||
end
|
||||
|
||||
test "@type with parameters" do
|
||||
@@ -170,9 +178,9 @@ defmodule Typespec.Test.Type do
|
||||
t3 = @type mytype2(x,y) :: {x,y}
|
||||
{t1,t2,t3}
|
||||
end
|
||||
assert {:type,{:mytype,{:var,_,:x},[{:var,_,:x}]}} = spec1
|
||||
assert {:type,{:mytype1,{:type,_,:list,[{:var,_,:x}]},[{:var,_,:x}]}} = spec2
|
||||
assert {:type,{:mytype2,{:type,_,:tuple,[{:var,_,:x},{:var,_,:y}]},[{:var,_,:x},{:var, _, :y}]}} = spec3
|
||||
assert {:mytype,{:var,_,:x},[{:var,_,:x}]} = spec1
|
||||
assert {:mytype1,{:type,_,:list,[{:var,_,:x}]},[{:var,_,:x}]} = spec2
|
||||
assert {:mytype2,{:type,_,:tuple,[{:var,_,:x},{:var,_,:y}]},[{:var,_,:x},{:var, _, :y}]} = spec3
|
||||
end
|
||||
|
||||
test "@type with annotations" do
|
||||
@@ -181,28 +189,37 @@ defmodule Typespec.Test.Type do
|
||||
t2 = @type mytype1 :: fun(a :: integer, do: integer)
|
||||
{t1,t2}
|
||||
end
|
||||
assert {:type, {:mytype, {:ann_type, _, [{:var, _, :named}, {:type, _, :integer, []}]}, []}} = spec1
|
||||
assert {:type, {:mytype1,{:type,_,:fun,[{:type,_,:product,[{:ann_type,_,[{:var,_,:a},{:type,_,:integer,[]}]}]},{:type,_,:integer,[]}]},[]}} = spec2
|
||||
assert {:mytype, {:ann_type, _, [{:var, _, :named}, {:type, _, :integer, []}]}, []} = spec1
|
||||
assert {:mytype1,{:type,_,:fun,[{:type,_,:product,[{:ann_type,_,[{:var,_,:a},{:type,_,:integer,[]}]}]},{:type,_,:integer,[]}]},[]} = spec2
|
||||
end
|
||||
|
||||
test "opaque @type" do
|
||||
test "@opaque(type)" do
|
||||
spec = test_module do
|
||||
@type mytype(x) :: x, opaque: true
|
||||
@opaque mytype(x) :: x
|
||||
end
|
||||
assert {:opaque,{:mytype,{:var,_,:x},[{:var,_,:x}]}} = spec
|
||||
assert {:mytype,{:var,_,:x},[{:var,_,:x}]} = spec
|
||||
end
|
||||
|
||||
test "get_types" do
|
||||
test "@type + opaque" do
|
||||
types = test_module do
|
||||
import Kernel.Typespec
|
||||
@type mytype :: tuple
|
||||
@type mytype1 :: {}, opaque: true
|
||||
get_types(__MODULE__)
|
||||
@opaque mytype1 :: {}
|
||||
@type ++ @opaque
|
||||
end
|
||||
assert [{:mytype,_,[]},{:mytype1,_,[]}] = types
|
||||
end
|
||||
|
||||
test "@spec" do
|
||||
test "defines_type?" do
|
||||
test_module do
|
||||
@type mytype :: tuple
|
||||
@type mytype(a) :: [a]
|
||||
assert Kernel.Typespec.defines_type?(__MODULE__, :mytype, 0)
|
||||
assert Kernel.Typespec.defines_type?(__MODULE__, :mytype, 1)
|
||||
refute Kernel.Typespec.defines_type?(__MODULE__, :mytype, 2)
|
||||
end
|
||||
end
|
||||
|
||||
test "@spec(spec)" do
|
||||
{spec1, spec2, spec3} = test_module do
|
||||
def myfun(x), do: x
|
||||
def myfun(), do: :ok
|
||||
@@ -212,40 +229,147 @@ defmodule Typespec.Test.Type do
|
||||
t3 = @spec myfun(integer, integer), do: {integer, integer}
|
||||
{t1,t2,t3}
|
||||
end
|
||||
assert {{:spec, {:myfun,1}},[{:type,_,:fun,[{:type,_,:product,[{:type,_,:integer,[]}]},{:type,_,:integer,[]}]}]} = spec1
|
||||
assert {{:spec, {:myfun,0}},[{:type,_,:fun,[{:type,_,:product,[]},{:type,_,:integer,[]}]}]} = spec2
|
||||
assert {{:spec, {:myfun,2}},[{:type,_,:fun,[{:type,_,:product,[{:type,_,:integer,[]},{:type,_,:integer,[]}]},{:type,_,:tuple,[{:type,_,:integer,[]},{:type,_,:integer,[]}]}]}]} = spec3
|
||||
assert {{:myfun,1},{:type,_,:fun,[{:type,_,:product,[{:type,_,:integer,[]}]},{:type,_,:integer,[]}]}} = spec1
|
||||
assert {{:myfun,0},{:type,_,:fun,[{:type,_,:product,[]},{:type,_,:integer,[]}]}} = spec2
|
||||
assert {{:myfun,2},{:type,_,:fun,[{:type,_,:product,[{:type,_,:integer,[]},{:type,_,:integer,[]}]},{:type,_,:tuple,[{:type,_,:integer,[]},{:type,_,:integer,[]}]}]}} = spec3
|
||||
end
|
||||
|
||||
test "@callback" do
|
||||
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,t2,t3}
|
||||
end
|
||||
assert {{:callback, {:myfun,1}},[{:type,_,:fun,[{:type,_,:product,[{:type,_,:integer,[]}]},{:type,_,:integer,[]}]}]} = spec1
|
||||
assert {{:callback, {:myfun,0}},[{:type,_,:fun,[{:type,_,:product,[]},{:type,_,:integer,[]}]}]} = spec2
|
||||
assert {{:callback, {:myfun,2}},[{:type,_,:fun,[{:type,_,:product,[{:type,_,:integer,[]},{:type,_,:integer,[]}]},{:type,_,:tuple,[{:type,_,:integer,[]},{:type,_,:integer,[]}]}]}]} = spec3
|
||||
assert {{:myfun,1},{:type,_,:fun,[{:type,_,:product,[{:type,_,:integer,[]}]},{:type,_,:integer,[]}]}} = spec1
|
||||
assert {{:myfun,0},{:type,_,:fun,[{:type,_,:product,[]},{:type,_,:integer,[]}]}} = spec2
|
||||
assert {{:myfun,2},{:type,_,:fun,[{:type,_,:product,[{:type,_,:integer,[]},{:type,_,:integer,[]}]},{:type,_,:tuple,[{:type,_,:integer,[]},{:type,_,:integer,[]}]}]}} = spec3
|
||||
end
|
||||
|
||||
test "get_specs" do
|
||||
specs = test_module do
|
||||
import Kernel.Typespec
|
||||
test "@spec + @callback" do
|
||||
{ specs, callbacks } = test_module do
|
||||
def myfun(x), do: x
|
||||
@spec myfun(integer), do: integer
|
||||
@spec myfun(string), do: string
|
||||
@spec myfun(char_list), do: char_list
|
||||
@callback cb(integer), do: integer
|
||||
get_specs(__MODULE__)
|
||||
{ @spec, @callback }
|
||||
end
|
||||
|
||||
assert [
|
||||
{{:callback, {:cb, 1}},[
|
||||
{:type,_,:fun,[{:type,_,:product,[{:type,_,:integer,[]}]},{:type,_,:integer,[]}]}
|
||||
]},
|
||||
{{:spec, {:myfun,1}},[
|
||||
{:type,_,:fun,[{:type,_,:product,[{:type,_,:integer,[]}]},{:type,_,:integer,[]}]},
|
||||
{:type,_,:fun,[{:type,_,:product,[{:type,_,:string,[]}]},{:type,_,:string,[]}]}]}
|
||||
] = List.sort(specs)
|
||||
{ {:cb, 1}, {:type,_,:fun,[{:type,_,:product,[{:type,_,:integer,[]}]},{:type,_,:integer,[]}]} }
|
||||
] = List.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)
|
||||
end
|
||||
|
||||
# Conversion to AST
|
||||
|
||||
test "type_to_ast" do
|
||||
quoted = [
|
||||
(quote do: @type with_ann() :: (t :: atom())),
|
||||
(quote do: @type empty_tuple_type() :: {}),
|
||||
(quote do: @type imm_type_1() :: 1),
|
||||
(quote do: @type imm_type_2() :: :atom),
|
||||
(quote do: @type simple_type() :: integer()),
|
||||
(quote do: @type param_type(p) :: list(p)),
|
||||
(quote do: @type union_type() :: integer() | binary() | boolean()),
|
||||
(quote do: @type binary_type1() :: <<_ :: _ * 8>>),
|
||||
(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())),
|
||||
]
|
||||
|
||||
types = test_module do
|
||||
Module.eval_quoted __MODULE__, quote do: unquote_splicing(quoted)
|
||||
@type ++ @opaque
|
||||
end
|
||||
|
||||
types = Enum.reverse(types)
|
||||
|
||||
lc { type, definition } inlist Enum.zip(types, quoted) do
|
||||
ast = Kernel.Typespec.type_to_ast(type)
|
||||
assert Macro.to_binary(quote do: @type unquote(ast)) == Macro.to_binary(definition)
|
||||
end
|
||||
end
|
||||
|
||||
test "type_to_ast for records" do
|
||||
record_type = { { :record, :my_record },
|
||||
[
|
||||
{ :typed_record_field,
|
||||
{ :record_field, 0, { :atom, 0, :field1 }},
|
||||
{ :type, 0, :atom, [] } },
|
||||
{ :typed_record_field,
|
||||
{ :record_field, 0, { :atom, 0, :field2 }},
|
||||
{ :type, 0, :integer, [] } },
|
||||
],
|
||||
[]}
|
||||
assert Kernel.Typespec.type_to_ast(record_type) == quote hygiene: false, do: my_record() :: {:my_record, field1 :: atom(), field2 :: integer() }
|
||||
end
|
||||
|
||||
test "spec_to_ast" do
|
||||
specs = [
|
||||
(quote do: @spec a(), do: integer()),
|
||||
(quote do: @spec a(atom()), do: integer()),
|
||||
]
|
||||
|
||||
compiled = test_module do
|
||||
def a, do: 1
|
||||
def a(a), do: a
|
||||
Module.eval_quoted __MODULE__, quote do: unquote_splicing(specs)
|
||||
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)
|
||||
assert Macro.to_binary(quoted) == Macro.to_binary(definition)
|
||||
end
|
||||
end
|
||||
|
||||
# types/specs retrieval
|
||||
|
||||
test "specs retrieval" do
|
||||
Code.compiler_options debug_info: true
|
||||
|
||||
{ :module, T, binary, _ } = defmodule T do
|
||||
@spec a, do: any
|
||||
def a, do: nil
|
||||
end
|
||||
|
||||
:code.delete(T)
|
||||
:code.purge(T)
|
||||
|
||||
assert [{{:a,_},[{:type,_,:fun,[{:type,_,:product,[]},{:type,_,:any,[]}]}]}] = Kernel.Typespec.beam_specs(binary)
|
||||
after
|
||||
Code.compiler_options debug_info: false
|
||||
end
|
||||
|
||||
test "types retrieval" do
|
||||
Code.compiler_options debug_info: true
|
||||
|
||||
{ :module, T, binary, _ } = defmodule T do
|
||||
@type a :: any
|
||||
@typep b :: any
|
||||
@spec t(b), do: b
|
||||
def t(b), do: b
|
||||
@opaque c :: any
|
||||
end
|
||||
|
||||
:code.delete(T)
|
||||
:code.purge(T)
|
||||
|
||||
assert [
|
||||
{:opaque, {:c,{:type,_,:any,[]},[]}},
|
||||
{:type, {:a,{:type,_,:any,[]},[]}},
|
||||
{:typep, {:b,{:type,_,:any,[]},[]}},
|
||||
] = Kernel.Typespec.beam_types(binary)
|
||||
after
|
||||
Code.compiler_options debug_info: false
|
||||
end
|
||||
end
|
||||
@@ -1,3 +1,5 @@
|
||||
Code.require_file "../test_helper.exs", __FILE__
|
||||
|
||||
defmodule URITest do
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
|
||||
@@ -35,6 +35,22 @@ function_apply_with_args_test() ->
|
||||
function_apply_and_clojure_test() ->
|
||||
{3,_} = eval("b = 1; a = fn -> b + 2 end; apply a, []").
|
||||
|
||||
function_parens_test() ->
|
||||
{0,_} = eval("(fn() -> 0 end).()"),
|
||||
{1,_} = eval("(fn(1) -> 1 end).(1)"),
|
||||
{3,_} = eval("(fn(1, 2) -> 3 end).(1, 2)"),
|
||||
|
||||
{0,_} = eval("(fn () -> 0 end).()"),
|
||||
{1,_} = eval("(fn (1) -> 1 end).(1)"),
|
||||
{3,_} = eval("(fn (1, 2) -> 3 end).(1, 2)").
|
||||
|
||||
function_macro_parens_test() ->
|
||||
{0,_} = eval("(function do () -> 0 end).()"),
|
||||
{1,_} = eval("(function do 1 -> 1 end).(1)"),
|
||||
{3,_} = eval("(function do 1, 2 -> 3 end).(1, 2)"),
|
||||
{1,_} = eval("(function do (1) -> 1 end).(1)"),
|
||||
{3,_} = eval("(function do (1, 2) -> 3 end).(1, 2)").
|
||||
|
||||
%% Function calls
|
||||
|
||||
function_call_test() ->
|
||||
|
||||
@@ -29,8 +29,8 @@ run_and_remove(Fun, Modules) ->
|
||||
|
||||
% Throws an error with the Erlang Abstract Form from the Elixir string
|
||||
throw_elixir(String) ->
|
||||
Forms = elixir_translator:'forms!'(String, 1, "nofile"),
|
||||
Tree = elixir_translator:translate(Forms, #elixir_scope{}),
|
||||
Forms = elixir_translator:'forms!'(String, 1, "nofile", []),
|
||||
Tree = elixir_translator:translate(Forms, elixir:scope_for_eval([])),
|
||||
erlang:error(io:format("~p~n", [Tree])).
|
||||
|
||||
% Throws an error with the Erlang Abstract Form from the Erlang string
|
||||
|
||||
@@ -79,6 +79,11 @@ dot_test() ->
|
||||
{'.',1},
|
||||
{identifier,1,baz}] = tokenize("foo.bar.baz").
|
||||
|
||||
dot_keyword_test() ->
|
||||
[{identifier,1,foo},
|
||||
{'.',1},
|
||||
{identifier,1,do}] = tokenize("foo.do").
|
||||
|
||||
newline_test() ->
|
||||
[{identifier,1,foo},
|
||||
{'.',2},
|
||||
|
||||
@@ -8,7 +8,7 @@ eval(Forms) ->
|
||||
Result.
|
||||
|
||||
eval(Forms, Binding) ->
|
||||
{ Transformed, _ } = elixir_translator:translate(Forms, #elixir_scope{}),
|
||||
{ Transformed, _ } = elixir_translator:translate(Forms, elixir:scope_for_eval([])),
|
||||
{ value, Result, NewBinding } = erl_eval:exprs(Transformed, Binding),
|
||||
{ Result, NewBinding }.
|
||||
|
||||
|
||||
@@ -43,8 +43,26 @@ defmodule ExUnit do
|
||||
Start ExUnit. Required to be invoked before loading
|
||||
any file that uses ExUnit.Case. Check `configure/1`
|
||||
to see the supported options.
|
||||
|
||||
This function will also try to read a user config from the following
|
||||
locations, in this order:
|
||||
|
||||
* $EXUNIT_CONFIG environment variable
|
||||
* $HOME/.ex_unit.exs
|
||||
|
||||
If none found, no user config will be read.
|
||||
|
||||
User config is an elixir file which should return a keyword list
|
||||
with ex_unit options. Please note that explicit options passed to start/1
|
||||
will take precedence over user options.
|
||||
|
||||
# User config example (~/.ex_unit.exs)
|
||||
|
||||
[formatter: ExUnit.Formatter.ANSI]
|
||||
|
||||
"""
|
||||
def start(options // []) do
|
||||
options = Keyword.merge(user_options, options)
|
||||
ExUnit.Server.start_link
|
||||
configure(options)
|
||||
System.at_exit fn status ->
|
||||
@@ -52,6 +70,21 @@ defmodule ExUnit do
|
||||
end
|
||||
end
|
||||
|
||||
@doc false
|
||||
def user_options(user_config // nil) do
|
||||
user_config = user_config ||
|
||||
System.get_env("EXUNIT_CONFIG") ||
|
||||
File.join(System.get_env("HOME"), ".ex_unit.exs")
|
||||
|
||||
case File.read(user_config) do
|
||||
{ :ok, contents } ->
|
||||
{ config, _ } = Code.eval(contents, [], file: user_config)
|
||||
config
|
||||
_ ->
|
||||
[]
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Configure ExUnit.
|
||||
|
||||
@@ -80,9 +113,8 @@ defmodule ExUnit do
|
||||
need to call it directly.
|
||||
"""
|
||||
def run do
|
||||
config = ExUnit.Runner.Config.new ExUnit.Server.options
|
||||
config = config.formatter(config.formatter.start)
|
||||
failures = ExUnit.Runner.loop config
|
||||
if failures > 0, do: halt(1), else: halt(0)
|
||||
config = ExUnit.Runner.Config.new ExUnit.Server.options
|
||||
failures = ExUnit.Runner.run config
|
||||
if failures > 0, do: System.halt(1), else: System.halt(0)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -69,15 +69,8 @@ defmodule ExUnit.Assertions do
|
||||
quote do: (var!(op) == :! or var!(op) == :not)
|
||||
end
|
||||
|
||||
defp translate_assertion({ :=, _, [expected, received] }, _else) do
|
||||
quote do
|
||||
try do
|
||||
unquote(expected) = unquote(received)
|
||||
rescue
|
||||
x in [MatchError] ->
|
||||
raise ExUnit.AssertionError, message: x.message
|
||||
end
|
||||
end
|
||||
defp translate_assertion({ :=, _, [_, _] } = expr, _else) do
|
||||
expr
|
||||
end
|
||||
|
||||
defp translate_assertion({ :==, _, [left, right] }, _else) do
|
||||
@@ -132,7 +125,7 @@ defmodule ExUnit.Assertions do
|
||||
unquote(expected) = x = unquote(received)
|
||||
flunk "Unexpected right side #{inspect x} match"
|
||||
rescue
|
||||
x in [MatchError] -> true
|
||||
_ in [MatchError] -> true
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -387,6 +380,8 @@ defmodule ExUnit.Assertions do
|
||||
flunk "This should raise an error"
|
||||
|
||||
"""
|
||||
@spec flunk, do: no_return
|
||||
@spec flunk(String.t), do: no_return
|
||||
def flunk(message // "Epic Fail!") do
|
||||
raise ExUnit.AssertionError, message: message
|
||||
end
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
defmodule ExUnit.CLIFormatter do
|
||||
@moduledoc """
|
||||
Formatter responsible for printing raw text
|
||||
on the CLI
|
||||
"""
|
||||
|
||||
@behaviour ExUnit.Formatter
|
||||
use GenServer.Behaviour
|
||||
|
||||
import Exception, only: [format_stacktrace: 1]
|
||||
defrecord Config, counter: 0, failures: []
|
||||
|
||||
## Behaviour
|
||||
|
||||
def suite_started do
|
||||
:gen_server.start_link({ :local, __MODULE__ }, __MODULE__, [], [])
|
||||
end
|
||||
|
||||
def suite_finished do
|
||||
:gen_server.call(__MODULE__, :suite_finished)
|
||||
end
|
||||
|
||||
def case_started(_) do
|
||||
:ok
|
||||
end
|
||||
|
||||
def case_finished(_) do
|
||||
:ok
|
||||
end
|
||||
|
||||
def test_started(_test_case, _test) do
|
||||
:ok
|
||||
end
|
||||
|
||||
def test_finished(test_case, test, result) do
|
||||
:gen_server.cast(__MODULE__, { :test_finished, test_case, test, result })
|
||||
end
|
||||
|
||||
## Callbacks
|
||||
|
||||
def init(_args) do
|
||||
{ :ok, Config.new }
|
||||
end
|
||||
|
||||
def handle_call(:suite_finished, _from, config) do
|
||||
IO.write "\n\n"
|
||||
Enum.reduce Enum.reverse(config.failures), 1, print_failure(&1, &2)
|
||||
failures_count = length(config.failures)
|
||||
IO.puts "#{config.counter} tests, #{failures_count} failures."
|
||||
{ :stop, :normal, failures_count, config }
|
||||
end
|
||||
|
||||
def handle_call(_, _, _) do
|
||||
super
|
||||
end
|
||||
|
||||
def handle_cast({ :test_finished, _test_case, _test, nil }, config) do
|
||||
IO.write "."
|
||||
{ :noreply, config.increment_counter }
|
||||
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}]) }
|
||||
end
|
||||
|
||||
def handle_cast(_, _) do
|
||||
super
|
||||
end
|
||||
|
||||
defp print_failure({test_case, test, { kind, reason, stacktrace }}, acc) do
|
||||
IO.puts "#{acc}) #{test} (#{inspect test_case})"
|
||||
IO.puts " ** #{format_catch(kind, reason)}\n stacktrace:"
|
||||
Enum.each filter_stacktrace(stacktrace), fn(s) -> IO.puts " #{format_stacktrace(s)}" end
|
||||
IO.write "\n"
|
||||
acc + 1
|
||||
end
|
||||
|
||||
defp format_catch(:error, exception) do
|
||||
"(#{inspect exception.__record__(:name)}) #{exception.message}"
|
||||
end
|
||||
|
||||
defp format_catch(kind, reason) do
|
||||
"(#{kind}) #{inspect(reason)}"
|
||||
end
|
||||
|
||||
defp filter_stacktrace([{ ExUnit.Assertions, _, _, _ }|t]), do: filter_stacktrace(t)
|
||||
defp filter_stacktrace([h|t]), do: [h|filter_stacktrace(t)]
|
||||
defp filter_stacktrace([]), do: []
|
||||
end
|
||||
@@ -1,63 +1,21 @@
|
||||
defmodule ExUnit.Formatter do
|
||||
# TODO: Eventually we need to come up with a
|
||||
# public API for the formatter to allow customization
|
||||
@moduledoc false
|
||||
@moduledoc """
|
||||
This module simply defines the callbacks
|
||||
expected by an ExUnit.Formatter.
|
||||
"""
|
||||
|
||||
use GenServer.Behaviour
|
||||
use Behaviour
|
||||
|
||||
defrecord Config, counter: 0, failures: []
|
||||
@type test_case :: module
|
||||
@type test :: atom
|
||||
@type result :: { kind :: atom, reason :: term, stacktrace :: list } | nil
|
||||
|
||||
import Exception, only: [format_stacktrace: 1]
|
||||
defcallback suite_started(), do: any
|
||||
defcallback suite_finished(), do: non_neg_integer
|
||||
|
||||
def start do
|
||||
{ :ok, pid } = :gen_server.start_link(__MODULE__, [], [])
|
||||
pid
|
||||
end
|
||||
defcallback case_started(test_case), do: any
|
||||
defcallback case_finished(test_case), do: any
|
||||
|
||||
def init(_args) do
|
||||
{ :ok, Config.new }
|
||||
end
|
||||
|
||||
def handle_call({:each, _test_case, _test, nil }, _from, config) do
|
||||
IO.write "."
|
||||
{ :reply, :ok, config.increment_counter }
|
||||
end
|
||||
|
||||
def handle_call({:each, test_case, test, failure }, _from, config) do
|
||||
IO.write "F"
|
||||
{ :reply, :ok, config.increment_counter.
|
||||
prepend_failures([{test_case, test, failure}]) }
|
||||
end
|
||||
|
||||
def handle_call({:each_case, _test_case}, _from, config) do
|
||||
{ :reply, :ok, config }
|
||||
end
|
||||
|
||||
def handle_call(:finish, _from, config) do
|
||||
IO.write "\n\n"
|
||||
Enum.reduce Enum.reverse(config.failures), 1, print_failure(&1, &2)
|
||||
failures_count = length(config.failures)
|
||||
IO.puts "#{config.counter} tests, #{failures_count} failures."
|
||||
{ :reply, failures_count, config }
|
||||
end
|
||||
|
||||
defp print_failure({test_case, test, { kind, reason, stacktrace }}, acc) do
|
||||
IO.puts "#{acc}) #{test} (#{inspect test_case})"
|
||||
IO.puts " ** #{format_catch(kind, reason)}\n stacktrace:"
|
||||
Enum.each filter_stacktrace(stacktrace), fn(s) -> IO.puts " #{format_stacktrace(s)}" end
|
||||
IO.write "\n"
|
||||
acc + 1
|
||||
end
|
||||
|
||||
defp format_catch(:error, exception) do
|
||||
"(#{inspect exception.__record__(:name)}) #{exception.message}"
|
||||
end
|
||||
|
||||
defp format_catch(kind, reason) do
|
||||
"(#{kind}) #{inspect(reason)}"
|
||||
end
|
||||
|
||||
defp filter_stacktrace([{ ExUnit.Assertions, _, _, _ }|t]), do: filter_stacktrace(t)
|
||||
defp filter_stacktrace([h|t]), do: [h|filter_stacktrace(t)]
|
||||
defp filter_stacktrace([]), do: []
|
||||
end
|
||||
defcallback test_started(test_case, test), do: any
|
||||
defcallback test_finished(test_case, test, result), do: any
|
||||
end
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
defmodule ExUnit.Runner do
|
||||
@moduledoc false
|
||||
|
||||
defrecord Config, formatter: ExUnit.Formatter, max_cases: 4, taken_cases: 0
|
||||
defrecord Config, formatter: ExUnit.CLIFormatter, max_cases: 4, taken_cases: 0
|
||||
|
||||
def loop(config) do
|
||||
def run(config) do
|
||||
config.formatter.suite_started
|
||||
loop config
|
||||
end
|
||||
|
||||
defp loop(config) do
|
||||
available = config.max_cases - config.taken_cases
|
||||
|
||||
cond do
|
||||
@@ -13,7 +18,7 @@ defmodule ExUnit.Runner do
|
||||
|
||||
# Slots are available, start with async cases
|
||||
cases = ExUnit.Server.take_async_cases(available) ->
|
||||
spawn_cases(cases, config)
|
||||
spawn_cases(config, cases)
|
||||
|
||||
# No more async cases, wait for them to finish
|
||||
config.taken_cases > 0 ->
|
||||
@@ -21,11 +26,11 @@ defmodule ExUnit.Runner do
|
||||
|
||||
# So we can start all sync cases
|
||||
cases = ExUnit.Server.take_sync_cases ->
|
||||
spawn_cases(cases, config)
|
||||
spawn_cases(config, cases)
|
||||
|
||||
# No more cases, we are done!
|
||||
true ->
|
||||
call_formatter config, :finish
|
||||
config.formatter.suite_finished
|
||||
end
|
||||
end
|
||||
|
||||
@@ -34,40 +39,44 @@ defmodule ExUnit.Runner do
|
||||
# cases counter and attempt to spawn new ones.
|
||||
defp wait_until_available(config) do
|
||||
receive do
|
||||
{ pid, :each, { test_case, test, final } } ->
|
||||
call_formatter config, { :each, test_case, test, final }
|
||||
{ pid, :test_finished, { test_case, test, final } } ->
|
||||
config.formatter.test_finished(test_case, test, final)
|
||||
wait_until_available config
|
||||
{ pid, :each_case, test_case } ->
|
||||
call_formatter config, { :each_case, test_case }
|
||||
{ pid, :case_finished, test_case } ->
|
||||
config.formatter.case_finished(test_case)
|
||||
loop config.increment_taken_cases(-1)
|
||||
end
|
||||
end
|
||||
|
||||
defp spawn_cases(cases, config) do
|
||||
Enum.each cases, spawn_case(&1)
|
||||
defp spawn_cases(config, cases) do
|
||||
Enum.each cases, spawn_case(config, &1)
|
||||
loop config.increment_taken_cases(length(cases))
|
||||
end
|
||||
|
||||
defp spawn_case(test_case) do
|
||||
defp spawn_case(config, test_case) do
|
||||
pid = self()
|
||||
spawn_link fn ->
|
||||
ExUnit.Server.run_after_spawn
|
||||
run_tests(pid, test_case)
|
||||
run_tests(config, pid, test_case)
|
||||
end
|
||||
end
|
||||
|
||||
defp run_tests(pid, test_case) do
|
||||
defp run_tests(config, pid, test_case) do
|
||||
config.formatter.case_started(test_case)
|
||||
|
||||
try do
|
||||
tests = tests_for(test_case)
|
||||
context = run_setup_all(test_case)
|
||||
Enum.each tests, run_test(pid, test_case, context, &1)
|
||||
Enum.each tests, run_test(config, pid, test_case, context, &1)
|
||||
run_teardown_all(test_case, context)
|
||||
after
|
||||
pid <- { self, :each_case, test_case }
|
||||
pid <- { self, :case_finished, test_case }
|
||||
end
|
||||
end
|
||||
|
||||
defp run_test(pid, test_case, setup_context, test) do
|
||||
defp run_test(config, pid, test_case, setup_context, test) do
|
||||
config.formatter.test_started(test_case, test)
|
||||
|
||||
final = try do
|
||||
context = run_setup(test_case, setup_context, test)
|
||||
|
||||
@@ -92,7 +101,7 @@ defmodule ExUnit.Runner do
|
||||
{ kind2, error2, System.stacktrace }
|
||||
end
|
||||
|
||||
pid <- { self, :each, { test_case, test, final } }
|
||||
pid <- { self, :test_finished, { test_case, test, final } }
|
||||
end
|
||||
|
||||
defp run_setup_all(test_case) do
|
||||
@@ -141,10 +150,6 @@ defmodule ExUnit.Runner do
|
||||
end
|
||||
end
|
||||
|
||||
defp call_formatter(config, message) do
|
||||
:gen_server.call(config.formatter, message)
|
||||
end
|
||||
|
||||
defp tests_for(mod) do
|
||||
exports = mod.__info__(:functions)
|
||||
lc { function, 0 } inlist exports, is_test?(atom_to_list(function)), do: function
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user