Compare commits

...
219 Commits
Author SHA1 Message Date
José Valim e02d7bf913 Release v1.0.4 2015-04-07 23:35:13 +02:00
José Valim fe5ff41514 Tag async tests as such 2015-04-07 11:35:31 +02:00
José Valim e9887a760e Wait until test process is down in ExUnit runner
This should fix a race condition where the next test would
start before all linked processes in the previous test were
killed, often leading to already registered errors.
2015-04-06 19:13:09 +02:00
Eric Meadows-Jönsson 0559fe5927 Fix doc formatting in ExUnit.Formatter
Signed-off-by: Eric Meadows-Jönsson <eric.meadows.jonsson@gmail.com>
2015-04-05 19:27:01 -05:00
José Valim 1a05bff45d Update CHANGELOG 2015-04-05 19:28:53 +02:00
José Valim 4d6670342b Fixes for Erlang 18.0-rc1 2015-04-05 18:28:13 +02:00
Eric Meadows-Jönsson 193e85882e Add compatibilty with OTP 18 typespec changes
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2015-04-05 17:59:29 +02:00
José Valim a58c007e0c Avoid missing behaviour warnings when compiling erlang files 2015-04-04 18:34:58 +02:00
José Valim ce6f4e6d15 Clean up ExUnit.Assertions docs 2015-04-03 17:08:03 +02:00
José Valim 321096665a Do not propagate protocol consolidation to children 2015-04-02 21:39:33 +02:00
José Valim b93aae9f86 Ensure we quote proper file on exceptions
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>

Conflicts:
	lib/elixir/test/elixir/exception_test.exs
2015-04-02 18:40:24 +02:00
José Valim a503069479 Ensure we consolidate only when there was a change 2015-04-02 15:42:56 +02:00
José Valim 120ea0fc8a Start v1.0.4-dev 2015-04-02 15:15:27 +02:00
José Valim d725c66d93 Update CHANGELOG 2015-04-02 15:04:00 +02:00
José Valim fab917dba3 Do not print app name as a filename 2015-04-02 15:00:52 +02:00
José Valim 23fb92f444 Improve error message for IEx.pry/1 2015-04-02 14:57:00 +02:00
José Valim db61109495 Generate build_embedded and start_permanent in new apps 2015-04-02 14:41:58 +02:00
José Valim c4e8ad0d65 Support start_permanent 2015-04-02 14:41:47 +02:00
José Valim ce77fa54b9 Support build_embedded and consolidate_protocols 2015-04-02 14:39:08 +02:00
Paulo Almeida eee921bf7b Support mix help --search PATTERN
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2015-04-02 09:47:07 +02:00
José Valim 73c6a0be8b Update CHANGELOG 2015-04-02 09:39:05 +02:00
José Valim 7644bd87a5 Merge pull request #3198 from eksperimental/pattern_operator
fix Kernel.=~/2 for "" =~ ""

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2015-04-02 09:37:05 +02:00
José Valim 9e2c5a29a4 Speed up decode_www_form
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2015-03-17 19:02:12 +01:00
José Valim 09498422d0 Speed up URI.decode_query/1
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2015-03-17 18:17:29 +01:00
Eric Meadows-Jönsson a697bab7f2 Store relative paths in erlang manifest
Signed-off-by: Eric Meadows-Jönsson <eric.meadows.jonsson@gmail.com>
2015-03-13 13:44:16 +01:00
José Valim 36e81d587e Increase elixir_counter timeout
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2015-03-12 19:00:56 +01:00
Booker C. Bense 17a0abd57a Added test for markup ending in . and code to pass the test
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2015-03-07 00:19:39 +01:00
Or Neeman e6cfc20556 Produce clearer messages for syntax errors before a sigil
Closes #3130

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2015-03-07 00:19:20 +01:00
Or Neeman 906f03102b Return value when matching with _ on last line of block
Closes #3125

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2015-03-02 14:27:47 +01:00
José Valim 6a594e2bba Ensure changes in child deps forces the parent to recompile
This test fell through the cracks when we changed mix
to no longer compile dependencies on get but during
project compilation.

Therefore, this patch fixes the bug and ensures the test
is up to date to check the original scenario it was supposed
to.

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2015-03-01 11:03:03 +01:00
José Valim 10dd8f843b Fallback to undocumented API only if necessary
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>

Conflicts:
	lib/elixir/src/elixir.erl
2015-03-01 11:02:51 +01:00
José Valim 53ecea8b03 Do not erase stacktrace on bad configs
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2015-03-01 11:02:05 +01:00
Eric Meadows-Jönsson efba8d8bfa Fix httpc proxy option profile
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2015-03-01 11:00:43 +01:00
José Valim 5a201065dc Update new major.minor instructions
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2015-02-12 11:17:22 +01:00
José Valim 307675284c Release v1.0.3 2015-02-12 10:57:06 +01:00
José Valim 38a9513111 Fix boundary in Enum.take/2, closes #3042
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2015-02-01 09:19:30 -02:00
José Valim 578f798cc1 Use which_applications instead of undocumented info
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2015-01-31 09:46:53 -02:00
José Valim efbdf4b9b0 Update CHANGELOG 2015-01-31 11:43:06 +01:00
José Valim 9679bed4d7 Revert "Don't ignore escape characters in regex"
This change will be on v1.1 only as it may break applications.

This reverts commit 337c040da8.
2015-01-31 11:32:45 +01:00
José Valim b6c0045fc2 Ensure rebar is quoted when running escript on windows
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2015-01-30 11:10:41 +01:00
José Valim 10612ddf22 Always rebuild structures on Windows
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2015-01-30 10:13:15 +01:00
José Valim cbcbbcf876 Use :application.info[:loaded] to avoid races 2015-01-29 12:05:09 +01:00
José Valim 95b81c2a13 Merge pull request #3025 from MSch/flush-logger-after-tests
Call Logger.flush in System.at_exit when running mix test.

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2015-01-29 11:45:32 +01:00
José Valim a525f71e13 Update CHANGELOG 2015-01-29 11:14:40 +01:00
José Valim 750d2a3f7a Ignore exported vars warning
Closes #2798

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2015-01-28 21:50:16 +01:00
José Valim 61e9fafb73 Improve error message with invalid structures in maps
Closes #2958

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2015-01-27 17:48:50 +01:00
José Valim aa48f6a881 Ensure priv/include are copied on Windows even if source did not change
Closes #2908

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2015-01-27 15:29:43 +01:00
José Valim b5e2d45b7d Update CHANGELOG 2015-01-24 14:10:17 +01:00
José Valim 29f2eb7dab Merge pull request #2801 from fishcakez/app_env
Move application state out of application env
2015-01-24 13:57:50 +01:00
José Valim a3754e3841 Do not show aliases and import warnings when code cannot compile
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2015-01-24 11:47:45 +01:00
José Valim 337c040da8 Don't ignore escape characters in regex
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2015-01-22 11:46:11 +01:00
José Valim b4d465e2ed Merge pull request #3018 from lexmag/logger-metadata-fix
Fix the logging with `metadata[:function]`
2015-01-21 11:47:02 +01:00
James Fish 0bb3d1c6bd Introduce Mix.State to store internal mix state
Moves non-config state from application env to an ETS table that is
updated with an Agent.
2015-01-10 15:20:22 +00:00
James Fish 86476615ef Fix logger to keep config through config_change/3
Store deleted handlers and live config data in ETS table
2015-01-10 15:20:22 +00:00
James Fish 860b3cc5a7 Introduce IEx.Config to store internal iex state
* All config get/put moved to IEx.Config
* after_spawn stored in ETS to prevent config_change deletion
* started? checks for IEx.Config process
* Return full config on IEx.configuration/1
2015-01-10 15:20:21 +00:00
James Fish bb5e6f65f5 Introduce elixir_config to store internal state
Adds ETS table that can only be updated by the gen_server elixir_config,
which means that reads are async and writes are sync.

