Compare commits

...
135 Commits
Author SHA1 Message Date
José Valim 243433d9d3 Release v0.7.2 2012-12-04 19:58:32 +01:00
Yurii Rashkovskii 7a69a0335c Fix return value of String.at/2 with negative position out of bounds 2012-12-04 01:43:16 -08:00
Yurii Rashkovskii 16da928df8 Fix String.at/1 documentation example 2012-12-04 01:36:32 -08:00
José Valim 4583162eda Improve docs for mix run 2012-12-03 22:47:27 +01:00
José Valim b20fea7c4f Ensure update_* fields work with record rewriter 2012-12-03 20:51:29 +01:00
José Valim d9029c7f98 Add a test for conflicting record inference 2012-12-03 20:43:39 +01:00
José Valim 362ff69e65 Move more behaviour to Enum 2012-12-03 20:23:22 +01:00
José Valim ba43183f1b Ensure RecordRewriter is no-op for non record fields 2012-12-03 14:48:11 +01:00
Yurii Rashkovskii af959582d7 Decapitalize Erlang-style named variables in type specifications' annotations when converting to AST 2012-12-03 04:11:42 -08:00
Yurii Rashkovskii 46b73173b5 Minor CHANGELOG improvements 2012-12-03 03:27:44 -08:00
José Valim 8b66976c96 Ensure Macro.to_binary work with unary not 2012-12-03 12:24:26 +01:00
José Valim 36e6261164 Update CHANGELOG 2012-12-03 12:08:57 +01:00
José Valim a629fdac0d Document the record access rewrite feature 2012-12-03 11:35:34 +01:00
José Valim 3c7cc9ab84 Merge branch 'jv-tuple-opt' 2012-12-03 11:23:02 +01:00
José Valim c7f67bc52a Rewrite records accessors when possible 2012-12-03 11:19:51 +01:00
Yurii Rashkovskii dffd8c59ad Improve documentation for the /> operator 2012-12-03 01:24:32 -08:00
José Valim fa9a78f0dc Merge pull request #683 from devinus/unicode-strip
Add String.Unicode.{lstrip,rstrip}
2012-12-03 00:06:22 -08:00
José Valim 82cf622e9b Enable record rewriter inference 2012-12-03 00:47:34 +01:00
Devin Torres 5c29d76bca Add String.Unicode.{lstrip,rstrip} 2012-12-02 17:42:32 -06:00
José Valim 088ef2fbe8 Support try/receive in record rewriter 2012-12-03 00:24:09 +01:00
José Valim 498ac849fb Record rewriting works with case clauses 2012-12-02 23:37:53 +01:00
José Valim d4c2436521 Extend reach of record rewriting 2012-12-02 22:33:12 +01:00
Yurii Rashkovskii 0f9b717c03 Improve IEX's s helper implementation 2012-12-02 12:14:47 -08:00
Yurii Rashkovskii 295d275d13 Fix IEX's s helper to correctly handle MF and MFA requests 2012-12-02 12:06:40 -08:00
Yurii Rashkovskii 4c1f4451e2 Type specification to AST conversion should support paren_type 2012-12-02 11:58:58 -08:00
José Valim 0f1584c0fa Initial work on RecordRewriter 2012-12-02 20:58:57 +01:00
Yurii Rashkovskii 0a42d7aa3c IEX's s helper should include callbacks in its output 2012-12-02 11:41:17 -08:00
Yurii Rashkovskii 31dcbfb8de mix compile.app should allow custom keys in .app 2012-12-02 11:01:50 -08:00
José Valim 49fa59d3a9 Improve Typespec stacktraces 2012-12-02 16:13:29 +01:00
José Valim c06a44c2fd Add Macro.extract_args, closes #677 2012-12-02 16:10:46 +01:00
José Valim 4e8fb9915b Validate all app properties and do not accept nil values 2012-12-02 15:18:21 +01:00
Yurii Rashkovskii cf5f57f07a Implement Mix.Project.application values validation
Closes #685

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2012-12-02 14:33:16 +01:00
José Valim fc03c8b43d Get rid of unused compilation mode 2012-12-02 14:32:36 +01:00
José Valim b32f6ef51f Keep variables in a single dictionary
This ensures we give the same treatment to quoted
and non-quoted variables.
2012-12-02 12:22:10 +01:00
José Valim 9b3d851a9c Deprecate record default-based generated functions 2012-12-01 11:14:45 +01:00
José Valim 8df803471c IEx.preload is gone 2012-11-30 17:20:31 +01:00
José Valim d55afa44c6 Ensure new IEx works on Windows 2012-11-30 17:07:12 +01:00
José Valim a061528731 Ensure IEx runs only after Elixir's CLI process its options 2012-11-30 17:07:12 +01:00
Yurii Rashkovskii 9e030a6102 Improve System module type specifications coverage 2012-11-30 05:54:05 -08:00
Yurii Rashkovskii 7c5ce0ce74 Fix System.halt/1 and System.halt/0 type specifications
Dialyzer no longer complains about them.
2012-11-30 05:29:44 -08:00
José Valim 73340a466d Consider zero-arity on Macro.to_binary, closes #682 2012-11-30 14:15:02 +01:00
Yurii Rashkovskii 94cc204e4f Add yrashk to .travis.yml notification recipients 2012-11-30 05:01:37 -08:00
José Valim 2ae2b88ea8 Fix mix executable 2012-11-30 13:56:18 +01:00
José Valim b3d203712a Fix mix iex 2012-11-30 13:49:18 +01:00
Yurii Rashkovskii 486cb79fab Store @specs in the order they were specified in the source code 2012-11-30 04:47:43 -08:00
José Valim fe13173aaf Update lib/iex/lib/iex/helpers.ex 2012-11-30 13:38:14 +01:00
Yurii Rashkovskii d8a070d145 Fix typespec to AST conversion for functions with variable arity (...) 2012-11-30 04:34:05 -08:00
Yurii Rashkovskii 8a6fddd477 Add a note about string(), char_list() and String.t() types 2012-11-30 04:03:00 -08:00
Yurii Rashkovskii 57a09f25eb Fix a bug that prevented from inspecting immediate values in typespecs (such as ...) 2012-11-30 03:59:24 -08:00
Yurii Rashkovskii 3e320fd10c Add IEx helper l/1 2012-11-30 03:48:11 -08:00
Yurii Rashkovskii 97bc7a3727 Mention r/1 helper in IEx help 2012-11-30 03:44:29 -08:00
José Valim 252886c2dc Simplify API exposed in IEx.Helpers as h/1, s/1 and t/1 are more flexible now 2012-11-30 11:29:27 +01:00
Yurii Rashkovskii 909db1170c Add IEx helper flush/0 2012-11-30 02:25:35 -08:00
José Valim 6a68bef35a Improve shelling out on mix, closes #492 2012-11-30 11:22:48 +01:00
Yurii Rashkovskii b04351f625 Add support for "local" function syntax in IEx's s helper
Example:

  s(atom_to_list)
  s(atom_to_list/1)
2012-11-30 02:17:28 -08:00
Yurii Rashkovskii 624f781c07 Catch invalid IEx's h helper invocations 2012-11-30 02:10:48 -08:00
Yurii Rashkovskii 5cd8de0493 Fix indentation 2012-11-30 02:08:03 -08:00
Yurii Rashkovskii 8742673da2 Make h/0 IEx helper work again 2012-11-30 02:04:03 -08:00
Yurii Rashkovskii 2fcbf077d0 Minor IEx helper improvement 2012-11-30 02:03:08 -08:00
Yurii Rashkovskii 2aee6bdf2d Add support for h(function_name) helper in IEx
Allows printing documentation for IEx helpers and Kernel's functions/macros without specifying arity.
2012-11-30 01:53:05 -08:00
Yurii Rashkovskii 51f2530f79 Add support for h(Module.function) helper in IEx
Allows printing documentation for functions/macros without specifying arity
2012-11-30 01:39:58 -08:00
Yurii Rashkovskii 1e2f170e95 Initial piece of documentation on type specification syntax
Closes #681
2012-11-30 01:25:29 -08:00
Yurii Rashkovskii 9ed5936f9a Fix a bug in macro_exported?/3
Previously, it would fail if a macro with multiple arities was defined but the first occurence of it won't match the requested arity.
2012-11-30 00:56:58 -08:00
Yurii Rashkovskii bf5431a105 Minor documentation fix in IEx.Helpers 2012-11-30 00:53:39 -08:00
Yurii Rashkovskii ab8da88aad Improve h/1 IEx helper to handle some cases that were not handled before
For example, this didn't work:

    iex(1)> h function_exported?/3
    ** (UndefinedFunctionError) undefined function: IEx.Helpers.function_exported?/0
2012-11-30 00:49:09 -08:00
Yurii Rashkovskii 670822ce63 IEx's h/1 helper should check IEx.Helper's macros before handing off to Kernel 2012-11-30 00:43:46 -08:00
Yurii Rashkovskii d731e8ca4a Add macro_exported?/3 helper 2012-11-30 00:41:21 -08:00
José Valim a18dbf8704 Update CHANGELOG 2012-11-30 09:35:41 +01:00
José Valim bc53005f98 Add new typespec syntax to defcallback too 2012-11-30 09:35:41 +01:00
José Valim d538bbbd22 Ensure proper stacktrace is shown in macros, closes #676 2012-11-30 09:35:41 +01:00
Yurii Rashkovskii 82716f12cb Add support for s/2 and h/2 helpers
These helpers will print all matching specs and helpers for specified module:name with any arity

Example:

    t(Enum.t)
    s(Enum.all?)
2012-11-30 00:21:10 -08:00
Yurii Rashkovskii f84800a9d3 AST representation for the :elixir.char_list() type should be char_list() as it is possible to use this type directly without specifying :elixir module name 2012-11-30 00:00:22 -08:00
José Valim 3eb509fdd2 Support -> in Macro.to_binary and improve fn printing too 2012-11-30 08:38:25 +01:00
José Valim 71de5f0524 Merge branch 'jv-new-spec' 2012-11-30 08:13:35 +01:00
José Valim c2076c79df Merge pull request #678 from yrashk/jv-new-spec
Change fun(... :: signature) specification format to (... -> signature)
2012-11-29 23:13:01 -08:00
Yurii Rashkovskii f0ab87fc1e Add a more explicit syntax for ((...) -> ...): (fun(...) -> ...) 2012-11-29 20:44:06 -08:00
Yurii Rashkovskii b1cb6801c7 Change fun(... :: signature) specification format to (... -> signature) 2012-11-29 15:53:37 -08:00
José Valim f249eab01e Ensure parenthesis are allowed in call args 2012-11-29 23:58:25 +01:00
José Valim ced5a4386b Support -> inside parenthesis
This is consistent with the fact that do...end
is a direct translation to do: (...).
2012-11-29 23:47:53 +01:00
José Valim e11285007e More work on improved typespec syntax 2012-11-29 14:13:26 +01:00
Yurii Rashkovskii 0db1f7279b Change function type specification syntax (WIP)
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2012-11-29 13:34:06 +01:00
José Valim f1b81fd726 Merge pull request #675 from yrashk/code-server-terminate
elixir_code_server should not trap exits and should not log on terminate
2012-11-29 00:18:15 -08:00
Yurii Rashkovskii b8d16dae6f elixir_code_server should not trap exits and should not log on terminate 2012-11-28 18:51:42 -08:00
José Valim b25d5f27d7 Ensure IEx boots fast and with Elixir available, closes #673 2012-11-28 23:19:00 +01:00
José Valim b9e5e490cd Do not add lines to __info__ function 2012-11-28 11:57:57 +01:00
José Valim 956773ee7e Make ExUnit exit happen in two steps.
This allows developers to hook custom mechanisms
after the tests are run but before the system halts.
2012-11-28 11:57:38 +01:00
José Valim 401ad781c3 Ensure debug_info is actually set to true 2012-11-28 09:48:04 +01:00
José Valim 4227d94603 Support \xXX and \x{HEX} escape sequences in strings and char lists
It implements the same behaviour as seen in regexes. Closes #661.
2012-11-27 15:50:45 +01:00
José Valim bf745b352e Make CHANGELOG more consistent 2012-11-27 15:09:12 +01:00
José Valim 3de677607c Deprecated assert left in right in favor of assert left inlist right, closes #662 2012-11-27 15:05:03 +01:00
José Valim f178d999b3 Update CHANGELOG 2012-11-27 14:52:19 +01:00
José Valim bce07f3c27 --debug-info is now true by default 2012-11-27 14:51:10 +01:00
José Valim c7f83ccda3 Merge pull request #665 from yrashk/function-reference-syntax
Add function(Module.fun/arity) and function(fun/arity) syntax
2012-11-27 01:37:29 -08:00
Yurii Rashkovskii e174cbfbfe Add function(Module.fun/arity) and function(fun/arity) syntax
These are equivalent to function(Module, :fun, arity) and function(:fun, arity) constructs, respectively
2012-11-27 01:35:06 -08:00
José Valim 703a7d833f Revert "/u is not required for Elixir regexes"
unicode option is actually required in order to
match \p escape groups.

This reverts commit 0f75b92b25.
2012-11-26 08:36:42 +01:00
José Valim 29f76a106c Merge pull request #660 from carlosmn/mix-dep-fmt
Fix mix dependency format error string
2012-11-25 23:09:12 -08:00
José Valim ba70744f14 Merge pull request #658 from kimshrier/kernel_special_forms_typo
fix some doc typos
2012-11-25 23:07:38 -08:00
Carlos Martín Nieto 54d72121c3 Fix mix dependency format error string
The app name is an atom, but the error message says it should be a
binary, which causes confusion.

Fix that and also add a version without the "requirement" field, which
is optional.
2012-11-26 02:42:56 +01:00
Kim Shrier 1b19c9a7bd fix some doc typos 2012-11-25 15:35:39 -06:00
José Valim 13abbeb1b2 Update lib/elixir/lib/record.ex
Use record field default value as the value
still can be a function/port/etc
2012-11-25 21:22:00 +01:00
José Valim f97b3cea18 Update lib/elixir/lib/record.ex
Use record field value in the exception message
2012-11-25 20:15:32 +01:00
José Valim 9a7bdc158a Speed up native compilation by skipping it in the bootstrap process 2012-11-25 19:09:55 +01:00
José Valim ed0eff4d1d Fix failing erlang tests 2012-11-25 18:25:03 +01:00
José Valim b8f05b2b37 Raise an exception if invalid record field is given, closes #554 2012-11-25 18:13:53 +01:00
José Valim b9600fb6c4 More improvements to typespecs, closes #617 2012-11-25 17:43:56 +01:00
José Valim 5e26cd01b6 Improve error message when invalid token is followed by new line 2012-11-25 17:26:47 +01:00
Yurii Rashkovskii 775803be1a WIP on bounded_fun support
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2012-11-25 17:06:30 +01:00
José Valim 9f16178898 Raise an error if clauses with different arities are defined in the same function, closes #657 2012-11-25 17:01:54 +01:00
José Valim 7ddab53707 Also allow multiclause functions on fn
The function macro is still necessary for function
retrieval and it will be extended when R16 is out
to supported named functions
2012-11-25 00:06:39 +01:00
José Valim 05e694f7e2 Improvements to Regex and String 2012-11-24 21:56:58 +01:00
José Valim 768ede8a1c Merge pull request #656 from l4u/string_strip
String.strip should strip other whitespace characters
2012-11-24 12:13:16 -08:00
Leo Lou 2984771883 Add String.strip/1 2012-11-24 22:59:51 +08:00
Leo Lou 875d1007a9 Strip whitespace characters instead of space in String.rstrip 2012-11-24 22:44:37 +08:00
Leo Lou 1c8993695d Strip whitespace characters instead of space in String.lstrip 2012-11-24 21:07:18 +08:00
José Valim 71837bfbf0 Show warning if a rescue clause can never match, closes #641 2012-11-24 12:55:28 +01:00
José Valim df8a33b16a Show better error message when rebar is not available, closes #647 2012-11-24 12:04:08 +01:00
José Valim 600c8022ba Deprecated Enum.qsort and List.sort in favor of Enum.sort 2012-11-23 21:51:11 +01:00
José Valim 5c967ace04 Add Enum.sort which uses merge sort and performs considerably faster than Enum.qsort 2012-11-23 20:10:26 +01:00
José Valim 87ab504e06 Perfomance improvements to Enum.drop/take 2012-11-23 18:51:10 +01:00
José Valim 36ceb48e21 Do not exit after each run on ExUnit 2012-11-22 10:21:56 +01:00
José Valim 678fafe7c1 Improve IEx by using the -user option (ht @yrashk) 2012-11-22 10:01:57 +01:00
José Valim b5eb2dbd3e Updated rebar with more env variable fixes 2012-11-20 15:44:39 +01:00
José Valim 44bcc00fce Remove tests since utf-8 names on env variables are poorly supported depending on the OS and ERTS 2012-11-20 11:30:53 +01:00
José Valim 2016b4b236 Remove previously deprecated code 2012-11-20 11:17:13 +01:00
José Valim bd67df4d8c CI doesn't like utf-8 chars as env, let's try iso 2012-11-20 11:16:05 +01:00
José Valim 69ba46ec3e Update version to 0.7.2.dev 2012-11-20 11:15:28 +01:00
José Valim 3cd1a7d1ce Ensure we are inside a git repository before retrieve git info, closes #649 2012-11-20 11:10:25 +01:00
José Valim 224f5e47fe Ensure System env functions properly handle utf-8 data 2012-11-20 11:09:53 +01:00
José Valim 0f75b92b25 /u is not required for Elixir regexes 2012-11-20 10:52:57 +01:00
José Valim 7b7b08c49e Update rebar executable with utf-8 env fixes 2012-11-20 10:31:42 +01:00
José Valim 46a293d6f5 Merge pull request #648 from devinus/install-task
Add `make install` task
2012-11-20 01:09:51 -08:00
José Valim 166217a105 Merge pull request #650 from tim/fix-uri-encode
Fix percent encoding algorithm used by URI.encode
2012-11-19 23:51:27 -08:00
Tim Fletcher bddb614927 Fix percent encoding algorithm used by URI.encode 2012-11-20 00:09:59 +00:00
José Valim 80866e3544 Improve docs and style on unicode.ex 2012-11-20 00:15:00 +01:00
Devin Torres 131db13763 Add make install task
To change the install path, simply override the `INSTALL_PATH`:

    $ make INSTALL_PATH=/opt/local install
2012-11-19 11:28:19 -06:00
119 changed files with 3271 additions and 1496 deletions
+1
View File
@@ -4,6 +4,7 @@ notifications:
irc: "irc.freenode.org#elixir-lang"
recipients:
- jose.valim@plataformatec.com.br
- yrashk@gmail.com
otp_release:
- R15B02
- R15B01
+31 -4
View File
@@ -1,3 +1,30 @@
# v0.7.2 (2012-12-04)
* enhancements
* [CLI] `--debug-info` is now true by default
* [ExUnit] Make ExUnit exit happen in two steps allowing developers to add custom `at_exit` hooks
* [IEx] Many improvements to helpers functions `h/1`, `s/1` and others
* [Kernel] Functions defined with `fn` can now handle many clauses
* [Kernel] Raise an error if clauses with different arities are defined in the same function
* [Kernel] `function` macro now accepts arguments in `M.f/a` and `f/a` formats
* [Macro] Improvements to `Macro.to_binary`
* [Mix] Mix now echoes the output as it comes when executing external commands such as git or rebar
* [Mix] Mix now validates `application` callback's values
* [Record] Record accessors are now optimized and can be up to 6x faster in some cases
* [String] Support `\xXX` and `\x{HEX}` escape sequences in strings, char lists and regexes
* bug fix
* [Bootstrap] Compiling Elixir source no longer fails if environment variables contain utf-8 entries
* [IEx] IEx will now wait for all command line options to be processed before starting
* [Kernel] Ensure proper stacktraces when showing deprecations
* deprecations
* [Enum] `Enum.qsort` is deprecated in favor of `Enum.sort`
* [ExUnit] `assert left in right` is deprecated in favor of `assert left inlist right`
* [List] `List.sort` and `List.uniq` have been deprecated in favor of their `Enum` counterparts
* [Record] Default-based generated functions are deprecated
* [Typespec] Enhancements and deprecations to the `@spec/@callback` and the fun type syntax
# v0.7.1 (2012-11-18)
* enhancements
@@ -25,9 +52,9 @@
* [File] File is opened by default on binary mode instead of utf-8
* deprecations
* [Behaviour] `defcallback/1` was deprecated in favor of `defcallback/2` which matches erlang `@callbacks`
* [Enum] `Enum.times` was deprecated in favor of using ranges
* [System] `halt` was moved to System module
* [Behaviour] `defcallback/1` is deprecated in favor of `defcallback/2` which matches erlang `@callbacks`
* [Enum] `Enum.times` is deprecated in favor of using ranges
* [System] `halt` moved to `System` module
# v0.7.0 (2012-10-20)
@@ -89,7 +116,7 @@
* deprecations
* [Access] The semantics of the access protocol were reduced from a broad query API to simple data structure key-based access
* [ExUnit] Some assertions are deprecated in favor of simply using `assert()`
* [ExUnit] Some assertions are deprecated in favor of simply using `assert()`
* [File] `File.read_info` is deprecated in favor of `File.stat`
* [IO] `IO.print` is deprecated in favor of `IO.write`
* [Kernel] Deprecate `__LINE__` and `__FUNCTION__` in favor of `__ENV__.line` and `__ENV__.function`
+24 -12
View File
@@ -1,9 +1,10 @@
REBAR:=$(shell echo `pwd`/rebar)
ELIXIRC:=bin/elixirc --debug-info --ignore-module-conflict $(ELIXIRC_OPTS)
ERLC:=erlc -I lib/elixir/include
ERL:=erl -I lib/elixir/include -noshell -env ERL_LIBS $ERL_LIBS:lib
VERSION:=0.7.1
RELEASE_FLAG:=.release
REBAR := $(shell echo `pwd`/rebar)
ELIXIRC := bin/elixirc --ignore-module-conflict $(ELIXIRC_OPTS)
ERLC := erlc -I lib/elixir/include
ERL := erl -I lib/elixir/include -noshell -env ERL_LIBS $ERL_LIBS:lib
VERSION := 0.7.2
RELEASE_FLAG := .release
INSTALL_PATH := /usr/local
.PHONY: 1
.NOTPARALLEL: compile
@@ -14,7 +15,7 @@ define APP_TEMPLATE
$(1): lib/$(1)/ebin/Elixir-$(2).beam lib/$(1)/ebin/$(1).app
lib/$(1)/ebin/$(1).app:
@ cd lib/$(1) && ../../bin/elixir ../../bin/mix compile.app
@ cd lib/$(1) && ../../bin/mix compile.app
lib/$(1)/ebin/Elixir-$(2).beam: $(wildcard lib/$(1)/lib/*.ex) $(wildcard lib/$(1)/lib/*/*.ex) $(wildcard lib/$(1)/lib/*/*/*.ex)
@ echo "==> $(1) (compile)"
@@ -68,6 +69,17 @@ $(eval $(call APP_TEMPLATE,eex,EEx))
$(eval $(call APP_TEMPLATE,mix,Mix))
$(eval $(call APP_TEMPLATE,iex,IEx))
install: compile
@ echo "==> elixir (install)"
for dir in lib/*; do \
install -m755 -d $(INSTALL_PATH)/lib/elixir/$$dir/ebin; \
install -m644 $$dir/ebin/* $(INSTALL_PATH)/lib/elixir/$$dir/ebin; \
done
install -m755 -d $(INSTALL_PATH)/lib/elixir/bin
install -m755 $(filter-out %.bat, $(wildcard bin/*)) $(INSTALL_PATH)/lib/elixir/bin
install -m755 -d $(INSTALL_PATH)/bin
ln -sf $(INSTALL_PATH)/lib/elixir/bin/* $(INSTALL_PATH)/bin
clean:
@ cd lib/elixir && $(REBAR) clean
rm -rf $(RELEASE_FLAG)
@@ -77,15 +89,11 @@ clean:
rm -rf lib/mix/test/fixtures/git_repo
rm -rf lib/mix/tmp
#==> Release tasks (modules compiled with --debug-info and --docs)
#==> Release tasks
$(RELEASE_FLAG): compile
touch $(RELEASE_FLAG)
zip: $(RELEASE_FLAG)
rm -rf v$(VERSION).zip
zip -9 -r v$(VERSION).zip bin CHANGELOG.md LEGAL lib/*/ebin LICENSE README.md rel
docs: $(RELEASE_FLAG)
mkdir -p ebin
rm -rf docs
@@ -93,6 +101,10 @@ docs: $(RELEASE_FLAG)
bin/elixir ../exdoc/bin/exdoc
rm -rf ebin
release_zip: $(RELEASE_FLAG)
rm -rf v$(VERSION).zip
zip -9 -r v$(VERSION).zip bin CHANGELOG.md LEGAL lib/*/ebin LICENSE README.md rel
release_docs: docs
cd ../elixir-lang.github.com && git checkout master
rm -rf ../elixir-lang.github.com/docs/master
+1
View File
@@ -23,6 +23,7 @@ This document simply outlines the release process:
## Places where version is mentioned
* src/elixir.app.src
* lib/elixir/src/elixir.app.src
* lib/elixir/lib/system.ex
* rel/reltool.config
* Makefile
+3 -3
View File
@@ -2,9 +2,9 @@
if [ $# -eq 0 ]; then
echo "Usage: `basename $0` [elixir switches] [compiler switches] [.ex files]
-o The directory to output compiled files
--no-docs Do not attach documentation with compiled code
--debug-info Attach debug info to compiled modules
-o The directory to output compiled files
--no-docs Do not attach documentation to compiled modules
--no-debug-info Do not attach debug info to compiled modules
--ignore-module-conflict
** Options given after -- are passed down to the executed code
+4 -7
View File
@@ -5,14 +5,11 @@ if "%*" == "" (
goto run
)
:documentation
echo Usage: %~nx0 [switches] [.ex files]
echo Usage: %~nx0 [elixir switches] [compiler switches] [.ex files]
echo.
echo -v Prints version and exit
echo -o The directory to output compiled files
echo -pa path Prepend the given path to Erlang code path (*)
echo -pz path Append the given path to Erlang code path (*)
echo --no-docs Do not attach documentation with compiled code
echo --debug-info Attach debug info to compiled modules
echo -o The directory to output compiled files
echo --no-docs Do not attach documentation to compiled modules
echo --no-debug-info Do not attach debug info to compiled modules
echo --ignore-module-conflict
echo.
echo ** Options marked with (*) can be given more than once
+1 -1
View File
@@ -11,4 +11,4 @@ readlink_f () {
SELF=$(readlink_f "$0")
SCRIPT_PATH=$(dirname "$SELF")
exec "$SCRIPT_PATH"/elixir --no-halt -e "IEx.cli" "$@"
exec "$SCRIPT_PATH"/elixir --no-halt --erl "-user Elixir-IEx-CLI" "$@"
+1 -1
View File
@@ -1,2 +1,2 @@
@echo off
call "%~dp0\elixir.bat" --no-halt -e "IEx.cli" %*
call "%~dp0\elixir.bat" --no-halt -e "IEx.start" %*
+17 -3
View File
@@ -1,3 +1,17 @@
#!/usr/bin/env elixir
Mix.start
Mix.CLI.run
#!/bin/sh
readlink_f () {
cd "$(dirname "$1")" > /dev/null
local filename="$(basename "$1")"
if [ -h "$filename" ]; then
readlink_f "$(readlink "$filename")"
else
echo "`pwd -P`/$filename"
fi
}
SELF=$(readlink_f "$0")
SCRIPT_PATH=$(dirname "$SELF")
EXECUTABLE="elixir"
if [ "$1" = "iex" ]; then EXECUTABLE="iex"; fi
exec "$SCRIPT_PATH"/$EXECUTABLE -e "Mix.start; Mix.CLI.run" -- "$@"
+9 -1
View File
@@ -1,2 +1,10 @@
@echo off
call "%~dp0\elixir.bat" "%~dp0\mix" %*
if "%1" == "iex" (
goto iex
) else (
goto elixir
)
:iex
call "%~dp0\iex.bat" -e "Mix.start" -e "Mix.CLI.run" -- %*
:elixir
call "%~dp0\elixir.bat" -e "Mix.start" -e "Mix.CLI.run" -- %*
+1 -1
View File
@@ -68,7 +68,7 @@ defmodule EEx.Compiler do
else
key = length(state.dict)
placeholder = '__EEX__(' ++ integer_to_list(key) ++ ');'
{ current ++ placeholder ++ new_lines ++ chars, state.prepend_dict([{key, buffer}]) }
{ current ++ placeholder ++ new_lines ++ chars, state.update_dict([{key, buffer}|&1]) }
end
end
-2
View File
@@ -16,10 +16,8 @@
name_args=false, %% when true, it means arguments should be named
module=nil, %% the current module
function=nil, %% the current function
recur=nil, %% the current loop function to be recurred
vars=[], %% a dict of defined variables and their alias
temp_vars=[], %% a dict of all variables defined in a particular assign
quote_vars=[], %% a dict of all quoted variables
clause_vars=nil, %% a dict of all variables defined in a particular clause
extra_guards=nil, %% extra guards from args expansion
counter=[], %% a counter for the variables defined
+25 -41
View File
@@ -12,10 +12,10 @@ defmodule Behaviour do
use Behaviour
@doc "Parses the given URL"
defcallback parse(uri_info :: URI.Info.t), do: URI.Info.t
defcallback parse(uri_info :: URI.Info.t) :: URI.Info.t
@doc "Defines a default port"
defcallback default_port(), do: integer
defcallback default_port() :: integer
end
And then a specific module may use it as:
@@ -46,10 +46,28 @@ defmodule Behaviour do
Defines a callback according to the given type specification.
"""
defmacro defcallback(fun, do: return) do
{ name, args } = :elixir_clauses.extract_args(fun)
IO.write "[WARNING] defcallback(fun, do: return) is deprecated, please use defcallback(fun :: return) instead\n#{Exception.env_stacktrace(__CALLER__)}"
do_defcallback(fun, return, __CALLER__)
end
defmacro defcallback({ :::, _, [fun, return] }) do
do_defcallback(fun, return, __CALLER__)
end
defmacro defcallback(fun) do
do_defcallback(fun, quote(do: term), __CALLER__)
end
defp do_defcallback(fun, return, caller) do
case Macro.extract_args(fun) do
{ name, args } -> :ok
:error ->
raise ArgumentError, message: "invalid syntax in defcallback #{Macro.to_binary(fun)}"
end
arity = length(args)
Enum.each args, (function do
Enum.each args, fn
{ :::, _, [left, right] } ->
ensure_not_default(left)
ensure_not_default(right)
@@ -57,11 +75,11 @@ defmodule Behaviour do
other ->
ensure_not_default(other)
other
end)
end
quote do
@callback unquote(name)(unquote_splicing(args)), do: unquote(return)
Behaviour.store_docs __MODULE__, unquote(__CALLER__.line), unquote(name), unquote(arity)
@callback unquote(name)(unquote_splicing(args)) :: unquote(return)
Behaviour.store_docs __MODULE__, unquote(caller.line), unquote(name), unquote(arity)
end
end
@@ -78,36 +96,9 @@ defmodule Behaviour do
Module.put_attribute module, :__behaviour_docs, { { name, arity }, line, doc }
end
@doc false
defmacro defcallback(fun) do
IO.write "[WARNING] Behaviour.defcallback/1 is deprecated, please use defcallback/2 instead\n#{Exception.formatted_stacktrace}"
{ name, args } = :elixir_clauses.extract_args(fun)
length = length(args)
{ args, defaults } = Enum.map_reduce(args, 0, function do
{ ://, _, [expr, _] }, acc ->
{ expr, acc + 1 }
expr, acc ->
{ expr, acc }
end)
attributes = Enum.map (length - defaults)..length, fn(i) ->
quote do
@__behaviour_callbacks { unquote(name), unquote(i) }
end
end
quote do
__block__ unquote(attributes)
def unquote(name)(unquote_splicing(args))
end
end
@doc false
defmacro __using__(_) do
quote do
Module.register_attribute(__MODULE__, :__behaviour_callbacks, accumulate: true)
Module.register_attribute(__MODULE__, :__behaviour_docs, accumulate: true)
@before_compile unquote(__MODULE__)
import unquote(__MODULE__)
@@ -117,13 +108,6 @@ defmodule Behaviour do
@doc false
defmacro __before_compile__(_) do
quote do
if @__behaviour_callbacks != [] do
@doc false
def behaviour_info(:callbacks) do
@__behaviour_callbacks
end
end
@doc false
def __behaviour__(:callbacks) do
__MODULE__.behaviour_info(:callbacks)
+26 -26
View File
@@ -32,17 +32,17 @@ defmodule Dict do
@type value :: any
@type t :: tuple
defcallback delete(t, key), do: t
defcallback empty(t), do: t
defcallback get(t, key, value), do: value
defcallback has_key?(t, key), do: boolean
defcallback keys(t), do: list(key)
defcallback merge(t, t, fun(key, value, value, do: value)), do: t
defcallback put(t, key, value), do: t
defcallback size(t), do: non_neg_integer()
defcallback to_list(t), do: list()
defcallback update(t, key, fun(value, do: value)), do: t
defcallback values(t), do: list(value)
defcallback delete(t, key) :: t
defcallback empty(t) :: t
defcallback get(t, key, value) :: value
defcallback has_key?(t, key) :: boolean
defcallback keys(t) :: list(key)
defcallback merge(t, t, (key, value, value -> value)) :: t
defcallback put(t, key, value) :: t
defcallback size(t) :: non_neg_integer()
defcallback to_list(t) :: list()
defcallback update(t, key, (value -> value)) :: t
defcallback values(t) :: list(value)
@doc """
Returns a list containing all dict's keys.
@@ -55,7 +55,7 @@ defmodule Dict do
Dict.keys d #=> [:a,:b]
"""
@spec keys(t), do: [key]
@spec keys(t) :: [key]
def keys(dict) do
elem(dict, 0).keys(dict)
end
@@ -69,7 +69,7 @@ defmodule Dict do
Dict.values d #=> [1,2]
"""
@spec values(t), do: [value]
@spec values(t) :: [value]
def values(dict) do
elem(dict, 0).values(dict)
end
@@ -83,7 +83,7 @@ defmodule Dict do
Dict.size d #=> 2
"""
@spec size(t), do: non_neg_integer
@spec size(t) :: non_neg_integer
def size(dict) do
elem(dict, 0).size(dict)
end
@@ -98,7 +98,7 @@ defmodule Dict do
Dict.has_key?(d, :b) #=> false
"""
@spec has_key?(t, key), do: boolean
@spec has_key?(t, key) :: boolean
def has_key?(dict, key) do
elem(dict, 0).has_key?(dict, key)
end
@@ -115,8 +115,8 @@ defmodule Dict do
Dict.get d, :b, 3 #=> 3
"""
@spec get(t, key), do: value | nil
@spec get(t, key, value), do: value
@spec get(t, key) :: value | nil
@spec get(t, key, value) :: value
def get(dict, key, default // nil) do
elem(dict, 0).get(dict, key, default)
end
@@ -132,7 +132,7 @@ defmodule Dict do
Dict.get d, :b #=> raises KeyError[key: :b]
"""
@spec get!(t, key), do: value | no_return
@spec get!(t, key) :: value | no_return
def get!(dict, key) do
elem(dict, 0).get!(dict, key)
end
@@ -148,7 +148,7 @@ defmodule Dict do
#=> [a: 3, b: 2]
"""
@spec put(t, key, value), do: t
@spec put(t, key, value) :: t
def put(dict, key, val) do
elem(dict, 0).put(dict, key, val)
end
@@ -166,7 +166,7 @@ defmodule Dict do
Dict.delete d, :a #=> [b: 2]
"""
@spec delete(t, key), do: t
@spec delete(t, key) :: t
def delete(dict, key) do
elem(dict, 0).delete(dict, key)
end
@@ -185,7 +185,7 @@ defmodule Dict do
#=> [a: 3, b: 2, d: 4]
"""
@spec merge(t, t), do: t
@spec merge(t, t) :: t
def merge(dict1, dict2) do
merge(dict1, dict2, fn(_k, _v1, v2) -> v2 end)
end
@@ -204,7 +204,7 @@ defmodule Dict do
#=> [a: 4, b: 2, d: 4]
"""
@spec merge(t, t, fun(key, value, value, do: value)), do: t
@spec merge(t, t, (key, value, value -> value)) :: t
def merge(dict1, dict2, fun) do
elem(dict1, 0).merge(dict1, dict2, fun)
end
@@ -220,7 +220,7 @@ defmodule Dict do
#=> [a: -1, b: 2]
"""
@spec update(t, key, fun(value, do: value)), do: t
@spec update(t, key, (value -> value)) :: t
def update(dict, key, fun) do
elem(dict, 0).update(dict, key, fun)
end
@@ -237,7 +237,7 @@ defmodule Dict do
#=> [a: 1, b: 2, c: 3]
"""
@spec update(t, key, value, fun(value, do: value)), do: t
@spec update(t, key, value, (value -> value)) :: t
def update(dict, key, initial, fun) do
elem(dict, 0).update(dict, key, initial, fun)
end
@@ -245,7 +245,7 @@ defmodule Dict do
@doc """
Returns an empty dict of the same type as `dict`.
"""
@spec empty(t), do: t
@spec empty(t) :: t
def empty(dict) do
elem(dict, 0).empty(dict)
end
@@ -254,7 +254,7 @@ defmodule Dict do
Returns a list of key-value pairs stored in `dict`.
No particular order is enforced.
"""
@spec to_list(t), do: list
@spec to_list(t) :: list
def to_list(dict) do
elem(dict, 0).to_list(dict)
end
+423 -166
View File
@@ -91,8 +91,8 @@ defmodule Enum do
Enum.all? [1,nil,3] #=> false
"""
@spec all?(t), do: boolean
@spec all?(t, fun(element, do: boolean)), do: boolean
@spec all?(t) :: boolean
@spec all?(t, (element -> boolean)) :: boolean
def all?(collection, fun // fn(x) -> x end)
@@ -128,8 +128,8 @@ defmodule Enum do
Enum.any? [false,true,false] #=> true
"""
@spec any?(t), do: boolean
@spec any?(t, fun(element, do: boolean)), do: boolean
@spec any?(t) :: boolean
@spec any?(t, (element -> boolean)) :: boolean
def any?(collection, fun // fn(x) -> x end)
@@ -160,7 +160,7 @@ defmodule Enum do
Enum.at! [2,4,6], 4 #=> raises Enum.OutOfBoundsError
"""
@spec at!(t, non_neg_integer), do: element | no_return
@spec at!(t, non_neg_integer) :: element | no_return
def at!(collection, n) when is_list(collection) and n >= 0 do
do_at!(collection, n)
end
@@ -182,7 +182,7 @@ defmodule Enum do
Enum.count [1,2,3] #=> 3
"""
@spec count(t), do: non_neg_integer
@spec count(t) :: non_neg_integer
def count(collection) do
I.count(collection)
end
@@ -190,7 +190,7 @@ defmodule Enum do
@doc """
Counts for how many items the function returns true.
"""
@spec count(t, fun(element, do: boolean)), do: non_neg_integer
@spec count(t, (element -> boolean)) :: non_neg_integer
def count(collection, fun) when is_list(collection) do
do_count(collection, fun)
end
@@ -205,8 +205,8 @@ defmodule Enum do
end
@doc """
Drops the first `count` items from the collection. Expects an ordered
collection.
Drops the first `count` items from the collection.
Expects an ordered collection.
## Examples
@@ -215,9 +215,23 @@ defmodule Enum do
Enum.drop [1,2,3], 0 #=> [1,2,3]
"""
@spec drop(t, non_neg_integer), do: t
def drop(collection, count) do
elem split(collection, count), 1
@spec drop(t, integer) :: list
def drop(collection, count) when is_list(collection) and count >= 0 do
do_drop(collection, count)
end
def drop(collection, count) when count >= 0 do
case I.iterator(collection) do
{ iterator, pointer } ->
do_drop(pointer, iterator, count)
list when is_list(list) ->
do_drop(list, count)
end
end
def drop(collection, count) when count < 0 do
{ list, count } = iterate_and_count(collection, count)
drop(list, count)
end
@doc """
@@ -229,7 +243,7 @@ defmodule Enum do
Enum.drop_while [1,2,3,4,5], fn(x) -> x < 3 end
#=> [3,4,5]
"""
@spec drop_while(t, fun(element, do: boolean)), do: t
@spec drop_while(t, (element -> boolean)) :: list
def drop_while(collection, fun) when is_list(collection) do
do_drop_while(collection, fun)
end
@@ -252,7 +266,7 @@ defmodule Enum do
Enum.each ['some', 'example'], fn(x) -> IO.puts x end
"""
@spec each(t, fun(element, do: any)), do: :ok
@spec each(t, (element -> any)) :: :ok
def each(collection, fun) when is_list(collection) do
:lists.foreach(fun, collection)
:ok
@@ -277,7 +291,7 @@ defmodule Enum do
Enum.empty? [1,2,3] #=> false
"""
@spec empty?(t), do: boolean
@spec empty?(t) :: boolean
def empty?(collection) when is_list(collection) do
collection == []
end
@@ -299,7 +313,7 @@ defmodule Enum do
#=> [2]
"""
@spec filter(t, fun(element, do: boolean)), do: t
@spec filter(t, (element -> boolean)) :: list
def filter(collection, fun) when is_list(collection) do
lc item inlist collection, fun.(item), do: item
end
@@ -322,7 +336,7 @@ defmodule Enum do
#=> [4]
"""
@spec filter_map(t, fun(element, do: boolean), fun(element, do: element)), do: t
@spec filter_map(t, (element -> boolean), (element -> element)) :: list
def filter_map(collection, filter, mapper) when is_list(collection) do
lc item inlist collection, filter.(item), do: mapper.(item)
end
@@ -352,8 +366,8 @@ defmodule Enum do
#=> 3
"""
@spec find(t, fun(element, do: any)), do: element | :nil
@spec find(t, any, fun(element, do: any)), do: element | :nil
@spec find(t, (element -> any)) :: element | :nil
@spec find(t, any, (element -> any)) :: element | :nil
def find(collection, ifnone // nil, fun)
@@ -383,8 +397,8 @@ defmodule Enum do
#=> true
"""
@spec find_value(t, fun(element, do: any)), do: any | :nil
@spec find_value(t, any, fun(element, do: any)), do: any | :nil
@spec find_value(t, (element -> any)) :: any | :nil
@spec find_value(t, any, (element -> any)) :: any | :nil
def find_value(collection, ifnone // nil, fun)
@@ -416,7 +430,7 @@ defmodule Enum do
#=> 2
"""
@spec find_index(t, fun(element, do: any)), do: non_neg_integer | :nil
@spec find_index(t, (element -> any)) :: non_neg_integer | :nil
def find_index(collection, fun) when is_list(collection) do
do_find_index(collection, 0, fun)
end
@@ -439,7 +453,7 @@ defmodule Enum do
Enum.first [1,2,3] #=> 1
"""
@spec first(t), do: :nil | element
@spec first(t) :: :nil | element
def first([]), do: nil
def first([h|_]), do: h
@@ -468,8 +482,8 @@ defmodule Enum do
Enum.join([1,2,3], ' = ') #=> '1 = 2 = 3'
"""
@spec join(t), do: String.t
@spec join(t, String.t | char_list), do: String.t | char_list
@spec join(t) :: String.t
@spec join(t, String.t | char_list) :: String.t | char_list
def join(collection, joiner // "")
def join(collection, joiner) when is_list(joiner) do
@@ -503,7 +517,7 @@ defmodule Enum do
#=> [a: -1, b: -2]
"""
@spec map(t, fun(element, do: any)), do: t
@spec map(t, (element -> any)) :: list
def map(collection, fun) when is_list(collection) do
lc item inlist collection, do: fun.(item)
end
@@ -534,8 +548,8 @@ defmodule Enum do
Enum.map_join([1,2,3], ' = ', &1 * 2) #=> '2 = 4 = 6'
"""
@spec map_join(t, fun(element, do: any)), do: String.t
@spec map_join(t, String.t | char_list, fun(element, do: any)), do: String.t | char_list
@spec map_join(t, (element -> any)) :: String.t
@spec map_join(t, String.t | char_list, (element -> any)) :: String.t | char_list
def map_join(collection, joiner // "", mapper)
def map_join(collection, joiner, mapper) when is_list(joiner) do
@@ -570,7 +584,7 @@ defmodule Enum do
#=> { [2, 4, 6], 6 }
"""
@spec map_reduce(t, any, fun(element, any, do: any)), do: any
@spec map_reduce(t, any, (element, any -> any)) :: any
def map_reduce(collection, acc, f) when is_list(collection) do
:lists.mapfoldl(f, acc, collection)
end
@@ -595,7 +609,7 @@ defmodule Enum do
#=> { [2], [1,3] }
"""
@spec partition(t, fun(element, do: any)), do: {t, t}
@spec partition(t, (element -> any)) :: {list, list}
def partition(collection, fun) when is_list(collection) do
do_partition(collection, fun, [], [])
end
@@ -620,7 +634,7 @@ defmodule Enum do
#=> 6
"""
@spec reduce(t, any, fun(element, any, do: any)), do: any
@spec reduce(t, any, (element, any -> any)) :: any
def reduce(collection, acc, fun) when is_list(collection) do
:lists.foldl(fun, acc, collection)
end
@@ -643,7 +657,7 @@ defmodule Enum do
#=> [3, 2, 1]
"""
@spec reverse(t), do: t
@spec reverse(t) :: list
def reverse(collection) when is_list(collection) do
:lists.reverse(collection)
end
@@ -655,22 +669,17 @@ defmodule Enum do
end
end
@doc """
Sorts the collection according to the quick sort algorithm.
## Examples
Enum.qsort [3,2,1] #=> [1,2,3]
"""
@spec qsort(t), do: t
@doc false
@spec qsort(t) :: list
def qsort(collection) when is_list(collection) do
IO.write "[WARNING] Enum.qsort is deprecated, please use Enum.sort instead\n#{Exception.formatted_stacktrace}"
do_list_qsort(collection, [])
end
def qsort(collection) do
case I.iterator(collection) do
{ iterator, pointer } ->
IO.write "[WARNING] Enum.qsort is deprecated, please use Enum.sort instead\n#{Exception.formatted_stacktrace}"
do_qsort(pointer, iterator, [])
list when is_list(list) ->
qsort(list)
@@ -678,11 +687,58 @@ defmodule Enum do
end
@doc """
Splits the enumerable into two collections, leaving `count` elements in the
first one. If `count` is a negative number, it starts couting from the back
to the beginning of the collection. Be aware that a negative `count`
implies in an iteration through the whole collection.
Expects an ordered collection.
Sorts the collection using the merge sort algorithm.
## Examples
Enum.sort [3,2,1] #=> [1,2,3]
"""
@spec sort(t) :: list
def sort(collection) when is_list(collection) do
:lists.sort(collection)
end
def sort(collection) do
case I.iterator(collection) do
{ iterator, pointer } ->
do_sort(pointer, iterator, &1 <= &2)
list when is_list(list) ->
sort(list)
end
end
@doc """
Sorts the collection using the merge sort algorithm.
## Examples
Enum.sort [3,2,1], &1 > &2 #=> [1,2,3]
"""
@spec sort(t, (element, element -> boolean)) :: list
def sort(collection, fun) when is_list(collection) do
:lists.sort(fun, collection)
end
def sort(collection, fun) do
case I.iterator(collection) do
{ iterator, pointer } ->
do_sort(pointer, iterator, fun)
list when is_list(list) ->
sort(list, fun)
end
end
@doc """
Splits the enumerable into two collections, leaving `count`
elements in the first one. If `count` is a negative number,
it starts couting from the back to the beginning of the
collection.
Be aware that a negative `count` implies the collection
will be iterate twice. One to calculate the position and
another one to do the actual splitting.
## Examples
@@ -693,7 +749,7 @@ defmodule Enum do
Enum.split [1,2,3], -5 #=> { [], [1,2,3] }
"""
@spec split(t, integer), do: {t, t}
@spec split(t, integer) :: {list, list}
def split(collection, count) when is_list(collection) and count >= 0 do
do_split(collection, count, [])
end
@@ -708,10 +764,8 @@ defmodule Enum do
end
def split(collection, count) when count < 0 do
reducer = fn(x, acc) -> {x, acc + 1} end
{ list , total_items } = Enum.map_reduce(collection, 0, reducer)
real_count = max(0, total_items - abs(count))
split(list, real_count)
{ list, count } = iterate_and_count(collection, count)
split(list, count)
end
@doc """
@@ -723,7 +777,7 @@ defmodule Enum do
Enum.split_while [1,2,3,4], fn x -> x == 2 end
#=> { [1], [2, 3, 4] }
"""
@spec split_while(t, fun(element, do: boolean)), do: {t, t}
@spec split_while(t, (element -> boolean)) :: {list, list}
def split_while(collection, fun) when is_list(collection) do
do_split_while(collection, fun, [])
end
@@ -748,9 +802,23 @@ defmodule Enum do
Enum.take [1,2,3], 0 #=> []
"""
@spec take(t, non_neg_integer), do: t
def take(collection, count) do
elem split(collection, count), 0
@spec take(t, integer) :: list
def take(collection, count) when is_list(collection) and count >= 0 do
do_take(collection, count)
end
def take(collection, count) when count >= 0 do
case I.iterator(collection) do
{ iterator, pointer } ->
do_take(pointer, iterator, count)
list when is_list(list) ->
do_take(list, count)
end
end
def take(collection, count) when count < 0 do
{ list, count } = iterate_and_count(collection, count)
take(list, count)
end
@doc """
@@ -763,7 +831,7 @@ defmodule Enum do
#=> [1, 2]
"""
@spec take_while(t, fun(element, do: boolean)), do: t
@spec take_while(t, (element -> boolean)) :: list
def take_while(collection, fun) when is_list(collection) do
do_take_while(collection, fun)
end
@@ -777,22 +845,27 @@ defmodule Enum do
end
end
@doc false
def times(times, function) when times >= 0 do
IO.write "[WARNING] Enum.times/2 is deprecated, please use ranges instead\n#{Exception.formatted_stacktrace}"
case is_function(function, 0) do
true ->
do_times_0(times, 1, function)
_ ->
do_times_1(times, 1, function)
end
times
@doc """
Iterates the enumerable removing all duplicated items.
## Examples
Enum.uniq [1,2,3,2,1]
#=> [1, 2, 3]
"""
@spec uniq(t) :: list
def uniq(collection) when is_list(collection) do
do_uniq(collection, [])
end
@doc false
def times(times, acc, function) when times >= 0 do
IO.write "[WARNING] Enum.times/3 is deprecated, please use ranges instead\n#{Exception.formatted_stacktrace}"
do_times_2(times, 1, function, acc)
def uniq(collection) do
case I.iterator(collection) do
{ iterator, pointer } ->
do_uniq(pointer, iterator, [])
list when is_list(list) ->
do_uniq(list, [])
end
end
@doc """
@@ -801,7 +874,7 @@ defmodule Enum do
dictated by the first enum. In case the second list is shorter,
values are filled with nil.
"""
@spec zip(t, t), do: [{any, any}]
@spec zip(t, t) :: [{any, any}]
def zip(coll1, coll2) when is_list(coll1) do
do_zip(coll1, iterator(coll2))
end
@@ -828,6 +901,25 @@ defmodule Enum do
[]
end
defp iterate_and_count(collection, count) do
{ list, total_items } = do_iterate_and_count(collection)
{ list, max(0, total_items - abs(count)) }
end
defp do_iterate_and_count(collection) when is_list(collection) do
{ collection, length(collection) }
end
defp do_iterate_and_count(collection) do
case I.iterator(collection) do
{ iterator, pointer } ->
reducer = fn(x, acc) -> { x, acc + 1 } end
do_map_reduce(pointer, iterator, [], 0, reducer)
list when is_list(list) ->
do_iterate_and_count(list)
end
end
## Implementations
## all?
@@ -918,6 +1010,32 @@ defmodule Enum do
0
end
## drop
defp do_drop([_|t], counter) when counter > 0 do
do_drop(t, counter - 1)
end
defp do_drop(list, 0) do
list
end
defp do_drop([], _) do
[]
end
defp do_drop({ _, next }, iterator, counter) when counter > 0 do
do_drop(iterator.(next), iterator, counter - 1)
end
defp do_drop(extra, iterator, 0) do
to_list(extra, iterator)
end
defp do_drop(:stop, _, _) do
[]
end
## drop_while
defp do_drop_while([h|t], fun) do
@@ -970,24 +1088,6 @@ defmodule Enum do
ifnone
end
## find_value
defp do_find_value([h|t], ifnone, fun) do
fun.(h) || do_find_value(t, ifnone, fun)
end
defp do_find_value([], ifnone, _) do
ifnone
end
defp do_find_value({ h, next }, iterator, ifnone, fun) do
fun.(h) || do_find_value(iterator.(next), iterator, ifnone, fun)
end
defp do_find_value(:stop, _, ifnone, _) do
ifnone
end
## find_index
defp do_find_index([h|t], counter, fun) do
@@ -1014,6 +1114,24 @@ defmodule Enum do
nil
end
## find_value
defp do_find_value([h|t], ifnone, fun) do
fun.(h) || do_find_value(t, ifnone, fun)
end
defp do_find_value([], ifnone, _) do
ifnone
end
defp do_find_value({ h, next }, iterator, ifnone, fun) do
fun.(h) || do_find_value(iterator.(next), iterator, ifnone, fun)
end
defp do_find_value(:stop, _, ifnone, _) do
ifnone
end
## each
defp do_each({ h, next }, iterator, fun) do
@@ -1053,42 +1171,6 @@ defmodule Enum do
[]
end
## reduce
defp do_reduce({ h, next }, iterator, acc, fun) do
do_reduce(iterator.(next), iterator, fun.(h, acc), fun)
end
defp do_reduce(:stop, _, acc, _) do
acc
end
## split_while
defp do_split_while([h|t], fun, acc) do
if fun.(h) do
do_split_while(t, fun, [h|acc])
else
{ :lists.reverse(acc), [h|t] }
end
end
defp do_split_while([], _, acc) do
{ :lists.reverse(acc), [] }
end
defp do_split_while({ h, next } = extra, iterator, fun, acc) do
if fun.(h) do
do_split_while(iterator.(next), iterator, fun, [h|acc])
else
{ :lists.reverse(acc), to_list(extra, iterator) }
end
end
defp do_split_while(:stop, _, _, acc) do
{ :lists.reverse(acc), [] }
end
## join
defp do_join([h|t], joiner, nil) do
@@ -1117,6 +1199,16 @@ defmodule Enum do
acc || ""
end
## map
defp do_map({ h, next }, iterator, fun) do
[fun.(h)|do_map(iterator.(next), iterator, fun)]
end
defp do_map(:stop, _, _) do
[]
end
## map join
defp do_map_join([h|t], mapper, joiner, nil) do
@@ -1145,16 +1237,6 @@ defmodule Enum do
acc || ""
end
## map
defp do_map({ h, next }, iterator, fun) do
[fun.(h)|do_map(iterator.(next), iterator, fun)]
end
defp do_map(:stop, _, _) do
[]
end
## map_reduce
defp do_map_reduce({ h, next }, iterator, list_acc, acc, f) do
@@ -1192,6 +1274,16 @@ defmodule Enum do
{ :lists.reverse(acc1), :lists.reverse(acc2) }
end
## reduce
defp do_reduce({ h, next }, iterator, acc, fun) do
do_reduce(iterator.(next), iterator, fun.(h, acc), fun)
end
defp do_reduce(:stop, _, acc, _) do
acc
end
## reverse
defp do_reverse({ h, next }, iterator, acc) do
@@ -1202,6 +1294,174 @@ defmodule Enum do
acc
end
## sort
defp do_sort(extra, iterator, fun) do
case sort_take(extra, iterator, 2, []) do
{ [y, x], next } -> sort_split(y, x, next, iterator, fun, [], [], fun.(x, y))
{ other, _ } -> other
end
end
defp sort_take({ h, next }, iterator, counter, acc) when counter > 0 do
sort_take(iterator.(next), iterator, counter - 1, [h|acc])
end
defp sort_take(extra, _iterator, 0, acc) do
{ acc, extra }
end
defp sort_take(:stop, _, _, acc) do
{ acc, :stop }
end
defp sort_split(y, x, { z, next }, iterator, fun, r, rs, bool) do
cond do
fun.(y, z) == bool ->
sort_split(z, y, iterator.(next), iterator, fun, [x | r], rs, bool)
fun.(x, z) == bool ->
sort_split(y, z, iterator.(next), iterator, fun, [x | r], rs, bool)
r == [] ->
sort_split(y, x, iterator.(next), iterator, fun, [z], rs, bool)
true ->
sort_split_pivot(y, x, iterator.(next), iterator, fun, r, rs, z, bool)
end
end
defp sort_split(y, x, :stop, _iterator, fun, r, rs, bool) do
sort_merge([[y, x | r] | rs], fun, bool)
end
defp sort_split_pivot(y, x, { z, next }, iterator, fun, r, rs, s, bool) do
cond do
fun.(y, z) == bool ->
sort_split_pivot(z, y, iterator.(next), iterator, fun, [x | r], rs, s, bool)
fun.(x, z) == bool ->
sort_split_pivot(y, z, iterator.(next), iterator, fun, [x | r], rs, s, bool)
fun.(s, z) == bool ->
sort_split(z, s, iterator.(next), iterator, fun, [], [[y, x | r] | rs], bool)
true ->
sort_split(s, z, iterator.(next), iterator, fun, [], [[y, x | r] | rs], bool)
end
end
defp sort_split_pivot(y, x, :stop, _iterator, fun, r, rs, s, bool) do
sort_merge([[s], [[y, x | r] | rs]], fun, bool)
end
defp sort_merge(list, fun, true), do:
reverse_sort_merge(list, [], fun, true)
defp sort_merge(list, fun, false), do:
sort_merge(list, [], fun, false)
defp sort_merge([t1, [h2 | t2] | l], acc, fun, true), do:
sort_merge(l, [sort_merge_1(t1, h2, t2, [], fun, false) | acc], fun, true)
defp sort_merge([[h2 | t2], t1 | l], acc, fun, false), do:
sort_merge(l, [sort_merge_1(t1, h2, t2, [], fun, false) | acc], fun, false)
defp sort_merge([l], [], _fun, _bool), do: l
defp sort_merge([l], acc, fun, bool), do:
reverse_sort_merge([:lists.reverse(l, []) | acc], [], fun, bool)
defp sort_merge([], acc, fun, bool), do:
reverse_sort_merge(acc, [], fun, bool)
defp reverse_sort_merge([[h2 | t2], t1 | l], acc, fun, true), do:
reverse_sort_merge(l, [sort_merge_1(t1, h2, t2, [], fun, true) | acc], fun, true)
defp reverse_sort_merge([t1, [h2 | t2] | l], acc, fun, false), do:
reverse_sort_merge(l, [sort_merge_1(t1, h2, t2, [], fun, true) | acc], fun, false)
defp reverse_sort_merge([l], acc, fun, bool), do:
sort_merge([:lists.reverse(l, []) | acc], [], fun, bool)
defp reverse_sort_merge([], acc, fun, bool), do:
sort_merge(acc, [], fun, bool)
defp sort_merge_1([h1 | t1], h2, t2, m, fun, bool) do
if fun.(h1, h2) == bool do
sort_merge_2(h1, t1, t2, [h2 | m], fun, bool)
else
sort_merge_1(t1, h2, t2, [h1 | m], fun, bool)
end
end
defp sort_merge_1([], h2, t2, m, _fun, _bool), do:
:lists.reverse(t2, [h2 | m])
defp sort_merge_2(h1, t1, [h2 | t2], m, fun, bool) do
if fun.(h1, h2) == bool do
sort_merge_2(h1, t1, t2, [h2 | m], fun, bool)
else
sort_merge_1(t1, h2, t2, [h1 | m], fun, bool)
end
end
defp sort_merge_2(h1, t1, [], m, _fun, _bool), do:
:lists.reverse(t1, [h1 | m])
## split
defp do_split([h|t], counter, acc) when counter > 0 do
do_split(t, counter - 1, [h|acc])
end
defp do_split(list, 0, acc) do
{ :lists.reverse(acc), list }
end
defp do_split([], _, acc) do
{ :lists.reverse(acc), [] }
end
defp do_split({ h, next }, iterator, counter, acc) when counter > 0 do
do_split(iterator.(next), iterator, counter - 1, [h|acc])
end
defp do_split(extra, iterator, 0, acc) do
{ :lists.reverse(acc), to_list(extra, iterator) }
end
defp do_split(:stop, _, _, acc) do
{ :lists.reverse(acc), [] }
end
## split_while
defp do_split_while([h|t], fun, acc) do
if fun.(h) do
do_split_while(t, fun, [h|acc])
else
{ :lists.reverse(acc), [h|t] }
end
end
defp do_split_while([], _, acc) do
{ :lists.reverse(acc), [] }
end
defp do_split_while({ h, next } = extra, iterator, fun, acc) do
if fun.(h) do
do_split_while(iterator.(next), iterator, fun, [h|acc])
else
{ :lists.reverse(acc), to_list(extra, iterator) }
end
end
defp do_split_while(:stop, _, _, acc) do
{ :lists.reverse(acc), [] }
end
## qsort (lists)
defp do_list_qsort([], acc) do
@@ -1252,30 +1512,30 @@ defmodule Enum do
end
end
## split
## take
defp do_split([h|t], counter, acc) when counter > 0 do
do_split(t, counter - 1, [h|acc])
defp do_take([h|t], counter) when counter > 0 do
[h|do_take(t, counter - 1)]
end
defp do_split(list, 0, acc) do
{ :lists.reverse(acc), list }
defp do_take(_list, 0) do
[]
end
defp do_split([], _, acc) do
{ :lists.reverse(acc), [] }
defp do_take([], _) do
[]
end
defp do_split({ h, next }, iterator, counter, acc) when counter > 0 do
do_split(iterator.(next), iterator, counter - 1, [h|acc])
defp do_take({ h, next }, iterator, counter) when counter > 0 do
[h|do_take(iterator.(next), iterator, counter - 1)]
end
defp do_split(extra, iterator, 0, acc) do
{ :lists.reverse(acc), to_list(extra, iterator) }
defp do_take(_extra, _iterator, 0) do
[]
end
defp do_split(:stop, _, _, acc) do
{ :lists.reverse(acc), [] }
defp do_take(:stop, _, _) do
[]
end
## take_while
@@ -1304,31 +1564,28 @@ defmodule Enum do
[]
end
## times
## uniq
defp do_times_0(limit, counter, _function) when counter >= limit do
defp do_uniq([h|t], acc) do
case :lists.member(h, acc) do
true -> do_uniq(t, acc)
false -> [h|do_uniq(t, [h|acc])]
end
end
defp do_times_0(limit, counter, function) do
function.()
do_times_0(limit, 1 + counter, function)
defp do_uniq([], _acc) do
[]
end
defp do_times_1(limit, counter, _function) when counter >= limit do
defp do_uniq({ h, next }, iterator, acc) do
case :lists.member(h, acc) do
true -> do_uniq(iterator.(next), iterator, acc)
false -> [h|do_uniq(iterator.(next), iterator, [h|acc])]
end
end
defp do_times_1(limit, counter, function) do
function.(counter)
do_times_1(limit, 1 + counter, function)
end
defp do_times_2(limit, counter, _function, acc) when counter >= limit do
acc
end
defp do_times_2(limit, counter, function, acc) do
new_acc = function.(counter, acc)
do_times_2(limit, 1 + counter, function, new_acc)
defp do_uniq(:stop, _, _acc) do
[]
end
## zip
+20 -1
View File
@@ -86,13 +86,32 @@ defmodule Exception do
@doc """
Returns the stacktrace as a binary formatted as per `format_stacktrace/1`.
"""
def formatted_stacktrace(trace // System.stacktrace) do
def formatted_stacktrace(trace // nil) do
trace = trace || try do
throw(:stacktrace)
catch
:stacktrace -> Enum.drop(System.stacktrace, 1)
end
case trace do
[] -> ""
s -> " " <> Enum.map_join(s, "\n ", format_stacktrace(&1)) <> "\n"
end
end
@doc """
Returns a formatted stacktrace from the environment.
"""
def env_stacktrace(env) do
rest =
case env.function do
{ name, arity } -> format_module_fun_arity(env.module, name, arity)
nil -> "#{inspect env.module} (body)"
end
" #{format_file_line(env.location)}#{rest}\n"
end
@doc """
Formats each line in the stacktrace.
"""
+54 -12
View File
@@ -1152,10 +1152,12 @@ defmodule File do
end
@doc """
Convert the file device into an iterator that can be
Converts the file device into an iterator that can be
passed into `Enum`. The device is iterated line
by line lazily, at the end of iteration the file is
closed.
by line, at the end of iteration the file is closed.
This reads the file as utf-8. CHeck out `File.biniterator`
to handle the file as a raw binary.
## Examples
@@ -1187,21 +1189,13 @@ defmodule File do
{ :error, reason } ->
raise File.IteratorError, reason: reason
data ->
{ strip_ending(data), :ok }
{ data, :ok }
end
end
{ function, function.(:start) }
end
end
defp strip_ending("\r\n"), do: <<>>
defp strip_ending("\n"), do: <<>>
defp strip_ending(""), do: <<>>
defp strip_ending(<< h, t :: binary >>) do
<< h, strip_ending(t) :: binary >>
end
@doc """
Opens the given `file` with the given `mode` and
returns its iterator. Fails for the same reasons
@@ -1222,6 +1216,54 @@ defmodule File do
open!(file, mode) /> iterator
end
@doc """
Converts the file device into an iterator that can
be passed into `Enum` to iterate line by line as a
binary. Check `iterator/1` for more information.
"""
def biniterator(device)
def biniterator(file) when is_binary(file) or is_list(file) do
biniterator(file, [])
end
def biniterator(device) do
fn ->
function = fn(_) ->
case :file.read_line(device) do
:eof ->
close(device)
:stop
{ :error, reason } ->
raise File.IteratorError, reason: reason
{ :ok, data } ->
{ data, :ok }
end
end
{ function, function.(:start) }
end
end
@doc """
Opens the given `file` with the given `mode` and
returns its biniterator. Fails for the same reasons
as `File.open`.
"""
def biniterator(file, mode) do
case open(file, mode) do
{ :ok, device } -> { :ok, biniterator(device) }
error -> error
end
end
@doc """
Same as `biniterator/2` but raises if the file
cannot be opened.
"""
def biniterator!(file, mode // []) do
open!(file, mode) /> biniterator
end
## Helpers
# Normalize the given path by removing "..".
+202 -137
View File
@@ -299,7 +299,7 @@ defmodule Kernel do
abs(-3.33) #=> 3.33
abs(-3) #=> 3
"""
@spec abs(number), do: number
@spec abs(number) :: number
def abs(number) do
:erlang.abs(number)
end
@@ -313,7 +313,7 @@ defmodule Kernel do
#=> 4
"""
@spec apply(fun, list), do: term
@spec apply((... -> any), list) :: term
def apply(fun, args) do
:erlang.apply(fun, args)
end
@@ -327,7 +327,7 @@ defmodule Kernel do
#=> [3,2,1]
"""
@spec apply(atom, atom, list), do: term
@spec apply(atom, atom, list) :: term
def apply(module, fun, args) do
:erlang.apply(module, fun, args)
end
@@ -344,7 +344,7 @@ defmodule Kernel do
atom_to_binary(:elixir, :utf8) #=> "elixir"
"""
@spec atom_to_binary(atom, :utf8 | :unicode | :latin1), do: binary
@spec atom_to_binary(atom, :utf8 | :unicode | :latin1) :: binary
def atom_to_binary(atom, encoding) do
:erlang.atom_to_binary(atom, encoding)
end
@@ -357,7 +357,7 @@ defmodule Kernel do
atom_to_list(:elixir) #=> 'elixir'
"""
@spec atom_to_list(atom), do: list
@spec atom_to_list(atom) :: list
def atom_to_list(atom) do
:erlang.atom_to_list(atom)
end
@@ -380,7 +380,7 @@ defmodule Kernel do
binary_part "foo", 3, -1 #=> 1
"""
@spec binary_part(binary, pos_integer, integer), do: binary
@spec binary_part(binary, pos_integer, integer) :: binary
def binary_part(binary, start, length) do
:erlang.binary_part(binary, start, length)
end
@@ -396,7 +396,7 @@ defmodule Kernel do
binary_to_atom("elixir", :utf8) #=> :elixir
"""
@spec binary_to_atom(binary, :utf8 | :unicode | :latin1), do: atom
@spec binary_to_atom(binary, :utf8 | :unicode | :latin1) :: atom
def binary_to_atom(binary, encoding) do
:erlang.binary_to_atom(binary, encoding)
end
@@ -404,7 +404,7 @@ defmodule Kernel do
@doc """
Works like `binary_to_atom/2`, but the atom must already exist.
"""
@spec binary_to_existing_atom(binary, :utf8 | :unicode | :latin1), do: atom
@spec binary_to_existing_atom(binary, :utf8 | :unicode | :latin1) :: atom
def binary_to_existing_atom(binary, encoding) do
:erlang.binary_to_existing_atom(binary, encoding)
end
@@ -412,7 +412,7 @@ defmodule Kernel do
@doc """
Returns a list of integers which correspond to the bytes of `binary`.
"""
@spec binary_to_list(binary), do: list
@spec binary_to_list(binary) :: list
def binary_to_list(binary) do
:erlang.binary_to_list(binary)
end
@@ -422,7 +422,7 @@ defmodule Kernel do
from position `start` to position `stop` in `binary`. Positions in the binary
are numbered starting from 1.
"""
@spec binary_to_list(binary, pos_integer, pos_integer), do: list
@spec binary_to_list(binary, pos_integer, pos_integer) :: list
def binary_to_list(binary, start, stop) do
:erlang.binary_to_list(binary, start, stop)
end
@@ -437,7 +437,7 @@ defmodule Kernel do
binary_to_term(term_to_binary("foo")) #=> "foo"
"""
@spec binary_to_term(binary), do: term
@spec binary_to_term(binary) :: term
def binary_to_term(binary) do
:erlang.binary_to_term(binary)
end
@@ -461,7 +461,7 @@ defmodule Kernel do
binary_to_term(term_to_binary("foo"), [:safe])
"""
@spec binary_to_term(binary, [] | [:safe]), do: term
@spec binary_to_term(binary, [] | [:safe]) :: term
def binary_to_term(binary, options) do
:erlang.binary_to_term(binary, options)
end
@@ -477,7 +477,7 @@ defmodule Kernel do
bit_size(<<1,2,3>>) #=> 24
"""
@spec bit_size(bitstring), do: non_neg_integer
@spec bit_size(bitstring) :: non_neg_integer
def bit_size(bitstring) do
:erlang.bit_size(bitstring)
end
@@ -487,7 +487,7 @@ defmodule Kernel do
number of bits in the binary is not divisible by 8, the last element of the list will
be a bitstring containing the remaining bits (1 up to 7 bits).
"""
@spec bitstring_to_list(bitstring), do: list
@spec bitstring_to_list(bitstring) :: list
def bitstring_to_list(bitstring) do
:erlang.bitstring_to_list(bitstring)
end
@@ -505,7 +505,7 @@ defmodule Kernel do
byte_size(<<1,2,3>>) #=> 3
"""
@spec byte_size(bitstring), do: non_neg_integer
@spec byte_size(bitstring) :: non_neg_integer
def byte_size(bitstring) do
:erlang.byte_size(bitstring)
end
@@ -521,7 +521,7 @@ defmodule Kernel do
exit(:seems_bad)
"""
@spec exit(term), do: no_return
@spec exit(term) :: no_return
def exit(reason) do
:erlang.exit(reason)
end
@@ -529,7 +529,7 @@ defmodule Kernel do
@doc """
Converts the given number to a float. Allowed in guard clauses.
"""
@spec float(number), do: float
@spec float(number) :: float
def float(number) do
:erlang.float(number)
end
@@ -543,7 +543,7 @@ defmodule Kernel do
#=> '7.00000000000000000000e+00'
"""
@spec float_to_list(number), do: char_list
@spec float_to_list(number) :: char_list
def float_to_list(number) do
:erlang.float_to_list(number)
end
@@ -551,7 +551,7 @@ defmodule Kernel do
@doc """
Returns the head of a list, raises badarg if the list is empty.
"""
@spec hd(list), do: term
@spec hd(list) :: term
def hd(list) do
:erlang.hd(list)
end
@@ -565,7 +565,7 @@ defmodule Kernel do
#=> '7'
"""
@spec integer_to_list(integer), do: char_list
@spec integer_to_list(integer) :: char_list
def integer_to_list(number) do
:erlang.integer_to_list(number)
end
@@ -580,7 +580,7 @@ defmodule Kernel do
#=> "3FF"
"""
@spec integer_to_list(integer, pos_integer), do: char_list
@spec integer_to_list(integer, pos_integer) :: char_list
def integer_to_list(number, base) do
:erlang.integer_to_list(number, base)
end
@@ -594,7 +594,7 @@ defmodule Kernel do
#=> 4
"""
@spec iolist_size(iolist), do: non_neg_integer
@spec iolist_size(iolist) :: non_neg_integer
def iolist_size(item) do
:erlang.iolist_size(item)
end
@@ -612,7 +612,7 @@ defmodule Kernel do
#=> <<1,2,3,1,2,3,4,5,4,6>>
"""
@spec iolist_to_binary(iolist), do: binary
@spec iolist_to_binary(iolist) :: binary
def iolist_to_binary(item) do
:erlang.iolist_to_binary(item)
end
@@ -621,7 +621,7 @@ defmodule Kernel do
Returns true if the local node is alive; that is,
if the node can be part of a distributed system.
"""
@spec is_alive, do: boolean
@spec is_alive :: boolean
def is_alive do
:erlang.is_alive
end
@@ -631,7 +631,7 @@ defmodule Kernel do
Allowed in guard tests.
"""
@spec is_atom(term), do: boolean
@spec is_atom(term) :: boolean
def is_atom(term) do
:erlang.is_atom(term)
end
@@ -643,7 +643,7 @@ defmodule Kernel do
Allowed in guard tests.
"""
@spec is_binary(term), do: boolean
@spec is_binary(term) :: boolean
def is_binary(term) do
:erlang.is_binary(term)
end
@@ -653,7 +653,7 @@ defmodule Kernel do
Allowed in guard tests.
"""
@spec is_bitstring(term), do: boolean
@spec is_bitstring(term) :: boolean
def is_bitstring(term) do
:erlang.is_bitstring(term)
end
@@ -664,7 +664,7 @@ defmodule Kernel do
Allowed in guard tests.
"""
@spec is_boolean(term), do: boolean
@spec is_boolean(term) :: boolean
def is_boolean(term) do
:erlang.is_boolean(term)
end
@@ -674,7 +674,7 @@ defmodule Kernel do
Allowed in guard tests.
"""
@spec is_float(term), do: boolean
@spec is_float(term) :: boolean
def is_float(term) do
:erlang.is_float(term)
end
@@ -684,7 +684,7 @@ defmodule Kernel do
Allowed in guard tests.
"""
@spec is_function(term), do: boolean
@spec is_function(term) :: boolean
def is_function(term) do
:erlang.is_function(term)
end
@@ -695,7 +695,7 @@ defmodule Kernel do
Allowed in guard tests.
"""
@spec is_function(term, non_neg_integer), do: boolean
@spec is_function(term, non_neg_integer) :: boolean
def is_function(term, arity) do
:erlang.is_function(term, arity)
end
@@ -705,7 +705,7 @@ defmodule Kernel do
Allowed in guard tests.
"""
@spec is_integer(term), do: boolean
@spec is_integer(term) :: boolean
def is_integer(term) do
:erlang.is_integer(term)
end
@@ -715,7 +715,7 @@ defmodule Kernel do
Allowed in guard tests.
"""
@spec is_list(term), do: boolean
@spec is_list(term) :: boolean
def is_list(term) do
:erlang.is_list(term)
end
@@ -726,7 +726,7 @@ defmodule Kernel do
Allowed in guard tests.
"""
@spec is_number(term), do: boolean
@spec is_number(term) :: boolean
def is_number(term) do
:erlang.is_number(term)
end
@@ -736,7 +736,7 @@ defmodule Kernel do
Allowed in guard tests.
"""
@spec is_pid(term), do: boolean
@spec is_pid(term) :: boolean
def is_pid(term) do
:erlang.is_pid(term)
end
@@ -746,7 +746,7 @@ defmodule Kernel do
Allowed in guard tests.
"""
@spec is_port(term), do: boolean
@spec is_port(term) :: boolean
def is_port(term) do
:erlang.is_port(term)
end
@@ -756,7 +756,7 @@ defmodule Kernel do
Allowed in guard tests.
"""
@spec is_reference(term), do: boolean
@spec is_reference(term) :: boolean
def is_reference(term) do
:erlang.is_reference(term)
end
@@ -766,7 +766,7 @@ defmodule Kernel do
Allowed in guard tests.
"""
@spec is_tuple(term), do: boolean
@spec is_tuple(term) :: boolean
def is_tuple(term) do
:erlang.is_tuple(term)
end
@@ -780,7 +780,7 @@ defmodule Kernel do
length([1,2,3,4,5,6,7,8,9]) #=> 9
"""
@spec length(list), do: non_neg_integer
@spec length(list) :: non_neg_integer
def length(list) do
:erlang.length(list)
end
@@ -792,7 +792,7 @@ defmodule Kernel do
list_to_atom('elixir') #=> :elixir
"""
@spec list_to_atom(char_list), do: atom
@spec list_to_atom(char_list) :: atom
def list_to_atom(char_list) do
:erlang.list_to_atom(char_list)
end
@@ -804,7 +804,7 @@ defmodule Kernel do
list_to_binary('Elixir') #=> "Elixir"
"""
@spec list_to_binary(iolist), do: binary
@spec list_to_binary(iolist) :: binary
def list_to_binary(char_list) do
:erlang.list_to_binary(char_list)
end
@@ -823,7 +823,7 @@ defmodule Kernel do
#=> <<1,2,3,1,2,3,4,5,4,6,7|4>>
"""
@spec list_to_bitstring(maybe_improper_list(char | binary | iolist | bitstring, binary | bitstring | [])), do: bitstring
@spec list_to_bitstring(maybe_improper_list(char | binary | iolist | bitstring, binary | bitstring | [])) :: bitstring
def list_to_bitstring(bitstring_list) do
:erlang.list_to_bitstring(bitstring_list)
end
@@ -832,7 +832,7 @@ defmodule Kernel do
Returns the atom whose text representation is `char_list`, but only if there already
exists such atom.
"""
@spec list_to_existing_atom(char_list), do: atom
@spec list_to_existing_atom(char_list) :: atom
def list_to_existing_atom(char_list) do
:erlang.list_to_existing_atom(char_list)
end
@@ -844,7 +844,7 @@ defmodule Kernel do
list_to_float('2.2017764e+0') #=> 2.2017764
"""
@spec list_to_float(char_list), do: float
@spec list_to_float(char_list) :: float
def list_to_float(char_list) do
:erlang.list_to_float(char_list)
end
@@ -856,7 +856,7 @@ defmodule Kernel do
list_to_integer('123') #=> 123
"""
@spec list_to_integer(char_list), do: integer
@spec list_to_integer(char_list) :: integer
def list_to_integer(char_list) do
:erlang.list_to_integer(char_list)
end
@@ -868,7 +868,7 @@ defmodule Kernel do
list_to_integer('3FF', 16) #=> 1023
"""
@spec list_to_integer(char_list, non_neg_integer), do: integer
@spec list_to_integer(char_list, non_neg_integer) :: integer
def list_to_integer(char_list, base) do
:erlang.list_to_integer(char_list, base)
end
@@ -886,7 +886,7 @@ defmodule Kernel do
## Examples
list_to_pid('<0.41>') #=> <0.4.1>
"""
@spec list_to_pid(char_list), do: pid
@spec list_to_pid(char_list) :: pid
def list_to_pid(char_list) do
:erlang.list_to_pid(char_list)
end
@@ -898,7 +898,7 @@ defmodule Kernel do
list_to_tuple([:share, [:elixir, 163]]). #=> {:share, [:elixir, 163]}
"""
@spec list_to_tuple(list), do: tuple
@spec list_to_tuple(list) :: tuple
def list_to_tuple(list) do
:erlang.list_to_tuple(list)
end
@@ -915,7 +915,7 @@ defmodule Kernel do
#=> #Ref<0.0.0.135>
"""
@spec make_ref(), do: reference
@spec make_ref() :: reference
def make_ref() do
:erlang.make_ref()
end
@@ -930,7 +930,7 @@ defmodule Kernel do
max(1, 2) #=> 2
"""
@spec max(term, term), do: term
@spec max(term, term) :: term
def max(first, second) do
:erlang.max(first, second)
end
@@ -945,7 +945,7 @@ defmodule Kernel do
min(1, 2) #=> 1
"""
@spec min(term, term), do: term
@spec min(term, term) :: term
def min(first, second) do
:erlang.min(first, second)
end
@@ -956,7 +956,7 @@ defmodule Kernel do
Allowed in guard tests.
"""
@spec node(), do: node
@spec node() :: node
def node do
:erlang.node
end
@@ -968,7 +968,7 @@ defmodule Kernel do
Allowed in guard tests.
"""
@spec node(pid|reference|port), do: node
@spec node(pid|reference|port) :: node
def node(arg) do
:erlang.node(arg)
end
@@ -985,7 +985,7 @@ defmodule Kernel do
It should not be used in application programs.
"""
@spec pid_to_list(pid), do: list
@spec pid_to_list(pid) :: list
def pid_to_list(pid) do
:erlang.pid_to_list(pid)
end
@@ -999,7 +999,7 @@ defmodule Kernel do
round(5.5) #=> 6
"""
@spec round(number), do: integer
@spec round(number) :: integer
def round(number) do
:erlang.round(number)
end
@@ -1008,7 +1008,7 @@ defmodule Kernel do
Returns the pid (process identifier) of the calling process.
Allowed in guard clauses.
"""
@spec self(), do: pid
@spec self() :: pid
def self() do
:erlang.self()
end
@@ -1017,7 +1017,7 @@ defmodule Kernel do
Returns the size of the given argument, which must be a tuple
or a binary. If possible, please use tuple_size or binary_size.
"""
@spec size(tuple|binary), do: non_neg_integer
@spec size(tuple|binary) :: non_neg_integer
def size(arg) do
:erlang.size(arg)
end
@@ -1038,7 +1038,7 @@ defmodule Kernel do
end
"""
@spec spawn(fun), do: pid
@spec spawn((() -> any)) :: pid
def spawn(fun) do
:erlang.spawn(fun)
end
@@ -1055,7 +1055,7 @@ defmodule Kernel do
spawn(SomeModule, :function, [1,2,3])
"""
@spec spawn(module, atom, list), do: pid
@spec spawn(module, atom, list) :: pid
def spawn(module, fun, args) do
:erlang.spawn(module, fun, args)
end
@@ -1077,7 +1077,7 @@ defmodule Kernel do
end
"""
@spec spawn_link(fun), do: pid
@spec spawn_link((() -> any)) :: pid
def spawn_link(fun) do
:erlang.spawn_link(fun)
end
@@ -1094,7 +1094,7 @@ defmodule Kernel do
spawn_link(SomeModule, :function, [1,2,3])
"""
@spec spawn_link(module, atom, list), do: pid
@spec spawn_link(module, atom, list) :: pid
def spawn_link(module, fun, args) do
:erlang.spawn_link(module, fun, args)
end
@@ -1107,7 +1107,7 @@ defmodule Kernel do
to a file in an efficient way, or sending an Erlang term to some type
of communications channel not supported by distributed :
"""
@spec term_to_binary(term), do: binary
@spec term_to_binary(term) :: binary
def term_to_binary(term) do
:erlang.term_to_binary(term)
end
@@ -1121,7 +1121,7 @@ defmodule Kernel do
for more details
"""
@spec term_to_binary(term, list({:compressed, 0..9}|{:minor_version, 0}|{:minor_version, 1})), do: binary
@spec term_to_binary(term, list({:compressed, 0..9}|{:minor_version, 0}|{:minor_version, 1})) :: binary
def term_to_binary(term, opts) do
:erlang.term_to_binary(term, opts)
end
@@ -1129,7 +1129,7 @@ defmodule Kernel do
@doc """
A non-local return from a function. Check try/2 for more information.
"""
@spec throw(term), do: no_return
@spec throw(term) :: no_return
def throw(term) do
:erlang.throw(term)
end
@@ -1137,7 +1137,7 @@ defmodule Kernel do
@doc """
Returns the tail of a list. Raises ArgumentError if the list is empty.
"""
@spec tl(maybe_improper_list), do: maybe_improper_list
@spec tl(maybe_improper_list) :: maybe_improper_list
def tl(list) do
:erlang.tl(list)
end
@@ -1151,7 +1151,7 @@ defmodule Kernel do
trunc(5.5) #=> 5
"""
@spec trunc(number), do: integer
@spec trunc(number) :: integer
def trunc(number) do
:erlang.trunc(number)
end
@@ -1159,7 +1159,7 @@ defmodule Kernel do
@doc """
Returns the size of a tuple.
"""
@spec tuple_size(tuple), do: non_neg_integer
@spec tuple_size(tuple) :: non_neg_integer
def tuple_size(tuple) do
:erlang.tuple_size(tuple)
end
@@ -1167,7 +1167,7 @@ defmodule Kernel do
@doc """
Converts a tuple to a list.
"""
@spec tuple_to_list(tuple), do: list
@spec tuple_to_list(tuple) :: list
def tuple_to_list(tuple) do
:erlang.tuple_to_list(tuple)
end
@@ -1354,7 +1354,7 @@ defmodule Kernel do
## Examples
defrecord FileInfo, atime: nil, mtime: nil
defrecord FileInfo, atime: nil, accesses: 0
The line above will define a module named `FileInfo` which
contains a function named `new` that returns a new record
@@ -1371,36 +1371,50 @@ defmodule Kernel do
inspect FileInfo.new, raw: true
#=> { FileInfo, nil, nil }
## Extensions
Besides defining readers and writers for each attribute. Elixir will
define extensions functions for each attribute. By default, it will
define an `update_#{attribute}` function to update the value. Such
Besides defining readers and writers for each attribute, Elixir also
defines an `update_#{attribute}` function to update the value. Such
functions expect a function as argument that receives the current
value and must return the new one:
value and must return the new one. For example, every time the file
is accessed, the accesses counter can be incremented with:
file_info.update_atime(fn(_old) -> now() end) #=> Updates the value of atime
file_info.update_accesses(fn(old) -> old + 1 end)
Besides, Elixir may define new functions depending on the default value.
For example, ExUnit defines a record which keeps track of how many tests
were executed and the failures that happened. The record definition is
similar to:
Which can be also written as:
defrecord Config, counter: 0, failures: []
file_info.update_accesses(&1 + 1)
Since `counter` is an integer, Elixir automatically defines a helper
named `increment_counter` that will increase the counter value:
## Access syntax
Config.new.increment_counter.counter #=> 1
Records in Elixir can be expanded at compilation time to provide
pattern matching and faster operations. For example, the clause
below will only match if a `FileInfo` is given and the number of
accesses is zero:
`increment_counter` also accepts a number of increment as argument:
def enforce_no_access(FileInfo[accesses: 0]), do: :ok
Config.new.increment_counter(10).counter #=> 10
The clause above will expand to:
Besides, if the default is a list, Elixir will define two helpers:
def enforce_no_access({ FileInfo, _, 0 }), do: :ok
* `merge_field` - Receives keywords and merge it into the current value;
* `prepend_field` - Receives another list and prepend its values;
The downside of using such syntax is that, every time the record
changes, your code now needs to be recompiled (which is usually
not a concern since Elixir build tools by default recompiles the
whole project whenever there is a change).
Finally, keep in mind that Elixir triggers some optimizations whenever
the access syntax is used. For example:
def no_access?(FileInfo[] = file_info) do
file_info.accesses == 0
end
Is translated to:
def no_access?({ FileInfo, _, _ } = file_info) do
elem(file_info, 1) == 0
end
Which provides faster get and set times for record operations.
## Documentation
@@ -1412,7 +1426,7 @@ defmodule Kernel do
For example, assuming the `Config` record defined above, it could be used
in typespecs as follow:
@spec handle_config(Config.t), do: boolean()
@spec handle_config(Config.t) :: boolean()
Inside the record definition, a developer can define his own types too:
@@ -1483,6 +1497,7 @@ defmodule Kernel do
differences:
1) Differently from records, exceptions are documented by default;
2) Exceptions **must** implement `message/1` as API that return a
binary as result;
@@ -1519,12 +1534,14 @@ defmodule Kernel do
case __CALLER__.in_guard? do
true ->
quote do
is_tuple(unquote(thing)) and :erlang.element(2, unquote(thing)) == :__exception__
is_tuple(unquote(thing)) and tuple_size(unquote(thing)) > 1 and
:erlang.element(2, unquote(thing)) == :__exception__
end
false ->
quote do
result = unquote(thing)
is_tuple(result) and :erlang.element(2, result) == :__exception__
is_tuple(result) and tuple_size(result) > 1 and
:erlang.element(2, result) == :__exception__
end
end
end
@@ -1681,7 +1698,7 @@ defmodule Kernel do
As in records, defining a protocol automatically defines a type named `t`,
which can be used as:
@spec present?(Blank.t), do: boolean
@spec present?(Blank.t) :: boolean
def present?(blank) do
not Blank.blank?(blank)
end
@@ -1965,26 +1982,26 @@ defmodule Kernel do
`do/end` always matches the furthest call, if we used the `function`
macro as below:
Enum.map [1,2,3], function(x) do
x * 2
Enum.map [1,2,3], function do
x -> x * 2
end
It would be parsed as:
Enum.map([1,2,3], function(x)) do
x * 2
Enum.map([1,2,3], function) do
x -> x * 2
end
The stab shortcut syntax has the proper precedence:
Enum.map [1,2,3], fn x ->
x * 2
Enum.map [1,2,3], fn
x -> x * 2
end
Which is handled as:
Enum.map([1,2,3], fn x ->
x * 2
Enum.map([1,2,3], fn
x -> x * 2
end)
## Function retrieval
@@ -2067,20 +2084,19 @@ defmodule Kernel do
IO.puts "This is printed regardless if it failed or succeed"
end
The rescue clause is used to handle errors, while the catch clause
can be used to catch throw values. Both catch and rescue clauses
accepts the same pattern matching rules as match.
The rescue clause is used to handle exceptions, while the catch
clause can be used to catch thrown values. Both catch and rescue
clauses work based on pattern matching.
Note that calls inside `try` are not tail recursive since the VM
needs to keep the stacktrace in case an exception happens.
## Rescue clauses
Besides accepting the same pattern matching rules as `match`
clauses, rescue provides some conveniences around exceptions
that allows one to rescue an exception by its name and not by
its internal contents. All the following formats are valid
rescue expressions:
Besides relying on pattern matching, rescue clauses provides some
conveniences around exceptions that allows one to rescue an
exception by its name. All the following formats are valid rescue
expressions:
try do
UndefinedModule.undefined_function
@@ -2108,29 +2124,9 @@ defmodule Kernel do
x -> nil
end
## Variable visibility
Since an expression inside `try` may not have been evaluted
due to an exception, any variable created inside `try` cannot
be accessed externaly.
For instance:
try do
x = 1
do_something_that_may_fail(same_arg)
:ok
catch
_ | _ -> :failed
end
x #=> Cannot access `x`
In the example above, `x` cannot be accessed since it was defined
inside the `try` clause.
## Catching exits and Erlang errors
The catch clause works exactly the same as in : Therefore,
The catch clause works exactly the same as in erlang. Therefore,
one can also handle exits/errors coming from Erlang as below:
try do
@@ -2148,6 +2144,36 @@ defmodule Kernel do
Although the second form should be avoided in favor of raise/rescue
control mechanisms.
## Variable visibility
Since an expression inside `try` may not have been evaluted
due to an exception, any variable created inside `try` cannot
be accessed externaly. For instance:
try do
x = 1
do_something_that_may_fail(same_arg)
:ok
catch
_, _ -> :failed
end
x #=> Cannot access `x`
In the example above, `x` cannot be accessed since it was defined
inside the `try` clause. A common practice to address this issue
is to return the variables defined inside `try`:
x =
try do
x = 1
do_something_that_may_fail(same_arg)
x
catch
_, _ -> :failed
end
"""
defmacro try(args)
@@ -2672,6 +2698,21 @@ defmodule Kernel do
Enum.map(List.flatten([1,[2],3]), &1 * 2)
Please be aware of operator precendence, when using
this operator. For example, the following expression:
String.graphemes "Hello" /> Enum.reverse
Is translated to:
String.graphemes("Hello" /> Enum.reverse)
Which will result in an error as Enum.Iterator protocol
is not defined for binaries. Adding explicit parenthesis
is recommended:
String.graphemes("Hello") /> Enum.reverse
"""
defmacro left /> right do
pipeline_op(left, right)
@@ -2716,9 +2757,9 @@ defmodule Kernel do
end
"""
@spec raise(binary | atom | tuple), do: no_return
@spec raise(binary | atom | tuple) :: no_return
def raise(msg) when is_binary(msg) do
:erlang.error RuntimeError.new(message: msg)
:erlang.error RuntimeError[message: msg]
end
def raise(exception) do
@@ -2735,14 +2776,23 @@ defmodule Kernel do
Any module defined via `defexception` automatically
defines `exception(args)` that returns a new instance
of the record and a `exception(args, current)` that
works as no-op.
updates the current exception.
Re-raising an exception will retrieve the previous
stacktrace so it keps the properties of the original
exception.
## Examples
raise ArgumentError, message: "Sample"
"""
@spec raise(tuple | atom, list), do: no_return
@spec raise(tuple | atom, list) :: no_return
def raise(exception, args) when is_tuple(exception) and tuple_size(exception) > 1 and
:erlang.element(2, exception) == :__exception__ do
:erlang.raise(:error, exception.exception(args), :erlang.get_stacktrace)
end
def raise(exception, args) do
:erlang.error exception.exception(args)
end
@@ -2758,7 +2808,7 @@ defmodule Kernel do
it is not loaded. Check `Code.ensure_loaded/1` for more
information.
"""
@spec function_exported?(atom | tuple, atom, integer), do: boolean
@spec function_exported?(atom | tuple, atom, integer) :: boolean
def function_exported?(module, function, arity) do
case is_tuple(module) do
true ->
@@ -2768,6 +2818,19 @@ defmodule Kernel do
end
end
@doc """
Returns true if the `module` is loaded and contains a
public `macro` with the given `arity`, otherwise false.
Notice that this function does not load the module in case
it is not loaded. Check `Code.ensure_loaded/1` for more
information.
"""
@spec macro_exported?(atom, atom, integer) :: boolean
def macro_exported?(module, macro, arity) do
:lists.member({macro, arity}, module.__info__(:macros))
end
@doc """
Access the given element using the qualifier according
to the `Access` protocol. All calls in the form `foo[bar]`
@@ -2927,7 +2990,10 @@ defmodule Kernel do
append_first = Keyword.get(opts, :append_first, false)
lc fun inlist funs do
{ name, args } = :elixir_clauses.extract_args(fun)
case Macro.extract_args(fun) do
{ name, args } -> :ok
:error -> raise ArgumentError, message: "invalid syntax in defdelegate #{Macro.to_binary(fun)}"
end
actual_args =
case append_first and args != [] do
@@ -3017,7 +3083,6 @@ defmodule Kernel do
Regex.match? %r(foo), "foo" #=> true
"""
defmacro __r__({ :<<>>, _line, [string] }, options) when is_binary(string) do
binary = Macro.unescape_binary(string, Regex.unescape_map(&1))
regex = Regex.compile!(binary, options)
+60 -36
View File
@@ -1,27 +1,45 @@
defrecord Kernel.CLI.Config, commands: [], output: ".",
compile: [], halt: true, compiler_options: []
defmodule Kernel.CLI do
@moduledoc false
@doc """
Invoked directly from erlang boot process. It parses all argv
options and execute them in the order they are specified.
@moduledoc """
Module responsible for controlling Elixir's CLI
"""
def process_argv(options) do
{ config, argv } = process_options(options, Kernel.CLI.Config.new)
defrecord Config, commands: [], output: ".",
compile: [], halt: true, compiler_options: []
# This is the API invoked by Elixir boot process.
@doc false
def main(options) do
{ config, argv } = process_argv(options, Kernel.CLI.Config.new)
argv = lc arg inlist argv, do: list_to_binary(arg)
:gen_server.call(:elixir_code_server, { :argv, argv })
all_commands = Enum.reverse(config.commands)
run fn -> Enum.map all_commands, process_command(&1, config) end, config.halt
run fn ->
Enum.map Enum.reverse(config.commands), process_command(&1, config)
:gen_server.cast(:elixir_code_server, :finished)
end, config.halt
end
@doc """
Runs the given function and wraps any exception or
error in messages useful for the command line.
Wait until the CLI finishes procesing options.
"""
def wait_until_finished do
case :gen_server.call(:elixir_code_server, { :wait_until_finished, self }) do
:wait ->
receive do
{ :elixir_code_server, :finished } -> :ok
end
:ok -> :ok
end
end
@doc """
Runs the given function by catching any failure
and printing them to stdout. `at_exit` hooks are
also invoked before exiting.
This function is used by Elixir's CLI and also
by escripts generated by Elixir.
"""
def run(fun, halt // true) do
try do
@@ -56,7 +74,8 @@ defmodule Kernel.CLI do
## Private
defp at_exit(status) do
hooks = :gen_server.call(:elixir_code_server, :at_exit)
hooks = :gen_server.call(:elixir_code_server, :flush_at_exit)
lc hook inlist hooks do
try do
hook.(status)
@@ -72,6 +91,10 @@ defmodule Kernel.CLI do
IO.puts Exception.formatted_stacktrace(trace)
end
end
# If an at_exit callback adds a
# new hook we need to invoke it.
unless hooks == [], do: at_exit(status)
end
defp invalid_option(option) do
@@ -100,7 +123,7 @@ defmodule Kernel.CLI do
end
defp process_shared(['-e',h|t], config) do
process_shared t, config.prepend_commands [eval: h]
process_shared t, config.update_commands [{:eval,h}|&1]
end
defp process_shared(['-pa',h|t], config) do
@@ -116,14 +139,14 @@ defmodule Kernel.CLI do
defp process_shared(['-r',h|t], config) do
h = list_to_binary(h)
config = Enum.reduce File.wildcard(h), config, fn path, config ->
config.prepend_commands [require: path]
config.update_commands [{:require,path}|&1]
end
process_shared t, config
end
defp process_shared(['-pr',h|t], config) do
h = list_to_binary(h)
process_shared t, config.prepend_commands [parallel_require: h]
process_shared t, config.update_commands [{:parallel_require,h}|&1]
end
defp process_shared([erl,_|t], config) when erl in ['--erl', '--sname', '--remsh', '--name'] do
@@ -136,35 +159,36 @@ defmodule Kernel.CLI do
# Process init options
def process_options(['--'|t], config) do
defp process_argv(['--'|t], config) do
{ config, t }
end
def process_options(['--compile'|t], config) do
defp process_argv(['--compile'|t], config) do
process_compiler t, config
end
def process_options(['-S',h|t], config) do
defp process_argv(['-S',h|t], config) do
exec = System.find_executable(h)
if exec do
{ config.prepend_commands([require: list_to_binary(exec)]), t }
bin = list_to_binary(exec)
{ config.update_commands([{:require,bin}|&1]), t }
else
IO.puts(:stderr, "Could not find executable #{h}")
System.halt(1)
end
end
def process_options([h|t] = list, config) do
defp process_argv([h|t] = list, config) do
case h do
'-' ++ _ ->
shared_option? list, config, process_options(&1, &2)
shared_option? list, config, process_argv(&1, &2)
_ ->
h = list_to_binary(h)
{ config.prepend_commands([require: h]), t }
bin = list_to_binary(h)
{ config.update_commands([{:require,bin}|&1]), t }
end
end
def process_options([], config) do
defp process_argv([], config) do
{ config, [] }
end
@@ -179,15 +203,15 @@ defmodule Kernel.CLI do
end
defp process_compiler(['--no-docs'|t], config) do
process_compiler t, config.merge_compiler_options(docs: false)
process_compiler t, config.update_compiler_options([{:docs,false}|&1])
end
defp process_compiler(['--debug-info'|t], config) do
process_compiler t, config.merge_compiler_options(debug_info: true)
defp process_compiler(['--no-debug-info'|t], config) do
process_compiler t, config.update_compiler_options([{:debug_info,false}|&1])
end
defp process_compiler(['--ignore-module-conflict'|t], config) do
process_compiler t, config.merge_compiler_options(ignore_module_conflict: true)
process_compiler t, config.update_compiler_options([{:ignore_module_conflict,true}|&1])
end
defp process_compiler([h|t] = list, config) do
@@ -197,12 +221,12 @@ defmodule Kernel.CLI do
_ ->
h = list_to_binary(h)
pattern = if File.dir?(h), do: "#{h}/**/*.ex", else: h
process_compiler t, config.prepend_compile [pattern]
process_compiler t, config.update_compile [pattern|&1]
end
end
defp process_compiler([], config) do
{ config.prepend_commands([compile: config.compile]), [] }
{ config.update_commands([{:compile,config.compile}|&1]), [] }
end
# Process commands
@@ -217,7 +241,7 @@ defmodule Kernel.CLI do
defp process_command({:parallel_require, pattern}, _config) when is_binary(pattern) do
files = File.wildcard(pattern)
files = List.uniq(files)
files = Enum.uniq(files)
files = Enum.filter files, File.regular?(&1)
Kernel.ParallelRequire.files(files)
end
@@ -226,7 +250,7 @@ defmodule Kernel.CLI do
File.mkdir_p(config.output)
files = Enum.map patterns, File.wildcard(&1)
files = List.uniq(List.concat(files))
files = Enum.uniq(List.concat(files))
files = Enum.filter files, File.regular?(&1)
Code.compiler_options(config.compiler_options)
+374
View File
@@ -0,0 +1,374 @@
# This is an optimization Elixir runs on function clauses.
# Whenever a variables matches against a record (a tagged
# tuple), this information is stored in order to optimize
# record calls.
defmodule Kernel.RecordRewriter do
@moduledoc false
def optimize_clause(clause) do
optimize_clause(clause, :orddict.new)
end
## Clause
defp optimize_clause({ :clause, line, args, guards, body }, dict) do
{ args, dict } = optimize_args(args, dict)
{ body, dict, res } = optimize_body(body, dict, [])
{ { :clause, line, args, guards, body }, dict, res }
end
defp optimize_args(args, dict) do
Enum.map_reduce args, dict, fn(arg, acc) ->
{ new_arg, new_acc, _res } = optimize_expr(arg, acc)
{ new_arg, new_acc }
end
end
defp optimize_body([], dict, _acc) do
{ [], dict, nil }
end
defp optimize_body([h], dict, acc) do
{ new_expr, new_dict, new_res } = optimize_expr(h, dict)
{ Enum.reverse([new_expr|acc]), new_dict, new_res }
end
defp optimize_body([h|t], dict, acc) do
{ new_expr, new_dict, _ } = optimize_expr(h, dict)
optimize_body(t, new_dict, [new_expr|acc])
end
## Record helpers
defp record_fields(record) do
if Code.ensure_loaded?(record) && function_exported?(record, :__record__, 1) do
try do
fields = lc { k, _ } inlist record.__record__(:fields), do: k
optimizable = record.__record__(:optimizable)
{ fields, optimizable }
rescue
[UndefinedFunctionError, FunctionClauseError] -> nil
end
end
end
defp record_field_info(function) do
case atom_to_list(function) do
'update_' ++ field -> { :update, list_to_atom(field) }
_ -> { :accessor, function }
end
end
defp optimize_call(line, { record, _ } = res, left, { :atom, _, function }, args) do
case record_fields(record) do
{ fields, optimizable } ->
if List.member?(optimizable, { function, length(args) + 1 }) do
{ kind, field } = record_field_info(function)
if index = Enum.find_index(fields, field == &1) do
optimize_call(line, res, kind, field, index, left, args)
end
end
nil -> nil
end
end
defp optimize_call(_line, _res, _left, _right, _args) do
nil
end
defp optimize_call(line, _res, :accessor, _field, index, left, []) do
call = { :call, line,
{ :remote, line, { :atom, 0, :erlang }, { :atom, 0, :element } },
[{ :integer, 0, index + 2 }, left]
}
{ call, nil }
end
defp optimize_call(line, res, :accessor, _field, index, left, [arg]) do
call = { :call, line,
{ :remote, line, { :atom, 0, :erlang }, { :atom, 0, :setelement } },
[{ :integer, 0, index + 2 }, left, arg]
}
{ call, res }
end
defp optimize_call(line, res, :update, field, _index, left, args) do
call = { :call, line,
{ :remote, line, left, { :atom, 0, :"update_#{field}" } },
args
}
{ call, res }
end
## Expr
defp optimize_expr({ :call, call_line, { :remote, line, left, right }, args }, dict) do
{ left, dict, res } = optimize_expr(left, dict)
{ right, dict, _ } = optimize_expr(right, dict)
{ args, dict } = optimize_args(args, dict)
case optimize_call(call_line, res, left, right, args) do
{ call, call_res } ->
{ call, dict, call_res }
nil ->
{ { :call, call_line, { :remote, line, left, right }, args }, dict, nil }
end
end
defp optimize_expr({ :call, line, expr, args }, dict) do
{ expr, dict, _ } = optimize_expr(expr, dict)
{ args, dict } = optimize_args(args, dict)
{ { :call, line, expr, args }, dict, nil }
end
defp optimize_expr({ :match, line, left, right }, dict) do
{ left, dict, left_res } = optimize_expr(left, dict)
{ right, dict, right_res } = optimize_expr(right, dict)
match = { :match, line, left, right }
if left_res do
dict = assign_vars(extract_vars(right, []), dict, left_res)
end
if right_res do
dict = assign_vars(extract_vars(left, []), dict, right_res)
end
{ match, dict, right_res || left_res }
end
defp optimize_expr({ :op, line, op, left, right }, dict) do
{ left, dict, _ } = optimize_expr(left, dict)
{ right, dict, _ } = optimize_expr(right, dict)
{ { :op, line, op, left, right }, dict, nil }
end
defp optimize_expr({ :op, line, op, expr }, dict) do
{ expr, dict, _ } = optimize_expr(expr, dict)
{ { :op, line, op, expr }, dict, nil }
end
defp optimize_expr({ :bin, line, elements }, dict) do
{ elements, dict } = optimize_args(elements, dict)
{ { :bin, line, elements }, dict, nil }
end
defp optimize_expr({ :bin_element, line, expr, type1, type2 }, dict) do
{ expr, dict, _ } = optimize_expr(expr, dict)
{ { :bin_element, line, expr, type1, type2 }, dict, nil }
end
defp optimize_expr({ :cons, line, left, right }, dict) do
{ left, dict, _ } = optimize_expr(left, dict)
{ right, dict, _ } = optimize_expr(right, dict)
{ { :cons, line, left, right }, dict, nil }
end
defp optimize_expr({ :block, line, args }, dict) do
{ args, dict, res } = optimize_body(args, dict, [])
{ { :block, line, args }, dict, res }
end
defp optimize_expr({ :tuple, line, args }, dict) do
{ args, dict, args_res } = optimize_tuple_args(args, dict)
args_res = if Enum.any?(args_res), do: args_res, else: nil
res =
case args do
[{ :atom, _, atom }|t] -> atom
_ -> nil
end
{ { :tuple, line, args }, dict, { res, args_res } }
end
defp optimize_expr({ :var, _, name } = var, dict) do
case :orddict.find(name, dict) do
{ :ok, res } -> { var, dict, res }
:error -> { var, dict, nil }
end
end
defp optimize_expr({ :case, line, expr, clauses }, dict) do
{ expr, dict, _ } = optimize_expr(expr, dict)
tuples = lc clause inlist clauses, do: optimize_clause(clause, dict)
clauses = lc { clause, _, _ } inlist tuples, do: clause
dict = join_dict(tuples)
res = join_result(tuples)
{ { :case, line, expr, clauses }, dict, res }
end
defp optimize_expr({ :receive, line, clauses }, dict) do
tuples = lc clause inlist clauses, do: optimize_clause(clause, dict)
clauses = lc { clause, _, _ } inlist tuples, do: clause
dict = join_dict(tuples)
res = join_result(tuples)
{ { :receive, line, clauses }, dict, res }
end
defp optimize_expr({ :receive, line, clauses, after_key, after_value }, dict) do
tuples = lc clause inlist clauses, do: optimize_clause(clause, dict)
clauses = lc { clause, _, _ } inlist tuples, do: clause
{ after_key, dict, _ } = optimize_expr(after_key, dict)
{ after_value, dict, res } = optimize_body(after_value, dict, [])
dict = join_dict(tuples, dict)
res = join_result(tuples, res)
{ { :receive, line, clauses, after_key, after_value }, dict, res }
end
defp optimize_expr({ :try, line, body, [], clauses, try_after }, dict) do
tuples = lc clause inlist clauses, do: optimize_clause(clause, dict)
clauses = lc { clause, _, _ } inlist tuples, do: clause
{ body, _, res } = optimize_body(body, dict, [])
res = join_result(tuples, res)
{ try_after, _, _ } = optimize_body(try_after, dict, [])
{ { :try, line, body, [], clauses, try_after }, dict, res }
end
defp optimize_expr({ :fun, line, { :function, module, name, arity } }, dict) do
{ module, dict, _ } = optimize_expr(module, dict)
{ name, dict, _ } = optimize_expr(name, dict)
{ arity, dict, _ } = optimize_expr(arity, dict)
{ { :fun, line, { :function, module, name, arity } }, dict, nil }
end
defp optimize_expr({ :fun, line, { :clauses, clauses } }, dict) do
clauses = lc clause inlist clauses do
{ clause, _, _ } = optimize_clause(clause, dict)
clause
end
{ { :fun, line, { :clauses, clauses } }, dict, nil }
end
defp optimize_expr({ comprehension, line, expr, args }, dict) when comprehension in [:lc, :bc] do
{ args, new_dict } = optimize_args(args, dict)
{ expr, _, _ } = optimize_expr(expr, new_dict)
{ { comprehension, line, expr, args }, dict, nil }
end
defp optimize_expr({ generate, line, left, right }, dict) when generate in [:generate, :b_generate] do
{ left, dict, _ } = optimize_expr(left, dict)
{ right, dict, _ } = optimize_expr(right, dict)
{ { generate, line, left, right }, dict, nil }
end
defp optimize_expr(other, dict) when elem(other, 0) in [:string, :atom, :integer, :float, :nil, :fun] do
{ other, dict, nil }
end
## Helpers
defp optimize_tuple_args(args, dict) do
{ final_args, { final_dict, final_acc } } =
Enum.map_reduce args, { dict, [] }, fn(arg, { acc_dict, acc_res }) ->
{ new_arg, new_acc, res } = optimize_expr(arg, acc_dict)
{ new_arg, { new_acc, [res|acc_res] } }
end
{ final_args, final_dict, Enum.reverse(final_acc) }
end
defp assign_vars([key|t], dict, { _, value } = res) when is_list(key) and is_list(value) and length(key) == length(value) do
assign_vars t, assign_nested_vars(key, dict, value), res
end
defp assign_vars([key|t], dict, { value, _ } = res) when is_atom(key) and value != nil do
dict =
case :orddict.find(key, dict) do
{ :ok, ^res } ->
dict
{ :ok, { ^value, _ } } ->
:orddict.store(key, { value, nil }, dict)
{ :ok, _ } ->
# We are overriding a type of an existing variable,
# which means the source code is invalid.
:orddict.store(key, nil, dict)
:error ->
:orddict.store(key, res, dict)
end
assign_vars t, dict, res
end
defp assign_vars([_|t], dict, res) do
assign_vars t, dict, res
end
defp assign_vars([], dict, _res) do
dict
end
defp assign_nested_vars([vars|vt], dict, [res|rt]) do
assign_nested_vars(vt, assign_vars(vars, dict, res), rt)
end
defp assign_nested_vars([], dict, []) do
dict
end
defp extract_vars({ :match, _, left, right }, vars) do
vars = extract_vars(right, vars)
extract_vars(left, vars)
end
defp extract_vars({ :var, _, name }, vars) do
[name|vars]
end
defp extract_vars({ :tuple, _, args }, vars) do
[Enum.map(args, extract_vars(&1, []))|vars]
end
defp extract_vars(_, vars) do
vars
end
defp join_dict([{ _, dict, _ }|t]) do
join_dict(t, dict)
end
defp join_dict([{ _, dict, _ }|t], other) do
other = Enum.reduce other, other, fn
{ key, { value, _ } = res }, acc ->
case :orddict.find(key, dict) do
{ :ok, ^res } -> acc
{ :ok, { ^value, _ } } -> :orddict.store(key, { value, nil }, acc)
{ :ok, _ } -> :orddict.store(key, nil, acc)
:error -> :orddict.erase(key, acc)
end
end
join_dict(t, other)
end
defp join_dict([], other) do
other
end
defp join_result([{ _, _, res }|t]) do
join_result(t, res)
end
defp join_result([{ _, _, res }|t], res) do
join_result(t, res)
end
defp join_result([{ _, _, { res, _ } }|t], { res, _ }) do
join_result(t, { res, nil })
end
defp join_result([{ _, _, _ }|_], _res) do
nil
end
defp join_result([], res) do
res
end
end
+2 -2
View File
@@ -56,7 +56,7 @@ defmodule Kernel.SpecialForms do
alias MyKeyword, as: Keyword
end
In the example above, we have set up `MyOrdict` to be alias
In the example above, we have set up `MyKeyword` to be alias
as `Keyword`. So now, any reference to `Keyword` will be
automatically replaced by `MyKeyword`.
@@ -276,7 +276,7 @@ defmodule Kernel.SpecialForms do
a = 10
NoHygiene.interference
a #=> 11
a #=> 1
Notice that aliases are not hygienic in Elixir, ambiguity
must be solved by prepending Elixir:
+184 -37
View File
@@ -6,6 +6,81 @@ defmodule Kernel.Typespec do
`@callback` available in modules are handled by the equivalent
macros defined by this module.
## Defining a type
@type type_name :: type
@typep type_name :: type
@opaque type_name :: type
For more details, see documentation for deftype, deftypep and defopaque in
Kernel.Typespec
## Defining a specification
@spec function_name(type, type) :: type
@callback function_name(type, type) :: type
For more details, see documentation for defspec and defcallback in
Kernel.Typespec
## Types
The type syntax provided by Elixir is fairly similar to the one
in Erlang.
Most of the built-in types provided in Erlang (for example, `pid()`)
are expressed the same way: `pid()` or simply `pid`. Parametrized types
are also supported: `list(integer())` and so are remote types: `Enum.t`.
Certain data type shortcuts ([...], <<>> and {...}) are supported as well.
Main differences lie in how bit strings and functions are defined:
### Bit Strings
Bit string with a base size of 3:
<<_ :: 3>>
Bit string with a unit size of 8:
<<_ :: _ * 8>>
### Functions
Any function:
(fun(...) -> any)
or
((...) -> any)
or
(... -> any)
Function with arity of zero:
(fun() -> type)
or
(() -> type)
Function with some arity:
(fun(type, type) -> type)
or
((type, type) -> type)
or
(type, type -> type)
## Notes
Elixir discourages the use of type `string()` as it might be confused
with binaries which are referred to as "strings" in Elixir (as opposed to
character lists). In order to use the type that is called `string()` in Erlang,
one has to use the `char_list()` type which is a synonym to `string()`. If yu
use `string()`, you'll get a warning from the compiler.
If you want to refer to the "string" type (the one operated by functions in the
String module), use `String.t()` type instead.
See http://www.erlang.org/doc/reference_manual/typespec.html
for more information.
"""
@@ -55,19 +130,34 @@ defmodule Kernel.Typespec do
end
end
@doc false
defmacro defspec(spec, [do: block]) do
IO.write "[WARNING] @spec f(...), do: type is deprecated, use @spec f(...) :: type\n#{Exception.env_stacktrace(__CALLER__)}"
quote do
Kernel.Typespec.defspec(:spec, (quote line: :keep, do: unquote(spec) :: unquote(block)), __ENV__)
end
end
@doc """
Defines a spec.
This macro is the one responsible to handle the attribute @spec.
## Examples
@spec add(number, number), do: number
@spec add(number, number) :: number
"""
defmacro defspec(spec, block) do
defmacro defspec(spec) do
quote do
Kernel.Typespec.defspec(:spec, (quote line: :keep, do: unquote spec),
(quote line: :keep, do: unquote block), __ENV__)
Kernel.Typespec.defspec(:spec, (quote line: :keep, do: unquote spec), __ENV__)
end
end
@doc false
defmacro defcallback(spec, [do: block]) do
IO.write "[WARNING] @callback f(...), do: type is deprecated, use @callback f(...) :: type\n#{Exception.env_stacktrace(__CALLER__)}"
quote do
Kernel.Typespec.defspec(:callback, (quote line: :keep, do: unquote(spec) :: unquote(block)), __ENV__)
end
end
@@ -77,13 +167,12 @@ defmodule Kernel.Typespec do
## Examples
@callback add(number, number), do: number
@callback add(number, number) :: number
"""
defmacro defcallback(spec, block) do
defmacro defcallback(spec) do
quote do
Kernel.Typespec.defspec(:callback, (quote line: :keep, do: unquote spec),
(quote line: :keep, do: unquote block), __ENV__)
Kernel.Typespec.defspec(:callback, (quote line: :keep, do: unquote(spec)), __ENV__)
end
end
@@ -101,11 +190,8 @@ defmodule Kernel.Typespec do
end
Module.compile_typespec module, kind, type
if export do
if export, do:
Module.compile_typespec module, :export_type, [{ name, length(vars) }]
end
type
end
@@ -154,15 +240,26 @@ defmodule Kernel.Typespec do
@doc """
Converts a spec clause back to Elixir AST.
Returns a 2-items tuple with the spec arguments and return result.
"""
def spec_to_ast({ :type, _line, :fun, [{:type, _, :product, args},result] }) do
def spec_to_ast(name, { :type, line, :fun, [{:type, _, :product, args},result] }) do
args = lc arg inlist args, do: typespec_to_ast(arg)
{ args, typespec_to_ast(result) }
{ :::, line, [{ name, line, args }, typespec_to_ast(result)] }
end
def spec_to_ast({ :type, _, :fun, [] }) do
{ [], quote do: term }
def spec_to_ast(name, { :type, line, :fun, [] }) do
{ :::, line, [{ name, line, [] }, quote(do: term)] }
end
def spec_to_ast(name, { :type, line, :bounded_fun, [{ :type, _, :fun, [{ :type, _, :product, args }, result] }, constraints] }) do
[h|t] =
lc {:type, line, :constraint, [{:atom, _, :is_subtype}, [var, type]]} inlist constraints do
{ :is_subtype, line, [typespec_to_ast(var), typespec_to_ast(type)] }
end
args = lc arg inlist args, do: typespec_to_ast(arg)
guards = Enum.reduce t, h, fn(x, acc) -> { :and, line, [acc, x] } end
{ :::, line, [{ :when, line, [{ name, line, args }, guards] }, typespec_to_ast(result)] }
end
@doc """
@@ -174,6 +271,7 @@ defmodule Kernel.Typespec do
type = { :{}, 0, [record|fields] }
quote do: unquote(record)(unquote_splicing(args)) :: unquote(type)
end
def type_to_ast({ name, type, args }) do
args = lc arg inlist args, do: typespec_to_ast(arg)
quote do: unquote(name)(unquote_splicing(args)) :: unquote(typespec_to_ast(type))
@@ -290,14 +388,33 @@ defmodule Kernel.Typespec do
end
@doc false
def defspec(type, { name, line, args }, [do: return], caller) do
def defspec(type, {:::, _, [{ :when, _, [{ name, line, args }, constraints_guard] }, return] }, caller) do
if is_atom(args), do: args = []
spec = { :type, line, :fun, fn_args(line, args, return, [], caller) }
code = { { name, Kernel.length(args) }, spec }
constraints = guard_to_constraints(constraints_guard, caller)
spec = { :type, line, :fun, fn_args(line, args, return, Keyword.keys(constraints), caller) }
spec = { :type, line, :bounded_fun, [spec, Keyword.values(constraints)] }
code = { { name, Kernel.length(args) }, spec }
Module.compile_typespec(caller.module, type, code)
code
end
def defspec(type, {:::, _, [{ name, line, args }, return]}, caller) do
if is_atom(args), do: args = []
spec = { :type, line, :fun, fn_args(line, args, return, [], caller) }
code = { { name, Kernel.length(args) }, spec }
Module.compile_typespec(caller.module, type, code)
code
end
defp guard_to_constraints({ :is_subtype, line, [{ name, _, _ }, type] }, caller) do
contraints = [{ :atom, line, :is_subtype }, [{:var, line, name}, typespec(type, [], caller)]]
[{ name, { :type, line, :constraint, contraints } }]
end
defp guard_to_constraints({ :and, _, [left, right] }, caller) do
guard_to_constraints(left, caller) ++ guard_to_constraints(right, caller)
end
## To AST conversion
defp typespec_to_ast({ :type, line, :tuple, :any }) do
@@ -329,11 +446,15 @@ defmodule Kernel.Typespec do
defp typespec_to_ast({ :type, line, :fun, [{:type, _, :product, args},result] }) do
args = lc arg inlist args, do: typespec_to_ast(arg)
{ :fun, line, args ++ [[do: typespec_to_ast(result)]] }
{ :"->", line, [{args, typespec_to_ast(result)}] }
end
defp typespec_to_ast({ :type, line, :fun, [args, result] }) do
{ :"->", line, [{[typespec_to_ast(args)], typespec_to_ast(result)}] }
end
defp typespec_to_ast({ :type, line, :fun, [] }) do
{ :fun, line, [] }
typespec_to_ast({ :type, line, :fun, [{:type, line, :any}, {:type,line,:any, []} ] })
end
defp typespec_to_ast({ :type, line, name, args }) do
@@ -342,9 +463,21 @@ defmodule Kernel.Typespec do
end
defp typespec_to_ast({ :var, line, var }) do
var =
case atom_to_binary(var) do
<<"_", c :: [binary, size(1)], rest :: binary>> ->
binary_to_atom("_#{String.downcase(c)}#{rest}")
<<c :: [binary, size(1)], rest :: binary>> ->
binary_to_atom("#{String.downcase(c)}#{rest}")
end
{ var, line, nil }
end
# special shortcut(s)
defp typespec_to_ast({ :remote_type, line, [{:atom, _, :elixir}, {:atom, _, :char_list}, []] }) do
typespec_to_ast({:type, line, :char_list, []})
end
defp typespec_to_ast({ :remote_type, line, [mod, name, args] }) do
args = lc arg inlist args, do: typespec_to_ast(arg)
dot = { :., line, [typespec_to_ast(mod), typespec_to_ast(name)] }
@@ -361,11 +494,20 @@ defmodule Kernel.Typespec do
typespec_to_ast({ :ann_type, line, [{ :var, line1, name }, type] })
end
defp typespec_to_ast({:type, _, :any}) do
quote do: ...
end
defp typespec_to_ast({:paren_type, _, [type]}) do
typespec_to_ast(type)
end
defp typespec_to_ast({ t, _line, atom }) when is_atom(t) do
atom
end
defp typespec_to_ast(other), do: other
## From AST conversion
# Handle unions
@@ -403,6 +545,19 @@ defmodule Kernel.Typespec do
typespec(atom, vars, caller)
end
# Handle funs
defp typespec({:fun, line, args}, vars, caller) when is_list(args) do
IO.write "[WARNING] fun() type is deprecated, use (... -> type) instead\n#{Exception.env_stacktrace(caller)}"
typespec({:"->", line, [{args, quote do: any}]}, vars, caller)
end
defp typespec({:"->", line, [{[{:fun, _, arguments}], return}]}, vars, caller) when is_list(arguments) do
typespec({:"->", line, [{arguments, return}]}, vars, caller)
end
defp typespec({:"->", line, [{arguments, return}]}, vars, caller) when is_list(arguments) do
args = fn_args(line, arguments, return, vars, caller)
{ :type, line, :fun, args }
end
# Handle type operator
defp typespec({:"::", line, [var, expr] }, vars, caller) do
left = typespec(var, [elem(var, 0)|vars], caller)
@@ -425,7 +580,7 @@ defmodule Kernel.Typespec do
# Handle remote calls
defp typespec({{:., line, [remote, name]}, _, args}, vars, caller) do
remote = Macro.expand remote, caller
unless is_atom(remote), do: raise(ArgumentError, message: "Invalid remote in typespec")
unless is_atom(remote), do: raise ArgumentError, message: "invalid remote in typespec"
remote_type({typespec(remote, vars, caller), line, typespec(name, vars, caller), args}, vars, caller)
end
@@ -443,18 +598,6 @@ defmodule Kernel.Typespec do
{ :type, line, :tuple, args }
end
# Handle funs
defp typespec({:fun, line, arguments}, vars, caller) when is_list(arguments) do
args =
case :lists.reverse(arguments) do
[[{:do,h}]|t] -> fn_args(line, :lists.reverse(t), h, vars, caller)
[] -> []
_ -> [fn_args(line, arguments, vars, caller)]
end
{ :type, line, :fun, args }
end
# Handle variables or local calls
defp typespec({name, line, atom}, vars, caller) when is_atom(atom) do
if List.member?(vars, name) do
@@ -466,7 +609,8 @@ defmodule Kernel.Typespec do
# Handle local calls
defp typespec({:string, line, arguments}, vars, caller) do
IO.write "#{caller.file}:#{caller.line}: warning: string() type use is discouraged. For character lists, use char_list() type, for strings, String.t()\n"
IO.write "warning: string() type use is discouraged. For character lists, use " <>
"char_list() type, for strings, String.t()\n#{Exception.env_stacktrace(caller)}"
arguments = lc arg inlist arguments, do: typespec(arg, vars, caller)
{ :type, line, :string, arguments }
end
@@ -517,7 +661,10 @@ defmodule Kernel.Typespec do
defp collect_union(v), do: [v]
defp fn_args(line, args, return, vars, caller) do
[fn_args(line, args, vars, caller), typespec(return, vars, caller)]
case [fn_args(line, args, vars, caller), typespec(return, vars, caller)] do
[{:type,_,:any},{:type,_,:any,[]}] -> []
x -> x
end
end
defp fn_args(line, [{:"...", _, _}], _vars, _caller) do
+19 -19
View File
@@ -22,7 +22,7 @@ defmodule Keyword do
which behaves as a dict, `Keyword.from_enum` do not remove
duplicated entries.
"""
@spec from_enum(Enum.t), do: t
@spec from_enum(Enum.t) :: t
def from_enum(enum) when is_list(enum) do
enum
end
@@ -34,7 +34,7 @@ defmodule Keyword do
@doc """
Checks if the given argument is a keywords list or not
"""
@spec keyword?(term), do: boolean
@spec keyword?(term) :: boolean
def keyword?([{ key, _value } | rest]) when is_atom(key) do
case atom_to_list(key) do
'Elixir-' ++ _ -> false
@@ -48,7 +48,7 @@ defmodule Keyword do
@doc """
Returns an empty keyword list, i.e. an empty list.
"""
@spec new, do: t
@spec new :: t
def new do
[]
end
@@ -63,7 +63,7 @@ defmodule Keyword do
#=> [a: 2, b: 1]
"""
@spec new(Enum.t), do: t
@spec new(Enum.t) :: t
def new(pairs) do
Enum.reduce pairs, [], fn { k, v }, keywords ->
put(keywords, k, v)
@@ -81,7 +81,7 @@ defmodule Keyword do
#=> [a: :a, b: :b]
"""
@spec new(Enum.t, fun({key, value}, do: {key, value})), do: t
@spec new(Enum.t, ({key, value} -> {key, value})) :: t
def new(pairs, transform) do
Enum.reduce pairs, [], fn i, keywords ->
{ k, v } = transform.(i)
@@ -105,8 +105,8 @@ defmodule Keyword do
Keyword.get [a: 1], :b, 3 #=> 3
"""
@spec get(t, key), do: value
@spec get(t, key, value), do: value
@spec get(t, key) :: value
@spec get(t, key, value) :: value
def get(keywords, key, default // nil) when is_atom(key) do
case :lists.keyfind(key, 1, keywords) do
{ ^key, value } -> value
@@ -124,7 +124,7 @@ defmodule Keyword do
Keyword.get! [a: 1], :b #=> raises KeyError[key: :b]
"""
@spec get!(t, key), do: value | no_return
@spec get!(t, key) :: value | no_return
def get!(keywords, key) when is_atom(key) do
case :lists.keyfind(key, 1, keywords) do
{ ^key, value } -> value
@@ -141,7 +141,7 @@ defmodule Keyword do
#=> [1,2]
"""
@spec get_values(t, key), do: [value]
@spec get_values(t, key) :: [value]
def get_values(keywords, key) when is_atom(key) do
lc { k, v } inlist keywords, key == k, do: v
end
@@ -155,7 +155,7 @@ defmodule Keyword do
Keyword.keys [a: 1, b: 2] #=> [:a,:b]
"""
@spec keys(t), do: [key]
@spec keys(t) :: [key]
def keys(keywords) do
lc { key, _ } inlist keywords, do: key
end
@@ -168,7 +168,7 @@ defmodule Keyword do
Keyword.values [a: 1, b: 2] #=> [1,2]
"""
@spec values(t), do: [value]
@spec values(t) :: [value]
def values(keywords) do
lc { _, value } inlist keywords, do: value
end
@@ -185,7 +185,7 @@ defmodule Keyword do
Keyword.delete [b: 2], :a #=> [b: 2]
"""
@spec delete(t, key), do: t
@spec delete(t, key) :: t
def delete(keywords, key) when is_atom(key) do
lc { k, _ } = tuple inlist keywords, key != k, do: tuple
end
@@ -202,7 +202,7 @@ defmodule Keyword do
#=> [a: 3, b: 2]
"""
@spec put(t, key, value), do: t
@spec put(t, key, value) :: t
def put(keywords, key, value) when is_atom(key) do
[{key, value}|delete(keywords, key)]
end
@@ -217,7 +217,7 @@ defmodule Keyword do
#=> true
"""
@spec equal?(t, t), do: boolean
@spec equal?(t, t) :: boolean
def equal?(left, right) do
:lists.sort(left) == :lists.sort(right)
end
@@ -232,7 +232,7 @@ defmodule Keyword do
#=> [a:3, b:2, d: 4]
"""
@spec merge(t, t), do: t
@spec merge(t, t) :: t
def merge(d1, d2) do
d2 ++ lc({ k, _ } = tuple inlist d1, not has_key?(d2, k), do: tuple)
end
@@ -249,7 +249,7 @@ defmodule Keyword do
#=> [a:4, b:2, d: 4]
"""
@spec merge(t, t, fun(key, value, value, do: value)), do: t
@spec merge(t, t, (key, value, value -> value)) :: t
def merge(d1, d2, fun) do
do_merge(d2, d1, fun)
end
@@ -273,7 +273,7 @@ defmodule Keyword do
#=> false
"""
@spec has_key?(t, key), do: boolean
@spec has_key?(t, key) :: boolean
def has_key?(keywords, key) when is_atom(key) do
:lists.keymember(key, 1, keywords)
end
@@ -290,7 +290,7 @@ defmodule Keyword do
#=> KeyError
"""
@spec update(t, key, fun(value, do: value)), do: t | no_return
@spec update(t, key, (value -> value)) :: t | no_return
def update([{key, value}|keywords], key, fun) do
[{key, fun.(value)}|delete(keywords, key)]
end
@@ -315,7 +315,7 @@ defmodule Keyword do
#=> [a: 1, b: 11]
"""
@spec update(t, key, value, fun(value, do: value)), do: t
@spec update(t, key, value, (value -> value)) :: t
def update([{key, value}|keywords], key, _initial, fun) do
[{key, fun.(value)}|delete(keywords, key)]
end
+13 -53
View File
@@ -252,25 +252,11 @@ defmodule List do
:lists.keydelete(key, position + 1, list)
end
@doc """
Returns a list of integers in the given range (both ends included when
possible). An optional step can be provided as well (defaults to 1).
If first > last and no step is provided, the numbers will be in descending
order.
## Examples
List.range 1, 3 #=> [1,2,3]
List.range 1, 8, 2 #=> [1,3,5,7]
List.range 1, 0 #=> []
List.range 3, 1 #=> [3,2,1]
List.range 5, 1, -2 #=> [5, 3, 1]
"""
@doc false
def range(first, last, step // nil)
def range(first, last, step) when is_integer(first) and is_integer(last) and first <= last do
IO.write "[WARNING] List.range is deprecated, please use ranges instead\n#{Exception.formatted_stacktrace}"
case step do
nil ->
:lists.seq(first, last, 1)
@@ -282,6 +268,7 @@ defmodule List do
end
def range(first, last, step) when is_integer(first) and is_integer(last) and first > last do
IO.write "[WARNING] List.range is deprecated, please use ranges instead\n#{Exception.formatted_stacktrace}"
case step do
nil ->
:lists.seq(first, last, -1)
@@ -292,44 +279,21 @@ defmodule List do
end
end
@doc """
Sorts the list by comparing each term. For an alternative
sorting algorithm, check `Enum.qsort`.
## Examples
List.sort [3, 4, 2, 1, 7]
#=> [1, 2, 3, 4, 7]
"""
@doc false
def sort(list) do
IO.write "[WARNING] List.sort is deprecated, please use Enum.sort instead\n#{Exception.formatted_stacktrace}"
:lists.sort list
end
@doc """
Sorts the list according to an ordering function. fun(a, b) should
return true if `a` compares less than or equal to `b`, `false` otherwise.
## Examples
List.sort [3, 4, 2, 1, 7], fn a, b -> b <= a end
#=> [7, 4, 3, 2, 1]
"""
@doc false
def sort(list, fun) do
IO.write "[WARNING] List.sort is deprecated, please use Enum.sort instead\n#{Exception.formatted_stacktrace}"
:lists.sort fun, list
end
@doc """
Returns a list without duplicated items.
## Examples
List.uniq [1,2,3,2,1]
#=> [1,2,3]
"""
@doc false
def uniq(list) when is_list(list) do
IO.write "[WARNING] List.uniq is deprecated, please use Enum.uniq instead\n#{Exception.formatted_stacktrace}"
do_uniq(list, [])
end
@@ -395,10 +359,8 @@ defmodule List do
defp do_uniq([h|t], acc) do
case :lists.member(h, acc) do
true ->
do_uniq(t, acc)
false ->
[h|do_uniq(t, [h|acc])]
true -> do_uniq(t, acc)
false -> [h|do_uniq(t, [h|acc])]
end
end
@@ -413,10 +375,8 @@ defmodule List do
{mlist, heads} = :lists.mapfoldl converter, [], list
case heads do
nil ->
:lists.reverse acc
_ ->
do_zip mlist, [list_to_tuple(:lists.reverse(heads))|acc]
nil -> :lists.reverse acc
_ -> do_zip mlist, [list_to_tuple(:lists.reverse(heads))|acc]
end
end
+65 -4
View File
@@ -29,6 +29,28 @@ defmodule Macro do
[:!, :@, :^, :not, :+, :-]
end
@doc """
Receives an expresion representing a possible definition
and extracts its arguments. It returns a tuple with the
function name and the arguments list or `:error` if not
a valid call syntax.
This is useful for macros that want to provide the same
arguments syntax available in def/defp/defmacro and friends.
## Examples
extract_args(quote do: foo) == { :foo, [] }
extract_args(quote do: foo()) == { :foo, [] }
extract_args(quote do: :foo.()) == { :foo, [] }
extract_args(quote do: foo(1,2,3)) == { :foo, [1,2,3] }
extract_args(quote do: 1.(1,2,3)) == :error
"""
def extract_args(expr) do
:elixir_clauses.extract_args(expr)
end
@doc """
Recursively escapes the given value so it can be inserted
into a syntax tree. Structures that are valid syntax nodes
@@ -111,6 +133,11 @@ defmodule Macro do
Octals will by default be escaped unless the map function
returns false for ?0.
## Hex
Octals will by default be escaped unless the map function
returns false for ?x.
## Examples
Using the unescape_map defined above is easy:
@@ -126,7 +153,7 @@ defmodule Macro do
Unescape the given tokens according to the default map.
Check `unescape/1` and `unescape/2` for more information
about unescaping. Only tokens that are binaries are
unescaped, all others are ignored. This method is useful
unescaped, all others are ignored. This function is useful
when implementing your own sigils. Check the implementation
of `Kernel.__b__` for examples.
"""
@@ -189,21 +216,40 @@ defmodule Macro do
end
# Fn keyword
def to_binary({ :fn, _, [[do: block]] }) do
def to_binary({ :fn, _, [[do: { :->, _, [{_,tuple}] } = arrow]] })
when not is_tuple(tuple) or elem(tuple, 0) != :__block__ do
"fn " <> arrow_to_binary(arrow) <> " end"
end
def to_binary({ :fn, _, [[do: { :->, _, [_] } = block]] }) do
"fn " <> block_to_binary(block) <> "\nend"
end
def to_binary({ :fn, _, [[do: block]] }) do
block = adjust_new_lines block_to_binary(block), "\n "
"fn\n " <> block <> "\nend"
end
# Partial call
def to_binary({ :&, _, [num] }) do
"&#{num}"
end
# left -> right
def to_binary({ :->, _, _ } = arrow) do
"(" <> arrow_to_binary(arrow, true) <> ")"
end
# Binary ops
def to_binary({ op, _, [left, right] }) when op in binary_ops do
op_to_binary(left) <> " #{op} " <> op_to_binary(right)
end
# Unary ops
def to_binary({ :not, _, [arg] }) do
"not " <> to_binary(arg)
end
def to_binary({ op, _, [arg] }) when op in unary_ops do
atom_to_binary(op, :utf8) <> to_binary(arg)
end
@@ -267,8 +313,8 @@ defmodule Macro do
defp block_to_binary({ :->, _, exprs }) do
Enum.map_join(exprs, "\n", fn({ left, right }) ->
left = Enum.map_join(left, ", ", to_binary(&1))
left <> " ->\n " <> adjust_new_lines block_to_binary(right), "\n "
left = comma_join_or_empty_paren(left, false)
left <> "->\n " <> adjust_new_lines block_to_binary(right), "\n "
end)
end
@@ -284,6 +330,20 @@ defmodule Macro do
defp op_to_binary(expr), do: to_binary(expr)
defp arrow_to_binary({ :->, _, pairs }, paren // false) do
Enum.map_join(pairs, "; ", fn({ left, right }) ->
left = comma_join_or_empty_paren(left, paren)
left <> "-> " <> to_binary(right)
end)
end
defp comma_join_or_empty_paren([], true), do: "() "
defp comma_join_or_empty_paren([], false), do: ""
defp comma_join_or_empty_paren(left, _) do
Enum.map_join(left, ", ", to_binary(&1)) <> " "
end
defp adjust_new_lines(block, replacement) do
bc <<x>> inbits block do
<< case x == ?\n do
@@ -300,6 +360,7 @@ defmodule Macro do
* Macros (local or remote);
* Aliases are expanded (if possible) and return atoms;
* All pseudo-variables (__FILE__, __MODULE__, etc);
* Module attributes reader (@foo);
In case the expression cannot be expanded, it returns the expression itself.
+1 -1
View File
@@ -16,7 +16,7 @@ defmodule Macro.Env do
functions: functions, macros: macros]
Record.deffunctions(fields, __MODULE__)
Record.deftypes(fields, types, __ENV__)
Record.deftypes(fields, types, __MODULE__)
@moduledoc """
A record that contains compile time environment information,
+12 -6
View File
@@ -1,6 +1,12 @@
defmodule Module do
require :ets, as: ETS
defmacrop is_env(env) do
quote do
is_tuple(unquote(env)) and size(unquote(env)) > 1 and elem(unquote(env), 0) == Macro.Env
end
end
@moduledoc """
This module provides many functions to deal with modules during
compilation time. It allows a developer to dynamically attach
@@ -51,11 +57,11 @@ defmodule Module do
"""
def eval_quoted(module, quoted, binding // [], opts // [])
def eval_quoted(Macro.Env[module: module] = env, quoted, binding, opts) do
eval_quoted(module, quoted, binding, Keyword.merge(env.to_keywords, opts))
def eval_quoted(env, quoted, binding, opts) when is_env(env) do
eval_quoted(env.module, quoted, binding, Keyword.merge(env.to_keywords, opts))
end
def eval_quoted(module, quoted, binding, Macro.Env[] = env) do
def eval_quoted(module, quoted, binding, env) when is_env(env) do
eval_quoted(module, quoted, binding, env.to_keywords)
end
@@ -95,12 +101,12 @@ defmodule Module do
"""
def create(module, quoted, opts // [])
def create(module, quoted, Macro.Env[] = env) do
def create(module, quoted, env) when is_env(env) do
create(module, quoted, env.to_keywords)
end
def create(module, quoted, opts) when is_atom(module) do
line = opts[:line] || 1
line = Keyword.get(opts, :line, 1)
:elixir_module.compile(line, module, quoted, [], :elixir.scope_for_eval(opts))
end
@@ -578,7 +584,7 @@ defmodule Module do
atom
end
defp normalize_attribute(:file, Macro.Env[file: file, line: line]), do: { binary_to_list(file), line}
defp normalize_attribute(:file, env) when is_env(env), do: { binary_to_list(env.file), env.line}
defp normalize_attribute(:file, { binary, line }) when is_binary(binary), do: { binary_to_list(binary), line }
defp normalize_attribute(:file, other) when not is_tuple(other), do: normalize_attribute(:file, { other, 1 })
+2 -2
View File
@@ -213,7 +213,7 @@ defmodule Protocol do
{ List, :is_list, quote do: list },
{ BitString, :is_bitstring, quote do: <<>> },
{ Number, :is_number, quote do: number },
{ Function, :is_function, quote do: fun },
{ Function, :is_function, quote do: (... -> any) },
{ PID, :is_pid, quote do: pid },
{ Port, :is_port, quote do: port },
{ Reference, :is_reference, quote do: reference },
@@ -393,7 +393,7 @@ defmodule Protocol.DSL do
# Convert the spec to callback if possible,
# otherwise generate a dummy callback
Protocol.DSL.callback_from_spec(__MODULE__, name, arity) ||
@callback unquote(name)(unquote_splicing(type_args)), do: term
@callback unquote(name)(unquote_splicing(type_args)) :: term
end
end
end
+113 -120
View File
@@ -65,27 +65,26 @@ defmodule Record do
end
"""
def deffunctions(values, opts // [], env) do
values = lc value inlist values, do: convert_value(value)
escaped = Macro.escape(values)
extensions = Keyword.get(opts, :extensions, Record.Extensions)
def deffunctions(values, _opts // [], env) do
values = lc value inlist values, do: convert_value(value)
escaped = Macro.escape(values)
contents = [
reflection(escaped),
initializer(escaped),
indexes(escaped),
readers(values, 1, []),
conversions(values),
writers(values, 1, []),
updater(values),
extensions(values, 1, [], extensions)
extensions(values, 1, [], Record.Extensions),
accessors(values),
]
contents = [quote(do: @__record__ unquote(escaped))|contents]
# Special case for bootstraping purposes
if env == Macro.Env do
:elixir_module.eval_quoted(env, contents, [], [])
Module.eval_quoted(env, contents, [], [])
else
contents = [quote(do: @__record__ unquote(escaped))|contents]
Module.eval_quoted(env.module, contents, [], env.location)
end
end
@@ -93,23 +92,21 @@ defmodule Record do
@doc """
Defines types and specs for the record.
"""
def deftypes(values, types, opts // [], env) do
def deftypes(values, types, _opts // [], env) do
types = types || []
values = lc value inlist values do
{ name, default } = convert_value(value)
{ name, default, find_spec(types, name) }
end
extensions = Keyword.get(opts, :extensions, Record.Extensions)
contents = [
core_specs(values),
accessor_specs(values, []),
extensions_specs(values, [], extensions)
accessor_specs(values, 1, [])
]
# Special case for bootstraping purposes
if :erlang.function_exported(Module, :eval_quoted, 2) do
if env == Macro.Env do
Module.eval_quoted(env, contents, [], [])
else
Module.eval_quoted(env.module, contents, [], env.location)
end
end
@@ -157,6 +154,34 @@ defmodule Record do
Module.eval_quoted(env.module, contents, [], env.location)
end
## Callbacks
# Store all optimizable fields in the record as well
@doc false
defmacro __before_compile__(_) do
quote do
def __record__(:optimizable), do: @record_optimizable
end
end
# Store fields that can be optimized and that cannot be
# optimized as they are overriden
@doc false
def __on_definition__(env, kind, name, args, _guards, _body) do
tuple = { name, length(args) }
module = env.module
functions = Module.get_attribute(module, :record_optimizable)
functions =
if kind in [:def] and Module.get_attribute(module, :record_optimized) do
[tuple|functions]
else
List.delete(functions, tuple)
end
Module.put_attribute(module, :record_optimizable, functions)
end
# Implements the access macro used by records.
# It returns a quoted expression that defines
# a record or a match in case the record is
@@ -389,7 +414,7 @@ defmodule Record do
end
end
# Implement readers. For a declaration like:
# Implement accessors. For a declaration like:
#
# defrecord FileInfo, atime: nil, mtime: nil
#
@@ -403,24 +428,6 @@ defmodule Record do
# elem(record, 2)
# end
#
defp readers([{ key, _default }|t], i, acc) do
contents = quote do
def unquote(key).(record) do
:erlang.element(unquote(i + 1), record)
end
end
readers(t, i + 1, [contents | acc])
end
defp readers([], _i, acc), do: acc
# Implement writers. For a declaration like:
#
# defrecord FileInfo, atime: nil, mtime: nil
#
# It will define four methods:
#
# def :atime.(value, record) do
# setelem(record, 1, value)
# end
@@ -429,17 +436,51 @@ defmodule Record do
# setelem(record, 2, value)
# end
#
defp writers([{ key, _default }|t], i, acc) do
# def :atime.(callback, record) do
# setelem(record, 1, callback.(elem(record, 1)))
# end
#
# def :mtime.(callback, record) do
# setelem(record, 2, callback.(elem(record, 2)))
# end
#
defp accessors(values) do
[ quote do
@record_optimized true
@record_optimizable []
@before_compile { unquote(__MODULE__), :__before_compile__ }
@on_definition { unquote(__MODULE__), :__on_definition__ }
end | accessors(values, 1) ]
end
defp accessors([{ :__exception__, _ }|t], 1) do
accessors(t, 2)
end
defp accessors([{ key, _default }|t], i) do
update = binary_to_atom "update_" <> atom_to_binary(key)
contents = quote do
def unquote(key).(record) do
:erlang.element(unquote(i + 1), record)
end
def unquote(key).(value, record) do
:erlang.setelement(unquote(i + 1), record, value)
end
def unquote(update).(function, record) do
:erlang.setelement(unquote(i + 1), record,
function.(:erlang.element(unquote(i + 1), record)))
end
end
writers(t, i + 1, [contents | acc])
[contents|accessors(t, i + 1)]
end
defp writers([], _i, acc), do: acc
defp accessors([], _i) do
[quote do: @record_optimized false]
end
# Define an updater method that receives a
# keyword list and updates the record.
@@ -483,30 +524,31 @@ defmodule Record do
@type t :: { __MODULE__, unquote_splicing(types) }
end
@spec new, do: t
@spec new(Keyword.t | tuple), do: t
@spec to_keywords(t), do: Keyword.t
@spec update(Keyword.t, t), do: t
@spec __index__(atom), do: non_neg_integer | nil
@spec new :: t
@spec new(Keyword.t | tuple) :: t
@spec to_keywords(t) :: Keyword.t
@spec update(Keyword.t, t) :: t
@spec __index__(atom) :: non_neg_integer | nil
end
end
defp accessor_specs([{ key, _default, spec }|t], acc) do
defp accessor_specs([{ :__exception__, _, _ }|t], 1, acc) do
accessor_specs(t, 2, acc)
end
defp accessor_specs([{ key, _default, spec }|t], i, acc) do
update = binary_to_atom "update_" <> atom_to_binary(key)
contents = quote do
@spec unquote(key)(t), do: unquote(spec)
@spec unquote(key)(unquote(spec), t), do: t
@spec unquote(key)(t) :: unquote(spec)
@spec unquote(key)(unquote(spec), t) :: t
@spec unquote(update)((unquote(spec) -> unquote(spec)), t) :: t
end
accessor_specs(t, [contents | acc])
accessor_specs(t, i + 1, [contents | acc])
end
defp accessor_specs([], acc), do: acc
defp extensions_specs([{ key, default, spec }|t], acc, extensions) do
specs = extensions.specs_for(key, default, spec)
extensions_specs(t, [specs | acc], extensions)
end
defp extensions_specs([], acc, _), do: acc
defp accessor_specs([], _i, acc), do: acc
## Helpers
@@ -517,7 +559,17 @@ defmodule Record do
defp is_keyword_tuple(_), do: false
defp convert_value(atom) when is_atom(atom), do: { atom, nil }
defp convert_value(other), do: other
defp convert_value({ atom, other }) when is_atom(atom) and is_function(other), do:
raise ArgumentError, message: "record field default value #{inspect atom} cannot be a function"
defp convert_value({ atom, other }) when is_atom(atom) and (is_reference(other) or is_pid(other) or is_port(other)), do:
raise ArgumentError, message: "record field default value #{inspect atom} cannot be a reference, pid or port"
defp convert_value({ atom, _ } = tuple) when is_atom(atom), do: tuple
defp convert_value({ field, _ }), do:
raise ArgumentError, message: "record field name has to be an atom, got #{inspect field}"
defp find_index([{ k, _ }|_], k, i), do: i
defp find_index([{ _, _ }|t], k, i), do: find_index(t, k, i + 1)
@@ -552,23 +604,7 @@ defmodule Record.Extensions do
# Function definition
def functions_for(key, default, i) do
[ default_for(key, default, i),
extension_for(key, default, i) ]
end
defp default_for(:__exception__, _default, _i) do
nil
end
defp default_for(key, _default, i) do
update = prefix("update_", key)
quote do
def unquote(update).(function, record) do
current = :erlang.element(unquote(i + 1), record)
:erlang.setelement(unquote(i + 1), record, function.(current))
end
end
extension_for(key, default, i)
end
defp extension_for(key, default, i) when is_list(default) do
@@ -577,11 +613,13 @@ defmodule Record.Extensions do
quote do
def unquote(prepend).(value, record) do
IO.write "[WARNING] record default-based generated function #{unquote(prepend)} is deprecated\n#{Exception.formatted_stacktrace}"
current = :erlang.element(unquote(i + 1), record)
:erlang.setelement(unquote(i + 1), record, value ++ current)
end
def unquote(merge).(value, record) do
IO.write "[WARNING] record default-based generated function #{unquote(merge)} is deprecated\n#{Exception.formatted_stacktrace}"
current = :erlang.element(unquote(i + 1), record)
:erlang.setelement(unquote(i + 1), record, Keyword.merge(current, value))
end
@@ -593,6 +631,7 @@ defmodule Record.Extensions do
quote do
def unquote(increment).(value // 1, record) do
IO.write "[WARNING] record default-based generated function #{unquote(increment)} is deprecated\n#{Exception.formatted_stacktrace}"
current = :erlang.element(unquote(i + 1), record)
:erlang.setelement(unquote(i + 1), record, current + value)
end
@@ -604,6 +643,7 @@ defmodule Record.Extensions do
quote do
def unquote(toggle).(record) do
IO.write "[WARNING] record default-based generated function #{unquote(toggle)} is deprecated\n#{Exception.formatted_stacktrace}"
current = :erlang.element(unquote(i + 1), record)
:erlang.setelement(unquote(i + 1), record, not current)
end
@@ -612,53 +652,6 @@ defmodule Record.Extensions do
defp extension_for(_, _, _), do: nil
# Specs definition
def specs_for(key, default, spec) do
[ default_specs_for(key, default, spec),
extension_specs_for(key, default, spec) ]
end
defp default_specs_for(:__exception__, _default, _spec) do
nil
end
defp default_specs_for(key, _default, spec) do
update = prefix("update_", key)
quote do
@spec unquote(update)(fun(unquote(spec), do: unquote(spec)), t), do: t
end
end
defp extension_specs_for(key, default, _spec) when is_list(default) do
prepend = prefix("prepend_", key)
merge = prefix("merge_", key)
quote do
@spec unquote(prepend)(list, t), do: t
@spec unquote(merge)(Keyword.t, t), do: t
end
end
defp extension_specs_for(key, default, _spec) when is_number(default) do
increment = prefix("increment_", key)
quote do
@spec unquote(increment)(number, t), do: t
end
end
defp extension_specs_for(key, default, _spec) when is_boolean(default) do
toggle = prefix("toggle_", key)
quote do
@spec unquote(toggle)(t), do: t
end
end
defp extension_specs_for(_, _, _), do: nil
# Helpers
defp prefix(prefix, key) do
+5 -5
View File
@@ -21,7 +21,7 @@ defmodule Record.Extractor do
case :code.lib_dir(to_char_list(app)) do
{ :error, _ } ->
raise ArgumentError, "Lib file #{to_binary(file)} could not be found"
raise ArgumentError, "lib file #{to_binary(file)} could not be found"
libpath ->
retrieve_record name, :filename.join([libpath|path])
end
@@ -33,7 +33,7 @@ defmodule Record.Extractor do
if record = List.keyfind(records, name, 0) do
parse_record(record)
else
raise ArgumentError, "No record #{name} found at #{to_binary(file)}"
raise ArgumentError, "no record #{name} found at #{to_binary(file)}"
end
end
@@ -50,13 +50,13 @@ defmodule Record.Extractor do
{ :ok, form } ->
form
other ->
raise "Error parsing file #{to_binary(file)}, got: #{inspect(other)}"
raise "error parsing file #{to_binary(file)}, got: #{inspect(other)}"
end
end
# Parse a tuple with name and fields and returns a
# list of second order tuples where the first element
# is the field and the second is its default value.
# list of tuples where the first element is the field
# and the second is its default value.
defp parse_record({ _name, fields }) do
cons = List.foldr fields, { nil, 0 }, fn f, acc ->
{ :cons, 0, parse_field(f), acc }
+10 -10
View File
@@ -16,7 +16,7 @@ defmodule Regex do
The re module provides several options, the one available in Elixir, followed by
their shortcut in parenthesis, are:
* unicode (u) - used when you want to match against specific unicode characters
* unicode (u) - enable unicode specific patterns like \p
* caseless (i) - add case insensitivity
* dotall (s) - causes dot to match newlines and also set newline to anycrlf.
The new line setting can be overwritten by setting `(*CR)` or `(*LF)` or
@@ -149,7 +149,7 @@ defmodule Regex do
def captures(regex(groups: groups) = regex, string, options // []) do
unless captures = Keyword.get(options, :capture) do
captures = if groups do
List.sort(groups)
Enum.sort(groups)
else
raise "Regex was not compiled with g"
end
@@ -218,7 +218,7 @@ defmodule Regex do
def scan(regex, string, options // [])
def scan(regex(re_pattern: compiled), string, options) do
return = options[:return] || return_for(string)
return = Keyword.get(options, :return, return_for(string))
options = [{ :capture, :all, return }, :global]
case :re.run(string, compiled, options) do
:nomatch -> []
@@ -236,13 +236,13 @@ defmodule Regex do
def split(regex(re_pattern: compiled), string, options) do
parts =
cond do
options[:global] == false -> 2
p = options[:parts] -> p
true -> :infinity
Keyword.get(options, :global) == false -> 2
p = Keyword.get(options, :parts) -> p
true -> :infinity
end
return = options[:return] || return_for(string)
opts = [{ :return, return }, { :parts, parts }]
return = Keyword.get(options, :return, return_for(string))
opts = [return: return, parts: parts]
:re.split(string, compiled, opts)
end
@@ -265,8 +265,8 @@ defmodule Regex do
"""
def replace(regex(re_pattern: compiled), string, replacement, options // []) do
opts = if options[:global] != false, do: [:global], else: []
return = options[:return] || return_for(string)
opts = if Keyword.get(options, :global) != false, do: [:global], else: []
return = Keyword.get(options, :return, return_for(string))
opts = [{ :return, return }|opts]
:re.replace(string, compiled, replacement, opts)
end
+71 -40
View File
@@ -27,7 +27,7 @@ defmodule String do
String.printable?("abc") #=> true
"""
@spec printable?(t), do: boolean
@spec printable?(t) :: boolean
# Allow basic ascii chars
def printable?(<<c, t :: binary>>) when c in ?\s..?~ do
printable?(t)
@@ -125,9 +125,9 @@ defmodule String do
String.split("a,b", %r{\.}) #=> ["a,b"]
"""
@spec split(t), do: [t]
@spec split(t, t | [t] | Regex.t), do: [t]
@spec split(t, t | [t] | Regex.t, Keyword.t), do: [t]
@spec split(t) :: [t]
@spec split(t, t | [t] | Regex.t) :: [t]
@spec split(t, t | [t] | Regex.t, Keyword.t) :: [t]
def split(binary, pattern // " ", options // [])
def split(binary, pattern, options) when is_regex(pattern) do
@@ -153,7 +153,7 @@ defmodule String do
String.upcase("josé") #=> "JOSÉ"
"""
@spec upcase(t), do: t
@spec upcase(t) :: t
defdelegate upcase(binary), to: String.Unicode
@doc """
@@ -170,22 +170,30 @@ defmodule String do
String.downcase("JOSÉ") #=> "josé"
"""
@spec downcase(t), do: t
@spec downcase(t) :: t
defdelegate downcase(binary), to: String.Unicode
@doc """
Returns a string where trailing char have been
removed. If no `char` is passed `space`is used.
Returns a string where trailing whitespace characters
and new line have been removed.
## Examples
String.rstrip(" abc ") #=> " abc"
"""
@spec rstrip(t) :: t
defdelegate rstrip(binary), to: String.Unicode
@doc """
Returns a string where trailing `char` have been removed.
## Examples
String.rstrip(" abc _", ?_) #=> " abc "
"""
@spec rstrip(t), do: t
@spec rstrip(t, char), do: t
def rstrip(string, char // ?\s)
@spec rstrip(t, char) :: t
def rstrip("", _char), do: ""
@@ -194,37 +202,45 @@ defmodule String do
# does not traverse the whole binary).
def rstrip(string, char) do
if :binary.last(string) == char do
rstrip(string, "", char)
do_rstrip(string, "", char)
else
string
end
end
defp rstrip(<<char, string :: binary>>, buffer, char) do
rstrip(string, <<char, buffer :: binary>>, char)
defp do_rstrip(<<char, string :: binary>>, buffer, char) do
do_rstrip(string, <<char, buffer :: binary>>, char)
end
defp rstrip(<<char, string :: binary>>, buffer, another_char) do
<<buffer :: binary, char, rstrip(string, "", another_char) :: binary>>
defp do_rstrip(<<char, string :: binary>>, buffer, another_char) do
<<buffer :: binary, char, do_rstrip(string, "", another_char) :: binary>>
end
defp rstrip(<<>>, _, _) do
defp do_rstrip(<<>>, _, _) do
<<>>
end
@doc """
Returns a string where leading char have been
removed. If no `char` is passed `space`is used.
Returns a string where leading whitespace characters
have been removed.
## Examples
String.lstrip(" abc ") #=> "abc "
"""
defdelegate lstrip(binary), to: String.Unicode
@doc """
Returns a string where leading `char` have been removed.
## Examples
String.lstrip("_ abc _", ?_) #=> " abc _"
"""
@spec lstrip(t), do: t
@spec lstrip(t, char), do: t
def lstrip(string, char // ?\s)
@spec lstrip(t, char) :: t
def lstrip(<<char, rest :: binary>>, char) do
<<lstrip(rest, char) :: binary>>
@@ -235,18 +251,32 @@ defmodule String do
end
@doc """
Returns a string where leading/trailing char have been
removed. If no `char` is passed `space`is used.
Returns a string where leading/trailing whitespace
and new line characters have been removed.
## Examples
String.strip(" abc ") #=> "abc"
"""
@spec strip(t) :: t
def strip(string) do
rstrip(lstrip(string))
end
@doc """
Returns a string where leading/trailing `char` have been
removed.
## Examples
String.strip("a abc a", ?a) #=> " abc "
"""
@spec strip(t), do: t
@spec strip(t, char), do: t
def strip(string, char // ?\s) do
@spec strip(t, char) :: t
def strip(string, char) do
rstrip(lstrip(string, char), char)
end
@@ -269,8 +299,9 @@ defmodule String do
String.replace("a,b,c", ",", "[]", insert_replaced: [1,1]) #=> "a[,,]b[,,]c"
"""
@spec replace(t, t, t), do: t
@spec replace(t, t, t, Keyword.t), do: t
@spec replace(t, t, t) :: t
@spec replace(t, t, t, Keyword.t) :: t
def replace(subject, pattern, replacement, options // []) do
opts = translate_replace_options(options)
:binary.replace(subject, pattern, replacement, opts)
@@ -295,7 +326,7 @@ defmodule String do
String.duplicate("abc", 2) #=> "abcabc"
"""
@spec duplicate(t, pos_integer), do: t
@spec duplicate(t, pos_integer) :: t
def duplicate(subject, n) when is_integer(n) and n > 0 do
:binary.copy(subject, n)
end
@@ -310,7 +341,7 @@ defmodule String do
String.codepoints("ἅἪῼ") #=> ["ἅ","Ἢ","ῼ"]
"""
@spec codepoints(t), do: [codepoint]
@spec codepoints(t) :: [codepoint]
defdelegate codepoints(string), to: String.Unicode
@doc """
@@ -325,7 +356,7 @@ defmodule String do
String.next_codepoint("josé") #=> { "j", "osé" }
"""
@spec next_codepoint(t), do: codepoint | :no_codepoint
@spec next_codepoint(t) :: codepoint | :no_codepoint
defdelegate next_codepoint(string), to: String.Unicode
@doc """
@@ -335,7 +366,7 @@ defmodule String do
String.graphemes("Ā̀stute") # => ["Ā̀","s","t","u","t","e"]
"""
@spec graphemes(t), do: [grapheme]
@spec graphemes(t) :: [grapheme]
defdelegate graphemes(string), to: String.Unicode
@doc """
@@ -350,7 +381,7 @@ defmodule String do
String.next_grapheme("josé") #=> { "j", "osé" }
"""
@spec next_grapheme(t), do: grapheme | :no_grapheme
@spec next_grapheme(t) :: grapheme | :no_grapheme
defdelegate next_grapheme(string), to: String.Unicode
@doc """
@@ -362,7 +393,7 @@ defmodule String do
String.first("եոգլի") #=> "ե"
"""
@spec first(t), do: grapheme
@spec first(t) :: grapheme
def first(string) do
case next_grapheme(string) do
{ char, _ } -> char
@@ -379,7 +410,7 @@ defmodule String do
String.last("եոգլի") #=> "ի"
"""
@spec last(t), do: grapheme
@spec last(t) :: grapheme
def last(string) do
do_last(next_grapheme(string), "")
end
@@ -399,7 +430,7 @@ defmodule String do
String.length("եոգլի") #=> 5
"""
@spec length(t), do: non_neg_integer
@spec length(t) :: non_neg_integer
def length(string) do
do_length(next_grapheme(string))
end
@@ -416,14 +447,14 @@ defmodule String do
## Examples
String.at("elixir", 0) #=> "1"
String.at("elixir", 0) #=> "e"
String.at("elixir", 1) #=> "l"
String.at("elixir", 10) #=> nil
String.at("elixir", -1) #=> "r"
String.at("elixir", -10) #=> nil
"""
@spec at(t, integer), do: grapheme | nil
@spec at(t, integer) :: grapheme | nil
def at(string, position) when position >= 0 do
do_at(next_grapheme(string), position, 0)
end
@@ -432,7 +463,7 @@ defmodule String do
real_pos = do_length(next_grapheme(string)) - abs(position)
case real_pos >= 0 do
true -> do_at(next_grapheme(string), real_pos, 0)
false -> ""
false -> nil
end
end
+23 -7
View File
@@ -8,7 +8,8 @@ defmodule System do
# Tries to run `git describe --always --tags`. In case of success
# returns the most recent tag, otherwise returns an empty string.
defmacrop get_describe do
if :os.find_executable('git') do
dotgit = File.join(File.cwd!, ".git")
if :os.find_executable('git') && File.exists?(dotgit) do
data = :os.cmd('git describe --always --tags')
Regex.replace %r/\n/, to_binary(data), ""
else
@@ -24,11 +25,13 @@ defmodule System do
@doc """
Returns Elixir's version as binary.
"""
def version, do: "0.7.1"
@spec version() :: String.t
def version, do: "0.7.2"
@doc """
Returns a keywords list with version, git tag info and date.
"""
@spec build_info() :: Keyword.t
def build_info do
[version: version, tag: get_describe, date: get_date]
end
@@ -36,6 +39,7 @@ defmodule System do
@doc """
Returns the list of command-line arguments passed to the program.
"""
@spec argv() :: [String.t]
def argv do
:gen_server.call(:elixir_code_server, :argv)
end
@@ -60,6 +64,8 @@ defmodule System do
If `command` is a char list, a char list is returned.
Returns a binary otherwise.
"""
@spec cmd(char_list) :: char_list
@spec cmd(String.t) :: String.t
def cmd(command) when is_list(command) do
:os.cmd(command)
end
@@ -76,6 +82,8 @@ defmodule System do
If `command` is a char list, a char list is returned.
Returns a binary otherwise.
"""
@spec find_executable(char_list) :: char_list | nil
@spec find_executable(String.t) :: String.t | nil
def find_executable(command) when is_list(command) do
:os.find_executable(command) || nil
end
@@ -92,8 +100,9 @@ defmodule System do
given as a single string of the format "VarName=Value", where VarName is the
name of the variable and Value its value.
"""
@spec get_env() :: [{String.t, String.t}]
def get_env do
Enum.map :os.getenv, list_to_binary &1
Enum.map :os.getenv, :unicode.characters_to_binary &1
end
@doc """
@@ -101,10 +110,11 @@ defmodule System do
`varname` as a binary, or nil if the environment
variable is undefined.
"""
@spec get_env(String.t) :: String.t | nil
def get_env(varname) do
case :os.getenv(to_char_list(varname)) do
false -> nil
other -> list_to_binary(other)
other -> :unicode.characters_to_binary(other)
end
end
@@ -114,19 +124,23 @@ defmodule System do
See http://www.erlang.org/doc/man/os.html#getpid-0 for more info.
"""
@spec get_pid() :: String.t
def get_pid, do: list_to_binary(:os.getpid)
@doc """
Sets a new `value` for the environment variable `varname`.
"""
def put_env(varname, value) do
:os.putenv to_char_list(varname), to_char_list(value)
@spec put_env(String.t, String.t | char_list) :: :ok
def put_env(varname, value) when is_binary(value) or is_list(value) do
:os.putenv to_char_list(varname), :unicode.characters_to_list(value)
:ok
end
@doc """
Sets a new value for each environment variable corresponding
to each key in `dict`.
"""
@spec put_env(Dict.t) :: :ok
def put_env(dict) do
Enum.each dict, fn {key, val} -> put_env key, val end
end
@@ -168,7 +182,9 @@ defmodule System do
System.halt(:abort)
"""
@spec halt(non_neg_integer | List.Chars.t | :abort, [] | [flush: false]), do: no_return
@spec halt() :: no_return
@spec halt(non_neg_integer | List.Chars.t | :abort) :: no_return
@spec halt(non_neg_integer | List.Chars.t | :abort, [] | [flush: false]) :: no_return
def halt(status // 0, options // [])
def halt(status, options) when is_integer(status) or status == :abort do
+1 -6
View File
@@ -81,14 +81,9 @@ defmodule URI do
<<c>>
end
defp percent(c), do: escape_byte(c)
defp escape_byte(c), do: "%" <> hex(c)
defp percent(c), do: "%" <> hex(bsr(c, 4)) <> hex(band(c, 15))
defp hex(n) when n <= 9, do: <<n + ?0>>
defp hex(n) when n > 15 do
hex(bsr(n, 4)) <> hex(band(n, 15))
end
defp hex(n), do: <<n + ?A - 10>>
@doc """
+2 -2
View File
@@ -9,10 +9,10 @@ defmodule URI.Parser do
@doc """
Responsible for parsing extra URL information.
"""
defcallback parse(uri_info :: URI.Info.t), do: URI.Info.t
defcallback parse(uri_info :: URI.Info.t) :: URI.Info.t
@doc """
Responsible for returning the default port.
"""
defcallback default_port(), do: integer
defcallback default_port() :: integer
end
+46 -12
View File
@@ -1,4 +1,4 @@
# This file has its compilation step because
# This file has its own compilation step because
# it needs to parse String.Unicode data and
# compile a digested module.
defmodule String.Unicode do
@@ -12,28 +12,32 @@ defmodule String.Unicode do
data_path = File.expand_path("../UnicodeData.txt", __FILE__)
codes = Enum.reduce File.iterator!(data_path), [], fn(line, acc) ->
{ codes, whitespace } = Enum.reduce File.iterator!(data_path), { [], [] }, fn(line, { cacc, wacc }) ->
[ codepoint, _name, _category,
_class, _bidi, _decomposition,
_class, bidi, _decomposition,
_numeric_1, _numeric_2, _numeric_3,
_bidi_mirror, _unicode_1, _iso,
upper, lower, _title ] = :binary.split(line, ";", [:global])
if upper != "" or lower != "" do
[{ to_binary.(codepoint), upper, lower }|acc]
else
acc
cond do
upper != "" or lower != "" ->
{ [{ to_binary.(codepoint), upper, lower } | cacc], wacc }
bidi in ["B", "S", "WS"] ->
{ cacc, [to_binary.(codepoint) | wacc] }
true ->
{ cacc, wacc }
end
end
seqs_path = File.expand_path("../NamedSequences.txt", __FILE__)
seqs = Enum.map(File.iterator!(seqs_path), fn(line) ->
seqs = Enum.map File.iterator!(seqs_path), fn(line) ->
[ _name, codepoints ] = :binary.split(line, ";", [:global])
codepoints = Enum.filter(:binary.split(codepoints, " ", [:global, :trim]),
fn(x) -> size(x) > 0 end)
Enum.map(codepoints, fn(x) -> to_binary.(x) end)
end)
codepoints = :binary.split(codepoints, " ", [:global])
codepoints = Enum.map codepoints, Regex.replace(%r/\s+/, &1, "")
codepoints = Enum.filter codepoints, fn(x) -> size(x) > 0 end
Enum.map codepoints, to_binary.(&1)
end
# Downcase
@@ -69,6 +73,36 @@ defmodule String.Unicode do
<< >>
end
# Strip
def lstrip(""), do: ""
lc char inlist whitespace do
args = quote do: [unquote(char) <> rest]
exprs = quote do: lstrip(rest)
def :lstrip, args, [], do: exprs
end
def lstrip(other), do: other
def rstrip(""), do: ""
def rstrip(string) do
do_rstrip(string, "")
end
lc char inlist whitespace do
args = quote do: [unquote(char) <> rest, buffer]
exprs = quote do: do_rstrip(rest, unquote(char) <> buffer)
defp :do_rstrip, args, [], do: exprs
end
defp do_rstrip(<< char, string :: binary >>, buffer) do
<< buffer :: binary, char, do_rstrip(string, "") :: binary >>
end
defp do_rstrip(<<>>, _), do: <<>>
# Graphemes
lc codepoints inlist seqs do
+14 -22
View File
@@ -1,6 +1,6 @@
-module(elixir).
-behaviour(application).
-export([main/1, start_cli/0, start_app/0,
-export([main/1, start_cli/0,
scope_for_eval/1, eval/2, eval/3, eval/4,
eval_quoted/2, eval_quoted/3, eval_quoted/4,
eval_forms/3]).
@@ -16,9 +16,12 @@
-export([start/2, stop/1, config_change/3]).
start(_Type, _Args) ->
%% Set the shell to unicode so printing inside files work
io:setopts(standard_io, [{encoding,unicode}]),
io:setopts(standard_error, [{encoding,unicode}]),
%% Set the shell to unicode so printing inside scripts work
%% Those can take a while, so let's do it in a new process
spawn(fun() ->
io:setopts(standard_io, [{encoding,unicode}]),
io:setopts(standard_error, [{encoding,unicode}])
end),
elixir_sup:start_link([]).
stop(_S) ->
@@ -30,25 +33,14 @@ config_change(_Changed, _New, _Remove) ->
%% escript entry point
main(Args) ->
start_app(),
'Elixir.Kernel.CLI':process_argv(Args).
%% ELIXIR ENTRY POINTS
% Start the Elixir app. This is the proper way to boot Elixir from
% inside an Erlang process.
start_app() ->
case lists:keyfind(?MODULE, 1, application:loaded_applications()) of
false -> application:start(?MODULE);
_ -> ok
end.
application:start(?MODULE),
'Elixir.Kernel.CLI':main(Args).
% Boot and process given options. Invoked by Elixir's script.
start_cli() ->
start_app(),
'Elixir.Kernel.CLI':process_argv(init:get_plain_arguments()).
application:start(?MODULE),
'Elixir.Kernel.CLI':main(init:get_plain_arguments()).
%% EVAL HOOKS
@@ -125,7 +117,6 @@ eval_forms(Tree, Binding, RawScope) ->
Scope = RawScope#elixir_scope{
vars=binding_dict(Binding),
temp_vars=[],
quote_vars=[],
clause_vars=nil,
counter=[]
},
@@ -143,7 +134,8 @@ to_binary(Bin) when is_binary(Bin) -> Bin;
to_binary(List) when is_list(List) -> list_to_binary(List).
binding_dict(List) -> binding_dict(List, orddict:new()).
binding_dict([{H,_}|T], Dict) -> binding_dict(T, orddict:store(H, H, Dict));
binding_dict([{{H,Kind},_}|T], Dict) -> binding_dict(T, orddict:store({ H, Kind }, H, Dict));
binding_dict([{H,_}|T], Dict) -> binding_dict(T, orddict:store({ H, nil }, H, Dict));
binding_dict([], Dict) -> Dict.
final_binding(Binding, Vars) -> final_binding(Binding, [], Binding, Vars).
@@ -152,7 +144,7 @@ final_binding([{Var,_}|T], Acc, Binding, Vars) ->
true ->
final_binding(T, Acc, Binding, Vars);
false ->
RealName = orddict:fetch(Var, Vars),
RealName = orddict:fetch({ Var, nil }, Vars),
RealValue = proplists:get_value(RealName, Binding, nil),
final_binding(T, [{Var, RealValue}|Acc], Binding, Vars)
end;
+27 -22
View File
@@ -75,7 +75,8 @@ extract_or_clauses(Term, Acc) -> [Term|Acc].
extract_args({ { '.', _, [Name] }, _, Args }) when is_atom(Name), is_list(Args) -> { Name, Args };
extract_args({ Name, _, Args }) when is_atom(Name), is_atom(Args) -> { Name, [] };
extract_args({ Name, _, Args }) when is_atom(Name), is_list(Args) -> { Name, Args }.
extract_args({ Name, _, Args }) when is_atom(Name), is_list(Args) -> { Name, Args };
extract_args(_) -> error.
% Extract guards when it is in the last element of the args
@@ -109,9 +110,11 @@ do_match(Line, DecoupledClauses, S) ->
% Now get all the variables defined inside each clause
CV = lists:reverse(RawCV),
NewVars = lists:umerge([lists:sort(orddict:fetch_keys(X)) || X <- CV]),
case NewVars of
AllVars = lists:umerge([orddict:fetch_keys(X) || X <- CV]),
SharedVars = ordsets:intersection(CV),
case AllVars of
[] -> { TClauses, TS };
_ ->
% Create a new scope that contains a list of all variables
@@ -119,7 +122,7 @@ do_match(Line, DecoupledClauses, S) ->
% a list of tuples where the first element is the variable name,
% the second one is the new pointer to the variable and the third
% is the old pointer.
{ FinalVars, FS } = lists:mapfoldl(fun normalize_vars/2, TS, NewVars),
{ FinalVars, FS } = lists:mapfoldl(fun(X, Acc) -> normalize_vars(X, SharedVars, Acc) end, TS, AllVars),
% Defines a tuple that will be used as left side of the match operator
LeftVars = [{var, Line, NewValue} || { _, _, NewValue, _ } <- FinalVars],
@@ -131,7 +134,8 @@ do_match(Line, DecoupledClauses, S) ->
end.
expand_clauses(Line, [Clause|T], [ClauseVars|V], LeftVars, FinalVars, Acc, S) ->
RightVars = [normalize_clause_var(Var, Kind, OldValue, ClauseVars) || { Var, Kind, _, OldValue } <- FinalVars],
RightVars = [normalize_clause_var(Var, Kind, OldValue, ClauseVars) ||
{ Var, Kind, _, OldValue } <- FinalVars],
AssignExpr = generate_match(Line, LeftVars, RightVars),
ClauseExprs = element(5, Clause),
@@ -202,29 +206,30 @@ has_match_tuple(_) -> false.
% Normalize the given var checking its existence in the scope var dictionary.
normalize_vars({ Var, quoted }, S) ->
normalize_vars(Var, quoted, #elixir_scope.quote_vars, S);
normalize_vars({ Var, Kind } = Key, Shared, #elixir_scope{vars=Vars,clause_vars=ClauseVars} = S) ->
{ NewValue, S1 } =
case orddict:find(Key, Shared) of
{ ok, SharedValue } ->
{ SharedValue, S };
error ->
{ { _, _, ErlValue }, ErlS } = case (Kind == quoted) or (S#elixir_scope.noname) of
true -> elixir_scope:build_erl_var(0, S);
false -> elixir_scope:build_erl_var(0, Var, "_@" ++ atom_to_list(Var), S)
end,
{ ErlValue, ErlS }
end,
normalize_vars({ Var, Kind }, S) ->
normalize_vars(Var, Kind, #elixir_scope.vars, S).
S2 = S1#elixir_scope{
vars=orddict:store(Key, NewValue, Vars),
clause_vars=orddict:store(Key, NewValue, ClauseVars)
},
normalize_vars(Var, Kind, Index, #elixir_scope{clause_vars=ClauseVars} = S) ->
Vars = element(Index, S),
{ { _, _, NewValue }, S1 } = if
(Kind == quoted) or (S#elixir_scope.noname) -> elixir_scope:build_erl_var(0, S);
true -> elixir_scope:build_erl_var(0, Var, "_@" ++ atom_to_list(Var), S)
end,
S2 = setelement(Index, S1, orddict:store(Var, NewValue, Vars)),
S3 = S2#elixir_scope{clause_vars=orddict:store({ Var, Kind }, NewValue, ClauseVars)},
Expr = case orddict:find(Var, Vars) of
Expr = case orddict:find(Key, Vars) of
{ ok, OldValue } -> { var, 0, OldValue };
error -> { atom, 0, nil }
end,
{ { Var, Kind, NewValue, Expr }, S3 }.
{ { Var, Kind, NewValue, Expr }, S2 }.
% Normalize a var by checking if it was defined in the clause.
% If so, use it, otherwise use from main scope.
+18 -5
View File
@@ -8,16 +8,23 @@
at_exit=[],
pool=[],
counter=0,
compiler_options=[{docs,true}]
compiler_options=[{docs,true},{debug_info,true}],
waiting=[]
}).
start_link() ->
{ ok, _ } = gen_server:start_link({local, elixir_code_server}, ?MODULE, [], []).
init(_args) ->
process_flag(trap_exit, true),
{ ok, #elixir_code_server{} }.
handle_call({ wait_until_finished, Pid }, _, Config) ->
Waiting = Config#elixir_code_server.waiting,
case is_list(Waiting) of
true -> { reply, wait, Config#elixir_code_server{waiting=[Pid|Waiting]} };
false -> { reply, ok, Config }
end;
handle_call({ acquire, Path }, From, Config) ->
Current = Config#elixir_code_server.loaded,
case orddict:find(Path, Current) of
@@ -47,6 +54,9 @@ handle_call(loaded, _From, Config) ->
handle_call(at_exit, _From, Config) ->
{ reply, Config#elixir_code_server.at_exit, Config };
handle_call(flush_at_exit, _From, Config) ->
{ reply, Config#elixir_code_server.at_exit, Config#elixir_code_server{at_exit=[]} };
handle_call(argv, _From, Config) ->
{ reply, Config#elixir_code_server.argv, Config };
@@ -65,6 +75,11 @@ handle_call(retrieve_module_name, _From, Config) ->
handle_call(_Request, _From, Config) ->
{ reply, undef, Config }.
handle_cast(finished, Config) ->
Waiting = Config#elixir_code_server.waiting,
[Pid ! { elixir_code_server, finished } || Pid <- lists:reverse(Waiting)],
{ noreply, Config#elixir_code_server{waiting=done} };
handle_cast({ loaded, Path }, Config) ->
Current = Config#elixir_code_server.loaded,
case orddict:find(Path, Current) of
@@ -93,9 +108,7 @@ handle_cast(_Request, Config) ->
handle_info(_Request, Config) ->
{ noreply, Config }.
terminate(Reason, Config) ->
io:format("[FATAL] ~p crashed:\n~p~n", [?MODULE, Reason]),
io:format("[FATAL] ~p snapshot:\n~p~n", [?MODULE, Config]),
terminate(_Reason, _Config) ->
ok.
code_change(_Old, Config, _Extra) ->
+13 -33
View File
@@ -27,7 +27,7 @@ string(Contents, File) when is_list(Contents), is_binary(File) ->
try
put(elixir_compiled, []),
Forms = elixir_translator:'forms!'(Contents, 1, File, []),
eval_forms(Forms, 1, File, [], elixir:scope_for_eval([{file,File}])),
eval_forms(Forms, 1, [], elixir:scope_for_eval([{file,File}])),
lists:reverse(get(elixir_compiled))
after
put(elixir_compiled, Previous)
@@ -49,32 +49,17 @@ file_to_path(File, Path) when is_binary(File), is_binary(Path) ->
%% Evaluates the contents/forms by compiling them to an Erlang module.
eval_forms(Forms, Line, Vars, #elixir_scope{module=nil} = S) ->
eval_forms(Forms, Line, nil, Vars, S);
eval_forms(Forms, Line, Vars, #elixir_scope{module=Value} = S) ->
eval_forms(Forms, Line, Value, Vars, S).
eval_forms(Forms, Line, Value, Vars, S) ->
case (Value == nil) andalso allows_fast_compilation(Forms) of
true -> eval_compilation(Forms, Vars, S);
false -> code_loading_compilation(Forms, Line, Value, Vars, S)
end.
eval_compilation(Forms, Vars, S) ->
Binding = [{ Var, Value } || { _, Var, Value } <- Vars],
{ Result, _Binding, FS } = elixir:eval_forms(Forms, [{'_@MODULE',nil}|Binding], S),
{ Result, FS }.
code_loading_compilation(Forms, Line, Value, Vars, S) ->
eval_forms(Forms, Line, Vars, S) ->
{ Module, I } = retrieve_module_name(),
{ Exprs, FS } = elixir_translator:translate(Forms, S),
ModuleForm = module_form(Exprs, Line, S#elixir_scope.file, Module, Vars),
Args = [X || { _, _, X } <- Vars],
Args = [X || { _, _, _, X } <- Vars],
{ module(ModuleForm, S#elixir_scope.file, [], true, fun(_, _) ->
Res = Module:'BOOTSTRAP'(Value, Args),
%% Pass { native, false } to speed up bootstrap
%% process when native is set to true
{ module(ModuleForm, S#elixir_scope.file, [{native,false}], true, fun(_, _) ->
Res = Module:'BOOTSTRAP'(S#elixir_scope.module, Args),
code:delete(Module),
case code:soft_purge(Module) of
true -> return_module_name(I);
@@ -112,8 +97,8 @@ module(Forms, File, Options, Bootstrap, Callback) when
%% Invoked from the Makefile.
core() ->
elixir:start_app(),
gen_server:call(elixir_code_server, { compiler_options, [{docs,false},{internal,true},{debug_info,true}] }),
application:start(elixir),
gen_server:call(elixir_code_server, { compiler_options, [{docs,false},{internal,true}] }),
[core_file(File) || File <- core_main()].
%% HELPERS
@@ -125,7 +110,7 @@ no_auto_import() ->
module_form(Exprs, Line, File, Module, Vars) when
is_binary(File), is_list(Exprs), is_integer(Line), is_atom(Module) ->
Cons = lists:foldr(fun({ _, Var, _ }, Acc) ->
Cons = lists:foldr(fun({ _, _, Var, _ }, Acc) ->
{ cons, Line, { var, Line, Var }, Acc }
end, { nil, Line }, Vars),
@@ -140,12 +125,6 @@ module_form(Exprs, Line, File, Module, Vars) when
] }
].
%% Fast compilation is available?
allows_fast_compilation([{defmodule,_,_}|T]) -> allows_fast_compilation(T);
allows_fast_compilation([]) -> true;
allows_fast_compilation(_) -> false.
%% Generate module names from code server.
retrieve_module_name() ->
@@ -180,11 +159,11 @@ core_main() ->
"lib/elixir/lib/keyword.ex",
"lib/elixir/lib/list.ex",
"lib/elixir/lib/kernel/typespec.ex",
"lib/elixir/lib/module.ex",
"lib/elixir/lib/record.ex",
"lib/elixir/lib/record/extractor.ex",
"lib/elixir/lib/macro.ex",
"lib/elixir/lib/macro/env.ex",
"lib/elixir/lib/module.ex",
"lib/elixir/lib/code.ex",
"lib/elixir/lib/protocol.ex",
"lib/elixir/lib/enum.ex",
@@ -199,7 +178,8 @@ core_main() ->
"lib/elixir/lib/system.ex",
"lib/elixir/lib/kernel/cli.ex",
"lib/elixir/lib/kernel/error_handler.ex",
"lib/elixir/lib/kernel/parallel_compiler.ex"
"lib/elixir/lib/kernel/parallel_compiler.ex",
"lib/elixir/lib/kernel/record_rewriter.ex"
].
%% ERROR HANDLING
+3 -3
View File
@@ -93,7 +93,7 @@ store_definition(Kind, Line, Module, Name, Args, Guards, Body, RawS) ->
end,
[store_each(false, Kind, File, Location, Table, 0,
function_for_default(Kind, Name, Default)) || Default <- Defaults],
default_function_for(Kind, Name, Default)) || Default <- Defaults],
{ Name, Arity }.
@@ -241,11 +241,11 @@ function_for_stored_definition({{Name, Arity}, _, Line, _, _, Location, _, Claus
]
}.
function_for_default(Kind, Name, { clause, Line, Args, _Guards, _Exprs } = Clause)
default_function_for(Kind, Name, { clause, Line, Args, _Guards, _Exprs } = Clause)
when Kind == defmacro; Kind == defmacrop ->
{ function, Line, Name, length(Args) - 1, [Clause] };
function_for_default(_, Name, { clause, Line, Args, _Guards, _Exprs } = Clause) ->
default_function_for(_, Name, { clause, Line, Args, _Guards, _Exprs } = Clause) ->
{ function, Line, Name, length(Args), [Clause] }.
%% Store each definition in the table.
+41 -23
View File
@@ -2,7 +2,6 @@
-module(elixir_interpolation).
-export([extract/5, unescape_chars/1, unescape_chars/2,
unescape_tokens/1, unescape_tokens/2, unescape_map/1]).
-define(is_octal(S), S >= $0 andalso S =< $7).
-include("elixir.hrl").
-compile({parse_transform, elixir_transform}).
@@ -75,42 +74,61 @@ unescape_token(Other, _Map) -> Other.
% Unescape chars. For instance, "\" "n" (two chars) needs to be converted to "\n" (one char).
-define(to_octal(List),
).
-define(is_octal(S), S >= $0 andalso S =< $7).
-define(is_hex(S), (S >= $0 andalso S =< $9) orelse (S >= $A andalso S =< $F)).
unescape_chars(String) ->
unescape_chars(String, fun unescape_map/1).
unescape_chars(String, Map) ->
Octals = case Map($0) of
false -> false;
_ -> true
end,
unescape_chars(String, Map, Octals, <<>>).
Octals = Map($0) /= false,
Hex = Map($x) /= false,
unescape_chars(String, Map, Octals, Hex, <<>>).
unescape_chars(<<$\\,A,B,C,Rest/binary>>, Map, true, Acc) when ?is_octal(A), ?is_octal(B), ?is_octal(C) ->
to_octal(Rest, Map, [A,B,C], Acc);
unescape_chars(<<$\\,A,B,C,Rest/binary>>, Map, true, Hex, Acc) when ?is_octal(A), ?is_octal(B), ?is_octal(C) ->
append_escaped(Rest, Map, [A,B,C], true, Hex, Acc, 8);
unescape_chars(<<$\\,A,B,Rest/binary>>, Map, true, Acc) when ?is_octal(A), ?is_octal(B) ->
to_octal(Rest, Map, [A,B], Acc);
unescape_chars(<<$\\,A,B,Rest/binary>>, Map, true, Hex, Acc) when ?is_octal(A), ?is_octal(B) ->
append_escaped(Rest, Map, [A,B], true, Hex, Acc, 8);
unescape_chars(<<$\\,A,Rest/binary>>, Map, true, Acc) when ?is_octal(A) ->
to_octal(Rest, Map, [A], Acc);
unescape_chars(<<$\\,A,Rest/binary>>, Map, true, Hex, Acc) when ?is_octal(A) ->
append_escaped(Rest, Map, [A], true, Hex, Acc, 8);
unescape_chars(<<$\\,Escaped,Rest/binary>>, Map, Octals, Acc) ->
unescape_chars(<<$\\,$x,A,B,Rest/binary>>, Map, Octal, true, Acc) when ?is_hex(A), ?is_hex(B) ->
append_escaped(Rest, Map, [A,B], Octal, true, Acc, 16);
unescape_chars(<<$\\,$x,${,A,$},Rest/binary>>, Map, Octal, true, Acc) when ?is_hex(A) ->
append_escaped(Rest, Map, [A], Octal, true, Acc, 16);
unescape_chars(<<$\\,$x,${,A,B,$},Rest/binary>>, Map, Octal, true, Acc) when ?is_hex(A), ?is_hex(B) ->
append_escaped(Rest, Map, [A,B], Octal, true, Acc, 16);
unescape_chars(<<$\\,$x,${,A,B,C,$},Rest/binary>>, Map, Octal, true, Acc) when ?is_hex(A), ?is_hex(B), ?is_hex(C) ->
append_escaped(Rest, Map, [A,B,C], Octal, true, Acc, 16);
unescape_chars(<<$\\,$x,${,A,B,C,D,$},Rest/binary>>, Map, Octal, true, Acc) when ?is_hex(A), ?is_hex(B), ?is_hex(C), ?is_hex(D) ->
append_escaped(Rest, Map, [A,B,C,D], Octal, true, Acc, 16);
unescape_chars(<<$\\,$x,${,A,B,C,D,E,$},Rest/binary>>, Map, Octal, true, Acc) when ?is_hex(A), ?is_hex(B), ?is_hex(C), ?is_hex(D), ?is_hex(E) ->
append_escaped(Rest, Map, [A,B,C,D,E], Octal, true, Acc, 16);
unescape_chars(<<$\\,$x,${,A,B,C,D,E,F,$},Rest/binary>>, Map, Octal, true, Acc) when ?is_hex(A), ?is_hex(B), ?is_hex(C), ?is_hex(D), ?is_hex(E), ?is_hex(F) ->
append_escaped(Rest, Map, [A,B,C,D,E,F], Octal, true, Acc, 16);
unescape_chars(<<$\\,Escaped,Rest/binary>>, Map, Octals, Hex, Acc) ->
case Map(Escaped) of
false -> unescape_chars(Rest, Map, Octals, <<Acc/binary, $\\, Escaped>>);
Other -> unescape_chars(Rest, Map, Octals, <<Acc/binary, Other>>)
false -> unescape_chars(Rest, Map, Octals, Hex, <<Acc/binary, $\\, Escaped>>);
Other -> unescape_chars(Rest, Map, Octals, Hex, <<Acc/binary, Other>>)
end;
unescape_chars(<<Char, Rest/binary>>, Map, Octals, Acc) ->
unescape_chars(Rest, Map, Octals, <<Acc/binary, Char>>);
unescape_chars(<<Char, Rest/binary>>, Map, Octals, Hex, Acc) ->
unescape_chars(Rest, Map, Octals, Hex, <<Acc/binary, Char>>);
unescape_chars(<<>>, _Map, _Octals, Acc) -> Acc.
unescape_chars(<<>>, _Map, _Octals, _Hex, Acc) -> Acc.
to_octal(Rest, Map, Octal, Acc) ->
unescape_chars(Rest, Map, true, <<Acc/binary, (list_to_integer(Octal, 8))/integer>>).
append_escaped(Rest, Map, List, Octal, Hex, Acc, Base) ->
Binary = unicode:characters_to_binary([list_to_integer(List, Base)]),
unescape_chars(Rest, Map, Octal, Hex, <<Acc/binary, Binary/binary>>).
% Unescape Helpers
+19 -1
View File
@@ -56,6 +56,12 @@ translate({ function, Line, [[{do,{ '->',_,Pairs}}]] }, S) ->
assert_no_assign_or_guard_scope(Line, 'function', S),
elixir_translator:translate_fn(Line, Pairs, S);
translate({ function, Line, [{ '/', _, [{{ '.', _ ,[M, F] }, _ , [] }, A]}] }, S) ->
translate({ function, Line, [M, F, A] }, S);
translate({ function, Line, [{ '/', _, [{F, _, Q}, A]}] }, S) when is_atom(Q) ->
translate({ function, Line, [F, A] }, S);
translate({ function, Line, [_] }, S) ->
assert_no_assign_or_guard_scope(Line, 'function', S),
syntax_error(Line, S#elixir_scope.file, "invalid args for function");
@@ -215,13 +221,21 @@ translate({Kind, Line, [Call]}, S) when ?FUNS(Kind) ->
translate({Kind, Line, [Call, Expr]}, S) when ?FUNS(Kind) ->
assert_module_scope(Line, Kind, S),
assert_no_function_scope(Line, Kind, S),
{ TCall, Guards } = elixir_clauses:extract_guards(Call),
{ Name, Args } = elixir_clauses:extract_args(TCall),
{ Name, Args } = case elixir_clauses:extract_args(TCall) of
error -> syntax_error(Line, S#elixir_scope.file,
"invalid syntax in ~s ~s", [Kind, 'Elixir.Macro':to_binary(TCall)]);
Tuple -> Tuple
end,
assert_no_aliases_name(Line, Name, Args, S),
TName = elixir_tree_helpers:abstract_syntax(Name),
TArgs = elixir_tree_helpers:abstract_syntax(Args),
TGuards = elixir_tree_helpers:abstract_syntax(Guards),
TExpr = elixir_tree_helpers:abstract_syntax(Expr),
{ elixir_def:wrap_definition(Kind, Line, TName, TArgs, TGuards, TExpr, S), S };
translate({Kind, Line, [Name, Args, Guards, Expr]}, S) when ?FUNS(Kind) ->
@@ -277,6 +291,10 @@ translate_in(Line, Left, Right, S) ->
lists:foldr(fun(X, Acc) ->
{ op, Line, 'orelse', { op, Line, '==', Var, X }, Acc }
end, { op, Line, '==', Var, H }, T);
{ string, _, [H|T] } ->
lists:foldl(fun(X, Acc) ->
{ op, Line, 'orelse', { op, Line, '==', Var, { integer, Line, X } }, Acc }
end, { op, Line, '==', Var, { integer, Line, H } }, T);
{ tuple, _, [{ atom, _, 'Elixir.Range' }, Start, End] } ->
case { Start, End } of
{ { K1, _, StartInt }, { K2, _, EndInt } } when ?IN_TYPES(K1), ?IN_TYPES(K2), StartInt =< EndInt ->
+47 -37
View File
@@ -40,18 +40,10 @@ docs_table(Module) ->
%% will be passed to the invoked function.
translate(Line, Ref, Block, S) ->
MetaBlock = elixir_tree_helpers:abstract_syntax(Block),
MetaS = elixir_scope:serialize(S),
MetaBlock = elixir_tree_helpers:abstract_syntax(Block),
{ MetaS, Vars } = elixir_scope:serialize_with_vars(Line, S),
Vars = orddict:fold(fun(Key, Value, { Acc, Counter }) ->
{ { cons, Line, { tuple, Line, [
{ atom, Line, Key },
{ atom, Line, ?ELIXIR_ATOM_CONCAT(["_@", Counter]) },
{ var, Line, Value }
] }, Acc }, Counter + 1 }
end, { { nil, Line }, 0 }, S#elixir_scope.vars),
Args = [{integer, Line, Line}, Ref, MetaBlock, element(1, Vars), MetaS],
Args = [{integer, Line, Line}, Ref, MetaBlock, Vars, MetaS],
?ELIXIR_WRAP_CALL(Line, ?MODULE, compile, Args).
%% The compilation hook.
@@ -92,8 +84,8 @@ compile(Line, Other, _Block, _Vars, #elixir_scope{file=File}) ->
elixir_errors:form_error(Line, File, ?MODULE, { invalid_module, Other });
compile(Line, Module, Block, Vars, RawS) ->
Dict = [{ X, Y } || { X, Y, _ } <- Vars],
S = elixir_scope:deserialize(RawS, Dict),
Dict = [{ { Name, Kind }, Value } || { Name, Kind, Value, _ } <- Vars],
S = elixir_scope:deserialize_with_vars(RawS, Dict),
compile(Line, Module, Block, Vars, S).
%% Hook that builds both attribute and functions and set up common hooks.
@@ -144,7 +136,12 @@ eval_form(Line, Module, Block, Vars, RawS) ->
Value.
%% Return the form with exports and function declarations.
functions_form(Line, File, Module, Export, Private, Def, Defmacro, Defmacrop, Functions, C) ->
functions_form(Line, File, Module, Export, Private, Def, Defmacro, Defmacrop, RawFunctions, C) ->
Functions = case elixir_compiler:get_opt(internal, C) of
true -> RawFunctions;
false -> record_rewrite_functions(RawFunctions)
end,
{ FinalExport, FinalFunctions } =
add_info_function(Line, File, Module, Export, Functions, Def, Defmacro, C),
@@ -155,6 +152,17 @@ functions_form(Line, File, Module, Export, Private, Def, Defmacro, Defmacrop, Fu
{attribute, Line, export, lists:sort(FinalExport)} | FinalFunctions
] }.
record_rewrite_functions(Functions) ->
lists:map(fun
({ function, Line, Name, Arity, Clauses }) ->
Rewriten = [begin
{ C, _, _ } = 'Elixir.Kernel.RecordRewriter':optimize_clause(Clause),
C
end || Clause <- Clauses],
{ function, Line, Name, Arity, Rewriten };
(Other) -> Other
end, Functions).
%% Add attributes handling to the form
attributes_form(Line, _File, Module, Current) ->
@@ -196,7 +204,9 @@ specs_form(Line, Module, Defmacro, DefmacropWithLine, Forms, C) ->
end.
specs_attributes(Line, Type, Forms, Specs) ->
Keys = lists:ukeysort(1, Specs),
Keys = lists:foldl(fun({ Tuple, Value }, Acc) ->
lists:keystore(Tuple, 1, Acc, { Tuple, Value } )
end, [], Specs),
lists:foldl(fun({ Tuple, _ }, Acc) ->
Values = [V || { K, V } <- Specs, K == Tuple],
[{ attribute, Line, Type, { Tuple, Values } }|Acc]
@@ -262,50 +272,50 @@ add_info_function(Line, File, Module, Export, Functions, Def, Defmacro, C) ->
true -> elixir_errors:form_error(Line, File, ?MODULE, {internal_function_overridden, Pair});
false ->
Docs = elixir_compiler:get_opt(docs, C),
Contents = { function, Line, '__info__', 1, [
functions_clause(Line, Def),
macros_clause(Line, Module, Def, Defmacro),
docs_clause(Line, Module, Docs),
Contents = { function, 0, '__info__', 1, [
functions_clause(Def),
macros_clause(Module, Def, Defmacro),
docs_clause(Module, Docs),
moduledoc_clause(Line, Module, Docs),
module_clause(Line, Module),
else_clause(Line)
module_clause(Module),
else_clause()
] },
{ [Pair|Export], [Contents|Functions] }
end.
functions_clause(Line, Def) ->
functions_clause(Def) ->
All = ordsets:add_element({'__info__',1}, Def),
{ clause, Line, [{ atom, Line, functions }], [], [elixir_tree_helpers:abstract_syntax(All)] }.
{ clause, 0, [{ atom, 0, functions }], [], [elixir_tree_helpers:abstract_syntax(All)] }.
macros_clause(Line, Module, Def, Defmacro) ->
macros_clause(Module, Def, Defmacro) ->
All = handle_builtin_macros(Module, Def, Defmacro),
{ clause, Line, [{ atom, Line, macros }], [], [elixir_tree_helpers:abstract_syntax(All)] }.
{ clause, 0, [{ atom, 0, macros }], [], [elixir_tree_helpers:abstract_syntax(All)] }.
handle_builtin_macros('Elixir.Kernel', Def, Defmacro) ->
ordsets:subtract(ordsets:union(Defmacro,
elixir_dispatch:in_erlang_macros()), Def);
handle_builtin_macros(_, _Def, Defmacro) -> Defmacro.
module_clause(Line, Module) ->
{ clause, Line, [{ atom, Line, module }], [], [{ atom, Line, Module }] }.
module_clause(Module) ->
{ clause, 0, [{ atom, 0, module }], [], [{ atom, 0, Module }] }.
docs_clause(Line, Module, true) ->
docs_clause(Module, true) ->
Docs = ordsets:from_list(ets:tab2list(docs_table(Module))),
{ clause, Line, [{ atom, Line, docs }], [], [elixir_tree_helpers:abstract_syntax(Docs)] };
{ clause, 0, [{ atom, 0, docs }], [], [elixir_tree_helpers:abstract_syntax(Docs)] };
docs_clause(Line, _Module, _) ->
{ clause, Line, [{ atom, Line, docs }], [], [{ atom, Line, nil }] }.
docs_clause(_Module, _) ->
{ clause, 0, [{ atom, 0, docs }], [], [{ atom, 0, nil }] }.
moduledoc_clause(Line, Module, true) ->
Docs = 'Elixir.Module':get_attribute(Module, moduledoc),
{ clause, Line, [{ atom, Line, moduledoc }], [], [elixir_tree_helpers:abstract_syntax({ Line, Docs })] };
{ clause, 0, [{ atom, 0, moduledoc }], [], [elixir_tree_helpers:abstract_syntax({ Line, Docs })] };
moduledoc_clause(Line, _Module, _) ->
{ clause, Line, [{ atom, Line, moduledoc }], [], [{ atom, Line, nil }] }.
moduledoc_clause(_Line, _Module, _) ->
{ clause, 0, [{ atom, 0, moduledoc }], [], [{ atom, 0, nil }] }.
else_clause(Line) ->
Info = { call, Line, { atom, Line, module_info }, [{ var, Line, atom }] },
{ clause, Line, [{ var, Line, atom }], [], [Info] }.
else_clause() ->
Info = { call, 0, { atom, 0, module_info }, [{ var, 0, atom }] },
{ clause, 0, [{ var, 0, atom }], [], [Info] }.
% HELPERS
+42 -42
View File
@@ -3,12 +3,12 @@
Nonterminals
grammar expr_list
expr block_expr fn_expr bracket_expr call_expr bracket_at_expr max_expr
expr paren_expr block_expr fn_expr bracket_expr call_expr bracket_at_expr max_expr
base_expr matched_expr matched_op_expr unmatched_expr op_expr
add_op mult_op unary_op two_op pipeline_op bin_concat_op
match_op send_op default_op when_op pipe_op in_op inc_op stab_op range_op
andand_op oror_op and_op or_op comp_expr_op colon_colon_op three_op at_op
open_paren close_paren
open_paren close_paren empty_paren
open_bracket close_bracket
open_curly close_curly
open_bit close_bit
@@ -20,13 +20,13 @@ Nonterminals
dot_op dot_ref dot_identifier dot_op_identifier dot_do_identifier
dot_paren_identifier dot_punctuated_identifier dot_bracket_identifier
var list bracket_access bit_string tuple
fn_block do_block do_eol end_eol block_eol block_item block_list
do_block fn_eol do_eol end_eol block_eol block_item block_list
.
Terminals
identifier kw_identifier punctuated_identifier
bracket_identifier paren_identifier do_identifier block_identifier
fn fn_paren 'end' '__aliases__'
fn 'end' '__aliases__'
number signed_number atom bin_string list_string sigil
dot_call_op comp_op op_identifier
'not' 'and' 'or' 'xor' 'when' 'in' 'inlist' 'inbits' 'do'
@@ -43,8 +43,8 @@ Rootsymbol grammar.
Left 5 do.
Right 10 '->'.
Left 20 ','. % Solve nested call_args conflicts
Right 30 when_op.
Right 40 colon_colon_op.
Right 30 colon_colon_op.
Right 40 when_op.
Right 50 default_op.
Left 60 pipe_op.
Left 70 inc_op.
@@ -80,9 +80,12 @@ grammar -> '$empty' : [nil].
% Note expressions are on reverse order
expr_list -> expr : ['$1'].
expr_list -> open_paren ')' : [nil].
expr_list -> empty_paren : [nil].
expr_list -> expr_list eol expr : ['$3'|'$1'].
paren_expr -> empty_paren : nil.
paren_expr -> expr : '$1'.
expr -> matched_expr : '$1'.
expr -> unmatched_expr : '$1'.
@@ -146,10 +149,8 @@ block_expr -> dot_punctuated_identifier call_args_no_parens do_block : build_ide
block_expr -> dot_do_identifier do_block : build_identifier('$1', '$2').
block_expr -> dot_identifier call_args_no_parens do_block : build_identifier('$1', '$2' ++ '$3').
fn_expr -> fn_paren call_args_parens fn_block : build_fn('$1', '$2', '$3').
fn_expr -> fn call_args_no_parens fn_block : build_fn('$1', '$2', '$3').
fn_expr -> fn call_args_parens_not_one fn_block : build_fn('$1', '$2', '$3').
fn_expr -> fn fn_block : build_fn('$1', [], '$2').
fn_expr -> fn_eol stab_expr_list end_eol : build_fn('$1', build_stab(lists:reverse('$2'))).
fn_expr -> fn_eol '->' grammar 'end' : build_fn('$1', { '->', ?line('$2'), [{ [], build_block('$3') }] }).
fn_expr -> call_expr : '$1'.
call_expr -> dot_punctuated_identifier call_args_no_parens : build_identifier('$1', '$2').
@@ -165,7 +166,7 @@ max_expr -> parens_call call_args_parens : build_identifier('$1', '$2').
max_expr -> parens_call call_args_parens call_args_parens : { build_identifier('$1', '$2'), ?line('$1'), '$3' }.
max_expr -> dot_ref : '$1'.
max_expr -> base_expr : '$1'.
max_expr -> open_paren expr_list close_paren : build_block('$2').
max_expr -> open_paren stab_expr_list close_paren : build_stab(lists:reverse('$2')).
bracket_expr -> dot_bracket_identifier bracket_access : build_access(build_identifier('$1', nil), '$2').
bracket_expr -> max_expr bracket_access : build_access('$1', '$2').
@@ -192,13 +193,14 @@ base_expr -> sigil : build_sigil('$1').
%% Blocks
fn_block -> '->' grammar 'end' : build_block('$2', false).
do_block -> do_eol 'end' : [[{do,nil}]].
do_block -> do_eol stab_expr_list end_eol : [[{ do, build_stab(lists:reverse('$2')) }]].
do_block -> do_eol block_list 'end' : [[{ do, nil }|'$2']].
do_block -> do_eol stab_expr_list eol block_list 'end' : [[{ do, build_stab(lists:reverse('$2')) }|'$4']].
fn_eol -> 'fn' : '$1'.
fn_eol -> 'fn' eol : '$1'.
do_eol -> 'do' : '$1'.
do_eol -> 'do' eol : '$1'.
@@ -230,6 +232,8 @@ open_paren -> '(' eol : '$1'.
close_paren -> ')' : '$1'.
close_paren -> eol ')' : '$2'.
empty_paren -> open_paren ')' : nil.
open_bracket -> '[' : '$1'.
open_bracket -> '[' eol : '$1'.
close_bracket -> ']' : '$1'.
@@ -385,32 +389,32 @@ call_args_no_parens -> matched_comma_expr : lists:reverse('$1').
call_args_no_parens -> matched_kw_base : ['$1'].
call_args_no_parens -> matched_comma_expr ',' matched_kw_base : lists:reverse(['$3'|'$1']).
base_comma_expr -> expr ',' : ['$1'].
base_comma_expr -> base_comma_expr expr ',' : ['$2'|'$1'].
call_args_parens_not_one -> open_paren ')' : [].
call_args_parens_not_one -> open_paren matched_kw_base close_paren : ['$2'].
call_args_parens_not_one -> open_paren matched_expr ',' call_args_no_parens close_paren : ['$2'|'$4'].
comma_expr -> expr : ['$1'].
base_comma_expr -> paren_expr ',' : ['$1'].
base_comma_expr -> base_comma_expr paren_expr ',' : ['$2'|'$1'].
comma_expr -> paren_expr : ['$1'].
comma_expr -> kw_base : ['$1'].
comma_expr -> base_comma_expr : '$1'.
comma_expr -> base_comma_expr expr : ['$2'|'$1'].
comma_expr -> base_comma_expr paren_expr : ['$2'|'$1'].
comma_expr -> base_comma_expr kw_base : ['$2'|'$1'].
optional_comma_expr -> expr : '$1'.
optional_comma_expr -> expr ',' : '$1'.
optional_comma_expr -> paren_expr : '$1'.
optional_comma_expr -> paren_expr ',' : '$1'.
call_args -> comma_expr : lists:reverse('$1').
call_args_parens -> open_paren ')' : [].
call_args_parens -> open_paren call_args close_paren : '$2'.
call_args_parens_not_one -> open_paren ')' : [].
call_args_parens_not_one -> open_paren kw_base close_paren : ['$2'].
call_args_parens_not_one -> open_paren expr ',' call_args close_paren : ['$2'|'$4'].
% KV
kw_eol -> kw_identifier : '$1'.
kw_eol -> kw_identifier eol : '$1'.
kw_expr -> kw_eol expr : { ?exprs('$1'),'$2' }.
kw_expr -> kw_eol paren_expr : { ?exprs('$1'),'$2' }.
kw_comma -> kw_expr ',' : ['$1'].
kw_comma -> kw_comma kw_expr ',' : ['$2'|'$1'].
@@ -420,6 +424,7 @@ kw_base -> kw_comma : lists:reverse('$1').
kw_base -> kw_comma kw_expr : lists:reverse(['$2'|'$1']).
matched_kw_expr -> kw_eol matched_expr : {?exprs('$1'),'$2'}.
matched_kw_expr -> kw_eol empty_paren : {?exprs('$1'),nil}.
matched_kw_comma -> matched_kw_expr : ['$1'].
matched_kw_comma -> matched_kw_expr ',' matched_kw_comma : ['$1'|'$3'].
matched_kw_base -> matched_kw_comma : '$1'.
@@ -433,13 +438,13 @@ bracket_access -> open_bracket kw_base close_bracket : { '$2', ?line('$1') }.
list -> open_bracket ']' : [].
list -> open_bracket kw_base close_bracket : '$2'.
list -> open_bracket optional_comma_expr close_bracket : ['$2'].
list -> open_bracket expr ',' call_args close_bracket : ['$2'|'$4'].
list -> open_bracket paren_expr ',' call_args close_bracket : ['$2'|'$4'].
% Tuple
tuple -> open_curly '}' : build_tuple('$1', []).
tuple -> open_curly optional_comma_expr close_curly : build_tuple('$1', ['$2']).
tuple -> open_curly expr ',' call_args close_curly : build_tuple('$1', ['$2'|'$4']).
tuple -> open_curly paren_expr ',' call_args close_curly : build_tuple('$1', ['$2'|'$4']).
% Bitstrings
@@ -481,13 +486,10 @@ build_tuple(Marker, Args) ->
%% Blocks
build_block(Exprs) -> build_block(Exprs, true).
build_block([nil], _) -> { '__block__', 0, [nil] };
build_block([{Op,_,[_]}]=Exprs, _) when ?rearrange_uop(Op) -> { '__block__', 0, Exprs };
build_block([Expr], _) when not is_list(Expr) -> Expr;
build_block(Exprs, true) -> { '__block__', 0, lists:reverse(Exprs) };
build_block(Exprs, false) -> { '__block__', 0, Exprs }.
build_block([nil]) -> { '__block__', 0, [nil] };
build_block([{Op,_,[_]}]=Exprs) when ?rearrange_uop(Op) -> { '__block__', 0, Exprs };
build_block([Expr]) when not is_list(Expr) -> Expr;
build_block(Exprs) -> { '__block__', 0, Exprs }.
%% Dots
@@ -509,7 +511,7 @@ build_identifier({ '.', Line, _ } = Dot, Args) ->
end,
{ Dot, Line, FArgs };
build_identifier({ Keyword, Line }, Args) when Keyword == fn; Keyword == fn_paren ->
build_identifier({ Keyword, Line }, Args) when Keyword == fn ->
{ fn, Line, Args };
build_identifier({ op_identifier, Line, Identifier }, Args) ->
@@ -527,10 +529,8 @@ extract_identifier(Other) -> Other.
%% Fn
build_fn(Op, Args, Expr) ->
Line = ?line(Op),
Stab = [{do, { '->', Line, [{ Args, Expr }] }}],
{ fn, Line, [Stab] }.
build_fn(Op, Stab) ->
{ fn, ?line(Op), [[{ do, Stab }]] }.
%% Access
@@ -558,15 +558,15 @@ build_stab([{ '->', Line, [Left, Right] }|T]) ->
{ '->', Line, build_stab(T, Left, [Right], []) };
build_stab(Else) ->
build_block(Else, false).
build_block(Else).
build_stab([{ '->', _, [Left, Right] }|T], Marker, Temp, Acc) ->
H = { Marker, build_block(Temp) },
H = { Marker, build_block(lists:reverse(Temp)) },
build_stab(T, Left, [Right], [H|Acc]);
build_stab([H|T], Marker, Temp, Acc) ->
build_stab(T, Marker, [H|Temp], Acc);
build_stab([], Marker, Temp, Acc) ->
H = { Marker, build_block(Temp) },
H = { Marker, build_block(lists:reverse(Temp)) },
lists:reverse([H|Acc]).
+25 -20
View File
@@ -1,4 +1,3 @@
%% Convenience functions used to manipulate scope
%% and its variables.
-module(elixir_scope).
@@ -6,7 +5,8 @@
build_erl_var/2, build_ex_var/2,
build_erl_var/3, build_ex_var/3,
build_erl_var/4, build_ex_var/4,
serialize/1, deserialize/1, deserialize/2,
serialize/1, deserialize/1,
serialize_with_vars/2, deserialize_with_vars/2,
to_erl_env/1, to_ex_env/1, filename/1,
umergev/2, umergec/2, merge_clause_vars/2
]).
@@ -23,20 +23,20 @@ translate_var(Line, Name, Kind, S) ->
case S#elixir_scope.context of
assign ->
TempVars = S#elixir_scope.temp_vars,
case { orddict:is_key(Name, Vars), orddict:find(Name, TempVars) } of
case { orddict:is_key({ Name, Kind }, Vars), orddict:find(Name, TempVars) } of
{ true, { ok, Kind } } ->
{ {var, Line, orddict:fetch(Name, Vars) }, S };
{ { var, Line, orddict:fetch({ Name, Kind }, Vars) }, S };
{ Else, _ } ->
{ NewVar, NS } = if
Kind == quoted -> build_erl_var(Line, S);
Else -> build_erl_var(Line, Name, S);
S#elixir_scope.noname -> build_erl_var(Line, Name, S);
true -> { {var, Line, Name}, S }
true -> { { var, Line, Name }, S }
end,
RealName = element(3, NewVar),
ClauseVars = S#elixir_scope.clause_vars,
{ NewVar, NS#elixir_scope{
vars=orddict:store(Name, RealName, Vars),
vars=orddict:store({ Name, Kind }, RealName, Vars),
temp_vars=orddict:store(Name, Kind, TempVars),
clause_vars=if
ClauseVars == nil -> nil;
@@ -45,9 +45,9 @@ translate_var(Line, Name, Kind, S) ->
} }
end;
_ ->
case orddict:is_key(Name, Vars) of
false -> elixir_translator:translate_each({Name, Line, []}, S);
true -> { {var, Line, orddict:fetch(Name, Vars) }, S }
case orddict:find({ Name, Kind }, Vars) of
{ ok, VarName } -> { { var, Line, VarName }, S };
error -> elixir_translator:translate_each({ Name, Line, [] }, S)
end
end
end.
@@ -98,11 +98,22 @@ serialize(S) ->
S#elixir_scope.requires, S#elixir_scope.macros, S#elixir_scope.aliases, S#elixir_scope.scheduled }
).
serialize_with_vars(Line, S) ->
{ Vars, _ } = orddict:fold(fun({ Key, Kind }, Value, { Acc, Counter }) ->
{ { cons, Line, { tuple, Line, [
{ atom, Line, Key },
{ atom, Line, Kind },
{ atom, Line, ?ELIXIR_ATOM_CONCAT(["_@", Counter]) },
{ var, Line, Value }
] }, Acc }, Counter + 1 }
end, { { nil, Line }, 0 }, S#elixir_scope.vars),
{ serialize(S), Vars }.
% Fill in the scope with the variables serialization set in serialize_scope.
deserialize(Tuple) -> deserialize(Tuple, []).
deserialize(Tuple) -> deserialize_with_vars(Tuple, []).
deserialize({ File, Functions, CheckClauses, Requires, Macros, Aliases, Scheduled }, Vars) ->
deserialize_with_vars({ File, Functions, CheckClauses, Requires, Macros, Aliases, Scheduled }, Vars) ->
#elixir_scope{
file=File,
functions=Functions,
@@ -121,13 +132,10 @@ deserialize({ File, Functions, CheckClauses, Requires, Macros, Aliases, Schedule
umergev(S1, S2) ->
V1 = S1#elixir_scope.vars,
V2 = S2#elixir_scope.vars,
Q1 = S1#elixir_scope.quote_vars,
Q2 = S2#elixir_scope.quote_vars,
C1 = S1#elixir_scope.clause_vars,
C2 = S2#elixir_scope.clause_vars,
S2#elixir_scope{
vars=orddict:merge(fun var_merger/3, V1, V2),
quote_vars=orddict:merge(fun var_merger/3, Q1, Q2),
clause_vars=merge_clause_vars(C1, C2)
}.
@@ -148,13 +156,10 @@ umergec(S1, S2) ->
merge_clause_vars(nil, _C2) -> nil;
merge_clause_vars(_C1, nil) -> nil;
merge_clause_vars(C1, C2) ->
orddict:merge(fun clause_var_merger/3, C1, C2).
orddict:merge(fun var_merger/3, C1, C2).
clause_var_merger({ Var, _ }, K1, K2) ->
var_merger(Var, K1, K2).
var_merger(Var, Var, K2) -> K2;
var_merger(Var, K1, Var) -> K1;
var_merger({ Var, _ }, Var, K2) -> K2;
var_merger({ Var, _ }, K1, Var) -> K1;
var_merger(_Var, K1, K2) ->
V1 = var_number(atom_to_list(K1), []),
V2 = var_number(atom_to_list(K2), []),
+6 -9
View File
@@ -269,10 +269,10 @@ tokenize([$:,T|Rest], Line, Scope, Tokens) when ?comp1(T); ?op1(T); T == $&; T =
% End of line
tokenize(";" ++ Rest, Line, Scope, []) ->
tokenize(Rest, Line, Scope, eol(Line, $;, []));
tokenize(Rest, Line, Scope, eol(Line, ';', []));
tokenize(";" ++ Rest, Line, Scope, [Top|Tokens]) when element(1, Top) /= eol ->
tokenize(Rest, Line, Scope, eol(Line, $;, [Top|Tokens]));
tokenize(Rest, Line, Scope, eol(Line, ';', [Top|Tokens]));
tokenize("\\\n" ++ Rest, Line, Scope, Tokens) ->
tokenize(Rest, Line + 1, Scope, Tokens);
@@ -281,10 +281,10 @@ tokenize("\\\r\n" ++ Rest, Line, Scope, Tokens) ->
tokenize(Rest, Line + 1, Scope, Tokens);
tokenize("\n" ++ Rest, Line, Scope, Tokens) ->
tokenize(Rest, Line + 1, Scope, eol(Line, $\n, Tokens));
tokenize(Rest, Line + 1, Scope, eol(Line, newline, Tokens));
tokenize("\r\n" ++ Rest, Line, Scope, Tokens) ->
tokenize(Rest, Line + 1, Scope, eol(Line, $\n, Tokens));
tokenize(Rest, Line + 1, Scope, eol(Line, newline, Tokens));
% Stand-alone tokens
@@ -702,7 +702,7 @@ next_is_block([$d,$o|_]) ->
next_is_block(_) ->
false.
add_token_with_nl(Left, [{eol,_,$\n}|T]) -> [Left|T];
add_token_with_nl(Left, [{eol,_,newline}|T]) -> [Left|T];
add_token_with_nl(Left, T) -> [Left|T].
% Error handling
@@ -729,7 +729,7 @@ handle_terminator(Token, #scope{terminators=Terminators} = Scope) ->
New -> Scope#scope{terminators=New}
end.
check_terminator({ S, Line }, Terminators) when S == 'fn'; S == 'fn_paren' ->
check_terminator({ S, Line }, Terminators) when S == 'fn' ->
[{ fn, Line }|Terminators];
check_terminator({ S, _ } = New, Terminators) when
@@ -770,9 +770,6 @@ terminator('{') -> '}';
terminator('<<') -> '>>'.
% Keywords check
handle_keyword(Line, paren_identifier, fn, Tokens) ->
[{ 'fn_paren', Line }|Tokens];
handle_keyword(Line, Identifier, Atom, [{ '.', _ }|_] = Tokens) ->
[{ Identifier, Line, Atom }|Tokens];
+28 -41
View File
@@ -77,9 +77,9 @@ translate_each({ '__op__', Line, [Op, Left, Right] }, S) when is_atom(Op) ->
{ { op, Line, convert_op(Op), TLeft, TRight }, NS };
translate_each({ '__ambiguousop__', Line, [Var, H|T] }, S) ->
{ Name, _, _ } = Var,
{ Name, _, Kind } = Var,
case orddict:find(Name, S#elixir_scope.vars) of
case orddict:find({ Name, Kind }, S#elixir_scope.vars) of
error -> translate_each({ Name, Line, [H|T] }, S);
_ ->
case T of
@@ -103,7 +103,7 @@ translate_each({ alias, Line, [Ref, KV] }, S) ->
{ atom, _, Old } ->
{ New, SF } = case lists:keyfind(as, 1, KV) of
Opt when Opt == { as, true }; Opt == false ->
{ elixir_aliases:last(Old), SR };
{ elixir_aliases:last(Old), SR };
{ as, false } ->
{ Old, SR };
{ as, Other } ->
@@ -284,7 +284,7 @@ translate_each({ quote, GivenLine, [T] }, S) when is_list(T) ->
translate_each({ quote, GivenLine, [_] }, S) ->
syntax_error(GivenLine, S#elixir_scope.file, "invalid args for quote");
%% Functions
translate_each({ fn, Line, [[{do, { '->', _, Pairs }}]] }, S) ->
@@ -330,41 +330,23 @@ translate_each({ 'super?', Line, [] }, S) ->
%% Variables
translate_each({ '^', Line, [ { Name, _, Args } ] }, S) ->
Dict = case Args of
nil -> S#elixir_scope.vars;
quoted -> S#elixir_scope.quote_vars;
_ ->
syntax_error(Line, S#elixir_scope.file, "cannot use ^ with expression at ^~s, ^ must be used only with variables", [Name])
end,
translate_each({ '^', Line, [ { Name, _, Kind } ] }, S) when is_list(Kind) ->
syntax_error(Line, S#elixir_scope.file, "cannot use ^ with expression at ^~s, ^ must be used only with variables", [Name]);
Result = case S#elixir_scope.context of
assign ->
case orddict:find(Name, Dict) of
error -> "unbound variable ^~s";
{ ok, Value } -> { {var, Line, Value}, S }
end;
_ -> "cannot access variable ^~s outside of assignment"
end,
case is_list(Result) of
true ->
syntax_error(Line, S#elixir_scope.file, Result, [Name]);
false ->
Result
translate_each({ '^', Line, [ { Name, _, Kind } ] }, #elixir_scope{context=assign} = S) when is_atom(Kind) ->
case orddict:find({ Name, Kind }, S#elixir_scope.vars) of
{ ok, Value } ->
{ { var, Line, Value }, S };
error ->
syntax_error(Line, S#elixir_scope.file, "unbound variable ^~s", [Name])
end;
translate_each({ Name, Line, quoted }, S) when is_atom(Name) ->
NewS = S#elixir_scope{vars=S#elixir_scope.quote_vars,noname=true},
{ TVar, VS } = elixir_scope:translate_var(Line, Name, quoted, NewS),
{ TVar, VS#elixir_scope{
quote_vars=VS#elixir_scope.vars,
noname=S#elixir_scope.noname,
vars=S#elixir_scope.vars
} };
translate_each({ '^', Line, [ { Name, _, Kind } ] }, S) when is_atom(Kind) ->
syntax_error(Line, S#elixir_scope.file,
"cannot access variable ^~s outside of assignment", [Name]);
translate_each({ Name, Line, nil }, S) when is_atom(Name) ->
elixir_scope:translate_var(Line, Name, nil, S);
translate_each({ Name, Line, Kind }, S) when is_atom(Name), (Kind == nil orelse Kind == quoted) ->
elixir_scope:translate_var(Line, Name, Kind, S);
%% Local calls
@@ -462,7 +444,14 @@ translate_fn(Line, Clauses, S) ->
end,
{ TClauses, NS } = lists:mapfoldl(Transformer, S, Clauses),
{ { 'fun', Line, {clauses, TClauses} }, umergec(S, NS) }.
Arities = [length(Args) || { clause, _Line, Args, _Guards, _Exprs } <- TClauses],
case length(lists:usort(Arities)) of
1 ->
{ { 'fun', Line, { clauses, TClauses } }, umergec(S, NS) };
_ ->
syntax_error(Line, S#elixir_scope.file, "cannot mix clauses with different arities in function definition")
end.
translate_local(Line, Name, Args, #elixir_scope{local=nil} = S) ->
{ TArgs, NS } = translate_args(Args, S),
@@ -511,11 +500,9 @@ translate_apply(Line, TLeft, TRight, Args, S, SL, SR) ->
Optimize = case (Args == []) orelse lists:last(Args) of
{ '|', _, _ } -> false;
_ ->
case { TLeft, TRight } of
{ { Kind, _, _ }, { atom, _, _ } } when Kind == var; Kind == tuple; Kind == atom ->
true;
_ ->
false
case TRight of
{ atom, _, _ } -> true;
_ -> false
end
end,
+17 -2
View File
@@ -1,5 +1,5 @@
-module(elixir_try).
-export([clauses/3]).
-export([clauses/3, format_error/1]).
-import(elixir_scope, [umergec/2]).
-include("elixir.hrl").
-compile({parse_transform, elixir_transform}).
@@ -86,6 +86,16 @@ normalize_rescue(_, { in, Line, [Left, Right] }, S) ->
end
end;
normalize_rescue(_, { '=', Line, [{ '__aliases__', _, _ } = Alias, { Name, _, Atom } = Var] }, S)
when is_atom(Name) and is_atom(Atom) ->
elixir_errors:handle_file_warning(S#elixir_scope.file, { Line, ?MODULE, { rescue_no_match, Var, Alias } }),
false;
normalize_rescue(_, { '=', Line, [{ Name, _, Atom } = Var, { '__aliases__', _, _ } = Alias] }, S)
when is_atom(Name) and is_atom(Atom) ->
elixir_errors:handle_file_warning(S#elixir_scope.file, { Line, ?MODULE, { rescue_no_match, Var, Alias } }),
false;
normalize_rescue(Line, Condition, S) ->
case elixir_translator:translate_each(Condition, S#elixir_scope{context=assign}) of
{ { atom, _, Atom }, _ } ->
@@ -94,7 +104,6 @@ normalize_rescue(Line, Condition, S) ->
false
end.
%% Convert rescue clauses into guards.
rescue_guards(_, Var, nil, _) -> { Var, false };
@@ -254,6 +263,12 @@ validate_rescue_access(_, _, _) -> [].
%% Helpers
format_error({ rescue_no_match, Var, Alias }) ->
VarBinary = 'Elixir.Macro':to_binary(Var),
AliasBinary = 'Elixir.Macro':to_binary(Alias),
Message = "rescue clause (~s = ~s) can never match, maybe you meant to write: ~s in [~s] ?",
io_lib:format(Message, [AliasBinary, VarBinary, VarBinary, AliasBinary]).
is_var({ Name, _, Atom }) when is_atom(Name), is_atom(Atom) -> true;
is_var(_) -> false.
+5 -5
View File
@@ -7,14 +7,14 @@ defmodule BehaviourTest do
use Behaviour
@doc "Foo"
defcallback foo(atom, binary), do: binary
defcallback foo(atom, binary) :: binary
@doc "Bar"
defcallback bar(External.hello, my_var :: binary), do: binary
defcallback bar(External.hello, my_var :: binary) :: binary
end
test :docs do
docs = Enum.qsort(Sample.__behaviour__(:docs))
docs = Enum.sort(Sample.__behaviour__(:docs))
assert docs == [
{{:bar,2},13,"Bar"},
{{:foo,2},10,"Foo"}
@@ -33,14 +33,14 @@ defmodule BehaviourTest do
assert_raise ArgumentError, fn ->
defmodule WithDefault do
use Behaviour
defcallback hello(num // 0 :: integer), do: integer
defcallback hello(num // 0 :: integer) :: integer
end
end
assert_raise ArgumentError, fn ->
defmodule WithDefault do
use Behaviour
defcallback hello(num :: integer // 0), do: integer
defcallback hello(num :: integer // 0) :: integer
end
end
end
+8
View File
@@ -52,6 +52,14 @@ bar
assert "\18" == <<1,?8>>
end
test :hex do
assert "\xE9" == "é"
assert "\xFF" == "ÿ"
assert "\x{A}"== "\n"
assert "\x{E9}"== "é"
assert "\x{10FFFF}" == <<244,143,191,191>>
end
test :match do
assert is_match?("ab", ?a)
assert not is_match?("cd", ?a)
+4 -4
View File
@@ -35,21 +35,21 @@ defmodule CodeTest do
test :require do
Code.require_file fixture_path("code_sample.exs")
assert fixture_path("code_sample.exs") in Code.loaded_files
assert fixture_path("code_sample.exs") inlist Code.loaded_files
assert Code.require_file(fixture_path("code_sample.exs")) == nil
Code.unload_files [fixture_path("code_sample.exs")]
refute fixture_path("code_sample.exs") in Code.loaded_files
refute fixture_path("code_sample.exs") inlist Code.loaded_files
assert Code.require_file(fixture_path("code_sample.exs")) != nil
end
test :path_manipulation do
path = File.expand_path("../binary", __FILE__)
Code.prepend_path path
assert binary_to_list(path) in :code.get_path
assert binary_to_list(path) inlist :code.get_path
Code.delete_path path
refute binary_to_list(path) in :code.get_path
refute binary_to_list(path) inlist :code.get_path
end
test :file do
+6 -6
View File
@@ -15,16 +15,16 @@ defmodule DictTest.Common do
dict = new_dict [{"first_key", 1}, {"second_key", 2}]
assert 2 == Dict.size dict
assert ["first_key", "second_key"] == List.sort Dict.keys dict
assert [1, 2] == List.sort Dict.values dict
assert ["first_key", "second_key"] == Enum.sort Dict.keys dict
assert [1, 2] == Enum.sort Dict.values dict
end
test :new_pairs_with_transform do
dict = new_dict [{1}, {2}, {3}], fn {x} -> { <<x + 64>>, x } end
assert 3 == Dict.size dict
assert ["A", "B", "C"] == List.sort Dict.keys dict
assert [1, 2, 3] == List.sort Dict.values dict
assert ["A", "B", "C"] == Enum.sort Dict.keys dict
assert [1, 2, 3] == Enum.sort Dict.values dict
end
test :get do
@@ -49,12 +49,12 @@ defmodule DictTest.Common do
end
test :keys do
assert ["first_key", "second_key"] == List.sort Dict.keys new_dict
assert ["first_key", "second_key"] == Enum.sort Dict.keys new_dict
assert [] == Dict.keys empty_dict
end
test :values do
assert [1, 2] ==List.sort Dict.values(new_dict)
assert [1, 2] ==Enum.sort Dict.values(new_dict)
assert [] == Dict.values empty_dict
end
+26 -5
View File
@@ -60,6 +60,7 @@ defmodule EnumTest.List do
assert Enum.drop([1,2,3], 2) == [3]
assert Enum.drop([1,2,3], 3) == []
assert Enum.drop([1,2,3], 4) == []
assert Enum.drop([1,2,3], -1) == [3]
assert Enum.drop([], 3) == []
end
@@ -173,8 +174,9 @@ defmodule EnumTest.List do
assert Enum.reverse([1,2,3]) == [3,2,1]
end
test :qsort do
assert Enum.qsort([5,3,2,4,1]) == [1,2,3,4,5]
test :sort do
assert Enum.sort([5,3,2,4,1]) == [1,2,3,4,5]
assert Enum.sort([5,3,2,4,1], &1 > &2) == [5,4,3,2,1]
end
test :split do
@@ -205,6 +207,7 @@ defmodule EnumTest.List do
assert Enum.take([1,2,3], 2) == [1,2]
assert Enum.take([1,2,3], 3) == [1,2,3]
assert Enum.take([1,2,3], 4) == [1,2,3]
assert Enum.take([1,2,3], -1) == [1,2]
assert Enum.take([], 3) == []
end
@@ -215,6 +218,10 @@ defmodule EnumTest.List do
assert Enum.take_while([], fn(_) -> true end) == []
end
test :uniq do
assert Enum.uniq([1,2,3,2,1]) == [1,2,3]
end
test :zip do
assert Enum.zip([:a, :b], [1, 2]) == [{:a, 1}, {:b, 2}]
assert Enum.zip([:a, :b], [1, 2, 3, 4]) == [{:a, 1}, {:b, 2}]
@@ -359,9 +366,9 @@ defmodule EnumTest.Dict.Common do
assert { all, [] } == Enum.partition(dict, fn({_k, v}) -> v < 10 end)
end
test :qsort do
test :sort do
dict = unquote(module).new [{:b,2},{:c,3},{:a,1},{:d,4}]
assert Enum.qsort(dict) == [a: 1, b: 2, c: 3, d: 4]
assert Enum.sort(dict) == [a: 1, b: 2, c: 3, d: 4]
end
end
end
@@ -458,10 +465,10 @@ defmodule EnumTest.Range do
assert_raise Enum.OutOfBoundsError, fn ->
assert Enum.at!(2..6, 8)
end
assert_raise Enum.OutOfBoundsError, fn ->
assert Enum.at!(-2..-6, 8)
end
end
test :count do
@@ -642,6 +649,16 @@ defmodule EnumTest.Range do
assert Enum.partition(range, fn(x) -> rem(x, 2) == 0 end) == { [2], [1,3] }
end
test :sort do
assert Enum.sort(Range.new(first: 3, last: 1)) == [1,2,3]
assert Enum.sort(Range.new(first: 2, last: 1)) == [1,2]
assert Enum.sort(Range.new(first: 1, last: 1)) == [1]
assert Enum.sort(Range.new(first: 3, last: 1), &1 > &2) == [3,2,1]
assert Enum.sort(Range.new(first: 2, last: 1), &1 > &2) == [2,1]
assert Enum.sort(Range.new(first: 1, last: 1), &1 > &2) == [1]
end
test :split do
range = Range.new(first: 1, last: 3)
assert Enum.split(range, 0) == { [], [1,2,3] }
@@ -690,6 +707,10 @@ defmodule EnumTest.Range do
assert Enum.take_while([], fn(_) -> true end) == []
end
test :uniq do
assert Enum.uniq(1..3) == [1,2,3]
end
test :zip do
assert Enum.zip([:a, :b], 1..2) == [{:a, 1}, {:b, 2}]
assert Enum.zip([:a, :b], 1..4) == [{:a, 1}, {:b, 2}]
@@ -8,6 +8,13 @@ defmodule Kernel.ExceptionTest do
refute is_exception({ :foo, :bar })
end
test :format_environment do
env = __ENV__.file("foo").line(12)
assert Exception.env_stacktrace(env) == " foo:12: Kernel.ExceptionTest.test_format_environment/0\n"
env = env.function(nil)
assert Exception.env_stacktrace(env) == " foo:12: Kernel.ExceptionTest (body)\n"
end
test :format_stacktrace_with_no_file_or_line do
assert Exception.format_stacktrace({Foo, :bar, [1, 2, 3], []}) == "Foo.bar(1, 2, 3)"
assert Exception.format_stacktrace({Foo, :bar, [], []}) == "Foo.bar()"
+63 -12
View File
@@ -189,9 +189,9 @@ defmodule FileTest do
{ :ok, files } = File.cp_r(src, dest)
assert length(files) == 8
assert tmp_path("tmp/cp_r/a") in files
assert tmp_path("tmp/cp_r/c") in files
assert tmp_path("tmp/cp_r/a/1.txt") in files
assert tmp_path("tmp/cp_r/a") inlist files
assert tmp_path("tmp/cp_r/c") inlist files
assert tmp_path("tmp/cp_r/a/1.txt") inlist files
assert { :ok, 'certainly/invalid' } = :file.read_link(tmp_path("tmp/cp_r/c"))
@@ -878,8 +878,8 @@ defmodule FileTest do
{ :ok, files } = File.rm_rf(fixture)
assert length(files) == 8
assert fixture in files
assert tmp_path("tmp/a/1.txt") in files
assert fixture inlist files
assert tmp_path("tmp/a/1.txt") inlist files
refute File.exists?(tmp_path("tmp/a/1.txt"))
refute File.exists?(tmp_path("tmp/a/a/2.txt"))
@@ -912,8 +912,8 @@ defmodule FileTest do
{ :ok, files } = File.rm_rf(fixture)
assert length(files) == 8
assert fixture in files
assert (tmp_path("tmp/a/1.txt") /> to_char_list) in files
assert fixture inlist files
assert (tmp_path("tmp/a/1.txt") /> to_char_list) inlist files
refute File.exists?(tmp_path("tmp/a/1.txt"))
refute File.exists?(tmp_path("tmp/a/a/2.txt"))
@@ -948,8 +948,8 @@ defmodule FileTest do
files = File.rm_rf!(fixture)
assert length(files) == 8
assert fixture in files
assert tmp_path("tmp/a/1.txt") in files
assert fixture inlist files
assert tmp_path("tmp/a/1.txt") inlist files
refute File.exists?(tmp_path("tmp/a/1.txt"))
refute File.exists?(tmp_path("tmp/a/a/2.txt"))
@@ -992,7 +992,7 @@ defmodule FileTest do
iterator = File.iterator(src)
File.open dest, [:write], fn(target) ->
Enum.each iterator, fn(line) ->
IO.puts target, Regex.replace(%r/"/, line, "'")
IO.write target, Regex.replace(%r/"/, line, "'")
end
end
assert File.read(dest) == { :ok, "FOO\n" }
@@ -1009,7 +1009,7 @@ defmodule FileTest do
{ :ok, iterator } = File.iterator(src)
File.open dest, [:write], fn(target) ->
Enum.each iterator, fn(line) ->
IO.puts target, Regex.replace(%r/"/, line, "'")
IO.write target, Regex.replace(%r/"/, line, "'")
end
end
assert File.read(dest) == { :ok, "FOO\n" }
@@ -1026,7 +1026,58 @@ defmodule FileTest do
iterator = File.iterator!(src)
File.open dest, [:write], fn(target) ->
Enum.each iterator, fn(line) ->
IO.puts target, Regex.replace(%r/"/, line, "'")
IO.write target, Regex.replace(%r/"/, line, "'")
end
end
assert File.read(dest) == { :ok, "FOO\n" }
after
File.rm(dest)
end
end
test :biniterator do
src = File.open! fixture_path("foo.txt")
dest = tmp_path("tmp_test.txt")
try do
iterator = File.biniterator(src)
File.open dest, [:write], fn(target) ->
Enum.each iterator, fn(line) ->
IO.write target, Regex.replace(%r/"/, line, "'")
end
end
assert File.read(dest) == { :ok, "FOO\n" }
after
File.rm(dest)
end
end
test :biniterator_with_path do
src = fixture_path("foo.txt")
dest = tmp_path("tmp_test.txt")
try do
{ :ok, iterator } = File.biniterator(src)
File.open dest, [:write], fn(target) ->
Enum.each iterator, fn(line) ->
IO.write target, Regex.replace(%r/"/, line, "'")
end
end
assert File.read(dest) == { :ok, "FOO\n" }
after
File.rm(dest)
end
end
test :biniterator! do
src = fixture_path("foo.txt")
dest = tmp_path("tmp_test.txt")
try do
iterator = File.biniterator!(src)
File.open dest, [:write], fn(target) ->
Enum.each iterator, fn(line) ->
IO.write target, Regex.replace(%r/"/, line, "'")
end
end
assert File.read(dest) == { :ok, "FOO\n" }
+6 -2
View File
@@ -3,6 +3,10 @@ Code.require_file "../../test_helper.exs", __FILE__
defmodule Kernel.CaseTest do
use ExUnit.Case, async: true
test :inline_case do
assert (case 1, do: (1 -> :ok; 2 -> :wrong)) == :ok
end
test :nested_case do
assert get_case == 2
end
@@ -31,8 +35,8 @@ defmodule Kernel.CaseTest do
test :in_operator_outside_case do
x = 1
y = 4
assert x in [1,2,3]
assert not y in [1, 2, 3]
assert x in [1,2,3], "in assertion"
assert not y in [1, 2, 3], "not in assertion"
end
test :in_operator_in_function_definition do
+4 -10
View File
@@ -22,18 +22,12 @@ defmodule Kernel.CLI.OptionParsingTest do
{ path, _ } = Code.eval list, []
# pa
assert File.expand_path('ebin', root) in path
assert File.expand_path('lib', root) in path
assert File.expand_path('src', root) in path
assert File.expand_path('ebin', root) inlist path
assert File.expand_path('lib', root) inlist path
assert File.expand_path('src', root) inlist path
# pz
assert File.expand_path('lib/list', root) in path
end
test :require do
options = ['-r', fixture_path('../../../lib/list/*') /> to_char_list, '-r', '/never/gonna/*/up']
{ config, _argv } = Kernel.CLI.process_options(options, Kernel.CLI.Config.new)
assert {:require, fixture_path "../../../lib/list/chars.ex"} in config.commands
assert File.expand_path('lib/list', root) inlist path
end
end
@@ -167,6 +167,11 @@ defmodule Kernel.ErrorsTest do
format_rescue 'defmodule Foo, do: (defmodule Elixir.Foo, do: true)'
end
test :invalid_definition do
assert "nofile:1: invalid syntax in def 1.(hello)" ==
format_rescue 'defmodule Foo, do: (def 1.(hello), do: true)'
end
test :duplicated_bitstring_size do
assert "nofile:1: duplicated size definition for bitstring" == format_rescue '<<1 :: [size(12), size(13)]>>'
end
@@ -234,6 +239,16 @@ defmodule Kernel.ErrorsTest do
format_rescue 'cond do _ -> true end'
end
test :fun_different_arities do
assert "nofile:1: cannot mix clauses with different arities in function definition" ==
format_rescue 'fn x -> x; x, y -> x + y end'
end
test :new_line_error do
assert "nofile:3: syntax error before: newline" ==
format_rescue 'if true do\n foo = [],\n baz\nend'
end
test :macros_error_stacktrace do
assert [{:erlang,:+,[1,:foo],_},{Foo,:sample,1,_}|_] =
rescue_stacktrace("defmodule Foo do\ndefmacro sample(num), do: num + :foo\ndef other, do: sample(1)\nend")
@@ -147,7 +147,7 @@ defmodule Kernel.OverridableTest do
end
test "overridable definitions are private" do
refute {:"with_super (overridable 0)",0} in Overridable.__info__(:exports)
refute {:"with_super (overridable 0)",0} inlist Overridable.__info__(:exports)
end
test "invalid super call" do
@@ -0,0 +1,257 @@
Code.require_file "../../test_helper.exs", __FILE__
defrecord BadRange, first: 0, last: 0 do
defoverridable [first: 1]
def first(_) do
:not_optimizable
end
end
defmodule Kernel.RecordRewriterTest do
use ExUnit.Case, async: true
## Helpers
defmacrop clause(expr) do
quote do: extract_clause(unquote(Macro.escape(expr)))
end
defp extract_clause(fun) do
{ { :fun, _, { :clauses, clauses } }, _ } =
:elixir_translator.translate_each(fun, :elixir.scope_for_eval([]))
hd(clauses)
end
defp optimize_clause(clause) do
{ clause, dict, res } = Kernel.RecordRewriter.optimize_clause(clause)
dict = Enum.map dict, fn { k, { v, _ } } -> { k, v }; other -> other; end
{ clause, dict, res }
end
## Inference tests
test "simple atom" do
clause = clause(fn -> :foo end)
assert optimize_clause(clause) == { clause, [], nil }
end
test "with left-side arg match" do
clause = clause(fn(x = Macro.Env[]) -> :foo end)
assert optimize_clause(clause) == { clause, [x: Macro.Env], nil }
end
test "with right-side arg match" do
clause = clause(fn(Macro.Env[] = x) -> :foo end)
assert optimize_clause(clause) == { clause, [x: Macro.Env], nil }
end
test "with nested left-side arg match" do
clause = clause(fn(x = y = Macro.Env[]) -> :foo end)
assert optimize_clause(clause) == { clause, [x: Macro.Env, y: Macro.Env], nil }
end
test "with nested right-side arg match" do
clause = clause(fn(Macro.Env[] = x = y) -> :foo end)
assert optimize_clause(clause) == { clause, [x: Macro.Env, y: Macro.Env], nil }
end
test "with deep left-side arg match" do
clause = clause(fn({ x, y = Macro.Env[] }) -> :foo end)
assert optimize_clause(clause) == { clause, [y: Macro.Env], nil }
end
test "with deep right-side arg match" do
clause = clause(fn({ x, Macro.Env[] = y }) -> :foo end)
assert optimize_clause(clause) == { clause, [y: Macro.Env], nil }
end
test "with tuple match" do
clause = clause(fn({ x, y } = { Macro.Env[], Range[] }) -> :foo end)
assert optimize_clause(clause) == { clause, [x: Macro.Env, y: Range], nil }
clause = clause(fn({ Macro.Env[], y } = { x, Range[] }) -> :foo end)
assert optimize_clause(clause) == { clause, [x: Macro.Env, y: Range], nil }
end
test "inside body" do
clause = clause(fn(x = Macro.Env[]) -> y = x end)
assert optimize_clause(clause) == { clause, [x: Macro.Env, y: Macro.Env], { Macro.Env, nil } }
end
test "inside body with variable overridden" do
clause = clause(fn(x = Macro.Env[], y = Range[]) -> y = x end)
assert optimize_clause(clause) == { clause, [x: Macro.Env, y: Range, "y@1": Macro.Env], { Macro.Env, nil } }
end
test "inside body with nested tuple" do
clause = clause(fn(x = Range[]) -> ^x = Range[first: { :hello, 2 }] end)
assert optimize_clause(clause) == { clause, [x: Range], { Range, [nil, { :hello, nil }, nil] } }
end
test "conflicting definition" do
clause = clause(fn(x = Macro.Env[]) -> ^x = Range[]; :foo end)
assert optimize_clause(clause) == { clause, [x: nil], nil }
end
test "inside list" do
clause = clause(fn -> [x = Macro.Env[]]; :foo end)
assert optimize_clause(clause) == { clause, [x: Macro.Env], nil }
end
test "inside tuple" do
clause = clause(fn -> { x = Macro.Env[] }; :foo end)
assert optimize_clause(clause) == { clause, [x: Macro.Env], nil }
end
test "inside bin" do
clause = clause(fn -> << x = Macro.Env[] >>; :foo end)
assert optimize_clause(clause) == { clause, [x: Macro.Env], nil }
end
test "inside operator" do
clause = clause(fn -> 1 + (x = Macro.Env[]); :foo end)
assert optimize_clause(clause) == { clause, [x: Macro.Env], nil }
end
test "inside block" do
clause = clause(fn -> :foo; (x = Macro.Env[]; x) end)
assert optimize_clause(clause) == { clause, [x: Macro.Env], { Macro.Env, nil } }
end
test "inside local call" do
clause = clause(fn -> (x = Macro.Env[]).(y = Range[]) end)
assert optimize_clause(clause) == { clause, [x: Macro.Env, y: Range], nil }
end
test "inside remote call" do
clause = clause(fn -> (x = Macro.Env[]).call(y = Range[]) end)
assert optimize_clause(clause) == { clause, [x: Macro.Env, y: Range], nil }
end
test "inside list comprehension" do
clause = clause(fn -> lc x = Macro.Env[] inlist sample, do: x end)
assert optimize_clause(clause) == { clause, [], nil }
end
test "inside bit comprehension" do
clause = clause(fn -> bc x = Macro.Env[] inbits sample, do: <<x>> end)
assert optimize_clause(clause) == { clause, [], nil }
end
test "inside function retrieval" do
clause = clause(fn -> function(x = Macro.Env[], y, z) end)
assert optimize_clause(clause) == { clause, [x: Macro.Env], nil }
end
test "inside anonymous function" do
clause = clause(fn -> fn (x = Macro.Env[]) -> x end end)
assert optimize_clause(clause) == { clause, [], nil }
end
test "inside case" do
clause = clause(fn -> case x = Macro.Env[] do _ -> x end end)
assert optimize_clause(clause) == { clause, [x: Macro.Env], { Macro.Env, nil } }
clause = clause(fn -> case something do x = Macro.Env[] -> x; Macro.Env[] = x -> x end end)
assert optimize_clause(clause) == { clause, [x: Macro.Env], { Macro.Env, nil } }
clause = clause(fn -> case something do x = Macro.Env[] -> x; Macro.Env[] = y -> y end end)
assert optimize_clause(clause) == { clause, [], { Macro.Env, nil } }
clause = clause(fn -> case something do 1 -> x = Macro.Env[]; 2 -> x = Macro.Env[] end end)
assert optimize_clause(clause) == { clause, [x: Macro.Env], { Macro.Env, nil } }
clause = clause(fn -> case something do 1 -> x = Macro.Env[]; 2 -> x = Range[] end end)
assert optimize_clause(clause) == { clause, [x: nil], nil }
end
test "inside case with nested tuple" do
clause = clause(fn -> case something do x = Range[first: { :foo, 2 }] -> x; Range[] = x -> x end end)
assert optimize_clause(clause) == { clause, [x: Range], { Range, nil } }
clause = clause(fn -> case something do x = Range[first: { :foo, 2 }] -> x; Range[first: { :foo, 2 }] = x -> x end end)
assert optimize_clause(clause) == { clause, [x: Range], { Range, [nil, { :foo, nil }, nil] } }
end
test "inside receive" do
clause = clause(fn -> receive do x = Macro.Env[] -> x; Macro.Env[] = x -> x end end)
assert optimize_clause(clause) == { clause, [x: Macro.Env], { Macro.Env, nil } }
clause = clause(fn -> receive do x = Macro.Env[] -> x; Macro.Env[] = y -> y end end)
assert optimize_clause(clause) == { clause, [], { Macro.Env, nil } }
clause = clause(fn -> receive do 1 -> x = Macro.Env[]; 2 -> x = Macro.Env[] end end)
assert optimize_clause(clause) == { clause, [x: Macro.Env], { Macro.Env, nil } }
clause = clause(fn -> receive do 1 -> x = Macro.Env[]; 2 -> x = Range[] end end)
assert optimize_clause(clause) == { clause, [x: nil], nil }
end
test "inside receive with after" do
clause = clause(fn -> receive do 1 -> x = Macro.Env[]; after 2 -> x = Macro.Env[]; end end)
assert optimize_clause(clause) == { clause, [x: Macro.Env], { Macro.Env, nil } }
clause = clause(fn -> receive do 1 -> x = Macro.Env[]; after 2 -> x = Range[]; end end)
assert optimize_clause(clause) == { clause, [x: nil], nil }
end
test "inside try" do
clause = clause(fn -> try do x = Macro.Env[]; x end end)
assert optimize_clause(clause) == { clause, [], { Macro.Env, nil } }
clause = clause(fn -> try do x = Macro.Env[]; x; catch :oops -> x = Macro.Env[]; end end)
assert optimize_clause(clause) == { clause, [], { Macro.Env, nil } }
clause = clause(fn -> try do x = Macro.Env[]; x; after x = Macro.Env[]; end end)
assert optimize_clause(clause) == { clause, [], { Macro.Env, nil } }
end
## Rewrite tests
test "getter call is rewriten" do
{ clause, rewriten } =
{ clause(fn(x = Range[]) -> x.first end), clause(fn(x = Range[]) -> :erlang.element(2, x) end) }
assert optimize_clause(clause) == { rewriten, [x: Range], nil }
end
test "setter call is rewriten" do
{ clause, rewriten } =
{ clause(fn(x = Range[]) -> x.first(:first) end), clause(fn(x = Range[]) -> :erlang.setelement(2, x, :first) end) }
assert optimize_clause(clause) == { rewriten, [x: Range], { Range, nil } }
end
test "nested setter call is rewriten" do
{ clause, rewriten } =
{ clause(fn(x = Range[]) -> x.first(:first).last(:last) end), clause(fn(x = Range[]) -> :erlang.setelement(3, :erlang.setelement(2, x, :first), :last) end) }
assert optimize_clause(clause) == { rewriten, [x: Range], { Range, nil } }
end
test "updater call is no-op (for now)" do
clause = clause(fn(x = Range[]) -> x.update_first(&1 + 1) end)
assert optimize_clause(clause) == { clause, [x: Range], { Range, nil } }
end
test "noop for unknown fields" do
clause = clause(fn(x = Range[]) -> x.unknown end)
assert optimize_clause(clause) == { clause, [x: Range], nil }
end
test "noop for not records fields" do
clause = clause(fn(x = { :not_a_record, _ }) -> x.unknown end)
assert optimize_clause(clause) == { clause, [x: :not_a_record], nil }
end
test "noop for rewriten fields" do
clause = clause(fn(x = BadRange[]) -> x.first end)
assert optimize_clause(clause) == { clause, [x: BadRange], nil }
end
test "noop for conflicting inference" do
clause = clause(fn(x = Macro.Env[]) -> ^x = Range[]; x.first end)
assert optimize_clause(clause) == { clause, [x: nil], nil }
end
end
+31 -1
View File
@@ -20,8 +20,28 @@ defmodule KernelTest do
refute x([])
end
test :paren do
assert nil?(())
assert [ 1, (), 3 ] == [1, nil, 3 ]
assert [do: ()] == [do: nil]
assert { 1, (), 3 } == { 1, nil, 3 }
end
test :__info__ do
assert { :in, 2 } in Kernel.__info__(:macros)
assert { :in, 2 } inlist Kernel.__info__(:macros)
end
test :macro_exported? do
assert macro_exported?(Kernel, :in, 2) == true
assert macro_exported?(Kernel, :def, 1) == true
assert macro_exported?(Kernel, :def, 2) == true
assert macro_exported?(Kernel, :def, 3) == false
assert macro_exported?(Kernel, :def, 4) == true
assert macro_exported?(Kernel, :no_such_macro, 2) == false
end
test :debug_info do
assert :debug_info inlist Kernel.__info__(:compile)[:options]
end
defp x(value) when value in [1,2,3], do: true
@@ -96,12 +116,22 @@ defmodule KernelTest do
assert function(:erlang, :atom_to_list, 1).(:a) == 'a'
end
test :remote_syntax_function do
assert function(:erlang.atom_to_list/1) ==
function(:erlang, :atom_to_list, 1)
assert function(Enum.map/2) == function(Enum, :map, 2)
end
test :retrieve_local_function do
assert is_function(function(:atl, 1))
assert :erlang.fun_info(function(:atl, 1), :arity) == {:arity, 1}
assert function(:atl, 1).(:a) == 'a'
end
test :local_syntax_function do
assert function(atl/1).(:a) == 'a'
end
test :retrieve_imported_function do
assert is_function(function(:atom_to_list, 1))
assert :erlang.fun_info(function(:atom_to_list, 1), :arity) == {:arity, 1}
+1 -29
View File
@@ -53,36 +53,12 @@ defmodule ListTest do
assert List.foldr([1,2,3,4], 0, fn x,y -> x - y end) == -2
end
def test_member? do
test :member? do
assert List.member? [1,2,3], 1
refute List.member? [1,2,3], 0
refute List.member? [], 0
end
test :range do
assert List.range(1,3) == [1,2,3]
assert List.range(1, 1) == [1]
assert List.range(5, 0) == [5,4,3,2,1,0]
assert List.range(1, 0, -1) == [1,0]
assert List.range(1,8,2) == [1,3,5,7]
assert List.range(7,-1,-3) == [7,4,1]
assert List.range(2,1,1) == []
assert List.range(8,1,1) == []
assert List.range(1,8,-1) == []
assert List.range(1,1,-1) == []
end
test :sort do
assert List.sort([3, 5, 1, 2, 4]) == [1,2,3,4,5]
assert List.sort([3, 5, 1, 2, 4], &2 <= &1) == [5,4,3,2,1]
assert List.sort(['2', '3', '0', '11', '10']) == ['0', '10', '11', '2', '3']
assert ['0', '2', '3', '10', '11'] == List.sort ['2', '3', '0', '11', '10'], fn a, b ->
{na, _} = :string.to_integer a
{nb, _} = :string.to_integer b
na <= nb
end
end
test :concat_1 do
assert List.concat([[1,[2],3], [4], [5,6]]) == [1,[2],3,4,5,6]
end
@@ -95,10 +71,6 @@ defmodule ListTest do
assert Enum.reverse([1,2,3]) == [3,2,1]
end
test :uniq do
assert List.uniq([1,2,3,2,1]) == [1,2,3]
end
test :duplicate do
assert List.duplicate(1, 3) == [1,1,1]
assert List.duplicate([1], 1) == [[1]]
+37 -2
View File
@@ -141,11 +141,19 @@ defmodule MacroTest do
assert Macro.to_binary(quote do: foo.bar.([1, 2, 3])) == "foo.bar().([1, 2, 3])"
end
test :atom_call_to_binary do
assert Macro.to_binary(quote do: :foo.(1, 2, 3)) == ":foo.(1, 2, 3)"
end
test :aliases_call_to_binary do
assert Macro.to_binary(quote do: Foo.Bar.baz(1, 2, 3)) == "Foo.Bar.baz(1, 2, 3)"
assert Macro.to_binary(quote do: Foo.Bar.baz([1, 2, 3])) == "Foo.Bar.baz([1, 2, 3])"
end
test :arrow_to_binary do
assert Macro.to_binary(quote do: foo(1, (2 -> 3))) == "foo(1, (2 -> 3))"
end
test :blocks_to_binary do
assert Macro.to_binary(quote do: (1; 2; (:foo; :bar); 3)) <> "\n" == """
(
@@ -183,9 +191,24 @@ defmodule MacroTest do
end
test :fn_to_binary do
assert Macro.to_binary(quote do: (fn(x) -> x + 1 end)) <> "\n" == """
assert Macro.to_binary(quote do: (() -> x)) == "(() -> x)"
assert Macro.to_binary(quote do: (fn -> 1 + 2 end)) == "fn -> 1 + 2 end"
assert Macro.to_binary(quote do: (fn(x) -> x + 1 end)) == "fn x -> x + 1 end"
assert Macro.to_binary(quote do: (fn(x) -> y = x + 1; y end)) <> "\n" == """
fn x ->
x + 1
y = (x + 1)
y
end
"""
assert Macro.to_binary(quote do: (fn(x) -> y = x + 1; y; (z) -> z end)) <> "\n" == """
fn
x ->
y = (x + 1)
y
z ->
z
end
"""
end
@@ -222,6 +245,8 @@ defmodule MacroTest do
end
test :unary_ops_to_binary do
assert Macro.to_binary(quote do: not 1) == "not 1"
assert Macro.to_binary(quote do: not foo) == "not foo"
assert Macro.to_binary(quote do: -1) == "-1"
assert Macro.to_binary(quote do: @foo(bar)) == "@foo(bar)"
end
@@ -248,4 +273,14 @@ defmodule MacroTest do
assert Macro.safe_term(quote do: [1+1]) == { :unsafe, quote do: 1 + 1 }
assert Macro.safe_term(quote do: {1+1}) == { :unsafe, quote do: 1 + 1 }
end
## extract_args
test :extract_args do
assert Macro.extract_args(quote do: foo) == { :foo, [] }
assert Macro.extract_args(quote do: foo()) == { :foo, [] }
assert Macro.extract_args(quote do: :foo.()) == { :foo, [] }
assert Macro.extract_args(quote do: foo(1,2,3)) == { :foo, [1,2,3] }
assert Macro.extract_args(quote do: 1.(1,2,3)) == :error
end
end
+1 -1
View File
@@ -8,7 +8,7 @@ end
defprotocol ProtocolTest.WithExcept do
@except [Atom, Number, List]
@spec blank(t), do: boolean
@spec blank(t) :: boolean
def blank(thing)
end
+6 -6
View File
@@ -17,12 +17,12 @@ defmodule RangeTest do
end
test :in do
refute 0 in 1..3, "raw range assertion"
assert 1 in 1..3, "raw range assertion"
assert 2 in 1..3, "raw range assertion"
assert 3 in 1..3, "raw range assertion"
refute 4 in 1..3, "raw range assertion"
assert -3 in -1..-3, "raw range assertion"
refute 0 in 1..3, "in range assertion"
assert 1 in 1..3, "in range assertion"
assert 2 in 1..3, "in range assertion"
assert 3 in 1..3, "in range assertion"
refute 4 in 1..3, "in range assertion"
assert -3 in -1..-3, "in range assertion"
end
test :is_range do
+13
View File
@@ -11,6 +11,12 @@ defrecord name, a: 0, b: 1 do
def get_a(RecordTest.DynamicName[a: a]) do
a
end
defoverridable [update_b: 2]
def update_b(_, _) do
:not_optimizable
end
end
## With types
@@ -152,6 +158,13 @@ defmodule RecordTest do
assert RecordTest.SomeRecord.b(record.update(b: 2)) == 2
end
test :optimizable do
assert { :b, 1 } inlist RecordTest.SomeRecord.__record__(:optimizable)
assert { :b, 2 } inlist RecordTest.SomeRecord.__record__(:optimizable)
assert { :update_b, 2 } inlist RecordTest.SomeRecord.__record__(:optimizable)
refute { :update_b, 2 } inlist RecordTest.DynamicName.__record__(:optimizable)
end
defp file_info do
{ :ok, file_info } = :file.read_file_info(__FILE__)
file_info
+4
View File
@@ -35,6 +35,10 @@ defmodule Regex.BinaryTest do
assert Regex.opts(Regex.compile!("foo", "u")) == "u"
end
test :unicode do
assert ("josé" =~ %r"\p{Latin}$"u) == 3
end
test :groups do
assert Regex.groups(%r/(?<FOO>foo)/g) == [:FOO]
assert Regex.groups(Regex.compile!("foo")) == nil
+11 -1
View File
@@ -45,6 +45,10 @@ defmodule StringTest do
assert String.rstrip("") == ""
assert String.rstrip(" abc ") == " abc"
assert String.rstrip(" abc a") == " abc a"
assert String.rstrip("a abc a\n\n") == "a abc a"
assert String.rstrip("a abc a\t\n\v\f\r\s") == "a abc a"
assert String.rstrip("a abc a " <> <<31>>) == "a abc a"
assert String.rstrip("a abc a" <> <<194,133>>) == "a abc a"
assert String.rstrip(" abc aa", ?a) == " abc "
assert String.rstrip(" abc __", ?_) == " abc "
end
@@ -53,12 +57,18 @@ defmodule StringTest do
assert String.lstrip("") == ""
assert String.lstrip(" abc ") == "abc "
assert String.lstrip("a abc a") == "a abc a"
assert String.lstrip("\n\na abc a") == "a abc a"
assert String.lstrip("\t\n\v\f\r\sa abc a") == "a abc a"
assert String.lstrip(<<31>> <> " a abc a") == "a abc a"
assert String.lstrip(<<194,133>> <> "a abc a") == "a abc a"
assert String.lstrip("__ abc _", ?_) == " abc _"
end
test :strip do
assert String.strip("") == ""
assert String.strip(" abc ") == "abc"
assert String.strip("a abc a\n\n") == "a abc a"
assert String.strip("a abc a\t\n\v\f\r\s") == "a abc a"
assert String.strip("___ abc ___", ?_) == " abc "
end
@@ -152,7 +162,7 @@ defmodule StringTest do
assert String.at("л", 10) == nil
assert String.at("elixir", -1) == "r"
assert String.at("がガちゃ", -2) == "ち"
assert String.at("л", -3) == ""
assert String.at("л", -3) == nil
assert String.at("Ā̀stute", 1) == "s"
assert String.at("elixir",6) == nil
end
+3 -3
View File
@@ -24,9 +24,9 @@ defmodule SystemTest do
end
test :env do
assert System.get_env("SYSTEM_ENV_TEST_VAR") == nil
System.put_env('SYSTEM_ENV_TEST_VAR', 'SAMPLE')
assert System.get_env("SYSTEM_ENV_TEST_VAR") == "SAMPLE"
assert System.get_env("SYSTEM_ELIXIR_ENV_TEST_VAR") == nil
System.put_env('SYSTEM_ELIXIR_ENV_TEST_VAR', 'SAMPLE')
assert System.get_env("SYSTEM_ELIXIR_ENV_TEST_VAR") == "SAMPLE"
end
test :cmd do
+50 -28
View File
@@ -123,23 +123,28 @@ defmodule Typespec.Test.Type do
test "@type with a fun" do
spec = test_module do
@type mytype :: fun
@type mytype :: (... -> any)
end
assert {:mytype,{:type,_,:fun, []},[]} = spec
end
test "@type with a fun with arguments and return type" do
spec = test_module do
@type mytype :: fun(integer, integer, do: integer)
{spec1, spec2} = test_module do
t1 = @type mytype :: (integer, integer -> integer)
t2 = @type mytype2 :: (fun(integer, integer) -> integer)
{t1, t2}
end
assert {:mytype,{:type,_,:fun, [{:type, _, :product,
[{:type, _, :integer, []}, {:type, _, :integer, []}]},
{:type, _, :integer, []}]},[]} = spec
{:type, _, :integer, []}]},[]} = spec1
assert {:mytype2,{:type,_,:fun, [{:type, _, :product,
[{:type, _, :integer, []}, {:type, _, :integer, []}]},
{:type, _, :integer, []}]},[]} = spec2
end
test "@type with a fun with no arguments and return type" do
spec = test_module do
@type mytype :: fun(do: integer)
@type mytype :: (() -> integer)
end
assert {:mytype,{:type,_,:fun, [{:type, _, :product, []},
{:type, _, :integer, []}]}, []} = spec
@@ -147,7 +152,7 @@ defmodule Typespec.Test.Type do
test "@type with a fun with any arity and return type" do
spec = test_module do
@type mytype :: fun(..., do: integer)
@type mytype :: (... -> integer)
end
assert {:mytype,{:type,_,:fun, [{:type, _, :any},
{:type, _, :integer, []}]}, []} = spec
@@ -186,7 +191,7 @@ defmodule Typespec.Test.Type do
test "@type with annotations" do
{spec1, spec2} = test_module do
t1 = @type mytype :: named :: integer
t2 = @type mytype1 :: fun(a :: integer, do: integer)
t2 = @type mytype1 :: (a :: integer -> integer)
{t1,t2}
end
assert {:mytype, {:ann_type, _, [{:var, _, :named}, {:type, _, :integer, []}]}, []} = spec1
@@ -224,9 +229,9 @@ defmodule Typespec.Test.Type do
def myfun(x), do: x
def myfun(), do: :ok
def myfun(x,y), do: {x,y}
t1 = @spec myfun(integer), do: integer
t2 = @spec myfun(), do: integer
t3 = @spec myfun(integer, integer), do: {integer, integer}
t1 = @spec myfun(integer) :: integer
t2 = @spec myfun() :: integer
t3 = @spec myfun(integer, integer) :: {integer, integer}
{t1,t2,t3}
end
assert {{:myfun,1},{:type,_,:fun,[{:type,_,:product,[{:type,_,:integer,[]}]},{:type,_,:integer,[]}]}} = spec1
@@ -234,11 +239,19 @@ defmodule Typespec.Test.Type do
assert {{:myfun,2},{:type,_,:fun,[{:type,_,:product,[{:type,_,:integer,[]},{:type,_,:integer,[]}]},{:type,_,:tuple,[{:type,_,:integer,[]},{:type,_,:integer,[]}]}]}} = spec3
end
test "@spec(spec) with guards" do
spec1 = test_module do
def myfun(x), do: x
@spec myfun(x) when is_subtype(x, integer) :: boolean
end
assert {{:myfun,1},{:type,_,:bounded_fun,[{:type,_,:fun,[{:type,_,:product,[{:var,_,:x}]},{:type,_,:boolean,[]}]},[{:type,_,:constraint,[{:atom,_,:is_subtype},[{:var,_,:x},{:type,_,:integer,[]}]]}]]}} = spec1
end
test "@callback(callback)" do
{spec1, spec2, spec3} = test_module do
t1 = @callback myfun(integer), do: integer
t2 = @callback myfun(), do: integer
t3 = @callback myfun(integer, integer), do: {integer, integer}
t1 = @callback myfun(integer) :: integer
t2 = @callback myfun() :: integer
t3 = @callback myfun(integer, integer) :: {integer, integer}
{t1,t2,t3}
end
assert {{:myfun,1},{:type,_,:fun,[{:type,_,:product,[{:type,_,:integer,[]}]},{:type,_,:integer,[]}]}} = spec1
@@ -249,22 +262,22 @@ defmodule Typespec.Test.Type do
test "@spec + @callback" do
{ specs, callbacks } = test_module do
def myfun(x), do: x
@spec myfun(integer), do: integer
@spec myfun(char_list), do: char_list
@callback cb(integer), do: integer
@spec myfun(integer) :: integer
@spec myfun(char_list) :: char_list
@callback cb(integer) :: integer
{ @spec, @callback }
end
assert [
{ {:cb, 1}, {:type,_,:fun,[{:type,_,:product,[{:type,_,:integer,[]}]},{:type,_,:integer,[]}]} }
] = List.sort(callbacks)
] = Enum.sort(callbacks)
assert [
{ {:myfun,1}, {:type,_,:fun,[{:type,_,:product,[{:type,_,:integer,[]}]},{:type,_,:integer,[]}]} },
{ {:myfun,1}, {:type,_,:fun,[{:type,_,:product,[
{:remote_type, _, [{:atom, _, :elixir},{:atom, _, :char_list}, []]}]},
{:remote_type, _, [{:atom, _, :elixir},{:atom, _, :char_list}, []]}]} }
] = List.sort(specs)
] = Enum.sort(specs)
end
# Conversion to AST
@@ -282,7 +295,9 @@ defmodule Typespec.Test.Type do
(quote do: @type binary_type2() :: <<_ :: 3 * 8>>),
(quote do: @type binary_type3() :: <<_ :: 3>>),
(quote do: @type tuple_type() :: {integer()}),
(quote do: @type ftype() :: fun() | fun(do: integer()) | fun(integer(), do: integer())),
(quote do: @type ftype() :: (() -> any()) | (() -> integer()) | ((integer() -> integer()))),
(quote do: @type cl() :: char_list()),
(quote do: @type vaf() :: (... -> any())),
]
types = test_module do
@@ -309,13 +324,21 @@ defmodule Typespec.Test.Type do
{ :type, 0, :integer, [] } },
],
[]}
assert Kernel.Typespec.type_to_ast(record_type) == quote hygiene: false, do: my_record() :: {:my_record, field1 :: atom(), field2 :: integer() }
assert Kernel.Typespec.type_to_ast(record_type) ==
quote(hygiene: false, do: my_record() :: {:my_record, field1 :: atom(), field2 :: integer() })
end
test "type_to_ast for paren_type" do
type = {:my_type, {:paren_type, 0, [{:type, 0, :integer, []}]}, []}
assert Kernel.Typespec.type_to_ast(type) ==
quote(hygiene: false, do: my_type() :: integer())
end
test "spec_to_ast" do
specs = [
(quote do: @spec a(), do: integer()),
(quote do: @spec a(atom()), do: integer()),
(quote do: @spec a() :: integer()),
(quote do: @spec a(atom()) :: integer()),
(quote do: @spec a(b) when is_subtype(b, integer()) :: integer()),
]
compiled = test_module do
@@ -325,9 +348,8 @@ defmodule Typespec.Test.Type do
Enum.reverse @spec
end
lc { { { name, _ }, spec }, definition } inlist Enum.zip(compiled, specs) do
{ args, result } = Kernel.Typespec.spec_to_ast(spec)
quoted = quote do: @spec unquote(name)(unquote_splicing(args)), do: unquote(result)
lc { { { _, _ }, spec }, definition } inlist Enum.zip(compiled, specs) do
quoted = quote do: @spec unquote(Kernel.Typespec.spec_to_ast(:a, spec))
assert Macro.to_binary(quoted) == Macro.to_binary(definition)
end
end
@@ -338,8 +360,8 @@ defmodule Typespec.Test.Type do
Code.compiler_options debug_info: true
{ :module, T, binary, _ } = defmodule T do
@spec a, do: any
def a, do: nil
@spec a :: any
def a, do: nil
end
:code.delete(T)
@@ -356,7 +378,7 @@ defmodule Typespec.Test.Type do
{ :module, T, binary, _ } = defmodule T do
@type a :: any
@typep b :: any
@spec t(b), do: b
@spec t(b) :: b
def t(b), do: b
@opaque c :: any
end
+2 -2
View File
@@ -4,8 +4,8 @@ defmodule URITest do
use ExUnit.Case, async: true
test :encode_with_binary do
raw = <<38,60,62,34,32,227,130,134,227,130,147,227,130,134,227,130,147>>
expected = "%26%3C%3E%22+%E3%82%86%E3%82%93%E3%82%86%E3%82%93"
raw = <<13,10,38,60,62,34,32,227,130,134,227,130,147,227,130,134,227,130,147>>
expected = "%0D%0A%26%3C%3E%22+%E3%82%86%E3%82%93%E3%82%86%E3%82%93"
assert URI.encode(raw) == expected
end
+10
View File
@@ -14,6 +14,16 @@ function_stab_end_test() ->
{_, [{a, Fun}]} = eval("a = fn -> 1 + 2 end"),
3 = Fun().
function_stab_many_test() ->
{_, [{a, Fun}]} = eval("a = fn\n{ :foo, x } -> x\n{ :bar, x } -> x\nend"),
1 = Fun({ foo, 1 }),
2 = Fun({ bar, 2 }).
function_stab_inline_test() ->
{_, [{a, Fun}]} = eval("a = fn { :foo, x } -> x; { :bar, x } -> x end"),
1 = Fun({ foo, 1 }),
2 = Fun({ bar, 2 }).
function_with_args_test() ->
{Fun, _} = eval("fn(a, b) -> a + b end"),
3 = Fun(1,2).
-31
View File
@@ -37,34 +37,3 @@ record_new_selective_test() ->
{ 20, _ } = eval("Foo.new(b: 20).b")
end,
test_helper:run_and_remove(F, ['Elixir.Foo']).
record_prepend_test() ->
F = fun() ->
eval("defrecord Foo, a: 1, b: [3], c: 3"),
{ { 'Elixir.Foo', 1, [1,2,3], 3 }, _ } = eval("Foo.new.prepend_b [1,2]")
end,
test_helper:run_and_remove(F, ['Elixir.Foo']).
record_merge_test() ->
F = fun() ->
eval("defrecord Foo, a: [foo: :bar]"),
{ { 'Elixir.Foo', [{foo,baz}, {a,1}] }, _ } = eval("Foo.new.merge_a [foo: :baz, a: 1]")
end,
test_helper:run_and_remove(F, ['Elixir.Foo']).
record_increment_test() ->
F = fun() ->
eval("defrecord Foo, a: 0"),
{ { 'Elixir.Foo', 1 }, _ } = eval("Foo.new.increment_a"),
{ { 'Elixir.Foo', 10 }, _ } = eval("Foo.new.increment_a 10"),
{ { 'Elixir.Foo', -2 }, _ } = eval("Foo.new.increment_a -2")
end,
test_helper:run_and_remove(F, ['Elixir.Foo']).
record_toggle_test() ->
F = fun() ->
eval("defrecord Foo, a: false, b: true"),
{ { 'Elixir.Foo', true, true }, _ } = eval("Foo.new.toggle_a"),
{ { 'Elixir.Foo', false, false }, _ } = eval("Foo.new.toggle_b")
end,
test_helper:run_and_remove(F, ['Elixir.Foo']).
+1 -1
View File
@@ -3,7 +3,7 @@
-export([test/0, run_and_remove/2, throw_elixir/1, throw_erlang/1]).
test() ->
elixir:start_app(),
application:start(elixir),
eunit:test([
atom_test,
conditionals_test,
+5 -5
View File
@@ -45,18 +45,18 @@ kw_test() ->
integer_test() ->
[{number, 1, 123}] = tokenize("123"),
[{number, 1, 123},{eol, 1, $;}] = tokenize("123;"),
[{eol, 1, $\n}, {number, 3, 123}] = tokenize("\n\n123"),
[{number, 1, 123},{eol, 1, ';'}] = tokenize("123;"),
[{eol, 1, newline}, {number, 3, 123}] = tokenize("\n\n123"),
[{number, 1, 123}, {number, 1, 234}] = tokenize(" 123 234 ").
float_test() ->
[{number, 1, 12.3}] = tokenize("12.3"),
[{number, 1, 12.3},{eol, 1, $;}] = tokenize("12.3;"),
[{eol, 1, $\n}, {number, 3, 12.3}] = tokenize("\n\n12.3"),
[{number, 1, 12.3},{eol, 1, ';'}] = tokenize("12.3;"),
[{eol, 1, newline}, {number, 3, 12.3}] = tokenize("\n\n12.3"),
[{number, 1, 12.3}, {number, 1, 23.4}] = tokenize(" 12.3 23.4 ").
comments_test() ->
[{number, 1, 1},{eol, 1, $\n},{number,2,2}] = tokenize("1 # Comment\n2").
[{number, 1, 1},{eol, 1, newline},{number,2,2}] = tokenize("1 # Comment\n2").
identifier_test() ->
[{identifier,1,abc}] = tokenize("abc "),
+12 -6
View File
@@ -65,12 +65,20 @@ defmodule ExUnit do
options = Keyword.merge(user_options, options)
ExUnit.Server.start_link
configure(options)
System.at_exit fn status ->
if status == 0, do: ExUnit.run
System.at_exit fn
0 ->
failures = ExUnit.run
System.at_exit fn _ ->
if failures > 0, do: System.halt(1), else: System.halt(0)
end
_ ->
:ok
end
end
@doc false
@doc """
Returns the configured user options.
"""
def user_options(user_config // nil) do
user_config = user_config ||
System.get_env("EXUNIT_CONFIG") ||
@@ -113,8 +121,6 @@ defmodule ExUnit do
need to call it directly.
"""
def run do
config = ExUnit.Runner.Config.new ExUnit.Server.options
failures = ExUnit.Runner.run config
if failures > 0, do: System.halt(1), else: System.halt(0)
ExUnit.Runner.run ExUnit.Server.options
end
end
+22 -3
View File
@@ -110,6 +110,7 @@ defmodule ExUnit.Assertions do
end
defp translate_assertion({ :in, _, [left, right] }, _else) do
IO.write "[WARNING] assert(left in right) is deprecated, please use assert(left inlist right)\n#{Exception.formatted_stacktrace}"
quote do
left = unquote(left)
right = unquote(right)
@@ -117,6 +118,14 @@ defmodule ExUnit.Assertions do
end
end
defp translate_assertion({ :inlist, _, [left, right] }, _else) do
quote do
left = unquote(left)
right = unquote(right)
assert(List.member?(right, left), "Expected #{inspect left} to be in #{inspect right}")
end
end
## Negative versions
defp translate_assertion({ op, _, [{ :=, _, [expected, received] }] }, _else) when negation?(op) do
@@ -139,6 +148,8 @@ defmodule ExUnit.Assertions do
end
defp translate_assertion({ op, _, [{ :in, _, [left, right] }] }, _else) when negation?(op) do
IO.write "[WARNING] refute(left in right) is deprecated, please use refute(left inlist right)\n#{Exception.formatted_stacktrace}"
quote do
left = unquote(left)
right = unquote(right)
@@ -146,6 +157,14 @@ defmodule ExUnit.Assertions do
end
end
defp translate_assertion({ op, _, [{ :inlist, _, [left, right] }] }, _else) when negation?(op) do
quote do
left = unquote(left)
right = unquote(right)
assert(!List.member?(right, left), "Expected #{inspect left} to not be in #{inspect right}")
end
end
## Fallback
defp translate_assertion(_expected, fallback) do
@@ -311,7 +330,7 @@ defmodule ExUnit.Assertions do
unquote(expr)
flunk "Expected to catch #{unquote(kind)}, got nothing"
rescue
ExUnit.AssertionError = e -> raise(e)
e in [ExUnit.AssertionError] -> raise(e)
catch
unquote(kind), what_we_got -> what_we_got
end
@@ -380,8 +399,8 @@ defmodule ExUnit.Assertions do
flunk "This should raise an error"
"""
@spec flunk, do: no_return
@spec flunk(String.t), do: no_return
@spec flunk :: no_return
@spec flunk(String.t) :: no_return
def flunk(message // "Epic Fail!") do
raise ExUnit.AssertionError, message: message
end
+3 -3
View File
@@ -56,13 +56,13 @@ defmodule ExUnit.CLIFormatter do
def handle_cast({ :test_finished, _test_case, _test, nil }, config) do
IO.write "."
{ :noreply, config.increment_counter }
{ :noreply, config.update_counter(&1 + 1) }
end
def handle_cast({ :test_finished, test_case, test, failure }, config) do
IO.write "F"
{ :noreply, config.increment_counter.
prepend_failures([{test_case, test, failure}]) }
{ :noreply, config.update_counter(&1 + 1).
update_failures([{test_case, test, failure}|&1]) }
end
def handle_cast(_, _) do
+6 -6
View File
@@ -10,12 +10,12 @@ defmodule ExUnit.Formatter do
@type test :: atom
@type result :: { kind :: atom, reason :: term, stacktrace :: list } | nil
defcallback suite_started(), do: any
defcallback suite_finished(), do: non_neg_integer
defcallback suite_started() :: any
defcallback suite_finished() :: non_neg_integer
defcallback case_started(test_case), do: any
defcallback case_finished(test_case), do: any
defcallback case_started(test_case) :: any
defcallback case_finished(test_case) :: any
defcallback test_started(test_case, test), do: any
defcallback test_finished(test_case, test, result), do: any
defcallback test_started(test_case, test) :: any
defcallback test_finished(test_case, test, result) :: any
end
+4 -3
View File
@@ -3,7 +3,8 @@ defmodule ExUnit.Runner do
defrecord Config, formatter: ExUnit.CLIFormatter, max_cases: 4, taken_cases: 0
def run(config) do
def run(opts) do
config = ExUnit.Runner.Config.new opts
config.formatter.suite_started
loop config
end
@@ -44,13 +45,13 @@ defmodule ExUnit.Runner do
wait_until_available config
{ pid, :case_finished, test_case } ->
config.formatter.case_finished(test_case)
loop config.increment_taken_cases(-1)
loop config.update_taken_cases(&1-1)
end
end
defp spawn_cases(config, cases) do
Enum.each cases, spawn_case(config, &1)
loop config.increment_taken_cases(length(cases))
loop config.update_taken_cases(&1+length(cases))
end
defp spawn_case(config, test_case) do
+4 -4
View File
@@ -82,19 +82,19 @@ defmodule ExUnit.Server do
end
def handle_cast({:add_async_case, name}, config) do
{ :noreply, config.prepend_async_cases [name] }
{ :noreply, config.update_async_cases [name|&1] }
end
def handle_cast({:add_sync_case, name}, config) do
{ :noreply, config.prepend_sync_cases [name] }
{ :noreply, config.update_sync_cases [name|&1] }
end
def handle_cast({:add_after_spawn, fun}, config) do
{ :noreply, config.prepend_after_spawn [fun] }
{ :noreply, config.update_after_spawn [fun|&1] }
end
def handle_cast({:merge_options, options}, config) do
{ :noreply, config.merge_options(options) }
{ :noreply, config.update_options(&1 /> Keyword.merge(options)) }
end
def handle_cast(request, config) do
+8 -8
View File
@@ -103,26 +103,26 @@ defmodule ExUnit.AssertionsTest do
end
end
test :assert_in_when_is_member do
true = assert 'foo' in ['foo', 'bar']
test :assert_inlist_when_is_member do
true = assert 'foo' inlist ['foo', 'bar']
end
test :assert_in_when_is_not_member do
test :assert_inlist_when_is_not_member do
try do
"This should never be tested" = assert 'foo' in 'bar'
"This should never be tested" = assert 'foo' inlist 'bar'
rescue
error in [ExUnit.AssertionError] ->
"Expected 'foo' to be in 'bar'" = error.message
end
end
test :refute_in_when_is_not_member do
false = refute 'baz' in ['foo', 'bar']
test :refute_inlist_when_is_not_member do
false = refute 'baz' inlist ['foo', 'bar']
end
test :refute_in_when_is_member do
test :refute_inlist_when_is_member do
try do
"This should never be tested" = refute 'foo' in ['foo', 'bar']
"This should never be tested" = refute 'foo' inlist ['foo', 'bar']
rescue
error in [ExUnit.AssertionError] ->
"Expected 'foo' to not be in ['foo','bar']" = error.message
+41 -130
View File
@@ -2,31 +2,22 @@ defrecord IEx.Config, binding: nil, cache: '', counter: 1, scope: nil, result: n
defmodule IEx do
@moduledoc """
This module implements interactive Elixir. It provides a main
function, `start` which will either delegate to `tty` or `simple`.
The former is meant for systems where tty is available and relies
on it in order to work properly. This makes all control commands
available in tty available to the developer.
This module implements Interactive Elixir.
In case `tty` is not available (for example, Windows), a
developer may invoke `simple` which starts a stripped
down version.
The interactive elixir needs to be set as the
proper `-user` when starting the Erlang VM and
so can be done with the help of IEx.CLI.
If possible, Elixir will start a tty (smart terminal)
which makes all control commands available in tty
available to the developer.
In case `tty` is not available (for example, Windows),
a dumb terminal version is started instead.
"""
@doc """
Simply loads IEx application. Most of the times it is
preloaded on demand, but if you want to pre-configure
IEx, you need to preload it.
"""
def preload do
:application.start(:iex)
__MODULE__
end
@doc """
Registers a function to be invoked after IEx
process is spawned. Requires IEx.preload to
be invoked.
Registers a function to be invoked after IEx process is spawned.
"""
def after_spawn(fun) when is_function(fun) do
:application.set_env(:iex, :after_spawn, [fun|after_spawn])
@@ -36,10 +27,15 @@ defmodule IEx do
Returns registered after spawn callbacks.
"""
def after_spawn do
case :application.get_env(:iex, :after_spawn) do
{ :ok, list } -> list
:undefined -> []
end
{ :ok, list } = :application.get_env(:iex, :after_spawn)
list
end
@doc """
Returns true if IEx was properly started.
"""
def started? do
match?({ :ok, true }, :application.get_env(:iex, :started))
end
@doc """
@@ -53,107 +49,20 @@ defmodule IEx do
Returns currently registered inspect options.
"""
def inspect_opts do
case :application.get_env(:iex, :inspect_opts) do
{ :ok, list } -> list
:undefined -> [limit: 50]
end
{ :ok, opts } = :application.get_env(:iex, :inspect_opts)
opts
end
@doc """
Interface to start IEx from CLI.
"""
def cli do
run([remsh: get_remsh(:init.get_plain_arguments)])
end
defp get_remsh(['--remsh',h|_]), do: list_to_binary(h)
defp get_remsh([_|t]), do: get_remsh(t)
defp get_remsh([]), do: nil
@doc """
Runs IEx checking if tty is available or not.
If so, invoke tty, otherwise go with the simple iex.
"""
def run(opts // []) when is_list(opts) do
if tty_works? do
tty(opts)
else
IO.puts "Warning: could not run smart terminal, falling back to dumb one"
simple(opts)
end
end
# Check if tty works. If it does not, we fall back to the
# simple/dumb terminal. This is starting the linked in
# driver twice, it would be nice and appropriate if we had
# to do it just once.
defp tty_works? do
# Dettack the error logger since we are spawning a new one
:error_logger.tty(false)
try do
port = Port.open { :spawn, :"tty_sl -c -e" }, [:eof]
Port.close(port)
catch
_, _ -> false
end
end
@doc """
Starts IEx using a tty server.
"""
def tty(opts // []) when is_list(opts) do
config = boot_config(opts)
remote =
if remsh = opts[:remsh] do
unless is_alive do
raise ArgumentError, message: "In order to use --remsh, you need to name the current node"
end
if is_atom(remsh), do: remsh, else: binary_to_atom(remsh)
end
function = fn ->
# We are inside the new tty and in a new process,
# reattach it the error logger.
:error_logger.tty(true)
start config
end
# Dettach the error logger because we are going to unregister
# the user process and start a new tty which will get control
# over the standardio. Dettaching it here allows us to get rid
# of warnings. We reattach it again when we get the new tty.
:error_logger.tty(false)
# Unregister the user process, user_drv command below
# will register the new one.
unregister_user_process
args =
if remote do
{ remote, :erlang, :apply, [function, []] }
else
{ :erlang, :apply, [function, []] }
end
:user_drv.start([:"tty_sl -c -e", args])
end
@doc """
Starts IEx simply using the current stdio.
"""
def simple(opts // []) when is_list(opts) do
start boot_config(opts)
end
# This is a callback invoked by Erlang shell utilities.
# This is a callback invoked by Erlang shell utilities
# when someone press Ctrl+G and adds 's Elixir-IEx'.
@doc false
def start(config // nil) do
preload
def start(config // [], callback // fn -> end) do
spawn fn ->
config = config || boot_config([])
config =
case config do
IEx.Config[] -> config
opts -> boot_config(opts)
end
case :init.notify_when_started(self()) do
:started -> :ok
@@ -162,18 +71,24 @@ defmodule IEx do
Process.flag(:trap_exit, true)
start_iex()
callback.()
set_expand_fun()
run_after_spawn()
IEx.Loop.start(config)
IEx.Server.start(config)
end
end
## Boot Helpers
defp boot_config(opts) do
IO.puts "Interactive Elixir (#{System.version}) - press Ctrl+C to exit (type h() ENTER for help)"
defp start_iex do
:application.start(:elixir)
:application.start(:iex)
end
scope = :elixir.scope_for_eval(
defp boot_config(opts) do
scope = :elixir.scope_for_eval(
file: "iex",
delegate_locals_to: IEx.Helpers
)
@@ -188,10 +103,6 @@ defmodule IEx do
]
end
defp unregister_user_process do
if is_pid(Process.whereis(:user)), do: Process.unregister :user
end
defp set_expand_fun do
gl = Process.group_leader
glnode = node gl
+57
View File
@@ -0,0 +1,57 @@
defmodule IEx.CLI do
@moduledoc false
@doc """
Starts IEx checking if tty is available or not.
If so, invoke tty, otherwise go with the simple iex.
"""
def start do
if tty_works? do
tty
else
:user.start
IO.puts "Warning: could not run smart terminal, falling back to dumb one"
IEx.start
end
end
# Check if tty works. If it does not, we fall back to the
# simple/dumb terminal. This is starting the linked in
# driver twice, it would be nice and appropriate if we had
# to do it just once.
defp tty_works? do
try do
port = Port.open { :spawn, :"tty_sl -c -e" }, [:eof]
Port.close(port)
catch
_, _ -> false
end
end
defp tty do
remote =
if remsh = get_remsh(:init.get_plain_arguments) do
unless is_alive do
raise ArgumentError, message: "In order to use --remsh, you need to name the current node"
end
if is_atom(remsh), do: remsh, else: binary_to_atom(remsh)
end
function = fn ->
IEx.start([], fn -> Kernel.CLI.wait_until_finished end)
end
args =
if remote do
{ remote, :erlang, :apply, [function, []] }
else
{ :erlang, :apply, [function, []] }
end
:user_drv.start([:"tty_sl -c -e", args])
end
defp get_remsh(['--remsh',h|_]), do: list_to_binary(h)
defp get_remsh([_|t]), do: get_remsh(t)
defp get_remsh([]), do: nil
end
+145 -54
View File
@@ -12,13 +12,15 @@ defmodule IEx.Helpers do
There are many other helpers available:
* `c/2` - compiles a file in the given path
* `h/0`,`h/1`, `h/2` - prints help/documentation
* `t/1`, `t/3` — prints type information
* `s/1`, `s/3` — prints spec information
* `h/0`, `h/1` - prints help/documentation
* `t/1` — prints type information
* `s/1` — prints spec information
* `m/0` - prints loaded modules
* `r/0` - recompiles and reloads the given module's source file
* `r/0`, `r/1` - recompiles and reloads the given module's source file
* `l/1` - loads given module beam code by purging the current version
* `v/0` - prints all commands and values
* `v/1` - retrieves nth value from console
* `flush/0` — flush all messages sent to the shell
Help for functions in this module can be consulted
directly from the command line, as an example, try:
@@ -54,7 +56,7 @@ defmodule IEx.Helpers do
"""
def m do
all = Enum.map :code.all_loaded, fn { mod, file } -> { inspect(mod), file } end
sorted = List.sort(all)
sorted = Enum.sort all
size = Enum.reduce sorted, 0, fn({ mod, _ }, acc) -> max(byte_size(mod), acc) end
format = "~-#{size}s ~s~n"
@@ -79,7 +81,7 @@ defmodule IEx.Helpers do
Shows the documentation for IEx.Helpers.
"""
def h() do
h(IEx.Helpers, :moduledoc)
h(IEx.Helpers, [])
end
@doc """
@@ -96,52 +98,55 @@ defmodule IEx.Helpers do
h receive/1
h Enum.all?/2
h Enum.all?
"""
defmacro h({ :/, _, [{ fun, _, nil }, arity] }) do
quote do
h(unquote(fun), unquote(arity))
end
end
defmacro h({ :/, _, [{ { :., _, [mod, fun] }, _, [] }, arity] }) do
quote do
h(unquote(mod), unquote(fun), unquote(arity))
end
end
defmacro h(other) do
defmacro h({ { :., _, [mod, fun] }, _, [] }) do
quote do
h(unquote(other), :moduledoc)
h(unquote(mod), unquote(fun))
end
end
@doc """
Prints the documentation for the given function and arity.
defmacro h({ :/, _, [{ fun, _, args }, arity] }) when args == [] or is_atom(args) do
quote do
h(unquote(fun), unquote(arity))
end
end
The function may either be a function defined inside `IEx.Helpers`
or in `Kernel`. To see functions from other module, use
`h/3` instead.
defmacro h({ name, _, args }) when args == [] or is_atom(args) do
quote do
h(unquote(__MODULE__), unquote(name))
h(Kernel, unquote(name))
end
end
## Examples
defmacro h(other) do
quote do
h(unquote(other), [])
end
end
h(:h, 2)
#=> Prints documentation for this function
"""
@doc false
def h(:h, 1) do
h(__MODULE__, :h, 1)
end
def h(function, arity) when is_atom(function) and is_integer(arity) do
if function_exported?(__MODULE__, function, arity) do
if function_exported?(__MODULE__, function, arity) or
macro_exported?(__MODULE__, function, arity) do
h(__MODULE__, function, arity)
else
h(Kernel, function, arity)
end
end
def h(module, :moduledoc) when is_atom(module) do
def h(module, []) when is_atom(module) do
case Code.ensure_loaded(module) do
{ :module, _ } ->
case module.__info__(:moduledoc) do
@@ -158,9 +163,20 @@ defmodule IEx.Helpers do
end
end
@doc """
Shows the documentation for the `function/arity` in `module`.
"""
def h(module, function) when is_atom(module) and is_atom(function) do
lc {{f, arity}, _line, _type, _args, doc } inlist module.__info__(:docs),
f == function and doc != false do
h(module, function, arity)
end
:ok
end
def h(_, _) do
IO.puts "Invalid h helper argument"
h()
end
@doc false
def h(module, function, arity) when is_atom(module) and is_atom(function) and is_integer(arity) do
if docs = module.__info__(:docs) do
doc =
@@ -223,6 +239,7 @@ defmodule IEx.Helpers do
t(Enum)
t(Enum.t/0)
t(Enum.t)
"""
defmacro t({ :/, _, [{ { :., _, [mod, fun] }, _, [] }, arity] }) do
@@ -231,15 +248,34 @@ defmodule IEx.Helpers do
end
end
defmacro t(module) do
defmacro t({ { :., _, [mod, fun] }, _, [] }) do
quote do
t(unquote(module), :all)
t(unquote(mod), unquote(fun))
end
end
defmacro t(module) do
quote do
t(unquote(module), [])
end
end
@doc false
def t(module, :all) do
def t(module, type) when is_atom(type) do
types = lc {_, {t, _, _args}} = typespec inlist Kernel.Typespec.beam_types(module),
t == type do
print_type(typespec)
typespec
end
if types == [] do
IO.puts "No types for #{inspect module}.#{type} have been found"
end
:ok
end
def t(module, []) do
types = lc type inlist Kernel.Typespec.beam_types(module), do: print_type(type)
if types == [] do
@@ -270,6 +306,10 @@ defmodule IEx.Helpers do
## Examples
s(Enum)
s(Enum.all?)
s(Enum.all?/2)
s(list_to_atom)
s(list_to_atom/1)
"""
defmacro s({ :/, _, [{ { :., _, [mod, fun] }, _, [] }, arity] }) do
@@ -278,15 +318,47 @@ defmodule IEx.Helpers do
end
end
defmacro s({ { :., _, [mod, fun] }, _, [] }) do
quote do
s(unquote(mod), unquote(fun))
end
end
defmacro s({ fun, _, args }) when args == [] or is_atom(args) do
quote do
s(Kernel, unquote(fun))
end
end
defmacro s({ :/, _, [{ fun, _, args }, arity] }) when args == [] or is_atom(args) do
quote do
s(Kernel, unquote(fun), unquote(arity))
end
end
defmacro s(module) do
quote do
s(unquote(module), :all)
s(unquote(module), [])
end
end
@doc false
def s(module, :all) do
specs = lc spec inlist Kernel.Typespec.beam_specs(module), do: print_spec(spec)
def s(module, function) when is_atom(function) do
specs = lc {_kind, {{f, _arity}, _spec}} = spec inlist beam_specs(module),
f == function do
print_spec(spec)
spec
end
if specs == [] do
IO.puts "No specs for #{inspect module}.#{function} have been found"
end
:ok
end
def s(module, []) do
specs = lc spec inlist beam_specs(module), do: print_spec(spec)
if specs == [] do
IO.puts "No specs for #{inspect module} have been found"
@@ -295,39 +367,37 @@ defmodule IEx.Helpers do
:ok
end
@doc """
Prints the specs for a given function.
## Examples
s(Enum.all?/2)
s(Enum.t/0)
"""
@doc false
def s(module, function, arity) do
spec = List.keyfind(Kernel.Typespec.beam_specs(module), { function, arity }, 0)
if spec do
specs = lc {_kind, {{f, a}, _spec}} = spec inlist beam_specs(module),
f == function and a == arity do
print_spec(spec)
else
IO.puts "No specs for #{inspect module}.#{function}/#{arity} have been found"
spec
end
if specs == [] do
IO.puts "No specs for #{inspect module}.#{function} have been found"
end
:ok
end
defp beam_specs(module) do
specs = Enum.map(Kernel.Typespec.beam_specs(module), {:spec, &1})
callbacks = Enum.map(Kernel.Typespec.beam_callbacks(module), {:callback, &1})
List.concat(specs, callbacks)
end
defp print_type({ kind, type }) do
ast = Kernel.Typespec.type_to_ast(type)
IO.puts "@#{kind} #{Macro.to_binary(ast)}"
true
end
defp print_spec({ { name, _arity }, specs }) do
defp print_spec({kind, { { name, _arity }, specs }}) do
Enum.each specs, fn(spec) ->
{ args, result } = Kernel.Typespec.spec_to_ast(spec)
bin_args = Macro.to_binary { name, 0, args }
bin_result = Macro.to_binary result
IO.puts "@spec #{bin_args}, do: #{bin_result}"
binary = Macro.to_binary Kernel.Typespec.spec_to_ast(name, spec)
IO.puts "@#{kind} #{binary}"
end
true
end
@@ -370,6 +440,27 @@ defmodule IEx.Helpers do
end
end
@doc """
Purges and reloads specified module
"""
def l(module) do
:code.purge(module)
:code.load_file(module)
end
@doc """
Flushes all messages sent to the shell and prints them out
"""
def flush do
receive do
msg ->
IO.inspect(msg)
flush
after 0 ->
:ok
end
end
defp iex_reloaded do
Process.get(:iex_reloaded) || :ordsets.new
end
@@ -1,7 +1,8 @@
defmodule IEx.Loop do
defmodule IEx.Server do
@moduledoc false
def start(config) do
IO.puts "Interactive Elixir (#{System.version}) - press Ctrl+C to exit (type h() ENTER for help)"
Process.put :iex_history, []
{ _, _, scope } = :elixir.eval('import IEx.Helpers', [], 0, config.scope)
do_loop(config.scope(scope))
@@ -21,7 +22,7 @@ defmodule IEx.Loop do
config = config.result(result)
update_history(config.cache(code).scope(nil))
config.increment_counter.cache('').binding(new_binding).scope(scope)
config.update_counter(&1+1).cache('').binding(new_binding).scope(scope)
rescue
TokenMissingError ->
config.cache(code)
+5 -1
View File
@@ -6,6 +6,10 @@ defmodule IEx.Mixfile do
end
def application do
[]
[env: [
after_spawn: [],
inspect_opts: [limit: 50],
started: true
]]
end
end
+4 -4
View File
@@ -67,8 +67,8 @@ defmodule IEx.AutocompleteTest do
test :elixir_root_completion do
{:yes, '', list} = expand('')
assert is_list(list)
assert 'h/1' in list
assert 'Elixir' in list
assert 'h/1' inlist list
assert 'Elixir' inlist list
end
test :elixir_kernel_completion do
@@ -77,13 +77,13 @@ defmodule IEx.AutocompleteTest do
test :elixir_proxy do
{:yes, '', list} = expand('E')
assert 'Elixir' in list
assert 'Elixir' inlist list
end
test :elixir_erlang_module_root_completion do
{:yes, '', list} = expand(':')
assert is_list(list)
assert 'lists' in list
assert 'lists' inlist list
end
test :completion_inside_expression do
+1 -1
View File
@@ -1,2 +1,2 @@
IEx.preload
:application.start(:iex)
ExUnit.start []
+6 -8
View File
@@ -1,10 +1,6 @@
defmodule Mix.CLI do
@moduledoc false
defmacrop exceptions do
[Mix.Error, Mix.NoTaskError, Mix.InvalidTaskError, Mix.NoProjectError, Mix.OutOfDateDepsError]
end
@doc """
Runs Mix according to the command line arguments.
"""
@@ -47,11 +43,13 @@ defmodule Mix.CLI do
rescue
# We only rescue exceptions in the mix namespace, all
# others pass through and will explode on the users face
exception in exceptions ->
if msg = exception.message do
Mix.shell.error msg
exception ->
if function_exported?(exception, :mix_error, 0) do
if msg = exception.message, do: Mix.shell.error "** (Mix) #{msg}"
else
raise(exception)
end
exit(1)
end
end
end
end
+1 -1
View File
@@ -92,7 +92,7 @@ defmodule Mix.Deps do
do: "the dependency is not available, run `mix deps.get`"
defp inspect_kw(list) do
middle = lc { key, value } inlist Enum.qsort(list), do: "#{key}: #{inspect value, raw: true}"
middle = lc { key, value } inlist Enum.sort(list), do: "#{key}: #{inspect value, raw: true}"
"[ " <> Enum.join(middle, ",\n ") <> " ]"
end
+1 -1
View File
@@ -28,7 +28,7 @@ defmodule Mix.Deps.Lock do
Receives a keyword list and writes it to the disk.
"""
def write(file // lockfile, dict) do
lines = lc { app, rev } inlist Enum.qsort(dict), rev != nil do
lines = lc { app, rev } inlist Enum.sort(dict), rev != nil do
%b("#{app}": #{inspect rev, raw: true})
end
+1 -1
View File
@@ -58,7 +58,7 @@ defmodule Mix.Deps.Project do
defp with_scm_and_status(other, _scms) do
raise Mix.Error, message: %b(dependency specified in the wrong format: #{inspect other}, ) <>
%b(expected { "app", "requirement", scm: "location" })
%b(expected { :app, scm: "location" } | { :app, "requirement", scm: "location" })
end
defp status(scm, app, req, opts) do
+5 -6
View File
@@ -1,22 +1,21 @@
defexception Mix.NoTaskError, task: nil do
defexception Mix.NoTaskError, task: nil, mix_error: true do
def message(exception) do
"The task #{task(exception)} could not be found"
end
end
defexception Mix.InvalidTaskError, task: nil do
defexception Mix.InvalidTaskError, task: nil, mix_error: true do
def message(exception) do
"The task #{task(exception)} does not respond to run/1"
end
end
defexception Mix.NoProjectError,
defexception Mix.NoProjectError, mix_error: true,
message: "Could not find a Mix.Project"
defexception Mix.Error,
message: nil
defexception Mix.Error, mix_error: true, message: nil
defexception Mix.OutOfDateDepsError, env: nil do
defexception Mix.OutOfDateDepsError, mix_error: true, env: nil do
def message(exception) do
"Some dependencies are out of date, please run `MIX_ENV=#{exception.env} mix deps.get` to proceed"
end
+9 -9
View File
@@ -13,13 +13,13 @@ defmodule Mix.SCM do
Returns an Elixir term that contains relevant SCM
information for printing.
"""
defcallback format(opts), do: term
defcallback format(opts) :: term
@doc """
Returns an Elixir term that contains relevant SCM
lock information for printing.
"""
defcallback format_lock(lock), do: term
defcallback format_lock(lock) :: term
@doc """
This behavior function receives a keyword list of `opts`
@@ -34,13 +34,13 @@ defmodule Mix.SCM do
sense for the Git SCM, it will return an update list of options
while other SCMs would simply return nil.
"""
defcallback accepts_options(opts), do: opts | nil
defcallback accepts_options(opts) :: opts | nil
@doc """
This behavior function returns a boolean if the
dependency is available.
"""
defcallback checked_out?(opts), do: boolean
defcallback checked_out?(opts) :: boolean
@doc """
This behavior function checks out dependencies.
@@ -50,7 +50,7 @@ defmodule Mix.SCM do
no lock is given and the repository can be checked out
to the latest version.
"""
defcallback checkout(opts), do: any
defcallback checkout(opts) :: any
@doc """
This behavior function updates dependencies. It may be
@@ -62,7 +62,7 @@ defmodule Mix.SCM do
It must return the current lock.
"""
defcallback update(opts), do: any
defcallback update(opts) :: any
@doc """
This behavior function checks if the dependency is locked and
@@ -70,19 +70,19 @@ defmodule Mix.SCM do
SCMs do not require a lock, for such, this function can simply
return true.
"""
defcallback matches_lock?(opts), do: boolean
defcallback matches_lock?(opts) :: boolean
@doc """
Receives two options and must return true if the refer to the
same repository. The options are guaranteed to belong to the
same SCM.
"""
defcallback equals?(opts1 :: opts, opts2 :: opts), do: boolean
defcallback equals?(opts1 :: opts, opts2 :: opts) :: boolean
@doc """
This behavior function should clean the given dependency.
"""
defcallback clean(opts), do: any
defcallback clean(opts) :: any
@doc """
Returns all available SCM.
+14 -10
View File
@@ -38,20 +38,20 @@ defmodule Mix.SCM.Git do
def checkout(opts) do
path = opts[:path]
location = opts[:git]
maybe_error System.cmd(%b[git clone --quiet --no-checkout "#{location}" "#{path}"])
command = %b[git clone --no-checkout "#{location}" "#{path}"]
if checked_out?(opts) do
File.cd! path, fn -> do_checkout(opts) end
end
run_cmd_or_raise(command)
File.cd! path, fn -> do_checkout(opts) end
end
def update(opts) do
File.cd! opts[:path], fn ->
command = "git fetch --force --quiet"
command = "git fetch --force"
if opts[:tag] do
command = command <> " --tags"
end
maybe_error System.cmd(command)
run_cmd_or_raise(command)
do_checkout(opts)
end
end
@@ -64,10 +64,10 @@ defmodule Mix.SCM.Git do
defp do_checkout(opts) do
ref = get_lock_rev(opts[:lock]) || get_opts_rev(opts)
maybe_error System.cmd("git checkout --quiet #{ref}")
run_cmd_or_raise "git checkout --quiet #{ref}"
if opts[:submodules] do
maybe_error System.cmd("git submodule update --init --recursive")
run_cmd_or_raise "git submodule update --init --recursive"
end
get_lock(opts, true)
@@ -120,6 +120,10 @@ defmodule Mix.SCM.Git do
nil
end
defp maybe_error(""), do: :ok
defp maybe_error(other), do: Mix.shell.error(other)
defp run_cmd_or_raise(command) do
if Mix.shell.cmd(command) != 0 do
raise Mix.Error, message: "command `#{command}` failed"
end
true
end
end
+3 -3
View File
@@ -92,11 +92,11 @@ defmodule Mix.Server do
def handle_cast({ :push_project, name, project }, config) do
project = Keyword.merge(project, config.post_config)
{ :noreply, config.post_config([]).prepend_projects [ { name, project } ] }
{ :noreply, config.post_config([]).update_projects [{ name, project }|&1] }
end
def handle_cast({ :post_config, value }, config) do
{ :noreply, config.merge_post_config value }
{ :noreply, config.update_post_config(&1 /> Keyword.merge(value)) }
end
def handle_cast({ :add_scm, mod }, config) do
@@ -104,7 +104,7 @@ defmodule Mix.Server do
end
def handle_cast({ :mixfile_cache, app, new }, config) do
{ :noreply, config.merge_mixfile([{ app, new }]) }
{ :noreply, config.update_mixfile(&1 /> Keyword.merge([{ app, new }])) }
end
def handle_cast(:clear_mixfile_cache, config) do
+29 -3
View File
@@ -8,15 +8,41 @@ defmodule Mix.Shell do
@doc """
Informs the given message.
"""
defcallback info(message :: binary), do: any
defcallback info(message :: binary) :: any
@doc """
Warns about the given error message.
"""
defcallback error(message :: binary), do: any
defcallback error(message :: binary) :: any
@doc """
Asks the user for confirmation.
"""
defcallback yes?(message :: binary), do: any
defcallback yes?(message :: binary) :: any
@doc """
Executes the given command and returns
its exit status.
"""
defcallback cmd(command :: binary) :: integer
@doc """
An implementation of the command callback that
is shared accross different shells.
"""
def cmd(command, callback) do
port = Port.open({ :spawn, to_char_list(command) },
[:stream, :binary, :exit_status, :hide])
do_cmd(port, callback)
end
defp do_cmd(port, callback) do
receive do
{ ^port, { :data, data } } ->
callback.(data)
do_cmd(port, callback)
{ ^port, { :exit_status, status } } ->
status
end
end
end
+10
View File
@@ -4,6 +4,16 @@ defmodule Mix.Shell.IO do
It simply prints messages to stdio and stderr.
"""
@behaviour Mix.Shell
@doc """
Executes the given command and prints its output
to stdout as it comes.
"""
def cmd(command) do
Mix.Shell.cmd(command, IO.write(&1))
end
@doc """
Writes a message to the shell followed by new line.
"""

Some files were not shown because too many files have changed in this diff Show More