Stores:
* URI scheme/ports
* Compiler options
* argv
* System.at_exit funs
2015-01-10 15:20:09 +00:00
Samuel Tonini 95127516f7 add missing assert call
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2015-01-09 19:09:11 +01:00
James Fish ef0c495f16 Set MIX_ARCHIVES in mix tasks archive tests
Previously archive tests would fail if MIX_ARCHIVES was set.

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2015-01-09 19:09:08 +01:00
José Valim 2472bb79ab Ensure mix compiler is able to handle nested directories
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2015-01-09 15:04:25 +01:00
José Valim 04b9174d75 Update CHANGELOG 2015-01-04 23:44:34 +01:00
Eric Meadows-Jönsson d31a58f0d9 Don't go through hex.pm API server to install Hex
Signed-off-by: Eric Meadows-Jönsson <eric.meadows.jonsson@gmail.com>
2015-01-04 20:33:11 +01:00
José Valim f97ab2649c Keep original stacktrace on inspect errors
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2015-01-03 20:26:37 +01:00
José Valim d969d42784 Compile docs to doc/
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2015-01-03 10:12:02 +01:00
José Valim 17c4c4d993 Do more strict matching on URI scheme
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2014-12-26 23:17:41 +01:00
José Valim 0f7a968389 Copyright requires just the starting year
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2014-12-26 21:00:26 +01:00
José Valim 272ec8ca47 Fix mix shell tests when ansi escapes are disabled
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2014-12-26 15:39:56 +01:00
José Valim a0e8e1c0fb Further improve URI parse docs
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2014-12-26 15:34:28 +01:00
José Valim b477dba34f Improve docs for URI
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2014-12-24 11:12:33 +01:00
José Valim 8d524ef971 Fix bootstrap issues
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2014-12-23 22:26:32 +01:00
José Valim 83cb5152fd Optimize reduce for maps
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2014-12-23 20:05:29 +01:00
José Valim 3c100575a3 Merge pull request #2953 from liveforeverx/fix_compile_error
resolves #2952

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2014-12-13 23:12:35 +01:00
José Valim d1b6161f2e Update CHANGELOG 2014-12-12 12:04:02 +01:00
José Valim 785781d089 Merge pull request #2950 from tonini/remove-dumb-terminal-warning
warning about dumb terminal just irritates users

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2014-12-12 12:01:03 +01:00
José Valim b2ac4c9394 Only shut down Logger if the application is being actually started
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2014-12-12 12:00:52 +01:00
José Valim 50ac792747 Optimize and also add completion for Erlang modules
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2014-12-11 09:42:40 +01:00
José Valim 7d433e80e5 Improve IEx autocomplete implementation
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2014-12-11 09:42:37 +01:00
José Valim f9cb812bc1 Always use the loaded application modules
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2014-12-11 05:01:57 +01:00
José Valim f8711f2e36 Merge pull request #2932 from tonini/add-modules-load-path-to-autocomplete
Fetch module names from load path for autocomplete

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2014-12-11 05:01:50 +01:00
José Valim 0251f564d9 Merge pull request #2933 from Kabie/Regex.replace
Fix and enhance &Regex.replace/4

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2014-12-08 16:20:59 +01:00
José Valim e4acc08cf5 Improve archive error on Windows
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2014-12-07 14:21:17 +01:00
José Valim 2ab5d25787 Update CHANGELOG 2014-12-06 22:14:22 +01:00
José Valim acde8078f8 Clean up IO.ANSI.Docs handling of `
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2014-12-06 22:12:56 +01:00
José Valim 487ac9c1c4 Merge pull request #2924 from jw2013/italic
Fix broken IO.ANSI.Docs parser
Conflicts:
	lib/elixir/lib/io/ansi/docs.ex
2014-12-06 22:12:10 +01:00
José Valim f0f65f8bec Use IO.ANSI.format/1 for escaping mix shell messages
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2014-12-04 22:17:07 +01:00
José Valim 23943b6180 Update CHANGELOG 2014-11-21 17:42:39 +01:00
José Valim d9125b0a81 Ensure compilation does not leave random spurious data on failure
Prior to this patch, the parallel compiler could leave spurious
processes and the module definition could leave stale data
in the elixir_modules table.

The issue has been fixed by emitting the proper exit signals
and by using monitors.

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2014-11-21 17:41:43 +01:00
José Valim d1d46db907 Start v1.0.3-dev 2014-11-13 10:41:38 +01:00
José Valim 02056d1030 Merge pull request #2891 from chrismccord/master
Fix command_to_module_name not converting snakecased names properly

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2014-11-13 10:40:58 +01:00
José Valim 14a3686a3f Merge pull request #2882 from Frost/no-more-bright-yellow-in-ansi-docs
Make IO.ANSI.Docs readable on white background

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2014-11-09 20:56:10 -02:00
José Valim c04b182c6c Merge pull request #2886 from jw2013/CRLF
fix wrong CRLF grapheme

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2014-11-09 20:55:26 -02:00
Eric Meadows-Jönsson f5462d98e0 Don't show hex update message on mix local.hex
Signed-off-by: Eric Meadows-Jönsson <eric.meadows.jonsson@gmail.com>
2014-11-08 22:55:56 +01:00
José Valim 75d0c07b46 Merge pull request #2838 from edgurgel/patch-1
Fix typo on CHANGELOG.md
2014-10-21 21:42:11 -02:00
Eduardo Gurgel 1de8dcb695 Fix typo on CHANGELOG.md
[ci skip]
2014-10-22 10:55:19 +13:00
José Valim 0e167bef27 More updates to instructions
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2014-10-21 15:32:48 -02:00
José Valim 34df817638 Release v1.0.2 2014-10-21 15:19:58 -02:00
José Valim 311a5790d6 Update release instructions
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>

Conflicts:
	Makefile
2014-10-21 15:02:37 -02:00
José Valim b4c75f97b4 Fail to compile try without else/catch/after/rescue, closes #2797
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2014-10-21 14:57:55 -02:00
José Valim 70692e6618 Improve token errors for aliases, closes #2818
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2014-10-21 14:57:47 -02:00
José Valim 529d584262 Unify error reporting from EEx, closes #2833
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>

Conflicts:
	lib/eex/test/eex_test.exs
2014-10-21 14:57:36 -02:00
José Valim c0fb275d5b Properly handle eval_failure warnings, closes #2835
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2014-10-21 14:56:55 -02:00
José Valim 24f46be144 Use unamed tables for module compilation
* We have created a main named table that keeps all module information

* We have reduced the amount of tables by storing internal, docs and
  attributes altogether and using table looks for retriving the relevant
  information

* At this moment, creating a module in Elixir defines three tables,
  one for data, another for functions and another for clauses

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2014-10-17 13:53:04 +02:00
José Valim 0fe3da9dea Update CHANGELOG 2014-10-15 20:54:31 +02:00
José Valim 03d0ad3f03 No longer inline binary expressions in EEx
String.Chars is now always inlined by the compiler.

Closes #2815

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2014-10-15 20:52:48 +02:00
José Valim a87f43701f Merge pull request #2819 from benjamintanweihao/patch-3
Fix missing concatenation operator.

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2014-10-15 20:52:34 +02:00
k1complete dce3a3a2a5 set null prompt function in dumbterm mode
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2014-10-15 20:52:19 +02:00
Eduardo Gurgel 7b20084b28 Add OTP 17.3 to run on Travis CI
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2014-10-15 20:51:57 +02:00
José Valim 542808102f Build .beam location from scratch on mix compile.elixir
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2014-10-15 20:51:41 +02:00
Eric Meadows-Jönsson fd18b6a41f Fix String.replace/4 typespec
Signed-off-by: Eric Meadows-Jönsson <eric.meadows.jonsson@gmail.com>
2014-10-07 19:41:53 +02:00
Eric Meadows-Jönsson 4c9c95f80f Add task for pushing standalone mix
Signed-off-by: Eric Meadows-Jönsson <eric.meadows.jonsson@gmail.com>
2014-10-07 15:23:47 +02:00
José Valim 89460154bb Release v1.0.1 2014-10-07 13:33:07 +02:00
José Valim b026e8877a --elixirc-paths should be a subset of project paths
Since the manifest is shared, --elixirc-paths must
be a subset as we need to know what to change and what
to not change in the manifest file.

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2014-10-07 13:25:42 +02:00
José Valim 597381b2e0 Write to manifest when file is removed
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2014-10-06 22:10:01 +02:00
José Valim df70729b14 Test that we do purge and delete artifacts
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2014-10-06 17:26:26 +02:00
José Valim 61b116c1a5 Also purge and delete during tests
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2014-10-06 17:21:31 +02:00
José Valim d37a10bbd0 Purge and delete modules before compiling them
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2014-10-06 17:21:25 +02:00
José Valim 5d75c5c861 Refactor and add new rewrite rules
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2014-10-06 16:46:01 +02:00
Eric Meadows-Jönsson db4089cc71 Don't crash on malformed proxy env var
Signed-off-by: Eric Meadows-Jönsson <eric.meadows.jonsson@gmail.com>
2014-10-06 11:35:24 +02:00
José Valim f9f4f14edf Update CHANGELOG 2014-10-02 21:10:39 +02:00
Eric Meadows-Jönsson 58946ce879 Give higher prio to powershell on windows
Signed-off-by: Eric Meadows-Jönsson <eric.meadows.jonsson@gmail.com>
2014-10-01 18:32:23 +02:00
Eric Meadows-Jönsson de1d5ff793 Send elixir version as parameter when installing hex
Signed-off-by: Eric Meadows-Jönsson <eric.meadows.jonsson@gmail.com>
2014-09-29 21:22:07 +02:00
Eric Meadows-Jönsson a4be8fb260 Fix :stderr_to_stdout option
Signed-off-by: Eric Meadows-Jönsson <eric.meadows.jonsson@gmail.com>
2014-09-29 21:21:58 +02:00
Eric Meadows-Jönsson 031a27f5fa Fix spec for surround_many
Signed-off-by: Eric Meadows-Jönsson <eric.meadows.jonsson@gmail.com>
2014-09-29 21:21:50 +02:00
José Valim f3c1931cac Update CHANGELOG 2014-09-27 11:34:48 +02:00
José Valim 2e48d81c20 Move printing out of copy_path, always create dir
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2014-09-27 11:33:03 +02:00
José Valim e7ef3fc95d Merge pull request #2787 from elixir-lang/emj-hex-file-error
Report correct location if local.hex failed

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2014-09-27 11:32:52 +02:00
José Valim 2d01aa1c6a Do not pass false into port command
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2014-09-27 11:08:04 +02:00
José Valim ae07124550 Merge pull request #2789 from alco/system-cmd-path-resolution
System.cmd path resolution

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2014-09-27 11:07:28 +02:00
José Valim 1f744f4dac Also remove elixir_exp from stacktraces
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2014-09-25 10:37:10 +02:00
José Valim ad88ac642f Remove :elixir module from stacktraces
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2014-09-25 10:30:19 +02:00
Eric Meadows-Jönsson f7956e9d80 Bump Hex requirement
Signed-off-by: Eric Meadows-Jönsson <eric.meadows.jonsson@gmail.com>
2014-09-23 21:44:24 +02:00
Eric Meadows-Jönsson da6892aa96 Add MIX_ENV output to archive.build
Signed-off-by: Eric Meadows-Jönsson <eric.meadows.jonsson@gmail.com>
2014-09-22 23:32:07 +02:00
José Valim 7a3bd20804 Update CHANGELOG 2014-09-20 13:43:52 +02:00
José Valim f7015166dc Filter out :elixir_lexical from stacktraces
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2014-09-20 13:41:47 +02:00
José Valim c6f346a355 Change version requirement to be MAJOR.MINOR
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2014-09-20 13:41:41 +02:00
José Valim 911a3ba97f Update release instructions
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2014-09-19 18:26:40 +02:00
José Valim a1668a6b28 Ensure Mix.Config is deep merged
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2014-09-16 19:53:47 +02:00
José Valim a4cb74a83f Include stracktrace on ExUnit timeouts
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2014-09-16 19:53:43 +02:00
José Valim bbf9552186 Add tests for add_process_handler/2
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2014-09-15 19:33:17 +02:00
Eric Meadows-Jönsson e621b5828e Load dependencies before deps.check compiles
Signed-off-by: Eric Meadows-Jönsson <eric.meadows.jonsson@gmail.com>
2014-09-15 10:40:35 +02:00
José Valim 68e282c50a Do not require {ref, :done}, instead allow sync removal
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2014-09-14 23:15:57 +02:00
José Valim 403a50fbcd Ensure chunk/4 is haltable
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2014-09-13 16:14:23 +02:00
José Valim 8e98cd4f41 Start v1.0.1-dev 2014-09-12 12:34:52 +02:00
budstein 499c385000 Fix a typo in Code.eval_file's doc
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2014-09-12 12:33:37 +02:00
Alexei Sholik aecafc099d Fix a typo in IO.inspect’s doc
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2014-09-12 12:33:31 +02:00
Haoming Zhu 785c7d700a fix previous commit typo bug
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2014-09-12 12:33:15 +02:00
Bill Gathen cecc8a0f95 Fix my own typo in helpers.ex
Brand-new language, and I put a ding in it. Sorry about that. :-(

Congratulations on 1.0!

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2014-09-12 12:32:53 +02:00
José Valim 52ff7e9686 Release v1.0.0 2014-09-10 14:11:13 +02:00
José Valim e2eb2cfdb1 Remove broken List.unzip 2014-09-10 01:24:34 +02:00
José Valim a5b4c1c04d Document and test Logger functions return :ok 2014-09-09 16:58:48 +02:00
José Valim e38f960d7e Update CHANGELOG 2014-09-09 16:55:47 +02:00
José Valim 9e74ee686e Add Logger.enable/1 and Logger.disable/1 2014-09-09 16:54:02 +02:00
José Valim e068625d7d Add integer guards around Enum.fetch/2 2014-09-09 00:09:18 +02:00
José Valim 00e8446a58 Remove support for inner swap_handler 2014-09-08 19:07:13 +02:00
José Valim 2aecfede5b Merge pull request #2759 from mikepack/transform-doc-fix
Fix typos in Stream.transform/3 docs
2014-09-07 22:29:51 +02:00
Mike Pack 9eebefb976 Fix typos in Stream.transform/3 docs 2014-09-07 14:24:28 -06:00
José Valim b709cba371 Properly document Task.Supervisor options 2014-09-07 16:40:01 +02:00
José Valim 1d919f59e3 Remove deprecated code 2014-09-07 15:36:07 +02:00
José Valim 0e8d3f9208 Start v1.0.0 2014-09-07 13:20:02 +02:00
José Valim 8931f34582 Release v1.0.0-rc2 2014-09-07 12:49:56 +02:00
José Valim 843706e36e Add free operators
<<~, ~>>, <~, ~>, <~>, <|>
2014-09-07 12:42:10 +02:00
José Valim 172bfdb128 Revert "Merge pull request #2702 from alexrp/master"
<~ is already a valid expression in Elixir.

This reverts commit 2c741ec6ae, reversing
changes made to 12a457829f.
2014-09-07 12:12:35 +02:00
José Valim 2c741ec6ae Merge pull request #2702 from alexrp/master
Introduce new operators intended for use by parser combinator libraries.
2014-09-07 12:06:29 +02:00
Eric Meadows-Jönsson 12a457829f Return the proper exit code from powershell 2014-09-06 21:10:48 +02:00
José Valim 0ff6850237 Raise if status code != 0 and use non-verbose modes 2014-09-06 20:33:02 +02:00
Eric Meadows-Jönsson 63f6088476 Bump Hex requirement 2014-09-06 20:13:37 +02:00
Eric Meadows-Jönsson 206f6413f8 Merge pull request #2756 from elixir-lang/emj-archive-shell
Fetch Hex with wget or curl
2014-09-06 20:12:49 +02:00
Eric Meadows-Jönsson a2c0424b37 Add file exists check on windows when installing archive 2014-09-06 19:14:36 +02:00
Eric Meadows-Jönsson 6ea7d3cef6 Fetch Hex with wget or curl
This way we can use https and do peer verification without bundling
certificates with elixir.
2014-09-06 16:37:57 +02:00
José Valim 2cafd8c060 Ensure we don't look up inexisting variables for default args 2014-09-05 17:59:45 +02:00
José Valim 7271dfebfb Update CHANGELOG 2014-09-05 16:52:26 +02:00
José Valim 2eff3590c1 Lowercase drive letters from cwd, closes #2747 2014-09-05 16:43:55 +02:00
José Valim b578f45060 Fix IEx test 2014-09-05 16:35:30 +02:00
José Valim a34ccfe129 Make the regex type non-opaque 2014-09-05 16:27:20 +02:00
José Valim 8f4442f2df Test :sys functions with GenEvent 2014-09-05 16:15:28 +02:00
José Valim 598e42e00b Tidy up GenEvent docs and test suite 2014-09-05 13:34:26 +02:00
José Valim b93912b9dc Deprecate monitor option in GenEvent 2014-09-05 13:20:40 +02:00
José Valim 71eca8a5d6 Move from already_added to already_present 2014-09-05 12:50:42 +02:00
José Valim f0bb0e1f7f Do not wrap process handlers in a tuple 2014-09-05 12:26:59 +02:00
José Valim 2cb0639208 Move from module_not_found to the more generic handler_not_found 2014-09-05 11:59:53 +02:00
José Valim 081e3b36ec Allow only one process handler per process 2014-09-05 11:51:33 +02:00
José Valim d521d7927f Leave clause in case the handler can fail 2014-09-04 19:21:37 +02:00
José Valim 7313d26720 Remove deprecated branches 2014-09-04 13:58:58 +02:00
José Valim 577a07d47f Do not consider handler swap to be failures 2014-09-04 13:24:17 +02:00
José Valim f6093d40d7 Tag all doctest tests 2014-09-04 12:10:35 +02:00
José Valim 1dd034d4aa Merge pull request #2752 from fishcakez/dialyzer_warns
Fix some dialyzer warnings
2014-09-03 21:05:29 +02:00
James Fish 2417b196a4 Fix elixir dialyzer warnings 2014-09-03 19:29:01 +01:00
James Fish 50bb712e22 Fix iex dialyzer warnings 2014-09-03 19:24:32 +01:00
James Fish 7b0747c907 Fix :sys.replace_state/2,3 for GenEvent 2014-09-03 19:24:01 +01:00
James Fish b878e414de Fix mix dialyzer warnings and improve task specs 2014-09-03 19:23:29 +01:00
James Fish c447868e0f Fix Mix lock files to use maps 2014-09-03 19:22:42 +01:00
James Fish 681d5aa9c4 Fix logger dialyzer warnings 2014-09-03 19:20:09 +01:00
James Fish 0263dcc621 Don't check for race conditions with dialyzer
For more information:
http://erlang.org/pipermail/erlang-bugs/2014-July/004518.html
2014-09-03 19:13:39 +01:00
José Valim 1ae24c5c5d Update CHANGELOG 2014-09-03 14:38:28 +02:00
José Valim 6c02ebe4af Merge pull request #2720 from edgurgel/print-mailbox-on-failing-assert-receive
Print process mailbox on failing assert_receive
2014-09-03 14:24:16 +02:00
José Valim a08e44c5a9 Default max_restarts to 3 (closer to Erlang) 2014-09-03 14:23:03 +02:00
José Valim 08f30098db Improve iex error message 2014-09-03 14:22:16 +02:00
José Valim 5dbd91e4fa Merge pull request #2746 from styx/master
Fixes #2744
2014-09-03 13:46:23 +02:00
Mikhail S. Pobolovets 5b2eb54794 #2744: Ensure Path.relative_to is case insensitive on the drive letter
Signed-off-by: Mikhail S. Pobolovets <styx.mp@gmail.com>
2014-09-03 14:40:03 +03:00
José Valim 2c17007693 Merge pull request #2749 from alco/macro-env-doc
Document how to obtain a custom instance of Macro.Env
2014-09-03 13:13:15 +02:00
Alexei Sholik 2770c32f3d Document how to obtain a custom instance of Macro.Env 2014-09-03 11:10:07 +03:00
José Valim 8c8f6d8346 Merge pull request #2743 from fishcakez/loggertest_exit_signal_rc
Fix exit signal race condition in logger tests
2014-09-02 12:22:22 +02:00
James Fish 3950130e73 Fix exit signal race condition in logger tests
Test process must wait for exit signal before untrapping exits,
otherwise exit signal may cause it to exit.
2014-09-02 11:07:18 +01:00
Alex Rønne Petersen c8bcd89c7c Introduce new operators intended for use by parser combinator libraries.
The expected definitions (which are by no means required) in
parser combinator speak are:

* `p  ~>> f`   =  `bind(p, f)`
* `p1 ~>  p2`  =  `pair_right(p1, p2)`
* `p1 <~  p2`  =  `pair_left(p1, p2)`
* `p1 <~> p2`  =  `pair_both(p1, p2)`
* `p  |~> f`   =  `map(p, f)`
* `1p <|> p2`  =  `either(p1, p2)`
2014-09-02 01:57:50 +02:00
José Valim 7f7aaa115f Merge pull request #2736 from fishcakez/logger_stopped_exit
Fix Logger.log/2 not to raise on Logger exit
2014-09-01 19:33:11 -04:00
José Valim 53aeb26407 Merge pull request #2741 from styx/master
Fixes #2737
2014-09-01 19:31:55 -04:00
Mikhail S. Pobolovets bba0d12510 #2737: deps.unlock should warn when given missing dependency
Signed-off-by: Mikhail S. Pobolovets <styx.mp@gmail.com>
2014-09-01 17:53:03 +03:00
Eric Meadows-Jönsson 035d8227a5 Merge pull request #2740 from styx/patch-1
Typo fix
2014-09-01 13:36:43 +02:00
Eric Meadows-Jönsson 003a4da87e Merge pull request #2738 from mgwidmann/master
Correct documentation for proper grammer.
2014-09-01 13:36:17 +02:00
Mikhail S. Pobolovets 1977631b7f Typo fix 2014-09-01 11:58:09 +03:00
matt c9a0ec000a Correct documentation for proper grammer. 2014-08-31 23:55:41 -04:00
Eric Meadows-Jönsson ba842ba113 No commas in deps.unlock 2014-08-31 19:49:50 +02:00
James Fish 6304919d0d Update Logger mode after configuration
Compute mode after configure as possible mode should change.
2014-08-31 17:04:23 +01:00
James Fish d2d27e96e5 Fix Logger.log/2 not to raise on Logger exit 2014-08-31 16:40:57 +01:00
Eric Meadows-Jönsson fc6f7c532c Fix unused warnings 2014-08-31 09:01:00 +02:00
José Valim 1bee85e518 Merge pull request #2734 from gjaldon/clearer-unquote-splicing-error
Friendlier unquote_splicing error with hints
2014-08-30 14:58:30 -04:00
Gabriel Jaldon 66b211987d Friendlier unquote_splicing error with hints 2014-08-31 02:01:17 +08:00
José Valim 15fdae4de6 Start v1.0.0-rc2 2014-08-30 13:38:53 -04:00
Eduardo Gurgel 60bcf1cadb Print process mailbox on failing assert_receive 2014-08-29 00:33:48 +12:00
Eduardo Gurgel d86ca5826c Format multiline AssertionError message with correct padding 2014-08-29 00:33:25 +12:00
207 changed files with 3776 additions and 2129 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
/.eunit
/.release
/docs
/doc
/ebin
/lib/*/ebin/*
/lib/*/tmp
+1
View File
@@ -8,3 +8,4 @@ notifications:
otp_release:
- 17.0
- 17.1
- 17.3
+111
View File
@@ -1,5 +1,116 @@
# Changelog
## v1.0.4
* Enhancements
* [Elixir] Support Erlang 17.5 and 18.0
* [Mix] Support "--search PATTERN" in "mix help"
* [Mix] Support `:start_permanent` that starts the application as permanent
* [Mix] Support `:build_embedded` that compile protocols, avoid symlinks and ensure protocols are loaded on boot
* [Mix] Make "mix app.start" public and support "--permanent" and "--temporary" options
* [URI] Speed decoding operations
* Bug fixes
* [Elixir] Produce clearer error messages for syntax errors before a sigil
* [Elixir] Return value when matching on `_` in the last line of a block
* [Elixir] Ensure changes in child deps forces the parent to recompile
* [Kernel] Fix a bug where `=~/2` returned false when matching on an empty string. The current behaviour was changed to match the behaviour of `String.contains?/2`
* [Mix] Store relative paths in erlang manifests
* [Mix] Fix httpc proxy option profile for downloading hex and other safe packges
* [Mix] Fix missing behaviour warning when compiling Erlang modules
## v1.0.3 (2015-02-12)
* Enhancements
* [Elixir] Do not show exported vars warning
* [Elixir] Improve error messages when using invalid expressions inside maps
* [Elixir] Skip aliases and import warnings when code cannot compile
* [IEx] Improve autocompletion for Erlang/Elixir based on loaded applications
* [IO] Make `IO.ANSI.Docs` readable on white background
* [Mix] Provide clearer error message when updating archives on Windows
* [Mix] Don't go through hex.pm API server to install Hex
* Bug fixes
* [Elixir] Ensure all Elixir applications work with code upgrades
* [Elixir] Don't leave spurious processes and stale data when the parallel compiler fails
* [Inspect] Keep original stacktrace on Inspect errors
* [IO] Fix `IO.ANSI.Docs` handling of `*`, `**`, `_` where they would be activated in the middle of words
* [Logger] Fix logging with `metadata[:function]`
* [Mix] Don't show hex update message on mix `local.hex`
* [Mix] Ensure tasks are properly underscored and camelized
* [Mix] Ensure Mix does not use ANSI escapes on Windows unless ANSI is enabled
* [Mix] Only shutdown Logger if the Mix application is being actually started
* [Mix] Ensure priv/include are copied on Windows even if source did not change
* [Mix] Ensure rebar dependencies work when user path has a space on Windows
* [Regex] Fix `Regex.replace/4` failures when the number of substitutions in pattern is more than the number of matches and support `\g{...}` patterns
* [String] Fix LFCR being treated as a grapheme instead of CRLF
* [URI] Do more strict matching on URI schema
## v1.0.2 (2014-10-21)
* Bug fixes
* [EEx] Remove invalid warning when attempting to inline code in EEx
* [EEx] Unify syntax errors to always include `file:line:`
* [IEx] Set null prompt function in dumb term mode
* [Kernel] Do not define an ets table with the same name as the module as it clashes with user code
* [Kernel] Fail compiling try expressions if they lack one of catch/after/rescue/else clauses
* [Kernel] Improve error messages when parsing fails on aliases
* [Kernel] Improve warnings coming from compile-time evaluation of arguments
* [String] Fix `String.replace/4` typespec
## v1.0.1 (2014-10-07)
* Enhancements
* [Mix] Add MIX_ENV output to archive.build (as on escript.build)
* Bug fixes
* [CLI] Fix Elixir CLI when running on Cygwin
* [ExUnit] Include stacktraces in ExUnit timeouts
* [Mix] Load dependencies before `deps.check` compiles dependencies
* [Mix] Ensure `Mix.Config` is deep merged
* [Mix] Change version requirement in new apps to `~> MAJOR.MINOR`
* [Mix] Report correct location if `local.hex` fails and give higher priority to powershell install on Windows
* [Mix] Ensure `--elixirc-paths` option for `compile.elixir` is a subset of the project `:elixirc_paths` configuration and it does not remove skipped entries from the manifest
* [Mix] Ensure `compile.elixir` writes to manifest when files are removed
* [Mix] Ensure `compile.elixir` purges and deletes modules before compiling
* [Mix] Do not crash on malformed proxy env var
* [Stream] Ensure `chunk/4` works correctly when halted
* [System] Do not allow relative paths in `System.cmd/3` as documented
* [System] Fix `:stderr_to_stdout` option in `System.cmd/3`
## v1.0.0 (2014-09-10)
* Enhancements
* [Logger] Add `Logger.enable/1` and `Logger.disable/1`
* Backwards incompatible changes
* [GenEvent] No longer support `{:swap_handler, ...}` as callback return value
* [List] `List.unzip/2` was removed
## v1.0.0-rc2 (2014-09-07)
* Enhancements
* [ExUnit] Print process mailbox on failing `assert_receive`
* [ExUnit] Tag all doctests with the `:doctest` tag
* [Kernel] Friendlier `unquote_splicing` error on inline quote
* [Kernel] Add `<~`, `~>`, `<<~`, `~>>`, `<~>`, `<|>` as operators
* [Mix] `mix deps.unlock` warns when given a missing dependency
* [Mix] Fetch hex using HTTPS
* Bug fixes
* [File] Ensure `File.cwd` generates lowercase drive letters on Windows
* [GenEvent] Ensure the event manager does not crash on `:sys.replace_state/3`
* [GenEvent] Do not consider swap handlers to be failures in a stream
* [Logger] Fix `Logger.log/2` not to raise on Logger exit
* [Logger] Recompute sync/async mode on Logger configuration
* [System] Ensure `System.cwd` generates lowercase drive letters on Windows
* Deprecations
* [GenEvent] The `:monitor` option in `add_handler/4` and `swap_handler/6` is deprecated in favor of `add_mon_handler/3` and `swap_mon_handler/5`
* Backwards incompatible changes
* [Supervisor] Make `max_restarts` default to 3 (closer to upcoming Erlang defaults)
## v1.0.0-rc1 (2014-08-30)
* Enhancements
+1 -1
View File
@@ -1,4 +1,4 @@
Copyright 2012-2013 Plataformatec.
Copyright 2012 Plataformatec
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
+22 -12
View File
@@ -1,4 +1,5 @@
REBAR := rebar
DOCS := v1.0
ELIXIRC := bin/elixirc --verbose --ignore-module-conflict
ERLC := erlc -I lib/elixir/include
ERL := erl -I lib/elixir/include -noshell -pa lib/elixir/ebin
@@ -125,16 +126,16 @@ clean_exbeam:
#==> Release tasks
SOURCE_REF = $(shell head="$$(git rev-parse HEAD)" tag="$$(git tag --points-at $$head | tail -1)" ; echo "$${tag:-$$head}\c")
DOCS = bin/elixir ../ex_doc/bin/ex_doc "$(1)" "$(VERSION)" "lib/$(2)/ebin" -m "$(3)" -u "https://github.com/elixir-lang/elixir" --source-ref "$(call SOURCE_REF)" -o docs/$(2) -p http://elixir-lang.org/docs.html
COMPILE_DOCS = bin/elixir ../ex_doc/bin/ex_doc "$(1)" "$(VERSION)" "lib/$(2)/ebin" -m "$(3)" -u "https://github.com/elixir-lang/elixir" --source-ref "$(call SOURCE_REF)" -o doc/$(2) -p http://elixir-lang.org/docs.html
docs: compile ../ex_doc/bin/ex_doc
$(Q) rm -rf docs
$(call DOCS,Elixir,elixir,Kernel)
$(call DOCS,EEx,eex,EEx)
$(call DOCS,Mix,mix,Mix)
$(call DOCS,IEx,iex,IEx)
$(call DOCS,ExUnit,ex_unit,ExUnit)
$(call DOCS,Logger,logger,Logger)
$(Q) rm -rf doc
$(call COMPILE_DOCS,Elixir,elixir,Kernel)
$(call COMPILE_DOCS,EEx,eex,EEx)
$(call COMPILE_DOCS,Mix,mix,Mix)
$(call COMPILE_DOCS,IEx,iex,IEx)
$(call COMPILE_DOCS,ExUnit,ex_unit,ExUnit)
$(call COMPILE_DOCS,Logger,logger,Logger)
../ex_doc/bin/ex_doc:
@ echo "ex_doc is not found in ../ex_doc as expected. See README for more information."
@@ -145,9 +146,18 @@ release_zip: compile
zip -9 -r v$(VERSION).zip bin CHANGELOG.md LEGAL lib/*/ebin LICENSE Makefile README.md VERSION
release_docs: docs
cd ../docs
rm -rf ../docs/master
mv docs ../docs/master
rm -rf ../docs/$(DOCS)/*/
mv doc/* ../docs/$(DOCS)
# This task requires aws-cli to be installed and set up for access to s3.hex.pm
# See: http://docs.aws.amazon.com/cli/latest/userguide/cli-chap-getting-set-up.html
publish_mix: compile
cd lib/mix && MIX_ENV=prod mix escript.build
aws s3 cp lib/mix/mix s3://s3.hex.pm/builds/mix/v$(VERSION)/mix --acl public-read
aws s3 cp lib/mix/mix s3://s3.hex.pm/builds/mix/mix --acl public-read
rm lib/mix/mix
rm -rf lib/mix/_build
#==> Tests tasks
@@ -183,7 +193,7 @@ test_stdlib: compile
#==> Dialyzer tasks
DIALYZER_OPTS = --no_check_plt --fullpath -Werror_handling -Wunmatched_returns -Wrace_conditions -Wunderspecs
DIALYZER_OPTS = --no_check_plt --fullpath -Werror_handling -Wunmatched_returns -Wunderspecs
PLT = .elixir.plt
$(PLT):
+23 -11
View File
@@ -1,28 +1,40 @@
# Release process
## All releases
This document simply outlines the release process:
1. Remove all `-dev` extension from versions (see below for all files)
1. Ensure you are running on the oldest supported Erlang version
2. Ensure CHANGELOG is updated and add current date
2. Remove all `-dev` extension from versions (see below for all files)
3. Commit changes above with title "Release vVERSION" and generate new tag
3. Ensure CHANGELOG is updated and add current date
4. Run `make clean test` to ensure all tests pass from scratch and the CI is green
4. Commit changes above with title "Release vVERSION" and generate new tag
5. Push master and the new tag
5. Run `make clean test` to ensure all tests pass from scratch and the CI is green
6. Release new docs with `make release_docs`, move docs to `docs/stable`
6. Ensure minimum supported Hex works with new release (instructions upcoming)
7. Release new zip with `make release_zip`, push new zip to GitHub Releases, name it `Precompiled.zip`
7. Push branch and the new tag
8. Fast-forward merge master into stable branch with `git merge master --ff` and push it
8. Release new docs with `make release_docs`, copy docs to `docs/stable` if appropriate, and push
9. After release, bump versions, add `-dev` back and commit
9. Release new zip with `make release_zip`, push `Precompiled.zip` to GitHub Releases
10. `make release_docs` once again and push it to `elixir-lang/docs`
10. Add the release to `elixir.csv` file in `elixir-lang/elixir-lang.github.com`
11. Add the release to `elixir.csv` file in `elixir-lang/elixir-lang.github.com`
11. Build and push standalone Mix with `make publish_mix` (requires AWS credentials)
## New vMAJOR.MINOR releases
12. Create a new branch "vMAJOR.MINOR"
13. Move docs generation to `docs/vMAJOR.MINOR` and copy them from `docs/stable`
14. In master, bump versions, start new CHANGELOG, add `-dev` back and commit "Start vVERSION+1"
15. `make release_docs` and push it to `elixir-lang/docs`
## Places where version is mentioned
+1 -1
View File
@@ -1 +1 @@
1.0.0-rc1
1.0.4
+1 -1
View File
@@ -81,7 +81,7 @@ done
SELF=$(readlink_f "$0")
SCRIPT_PATH=$(dirname "$SELF")
if [ "$OSTYPE" = "cygwin" ]; then SCRIPT_PATH=$(cygpath -m "SCRIPT_PATH"); fi
if [ "$OSTYPE" = "cygwin" ]; then SCRIPT_PATH=$(cygpath -m "$SCRIPT_PATH"); fi
if [ "$MODE" != "iex" ]; then ERL="-noshell -s elixir start_cli $ERL"; fi
# Check for terminal support
+5 -1
View File
@@ -1,5 +1,9 @@
defmodule EEx.SyntaxError do
defexception [:message]
defexception [:message, :file, :line]
def message(exception) do
"#{exception.file}:#{exception.line}: #{exception.message}"
end
end
defmodule EEx do
+13 -8
View File
@@ -12,10 +12,14 @@ defmodule EEx.Compiler do
def compile(source, opts) do
file = opts[:file] || "nofile"
line = opts[:line] || 1
tokens = EEx.Tokenizer.tokenize(source, line)
state = %{engine: opts[:engine] || @default_engine,
file: file, line: line, quoted: [], start_line: nil}
generate_buffer(tokens, "", [], state)
case EEx.Tokenizer.tokenize(source, line) do
{:ok, tokens} ->
state = %{engine: opts[:engine] || @default_engine,
file: file, line: line, quoted: [], start_line: nil}
generate_buffer(tokens, "", [], state)
{:error, line, message} ->
raise EEx.SyntaxError, line: line, file: file, message: message
end
end
# Generates the buffers by handling each expression from the tokenizer
@@ -51,16 +55,17 @@ defmodule EEx.Compiler do
{buffer, t}
end
defp generate_buffer([{:end_expr, line, _, chars}|_], _buffer, [], _state) do
raise EEx.SyntaxError, message: "unexpected token: #{inspect chars} at line #{inspect line}"
defp generate_buffer([{:end_expr, line, _, chars}|_], _buffer, [], state) do
raise EEx.SyntaxError, message: "unexpected token #{inspect chars}", file: state.file, line: line
end
defp generate_buffer([], buffer, [], state) do
state.engine.handle_body(buffer)
end
defp generate_buffer([], _buffer, _scope, _state) do
raise EEx.SyntaxError, message: "unexpected end of string. expecting a closing <% end %>."
defp generate_buffer([], _buffer, _scope, state) do
raise EEx.SyntaxError, message: "unexpected end of string, expected a closing '<% end %>'",
file: state.file, line: state.line
end
# Creates a placeholder and wrap it inside the expression block
+1 -4
View File
@@ -99,10 +99,7 @@ defmodule EEx.Engine do
def handle_expr(buffer, "=", expr) do
quote do
tmp = unquote(buffer)
tmp <> (case unquote(expr) do
bin when is_binary(bin) -> bin
oth -> String.Chars.to_string(oth)
end)
tmp <> String.Chars.to_string(unquote(expr))
end
end
+31 -20
View File
@@ -3,7 +3,8 @@ defmodule EEx.Tokenizer do
@doc """
Tokenizes the given char list or binary.
It returns 4 different types of tokens as result:
It returns {:ok, list} with the following tokens:
* `{:text, contents}`
* `{:expr, line, marker, contents}`
@@ -11,33 +12,43 @@ defmodule EEx.Tokenizer do
* `{:middle_expr, line, marker, contents}`
* `{:end_expr, line, marker, contents}`
Or `{:error, line, error}` in case of errors.
"""
def tokenize(bin, line) when is_binary(bin) do
tokenize(String.to_char_list(bin), line)
end
def tokenize(list, line) do
Enum.reverse(tokenize(list, line, [], []))
tokenize(list, line, [], [])
end
defp tokenize('<%%' ++ t, line, buffer, acc) do
{buffer, new_line, rest} = tokenize_expr t, line, [?%, ?<|buffer]
tokenize rest, new_line, [?>, ?%|buffer], acc
case expr(t, line, [?%, ?<|buffer]) do
{:error, _, _} = error -> error
{:ok, buffer, new_line, rest} ->
tokenize rest, new_line, [?>, ?%|buffer], acc
end
end
defp tokenize('<%#' ++ t, line, buffer, acc) do
{_, new_line, rest} = tokenize_expr t, line, []
tokenize rest, new_line, buffer, acc
case expr(t, line, []) do
{:error, _, _} = error -> error
{:ok, _, new_line, rest} ->
tokenize rest, new_line, buffer, acc
end
end
defp tokenize('<%' ++ t, line, buffer, acc) do
{marker, t} = retrieve_marker(t)
{expr, new_line, rest} = tokenize_expr t, line, []
token = token_name(expr)
acc = tokenize_text(buffer, acc)
final = {token, line, marker, Enum.reverse(expr)}
tokenize rest, new_line, [], [final | acc]
case expr(t, line, []) do
{:error, _, _} = error -> error
{:ok, expr, new_line, rest} ->
token = token_name(expr)
acc = tokenize_text(buffer, acc)
final = {token, line, marker, Enum.reverse(expr)}
tokenize rest, new_line, [], [final | acc]
end
end
defp tokenize('\n' ++ t, line, buffer, acc) do
@@ -49,7 +60,7 @@ defmodule EEx.Tokenizer do
end
defp tokenize([], _line, buffer, acc) do
tokenize_text(buffer, acc)
{:ok, Enum.reverse(tokenize_text(buffer, acc))}
end
# Retrieve marker for <%
@@ -64,20 +75,20 @@ defmodule EEx.Tokenizer do
# Tokenize an expression until we find %>
defp tokenize_expr([?%, ?>|t], line, buffer) do
{buffer, line, t}
defp expr([?%, ?>|t], line, buffer) do
{:ok, buffer, line, t}
end
defp tokenize_expr('\n' ++ t, line, buffer) do
tokenize_expr t, line + 1, [?\n|buffer]
defp expr('\n' ++ t, line, buffer) do
expr t, line + 1, [?\n|buffer]
end
defp tokenize_expr([h|t], line, buffer) do
tokenize_expr t, line, [h|buffer]
defp expr([h|t], line, buffer) do
expr t, line, [h|buffer]
end
defp tokenize_expr([], _line, _buffer) do
raise EEx.SyntaxError, message: "missing token: %>"
defp expr([], line, _buffer) do
{:error, line, "missing token '%>'"}
end
# Receive an expression content and check
+25 -24
View File
@@ -5,23 +5,26 @@ defmodule EEx.TokenizerTest do
require EEx.Tokenizer, as: T
test "simple chars lists" do
assert T.tokenize('foo', 1) == [ {:text, 'foo'} ]
assert T.tokenize('foo', 1) == {:ok, [{:text, 'foo'}]}
end
test "simple strings" do
assert T.tokenize("foo", 1) == [ {:text, 'foo'} ]
assert T.tokenize("foo", 1) == {:ok, [{:text, 'foo'}]}
end
test "strings with embedded code" do
assert T.tokenize('foo <% bar %>', 1) == [ {:text, 'foo '}, {:expr, 1, "", ' bar '} ]
assert T.tokenize('foo <% bar %>', 1) ==
{:ok, [{:text, 'foo '}, {:expr, 1, "", ' bar '}]}
end
test "strings with embedded equals code" do
assert T.tokenize('foo <%= bar %>', 1) == [ {:text, 'foo '}, {:expr, 1, "=", ' bar '} ]
assert T.tokenize('foo <%= bar %>', 1) ==
{:ok, [{:text, 'foo '}, {:expr, 1, "=", ' bar '}]}
end
test "strings with more than one line" do
assert T.tokenize('foo\n<%= bar %>', 1) == [ {:text, 'foo\n'}, {:expr, 2, "=", ' bar '} ]
assert T.tokenize('foo\n<%= bar %>', 1) ==
{:ok, [{:text, 'foo\n'}, {:expr, 2, "=", ' bar '}]}
end
test "strings with more than one line and expression with more than one line" do
@@ -32,50 +35,50 @@ baz %>
<% foo %>
'''
assert T.tokenize(string, 1) == [
assert T.tokenize(string, 1) == {:ok, [
{:text, 'foo '},
{:expr, 1, "=", ' bar\n\nbaz '},
{:text, '\n'},
{:expr, 4, "", ' foo '},
{:text, '\n'}
]
]}
end
test "quotation" do
assert T.tokenize('foo <%% true %>', 1) == [
assert T.tokenize('foo <%% true %>', 1) == {:ok, [
{:text, 'foo <% true %>'}
]
]}
end
test "quotation with do/end" do
assert T.tokenize('foo <%% true do %>bar<%% end %>', 1) == [
assert T.tokenize('foo <%% true do %>bar<%% end %>', 1) == {:ok, [
{:text, 'foo <% true do %>bar<% end %>'}
]
]}
end
test "comments" do
assert T.tokenize('foo <%# true %>', 1) == [
assert T.tokenize('foo <%# true %>', 1) == {:ok, [
{:text, 'foo '}
]
]}
end
test "comments with do/end" do
assert T.tokenize('foo <%# true do %>bar<%# end %>', 1) == [
assert T.tokenize('foo <%# true do %>bar<%# end %>', 1) == {:ok, [
{:text, 'foo bar'}
]
]}
end
test "strings with embedded do end" do
assert T.tokenize('foo <% if true do %>bar<% end %>', 1) == [
assert T.tokenize('foo <% if true do %>bar<% end %>', 1) == {:ok, [
{:text, 'foo '},
{:start_expr, 1, "", ' if true do '},
{:text, 'bar'},
{:end_expr, 1, "", ' end '}
]
]}
end
test "strings with embedded -> end" do
assert T.tokenize('foo <% cond do %><% false -> %>bar<% true -> %>baz<% end %>', 1) == [
assert T.tokenize('foo <% cond do %><% false -> %>bar<% true -> %>baz<% end %>', 1) == {:ok, [
{:text, 'foo '},
{:start_expr, 1, "", ' cond do '},
{:middle_expr, 1, "", ' false -> '},
@@ -83,23 +86,21 @@ baz %>
{:middle_expr, 1, "", ' true -> '},
{:text, 'baz'},
{:end_expr, 1, "", ' end '}
]
]}
end
test "strings with embedded keywords blocks" do
assert T.tokenize('foo <% if true do %>bar<% else %>baz<% end %>', 1) == [
assert T.tokenize('foo <% if true do %>bar<% else %>baz<% end %>', 1) == {:ok, [
{:text, 'foo '},
{:start_expr, 1, "", ' if true do '},
{:text, 'bar'},
{:middle_expr, 1, "", ' else '},
{:text, 'baz'},
{:end_expr, 1, "", ' end '}
]
]}
end
test "raise syntax error when there is start mark and no end mark" do
assert_raise EEx.SyntaxError, "missing token: %>", fn ->
T.tokenize('foo <% :bar', 1)
end
assert T.tokenize('foo <% :bar', 1) == {:error, 1, "missing token '%>'"}
end
end
+6 -6
View File
@@ -111,25 +111,25 @@ defmodule EExTest do
end
test "raises a syntax error when the token is invalid" do
assert_raise EEx.SyntaxError, "missing token: %>", fn ->
assert_raise EEx.SyntaxError, "nofile:1: missing token '%>'", fn ->
EEx.compile_string "foo <%= bar"
end
end
test "raises a syntax error when end expression is found without a start expression" do
assert_raise EEx.SyntaxError, "unexpected token: ' end ' at line 1", fn ->
assert_raise EEx.SyntaxError, "nofile:1: unexpected token ' end '", fn ->
EEx.compile_string "foo <% end %>"
end
end
test "raises a syntax error when start expression is found without an end expression" do
assert_raise EEx.SyntaxError, "unexpected end of string. expecting a closing <% end %>.", fn ->
EEx.compile_string "foo <% if true do %>"
assert_raise EEx.SyntaxError, "nofile:2: unexpected end of string, expected a closing '<% end %>'", fn ->
EEx.compile_string "foo\n<% if true do %>"
end
end
test "raises a syntax error when nested end expression is found without an start expression" do
assert_raise EEx.SyntaxError, "unexpected token: ' end ' at line 1", fn ->
test "raises a syntax error when nested end expression is found without a start expression" do
assert_raise EEx.SyntaxError, "nofile:1: unexpected token ' end '", fn ->
EEx.compile_string "foo <% if true do %><% end %><% end %>"
end
end
+1 -1
View File
@@ -201,7 +201,7 @@ defmodule Application do
`:applications` in the `.app` file in case they were not previously
started.
"""
@spec ensure_all_started(app, start_type) :: {:ok, [app]} | {:error, term}
@spec ensure_all_started(app, start_type) :: {:ok, [app]} | {:error, {app, term}}
def ensure_all_started(app, type \\ :temporary) when is_atom(app) do
:application.ensure_all_started(app, type)
end
+5 -13
View File
@@ -51,16 +51,7 @@ defmodule Code do
:code.del_path(to_char_list(Path.expand path))
end
@doc """
Re-add paths given to the command line to keep their position
on the overall code path.
Some tools may change the code path by prepending new items but
still want the paths given by the user to have higher priority.
Calling this function guarantees the paths are re-added on
top of the user given ones.
"""
@spec readd_paths() :: :ok
@doc false
def readd_paths() do
{pa, pz} = :elixir_code_server.call(:paths)
:code.add_pathsa(pa)
@@ -260,7 +251,7 @@ defmodule Code do
Accepts `relative_to` as an argument to tell where the file is located.
While `load_file` loads a file and returns the loaded modules and their
byte code, `eval_file` simply evalutes the file contents and returns the
byte code, `eval_file` simply evaluates the file contents and returns the
evaluation result and its bindings.
"""
def eval_file(file, relative_to \\ nil) do
@@ -325,7 +316,7 @@ defmodule Code do
Check `compiler_options/1` for more information.
"""
def compiler_options do
:elixir_code_server.call :compiler_options
:elixir_config.get :compiler_options
end
@doc """
@@ -364,7 +355,8 @@ defmodule Code do
bad = bad |> Keyword.keys |> Enum.join(", ")
raise ArgumentError, message: "unknown compiler options: #{bad}"
end
:elixir_code_server.cast {:compiler_options, opts}
update = &:orddict.merge(fn(_, _, value) -> value end, &1, opts)
:elixir_config.update :compiler_options, update
end
@doc """
+3 -23
View File
@@ -21,21 +21,13 @@ defprotocol Collectable do
shape where just the range limits are stored.
The `Collectable` module was designed to fill the gap left by the
`Enumerable` protocol. It provides two functions: `into/1` and `empty/1`.
`into/1` can be seen as the opposite of `Enumerable.reduce/3`. If
`Enumerable` is about taking values out, `Collectable.into/1` is about
collecting those values into a structure.
`Enumerable` protocol. `into/1` can be seen as the opposite of
`Enumerable.reduce/3`. If `Enumerable` is about taking values out,
`Collectable.into/1` is about collecting those values into a structure.
"""
@type command :: {:cont, term} | :done | :halt
@doc """
Receives a collectable structure and returns an empty one.
"""
@spec empty(t) :: t
def empty(collectable)
@doc """
Returns a function that collects values alongside
the initial accumulation value.
@@ -55,10 +47,6 @@ defprotocol Collectable do
end
defimpl Collectable, for: List do
def empty(_list) do
[]
end
def into(original) do
{[], fn
list, {:cont, x} -> [x|list]
@@ -69,10 +57,6 @@ defimpl Collectable, for: List do
end
defimpl Collectable, for: BitString do
def empty(_bitstring) do
""
end
def into(original) do
{original, fn
acc, {:cont, x} when is_bitstring(x) -> [acc|x]
@@ -83,10 +67,6 @@ defimpl Collectable, for: BitString do
end
defimpl Collectable, for: Map do
def empty(_map) do
%{}
end
def into(original) do
{original, fn
map, {:cont, {k, v}} -> :maps.put(k, v, map)
+16 -16
View File
@@ -593,11 +593,11 @@ defmodule Enum do
"""
@spec fetch(t, integer) :: {:ok, element} | :error
def fetch(collection, n) when is_list(collection) and n >= 0 do
def fetch(collection, n) when is_list(collection) and is_integer(n) and n >= 0 do
do_fetch(collection, n)
end
def fetch(collection, n) when n >= 0 do
def fetch(collection, n) when is_integer(n) and n >= 0 do
res =
Enumerable.reduce(collection, {:cont, 0}, fn(entry, acc) ->
if acc == n do
@@ -613,7 +613,7 @@ defmodule Enum do
end
end
def fetch(collection, n) when n < 0 do
def fetch(collection, n) when is_integer(n) and n < 0 do
do_fetch(reverse(collection), abs(n + 1))
end
@@ -1261,6 +1261,15 @@ defmodule Enum do
:lists.foldl(fun, acc, collection)
end
def reduce(%{__struct__: _} = collection, acc, fun) do
Enumerable.reduce(collection, {:cont, acc},
fn x, acc -> {:cont, fun.(x, acc)} end) |> elem(1)
end
def reduce(%{} = collection, acc, fun) do
:maps.fold(fn k, v, acc -> fun.({k, v}, acc) end, acc, collection)
end
def reduce(collection, acc, fun) do
Enumerable.reduce(collection, {:cont, acc},
fn x, acc -> {:cont, fun.(x, acc)} end) |> elem(1)
@@ -1729,10 +1738,10 @@ defmodule Enum do
def take(collection, count) when count > 0 do
{_, {res, _}} =
Enumerable.reduce(collection, {:cont, {[], count}}, fn(entry, {list, count}) ->
if count > 1 do
{:cont, {[entry|list], count - 1}}
else
{:halt, {[entry|list], count}}
case count do
0 -> {:halt, {list, count}}
1 -> {:halt, {[entry|list], count - 1}}
_ -> {:cont, {[entry|list], count - 1}}
end
end)
:lists.reverse(res)
@@ -1798,15 +1807,6 @@ defmodule Enum do
reverse(collection) |> :lists.reverse
end
@doc false
def traverse(collection, transform) when is_list(collection) do
:lists.map(transform, collection)
end
def traverse(collection, transform) do
into(collection, apply(Collectable, :empty, [collection]), transform)
end
@doc """
Enumerates the collection, removing all duplicated items.
+31 -15
View File
@@ -75,6 +75,12 @@ defmodule File do
@type posix :: :file.posix()
@type io_device :: :file.io_device()
@type stat_options :: [time: :local | :universal | :posix]
@type mode :: :append | :binary | :compressed | :delayed_write | :exclusive |
:raw | :read | :read_ahead | :sync | :write |
{:encoding , :latin1 | :unicode | :utf16 | :utf32 | :utf8 |
{:utf16, :big | :little} | {:utf32, :big | :little}} |
{:read_ahead, pos_integer} |
{:delayed_write, non_neg_integer, non_neg_integer}
@doc """
Returns `true` if the path is a regular file.
@@ -612,7 +618,7 @@ defmodule File do
Check `File.open/2` for other available options.
"""
@spec write(Path.t, iodata, list) :: :ok | {:error, posix}
@spec write(Path.t, iodata, [mode]) :: :ok | {:error, posix}
def write(path, content, modes \\ []) do
F.write_file(IO.chardata_to_string(path), content, modes)
end
@@ -620,7 +626,7 @@ defmodule File do
@doc """
Same as `write/3` but raises an exception if it fails, returns `:ok` otherwise.
"""
@spec write!(Path.t, iodata, list) :: :ok | no_return
@spec write!(Path.t, iodata, [mode]) :: :ok | no_return
def write!(path, content, modes \\ []) do
path = IO.chardata_to_string(path)
case F.write_file(path, content, modes) do
@@ -902,7 +908,8 @@ defmodule File do
File.close(file)
"""
@spec open(Path.t, list) :: {:ok, io_device} | {:error, posix}
@spec open(Path.t, [mode | :ram]) :: {:ok, io_device} | {:error, posix}
@spec open(Path.t, (io_device -> res)) :: {:ok, res} | {:error, posix} when res: var
def open(path, modes \\ [])
def open(path, modes) when is_list(modes) do
@@ -935,7 +942,7 @@ defmodule File do
end)
"""
@spec open(Path.t, list, (io_device -> res)) :: {:ok, res} | {:error, posix} when res: var
@spec open(Path.t, [mode | :ram], (io_device -> res)) :: {:ok, res} | {:error, posix} when res: var
def open(path, modes, function) do
case open(path, modes) do
{:ok, device} ->
@@ -953,7 +960,7 @@ defmodule File do
Returns the `io_device` otherwise.
"""
@spec open!(Path.t, list) :: io_device | no_return
@spec open!(Path.t, [mode]) :: io_device | no_return
def open!(path, modes \\ []) do
path = IO.chardata_to_string(path)
case open(path, modes) do
@@ -968,7 +975,7 @@ defmodule File do
Returns the function result otherwise.
"""
@spec open!(Path.t, list, (io_device -> res)) :: res | no_return when res: var
@spec open!(Path.t, [mode | :ram], (io_device -> res)) :: res | no_return when res: var
def open!(path, modes, function) do
path = IO.chardata_to_string(path)
case open(path, modes, function) do
@@ -989,18 +996,27 @@ defmodule File do
@spec cwd() :: {:ok, binary} | {:error, posix}
def cwd() do
case F.get_cwd do
{:ok, base} -> {:ok, IO.chardata_to_string(base)}
{:ok, base} -> {:ok, IO.chardata_to_string(fix_drive_letter(base))}
{:error, _} = error -> error
end
end
defp fix_drive_letter([l, ?:, ?/ | rest] = original) when l in ?A..?Z do
case :os.type() do
{:win32, _} -> [l+?a-?A, ?:, ?/ | rest]
_ -> original
end
end
defp fix_drive_letter(original), do: original
@doc """
The same as `cwd/0`, but raises an exception if it fails.
"""
@spec cwd!() :: binary | no_return
def cwd!() do
case F.get_cwd do
{:ok, cwd} -> IO.chardata_to_string(cwd)
case cwd() do
{:ok, cwd} -> cwd
{:error, reason} ->
raise File.Error, reason: reason, action: "get current working directory"
end
@@ -1124,7 +1140,7 @@ defmodule File do
Returns `:ok` on success, or `{:error, reason}`
on failure.
"""
@spec chmod(Path.t, integer) :: :ok | {:error, posix}
@spec chmod(Path.t, non_neg_integer) :: :ok | {:error, posix}
def chmod(path, mode) do
F.change_mode(IO.chardata_to_string(path), mode)
end
@@ -1132,7 +1148,7 @@ defmodule File do
@doc """
Same as `chmod/2`, but raises an exception in case of failure. Otherwise `:ok`.
"""
@spec chmod!(Path.t, integer) :: :ok | no_return
@spec chmod!(Path.t, non_neg_integer) :: :ok | no_return
def chmod!(path, mode) do
path = IO.chardata_to_string(path)
case chmod(path, mode) do
@@ -1147,7 +1163,7 @@ defmodule File do
for a given `file`. Returns `:ok` on success, or
`{:error, reason}` on failure.
"""
@spec chgrp(Path.t, integer) :: :ok | {:error, posix}
@spec chgrp(Path.t, non_neg_integer) :: :ok | {:error, posix}
def chgrp(path, gid) do
F.change_group(IO.chardata_to_string(path), gid)
end
@@ -1155,7 +1171,7 @@ defmodule File do
@doc """
Same as `chgrp/2`, but raises an exception in case of failure. Otherwise `:ok`.
"""
@spec chgrp!(Path.t, integer) :: :ok | no_return
@spec chgrp!(Path.t, non_neg_integer) :: :ok | no_return
def chgrp!(path, gid) do
path = IO.chardata_to_string(path)
case chgrp(path, gid) do
@@ -1170,7 +1186,7 @@ defmodule File do
for a given `file`. Returns `:ok` on success,
or `{:error, reason}` on failure.
"""
@spec chown(Path.t, integer) :: :ok | {:error, posix}
@spec chown(Path.t, non_neg_integer) :: :ok | {:error, posix}
def chown(path, uid) do
F.change_owner(IO.chardata_to_string(path), uid)
end
@@ -1178,7 +1194,7 @@ defmodule File do
@doc """
Same as `chown/2`, but raises an exception in case of failure. Otherwise `:ok`.
"""
@spec chown!(Path.t, integer) :: :ok | no_return
@spec chown!(Path.t, non_neg_integer) :: :ok | no_return
def chown!(path, uid) do
path = IO.chardata_to_string(path)
case chown(path, uid) do
+90 -122
View File
@@ -57,7 +57,7 @@ defmodule GenEvent do
Notifications can be sent to the event manager which will then
invoke `handle_event/2` for each registered handler.
We can add new handlers with `add_handler/4`. Calls can also
We can add new handlers with `add_handler/3`. Calls can also
be made to specific handlers by using `call/3`.
## Callbacks
@@ -82,7 +82,6 @@ defmodule GenEvent do
- `{:ok, new_state}`
- `{:ok, new_state, :hibernate}`
- `{:swap_handler, args1, new_state, handler2, args2}`
- `:remove_handler`
* `handle_call(msg, state)` - invoked when a `call/3` is done to a specific
@@ -92,7 +91,6 @@ defmodule GenEvent do
- `{:ok, reply, new_state}`
- `{:ok, reply, new_state, :hibernate}`
- `{:swap_handler, reply, args1, new_state, handler2, args2}`
- `{:remove_handler, reply}`
* `handle_info(msg, state)` - invoked to handle all other messages which
@@ -166,7 +164,7 @@ defmodule GenEvent do
Keep in mind though Elixir and Erlang gen events are not 100% compatible.
The `:gen_event.add_sup_handler/3` is not supported by Elixir's GenEvent,
which in turn supports `monitor: true` in `GenEvent.add_handler/4`.
which in turn supports `GenEvent.add_mon_handler/3`.
The benefits of the monitoring approach are described in the "Don't drink
too much kool aid" section of the "Learn you some Erlang" link above. Due
@@ -190,7 +188,7 @@ defmodule GenEvent do
@type manager :: pid | name | {atom, node}
@typedoc "Supported values for new handlers"
@type handler :: module | {module, term}
@type handler :: atom | {atom, term} | {pid, reference}
@doc false
defmacro __using__(_) do
@@ -315,12 +313,22 @@ defmodule GenEvent do
the event handler is ignored and this function returns `{:error, reason}`.
If the given handler was previously installed at the manager, this
function returns `{:error, :already_added}`.
function returns `{:error, :already_present}`.
"""
@spec add_handler(manager, handler, term) :: :ok | {:error, term}
def add_handler(manager, handler, args) do
rpc(manager, {:add_handler, handler, args})
end
@doc """
Adds a monitored event handler to the event `manager`.
Expects the same input and returns the same values as `add_handler/3`.
## Monitored handlers
When adding a handler, a `:monitor` option with value `true` can be given.
This means the calling process will now be monitored by the GenEvent handler.
A monitored handler implies the calling process will now be monitored
by the GenEvent manager.
If the calling process later terminates with `reason`, the event manager
will delete the event handler by calling the `terminate/2` callback with
@@ -352,16 +360,9 @@ defmodule GenEvent do
Finally, this functionality only works with GenEvent started via this
module (it is not backwards compatible with Erlang's `:gen_event`).
"""
@spec add_handler(manager, handler, term, [monitor: boolean]) :: :ok | {:error, term}
def add_handler(manager, handler, args, options \\ []) do
cond do
Keyword.get(options, :link, false) ->
raise ArgumentError, message: "GenEvent.add_handler/4 with link is deprecated and no longer works"
Keyword.get(options, :monitor, false) ->
rpc(manager, {:add_mon_handler, handler, args, self()})
true ->
rpc(manager, {:add_handler, handler, args})
end
@spec add_mon_handler(manager, handler, term) :: :ok | {:error, term}
def add_mon_handler(manager, handler, args) do
rpc(manager, {:add_mon_handler, handler, args, self()})
end
@doc """
@@ -437,7 +438,7 @@ defmodule GenEvent do
The return value `reply` is defined in the return value of `handle_call/2`.
If the specified event handler is not installed, the function returns
`{:error, :module_not_found}`.
`{:error, :not_found}`.
"""
@spec call(manager, handler, term, timeout) :: term | {:error, term}
def call(manager, handler, request, timeout \\ 5000) do
@@ -456,7 +457,7 @@ defmodule GenEvent do
The event manager will call `terminate/2` to terminate the event handler
and return the callback value. If the specified event handler is not
installed, the function returns `{:error, :module_not_found}`.
installed, the function returns `{:error, :not_found}`.
"""
@spec remove_handler(manager, handler, term) :: term | {:error, term}
def remove_handler(manager, handler, args) do
@@ -476,23 +477,22 @@ defmodule GenEvent do
is not installed or if the handler fails to terminate with a given reason
in which case `state = {:error, term}`.
A `:monitor` option can also be set to specify if the new handler
should be monitored by the manager. See `add_handler/4` for more
information.
If `init/1` in the second handler returns a correct value, this
function returns `:ok`.
"""
@spec swap_handler(manager, handler, term, handler, term, [monitor: boolean]) :: :ok | {:error, term}
def swap_handler(manager, handler1, args1, handler2, args2, options \\ []) do
cond do
Keyword.get(options, :link, false) ->
raise ArgumentError, message: "GenEvent.swap_handler/6 with link is deprecated and no longer works"
Keyword.get(options, :monitor, false) ->
rpc(manager, {:swap_mon_handler, handler1, args1, handler2, args2, self()})
true ->
rpc(manager, {:swap_handler, handler1, args1, handler2, args2})
end
@spec swap_handler(manager, handler, term, handler, term) :: :ok | {:error, term}
def swap_handler(manager, handler1, args1, handler2, args2) do
rpc(manager, {:swap_handler, handler1, args1, handler2, args2})
end
@doc """
Replaces an old event handler with a new monitored one in the event `manager`.
Read the docs for `add_mon_handler/3` and `swap_handler/5` for more information.
"""
@spec swap_mon_handler(manager, handler, term, handler, term) :: :ok | {:error, term}
def swap_mon_handler(manager, handler1, args1, handler2, args2) do
rpc(manager, {:swap_mon_handler, handler1, args1, handler2, args2, self()})
end
@doc """
@@ -531,6 +531,7 @@ defmodule GenEvent do
end
def init_it(starter, parent, name, _, _, options) do
Process.put(:"$initial_call", {__MODULE__, :init_it, 6})
debug = :gen.debug_options(options)
:proc_lib.init_ack(starter, {:ok, self()})
loop(parent, name(name), [], debug, false)
@@ -603,12 +604,12 @@ defmodule GenEvent do
{hib, reply, handlers} = server_add_handler(handler, args, handlers)
reply(tag, reply)
loop(parent, name, handlers, debug, hib)
{_from, tag, {:add_mon_handler, handler, args, mon}} ->
{hib, reply, handlers} = server_add_mon_handler(handler, args, handlers, mon)
{_from, tag, {:add_mon_handler, handler, args, notify}} ->
{hib, reply, handlers} = server_add_mon_handler(handler, args, handlers, notify)
reply(tag, reply)
loop(parent, name, handlers, debug, hib)
{_from, tag, {:add_process_handler, pid, notify}} ->
{hib, reply, handlers} = server_add_process_handler(pid, notify, handlers)
{hib, reply, handlers} = server_add_process_handler(pid, handlers, notify)
reply(tag, reply)
loop(parent, name, handlers, debug, hib)
{_from, tag, {:delete_handler, handler, args}} ->
@@ -679,7 +680,7 @@ defmodule GenEvent do
@doc false
def system_replace_state(fun, [name, handlers, hib]) do
{handlers, states} =
List.unzip(for handler <- handlers do
:lists.unzip(for handler <- handlers do
handler(module: mod, id: id, state: state) = handler
cur = {mod, id, state}
try do
@@ -713,8 +714,8 @@ defmodule GenEvent do
end
[header: header,
data: [{"Status", sys_state}, {"Parent", parent}],
items: {"Installed handlers", formatted}]
data: [{'Status', sys_state}, {'Parent', parent}],
items: {'Installed handlers', formatted}]
end
## Loop helpers
@@ -736,57 +737,31 @@ defmodule GenEvent do
defp server_add_handler({module, id}, args, handlers) do
handler = handler(module: module, id: {module, id})
server_add_handler(module, handler, args, handlers)
do_add_handler(module, handler, args, handlers, :ok)
end
defp server_add_handler(module, args, handlers) do
handler = handler(module: module, id: module)
server_add_handler(module, handler, args, handlers)
do_add_handler(module, handler, args, handlers, :ok)
end
defp server_add_handler(module, handler, arg, handlers) do
case :lists.keyfind(handler(handler, :id), handler(:id) + 1, handlers) do
false ->
case do_handler(module, :init, [arg]) do
{:ok, res} ->
case res do
{:ok, state} ->
{false, :ok, [handler(handler, state: state)|handlers]}
{:ok, state, :hibernate} ->
{true, :ok, [handler(handler, state: state)|handlers]}
{:error, _} = error ->
{false, error, handlers}
other ->
{false, {:error, {:bad_return_value, other}}, handlers}
end
{:error, _} = error ->
{false, error, handlers}
end
_ ->
{false, {:error, :already_added}, handlers}
end
defp server_add_mon_handler({module, id}, args, handlers, notify) do
ref = Process.monitor(notify)
handler = handler(module: module, id: {module, id}, pid: notify, ref: ref)
do_add_handler(module, handler, args, handlers, :ok)
end
defp server_add_mon_handler({module, id}, args, handlers, pid) do
defp server_add_mon_handler(module, args, handlers, notify) do
ref = Process.monitor(notify)
handler = handler(module: module, id: module, pid: notify, ref: ref)
do_add_handler(module, handler, args, handlers, :ok)
end
defp server_add_process_handler(pid, handlers, notify) do
ref = Process.monitor(pid)
handler = handler(module: module, id: {module, id}, pid: pid, ref: ref)
server_add_handler(module, handler, args, handlers)
end
defp server_add_mon_handler(module, args, handlers, pid) do
ref = Process.monitor(pid)
handler = handler(module: module, id: module, pid: pid, ref: ref)
server_add_handler(module, handler, args, handlers)
end
defp server_add_process_handler(pid, notify, handlers) do
ref = Process.monitor(pid)
{:ok, state} = GenEvent.Stream.init({pid, ref})
# Notice the pid is set only when notifications
# are explicitly required.
handler = handler(module: GenEvent.Stream, id: {GenEvent.Stream, ref},
pid: if(notify, do: pid), ref: ref, state: state)
{false, ref, [handler|handlers]}
handler = handler(module: GenEvent.Stream, id: {self(), ref},
pid: notify, ref: ref)
do_add_handler(GenEvent.Stream, handler, {pid, ref}, handlers, {self(), ref})
end
defp server_remove_handler(module, args, handlers, name) do
@@ -817,7 +792,7 @@ defmodule GenEvent do
defp server_split_process_handlers(mode, event, [handler|t], handlers, streams) do
case handler(handler, :id) do
{GenEvent.Stream, _ref} ->
{pid, _ref} when is_pid(pid) ->
server_process_notify(mode, event, handler)
server_split_process_handlers(mode, event, t, handlers, [handler|streams])
_ ->
@@ -850,7 +825,7 @@ defmodule GenEvent do
{hib, acc}
end
defp server_update(handler, fun, event, name, handlers) do
defp server_update(handler, fun, event, name, _handlers) do
handler(module: module, state: state) = handler
case do_handler(module, fun, [event, state]) do
@@ -860,8 +835,6 @@ defmodule GenEvent do
{false, handler(handler, state: state)}
{:ok, state, :hibernate} ->
{true, handler(handler, state: state)}
{:swap_handler, args1, state, handler2, args2} ->
do_swap(handler(handler, state: state), args1, handler2, args2, name, handlers)
:remove_handler ->
do_terminate(handler, :remove_handler, event, name, :normal)
:error
@@ -881,17 +854,18 @@ defmodule GenEvent do
end
defp server_collect_process_handlers(mode, event, [handler|t], handlers, name) when mode in [:sync, :ack] do
handler(ref: ref) = handler
handler(ref: ref, id: id) = handler
receive do
{^ref, :ok} ->
server_collect_process_handlers(:sync, event, t, [handler|handlers], name)
{^ref, :done} ->
do_terminate(handler, :remove_handler, event, name, :normal)
server_collect_process_handlers(:sync, event, t, handlers, name)
server_collect_process_handlers(mode, event, t, [handler|handlers], name)
{_from, tag, {:delete_handler, ^id, args}} ->
do_terminate(handler, args, :remove, name, :normal)
reply(tag, :ok)
server_collect_process_handlers(mode, event, t, handlers, name)
{:DOWN, ^ref, _, _, reason} ->
do_terminate(handler, {:stop, reason}, :remove, name, :shutdown)
server_collect_process_handlers(:sync, event, t, handlers, name)
do_terminate(handler, {:stop, reason}, :DOWN, name, :shutdown)
server_collect_process_handlers(mode, event, t, handlers, name)
end
end
@@ -902,7 +876,7 @@ defmodule GenEvent do
defp server_call(module, query, handlers, name) do
case :lists.keyfind(module, handler(:id) + 1, handlers) do
false ->
{false, {:error, :module_not_found}, handlers}
{false, {:error, :not_found}, handlers}
handler ->
case server_call_update(handler, query, name, handlers) do
{{hib, handler}, reply} ->
@@ -913,7 +887,7 @@ defmodule GenEvent do
end
end
defp server_call_update(handler, query, name, handlers) do
defp server_call_update(handler, query, name, _handlers) do
handler(module: module, state: state) = handler
case do_handler(module, :handle_call, [query, state]) do
{:ok, res} ->
@@ -922,8 +896,6 @@ defmodule GenEvent do
{{false, handler(handler, state: state)}, reply}
{:ok, reply, state, :hibernate} ->
{{true, handler(handler, state: state)}, reply}
{:swap_handler, reply, args1, state, handler2, args2} ->
{do_swap(handler(handler, state: state), args1, handler2, args2, name, handlers), reply}
{:remove_handler, reply} ->
do_terminate(handler, :remove_handler, query, name, :normal)
{:error, reply}
@@ -964,35 +936,31 @@ defmodule GenEvent do
case :lists.keyfind(ref, handler(:ref) + 1, handlers) do
false -> :error
handler ->
do_terminate(handler, {:stop, reason}, :remove, name, :shutdown)
do_terminate(handler, {:stop, reason}, :DOWN, name, :shutdown)
{:ok, :lists.keydelete(ref, handler(:ref) + 1, handlers)}
end
end
defp do_swap(handler, args1, module2, args2, name, handlers) do
pid = handler(handler, :pid)
state = do_terminate(handler,
args1, :swapped, name, {:swapped, module2, pid})
{hib, res, handlers} =
if handler(handler, :id) == module2 or
:lists.keyfind(module2, handler(:id) + 1, handlers) == false do
if pid do
server_add_mon_handler(module2, {args2, state}, [], pid)
else
server_add_handler(module2, {args2, state}, [])
defp do_add_handler(module, handler, arg, handlers, succ) do
case :lists.keyfind(handler(handler, :id), handler(:id) + 1, handlers) do
false ->
case do_handler(module, :init, [arg]) do
{:ok, res} ->
case res do
{:ok, state} ->
{false, succ, [handler(handler, state: state)|handlers]}
{:ok, state, :hibernate} ->
{true, succ, [handler(handler, state: state)|handlers]}
{:error, _} = error ->
{false, error, handlers}
other ->
{false, {:error, {:bad_return_value, other}}, handlers}
end
{:error, _} = error ->
{false, error, handlers}
end
else
{false, {:error, :already_added}, []}
end
case res do
:ok ->
{hib, hd(handlers)}
{:error, reason} ->
report_terminate(handler, reason, state, :swapped, name)
:error
_ ->
{false, {:error, :already_present}, handlers}
end
end
@@ -1001,7 +969,7 @@ defmodule GenEvent do
{:value, handler, handlers} ->
{do_terminate(handler, args, last_in, name, reason), handlers}
false ->
{{:error, :module_not_found}, handlers}
{{:error, :not_found}, handlers}
end
end
+29 -42
View File
@@ -79,46 +79,20 @@ defimpl Enumerable, for: GenEvent.Stream do
end
defp start(%{manager: manager} = stream) do
{pid, ref, mon_ref} =
try do
pid = whereis(manager)
ref = Process.monitor(pid)
{:ok, msg_ref} = :gen.call(pid, self(), {:add_process_handler, self(), true}, :infinity)
{pid, msg_ref, ref}
catch
:exit, reason -> exit({reason, {__MODULE__, :start, [stream]}})
end
{pid, ref, mon_ref}
end
defp whereis(pid) when is_pid(pid), do: pid
defp whereis(atom) when is_atom(atom), do: Process.whereis(atom) || exit(:noproc)
defp whereis({:global, name}), do: :global.whereis_name(name)
defp whereis({:via, module, name}), do: module.whereis_name(name)
defp whereis({atom, node}) do
case :rpc.call(node, :erlang, :whereis, [atom]) do
:undefined -> exit(:noproc)
{:badrpc, :nodedown} -> exit({:nodedown, node})
pid when is_pid(pid) -> pid
try do
{:ok, {pid, ref}} = :gen.call(manager, self(),
{:add_process_handler, self(), self()}, :infinity)
mon_ref = Process.monitor(pid)
{pid, ref, mon_ref}
catch
:exit, reason -> exit({reason, {__MODULE__, :start, [stream]}})
end
end
defp next(%{timeout: timeout} = stream, {pid, ref, mon_ref} = acc) do
self = self()
receive do
# The handler was removed. Stop iteration, resolve the
# event later. We need to demonitor now, otherwise DOWN
# appears with higher priority in the shutdown process.
{:gen_event_EXIT, {GenEvent.Stream, ^ref}, _reason} = event ->
Process.demonitor(mon_ref, [:flush])
send(self(), event)
{:halt, {:removed, acc}}
# The manager died. Stop iteration, resolve the event later.
{:DOWN, ^mon_ref, _, _, _} = event ->
send(self(), event)
{:halt, {:removed, acc}}
# Got an async event.
{_from, {^pid, ^ref}, {:notify, event}} ->
{[{:async, pid, ref, event}], acc}
@@ -130,6 +104,19 @@ defimpl Enumerable, for: GenEvent.Stream do
# Got an ack event.
{_from, {^pid, ^ref}, {:ack_notify, event}} ->
{[{:ack, pid, ref, event}], acc}
# The handler was removed. Stop iteration, resolve the
# event later. We need to demonitor now, otherwise DOWN
# appears with higher priority in the shutdown process.
{:gen_event_EXIT, {^pid, ^ref}, _reason} = event ->
Process.demonitor(mon_ref, [:flush])
send(self, event)
{:halt, {:removed, acc}}
# The manager died. Stop iteration, resolve the event later.
{:DOWN, ^mon_ref, _, _, _} = event ->
send(self, event)
{:halt, {:removed, acc}}
after
timeout ->
exit({:timeout, {__MODULE__, :next, [stream, acc]}})
@@ -150,19 +137,19 @@ defimpl Enumerable, for: GenEvent.Stream do
# If we reach this branch, the handler was not removed yet,
# so we trigger a request for doing so.
defp stop(stream, {pid, ref, _} = acc) do
Task.start(fn -> GenEvent.remove_handler(pid, {GenEvent.Stream, ref}, :shutdown) end)
_ = GenEvent.remove_handler(pid, {pid, ref}, :shutdown)
stop(stream, {:removed, acc})
end
defp wait_for_handler_removal(pid, ref, mon_ref) do
receive do
{_from, {^pid, ^ref}, {notify, _event}} when notify in [:ack_notify, :sync_notify] ->
send pid, {ref, :done}
wait_for_handler_removal(pid, ref, mon_ref)
{:gen_event_EXIT, {GenEvent.Stream, ^ref}, reason} when reason in [:normal, :shutdown] ->
{:gen_event_EXIT, {^pid, ^ref}, reason}
when reason == :normal
when reason == :shutdown
when tuple_size(reason) == 3 and elem(reason, 0) == :swapped ->
Process.demonitor(mon_ref, [:flush])
:ok
{:gen_event_EXIT, {GenEvent.Stream, ^ref}, reason} ->
{:gen_event_EXIT, {^pid, ^ref}, reason} ->
Process.demonitor(mon_ref, [:flush])
{:error, reason}
{:DOWN, ^mon_ref, _, _, reason} ->
@@ -172,7 +159,7 @@ defimpl Enumerable, for: GenEvent.Stream do
defp flush_events(ref) do
receive do
{_from, {_pid, ^ref}, {:notify, _event}} ->
{_from, {_pid, ^ref}, {notify, _event}} when notify in [:notify, :ack_notify, :sync_notify] ->
flush_events(ref)
after
0 -> :ok
+7 -4
View File
@@ -185,6 +185,8 @@ defmodule Inspect.Algebra do
Inspect.inspect(map, opts)
rescue
e ->
stacktrace = System.stacktrace
# Because we try to raise a nice error message in case
# we can't inspect a struct, there is a chance the error
# message itself relies on the struct being printed, so
@@ -198,9 +200,10 @@ defmodule Inspect.Algebra do
Process.put(:inspect_trap, true)
res = Inspect.Map.inspect(map, opts)
formatted = IO.iodata_to_binary(format(res, :infinity))
raise ArgumentError,
reraise ArgumentError,
"Got #{inspect e.__struct__} with message " <>
"\"#{Exception.message(e)}\" while inspecting #{formatted}"
"\"#{Exception.message(e)}\" while inspecting #{formatted}",
stacktrace
after
Process.delete(:inspect_trap)
end
@@ -435,7 +438,7 @@ defmodule Inspect.Algebra do
iex> Inspect.Algebra.format(doc, 20) |> IO.iodata_to_binary
"[1! 2! 3! ...]"
"""
@spec surround_many(binary, [any], binary, integer | :infinity, (term -> t), binary) :: t
@spec surround_many(binary, [any], binary, Inspect.Opts.t, (term, Inspect.Opts.t -> t), binary) :: t
def surround_many(left, docs, right, opts, fun, separator \\ @surround_separator) do
do_surround_many(left, docs, right, opts.limit, opts, fun, separator)
end
@@ -482,7 +485,7 @@ defmodule Inspect.Algebra do
and returns an IO data representation of the best layout for the
document to fit in the given width.
"""
@spec format(t, non_neg_integer | :infinity) :: binary
@spec format(t, non_neg_integer | :infinity) :: iodata
def format(d, w) do
format(w, 0, [{0, default_mode(w), doc_group(d)}])
end
+2 -12
View File
@@ -5,11 +5,6 @@ defmodule Integer do
import Bitwise
@doc false
defmacro odd?(n) do
quote do: (unquote(n) &&& 1) == 1
end
@doc """
Determines if an integer is odd.
@@ -21,11 +16,6 @@ defmodule Integer do
quote do: (unquote(n) &&& 1) == 1
end
@doc false
defmacro even?(n) do
quote do: (unquote(n) &&& 1) == 0
end
@doc """
Determines if an integer is even.
@@ -111,7 +101,7 @@ defmodule Integer do
"64"
"""
@spec to_string(integer, pos_integer) :: String.t
@spec to_string(integer, 2..36) :: String.t
def to_string(some_integer, base) do
:erlang.integer_to_binary(some_integer, base)
end
@@ -144,7 +134,7 @@ defmodule Integer do
'3FF'
"""
@spec to_char_list(integer, pos_integer) :: char_list
@spec to_char_list(integer, 2..36) :: char_list
def to_char_list(number, base) do
:erlang.integer_to_list(number, base)
end
+1 -1
View File
@@ -180,7 +180,7 @@ defmodule IO do
Inspects and writes the given argument to the device.
It enables pretty printing by default with width of
80 characters. Th width can be changed by explicitly
80 characters. The width can be changed by explicitly
passing the `:width` option.
## Examples
+79 -36
View File
@@ -25,10 +25,10 @@ defmodule IO.ANSI.Docs do
[enabled: true,
doc_bold: [:bright],
doc_code: [:cyan, :bright],
doc_headings: [:yellow, :bright],
doc_headings: [:yellow],
doc_inline_code: [:cyan],
doc_table_heading: [:reverse],
doc_title: [:reverse, :yellow, :bright],
doc_title: [:reverse, :yellow],
doc_underline: [:underline],
width: 80]
end
@@ -194,7 +194,7 @@ defmodule IO.ANSI.Docs do
lines
|> Enum.join(" ")
|> handle_links
|> handle_inline(nil, [], [], options)
|> handle_inline(options)
|> String.split(~r{\s})
|> write_with_wrap(options[:width] - byte_size(indent), indent, no_wrap)
@@ -262,7 +262,7 @@ defmodule IO.ANSI.Docs do
col = col
|> String.replace(~r/\\ \|/x, "|")
|> handle_links
|> handle_inline(nil, [], [], options)
|> handle_inline(options)
{col, length_without_escape(col, 0)}
end
@@ -392,61 +392,104 @@ defmodule IO.ANSI.Docs do
Regex.replace(~r{\[(.*?)\]\((.*?)\)}, text, "\\1 (\\2)")
end
# Single inline quotes.
@single [?`, ?_, ?*]
# ` does not require space in between
@spaced [?_, ?*]
# We have four entries: **, *, _ and `.
#
# The first three behave the same while the last one is simpler
# when it comes to delimiters. But, since the first has two
# characters, we need to handle 3 cases:
#
# 1. **
# 2. _ and *
# 3. `
#
# Where the first two should have the same code but match differently.
@single [?_, ?*]
# Clauses for handling spaces
defp handle_inline(<<?*, ?*, ?\s, rest :: binary>>, nil, buffer, acc, options) do
handle_inline(rest, nil, [?\s, ?*, ?*|buffer], acc, options)
# Characters that can mark the beginning or the end of a word.
# Only support the most common ones at this moment.
@delimiters [?\s, ?', ?", ?!, ?@, ?#, ?$, ?%, ?^, ?&, ?-, ?+, ?(, ?), ?[, ?], ?{, ?}, ?<, ?>, ?.]
# Inline start
defp handle_inline(<<?*, ?*, rest :: binary>>, options) do
handle_inline(rest, ?d, ["**"], [], options)
end
defp handle_inline(<<mark, ?\s, rest :: binary>>, nil, buffer, acc, options) when mark in @spaced do
handle_inline(rest, nil, [?\s, mark|buffer], acc, options)
defp handle_inline(<<mark, rest :: binary>>, options) when mark in @single do
handle_inline(rest, mark, [<<mark>>], [], options)
end
defp handle_inline(<<?\s, ?*, ?*, rest :: binary>>, limit, buffer, acc, options) do
handle_inline(rest, limit, [?*, ?*, ?\s|buffer], acc, options)
defp handle_inline(rest, options) do
handle_inline(rest, nil, [], [], options)
end
defp handle_inline(<<?\s, mark, rest :: binary>>, limit, buffer, acc, options) when mark in @spaced do
handle_inline(rest, limit, [mark, ?\s|buffer], acc, options)
# Inline delimiters
defp handle_inline(<<delimiter, ?*, ?*, rest :: binary>>, nil, buffer, acc, options)
when rest != "" and delimiter in @delimiters do
handle_inline(rest, ?d, ["**"], [delimiter, Enum.reverse(buffer)|acc], options)
end
defp handle_inline(<<delimiter, mark, rest :: binary>>, nil, buffer, acc, options)
when rest != "" and delimiter in @delimiters and mark in @single do
handle_inline(rest, mark, [<<mark>>], [delimiter, Enum.reverse(buffer)|acc], options)
end
defp handle_inline(<<?`, rest :: binary>>, nil, buffer, acc, options)
when rest != "" do
handle_inline(rest, ?`, ["`"], [Enum.reverse(buffer)|acc], options)
end
# Clauses for handling escape
defp handle_inline(<<?\\, ?\\, ?*, ?*, rest :: binary>>, nil, buffer, acc, options)
when rest != "" do
handle_inline(rest, ?d, ["**"], [?\\, Enum.reverse(buffer)|acc], options)
end
defp handle_inline(<<?\\, ?\\, mark, rest :: binary>>, nil, buffer, acc, options)
when rest != "" and mark in @single do
handle_inline(rest, mark, [<<mark>>], [?\\, Enum.reverse(buffer)|acc], options)
end
defp handle_inline(<<?\\, ?\\, rest :: binary>>, limit, buffer, acc, options) do
handle_inline(rest, limit, [?\\|buffer], acc, options)
end
defp handle_inline(<<?\\, ?*, ?*, rest :: binary>>, limit, buffer, acc, options) do
handle_inline(rest, limit, [?*, ?*|buffer], acc, options)
end
# A escape is not valid inside `
# An escape is not valid inside `
defp handle_inline(<<?\\, mark, rest :: binary>>, limit, buffer, acc, options)
when mark in [?_, ?*, ?`] and not(mark == limit and mark == ?`) do
when not(mark == limit and mark == ?`) do
handle_inline(rest, limit, [mark|buffer], acc, options)
end
# Inline start
defp handle_inline(<<?*, ?*, rest :: binary>>, nil, buffer, acc, options) when rest != "" do
handle_inline(rest, ?d, ["**"], [Enum.reverse(buffer)|acc], options)
end
defp handle_inline(<<mark, rest :: binary>>, nil, buffer, acc, options) when rest != "" and mark in @single do
handle_inline(rest, mark, [<<mark>>], [Enum.reverse(buffer)|acc], options)
end
# Inline end
defp handle_inline(<<?*, ?*, rest :: binary>>, ?d, buffer, acc, options) do
defp handle_inline(<<?*, ?*, delimiter, rest :: binary>>, ?d, buffer, acc, options)
when delimiter in @delimiters do
handle_inline(<<delimiter, rest :: binary>>, nil, [], [inline_buffer(buffer, options)|acc], options)
end
defp handle_inline(<<mark, delimiter, rest :: binary>>, mark, buffer, acc, options)
when delimiter in @delimiters and mark in @single do
handle_inline(<<delimiter, rest :: binary>>, nil, [], [inline_buffer(buffer, options)|acc], options)
end
defp handle_inline(<<?*, ?*, rest:: binary>>, ?d, buffer, acc, options)
when rest == "" do
handle_inline(<<>>, nil, [], [inline_buffer(buffer, options)|acc], options)
end
defp handle_inline(<<mark, rest :: binary>>, mark, buffer, acc, options)
when rest == "" and mark in @single do
handle_inline(<<>>, nil, [], [inline_buffer(buffer, options)|acc], options)
end
defp handle_inline(<<?`, rest :: binary>>, ?`, buffer, acc, options) do
handle_inline(rest, nil, [], [inline_buffer(buffer, options)|acc], options)
end
defp handle_inline(<<mark, rest :: binary>>, mark, buffer, acc, options) when mark in @single do
handle_inline(rest, nil, [], [inline_buffer(buffer, options)|acc], options)
end
# Catch all
defp handle_inline(<<char, rest :: binary>>, mark, buffer, acc, options) do
handle_inline(rest, mark, [char|buffer], acc, options)
+15 -11
View File
@@ -1386,7 +1386,14 @@ defmodule Kernel do
iex> "abcd" =~ "ad"
false
iex> "abcd" =~ ""
true
"""
@spec (String.t =~ (String.t | Regex.t)) :: boolean
def left =~ "" when is_binary(left), do: true
def left =~ right when is_binary(left) and is_binary(right) do
:binary.match(left, right) != :nomatch
end
@@ -1643,7 +1650,7 @@ defmodule Kernel do
iex> users = [%{name: "john", age: 27}, %{name: "meg", age: 23}]
iex> all = fn :get_and_update, data, next ->
...> Enum.map(data, next) |> List.unzip() |> List.to_tuple()
...> Enum.map(data, next) |> :lists.unzip
...> end
iex> get_and_update_in(users, [all, :age], &{&1, &1 + 1})
{[27, 23], [%{name: "john", age: 28}, %{name: "meg", age: 24}]}
@@ -1813,7 +1820,9 @@ defmodule Kernel do
end
defp unnest({{:., _, [expr, key]}, _, []}, acc, kind)
when is_tuple(expr) and elem(expr, 0) != :__aliases__ and elem(expr, 0) != :__MODULE__ do
when is_tuple(expr) and
:erlang.element(1, expr) != :__aliases__ and
:erlang.element(1, expr) != :__MODULE__ do
unnest(expr, [{:map, key}|acc], kind)
end
@@ -1834,8 +1843,8 @@ defmodule Kernel do
defp proper_start?({{:., _, [expr, _]}, _, _args})
when is_atom(expr)
when elem(expr, 0) == :__aliases__
when elem(expr, 0) == :__MODULE__, do: true
when :erlang.element(1, expr) == :__aliases__
when :erlang.element(1, expr) == :__MODULE__, do: true
defp proper_start?({atom, _, _args})
when is_atom(atom), do: true
@@ -1875,11 +1884,6 @@ defmodule Kernel do
quote do: List.Chars.to_char_list(unquote(arg))
end
@doc false
defmacro nil?(x) do
quote do: unquote(x) == nil
end
@doc """
Checks if the given argument is nil or not.
Allowed in guard clauses.
@@ -2062,7 +2066,7 @@ defmodule Kernel do
end
defp warn_info([entry|_]) do
opts = elem(entry, tuple_size(entry) - 1)
opts = :erlang.element(tuple_size(entry), entry)
Exception.format_file_line(Keyword.get(opts, :file), Keyword.get(opts, :line)) <> " "
end
@@ -2384,7 +2388,7 @@ defmodule Kernel do
it is not loaded. Check `Code.ensure_loaded/1` for more
information.
"""
@spec function_exported?(atom | tuple, atom, integer) :: boolean
@spec function_exported?(atom | tuple, atom, arity) :: boolean
def function_exported?(module, function, arity) do
:erlang.function_exported(module, function, arity)
end
+4 -3
View File
@@ -56,7 +56,7 @@ defmodule Kernel.CLI do
## Helpers
defp at_exit(res) do
hooks = :elixir_code_server.call(:flush_at_exit)
hooks = :elixir_config.get_and_put(:at_exit, [])
res = Enum.reduce(hooks, res, &exec_fun/2)
if hooks == [], do: res, else: at_exit(res)
end
@@ -113,7 +113,8 @@ defmodule Kernel.CLI do
IO.puts :stderr, Exception.format(kind, reason, prune_stacktrace(trace))
end
@elixir_internals [:elixir_compiler, :elixir_module, :elixir_translator, :elixir_expand]
@elixir_internals [:elixir, :elixir_exp, :elixir_compiler, :elixir_module, :elixir_translator,
:elixir_expand, :elixir_lexical]
defp prune_stacktrace([{mod, _, _, _}|t]) when mod in @elixir_internals do
prune_stacktrace(t)
@@ -408,7 +409,7 @@ defmodule Kernel.CLI do
end
defp wrapper(fun) do
fun.()
_ = fun.()
:ok
end
+1 -1
View File
@@ -33,7 +33,7 @@ defmodule Kernel.LexicalTracker do
defp to_pid(pid) when is_pid(pid), do: pid
defp to_pid(mod) when is_atom(mod) do
table = :elixir_module.data_table(mod)
[{_, val}] = :ets.lookup(table, :__lexical_tracker)
[{_, val}] = :ets.lookup(table, {:elixir, :lexical_tracker})
val
end
+11 -4
View File
@@ -96,7 +96,7 @@ defmodule Kernel.ParallelCompiler do
else
:elixir_compiler.file(h, Keyword.get(options, :dest))
end
{:compiled, h}
{:shutdown, h}
catch
kind, reason ->
{:failure, kind, reason, System.stacktrace}
@@ -166,7 +166,7 @@ defmodule Kernel.ParallelCompiler do
spawn_compilers(entries, original, output, options, waiting, queued, schedulers, result)
{:DOWN, _down_ref, :process, down_pid, {:compiled, file}} ->
{:DOWN, _down_ref, :process, down_pid, {:shutdown, file}} ->
if callback = Keyword.get(options, :each_file) do
callback.(file)
end
@@ -187,9 +187,15 @@ defmodule Kernel.ParallelCompiler do
defp handle_failure(ref, reason, entries, waiting, queued) do
if file = find_failure(ref, queued) do
print_failure(file, reason)
if all_missing?(entries, waiting, queued) do
collect_failures(queued, length(queued) - 1)
end
Enum.each queued, fn {child, _, _} ->
Process.exit(child, :kill)
end
exit({:shutdown, 1})
end
end
@@ -201,7 +207,7 @@ defmodule Kernel.ParallelCompiler do
end
end
defp print_failure(_file, {:compiled, _}) do
defp print_failure(_file, {:shutdown, _}) do
:ok
end
@@ -215,7 +221,8 @@ defmodule Kernel.ParallelCompiler do
IO.puts Exception.format(:exit, reason, [])
end
@elixir_internals [:elixir_compiler, :elixir_module, :elixir_translator, :elixir_expand]
@elixir_internals [:elixir, :elixir_exp, :elixir_compiler, :elixir_module, :elixir_translator,
:elixir_expand, :elixir_lexical]
defp prune_stacktrace([{mod, _, _, _}|t]) when mod in @elixir_internals do
prune_stacktrace(t)
+37 -7
View File
@@ -644,6 +644,10 @@ defmodule Kernel.Typespec do
[]
end
defp typespec_to_ast({:user_type, line, name, args}) do
typespec_to_ast({:type, line, name, args})
end
defp typespec_to_ast({:type, line, :tuple, :any}) do
{:tuple, [line: line], []}
end
@@ -677,8 +681,13 @@ defmodule Kernel.Typespec do
end
defp typespec_to_ast({:type, line, :map, fields}) do
fields = Enum.map fields, fn {:type, _, :map_field_assoc, k, v} ->
{typespec_to_ast(k), typespec_to_ast(v)}
fields = Enum.map fields, fn
# OTP 18
{:type, _, :map_field_assoc, [k, v]} ->
{typespec_to_ast(k), typespec_to_ast(v)}
# OTP 17
{:type, _, :map_field_assoc, k, v} ->
{typespec_to_ast(k), typespec_to_ast(v)}
end
{struct, fields} = Keyword.pop(fields, :__struct__)
@@ -821,9 +830,17 @@ defmodule Kernel.Typespec do
## Handle maps and structs
defp typespec({:%{}, meta, fields}, vars, caller) do
fields = :lists.map(fn {k, v} ->
{:type, line(meta), :map_field_assoc, typespec(k, vars, caller), typespec(v, vars, caller)}
end, fields)
fields =
if :erlang.system_info(:otp_release) >= '18' do
:lists.map(fn {k, v} ->
{:type, line(meta), :map_field_assoc, [typespec(k, vars, caller), typespec(v, vars, caller)]}
end, fields)
else
:lists.map(fn {k, v} ->
{:type, line(meta), :map_field_assoc, typespec(k, vars, caller), typespec(v, vars, caller)}
end, fields)
end
{:type, line(meta), :map, fields}
end
@@ -884,7 +901,8 @@ defmodule Kernel.Typespec do
# Handle ranges
defp typespec({:.., meta, args}, vars, caller) do
typespec({:range, meta, args}, vars, caller)
args = for arg <- args, do: typespec(arg, vars, caller)
{:type, line(meta), :range, args}
end
# Handle special forms
@@ -968,9 +986,21 @@ defmodule Kernel.Typespec do
typespec((quote do: :elixir.as_boolean(unquote(arg))), vars, caller)
end
defp typespec({:fun, meta, args}, vars, caller) do
args = for arg <- args, do: typespec(arg, vars, caller)
{:type, line(meta), :fun, args}
end
defp typespec({name, meta, arguments}, vars, caller) do
arguments = for arg <- arguments, do: typespec(arg, vars, caller)
{:type, line(meta), name, arguments}
if :erlang.system_info(:otp_release) >= '18' do
arity = length(arguments)
type = if :erl_internal.is_type(name, arity), do: :type, else: :user_type
{type, line(meta), name, arguments}
else
{:type, line(meta), name, arguments}
end
end
# Handle literals
+1 -1
View File
@@ -136,7 +136,7 @@ defmodule Keyword do
:error
"""
@spec fetch(t, key) :: {:ok, value}
@spec fetch(t, key) :: {:ok, value} | :error
def fetch(keywords, key) when is_list(keywords) and is_atom(key) do
case :lists.keyfind(key, 1, keywords) do
{^key, value} -> {:ok, value}
+2 -22
View File
@@ -247,7 +247,7 @@ defmodule List do
[a: 1, b: 2, c: 3]
"""
@spec keystore([tuple], any, non_neg_integer, tuple) :: [tuple]
@spec keystore([tuple], any, non_neg_integer, tuple) :: [tuple, ...]
def keystore(list, key, position, new_tuple) do
:lists.keystore(key, position + 1, list, new_tuple)
end
@@ -324,26 +324,6 @@ defmodule List do
do_zip(list_of_lists, [])
end
@doc """
Unzips the given list of tuples into a list of lists.
The number of unzipped elements is equal to the size
of the smallest tuple in the list.
## Examples
iex> List.unzip([{1, 2}, {3, 4}])
[[1, 3], [2, 4]]
iex> List.unzip([{1, :a, "apple"}, {2, :b, "banana"}, {3, :c}])
[[1, 2, 3], [:a, :b, :c]]
"""
@spec unzip([tuple]) :: [list]
def unzip(list) when is_list(list) do
:lists.map &Tuple.to_list/1, zip(list)
end
@doc """
Returns a list with `value` inserted at the specified `index`.
Note that `index` is capped at the list length. Negative indices
@@ -532,7 +512,7 @@ defmodule List do
1023
"""
@spec to_integer(char_list, non_neg_integer) :: integer
@spec to_integer(char_list, 2..36) :: integer
def to_integer(char_list, base) do
:erlang.list_to_integer(char_list, base)
end
+4 -2
View File
@@ -17,6 +17,7 @@ defmodule Macro do
:<, :>, :->,
:+, :-, :*, :/, :=, :|, :.,
:and, :or, :when, :in,
:~>>, :<<~, :~>, :<~, :<~>, :<|>,
:<<<, :>>>, :|||, :&&&, :^^^, :~~~]
@doc false
@@ -39,7 +40,8 @@ defmodule Macro do
o when o in [:&&, :&&&, :and] -> {:left, 140}
o when o in [:==, :!=, :=~, :===, :!==] -> {:left, 150}
o when o in [:<, :<=, :>=, :>] -> {:left, 160}
o when o in [:|>, :<<<, :>>>] -> {:left, 170}
o when o in [:|>, :<<<, :>>>, :<~, :~>,
:<<~, :~>>, :<~>, :<|>] -> {:left, 170}
:in -> {:left, 180}
o when o in [:++, :--, :.., :<>] -> {:right, 200}
o when o in [:+, :-] -> {:left, 210}
@@ -831,7 +833,7 @@ defmodule Macro do
:elixir_lexical.record_remote(receiver, env.lexical_tracker)
{receiver, true}
aliases ->
aliases = for alias <- aliases, do: elem(do_expand_once(alias, env), 0)
aliases = :lists.map(&elem(do_expand_once(&1, env), 0), aliases)
case :lists.all(&is_atom/1, aliases) do
true ->
+16 -1
View File
@@ -4,7 +4,22 @@ defmodule Macro.Env do
The current environment can be accessed at any time as
`__ENV__`. Inside macros, the caller environment can be
accessed as `__CALLER__`. It contains the following fields:
accessed as `__CALLER__`.
An instance of `Macro.Env` must not be modified by hand. If you need to
create a custom environment to pass to `Code.eval_quoted/3`, use the
following trick:
def make_custom_env do
import SomeModule, only: [some_function: 2]
alias A.B.C
__ENV__
end
You may then call `make_custom_env()` to get a struct with the desired
imports and aliases included.
It contains the following fields:
* `module` - the current module name
* `file` - the current file name as a binary
+28 -35
View File
@@ -504,19 +504,19 @@ defmodule Module do
def add_doc(module, line, kind, tuple, signature, doc) when
kind in [:def, :defmacro, :type, :opaque] and (is_binary(doc) or is_boolean(doc) or doc == nil) do
assert_not_compiled!(:add_doc, module)
table = docs_table_for(module)
table = data_table_for(module)
{signature, _} = :lists.mapfoldl fn(x, acc) ->
{simplify_signature(x, acc), acc + 1}
end, 1, signature
case :ets.lookup(table, tuple) do
case :ets.lookup(table, {:doc, tuple}) do
[] ->
:ets.insert(table, {tuple, line, kind, signature, doc})
:ets.insert(table, {{:doc, tuple}, line, kind, signature, doc})
:ok
[{tuple, line, _old_kind, old_sign, old_doc}] ->
[{doc_tuple, line, _old_kind, old_sign, old_doc}] ->
:ets.insert(table, {
tuple,
doc_tuple,
line,
kind,
merge_signatures(old_sign, signature, 1),
@@ -612,7 +612,7 @@ defmodule Module do
"""
def defines?(module, tuple) when is_tuple(tuple) do
assert_not_compiled!(:defines?, module)
table = function_table_for(module)
table = defs_table_for(module)
:ets.lookup(table, tuple) != []
end
@@ -632,7 +632,7 @@ defmodule Module do
"""
def defines?(module, tuple, kind) do
assert_not_compiled!(:defines?, module)
table = function_table_for(module)
table = defs_table_for(module)
case :ets.lookup(table, tuple) do
[{_, ^kind, _, _, _, _, _}] -> true
_ -> false
@@ -652,8 +652,8 @@ defmodule Module do
"""
def definitions_in(module) do
assert_not_compiled!(:definitions_in, module)
table = function_table_for(module)
for {tuple, _, _, _, _, _, _} <- :ets.tab2list(table), do: tuple
table = defs_table_for(module)
:lists.concat :ets.match(table, {:'$1', :_, :_, :_, :_, :_, :_})
end
@doc """
@@ -671,8 +671,8 @@ defmodule Module do
"""
def definitions_in(module, kind) do
assert_not_compiled!(:definitions_in, module)
table = function_table_for(module)
for {tuple, stored_kind, _, _, _, _, _} <- :ets.tab2list(table), stored_kind == kind, do: tuple
table = defs_table_for(module)
:lists.concat :ets.match(table, {:'$1', kind, :_, :_, :_, :_, :_})
end
@doc """
@@ -684,7 +684,7 @@ defmodule Module do
def make_overridable(module, tuples) do
assert_not_compiled!(:make_overridable, module)
for tuple <- tuples do
:lists.foreach(fn tuple ->
case :elixir_def.lookup_definition(module, tuple) do
false ->
{name, arity} = tuple
@@ -692,27 +692,26 @@ defmodule Module do
clause ->
:elixir_def.delete_definition(module, tuple)
neighbours = if loaded?(Module.LocalsTracker) do
Module.LocalsTracker.yank(module, tuple)
else
neighbours = if :elixir_compiler.get_opt(:internal) do
[]
else
Module.LocalsTracker.yank(module, tuple)
end
old = get_attribute(module, :__overridable)
old = :elixir_def_overridable.overridable(module)
merged = :orddict.update(tuple, fn({count, _, _, _}) ->
{count + 1, clause, neighbours, false}
end, {1, clause, neighbours, false}, old)
put_attribute(module, :__overridable, merged)
:elixir_def_overridable.overridable(module, merged)
end
end
end, tuples)
end
@doc """
Returns `true` if `tuple` in `module` is marked as overridable.
"""
def overridable?(module, tuple) do
!!List.keyfind(get_attribute(module, :__overridable), tuple, 0)
!!List.keyfind(:elixir_def_overridable.overridable(module), tuple, 0)
end
@doc """
@@ -731,7 +730,7 @@ defmodule Module do
assert_not_compiled!(:put_attribute, module)
table = data_table_for(module)
value = normalize_attribute(key, value)
acc = :ets.lookup_element(table, :__acc_attributes, 2)
acc = :ets.lookup_element(table, {:elixir, :acc_attributes}, 2)
new =
if :lists.member(key, acc) do
@@ -832,13 +831,13 @@ defmodule Module do
table = data_table_for(module)
if Keyword.get(opts, :persist) do
old = :ets.lookup_element(table, :__persisted_attributes, 2)
:ets.insert(table, {:__persisted_attributes, [new|old]})
old = :ets.lookup_element(table, {:elixir, :persisted_attributes}, 2)
:ets.insert(table, {{:elixir, :persisted_attributes}, [new|old]})
end
if Keyword.get(opts, :accumulate) do
old = :ets.lookup_element(table, :__acc_attributes, 2)
:ets.insert(table, {:__acc_attributes, [new|old]})
old = :ets.lookup_element(table, {:elixir, :acc_attributes}, 2)
:ets.insert(table, {{:elixir, :acc_attributes}, [new|old]})
end
end
@@ -901,7 +900,7 @@ defmodule Module do
case :ets.lookup(table, key) do
[{^key, val}] -> val
[] ->
acc = :ets.lookup_element(table, :__acc_attributes, 2)
acc = :ets.lookup_element(table, {:elixir, :acc_attributes}, 2)
cond do
:lists.member(key, acc) ->
@@ -956,15 +955,11 @@ defmodule Module do
end
defp data_table_for(module) do
module
:elixir_module.data_table(module)
end
defp function_table_for(module) do
:elixir_def.table(module)
end
defp docs_table_for(module) do
:elixir_module.docs_table(module)
defp defs_table_for(module) do
:elixir_module.defs_table(module)
end
defp assert_not_compiled!(fun, module) do
@@ -972,6 +967,4 @@ defmodule Module do
raise ArgumentError,
"could not call #{fun} on module #{inspect module} because it was already compiled"
end
defp loaded?(module), do: is_tuple :code.is_loaded(module)
end
+2 -2
View File
@@ -83,7 +83,7 @@ defmodule Module.LocalsTracker do
defp to_pid(pid) when is_pid(pid), do: pid
defp to_pid(mod) when is_atom(mod) do
table = :elixir_module.data_table(mod)
[{_, val}] = :ets.lookup(table, :__locals_tracker)
[{_, val}] = :ets.lookup(table, {:elixir, :locals_tracker})
val
end
@@ -360,7 +360,7 @@ defmodule Module.LocalsTracker do
end
defp replace_edge!(d, from, to) do
unless :lists.member(to, :digraph.out_neighbours(d, from)) do
_ = unless :lists.member(to, :digraph.out_neighbours(d, from)) do
[:"$e"|_] = :digraph.add_edge(d, from, to)
end
:ok
+1 -1
View File
@@ -30,7 +30,7 @@ defmodule Node do
returns `{:error, :not_allowed}`. Returns `{:error, :not_found}` if the
local node is not alive.
"""
@spec stop() :: :ok | {:error, term}
@spec stop() :: :ok | {:error, :not_allowed | :not_found}
def stop() do
:net_kernel.stop()
end
+1 -1
View File
@@ -345,7 +345,7 @@ defmodule Process do
See http://www.erlang.org/doc/man/erlang.html#process_flag-3 for more info.
"""
@spec flag(pid, process_flag, term) :: term
@spec flag(pid, :save_calls, non_neg_integer) :: non_neg_integer
def flag(pid, flag, value) do
:erlang.process_flag(pid, flag, value)
end
+14 -12
View File
@@ -19,11 +19,12 @@ defmodule Protocol do
defmacro def({name, _, args}) when is_atom(name) and is_list(args) do
arity = length(args)
type_args = for _ <- :lists.seq(2, arity), do: quote(do: term)
type_args = :lists.map(fn _ -> quote(do: term) end,
:lists.seq(2, arity))
type_args = [quote(do: t) | type_args]
call_args = for i <- :lists.seq(2, arity),
do: {String.to_atom(<<?x, i + 64>>), [], __MODULE__}
call_args = :lists.map(fn i -> {String.to_atom(<<?x, i + 64>>), [], __MODULE__} end,
:lists.seq(2, arity))
call_args = [quote(do: t) | call_args]
quote do
@@ -440,7 +441,7 @@ defmodule Protocol do
end
# Define the implementation for builtins.
for {guard, mod} <- builtin do
:lists.foreach(fn {guard, mod} ->
target = Module.concat(__MODULE__, mod)
Kernel.def impl_for(data) when :erlang.unquote(guard)(data) do
@@ -449,7 +450,7 @@ defmodule Protocol do
false -> any_impl_for
end
end
end
end, builtin)
@spec impl_for!(term) :: atom() | no_return()
Kernel.def impl_for!(data) do
@@ -609,16 +610,17 @@ defmodule Protocol do
@doc false
def __spec__?(module, name, arity) do
signature = {name, arity}
specs = Module.get_attribute(module, :spec)
specs = Module.get_attribute(module, :spec)
found =
for {:spec, expr, caller} <- specs,
Kernel.Typespec.spec_to_signature(expr) == signature do
Kernel.Typespec.define_spec(:callback, expr, caller)
true
end
:lists.map(fn {:spec, expr, caller} ->
if Kernel.Typespec.spec_to_signature(expr) == signature do
Kernel.Typespec.define_spec(:callback, expr, caller)
true
end
end, specs)
found != []
:lists.any(& &1 == true, found)
end
## Helpers
-34
View File
@@ -56,23 +56,6 @@ defmodule Record do
Macro.escape Record.Extractor.extract(name, opts)
end
@doc false
defmacro record?(data, kind) do
case Macro.Env.in_guard?(__CALLER__) do
true ->
quote do
is_tuple(unquote(data)) and tuple_size(unquote(data)) > 0
and :erlang.element(1, unquote(data)) == unquote(kind)
end
false ->
quote do
result = unquote(data)
is_tuple(result) and tuple_size(result) > 0
and :erlang.element(1, result) == unquote(kind)
end
end
end
@doc """
Checks if the given `data` is a record of `kind`.
@@ -101,23 +84,6 @@ defmodule Record do
end
end
@doc false
defmacro record?(data) do
case Macro.Env.in_guard?(__CALLER__) do
true ->
quote do
is_tuple(unquote(data)) and tuple_size(unquote(data)) > 0
and is_atom(:erlang.element(1, unquote(data)))
end
false ->
quote do
result = unquote(data)
is_tuple(result) and tuple_size(result) > 0
and is_atom(:erlang.element(1, result))
end
end
end
@doc """
Checks if the given `data` is a record.
+22 -5
View File
@@ -78,7 +78,7 @@ defmodule Regex do
defstruct re_pattern: nil, source: "", opts: ""
@opaque t :: %__MODULE__{re_pattern: term, source: binary, opts: binary}
@type t :: %__MODULE__{re_pattern: term, source: binary, opts: binary}
defmodule CompileError do
defexception message: "regex could not be compiled"
@@ -133,6 +133,7 @@ defmodule Regex do
Compiles the regular expression according to the given options.
Fails with `Regex.CompileError` if the regex cannot be compiled.
"""
@spec compile(binary, binary | [term]) :: t
def compile!(source, options \\ "") do
case compile(source, options) do
{:ok, regex} -> regex
@@ -152,6 +153,7 @@ defmodule Regex do
false
"""
@spec match?(t, String.t) :: boolean
def match?(%Regex{re_pattern: compiled}, string) when is_binary(string) do
:re.run(string, compiled, [{:capture, :none}]) == :match
end
@@ -168,6 +170,8 @@ defmodule Regex do
false
"""
@spec regex?(t) :: true
@spec regex?(any) :: false
def regex?(%Regex{}), do: true
def regex?(_), do: false
@@ -193,6 +197,7 @@ defmodule Regex do
[{2,2},{3,1}]
"""
@spec run(t, binary, [term]) :: nil | [binary] | [{integer, integer}]
def run(regex, string, options \\ [])
def run(%Regex{re_pattern: compiled}, string, options) when is_binary(string) do
@@ -223,6 +228,7 @@ defmodule Regex do
nil
"""
@spec named_captures(t, String.t, [term]) :: map | nil
def named_captures(regex, string, options \\ []) when is_binary(string) do
names = names(regex)
options = Keyword.put(options, :capture, names)
@@ -233,6 +239,7 @@ defmodule Regex do
@doc """
Returns the underlying `re_pattern` in the regular expression.
"""
@spec re_pattern(t) :: term
def re_pattern(%Regex{re_pattern: compiled}) do
compiled
end
@@ -246,6 +253,7 @@ defmodule Regex do
"foo"
"""
@spec source(t) :: String.t
def source(%Regex{source: source}) do
source
end
@@ -259,6 +267,7 @@ defmodule Regex do
"m"
"""
@spec opts(t) :: String.t
def opts(%Regex{opts: opts}) do
opts
end
@@ -272,6 +281,7 @@ defmodule Regex do
["foo"]
"""
@spec names(t) :: [String.t]
def names(%Regex{re_pattern: re_pattern}) do
{:namelist, names} = :re.inspect(re_pattern, :namelist)
names
@@ -301,6 +311,7 @@ defmodule Regex do
[]
"""
@spec scan(t, String.t, [term]) :: [[String.t]]
def scan(regex, string, options \\ [])
def scan(%Regex{re_pattern: compiled}, string, options) when is_binary(string) do
@@ -353,7 +364,7 @@ defmodule Regex do
["a", "c"]
"""
@spec split(t, String.t, [term]) :: [String.t]
def split(regex, string, options \\ [])
def split(%Regex{}, "", _opts), do: [""]
@@ -408,8 +419,8 @@ defmodule Regex do
The replacement can be either a string or a function. The string
is used as a replacement for every match and it allows specific
captures to be accessed via `\N`, where `N` is the capture. In
case `\0` is used, the whole match is inserted.
captures to be accessed via `\N` or `\g{N}`, where `N` is the
capture. In case `\0` is used, the whole match is inserted.
When the replacement is a function, the function may have arity
N where each argument maps to a capture, with the first argument
@@ -439,6 +450,7 @@ defmodule Regex do
"[b][d]"
"""
@spec replace(t, String.t, String.t | (... -> String.t), [term]) :: String.t
def replace(regex, string, replacement, options \\ [])
def replace(regex, string, replacement, options) when is_binary(replacement) do
@@ -467,6 +479,11 @@ defmodule Regex do
defp precompile_replacement(""),
do: []
defp precompile_replacement(<<?\\, ?g, ?{, rest :: binary>>) when byte_size(rest) > 0 do
{ns, <<?}, rest :: binary>>} = pick_int(rest)
[List.to_integer(ns) | precompile_replacement(rest)]
end
defp precompile_replacement(<<?\\, x, rest :: binary>>) when x < ?0 or x > ?9 do
case precompile_replacement(rest) do
[head | t] when is_binary(head) ->
@@ -538,7 +555,7 @@ defmodule Regex do
cond do
is_binary(part) ->
part
part > tuple_size(indexes) ->
part >= tuple_size(indexes) ->
""
true ->
get_index(string, elem(indexes, part))
+64 -41
View File
@@ -156,18 +156,22 @@ defmodule Stream do
@spec chunk(Enumerable.t, non_neg_integer, non_neg_integer, Enumerable.t | nil) :: Enumerable.t
def chunk(enum, n, step, pad \\ nil) when n > 0 and step > 0 do
limit = :erlang.max(n, step)
lazy enum, {[], 0},
fn(f1) -> R.chunk(n, step, limit, f1) end,
fn(f1) -> &do_chunk(&1, n, pad, f1) end
if is_nil(pad) do
lazy enum, {[], 0}, fn(f1) -> R.chunk(n, step, limit, f1) end
else
lazy enum, {[], 0},
fn(f1) -> R.chunk(n, step, limit, f1) end,
&do_chunk(&1, n, pad, &2)
end
end
defp do_chunk(acc(h, {buffer, count} = old, t) = acc, n, pad, f1) do
if is_nil(pad) || count == 0 do
{:cont, acc}
else
buffer = :lists.reverse(buffer) ++ Enum.take(pad, n - count)
cont_with_acc(f1, buffer, h, old, t)
end
defp do_chunk(acc(_, {_, 0}, _) = acc, _, _, _) do
{:cont, acc}
end
defp do_chunk(acc(h, {buffer, count} = old, t), n, pad, f1) do
buffer = :lists.reverse(buffer) ++ Enum.take(pad, n - count)
cont_with_acc(f1, buffer, h, old, t)
end
@doc """
@@ -186,7 +190,7 @@ defmodule Stream do
def chunk_by(enum, fun) do
lazy enum, nil,
fn(f1) -> R.chunk_by(fun, f1) end,
fn(f1) -> &do_chunk_by(&1, f1) end
&do_chunk_by(&1, &2)
end
defp do_chunk_by(acc(_, nil, _) = acc, _f1) do
@@ -612,7 +616,7 @@ defmodule Stream do
## Examples
`Stream.transform/3` is a useful as it can be used as basis to implement
`Stream.transform/3` is useful as it can be used as the basis to implement
many of the functions defined in this module. For example, we can implement
`Stream.take(enum, n)` as follows:
@@ -1017,17 +1021,13 @@ defmodule Stream do
{:halted, acc}
end
# TODO: We should check this is a two-item tuple once
# we remove support for the deprecated nil
# TODO: Support non lists once the previous format
# is removed
defp do_resource(next_acc, next_fun, {:cont, acc}, fun, after_fun) do
try do
# Optimize the most common cases
case next_fun.(next_acc) do
{[], next_acc} -> {:opt, {:cont, acc}, next_acc}
{[v], next_acc} -> {:opt, fun.(v, acc), next_acc}
other -> other
{_, _} = other -> other
end
catch
kind, reason ->
@@ -1042,15 +1042,10 @@ defmodule Stream do
{list, next_acc} when is_list(list) ->
do_list_resource(next_acc, next_fun, {:cont, acc}, fun, after_fun,
&Enumerable.List.reduce(list, &1, fun))
nil ->
IO.write :stderr, "warning: returning nil from Stream.resource/3 is deprecated, " <>
"please return {:halt, acc} instead\n#{Exception.format_stacktrace}"
do_resource(next_acc, next_fun, {:halt, acc}, fun, after_fun)
{v, next_acc} ->
IO.write :stderr, "warning: returning {next, acc} from Stream.resource/3 is deprecated, " <>
"please return {[val], acc} instead\n#{Exception.format_stacktrace}"
do_list_resource(next_acc, next_fun, {:cont, acc}, fun, after_fun,
&Enumerable.List.reduce([v], &1, fun))
{enum, next_acc} ->
inner = &do_resource_each(&1, &2, fun)
do_enum_resource(next_acc, next_fun, {:cont, acc}, fun, after_fun,
&Enumerable.reduce(enum, &1, inner))
end
end
@@ -1072,6 +1067,33 @@ defmodule Stream do
end
end
defp do_enum_resource(next_acc, next_fun, {op, acc}, fun, after_fun, reduce) do
try do
reduce.({op, [:outer|acc]})
catch
kind, reason ->
stacktrace = System.stacktrace
after_fun.(next_acc)
:erlang.raise(kind, reason, stacktrace)
else
{:halted, [:outer|acc]} ->
do_resource(next_acc, next_fun, {:cont, acc}, fun, after_fun)
{:halted, [:inner|acc]} ->
do_resource(next_acc, next_fun, {:halt, acc}, fun, after_fun)
{:done, [_|acc]} ->
do_resource(next_acc, next_fun, {:cont, acc}, fun, after_fun)
{:suspended, [_|acc], c} ->
{:suspended, acc, &do_enum_resource(next_acc, next_fun, &1, fun, after_fun, c)}
end
end
defp do_resource_each(x, [:outer|acc], f) do
case f.(x, acc) do
{:halt, res} -> {:halt, [:inner|res]}
{op, res} -> {op, [:outer|res]}
end
end
@doc """
Emits a sequence of values for the given accumulator.
@@ -1109,12 +1131,12 @@ defmodule Stream do
@compile {:inline, lazy: 2, lazy: 3, lazy: 4}
defp lazy(%Stream{funs: funs} = lazy, fun),
defp lazy(%Stream{done: nil, funs: funs} = lazy, fun),
do: %{lazy | funs: [fun|funs] }
defp lazy(enum, fun),
do: %Stream{enum: enum, funs: [fun]}
defp lazy(%Stream{funs: funs, accs: accs} = lazy, acc, fun),
defp lazy(%Stream{done: nil, funs: funs, accs: accs} = lazy, acc, fun),
do: %{lazy | funs: [fun|funs], accs: [acc|accs] }
defp lazy(enum, acc, fun),
do: %Stream{enum: enum, funs: [fun], accs: [acc]}
@@ -1153,19 +1175,20 @@ defimpl Enumerable, for: Stream do
case reduce.({command, [acc|accs]}) do
{:suspended, [acc|accs], continuation} ->
{:suspended, acc, &do_each(continuation, done, accs, &1)}
{:halted, [acc|_]} ->
{:halted, acc}
{:done, [acc|_] = accs} ->
case done do
nil ->
{:done, acc}
{done, fun} ->
case done.(fun).(accs) do
{:cont, [acc|_]} -> {:done, acc}
{:halt, [acc|_]} -> {:halted, acc}
{:suspend, [acc|_]} -> {:suspended, acc, &({:done, elem(&1, 1)})}
end
end
{:halted, accs} ->
do_done {:halted, accs}, done
{:done, accs} ->
do_done {:done, accs}, done
end
end
defp do_done({reason, [acc|_]}, nil), do: {reason, acc}
defp do_done({reason, [acc|t]}, {done, fun}) do
[h|_] = Enum.reverse(t)
case done.([acc, h], fun) do
{:cont, [acc|_]} -> {reason, acc}
{:halt, [acc|_]} -> {:halted, acc}
{:suspend, [acc|_]} -> {:suspended, acc, &({:done, elem(&1, 1)})}
end
end
end
+13 -12
View File
@@ -74,21 +74,21 @@ defmodule String do
be avoided in favor of binary functions or pattern matching.
For example, imagine you have a string `prefix` and you want to
remove this prefix from another string named `full`.
One may be tempted to write:
iex> take_prefix = fn full, prefix ->
...> base = String.length(prefix)
...> String.slice(full, base, String.length(full) - base)
...> end
...> take_prefix.("Mr. John", "Mr. ")
"John"
Although the function above works, it performs poorly. To
calculate the length of the string, we need to traverse it
fully, so we traverse both `prefix` and `full` strings, then
slice the `full` one, traversing it again.
A first attempting at improving it could be with ranges:
iex> take_prefix = fn full, prefix ->
@@ -103,7 +103,7 @@ defmodule String do
extract a substring from a string, we can use `byte_size/1`
and `binary_part/3` as there is no chance we will slice in
the middle of a codepoint made of more than one byte:
iex> take_prefix = fn full, prefix ->
...> base = byte_size(prefix)
...> binary_part(full, base, byte_size(full) - base)
@@ -112,7 +112,7 @@ defmodule String do
"John"
Or simply used pattern matching:
iex> take_prefix = fn full, prefix ->
...> base = byte_size(prefix)
...> <<_ :: binary-size(base), rest :: binary>> = full
@@ -650,13 +650,14 @@ defmodule String do
iex> String.replace("a,b,c", ",", "-", global: false)
"a-b,c"
The pattern can also be a regex. In those cases, one can give `\N`
in the `replacement` string to access a specific capture in the regex:
The pattern can also be a regex. In those cases, one can give `\N` or
`\g{N}` in the `replacement` string to access a specific capture in the
regex:
iex> String.replace("a,b,c", ~r/,(.)/, ",\\1\\1")
"a,bb,cc"
Notice we had to escape the escape character `\`. By giving `&`,
Notice we had to escape the escape character `\`. By giving `\0`,
one can inject the whole matched pattern in the replacement string.
When strings are used as a pattern, a developer can also use the
@@ -672,8 +673,8 @@ defmodule String do
"a[,,]b[,,]c"
"""
@spec replace(t, t, t) :: t
@spec replace(t, t, t, Keyword.t) :: t
@spec replace(t, t | Regex.t, t) :: t
@spec replace(t, t | Regex.t, t, Keyword.t) :: t
def replace(subject, pattern, replacement, options \\ []) when is_binary(replacement) do
if Regex.regex?(pattern) do
@@ -1423,7 +1424,7 @@ defmodule String do
1023
"""
@spec to_integer(String.t, pos_integer) :: integer
@spec to_integer(String.t, 2..36) :: integer
def to_integer(string, base) do
:erlang.binary_to_integer(string, base)
end
+3 -3
View File
@@ -132,7 +132,7 @@ defmodule Supervisor do
* `:simple_one_for_one` - similar to `:one_for_one` but suits better
when dynamically attaching children. This strategy requires the
supervisor specification to contain only one children. Many functions
supervisor specification to contain only one child. Many functions
in this module behave slightly differently when this strategy is
used.
@@ -391,8 +391,8 @@ defmodule Supervisor do
"""
@spec count_children(supervisor) ::
[specs: non_neg_integer, active: non_neg_integer,
supervisors: non_neg_integer, workers: non_neg_integer]
%{specs: non_neg_integer, active: non_neg_integer,
supervisors: non_neg_integer, workers: non_neg_integer}
def count_children(supervisor) do
call(supervisor, :count_children) |> :maps.from_list
end
+3 -3
View File
@@ -145,12 +145,12 @@ defmodule Supervisor.Spec do
in the `Supervisor` module docs.
* `:max_restarts` - the maximum amount of restarts allowed in
a time frame. Defaults to 5.
a time frame. Defaults to 3.
* `:max_seconds` - the time frame in which `:max_restarts` applies.
Defaults to 5.
The `:strategy` option is required and by default maximum 5 restarts
The `:strategy` option is required and by default maximum 3 restarts
are allowed within 5 seconds. Please check the `Supervisor` module for
a complete description of the available strategies.
"""
@@ -162,7 +162,7 @@ defmodule Supervisor.Spec do
raise ArgumentError, "expected :strategy option to be given"
end
maxR = Keyword.get(options, :max_restarts, 5)
maxR = Keyword.get(options, :max_restarts, 3)
maxS = Keyword.get(options, :max_seconds, 5)
assert_unique_ids(Enum.map(children, &elem(&1, 0)))
+23 -11
View File
@@ -72,7 +72,7 @@ defmodule System do
"""
@spec argv() :: [String.t]
def argv do
:elixir_code_server.call :argv
:elixir_config.get(:argv)
end
@doc """
@@ -83,7 +83,7 @@ defmodule System do
"""
@spec argv([String.t]) :: :ok
def argv(args) do
:elixir_code_server.cast({:argv, args})
:elixir_config.put(:argv, args)
end
@doc """
@@ -94,11 +94,20 @@ defmodule System do
"""
def cwd do
case :file.get_cwd do
{:ok, base} -> IO.chardata_to_string(base)
{:ok, base} -> IO.chardata_to_string(fix_drive_letter(base))
_ -> nil
end
end
defp fix_drive_letter([l, ?:, ?/ | rest] = original) when l in ?A..?Z do
case :os.type() do
{:win32, _} -> [l+?a-?A, ?:, ?/ | rest]
_ -> original
end
end
defp fix_drive_letter(original), do: original
@doc """
Current working directory, exception on error.
@@ -215,7 +224,7 @@ defmodule System do
The function must receive the exit status code as an argument.
"""
def at_exit(fun) when is_function(fun, 1) do
:elixir_code_server.cast {:at_exit, fun}
:elixir_config.update :at_exit, &[fun|&1]
end
@doc """
@@ -422,18 +431,18 @@ defmodule System do
@spec cmd(binary, [binary], Keyword.t) ::
{Collectable.t, exit_status :: non_neg_integer}
def cmd(command, args, opts \\ []) when is_binary(command) and is_list(args) do
command = String.to_char_list(command)
cmd = String.to_char_list(command)
command =
if Path.type(command) == :absolute do
command
cmd =
if Path.type(cmd) == :absolute do
cmd
else
:os.find_executable(command) || command
:os.find_executable(cmd) || :erlang.error(:enoent, [command, args, opts])
end
{into, opts} = cmd_opts(opts, [:use_stdio, :exit_status, :binary, :hide, args: args], "")
{initial, fun} = Collectable.into(into)
do_cmd Port.open({:spawn_executable, command}, opts), initial, fun
do_cmd Port.open({:spawn_executable, cmd}, opts), initial, fun
end
defp do_cmd(port, acc, fun) do
@@ -454,9 +463,12 @@ defmodule System do
defp cmd_opts([{:arg0, bin}|t], opts, into) when is_binary(bin),
do: cmd_opts(t, [{:arg0, bin}|opts], into)
defp cmd_opts([{:stderr_to_stdout, bool}|t], opts, into) when is_boolean(bool),
defp cmd_opts([{:stderr_to_stdout, true}|t], opts, into),
do: cmd_opts(t, [:stderr_to_stdout|opts], into)
defp cmd_opts([{:stderr_to_stdout, false}|t], opts, into),
do: cmd_opts(t, opts, into)
defp cmd_opts([{:parallelism, bool}|t], opts, into) when is_boolean(bool),
do: cmd_opts(t, [{:parallelism, bool}|opts], into)
+10 -1
View File
@@ -25,14 +25,23 @@ defmodule Task.Supervisor do
described under the `Name Registration` section in the `GenServer` module
docs;
* `:restart` - the restart strategy, may be `:temporary` (the default),
`:transient` or `:permanent`. Check `Supervisor.Spec` for more info.
Defaults to temporary as most tasks can't be effectively restarted after
a crash;
* `:shutdown` - `:brutal_kill` if the tasks must be killed directly on shutdown
or an integer indicating the timeout value, defaults to 5000 milliseconds;
* `:max_restarts` and `:max_seconds` - as specified in `Supervisor.Spec.supervise/2`;
"""
@spec start_link(Supervisor.options) :: Supervisor.on_start
def start_link(opts \\ []) do
import Supervisor.Spec
{restart, opts} = Keyword.pop(opts, :restart, :temporary)
{shutdown, opts} = Keyword.pop(opts, :shutdown, 5000)
children = [worker(Task.Supervised, [], restart: :temporary, shutdown: shutdown)]
children = [worker(Task.Supervised, [], restart: restart, shutdown: shutdown)]
Supervisor.start_link(children, [strategy: :simple_one_for_one] ++ opts)
end
+55 -36
View File
@@ -11,18 +11,17 @@ defmodule URI do
import Bitwise
@ports %{
"ftp" => 21,
"http" => 80,
"https" => 443,
"ldap" => 389,
"sftp" => 22,
"tftp" => 69,
}
@schemes [
"ftp",
"http",
"https",
"ldap",
"sftp",
"tftp",
]
Enum.each @ports, fn {scheme, port} ->
Enum.each @schemes, fn scheme ->
def normalize_scheme(unquote(scheme)), do: unquote(scheme)
def default_port(unquote(scheme)), do: unquote(port)
end
@doc """
@@ -47,8 +46,7 @@ defmodule URI do
"""
def default_port(scheme) when is_binary(scheme) do
{:ok, dict} = Application.fetch_env(:elixir, :uri)
Map.get(dict, scheme)
:elixir_config.get({:uri, scheme})
end
@doc """
@@ -59,8 +57,7 @@ defmodule URI do
new URIs.
"""
def default_port(scheme, port) when is_binary(scheme) and port > 0 do
{:ok, dict} = Application.fetch_env(:elixir, :uri)
Application.put_env(:elixir, :uri, Map.put(dict, scheme, port), persistent: true)
:elixir_config.put({:uri, scheme}, port)
end
@doc """
@@ -98,7 +95,10 @@ defmodule URI do
"""
def decode_query(q, dict \\ %{}) when is_binary(q) do
Enum.reduce query_decoder(q), dict, fn({k, v}, acc) -> Dict.put(acc, k, v) end
case do_decode_query(q) do
nil -> dict
{{k, v}, q} -> decode_query(q, Dict.put(dict, k, v))
end
end
@doc """
@@ -107,19 +107,19 @@ defmodule URI do
## Examples
iex> URI.query_decoder("foo=1&bar=2") |> Enum.map &(&1)
iex> URI.query_decoder("foo=1&bar=2") |> Enum.map(&(&1))
[{"foo", "1"}, {"bar", "2"}]
"""
def query_decoder(q) when is_binary(q) do
Stream.unfold(q, &do_decoder/1)
Stream.unfold(q, &do_decode_query/1)
end
defp do_decoder("") do
defp do_decode_query("") do
nil
end
defp do_decoder(q) do
defp do_decode_query(q) do
{first, next} =
case :binary.split(q, "&") do
[first, rest] -> {first, rest}
@@ -234,7 +234,7 @@ defmodule URI do
"""
def decode(uri) do
unpercent(uri)
unpercent(uri, "", false)
catch
:malformed_uri ->
raise ArgumentError, "malformed URI #{inspect uri}"
@@ -250,23 +250,25 @@ defmodule URI do
"""
def decode_www_form(str) do
String.split(str, "+") |> Enum.map_join(" ", &unpercent/1)
unpercent(str, "", true)
catch
:malformed_uri ->
raise ArgumentError, "malformed URI #{inspect str}"
end
defp unpercent(<<?%, hex_1, hex_2, tail :: binary>>) do
<<bsl(hex_to_dec(hex_1), 4) + hex_to_dec(hex_2)>> <> unpercent(tail)
end
defp unpercent(<<?%, _>>), do: throw(:malformed_uri)
defp unpercent(<<?%>>), do: throw(:malformed_uri)
defp unpercent(<<head, tail :: binary>>) do
<<head>> <> unpercent(tail)
defp unpercent(<<?+, tail::binary>>, acc, spaces = true) do
unpercent(tail, <<acc::binary, ?\s>>, spaces)
end
defp unpercent(<<>>), do: <<>>
defp unpercent(<<?%, hex_1, hex_2, tail::binary>>, acc, spaces) do
unpercent(tail, <<acc::binary, bsl(hex_to_dec(hex_1), 4) + hex_to_dec(hex_2)>>, spaces)
end
defp unpercent(<<?%, _::binary>>, _acc, _spaces), do: throw(:malformed_uri)
defp unpercent(<<head, tail::binary>>, acc, spaces) do
unpercent(tail, <<acc::binary, head>>, spaces)
end
defp unpercent(<<>>, acc, _spaces), do: acc
defp hex_to_dec(n) when n in ?A..?F, do: n - ?A + 10
defp hex_to_dec(n) when n in ?a..?f, do: n - ?a + 10
@@ -274,12 +276,17 @@ defmodule URI do
defp hex_to_dec(_n), do: throw(:malformed_uri)
@doc """
Parses a URI into components.
Parses a well-formed URI reference into its components.
URIs have portions that are handled specially for the particular
scheme of the URI. For example, http and https have different
default ports. Such values can be accessed and registered via
`URI.default_port/1` and `URI.default_port/2`.
Note this function expects a well-formed URI and does not perform
any validation. See the examples section below of how `URI.parse/1`
can be used to parse a wide range of relative URIs.
This function uses the parsing regular expression as defined
in the Appendix B of RFC3986.
When a URI is given without a port, the values registered via
`URI.default_port/1` and `URI.default_port/2` are used.
## Examples
@@ -288,12 +295,24 @@ defmodule URI do
authority: "elixir-lang.org", userinfo: nil,
host: "elixir-lang.org", port: 80}
iex> URI.parse("//elixir-lang.org/")
%URI{authority: "elixir-lang.org", fragment: nil, host: "elixir-lang.org",
path: "/", port: nil, query: nil, scheme: nil, userinfo: nil}
iex> URI.parse("/foo/bar")
%URI{authority: nil, fragment: nil, host: nil, path: "/foo/bar",
port: nil, query: nil, scheme: nil, userinfo: nil}
iex> URI.parse("foo/bar")
%URI{authority: nil, fragment: nil, host: nil, path: "foo/bar",
port: nil, query: nil, scheme: nil, userinfo: nil}
"""
def parse(%URI{} = uri), do: uri
def parse(s) when is_binary(s) do
# From http://tools.ietf.org/html/rfc3986#appendix-B
regex = ~r/^(([^:\/?#]+):)?(\/\/([^\/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?/
regex = ~r/^(([a-z][a-z0-9\+\-\.]*):)?(\/\/([^\/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?/i
parts = nillify(Regex.run(regex, s))
destructure [_, _, scheme, _, authority, path, _, query, _, fragment], parts
+28 -6
View File
@@ -29,9 +29,12 @@ start(_Type, _Args) ->
end,
ok = io:setopts(standard_io, Opts),
%% Must use undocument {unicode, true} to set unicode on standard_error, more
%% info: http://erlang.org/pipermail/erlang-bugs/2014-April/004310.html
ok = io:setopts(standard_error, [{unicode,true}]),
ok = case io:setopts(standard_error, [{encoding,utf8}]) of
ok -> ok;
{error, _} -> io:setopts(standard_error, [{unicode,true}]) %% R17.3 and earlier
end,
case file:native_name_encoding() of
latin1 ->
io:format(standard_error,
@@ -42,10 +45,29 @@ start(_Type, _Args) ->
ok
end,
elixir_sup:start_link().
URIs = [{<<"ftp">>, 21},
{<<"sftp">>, 22},
{<<"tftp">>, 69},
{<<"http">>, 80},
{<<"https">>, 443},
{<<"ldap">>, 389}],
URIConfig = [{{uri, Scheme}, Port} || {Scheme, Port} <- URIs],
CompilerOpts = [{docs,true},{debug_info,true},{warnings_as_errors,false}],
Config = [{at_exit, []},
{compiler_options, orddict:from_list(CompilerOpts)}
| URIConfig],
Tab = elixir_config:new(Config),
case elixir_sup:start_link() of
{ok, Sup} ->
{ok, Sup, Tab};
{error, _Reason} = Error ->
elixir_config:delete(Tab),
Error
end.
stop(Tab) ->
elixir_config:delete(Tab).
stop(_S) ->
ok.
config_change(_Changed, _New, _Remove) ->
ok.
+1 -1
View File
@@ -44,7 +44,7 @@ define({Line,E}, Kind, Call, Expr) ->
unless_loaded(Fun, Args, Callback) ->
case code:is_loaded(?kernel) of
{_, _} -> apply(?kernel, Fun, Args);
false -> Callback()
false -> Callback()
end.
env() ->
+39 -67
View File
@@ -6,14 +6,11 @@
-define(timeout, 30000).
-record(elixir_code_server, {
compilation_status=[],
argv=[],
loaded=[],
at_exit=[],
pool={[],0},
paths={[],[]},
compiler_options=[{docs,true},{debug_info,true},{warnings_as_errors,false}],
erl_compiler_options=nil
mod_pool={[],0},
mod_ets=dict:new(),
compilation_status=[]
}).
call(Args) ->
@@ -29,14 +26,21 @@ start_link() ->
init(ok) ->
%% We attempt to load those modules here so throughout
%% the codebase we can use a code:is_loaded/1 check on
%% bootstrap instead of using the more expensive
%% code:ensure_loaded/1 multiple times.
%% the codebase we can avoid code:is_loaded/1 checks.
_ = code:ensure_loaded('Elixir.Macro.Env'),
_ = code:ensure_loaded('Elixir.Module.LocalsTracker'),
_ = code:ensure_loaded('Elixir.Kernel.LexicalTracker'),
%% The table where we store module definitions
_ = ets:new(elixir_modules, [set, protected, named_table, {read_concurrency, true}]),
{ok, #elixir_code_server{}}.
handle_call({defmodule, Pid, Tuple}, _From, Config) ->
{Ref, New} = defmodule(Pid, Tuple, Config),
{reply, Ref, New};
handle_call({undefmodule, Ref}, _From, Config) ->
{reply, ok, undefmodule(Ref, Config)};
handle_call({acquire, Path}, From, Config) ->
Current = Config#elixir_code_server.loaded,
case orddict:find(Path, Current) of
@@ -53,18 +57,6 @@ handle_call({acquire, Path}, From, Config) ->
handle_call(loaded, _From, Config) ->
{reply, [F || {F, true} <- Config#elixir_code_server.loaded], 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};
handle_call(compiler_options, _From, Config) ->
{reply, Config#elixir_code_server.compiler_options, Config};
handle_call({compilation_status, CompilerPid}, _From, Config) ->
CompilationStatusList = Config#elixir_code_server.compilation_status,
CompilationStatusListNew = orddict:erase(CompilerPid, CompilationStatusList),
@@ -73,20 +65,11 @@ handle_call({compilation_status, CompilerPid}, _From, Config) ->
Config#elixir_code_server{compilation_status=CompilationStatusListNew}};
handle_call(retrieve_module_name, _From, Config) ->
case Config#elixir_code_server.pool of
case Config#elixir_code_server.mod_pool of
{[H|T], Counter} ->
{reply, module_tuple(H), Config#elixir_code_server{pool={T,Counter}}};
{reply, module_tuple(H), Config#elixir_code_server{mod_pool={T,Counter}}};
{[], Counter} ->
{reply, module_tuple(Counter), Config#elixir_code_server{pool={[],Counter+1}}}
end;
handle_call(erl_compiler_options, _From, Config) ->
case Config#elixir_code_server.erl_compiler_options of
nil ->
Opts = erl_compiler_options(),
{reply, Opts, Config#elixir_code_server{erl_compiler_options=Opts}};
Opts ->
{reply, Opts, Config}
{reply, module_tuple(Counter), Config#elixir_code_server{mod_pool={[],Counter+1}}}
end;
handle_call(paths, _From, Config) ->
@@ -95,20 +78,11 @@ handle_call(paths, _From, Config) ->
handle_call(Request, _From, Config) ->
{stop, {badcall, Request}, Config}.
handle_cast({at_exit, AtExit}, Config) ->
{noreply, Config#elixir_code_server{at_exit=[AtExit|Config#elixir_code_server.at_exit]}};
handle_cast({argv, Argv}, Config) ->
{noreply, Config#elixir_code_server{argv=Argv}};
handle_cast({compiler_options, Options}, Config) ->
Final = orddict:merge(fun(_,_,V) -> V end, Config#elixir_code_server.compiler_options, Options),
{noreply, Config#elixir_code_server{compiler_options=Final}};
handle_cast({register_warning, CompilerPid}, Config) ->
CompilationStatusCurrent = Config#elixir_code_server.compilation_status,
CompilationStatusNew = orddict:store(CompilerPid, error, CompilationStatusCurrent),
case orddict:find(warnings_as_errors, Config#elixir_code_server.compiler_options) of
CompilerOptions = elixir_config:get(compiler_options),
case orddict:find(warnings_as_errors, CompilerOptions) of
{ok, true} -> {noreply, Config#elixir_code_server{compilation_status=CompilationStatusNew}};
_ -> {noreply, Config}
end;
@@ -137,8 +111,8 @@ handle_cast({unload_files, Files}, Config) ->
Unloaded = lists:foldl(fun(File, Acc) -> orddict:erase(File, Acc) end, Current, Files),
{noreply, Config#elixir_code_server{loaded=Unloaded}};
handle_cast({return_module_name, H}, #elixir_code_server{pool={T,Counter}} = Config) ->
{noreply, Config#elixir_code_server{pool={[H|T],Counter}}};
handle_cast({return_module_name, H}, #elixir_code_server{mod_pool={T,Counter}} = Config) ->
{noreply, Config#elixir_code_server{mod_pool={[H|T],Counter}}};
handle_cast({paths, PA, PZ}, #elixir_code_server{} = Config) ->
{noreply, Config#elixir_code_server{paths={PA,PZ}}};
@@ -146,7 +120,10 @@ handle_cast({paths, PA, PZ}, #elixir_code_server{} = Config) ->
handle_cast(Request, Config) ->
{stop, {badcast, Request}, Config}.
handle_info(_Request, Config) ->
handle_info({'DOWN', Ref, process, _Pid, _Reason}, Config) ->
{noreply, undefmodule(Ref, Config)};
handle_info(_Msg, Config) ->
{noreply, Config}.
terminate(_Reason, _Config) ->
@@ -158,22 +135,17 @@ code_change(_Old, Config, _Extra) ->
module_tuple(I) ->
{list_to_atom("elixir_compiler_" ++ integer_to_list(I)), I}.
erl_compiler_options() ->
Key = "ERL_COMPILER_OPTIONS",
case os:getenv(Key) of
false -> [];
Str when is_list(Str) ->
case erl_scan:string(Str) of
{ok,Tokens,_} ->
case erl_parse:parse_term(Tokens ++ [{dot, 1}]) of
{ok,List} when is_list(List) -> List;
{ok,Term} -> [Term];
{error,_Reason} ->
io:format("Ignoring bad term in ~ts\n", [Key]),
[]
end;
{error, {_,_,_Reason}, _} ->
io:format("Ignoring bad term in ~ts\n", [Key]),
[]
end
defmodule(Pid, Tuple, #elixir_code_server{mod_ets=ModEts} = Config) ->
ets:insert(elixir_modules, Tuple),
Ref = erlang:monitor(process, Pid),
Mod = erlang:element(1, Tuple),
{Ref, Config#elixir_code_server{mod_ets=dict:store(Ref, Mod, ModEts)}}.
undefmodule(Ref, #elixir_code_server{mod_ets=ModEts} = Config) ->
case dict:find(Ref, ModEts) of
{ok, Mod} ->
ets:delete(elixir_modules, Mod),
Config#elixir_code_server{mod_ets=dict:erase(Ref, ModEts)};
error ->
Config
end.
+38 -5
View File
@@ -6,7 +6,7 @@
%% Public API
get_opt(Key) ->
Dict = elixir_code_server:call(compiler_options),
Dict = elixir_config:get(compiler_options),
case lists:keyfind(Key, 1, Dict) of
false -> false;
{Key, Value} -> Value
@@ -78,7 +78,7 @@ code_loading_compilation(Forms, Vars, #{line := Line} = E) ->
%% Pass {native, false} to speed up bootstrap
%% process when native is set to true
AllOpts = elixir_code_server:call(erl_compiler_options),
AllOpts = options(),
FinalOpts = AllOpts -- [native, warn_missing_spec],
inner_module(Form, FinalOpts, true, E, fun(_, Binary) ->
%% If we have labeled locals, anonymous functions
@@ -92,6 +92,36 @@ code_loading_compilation(Forms, Vars, #{line := Line} = E) ->
dispatch_loaded(Module, Fun, Args, Purgeable, I, EE)
end).
options() ->
case elixir_config:get(erl_compiler_options) of
nil ->
elixir_config:update(erl_compiler_options, fun options/1);
Opts ->
Opts
end.
options(nil) ->
Key = "ERL_COMPILER_OPTIONS",
case os:getenv(Key) of
false -> [];
Str when is_list(Str) ->
case erl_scan:string(Str) of
{ok,Tokens,_} ->
case erl_parse:parse_term(Tokens ++ [{dot, 1}]) of
{ok,List} when is_list(List) -> List;
{ok,Term} -> [Term];
{error,_Reason} ->
io:format("Ignoring bad term in ~ts\n", [Key]),
[]
end;
{error, {_,_,_Reason}, _} ->
io:format("Ignoring bad term in ~ts\n", [Key]),
[]
end
end;
options(Opts) ->
Opts.
dispatch_loaded(Module, Fun, Args, Purgeable, I, E) ->
Res = Module:Fun(Args),
code:delete(Module),
@@ -142,8 +172,8 @@ module(Forms, Opts, E, Callback) ->
Final =
case (get_opt(debug_info) == true) orelse
lists:member(debug_info, Opts) of
true -> [debug_info] ++ elixir_code_server:call(erl_compiler_options);
false -> elixir_code_server:call(erl_compiler_options)
true -> [debug_info] ++ options();
false -> options()
end,
inner_module(Forms, Final, false, E, Callback).
@@ -177,7 +207,10 @@ no_auto_import() ->
core() ->
{ok, _} = application:ensure_all_started(elixir),
elixir_code_server:cast({compiler_options, [{docs,false},{internal,true}]}),
New = orddict:from_list([{docs,false},{internal,true}]),
Merge = fun(_, _, Value) -> Value end,
Update = fun(Old) -> orddict:merge(Merge, Old, New) end,
_ = elixir_config:update(compiler_options, Update),
[core_file(File) || File <- core_main()].
core_file(File) ->
+65
View File
@@ -0,0 +1,65 @@
-module(elixir_config).
-compile({no_auto_import, [get/1]}).
-export([new/1, delete/1, put/2, get/1, update/2, get_and_put/2]).
-export([start_link/0, init/1, handle_call/3, handle_cast/2,
handle_info/2, code_change/3, terminate/2]).
-behaviour(gen_server).
%% public api
new(Opts) ->
Tab = ets:new(?MODULE, [named_table, public, {read_concurrency, true}]),
true = ets:insert_new(?MODULE, Opts),
Tab.
delete(?MODULE) ->
ets:delete(?MODULE).
put(Key, Value) ->
gen_server:call(?MODULE, {put, Key, Value}).
get(Key) ->
case ets:lookup(?MODULE, Key) of
[{_, Value}] -> Value;
[] -> nil
end.
update(Key, Fun) ->
gen_server:call(?MODULE, {update, Key, Fun}).
get_and_put(Key, Value) ->
gen_server:call(?MODULE, {get_and_put, Key, Value}).
start_link() ->
gen_server:start_link({local, ?MODULE}, ?MODULE, ?MODULE, []).
%% gen_server api
init(Tab) ->
%% Ets table must be writable
public = ets:info(Tab, protection),
{ok, Tab}.
handle_call({put, Key, Value}, _From, Tab) ->
ets:insert(Tab, {Key, Value}),
{reply, ok, Tab};
handle_call({update, Key, Fun}, _From, Tab) ->
Value = Fun(get(Key)),
ets:insert(Tab, {Key, Value}),
{reply, Value, Tab};
handle_call({get_and_put, Key, Value}, _From, Tab) ->
OldValue = get(Key),
ets:insert(Tab, {Key, Value}),
{reply, OldValue, Tab}.
handle_cast(Cast, Tab) ->
{stop, {bad_cast, Cast}, Tab}.
handle_info(_Msg, Tab) ->
{noreply, Tab}.
code_change(_OldVsn, Tab, _Extra) ->
{ok, Tab}.
terminate(_Reason, _Tab) ->
ok.
+2 -2
View File
@@ -3,8 +3,8 @@
handle_info/2, terminate/2, code_change/3, next/0]).
-behaviour(gen_server).
-define(timeout, 1000). %% 1 second
-define(limit, 4294967296). %% 2^32
-define(timeout, 30000). %% 30 seconds
-define(limit, 4294967295). %% 2^32 - 1
next() ->
gen_server:call(?MODULE, next, ?timeout).
+37 -49
View File
@@ -1,49 +1,35 @@
% Holds the logic responsible for function definitions (def(p) and defmacro(p)).
-module(elixir_def).
-export([table/1, clauses_table/1, setup/1,
cleanup/1, reset_last/1, lookup_definition/2,
-export([setup/1, reset_last/1, lookup_definition/2,
delete_definition/2, store_definition/6, unwrap_definitions/2,
store_each/8, format_error/1]).
store_each/7, format_error/1]).
-include("elixir.hrl").
-define(attr, '__def_table').
-define(clauses_attr, '__clauses_table').
-define(last_def, {elixir, last_def}).
-define(attr, {elixir, def_table}).
-define(clauses_attr, {elixir, clauses_table}).
%% Table management functions. Called internally.
table(Module) ->
ets:lookup_element(Module, ?attr, 2).
clauses_table(Module) ->
ets:lookup_element(Module, ?clauses_attr, 2).
setup(Module) ->
ets:insert(Module, {?attr, ets:new(Module, [set, public])}),
ets:insert(Module, {?clauses_attr, ets:new(Module, [bag, public])}),
reset_last(Module),
ok.
cleanup(Module) ->
ets:delete(table(Module)),
ets:delete(clauses_table(Module)).
%% Reset the last item. Useful when evaling code.
reset_last(Module) ->
ets:insert(table(Module), {last, []}).
ets:insert(elixir_module:data_table(Module), {?last_def, []}).
%% Looks up a definition from the database.
lookup_definition(Module, Tuple) ->
case ets:lookup(table(Module), Tuple) of
case ets:lookup(elixir_module:defs_table(Module), Tuple) of
[Result] ->
CTable = clauses_table(Module),
CTable = elixir_module:clas_table(Module),
{Result, [Clause || {_, Clause} <- ets:lookup(CTable, Tuple)]};
_ ->
false
end.
delete_definition(Module, Tuple) ->
ets:delete(table(Module), Tuple),
ets:delete(clauses_table(Module), Tuple).
ets:delete(elixir_module:defs_table(Module), Tuple),
ets:delete(elixir_module:clas_table(Module), Tuple).
% Invoked by the wrap definition with the function abstract tree.
% Each function is then added to the function table.
@@ -98,16 +84,12 @@ store_definition(Line, Kind, CheckClauses, Name, Args, Guards, Body, KeepLocatio
DefaultsLength = length(Defaults),
elixir_locals:record_defaults(Tuple, Kind, Module, DefaultsLength),
File = ?m(E, file),
Table = table(Module),
CTable = clauses_table(Module),
File = ?m(E, file),
compile_super(Module, Super, E),
check_previous_defaults(Table, Line, Name, Arity, Kind, DefaultsLength, E),
check_previous_defaults(Line, Module, Name, Arity, Kind, DefaultsLength, E),
store_each(CheckClauses, Kind, File, Location,
Table, CTable, DefaultsLength, Function),
[store_each(false, Kind, File, Location, Table, CTable, 0,
store_each(CheckClauses, Kind, File, Location, Module, DefaultsLength, Function),
[store_each(false, Kind, File, Location, Module, 0,
default_function_for(Kind, Name, Default)) || Default <- Defaults],
make_struct_available(Kind, Module, Name, Args),
@@ -166,7 +148,7 @@ normalize_location(File) ->
%% Compile super
compile_super(Module, true, #{function := Function}) ->
elixir_def_overridable:store(Module, Function, true);
elixir_def_overridable:super(Module, Function);
compile_super(_Module, _, _E) -> ok.
%% Translate the given call and expression given
@@ -176,7 +158,7 @@ translate_definition(Kind, Line, Module, Name, Args, Guards, Body, E) when is_in
Arity = length(Args),
{EArgs, EGuards, EBody, _} = elixir_exp_clauses:def(fun elixir_def_defaults:expand/2,
Args, Guards, expr_from_body(Line, Body), E),
Args, Guards, expr_from_body(Line, Body), E),
Body == nil andalso check_args_for_bodyless_clause(Line, EArgs, E),
@@ -229,9 +211,8 @@ is_macro(_) -> false.
% It returns a list of all functions to be exported, plus the macros,
% and the body of all functions.
unwrap_definitions(File, Module) ->
Table = table(Module),
CTable = clauses_table(Module),
ets:delete(Table, last),
Table = elixir_module:defs_table(Module),
CTable = elixir_module:clas_table(Module),
{All, Private} = unwrap_definition(ets:tab2list(Table), File, Module, CTable, [], []),
Unreachable = elixir_locals:warn_unused_local(File, Module, Private),
@@ -327,26 +308,33 @@ warn_bodyless_function(Line, File, _Module, Kind, Tuple) ->
%% This function also checks and emit warnings in case
%% the kind, of the visibility of the function changes.
store_each(Check, Kind, File, Location, Table, CTable, Defaults, {function, Line, Name, Arity, Clauses}) ->
Tuple = {Name, Arity},
store_each(Check, Kind, File, Location, Module, Defaults, {function, Line, Name, Arity, Clauses}) ->
Data = elixir_module:data_table(Module),
Defs = elixir_module:defs_table(Module),
Clas = elixir_module:clas_table(Module),
Tuple = {Name, Arity},
HasBody = Clauses =/= [],
case ets:lookup(Table, Tuple) of
[{Tuple, StoredKind, StoredLine, StoredFile, StoredCheck, StoredLocation, {StoredDefaults, LastHasBody, LastDefaults}}] ->
case ets:lookup(Defs, Tuple) of
[{Tuple, StoredKind, StoredLine, StoredFile, StoredCheck,
StoredLocation, {StoredDefaults, LastHasBody, LastDefaults}}] ->
FinalLine = StoredLine,
FinalLocation = StoredLocation,
FinalDefaults = {max(Defaults, StoredDefaults), HasBody, Defaults},
check_valid_kind(Line, File, Name, Arity, Kind, StoredKind),
(Check and StoredCheck) andalso
check_valid_clause(Line, File, Name, Arity, Kind, Table, StoredLine, StoredFile),
check_valid_clause(Line, File, Name, Arity, Kind, Data, StoredLine, StoredFile),
check_valid_defaults(Line, File, Name, Arity, Kind, Defaults, LastDefaults, LastHasBody);
[] ->
FinalLine = Line,
FinalLocation = Location,
FinalDefaults = {Defaults, HasBody, Defaults}
end,
Check andalso ets:insert(Table, {last, {Name, Arity}}),
ets:insert(CTable, [{Tuple, Clause} || Clause <- Clauses ]),
ets:insert(Table, {Tuple, Kind, FinalLine, File, Check, FinalLocation, FinalDefaults}).
Check andalso ets:insert(Data, {?last_def, {Name, Arity}}),
ets:insert(Clas, [{Tuple, Clause} || Clause <- Clauses]),
ets:insert(Defs, {Tuple, Kind, FinalLine, File, Check, FinalLocation, FinalDefaults}).
%% Validations
@@ -355,8 +343,8 @@ check_valid_kind(Line, File, Name, Arity, Kind, StoredKind) ->
elixir_errors:form_error([{line, Line}], File, ?MODULE,
{changed_kind, {Name, Arity, StoredKind, Kind}}).
check_valid_clause(Line, File, Name, Arity, Kind, Table, StoredLine, StoredFile) ->
case ets:lookup_element(Table, last, 2) of
check_valid_clause(Line, File, Name, Arity, Kind, Data, StoredLine, StoredFile) ->
case ets:lookup_element(Data, ?last_def, 2) of
{Name,Arity} -> [];
[] -> [];
_ ->
@@ -379,8 +367,8 @@ check_valid_defaults(Line, File, Name, Arity, Kind, _, _, _) ->
elixir_errors:form_error([{line, Line}], File, ?MODULE,
{clauses_with_defaults, {Kind, Name, Arity}}).
check_previous_defaults(Table, Line, Name, Arity, Kind, Defaults, E) ->
Matches = ets:match(Table, {{Name, '$2'}, '$1', '_', '_', '_', '_', {'$3', '_', '_'}}),
check_previous_defaults(Line, Module, Name, Arity, Kind, Defaults, E) ->
Matches = ets:match(elixir_module:defs_table(Module), {{Name, '$2'}, '$1', '_', '_', '_', '_', {'$3', '_', '_'}}),
[ begin
elixir_errors:form_error([{line, Line}], ?m(E, file), ?MODULE,
{defs_with_defaults, Name, {Kind, Arity}, {K, A}})
+2 -1
View File
@@ -4,10 +4,11 @@
-include("elixir.hrl").
expand(Args, E) ->
NoContext = E#{context := nil},
lists:mapfoldl(fun
({'\\\\', Meta, [Left, Right]}, Acc) ->
{ELeft, EL} = elixir_exp:expand(Left, Acc),
{ERight, _} = elixir_exp:expand(Right, Acc#{context := nil}),
{ERight, _} = elixir_exp:expand(Right, NoContext),
{{'\\\\', Meta, [ELeft, ERight]}, EL};
(Left, Acc) ->
elixir_exp:expand(Left, Acc)
+15 -11
View File
@@ -1,14 +1,18 @@
% Holds the logic responsible for defining overridable functions and handling super.
-module(elixir_def_overridable).
-export([store_pending/1, ensure_defined/4,
name/2, store/3, format_error/1]).
-export([setup/1, overridable/1, overridable/2, name/2, super/2, store_pending/1,
ensure_defined/4, format_error/1]).
-include("elixir.hrl").
-define(attr, {elixir, overridable}).
setup(Module) ->
overridable(Module, []).
overridable(Module) ->
ets:lookup_element(elixir_module:data_table(Module), '__overridable', 2).
ets:lookup_element(elixir_module:data_table(Module), ?attr, 2).
overridable(Module, Value) ->
ets:insert(elixir_module:data_table(Module), {'__overridable', Value}).
ets:insert(elixir_module:data_table(Module), {?attr, Value}).
%% Check if an overridable function is defined.
@@ -43,19 +47,19 @@ store(Module, Function, GenerateName) ->
false -> {Kind, Name}
end,
case code:is_loaded('Elixir.Module.LocalsTracker') of
{_, _} ->
case elixir_compiler:get_opt(internal) of
false ->
'Elixir.Module.LocalsTracker':reattach(Module, Kind, {Name, Arity}, Neighbours);
_ ->
true ->
ok
end,
Def = {function, Line, FinalName, Arity, Clauses},
elixir_def:store_each(false, FinalKind, File, Location,
elixir_def:table(Module), elixir_def:clauses_table(Module), Defaults, Def)
elixir_def:store_each(false, FinalKind, File, Location, Module, Defaults, Def)
end.
%% Store pending declarations that were not manually made concrete.
super(Module, Function) ->
store(Module, Function, true).
store_pending(Module) ->
_ = [store(Module, X, false) || {X, {_, _, _, false}} <- overridable(Module),
@@ -73,4 +77,4 @@ format_error({no_super, Module, {Name, Arity}}) ->
format_fa({Name, Arity}) ->
A = atom_to_binary(Name, utf8),
B = integer_to_binary(Arity),
<< A/binary, $/, B/binary >>.
<<A/binary, $/, B/binary>>.
+8 -196
View File
@@ -9,20 +9,7 @@
find_import/4, format_error/1]).
-include("elixir.hrl").
-import(ordsets, [is_element/2]).
-define(atom, 'Elixir.Atom').
-define(float, 'Elixir.Float').
-define(io, 'Elixir.IO').
-define(integer, 'Elixir.Integer').
-define(kernel, 'Elixir.Kernel').
-define(list, 'Elixir.List').
-define(map, 'Elixir.Map').
-define(node, 'Elixir.Node').
-define(port, 'Elixir.Port').
-define(process, 'Elixir.Process').
-define(string, 'Elixir.String').
-define(system, 'Elixir.System').
-define(tuple, 'Elixir.Tuple').
default_functions() ->
[{?kernel, elixir_imported_functions()}].
@@ -82,7 +69,7 @@ remote_function(Meta, Receiver, Name, Arity, E) ->
check_deprecation(Meta, Receiver, Name, Arity, E),
elixir_lexical:record_remote(Receiver, ?m(E, lexical_tracker)),
case inline(Receiver, Name, Arity) of
case elixir_rewrite:inline(Receiver, Name, Arity) of
{AR, AN} -> {remote, AR, AN, Arity};
false -> {remote, Receiver, Name, Arity}
end.
@@ -103,9 +90,9 @@ dispatch_import(Meta, Name, Args, E, Callback) ->
dispatch_require(Meta, Receiver, Name, Args, E, Callback) when is_atom(Receiver) ->
Arity = length(Args),
case rewrite(Receiver, Name, Args, Arity) of
{ok, AR, AN, AA} ->
Callback(AR, AN, AA);
case elixir_rewrite:inline(Receiver, Name, Arity) of
{AR, AN} ->
Callback(AR, AN, Args);
false ->
case expand_require(Meta, Receiver, {Name, Arity}, Args, E) of
{ok, Receiver, Quoted} -> expand_quoted(Meta, Receiver, Name, Arity, Quoted, E);
@@ -151,11 +138,7 @@ do_expand_import(Meta, {Name, Arity} = Tuple, Args, Module, E, Result) ->
{function, Receiver} ->
elixir_lexical:record_import(Receiver, ?m(E, lexical_tracker)),
elixir_locals:record_import(Tuple, Receiver, Module, ?m(E, function)),
case rewrite(Receiver, Name, Args, Arity) of
{ok, _, _, _} = Res -> Res;
false -> {ok, Receiver, Name, Args}
end;
{ok, Receiver, Name, Args};
{macro, Receiver} ->
check_deprecation(Meta, Receiver, Name, Arity, E),
elixir_lexical:record_import(Receiver, ?m(E, lexical_tracker)),
@@ -167,8 +150,8 @@ do_expand_import(Meta, {Name, Arity} = Tuple, Args, Module, E, Result) ->
error -> {ok, Receiver, Name, Args}
end;
false when Module == ?kernel ->
case rewrite(Module, Name, Args, Arity) of
{ok, _, _, _} = Res -> Res;
case elixir_rewrite:inline(Module, Name, Arity) of
{AR, AN} -> {ok, AR, AN, Args};
false -> error
end;
false ->
@@ -232,7 +215,7 @@ expand_quoted(Meta, Receiver, Name, Arity, Quoted, E) ->
end.
caller(Line, #{module := nil} = E) ->
{elixir_compiler, '__FILE__', 2, location(Line, E)};
{elixir_compiler_0, '__FILE__', 1, location(Line, E)};
caller(Line, #{module := Module, function := nil} = E) ->
{Module, '__MODULE__', 0, location(Line, E)};
caller(Line, #{module := Module, function := {Name, Arity}} = E) ->
@@ -342,163 +325,6 @@ elixir_imported_macros() ->
error:undef -> []
end.
rewrite(?atom, to_string, [Arg], _) ->
{ok, erlang, atom_to_binary, [Arg, utf8]};
rewrite(?kernel, elem, [Tuple, Index], _) ->
{ok, erlang, element, [increment(Index), Tuple]};
rewrite(?kernel, put_elem, [Tuple, Index, Value], _) ->
{ok, erlang, setelement, [increment(Index), Tuple, Value]};
rewrite(?map, 'has_key?', [Map, Key], _) ->
{ok, maps, is_key, [Key, Map]};
rewrite(?map, fetch, [Map, Key], _) ->
{ok, maps, find, [Key, Map]};
rewrite(?map, put, [Map, Key, Value], _) ->
{ok, maps, put, [Key, Value, Map]};
rewrite(?map, delete, [Map, Key], _) ->
{ok, maps, remove, [Key, Map]};
rewrite(?process, monitor, [Arg], _) ->
{ok, erlang, monitor, [process, Arg]};
rewrite(?string, to_atom, [Arg], _) ->
{ok, erlang, binary_to_atom, [Arg, utf8]};
rewrite(?string, to_existing_atom, [Arg], _) ->
{ok, erlang, binary_to_existing_atom, [Arg, utf8]};
rewrite(?tuple, insert_at, [Tuple, Index, Term], _) ->
{ok, erlang, insert_element, [increment(Index), Tuple, Term]};
rewrite(?tuple, delete_at, [Tuple, Index], _) ->
{ok, erlang, delete_element, [increment(Index), Tuple]};
rewrite(?tuple, duplicate, [Data, Size], _) ->
{ok, erlang, make_tuple, [Size, Data]};
rewrite(Receiver, Name, Args, Arity) ->
case inline(Receiver, Name, Arity) of
{AR, AN} -> {ok, AR, AN, Args};
false -> false
end.
increment(Number) when is_number(Number) ->
Number + 1;
increment(Other) ->
{{'.', [], [erlang, '+']}, [], [Other, 1]}.
inline(?atom, to_char_list, 1) -> {erlang, atom_to_list};
inline(?io, iodata_length, 1) -> {erlang, iolist_size};
inline(?io, iodata_to_binary, 1) -> {erlang, iolist_to_binary};
inline(?integer, to_string, 1) -> {erlang, integer_to_binary};
inline(?integer, to_string, 2) -> {erlang, integer_to_binary};
inline(?integer, to_char_list, 1) -> {erlang, integer_to_list};
inline(?integer, to_char_list, 2) -> {erlang, integer_to_list};
inline(?float, to_string, 1) -> {erlang, float_to_binary};
inline(?float, to_char_list, 1) -> {erlang, float_to_list};
inline(?list, to_atom, 1) -> {erlang, list_to_atom};
inline(?list, to_existing_atom, 1) -> {erlang, list_to_existing_atom};
inline(?list, to_float, 1) -> {erlang, list_to_float};
inline(?list, to_integer, 1) -> {erlang, list_to_integer};
inline(?list, to_integer, 2) -> {erlang, list_to_integer};
inline(?list, to_tuple, 1) -> {erlang, list_to_tuple};
inline(?kernel, '+', 2) -> {erlang, '+'};
inline(?kernel, '-', 2) -> {erlang, '-'};
inline(?kernel, '+', 1) -> {erlang, '+'};
inline(?kernel, '-', 1) -> {erlang, '-'};
inline(?kernel, '*', 2) -> {erlang, '*'};
inline(?kernel, '/', 2) -> {erlang, '/'};
inline(?kernel, '++', 2) -> {erlang, '++'};
inline(?kernel, '--', 2) -> {erlang, '--'};
inline(?kernel, 'not', 1) -> {erlang, 'not'};
inline(?kernel, '<', 2) -> {erlang, '<'};
inline(?kernel, '>', 2) -> {erlang, '>'};
inline(?kernel, '<=', 2) -> {erlang, '=<'};
inline(?kernel, '>=', 2) -> {erlang, '>='};
inline(?kernel, '==', 2) -> {erlang, '=='};
inline(?kernel, '!=', 2) -> {erlang, '/='};
inline(?kernel, '===', 2) -> {erlang, '=:='};
inline(?kernel, '!==', 2) -> {erlang, '=/='};
inline(?kernel, abs, 1) -> {erlang, abs};
inline(?kernel, apply, 2) -> {erlang, apply};
inline(?kernel, apply, 3) -> {erlang, apply};
inline(?kernel, binary_part, 3) -> {erlang, binary_part};
inline(?kernel, bit_size, 1) -> {erlang, bit_size};
inline(?kernel, byte_size, 1) -> {erlang, byte_size};
inline(?kernel, 'div', 2) -> {erlang, 'div'};
inline(?kernel, exit, 1) -> {erlang, exit};
inline(?kernel, hd, 1) -> {erlang, hd};
inline(?kernel, is_atom, 1) -> {erlang, is_atom};
inline(?kernel, is_binary, 1) -> {erlang, is_binary};
inline(?kernel, is_bitstring, 1) -> {erlang, is_bitstring};
inline(?kernel, is_boolean, 1) -> {erlang, is_boolean};
inline(?kernel, is_float, 1) -> {erlang, is_float};
inline(?kernel, is_function, 1) -> {erlang, is_function};
inline(?kernel, is_function, 2) -> {erlang, is_function};
inline(?kernel, is_integer, 1) -> {erlang, is_integer};
inline(?kernel, is_list, 1) -> {erlang, is_list};
inline(?kernel, is_map, 1) -> {erlang, is_map};
inline(?kernel, is_number, 1) -> {erlang, is_number};
inline(?kernel, is_pid, 1) -> {erlang, is_pid};
inline(?kernel, is_port, 1) -> {erlang, is_port};
inline(?kernel, is_reference, 1) -> {erlang, is_reference};
inline(?kernel, is_tuple, 1) -> {erlang, is_tuple};
inline(?kernel, length, 1) -> {erlang, length};
inline(?kernel, make_ref, 0) -> {erlang, make_ref};
inline(?kernel, map_size, 1) -> {erlang, map_size};
inline(?kernel, max, 2) -> {erlang, max};
inline(?kernel, min, 2) -> {erlang, min};
inline(?kernel, node, 0) -> {erlang, node};
inline(?kernel, node, 1) -> {erlang, node};
inline(?kernel, 'rem', 2) -> {erlang, 'rem'};
inline(?kernel, round, 1) -> {erlang, round};
inline(?kernel, self, 0) -> {erlang, self};
inline(?kernel, send, 2) -> {erlang, send};
inline(?kernel, spawn, 1) -> {erlang, spawn};
inline(?kernel, spawn, 3) -> {erlang, spawn};
inline(?kernel, spawn_link, 1) -> {erlang, spawn_link};
inline(?kernel, spawn_link, 3) -> {erlang, spawn_link};
inline(?kernel, spawn_monitor, 1) -> {erlang, spawn_monitor};
inline(?kernel, spawn_monitor, 3) -> {erlang, spawn_monitor};
inline(?kernel, throw, 1) -> {erlang, throw};
inline(?kernel, tl, 1) -> {erlang, tl};
inline(?kernel, trunc, 1) -> {erlang, trunc};
inline(?kernel, tuple_size, 1) -> {erlang, tuple_size};
inline(?map, keys, 1) -> {maps, keys};
inline(?map, merge, 2) -> {maps, merge};
inline(?map, size, 1) -> {maps, size};
inline(?map, values, 1) -> {maps, values};
inline(?map, to_list, 1) -> {maps, to_list};
inline(?node, spawn, 2) -> {erlang, spawn};
inline(?node, spawn, 3) -> {erlang, spawn_opt};
inline(?node, spawn, 4) -> {erlang, spawn};
inline(?node, spawn, 5) -> {erlang, spawn_opt};
inline(?node, spawn_link, 2) -> {erlang, spawn_link};
inline(?node, spawn_link, 4) -> {erlang, spawn_link};
inline(?process, exit, 2) -> {erlang, exit};
inline(?process, spawn, 2) -> {erlang, spawn_opt};
inline(?process, spawn, 4) -> {erlang, spawn_opt};
inline(?process, demonitor, 1) -> {erlang, demonitor};
inline(?process, demonitor, 2) -> {erlang, demonitor};
inline(?process, link, 1) -> {erlang, link};
inline(?process, unlink, 1) -> {erlang, unlink};
inline(?port, open, 2) -> {erlang, open_port};
inline(?port, call, 3) -> {erlang, port_call};
inline(?port, close, 1) -> {erlang, port_close};
inline(?port, command, 2) -> {erlang, port_command};
inline(?port, command, 3) -> {erlang, port_command};
inline(?port, connect, 2) -> {erlang, port_connect};
inline(?port, control, 3) -> {erlang, port_control};
inline(?port, info, 1) -> {erlang, port_info};
inline(?port, info, 2) -> {erlang, port_info};
inline(?port, list, 0) -> {erlang, ports};
inline(?string, to_float, 1) -> {erlang, binary_to_float};
inline(?string, to_integer, 1) -> {erlang, binary_to_integer};
inline(?string, to_integer, 2) -> {erlang, binary_to_integer};
inline(?system, stacktrace, 0) -> {erlang, get_stacktrace};
inline(?tuple, to_list, 1) -> {erlang, tuple_to_list};
inline(_, _, _) -> false.
check_deprecation(Meta, Receiver, Name, Arity, #{file := File}) ->
case deprecation(Receiver, Name, Arity) of
false -> ok;
@@ -522,19 +348,5 @@ deprecation_message(Warning, Message) ->
Message -> Warning ++ ", " ++ Message
end.
deprecation('Elixir.Collectable', 'empty', 1) ->
true;
deprecation('Elixir.Enum', 'traverse', 2) ->
true;
deprecation('Elixir.Kernel', 'nil?', 1) ->
true;
deprecation('Elixir.Record', 'record?', 1) ->
true;
deprecation('Elixir.Record', 'record?', 2) ->
true;
deprecation('Elixir.Integer', 'even?', 1) ->
true;
deprecation('Elixir.Integer', 'odd?', 1) ->
true;
deprecation(_, _, _) ->
false.
+31 -7
View File
@@ -6,12 +6,12 @@
handle_file_warning/2, handle_file_warning/3, handle_file_error/2]).
-include("elixir.hrl").
-spec warn(non_neg_integer(), binary(), iolist()) -> ok.
-spec warn(non_neg_integer(), binary(), unicode:chardata()) -> ok.
warn(Line, File, Warning) when is_integer(Line), is_binary(File) ->
warn(file_format(Line, File), Warning).
-spec warn(iolist(), iolist()) -> ok.
-spec warn(unicode:chardata(), unicode:chardata()) -> ok.
warn(Caller, Warning) ->
do_warn([Caller, "warning: ", Warning, $\n]).
@@ -31,8 +31,8 @@ form_warn(Meta, File, Module, Desc) when is_list(Meta) ->
%% Compilation error.
-spec compile_error(list(), binary(), iolist()) -> no_return().
-spec compile_error(list(), binary(), iolist(), list()) -> no_return().
-spec compile_error(list(), binary(), unicode:charlist()) -> no_return().
-spec compile_error(list(), binary(), string(), list()) -> no_return().
compile_error(Meta, File, Message) when is_list(Message) ->
raise(Meta, File, 'Elixir.CompileError', elixir_utils:characters_to_binary(Message)).
@@ -55,7 +55,23 @@ parse_error(Line, File, Error, <<>>) ->
parse_error(Line, File, <<"syntax error before: ">>, <<"'end'">>) ->
do_raise(Line, File, 'Elixir.SyntaxError', <<"unexpected token: end">>);
%% Binaries are wrapped in [<<...>>], so we need to unwrap them
%% Produce a human-readable message for errors before a sigil
parse_error(Line, File, <<"syntax error before: ">>, <<"{sigil,", _Rest/binary>> = Full) ->
{ok, Tokens, _} = erl_scan:string(binary_to_list(Full)),
{ok, {sigil, _, Sigil, [Content], _}} = erl_parse:parse_term(Tokens ++ [{dot, 1}]),
Message = <<"syntax error before: sigil ~", Sigil," with content '", Content/binary, "'">>,
do_raise(Line, File, 'Elixir.SyntaxError', Message);
%% Aliases are wrapped in ['']
parse_error(Line, File, Error, <<"['", Token/binary>>) when is_binary(Error) ->
Rest =
case binary:split(Token, <<"'">>) of
[Part, _] -> Part;
_ -> <<>>
end,
do_raise(Line, File, 'Elixir.SyntaxError', <<Error/binary, Rest/binary>>);
%% Binaries (and interpolation) are wrapped in [<<...>>]
parse_error(Line, File, Error, <<"[", _/binary>> = Full) when is_binary(Error) ->
Rest =
case binary:split(Full, <<"<<">>) of
@@ -127,10 +143,13 @@ handle_file_warning(_, File, {Line,erl_lint,{undefined_behaviour,Module}}) ->
handle_file_warning(_, _File, {Line,erl_lint,{unused_var,_Var}}) when Line =< 0 ->
ok;
%% Ignore shadowed vars as we guarantee no conflicts ourselves
%% Ignore shadowed and exported vars as we guarantee no conflicts ourselves
handle_file_warning(_, _File, {_Line,erl_lint,{shadowed_var,_Var,_Where}}) ->
ok;
handle_file_warning(_, _File, {_Line,erl_lint,{exported_var,_Var,_Where}}) ->
ok;
%% Rewrite nomatch_guard to be more generic it can happen inside if, unless, etc
handle_file_warning(_, File, {Line, sys_core_fold, nomatch_guard}) ->
warn(Line, File, "this check/guard will always yield the same result");
@@ -140,6 +159,11 @@ handle_file_warning(_, File, {Line,erl_lint,{unused_var,Var}}) ->
Message = format_error(erl_lint, {unused_var, format_var(Var)}),
warn(Line, File, Message);
%% Handle literal eval failures
handle_file_warning(_, File, {Line,sys_core_fold,{eval_failure, Error}}) ->
#{'__struct__' := Struct} = 'Elixir.Exception':normalize(error, Error),
warn(Line, File, ["this expression will fail with ", elixir_aliases:inspect(Struct)]);
%% Default behaviour
handle_file_warning(_, File, {Line,Module,Desc}) ->
Message = format_error(Module, Desc),
@@ -241,4 +265,4 @@ do_raise(Line, File, Kind, Message) when is_integer(Line), is_binary(File), is_b
end,
Stacktrace = erlang:get_stacktrace(),
Exception = Kind:exception([{description, Message}, {file, File}, {line, Line}]),
erlang:raise(error, Exception, tl(Stacktrace)).
erlang:raise(error, Exception, tl(Stacktrace)).
+4 -3
View File
@@ -474,7 +474,8 @@ expand_remote(Receiver, DotMeta, Right, Meta, Args, E, EL) ->
true -> ok
end,
{EArgs, EA} = expand_args(Args, E),
{{{'.', DotMeta, [Receiver, Right]}, Meta, EArgs}, elixir_env:mergev(EL, EA)}.
{elixir_rewrite:rewrite(Receiver, DotMeta, Right, Meta, EArgs),
elixir_env:mergev(EL, EA)}.
%% Lexical helpers
@@ -489,8 +490,8 @@ validate_opts(Meta, Kind, Allowed, Opts, E) when is_list(Opts) ->
"unsupported option ~ts given to ~s", ['Elixir.Kernel':inspect(Key), Kind])
end || {Key, _} <- Opts, not lists:member(Key, Allowed)];
validate_opts(Meta, Kind, _Allowed, _Opts, S) ->
compile_error(Meta, S#elixir_scope.file, "invalid options for ~s, expected a keyword list", [Kind]).
validate_opts(Meta, Kind, _Allowed, _Opts, E) ->
compile_error(Meta, ?m(E, file), "invalid options for ~s, expected a keyword list", [Kind]).
no_alias_opts(KV) when is_list(KV) ->
case lists:keyfind(as, 1, KV) of
+3 -1
View File
@@ -101,7 +101,9 @@ do_receive(Meta, {Key, _}, _Acc, E) ->
%% Try
'try'(Meta, [], E) ->
compile_error(Meta, ?m(E, file), "missing do keywords in try");
compile_error(Meta, ?m(E, file), "missing do keyword in try");
'try'(Meta, [{do, _}], E) ->
compile_error(Meta, ?m(E, file), "missing catch/rescue/after/else keyword in try");
'try'(Meta, KV, E) when not is_list(KV) ->
elixir_errors:compile_error(Meta, ?m(E, file), "invalid arguments for try");
'try'(Meta, KV, E) ->
+5 -33
View File
@@ -126,29 +126,6 @@ collect_filters([H|T], Acc) ->
collect_filters([], Acc) ->
{Acc, []}.
%% If all we have is one enum generator, we check if it is a list
%% for optimization otherwise fallback to the reduce generator.
build_inline(Line, [{enum, Meta, Left, Right, Filters}] = Orig, Expr, Into, Var, Acc, S) ->
case Right of
{cons, _, _, _} ->
build_comprehension(Line, Orig, Expr, Into);
{Other, _, _} when Other == tuple; Other == map ->
build_reduce(Orig, Expr, Into, Acc, S);
_ ->
Clauses = [{enum, Meta, Left, Var, Filters}],
{'case', -1, Right, [
{clause, -1,
[Var],
[[elixir_utils:erl_call(Line, erlang, is_list, [Var])]],
[build_comprehension(Line, Clauses, Expr, Into)]},
{clause, -1,
[Var],
[],
[build_reduce(Clauses, Expr, Into, Acc, S)]}
]}
end;
build_inline(Line, Clauses, Expr, Into, _Var, Acc, S) ->
case lists:all(fun(Clause) -> element(1, Clause) == bin end, Clauses) of
true -> build_comprehension(Line, Clauses, Expr, Into);
@@ -165,7 +142,7 @@ build_into(Line, Clauses, Expr, Into, Fun, Acc, S) ->
MatchExpr = {match, Line,
{tuple, Line, [Acc, Fun]},
elixir_utils:erl_call(Line, 'Elixir.Collectable', into, [Into])
},
},
TryExpr =
{'try', Line,
@@ -199,10 +176,8 @@ build_reduce(Clauses, Expr, {bin, _, _} = Into, Acc, S) ->
build_reduce_clause([{enum, Meta, Left, Right, Filters}|T], Expr, Arg, Acc, S) ->
Line = ?line(Meta),
Inner = build_reduce_clause(T, Expr, Acc, Acc, S),
True = pair(Line, cont, Inner),
False = pair(Line, cont, Acc),
True = build_reduce_clause(T, Expr, Acc, Acc, S),
False = Acc,
Clauses0 =
case is_var(Left) of
@@ -218,11 +193,8 @@ build_reduce_clause([{enum, Meta, Left, Right, Filters}|T], Expr, Arg, Acc, S) -
[Left, Acc], [],
[join_filters(Line, Filters, True, False)]}|Clauses0],
Args = [Right, pair(Line, cont, Arg), {'fun', Line, {clauses, Clauses1}}],
Tuple = elixir_utils:erl_call(Line, 'Elixir.Enumerable', reduce, Args),
%% Use -1 because in case of no returns we don't care about the result
elixir_utils:erl_call(-1, erlang, element, [{integer, Line, 2}, Tuple]);
Args = [Right, Arg, {'fun', Line, {clauses, Clauses1}}],
elixir_utils:erl_call(Line, 'Elixir.Enum', reduce, Args);
build_reduce_clause([{bin, Meta, Left, Right, Filters}|T], Expr, Arg, Acc, S) ->
Line = ?line(Meta),
+12 -10
View File
@@ -10,17 +10,19 @@
-define(tracker, 'Elixir.Kernel.LexicalTracker').
run(File, Dest, Callback) ->
case code:is_loaded(?tracker) of
{file, _} ->
{ok, Pid} = ?tracker:start_link(Dest),
try
Callback(Pid)
after
warn_unused_aliases(File, Pid),
warn_unused_imports(File, Pid),
unlink(Pid), ?tracker:stop(Pid)
end;
case elixir_compiler:get_opt(internal) of
false ->
{ok, Pid} = ?tracker:start_link(Dest),
try Callback(Pid) of
Res ->
warn_unused_aliases(File, Pid),
warn_unused_imports(File, Pid),
Res
after
unlink(Pid),
?tracker:stop(Pid)
end;
true ->
Callback(nil)
end.
+8 -8
View File
@@ -9,7 +9,7 @@
-export([macro_for/3, local_for/3, local_for/4]).
-include("elixir.hrl").
-define(attr, '__locals_tracker').
-define(attr, {elixir, locals_tracker}).
-define(tracker, 'Elixir.Module.LocalsTracker').
macro_for(Module, Name, Arity) ->
@@ -72,12 +72,12 @@ rewrite_clause(Else, _) -> Else.
%% TRACKING
setup(Module) ->
case code:is_loaded(?tracker) of
{file, _} ->
{ok, Pid} = ?tracker:start_link(),
ets:insert(Module, {?attr, Pid}),
ok;
case elixir_compiler:get_opt(internal) of
false ->
{ok, Pid} = ?tracker:start_link(),
ets:insert(elixir_module:data_table(Module), {?attr, Pid}),
ok;
true ->
ok
end.
@@ -108,7 +108,7 @@ if_tracker(Module, Callback) ->
if_tracker(Module, ok, Callback).
if_tracker(Module, Default, Callback) ->
try ets:lookup_element(Module, ?attr, 2) of
try ets:lookup_element(elixir_module:data_table(Module), ?attr, 2) of
Pid -> Callback(Pid)
catch
error:badarg -> Default
@@ -118,7 +118,7 @@ if_tracker(Module, Default, Callback) ->
cache_env(#{module := Module} = RE) ->
E = RE#{line := nil,vars := []},
try ets:lookup_element(Module, ?attr, 2) of
try ets:lookup_element(elixir_module:data_table(Module), ?attr, 2) of
Pid ->
{Pid, ?tracker:cache_env(Pid, E)}
catch
+15 -8
View File
@@ -5,9 +5,11 @@
expand_map(Meta, [{'|', UpdateMeta, [Left, Right]}], E) ->
{[ELeft|ERight], EA} = elixir_exp:expand_args([Left|Right], E),
validate_kv(Meta, ERight, Right, E),
{{'%{}', Meta, [{'|', UpdateMeta, [ELeft, ERight]}]}, EA};
expand_map(Meta, Args, E) ->
{EArgs, EA} = elixir_exp:expand_args(Args, E),
validate_kv(Meta, EArgs, Args, E),
{{'%{}', Meta, EArgs}, EA}.
expand_struct(Meta, Left, Right, E) ->
@@ -35,6 +37,15 @@ expand_struct(Meta, Left, Right, E) ->
{{'%', EMeta, [ELeft, ERight]}, EE}.
validate_kv(Meta, KV, Original, E) ->
lists:foldl(fun
({_K, _V}, Acc) -> Acc + 1;
(_, Acc) ->
compile_error(Meta, ?m(E, file),
"expected key-value pairs in a map, got: ~ts",
['Elixir.Macro':to_string(lists:nth(Acc, Original))])
end, 1, KV).
translate_map(Meta, Args, S) ->
{Assocs, TUpdate, US} = extract_assoc_update(Args, S),
translate_map(Meta, Assocs, TUpdate, US).
@@ -139,14 +150,10 @@ translate_map(Meta, Assocs, TUpdate, #elixir_scope{extra=Extra} = S) ->
Line = ?line(Meta),
{TArgs, SA} = lists:mapfoldl(fun
({Key, Value}, Acc) ->
{TKey, Acc1} = KeyFun(Key, Acc),
{TValue, Acc2} = ValFun(Value, Acc1#elixir_scope{extra=Extra}),
{{Op, ?line(Meta), TKey, TValue}, Acc2};
(Other, _Acc) ->
compile_error(Meta, S#elixir_scope.file, "expected key-value pairs in map, got: ~ts",
['Elixir.Macro':to_string(Other)])
{TArgs, SA} = lists:mapfoldl(fun({Key, Value}, Acc) ->
{TKey, Acc1} = KeyFun(Key, Acc),
{TValue, Acc2} = ValFun(Value, Acc1#elixir_scope{extra=Extra}),
{{Op, ?line(Meta), TKey, TValue}, Acc2}
end, S, Assocs),
build_map(Line, TUpdate, TArgs, SA).
+94 -95
View File
@@ -1,25 +1,27 @@
-module(elixir_module).
-export([compile/4, data_table/1, docs_table/1, is_open/1,
expand_callback/6, add_beam_chunk/3, format_error/1]).
-export([data_table/1, defs_table/1, clas_table/1, is_open/1,
compile/4, expand_callback/6, add_beam_chunk/3, format_error/1]).
-include("elixir.hrl").
-define(acc_attr, '__acc_attributes').
-define(docs_attr, '__docs_table').
-define(lexical_attr, '__lexical_tracker').
-define(persisted_attr, '__persisted_attributes').
-define(overridable_attr, '__overridable').
-define(location_attr, '__location').
-define(acc_attr, {elixir, acc_attributes}).
-define(lexical_attr, {elixir, lexical_tracker}).
-define(persisted_attr, {elixir, persisted_attributes}).
-define(overridable_attr, {elixir, overridable}).
-define(location_attr, {elixir, location}).
%% TABLE METHODS
data_table(Module) ->
Module.
ets:lookup_element(elixir_modules, Module, 2).
docs_table(Module) ->
ets:lookup_element(Module, ?docs_attr, 2).
defs_table(Module) ->
ets:lookup_element(elixir_modules, Module, 3).
clas_table(Module) ->
ets:lookup_element(elixir_modules, Module, 4).
is_open(Module) ->
Module == ets:info(Module, name).
ets:lookup(elixir_modules, Module) /= [].
%% Compilation hook
@@ -49,12 +51,12 @@ do_compile(Line, Module, Block, Vars, E) ->
check_module_availability(Line, File, Module),
Docs = elixir_compiler:get_opt(docs),
build(Line, File, Module, Docs, ?m(E, lexical_tracker)),
{Data, Defs, Clas, Ref} = build(Line, File, Module, Docs, ?m(E, lexical_tracker)),
try
{Result, NE} = eval_form(Line, Module, Block, Vars, E),
{Result, NE} = eval_form(Line, Module, Data, Block, Vars, E),
_ = case ets:lookup(data_table(Module), 'on_load') of
_ = case ets:lookup(Data, 'on_load') of
[] -> ok;
[{on_load,OnLoad}] ->
[elixir_locals:record_local(Tuple, Module) || Tuple <- OnLoad]
@@ -65,14 +67,14 @@ do_compile(Line, Module, Block, Vars, E) ->
{All, Forms0} = functions_form(Line, File, Module, Def, Defp,
Defmacro, Defmacrop, Exports, Functions),
Forms1 = specs_form(Module, Defmacro, Defmacrop, Forms0),
Forms2 = types_form(Line, File, Module, Forms1),
Forms3 = attributes_form(Line, File, Module, Forms2),
Forms1 = specs_form(Data, Defmacro, Defmacrop, Forms0),
Forms2 = types_form(Line, File, Data, Forms1),
Forms3 = attributes_form(Line, File, Data, Forms2),
elixir_locals:ensure_no_import_conflict(Line, File, Module, All),
case Docs of
true -> warn_unused_docs(Line, File, Module, doc);
true -> warn_unused_docs(Line, File, Data, doc);
false -> false
end,
@@ -83,65 +85,68 @@ do_compile(Line, Module, Block, Vars, E) ->
{attribute, Line, module, Module} | Forms3
],
Binary = load_form(Line, Final, compile_opts(Module), NE),
Binary = load_form(Line, Data, Final, compile_opts(Module), NE),
{module, Module, Binary, Result}
after
elixir_locals:cleanup(Module),
elixir_def:cleanup(Module),
ets:delete(docs_table(Module)),
ets:delete(data_table(Module))
ets:delete(Data),
ets:delete(Defs),
ets:delete(Clas),
elixir_code_server:call({undefmodule, Ref})
end.
%% Hook that builds both attribute and functions and set up common hooks.
build(Line, File, Module, Docs, Lexical) ->
%% Table with meta information about the module.
DataTable = data_table(Module),
OldTable = ets:info(DataTable, name),
case OldTable == DataTable of
true ->
[{OldFile, OldLine}] = ets:lookup_element(OldTable, ?location_attr, 2),
case ets:lookup(elixir_modules, Module) of
[{Module, _, _, _, OldLine, OldFile}] ->
Error = {module_in_definition, Module, OldFile, OldLine},
elixir_errors:form_error([{line, Line}], File, ?MODULE, Error);
false ->
_ ->
[]
end,
DataTable = ets:new(DataTable, [set, named_table, public]),
ets:insert(DataTable, {before_compile, []}),
ets:insert(DataTable, {after_compile, []}),
Data = ets:new(Module, [set, public]),
Defs = ets:new(Module, [set, public]),
Clas = ets:new(Module, [bag, public]),
Ref = elixir_code_server:call({defmodule, self(),
{Module, Data, Defs, Clas, Line, File}}),
ets:insert(Data, {before_compile, []}),
ets:insert(Data, {after_compile, []}),
ets:insert(Data, {moduledoc, nil}),
case Docs of
true -> ets:insert(DataTable, {on_definition, [{'Elixir.Module', compile_doc}]});
_ -> ets:insert(DataTable, {on_definition, []})
true -> ets:insert(Data, {on_definition, [{'Elixir.Module', compile_doc}]});
_ -> ets:insert(Data, {on_definition, []})
end,
Attributes = [behaviour, on_load, spec, type, typep, opaque, callback, compile, external_resource],
ets:insert(DataTable, {?acc_attr, [before_compile, after_compile, on_definition, derive|Attributes]}),
ets:insert(DataTable, {?persisted_attr, [vsn|Attributes]}),
ets:insert(DataTable, {?docs_attr, ets:new(DataTable, [ordered_set, public])}),
ets:insert(DataTable, {?lexical_attr, Lexical}),
ets:insert(DataTable, {?overridable_attr, []}),
ets:insert(DataTable, {?location_attr, [{File, Line}]}),
Attributes = [behaviour, on_load, compile, external_resource],
ets:insert(Data, {?acc_attr, [before_compile, after_compile, on_definition, derive,
spec, type, typep, opaque, callback|Attributes]}),
ets:insert(Data, {?persisted_attr, [vsn|Attributes]}),
ets:insert(Data, {?lexical_attr, Lexical}),
%% Setup other modules
%% Setup definition related modules
elixir_def:setup(Module),
elixir_locals:setup(Module),
ok.
elixir_def_overridable:setup(Module),
{Data, Defs, Clas, Ref}.
%% Receives the module representation and evaluates it.
eval_form(Line, Module, Block, Vars, E) ->
eval_form(Line, Module, Data, Block, Vars, E) ->
{Value, EE} = elixir_compiler:eval_forms(Block, Vars, E),
elixir_def_overridable:store_pending(Module),
EV = elixir_env:linify({Line, EE#{vars := [], export_vars := nil}}),
EC = eval_callbacks(Line, Module, before_compile, [EV], EV),
EC = eval_callbacks(Line, Data, before_compile, [EV], EV),
elixir_def_overridable:store_pending(Module),
{Value, EC}.
eval_callbacks(Line, Module, Name, Args, E) ->
Callbacks = lists:reverse(ets:lookup_element(data_table(Module), Name, 2)),
eval_callbacks(Line, Data, Name, Args, E) ->
Callbacks = lists:reverse(ets:lookup_element(Data, Name, 2)),
lists:foldl(fun({M,F}, Acc) ->
expand_callback(Line, M, F, Args, Acc#{vars := [], export_vars := nil},
@@ -160,13 +165,11 @@ functions_form(Line, File, Module, Def, Defp, Defmacro, Defmacrop, Exports, Body
%% Add attributes handling to the form
attributes_form(Line, File, Module, Current) ->
Table = data_table(Module),
attributes_form(Line, File, Data, Current) ->
AccAttrs = ets:lookup_element(Data, ?acc_attr, 2),
PersistedAttrs = ets:lookup_element(Data, ?persisted_attr, 2),
AccAttrs = ets:lookup_element(Table, '__acc_attributes', 2),
PersistedAttrs = ets:lookup_element(Table, '__persisted_attributes', 2),
Transform = fun({Key, Value}, Acc) ->
Transform = fun({Key, Value}, Acc) when is_atom(Key) ->
case lists:member(Key, PersistedAttrs) of
false -> Acc;
true ->
@@ -182,7 +185,8 @@ attributes_form(Line, File, Module, Current) ->
end
end,
ets:foldl(Transform, Current, Table).
Results = ets:select(Data, [{{'$1', '_'}, [{is_atom, '$1'}], ['$_']}]),
lists:foldl(Transform, Current, Results).
process_attribute(Line, File, external_resource, Values) ->
lists:usort([process_external_resource(Line, File, Value) || Value <- Values]);
@@ -197,27 +201,21 @@ process_external_resource(Line, File, Value) ->
%% Types
types_form(Line, File, Module, Forms0) ->
case code:ensure_loaded('Elixir.Kernel.Typespec') of
{module, 'Elixir.Kernel.Typespec'} ->
Types0 = 'Elixir.Module':get_attribute(Module, type) ++
'Elixir.Module':get_attribute(Module, typep) ++
'Elixir.Module':get_attribute(Module, opaque),
types_form(Line, File, Data, Forms0) ->
case elixir_compiler:get_opt(internal) of
false ->
Types0 = get_typespec(Data, type) ++ get_typespec(Data, typep)
++ get_typespec(Data, opaque),
Types1 = ['Elixir.Kernel.Typespec':translate_type(Kind, Expr, Doc, Caller) ||
{Kind, Expr, Doc, Caller} <- Types0],
'Elixir.Module':delete_attribute(Module, type),
'Elixir.Module':delete_attribute(Module, typep),
'Elixir.Module':delete_attribute(Module, opaque),
warn_unused_docs(Line, File, Module, typedoc),
warn_unused_docs(Line, File, Data, typedoc),
Forms1 = types_attributes(Types1, Forms0),
Forms2 = export_types_attributes(Types1, Forms1),
typedocs_attributes(Types1, Forms2);
{error, _} ->
true ->
Forms0
end.
@@ -247,23 +245,17 @@ typedocs_attributes(Types, Forms) ->
%% Specs
specs_form(Module, Defmacro, Defmacrop, Forms) ->
case code:ensure_loaded('Elixir.Kernel.Typespec') of
{module, 'Elixir.Kernel.Typespec'} ->
Specs0 = 'Elixir.Module':get_attribute(Module, spec) ++
'Elixir.Module':get_attribute(Module, callback),
specs_form(Data, Defmacro, Defmacrop, Forms) ->
case elixir_compiler:get_opt(internal) of
false ->
Specs0 = get_typespec(Data, spec) ++ get_typespec(Data, callback),
Specs1 = ['Elixir.Kernel.Typespec':translate_spec(Kind, Expr, Caller) ||
{Kind, Expr, Caller} <- Specs0],
Specs2 = lists:flatmap(fun(Spec) ->
translate_macro_spec(Spec, Defmacro, Defmacrop)
end, Specs1),
'Elixir.Module':delete_attribute(Module, spec),
'Elixir.Module':delete_attribute(Module, callback),
specs_attributes(Forms, Specs2);
{error, _} ->
true ->
Forms
end.
@@ -307,11 +299,11 @@ compile_opts(Module) ->
[] -> []
end.
load_form(Line, Forms, Opts, E) ->
load_form(Line, Data, Forms, Opts, E) ->
elixir_compiler:module(Forms, Opts, E, fun(Module, Binary0) ->
Docs = elixir_compiler:get_opt(docs),
Binary = add_docs_chunk(Binary0, Module, Line, Docs),
eval_callbacks(Line, Module, after_compile, [E, Binary], E),
Binary = add_docs_chunk(Binary0, Data, Line, Docs),
eval_callbacks(Line, Data, after_compile, [E, Binary], E),
case get(elixir_compiled) of
Current when is_list(Current) ->
@@ -331,23 +323,30 @@ load_form(Line, Forms, Opts, E) ->
Binary
end).
add_docs_chunk(Bin, Module, Line, true) ->
add_docs_chunk(Bin, Data, Line, true) ->
ChunkData = term_to_binary({elixir_docs_v1, [
{docs, get_docs(Module)},
{moduledoc, get_moduledoc(Line, Module)}
]}),
{docs, get_docs(Data)},
{moduledoc, get_moduledoc(Line, Data)}
]}),
add_beam_chunk(Bin, "ExDc", ChunkData);
add_docs_chunk(Bin, _, _, _) -> Bin.
get_docs(Module) ->
ordsets:from_list(
get_docs(Data) ->
Match = ets:match(Data, {{doc, '$1'}, '$2', '$3', '$4', '$5'}),
lists:usort(
[{Tuple, Line, Kind, Sig, Doc} ||
{Tuple, Line, Kind, Sig, Doc} <- ets:tab2list(docs_table(Module)),
Kind =/= type, Kind =/= opaque]).
[Tuple, Line, Kind, Sig, Doc] <- Match,
Kind =/= type, Kind =/= opaque]).
get_moduledoc(Line, Module) ->
{Line, 'Elixir.Module':get_attribute(Module, moduledoc)}.
get_moduledoc(Line, Data) ->
{Line, ets:lookup_element(Data, moduledoc, 2)}.
get_typespec(Data, Key) ->
case ets:lookup(Data, Key) of
[{Key, Value}] -> Value;
[] -> []
end.
check_module_availability(Line, File, Module) ->
Reserved = ['Elixir.Any', 'Elixir.BitString', 'Elixir.Function', 'Elixir.PID',
@@ -370,8 +369,8 @@ check_module_availability(Line, File, Module) ->
ok
end.
warn_unused_docs(Line, File, Module, Attribute) ->
case ets:member(data_table(Module), Attribute) of
warn_unused_docs(Line, File, Data, Attribute) ->
case ets:member(Data, Attribute) of
true ->
elixir_errors:form_warn([{line, Line}], File, ?MODULE, {unused_doc, Attribute});
_ ->
+2 -2
View File
@@ -58,12 +58,12 @@ Left 130 or_op_eol. %% ||, |||, or
Left 140 and_op_eol. %% &&, &&&, and
Left 150 comp_op_eol. %% ==, !=, =~, ===, !==
Left 160 rel_op_eol. %% <, >, <=, >=
Left 170 arrow_op_eol. %% < (op), (op) > (e.g |>, <<<, >>>)
Left 170 arrow_op_eol. %% < (op), (op) > (|>, <<<, >>>, ~>>, <<~, ~>, <~, <~>, <|>)
Left 180 in_op_eol. %% in
Right 200 two_op_eol. %% ++, --, .., <>
Left 210 add_op_eol. %% + (op), - (op)
Left 220 mult_op_eol. %% * (op), / (op)
Left 250 hat_op_eol. %% ^ (op) (e.g ^^^)
Left 250 hat_op_eol. %% ^ (op) (^^^)
Nonassoc 300 unary_op_eol. %% +, -, !, ^, not, ~~~
Left 310 dot_call_op.
Left 310 dot_op. %% .
+4
View File
@@ -151,6 +151,10 @@ escape(Expr, Unquote) ->
%% Quotes an expression and return its quoted Elixir AST.
quote({unquote_splicing, _, [_]}, _Binding, #elixir_quote{unquote=true}, _) ->
argument_error(<<"unquote_splicing only works inside arguments and block contexts, "
"wrap it in parens if you want it to work with one-liners">>);
quote(Expr, nil, Q, E) ->
do_quote(Expr, Q, E);
+224
View File
@@ -0,0 +1,224 @@
-module(elixir_rewrite).
-export([rewrite/5, inline/3]).
%% Convenience variables
-define(hidden, [{line,-1}]).
-define(atom, 'Elixir.Atom').
-define(enum, 'Elixir.Enum').
-define(float, 'Elixir.Float').
-define(io, 'Elixir.IO').
-define(integer, 'Elixir.Integer').
-define(kernel, 'Elixir.Kernel').
-define(list, 'Elixir.List').
-define(map, 'Elixir.Map').
-define(node, 'Elixir.Node').
-define(port, 'Elixir.Port').
-define(process, 'Elixir.Process').
-define(string, 'Elixir.String').
-define(string_chars, 'Elixir.String.Chars').
-define(system, 'Elixir.System').
-define(tuple, 'Elixir.Tuple').
%% Inline
%% Inline rules are straight-forward, they keep the same
%% number and order of arguments and show up on captures.
inline(?atom, to_char_list, 1) -> {erlang, atom_to_list};
inline(?io, iodata_length, 1) -> {erlang, iolist_size};
inline(?io, iodata_to_binary, 1) -> {erlang, iolist_to_binary};
inline(?integer, to_string, 1) -> {erlang, integer_to_binary};
inline(?integer, to_string, 2) -> {erlang, integer_to_binary};
inline(?integer, to_char_list, 1) -> {erlang, integer_to_list};
inline(?integer, to_char_list, 2) -> {erlang, integer_to_list};
inline(?float, to_string, 1) -> {erlang, float_to_binary};
inline(?float, to_char_list, 1) -> {erlang, float_to_list};
inline(?list, to_atom, 1) -> {erlang, list_to_atom};
inline(?list, to_existing_atom, 1) -> {erlang, list_to_existing_atom};
inline(?list, to_float, 1) -> {erlang, list_to_float};
inline(?list, to_integer, 1) -> {erlang, list_to_integer};
inline(?list, to_integer, 2) -> {erlang, list_to_integer};
inline(?list, to_tuple, 1) -> {erlang, list_to_tuple};
inline(?kernel, '+', 2) -> {erlang, '+'};
inline(?kernel, '-', 2) -> {erlang, '-'};
inline(?kernel, '+', 1) -> {erlang, '+'};
inline(?kernel, '-', 1) -> {erlang, '-'};
inline(?kernel, '*', 2) -> {erlang, '*'};
inline(?kernel, '/', 2) -> {erlang, '/'};
inline(?kernel, '++', 2) -> {erlang, '++'};
inline(?kernel, '--', 2) -> {erlang, '--'};
inline(?kernel, 'not', 1) -> {erlang, 'not'};
inline(?kernel, '<', 2) -> {erlang, '<'};
inline(?kernel, '>', 2) -> {erlang, '>'};
inline(?kernel, '<=', 2) -> {erlang, '=<'};
inline(?kernel, '>=', 2) -> {erlang, '>='};
inline(?kernel, '==', 2) -> {erlang, '=='};
inline(?kernel, '!=', 2) -> {erlang, '/='};
inline(?kernel, '===', 2) -> {erlang, '=:='};
inline(?kernel, '!==', 2) -> {erlang, '=/='};
inline(?kernel, abs, 1) -> {erlang, abs};
inline(?kernel, apply, 2) -> {erlang, apply};
inline(?kernel, apply, 3) -> {erlang, apply};
inline(?kernel, binary_part, 3) -> {erlang, binary_part};
inline(?kernel, bit_size, 1) -> {erlang, bit_size};
inline(?kernel, byte_size, 1) -> {erlang, byte_size};
inline(?kernel, 'div', 2) -> {erlang, 'div'};
inline(?kernel, exit, 1) -> {erlang, exit};
inline(?kernel, hd, 1) -> {erlang, hd};
inline(?kernel, is_atom, 1) -> {erlang, is_atom};
inline(?kernel, is_binary, 1) -> {erlang, is_binary};
inline(?kernel, is_bitstring, 1) -> {erlang, is_bitstring};
inline(?kernel, is_boolean, 1) -> {erlang, is_boolean};
inline(?kernel, is_float, 1) -> {erlang, is_float};
inline(?kernel, is_function, 1) -> {erlang, is_function};
inline(?kernel, is_function, 2) -> {erlang, is_function};
inline(?kernel, is_integer, 1) -> {erlang, is_integer};
inline(?kernel, is_list, 1) -> {erlang, is_list};
inline(?kernel, is_map, 1) -> {erlang, is_map};
inline(?kernel, is_number, 1) -> {erlang, is_number};
inline(?kernel, is_pid, 1) -> {erlang, is_pid};
inline(?kernel, is_port, 1) -> {erlang, is_port};
inline(?kernel, is_reference, 1) -> {erlang, is_reference};
inline(?kernel, is_tuple, 1) -> {erlang, is_tuple};
inline(?kernel, length, 1) -> {erlang, length};
inline(?kernel, make_ref, 0) -> {erlang, make_ref};
inline(?kernel, map_size, 1) -> {erlang, map_size};
inline(?kernel, max, 2) -> {erlang, max};
inline(?kernel, min, 2) -> {erlang, min};
inline(?kernel, node, 0) -> {erlang, node};
inline(?kernel, node, 1) -> {erlang, node};
inline(?kernel, 'rem', 2) -> {erlang, 'rem'};
inline(?kernel, round, 1) -> {erlang, round};
inline(?kernel, self, 0) -> {erlang, self};
inline(?kernel, send, 2) -> {erlang, send};
inline(?kernel, spawn, 1) -> {erlang, spawn};
inline(?kernel, spawn, 3) -> {erlang, spawn};
inline(?kernel, spawn_link, 1) -> {erlang, spawn_link};
inline(?kernel, spawn_link, 3) -> {erlang, spawn_link};
inline(?kernel, spawn_monitor, 1) -> {erlang, spawn_monitor};
inline(?kernel, spawn_monitor, 3) -> {erlang, spawn_monitor};
inline(?kernel, throw, 1) -> {erlang, throw};
inline(?kernel, tl, 1) -> {erlang, tl};
inline(?kernel, trunc, 1) -> {erlang, trunc};
inline(?kernel, tuple_size, 1) -> {erlang, tuple_size};
inline(?map, keys, 1) -> {maps, keys};
inline(?map, merge, 2) -> {maps, merge};
inline(?map, size, 1) -> {maps, size};
inline(?map, values, 1) -> {maps, values};
inline(?map, to_list, 1) -> {maps, to_list};
inline(?node, spawn, 2) -> {erlang, spawn};
inline(?node, spawn, 3) -> {erlang, spawn_opt};
inline(?node, spawn, 4) -> {erlang, spawn};
inline(?node, spawn, 5) -> {erlang, spawn_opt};
inline(?node, spawn_link, 2) -> {erlang, spawn_link};
inline(?node, spawn_link, 4) -> {erlang, spawn_link};
inline(?process, exit, 2) -> {erlang, exit};
inline(?process, spawn, 2) -> {erlang, spawn_opt};
inline(?process, spawn, 4) -> {erlang, spawn_opt};
inline(?process, demonitor, 1) -> {erlang, demonitor};
inline(?process, demonitor, 2) -> {erlang, demonitor};
inline(?process, link, 1) -> {erlang, link};
inline(?process, unlink, 1) -> {erlang, unlink};
inline(?port, open, 2) -> {erlang, open_port};
inline(?port, call, 3) -> {erlang, port_call};
inline(?port, close, 1) -> {erlang, port_close};
inline(?port, command, 2) -> {erlang, port_command};
inline(?port, command, 3) -> {erlang, port_command};
inline(?port, connect, 2) -> {erlang, port_connect};
inline(?port, control, 3) -> {erlang, port_control};
inline(?port, info, 1) -> {erlang, port_info};
inline(?port, info, 2) -> {erlang, port_info};
inline(?port, list, 0) -> {erlang, ports};
inline(?string, to_float, 1) -> {erlang, binary_to_float};
inline(?string, to_integer, 1) -> {erlang, binary_to_integer};
inline(?string, to_integer, 2) -> {erlang, binary_to_integer};
inline(?system, stacktrace, 0) -> {erlang, get_stacktrace};
inline(?tuple, to_list, 1) -> {erlang, tuple_to_list};
inline(_, _, _) -> false.
%% Rewrite rules
%% Rewrite rules are more complex than regular inlining code.
%% It receives all remote call arguments and return quoted
%% expressions with the new environment.
%% Complex rewrite rules
rewrite(?string_chars, _DotMeta, 'to_string', _Meta, [String]) when is_binary(String) ->
String;
rewrite(?string_chars, DotMeta, 'to_string', Meta, [String]) ->
Var = {'_rewrite', ?hidden, nil},
Guard = {{'.', ?hidden, [erlang, is_binary]}, ?hidden, [Var]},
Slow = remote(?string_chars, DotMeta, 'to_string', Meta, [Var]),
Fast = Var,
{'case', ?hidden, [String, [{do,
[{'->', ?hidden, [[{'when', ?hidden, [Var, Guard]}], Fast]},
{'->', ?hidden, [[Var], Slow]}]
}]]};
rewrite(?enum, DotMeta, 'reverse', Meta, [List]) when is_list(List) ->
remote(lists, DotMeta, 'reverse', Meta, [List]);
rewrite(?enum, DotMeta, 'reverse', Meta, [List]) ->
Var = {'_rewrite', ?hidden, nil},
Guard = {{'.', ?hidden, [erlang, is_list]}, ?hidden, [Var]},
Slow = remote(?enum, DotMeta, 'reverse', Meta, [Var]),
Fast = remote(lists, DotMeta, 'reverse', Meta, [Var]),
{'case', ?hidden, [List, [{do,
[{'->', ?hidden, [[{'when', ?hidden, [Var, Guard]}], Fast]},
{'->', ?hidden, [[Var], Slow]}]
}]]};
rewrite(Receiver, DotMeta, Right, Meta, Args) ->
{EReceiver, ERight, EArgs} = rewrite(Receiver, Right, Args),
remote(EReceiver, DotMeta, ERight, Meta, EArgs).
%% Simple rewrite rules
rewrite(?atom, to_string, [Arg]) ->
{erlang, atom_to_binary, [Arg, utf8]};
rewrite(?kernel, elem, [Tuple, Index]) ->
{erlang, element, [increment(Index), Tuple]};
rewrite(?kernel, put_elem, [Tuple, Index, Value]) ->
{erlang, setelement, [increment(Index), Tuple, Value]};
rewrite(?map, 'has_key?', [Map, Key]) ->
{maps, is_key, [Key, Map]};
rewrite(?map, fetch, [Map, Key]) ->
{maps, find, [Key, Map]};
rewrite(?map, put, [Map, Key, Value]) ->
{maps, put, [Key, Value, Map]};
rewrite(?map, delete, [Map, Key]) ->
{maps, remove, [Key, Map]};
rewrite(?process, monitor, [Arg]) ->
{erlang, monitor, [process, Arg]};
rewrite(?string, to_atom, [Arg]) ->
{erlang, binary_to_atom, [Arg, utf8]};
rewrite(?string, to_existing_atom, [Arg]) ->
{erlang, binary_to_existing_atom, [Arg, utf8]};
rewrite(?tuple, insert_at, [Tuple, Index, Term]) ->
{erlang, insert_element, [increment(Index), Tuple, Term]};
rewrite(?tuple, delete_at, [Tuple, Index]) ->
{erlang, delete_element, [increment(Index), Tuple]};
rewrite(?tuple, duplicate, [Data, Size]) ->
{erlang, make_tuple, [Size, Data]};
rewrite(Receiver, Fun, Args) ->
{Receiver, Fun, Args}.
%% Rewrite helpers
remote(Receiver, DotMeta, Right, Meta, Args) ->
{{'.', DotMeta, [Receiver, Right]}, Meta, Args}.
increment(Number) when is_number(Number) ->
Number + 1;
increment(Other) ->
{{'.', [], [erlang, '+']}, [], [Other, 1]}.
+10
View File
@@ -7,6 +7,16 @@ start_link() ->
init(ok) ->
Workers = [
{
elixir_config,
{elixir_config, start_link, []},
permanent, % Restart = permanent | transient | temporary
2000, % Shutdown = brutal_kill | int() >= 0 | infinity
worker, % Type = worker | supervisor
[elixir_config] % Modules = [Module] | dynamic
},
{
elixir_code_server,
{elixir_code_server, start_link, []},
+8 -2
View File
@@ -33,10 +33,16 @@
-define(arrow_op3(T1, T2, T3),
T1 == $<, T2 == $<, T3 == $<;
T1 == $>, T2 == $>, T3 == $>).
T1 == $>, T2 == $>, T3 == $>;
T1 == $~, T2 == $>, T3 == $>;
T1 == $<, T2 == $<, T3 == $~;
T1 == $<, T2 == $~, T3 == $>;
T1 == $<, T2 == $|, T3 == $>).
-define(arrow_op(T1, T2),
T1 == $|, T2 == $>).
T1 == $|, T2 == $>;
T1 == $~, T2 == $>;
T1 == $<, T2 == $~).
-define(rel_op(T),
T == $<;
+1 -1
View File
@@ -10,7 +10,7 @@
translate({'=', Meta, [Left, Right]}, S) ->
Return = case Left of
{'_', _, Atom} when is_atom(Atom) -> false;
{'_', _, Atom} when is_atom(Atom) -> S#elixir_scope.return;
_ -> true
end,
+16
View File
@@ -327,4 +327,20 @@ defmodule Kernel.ExceptionTest do
assert %ErlangError{original: :sample} |> message ==
"erlang error: :sample"
end
test "format_stacktrace from file" do
assert_raise ArgumentError, fn ->
Code.eval_string("def foo do end", [], file: "myfile")
end
assert Exception.format_stacktrace(System.stacktrace) =~ "myfile:1: (file)"
end
test "format_stacktrace from module" do
assert_raise ArgumentError, fn ->
Code.eval_string("defmodule Foo do raise ArgumentError, ~s(oops) end", [], file: "myfile")
end
assert Exception.format_stacktrace(System.stacktrace) =~ "myfile:1: (module)"
end
end
@@ -147,8 +147,8 @@ defmodule GenEvent.StreamTest do
Process.flag(:trap_exit, true)
Process.exit(pid, :kill)
assert_receive {:EXIT, ^pid, :killed}, @receive_timeout
assert_receive {:EXIT, ^child,
{:killed, {Enumerable.GenEvent.Stream, :stop, [_,_]}}}, @receive_timeout
assert_receive ({:EXIT, ^child, {stat, {Enumerable.GenEvent.Stream, :stop, [_,_]}}}
when stat in [:killed, :noproc]), @receive_timeout
end
test "stream/2 with manager not alive" do
@@ -214,7 +214,7 @@ defmodule GenEvent.StreamTest do
stream = GenEvent.stream(pid, id: make_ref())
parent = self()
child = spawn_link fn -> send parent, Enum.take(stream, 5) end
spawn_link(fn -> send parent, Enum.take(stream, 5) end)
wait_for_handlers(pid, 1)
for i <- 1..3 do
@@ -222,11 +222,8 @@ defmodule GenEvent.StreamTest do
end
[handler] = GenEvent.which_handlers(pid)
Process.flag(:trap_exit, true)
GenEvent.swap_handler(pid, handler, :swap_handler, UnknownHandler, [])
assert_receive {:EXIT, ^child,
{{:swapped, UnknownHandler, _},
{Enumerable.GenEvent.Stream, :stop, [_, _]}}}, @receive_timeout
assert_receive [1, 2, 3], @receive_timeout
end
test "#{mode} stream/2 with manager unregistered" do
+101 -72
View File
@@ -18,8 +18,8 @@ defmodule GenEventTest do
raise "oops"
end
def init({:swap, {:error, :module_not_found}}) do
{:error, :module_not_found_on_swap}
def init({:swap, {:error, :not_found}}) do
{:error, :not_found_on_swap}
end
def init({:swap, parent}) when is_pid(parent) do
@@ -182,27 +182,6 @@ defmodule GenEventTest do
refute_received {:terminate, _}
end
defp hibernating?(pid) do
Process.info(pid, :current_function) ==
{:current_function,{:erlang,:hibernate,3}}
end
defp wait_until(fun, counter \\ 0) do
cond do
counter > 100 ->
flunk "Waited for 1s, but #{inspect fun} never returned true"
fun.() ->
true
true ->
receive after: (10 -> wait_until(fun, counter + 1))
end
end
defp wake_up(pid) do
send pid, :wake
assert_receive {:info, :wake}
end
test "hibernates" do
{:ok, pid} = GenEvent.start()
:ok = GenEvent.add_handler(pid, ReplyHandler, {self(), :hibernate})
@@ -221,7 +200,7 @@ defmodule GenEventTest do
GenEvent.stop(pid)
end
test "add_handler/4" do
test "add_handler/3" do
{:ok, pid} = GenEvent.start()
assert GenEvent.add_handler(pid, ReplyHandler, {:custom, {:error, :my_error}}) ==
@@ -234,18 +213,18 @@ defmodule GenEventTest do
assert GenEvent.add_handler(pid, ReplyHandler, {:throw, self()}) == :ok
assert GenEvent.which_handlers(pid) == [ReplyHandler]
assert GenEvent.add_handler(pid, ReplyHandler, {:throw, self()}) == {:error, :already_added}
assert GenEvent.add_handler(pid, ReplyHandler, {:throw, self()}) == {:error, :already_present}
assert GenEvent.add_handler(pid, {ReplyHandler, self()}, {self(), false}) == :ok
assert GenEvent.which_handlers(pid) == [{ReplyHandler, self()}, ReplyHandler]
end
test "add_handler/4 with monitor" do
test "add_mon_handler/3" do
{:ok, pid} = GenEvent.start()
parent = self()
{mon_pid, mon_ref} = spawn_monitor(fn ->
assert GenEvent.add_handler(pid, ReplyHandler, {self(), false}, monitor: true) == :ok
assert GenEvent.add_mon_handler(pid, ReplyHandler, {self(), false}) == :ok
send parent, :ok
receive after: (:infinity -> :ok)
end)
@@ -265,20 +244,20 @@ defmodule GenEventTest do
assert GenEvent.which_handlers(pid) == [{ReplyHandler, self()}]
end
test "add_handler/4 with notifications" do
test "add_mon_handler/3 with notifications" do
{:ok, pid} = GenEvent.start()
self = self()
GenEvent.add_handler(pid, ReplyHandler, {self(), false}, monitor: true)
GenEvent.add_mon_handler(pid, ReplyHandler, {self(), false})
GenEvent.remove_handler(pid, ReplyHandler, :ok)
assert_receive {:gen_event_EXIT, ReplyHandler, :normal}
GenEvent.add_handler(pid, ReplyHandler, {self(), false}, monitor: true)
GenEvent.swap_handler(pid, ReplyHandler, :swapped, ReplyHandler, :swap)
:ok = GenEvent.add_mon_handler(pid, ReplyHandler, {self(), false})
:ok = GenEvent.swap_handler(pid, ReplyHandler, :swapped, ReplyHandler, :swap)
assert_receive {:gen_event_EXIT, ReplyHandler, {:swapped, ReplyHandler, nil}}
GenEvent.swap_handler(pid, ReplyHandler, :swapped, ReplyHandler, :swap, monitor: true)
GenEvent.swap_handler(pid, ReplyHandler, :swapped, ReplyHandler, :swap, monitor: true)
:ok = GenEvent.swap_mon_handler(pid, ReplyHandler, :swapped, ReplyHandler, :swap)
:ok = GenEvent.swap_mon_handler(pid, ReplyHandler, :swapped, ReplyHandler, :swap)
assert_receive {:gen_event_EXIT, ReplyHandler, {:swapped, ReplyHandler, ^self}}
GenEvent.stop(pid)
@@ -288,25 +267,25 @@ defmodule GenEventTest do
test "remove_handler/3" do
{:ok, pid} = GenEvent.start()
GenEvent.add_handler(pid, ReplyHandler, {self(), false}, monitor: true)
GenEvent.add_mon_handler(pid, ReplyHandler, {self(), false})
assert GenEvent.remove_handler(pid, {ReplyHandler, self()}, :ok) ==
{:error, :module_not_found}
{:error, :not_found}
assert GenEvent.remove_handler(pid, ReplyHandler, :ok) ==
{:terminate, :ok}
assert_receive {:terminate, :ok}
GenEvent.add_handler(pid, {ReplyHandler, self()}, {self(), false}, monitor: true)
GenEvent.add_mon_handler(pid, {ReplyHandler, self()}, {self(), false})
assert GenEvent.remove_handler(pid, ReplyHandler, :ok) ==
{:error, :module_not_found}
{:error, :not_found}
assert {:error, {%RuntimeError{}, _}} =
GenEvent.remove_handler(pid, {ReplyHandler, self()}, :raise)
assert GenEvent.which_handlers(pid) == []
end
test "swap_handler/6" do
test "swap_handler/5" do
{:ok, pid} = GenEvent.start()
GenEvent.add_handler(pid, ReplyHandler, {self(), false})
@@ -317,7 +296,7 @@ defmodule GenEventTest do
assert GenEvent.add_handler(pid, ReplyHandler, {self(), false}) == :ok
assert GenEvent.swap_handler(pid, ReplyHandler, :swapped,
{ReplyHandler, self()}, :swap) == {:error, :already_added}
{ReplyHandler, self()}, :swap) == {:error, :already_present}
assert GenEvent.which_handlers(pid) == [{ReplyHandler, self()}]
assert GenEvent.remove_handler(pid, {ReplyHandler, self()}, :remove_handler) ==
@@ -326,7 +305,7 @@ defmodule GenEventTest do
# The handler is initialized even when the module does not exist
# on swap. However, in this case, we are returning an error on init.
assert GenEvent.swap_handler(pid, ReplyHandler, :swapped, ReplyHandler, :swap) ==
{:error, :module_not_found_on_swap}
{:error, :not_found_on_swap}
end
test "notify/2" do
@@ -336,11 +315,6 @@ defmodule GenEventTest do
assert GenEvent.notify(pid, :hello) == :ok
assert_receive {:event, :hello}
msg = {:custom, {:swap_handler, :swapped, self(), ReplyHandler, :swap}}
assert GenEvent.notify(pid, msg) == :ok
assert_receive {:terminate, :swapped}
assert_receive :swapped
assert GenEvent.notify(pid, {:custom, :remove_handler}) == :ok
assert_receive {:terminate, :remove_handler}
assert GenEvent.which_handlers(pid) == []
@@ -375,11 +349,6 @@ defmodule GenEventTest do
assert GenEvent.ack_notify(pid, :hello) == :ok
assert_receive {:event, :hello}
msg = {:custom, {:swap_handler, :swapped, self(), ReplyHandler, :swap}}
assert GenEvent.ack_notify(pid, msg) == :ok
assert_receive {:terminate, :swapped}
assert_receive :swapped
assert GenEvent.ack_notify(pid, {:custom, :remove_handler}) == :ok
assert_receive {:terminate, :remove_handler}
assert GenEvent.which_handlers(pid) == []
@@ -404,11 +373,6 @@ defmodule GenEventTest do
assert GenEvent.sync_notify(pid, :hello) == :ok
assert_received {:event, :hello}
msg = {:custom, {:swap_handler, :swapped, self(), ReplyHandler, :swap}}
assert GenEvent.sync_notify(pid, msg) == :ok
assert_received {:terminate, :swapped}
assert_received :swapped
assert GenEvent.sync_notify(pid, {:custom, :remove_handler}) == :ok
assert_received {:terminate, :remove_handler}
assert GenEvent.which_handlers(pid) == []
@@ -433,28 +397,12 @@ defmodule GenEventTest do
assert GenEvent.call(pid, ReplyHandler, :hello) == :ok
assert_receive {:call, :hello}
msg = {:custom, {:swap_handler, :ok, :swapped, self(), ReplyHandler, :swap}}
assert GenEvent.call(pid, ReplyHandler, msg) == :ok
assert_receive {:terminate, :swapped}
assert_receive :swapped
assert GenEvent.call(pid, ReplyHandler, {:custom, {:remove_handler, :ok}}) == :ok
assert_receive {:terminate, :remove_handler}
assert GenEvent.which_handlers(pid) == []
GenEvent.add_handler(pid, ReplyHandler, {self(), false})
msg = {:custom, {:swap_handler, :ok, :swapped, self(), ReplyHandler, :raise}}
assert GenEvent.call(pid, ReplyHandler, msg) == :ok
assert GenEvent.which_handlers(pid) == []
Logger.remove_backend(:console)
GenEvent.add_handler(pid, ReplyHandler, {self(), false})
GenEvent.add_handler(pid, {ReplyHandler, self}, {self(), false})
msg = {:custom, {:swap_handler, :ok, :swapped, self(), ReplyHandler, :raise}}
assert GenEvent.call(pid, {ReplyHandler, self()}, msg) == :ok
assert GenEvent.which_handlers(pid) == [ReplyHandler]
assert {:error, {:bad_return_value, :oops}} =
GenEvent.call(pid, ReplyHandler, {:custom, :oops})
@@ -475,7 +423,7 @@ defmodule GenEventTest do
assert GenEvent.add_handler(pid, DefaultHandler, []) == :ok
assert GenEvent.call(pid, UnknownHandler, :messages) ==
{:error, :module_not_found}
{:error, :not_found}
assert GenEvent.call(pid, DefaultHandler, :whatever) ==
{:error, {:bad_call, :whatever}}
assert GenEvent.which_handlers(pid) == []
@@ -483,4 +431,85 @@ defmodule GenEventTest do
after
Logger.add_backend(:console, flush: true)
end
test "add_process_handler/2 with GenEvent" do
{:ok, snd} = GenEvent.start_link()
GenEvent.add_handler(snd, ReplyHandler, {self(), false})
{:ok, fst} = GenEvent.start_link()
:gen.call(fst, self(), {:add_process_handler, snd, snd})
assert GenEvent.notify(fst, :hello) == :ok
assert_receive {:event, :hello}
assert GenEvent.ack_notify(fst, :hello) == :ok
assert_receive {:event, :hello}
assert GenEvent.sync_notify(fst, :hello) == :ok
assert_received {:event, :hello}
end
test ":sys.get_status/2" do
{:ok, pid} = GenEvent.start(name: :my_gen_event_name)
:ok = GenEvent.add_handler(pid, {ReplyHandler, :ok}, {self(), true})
self = self()
status = :sys.get_status(pid)
GenEvent.stop(pid)
assert {:status, ^pid, {:module, GenEvent},
[pdict, _, ^pid, [], data]} = status
assert pdict[:"$ancestors"] == [self()]
assert pdict[:"$initial_call"] == {GenEvent, :init_it, 6}
assert {'Installed handlers', [
{:handler, ReplyHandler, {ReplyHandler, :ok}, ^self, nil, nil}]} = data[:items]
end
test ":sys.get_state/1 and :sys.replace_state/2" do
{:ok, pid} = GenEvent.start_link(name: :my_gen_event_name)
self = self()
assert [] = :sys.get_state(pid)
:ok = GenEvent.add_handler(pid, ReplyHandler, {self, true})
assert [{ReplyHandler, ReplyHandler, ^self}] = :sys.get_state(pid)
replacer = fn {ReplyHandler, ReplyHandler, _} -> {ReplyHandler, ReplyHandler, :unknown} end
:sys.replace_state(pid, replacer)
assert [{ReplyHandler, ReplyHandler, :unknown}] = :sys.get_state(pid)
# Fail while replacing does not cause a crash
:sys.replace_state(pid, fn _ -> exit(:fail) end)
assert [{ReplyHandler, ReplyHandler, :unknown}] = :sys.get_state(pid)
:ok = GenEvent.add_handler(pid, {ReplyHandler, :ok}, {self, true})
assert [{ReplyHandler, {ReplyHandler, :ok}, ^self},
{ReplyHandler, ReplyHandler, :unknown}] = :sys.get_state(pid)
:ok = :sys.suspend(pid)
assert [{ReplyHandler, {ReplyHandler, :ok}, ^self},
{ReplyHandler, ReplyHandler, :unknown}] = :sys.get_state(pid)
:ok = :sys.resume(pid)
end
defp hibernating?(pid) do
Process.info(pid, :current_function) ==
{:current_function,{:erlang,:hibernate,3}}
end
defp wait_until(fun, counter \\ 0) do
cond do
counter > 100 ->
flunk "Waited for 1s, but #{inspect fun} never returned true"
fun.() ->
true
true ->
receive after: (10 -> wait_until(fun, counter + 1))
end
end
defp wake_up(pid) do
send pid, :wake
assert_receive {:info, :wake}
end
end
+8
View File
@@ -41,8 +41,14 @@ defmodule Inspect.AtomTest do
test :op do
assert inspect(:+) == ":+"
assert inspect(:<~) == ":<~"
assert inspect(:~>) == ":~>"
assert inspect(:&&&) == ":&&&"
assert inspect(:~~~) == ":~~~"
assert inspect(:<<~) == ":<<~"
assert inspect(:~>>) == ":~>>"
assert inspect(:<~>) == ":<~>"
assert inspect(:<|>) == ":<|>"
end
test :... do
@@ -293,6 +299,8 @@ defmodule Inspect.MapTest do
assert_raise ArgumentError, msg, fn ->
inspect(%Failing{})
end
assert [{Inspect.Inspect.MapTest.Failing, :inspect, 2, _}|_] = System.stacktrace
end
test :exception do
+66 -9
View File
@@ -14,24 +14,24 @@ defmodule IO.ANSI.DocsTest do
test "heading is formatted" do
result = format_heading("wibble")
assert String.starts_with?(result, "\e[0m\n\e[7m\e[33m\e[1m")
assert String.starts_with?(result, "\e[0m\n\e[7m\e[33m")
assert String.ends_with?(result, "\e[0m\n\e[0m")
assert String.contains?(result, " wibble ")
end
test "first level heading is converted" do
result = format("# wibble\n\ntext\n")
assert result == "\e[33m\e[1mWIBBLE\e[0m\n\e[0m\ntext\n\e[0m"
assert result == "\e[33mWIBBLE\e[0m\n\e[0m\ntext\n\e[0m"
end
test "second level heading is converted" do
result = format("## wibble\n\ntext\n")
assert result == "\e[33m\e[1mwibble\e[0m\n\e[0m\ntext\n\e[0m"
assert result == "\e[33mwibble\e[0m\n\e[0m\ntext\n\e[0m"
end
test "third level heading is converted" do
result = format("## wibble\n\ntext\n")
assert result == "\e[33m\e[1mwibble\e[0m\n\e[0m\ntext\n\e[0m"
assert result == "\e[33mwibble\e[0m\n\e[0m\ntext\n\e[0m"
end
test "code block is converted" do
@@ -103,6 +103,9 @@ defmodule IO.ANSI.DocsTest do
result = format("*world*")
assert result == "\e[1mworld\e[0m\n\e[0m"
result = format("*world*.")
assert result == "\e[1mworld\e[0m.\n\e[0m"
result = format("**world**")
assert result == "\e[1mworld\e[0m\n\e[0m"
@@ -127,6 +130,28 @@ defmodule IO.ANSI.DocsTest do
assert result == "\e[36mhello world\e[0m\n\e[0m"
end
test "multiple stars/underscores/backticks work" do
result = format("*hello world* *hello world*")
assert result == "\e[1mhello world\e[0m \e[1mhello world\e[0m\n\e[0m"
result = format("_hello world_ _hello world_")
assert result == "\e[4mhello world\e[0m \e[4mhello world\e[0m\n\e[0m"
result = format("`hello world` `hello world`")
assert result == "\e[36mhello world\e[0m \e[36mhello world\e[0m\n\e[0m"
end
test "multiple stars/underscores/backticks work when separated by other words" do
result = format("*hello world* unit test *hello world*")
assert result == "\e[1mhello world\e[0m unit test \e[1mhello world\e[0m\n\e[0m"
result = format("_hello world_ unit test _hello world_")
assert result == "\e[4mhello world\e[0m unit test \e[4mhello world\e[0m\n\e[0m"
result = format("`hello world` unit test `hello world`")
assert result == "\e[36mhello world\e[0m unit test \e[36mhello world\e[0m\n\e[0m"
end
test "star/underscore preceeded by space doesn't get interpreted" do
result = format("_unit _size")
assert result == "_unit _size\n\e[0m"
@@ -138,6 +163,43 @@ defmodule IO.ANSI.DocsTest do
assert result == "*unit *size\n\e[0m"
end
test "star/underscore/backtick preceeded by non-space delimiters gets interpreted" do
result = format("(`hello world`)")
assert result == "(\e[36mhello world\e[0m)\n\e[0m"
result = format("<`hello world`>")
assert result == "<\e[36mhello world\e[0m>\n\e[0m"
result = format("(*hello world*)")
assert result == "(\e[1mhello world\e[0m)\n\e[0m"
result = format("@*hello world*@")
assert result == "@\e[1mhello world\e[0m@\n\e[0m"
result = format("(_hello world_)")
assert result == "(\e[4mhello world\e[0m)\n\e[0m"
result = format("'_hello world_'")
assert result == "'\e[4mhello world\e[0m'\n\e[0m"
end
test "star/underscore/backtick starts/ends within a word doesn't get interpreted" do
result = format("foo_bar, foo_bar_baz!")
assert result == "foo_bar, foo_bar_baz!\n\e[0m"
result = format("_foo_bar")
assert result == "_foo_bar\n\e[0m"
result = format("foo_bar_")
assert result == "foo_bar_\n\e[0m"
result = format("foo*bar, foo*bar*baz!")
assert result == "foo*bar, foo*bar*baz!\n\e[0m"
result = format("*foo*bar")
assert result == "*foo*bar\n\e[0m"
result = format("foo*bar*")
assert result == "foo*bar*\n\e[0m"
end
test "backtick preceeded by space gets interpreted" do
result = format("`unit `size")
assert result == "\e[36munit \e[0msize\n\e[0m"
@@ -200,11 +262,6 @@ defmodule IO.ANSI.DocsTest do
assert result == "\e[36m__world__\e[0m\n\e[0m"
end
test "backtick works inside parenthesis" do
result = format("(`hello world`)")
assert result == "(\e[36mhello world\e[0m)\n\e[0m"
end
test "escaping of underlines within links" do
result = format("(http://en.wikipedia.org/wiki/ANSI_escape_code)")
assert result == "(http://en.wikipedia.org/wiki/ANSI_escape_code)\n\e[0m"
@@ -151,6 +151,12 @@ defmodule Kernel.ComprehensionTest do
assert for({_,x} <- [1, 2, a: 3, b: 4, c: 5], do: x * 2) == [6, 8, 10]
end
test "list for comprehension matched to '_' on last line of block" do
assert (if true do
_ = for x <- [1, 2, 3], do: x * 2
end) == [2, 4, 6]
end
test "list for comprehensions with filters" do
assert for(x <- [1, 2, 3], x > 1, x < 3, do: x * 2) == [4]
end
+54 -2
View File
@@ -20,6 +20,10 @@ defmodule Kernel.ErrorsTest do
"nofile:1: syntax error before: \"world\"",
'"hello" "world"'
assert_compile_fail SyntaxError,
"nofile:1: syntax error before: Foobar",
'1 Foobar'
assert_compile_fail SyntaxError,
"nofile:1: syntax error before: foo",
'Foo.:foo'
@@ -85,6 +89,12 @@ defmodule Kernel.ErrorsTest do
'+.foo'
end
test :syntax_error_before_sigil do
assert_compile_fail SyntaxError,
"nofile:1: syntax error before: sigil ~s with content 'bar baz'",
'~s(foo) ~s(bar baz)'
end
test :compile_error_on_op_ambiguity do
msg = "nofile:1: \"a -1\" looks like a function call but there is a variable named \"a\", " <>
"please use explicit parenthesis or even spaces"
@@ -150,6 +160,15 @@ defmodule Kernel.ErrorsTest do
def hello(arg), do: arg
end
'''
assert_compile_fail CompileError,
"nofile:2: function foo/0 undefined",
~C'''
defmodule ErrorsTest do
def hello(foo, bar \\ foo)
def hello(foo, bar), do: foo + bar
end
'''
end
test :invalid_match_pattern do
@@ -218,6 +237,10 @@ defmodule Kernel.ErrorsTest do
assert_compile_fail SyntaxError,
"nofile:1: syntax error before: '{'",
'%{:a, :b}{a: :b}'
assert_compile_fail CompileError,
"nofile:1: expected key-value pairs in a map, got: put_in(foo.bar().baz(), nil)",
'foo = 1; %{put_in(foo.bar.baz, nil), :bar}'
end
test :struct_fields_on_defstruct do
@@ -290,10 +313,34 @@ defmodule Kernel.ErrorsTest do
'unquote 1'
end
test :invalid_unquote_splicing_in_oneliners do
assert_compile_fail ArgumentError,
"unquote_splicing only works inside arguments and block contexts, " <>
"wrap it in parens if you want it to work with one-liners",
'''
defmodule Foo do
defmacro oneliner2 do
quote do: unquote_splicing 1
end
def callme do
oneliner2
end
end
'''
end
test :invalid_quote_args do
assert_compile_fail CompileError,
"nofile:1: invalid arguments for quote",
'quote 1'
assert_compile_fail CompileError,
"nofile:1: invalid options for quote, expected a keyword list",
'''
quote :foo do
foo
end
'''
end
test :invalid_calls do
@@ -709,8 +756,13 @@ defmodule Kernel.ErrorsTest do
end
'''
assert_compile_fail CompileError,
"nofile:2: spec for undefined function ErrorsTest.omg/0",
if :erlang.system_info(:otp_release) >= '18' do
message = "nofile:2: spec for undefined function omg/0"
else
message = "nofile:2: spec for undefined function ErrorsTest.omg/0"
end
assert_compile_fail CompileError, message,
'''
defmodule ErrorsTest do
@spec omg :: atom
@@ -392,11 +392,6 @@ defmodule Kernel.ExpansionTest do
## Try
test "try: expands do" do
assert expand(quote do: (try do x = y end; x)) ==
quote do: (try do x = y() end; x())
end
test "try: expands catch" do
assert expand(quote do: (try do x catch x, y -> z = :erlang.+(x, y) end; z)) ==
quote do: (try do x() catch x, y -> z = :erlang.+(x, y) end; z())
@@ -417,6 +412,12 @@ defmodule Kernel.ExpansionTest do
quote do: (try do x() rescue unquote(:in)(x, _) -> x; unquote(:in)(_, [:"Elixir.Error"]) -> x() end; x())
end
test "try: expects more than do" do
assert_raise CompileError, ~r"missing catch/rescue/after/else keyword in try", fn ->
expand(quote do: (try do x = y end; x))
end
end
## Binaries
test "bitstrings: size * unit" do
+30 -21
View File
@@ -154,15 +154,6 @@ defmodule Kernel.TypespecTest do
types(module)
end
test "@type with a range" do
module = test_module do
@type mytype :: range(1, 10)
end
assert [type: {:mytype, {:type, _, :range, [{:integer, _, 1}, {:integer, _, 10}]}, []}] =
types(module)
end
test "@type with a range op" do
module = test_module do
@type mytype :: 1..10
@@ -177,11 +168,19 @@ defmodule Kernel.TypespecTest do
@type mytype :: %{hello: :world}
end
assert [type: {:mytype,
{:type, _, :map, [
{:type, _, :map_field_assoc, {:atom, _, :hello}, {:atom, _, :world}}
]},
[]}] = types(module)
if :erlang.system_info(:otp_release) >= '18' do
assert [type: {:mytype,
{:type, _, :map, [
{:type, _, :map_field_assoc, [{:atom, _, :hello}, {:atom, _, :world}]}
]},
[]}] = types(module)
else
assert [type: {:mytype,
{:type, _, :map, [
{:type, _, :map_field_assoc, {:atom, _, :hello}, {:atom, _, :world}}
]},
[]}] = types(module)
end
end
test "@type with a struct" do
@@ -190,13 +189,23 @@ defmodule Kernel.TypespecTest do
@type mytype :: %TestTypespec{hello: :world}
end
assert [type: {:mytype,
{:type, _, :map, [
{:type, _, :map_field_assoc, {:atom, _, :__struct__}, {:atom, _, TestTypespec}},
{:type, _, :map_field_assoc, {:atom, _, :hello}, {:atom, _, :world}},
{:type, _, :map_field_assoc, {:atom, _, :other}, {:type, _, :term, []}}
]},
[]}] = types(module)
if :erlang.system_info(:otp_release) >= '18' do
assert [type: {:mytype,
{:type, _, :map, [
{:type, _, :map_field_assoc, [{:atom, _, :__struct__}, {:atom, _, TestTypespec}]},
{:type, _, :map_field_assoc, [{:atom, _, :hello}, {:atom, _, :world}]},
{:type, _, :map_field_assoc, [{:atom, _, :other}, {:type, _, :term, []}]}
]},
[]}] = types(module)
else
assert [type: {:mytype,
{:type, _, :map, [
{:type, _, :map_field_assoc, {:atom, _, :__struct__}, {:atom, _, TestTypespec}},
{:type, _, :map_field_assoc, {:atom, _, :hello}, {:atom, _, :world}},
{:type, _, :map_field_assoc, {:atom, _, :other}, {:type, _, :term, []}}
]},
[]}] = types(module)
end
end
test "@type with undefined struct" do
@@ -375,6 +375,20 @@ defmodule Kernel.WarningTest do
purge Sample
end
test :badarg_warning do
assert capture_err(fn ->
assert_raise ArgumentError, fn ->
Code.eval_string """
defmodule Sample do
Atom.to_string "abc"
end
"""
end
end) =~ "warning: this expression will fail with ArgumentError"
after
purge [Sample]
end
test :undefined_function_for_behaviour do
assert capture_err(fn ->
Code.eval_string """
+42
View File
@@ -6,11 +6,25 @@ defmodule KernelTest do
test "=~/2" do
assert ("abcd" =~ ~r/c(d)/) == true
assert ("abcd" =~ ~r/e/) == false
assert ("abcd" =~ ~R/c(d)/) == true
assert ("abcd" =~ ~R/e/) == false
string = "^ab+cd*$"
assert (string =~ "ab+") == true
assert (string =~ "bb") == false
assert ("abcd" =~ ~r//) == true
assert ("abcd" =~ ~R//) == true
assert ("abcd" =~ "") == true
assert ("" =~ ~r//) == true
assert ("" =~ ~R//) == true
assert ("" =~ "") == true
assert ("" =~ "abcd") == false
assert ("" =~ ~r/abcd/) == false
assert ("" =~ ~R/abcd/) == false
assert_raise FunctionClauseError, "no function clause matching in Kernel.=~/2", fn ->
1234 =~ "hello"
end
@@ -18,6 +32,34 @@ defmodule KernelTest do
assert_raise FunctionClauseError, "no function clause matching in Kernel.=~/2", fn ->
1234 =~ ~r"hello"
end
assert_raise FunctionClauseError, "no function clause matching in Kernel.=~/2", fn ->
1234 =~ ~R"hello"
end
assert_raise FunctionClauseError, "no function clause matching in Kernel.=~/2", fn ->
~r"hello" =~ "hello"
end
assert_raise FunctionClauseError, "no function clause matching in Kernel.=~/2", fn ->
~r"hello" =~ ~r"hello"
end
assert_raise FunctionClauseError, "no function clause matching in Kernel.=~/2", fn ->
:abcd =~ ~r//
end
assert_raise FunctionClauseError, "no function clause matching in Kernel.=~/2", fn ->
:abcd =~ ""
end
assert_raise FunctionClauseError, "no function clause matching in Regex.match?/2", fn ->
"abcd" =~ nil
end
assert_raise FunctionClauseError, "no function clause matching in Regex.match?/2", fn ->
"abcd" =~ :abcd
end
end
test "^" do
-7
View File
@@ -75,13 +75,6 @@ defmodule ListTest do
assert List.zip([[1, 4], [2, 5], []]) == []
end
test :unzip do
assert List.unzip([{1, 2, 3}, {4, 5, 6}]) == [[1, 4], [2, 5], [3, 6]]
assert List.unzip([{1, 2, 3}, {4, 5}]) == [[1, 4], [2, 5]]
assert List.unzip([[1, 2, 3], [4, 5]]) == [[1, 4], [2, 5]]
assert List.unzip([]) == []
end
test :keyfind do
assert List.keyfind([a: 1, b: 2], :a, 0) == {:a, 1}
assert List.keyfind([a: 1, b: 2], 2, 1) == {:b, 2}
+6
View File
@@ -38,6 +38,12 @@ defmodule PathTest do
assert Path.relative("/usr/local/bin") == "usr/local/bin"
assert Path.relative("usr/local/bin") == "usr/local/bin"
assert Path.relative("../usr/local/bin") == "../usr/local/bin"
assert Path.relative_to("D:/usr/local/foo", "D:/usr/") == "local/foo"
assert Path.relative_to("D:/usr/local/foo", "d:/usr/") == "local/foo"
assert Path.relative_to("d:/usr/local/foo", "D:/usr/") == "local/foo"
assert Path.relative_to("D:/usr/local/foo", "d:") == "usr/local/foo"
assert Path.relative_to("D:/usr/local/foo", "D:") == "usr/local/foo"
end
test :type_win do
+12 -4
View File
@@ -121,11 +121,19 @@ defmodule ProtocolTest do
end
test "protocol defines callbacks" do
assert get_callbacks(Sample, :ok, 1) ==
[{:type, 9, :fun, [{:type, 9, :product, [{:type, 9, :t, []}]}, {:type, 9, :boolean, []}]}]
if :erlang.system_info(:otp_release) >= '18' do
assert get_callbacks(Sample, :ok, 1) ==
[{:type, 9, :fun, [{:type, 9, :product, [{:user_type, 9, :t, []}]}, {:type, 9, :boolean, []}]}]
assert get_callbacks(WithAny, :ok, 1) ==
[{:type, 16, :fun, [{:type, 16, :product, [{:type, 16, :t, []}]}, {:type, 16, :term, []}]}]
assert get_callbacks(WithAny, :ok, 1) ==
[{:type, 16, :fun, [{:type, 16, :product, [{:user_type, 16, :t, []}]}, {:type, 16, :term, []}]}]
else
assert get_callbacks(Sample, :ok, 1) ==
[{:type, 9, :fun, [{:type, 9, :product, [{:type, 9, :t, []}]}, {:type, 9, :boolean, []}]}]
assert get_callbacks(WithAny, :ok, 1) ==
[{:type, 16, :fun, [{:type, 16, :product, [{:type, 16, :t, []}]}, {:type, 16, :term, []}]}]
end
end
test "protocol defines attributes" do
+4
View File
@@ -189,6 +189,10 @@ defmodule RegexTest do
assert Regex.replace(~r(b), "abc", "d") == "adc"
assert Regex.replace(~r(b), "abc", "[\\0]") == "a[b]c"
assert Regex.replace(~r[(b)], "abc", "[\\1]") == "a[b]c"
assert Regex.replace(~r[(b)], "abc", "[\\2]") == "a[]c"
assert Regex.replace(~r[(b)], "abc", "[\\3]") == "a[]c"
assert Regex.replace(~r(b), "abc", "[\\g{0}]") == "a[b]c"
assert Regex.replace(~r[(b)], "abc", "[\\g{1}]") == "a[b]c"
assert Regex.replace(~r(b), "abcbe", "d") == "adcde"
assert Regex.replace(~r(b), "abcbe", "d", global: false) == "adcbe"
+49
View File
@@ -67,6 +67,19 @@ defmodule StreamTest do
assert Enum.zip(list, list) == Enum.zip(stream, stream)
end
test "chunk/4 is haltable" do
assert 1..10 |> Stream.take(6) |> Stream.chunk(4, 4, [7, 8]) |> Enum.to_list ==
[[1, 2, 3, 4], [5, 6, 7, 8]]
assert 1..10 |> Stream.take(6) |> Stream.chunk(4, 4, [7, 8]) |> Stream.take(3) |> Enum.to_list ==
[[1, 2, 3, 4], [5, 6, 7, 8]]
assert 1..10 |> Stream.take(6) |> Stream.chunk(4, 4, [7, 8]) |> Stream.take(2) |> Enum.to_list ==
[[1, 2, 3, 4], [5, 6, 7, 8]]
assert 1..10 |> Stream.take(6) |> Stream.chunk(4, 4, [7, 8]) |> Stream.take(1) |> Enum.to_list ==
[[1, 2, 3, 4]]
assert 1..6 |> Stream.take(6) |> Stream.chunk(4, 4, [7, 8]) |> Enum.to_list ==
[[1, 2, 3, 4], [5, 6, 7, 8]]
end
test "chunk_by/2" do
stream = Stream.chunk_by([1, 2, 2, 3, 4, 4, 6, 7, 7], &(rem(&1, 2) == 1))
@@ -75,6 +88,8 @@ defmodule StreamTest do
[[1], [2, 2], [3], [4, 4, 6], [7, 7]]
assert stream |> Stream.take(3) |> Enum.to_list ==
[[1], [2, 2], [3]]
assert 1..10 |> Stream.chunk(2) |> Enum.take(2) ==
[[1, 2], [3, 4]]
end
test "chunk_by/2 is zippable" do
@@ -480,6 +495,40 @@ defmodule StreamTest do
assert Process.get(:stream_resource)
end
test "resource/3 halts with inner enum" do
stream = Stream.resource(fn -> 1 end,
fn acc -> {acc..acc+2, acc + 1} end,
fn _ -> Process.put(:stream_resource, true) end)
Process.put(:stream_resource, false)
assert Enum.take(stream, 5) == [1, 2, 3, 2, 3]
assert Process.get(:stream_resource)
end
test "resource/3 closes on errors with inner enum" do
stream = Stream.resource(fn -> 1 end,
fn acc -> {acc..acc+2, acc + 1} end,
fn _ -> Process.put(:stream_resource, true) end)
Process.put(:stream_resource, false)
stream = Stream.map(stream, fn x -> if x > 2, do: throw(:error), else: x end)
assert catch_throw(Enum.to_list(stream)) == :error
assert Process.get(:stream_resource)
end
test "resource/3 is zippable with inner enum" do
stream = Stream.resource(fn -> 1 end,
fn 10 -> {:halt, 10}
acc -> {acc..acc+2, acc + 1}
end,
fn _ -> Process.put(:stream_resource, true) end)
list = Enum.to_list(stream)
Process.put(:stream_resource, false)
assert Enum.zip(list, list) == Enum.zip(stream, stream)
assert Process.get(:stream_resource)
end
test "scan/2" do
stream = Stream.scan(1..5, &(&1 + &2))
assert is_lazy(stream)
+2 -1
View File
@@ -192,6 +192,7 @@ defmodule StringTest do
assert String.reverse("Hello ∂og") == "go∂ olleH"
assert String.reverse("Ā̀stute") == "etutsĀ̀"
assert String.reverse(String.reverse("Hello World")) == "Hello World"
assert String.reverse(String.reverse("Hello \r\n World")) == "Hello \r\n World"
end
test :replace do
@@ -238,7 +239,7 @@ defmodule StringTest do
# Extended
assert String.graphemes("Ā̀stute") == ["Ā̀", "s", "t", "u", "t", "e"]
# CLRF
assert String.graphemes("\n\r\f") == ["\n\r", "\f"]
assert String.graphemes("\r\n\f") == ["\r\n", "\f"]
# Regional indicator
assert String.graphemes("\x{1F1E6}\x{1F1E7}\x{1F1E8}") == ["\x{1F1E6}\x{1F1E7}\x{1F1E8}"]
# Hangul
@@ -64,7 +64,7 @@ defmodule Supervisor.SpecTest do
test "supervise/2" do
assert supervise([], strategy: :one_for_one) == {
:ok, {{:one_for_one, 5, 5}, []}
:ok, {{:one_for_one, 3, 5}, []}
}
children = [worker(GenEvent, [])]
+23
View File
@@ -65,6 +65,16 @@ defmodule SystemTest do
opts = [into: [], cd: System.cwd!, env: %{"foo" => "bar"},
arg0: "hecho", stderr_to_stdout: true, parallelism: true]
assert {["hello\n"], 0} = System.cmd "echo", ["hello"], opts
with_tmp_dir(fn dir ->
new_path = Path.join([dir, "echo2"])
File.cp!(System.find_executable("echo"), new_path)
assert :enoent = catch_error(System.cmd(new_path, ["hello"]))
File.cd!(dir)
assert :enoent = catch_error(System.cmd("echo2", ["hello"]))
assert {"hello\n", 0} = System.cmd(Path.join([System.cwd, "echo2"]), ["hello"])
end)
end
test "find_executable/1" do
@@ -72,4 +82,17 @@ defmodule SystemTest do
assert is_binary System.find_executable("erl")
assert !System.find_executable("does-not-really-exist-from-elixir")
end
defp with_tmp_dir(function) do
dir = tmp_dir_name()
tmp_dir = System.tmp_dir!
tmp_path = Path.join [tmp_dir, dir]
File.rm_rf! tmp_path
File.mkdir_p! tmp_path
File.cd! tmp_dir, fn -> function.(dir) end
end
defp tmp_dir_name do
:crypto.rand_bytes(4) |> Base.encode16
end
end
+12 -4
View File
@@ -142,17 +142,25 @@ defmodule URITest do
test :default_port do
assert URI.default_port("http") == 80
assert URI.default_port("unknown") == nil
try do
URI.default_port("http", 8000)
assert URI.default_port("http") == 8000
after
URI.default_port("http", 80)
end
assert URI.default_port("unknown") == nil
URI.default_port("unknown", 13)
assert URI.default_port("unknown") == 13
end
test :parse_bad_uris do
assert URI.parse("https:??@?F?@#>F//23/")
assert URI.parse("")
assert URI.parse(":https")
assert URI.parse("https")
assert URI.parse("https:??@?F?@#>F//23/")
assert URI.parse(":https").path == ":https"
assert URI.parse("https").path == "https"
assert URI.parse("ht\0tps://foo.com").path == "ht\0tps://foo.com"
end
test :ipv6_addresses do
+2 -2
View File
@@ -208,8 +208,8 @@ defmodule String.Graphemes do
end
# Don't break CRLF
def next_grapheme(<< ?\n, ?\r, rest :: binary >>) do
{"\n\r", rest}
def next_grapheme(<< ?\r, ?\n, rest :: binary >>) do
{"\r\n", rest}
end
# Break on control
+41 -22
View File
@@ -207,26 +207,16 @@ defmodule ExUnit.Assertions do
true
end
@doc """
Asserts `value` is true.
If it fails, it raises an expectation error
using the given `left` and `right` values.
You probably don't need to use this—the regular `assert` function
handles this for you.
## Examples
assert this > that, this, that, "more than"
"""
@doc false
def assert(value, left, right, message) when is_binary(message) do
assert(value, left: left, right: right, message: message)
end
@doc """
Asserts a message was or is going to be received. Unlike
`assert_received`, it has a default timeout of 100 milliseconds.
Asserts a message was or is going to be received.
Unlike `assert_received`, it has a default timeout
of 100 milliseconds.
The `expected` argument is a pattern.
@@ -273,19 +263,50 @@ defmodule ExUnit.Assertions do
defp do_assert_receive(expected, timeout, message) do
binary = Macro.to_string(expected)
message = message || "No message matching #{binary}"
pattern =
case expected do
{:when, meta, [left, right]} ->
{:when, meta, [quote(do: unquote(left) = received), right]}
left ->
quote(do: unquote(left) = received)
end
{:receive, meta, args} =
quote do
receive do
unquote(expected) = received -> received
unquote(pattern) ->
received
after
unquote(timeout) ->
flunk unquote(message)
timeout ->
message = unquote(message) || "No message matching #{unquote(binary)} after #{timeout}ms"
flunk(message <> ExUnit.Assertions.__mailbox__(self()))
end
end
{:receive, [{:export_head, true}|meta], args}
quote do
timeout = unquote(timeout)
unquote({:receive, [{:export_head, true}|meta], args})
end
end
@max_mailbox_length 10
@doc false
def __mailbox__(pid) do
{:messages, messages} = Process.info(pid, :messages)
length = length(messages)
mailbox = Enum.take(messages, @max_mailbox_length) |> Enum.map_join("\n", &inspect/1)
mailbox_message(length, mailbox)
end
defp mailbox_message(0, _mailbox), do: ". The process mailbox is empty."
defp mailbox_message(length, mailbox) when length > 10 do
". Process mailbox:\n" <> mailbox
<> "\nShowing only #{@max_mailbox_length} of #{length} messages."
end
defp mailbox_message(_length, mailbox) do
". Process mailbox:\n" <> mailbox
end
@doc """
@@ -429,8 +450,6 @@ defmodule ExUnit.Assertions do
end
@doc """
refute_receive message, timeout \\ 100, message \\ nil
Asserts `message` was not received (and won't be received) within
the `timeout` period.
+8 -6
View File
@@ -162,15 +162,17 @@ defmodule ExUnit.Case do
quote do
unless Module.get_attribute(__MODULE__, :ex_unit_tests) do
if unquote(async) do
ExUnit.Server.add_async_case(__MODULE__)
else
ExUnit.Server.add_sync_case(__MODULE__)
end
Enum.each [:ex_unit_tests, :tag, :moduletag],
&Module.register_attribute(__MODULE__, &1, accumulate: true)
if unquote(async) do
@moduletag async: true
ExUnit.Server.add_async_case(__MODULE__)
else
@moduletag async: false
ExUnit.Server.add_sync_case(__MODULE__)
end
@before_compile ExUnit.Case
use ExUnit.Callbacks
end
+4 -3
View File
@@ -152,6 +152,7 @@ defmodule ExUnit.DocTest do
tests = quote bind_quoted: binding do
file = "(for doctest at) " <> Path.relative_to_cwd(mod.__info__(:compile)[:source])
for {name, test} <- ExUnit.DocTest.__doctests__(mod, opts) do
@tag :doctest
@file file
test name, do: unquote(test)
end
@@ -348,9 +349,9 @@ defmodule ExUnit.DocTest do
moduledocs = extract_from_moduledoc(all_docs[:moduledoc])
docs = for doc <- all_docs[:docs] do
extract_from_doc(doc)
end |> Enum.concat
docs = for doc <- all_docs[:docs],
doc <- extract_from_doc(doc),
do: doc
moduledocs ++ docs
end
+8 -7
View File
@@ -9,23 +9,23 @@ defmodule ExUnit.Formatter do
The following events are possible:
* `{:suite_started, opts}` -
the suite has started with the specified options to the runner.
the suite has started with the specified options to the runner.
* `{:suite_finished, run_us, load_us}` -
the suite has finished. `run_us` and `load_us` are the run and load
times in microseconds respectively.
the suite has finished. `run_us` and `load_us` are the run and load
times in microseconds respectively.
* `{:case_started, test_case}` -
a test case has started. See `ExUnit.TestCase` for details.
a test case has started. See `ExUnit.TestCase` for details.
* `{:case_finished, test_case}` -
a test case has finished. See `ExUnit.TestCase` for details.
a test case has finished. See `ExUnit.TestCase` for details.
* `{:test_started, test_case}` -
a test case has started. See `ExUnit.Test` for details.
a test case has started. See `ExUnit.Test` for details.
* `{:test_finished, test_case}` -
a test case has finished. See `ExUnit.Test` for details.
a test case has finished. See `ExUnit.Test` for details.
"""
@@ -171,6 +171,7 @@ defmodule ExUnit.Formatter do
end
defp format_banner(value, formatter) do
value = String.replace(value, "\n", "\n" <> @counter_padding)
formatter.(:error_info, value)
end
+16 -4
View File
@@ -47,7 +47,7 @@ defmodule ExUnit.Runner do
|> Keyword.put(:exclude, exclude)
|> Keyword.put(:include, include)
|> Keyword.put_new(:max_cases, :erlang.system_info(:schedulers_online))
|> Keyword.put_new(:seed, :erlang.now |> elem(2))
|> Keyword.put_new(:seed, :os.timestamp |> elem(2))
end
defp loop(config) do
@@ -159,7 +159,9 @@ defmodule ExUnit.Runner do
{test_case, pending} =
receive do
{^case_pid, :case_finished, test_case, tests} ->
Process.demonitor(case_ref, [:flush])
receive do
{:DOWN, ^case_ref, :process, ^case_pid, _} -> :ok
end
{test_case, tests}
{:DOWN, ^case_ref, :process, ^case_pid, error} ->
test_case = %{test_case | state: {:failed, {{:EXIT, case_pid}, error, []}}}
@@ -214,15 +216,25 @@ defmodule ExUnit.Runner do
test =
receive do
{^test_pid, :test_finished, test} ->
Process.demonitor(test_ref, [:flush])
receive do
{:DOWN, ^test_ref, :process, ^test_pid, _} -> :ok
end
test
{:DOWN, ^test_ref, :process, ^test_pid, error} ->
%{test | state: {:failed, {{:EXIT, test_pid}, error, []}}}
after
timeout ->
stacktrace =
try do
Process.info(test_pid, :current_stacktrace)
catch
_, _ -> []
else
{:current_stacktrace, stacktrace} -> stacktrace
end
Process.exit(test_pid, :kill)
Process.demonitor(test_ref, [:flush])
%{test | state: {:failed, {:error, %ExUnit.TimeoutError{timeout: timeout}, []}}}
%{test | state: {:failed, {:error, %ExUnit.TimeoutError{timeout: timeout}, stacktrace}}}
end
exec_on_exit(test, test_pid)
+25 -2
View File
@@ -86,12 +86,35 @@ defmodule ExUnit.AssertionsTest do
:hello = assert_received :hello
end
test "assert received when different" do
test "assert received when empty mailbox" do
try do
"This should never be tested" = assert_received :hello
rescue
error in [ExUnit.AssertionError] ->
"No message matching :hello" = error.message
"No message matching :hello after 0ms. The process mailbox is empty." = error.message
end
end
test "assert received when different message" do
send self, {:message, :not_expected, :at_all}
try do
"This should never be tested" = assert_received :hello
rescue
error in [ExUnit.AssertionError] ->
"No message matching :hello after 0ms. Process mailbox:\n{:message, :not_expected, :at_all}" = error.message
end
end
test "assert received when different message having more than 10 on mailbox" do
for i <- 1..11, do: send(self, {:message, i})
try do
"This should never be tested" = assert_received x when x == :hello
rescue
error in [ExUnit.AssertionError] ->
"No message matching x when x == :hello after 0ms. Process mailbox:\n" <>
"{:message, 1}\n{:message, 2}\n{:message, 3}\n{:message, 4}\n" <>
"{:message, 5}\n{:message, 6}\n{:message, 7}\n{:message, 8}\n" <>
"{:message, 9}\n{:message, 10}\nShowing only 10 of 11 messages." = error.message
end
end
+1
View File
@@ -19,6 +19,7 @@ defmodule ExUnit.CaseTest do
assert context[:case] == __MODULE__
assert context[:test] == __ENV__.function |> elem(0)
assert context[:line] == line
assert context[:async] == true
assert context[:hello] == true
assert context[:world] == :good
end

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