Compare 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
José Valim 5b4497fa6b Release v1.0.0-rc1 2014-08-30 13:33:02 -04:00
José Valim 612e124ec9 Add tests for sync_threshold 2014-08-29 08:13:53 -04:00
José Valim abd2b35738 Get rid of Logger timeout config, closes #2724 2014-08-29 08:01:03 -04:00
José Valim 2afb875099 Merge pull request #2725 from MSch/document-compile-attr-accumulation
Document that @compile accumulates.
2014-08-28 14:10:41 -04:00
Martin Schürrer 88507f65f8 Document that @compile accumulates. 2014-08-28 20:05:56 +02:00
José Valim 275d1f5494 Straight up and unify impls of clean, deps.clean and deps.unlock 2014-08-28 13:00:36 -04:00
José Valim e6420c8d13 Merge pull request #2715 from nurugger07/remove_unused_deps
Add an unused flag to deps.clean & deps.unlock
2014-08-28 11:38:46 -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
José Valim 2af00f4c33 Merge pull request #2723 from edgurgel/fix-private-function-on-algebra
Make do_surround_many private on Inspect.Algebra
2014-08-27 16:08:55 -04:00
Eduardo Gurgel 0fe86e77c1 Make do_surround_many private on Inspect.Algebra 2014-08-28 08:01:32 +12:00
José Valim 1ca451f779 Normalize is_guard checks 2014-08-27 15:28:31 -04:00
José Valim ebe0c8cf8e Update CHANGELOG 2014-08-27 12:21:12 -04:00
José Valim de9feb26d5 Deprecate x.Alias syntax 2014-08-27 12:17:15 -04:00
José Valim b8528f6c8e Remove warnings from the test suite 2014-08-25 14:42:24 -04:00
Alexei Sholik c9fbbac391 Add version checks and proper error formatting to escripts. Closes #2709
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2014-08-25 14:26:54 -04:00
José Valim 98e1b4ccf8 Ensure we tag the destination when compiling through mix
Closes #2533
2014-08-25 14:26:10 -04:00
Alexei Sholik ae19491620 Merge pull request #2701 from alco/escript-protocol-consolidation
Escript protocol consolidation
2014-08-23 23:24:48 +03:00
Alexei Sholik 64f3760b19 Add missing fixtures for escript test 2014-08-23 23:16:29 +03:00
José Valim 0b38bc050f Demonitor and flush when appropriate 2014-08-23 15:56:38 -04:00
José Valim 9431338654 Add timeouts to ExUnit, closes #2660 2014-08-23 15:55:33 -04:00
José Valim 88be750a32 import_config/1 now accepts wildcards 2014-08-23 15:14:12 -04:00
José Valim 032c8b8291 Update CHANGELOG 2014-08-23 14:42:52 -04:00
José Valim 2d90d8ac60 Remove code duplication on watcher 2014-08-23 14:38:28 -04:00
José Valim a8b2cb99ee Use to_string when possible on Logger.log 2014-08-23 14:34:51 -04:00
José Valim 68274311fb Merge pull request #2712 from fishcakez/logger_rc
Fix logger watcher race condition
2014-08-23 14:34:05 -04:00
José Valim 88f84f80b5 Merge pull request #2713 from fishcakez/logger_eh_racecon
Fix race condition in Logger.ErrorHandlerTest
2014-08-23 14:32:48 -04:00
James Fish 2bf898e8fe Fix race condition in Logger.ErrorHandlerTest
When crashing the error logger handler, the handler's watcher could log
its message before Logger was flushed, causing the watcher's log
message to appear in the first, rather than second, capture_log.
2014-08-23 19:20:06 +01:00
José Valim d45f451943 Allow configurable timeout and catch exits on logger 2014-08-23 14:09:11 -04:00
James Fish c0a075724f Fix logger watcher race condition
Previously a backend could be removed from logger and the watcher would
not have exited before the remove_backend/1 call returned. This meant
that a new watcher could not always be started because the child
(watcher) might still exist but be about to die.
2014-08-23 18:59:13 +01:00
José Valim f1a39375c9 Do not crash the caller when Logger is not available
Instead, return :ok | {:error, :not_available} and leave it up
to the caller to decide.
2014-08-23 13:43:58 -04:00
Alexei Sholik b9c1b22163 Add protocol consolidation support to escript.build. Closes #2699 2014-08-23 20:40:56 +03:00
José Valim be75e3be17 Purge in memory in modules before consolidating, closes #2700 2014-08-23 13:32:19 -04:00
José Valim 9793caa1ef Ensure we automatically load deps on compile 2014-08-23 12:22:38 -04:00
José Valim 0792fe2c52 Merge pull request #2708 from alco/typo
Fix a typo in Mix.Project.compile_path doc
2014-08-22 12:02:31 -04:00
Alexei Sholik 4408aa3450 Fix a typo in Mix.Project.compile_path doc 2014-08-22 18:33:24 +03:00
Eric Meadows-Jönsson e4ba8d5d21 Mix should not depend directly on Logger 2014-08-22 15:56:25 +02:00
Eric Meadows-Jönsson 689f14a867 Always compile project if task cannot be found
Closes #2704.
2014-08-21 15:26:58 +02:00
Eric Meadows-Jönsson aa5b414e8e Merge pull request #2703 from Kosmas/master
Update CONTRIBUTING.md
2014-08-21 12:19:11 +02:00
Kosmas Chatzimichalis 07467a733b Update CONTRIBUTING.md
Corrected 'should be send' to 'should be sent'
2014-08-21 11:34:34 +02:00
Alexei Sholik 74391b793c Merge pull request #2687 from alco/mix-language-setting
Mix language setting
2014-08-20 20:56:20 +03:00
Alexei Sholik f0b8bc1756 Use common code for Elixir and Erlang escripts 2014-08-20 20:38:24 +03:00
José Valim 3e54b95bf2 Fix the build 2014-08-20 11:30:30 -04:00
José Valim 313d26cb3b Use task start to get better reports 2014-08-20 10:28:11 -04:00
José Valim 44b85be014 Add Task.start/1 and Task.start/3 2014-08-20 10:27:23 -04:00
José Valim e344b1f9d8 Add a small note to UndefinedFunctionError when the module is not available 2014-08-20 10:22:36 -04:00
Eric Meadows-Jönsson f5f4205969 Merge pull request #2698 from Kosmas/master
Update CONTRIBUTING.md
2014-08-20 15:08:20 +02:00
Kosmas Chatzimichalis 538c717ded Update CONTRIBUTING.md
renamed 'issues tracker' to 'issue tracker' for consistency
2014-08-20 14:54:50 +02:00
Eric Meadows-Jönsson 8dd81e661a Merge pull request #2697 from hamiltop/patch-1
Fix docs (missing indentation in examples)
2014-08-20 11:24:32 +02:00
hamiltop 26b61b98f9 Fix docs (missing indentation in examples) 2014-08-19 20:28:24 -07:00
Eric Meadows-Jönsson ab9450d92f Don't print the underlying type for @opaque
Closes #2612
2014-08-19 17:14:30 +02:00
Eric Meadows-Jönsson a4a23b9a93 Fix Enum.traverse deprecated warning doctest 2014-08-19 17:00:58 +02:00
Eric Meadows-Jönsson 2f83ea5a7f Wrap erlang macros in () 2014-08-19 17:00:58 +02:00
Eric Meadows-Jönsson 7940d223ec Move elixir.hrl to src/ 2014-08-19 17:00:58 +02:00
Eric Meadows-Jönsson c6fa951520 Better error message on nbsp 2014-08-19 17:00:58 +02:00
José Valim e5c8194a70 Rename :add_stream_handler to :add_process_handler 2014-08-19 09:55:26 -04:00
Eric Meadows-Jönsson 4a7e3dd11f Document alias arguments 2014-08-19 14:16:19 +02:00
José Valim 638fccb79d Remove duration from GenEvent.Stream 2014-08-19 07:34:49 -04:00
José Valim e9d84ec43b Allow gen_event_EXIT notifications to be disabled for streams 2014-08-19 07:07:23 -04:00
Eric Meadows-Jönsson bb35b9afb1 Include makefile in precompiled zip 2014-08-18 23:11:27 +02:00
Eric Meadows-Jönsson 1496c04592 Improve error message for invalid typespec 2014-08-18 23:08:26 +02:00
José Valim 3e9725a5a8 Make tests that remove the logging backend sync 2014-08-18 16:51:38 -04:00
José Valim 1565ec7658 Update CHANGELOG 2014-08-18 16:43:17 -04:00
José Valim f1aa2f01c7 Merge pull request #2695 from elixir-lang/jv-ge
GenEvent++
2014-08-18 16:33:11 -04:00
José Valim 7284542116 Split GenEvent.Stream into its own file 2014-08-18 16:28:25 -04:00
José Valim 4f17e8fd1c Also tag notify messages 2014-08-18 16:20:25 -04:00
José Valim f536507b9d Make the mode a property of the sender 2014-08-18 15:38:55 -04:00
José Valim 592300a0f0 Ensure Logger watcher monitors event manager 2014-08-18 09:18:28 -04:00
Alexei Sholik 28b1bd16de Document the :language setting 2014-08-18 16:12:15 +03:00
José Valim 19fdd84f80 Update docs 2014-08-17 19:48:50 -04:00
José Valim 631e2a051a Document lack of guaranteed on monitored handlers 2014-08-17 18:44:06 -04:00
José Valim df0f7b5f95 Fix failures in logger 2014-08-17 18:36:59 -04:00
José Valim a0dfc4160e Ensure proper whereis expansion 2014-08-17 18:28:14 -04:00
José Valim ee8331023e Do not add duplicated handlers 2014-08-17 16:55:11 -04:00
José Valim a2ee71f5ab Implement some of fishcakez feedback 2014-08-17 13:18:55 -04:00
José Valim 60163194e2 Improve coverage for GenEvent 2014-08-17 09:20:02 -04:00
José Valim ba37682300 Merge pull request #2691 from herenowcoder/patch-1
fix doc typo in Macro.expand/2
2014-08-17 08:57:53 -04:00
Wojciech Kaczmarek 15265b9352 fix doc typo in Macro.expand/2 2014-08-17 14:55:25 +02:00
Alexei Sholik 0a929310de Support building Erlang-based escripts in mix escript.build 2014-08-17 15:19:42 +03:00
José Valim a8670c1cfa Do not pre-compile a project if an alias was found
Closes #2689
2014-08-17 06:10:20 -04:00
Alexei Sholik ebd18d45fa Only add :elixir application for Elixir projects in compile.app task 2014-08-17 01:22:33 +03:00
José Valim ccbfec611a Merge pull request #2684 from alco/mix-compile-erlang
Properly handle compilation errors in Erlang compiler
2014-08-16 17:05:00 -04:00
Alexei Sholik 98a0999c2e Properly handle compilation errors in Erlang compiler 2014-08-16 23:45:44 +03:00
José Valim e504142d3a Merge pull request #2686 from billgathen/patch-1
Docs: Using autocomplete for function discovery in IEx
2014-08-16 15:58:01 -04:00
José Valim c04e13169f Deprecate Collectable.empty and Enum.traverse
The current implementation has three issues:

1. It is slow as we can't reuse the data structure but rather
   build a new one from scratch

2. Due to 1., trees can't be traversed

3. empty/1 is unable to deal with resources
2014-08-16 15:46:58 +02:00
José Valim 96a4bff67b Use the new resource acc on GenEvent.stream/2 2014-08-16 15:41:30 +02:00
José Valim 46de5ea462 Make Stream.resource/3 more like Stream.transform/2
With this change, we allow the accumulator to be passed
to after_fun once iteration is done.

This, however, introduces a deprecation on the old
format used by Stream.resource/3.
2014-08-16 15:35:56 +02:00
Bill Gathen 22257b8342 Using autocomplete for function discovery
I had to go to the mailing list to discover this trick. Knowing it's autocomplete supplying the information makes it obvious in hindsight, but I had it in my head that it would be part of the h() command. It might also be worth adding a mention of autocomplete to the h(IEx) page, as well, but since this is likely first help page a new user would come across, this may be enough.
2014-08-16 08:59:18 -04:00
José Valim a0be9fe272 Improve Logger formatter defaults
* Make colors in the docs consistent with the ones in the code
* Add new lines before and after each message
2014-08-15 18:30:30 +02:00
José Valim 1fd5a2a38e Use monitored handlers instead of linked ones 2014-08-15 18:02:47 +02:00
José Valim 13aec18bd5 Update docs 2014-08-15 13:57:22 +02:00
José Valim a85938d092 Fix the build 2014-08-15 13:54:08 +02:00
José Valim 0d4af57abe Port GenEvent to Elixir for upcoming extensions 2014-08-15 13:16:14 +02:00
José Valim e00db53112 Talk about correctness first, performance later 2014-08-14 22:09:00 +02:00
José Valim b941618a10 Port the :gen_event client API to Elixir
The only difference is that notify is made similar to
GenServer's cast where an unknown atom name does not
trigger an error.
2014-08-13 18:20:10 +02:00
Eric Meadows-Jönsson d895c762a1 Add --force option to 'mix local.rebar' 2014-08-13 17:11:28 +02:00
Eric Meadows-Jönsson 427c4b01c9 Ensure load_tasks return no duplicates 2014-08-13 17:10:40 +02:00
Eric Meadows-Jönsson 9d5a728cb8 Output mix version on 'mix -v' 2014-08-13 17:10:40 +02:00
José Valim 1ed99d4ac7 Improve error message for undefined struct 2014-08-13 09:19:26 +02:00
José Valim ceb17fac7d Use GenServer when possible 2014-08-12 23:22:51 +02:00
José Valim c638f45542 Merge pull request #2674 from koudelka/unquote-doc-fix
Minor unquote/1 doc fix.
2014-08-12 21:40:02 +02:00
Michael Shapiro c09905d537 Minor unquote/1 doc fix. 2014-08-12 12:23:08 -07:00
José Valim 117a428030 Merge pull request #2668 from reset/fix-nocolor
fix --no-color / --color option for `mix test`
2014-08-12 19:32:33 +02:00
Jamie Winsor 2ada7c40d7 fix --no-color / --color option for mix test 2014-08-12 10:32:10 -07:00
José Valim cd162bf3d6 Do not inherit bugs on GenServer calls and casts
Prior to this change, we were inheriting OTP bug where
bad arguments in call were treated as exits.

We also fixed the bug in cast where messages could be
delivered out of order. It is preferrable to wait on
connection.
2014-08-12 17:30:25 +02:00
José Valim e6e71675ac Make a distinction between eol and ; 2014-08-12 16:27:45 +02:00
José Valim c54e0caf6f Merge pull request #2670 from chyndman/cygpath-quotes
Use quotes to avoid space-in-path issues with cygpath
2014-08-12 09:34:41 +02:00
José Valim b64552780e Merge pull request #2671 from lexmag/no-purge
No need in some purges in `Kernel.WarningTest`
2014-08-12 09:34:25 +02:00
Eric Meadows-Jönsson add8c66e82 Merge pull request #2673 from alexrp/master
Clarify match?/2 docs.
2014-08-12 02:02:35 +02:00
Alex Rønne Petersen aecbd2c16a Clarify match?/2 docs. 2014-08-12 01:54:08 +02:00
Aleksei Magusev 32a649c1cd No need for Stream. prefix 2014-08-12 03:31:03 +04:00
Aleksei Magusev a1e657cb75 No need in some purges in Kernel.WarningTest 2014-08-12 03:06:48 +04:00
Chris Hyndman 6bb4c4de03 Use quotes to avoid space-in-path issues with cygpath 2014-08-11 15:13:50 -07:00
José Valim ea5b56d434 Merge pull request #2667 from MSch/fix-new-config-exs
Fix example in config/config.exs that's created for new projects.
2014-08-11 20:51:03 +02:00
Martin Schürrer a4082748cf Fix example in config/config.exs that's created for new projects. 2014-08-11 20:43:36 +02:00
José Valim 4d763846c3 Ensure code escape \ works in IEx 2014-08-11 10:05:24 +02:00
José Valim d56cc88f5f Start v0.15.2-dev 2014-08-11 10:05:24 +02:00
José Valim 5e27b7b72b Merge pull request #2666 from hamiltop/stream_cycle_performance
Add special case for lists in Stream.cycle
2014-08-11 09:53:38 +02:00
Peter Hamilton 260d6d16ab Add special case for lists in Stream.cycle 2014-08-11 00:38:53 -07:00
José Valim 1a82f126a4 Merge pull request #2664 from hamiltop/add_timer_and_interval_streams
Add Stream.interval/1 and Stream.timer/1
2014-08-11 08:16:51 +02:00
Peter Hamilton 3cf3eee2ea Clean up interval/1 and timer/1 docs 2014-08-10 16:03:43 -07:00
Peter Hamilton 9e8a41b2d3 Add Stream.interval/1 and Stream.timer/1 2014-08-10 11:25:53 -07:00
José Valim a54d9212b8 Release v0.15.1 2014-08-10 19:06:49 +02:00
José Valim ee33608f75 Update CHANGELOG 2014-08-10 19:05:45 +02:00
José Valim 6e91275782 Merge pull request #2663 from lexmag/fix-pipe_op
Bind value to proper name in `elixir_tokenizer`
2014-08-10 17:56:59 +02:00
Aleksei Magusev c6f27541c1 Bind value to proper name in elixir_tokenizer 2014-08-10 19:41:13 +04:00
José Valim a2662a4b51 Ensure elixir and iex works on MinGW 2014-08-10 15:11:18 +02:00
José Valim 1d29262949 Check for terminal on both stdout and stderr 2014-08-10 14:40:46 +02:00
José Valim 1d655fa8f2 Check for terminal support from Elixir command line 2014-08-10 13:57:41 +02:00
José Valim 4bec785f4c Also support :sync mode in GenEvent, make :ack one the default 2014-08-10 12:00:13 +02:00
José Valim e3273f731f Fix build 2014-08-09 21:50:24 +02:00
José Valim a8564a7045 Deprecate functions as collectable 2014-08-09 21:32:32 +02:00
José Valim e2bac56556 Reply to GenEvent manager from stream right away
This way the GenEvent can start processing the other
handlers, reducing the processing time for each event
from sum(handlers) to max(handlers).
2014-08-09 21:12:51 +02:00
José Valim a38e6b7a0d Ensure aliases are invoked for recursive tasks
Closes #2650
2014-08-09 13:59:32 +02:00
José Valim 72199ca71d Fix build 2014-08-09 13:47:48 +02:00
José Valim 9420945f59 Leave it to the application to start Logger after compilation
Closes #2649
2014-08-09 13:38:27 +02:00
Eric Meadows-Jönsson 4ca1736f86 Accept more forms of git versions
Fixes the parsing of “git version 1.9.3 (Apple Git-50)”.
Closes #2653.
2014-08-09 11:16:25 +02:00
José Valim fd85ca443e Merge pull request #2652 from chyndman/elixir-bat-iex-startcli
Suppress "-s elixir start_cli" for IEx
2014-08-09 10:36:32 +02:00
Chris Hyndman 187e86749e Suppress "-s elixir start_cli" for IEx 2014-08-08 18:13:42 -07:00
José Valim e04a7aef6a Merge pull request #2651 from fishcakez/translator_format
Improve translator formatting
2014-08-08 22:19:23 +02:00
José Valim 0b1c8c92c0 Set -noshell on bin/iex 2014-08-08 22:11:06 +02:00
Chris Hyndman bf270d144f Prepend -noshell and friends always within Cygwin
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2014-08-08 22:11:06 +02:00
James Fish 2c009d1ec3 Improve translator formatting
* Improve application stop reasons
* Include stacktrace in crash reports
2014-08-08 21:08:36 +01:00
José Valim 08555f545c Merge pull request #2648 from sunaku/makefile-message-clarity
Makefile: shorten tip about Unicode embedding time
2014-08-08 20:41:26 +02:00
Suraj N. Kurapati 65d3af694b Makefile: shorten tip about Unicode embedding time
The estimate about this step taking "up to a minute" really depends on
the machine we're compiling on.  For example, on an old or underpowered
machine, it may take _longer_ than a minute.  So this patch changes the
wording accordingly and puts the reason for the delay at the beginning.
2014-08-08 09:07:26 -07:00
José Valim 8aa43eaedd Merge pull request #2647 from jwarwick/task_docs
Fixed doc typos in Task and URI
2014-08-08 16:30:12 +02:00
John Warwick 334c732691 Fixed doc typos in Task and URI 2014-08-08 10:23:36 -04:00
José Valim 24587110ad Merge pull request #2646 from jwarwick/logger_type
Fixed Logger.configure_backend spec
2014-08-08 16:18:46 +02:00
John Warwick b98421fe51 Fixed Logger.configure_backend spec 2014-08-08 10:11:39 -04:00
José Valim 752e3f8bc3 Update CHANGELOG 2014-08-08 13:31:40 +02:00
José Valim 6c8e70b1f8 Load handlers inside supervisor as they are now dynamic 2014-08-08 13:21:55 +02:00
José Valim c6fbfd2a67 Copy and check elixir version in archives
Closes #2396
2014-08-08 12:53:50 +02:00
José Valim 56ea6e52e4 Update configure_backend docs 2014-08-08 11:39:10 +02:00
José Valim 4726e0af91 Persist calls to add_backend and remove_backend 2014-08-08 11:38:18 +02:00
José Valim ee82679908 Merge pull request #2645 from tomkadwill/doc_false_handle_info
Added @doc false to handle_info function
2014-08-08 11:25:56 +02:00
Tom Kadwill aedd5a938f Added @doc false to handle_info function 2014-08-08 10:21:26 +01:00
José Valim 2d9c3fb6f0 Print warnings when used ? with characters with escape codes
Closes #2640
2014-08-08 11:13:13 +02:00
José Valim 6f07f4af22 Create the config directory before writing to config file 2014-08-08 10:23:54 +02:00
José Valim 656bd640e6 Load config/config.exs into escript
Closes #2643
2014-08-08 10:13:17 +02:00
José Valim bc2cb15a96 Add Logger.flush/0 2014-08-07 20:14:04 +02:00
José Valim dbf205b311 Deprecate Module.function/3 2014-08-07 20:00:57 +02:00
José Valim 0e0bb1c2a7 Do not raise conflicts on imports used from quotes, closes #2589 2014-08-07 18:09:19 +02:00
José Valim 736406a9b3 Do not allow invalid quoted expression to leak
Closes #2499
2014-08-07 17:42:05 +02:00
José Valim 886917753e Ensure we can escape bitstrings, add Macro.validate/1 2014-08-07 17:42:05 +02:00
José Valim f29889da99 Make environment explicit on escript build 2014-08-07 17:42:05 +02:00
José Valim aee33eac6d Merge pull request #2638 from jwarwick/logger_docs
Fixing typos in Logger module docs
2014-08-07 16:16:30 +02:00
John Warwick d8428ba1e6 Fixing typos in Logger module docs 2014-08-07 10:11:36 -04:00
José Valim 1f725b0d2c Merge pull request #2637 from fishcakez/improve_sasl_reports
Improve Logger's SASL report translation
2014-08-06 22:37:22 +02:00
James Fish 38f895cc13 Improve Logger's SASL report translation
* Pattern match keyword lists for faster parsing
* Support :supervisor_bridge reports
* Support missing :initial_call in :crash_report
* Improve formatting of registered name in :crash_report output
* More tests
2014-08-06 21:21:36 +01:00
José Valim 3fc07eff8e Merge pull request #2636 from voltone/uri-recomposition
Improved RFC compliance for URI to_string
2014-08-06 21:20:48 +02:00
Bram Verburg aa244c30fc Improved RFC compliance for URI to_string 2014-08-06 21:55:48 +03:00
José Valim f8647dae3e Ensure backends receive the id when added, closes #2635 2014-08-06 19:56:22 +02:00
José Valim 39fa2878ce Fix list rendering on ANSI docs 2014-08-06 19:42:26 +02:00
José Valim 5b5b95eb11 Update CHANGELOG 2014-08-06 13:27:54 +02:00
José Valim 67caa3ebb3 Fix padding in Logger 2014-08-06 13:20:01 +02:00
José Valim d09cd173bf Merge pull request #2634 from fishcakez/sasl_reports
Add SASL log forwarding option to Logger
2014-08-06 12:05:59 +02:00
José Valim 79fb40ab6a Correctly follow code specs when loading binaries
If we are compiling the module, we will load it with the
output of its beam file. If the module exists only in
memory, :code.which/1 will return `:in_memory`.

Closes #2533
2014-08-06 12:05:55 +02:00
José Valim ae2cb6bc43 Add the Windows wiki link to the README 2014-08-06 12:05:55 +02:00
James Fish 413b815ecc Add tests for SASL report logging 2014-08-06 09:47:17 +01:00
James Fish df89b5022e Add SASL log forwarding option to Logger 2014-08-05 21:22:50 +01:00
José Valim 90729655ee Merge pull request #2633 from lexmag/not-match
Use `not` instead of `!` in `IO.ANSI.enabled?/0`
2014-08-05 18:12:12 +02:00
Aleksei Magusev 171e9b3659 Use not instead of ! in IO.ANSI.enabled?/0 2014-08-05 19:29:39 +04:00
José Valim 3e256fcc1e Merge pull request #2631 from chyndman/contributing-windows
Remove Windows-related CONTRIBUTING.md section (now in Wiki)
2014-08-05 09:18:27 +02:00
Chris Hyndman 29ebbeda93 Remove Windows-related CONTRIBUTING.md section (now in Wiki) 2014-08-04 15:14:02 -07:00
José Valim 214d056911 Support on log messages 2014-08-04 20:38:13 +02:00
pragdave 9f065a77ca align the messages between log messages with different length level text. Added an extra format $levelpad, which is a space if the level is "warn" or "info"
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2014-08-04 20:32:43 +02:00
José Valim e70ac9768e Fix iex test 2014-08-04 19:41:37 +02:00
José Valim ec6c98ef47 Merge Logger.metadata 2014-08-04 19:03:24 +02:00
José Valim 7d0f92e12a Fix docs and specs introspection in iex 2014-08-04 19:03:24 +02:00
José Valim 8bf6193ccc Merge pull request #2630 from scrogson/patch-2
Doc correction for GenEvent
2014-08-04 18:51:15 +02:00
Sonny Scroggin 50f5dc76fb Doc correction for GenEvent 2014-08-04 11:45:22 -05:00
José Valim b9000999c9 Use the Logger PID to avoid race conditions on shutdown, closes #2628 2014-08-04 15:21:06 +02:00
José Valim 52be06c314 Do not expand args for unknown functions, closes #2387 2014-08-04 13:46:49 +02:00
José Valim 3da560f10c Update Mix.Dep.Lock documentation 2014-08-04 13:18:58 +02:00
José Valim f0f25300a0 Load mix deps only when there is a need to use them
This helps speed up peformance to many tasks. Tasks like
clean, help and compile --list must now explicitly call
loadpaths with checks disabled so they can still show
data coming from dependencies but without causing crashes.
2014-08-04 13:04:56 +02:00
José Valim c8a3a619af Update CHANGELOG 2014-08-04 10:45:59 +02:00
José Valim 25f64051ba Remove documentation from defimpls 2014-08-04 10:45:00 +02:00
José Valim 5efa6bb1c4 Since IO.ANSI.enabled? is a fast operation, no need to prefetch it on IEx 2014-08-04 10:43:02 +02:00
José Valim cb48f90f28 Merge pull request #2627 from smpallen99/inspect_base
Add integer base support to inspect
2014-08-04 10:42:34 +02:00
Stephen M. Pallen 3ba93b1186 Add integer base support to inspect
Option :base for inspect/2 supports :binary, :octal, :decimal, :hex.
Includes tests and documentation.
2014-08-03 20:17:58 -04:00
José Valim 7babcd0ed2 Merge pull request #2625 from niku/doc-about-.iex.exs
Improve IEx docs about .iex.exs
2014-08-03 20:04:12 +02:00
José Valim b6c1f2b2e1 Merge pull request #2624 from lexmag/pathtype
Reuse pathtype detection in `Path` module
2014-08-03 20:03:25 +02:00
Johnny Winn 5f853f8e71 Implement flag to clean unused dependencies
This addresses issues #2569
https://github.com/elixir-lang/elixir/issues/2569
2014-08-03 13:42:51 -04:00
Aleksei Magusev 6cccf3ac22 Reuse pathtype detection in Path module 2014-08-03 21:20:11 +04:00
niku 5b9b186d4b Improve IEx docs about .iex.exs 2014-08-03 23:02:03 +09:00
José Valim 57898bee03 Do not normalize paths on join, closes #2500 2014-08-03 14:59:38 +02:00
José Valim 6a6ba9f50d Remove deprecated code 2014-08-02 22:35:57 +02:00
José Valim 1e3135514f Start v0.15.1-dev 2014-08-02 21:57:43 +02:00
José Valim 2f2a52a648 Release v0.15.0 2014-08-02 21:49:21 +02:00
José Valim 89fd8f305f Print a nice prelude if we fail to load a project, closes #2430 2014-08-02 15:19:53 +02:00
José Valim 353c043d8c Use Mix.Shell.printable_app_name 2014-08-02 14:50:55 +02:00
José Valim 54a40efeec Push the given app name as default on Mix.Project 2014-08-02 14:29:12 +02:00
José Valim f781795032 Bump hex requirement 2014-08-02 14:04:07 +02:00
José Valim 54ffb5768d Add a test case for printing samples on IO shell 2014-08-02 12:13:36 +02:00
José Valim 8fc068821f Print app name on Mix.shell.cmd/1/2 2014-08-02 12:08:42 +02:00
Eric Meadows-Jönsson 1f91fd351e Merge pull request #2619 from knewter/feature/2616_print_app_name_before_compiling_makefile_dependencies
Print app name before compiling Makefile deps
2014-08-02 03:00:03 +02:00
Josh Adams ab4399323f Print app name before compiling Makefile deps
This should close out #2616.
2014-08-01 19:40:11 -05:00
José Valim 3b34a57bd2 Avoid race conditions in the struct detection 2014-08-01 23:58:02 +02:00
José Valim 62a469dc23 Improve mix new error messages 2014-08-01 21:36:28 +02:00
José Valim 8b0c4127ef Merge pull request #2618 from patrickgombert/mix-new
Validate app and module on mix new
2014-08-01 21:22:56 +02:00
Patrick Gombert dc4b7c89ea Validate app and module on mix new 2014-08-01 14:15:15 -05:00
José Valim b5febd991c Allow elixirc_paths to also be given through the command line
Once given, the command line overrides the project
configuration in `mix compile.elixir`.

Closes #2557
2014-08-01 19:52:32 +02:00
José Valim f1323ade7c Accept upcoming hex formats 2014-08-01 18:28:56 +02:00
José Valim 89561c321d Ensure defoverridable is not order dependant, closes #2615 2014-08-01 16:49:57 +02:00
José Valim d1b8657933 Do not load mix.exs for mix deps coming from hex
This is an optimization that allows us to shave off some
seconds from booting applications.
2014-08-01 14:53:41 +02:00
José Valim 89d9cc89ca Ensure IO.ANSI.Docs can correctly fill missing columns 2014-08-01 14:10:42 +02:00
José Valim 7b0cbb1632 Speed up and calculate effective length after rendering 2014-08-01 13:34:11 +02:00
pragdave 3dc0e87c33 Add basic support for markdown tables to built-in formatter
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2014-08-01 13:14:12 +02:00
José Valim a19613cf19 Quote bash comparison arguments
/cc @chyndman
2014-08-01 12:50:47 +02:00
José Valim a8e4fcdb6c Reduce limit as we traverse collections on inspect 2014-08-01 12:50:36 +02:00
José Valim a70ca83ba2 Add logger to the list of elixir apps for escript 2014-08-01 11:15:27 +02:00
José Valim 11c857a9a2 Merge pull request #2613 from chyndman/stdlib-test-win-cmd
Use Command Prompt for stdlib test on Windows
2014-08-01 10:12:05 +02:00
Chris Hyndman 84b1f6546b Use Command Prompt for stdlib test on Windows 2014-07-31 15:06:11 -07:00
José Valim c8c1757fdd Merge pull request #2611 from lexmag/tilde-in-path
Handle tilde in `Path.expand/1,2` properly
2014-07-31 19:05:34 +02:00
José Valim 9a162b5076 Inspect.Algebra.surround_many/6 now expects Inspect.Opts 2014-07-31 18:33:34 +02:00
Aleksei Magusev 92d2f25048 Handle tilde in Path.expand/1,2 properly 2014-07-31 20:09:58 +04:00
José Valim a2ddb6abbb Add Agent MFAs 2014-07-31 17:55:48 +02:00
José Valim ac629c875b Move children decision down the tree so we don't ignore umbrella children 2014-07-31 16:49:59 +02:00
José Valim ab8ba93d1d Only print req mismatch for dependencies on compile 2014-07-31 16:06:03 +02:00
José Valim 9d62e674d7 Fix test suite 2014-07-31 14:01:18 +02:00
José Valim 58ca3401e4 Deprecate IO.ANSI.terminal? and tidy up colors 2014-07-31 13:54:08 +02:00
José Valim a5926616a6 Ensure log configuration triggers a restart 2014-07-31 12:59:34 +02:00
José Valim 59910ae4bf Add flush to Logger.add_backend/2 2014-07-31 12:43:11 +02:00
José Valim 69b47483c1 Merge pull request #2609 from fishcakez/logger_ansi_console
Add ANSI support to console logger
2014-07-31 12:43:06 +02:00
José Valim 284e75d031 Remove warnings from the test suite 2014-07-31 10:46:29 +02:00
José Valim c1d63e4639 Optimize IO.ANSI.format/2 by avoiding remote calls 2014-07-31 01:50:10 +02:00
James Fish 212b204af8 Add ANSI support to console logger 2014-07-31 00:06:31 +01:00
José Valim 90f83e1b3d Improve docs for Logger 2014-07-30 21:22:40 +02:00
José Valim 562af52d07 Fix logger docs 2014-07-30 20:16:53 +02:00
José Valim ed79d60cef Update CHANGELOG 2014-07-30 20:11:29 +02:00
José Valim 42d378142b No more binding with a list 2014-07-30 20:00:12 +02:00
José Valim aeefd59b15 Add Logger 2014-07-30 19:46:26 +02:00
José Valim 6db5091378 Provide :on option for Regex.split/3 2014-07-30 15:45:48 +02:00
José Valim 4e7ba9d598 Deprecate IO.ANSI.escape/2 and IO.ANSI.escape_fragment/2 2014-07-30 13:14:53 +02:00
José Valim 60b66ac669 Merge pull request #2562 from thmzlt/ansi
Implement IO.ANSI format/2 and format_fragment/2
2014-07-30 13:00:20 +02:00
José Valim 1fda82d9c2 Merge pull request #2606 from edgurgel/patch-1
Add OTP 17.1 to run on Travis CI
2014-07-30 12:50:12 +02:00
José Valim 55dbd617a6 Ensure no lock for rebar dependencies, closes #2561 2014-07-30 12:48:16 +02:00
Eduardo Gurgel 841a4e0195 Add OTP 17.1 to run on Travis CI 2014-07-30 22:47:01 +12:00
José Valim 5d60728d4b Always touch all marked dependencies from deps fetcher 2014-07-30 12:42:43 +02:00
José Valim 9717a63604 Do not consider subpatterns on Regex.split/3 2014-07-30 11:13:18 +02:00
José Valim fae20670ed Update CHANGELOG 2014-07-29 20:40:54 +02:00
José Valim 6874eb7ce8 Deprecate octal and upcase char syntaxes 2014-07-29 20:33:42 +02:00
Joe-noh 404e95b7ac Fix indent
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2014-07-29 18:02:15 +02:00
José Valim 30a5914a92 Merge pull request #2600 from niku/remove-duplicate-test
Remove duplicate test
2014-07-29 16:53:41 +02:00
José Valim f97a36ff98 Merge pull request #2599 from rclements/patch-1
Update gen_server.ex
2014-07-29 16:53:27 +02:00
niku e68f617e2a Remove duplicate test 2014-07-29 23:51:24 +09:00
Robby Clements 3056e95ded Update gen_server.ex 2014-07-29 09:49:47 -05:00
José Valim 9ba77e1ce6 List crypto, compiler and syntax tools as dependencies, closes #2593 2014-07-29 11:42:12 +02:00
José Valim 84010d4467 Ensure private functions are not exported, closes #2587 2014-07-29 11:22:19 +02:00
José Valim a91aa70103 Do not expose protocol convention on assert_impl!
Closes #2594
2014-07-29 10:59:25 +02:00
José Valim 301c7fd122 Update CHANGELOG 2014-07-29 10:45:12 +02:00
José Valim 8809ab2f5e Do not leak Stream representation 2014-07-29 10:41:02 +02:00
José Valim 71e441dcc2 Remove IEx warnings 2014-07-29 10:40:40 +02:00
José Valim f11197fd04 Remove escript from elixir app 2014-07-29 10:38:20 +02:00
José Valim 7ac771da67 Do two separate :io.setopts calls 2014-07-29 10:38:20 +02:00
José Valim a47751f4de Merge pull request #2592 from tomkadwill/doc-false
Added @doc false to terminate and code_change
2014-07-28 22:09:33 +02:00
José Valim c91217860c Merge pull request #2573 from chyndman/iex-werl-option
Windows IEx enhancements
2014-07-28 22:09:00 +02:00
Chris Hyndman f69e9df983 Add new --werl option 2014-07-28 07:49:02 -10:00
Chris Hyndman 2ee1d5daef Suppress "dumb terminal" warning 2014-07-28 07:48:21 -10:00
Chris Hyndman d3dba63659 Use start to spawn werl without lingering cmd window 2014-07-28 07:42:28 -10:00
Chris Hyndman c016d74379 Use setlocal/endlocal to prevent polluting user's environment 2014-07-28 07:42:27 -10:00
Tom Kadwill a6d887ee09 Added @doc false to terminate and code_change 2014-07-28 12:49:33 +01:00
José Valim aa017a4dcb Merge pull request #2585 from herenowcoder/enum-docs
use os.timestamp for setting random seed in doc examples
2014-07-28 09:34:59 +02:00
José Valim b7c7d7e72c Merge pull request #2586 from jquadrin/patch-1
small IEx evaluator refactoring
2014-07-28 09:34:23 +02:00
José Valim d164a9b93f Merge pull request #2590 from MSch/fix-mix-escript
Fix mix escript
2014-07-28 09:30:49 +02:00
Martin Schürrer 0105cf538e Update Elixir's mix.exs to fix escript.build 2014-07-27 22:47:21 -05:00
Martin Schürrer 957e389fe7 Update mix's mix.exs to fix escript.build 2014-07-27 22:45:40 -05:00
Joseph Quadrino 88d4d38afa small evaluator refactoring 2014-07-27 16:52:16 -04:00
Wojciech Kaczmarek 8b4b985894 use os.timestamp for setting random seed in doc examples 2014-07-27 17:59:22 +02:00
José Valim 986eb8bdb1 Merge pull request #2556 from patrickgombert/master
Allow --app flag to be passed on mix new
2014-07-27 08:33:58 -05:00
José Valim 052eac333c Merge pull request #2580 from Joe-noh/lowercase-http-s-proxy
[MIX] allow lowercase http(s)_proxy env var
2014-07-27 08:31:43 -05:00
José Valim acae645db1 Merge pull request #2581 from mururu/parse-space
Parse unary/binary operators regardless of number of spaces
2014-07-27 08:27:29 -05:00
José Valim 7cb5f6ff3c Merge pull request #2582 from tomkadwill/added-doc-false
Added @doc false to handle_call functions
2014-07-27 07:58:05 -05:00
Tom Kadwill 0fe940a621 Added @doc false to handle call 2014-07-27 11:52:15 +01:00
Yuki Ito 81ed03ffb2 Parse unary/binary operators regardless of number of spaces
closes #2534.
2014-07-26 20:03:38 +09:00
Joe-noh 3947f7455a allow lowercase http(s)_proxy env var 2014-07-26 17:03:25 +09:00
José Valim 406a2e6755 Merge pull request #2576 from Gazler/docs/dict
Docs: change Dict.merge description to read more naturally
2014-07-25 23:30:30 -05:00
Gary Rennie 0b3fcb254a Docs: change Dict.merge description to read more naturally
Previously there was an additional "dict" - this is as a result of it
previously saying "dict `a`"
2014-07-25 23:10:50 +01:00
Eric Meadows-Jönsson 054843505a Merge pull request #2575 from Gazler/docs/dict
Docs: ensure docs for Dict use full variable names
2014-07-25 15:42:48 -05:00
Gary Rennie 92016cd80e Docs: ensure docs for Dict use full variable names
The following variable switches have been made:

 * `d` has been changed to `dict`
 * `a` has been changed to `dict1`
 * `b` has been changed to `dict2`
 * `d1` has been changed to `dict1`
 * `d2` has been changed to `dict2`

 This fixes #2574
2014-07-25 21:07:53 +01:00
José Valim 0c06e72f0c Merge pull request #2570 from chyndman/iex-expand-fun-fallback
Fallback to no tab expansion if not supported
2014-07-23 22:12:38 -07:00
Chris Hyndman a722a4b33c Fallback to no tab expansion if not supported 2014-07-23 08:53:27 -10:00
José Valim 4afb8a516c Remove bug where access/update was incorrectly filling values with maps 2014-07-22 12:44:05 -07:00
José Valim e9fc50bc6d Exit on GenEvent call, move stream to its own module 2014-07-22 09:49:11 -07:00
José Valim 0a5a980ab7 Merge pull request #2566 from jwarwick/mix_clean_typo
Fixed typos in Mix.Tasks.Clean docs
2014-07-21 16:42:52 -05:00
John Warwick 30505b7758 Fixed typos in Mix.Tasks.Clean docs 2014-07-21 15:35:05 -04:00
Thomaz Leite 9f867f2ec8 Fix IO.ANSI doctests 2014-07-21 12:22:13 -03:00
Thomaz Leite 5e2020804a Reimplement IO.ANSI do_format
This avoids copying partial lists, and handles nested improper lists.
2014-07-21 12:07:39 -03:00
Thomaz Leite b5cea26e57 Add test case for nested improper lists 2014-07-21 11:33:37 -03:00
Thomaz Leite 03e6d3a5a2 Add docs to format/2 and format_fragment/2 2014-07-20 13:36:34 -03:00
Thomaz Leite 89513f9d15 Put IO.ANSI.reset inside a list before concat'ing 2014-07-20 13:15:06 -03:00
Thomaz Leite f20ef46e90 Implement IO.ANSI format/2 and format_fragment/2 2014-07-20 12:11:54 -03:00
José Valim d7ebc46a2d Improve config.exs docs 2014-07-20 11:07:16 +02:00
José Valim bd6d4bcd6f Merge pull request #2563 from alco/typespec-typo
Fix a typo in typespec doc
2014-07-20 11:05:43 +02:00
Alexei Sholik 4f8862cedd Fix a typo in typespec doc 2014-07-20 11:57:22 +03:00
Eric Meadows-Jönsson 1afc5ec71b Trigger rebuild in Makefile for io/ansi/docs.ex
Closes #2558.
2014-07-19 23:26:29 +02:00
José Valim 925ff6050a Merge pull request #2554 from chyndman/mix-ps1-revised
mix.ps1 Revision
2014-07-19 20:52:29 +02:00
Thomaz Leite 6ce97090f3 Change IO.ANSI tests to assume chardata is returned 2014-07-19 14:36:32 -03:00
Thomaz Leite 6538f33825 Bring back original tests for IO.ANSI
To avoid conflict and preserve compatibilty, the new tests use the
functions `IO.ANSI.format/2` and `IO.ANSI.format_fragment/2`.
2014-07-19 13:26:53 -03:00
José Valim a1e4a2741f Avoid unecessary wrap inside do keyword 2014-07-19 18:13:38 +02:00
Thomaz Leite fe57a821d8 Rewrite IO.ANSI tests to match new behavior 2014-07-19 12:44:59 -03:00
José Valim c42fe88dcc Provide Inspect.Algebra.format/2 that returns iodata 2014-07-19 13:32:45 +02:00
José Valim 28d7fc35a6 Merge pull request #2559 from jwarwick/keyword_docs
Fixing doc typos in Keyword
2014-07-19 10:58:16 +02:00
John Warwick 9fa279897b Fixing doc typos in Keyword 2014-07-18 16:26:46 -04:00
Patrick Gombert 6465a3a4af Allow --app flag to be passed on mix new
Allows path to not follow app name conventions
2014-07-17 20:48:04 -05:00
Chris Hyndman 1582df8d70 Fixed to use flattened array $newArgs 2014-07-17 16:19:26 -07:00
Chris Hyndman 8d9aec5a55 Revise mix.ps1 to use FileInfo object to represent path, modify $args directly 2014-07-17 15:09:38 -07:00
José Valim 7f4df95922 Merge pull request #2553 from chyndman/mix-test-archive-win-refute
Skip additional refutation due to code server file handle on Windows
2014-07-17 21:02:06 +02:00
Chris Hyndman 98a5affa4a Skip additional refutation due to code server file handle on Windows 2014-07-17 11:33:07 -07:00
José Valim b720559d56 Merge pull request #2552 from alco/application-doc-typo
Fix a typo on Application.format_error doc
2014-07-17 16:22:22 +02:00
Alexei Sholik cd5cc91cd4 Fix a typo on Application.format_error doc 2014-07-17 17:12:51 +03:00
José Valim 93d92d1f36 Optimize rstrip for ascii chars 2014-07-16 23:59:09 +02:00
José Valim ce3674dd07 Add OptionParser.to_argv/1 2014-07-16 22:22:02 +02:00
José Valim e4cfb62a74 Merge pull request #2549 from jwarwick/exception_typo
Fixed typos in Exception docs
2014-07-16 22:08:03 +02:00
John Warwick bb51701614 Fixed typos in Exception docs 2014-07-16 16:05:48 -04:00
José Valim 6ffb248f03 No longer allow empty access as in [][] 2014-07-16 17:54:26 +02:00
José Valim 50af8058ef Use john and meg in code samples 2014-07-16 17:41:28 +02:00
José Valim b1d9e147b3 Update CHANGELOG 2014-07-16 15:06:35 +02:00
José Valim 7ea91d2d68 Clean up String.slice/2/3 impls 2014-07-16 14:03:52 +02:00
Alexei Sholik c7640d3cf4 Do not traverse the string twice in String.slice(<str>, <range>)
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2014-07-16 13:05:54 +02:00
José Valim 399240295b Remove invalid return values from GenEvent.handle_info/2 docs 2014-07-16 12:58:03 +02:00
José Valim b965d8eb5d Also allow structs atoms on Map.from_struct/1 2014-07-16 12:57:11 +02:00
Josh Nussbaum 380bf1e279 Added Map.from_struct/1
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2014-07-16 12:50:14 +02:00
José Valim 6272b60d36 Ensure all elixir dependencies are started before hand, closes #2542 2014-07-16 12:48:46 +02:00
José Valim 0c7699f21c Merge pull request #2541 from chyndman/mix-bat-exec-directly
Call Mix.start, Mix.CLI.main directly in bin\mix.bat
2014-07-16 12:07:04 +02:00
José Valim 1086a8455f Merge pull request #2544 from eproxus/patch-1
Remove non-existing from argument from handle_cast
2014-07-16 11:44:47 +02:00
Adam Lindberg d8e7578e41 Remove non-existing from argument from handle_cast
`handle_cast` does not support a `from` argument and this example will crash if it has one.
2014-07-16 10:54:02 +02:00
Chris Hyndman 2d0fbcc23c Call Mix.start, Mix.CLI.main directly in bin\mix.bat 2014-07-15 13:42:58 -07:00
José Valim 21d5d43ac5 Merge pull request #2539 from hamiltop/update_genevent_stream_doc
Update doc for GenEvent.stream/2
2014-07-15 20:56:59 +02:00
Peter Hamilton 027f90380b Update doc for GenEvent.stream/2
- GenEvent streams are not two way, so remove language suggesting that
you can notify via the streams.
- Clarify that consumption doesn't start until we enumerate.
2014-07-15 11:54:04 -07:00
José Valim 1a68f63525 Improve description of operations that takes linear time 2014-07-15 20:14:44 +02:00
José Valim 798c53a4cb Add linearity notes to Enum.fetch and friends 2014-07-15 16:33:25 +02:00
José Valim e3a89050f2 Merge pull request #2536 from alco/string-slice-speedup
Optimize String.slice by using binary_part
2014-07-15 10:56:42 +02:00
Alexei Sholik f176cb55c1 Optimize String.slice by using binary_part 2014-07-15 02:40:03 +03:00
José Valim 97f2e8744b Insert new line after IEx clear message 2014-07-14 19:48:24 +02:00
José Valim c513459c10 Merge pull request #2531 from jwarwick/system_cmd_type
Updating System.cmd return types
2014-07-14 16:11:04 +02:00
John Warwick fd6f1d47be Updating System.cmd return types 2014-07-14 10:06:23 -04:00
José Valim 6b8b5bfd01 Revert "Merge pull request #2530 from qingliangcn/master"
This reverts commit 42875c0b72, reversing
changes made to be320880b2.
2014-07-14 14:02:13 +02:00
Eric Meadows-Jönsson e6e96165aa Ensure type_to_signature works for all AST variables 2014-07-14 12:05:40 +02:00
José Valim 42875c0b72 Merge pull request #2530 from qingliangcn/master
use pure erlang function to judge the erlang version, no shell.
2014-07-14 11:29:57 +02:00
qingliangcn b35e2cf82a use pure erlang function to judge the erlang version, no shell. 2014-07-14 14:49:06 +08:00
José Valim be320880b2 Ensure functions in Kernel.SpecialForms and IEx.Helpers are also auto-completed
Closes #2527
2014-07-13 09:50:55 +02:00
José Valim fe1712d642 Merge pull request #2528 from alco/base-warnings
Update bitstring syntax in base.ex
2014-07-13 09:22:59 +02:00
Alexei Sholik 63d815912e Update bitstring syntax in base.ex 2014-07-13 06:03:22 +03:00
José Valim cd4dc75c93 Merge pull request #2526 from alco/at-exit-doc
Clarify the consequences of using System.at_exit
2014-07-12 16:31:42 +02:00
José Valim 058b157b36 Remove deprecated features and deprecate soft ones 2014-07-12 16:23:17 +02:00
Alexei Sholik 19c9941e7f Clarify the consequences of using System.at_exit 2014-07-12 17:11:57 +03:00
Eric Meadows-Jönsson 7895ca0821 Clarify release instructions 2014-07-12 15:21:29 +02:00
Eric Meadows-Jönsson 2b20bb424f Start v0.14.4-dev 2014-07-12 15:06:10 +02:00
Eric Meadows-Jönsson 82535feb90 Release v0.14.3 2014-07-12 14:49:29 +02:00
José Valim 81f007bdd7 Update CHANGELOG 2014-07-12 14:11:14 +02:00
José Valim 0b35996d5e Optimize and improve docs for Float operations 2014-07-12 14:05:18 +02:00
José Valim fc5cf506ae Merge pull request #2520 from nurugger07/float_modification_merges
Add precision to Float.ceil/1 & Float.floor/1
2014-07-12 12:38:35 +02:00
José Valim c4582e0041 Clarify attributes and exit signals in the language 2014-07-11 22:19:14 +02:00
Eric Meadows-Jönsson c80d215674 Fix stacktrace in Module.get_attribute example
Closes #2522.
2014-07-11 21:56:48 +02:00
José Valim 3ee9d5d0df Merge pull request #2521 from ericmj/record-typespecs
Add typespec syntax for records
2014-07-11 21:08:16 +02:00
Eric Meadows-Jönsson d86ba0e1c7 Add typespec syntax for records 2014-07-11 20:35:35 +02:00
Eric Meadows-Jönsson c295ee50de Allow expansion of local macros via Macro.expand/2 2014-07-11 20:35:35 +02:00
Eric Meadows-Jönsson 54c25a55fe Add compile time optimisation to record fields macro 2014-07-11 20:35:35 +02:00
Johnny Winn 583a4ec5f6 Add precision to Float.ceil/1 & Float.floor/1 2014-07-11 12:34:24 -04:00
José Valim 69b78dad51 Update docs and CHANGELOG 2014-07-11 17:49:34 +02:00
José Valim 0685e0315a Document and list aliases on help --names 2014-07-11 17:32:41 +02:00
José Valim 5e429001d9 Support aliases in Mix.Task.run/2 2014-07-11 17:32:41 +02:00
José Valim 707b810f1b Merge pull request #2517 from ericmj/record-fields
Add field retrieval of records via `record(record())`
2014-07-11 17:31:21 +02:00
José Valim e51cfc142d Merge pull request #2516 from alco/sigil-docs
Make sigil docs more readable in the source
2014-07-11 17:29:02 +02:00
Eric Meadows-Jönsson b259e793bb Fix mix archive in changelog 2014-07-11 17:12:21 +02:00
Eric Meadows-Jönsson 32738a97d9 Add field retrieval of records via record(record()) 2014-07-11 17:09:16 +02:00
Alexei Sholik 45c6fbaced Make sigil doctests more readable in the source 2014-07-11 17:17:42 +03:00
José Valim bba4f1bd1b Optimize and add specs to Mix.Task module 2014-07-10 21:58:35 +02:00
Eric Meadows-Jönsson ade2596e92 Fix bug where umbrella tasks were not reenabled
Closes #2058.
2014-07-10 21:21:23 +02:00
Eric Meadows-Jönsson 0e804a2b5c Merge pull request #2513 from ericmj/mix-mtimes
Warn and reset mime on future files
2014-07-10 20:44:28 +02:00
Eric Meadows-Jönsson 33ab1679f3 Warn and reset mime on future files 2014-07-10 20:08:06 +02:00
Eric Meadows-Jönsson f2b12633f3 Only read mtime once per source file 2014-07-10 19:26:25 +02:00
José Valim 5a4b2769ee Clarify elixirc_options are overridden via CLI 2014-07-10 18:04:23 +02:00
José Valim 2439bae409 Use Keyword syntax instead of list check 2014-07-10 17:59:56 +02:00
José Valim 6ed7e40d03 Add OptionParser.split/1 2014-07-10 17:58:19 +02:00
José Valim 2fc76fe1b0 Don't keep all in the option parser config 2014-07-10 16:46:29 +02:00
Eric Meadows-Jönsson e541a56130 Update changelog 2014-07-10 15:56:37 +02:00
José Valim 6378750b12 Only accept --no-SWITCH for booleans
The previous behaviour was confusing and a special case.
This enables negated switches only for booleans, otherwise
it is considered as a regular switch (as the combination
of --no-SWITCH for any other type is an error anyway).
2014-07-10 14:05:49 +02:00
José Valim fc25f60718 Do not remove interpolation escape on uppercase sigils, closes #2512 2014-07-10 12:54:16 +02:00
José Valim 3b351e7650 ErlangError -> CompileError 2014-07-10 11:49:56 +02:00
José Valim 999a535603 Manually call check_command before eval, closes #2449 2014-07-10 11:44:03 +02:00
José Valim a726dd812b Improve cannot pipe error 2014-07-10 11:30:00 +02:00
Eric Meadows-Jönsson 58d54afac6 Merge pull request #2511 from ericmj/macro-tostring-sigils
Use sigil syntax in Macro.to_string
2014-07-10 00:32:58 +02:00
Eric Meadows-Jönsson 001d776176 Use sigil syntax in Macro.to_string 2014-07-09 19:34:07 +02:00
José Valim 4bcd25fb4d Require Record before in defrecord samples 2014-07-09 19:22:23 +02:00
José Valim f0fa9e66a6 Add performance note to String.slice/1 2014-07-09 18:47:25 +02:00
José Valim 12f9bf09b0 Mention app: false on no app error 2014-07-09 18:47:25 +02:00
José Valim bf8f5a995f Merge pull request #2508 from chyndman/mix-bat-short-names
Use short filenames to handle spaces in path to bin\mix
2014-07-09 11:06:37 +02:00
Chris Hyndman d3a3feace4 Use short filenames to handle spaces in path to bin\mix 2014-07-08 17:09:23 -07:00
Eric Meadows-Jönsson 30331b2bea Do not allow deps names on mix deps.get
Closes #2507.
2014-07-09 00:24:13 +02:00
José Valim f5bf9d9659 Do not call handle_file_warning directly 2014-07-08 19:03:22 +02:00
José Valim 2865bd8653 Make elixir_errors API more strict and consider :file in meta 2014-07-08 18:54:40 +02:00
José Valim 4513b6965f Emit :file attribute on all nodes instead of a select few 2014-07-08 15:53:32 +02:00
José Valim b47e8c61d7 Use line from :keep when there is a :file meta in definitions 2014-07-08 13:35:17 +02:00
José Valim 40bf079566 Update CHANGELOG 2014-07-08 12:06:43 +02:00
José Valim 2eaf854e79 Slightly improve IO docs 2014-07-08 11:41:25 +02:00
Joe-noh 7c12cf029c implement IO.(bin)read which accept :all
IO.(bin)read(dev, `:all`) reads all contents from device

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2014-07-08 11:39:18 +02:00
José Valim 597d90b326 Add comments to code:ensure_loaded/1 snippets 2014-07-08 11:28:24 +02:00
José Valim 5dcad26b79 Ensure we don't loop when printing inspect failures, closes #2503 2014-07-08 11:25:49 +02:00
José Valim f8c2d5cb4f Merge pull request #2502 from alco/escript-embed-elixir
Fix the usage of `embed_elixir` in escript.build
2014-07-08 10:46:31 +02:00
Alexei Sholik b0f432b2f8 Fix the usage of embed_elixir in escript.build 2014-07-08 01:27:43 +03:00
José Valim 651a4bad67 Clarify behaviour of split on Windows 2014-07-07 20:31:27 +02:00
José Valim f52e350adc Ensure deps are not recompiled every second time, closes #2471 2014-07-07 17:38:51 +02:00
José Valim 35e8019447 Improve mix docs 2014-07-07 17:29:02 +02:00
José Valim a6fab4a111 Use a native splitting on String.split/1 instead of relying on regexes
The new implementation is 20x faster for small strings and about
100x times faster for large ones.
2014-07-06 21:20:52 +02:00
José Valim 64d62d2472 Emit warnings for underscored variables appearing twice in a pattern, closes #2480 2014-07-06 20:18:00 +02:00
Eric Meadows-Jönsson 10b322aa48 Rename Mix.Shell.info option :escape => :ansi 2014-07-06 16:28:38 +02:00
Eric Meadows-Jönsson 981955301d Shell info messages can still be escaped 2014-07-06 02:39:16 +02:00
Eric Meadows-Jönsson ac425f04ef Ensure the message is not ANSI escaped
Closes #2496.
2014-07-06 02:20:40 +02:00
José Valim 1dda1563da Ensure docs with no function raise warnings 2014-07-05 22:25:15 +02:00
José Valim 0e71b1e711 Merge pull request #2495 from thmzlt/patch-1
Fix typo in GenEvent moduledoc
2014-07-05 22:11:31 +02:00
thmzlt 841f6f2387 Fix typo in GenEvent moduledoc 2014-07-05 17:08:49 -03:00
José Valim 2eb7fb97de Check if ANSI escape codes are enabled on IEx's clear, closes #2441 2014-07-05 15:16:00 +02:00
José Valim e781983e6b Fix regex compilation and set ucp flags 2014-07-05 15:01:25 +02:00
José Valim 7d17a12783 Add test cases for ExUnit.DocTest failures 2014-07-05 14:53:24 +02:00
jw2013 d888057689 throws error for missing docs in DocTest
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2014-07-05 14:49:10 +02:00
José Valim 76d6e05399 Ensure case wrapper in cond gets the proper line 2014-07-05 14:45:07 +02:00
José Valim 1bd0ca6dea Return explicit :ok from more functions 2014-07-05 13:43:52 +02:00
José Valim 8e1f7fddca Always return ok from tracking functions 2014-07-05 13:21:59 +02:00
José Valim abda47903b Merge pull request #2494 from jw2013/typo
fix typo in range moduledoc
2014-07-05 10:39:49 +02:00
jw2013 030e72c9c6 fix range typo 2014-07-04 22:07:08 -07:00
Eric Meadows-Jönsson 88dbad9650 Fix some sentence tenses in changelog 2014-07-05 03:19:47 +02:00
Eric Meadows-Jönsson 07263b8533 Merge pull request #2493 from jwarwick/changelog_typos
Fixed Changelog typos
2014-07-05 03:13:44 +02:00
John Warwick f67741b6dc Fixed Changelog typos 2014-07-04 20:51:13 -04:00
José Valim 4724ff12e2 Fix bug when flat_map is used inside another flat_map with enumerable, closes #2483 2014-07-04 21:51:57 +02:00
José Valim f7c7ff46b4 Store stacktrace before invoking callback function 2014-07-04 19:27:05 +02:00
José Valim c4868f8549 s/file/function in IO docs 2014-07-04 19:26:31 +02:00
José Valim 85951052ca Add a note for binary_part/3 on String.slice/3 2014-07-04 18:37:02 +02:00
José Valim 28bd15b76c Consider multiple @external_resource entries in mix 2014-07-04 18:30:55 +02:00
José Valim 1fdb715967 Add a note about IO.bin* 2014-07-04 18:30:54 +02:00
Eric Meadows-Jönsson 0279547232 Re-add deprecated local tasks 2014-07-04 15:46:23 +02:00
José Valim 7fabf32d9e Do not crash if applications are already started 2014-07-04 12:13:53 +02:00
José Valim 4cbf0eb2ac Merge pull request #2484 from fishcakez/dialyzer_fixes
Fix unmatched returns dialyzer warnings
2014-07-04 11:54:55 +02:00
José Valim 071b0830f8 Ensure line numbers are properly preserved in EEx, closes #2485 2014-07-04 11:54:07 +02:00
James Fish 39cd4a6594 Fix unmatched returns dialyzer warnings 2014-07-04 02:45:16 +01:00
José Valim 0d2332fe5e Update CHANGELOG 2014-07-03 21:03:17 +02:00
José Valim c807fa6ec5 Add Mix.compilers 2014-07-03 21:01:13 +02:00
José Valim 4a10806d9a Pass a list to :error_logger 2014-07-03 20:51:41 +02:00
José Valim 57031f8cb0 Improve typespecs for protocols once more 2014-07-03 20:04:06 +02:00
José Valim b292486ac9 Improve more typespecs 2014-07-03 19:14:06 +02:00
José Valim 595f9bb0b1 Improve poorly specified callbacks 2014-07-03 16:50:37 +02:00
José Valim ebe3397ad9 Do not include unreachable locals 2014-07-03 16:26:44 +02:00
José Valim bc12b76bcc Merge pull request #2481 from fishcakez/remote_call_ast
Fix remote call AST
2014-07-03 14:45:17 +02:00
José Valim d8df85e99c Update plt in .gitignore 2014-07-03 14:15:58 +02:00
José Valim 80b9879d0a Refactor how definitions are analyzed 2014-07-03 14:15:24 +02:00
James Fish d54f9ec931 Fix remote call AST 2014-07-03 11:59:25 +01:00
José Valim d2258acbb3 Ensure yanked locals are added back with all nodes, closes #2188 2014-07-03 11:43:42 +02:00
José Valim 6c3a4bd019 Fix the guard for this clause evaluates to false warning
Since the guard can be hidden inside if/unless, it is better
to have a more generic error, stating we will always get the
same result from an check/guard.
2014-07-03 00:58:02 +02:00
José Valim 7903211546 Merge pull request #2479 from jwarwick/process_typo
Fixed Process doc typos
2014-07-02 22:35:49 +02:00
John Warwick 6b87fcf903 Fixed Process doc typos 2014-07-02 16:33:57 -04:00
José Valim 07d0d1b275 Improve some typespecs 2014-07-02 22:32:16 +02:00
José Valim 95361172a0 Update CHANGELOG 2014-07-02 22:05:41 +02:00
José Valim a0f3086167 Convert existing bitstring examples to new syntax 2014-07-02 21:32:00 +02:00
José Valim 65a59b41e5 New bitstring syntax, closes #1950 2014-07-02 21:19:01 +02:00
José Valim 78dc02fb81 Merge pull request #2477 from fishcakez/dialyze
Fix dialyzer makefile commands
2014-07-02 17:37:17 +02:00
José Valim dc577b8bfa Ensure mix preserves -pa and -pz given to the command line 2014-07-02 17:30:20 +02:00
José Valim ed9862ea91 First deprecate types in struct fields 2014-07-02 17:30:20 +02:00
James Fish f710c61179 Fix dialyzer makefile commands 2014-07-02 15:55:02 +01:00
Eric Meadows-Jönsson d86cf14f0d Add shorthand syntax for nonempty_list() 2014-07-02 15:48:03 +02:00
Eric Meadows-Jönsson bdfd5817dc Fix typespec_to_ast for lists 2014-07-02 02:44:47 +02:00
José Valim 3b2ece4ddc Merge pull request #2474 from chyndman/elixir-bat-short-circuit
Fix for batch file misbehaving with parenthetical arguments
2014-07-02 00:12:56 +02:00
José Valim 3452f364aa Improve typespecs and docs 2014-07-02 00:12:06 +02:00
José Valim dad77a4d4b Mention MIX_ENV on compile.protocols 2014-07-02 00:12:06 +02:00
Eric Meadows-Jönsson c8b925c581 Merge pull request #2475 from alco/version-doc-fix
Fix weird examples in Version docs
2014-07-02 00:02:43 +02:00
Alexei Sholik 24297c87e6 Fix weird examples in Version docs 2014-07-02 00:26:54 +03:00
Chris Hyndman 0c589eff04 Umanbiguous check for parameters 2014-07-01 13:51:25 -07:00
Chris Hyndman 3a3684ca78 Process Erlang commands last 2014-07-01 13:44:23 -07:00
José Valim 6e39366c40 Merge pull request #2473 from jwarwick/changelog_typo
Fixed CHANGELOG typo
2014-07-01 20:45:46 +02:00
John Warwick fd0ccb7741 Fixed CHANGELOG typo 2014-07-01 14:09:09 -04:00
José Valim 81f151245e Merge pull request #2472 from michaelkschmidt/master
Use absolute symbolic links on windows
2014-07-01 19:06:30 +02:00
Michael Schmidt acfdd5b59e Use absolute symbolic links on windows 2014-07-01 11:17:30 -05:00
Eric Meadows-Jönsson 588800c495 Merge pull request #2467 from ericmj/multiclause-defaule
Raise on multiple clauses with default arg
2014-07-01 16:51:12 +02:00
Eric Meadows-Jönsson cfd157ab7d Merge pull request #2466 from ericmj/typespec-struct
Remove default generation of `@type t` for structs
2014-07-01 16:51:01 +02:00
Eric Meadows-Jönsson 377eb6126d Raise on multiple clauses with default arg 2014-07-01 16:41:19 +02:00
Eric Meadows-Jönsson 2cad4c96bd Remove default generation of @type t for structs
`Struct{}` in typespecs will generate default types for all missing
fields.
2014-07-01 16:23:30 +02:00
José Valim 2453501c83 Rename escriptize to escript.build 2014-07-01 15:23:24 +02:00
José Valim dcc3c6290e Deprecate embed_extra_apps in escriptize 2014-07-01 15:17:59 +02:00
José Valim 54240e1b4f Inline functions in Port module 2014-07-01 14:54:56 +02:00
José Valim 6639f91ba2 No longer fill in missing intermediate values with empty maps in Access 2014-07-01 13:46:49 +02:00
José Valim 15bf309760 Improve error message for struct expansion in invalid contexts 2014-07-01 12:41:27 +02:00
José Valim 1de0302ecb Fix failing test and improve zip/unzip docs 2014-07-01 12:39:06 +02:00
José Valim 871cbda3f8 Allow functions in get_in/2 and get_and_update_in/3 (#2470) 2014-07-01 12:25:07 +02:00
José Valim c4ea85d7e5 Bug fixes on windows for buggy 17.1 2014-07-01 10:16:39 +02:00
José Valim a8f3125774 Deprecate lists in find_executable/1 2014-06-30 21:43:26 +02:00
José Valim c564af180b Update CHANGELOG 2014-06-30 21:35:54 +02:00
José Valim 3ebe1ed728 Use binaries so we can use Elixir functions 2014-06-30 21:30:01 +02:00
José Valim 6b030b2978 Use the new System.cmd/3 in stdlib and apps 2014-06-30 21:15:00 +02:00
José Valim 9f2fa0f7af Add System.cmd/3, closes #2251 2014-06-30 20:50:44 +02:00
José Valim da06e35443 Remove wrong usage of exit(0|1) 2014-06-30 20:35:06 +02:00
José Valim da4d047a37 Use iolists in collectable for binaries 2014-06-30 20:33:38 +02:00
José Valim 782314abff Also use exit({:shutdown, 1}) on mix test 2014-06-30 16:59:11 +02:00
José Valim 94d53f052b Use exit({:shutdown, int}) to signal shell status 2014-06-30 16:38:26 +02:00
José Valim 9ceebf96db Move local.install and local.uninstall to archive.* 2014-06-30 12:28:51 +02:00
José Valim 8611bb7322 Run command line and escripts in a new process without trap exit
Closes #1985
2014-06-30 11:48:31 +02:00
José Valim ba2e1c95ef Remove duplicated specs and old PR example 2014-06-30 10:17:31 +02:00
José Valim 5979b5ca75 Merge pull request #2464 from KronicDeth/feature/2460/enum-sort-by
Enum.sort_by/3
2014-06-30 10:09:45 +02:00
Luke Imhoff f144cae300 Fix phrasing of Enum.sort_by/3 @doc 2014-06-29 16:09:52 -05:00
Luke Imhoff 8bcf473b68 Pull reused test data into variable 2014-06-29 16:03:42 -05:00
Luke Imhoff b10fdfc7b8 Fix spacing 2014-06-29 16:03:09 -05:00
Luke Imhoff 95eb1c0978 Fix @spec sort_by
Use format provided by José Valim that uses `when` to eliminate the need
for `@type mapped_element`.
2014-06-29 15:30:34 -05:00
Luke Imhoff 7a5cb8d374 Add @spec for Enum.sort_by/3 2014-06-28 20:38:01 -05:00
Luke Imhoff f23b133d29 Add @doc for Enum.sort_by/3 2014-06-28 20:23:32 -05:00
Luke Imhoff 0bdee49f7f Enum.sort_by/3
Enum.sort_by/3 takes a collection, a mapper function, an an optional
sorter function.  The function sorts by the mapped value using the sorter.
The optional sorter default to `<=/2`.
2014-06-28 17:52:04 -05:00
328 changed files with 15446 additions and 6496 deletions
+2 -3
View File
@@ -1,6 +1,6 @@
/.eunit /.eunit
/.release /.release
/docs /doc
/ebin /ebin
/lib/*/ebin/* /lib/*/ebin/*
/lib/*/tmp /lib/*/tmp
@@ -10,5 +10,4 @@
/lib/elixir/test/ebin /lib/elixir/test/ebin
/rel/elixir /rel/elixir
erl_crash.dump erl_crash.dump
.dialyzer_plt .elixir.plt
.dialyzer.base_plt
+2
View File
@@ -7,3 +7,5 @@ notifications:
- eric.meadows.jonsson@gmail.com - eric.meadows.jonsson@gmail.com
otp_release: otp_release:
- 17.0 - 17.0
- 17.1
- 17.3
+272 -7
View File
@@ -1,18 +1,283 @@
# Changelog # Changelog
## v0.14.3-dev ## v1.0.4
* Enhancements * 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 * Bug fixes
* [Kernel] `|>`, `<<<`, `>>>` and `^^^` were made left associative in operator table * [Elixir] Produce clearer error messages for syntax errors before a sigil
* [Kernel] `<`, `>`, `<=`, `>=` were given higher precedence than comparison ones (`==`, `!=`, etc) in operator table * [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
* Soft deprecations (no warnings emitted) ## v1.0.3 (2015-02-12)
* Deprecations * 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 * 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
* [ExUnit] Add timeouts to tests (which is configurable via tags)
* [GenEvent] Add support for `GenEvent.ack_notify/2`
* [GenEvent] Add support for monitored handlers to `GenEvent.add_handler/4` that removes the drawbacks in the previous linked handlers
* [Logger] Allow any value that implements `String.Chars` to be logger
* [Logger] Allow `Logger.log/3` timeout to be configured
* [Mix] Add `--force` option to `mix local.rebar`
* [Mix] Fix `--no-color`/`--color` option for `mix test`
* [Mix] Add a `:language` option to improve the usability of Erlang projects using Mix
* [Mix] Accept wildcards on `Mix.Config.import_config/1`
* [Mix] Support protocol consolidation on escripts
* Bug fixes
* [IEx] Do not print the underlying type for `@opaque` in the `t` helper
* [GenEvent] Do not allow a handler to be added more than once
* [GenServer] Do not treat bad arguments as exits in `GenServer.call/2`
* [GenServer] Do not deliver out of order messages on `GenServer.cast/2` on distributed mode
* [Mix] Do not pre-compile a Mix project if an alias was found
* [Mix] Properly handle compilation errors in the Erlang compiler
* [Mix] Always try to compile project if task cannot be found
* [Mix] Purge in memory modules before consolidating
* [Mix] Always compile a project if a task cannot be found
* Deprecations
* [Collectable] Deprecate `Collectable.empty/1` and `Enum.traverse/2`
* [Integer] `odd?/1` and `even?/1` are deprecated in favor of `is_odd/1` and `is_even/1`
* [Kernel] `nil?/1` is deprecated in favor of `is_nil/1`
* [Kernel] `x.Alias` is deprecated in favor of an explicit `Module.concat/2`
* [Record] `record?/1` and `record?/2` are deprecated in favor of `is_record/1` and `is_record/2`
* [Stream] Returning `{item, acc} | nil` from `Stream.resource/2` is deprecated, instead return `{[item], acc} | {:halt, acc}` (similar to `Stream.transform/3`)
* Backwards incompatible changes
* [GenEvent] `GenEvent.cancel_stream/1` no longer is
* [GenEvent] The `:mode` and `:duration` options are deprecated and no longer supported in `GenEvent.stream/2` (the mode is now a property of the sender)
* [GenEvent] The `:linked` option is deprecated and no longer supported in `GenEvent.add_handler/4` (check monitored handlers instead)
## v0.15.1 (2014-08-10)
* Enhancements
* [GenEvent] Support `:ack` mode for GenEvent streams
* [Inspect] Support `:base` option in `inspect/2` to choose the base (`:binary`, `:octal`, `:decimal` or `:hex`) numbers a printed
* [kernel] Print warnings when used ? with characters with escape codes
* [Logger] Add SASL log forwarding option to Logger
* [Logger] Add `$padlevel` to option Logger formatter
* [Logger] Backends receive the exact handler value when added, allowing a key to be passed for configuration lookup
* [Logger] Add `Logger.flush/0` to flush the Logger (useful for testing)
* [Logger] Persist backends dynamically added or removed via `add_backend/2` and `remove_backend/2`
* [Macro] Add `Macro.validate/1` to recursively check if a value is a valid quoted expression
* [Mix] Load mix deps only when there is a need to use them (this improves the timing for the majority of tasks in a Mix project)
* [Mix] Make the environment explicit on the success message generated after `escript.build` is invoked
* [Mix] Load `config/config.exs` inside escripts
* [Mix] Store and check Elixir version requirement for generated archives
* Bug fixes
* [CLI] Fix shell bugs when running on Windows with Cygwin or MinGW
* [Kernel] Modules compiled by Elixir now report the correct beam location when `:code.which/1` is invoked. If the bytecode is only available in memory, `:code.which/1` returns `:in_memory`
* [Kernel] Do not expand args for unknown functions/macros
* [Kernel] Ensure `defstruct`, `@attr` inside and friends raise a nice error messages when values cannot be properly escaped
* [Kernel] Do not raise conflicts on imports used from inside quotes
* [Logger] Metadata is now correctly merged on each `Logger.metadata/1` call
* [Logger] Use the Logger PID on `:error_logger` wrapper to avoid race conditions on shutdown
* [Macro] Ensure bitstrings work with `Macro.escape/1`
* [Mix] Ensure aliases are invoked on umbrella recursive tasks
* [Mix] Leave it up to the application to start the Logger after compilation
* [Mix] Accept more forms of git versions (like "git version 1.9.3 (Apple Git-50)")
* [Path] Do not normalize paths in `Path.join/2` as normalization is beyond the scope of such function
* [URI] `to_string/1` now properly converts URI to strings when the schema is missing
* Deprecations
* [Collectable] Having a function as collectable is deprecated
* [Module] `Module.function/3` is deprecated, please use `:erlang.make_fun/3` instead
## v0.15.0 (2014-08-02)
* Enhancements
* [Agent] Improve the Agent API to also accept functions that receive explicit module, function and arguments
* [IEx] Support `--werl` call on Windows
* [Logger] Add `Logger`
* [Map] Add `Map.from_struct/1`
* [Mix] Allow `--app` flag to be passed to `mix new`
* [Mix] Support lowercase `http(s)_proxy` environment variables
* [Mix] Allow `elixirc_paths` to also be given through the command line to `mix compile.elixir`
* [String] `String.slice/2` and `String.slice/3` have been optimized
* Bug fixes
* [IEx] Ensure functions in `Kernel.SpecialForms` and `IEx.Helpers` are also auto-completed
* [IEx] Ensure remote shells can be started with `--remsh`
* [Kernel] Correctly parse unary/binary operators regardless of number of spaces
* [Kernel] Ensure private functions are not exported
* [Protocol] Do not expose protocol convention on `assert_impl!/2`
* [Regex] Do not consider include captures on `Regex.split/3` results
* [Stream] Implement the `Inspect` protocol for Streams so we do not leak the Stream representation
* Deprecations
* [IEx] IEx color configuration expects a list of atoms instead of a string with colors separated by comma
* [Inspect] `Inspect.Algebra.surround_many/6` now expects Inspect.Opts instead of an integer limit
* [Inspect] `Inspect.Algebra.pretty/2` is deprecated in favor of `Inspect.Algebra.format/2` that instead returns iodata. This function was used only by documentation examples and it is unlikely to affect actual code
* [IO] `IO.ANSI.terminal?` is deprecated in favor of `IO.ANSI.enabled?`
* [IO] `IO.ANSI.escape/2` and `IO.ANSI.escape_fragment/2` is deprecated in favor of `IO.ANSI.format/2` and `IO.ANSI.format_fragment/2`
* [Kernel] Leading `0` for octals is deprecated in favor of `0o`
* [Kernel] `0X` for hexadecimals is deprecated in favor of `0x`
* [Kernel] `0B` for binaries is deprecated in favor of `0b`
* [Mix] Mix color configuration expects a list of atoms instead of a string with colors separated by comma
* [String] `\NNN`, `\NN` and `\N` for octals are deprecated inside string, sigils and chars in favor of hexadecimal entries with `\x`
* Backwards incompatible changes
* [Kernel] `binding/1` and `binding/2` expecting a list were removed
* [Regex] Do not consider include captures on `Regex.split/3` results
## v0.14.3 (2014-07-12)
* Enhancements
* [Access] Allow function access on `get_in/2` and `get_and_update_in/3`
* [Enum] Add `Enum.sort_by/3`
* [ExUnit] Match the line filter by proximity instead of exact match
* [Float] Support precision in `Float.ceil/1` and `Float.floor/1`
* [IO] Add `IO.(bin)read(device, :all)`
* [Kernel] Print a warning if a dangling `@doc` clause is found
* [Mix] Use absolute symbolic links on Windows for `_build` instead of copying
* [Mix] Add `Mix.compilers` that returns all default compilers used by mix tasks
* [Mix] Issue warning and reset mtime for source files from the future
* [Mix] Support task aliases in Mix
* [OptionParser] Add `OptionParser.split/1` that splits a string into argv
* [Record] Allow a record to be converted to a keyword list with `record(some_record)`
* [String] Improve performance of `String.split/1`
* [Typespec] Allow `%Struct{}` syntax to be used in typespecs
* [Typespec] Allow `record(:record, fields)` syntax to be used in typespecs
* Bug fixes
* [IEx] Do not print ANSI sequences on `IEx.Helpers.clear/0` if ANSI sequences are not supported
* [Inspect] Ensure `Inspect.Algebra.to_doc/2` doesn't go into a loop when there is a failure printing a struct
* [Kernel] `|>`, `<<<`, `>>>` and `^^^` made left associative in operator table
* [Kernel] `<`, `>`, `<=`, `>=` given higher precedence than comparison operators (`==`, `!=`, etc) in the operator table
* [Kernel] Run command line and escripts in a process that does not trap exits
* [Kernel] Fix a bug where Mix paths had higher priority than CLI ones, causing protocol consolidations to not be properly loaded
* [Kernel] Fix wording on error messages when a check/guard always passes or always fails
* [Kernel] Fix a bug where an unused function warning was printed even when the function was used via `defoverridable`
* [Kernel] Improve typespecs so they don't generate supertype dialyzer warnings
* [Macro] `to_string` correctly displays sigils
* [Mix] Ensure Mix dependencies are not compiled every second time when `mix deps.compile` is invoked
* [Mix] Fix a bug where `Mix.shell.error/1` and friends choked when printing a map
* [Mix] Ensure multiple `@external_resource` entries are read by Mix compilers
* [Mix] Fix a bug where tasks for umbrella projects were not properly reenabled
* [Stream] Fix bug when `flat_map` is used inside another `flat_map` with an Enumerable
* [Typespec] Fix a bug where the `list` typespec was incorrectly rendered as `[]`
* Soft deprecations (no warnings emitted)
* [Kernel] Using a list for bitstring modifiers is deprecated (as in `<<x :: [little, utf16]>>`), please use `-` as the separator instead (as in `<<x :: little-utf16>>`)
* [System] `System.cmd/1` is deprecated in favor of `System.cmd/3`
* Deprecations
* [Mix] `mix escriptize` is deprecated in favor of `escript.build`
* [Mix] `mix local.install` and `mix local.uninstall` have been deprecated in favor of `mix archive.install` and `mix archive.uninstall` respectively
* [Mix] `:embed_extra_apps` for escripts is deprecated, instead list the dependencies inside `def application`
* [System] Giving a char list to `System.find_executable/1` is deprecated
* Backwards incompatible changes
* [Access] No longer fill in missing intermediate values with empty maps
* [Float] `Float.ceil/2` and `Float.floor/2` now always returns floats and no longer accept integers
* [Kernel] `defstruct` no longer automatically defines a type
* [Kernel] `exit(integer)` is no longer supported from scripts to configure the exit signal. Use `exit({:shutdown, integer})` instead
* [Kernel] Default argument values have to be defined in a function head if the function has multiple clauses
* [Mix] `mix archive.build` replaces the functionality of `mix archive`, `mix archive` now lists locally installed archives
* [Mix] `Mix.shell.info/1` no longer automatically escape ANSI sequences. Instead if has to be explicitly enabled with the `ansi: true` option
* [OptionParser] `--no-SWITCH` are only allowed for declared booleans switches
## v0.14.2 (2014-06-29) ## v0.14.2 (2014-06-29)
@@ -28,7 +293,7 @@
* Bug fixes * Bug fixes
* [CLI] Support paths inside archives in `-pa` and `-pz` options * [CLI] Support paths inside archives in `-pa` and `-pz` options
* [IEx] Remove delay when printing data from the an application start callback * [IEx] Remove delay when printing data from the application start callback
* [IEx] Ensure we show a consistent error when we cannot evaluate `.iex.exs` * [IEx] Ensure we show a consistent error when we cannot evaluate `.iex.exs`
* [Kernel] Ensure derived protocols are defined with a file * [Kernel] Ensure derived protocols are defined with a file
* [Kernel] Change precedence of `&` to not special case `/` * [Kernel] Change precedence of `&` to not special case `/`
@@ -70,7 +335,7 @@
* [URI] `encode/1` does not escape reserved/unreserved characters by default nor encodes whitespace as `+` (check `URI.encode_www_form/1` and `URI.decode_www_form/1` for previous behaviour) * [URI] `encode/1` does not escape reserved/unreserved characters by default nor encodes whitespace as `+` (check `URI.encode_www_form/1` and `URI.decode_www_form/1` for previous behaviour)
* Deprecations * Deprecations
* [Mix] `:escript_*` options were moved into a single `:escript` group * [Mix] `:escript_*` options moved into a single `:escript` group
* Backwards incompatible changes * Backwards incompatible changes
* [GenEvent] `GenEvent.stream/2` defaults to `:sync` mode * [GenEvent] `GenEvent.stream/2` defaults to `:sync` mode
+2 -23
View File
@@ -10,7 +10,7 @@ Use the issues tracker for:
* [bug reports](#bugs-reports) * [bug reports](#bugs-reports)
* [submitting pull requests](#pull-requests) * [submitting pull requests](#pull-requests)
Please **do not** use the issues tracker for personal support requests nor feature requests. Support requests should be send to: Please **do not** use the issue tracker for personal support requests nor feature requests. Support requests should be sent to:
* [the elixir-talk mailing list](http://groups.google.com/group/elixir-lang-talk) * [the elixir-talk mailing list](http://groups.google.com/group/elixir-lang-talk)
* [Stack Overflow](http://stackoverflow.com/questions/ask?tags=elixir) * [Stack Overflow](http://stackoverflow.com/questions/ask?tags=elixir)
@@ -18,7 +18,7 @@ Please **do not** use the issues tracker for personal support requests nor featu
Feature requests can be discussed on [the elixir-core mailing list](http://groups.google.com/group/elixir-lang-core). Feature requests can be discussed on [the elixir-core mailing list](http://groups.google.com/group/elixir-lang-core).
We do our best to keep the issues tracker tidy and organized, making it useful We do our best to keep the issue tracker tidy and organized, making it useful
for everyone. For example, we classify open issues per application and perceived for everyone. For example, we classify open issues per application and perceived
difficulty of the issue, making it easier for developers to difficulty of the issue, making it easier for developers to
[contribute to Elixir](#contributing). [contribute to Elixir](#contributing).
@@ -107,26 +107,6 @@ check [the official build status on Travis-CI](https://travis-ci.org/elixir-lang
With tests running and passing, you are ready to contribute to Elixir and With tests running and passing, you are ready to contribute to Elixir and
send your pull requests. send your pull requests.
### Building on Windows
There are a few extra steps you'll need to take for contributing from Windows.
Basically, once you have Erlang 17, Git, and MSYS from MinGW on your system,
you're all set. Specifically, here's what you need to do to get up and running:
1. Install [Git](http://www.git-scm.com/download/win),
[Erlang](http://www.erlang.org/download.html), and the
[MinGW Installation Manager](http://sourceforge.net/projects/mingw/files/latest/download?source=files).
2. Use the MinGW Installation Manager to install the msys-bash, msys-make, and
msys-grep packages.
3. Add `;C:\Program Files (x86)\Git\bin;C:\Program Files\erl6.0\bin;C:\Program Files\erl6.0\erts-6.0\bin;C:\MinGW\msys\1.0\bin`
to your "Path" environment variable . (This is under Control Panel > System
and Security > System > Advanced system settings > Environment Variables >
System variables)
You can now work in the Command Prompt similar to how you would on other OS'es,
except for some things (like creating symlinks) you'll need to run the Command
Prompt as an Administrator.
## Contributing Documentation ## Contributing Documentation
Code documentation (`@doc`, `@moduledoc`, `@typedoc`) has a special convention: Code documentation (`@doc`, `@moduledoc`, `@typedoc`) has a special convention:
@@ -279,7 +259,6 @@ We have saved some excellent pull requests we have received in the past in case
you are looking for some examples: you are looking for some examples:
* https://github.com/elixir-lang/elixir/pull/992 * https://github.com/elixir-lang/elixir/pull/992
* https://github.com/elixir-lang/elixir/pull/1041
* https://github.com/elixir-lang/elixir/pull/1058 * https://github.com/elixir-lang/elixir/pull/1058
* https://github.com/elixir-lang/elixir/pull/1059 * https://github.com/elixir-lang/elixir/pull/1059
+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"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
+49 -27
View File
@@ -1,4 +1,5 @@
REBAR := rebar REBAR := rebar
DOCS := v1.0
ELIXIRC := bin/elixirc --verbose --ignore-module-conflict ELIXIRC := bin/elixirc --verbose --ignore-module-conflict
ERLC := erlc -I lib/elixir/include ERLC := erlc -I lib/elixir/include
ERL := erl -I lib/elixir/include -noshell -pa lib/elixir/ebin ERL := erl -I lib/elixir/include -noshell -pa lib/elixir/ebin
@@ -11,7 +12,7 @@ INSTALL_DIR = $(INSTALL) -m755 -d
INSTALL_DATA = $(INSTALL) -m644 INSTALL_DATA = $(INSTALL) -m644
INSTALL_PROGRAM = $(INSTALL) -m755 INSTALL_PROGRAM = $(INSTALL) -m755
.PHONY: install compile erlang elixir dialyze test clean docs release_docs release_zip check_erlang_release .PHONY: install compile erlang elixir build_plt clean_plt dialyze test clean docs release_docs release_zip check_erlang_release
.NOTPARALLEL: compile .NOTPARALLEL: compile
#==> Functions #==> Functions
@@ -32,7 +33,7 @@ $(1): lib/$(1)/ebin/Elixir.$(2).beam lib/$(1)/ebin/$(1).app
lib/$(1)/ebin/$(1).app: lib/$(1)/mix.exs lib/$(1)/ebin/$(1).app: lib/$(1)/mix.exs
$(Q) mkdir -p lib/$(1)/_build/shared/lib/$(1) $(Q) mkdir -p lib/$(1)/_build/shared/lib/$(1)
$(Q) cp -R lib/$(1)/ebin lib/$(1)/_build/shared/lib/$(1)/ $(Q) cp -R lib/$(1)/ebin lib/$(1)/_build/shared/lib/$(1)/
$(Q) cd lib/$(1) && ../../bin/elixir -e "Mix.start(:permanent, [])" -r mix.exs -e "Mix.Task.run('compile.app')" $(Q) cd lib/$(1) && ../../bin/elixir -e 'Mix.start(:permanent, [])' -r mix.exs -e 'Mix.Task.run("compile.app")'
$(Q) cp lib/$(1)/_build/shared/lib/$(1)/ebin/$(1).app lib/$(1)/ebin/$(1).app $(Q) cp lib/$(1)/_build/shared/lib/$(1)/ebin/$(1).app lib/$(1)/ebin/$(1).app
$(Q) rm -rf lib/$(1)/_build $(Q) rm -rf lib/$(1)/_build
@@ -68,10 +69,10 @@ erlang:
# Since Mix depends on EEx and EEx depends on # Since Mix depends on EEx and EEx depends on
# Mix, we first compile EEx without the .app # Mix, we first compile EEx without the .app
# file, then mix and then compile EEx fully # file, then mix and then compile EEx fully
elixir: stdlib lib/eex/ebin/Elixir.EEx.beam mix ex_unit eex iex elixir: stdlib lib/eex/ebin/Elixir.EEx.beam mix ex_unit logger eex iex
stdlib: $(KERNEL) VERSION stdlib: $(KERNEL) VERSION
$(KERNEL): lib/elixir/lib/*.ex lib/elixir/lib/*/*.ex $(KERNEL): lib/elixir/lib/*.ex lib/elixir/lib/*/*.ex lib/elixir/lib/*/*/*.ex
$(Q) if [ ! -f $(KERNEL) ]; then \ $(Q) if [ ! -f $(KERNEL) ]; then \
echo "==> bootstrap (compile)"; \ echo "==> bootstrap (compile)"; \
$(ERL) -s elixir_compiler core -s erlang halt; \ $(ERL) -s elixir_compiler core -s erlang halt; \
@@ -86,10 +87,11 @@ $(KERNEL): lib/elixir/lib/*.ex lib/elixir/lib/*/*.ex
unicode: $(UNICODE) unicode: $(UNICODE)
$(UNICODE): lib/elixir/unicode/* $(UNICODE): lib/elixir/unicode/*
@ echo "==> unicode (compile)"; @ echo "==> unicode (compile)";
@ echo "This step can take up to a minute to compile in order to embed the Unicode database" @ echo "Embedding the Unicode database... (this may take a while)"
$(Q) cd lib/elixir && ../../$(ELIXIRC) unicode/unicode.ex -o ebin; $(Q) cd lib/elixir && ../../$(ELIXIRC) unicode/unicode.ex -o ebin;
$(eval $(call APP_TEMPLATE,ex_unit,ExUnit)) $(eval $(call APP_TEMPLATE,ex_unit,ExUnit))
$(eval $(call APP_TEMPLATE,logger,Logger))
$(eval $(call APP_TEMPLATE,eex,EEx)) $(eval $(call APP_TEMPLATE,eex,EEx))
$(eval $(call APP_TEMPLATE,mix,Mix)) $(eval $(call APP_TEMPLATE,mix,Mix))
$(eval $(call APP_TEMPLATE,iex,IEx)) $(eval $(call APP_TEMPLATE,iex,IEx))
@@ -124,15 +126,16 @@ clean_exbeam:
#==> Release tasks #==> Release tasks
SOURCE_REF = $(shell head="$$(git rev-parse HEAD)" tag="$$(git tag --points-at $$head | tail -1)" ; echo "$${tag:-$$head}\c") 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 docs: compile ../ex_doc/bin/ex_doc
$(Q) rm -rf docs $(Q) rm -rf doc
$(call DOCS,Elixir,elixir,Kernel) $(call COMPILE_DOCS,Elixir,elixir,Kernel)
$(call DOCS,EEx,eex,EEx) $(call COMPILE_DOCS,EEx,eex,EEx)
$(call DOCS,Mix,mix,Mix) $(call COMPILE_DOCS,Mix,mix,Mix)
$(call DOCS,IEx,iex,IEx) $(call COMPILE_DOCS,IEx,iex,IEx)
$(call DOCS,ExUnit,ex_unit,ExUnit) $(call COMPILE_DOCS,ExUnit,ex_unit,ExUnit)
$(call COMPILE_DOCS,Logger,logger,Logger)
../ex_doc/bin/ex_doc: ../ex_doc/bin/ex_doc:
@ echo "ex_doc is not found in ../ex_doc as expected. See README for more information." @ echo "ex_doc is not found in ../ex_doc as expected. See README for more information."
@@ -140,12 +143,21 @@ docs: compile ../ex_doc/bin/ex_doc
release_zip: compile release_zip: compile
rm -rf v$(VERSION).zip rm -rf v$(VERSION).zip
zip -9 -r v$(VERSION).zip bin CHANGELOG.md LEGAL lib/*/ebin LICENSE README.md VERSION zip -9 -r v$(VERSION).zip bin CHANGELOG.md LEGAL lib/*/ebin LICENSE Makefile README.md VERSION
release_docs: docs release_docs: docs
cd ../docs rm -rf ../docs/$(DOCS)/*/
rm -rf ../docs/master mv doc/* ../docs/$(DOCS)
mv docs ../docs/master
# 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 #==> Tests tasks
@@ -164,7 +176,7 @@ $(TEST_EBIN)/%.beam: $(TEST_ERL)/%.erl
$(Q) mkdir -p $(TEST_EBIN) $(Q) mkdir -p $(TEST_EBIN)
$(Q) $(ERLC) -o $(TEST_EBIN) $< $(Q) $(ERLC) -o $(TEST_EBIN) $<
test_elixir: test_stdlib test_ex_unit test_doc_test test_mix test_eex test_iex test_elixir: test_stdlib test_ex_unit test_logger test_doc_test test_mix test_eex test_iex
test_doc_test: compile test_doc_test: compile
@ echo "==> doctest (exunit)" @ echo "==> doctest (exunit)"
@@ -173,16 +185,26 @@ test_doc_test: compile
test_stdlib: compile test_stdlib: compile
@ echo "==> elixir (exunit)" @ echo "==> elixir (exunit)"
$(Q) exec epmd & exit $(Q) exec epmd & exit
$(Q) cd lib/elixir && ../../bin/elixir -r "test/elixir/test_helper.exs" -pr "test/elixir/**/*_test.exs"; $(Q) if [ "$(OS)" = "Windows_NT" ]; then \
cd lib/elixir && cmd //C call ../../bin/elixir.bat -r "test/elixir/test_helper.exs" -pr "test/elixir/**/*_test.exs"; \
else \
cd lib/elixir && ../../bin/elixir -r "test/elixir/test_helper.exs" -pr "test/elixir/**/*_test.exs"; \
fi
.dialyzer.base_plt: #==> Dialyzer tasks
@ echo "==> Adding Erlang/OTP basic applications to a new base PLT"
$(Q) dialyzer --output_plt .dialyzer.base_plt --build_plt --apps erts kernel stdlib compiler tools syntax_tools parsetools
dialyze: .dialyzer.base_plt DIALYZER_OPTS = --no_check_plt --fullpath -Werror_handling -Wunmatched_returns -Wunderspecs
$(Q) rm -f .dialyzer_plt PLT = .elixir.plt
$(Q) cp .dialyzer.base_plt .dialyzer_plt
@ echo "==> Adding Elixir to PLT..." $(PLT):
$(Q) dialyzer --plt .dialyzer_plt --add_to_plt -r lib/elixir/ebin lib/ex_unit/ebin lib/eex/ebin lib/iex/ebin lib/mix/ebin @ echo "==> Building PLT with Elixir's dependencies..."
$(Q) dialyzer --output_plt $(PLT) --build_plt --apps erts kernel stdlib compiler syntax_tools parsetools tools ssl inets
clean_plt:
$(Q) rm -f $(PLT)
build_plt: clean_plt $(PLT)
dialyze: compile $(PLT)
@ echo "==> Dialyzing Elixir..." @ echo "==> Dialyzing Elixir..."
$(Q) dialyzer --plt .dialyzer_plt -r lib/elixir/ebin lib/ex_unit/ebin lib/eex/ebin lib/iex/ebin lib/mix/ebin $(Q) dialyzer --plt $(PLT) $(DIALYZER_OPTS) lib/*/ebin
+2
View File
@@ -12,6 +12,8 @@ If you want to contribute to Elixir or run it from source, clone this repository
$ cd elixir $ cd elixir
$ make clean test $ make clean test
> Note: if you are running on Windows, [this article includes important notes for compiling Elixir from source on Windows](https://github.com/elixir-lang/elixir/wiki/Windows).
If Elixir fails to build (specifically when pulling in a new version via git), be sure to remove any previous build artifacts by running `make clean`, then `make test`. If Elixir fails to build (specifically when pulling in a new version via git), be sure to remove any previous build artifacts by running `make clean`, then `make test`.
If tests pass, you are ready to move on to the [Getting Started guide][1] or to try Interactive Elixir by running: `bin/iex` in your terminal. If tests pass, you are ready to move on to the [Getting Started guide][1] or to try Interactive Elixir by running: `bin/iex` in your terminal.
+26 -14
View File
@@ -1,31 +1,43 @@
# Release process # Release process
## All releases
This document simply outlines the release process: This document simply outlines the release process:
1. Remove `-dev` extension from VERSION 1. Ensure you are running on the oldest supported Erlang version
2. Ensure CHANGELOG is updated and timestamp 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 tags 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 7. Push branch and the new tag
8. Merge master into stable branch 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.org` 10. Add the release to `elixir.csv` file in `elixir-lang/elixir-lang.github.com`
11. Also update `release` file in `elixir-lang.org` 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 ## Places where version is mentioned
* VERSION * VERSION (make sure there is no newline in this file)
* CHANGELOG * CHANGELOG.md
* src/elixir.app.src * src/elixir.app.src (not lib/elixir/src/elixir.app.src)
+1 -1
View File
@@ -1 +1 @@
0.14.3-dev 1.0.4
+8 -1
View File
@@ -80,7 +80,14 @@ done
SELF=$(readlink_f "$0") SELF=$(readlink_f "$0")
SCRIPT_PATH=$(dirname "$SELF") SCRIPT_PATH=$(dirname "$SELF")
if [ "$MODE" != "iex" ]; then ERL="$ERL -noshell -s elixir start_cli"; 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
if [ "$OS" != "Windows_NT" ]; then
if test -t 1 -a -t 2; then ERL="-elixir ansi_enabled true $ERL"; fi
fi
if [ -z "$ERL_PATH" ]; then if [ -z "$ERL_PATH" ]; then
if [ -f "$SCRIPT_PATH/../releases/RELEASES" ] && [ -f "$SCRIPT_PATH/erl" ]; then if [ -f "$SCRIPT_PATH/../releases/RELEASES" ] && [ -f "$SCRIPT_PATH/erl" ]; then
+31 -20
View File
@@ -1,4 +1,5 @@
@echo off @echo off
setlocal
if "%1"=="" goto documentation if "%1"=="" goto documentation
if "%1"=="--help" goto documentation if "%1"=="--help" goto documentation
if "%1"=="-h" goto documentation if "%1"=="-h" goto documentation
@@ -22,12 +23,13 @@ echo --sname name Makes and assigns a short name to the distributed node
echo --cookie cookie Sets a cookie for this distributed node echo --cookie cookie Sets a cookie for this distributed node
echo --hidden Makes a hidden node echo --hidden Makes a hidden node
echo --detached Starts the Erlang VM detached from console echo --detached Starts the Erlang VM detached from console
echo --werl Uses Erlang's Windows shell GUI
echo --no-halt Does not halt the Erlang VM after execution echo --no-halt Does not halt the Erlang VM after execution
echo. echo.
echo ** Options marked with (*) can be given more than once echo ** Options marked with (*) can be given more than once
echo ** Options given after the .exs file or -- are passed down to the executed code echo ** Options given after the .exs file or -- are passed down to the executed code
echo ** Options can be passed to the erlang runtime using ELIXIR_ERL_OPTIONS or --erl echo ** Options can be passed to the erlang runtime using ELIXIR_ERL_OPTIONS or --erl
goto :EOF goto end
:parseopts :parseopts
@@ -46,6 +48,9 @@ set beforeExtra=
rem Flag which determines whether or not to use werl vs erl rem Flag which determines whether or not to use werl vs erl
set useWerl=0 set useWerl=0
rem Designates which mode / Elixir component to run as
set runMode="elixir"
rem Recursive loop called for each parameter that parses the cmd line parameters rem Recursive loop called for each parameter that parses the cmd line parameters
:startloop :startloop
set par="%1" set par="%1"
@@ -59,23 +64,24 @@ if "%par%"=="""" (
goto :expand_erl_libs goto :expand_erl_libs
) )
rem ******* EXECUTION OPTIONS ********************** rem ******* EXECUTION OPTIONS **********************
IF "%par%"==""+iex"" (Set useWerl=1) IF "%par%"==""--werl"" (Set useWerl=1)
rem ******* ERLANG PARAMETERS ********************** IF "%par%"==""+iex"" (Set runMode="iex")
IF NOT "%par%"=="%par:--detached=%" (Set parsErlang=%parsErlang% -detached)
IF NOT "%par%"=="%par:--hidden=%" (Set parsErlang=%parsErlang% -hidden)
IF NOT "%par%"=="%par:--cookie=%" (Set parsErlang=%parsErlang% -setcookie %1 && shift)
IF NOT "%par%"=="%par:--sname=%" (Set parsErlang=%parsErlang% -sname %1 && shift)
IF NOT "%par%"=="%par:--name=%" (Set parsErlang=%parsErlang% -name %1 && shift)
IF NOT "%par%"=="%par:--erl=%" (Set beforeExtra=%beforeExtra% %~1 && shift)
rem ******* elixir parameters ********************** rem ******* elixir parameters **********************
rem Note: we don't have to do anything with options that don't take an argument rem Note: we don't have to do anything with options that don't take an argument
IF NOT "%par%"=="%par:-e=%" (shift) IF """"=="%par:-e=%" (shift)
IF NOT "%par%"=="%par:-r=%" (shift) IF """"=="%par:-r=%" (shift)
IF NOT "%par%"=="%par:-pr=%" (shift) IF """"=="%par:-pr=%" (shift)
IF NOT "%par%"=="%par:-pa=%" (shift) IF """"=="%par:-pa=%" (shift)
IF NOT "%par%"=="%par:-pz=%" (shift) IF """"=="%par:-pz=%" (shift)
IF NOT "%par%"=="%par:--app=%" (shift) IF """"=="%par:--app=%" (shift)
IF NOT "%par%"=="%par:--remsh=%" (shift) IF """"=="%par:--remsh=%" (shift)
rem ******* ERLANG PARAMETERS **********************
IF """"=="%par:--detached=%" (Set parsErlang=%parsErlang% -detached)
IF """"=="%par:--hidden=%" (Set parsErlang=%parsErlang% -hidden)
IF """"=="%par:--cookie=%" (Set parsErlang=%parsErlang% -setcookie %1 && shift)
IF """"=="%par:--sname=%" (Set parsErlang=%parsErlang% -sname %1 && shift)
IF """"=="%par:--name=%" (Set parsErlang=%parsErlang% -name %1 && shift)
IF """"=="%par:--erl=%" (Set beforeExtra=%beforeExtra% %~1 && shift)
goto:startloop goto:startloop
rem ******* assume all pre-params are parsed ******************** rem ******* assume all pre-params are parsed ********************
@@ -88,8 +94,13 @@ for /d %%d in ("%originPath%..\lib\*.") do (
) )
SETLOCAL disabledelayedexpansion SETLOCAL disabledelayedexpansion
:run :run
IF %useWerl% EQU 1 ( IF NOT %runMode% == "iex" (
werl.exe %ext_libs% %ELIXIR_ERL_OPTIONS% %parsErlang% -s elixir start_cli %beforeExtra% -extra %* set beforeExtra=-s elixir start_cli %beforeExtra%
) ELSE (
erl.exe %ext_libs% -noshell %ELIXIR_ERL_OPTIONS% %parsErlang% -s elixir start_cli %beforeExtra% -extra %*
) )
IF %useWerl% EQU 1 (
start werl.exe %ext_libs% %ELIXIR_ERL_OPTIONS% %parsErlang% %beforeExtra% -extra %*
) ELSE (
erl.exe %ext_libs% -noshell %ELIXIR_ERL_OPTIONS% %parsErlang% %beforeExtra% -extra %*
)
:end
endlocal
+3 -1
View File
@@ -38,4 +38,6 @@ readlink_f () {
SELF=$(readlink_f "$0") SELF=$(readlink_f "$0")
SCRIPT_PATH=$(dirname "$SELF") SCRIPT_PATH=$(dirname "$SELF")
exec "$SCRIPT_PATH"/elixir --no-halt --erl "-user Elixir.IEx.CLI" +iex "$@"
if [ "$OS" = "Windows_NT" ]; then NOSHELL="-noshell "; fi
exec "$SCRIPT_PATH"/elixir --no-halt --erl "$NOSHELL -user Elixir.IEx.CLI" +iex "$@"
+1
View File
@@ -1,3 +1,4 @@
#!/usr/bin/env elixir #!/usr/bin/env elixir
# Reminder: apply any changes in this file to bin\mix.bat
Mix.start Mix.start
Mix.CLI.main Mix.CLI.main
+1 -1
View File
@@ -1,2 +1,2 @@
@echo off @echo off
call "%~dp0\elixir.bat" "%~dp0\mix" %* call "%~dp0\elixir.bat" -e Mix.start -e Mix.CLI.main %*
+10 -14
View File
@@ -1,27 +1,23 @@
# Initialize with path to mix.bat relative to caller's working directory # Store path to mix.bat as a FileInfo object
$toCmd = '' + (Resolve-Path -relative (Split-Path $MyInvocation.MyCommand.Path)) + '\mix.bat' $mixBatPath = (Get-ChildItem (((Get-ChildItem $MyInvocation.MyCommand.Path).Directory.FullName) + '\mix.bat'))
$newArgs = @()
foreach ($arg in $args) for ($i = 0; $i -lt $args.length; $i++)
{ {
$toCmd += ' ' if ($args[$i] -is [array])
if ($arg -is [array])
{ {
# Commas created the array so we need to reintroduce those commas # Commas created the array so we need to reintroduce those commas
for ($i = 0; $i -lt $arg.length; $i++) for ($j = 0; $j -lt $args[$i].length - 1; $j++)
{ {
$toCmd += $arg[$i] $newArgs += ($args[$i][$j] + ',')
if ($i -ne ($arg.length - 1))
{
$toCmd += ', '
}
} }
$newArgs += $args[$i][-1]
} }
else else
{ {
$toCmd += $arg $newArgs += $args[$i]
} }
} }
# Corrected arguments are ready to pass to batch file # Corrected arguments are ready to pass to batch file
cmd /c $toCmd & $mixBatPath $newArgs
+8 -5
View File
@@ -1,5 +1,9 @@
defmodule EEx.SyntaxError do defmodule EEx.SyntaxError do
defexception [:message] defexception [:message, :file, :line]
def message(exception) do
"#{exception.file}:#{exception.line}: #{exception.message}"
end
end end
defmodule EEx do defmodule EEx do
@@ -34,10 +38,9 @@ defmodule EEx do
All functions in this module accepts EEx-related options. All functions in this module accepts EEx-related options.
They are: They are:
* `:line` - the line to be used as the template start. Defaults to 1. * `:line` - the line to be used as the template start. Defaults to 1.
* `:file` - the file to be used in the template. Defaults to the given * `:file` - the file to be used in the template. Defaults to the given
file the template is read from or to "nofile" when compiling file the template is read from or to "nofile" when compiling from a string.
from a string.
* `:engine` - the EEx engine to be used for compilation. * `:engine` - the EEx engine to be used for compilation.
## Engine ## Engine
+13 -8
View File
@@ -12,10 +12,14 @@ defmodule EEx.Compiler do
def compile(source, opts) do def compile(source, opts) do
file = opts[:file] || "nofile" file = opts[:file] || "nofile"
line = opts[:line] || 1 line = opts[:line] || 1
tokens = EEx.Tokenizer.tokenize(source, line) case EEx.Tokenizer.tokenize(source, line) do
state = %{engine: opts[:engine] || @default_engine, {:ok, tokens} ->
file: file, line: line, quoted: [], start_line: nil} state = %{engine: opts[:engine] || @default_engine,
generate_buffer(tokens, "", [], state) 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 end
# Generates the buffers by handling each expression from the tokenizer # Generates the buffers by handling each expression from the tokenizer
@@ -51,16 +55,17 @@ defmodule EEx.Compiler do
{buffer, t} {buffer, t}
end end
defp generate_buffer([{:end_expr, line, _, chars}|_], _buffer, [], _state) do defp generate_buffer([{:end_expr, line, _, chars}|_], _buffer, [], state) do
raise EEx.SyntaxError, message: "unexpected token: #{inspect chars} at line #{inspect line}" raise EEx.SyntaxError, message: "unexpected token #{inspect chars}", file: state.file, line: line
end end
defp generate_buffer([], buffer, [], state) do defp generate_buffer([], buffer, [], state) do
state.engine.handle_body(buffer) state.engine.handle_body(buffer)
end end
defp generate_buffer([], _buffer, _scope, _state) do defp generate_buffer([], _buffer, _scope, state) do
raise EEx.SyntaxError, message: "unexpected end of string. expecting a closing <% end %>." raise EEx.SyntaxError, message: "unexpected end of string, expected a closing '<% end %>'",
file: state.file, line: state.line
end end
# Creates a placeholder and wrap it inside the expression block # Creates a placeholder and wrap it inside the expression block
+3 -2
View File
@@ -64,7 +64,8 @@ defmodule EEx.Engine do
end end
""" """
def handle_assign({:@, line, [{name, _, atom}]}) when is_atom(name) and is_atom(atom) do def handle_assign({:@, meta, [{name, _, atom}]}) when is_atom(name) and is_atom(atom) do
line = meta[:line] || 0
quote line: line, do: Dict.get(var!(assigns), unquote(name)) quote line: line, do: Dict.get(var!(assigns), unquote(name))
end end
@@ -98,7 +99,7 @@ defmodule EEx.Engine do
def handle_expr(buffer, "=", expr) do def handle_expr(buffer, "=", expr) do
quote do quote do
tmp = unquote(buffer) tmp = unquote(buffer)
tmp <> to_string(unquote(expr)) tmp <> String.Chars.to_string(unquote(expr))
end end
end end
-60
View File
@@ -1,63 +1,3 @@
defmodule EEx.TransformerEngine do
@moduledoc false
@doc false
defmacro __using__(_) do
quote do
@behaviour EEx.Engine
def handle_body(body) do
EEx.Engine.handle_body(body)
end
def handle_text(buffer, text) do
EEx.Engine.handle_text(buffer, text)
end
def handle_expr(buffer, mark, expr) do
EEx.Engine.handle_expr(buffer, mark, transform(expr))
end
defp transform({a, b, c}) do
{transform(a), b, transform(c)}
end
defp transform({a, b}) do
{transform(a), transform(b)}
end
defp transform(list) when is_list(list) do
for i <- list, do: transform(i)
end
defp transform(other) do
other
end
defoverridable [transform: 1, handle_body: 1, handle_expr: 3, handle_text: 2]
end
end
end
defmodule EEx.AssignsEngine do
@moduledoc false
@doc false
defmacro __using__(_) do
quote unquote: false do
defp transform({:@, line, [{name, _, atom}]}) when is_atom(name) and is_atom(atom) do
quote do: Dict.get(var!(assigns), unquote(name))
end
defp transform(arg) do
super(arg)
end
defoverridable [transform: 1]
end
end
end
defmodule EEx.SmartEngine do defmodule EEx.SmartEngine do
@moduledoc """ @moduledoc """
The default engine used by EEx. The default engine used by EEx.
+31 -20
View File
@@ -3,7 +3,8 @@ defmodule EEx.Tokenizer do
@doc """ @doc """
Tokenizes the given char list or binary. 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}` * `{:text, contents}`
* `{:expr, line, marker, contents}` * `{:expr, line, marker, contents}`
@@ -11,33 +12,43 @@ defmodule EEx.Tokenizer do
* `{:middle_expr, line, marker, contents}` * `{:middle_expr, line, marker, contents}`
* `{:end_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 def tokenize(bin, line) when is_binary(bin) do
tokenize(String.to_char_list(bin), line) tokenize(String.to_char_list(bin), line)
end end
def tokenize(list, line) do def tokenize(list, line) do
Enum.reverse(tokenize(list, line, [], [])) tokenize(list, line, [], [])
end end
defp tokenize('<%%' ++ t, line, buffer, acc) do defp tokenize('<%%' ++ t, line, buffer, acc) do
{buffer, new_line, rest} = tokenize_expr t, line, [?%, ?<|buffer] case expr(t, line, [?%, ?<|buffer]) do
tokenize rest, new_line, [?>, ?%|buffer], acc {:error, _, _} = error -> error
{:ok, buffer, new_line, rest} ->
tokenize rest, new_line, [?>, ?%|buffer], acc
end
end end
defp tokenize('<%#' ++ t, line, buffer, acc) do defp tokenize('<%#' ++ t, line, buffer, acc) do
{_, new_line, rest} = tokenize_expr t, line, [] case expr(t, line, []) do
tokenize rest, new_line, buffer, acc {:error, _, _} = error -> error
{:ok, _, new_line, rest} ->
tokenize rest, new_line, buffer, acc
end
end end
defp tokenize('<%' ++ t, line, buffer, acc) do defp tokenize('<%' ++ t, line, buffer, acc) do
{marker, t} = retrieve_marker(t) {marker, t} = retrieve_marker(t)
{expr, new_line, rest} = tokenize_expr t, line, []
token = token_name(expr) case expr(t, line, []) do
acc = tokenize_text(buffer, acc) {:error, _, _} = error -> error
final = {token, line, marker, Enum.reverse(expr)} {:ok, expr, new_line, rest} ->
tokenize rest, new_line, [], [final | acc] token = token_name(expr)
acc = tokenize_text(buffer, acc)
final = {token, line, marker, Enum.reverse(expr)}
tokenize rest, new_line, [], [final | acc]
end
end end
defp tokenize('\n' ++ t, line, buffer, acc) do defp tokenize('\n' ++ t, line, buffer, acc) do
@@ -49,7 +60,7 @@ defmodule EEx.Tokenizer do
end end
defp tokenize([], _line, buffer, acc) do defp tokenize([], _line, buffer, acc) do
tokenize_text(buffer, acc) {:ok, Enum.reverse(tokenize_text(buffer, acc))}
end end
# Retrieve marker for <% # Retrieve marker for <%
@@ -64,20 +75,20 @@ defmodule EEx.Tokenizer do
# Tokenize an expression until we find %> # Tokenize an expression until we find %>
defp tokenize_expr([?%, ?>|t], line, buffer) do defp expr([?%, ?>|t], line, buffer) do
{buffer, line, t} {:ok, buffer, line, t}
end end
defp tokenize_expr('\n' ++ t, line, buffer) do defp expr('\n' ++ t, line, buffer) do
tokenize_expr t, line + 1, [?\n|buffer] expr t, line + 1, [?\n|buffer]
end end
defp tokenize_expr([h|t], line, buffer) do defp expr([h|t], line, buffer) do
tokenize_expr t, line, [h|buffer] expr t, line, [h|buffer]
end end
defp tokenize_expr([], _line, _buffer) do defp expr([], line, _buffer) do
raise EEx.SyntaxError, message: "missing token: %>" {:error, line, "missing token '%>'"}
end end
# Receive an expression content and check # Receive an expression content and check
+10
View File
@@ -19,6 +19,16 @@ defmodule EEx.SmartEngineTest do
assert_eval "1\n2\n3\n", "<%= for x <- [1, 2, 3] do %><%= x %>\n<% end %>" assert_eval "1\n2\n3\n", "<%= for x <- [1, 2, 3] do %><%= x %>\n<% end %>"
end end
test "compiled preserved line numbers" do
result = EEx.compile_string("<%= @hello %>", engine: EEx.SmartEngine)
Macro.prewalk(result, fn
{_left, meta, _right} ->
assert Keyword.get(meta, :line, 0) in [0, 1]
_ ->
:ok
end)
end
defp assert_eval(expected, actual, binding \\ []) do defp assert_eval(expected, actual, binding \\ []) do
result = EEx.eval_string(actual, binding, file: __ENV__.file) result = EEx.eval_string(actual, binding, file: __ENV__.file)
assert result == expected assert result == expected
+25 -24
View File
@@ -5,23 +5,26 @@ defmodule EEx.TokenizerTest do
require EEx.Tokenizer, as: T require EEx.Tokenizer, as: T
test "simple chars lists" do test "simple chars lists" do
assert T.tokenize('foo', 1) == [ {:text, 'foo'} ] assert T.tokenize('foo', 1) == {:ok, [{:text, 'foo'}]}
end end
test "simple strings" do test "simple strings" do
assert T.tokenize("foo", 1) == [ {:text, 'foo'} ] assert T.tokenize("foo", 1) == {:ok, [{:text, 'foo'}]}
end end
test "strings with embedded code" do 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 end
test "strings with embedded equals code" do 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 end
test "strings with more than one line" do 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 end
test "strings with more than one line and expression with more than one line" do test "strings with more than one line and expression with more than one line" do
@@ -32,50 +35,50 @@ baz %>
<% foo %> <% foo %>
''' '''
assert T.tokenize(string, 1) == [ assert T.tokenize(string, 1) == {:ok, [
{:text, 'foo '}, {:text, 'foo '},
{:expr, 1, "=", ' bar\n\nbaz '}, {:expr, 1, "=", ' bar\n\nbaz '},
{:text, '\n'}, {:text, '\n'},
{:expr, 4, "", ' foo '}, {:expr, 4, "", ' foo '},
{:text, '\n'} {:text, '\n'}
] ]}
end end
test "quotation" do test "quotation" do
assert T.tokenize('foo <%% true %>', 1) == [ assert T.tokenize('foo <%% true %>', 1) == {:ok, [
{:text, 'foo <% true %>'} {:text, 'foo <% true %>'}
] ]}
end end
test "quotation with do/end" do 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 %>'} {:text, 'foo <% true do %>bar<% end %>'}
] ]}
end end
test "comments" do test "comments" do
assert T.tokenize('foo <%# true %>', 1) == [ assert T.tokenize('foo <%# true %>', 1) == {:ok, [
{:text, 'foo '} {:text, 'foo '}
] ]}
end end
test "comments with do/end" do 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'} {:text, 'foo bar'}
] ]}
end end
test "strings with embedded do end" do 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 '}, {:text, 'foo '},
{:start_expr, 1, "", ' if true do '}, {:start_expr, 1, "", ' if true do '},
{:text, 'bar'}, {:text, 'bar'},
{:end_expr, 1, "", ' end '} {:end_expr, 1, "", ' end '}
] ]}
end end
test "strings with embedded -> end" do 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 '}, {:text, 'foo '},
{:start_expr, 1, "", ' cond do '}, {:start_expr, 1, "", ' cond do '},
{:middle_expr, 1, "", ' false -> '}, {:middle_expr, 1, "", ' false -> '},
@@ -83,23 +86,21 @@ baz %>
{:middle_expr, 1, "", ' true -> '}, {:middle_expr, 1, "", ' true -> '},
{:text, 'baz'}, {:text, 'baz'},
{:end_expr, 1, "", ' end '} {:end_expr, 1, "", ' end '}
] ]}
end end
test "strings with embedded keywords blocks" do 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 '}, {:text, 'foo '},
{:start_expr, 1, "", ' if true do '}, {:start_expr, 1, "", ' if true do '},
{:text, 'bar'}, {:text, 'bar'},
{:middle_expr, 1, "", ' else '}, {:middle_expr, 1, "", ' else '},
{:text, 'baz'}, {:text, 'baz'},
{:end_expr, 1, "", ' end '} {:end_expr, 1, "", ' end '}
] ]}
end end
test "raise syntax error when there is start mark and no end mark" do test "raise syntax error when there is start mark and no end mark" do
assert_raise EEx.SyntaxError, "missing token: %>", fn -> assert T.tokenize('foo <% :bar', 1) == {:error, 1, "missing token '%>'"}
T.tokenize('foo <% :bar', 1)
end
end end
end end
+8 -7
View File
@@ -50,7 +50,8 @@ defmodule EExTest do
use ExUnit.Case, async: true use ExUnit.Case, async: true
doctest EEx doctest EEx
doctest EEx.AssignsEngine doctest EEx.Engine
doctest EEx.SmartEngine
test "evaluates simple string" do test "evaluates simple string" do
assert_eval "foo bar", "foo bar" assert_eval "foo bar", "foo bar"
@@ -110,25 +111,25 @@ defmodule EExTest do
end end
test "raises a syntax error when the token is invalid" do 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" EEx.compile_string "foo <%= bar"
end end
end end
test "raises a syntax error when end expression is found without a start expression" do 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 %>" EEx.compile_string "foo <% end %>"
end end
end end
test "raises a syntax error when start expression is found without an end expression" do 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 -> assert_raise EEx.SyntaxError, "nofile:2: unexpected end of string, expected a closing '<% end %>'", fn ->
EEx.compile_string "foo <% if true do %>" EEx.compile_string "foo\n<% if true do %>"
end end
end end
test "raises a syntax error when nested end expression is found without an start expression" do test "raises a syntax error when nested 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 <% if true do %><% end %><% end %>" EEx.compile_string "foo <% if true do %><% end %><% end %>"
end end
end end
+4 -3
View File
@@ -119,7 +119,7 @@ defimpl Access, for: Map do
def get_and_update!(other, key, _fun) do def get_and_update!(other, key, _fun) do
raise ArgumentError, raise ArgumentError,
"could not update key #{inspect key}. Expected map/struct, got: #{inspect other}" "could not put/update key #{inspect key}. Expected map/struct, got: #{inspect other}"
end end
end end
@@ -132,8 +132,9 @@ defimpl Access, for: Atom do
undefined(atom) undefined(atom)
end end
def get_and_update(nil, _, fun) do def get_and_update(nil, key, _fun) do
fun.(nil) raise ArgumentError,
"could not put/update key #{inspect key} on a nil value"
end end
def get_and_update(atom, _key, _fun) do def get_and_update(atom, _key, _fun) do
+89 -18
View File
@@ -68,22 +68,22 @@ defmodule Agent do
## A word on distributed agents ## A word on distributed agents
It is important to consider the limitations of distributed agents. Agents It is important to consider the limitations of distributed agents. Agents
work by sending anonymous functions between the caller and the agent. provides two APIs, one that works with anonymous functions and another
In a distributed setup with multiple nodes, agents only work if the caller that expects explicit module, function and arguments.
(client) and the agent have the same version of a given module.
This setup may exhibit issues when doing "rolling upgrades". By rolling In a distributed setup with multiple nodes, the API that accepts anonymous
upgrades we mean the following situation: you wish to deploy a new version of functions only works if the caller (client) and the agent have the same
your software by *shutting down* some of your nodes and replacing them with version of the caller module.
nodes running a new version of the software. In this setup, part of your
environment will have one version of a given module and the other part
another version (the newer one) of the same module; this may cause agents to
crash. That said, if you plan to run in distributed environments, agents
should likely be avoided.
Note, however, that agents work fine if you want to perform hot code Keep in mind this issue also shows up when performing "rolling upgrades"
swapping, as it keeps both the old and new versions of a given module. with agents. By rolling upgrades we mean the following situation: you wish
We detail how to do hot code swapping with agents in the next section. to deploy a new version of your software by *shutting down* some of your
nodes and replacing them with nodes running a new version of the software.
In this setup, part of your environment will have one version of a given
module and the other part another version (the newer one) of the same module.
The best solution is to simply use the explicit module, function and arguments
APIs when working with distributed agents.
## Hot code swapping ## Hot code swapping
@@ -111,7 +111,7 @@ defmodule Agent do
@type state :: term @type state :: term
@doc """ @doc """
Starts an agent linked to the current process. Starts an agent linked to the current process with the given function.
This is often used to start the agent as part of a supervision tree. This is often used to start the agent as part of a supervision tree.
@@ -149,6 +149,18 @@ defmodule Agent do
GenServer.start_link(Agent.Server, fun, options) GenServer.start_link(Agent.Server, fun, options)
end end
@doc """
Starts an agent linked to the current process with the given module
function and arguments.
Same as `start_link/2` but a module, function and args are expected
instead of an anonymous function.
"""
@spec start_link(module, atom, [any], GenServer.options) :: on_start
def start_link(module, fun, args, options \\ []) do
GenServer.start_link(Agent.Server, {module, fun, args}, options)
end
@doc """ @doc """
Starts an agent process without links (outside of a supervision tree). Starts an agent process without links (outside of a supervision tree).
@@ -160,7 +172,18 @@ defmodule Agent do
end end
@doc """ @doc """
Gets the agent value and executes the given function. Starts an agent with the given module function and arguments.
Similar to `start/2` but a module, function and args are expected
instead of an anonymous function.
"""
@spec start(module, atom, [any], GenServer.options) :: on_start
def start(module, fun, args, options \\ []) do
GenServer.start(Agent.Server, {module, fun, args}, options)
end
@doc """
Gets an agent value via the given function.
The function `fun` is sent to the `agent` which invokes the function The function `fun` is sent to the `agent` which invokes the function
passing the agent state. The result of the function invocation is passing the agent state. The result of the function invocation is
@@ -173,6 +196,18 @@ defmodule Agent do
GenServer.call(agent, {:get, fun}, timeout) GenServer.call(agent, {:get, fun}, timeout)
end end
@doc """
Gets an agent value via the given function.
Same as `get/3` but a module, function and args are expected
instead of an anonymous function. The state is added as first
argument to the given list of args.
"""
@spec get(agent, module, atom, [term], timeout) :: any
def get(agent, module, fun, args, timeout \\ 5000) do
GenServer.call(agent, {:get, {module, fun, args}}, timeout)
end
@doc """ @doc """
Gets and updates the agent state in one operation. Gets and updates the agent state in one operation.
@@ -188,6 +223,18 @@ defmodule Agent do
GenServer.call(agent, {:get_and_update, fun}, timeout) GenServer.call(agent, {:get_and_update, fun}, timeout)
end end
@doc """
Gets and updates the agent state in one operation.
Same as `get_and_update/3` but a module, function and args are expected
instead of an anonymous function. The state is added as first
argument to the given list of args.
"""
@spec get_and_update(agent, module, atom, [term], timeout) :: any
def get_and_update(agent, module, fun, args, timeout \\ 5000) do
GenServer.call(agent, {:get_and_update, {module, fun, args}}, timeout)
end
@doc """ @doc """
Updates the agent state. Updates the agent state.
@@ -197,11 +244,23 @@ defmodule Agent do
A timeout can also be specified (it has a default value of 5000). A timeout can also be specified (it has a default value of 5000).
This function always returns `:ok`. This function always returns `:ok`.
""" """
@spec update(agent, (state -> state)) :: :ok @spec update(agent, (state -> state), timeout) :: :ok
def update(agent, fun, timeout \\ 5000) when is_function(fun, 1) do def update(agent, fun, timeout \\ 5000) when is_function(fun, 1) do
GenServer.call(agent, {:update, fun}, timeout) GenServer.call(agent, {:update, fun}, timeout)
end end
@doc """
Updates the agent state.
Same as `update/3` but a module, function and args are expected
instead of an anonymous function. The state is added as first
argument to the given list of args.
"""
@spec update(agent, module, atom, [term], timeout) :: :ok
def update(agent, module, fun, args, timeout \\ 5000) do
GenServer.call(agent, {:update, {module, fun, args}}, timeout)
end
@doc """ @doc """
Performs a cast (fire and forget) operation on the agent state. Performs a cast (fire and forget) operation on the agent state.
@@ -213,7 +272,19 @@ defmodule Agent do
""" """
@spec cast(agent, (state -> state)) :: :ok @spec cast(agent, (state -> state)) :: :ok
def cast(agent, fun) when is_function(fun, 1) do def cast(agent, fun) when is_function(fun, 1) do
GenServer.cast(agent, fun) GenServer.cast(agent, {:cast, fun})
end
@doc """
Performs a cast (fire and forget) operation on the agent state.
Same as `cast/2` but a module, function and args are expected
instead of an anonymous function. The state is added as first
argument to the given list of args.
"""
@spec cast(agent, module, atom, [term]) :: :ok
def cast(agent, module, fun, args) do
GenServer.cast(agent, {:cast, {module, fun, args}})
end end
@doc """ @doc """
+11 -8
View File
@@ -4,20 +4,20 @@ defmodule Agent.Server do
use GenServer use GenServer
def init(fun) do def init(fun) do
{:ok, fun.()} {:ok, run(fun, [])}
end end
def handle_call({:get, fun}, _from, state) do def handle_call({:get, fun}, _from, state) do
{:reply, fun.(state), state} {:reply, run(fun, [state]), state}
end end
def handle_call({:get_and_update, fun}, _from, state) do def handle_call({:get_and_update, fun}, _from, state) do
{reply, state} = fun.(state) {reply, state} = run(fun, [state])
{:reply, reply, state} {:reply, reply, state}
end end
def handle_call({:update, fun}, _from, state) do def handle_call({:update, fun}, _from, state) do
{:reply, :ok, fun.(state)} {:reply, :ok, run(fun, [state])}
end end
def handle_call(:stop, _from, state) do def handle_call(:stop, _from, state) do
@@ -28,16 +28,16 @@ defmodule Agent.Server do
super(msg, from, state) super(msg, from, state)
end end
def handle_cast(fun, state) when is_function(fun, 1) do def handle_cast({:cast, fun}, state) do
{:noreply, fun.(state)} {:noreply, run(fun, [state])}
end end
def handle_cast(msg, state) do def handle_cast(msg, state) do
super(msg, state) super(msg, state)
end end
def code_change(_old, state, { m, f, a }) do def code_change(_old, state, fun) do
{:ok, apply(m, f, [state|a])} {:ok, run(fun, [state])}
end end
def terminate(_reason, _state) do def terminate(_reason, _state) do
@@ -50,4 +50,7 @@ defmodule Agent.Server do
end end
:ok :ok
end end
defp run({m, f, a}, extra), do: apply(m, f, extra ++ a)
defp run(fun, extra), do: apply(fun, extra)
end end
+2 -2
View File
@@ -201,7 +201,7 @@ defmodule Application do
`:applications` in the `.app` file in case they were not previously `:applications` in the `.app` file in case they were not previously
started. 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 def ensure_all_started(app, type \\ :temporary) when is_atom(app) do
:application.ensure_all_started(app, type) :application.ensure_all_started(app, type)
end end
@@ -322,7 +322,7 @@ defmodule Application do
@doc """ @doc """
Formats the error reason returned by `start/2`, Formats the error reason returned by `start/2`,
`ensure_started/2, `stop/1`, `load/1` and `unload/1`, `ensure_started/2`, `stop/1`, `load/1` and `unload/1`,
returns a string. returns a string.
""" """
@spec format_error(any) :: String.t @spec format_error(any) :: String.t
+4 -4
View File
@@ -499,7 +499,7 @@ defmodule Base do
defp do_encode64(<<>>, _), do: <<>> defp do_encode64(<<>>, _), do: <<>>
defp do_encode64(data, enc) do defp do_encode64(data, enc) do
split = 3 * div(byte_size(data), 3) split = 3 * div(byte_size(data), 3)
<<main::[size(split), binary], rest::binary>> = data <<main::size(split)-binary, rest::binary>> = data
main = for <<c::6 <- main>>, into: <<>>, do: <<enc.(c)::8>> main = for <<c::6 <- main>>, into: <<>>, do: <<enc.(c)::8>>
case rest do case rest do
<<c1::6, c2::6, c3::4>> -> <<c1::6, c2::6, c3::4>> ->
@@ -514,7 +514,7 @@ defmodule Base do
defp do_decode64(<<>>, _), do: <<>> defp do_decode64(<<>>, _), do: <<>>
defp do_decode64(string, dec) when rem(byte_size(string), 4) == 0 do defp do_decode64(string, dec) when rem(byte_size(string), 4) == 0 do
split = byte_size(string) - 4 split = byte_size(string) - 4
<<main::[size(split), binary], rest::binary>> = string <<main::size(split)-binary, rest::binary>> = string
main = for <<c::8 <- main>>, into: <<>>, do: <<dec.(c)::6>> main = for <<c::8 <- main>>, into: <<>>, do: <<dec.(c)::6>>
case rest do case rest do
<<c1::8, c2::8, ?=, ?=>> -> <<c1::8, c2::8, ?=, ?=>> ->
@@ -534,7 +534,7 @@ defmodule Base do
defp do_encode32(<<>>, _), do: <<>> defp do_encode32(<<>>, _), do: <<>>
defp do_encode32(data, enc) do defp do_encode32(data, enc) do
split = 5 * div(byte_size(data), 5) split = 5 * div(byte_size(data), 5)
<<main::[size(split), binary], rest::binary>> = data <<main::size(split)-binary, rest::binary>> = data
main = for <<c::5 <- main>>, into: <<>>, do: <<enc.(c)::8>> main = for <<c::5 <- main>>, into: <<>>, do: <<enc.(c)::8>>
case rest do case rest do
<<c1::5, c2::5, c3::5, c4::5, c5::5, c6::5, c7::2>> -> <<c1::5, c2::5, c3::5, c4::5, c5::5, c6::5, c7::2>> ->
@@ -561,7 +561,7 @@ defmodule Base do
defp do_decode32(<<>>, _), do: <<>> defp do_decode32(<<>>, _), do: <<>>
defp do_decode32(string, dec) when rem(byte_size(string), 8) == 0 do defp do_decode32(string, dec) when rem(byte_size(string), 8) == 0 do
split = byte_size(string) - 8 split = byte_size(string) - 8
<<main::[size(split), binary], rest::binary>> = string <<main::size(split)-binary, rest::binary>> = string
main = for <<c::8 <- main>>, into: <<>>, do: <<dec.(c)::5>> main = for <<c::8 <- main>>, into: <<>>, do: <<dec.(c)::5>>
case rest do case rest do
<<c1::8, c2::8, ?=, ?=, ?=, ?=, ?=, ?=>> -> <<c1::8, c2::8, ?=, ?=, ?=, ?=, ?=, ?=>> ->
+12 -3
View File
@@ -51,6 +51,14 @@ defmodule Code do
:code.del_path(to_char_list(Path.expand path)) :code.del_path(to_char_list(Path.expand path))
end end
@doc false
def readd_paths() do
{pa, pz} = :elixir_code_server.call(:paths)
:code.add_pathsa(pa)
:code.add_pathsz(pz)
:ok
end
@doc """ @doc """
Evaluate the contents given by `string`. Evaluate the contents given by `string`.
@@ -243,7 +251,7 @@ defmodule Code do
Accepts `relative_to` as an argument to tell where the file is located. 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 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. evaluation result and its bindings.
""" """
def eval_file(file, relative_to \\ nil) do def eval_file(file, relative_to \\ nil) do
@@ -308,7 +316,7 @@ defmodule Code do
Check `compiler_options/1` for more information. Check `compiler_options/1` for more information.
""" """
def compiler_options do def compiler_options do
:elixir_code_server.call :compiler_options :elixir_config.get :compiler_options
end end
@doc """ @doc """
@@ -347,7 +355,8 @@ defmodule Code do
bad = bad |> Keyword.keys |> Enum.join(", ") bad = bad |> Keyword.keys |> Enum.join(", ")
raise ArgumentError, message: "unknown compiler options: #{bad}" raise ArgumentError, message: "unknown compiler options: #{bad}"
end end
:elixir_code_server.cast {:compiler_options, opts} update = &:orddict.merge(fn(_, _, value) -> value end, &1, opts)
:elixir_config.update :compiler_options, update
end end
@doc """ @doc """
+5 -45
View File
@@ -8,12 +8,6 @@ defprotocol Collectable do
iex> Enum.into([a: 1, b: 2], %{}) iex> Enum.into([a: 1, b: 2], %{})
%{a: 1, b: 2} %{a: 1, b: 2}
If a collection implements both `Enumerable` and `Collectable`, both
operations can be combined with `Enum.traverse/2`:
iex> Enum.traverse(%{a: 1, b: 2}, fn {k, v} -> {k, v * 2} end)
%{a: 2, b: 4}
## Why Collectable? ## Why Collectable?
The `Enumerable` protocol is useful to take values out of a collection. The `Enumerable` protocol is useful to take values out of a collection.
@@ -27,25 +21,13 @@ defprotocol Collectable do
shape where just the range limits are stored. shape where just the range limits are stored.
The `Collectable` module was designed to fill the gap left by the The `Collectable` module was designed to fill the gap left by the
`Enumerable` protocol. It provides two functions: `into/1` and `empty/1`. `Enumerable` protocol. `into/1` can be seen as the opposite of
`Enumerable.reduce/3`. If `Enumerable` is about taking values out,
`into/1` can be seen as the opposite of `Enumerable.reduce/3`. If `Collectable.into/1` is about collecting those values into a structure.
`Enumerable` is about taking values out, `Collectable.into/1` is about
collecting those values into a structure.
`empty/1` receives a collectable and returns an empty version of the
same collectable. By combining the enumerable functionality with `into/1`
and `empty/1`, one can, for example, implement a traversal mechanism.
""" """
@type command :: {:cont, term} | :done | :halt @type command :: {:cont, term} | :done | :halt
@doc """
Receives a collectable structure and returns an empty one.
"""
@spec empty(t) :: t
def empty(collectable)
@doc """ @doc """
Returns a function that collects values alongside Returns a function that collects values alongside
the initial accumulation value. the initial accumulation value.
@@ -65,10 +47,6 @@ defprotocol Collectable do
end end
defimpl Collectable, for: List do defimpl Collectable, for: List do
def empty(_list) do
[]
end
def into(original) do def into(original) do
{[], fn {[], fn
list, {:cont, x} -> [x|list] list, {:cont, x} -> [x|list]
@@ -79,34 +57,16 @@ defimpl Collectable, for: List do
end end
defimpl Collectable, for: BitString do defimpl Collectable, for: BitString do
def empty(_bitstring) do
""
end
def into(original) do def into(original) do
{original, fn {original, fn
bitstring, {:cont, x} -> <<bitstring :: bits, x :: bits>> acc, {:cont, x} when is_bitstring(x) -> [acc|x]
bitstring, :done -> bitstring acc, :done -> IO.iodata_to_binary(acc)
_, :halt -> :ok _, :halt -> :ok
end} end}
end end
end end
defimpl Collectable, for: Function do
def empty(function) do
function
end
def into(function) do
{function, function}
end
end
defimpl Collectable, for: Map do defimpl Collectable, for: Map do
def empty(_map) do
%{}
end
def into(original) do def into(original) do
{original, fn {original, fn
map, {:cont, {k, v}} -> :maps.put(k, v, map) map, {:cont, {k, v}} -> :maps.put(k, v, map)
+78 -78
View File
@@ -288,8 +288,8 @@ defmodule Dict do
## Examples ## Examples
iex> d = Enum.into([a: 1, b: 2], dict_impl.new) iex> dict = Enum.into([a: 1, b: 2], dict_impl.new)
iex> Enum.sort(Dict.keys(d)) iex> Enum.sort(Dict.keys(dict))
[:a,:b] [:a,:b]
""" """
@@ -304,8 +304,8 @@ defmodule Dict do
## Examples ## Examples
iex> d = Enum.into([a: 1, b: 2], dict_impl.new) iex> dict = Enum.into([a: 1, b: 2], dict_impl.new)
iex> Enum.sort(Dict.values(d)) iex> Enum.sort(Dict.values(dict))
[1,2] [1,2]
""" """
@@ -319,8 +319,8 @@ defmodule Dict do
## Examples ## Examples
iex> d = Enum.into([a: 1, b: 2], dict_impl.new) iex> dict = Enum.into([a: 1, b: 2], dict_impl.new)
iex> Dict.size(d) iex> Dict.size(dict)
2 2
""" """
@@ -334,10 +334,10 @@ defmodule Dict do
## Examples ## Examples
iex> d = Enum.into([a: 1], dict_impl.new) iex> dict = Enum.into([a: 1], dict_impl.new)
iex> Dict.has_key?(d, :a) iex> Dict.has_key?(dict, :a)
true true
iex> Dict.has_key?(d, :b) iex> Dict.has_key?(dict, :b)
false false
""" """
@@ -352,12 +352,12 @@ defmodule Dict do
## Examples ## Examples
iex> d = Enum.into([a: 1], dict_impl.new) iex> dict = Enum.into([a: 1], dict_impl.new)
iex> Dict.get(d, :a) iex> Dict.get(dict, :a)
1 1
iex> Dict.get(d, :b) iex> Dict.get(dict, :b)
nil nil
iex> Dict.get(d, :b, 3) iex> Dict.get(dict, :b, 3)
3 3
""" """
@spec get(t, key, value) :: value @spec get(t, key, value) :: value
@@ -371,10 +371,10 @@ defmodule Dict do
## Examples ## Examples
iex> d = Enum.into([a: 1], dict_impl.new) iex> dict = Enum.into([a: 1], dict_impl.new)
iex> Dict.fetch(d, :a) iex> Dict.fetch(dict, :a)
{:ok, 1} {:ok, 1}
iex> Dict.fetch(d, :b) iex> Dict.fetch(dict, :b)
:error :error
""" """
@@ -389,8 +389,8 @@ defmodule Dict do
## Examples ## Examples
iex> d = Enum.into([a: 1], dict_impl.new) iex> dict = Enum.into([a: 1], dict_impl.new)
iex> Dict.fetch!(d, :a) iex> Dict.fetch!(dict, :a)
1 1
""" """
@@ -405,9 +405,9 @@ defmodule Dict do
## Examples ## Examples
iex> d = Enum.into([a: 1, b: 2], dict_impl.new) iex> dict = Enum.into([a: 1, b: 2], dict_impl.new)
iex> d = Dict.put(d, :a, 3) iex> dict = Dict.put(dict, :a, 3)
iex> Dict.get(d, :a) iex> Dict.get(dict, :a)
3 3
""" """
@@ -421,9 +421,9 @@ defmodule Dict do
## Examples ## Examples
iex> d = Enum.into([a: 1, b: 2], dict_impl.new) iex> dict = Enum.into([a: 1, b: 2], dict_impl.new)
iex> d = Dict.put_new(d, :a, 3) iex> dict = Dict.put_new(dict, :a, 3)
iex> Dict.get(d, :a) iex> Dict.get(dict, :a)
1 1
""" """
@@ -438,13 +438,13 @@ defmodule Dict do
## Examples ## Examples
iex> d = Enum.into([a: 1, b: 2], dict_impl.new) iex> dict = Enum.into([a: 1, b: 2], dict_impl.new)
iex> d = Dict.delete(d, :a) iex> dict = Dict.delete(dict, :a)
iex> Dict.get(d, :a) iex> Dict.get(dict, :a)
nil nil
iex> d = Enum.into([b: 2], dict_impl.new) iex> dict = Enum.into([b: 2], dict_impl.new)
iex> Dict.delete(d, :a) == d iex> Dict.delete(dict, :a) == dict
true true
""" """
@@ -454,10 +454,10 @@ defmodule Dict do
end end
@doc """ @doc """
Merges the dict `b` into dict `a`. Merges the dict `dict2` into dict `dict1`.
If one of the dict `b` entries already exists in the `dict`, If one of the `dict2` entries already exists in `dict1`, the
the functions in entries in `b` have higher precedence unless a functions in entries in `dict2` have higher precedence unless a
function is given to resolve conflicts. function is given to resolve conflicts.
Notice this function is polymorphic as it merges dicts of any Notice this function is polymorphic as it merges dicts of any
@@ -466,18 +466,18 @@ defmodule Dict do
## Examples ## Examples
iex> d1 = Enum.into([a: 1, b: 2], dict_impl.new) iex> dict1 = Enum.into([a: 1, b: 2], dict_impl.new)
iex> d2 = Enum.into([a: 3, d: 4], dict_impl.new) iex> dict2 = Enum.into([a: 3, d: 4], dict_impl.new)
iex> d = Dict.merge(d1, d2) iex> dict = Dict.merge(dict1, dict2)
iex> [a: Dict.get(d, :a), b: Dict.get(d, :b), d: Dict.get(d, :d)] iex> [a: Dict.get(dict, :a), b: Dict.get(dict, :b), d: Dict.get(dict, :d)]
[a: 3, b: 2, d: 4] [a: 3, b: 2, d: 4]
iex> d1 = Enum.into([a: 1, b: 2], dict_impl.new) iex> dict1 = Enum.into([a: 1, b: 2], dict_impl.new)
iex> d2 = Enum.into([a: 3, d: 4], dict_impl.new) iex> dict2 = Enum.into([a: 3, d: 4], dict_impl.new)
iex> d = Dict.merge(d1, d2, fn(_k, v1, v2) -> iex> dict = Dict.merge(dict1, dict2, fn(_k, v1, v2) ->
...> v1 + v2 ...> v1 + v2
...> end) ...> end)
iex> [a: Dict.get(d, :a), b: Dict.get(d, :b), d: Dict.get(d, :d)] iex> [a: Dict.get(dict, :a), b: Dict.get(dict, :b), d: Dict.get(dict, :d)]
[a: 4, b: 2, d: 4] [a: 4, b: 2, d: 4]
""" """
@@ -502,18 +502,18 @@ defmodule Dict do
## Examples ## Examples
iex> dict = Enum.into([a: 1], dict_impl.new) iex> dict = Enum.into([a: 1], dict_impl.new)
iex> {v, d} = Dict.pop dict, :a iex> {v, dict} = Dict.pop dict, :a
iex> {v, Enum.sort(d)} iex> {v, Enum.sort(dict)}
{1,[]} {1,[]}
iex> dict = Enum.into([a: 1], dict_impl.new) iex> dict = Enum.into([a: 1], dict_impl.new)
iex> {v, d} = Dict.pop dict, :b iex> {v, dict} = Dict.pop dict, :b
iex> {v, Enum.sort(d)} iex> {v, Enum.sort(dict)}
{nil,[a: 1]} {nil,[a: 1]}
iex> dict = Enum.into([a: 1], dict_impl.new) iex> dict = Enum.into([a: 1], dict_impl.new)
iex> {v, d} = Dict.pop dict, :b, 3 iex> {v, dict} = Dict.pop dict, :b, 3
iex> {v, Enum.sort(d)} iex> {v, Enum.sort(dict)}
{3,[a: 1]} {3,[a: 1]}
""" """
@@ -528,9 +528,9 @@ defmodule Dict do
## Examples ## Examples
iex> d = Enum.into([a: 1, b: 2], dict_impl.new) iex> dict = Enum.into([a: 1, b: 2], dict_impl.new)
iex> d = Dict.update!(d, :a, fn(val) -> -val end) iex> dict = Dict.update!(dict, :a, fn(val) -> -val end)
iex> Dict.get(d, :a) iex> Dict.get(dict, :a)
-1 -1
""" """
@@ -546,9 +546,9 @@ defmodule Dict do
## Examples ## Examples
iex> d = Enum.into([a: 1, b: 2], dict_impl.new) iex> dict = Enum.into([a: 1, b: 2], dict_impl.new)
iex> d = Dict.update(d, :c, 3, fn(val) -> -val end) iex> dict = Dict.update(dict, :c, 3, fn(val) -> -val end)
iex> Dict.get(d, :c) iex> Dict.get(dict, :c)
3 3
""" """
@@ -566,19 +566,19 @@ defmodule Dict do
## Examples ## Examples
iex> d = Enum.into([a: 1, b: 2, c: 3, d: 4], dict_impl.new) iex> dict = Enum.into([a: 1, b: 2, c: 3, d: 4], dict_impl.new)
iex> {d1, d2} = Dict.split(d, [:a, :c, :e]) iex> {dict1, dict2} = Dict.split(dict, [:a, :c, :e])
iex> {Dict.to_list(d1) |> Enum.sort, Dict.to_list(d2) |> Enum.sort} iex> {Dict.to_list(dict1) |> Enum.sort, Dict.to_list(dict2) |> Enum.sort}
{[a: 1, c: 3], [b: 2, d: 4]} {[a: 1, c: 3], [b: 2, d: 4]}
iex> d = Enum.into([], dict_impl.new) iex> dict = Enum.into([], dict_impl.new)
iex> {d1, d2} = Dict.split(d, [:a, :c]) iex> {dict1, dict2} = Dict.split(dict, [:a, :c])
iex> {Dict.to_list(d1), Dict.to_list(d2)} iex> {Dict.to_list(dict1), Dict.to_list(dict2)}
{[], []} {[], []}
iex> d = Enum.into([a: 1, b: 2], dict_impl.new) iex> dict = Enum.into([a: 1, b: 2], dict_impl.new)
iex> {d1, d2} = Dict.split(d, [:a, :b, :c]) iex> {dict1, dict2} = Dict.split(dict, [:a, :b, :c])
iex> {Dict.to_list(d1) |> Enum.sort, Dict.to_list(d2)} iex> {Dict.to_list(dict1) |> Enum.sort, Dict.to_list(dict2)}
{[a: 1, b: 2], []} {[a: 1, b: 2], []}
""" """
@@ -593,14 +593,14 @@ defmodule Dict do
## Examples ## Examples
iex> d = Enum.into([a: 1, b: 2], dict_impl.new) iex> dict = Enum.into([a: 1, b: 2], dict_impl.new)
iex> d = Dict.drop(d, [:a, :c, :d]) iex> dict = Dict.drop(dict, [:a, :c, :d])
iex> Dict.to_list(d) iex> Dict.to_list(dict)
[b: 2] [b: 2]
iex> d = Enum.into([a: 1, b: 2], dict_impl.new) iex> dict = Enum.into([a: 1, b: 2], dict_impl.new)
iex> d = Dict.drop(d, [:c, :d]) iex> dict = Dict.drop(dict, [:c, :d])
iex> Dict.to_list(d) |> Enum.sort iex> Dict.to_list(dict) |> Enum.sort
[a: 1, b: 2] [a: 1, b: 2]
""" """
@@ -616,12 +616,12 @@ defmodule Dict do
## Examples ## Examples
iex> d = Enum.into([a: 1, b: 2], dict_impl.new) iex> dict = Enum.into([a: 1, b: 2], dict_impl.new)
iex> d = Dict.take(d, [:a, :c, :d]) iex> dict = Dict.take(dict, [:a, :c, :d])
iex> Dict.to_list(d) iex> Dict.to_list(dict)
[a: 1] [a: 1]
iex> d = Dict.take(d, [:c, :d]) iex> dict = Dict.take(dict, [:c, :d])
iex> Dict.to_list(d) iex> Dict.to_list(dict)
[] []
""" """
@@ -645,14 +645,14 @@ defmodule Dict do
## Examples ## Examples
iex> a = Enum.into([a: 2, b: 3, f: 5, c: 123], dict_impl.new) iex> dict1 = Enum.into([a: 2, b: 3, f: 5, c: 123], dict_impl.new)
iex> b = [a: 2, b: 3, f: 5, c: 123] iex> dict2 = [a: 2, b: 3, f: 5, c: 123]
iex> Dict.equal?(a, b) iex> Dict.equal?(dict1, dict2)
true true
iex> a = Enum.into([a: 2, b: 3, f: 5, c: 123], dict_impl.new) iex> dict1 = Enum.into([a: 2, b: 3, f: 5, c: 123], dict_impl.new)
iex> b = [] iex> dict2 = []
iex> Dict.equal?(a, b) iex> Dict.equal?(dict1, dict2)
false false
""" """
+73 -36
View File
@@ -270,8 +270,13 @@ defmodule Enum do
@doc """ @doc """
Finds the element at the given index (zero-based). Finds the element at the given index (zero-based).
Returns `default` if index is out of bounds. Returns `default` if index is out of bounds.
Note this operation takes linear time. In order to access
the element at index `n`, it will need to traverse `n`
previous elements.
## Examples ## Examples
iex> Enum.at([2, 4, 6], 0) iex> Enum.at([2, 4, 6], 0)
@@ -287,7 +292,6 @@ defmodule Enum do
:none :none
""" """
@spec at(t, integer) :: element | nil
@spec at(t, integer, default) :: element | default @spec at(t, integer, default) :: element | default
def at(collection, n, default \\ nil) do def at(collection, n, default \\ nil) do
case fetch(collection, n) do case fetch(collection, n) do
@@ -330,7 +334,6 @@ defmodule Enum do
[[1, 2, 3], [4, 5, 6]] [[1, 2, 3], [4, 5, 6]]
""" """
@spec chunk(t, non_neg_integer, non_neg_integer) :: [list]
@spec chunk(t, non_neg_integer, non_neg_integer, t | nil) :: [list] @spec chunk(t, non_neg_integer, non_neg_integer, t | nil) :: [list]
def chunk(coll, n, step, pad \\ nil) when n > 0 and step > 0 do def chunk(coll, n, step, pad \\ nil) when n > 0 and step > 0 do
limit = :erlang.max(n, step) limit = :erlang.max(n, step)
@@ -338,7 +341,7 @@ defmodule Enum do
{_, {acc, {buffer, i}}} = {_, {acc, {buffer, i}}} =
Enumerable.reduce(coll, {:cont, {[], {[], 0}}}, R.chunk(n, step, limit)) Enumerable.reduce(coll, {:cont, {[], {[], 0}}}, R.chunk(n, step, limit))
if nil?(pad) || i == 0 do if is_nil(pad) || i == 0 do
:lists.reverse(acc) :lists.reverse(acc)
else else
buffer = :lists.reverse(buffer) ++ take(pad, n - i) buffer = :lists.reverse(buffer) ++ take(pad, n - i)
@@ -566,12 +569,17 @@ defmodule Enum do
@doc """ @doc """
Finds the element at the given index (zero-based). Finds the element at the given index (zero-based).
Returns `{:ok, element}` if found, otherwise `:error`. Returns `{:ok, element}` if found, otherwise `:error`.
A negative index can be passed, which means the collection is A negative index can be passed, which means the collection is
enumerated once and the index is counted from the end (i.e. enumerated once and the index is counted from the end (i.e.
`-1` fetches the last element). `-1` fetches the last element).
Note this operation takes linear time. In order to access
the element at index `n`, it will need to traverse `n`
previous elements.
## Examples ## Examples
iex> Enum.fetch([2, 4, 6], 0) iex> Enum.fetch([2, 4, 6], 0)
@@ -585,11 +593,11 @@ defmodule Enum do
""" """
@spec fetch(t, integer) :: {:ok, element} | :error @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) do_fetch(collection, n)
end end
def fetch(collection, n) when n >= 0 do def fetch(collection, n) when is_integer(n) and n >= 0 do
res = res =
Enumerable.reduce(collection, {:cont, 0}, fn(entry, acc) -> Enumerable.reduce(collection, {:cont, 0}, fn(entry, acc) ->
if acc == n do if acc == n do
@@ -605,15 +613,20 @@ defmodule Enum do
end end
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)) do_fetch(reverse(collection), abs(n + 1))
end end
@doc """ @doc """
Finds the element at the given index (zero-based). Finds the element at the given index (zero-based).
Raises `OutOfBoundsError` if the given position Raises `OutOfBoundsError` if the given position
is outside the range of the collection. is outside the range of the collection.
Note this operation takes linear time. In order to access
the element at index `n`, it will need to traverse `n`
previous elements.
## Examples ## Examples
iex> Enum.fetch!([2, 4, 6], 0) iex> Enum.fetch!([2, 4, 6], 0)
@@ -689,7 +702,6 @@ defmodule Enum do
3 3
""" """
@spec find(t, (element -> any)) :: element | nil
@spec find(t, default, (element -> any)) :: element | default @spec find(t, default, (element -> any)) :: element | default
def find(collection, ifnone \\ nil, fun) def find(collection, ifnone \\ nil, fun)
@@ -716,7 +728,6 @@ defmodule Enum do
true true
""" """
@spec find_value(t, (element -> any)) :: any | :nil
@spec find_value(t, any, (element -> any)) :: any | :nil @spec find_value(t, any, (element -> any)) :: any | :nil
def find_value(collection, ifnone \\ nil, fun) def find_value(collection, ifnone \\ nil, fun)
@@ -931,7 +942,6 @@ defmodule Enum do
"1 = 2 = 3" "1 = 2 = 3"
""" """
@spec join(t) :: String.t
@spec join(t, String.t) :: String.t @spec join(t, String.t) :: String.t
def join(collection, joiner \\ "") def join(collection, joiner \\ "")
@@ -990,7 +1000,6 @@ defmodule Enum do
"2 = 4 = 6" "2 = 4 = 6"
""" """
@spec map_join(t, (element -> any)) :: String.t
@spec map_join(t, String.t, (element -> any)) :: String.t @spec map_join(t, String.t, (element -> any)) :: String.t
def map_join(collection, joiner \\ "", mapper) def map_join(collection, joiner \\ "", mapper)
@@ -1252,6 +1261,15 @@ defmodule Enum do
:lists.foldl(fun, acc, collection) :lists.foldl(fun, acc, collection)
end 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 def reduce(collection, acc, fun) do
Enumerable.reduce(collection, {:cont, acc}, Enumerable.reduce(collection, {:cont, acc},
fn x, acc -> {:cont, fun.(x, acc)} end) |> elem(1) fn x, acc -> {:cont, fun.(x, acc)} end) |> elem(1)
@@ -1396,7 +1414,7 @@ defmodule Enum do
result. For example, one could do the following to set a seed result. For example, one could do the following to set a seed
dynamically: dynamically:
:random.seed(:erlang.now) :random.seed(:os.timestamp)
## Examples ## Examples
@@ -1568,6 +1586,46 @@ defmodule Enum do
reduce(collection, [], &sort_reducer(&1, &2, fun)) |> sort_terminator(fun) reduce(collection, [], &sort_reducer(&1, &2, fun)) |> sort_terminator(fun)
end end
@doc """
Sorts the mapped results of the `collection` according to the `sorter` function.
This function maps each element of the collection using the `mapper`
function. The collection is then sorted by the mapped elements using the
`sorter` function, which defaults to `<=/2`
`sort_by/3` differs from `sort/2` in that it only calculates the comparison
value for each element in the collection once instead of once for each
element in each comparison. If the same function is being called on both
element, it's also more compact to use `sort_by/3`.
This technique is also known as a
[Schwartzian Transform](https://en.wikipedia.org/wiki/Schwartzian_transform),
or the Lisp decorate-sort-undecorate idiom as the `mapper` is decorating the
original `collection`, then `sorter` is sorting the decorations, and finally
the `collection` is being undecorated so only the original elements remain,
but now in sorted order.
## Examples
Using the default `sorter` of `<=/2`:
iex> Enum.sort_by ["some", "kind", "of", "monster"], &byte_size/1
["of", "some", "kind", "monster"]
Using a custom `sorter` to override the order:
iex> Enum.sort_by ["some", "kind", "of", "monster"], &byte_size/1, &>=/2
["monster", "some", "kind", "of"]
"""
@spec sort_by(t, (element -> mapped_element), (mapped_element, mapped_element -> boolean)) :: list when mapped_element: element
def sort_by(collection, mapper, sorter \\ &<=/2) do
collection
|> map(&{&1, mapper.(&1)})
|> sort(&sorter.(elem(&1, 1), elem(&2, 1)))
|> map(&elem(&1, 0))
end
@doc """ @doc """
Splits the enumerable into two collections, leaving `count` Splits the enumerable into two collections, leaving `count`
elements in the first one. If `count` is a negative number, elements in the first one. If `count` is a negative number,
@@ -1680,10 +1738,10 @@ defmodule Enum do
def take(collection, count) when count > 0 do def take(collection, count) when count > 0 do
{_, {res, _}} = {_, {res, _}} =
Enumerable.reduce(collection, {:cont, {[], count}}, fn(entry, {list, count}) -> Enumerable.reduce(collection, {:cont, {[], count}}, fn(entry, {list, count}) ->
if count > 1 do case count do
{:cont, {[entry|list], count - 1}} 0 -> {:halt, {list, count}}
else 1 -> {:halt, {[entry|list], count - 1}}
{:halt, {[entry|list], count}} _ -> {:cont, {[entry|list], count - 1}}
end end
end) end)
:lists.reverse(res) :lists.reverse(res)
@@ -1749,27 +1807,6 @@ defmodule Enum do
reverse(collection) |> :lists.reverse reverse(collection) |> :lists.reverse
end end
@doc """
Traverses the given enumerable keeping its shape.
It also expects the enumerable to implement the `Collectable` protocol.
## Examples
iex> Enum.traverse(%{a: 1, b: 2}, fn {k, v} -> {k, v * 2} end)
%{a: 2, b: 4}
"""
@spec traverse(Enumerable.t, (term -> term)) :: Collectable.t
def traverse(collection, transform) when is_list(collection) do
:lists.map(transform, collection)
end
def traverse(collection, transform) do
into(collection, Collectable.empty(collection), transform)
end
@doc """ @doc """
Enumerates the collection, removing all duplicated items. Enumerates the collection, removing all duplicated items.
+21 -10
View File
@@ -5,16 +5,16 @@ defmodule Exception do
Note that stacktraces in Elixir are updated on throw, Note that stacktraces in Elixir are updated on throw,
errors and exits. For example, at any given moment, errors and exits. For example, at any given moment,
`System.stacktrace` will return the stacktrace for the `System.stacktrace` will return the stacktrace for the
last throw/error/exit that ocurred in the current process. last throw/error/exit that occurred in the current process.
Do not rely on the particular format returned by the `format` Do not rely on the particular format returned by the `format`
functions in this module. They may be changed in future releases functions in this module. They may be changed in future releases
in order to better suit Elixir's tool chain. In other words, in order to better suit Elixir's tool chain. In other words,
by using the functions in this module it is guarantee you will by using the functions in this module it is guaranteed you will
format exceptions as in the current Elixir version being used. format exceptions as in the current Elixir version being used.
""" """
@typedoc "The exception type (as generated by defexception)" @typedoc "The exception type"
@type t :: %{__struct__: module, __exception__: true} @type t :: %{__struct__: module, __exception__: true}
@typedoc "The kind handled by formatting functions" @typedoc "The kind handled by formatting functions"
@@ -482,6 +482,7 @@ end
defmodule RuntimeError do defmodule RuntimeError do
defexception message: "runtime error" defexception message: "runtime error"
@spec exception(String.t) :: Exception.t
def exception(msg) when is_binary(msg) do def exception(msg) when is_binary(msg) do
%RuntimeError{message: msg} %RuntimeError{message: msg}
end end
@@ -494,6 +495,7 @@ end
defmodule ArgumentError do defmodule ArgumentError do
defexception message: "argument error" defexception message: "argument error"
@spec exception(String.t) :: Exception.t
def exception(msg) when is_binary(msg) do def exception(msg) when is_binary(msg) do
%ArgumentError{message: msg} %ArgumentError{message: msg}
end end
@@ -611,12 +613,17 @@ defmodule BadArityError do
end end
defmodule UndefinedFunctionError do defmodule UndefinedFunctionError do
defexception [module: nil, function: nil, arity: nil] defexception [module: nil, function: nil, arity: nil, self: false]
def message(exception) do def message(%{function: function, module: module, arity: arity, self: self}) do
if exception.function do if function do
formatted = Exception.format_mfa exception.module, exception.function, exception.arity formatted = Exception.format_mfa module, function, arity
"undefined function: #{formatted}" suffix = if self or is_nil(module) or :code.is_loaded(module) do
""
else
" (module #{inspect module} is not available)"
end
"undefined function: #{formatted}" <> suffix
else else
"undefined function" "undefined function"
end end
@@ -770,8 +777,9 @@ defmodule ErlangError do
end end
def normalize(:undef, stacktrace) do def normalize(:undef, stacktrace) do
{mod, fun, arity} = from_stacktrace(stacktrace || :erlang.get_stacktrace) stacktrace = stacktrace || :erlang.get_stacktrace
%UndefinedFunctionError{module: mod, function: fun, arity: arity} {mod, fun, arity} = from_stacktrace(stacktrace)
%UndefinedFunctionError{module: mod, function: fun, arity: arity, self: from_self(stacktrace)}
end end
def normalize(:function_clause, stacktrace) do def normalize(:function_clause, stacktrace) do
@@ -798,4 +806,7 @@ defmodule ErlangError do
defp from_stacktrace(_) do defp from_stacktrace(_) do
{nil, nil, nil} {nil, nil, nil}
end end
defp from_self([{module, _, _, _}, {module, _, _, _}|_]), do: true
defp from_self(_), do: false
end end
+54 -26
View File
@@ -75,6 +75,12 @@ defmodule File do
@type posix :: :file.posix() @type posix :: :file.posix()
@type io_device :: :file.io_device() @type io_device :: :file.io_device()
@type stat_options :: [time: :local | :universal | :posix] @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 """ @doc """
Returns `true` if the path is a regular file. Returns `true` if the path is a regular file.
@@ -126,12 +132,12 @@ defmodule File do
Typical error reasons are: Typical error reasons are:
* `:eacces` - missing search or write permissions for the parent * `:eacces` - missing search or write permissions for the parent
directories of `path` directories of `path`
* `:eexist` - there is already a file or directory named `path` * `:eexist` - there is already a file or directory named `path`
* `:enoent` - a component of `path` does not exist * `:enoent` - a component of `path` does not exist
* `:enospc` - there is a no space left on the device * `:enospc` - there is a no space left on the device
* `:enotdir` - a component of `path` is not a directory; * `:enotdir` - a component of `path` is not a directory;
on some platforms, `:enoent` is returned instead on some platforms, `:enoent` is returned instead
""" """
@spec mkdir(Path.t) :: :ok | {:error, posix} @spec mkdir(Path.t) :: :ok | {:error, posix}
def mkdir(path) do def mkdir(path) do
@@ -308,11 +314,15 @@ defmodule File do
def touch(path, time \\ :calendar.local_time) do def touch(path, time \\ :calendar.local_time) do
path = IO.chardata_to_string(path) path = IO.chardata_to_string(path)
case F.change_time(path, time) do case F.change_time(path, time) do
{:error, :enoent} -> {:error, :enoent} -> touch_new(path, time)
write(path, "") other -> other
F.change_time(path, time) end
other -> end
other
defp touch_new(path, time) do
case write(path, "", [:append]) do
:ok -> F.change_time(path, time)
{:error, _reason} = error -> error
end end
end end
@@ -548,7 +558,8 @@ defmodule File do
[dest|acc] [dest|acc]
{:error, :eexist} -> {:error, :eexist} ->
if callback.(src, dest) do if callback.(src, dest) do
rm(dest) # If rm/1 fails, copy/2 will fail
_ = rm(dest)
case copy(src, dest) do case copy(src, dest) do
{:ok, _} -> {:ok, _} ->
copy_file_mode!(src, dest) copy_file_mode!(src, dest)
@@ -569,7 +580,8 @@ defmodule File do
[dest|acc] [dest|acc]
{:error, :eexist} -> {:error, :eexist} ->
if callback.(src, dest) do if callback.(src, dest) do
rm(dest) # If rm/1 fails, iF.make_symlink/2 will fail
_ = rm(dest)
case F.make_symlink(link, dest) do case F.make_symlink(link, dest) do
:ok -> [dest|acc] :ok -> [dest|acc]
{:error, reason} -> {:error, reason, src} {:error, reason} -> {:error, reason, src}
@@ -606,7 +618,7 @@ defmodule File do
Check `File.open/2` for other available options. 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 def write(path, content, modes \\ []) do
F.write_file(IO.chardata_to_string(path), content, modes) F.write_file(IO.chardata_to_string(path), content, modes)
end end
@@ -614,7 +626,7 @@ defmodule File do
@doc """ @doc """
Same as `write/3` but raises an exception if it fails, returns `:ok` otherwise. 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 def write!(path, content, modes \\ []) do
path = IO.chardata_to_string(path) path = IO.chardata_to_string(path)
case F.write_file(path, content, modes) do case F.write_file(path, content, modes) do
@@ -665,14 +677,20 @@ defmodule File do
if match? {:win32, _}, :os.type do if match? {:win32, _}, :os.type do
case F.read_file_info(IO.chardata_to_string(path)) do case F.read_file_info(IO.chardata_to_string(path)) do
{:ok, file_info} when elem(file_info, 3) in [:read, :none] -> {:ok, file_info} when elem(file_info, 3) in [:read, :none] ->
File.chmod(path, (elem(file_info, 7) + 0200)) change_mode_windows(path, file_info)
F.delete(path)
_ -> _ ->
nil nil
end end
end end
end end
defp change_mode_windows(path, file_info) do
case File.chmod(path, (elem(file_info, 7) + 0o200)) do
:ok -> F.delete(path)
{:error, _reason} = error -> error
end
end
@doc """ @doc """
Same as `rm/1`, but raises an exception in case of failure. Otherwise `:ok`. Same as `rm/1`, but raises an exception in case of failure. Otherwise `:ok`.
""" """
@@ -890,7 +908,8 @@ defmodule File do
File.close(file) 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 \\ [])
def open(path, modes) when is_list(modes) do def open(path, modes) when is_list(modes) do
@@ -923,7 +942,7 @@ defmodule File do
end) 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 def open(path, modes, function) do
case open(path, modes) do case open(path, modes) do
{:ok, device} -> {:ok, device} ->
@@ -941,7 +960,7 @@ defmodule File do
Returns the `io_device` otherwise. 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 def open!(path, modes \\ []) do
path = IO.chardata_to_string(path) path = IO.chardata_to_string(path)
case open(path, modes) do case open(path, modes) do
@@ -956,7 +975,7 @@ defmodule File do
Returns the function result otherwise. 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 def open!(path, modes, function) do
path = IO.chardata_to_string(path) path = IO.chardata_to_string(path)
case open(path, modes, function) do case open(path, modes, function) do
@@ -977,18 +996,27 @@ defmodule File do
@spec cwd() :: {:ok, binary} | {:error, posix} @spec cwd() :: {:ok, binary} | {:error, posix}
def cwd() do def cwd() do
case F.get_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 {:error, _} = error -> error
end end
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 """ @doc """
The same as `cwd/0`, but raises an exception if it fails. The same as `cwd/0`, but raises an exception if it fails.
""" """
@spec cwd!() :: binary | no_return @spec cwd!() :: binary | no_return
def cwd!() do def cwd!() do
case F.get_cwd do case cwd() do
{:ok, cwd} -> IO.chardata_to_string(cwd) {:ok, cwd} -> cwd
{:error, reason} -> {:error, reason} ->
raise File.Error, reason: reason, action: "get current working directory" raise File.Error, reason: reason, action: "get current working directory"
end end
@@ -1112,7 +1140,7 @@ defmodule File do
Returns `:ok` on success, or `{:error, reason}` Returns `:ok` on success, or `{:error, reason}`
on failure. 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 def chmod(path, mode) do
F.change_mode(IO.chardata_to_string(path), mode) F.change_mode(IO.chardata_to_string(path), mode)
end end
@@ -1120,7 +1148,7 @@ defmodule File do
@doc """ @doc """
Same as `chmod/2`, but raises an exception in case of failure. Otherwise `:ok`. 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 def chmod!(path, mode) do
path = IO.chardata_to_string(path) path = IO.chardata_to_string(path)
case chmod(path, mode) do case chmod(path, mode) do
@@ -1135,7 +1163,7 @@ defmodule File do
for a given `file`. Returns `:ok` on success, or for a given `file`. Returns `:ok` on success, or
`{:error, reason}` on failure. `{: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 def chgrp(path, gid) do
F.change_group(IO.chardata_to_string(path), gid) F.change_group(IO.chardata_to_string(path), gid)
end end
@@ -1143,7 +1171,7 @@ defmodule File do
@doc """ @doc """
Same as `chgrp/2`, but raises an exception in case of failure. Otherwise `:ok`. 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 def chgrp!(path, gid) do
path = IO.chardata_to_string(path) path = IO.chardata_to_string(path)
case chgrp(path, gid) do case chgrp(path, gid) do
@@ -1158,7 +1186,7 @@ defmodule File do
for a given `file`. Returns `:ok` on success, for a given `file`. Returns `:ok` on success,
or `{:error, reason}` on failure. 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 def chown(path, uid) do
F.change_owner(IO.chardata_to_string(path), uid) F.change_owner(IO.chardata_to_string(path), uid)
end end
@@ -1166,7 +1194,7 @@ defmodule File do
@doc """ @doc """
Same as `chown/2`, but raises an exception in case of failure. Otherwise `:ok`. 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 def chown!(path, uid) do
path = IO.chardata_to_string(path) path = IO.chardata_to_string(path)
case chown(path, uid) do case chown(path, uid) do
+1
View File
@@ -57,6 +57,7 @@ defmodule File.Stat do
pairs = :lists.zip(keys, vals) pairs = :lists.zip(keys, vals)
defstruct keys defstruct keys
@type t :: %__MODULE__{}
@doc """ @doc """
Converts a `File.Stat` struct to a `:file_info` record. Converts a `File.Stat` struct to a `:file_info` record.
+8 -2
View File
@@ -13,6 +13,8 @@ defmodule File.Stream do
defstruct path: nil, modes: [], line_or_bytes: :line, raw: true defstruct path: nil, modes: [], line_or_bytes: :line, raw: true
@type t :: %__MODULE__{}
@doc false @doc false
def __build__(path, modes, line_or_bytes) do def __build__(path, modes, line_or_bytes) do
raw = :lists.keyfind(:encoding, 1, modes) == false raw = :lists.keyfind(:encoding, 1, modes) == false
@@ -55,10 +57,14 @@ defmodule File.Stream do
false -> IO.write(device, x) false -> IO.write(device, x)
end end
:ok, :done -> :ok, :done ->
:file.close(device) # If delayed_write option is used and the last write failed will
# MatchError here as {:error, _} is returned.
:ok = :file.close(device)
stream stream
:ok, :halt -> :ok, :halt ->
:file.close(device) # If delayed_write option is used and the last write failed will
# MatchError here as {:error, _} is returned.
:ok = :file.close(device)
end end
end end
end end
+51 -29
View File
@@ -1,3 +1,5 @@
import Kernel, except: [round: 1]
defmodule Float do defmodule Float do
@moduledoc """ @moduledoc """
Functions for working with floating point numbers. Functions for working with floating point numbers.
@@ -99,57 +101,71 @@ defmodule Float do
@doc """ @doc """
Rounds a float to the largest integer less than or equal to `num`. Rounds a float to the largest integer less than or equal to `num`.
Floor also accepts a precision to round a floating point value down
to an arbitrary number of fractional digits (between 0 and 15).
This function always returns floats. One may use `Kernel.trunc/1` to
truncate the result to an integer afterwards.
## Examples ## Examples
iex> Float.floor(34)
34
iex> Float.floor(34.25) iex> Float.floor(34.25)
34 34.0
iex> Float.floor(-56.5) iex> Float.floor(-56.5)
-57 -57.0
iex> Float.floor(34.253, 2)
34.25
""" """
@spec floor(float | integer) :: integer @spec floor(float, 0..15) :: float
def floor(num) when is_integer(num), do: num def floor(number, precision \\ 0) when is_float(number) and precision in 0..15 do
def floor(num) when is_float(num) do power = power_of_10(precision)
truncated = :erlang.trunc(num) number = number * power
case :erlang.abs(num - truncated) do truncated = trunc(number)
x when x > 0 and num < 0 -> truncated - 1 variance = if number - truncated < 0, do: -1.0, else: 0.0
_ -> truncated (truncated + variance) / power
end
end end
@doc """ @doc """
Rounds a float to the largest integer greater than or equal to `num`. Rounds a float to the largest integer greater than or equal to `num`.
Ceil also accepts a precision to round a floating point value down to
an arbitrary number of fractional digits (between 0 and 15).
This function always returns floats. One may use `Kernel.trunc/1` to
truncate the result to an integer afterwards.
## Examples ## Examples
iex> Float.ceil(34)
34
iex> Float.ceil(34.25) iex> Float.ceil(34.25)
35 35.0
iex> Float.ceil(-56.5) iex> Float.ceil(-56.5)
-56 -56.0
iex> Float.ceil(34.253, 2)
34.26
""" """
@spec ceil(float | integer) :: integer @spec ceil(float, 0..15) :: float
def ceil(num) when is_integer(num), do: num def ceil(number, precision \\ 0) when is_float(number) and precision in 0..15 do
def ceil(num) when is_float(num) do power = power_of_10(precision)
truncated = :erlang.trunc(num) number = number * power
case :erlang.abs(num - truncated) do truncated = trunc(number)
x when x > 0 and num > 0 -> truncated + 1 variance = if number - truncated > 0, do: 1.0, else: 0.0
_ -> truncated (truncated + variance) / power
end
end end
@doc """ @doc """
Rounds a floating point value to an arbitrary number of fractional digits Rounds a floating point value to an arbitrary number of fractional digits
(between 0 and 15). (between 0 and 15).
This function only accepts floats and returns floats. Use `Kernel.round/1`
if you want a function that accepts both floats and integers and always
returns an integer.
## Examples ## Examples
iex> Float.round(5.5674, 3) iex> Float.round(5.5674, 3)
@@ -165,9 +181,15 @@ defmodule Float do
-5.568 -5.568
""" """
@spec round(float, integer) :: float @spec round(float, 0..15) :: float
def round(number, precision) when is_float(number) and is_integer(precision) and precision in 0..15 do def round(number, precision \\ 0) when is_float(number) and precision in 0..15 do
Kernel.round(number * :math.pow(10, precision)) / :math.pow(10, precision) power = power_of_10(precision)
Kernel.round(number * power) / power
end
Enum.reduce 0..15, 1, fn x, acc ->
defp power_of_10(unquote(x)), do: unquote(acc)
acc * 10
end end
@doc """ @doc """
File diff suppressed because it is too large Load Diff
+168
View File
@@ -0,0 +1,168 @@
defmodule GenEvent.Stream do
@moduledoc """
Defines a `GenEvent` stream.
This is a struct returned by `stream/2`. The struct is public and
contains the following fields:
* `:manager` - the manager reference given to `GenEvent.stream/2`
* `:timeout` - the timeout in between events, defaults to `:infinity`
"""
defstruct manager: nil, timeout: :infinity
@type t :: %__MODULE__{
manager: GenEvent.manager,
timeout: timeout}
@doc false
def init({_pid, _ref} = state) do
{:ok, state}
end
@doc false
def handle_event(event, _state) do
exit({:bad_event, event})
end
@doc false
def handle_call(msg, _state) do
exit({:bad_call, msg})
end
@doc false
def handle_info(_msg, state) do
{:ok, state}
end
@doc false
def terminate(_reason, _state) do
:ok
end
@doc false
def code_change(_old, state, _extra) do
{:ok, state}
end
end
defimpl Enumerable, for: GenEvent.Stream do
def reduce(stream, acc, fun) do
start_fun = fn() -> start(stream) end
next_fun = &next(stream, &1)
stop_fun = &stop(stream, &1)
Stream.resource(start_fun, next_fun, stop_fun).(acc, wrap_reducer(fun))
end
def count(_stream) do
{:error, __MODULE__}
end
def member?(_stream, _item) do
{:error, __MODULE__}
end
defp wrap_reducer(fun) do
fn
{:ack, manager, ref, event}, acc ->
send manager, {ref, :ok}
fun.(event, acc)
{:async, _manager, _ref, event}, acc ->
fun.(event, acc)
{:sync, manager, ref, event}, acc ->
try do
fun.(event, acc)
after
send manager, {ref, :ok}
end
end
end
defp start(%{manager: manager} = stream) do
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
# Got an async event.
{_from, {^pid, ^ref}, {:notify, event}} ->
{[{:async, pid, ref, event}], acc}
# Got a sync event.
{_from, {^pid, ^ref}, {:sync_notify, event}} ->
{[{:sync, pid, ref, event}], acc}
# 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]}})
end
end
# If we reach this branch, we know the handler was already
# removed, so we don't trigger a request for doing so.
defp stop(stream, {:removed, {pid, ref, mon_ref} = acc}) do
case wait_for_handler_removal(pid, ref, mon_ref) do
:ok ->
flush_events(ref)
{:error, reason} ->
exit({reason, {__MODULE__, :stop, [stream, acc]}})
end
end
# 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
_ = GenEvent.remove_handler(pid, {pid, ref}, :shutdown)
stop(stream, {:removed, acc})
end
defp wait_for_handler_removal(pid, ref, mon_ref) do
receive do
{: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, {^pid, ^ref}, reason} ->
Process.demonitor(mon_ref, [:flush])
{:error, reason}
{:DOWN, ^mon_ref, _, _, reason} ->
{:error, reason}
end
end
defp flush_events(ref) do
receive do
{_from, {_pid, ^ref}, {notify, _event}} when notify in [:notify, :ack_notify, :sync_notify] ->
flush_events(ref)
after
0 -> :ok
end
end
end
+58 -8
View File
@@ -11,7 +11,7 @@ defmodule GenServer do
## Example ## Example
The GenServer behaviour abstracts the common client-server interaction. The GenServer behaviour abstracts the common client-server interaction.
Developer are only required to implement the callbacks and functionality they are Developers are only required to implement the callbacks and functionality they are
interested in. interested in.
Let's start with a code example and then explore the available callbacks. Let's start with a code example and then explore the available callbacks.
@@ -349,18 +349,57 @@ defmodule GenServer do
""" """
@spec call(server, term, timeout) :: term @spec call(server, term, timeout) :: term
def call(server, request, timeout \\ 5000) do def call(server, request, timeout \\ 5000) do
:gen_server.call(server, request, timeout) try do
:gen.call(server, :"$gen_call", request, timeout)
catch
:exit, reason ->
exit({reason, {__MODULE__, :call, [server, request, timeout]}})
else
{:ok, res} -> res
end
end end
@doc """ @doc """
Sends an asynchronous request to the `server`. Sends an asynchronous request to the `server`.
This function returns `:ok` immediately, regardless of whether the This function returns `:ok` immediately, regardless of
destination node or server does exists. `handle_cast/2` will be called on the whether the destination node or server does exists, unless
server to handle the request. the server is specified as an atom.
`handle_cast/2` will be called on the server to handle
the request. In case the server is a node which is not
yet connected to the caller one, the call is going to
block until a connection happens. This is different than
the behaviour in OTP's `:gen_server` where the message
would be sent by another process, which could cause
messages to arrive out of order.
""" """
@spec cast(server, term) :: :ok @spec cast(server, term) :: :ok
defdelegate cast(server, request), to: :gen_server def cast(server, request)
def cast({:global, name}, request) do
try do
:global.send(name, cast_msg(request))
:ok
catch
_, _ -> :ok
end
end
def cast({:via, mod, name}, request) do
try do
mod.send(name, cast_msg(request))
:ok
catch
_, _ -> :ok
end
end
def cast({name, node}, request) when is_atom(name) and is_atom(node),
do: do_send({name, node}, cast_msg(request))
def cast(dest, request) when is_atom(dest) or is_pid(dest),
do: do_send(dest, cast_msg(request))
@doc """ @doc """
Casts all servers locally registered as `name` at the specified nodes. Casts all servers locally registered as `name` at the specified nodes.
@@ -371,8 +410,19 @@ defmodule GenServer do
See `multi_call/4` for more information. See `multi_call/4` for more information.
""" """
@spec abcast([node], name :: atom, term) :: :abcast @spec abcast([node], name :: atom, term) :: :abcast
def abcast(nodes \\ nodes(), name, request) do def abcast(nodes \\ nodes(), name, request) when is_list(nodes) and is_atom(name) do
:gen_server.abcast(nodes, name, request) msg = cast_msg(request)
_ = for node <- nodes, do: do_send({name, node}, msg)
:abcast
end
defp cast_msg(req) do
{:"$gen_cast", req}
end
defp do_send(dest, msg) do
send(dest, msg)
:ok
end end
@doc """ @doc """
+1 -1
View File
@@ -21,7 +21,7 @@ defmodule HashDict do
@node_size 8 @node_size 8
@node_template :erlang.make_tuple(@node_size, []) @node_template :erlang.make_tuple(@node_size, [])
@opaque t :: map @opaque t :: %__MODULE__{size: non_neg_integer, root: term}
@doc false @doc false
defstruct size: 0, root: @node_template defstruct size: 0, root: @node_template
+1 -1
View File
@@ -21,7 +21,7 @@ defmodule HashSet do
@node_size 8 @node_size 8
@node_template :erlang.make_tuple(@node_size, []) @node_template :erlang.make_tuple(@node_size, [])
@opaque t :: map @opaque t :: %__MODULE__{size: non_neg_integer, root: term}
@doc false @doc false
defstruct size: 0, root: @node_template defstruct size: 0, root: @node_template
+39 -39
View File
@@ -212,29 +212,28 @@ defimpl Inspect, for: BitString do
@doc false @doc false
# Also used by Regex # Also used by Regex
def escape_char(char) when char in ?\000..?\377, def escape_char(0) do
do: octify(char) <<?\\, ?0>>
def escape_char(char), do: hexify(char)
defp octify(byte) do
<< hi :: size(2), mi :: size(3), lo :: size(3) >> = << byte >>
<< ?\\, ?0 + hi, ?0 + mi, ?0 + lo >>
end end
defp hexify(char) when char < 0x10000 do def escape_char(char) when char < 0x100 do
<<a::4, b::4>> = <<char::size(8)>>
<<?\\, ?x, to_hex(a), to_hex(b)>>
end
def escape_char(char) when char < 0x10000 do
<<a::4, b::4, c::4, d::4>> = <<char::size(16)>> <<a::4, b::4, c::4, d::4>> = <<char::size(16)>>
<<?\\, ?x, ?{, to_hex(a), to_hex(b), to_hex(c), to_hex(d), ?}>> <<?\\, ?x, ?{, to_hex(a), to_hex(b), to_hex(c), to_hex(d), ?}>>
end end
defp hexify(char) when char < 0x1000000 do def escape_char(char) when char < 0x1000000 do
<<a::4, b::4, c::4, d::4, e::4, f::4>> = <<char::size(24)>> <<a::4, b::4, c::4, d::4, e::4, f::4>> = <<char::size(24)>>
<<?\\, ?x, ?{, to_hex(a), to_hex(b), to_hex(c), <<?\\, ?x, ?{, to_hex(a), to_hex(b), to_hex(c),
to_hex(d), to_hex(e), to_hex(f), ?}>> to_hex(d), to_hex(e), to_hex(f), ?}>>
end end
defp to_hex(c) when c in 0..9, do: ?0+c defp to_hex(c) when c in 0..9, do: ?0+c
defp to_hex(c) when c in 10..15, do: ?a+c-10 defp to_hex(c) when c in 10..15, do: ?A+c-10
defp append(<<h, t :: binary>>, binary), do: append(t, << binary :: binary, h >>) defp append(<<h, t :: binary>>, binary), do: append(t, << binary :: binary, h >>)
defp append(<<>>, binary), do: binary defp append(<<>>, binary), do: binary
@@ -272,25 +271,6 @@ defimpl Inspect, for: BitString do
end end
defimpl Inspect, for: List do defimpl Inspect, for: List do
@doc ~S"""
Represents a list, checking if it can be printed or not.
If so, a single-quoted representation is returned,
otherwise the brackets syntax is used. Keywords are
printed in keywords syntax.
## Examples
iex> inspect('bar')
"'bar'"
iex> inspect([0|'bar'])
"[0, 98, 97, 114]"
iex> inspect([:foo,:bar])
"[:foo, :bar]"
"""
def inspect([], _opts), do: "[]" def inspect([], _opts), do: "[]"
def inspect(thing, %Inspect.Opts{char_lists: lists} = opts) do def inspect(thing, %Inspect.Opts{char_lists: lists} = opts) do
@@ -298,9 +278,9 @@ defimpl Inspect, for: List do
lists == :as_char_lists or (lists == :infer and printable?(thing)) -> lists == :as_char_lists or (lists == :infer and printable?(thing)) ->
<< ?', Inspect.BitString.escape(IO.chardata_to_string(thing), ?') :: binary, ?' >> << ?', Inspect.BitString.escape(IO.chardata_to_string(thing), ?') :: binary, ?' >>
keyword?(thing) -> keyword?(thing) ->
surround_many("[", thing, "]", opts.limit, &keyword(&1, opts)) surround_many("[", thing, "]", opts, &keyword/2)
true -> true ->
surround_many("[", thing, "]", opts.limit, &to_doc(&1, opts)) surround_many("[", thing, "]", opts, &to_doc/2)
end end
end end
@@ -347,7 +327,7 @@ defimpl Inspect, for: Tuple do
def inspect({}, _opts), do: "{}" def inspect({}, _opts), do: "{}"
def inspect(tuple, opts) do def inspect(tuple, opts) do
surround_many("{", Tuple.to_list(tuple), "}", opts.limit, &to_doc(&1, opts)) surround_many("{", Tuple.to_list(tuple), "}", opts, &to_doc/2)
end end
end end
@@ -358,14 +338,14 @@ defimpl Inspect, for: Map do
def inspect(map, name, opts) do def inspect(map, name, opts) do
map = :maps.to_list(map) map = :maps.to_list(map)
surround_many("%" <> name <> "{", map, "}", opts.limit, traverse_fun(map, opts)) surround_many("%" <> name <> "{", map, "}", opts, traverse_fun(map))
end end
defp traverse_fun(list, opts) do defp traverse_fun(list) do
if Inspect.List.keyword?(list) do if Inspect.List.keyword?(list) do
&Inspect.List.keyword(&1, opts) &Inspect.List.keyword/2
else else
&to_map(&1, opts) &to_map/2
end end
end end
@@ -378,8 +358,28 @@ defimpl Inspect, for: Map do
end end
defimpl Inspect, for: Integer do defimpl Inspect, for: Integer do
def inspect(thing, _opts) do def inspect(thing, %Inspect.Opts{base: base}) do
Integer.to_string(thing) Integer.to_string(thing, base_to_value(base))
|> prepend_prefix(base)
end
defp base_to_value(base) do
case base do
:binary -> 2
:decimal -> 10
:octal -> 8
:hex -> 16
end
end
defp prepend_prefix(value, :decimal), do: value
defp prepend_prefix(value, base) do
prefix = case base do
:binary -> "0b"
:octal -> "0o"
:hex -> "0x"
end
prefix <> value
end end
end end
+128 -97
View File
@@ -29,15 +29,30 @@ defmodule Inspect.Opts do
* `:pretty` - if set to true enables pretty printing, defaults to false. * `:pretty` - if set to true enables pretty printing, defaults to false.
* `:width` - defaults to the 80 characters. * `:width` - defaults to the 80 characters, used when pretty is true or
when printing to IO devices.
* `:base` - print integers as :binary, :octal, :decimal, or :hex, defaults
to :decimal
""" """
defstruct structs: true :: boolean, defstruct structs: true,
binaries: :infer :: :infer | :as_binaries | :as_strings, binaries: :infer,
char_lists: :infer :: :infer | :as_lists | :as_char_lists, char_lists: :infer,
limit: 50 :: pos_integer, limit: 50,
width: 80 :: pos_integer | :infinity, width: 80,
pretty: false :: boolean base: :decimal,
pretty: false
@type t :: %__MODULE__{
structs: boolean,
binaries: :infer | :as_binaries | :as_strings,
char_lists: :infer | :as_lists | :as_char_lists,
limit: pos_integer,
width: pos_integer | :infinity,
base: :decimal | :binary | :hex | :octal,
pretty: boolean}
end end
defmodule Inspect.Algebra do defmodule Inspect.Algebra do
@@ -58,8 +73,8 @@ defmodule Inspect.Algebra do
elements together and render them: elements together and render them:
iex> doc = Inspect.Algebra.concat(Inspect.Algebra.empty, "foo") iex> doc = Inspect.Algebra.concat(Inspect.Algebra.empty, "foo")
iex> Inspect.Algebra.pretty(doc, 80) iex> Inspect.Algebra.format(doc, 80)
"foo" ["foo"]
The functions `nest/2`, `space/2` and `line/2` help you put the The functions `nest/2`, `space/2` and `line/2` help you put the
document together into a rigid structure. However, the document document together into a rigid structure. However, the document
@@ -69,15 +84,15 @@ defmodule Inspect.Algebra do
render it: render it:
iex> doc = Inspect.Algebra.glue("a", " ", "b") iex> doc = Inspect.Algebra.glue("a", " ", "b")
iex> Inspect.Algebra.pretty(doc, 80) iex> Inspect.Algebra.format(doc, 80)
"a b" ["a", " ", "b"]
Notice the break was represented as is, because we haven't reached Notice the break was represented as is, because we haven't reached
a line limit. Once we do, it is replaced by a newline: a line limit. Once we do, it is replaced by a newline:
iex> doc = Inspect.Algebra.glue(String.duplicate("a", 20), " ", "b") iex> doc = Inspect.Algebra.glue(String.duplicate("a", 20), " ", "b")
iex> Inspect.Algebra.pretty(doc, 10) iex> Inspect.Algebra.format(doc, 10)
"aaaaaaaaaaaaaaaaaaaa\nb" ["aaaaaaaaaaaaaaaaaaaa", "\n", "b"]
Finally, this module also contains Elixir related functions, a bit Finally, this module also contains Elixir related functions, a bit
tied to Elixir formatting, namely `surround/3` and `surround_many/5`. tied to Elixir formatting, namely `surround/3` and `surround_many/5`.
@@ -170,10 +185,29 @@ defmodule Inspect.Algebra do
Inspect.inspect(map, opts) Inspect.inspect(map, opts)
rescue rescue
e -> e ->
res = Inspect.Map.inspect(map, opts) stacktrace = System.stacktrace
raise ArgumentError,
"Got #{inspect e.__struct__} with message " <> # Because we try to raise a nice error message in case
"\"#{Exception.message(e)}\" while inspecting #{pretty(res, opts.width)}" # we can't inspect a struct, there is a chance the error
# message itself relies on the struct being printed, so
# we need to trap the inspected messages to guarantee
# we won't try to render any failed instruct when building
# the error message.
if Process.get(:inspect_trap) do
Inspect.Map.inspect(map, opts)
else
try do
Process.put(:inspect_trap, true)
res = Inspect.Map.inspect(map, opts)
formatted = IO.iodata_to_binary(format(res, :infinity))
reraise ArgumentError,
"Got #{inspect e.__struct__} with message " <>
"\"#{Exception.message(e)}\" while inspecting #{formatted}",
stacktrace
after
Process.delete(:inspect_trap)
end
end
end end
else else
Inspect.Map.inspect(map, opts) Inspect.Map.inspect(map, opts)
@@ -201,9 +235,9 @@ defmodule Inspect.Algebra do
## Examples ## Examples
iex> doc = Inspect.Algebra.concat "Tasteless", "Artosis" iex> doc = Inspect.Algebra.concat "hello", "world"
iex> Inspect.Algebra.pretty(doc, 80) iex> Inspect.Algebra.format(doc, 80)
"TastelessArtosis" ["hello", "world"]
""" """
@spec concat(t, t) :: doc_cons @spec concat(t, t) :: doc_cons
@@ -227,8 +261,8 @@ defmodule Inspect.Algebra do
## Examples ## Examples
iex> doc = Inspect.Algebra.nest(Inspect.Algebra.glue("hello", "world"), 5) iex> doc = Inspect.Algebra.nest(Inspect.Algebra.glue("hello", "world"), 5)
iex> Inspect.Algebra.pretty(doc, 5) iex> Inspect.Algebra.format(doc, 5)
"hello\n world" ["hello", "\n ", "world"]
""" """
@spec nest(t, non_neg_integer) :: doc_nest @spec nest(t, non_neg_integer) :: doc_nest
@@ -252,15 +286,15 @@ defmodule Inspect.Algebra do
Let's glue two docs together with a break and then render it: Let's glue two docs together with a break and then render it:
iex> doc = Inspect.Algebra.glue("a", " ", "b") iex> doc = Inspect.Algebra.glue("a", " ", "b")
iex> Inspect.Algebra.pretty(doc, 80) iex> Inspect.Algebra.format(doc, 80)
"a b" ["a", " ", "b"]
Notice the break was represented as is, because we haven't reached Notice the break was represented as is, because we haven't reached
a line limit. Once we do, it is replaced by a newline: a line limit. Once we do, it is replaced by a newline:
iex> doc = Inspect.Algebra.glue(String.duplicate("a", 20), " ", "b") iex> doc = Inspect.Algebra.glue(String.duplicate("a", 20), " ", "b")
iex> Inspect.Algebra.pretty(doc, 10) iex> Inspect.Algebra.format(doc, 10)
"aaaaaaaaaaaaaaaaaaaa\nb" ["aaaaaaaaaaaaaaaaaaaa", "\n", "b"]
""" """
@spec break(binary) :: doc_break @spec break(binary) :: doc_break
@@ -303,10 +337,10 @@ defmodule Inspect.Algebra do
...> "B" ...> "B"
...> ) ...> )
...> )) ...> ))
iex> Inspect.Algebra.pretty(doc, 80) iex> Inspect.Algebra.format(doc, 80)
"Hello, A B" ["Hello,", " ", "A", " ", "B"]
iex> Inspect.Algebra.pretty(doc, 6) iex> Inspect.Algebra.format(doc, 6)
"Hello,\nA B" ["Hello,", "\n", "A", " ", "B"]
""" """
@spec group(t) :: doc_group @spec group(t) :: doc_group
@@ -320,8 +354,8 @@ defmodule Inspect.Algebra do
## Examples ## Examples
iex> doc = Inspect.Algebra.space "Hughes", "Wadler" iex> doc = Inspect.Algebra.space "Hughes", "Wadler"
iex> Inspect.Algebra.pretty(doc, 80) iex> Inspect.Algebra.format(doc, 80)
"Hughes Wadler" ["Hughes", " ", "Wadler"]
""" """
@spec space(t, t) :: doc_cons @spec space(t, t) :: doc_cons
@@ -333,8 +367,8 @@ defmodule Inspect.Algebra do
## Examples ## Examples
iex> doc = Inspect.Algebra.line "Hughes", "Wadler" iex> doc = Inspect.Algebra.line "Hughes", "Wadler"
iex> Inspect.Algebra.pretty(doc, 80) iex> Inspect.Algebra.format(doc, 80)
"Hughes\nWadler" ["Hughes", "\n", "Wadler"]
""" """
@spec line(t, t) :: doc_cons @spec line(t, t) :: doc_cons
@@ -350,8 +384,8 @@ defmodule Inspect.Algebra do
iex> doc = Inspect.Algebra.folddoc(doc, fn(x,y) -> iex> doc = Inspect.Algebra.folddoc(doc, fn(x,y) ->
...> Inspect.Algebra.concat [x, "!", y] ...> Inspect.Algebra.concat [x, "!", y]
...> end) ...> end)
iex> Inspect.Algebra.pretty(doc, 80) iex> Inspect.Algebra.format(doc, 80)
"A!B" ["A", "!", "B"]
""" """
@spec folddoc([t], ((t, t) -> t)) :: t @spec folddoc([t], ((t, t) -> t)) :: t
@@ -371,8 +405,8 @@ defmodule Inspect.Algebra do
## Examples ## Examples
iex> doc = Inspect.Algebra.surround "[", Inspect.Algebra.glue("a", "b"), "]" iex> doc = Inspect.Algebra.surround "[", Inspect.Algebra.glue("a", "b"), "]"
iex> Inspect.Algebra.pretty(doc, 3) iex> Inspect.Algebra.format(doc, 3)
"[a\n b]" ["[", "a", "\n ", "b", "]"]
""" """
@spec surround(binary, t, binary) :: t @spec surround(binary, t, binary) :: t
@@ -381,55 +415,63 @@ defmodule Inspect.Algebra do
end end
@doc ~S""" @doc ~S"""
Maps and glues a collection of items together using the given separator Maps and glues a collection of items.
and surrounds them. A limit can be passed which, once reached, stops
gluing and outputs "..." instead. It uses the given left and right as surrounding and a separator for
each item. A limit can be passed which, once reached, stops gluing
and outputs "..." instead.
## Examples ## Examples
iex> doc = Inspect.Algebra.surround_many("[", Enum.to_list(1..5), "]", :infinity, &Integer.to_string(&1)) iex> doc = Inspect.Algebra.surround_many("[", Enum.to_list(1..5), "]",
iex> Inspect.Algebra.pretty(doc, 5) ...> %Inspect.Opts{limit: :infinity}, fn i, _opts -> to_string(i) end)
iex> Inspect.Algebra.format(doc, 5) |> IO.iodata_to_binary
"[1,\n 2,\n 3,\n 4,\n 5]" "[1,\n 2,\n 3,\n 4,\n 5]"
iex> doc = Inspect.Algebra.surround_many("[", Enum.to_list(1..5), "]", 3, &Integer.to_string(&1)) iex> doc = Inspect.Algebra.surround_many("[", Enum.to_list(1..5), "]",
iex> Inspect.Algebra.pretty(doc, 20) ...> %Inspect.Opts{limit: 3}, fn i, _opts -> to_string(i) end)
iex> Inspect.Algebra.format(doc, 20) |> IO.iodata_to_binary
"[1, 2, 3, ...]" "[1, 2, 3, ...]"
iex> doc = Inspect.Algebra.surround_many("[", Enum.to_list(1..5), "]", 3, &Integer.to_string(&1), "!") iex> doc = Inspect.Algebra.surround_many("[", Enum.to_list(1..5), "]",
iex> Inspect.Algebra.pretty(doc, 20) ...> %Inspect.Opts{limit: 3}, fn i, _opts -> to_string(i) end, "!")
iex> Inspect.Algebra.format(doc, 20) |> IO.iodata_to_binary
"[1! 2! 3! ...]" "[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, limit, fun, separator \\ @surround_separator) def surround_many(left, docs, right, opts, fun, separator \\ @surround_separator) do
do_surround_many(left, docs, right, opts.limit, opts, fun, separator)
end
def surround_many(left, [], right, _, _fun, _) do defp do_surround_many(left, [], right, _, _opts, _fun, _) do
concat(left, right) concat(left, right)
end end
def surround_many(left, docs, right, limit, fun, sep) do defp do_surround_many(left, docs, right, limit, _opts, fun, sep) do
surround(left, surround_many(docs, limit, fun, sep), right) surround(left, do_surround_many(docs, limit, _opts, fun, sep), right)
end end
defp surround_many(_, 0, _fun, _sep) do defp do_surround_many(_, 0, _opts, _fun, _sep) do
"..." "..."
end end
defp surround_many([h], _limit, fun, _sep) do defp do_surround_many([h], limit, opts, fun, _sep) do
fun.(h) fun.(h, %{opts | limit: limit})
end end
defp surround_many([h|t], limit, fun, sep) when is_list(t) do defp do_surround_many([h|t], limit, opts, fun, sep) when is_list(t) do
limit = decrement(limit)
glue( glue(
concat(fun.(h), sep), concat(fun.(h, %{opts | limit: limit}), sep),
surround_many(t, decrement(limit), fun, sep) do_surround_many(t, limit, opts, fun, sep)
) )
end end
defp surround_many([h|t], _limit, fun, _sep) do defp do_surround_many([h|t], limit, opts, fun, _sep) do
limit = decrement(limit)
glue( glue(
concat(fun.(h), @tail_separator), concat(fun.(h, %{opts | limit: limit}), @tail_separator),
fun.(t) fun.(t, %{opts | limit: limit})
) )
end end
@@ -437,50 +479,45 @@ defmodule Inspect.Algebra do
defp decrement(counter), do: counter - 1 defp decrement(counter), do: counter - 1
@doc """ @doc """
The pretty printing function. The formatting function.
Takes the maximum width and a document to print as its arguments Takes the maximum width and a document to print as its arguments
and returns the string representation of the best layout for the and returns an IO data representation of the best layout for the
document to fit in the given width. document to fit in the given width.
""" """
@spec pretty(t, non_neg_integer | :infinity) :: binary @spec format(t, non_neg_integer | :infinity) :: iodata
def pretty(d, w) do def format(d, w) do
sdoc = format w, 0, [{0, default_mode(w), doc_group(d)}] format(w, 0, [{0, default_mode(w), doc_group(d)}])
render(sdoc)
end end
defp default_mode(:infinity), do: :flat defp default_mode(:infinity), do: :flat
defp default_mode(_), do: :break defp default_mode(_), do: :break
# Rendering and internal helpers
# Record representing the document mode to be rendered: flat or broken # Record representing the document mode to be rendered: flat or broken
@typep mode :: :flat | :break @typep mode :: :flat | :break
@doc false
@spec fits?(integer, [{integer, mode, t}]) :: boolean @spec fits?(integer, [{integer, mode, t}]) :: boolean
def fits?(w, _) when w < 0, do: false defp fits?(w, _) when w < 0, do: false
def fits?(_, []), do: true defp fits?(_, []), do: true
def fits?(_, [{_, _, :doc_line} | _]), do: true defp fits?(_, [{_, _, :doc_line} | _]), do: true
def fits?(w, [{_, _, :doc_nil} | t]), do: fits?(w, t) defp fits?(w, [{_, _, :doc_nil} | t]), do: fits?(w, t)
def fits?(w, [{i, m, doc_cons(x, y)} | t]), do: fits?(w, [{i, m, x} | [{i, m, y} | t]]) defp fits?(w, [{i, m, doc_cons(x, y)} | t]), do: fits?(w, [{i, m, x} | [{i, m, y} | t]])
def fits?(w, [{i, m, doc_nest(x, j)} | t]), do: fits?(w, [{i + j, m, x} | t]) defp fits?(w, [{i, m, doc_nest(x, j)} | t]), do: fits?(w, [{i + j, m, x} | t])
def fits?(w, [{i, _, doc_group(x)} | t]), do: fits?(w, [{i, :flat, x} | t]) defp fits?(w, [{i, _, doc_group(x)} | t]), do: fits?(w, [{i, :flat, x} | t])
def fits?(w, [{_, _, s} | t]) when is_binary(s), do: fits?((w - byte_size s), t) defp fits?(w, [{_, _, s} | t]) when is_binary(s), do: fits?((w - byte_size s), t)
def fits?(w, [{_, :flat, doc_break(s)} | t]), do: fits?((w - byte_size s), t) defp fits?(w, [{_, :flat, doc_break(s)} | t]), do: fits?((w - byte_size s), t)
def fits?(_, [{_, :break, doc_break(_)} | _]), do: true defp fits?(_, [{_, :break, doc_break(_)} | _]), do: true
@doc false
@spec format(integer | :infinity, integer, [{integer, mode, t}]) :: [binary] @spec format(integer | :infinity, integer, [{integer, mode, t}]) :: [binary]
def format(_, _, []), do: [] defp format(_, _, []), do: []
def format(w, _, [{i, _, :doc_line} | t]), do: [indent(i) | format(w, i, t)] defp format(w, _, [{i, _, :doc_line} | t]), do: [indent(i) | format(w, i, t)]
def format(w, k, [{_, _, :doc_nil} | t]), do: format(w, k, t) defp format(w, k, [{_, _, :doc_nil} | t]), do: format(w, k, t)
def format(w, k, [{i, m, doc_cons(x, y)} | t]), do: format(w, k, [{i, m, x} | [{i, m, y} | t]]) defp format(w, k, [{i, m, doc_cons(x, y)} | t]), do: format(w, k, [{i, m, x} | [{i, m, y} | t]])
def format(w, k, [{i, m, doc_nest(x, j)} | t]), do: format(w, k, [{i + j, m, x} | t]) defp format(w, k, [{i, m, doc_nest(x, j)} | t]), do: format(w, k, [{i + j, m, x} | t])
def format(w, k, [{i, m, doc_group(x)} | t]), do: format(w, k, [{i, m, x} | t]) defp format(w, k, [{i, m, doc_group(x)} | t]), do: format(w, k, [{i, m, x} | t])
def format(w, k, [{_, _, s} | t]) when is_binary(s), do: [s | format(w, (k + byte_size s), t)] defp format(w, k, [{_, _, s} | t]) when is_binary(s), do: [s | format(w, (k + byte_size s), t)]
def format(w, k, [{_, :flat, doc_break(s)} | t]), do: [s | format(w, (k + byte_size s), t)] defp format(w, k, [{_, :flat, doc_break(s)} | t]), do: [s | format(w, (k + byte_size s), t)]
def format(w, k, [{i, :break, doc_break(s)} | t]) do defp format(w, k, [{i, :break, doc_break(s)} | t]) do
k = k + byte_size(s) k = k + byte_size(s)
if w == :infinity or fits?(w - k, t) do if w == :infinity or fits?(w - k, t) do
@@ -492,10 +529,4 @@ defmodule Inspect.Algebra do
defp indent(0), do: @newline defp indent(0), do: @newline
defp indent(i), do: @newline <> :binary.copy(" ", i) defp indent(i), do: @newline <> :binary.copy(" ", i)
@doc false
@spec render([binary]) :: binary
def render(sdoc) do
IO.iodata_to_binary sdoc
end
end end
+9 -7
View File
@@ -9,9 +9,10 @@ defmodule Integer do
Determines if an integer is odd. Determines if an integer is odd.
Returns `true` if `n` is an odd number, otherwise `false`. Returns `true` if `n` is an odd number, otherwise `false`.
Implemented as a macro so it is allowed in guard clauses.
Allowed in guard clauses.
""" """
defmacro odd?(n) do defmacro is_odd(n) do
quote do: (unquote(n) &&& 1) == 1 quote do: (unquote(n) &&& 1) == 1
end end
@@ -19,9 +20,10 @@ defmodule Integer do
Determines if an integer is even. Determines if an integer is even.
Returns `true` if `n` is an even number, otherwise `false`. Returns `true` if `n` is an even number, otherwise `false`.
Implemented as a macro so it is allowed in guard clauses.
Allowed in guard clauses.
""" """
defmacro even?(n) do defmacro is_even(n) do
quote do: (unquote(n) &&& 1) == 0 quote do: (unquote(n) &&& 1) == 0
end end
@@ -99,7 +101,7 @@ defmodule Integer do
"64" "64"
""" """
@spec to_string(integer, pos_integer) :: String.t @spec to_string(integer, 2..36) :: String.t
def to_string(some_integer, base) do def to_string(some_integer, base) do
:erlang.integer_to_binary(some_integer, base) :erlang.integer_to_binary(some_integer, base)
end end
@@ -115,7 +117,7 @@ defmodule Integer do
'7' '7'
""" """
@spec to_char_list(integer) :: list @spec to_char_list(integer) :: char_list
def to_char_list(number) do def to_char_list(number) do
:erlang.integer_to_list(number) :erlang.integer_to_list(number)
end end
@@ -132,7 +134,7 @@ defmodule Integer do
'3FF' '3FF'
""" """
@spec to_char_list(integer, pos_integer) :: list @spec to_char_list(integer, 2..36) :: char_list
def to_char_list(number, base) do def to_char_list(number, base) do
:erlang.integer_to_list(number, base) :erlang.integer_to_list(number, base)
end end
+60 -20
View File
@@ -43,8 +43,10 @@ defmodule IO do
end end
@doc """ @doc """
Reads `count` characters from the IO device or until Reads `count` characters from the IO device, a whole
the end of the line if `:line` is given. It returns: `:line` or the whole device with `:all`.
It returns:
* `data` - the input characters * `data` - the input characters
@@ -53,10 +55,17 @@ defmodule IO do
* `{:error, reason}` - other (rare) error condition; * `{:error, reason}` - other (rare) error condition;
for instance, `{:error, :estale}` if reading from an for instance, `{:error, :estale}` if reading from an
NFS volume NFS volume
If `:all` is given, `:eof` is never returned, but an
empty string in case the device has reached EOF.
""" """
@spec read(device, :line | non_neg_integer) :: chardata | nodata @spec read(device, :all | :line | non_neg_integer) :: chardata | nodata
def read(device \\ group_leader, chars_or_line) def read(device \\ group_leader, chars_or_line)
def read(device, :all) do
do_read_all(map_dev(device), "")
end
def read(device, :line) do def read(device, :line) do
:io.get_line(map_dev(device), '') :io.get_line(map_dev(device), '')
end end
@@ -65,9 +74,19 @@ defmodule IO do
:io.get_chars(map_dev(device), '', count) :io.get_chars(map_dev(device), '', count)
end end
defp do_read_all(mapped_dev, acc) do
case :io.get_line(mapped_dev, "") do
line when is_binary(line) -> do_read_all(mapped_dev, acc <> line)
:eof -> acc
other -> other
end
end
@doc """ @doc """
Reads `count` bytes from the IO device or until Reads `count` characters from the IO device, a whole
the end of the line if `:line` is given. It returns: `:line` or the whole device with `:all`.
It returns:
* `data` - the input characters * `data` - the input characters
@@ -76,10 +95,20 @@ defmodule IO do
* `{:error, reason}` - other (rare) error condition; * `{:error, reason}` - other (rare) error condition;
for instance, `{:error, :estale}` if reading from an for instance, `{:error, :estale}` if reading from an
NFS volume NFS volume
If `:all` is given, `:eof` is never returned, but an
empty string in case the device has reached EOF.
Note: do not use this function on IO devices in unicode mode
as it will return the wrong result.
""" """
@spec binread(device, :line | non_neg_integer) :: iodata | nodata @spec binread(device, :all | :line | non_neg_integer) :: iodata | nodata
def binread(device \\ group_leader, chars_or_line) def binread(device \\ group_leader, chars_or_line)
def binread(device, :all) do
do_binread_all(map_dev(device), "")
end
def binread(device, :line) do def binread(device, :line) do
case :file.read_line(map_dev(device)) do case :file.read_line(map_dev(device)) do
{:ok, data} -> data {:ok, data} -> data
@@ -94,6 +123,14 @@ defmodule IO do
end end
end end
defp do_binread_all(mapped_dev, acc) do
case :file.read_line(mapped_dev) do
{:ok, data} -> do_binread_all(mapped_dev, acc <> data)
:eof -> acc
other -> other
end
end
@doc """ @doc """
Writes the given argument to the given device. Writes the given argument to the given device.
@@ -119,6 +156,9 @@ defmodule IO do
as a binary, no unicode conversion happens. as a binary, no unicode conversion happens.
Check `write/2` for more information. Check `write/2` for more information.
Note: do not use this function on IO devices in unicode mode
as it will return the wrong result.
""" """
@spec binwrite(device, iodata) :: :ok | {:error, term} @spec binwrite(device, iodata) :: :ok | {:error, term}
def binwrite(device \\ group_leader(), item) when is_iodata(item) do def binwrite(device \\ group_leader(), item) when is_iodata(item) do
@@ -139,17 +179,13 @@ defmodule IO do
@doc """ @doc """
Inspects and writes the given argument to the device. Inspects and writes the given argument to the device.
It sets by default pretty printing to true and returns It enables pretty printing by default with width of
the item itself. 80 characters. The width can be changed by explicitly
passing the `:width` option.
Note this function does not use the IO device width
because some IO devices does not implement the
appropriate functions. Setting the width must be done
explicitly by passing the `:width` option.
## Examples ## Examples
IO.inspect Process.list IO.inspect Process.list, width: 40
""" """
@spec inspect(term, Keyword.t) :: term @spec inspect(term, Keyword.t) :: term
@@ -162,8 +198,9 @@ defmodule IO do
""" """
@spec inspect(device, term, Keyword.t) :: term @spec inspect(device, term, Keyword.t) :: term
def inspect(device, item, opts) when is_list(opts) do def inspect(device, item, opts) when is_list(opts) do
opts = Keyword.put_new(opts, :pretty, true) opts = struct(Inspect.Opts, opts)
puts device, Kernel.inspect(item, opts) iodata = Inspect.Algebra.format(Inspect.Algebra.to_doc(item, opts), opts.width)
puts device, iodata
item item
end end
@@ -263,6 +300,9 @@ defmodule IO do
Note that an IO stream has side effects and every time Note that an IO stream has side effects and every time
you go over the stream you may get different results. you go over the stream you may get different results.
Finally, do not use this function on IO devices in unicode
mode as it will return the wrong result.
""" """
@spec binstream(device, :line | pos_integer) :: Enumerable.t @spec binstream(device, :line | pos_integer) :: Enumerable.t
def binstream(device, line_or_bytes) do def binstream(device, line_or_bytes) do
@@ -354,11 +394,11 @@ defmodule IO do
def each_stream(device, what) do def each_stream(device, what) do
case read(device, what) do case read(device, what) do
:eof -> :eof ->
nil {:halt, device}
{:error, reason} -> {:error, reason} ->
raise IO.StreamError, reason: reason raise IO.StreamError, reason: reason
data -> data ->
{data, device} {[data], device}
end end
end end
@@ -366,11 +406,11 @@ defmodule IO do
def each_binstream(device, what) do def each_binstream(device, what) do
case binread(device, what) do case binread(device, what) do
:eof -> :eof ->
nil {:halt, device}
{:error, reason} -> {:error, reason} ->
raise IO.StreamError, reason: reason raise IO.StreamError, reason: reason
data -> data ->
{data, device} {[data], device}
end end
end end
+49 -68
View File
@@ -7,7 +7,7 @@ defmodule IO.ANSI.Sequence do
"\e[#{unquote(code)}#{unquote(terminator)}" "\e[#{unquote(code)}#{unquote(terminator)}"
end end
defp escape_sequence(unquote(Atom.to_char_list(name))) do defp format_sequence(unquote(name)) do
unquote(name)() unquote(name)()
end end
end end
@@ -24,19 +24,21 @@ defmodule IO.ANSI do
import IO.ANSI.Sequence import IO.ANSI.Sequence
@doc """ @typep ansicode :: atom()
Checks whether the default I/O device is a terminal or a file. @typep ansilist :: maybe_improper_list(char() | ansicode() | binary() | ansilist(), binary() | ansicode() | [])
@type ansidata :: ansilist() | ansicode() | binary()
Used to identify whether printing ANSI escape sequences will likely @doc """
be displayed as intended. This is checked by sending a message to Checks if ANSI coloring is supported and enabled on this machine.
the group leader. In case the group leader does not support the message,
it will likely lead to a timeout (and a slow down on execution time). This function simply reads the configuration value for
`:ansi_enabled` in the `:elixir` application. The value is by
default false unless Elixir can detect during startup that
both `stdout` and `stderr` are terminals.
""" """
@spec terminal? :: boolean @spec enabled? :: boolean
@spec terminal?(:io.device) :: boolean def enabled? do
def terminal?(device \\ :erlang.group_leader) do Application.get_env(:elixir, :ansi_enabled, false)
!match?({:win32, _}, :os.type()) and
match?({:ok, _}, :io.columns(device))
end end
@doc "Resets all attributes" @doc "Resets all attributes"
@@ -130,99 +132,78 @@ defmodule IO.ANSI do
@doc "Clear screen" @doc "Clear screen"
defsequence :clear, "2", "J" defsequence :clear, "2", "J"
defp escape_sequence(other) do defp format_sequence(other) do
raise ArgumentError, "invalid ANSI sequence specification: #{other}" raise ArgumentError, "invalid ANSI sequence specification: #{other}"
end end
@doc ~S""" @doc ~S"""
Escapes a string by converting named ANSI sequences into actual ANSI codes. Formats a chardata-like argument by converting named ANSI sequences into actual
ANSI codes.
The format for referring to sequences is `%{red}` and `%{red,bright}` (for The named sequences are represented by atoms.
multiple sequences).
It will also append a `%{reset}` to the string. If you don't want this It will also append an `IO.ANSI.reset` to the chardata when a conversion is
behaviour, use `escape_fragment/2`. performed. If you don't want this behaviour, use `format_fragment/2`.
An optional boolean parameter can be passed to enable or disable An optional boolean parameter can be passed to enable or disable
emitting actual ANSI codes. When `false`, no ANSI codes will emitted. emitting actual ANSI codes. When `false`, no ANSI codes will emitted.
By default, standard output will be checked if it is a terminal capable By default checks if ANSI is enabled using the `enabled?/0` function.
of handling these sequences (using `terminal?/1` function)
## Examples ## Examples
iex> IO.ANSI.escape("Hello %{red,bright,green}yes", true) iex> IO.ANSI.format(["Hello, ", :red, :bright, "world!"], true)
"Hello \e[31m\e[1m\e[32myes\e[0m" [[[[[[], "Hello, "] | "\e[31m"] | "\e[1m"], "world!"] | "\e[0m"]
""" """
@spec escape(String.t, emit :: boolean) :: String.t def format(chardata, emit \\ enabled?) when is_boolean(emit) do
def escape(string, emit \\ terminal?) when is_binary(string) and is_boolean(emit) do do_format(chardata, [], [], emit, :maybe)
{rendered, emitted} = do_escape(string, emit, false, nil, [])
if emitted do
rendered <> reset
else
rendered
end
end end
@doc ~S""" @doc ~S"""
Escapes a string by converting named ANSI sequences into actual ANSI codes. Formats a chardata-like argument by converting named ANSI sequences into actual
ANSI codes.
The format for referring to sequences is `%{red}` and `%{red,bright}` (for The named sequences are represented by atoms.
multiple sequences).
An optional boolean parameter can be passed to enable or disable An optional boolean parameter can be passed to enable or disable
emitting actual ANSI codes. When `false`, no ANSI codes will emitted. emitting actual ANSI codes. When `false`, no ANSI codes will emitted.
By default, standard output will be checked if it is a terminal capable By default checks if ANSI is enabled using the `enabled?/0` function.
of handling these sequences (using `terminal?/1` function)
## Examples ## Examples
iex> IO.ANSI.escape_fragment("Hello %{red,bright,green}yes", true) iex> IO.ANSI.format_fragment([:bright, 'Word'], true)
"Hello \e[31m\e[1m\e[32myes" [[[[[[] | "\e[1m"], 87], 111], 114], 100]
iex> IO.ANSI.escape_fragment("%{reset}bye", true)
"\e[0mbye"
""" """
@spec escape_fragment(String.t, emit :: boolean) :: String.t def format_fragment(chardata, emit \\ enabled?) when is_boolean(emit) do
def escape_fragment(string, emit \\ terminal?) when is_binary(string) and is_boolean(emit) do do_format(chardata, [], [], emit, false)
{escaped, _emitted} = do_escape(string, emit, false, nil, [])
escaped
end end
defp do_escape(<<?}, t :: binary>>, emit, emitted, buffer, acc) when is_list(buffer) do defp do_format([term | rest], rem, acc, emit, append_reset) do
sequences = do_format(term, [rest | rem], acc, emit, append_reset)
buffer
|> Enum.reverse()
|> :string.tokens(',')
|> Enum.map(&(&1 |> :string.strip |> escape_sequence))
|> Enum.reverse()
if emit and sequences != [] do
do_escape(t, emit, true, nil, sequences ++ acc)
else
do_escape(t, emit, emitted, nil, acc)
end
end end
defp do_escape(<<h, t :: binary>>, emit, emitted, buffer, acc) when is_list(buffer) do defp do_format(term, rem, acc, true, append_reset) when is_atom(term) do
do_escape(t, emit, emitted, [h|buffer], acc) do_format([], rem, [acc | format_sequence(term)], true, !!append_reset)
end end
defp do_escape(<<>>, _emit, _emitted, buffer, _acc) when is_list(buffer) do defp do_format(term, rem, acc, false, append_reset) when is_atom(term) do
buffer = IO.iodata_to_binary Enum.reverse(buffer) do_format([], rem, acc, false, append_reset)
raise ArgumentError, "missing } for escape fragment #{buffer}"
end end
defp do_escape(<<?%, ?{, t :: binary>>, emit, emitted, nil, acc) do defp do_format(term, rem, acc, emit, append_reset) when not is_list(term) do
do_escape(t, emit, emitted, [], acc) do_format([], rem, [acc | [term]], emit, append_reset)
end end
defp do_escape(<<h, t :: binary>>, emit, emitted, nil, acc) do defp do_format([], [next | rest], acc, emit, append_reset) do
do_escape(t, emit, emitted, nil, [h|acc]) do_format(next, rest, acc, emit, append_reset)
end end
defp do_escape(<<>>, _emit, emitted, nil, acc) do defp do_format([], [], acc, true, true) do
{IO.iodata_to_binary(Enum.reverse(acc)), emitted} [acc | IO.ANSI.reset]
end
defp do_format([], [], acc, _emit, _append_reset) do
acc
end end
end end
+295 -149
View File
@@ -8,27 +8,29 @@ defmodule IO.ANSI.Docs do
The supported values are: The supported values are:
* `:enabled` - toggles coloring on and off (true) * `:enabled` - toggles coloring on and off (true)
* `:doc_code` - code blocks (cyan, bright) * `:doc_bold` - bold text (bright)
* `:doc_inline_code` - inline code (cyan) * `:doc_code` - code blocks (cyan, bright)
* `:doc_headings` - h1 and h2 headings (yellow, bright) * `:doc_headings` - h1 and h2 headings (yellow, bright)
* `:doc_title` - top level heading (reverse, yellow, bright) * `:doc_inline_code` - inline code (cyan)
* `:doc_bold` - bold text (bright) * `:doc_table_heading` - style for table headings
* `:doc_underline` - underlined text (underline) * `:doc_title` - top level heading (reverse, yellow, bright)
* `:width` - the width to format the text (80) * `:doc_underline` - underlined text (underline)
* `:width` - the width to format the text (80)
Values for the color settings are strings with Values for the color settings are strings with
comma-separated ANSI values. comma-separated ANSI values.
""" """
def default_options do def default_options do
[enabled: true, [enabled: true,
doc_code: "cyan,bright", doc_bold: [:bright],
doc_inline_code: "cyan", doc_code: [:cyan, :bright],
doc_headings: "yellow,bright", doc_headings: [:yellow],
doc_title: "reverse,yellow,bright", doc_inline_code: [:cyan],
doc_bold: "bright", doc_table_heading: [:reverse],
doc_underline: "underline", doc_title: [:reverse, :yellow],
width: 80] doc_underline: [:underline],
width: 80]
end end
@doc """ @doc """
@@ -43,12 +45,13 @@ defmodule IO.ANSI.Docs do
padding = div(width + String.length(heading), 2) padding = div(width + String.length(heading), 2)
heading = heading |> String.rjust(padding) |> String.ljust(width) heading = heading |> String.rjust(padding) |> String.ljust(width)
write(:doc_title, heading, options) write(:doc_title, heading, options)
newline_after_block
end end
@doc """ @doc """
Prints the documentation body. Prints the documentation body.
In addition to the priting string, takes a set of options In addition to the printing string, takes a set of options
defined in `default_options/1`. defined in `default_options/1`.
""" """
def print(doc, options \\ []) do def print(doc, options \\ []) do
@@ -56,52 +59,63 @@ defmodule IO.ANSI.Docs do
doc doc
|> String.split(["\r\n","\n"], trim: false) |> String.split(["\r\n","\n"], trim: false)
|> Enum.map(&String.rstrip/1) |> Enum.map(&String.rstrip/1)
|> process("", options) |> process([], "", options)
end end
defp process([], _indent, _options), do: nil defp process([], text, indent, options) do
write_text(text, indent, options)
end
defp process(["# " <> heading | rest], _indent, options) do defp process(["# " <> heading | rest], text, indent, options) do
write_text(text, indent, options)
write_h1(String.strip(heading), options) write_h1(String.strip(heading), options)
process(rest, "", options) process(rest, [], "", options)
end end
defp process(["## " <> heading | rest], _indent, options) do defp process(["## " <> heading | rest], text, indent, options) do
write_text(text, indent, options)
write_h2(String.strip(heading), options) write_h2(String.strip(heading), options)
process(rest, "", options) process(rest, [], "", options)
end end
defp process(["### " <> heading | rest], indent, options) do defp process(["### " <> heading | rest], text, indent, options) do
write_text(text, indent, options)
write_h3(String.strip(heading), indent, options) write_h3(String.strip(heading), indent, options)
process(rest, indent, options) process(rest, [], "", options)
end end
defp process(["" | rest], indent, options) do defp process(["" | rest], text, indent, options) do
process(rest, indent, options) write_text(text, indent, options)
process(rest, [], indent, options)
end end
defp process([" " <> line | rest], indent, options) do defp process([" " <> line | rest], text, indent, options) do
write_text(text, indent, options)
process_code(rest, [line], indent, options) process_code(rest, [line], indent, options)
end end
defp process([line | rest], indent, options) do defp process(all=[line | rest], text, indent, options) do
{stripped, count} = strip_spaces(line, 0) {stripped, count} = strip_spaces(line, 0, :infinity)
case stripped do if is_table_line?(stripped) and rest != [] and is_table_line?(hd(rest)) do
<<bullet, ?\s, item :: binary >> when bullet in @bullets -> write_text(text, indent, options)
process_list(item, rest, count, indent, options) process_table(all, indent, options)
_ -> else
process_text(rest, [line], indent, false, options) case stripped do
<<bullet, ?\s, item :: binary>> when bullet in @bullets ->
write_text(text, indent, options)
process_list("• ", item, rest, count, indent, options)
<<d1, ?., ?\s, item :: binary>> when d1 in ?0..?9 ->
write_text(text, indent, options)
process_list(<<d1, ?., ?\s>>, item, rest, count, indent, options)
<<d1, d2, ?., ?\s, item :: binary>> when d1 in ?0..?9 and d2 in ?0..?9 ->
write_text(text, indent, options)
process_list(<<d1, d2, ?., ?\s>>, item, rest, count, indent, options)
_ ->
process(rest, [stripped | text], indent, options)
end
end end
end end
defp strip_spaces(" " <> line, acc) do
strip_spaces(line, acc + 1)
end
defp strip_spaces(rest, acc) do
{rest, acc}
end
## Headings ## Headings
defp write_h1(heading, options) do defp write_h1(heading, options) do
@@ -110,91 +124,81 @@ defmodule IO.ANSI.Docs do
defp write_h2(heading, options) do defp write_h2(heading, options) do
write(:doc_headings, heading, options) write(:doc_headings, heading, options)
newline_after_block
end end
defp write_h3(heading, indent, options) do defp write_h3(heading, indent, options) do
IO.write(indent) IO.write(indent)
write(:doc_headings, heading, options) write(:doc_headings, heading, options)
newline_after_block
end end
## Lists ## Lists
defp process_list(line, rest, count, indent, options) do defp process_list(entry, line, rest, count, indent, options) do
IO.write indent <> "• " # The first list always win some extra padding
{contents, rest, done} = process_list_next(rest, count, false, []) if indent == "", do: entry = " " <> entry
process_text(contents, [line], indent <> " ", true, options) new_indent = indent <> String.duplicate(" ", String.length(entry))
if done, do: IO.puts(IO.ANSI.reset)
process(rest, indent, options) {contents, rest, done} = process_list_next(rest, count, byte_size(new_indent), [])
process(contents, [indent <> entry <> line, :no_wrap], new_indent, options)
if done, do: newline_after_block()
process(rest, [], indent, options)
end end
# Process the thing after a list item entry. It can be either: defp process_list_next([line | rest], count, max, acc) do
# {stripped, next_count} = strip_spaces(line, 0, max)
# * Continuation of the list case process_list_next_kind(stripped, rest, count, next_count) do
# * A nested list :next -> process_list_next(rest, count, max, [stripped | acc])
# * The end of the list :done -> {Enum.reverse(acc), [line | rest], true}
# :list -> {Enum.reverse(acc), [line | rest], false}
defp process_list_next([" " <> _ = line | rest], count, _done, acc) do
case list_next(line, count) do
:done -> {Enum.reverse(acc), [line|rest], false}
chopped -> process_list_next(rest, count, false, [chopped|acc])
end end
end end
defp process_list_next([<<bullet, ?\s, _ :: binary>> | _] = rest, _count, _done, acc) when bullet in @bullets do defp process_list_next([], _count, _max, acc) do
{Enum.reverse(acc), rest, false} {Enum.reverse(acc), [], true}
end end
defp process_list_next(["" | rest], count, _done, acc) do defp process_list_next_kind(stripped, rest, count, next_count) do
process_list_next(rest, count, true, [""|acc]) case {stripped, rest} do
end {<<bullet, ?\s, _ :: binary>>, _} when bullet in @bullets and next_count <= count ->
:list
defp process_list_next(rest, _count, done, acc) do {<<d1, ?., ?\s, _ :: binary>>, _} when d1 in ?0..?9 and next_count <= count ->
{Enum.reverse(acc), rest, done} :list
end {<<d1, d2, ?., ?\s, _ :: binary>>, _} when d1 in ?0..?9 and d2 in ?0..?9 and next_count <= count ->
:list
defp list_next(<<bullet, ?\s, _ :: binary>>, 0) when bullet in @bullets, do: :done {"", [" " <> _ | _]} ->
defp list_next(line, 0), do: chop(line, 2) :next
defp list_next(" " <> line, acc), do: list_next(line, acc - 1) {"", _} ->
defp list_next(line, _acc), do: line :done
defp chop(" " <> line, acc) when acc > 0, do: chop(line, acc - 1)
defp chop(line, _acc), do: line
## Text (paragraphs / lists)
defp process_text(doc=["" | _], para, indent, from_list, options) do
write_text(Enum.reverse(para), indent, from_list, options)
process(doc, indent, options)
end
defp process_text([], para, indent, from_list, options) do
write_text(Enum.reverse(para), indent, from_list, options)
end
defp process_text([line | rest], para, indent, true, options) do
{stripped, count} = strip_spaces(line, 0)
case stripped do
<<bullet, ?\s, item :: binary>> when bullet in @bullets ->
write_text(Enum.reverse(para), indent, true, options)
process_list(item, rest, count, indent, options)
_ -> _ ->
process_text(rest, [line | para], indent, true, options) :next
end end
end end
defp process_text([line | rest], para, indent, from_list, options) do ## Text
process_text(rest, [line | para], indent, from_list, options)
defp write_text(text, indent, options) do
case Enum.reverse(text) do
[:no_wrap|rest] -> write_text(rest, indent, options, true)
rest -> write_text(rest, indent, options, false)
end
end end
defp write_text(lines, indent, from_list, options) do defp write_text([], _indent, _options, _no_wrap) do
:ok
end
defp write_text(lines, indent, options, no_wrap) do
lines lines
|> Enum.join(" ") |> Enum.join(" ")
|> handle_links |> handle_links
|> handle_inline(nil, [], [], options) |> handle_inline(options)
|> String.split(~r{\s}) |> String.split(~r{\s})
|> write_with_wrap(options[:width] - byte_size(indent), indent, from_list) |> write_with_wrap(options[:width] - byte_size(indent), indent, no_wrap)
unless from_list, do: IO.puts(IO.ANSI.reset) unless no_wrap, do: newline_after_block()
end end
## Code blocks ## Code blocks
@@ -204,28 +208,124 @@ defmodule IO.ANSI.Docs do
end end
# Blank line between code blocks # Blank line between code blocks
defp process_code([ "", " " <> line | rest ], code, indent, options) do defp process_code(["", " " <> line | rest], code, indent, options) do
process_code(rest, [line, "" | code], indent, options) process_code(rest, [line, "" | code], indent, options)
end end
defp process_code([ " " <> line | rest ], code, indent, options) do defp process_code([" " <> line | rest], code, indent, options) do
process_code(rest, [line|code], indent, options) process_code(rest, [line|code], indent, options)
end end
defp process_code(rest, code, indent, options) do defp process_code(rest, code, indent, options) do
write_code(code, indent, options) write_code(code, indent, options)
process(rest, indent, options) process(rest, [], indent, options)
end end
defp write_code(code, indent, options) do defp write_code(code, indent, options) do
write(:doc_code, "#{indent}┃ #{Enum.join(Enum.reverse(code), "\n#{indent}┃ ")}", options) write(:doc_code, "#{indent}┃ #{Enum.join(Enum.reverse(code), "\n#{indent}┃ ")}", options)
newline_after_block
end
## Tables
defp process_table(lines, indent, options) do
{table, rest} = Enum.split_while(lines, &is_table_line?/1)
table_lines(table, options)
newline_after_block
process(rest, [], indent, options)
end
defp table_lines(lines, options) do
lines = Enum.map(lines, &split_into_columns(&1, options))
count = Enum.map(lines, &length/1) |> Enum.max
lines = Enum.map(lines, &pad_to_number_of_columns(&1, count))
widths = for line <- lines, do:
(for {_col, length} <- line, do: length)
col_widths = Enum.reduce(widths,
List.duplicate(0, count),
&max_column_widths/2)
render_table(lines, col_widths, options)
end
defp split_into_columns(line, options) do
line
|> String.strip(?|)
|> String.strip()
|> String.split(~r/\s\|\s/)
|> Enum.map(&render_column(&1, options))
end
defp render_column(col, options) do
col = col
|> String.replace(~r/\\ \|/x, "|")
|> handle_links
|> handle_inline(options)
{col, length_without_escape(col, 0)}
end
defp pad_to_number_of_columns(cols, col_count),
do: cols ++ List.duplicate({"", 0}, col_count - length(cols))
defp max_column_widths(cols, widths),
do: Enum.zip(cols, widths) |> Enum.map(fn {a,b} -> max(a,b) end)
# If second line is heading separator, use the heading style on the first
defp render_table([first, second | rest], widths, options) do
combined = Enum.zip(first, widths)
if table_header?(second) do
draw_table_row(combined, options, :heading)
render_table(rest, widths, options)
else
draw_table_row(combined, options)
render_table([second | rest], widths, options)
end
end
defp render_table([first | rest], widths, options) do
combined = Enum.zip(first, widths)
draw_table_row(combined, options)
render_table(rest, widths, options)
end
defp render_table([], _, _),
do: nil
defp table_header?(row), do:
Enum.all?(row, fn {col, _} -> col =~ ~r/^:?-+:?$/ end)
defp draw_table_row(cols_and_widths, options, heading \\ false) do
columns =
Enum.map_join(cols_and_widths, " | ", fn {{col, length}, width} ->
col <> String.duplicate(" ", width - length)
end)
if heading do
write(:doc_table_heading, columns, options)
else
IO.puts columns
end
end
defp is_table_line?(line) do
Regex.match?(~r'''
( ^ \s{0,3} \| (?: [^|]+ \|)+ \s* $ )
|
(\s \| \s)
'''x, line)
end end
## Helpers ## Helpers
defp strip_spaces(" " <> line, acc, max) when acc < max,
do: strip_spaces(line, acc + 1, max)
defp strip_spaces(rest, acc, _max),
do: {rest, acc}
defp write(style, string, options) do defp write(style, string, options) do
IO.puts color(style, options) <> string <> IO.ANSI.reset IO.puts [color(style, options), string, IO.ANSI.reset]
IO.puts IO.ANSI.reset
end end
defp write_with_wrap([], _available, _indent, _first) do defp write_with_wrap([], _available, _indent, _first) do
@@ -270,7 +370,7 @@ defmodule IO.ANSI.Docs do
defp length_without_escape(rest, count) do defp length_without_escape(rest, count) do
case String.next_grapheme(rest) do case String.next_grapheme(rest) do
{_, rest} -> length_without_escape(rest, count + 1) {_, rest} -> length_without_escape(rest, count + 1)
nil -> count nil -> count
end end
end end
@@ -283,10 +383,8 @@ defmodule IO.ANSI.Docs do
defp escape_underlines_in_link(text) do defp escape_underlines_in_link(text) do
case Regex.match?(~r{.*(https?\S*)}, text) do case Regex.match?(~r{.*(https?\S*)}, text) do
true -> true -> Regex.replace(~r{_}, text, "\\\\_")
Regex.replace(~r{_}, text, "\\\\_") _ -> text
_ ->
text
end end
end end
@@ -294,61 +392,104 @@ defmodule IO.ANSI.Docs do
Regex.replace(~r{\[(.*?)\]\((.*?)\)}, text, "\\1 (\\2)") Regex.replace(~r{\[(.*?)\]\((.*?)\)}, text, "\\1 (\\2)")
end end
# Single inline quotes.
@single [?`, ?_, ?*]
# ` does not require space in between # We have four entries: **, *, _ and `.
@spaced [?_, ?*] #
# 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 # Characters that can mark the beginning or the end of a word.
defp handle_inline(<<?*, ?*, ?\s, rest :: binary>>, nil, buffer, acc, options) do # Only support the most common ones at this moment.
handle_inline(rest, nil, [?\s, ?*, ?*|buffer], acc, options) @delimiters [?\s, ?', ?", ?!, ?@, ?#, ?$, ?%, ?^, ?&, ?-, ?+, ?(, ?), ?[, ?], ?{, ?}, ?<, ?>, ?.]
# Inline start
defp handle_inline(<<?*, ?*, rest :: binary>>, options) do
handle_inline(rest, ?d, ["**"], [], options)
end end
defp handle_inline(<<mark, ?\s, rest :: binary>>, nil, buffer, acc, options) when mark in @spaced do defp handle_inline(<<mark, rest :: binary>>, options) when mark in @single do
handle_inline(rest, nil, [?\s, mark|buffer], acc, options) handle_inline(rest, mark, [<<mark>>], [], options)
end end
defp handle_inline(<<?\s, ?*, ?*, rest :: binary>>, limit, buffer, acc, options) do defp handle_inline(rest, options) do
handle_inline(rest, limit, [?*, ?*, ?\s|buffer], acc, options) handle_inline(rest, nil, [], [], options)
end end
defp handle_inline(<<?\s, mark, rest :: binary>>, limit, buffer, acc, options) when mark in @spaced do # Inline delimiters
handle_inline(rest, limit, [mark, ?\s|buffer], acc, options)
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 end
# Clauses for handling escape # 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 defp handle_inline(<<?\\, ?\\, rest :: binary>>, limit, buffer, acc, options) do
handle_inline(rest, limit, [?\\|buffer], acc, options) handle_inline(rest, limit, [?\\|buffer], acc, options)
end end
defp handle_inline(<<?\\, ?*, ?*, rest :: binary>>, limit, buffer, acc, options) do # An escape is not valid inside `
handle_inline(rest, limit, [?*, ?*|buffer], acc, options)
end
# A escape is not valid inside `
defp handle_inline(<<?\\, mark, rest :: binary>>, limit, buffer, acc, options) 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) handle_inline(rest, limit, [mark|buffer], acc, options)
end 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 # 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) handle_inline(rest, nil, [], [inline_buffer(buffer, options)|acc], options)
end end
defp handle_inline(<<mark, rest :: binary>>, mark, buffer, acc, options) when mark in @single do # Catch all
handle_inline(rest, nil, [], [inline_buffer(buffer, options)|acc], options)
end
defp handle_inline(<<char, rest :: binary>>, mark, buffer, acc, options) do defp handle_inline(<<char, rest :: binary>>, mark, buffer, acc, options) do
handle_inline(rest, mark, [char|buffer], acc, options) handle_inline(rest, mark, [char|buffer], acc, options)
@@ -363,14 +504,19 @@ defmodule IO.ANSI.Docs do
[color_for(h, options)|t] [color_for(h, options)|t]
end end
defp color_for("`", colors), do: color(:doc_inline_code, colors) defp color_for(mark, colors) do
defp color_for("_", colors), do: color(:doc_underline, colors) case mark do
defp color_for("*", colors), do: color(:doc_bold, colors) "`" -> color(:doc_inline_code, colors)
defp color_for("**", colors), do: color(:doc_bold, colors) "_" -> color(:doc_underline, colors)
"*" -> color(:doc_bold, colors)
"**" -> color(:doc_bold, colors)
end
end
defp color(style, colors) do defp color(style, colors) do
color = colors[style] color = colors[style]
enabled = colors[:enabled] IO.ANSI.format_fragment(color, colors[:enabled])
IO.ANSI.escape_fragment("%{#{color}}", enabled)
end end
defp newline_after_block, do: IO.puts(IO.ANSI.reset)
end end
+3 -1
View File
@@ -22,6 +22,8 @@ defmodule IO.Stream do
defstruct device: nil, raw: true, line_or_bytes: :line defstruct device: nil, raw: true, line_or_bytes: :line
@type t :: %__MODULE__{}
@doc false @doc false
def __build__(device, raw, line_or_bytes) do def __build__(device, raw, line_or_bytes) do
%IO.Stream{device: device, raw: raw, line_or_bytes: line_or_bytes} %IO.Stream{device: device, raw: raw, line_or_bytes: line_or_bytes}
@@ -55,7 +57,7 @@ defmodule IO.Stream do
true -> &IO.each_binstream(&1, line_or_bytes) true -> &IO.each_binstream(&1, line_or_bytes)
false -> &IO.each_stream(&1, line_or_bytes) false -> &IO.each_stream(&1, line_or_bytes)
end end
Stream.unfold(device, next_fun).(acc, fun) Stream.resource(fn -> device end, next_fun, &(&1)).(acc, fun)
end end
def count(_stream) do def count(_stream) do
+296 -250
View File
@@ -150,9 +150,9 @@ defmodule Kernel do
3 3
""" """
@spec byte_size(bitstring) :: non_neg_integer @spec byte_size(binary) :: non_neg_integer
def byte_size(bitstring) do def byte_size(binary) do
:erlang.byte_size(bitstring) :erlang.byte_size(binary)
end end
@doc """ @doc """
@@ -183,9 +183,56 @@ defmodule Kernel do
## Examples ## Examples
When a process reaches its end, by default it exits with
reason `:normal`. You can also call it explicitly if you
want to terminate a process but not signal any failure:
exit(:normal) exit(:normal)
In case something goes wrong, you can also use `exit/1` with
a different reason:
exit(:seems_bad) exit(:seems_bad)
If the reason is not `:normal`, all linked process to the
exited process will crash (unless they are trapping exits).
## OTP exits
Exits are used by OTP to determine if a process exited abnormally
or not. The following exits are considered "normal":
* `exit(:normal)`
* `exit(:shutdown)`
* `exit({:shutdown, term})`
Exiting with any other reason is considered abnormal and treated
as a crash. This means the default supervisor behaviour kicks in,
error reports are emitted, etc.
This behaviour is relied on in many different places. For example,
`ExUnit` uses `exit(:shutdown)` when exiting the test process to
signal linked processes, supervision trees and so on to politely
shutdown too.
## CLI exits
Building on top of the exit signals mentioned above, if the
process started by the command line exits with any of the three
reasons above, its exit is considered normal and the Operating
System process will exit with status 0.
It is, however, possible to customize the Operating System exit
signal by invoking:
exit({:shutdown, integer})
This will cause the OS process to exit with the status given by
`integer` while signaling all linked OTP processes to politely
shutdown.
Any other exit reason will cause the OS process to exit with
status `1` and linked OTP processes to crash.
""" """
@spec exit(term) :: no_return @spec exit(term) :: no_return
def exit(reason) do def exit(reason) do
@@ -838,11 +885,6 @@ defmodule Kernel do
:erlang.--(left, right) :erlang.--(left, right)
end end
@doc false
def left xor right do
:erlang.xor(left, right)
end
@doc """ @doc """
Boolean not. Argument must be a boolean. Boolean not. Argument must be a boolean.
@@ -1344,7 +1386,14 @@ defmodule Kernel do
iex> "abcd" =~ "ad" iex> "abcd" =~ "ad"
false 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 def left =~ right when is_binary(left) and is_binary(right) do
:binary.match(left, right) != :nomatch :binary.match(left, right) != :nomatch
end end
@@ -1372,14 +1421,26 @@ defmodule Kernel do
iex> inspect [1, 2, 3, 4, 5], limit: 3 iex> inspect [1, 2, 3, 4, 5], limit: 3
"[1, 2, 3, ...]" "[1, 2, 3, ...]"
iex> inspect("josé" <> <<0>>) iex> inspect("olá" <> <<0>>)
"<<106, 111, 115, 195, 169, 0>>" "<<111, 108, 195, 161, 0>>"
iex> inspect("josé" <> <<0>>, binaries: :as_strings) iex> inspect("olá" <> <<0>>, binaries: :as_strings)
"\"josé\\000\"" "\"olá\\0\""
iex> inspect("josé", binaries: :as_binaries) iex> inspect("olá", binaries: :as_binaries)
"<<106, 111, 115, 195, 169>>" "<<111, 108, 195, 161>>"
iex> inspect('bar')
"'bar'"
iex> inspect([0|'bar'])
"[0, 98, 97, 114]"
iex> inspect(100, base: :octal)
"0o144"
iex> inspect(100, base: :hex)
"0x64"
Note that the inspect protocol does not necessarily return a valid Note that the inspect protocol does not necessarily return a valid
representation of an Elixir term. In such cases, the inspected result representation of an Elixir term. In such cases, the inspected result
@@ -1396,7 +1457,9 @@ defmodule Kernel do
true -> opts.width true -> opts.width
false -> :infinity false -> :infinity
end end
Inspect.Algebra.pretty(Inspect.Algebra.to_doc(arg, opts), limit) IO.iodata_to_binary(
Inspect.Algebra.format(Inspect.Algebra.to_doc(arg, opts), limit)
)
end end
@doc """ @doc """
@@ -1415,18 +1478,18 @@ defmodule Kernel do
## Example ## Example
defmodule User do defmodule User do
defstruct name: "jose" defstruct name: "john"
end end
struct(User) struct(User)
#=> %User{name: "jose"} #=> %User{name: "john"}
opts = [name: "eric"] opts = [name: "meg"]
user = struct(User, opts) user = struct(User, opts)
#=> %User{name: "eric"} #=> %User{name: "meg"}
struct(user, unknown: "value") struct(user, unknown: "value")
#=> %User{name: "eric"} #=> %User{name: "meg"}
""" """
@spec struct(module | map, Enum.t) :: map @spec struct(module | map, Enum.t) :: map
@@ -1453,47 +1516,77 @@ defmodule Kernel do
Gets a value from a nested structure. Gets a value from a nested structure.
Uses the `Access` protocol to traverse the structures Uses the `Access` protocol to traverse the structures
according to the given `keys`. according to the given `keys`, unless the `key` is a
function.
If a key is a function, the function will be invoked
passing three arguments, the operation (`:get`), the
data to be accessed, and a function to be invoked next.
This means `get_in/2` can be extended to provide
custom lookups. The downside is that functions cannot be
stored as keys in the accessed data structures.
## Examples ## Examples
iex> users = %{"josé" => %{age: 27}, "eric" => %{age: 23}} iex> users = %{"john" => %{age: 27}, "meg" => %{age: 23}}
iex> get_in(users, ["josé", :age]) iex> get_in(users, ["john", :age])
27 27
In case any of entries in the middle returns `nil`, `nil` will be returned In case any of entries in the middle returns `nil`, `nil` will be returned
as per the Access protocol: as per the Access protocol:
iex> users = %{"josé" => %{age: 27}, "eric" => %{age: 23}} iex> users = %{"john" => %{age: 27}, "meg" => %{age: 23}}
iex> get_in(users, ["unknown", :age]) iex> get_in(users, ["unknown", :age])
nil nil
When one of the keys is a function, the function is invoked.
In the example below, we use a function to get all the maps
inside a list:
iex> users = [%{name: "john", age: 27}, %{name: "meg", age: 23}]
iex> all = fn :get, data, next -> Enum.map(data, next) end
iex> get_in(users, [all, :age])
[27, 23]
If the previous value before invoking the function is nil,
the function *will* receive nil as a value and must handle it
accordingly.
""" """
@spec get_in(Access.t, nonempty_list(term)) :: term @spec get_in(Access.t, nonempty_list(term)) :: term
def get_in(data, keys) def get_in(data, keys)
def get_in(nil, list) when is_list(list), do: nil
def get_in(data, [h]), do: Access.get(data, h) def get_in(data, [h]) when is_function(h),
def get_in(data, [h|t]), do: get_in(Access.get(data, h), t) do: h.(:get, data, &(&1))
def get_in(data, [h|t]) when is_function(h),
do: h.(:get, data, &get_in(&1, t))
def get_in(nil, [_]),
do: nil
def get_in(nil, [_|t]),
do: get_in(nil, t)
def get_in(data, [h]),
do: Access.get(data, h)
def get_in(data, [h|t]),
do: get_in(Access.get(data, h), t)
@doc """ @doc """
Puts a value in a nested structure. Puts a value in a nested structure.
Uses the `Access` protocol to traverse the structures Uses the `Access` protocol to traverse the structures
according to the given `keys`. according to the given `keys`, unless the `key` is a
function. If the key is a function, it will be invoked
as specified in `get_and_update_in/3`.
## Examples ## Examples
iex> users = %{"josé" => %{age: 27}, "eric" => %{age: 23}} iex> users = %{"john" => %{age: 27}, "meg" => %{age: 23}}
iex> put_in(users, ["josé", :age], 28) iex> put_in(users, ["john", :age], 28)
%{"josé" => %{age: 28}, "eric" => %{age: 23}} %{"john" => %{age: 28}, "meg" => %{age: 23}}
In case any of entries in the middle returns `nil`, a map is dynamically
created:
iex> users = %{"josé" => %{age: 27}, "eric" => %{age: 23}}
iex> put_in(users, ["dave", :age], 13)
%{"josé" => %{age: 27}, "eric" => %{age: 23}, "dave" => %{age: 13}}
In case any of entries in the middle returns `nil`,
an error will be raised when trying to access it next.
""" """
@spec put_in(Access.t, nonempty_list(term), term) :: Access.t @spec put_in(Access.t, nonempty_list(term), term) :: Access.t
def put_in(data, keys, value) do def put_in(data, keys, value) do
@@ -1504,21 +1597,18 @@ defmodule Kernel do
Updates a key in a nested structure. Updates a key in a nested structure.
Uses the `Access` protocol to traverse the structures Uses the `Access` protocol to traverse the structures
according to the given `keys`. according to the given `keys`, unless the `key` is a
function. If the key is a function, it will be invoked
as specified in `get_and_update_in/3`.
## Examples ## Examples
iex> users = %{"josé" => %{age: 27}, "eric" => %{age: 23}} iex> users = %{"john" => %{age: 27}, "meg" => %{age: 23}}
iex> update_in(users, ["josé", :age], &(&1 + 1)) iex> update_in(users, ["john", :age], &(&1 + 1))
%{"josé" => %{age: 28}, "eric" => %{age: 23}} %{"john" => %{age: 28}, "meg" => %{age: 23}}
In case any of entries in the middle returns `nil`, a map is dynamically
created:
iex> users = %{"josé" => %{age: 27}, "eric" => %{age: 23}}
iex> update_in(users, ["dave", :age], &((&1 || 0) + 1))
%{"josé" => %{age: 27}, "eric" => %{age: 23}, "dave" => %{age: 1}}
In case any of entries in the middle returns `nil`,
an error will be raised when trying to access it next.
""" """
@spec update_in(Access.t, nonempty_list(term), (term -> term)) :: Access.t @spec update_in(Access.t, nonempty_list(term), (term -> term)) :: Access.t
def update_in(data, keys, fun) do def update_in(data, keys, fun) do
@@ -1528,9 +1618,20 @@ defmodule Kernel do
@doc """ @doc """
Gets a value and updates a nested structure. Gets a value and updates a nested structure.
It expects a tuple to be returned, containing the value retrieved It expects a tuple to be returned, containing the value
and the update one. Uses the `Access` protocol to traverse the retrieved and the update one.
structures according to the given `keys`.
Uses the `Access` protocol to traverse the structures
according to the given `keys`, unless the `key` is a
function.
If a key is a function, the function will be invoked
passing three arguments, the operation (`:get_and_update`),
the data to be accessed, and a function to be invoked next.
This means `get_and_update_in/3` can be extended to provide
custom lookups. The downside is that functions cannot be stored
as keys in the accessed data structures.
## Examples ## Examples
@@ -1539,27 +1640,38 @@ defmodule Kernel do
update it at the same time. For example, it could be used to increase update it at the same time. For example, it could be used to increase
the age of a user by one and return the previous age in one pass: the age of a user by one and return the previous age in one pass:
iex> users = %{"josé" => %{age: 27}, "eric" => %{age: 23}} iex> users = %{"john" => %{age: 27}, "meg" => %{age: 23}}
iex> get_and_update_in(users, ["josé", :age], &{&1, &1 + 1}) iex> get_and_update_in(users, ["john", :age], &{&1, &1 + 1})
{27, %{"josé" => %{age: 28}, "eric" => %{age: 23}}} {27, %{"john" => %{age: 28}, "meg" => %{age: 23}}}
In case any of entries in the middle returns `nil`, a map is dynamically When one of the keys is a function, the function is invoked.
created: In the example below, we use a function to get and increment all
ages inside a list:
iex> users = %{"josé" => %{age: 27}, "eric" => %{age: 23}} iex> users = [%{name: "john", age: 27}, %{name: "meg", age: 23}]
iex> get_and_update_in(users, ["dave", :age], &{&1, 13}) iex> all = fn :get_and_update, data, next ->
{nil, %{"josé" => %{age: 27}, "eric" => %{age: 23}, "dave" => %{age: 13}}} ...> 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}]}
If the previous value before invoking the function is nil,
the function *will* receive `nil` as a value and must handle it
accordingly (be it by failing or providing a sane default).
""" """
@spec get_and_update_in(Access.t, nonempty_list(term), @spec get_and_update_in(Access.t, nonempty_list(term),
(term -> {get, term})) :: {get, Access.t} when get: var (term -> {get, term})) :: {get, Access.t} when get: var
def get_and_update_in(data, keys, fun) def get_and_update_in(data, keys, fun)
def get_and_update_in(nil, list, fun), do: get_and_update_in(%{}, list, fun) def get_and_update_in(data, [h], fun) when is_function(h),
def get_and_update_in(data, [h], fun), do: Access.get_and_update(data, h, fun) do: h.(:get_and_update, data, fun)
def get_and_update_in(data, [h|t], fun) do def get_and_update_in(data, [h|t], fun) when is_function(h),
Access.get_and_update(data, h, &get_and_update_in(&1, t, fun)) do: h.(:get_and_update, data, &get_and_update_in(&1, t, fun))
end
def get_and_update_in(data, [h], fun),
do: Access.get_and_update(data, h, fun)
def get_and_update_in(data, [h|t], fun),
do: Access.get_and_update(data, h, &get_and_update_in(&1, t, fun))
@doc """ @doc """
Puts a value in a nested structure via the given `path`. Puts a value in a nested structure via the given `path`.
@@ -1579,13 +1691,13 @@ defmodule Kernel do
## Examples ## Examples
iex> users = %{"josé" => %{age: 27}, "eric" => %{age: 23}} iex> users = %{"john" => %{age: 27}, "meg" => %{age: 23}}
iex> put_in(users["josé"][:age], 28) iex> put_in(users["john"][:age], 28)
%{"josé" => %{age: 28}, "eric" => %{age: 23}} %{"john" => %{age: 28}, "meg" => %{age: 23}}
iex> users = %{"josé" => %{age: 27}, "eric" => %{age: 23}} iex> users = %{"john" => %{age: 27}, "meg" => %{age: 23}}
iex> put_in(users["josé"].age, 28) iex> put_in(users["john"].age, 28)
%{"josé" => %{age: 28}, "eric" => %{age: 23}} %{"john" => %{age: 28}, "meg" => %{age: 23}}
""" """
defmacro put_in(path, value) do defmacro put_in(path, value) do
@@ -1612,13 +1724,13 @@ defmodule Kernel do
## Examples ## Examples
iex> users = %{"josé" => %{age: 27}, "eric" => %{age: 23}} iex> users = %{"john" => %{age: 27}, "meg" => %{age: 23}}
iex> update_in(users["josé"][:age], &(&1 + 1)) iex> update_in(users["john"][:age], &(&1 + 1))
%{"josé" => %{age: 28}, "eric" => %{age: 23}} %{"john" => %{age: 28}, "meg" => %{age: 23}}
iex> users = %{"josé" => %{age: 27}, "eric" => %{age: 23}} iex> users = %{"john" => %{age: 27}, "meg" => %{age: 23}}
iex> update_in(users["josé"].age, &(&1 + 1)) iex> update_in(users["john"].age, &(&1 + 1))
%{"josé" => %{age: 28}, "eric" => %{age: 23}} %{"john" => %{age: 28}, "meg" => %{age: 23}}
""" """
defmacro update_in(path, fun) do defmacro update_in(path, fun) do
@@ -1644,9 +1756,9 @@ defmodule Kernel do
## Examples ## Examples
iex> users = %{"josé" => %{age: 27}, "eric" => %{age: 23}} iex> users = %{"john" => %{age: 27}, "meg" => %{age: 23}}
iex> get_and_update_in(users["josé"][:age], &{&1, &1 + 1}) iex> get_and_update_in(users["john"].age, &{&1, &1 + 1})
{27, %{"josé" => %{age: 28}, "eric" => %{age: 23}}} {27, %{"john" => %{age: 28}, "meg" => %{age: 23}}}
## Paths ## Paths
@@ -1661,15 +1773,15 @@ defmodule Kernel do
Here are some valid paths: Here are some valid paths:
users["josé"][:age] users["john"][:age]
users["josé"].age users["john"].age
User.all["josé"].age User.all["john"].age
all_users()["josé"].age all_users()["john"].age
Here are some invalid ones: Here are some invalid ones:
# Does a remote call after the initial value # Does a remote call after the initial value
users["josé"].do_something(arg1, arg2) users["john"].do_something(arg1, arg2)
# Does not access any field # Does not access any field
users users
@@ -1690,7 +1802,7 @@ defmodule Kernel do
defp nest_get_and_update_in(h, [{:access, key}|t], fun) do defp nest_get_and_update_in(h, [{:access, key}|t], fun) do
quote do quote do
Access.get_and_update( Access.get_and_update(
case(unquote(h), do: (nil -> %{}; o -> o)), unquote(h),
unquote(key), unquote(key),
unquote(nest_get_and_update_in(t, fun)) unquote(nest_get_and_update_in(t, fun))
) )
@@ -1708,13 +1820,15 @@ defmodule Kernel do
end end
defp unnest({{:., _, [expr, key]}, _, []}, acc, kind) 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) unnest(expr, [{:map, key}|acc], kind)
end end
defp unnest(other, [], kind) do defp unnest(other, [], kind) do
raise ArgumentError, raise ArgumentError,
"expected expression given to #{kind} to access at least one field, got: #{Macro.to_string other}" "expected expression given to #{kind} to access at least one element, got: #{Macro.to_string other}"
end end
defp unnest(other, acc, kind) do defp unnest(other, acc, kind) do
@@ -1723,14 +1837,14 @@ defmodule Kernel do
false -> false ->
raise ArgumentError, raise ArgumentError,
"expression given to #{kind} must start with a variable, local or remote call " <> "expression given to #{kind} must start with a variable, local or remote call " <>
"and be followed by field access, got: #{Macro.to_string other}" "and be followed by an element access, got: #{Macro.to_string other}"
end end
end end
defp proper_start?({{:., _, [expr, _]}, _, _args}) defp proper_start?({{:., _, [expr, _]}, _, _args})
when is_atom(expr) when is_atom(expr)
when elem(expr, 0) == :__aliases__ when :erlang.element(1, expr) == :__aliases__
when elem(expr, 0) == :__MODULE__, do: true when :erlang.element(1, expr) == :__MODULE__, do: true
defp proper_start?({atom, _, _args}) defp proper_start?({atom, _, _args})
when is_atom(atom), do: true when is_atom(atom), do: true
@@ -1776,20 +1890,20 @@ defmodule Kernel do
## Examples ## Examples
iex> nil?(1) iex> is_nil(1)
false false
iex> nil?(nil) iex> is_nil(nil)
true true
""" """
defmacro nil?(x) do defmacro is_nil(x) do
quote do: unquote(x) == nil quote do: unquote(x) == nil
end end
@doc """ @doc """
A convenient macro that checks if the right side matches A convenience macro that checks if the right side (an expression)
the left side. The left side is allowed to be a match pattern. matches the left side (a pattern).
## Examples ## Examples
@@ -1894,10 +2008,7 @@ defmodule Kernel do
case is_list(args) and length(args) == 1 and typespec(name) do case is_list(args) and length(args) == 1 and typespec(name) do
false -> false ->
case name == :typedoc and not bootstraped?(Kernel.Typespec) do do_at(args, name, function?, __CALLER__)
true -> nil
false -> do_at(args, name, function?, __CALLER__)
end
macro -> macro ->
case bootstraped?(Kernel.Typespec) do case bootstraped?(Kernel.Typespec) do
false -> nil false -> nil
@@ -1911,7 +2022,7 @@ defmodule Kernel do
defp do_at([arg], name, function?, env) do defp do_at([arg], name, function?, env) do
case function? do case function? do
true -> true ->
raise ArgumentError, "cannot dynamically set attribute @#{name} inside function" raise ArgumentError, "cannot set attribute @#{name} inside function/macro"
false -> false ->
case name do case name do
:behavior -> :behavior ->
@@ -1932,7 +2043,14 @@ defmodule Kernel do
case function? do case function? do
true -> true ->
attr = Module.get_attribute(env.module, name, stack) attr = Module.get_attribute(env.module, name, stack)
:erlang.element(1, :elixir_quote.escape(attr, false)) try do
:elixir_quote.escape(attr, false)
rescue
e in [ArgumentError] ->
raise ArgumentError, "cannot inject attribute @#{name} into function/macro because " <> Exception.message(e)
else
{val, _} -> val
end
false -> false ->
escaped = case stack do escaped = case stack do
[] -> [] [] -> []
@@ -1948,7 +2066,7 @@ defmodule Kernel do
end end
defp warn_info([entry|_]) do 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)) <> " " Exception.format_file_line(Keyword.get(opts, :file), Keyword.get(opts, :line)) <> " "
end end
@@ -1964,8 +2082,9 @@ defmodule Kernel do
defp typespec(_), do: false defp typespec(_), do: false
@doc """ @doc """
Returns the binding as a keyword list where the variable name Returns the binding for the given context as a keyword list.
is the key and the variable value is the value.
The variable name is the key and the variable value is the value.
## Examples ## Examples
@@ -1976,59 +2095,18 @@ defmodule Kernel do
iex> binding() iex> binding()
[x: 2] [x: 2]
""" iex> binding(:foo)
defmacro binding() do
do_binding(nil, nil, __CALLER__.vars, Macro.Env.in_match?(__CALLER__))
end
@doc """
Receives a list of atoms at compilation time and returns the
binding of the given variables as a keyword list where the
variable name is the key and the variable value is the value.
In case a variable in the list does not exist in the binding,
it is not included in the returned result.
## Examples
iex> x = 1
iex> binding([:x, :y])
[x: 1]
"""
defmacro binding(list) when is_list(list) do
do_binding(list, nil, __CALLER__.vars, Macro.Env.in_match?(__CALLER__))
end
defmacro binding(context) when is_atom(context) do
do_binding(nil, context, __CALLER__.vars, Macro.Env.in_match?(__CALLER__))
end
@doc """
Receives a list of atoms at compilation time and returns the
binding of the given variables in the given context as a keyword
list where the variable name is the key and the variable value
is the value.
In case a variable in the list does not exist in the binding,
it is not included in the returned result.
## Examples
iex> var!(x, :foo) = 1
iex> binding([:x, :y])
[] []
iex> binding([:x, :y], :foo) iex> var!(x, :foo) = 1
1
iex> binding(:foo)
[x: 1] [x: 1]
""" """
defmacro binding(list, context) when is_list(list) and is_atom(context) do defmacro binding(context \\ nil) do
do_binding(list, context, __CALLER__.vars, Macro.Env.in_match?(__CALLER__)) in_match? = Macro.Env.in_match?(__CALLER__)
end for {v, c} <- __CALLER__.vars, c == context do
{v, wrap_binding(in_match?, {v, [], c})}
defp do_binding(list, context, vars, in_match) do
for {v, c} <- vars, c == context, list == nil or :lists.member(v, list) do
{v, wrap_binding(in_match, {v, [], c})}
end end
end end
@@ -2310,7 +2388,7 @@ defmodule Kernel do
it is not loaded. Check `Code.ensure_loaded/1` for more it is not loaded. Check `Code.ensure_loaded/1` for more
information. information.
""" """
@spec function_exported?(atom | tuple, atom, integer) :: boolean @spec function_exported?(atom | tuple, atom, arity) :: boolean
def function_exported?(module, function, arity) do def function_exported?(module, function, arity) do
:erlang.function_exported(module, function, arity) :erlang.function_exported(module, function, arity)
end end
@@ -2561,7 +2639,7 @@ defmodule Kernel do
# Generate the alias for this module definition # Generate the alias for this module definition
{new, old} = module_nesting(env.module, full) {new, old} = module_nesting(env.module, full)
meta = [defined: full, context: true] ++ alias_meta(alias) meta = [defined: full, context: env.module] ++ alias_meta(alias)
{full, {:alias, meta, [old, [as: new, warn: false]]}} {full, {:alias, meta, [old, [as: new, warn: false]]}}
false -> false ->
@@ -2819,97 +2897,59 @@ defmodule Kernel do
## Types ## Types
`defstruct` automatically generates a type `t` unless one exists. It is recommended to define types for structs, by convention this type
The following definition: is called `t`. To define a struct in a type the struct literal syntax
is used:
defmodule User do defmodule User do
defstruct name: "José" :: String.t, defstruct name: "john", age: 25
age: 25 :: integer @type t :: %User{name: String.t, age: integer}
end end
Generates a type as follows: It is recommended to only use the struct syntax when defining the struct's
type. When referring to another struct use `User.t`, not `%User{}`. Fields
in the struct not included in the type defaults to `term`.
@type t :: %User{name: String.t, age: integer} Private structs that are not used outside its module should use the private
type attribute `@typep`. Public structs whose internal structure is private
In case a struct does not declare a field type, it defaults to `term`. to the local module (you are not allowed to pattern match it or directly
access fields) should use the `@opaque` attribute. Structs whose internal
structure is public should use `@type`.
""" """
defmacro defstruct(kv) do defmacro defstruct(fields) do
{fields, types} = split_fields_and_types(kv) quote bind_quoted: [fields: fields] do
fields = :lists.map(fn
{key, val} when is_atom(key) ->
try do
Macro.escape(val)
rescue
e in [ArgumentError] ->
raise ArgumentError, "invalid value for struct field #{key}, " <> Exception.message(e)
else
_ -> {key, val}
end
key when is_atom(key) ->
{key, nil}
other ->
raise ArgumentError, "struct field names must be atoms, got: #{inspect other}"
end, fields)
fields = @struct :maps.put(:__struct__, __MODULE__, :maps.from_list(fields))
quote bind_quoted: [fields: fields] do
fields = :lists.map(fn
{ key, _ } = pair when is_atom(key) -> pair
key when is_atom(key) -> { key, nil }
other -> raise ArgumentError, "struct field names must be atoms, got: #{inspect other}"
end, fields)
@struct :maps.put(:__struct__, __MODULE__, :maps.from_list(fields)) case Module.get_attribute(__MODULE__, :derive) do
[] -> :ok
case Module.get_attribute(__MODULE__, :derive) do derive -> Protocol.__derive__(derive, __MODULE__, __ENV__)
[] ->
:ok
derive ->
Protocol.__derive__(derive, __MODULE__, __ENV__)
end
@spec __struct__() :: t
def __struct__() do
@struct
end
end end
types = @spec __struct__() :: %__MODULE__{}
case bootstraped?(Kernel.Typespec) do def __struct__() do
true when types == [] -> @struct
quote unquote: false do
unless Kernel.Typespec.defines_type?(__MODULE__, :t, 0) do
types = :lists.map(fn {key, _} ->
{key, quote(do: term)}
end, fields)
@type t :: %{unquote_splicing(types), __struct__: __MODULE__}
end
end
true ->
quote do
unless Kernel.Typespec.defines_type?(__MODULE__, :t, 0) do
@type t :: %{unquote_splicing(types), __struct__: __MODULE__}
end
end
false ->
nil
end end
quote do
unquote(fields)
unquote(types)
fields fields
end end
end end
defp split_fields_and_types(kv) do
case Keyword.keyword?(kv) do
true -> split_fields_and_types(kv, [], [])
false -> {kv, []}
end
end
defp split_fields_and_types([{field, {:::, _, [default, type]}}|t], fields, types) do
split_fields_and_types(t, [{field, default}|fields], [{field, type}|types])
end
defp split_fields_and_types([{field, default}|t], fields, types) do
split_fields_and_types(t, [{field, default}|fields], [{field, quote(do: term)}|types])
end
defp split_fields_and_types([field|t], fields, types) do
split_fields_and_types(t, [field|fields], [{field, quote(do: term)}|types])
end
defp split_fields_and_types([], fields, types) do
{:lists.reverse(fields), :lists.reverse(types)}
end
@doc ~S""" @doc ~S"""
Defines an exception. Defines an exception.
@@ -2970,7 +3010,7 @@ defmodule Kernel do
@behaviour Exception @behaviour Exception
fields = defstruct unquote(fields) fields = defstruct unquote(fields)
@spec exception(term) :: t @spec exception(Keyword.t) :: Exception.t
def exception(args) when is_list(args) do def exception(args) when is_list(args) do
Kernel.struct(__struct__, args) Kernel.struct(__struct__, args)
end end
@@ -2978,7 +3018,7 @@ defmodule Kernel do
defoverridable exception: 1 defoverridable exception: 1
if Keyword.has_key?(fields, :message) do if Keyword.has_key?(fields, :message) do
@spec message(t) :: String.t @spec message(Exception.t) :: String.t
def message(exception) do def message(exception) do
exception.message exception.message
end end
@@ -3309,7 +3349,7 @@ defmodule Kernel do
## Sigils ## Sigils
@doc """ @doc ~S"""
Handles the sigil ~S. It simply returns a string Handles the sigil ~S. It simply returns a string
without escaping characters and without interpolations. without escaping characters and without interpolations.
@@ -3318,15 +3358,15 @@ defmodule Kernel do
iex> ~S(foo) iex> ~S(foo)
"foo" "foo"
iex> ~S(f\#{o}o) iex> ~S(f#{o}o)
"f\\\#{o}o" "f\#{o}o"
""" """
defmacro sigil_S(string, []) do defmacro sigil_S(string, []) do
string string
end end
@doc """ @doc ~S"""
Handles the sigil ~s. It returns a string as if it was double quoted Handles the sigil ~s. It returns a string as if it was double quoted
string, unescaping characters and replacing interpolations. string, unescaping characters and replacing interpolations.
@@ -3335,15 +3375,18 @@ defmodule Kernel do
iex> ~s(foo) iex> ~s(foo)
"foo" "foo"
iex> ~s(f\#{:o}o) iex> ~s(f#{:o}o)
"foo" "foo"
iex> ~s(f\#{:o}o)
"f\#{:o}o"
""" """
defmacro sigil_s({:<<>>, line, pieces}, []) do defmacro sigil_s({:<<>>, line, pieces}, []) do
{:<<>>, line, Macro.unescape_tokens(pieces)} {:<<>>, line, Macro.unescape_tokens(pieces)}
end end
@doc """ @doc ~S"""
Handles the sigil ~C. It simply returns a char list Handles the sigil ~C. It simply returns a char list
without escaping characters and without interpolations. without escaping characters and without interpolations.
@@ -3352,15 +3395,15 @@ defmodule Kernel do
iex> ~C(foo) iex> ~C(foo)
'foo' 'foo'
iex> ~C(f\#{o}o) iex> ~C(f#{o}o)
'f\\\#{o}o' 'f\#{o}o'
""" """
defmacro sigil_C({:<<>>, _line, [string]}, []) when is_binary(string) do defmacro sigil_C({:<<>>, _line, [string]}, []) when is_binary(string) do
String.to_char_list(string) String.to_char_list(string)
end end
@doc """ @doc ~S"""
Handles the sigil ~c. It returns a char list as if it were a single Handles the sigil ~c. It returns a char list as if it were a single
quoted string, unescaping characters and replacing interpolations. quoted string, unescaping characters and replacing interpolations.
@@ -3369,9 +3412,12 @@ defmodule Kernel do
iex> ~c(foo) iex> ~c(foo)
'foo' 'foo'
iex> ~c(f\#{:o}o) iex> ~c(f#{:o}o)
'foo' 'foo'
iex> ~c(f\#{:o}o)
'f\#{:o}o'
""" """
# We can skip the runtime conversion if we are # We can skip the runtime conversion if we are
@@ -3405,13 +3451,13 @@ defmodule Kernel do
quote do: Regex.compile!(unquote(binary), unquote(:binary.list_to_bin(options))) quote do: Regex.compile!(unquote(binary), unquote(:binary.list_to_bin(options)))
end end
@doc """ @doc ~S"""
Handles the sigil ~R. It returns a Regex pattern without escaping Handles the sigil ~R. It returns a Regex pattern without escaping
nor interpreting interpolations. nor interpreting interpolations.
## Examples ## Examples
iex> Regex.match?(~R(f\#{1,3}o), "f\#o") iex> Regex.match?(~R(f#{1,3}o), "f#o")
true true
""" """
@@ -3420,7 +3466,7 @@ defmodule Kernel do
Macro.escape(regex) Macro.escape(regex)
end end
@doc """ @doc ~S"""
Handles the sigil ~w. It returns a list of "words" split by whitespace. Handles the sigil ~w. It returns a list of "words" split by whitespace.
## Modifiers ## Modifiers
@@ -3431,7 +3477,7 @@ defmodule Kernel do
## Examples ## Examples
iex> ~w(foo \#{:bar} baz) iex> ~w(foo #{:bar} baz)
["foo", "bar", "baz"] ["foo", "bar", "baz"]
iex> ~w(--source test/enum_test.exs) iex> ~w(--source test/enum_test.exs)
@@ -3451,7 +3497,7 @@ defmodule Kernel do
split_words(binary, modifiers) split_words(binary, modifiers)
end end
@doc """ @doc ~S"""
Handles the sigil ~W. It returns a list of "words" split by whitespace Handles the sigil ~W. It returns a list of "words" split by whitespace
without escaping nor interpreting interpolations. without escaping nor interpreting interpolations.
@@ -3463,8 +3509,8 @@ defmodule Kernel do
## Examples ## Examples
iex> ~W(foo \#{bar} baz) iex> ~W(foo #{bar} baz)
["foo", "\\\#{bar}", "baz"] ["foo", "\#{bar}", "baz"]
""" """
defmacro sigil_W({:<<>>, _line, [string]}, modifiers) when is_binary(string) do defmacro sigil_W({:<<>>, _line, [string]}, modifiers) when is_binary(string) do
+65 -52
View File
@@ -3,7 +3,7 @@ defmodule Kernel.CLI do
@blank_config %{commands: [], output: ".", compile: [], @blank_config %{commands: [], output: ".", compile: [],
halt: true, compiler_options: [], errors: [], halt: true, compiler_options: [], errors: [],
verbose_compile: false} pa: [], pz: [], verbose_compile: false}
@doc """ @doc """
This is the API invoked by Elixir boot process. This is the API invoked by Elixir boot process.
@@ -12,9 +12,10 @@ defmodule Kernel.CLI do
argv = for arg <- argv, do: IO.chardata_to_string(arg) argv = for arg <- argv, do: IO.chardata_to_string(arg)
{config, argv} = parse_argv(argv) {config, argv} = parse_argv(argv)
:elixir_code_server.cast({:paths, config.pa, config.pz})
System.argv(argv) System.argv(argv)
run fn -> run fn _ ->
errors = process_commands(config) errors = process_commands(config)
if errors != [] do if errors != [] do
@@ -33,37 +34,19 @@ defmodule Kernel.CLI do
by escripts generated by Elixir. by escripts generated by Elixir.
""" """
def run(fun, halt \\ true) do def run(fun, halt \\ true) do
try do res = exec_fun(fun, {:ok, 0})
fun.() if elem(res, 0) == :shutdown or halt do
if halt do {_, int} = at_exit(res)
at_exit(0) System.halt(int)
System.halt(0)
end
catch
:exit, reason when is_integer(reason) ->
at_exit(reason)
System.halt(reason)
:exit, :normal ->
at_exit(0)
System.halt(0)
kind, reason ->
at_exit(1)
print_error(kind, reason, System.stacktrace)
System.halt(1)
end end
end end
@doc """ @doc false
Parses ARGV returning the CLI config and trailing args.
"""
def parse_argv(argv) do def parse_argv(argv) do
parse_argv(argv, @blank_config) parse_argv(argv, @blank_config)
end end
@doc """ @doc false
Process commands according to the parsed config from `parse_argv/1`.
Returns all errors.
"""
def process_commands(config) do def process_commands(config) do
results = Enum.map(Enum.reverse(config.commands), &process_command(&1, config)) results = Enum.map(Enum.reverse(config.commands), &process_command(&1, config))
errors = for {:error, msg} <- results, do: msg errors = for {:error, msg} <- results, do: msg
@@ -72,21 +55,48 @@ defmodule Kernel.CLI do
## Helpers ## Helpers
defp at_exit(status) do 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
for hook <- hooks do defp exec_fun(fun, res) when is_function(fun, 1) and is_tuple(res) do
try do parent = self()
hook.(status)
catch {pid, ref} =
kind, reason -> spawn_monitor fn ->
print_error(kind, reason, System.stacktrace) try do
fun.(elem(res, 1))
catch
:exit, {:shutdown, int} when is_integer(int) ->
send parent, {self, {:shutdown, int}}
exit({:shutdown, int})
:exit, reason
when reason == :normal
when reason == :shutdown
when tuple_size(reason) == 2 and elem(reason, 0) == :shutdown ->
send parent, {self, {:shutdown, 0}}
exit(reason)
kind, reason ->
stack = System.stacktrace
print_error(kind, reason, stack)
send parent, {self, {:shutdown, 1}}
:erlang.raise(kind, reason, stack)
else
_ ->
send parent, {self, res}
end
end end
end
# If an at_exit callback adds a receive do
# new hook we need to invoke it. {^pid, res} ->
unless hooks == [], do: at_exit(status) :erlang.demonitor(ref, [:flush])
res
{:DOWN, ^ref, _, _, other} ->
print_error({:EXIT, pid}, other, [])
{:shutdown, 1}
end
end end
defp shared_option?(list, config, callback) do defp shared_option?(list, config, callback) do
@@ -103,7 +113,8 @@ defmodule Kernel.CLI do
IO.puts :stderr, Exception.format(kind, reason, prune_stacktrace(trace)) IO.puts :stderr, Exception.format(kind, reason, prune_stacktrace(trace))
end 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 defp prune_stacktrace([{mod, _, _, _}|t]) when mod in @elixir_internals do
prune_stacktrace(t) prune_stacktrace(t)
@@ -129,13 +140,15 @@ defmodule Kernel.CLI do
end end
defp parse_shared(["-pa", h|t], config) do defp parse_shared(["-pa", h|t], config) do
add_code_path(h, &Code.prepend_path/1) paths = expand_code_path(h)
parse_shared t, config Enum.each(paths, &:code.add_patha/1)
parse_shared t, %{config | pa: config.pa ++ paths}
end end
defp parse_shared(["-pz", h|t], config) do defp parse_shared(["-pz", h|t], config) do
add_code_path(h, &Code.append_path/1) paths = expand_code_path(h)
parse_shared t, config Enum.each(paths, &:code.add_pathz/1)
parse_shared t, %{config | pz: config.pz ++ paths}
end end
defp parse_shared(["--app", h|t], config) do defp parse_shared(["--app", h|t], config) do
@@ -162,7 +175,7 @@ defmodule Kernel.CLI do
parse_shared t, config parse_shared t, config
end end
defp parse_shared([erl|t], config) when erl in ["--detached", "--hidden"] do defp parse_shared([erl|t], config) when erl in ["--detached", "--hidden", "--werl"] do
parse_shared t, config parse_shared t, config
end end
@@ -170,12 +183,11 @@ defmodule Kernel.CLI do
{list, config} {list, config}
end end
defp expand_code_path(path) do
defp add_code_path(path, fun) do
path = Path.expand(path) path = Path.expand(path)
case Path.wildcard(path) do case Path.wildcard(path) do
[] -> fun.(path) [] -> [to_char_list(path)]
list -> Enum.each(list, fun) list -> Enum.map(list, &to_char_list/1)
end end
end end
@@ -352,7 +364,8 @@ defmodule Kernel.CLI do
end end
defp process_command({:compile, patterns}, config) do defp process_command({:compile, patterns}, config) do
:filelib.ensure_dir(:filename.join(config.output, ".")) # If ensuring the dir returns an error no files will be found.
_ = :filelib.ensure_dir(:filename.join(config.output, "."))
case filter_multiple_patterns(patterns) do case filter_multiple_patterns(patterns) do
{:ok, []} -> {:ok, []} ->
@@ -396,7 +409,7 @@ defmodule Kernel.CLI do
end end
defp wrapper(fun) do defp wrapper(fun) do
fun.() _ = fun.()
:ok :ok
end end
@@ -407,8 +420,8 @@ defmodule Kernel.CLI do
# the actual Elixir executable. # the actual Elixir executable.
case :os.type() do case :os.type() do
{:win32, _} -> {:win32, _} ->
exec = Path.rootname(exec) base = Path.rootname(exec)
if File.regular?(exec), do: exec if File.regular?(base), do: base, else: exec
_ -> _ ->
exec exec
end end
+2 -2
View File
@@ -25,11 +25,11 @@ defmodule Kernel.ErrorHandler do
defp ensure_loaded(module) do defp ensure_loaded(module) do
case Code.ensure_loaded(module) do case Code.ensure_loaded(module) do
{:module, _} -> [] {:module, _} -> :ok
{:error, _} -> {:error, _} ->
parent = :erlang.get(:elixir_compiler_pid) parent = :erlang.get(:elixir_compiler_pid)
ref = :erlang.make_ref ref = :erlang.make_ref
send parent, {:waiting, module, self(), ref, module} send parent, {:waiting, :module, self(), ref, module}
:erlang.garbage_collect(self) :erlang.garbage_collect(self)
receive do receive do
{^ref, :ready} -> :ok {^ref, :ready} -> :ok
+46 -38
View File
@@ -18,20 +18,22 @@ defmodule Kernel.LexicalTracker do
Returns all remotes linked to in this lexical scope. Returns all remotes linked to in this lexical scope.
""" """
def remotes(arg) do def remotes(arg) do
# If the module is compiled from a function, its lexical ets = :gen_server.call(to_pid(arg), :ets, @timeout)
# scope may be long gone, so it has no associated PID. :ets.match(ets, {:"$1", :_, :_}) |> List.flatten
if pid = to_pid(arg) do end
ets = :gen_server.call(pid, :ets, @timeout)
:ets.match(ets, {:"$1", :_, :_}) |> List.flatten @doc """
else Gets the destination the lexical scope is meant to
[] compile to.
end """
def dest(arg) do
:gen_server.call(to_pid(arg), :dest, @timeout)
end end
defp to_pid(pid) when is_pid(pid), do: pid defp to_pid(pid) when is_pid(pid), do: pid
defp to_pid(mod) when is_atom(mod) do defp to_pid(mod) when is_atom(mod) do
table = :elixir_module.data_table(mod) table = :elixir_module.data_table(mod)
[{_, val}] = :ets.lookup(table, :__lexical_tracker) [{_, val}] = :ets.lookup(table, {:elixir, :lexical_tracker})
val val
end end
@@ -39,9 +41,8 @@ defmodule Kernel.LexicalTracker do
# Starts the tracker and returns its pid. # Starts the tracker and returns its pid.
@doc false @doc false
def start_link do def start_link(dest) do
{:ok, pid} = :gen_server.start_link(__MODULE__, [], []) :gen_server.start_link(__MODULE__, dest, [])
pid
end end
@doc false @doc false
@@ -96,62 +97,69 @@ defmodule Kernel.LexicalTracker do
# Callbacks # Callbacks
def init(dest) do
def init([]) do {:ok, {:ets.new(:lexical, [:protected]), dest}}
{:ok, :ets.new(:lexical, [:protected])}
end end
def handle_call(:ets, _from, d) do @doc false
{:reply, d, d} def handle_call(:ets, _from, {d, dest}) do
{:reply, d, {d, dest}}
end end
def handle_call(request, _from, d) do def handle_call(:dest, _from, {d, dest}) do
{:stop, {:bad_call, request}, d} {:reply, dest, {d, dest}}
end end
def handle_cast({:remote_dispatch, module}, d) do def handle_call(request, _from, {d, dest}) do
{:stop, {:bad_call, request}, {d, dest}}
end
def handle_cast({:remote_dispatch, module}, {d, dest}) do
add_module(d, module) add_module(d, module)
{:noreply, d} {:noreply, {d, dest}}
end end
def handle_cast({:import_dispatch, module}, d) do def handle_cast({:import_dispatch, module}, {d, dest}) do
add_dispatch(d, module, @import) add_dispatch(d, module, @import)
{:noreply, d} {:noreply, {d, dest}}
end end
def handle_cast({:alias_dispatch, module}, d) do def handle_cast({:alias_dispatch, module}, {d, dest}) do
add_dispatch(d, module, @alias) add_dispatch(d, module, @alias)
{:noreply, d} {:noreply, {d, dest}}
end end
def handle_cast({:add_import, module, line, warn}, d) do def handle_cast({:add_import, module, line, warn}, {d, dest}) do
add_directive(d, module, line, warn, @import) add_directive(d, module, line, warn, @import)
{:noreply, d} {:noreply, {d, dest}}
end end
def handle_cast({:add_alias, module, line, warn}, d) do def handle_cast({:add_alias, module, line, warn}, {d, dest}) do
add_directive(d, module, line, warn, @alias) add_directive(d, module, line, warn, @alias)
{:noreply, d} {:noreply, {d, dest}}
end end
def handle_cast(:stop, d) do def handle_cast(:stop, {d, dest}) do
{:stop, :normal, d} {:stop, :normal, {d, dest}}
end end
def handle_cast(msg, d) do def handle_cast(msg, {d, dest}) do
{:stop, {:bad_cast, msg}, d} {:stop, {:bad_cast, msg}, {d, dest}}
end end
def handle_info(_msg, d) do @doc false
{:noreply, d} def handle_info(_msg, {d, dest}) do
{:noreply, {d, dest}}
end end
def terminate(_reason, _d) do @doc false
def terminate(_reason, _state) do
:ok :ok
end end
def code_change(_old, d, _extra) do @doc false
{:ok, d} def code_change(_old, state, _extra) do
{:ok, state}
end end
# Callbacks helpers # Callbacks helpers
+49 -38
View File
@@ -15,7 +15,7 @@ defmodule Kernel.ParallelCompiler do
is set to `true` and there is a warning, this function will fail is set to `true` and there is a warning, this function will fail
with an exception. with an exception.
This function receives a set of callbacks as options: This function accepts the following options:
* `:each_file` - for each file compiled, invokes the callback passing the * `:each_file` - for each file compiled, invokes the callback passing the
file file
@@ -23,57 +23,61 @@ defmodule Kernel.ParallelCompiler do
* `:each_module` - for each module compiled, invokes the callback passing * `:each_module` - for each module compiled, invokes the callback passing
the file, module and the module bytecode the file, module and the module bytecode
The compiler doesn't care about the return values of the callbacks. * `:dest` - the destination directory for the beam files. When using `files/2`,
this information is only used to properly annotate the beam files before
they are loaded into memory. If you want a file to actually be writen to
`dest`, use `files_to_path/3` instead.
Returns the modules generated by each compiled file. Returns the modules generated by each compiled file.
""" """
def files(files, callbacks \\ []) def files(files, options \\ [])
def files(files, callbacks) when is_list(callbacks) do def files(files, options) when is_list(options) do
spawn_compilers(files, nil, callbacks) spawn_compilers(files, nil, options)
end end
@doc """ @doc """
Compiles the given files to the given path. Compiles the given files to the given path.
Read `files/2` for more information. Read `files/2` for more information.
""" """
def files_to_path(files, path, callbacks \\ []) def files_to_path(files, path, options \\ [])
def files_to_path(files, path, callbacks) when is_binary(path) and is_list(callbacks) do def files_to_path(files, path, options) when is_binary(path) and is_list(options) do
spawn_compilers(files, path, callbacks) spawn_compilers(files, path, options)
end end
defp spawn_compilers(files, path, callbacks) do defp spawn_compilers(files, path, options) do
Code.ensure_loaded(Kernel.ErrorHandler) true = Code.ensure_loaded?(Kernel.ErrorHandler)
compiler_pid = self() compiler_pid = self()
:elixir_code_server.cast({:reset_warnings, compiler_pid}) :elixir_code_server.cast({:reset_warnings, compiler_pid})
schedulers = max(:erlang.system_info(:schedulers_online), 2) schedulers = max(:erlang.system_info(:schedulers_online), 2)
result = spawn_compilers(files, files, path, callbacks, [], [], schedulers, []) result = spawn_compilers(files, files, path, options, [], [], schedulers, [])
# In case --warning-as-errors is enabled and there was a warning, # In case --warning-as-errors is enabled and there was a warning,
# compilation status will be set to error and we fail with CompileError # compilation status will be set to error and we fail with CompileError
case :elixir_code_server.call({:compilation_status, compiler_pid}) do case :elixir_code_server.call({:compilation_status, compiler_pid}) do
:ok -> result :ok -> result
:error -> exit(1) :error -> exit({:shutdown, 1})
end end
end end
# We already have 4 currently running, don't spawn new ones # We already have 4 currently running, don't spawn new ones
defp spawn_compilers(entries, original, output, callbacks, waiting, queued, schedulers, result) when defp spawn_compilers(entries, original, output, options, waiting, queued, schedulers, result) when
length(queued) - length(waiting) >= schedulers do length(queued) - length(waiting) >= schedulers do
wait_for_messages(entries, original, output, callbacks, waiting, queued, schedulers, result) wait_for_messages(entries, original, output, options, waiting, queued, schedulers, result)
end end
# Release waiting processes # Release waiting processes
defp spawn_compilers([h|t], original, output, callbacks, waiting, queued, schedulers, result) when is_pid(h) do defp spawn_compilers([h|t], original, output, options, waiting, queued, schedulers, result) when is_pid(h) do
{_kind, ^h, ref, _module} = List.keyfind(waiting, h, 1) {_kind, ^h, ref, _module} = List.keyfind(waiting, h, 1)
send h, {ref, :ready} send h, {ref, :ready}
waiting = List.keydelete(waiting, h, 1) waiting = List.keydelete(waiting, h, 1)
spawn_compilers(t, original, output, callbacks, waiting, queued, schedulers, result) spawn_compilers(t, original, output, options, waiting, queued, schedulers, result)
end end
# Spawn a compiler for each file in the list until we reach the limit # Spawn a compiler for each file in the list until we reach the limit
defp spawn_compilers([h|t], original, output, callbacks, waiting, queued, schedulers, result) do defp spawn_compilers([h|t], original, output, options, waiting, queued, schedulers, result) do
parent = self() parent = self()
{pid, ref} = {pid, ref} =
@@ -87,43 +91,43 @@ defmodule Kernel.ParallelCompiler do
:erlang.process_flag(:error_handler, Kernel.ErrorHandler) :erlang.process_flag(:error_handler, Kernel.ErrorHandler)
exit(try do exit(try do
if output do _ = if output do
:elixir_compiler.file_to_path(h, output) :elixir_compiler.file_to_path(h, output)
else else
:elixir_compiler.file(h) :elixir_compiler.file(h, Keyword.get(options, :dest))
end end
{:compiled, h} {:shutdown, h}
catch catch
kind, reason -> kind, reason ->
{:failure, kind, reason, System.stacktrace} {:failure, kind, reason, System.stacktrace}
end) end)
end end
spawn_compilers(t, original, output, callbacks, waiting, spawn_compilers(t, original, output, options, waiting,
[{pid, ref, h}|queued], schedulers, result) [{pid, ref, h}|queued], schedulers, result)
end end
# No more files, nothing waiting, queue is empty, we are done # No more files, nothing waiting, queue is empty, we are done
defp spawn_compilers([], _original, _output, _callbacks, [], [], _schedulers, result) do defp spawn_compilers([], _original, _output, _options, [], [], _schedulers, result) do
for {:module, mod} <- result, do: mod for {:module, mod} <- result, do: mod
end end
# Queued x, waiting for x: POSSIBLE ERROR! Release processes so we get the failures # Queued x, waiting for x: POSSIBLE ERROR! Release processes so we get the failures
defp spawn_compilers([], original, output, callbacks, waiting, queued, schedulers, result) when length(waiting) == length(queued) do defp spawn_compilers([], original, output, options, waiting, queued, schedulers, result) when length(waiting) == length(queued) do
Enum.each queued, fn {child, _, _} -> Enum.each queued, fn {child, _, _} ->
{_kind, ^child, ref, _module} = List.keyfind(waiting, child, 1) {_kind, ^child, ref, _module} = List.keyfind(waiting, child, 1)
send child, {ref, :release} send child, {ref, :release}
end end
wait_for_messages([], original, output, callbacks, waiting, queued, schedulers, result) wait_for_messages([], original, output, options, waiting, queued, schedulers, result)
end end
# No more files, but queue and waiting are not full or do not match # No more files, but queue and waiting are not full or do not match
defp spawn_compilers([], original, output, callbacks, waiting, queued, schedulers, result) do defp spawn_compilers([], original, output, options, waiting, queued, schedulers, result) do
wait_for_messages([], original, output, callbacks, waiting, queued, schedulers, result) wait_for_messages([], original, output, options, waiting, queued, schedulers, result)
end end
# Wait for messages from child processes # Wait for messages from child processes
defp wait_for_messages(entries, original, output, callbacks, waiting, queued, schedulers, result) do defp wait_for_messages(entries, original, output, options, waiting, queued, schedulers, result) do
receive do receive do
{:struct_available, module} -> {:struct_available, module} ->
available = for {:struct, pid, _, waiting_module} <- waiting, available = for {:struct, pid, _, waiting_module} <- waiting,
@@ -131,11 +135,11 @@ defmodule Kernel.ParallelCompiler do
not pid in entries, not pid in entries,
do: pid do: pid
spawn_compilers(available ++ entries, original, output, callbacks, spawn_compilers(available ++ entries, original, output, options,
waiting, queued, schedulers, [{:struct, module}|result]) waiting, queued, schedulers, [{:struct, module}|result])
{:module_available, child, ref, file, module, binary} -> {:module_available, child, ref, file, module, binary} ->
if callback = Keyword.get(callbacks, :each_module) do if callback = Keyword.get(options, :each_module) do
callback.(file, module, binary) callback.(file, module, binary)
end end
@@ -147,7 +151,7 @@ defmodule Kernel.ParallelCompiler do
not pid in entries, not pid in entries,
do: pid do: pid
spawn_compilers(available ++ entries, original, output, callbacks, spawn_compilers(available ++ entries, original, output, options,
waiting, queued, schedulers, [{:module, module}|result]) waiting, queued, schedulers, [{:module, module}|result])
{:waiting, kind, child, ref, on} -> {:waiting, kind, child, ref, on} ->
@@ -160,10 +164,10 @@ defmodule Kernel.ParallelCompiler do
waiting = [{kind, child, ref, on}|waiting] waiting = [{kind, child, ref, on}|waiting]
end end
spawn_compilers(entries, original, output, callbacks, waiting, queued, schedulers, result) 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(callbacks, :each_file) do if callback = Keyword.get(options, :each_file) do
callback.(file) callback.(file)
end end
@@ -172,21 +176,27 @@ defmodule Kernel.ParallelCompiler do
new_entries = List.delete(entries, down_pid) new_entries = List.delete(entries, down_pid)
new_queued = List.keydelete(queued, down_pid, 0) new_queued = List.keydelete(queued, down_pid, 0)
new_waiting = List.keydelete(waiting, down_pid, 1) new_waiting = List.keydelete(waiting, down_pid, 1)
spawn_compilers(new_entries, original, output, callbacks, new_waiting, new_queued, schedulers, result) spawn_compilers(new_entries, original, output, options, new_waiting, new_queued, schedulers, result)
{:DOWN, down_ref, :process, _down_pid, reason} -> {:DOWN, down_ref, :process, _down_pid, reason} ->
handle_failure(down_ref, reason, entries, waiting, queued) handle_failure(down_ref, reason, entries, waiting, queued)
wait_for_messages(entries, original, output, callbacks, waiting, queued, schedulers, result) wait_for_messages(entries, original, output, options, waiting, queued, schedulers, result)
end end
end end
defp handle_failure(ref, reason, entries, waiting, queued) do defp handle_failure(ref, reason, entries, waiting, queued) do
if file = find_failure(ref, queued) do if file = find_failure(ref, queued) do
print_failure(file, reason) print_failure(file, reason)
if all_missing?(entries, waiting, queued) do if all_missing?(entries, waiting, queued) do
collect_failures(queued, length(queued) - 1) collect_failures(queued, length(queued) - 1)
end end
exit(1)
Enum.each queued, fn {child, _, _} ->
Process.exit(child, :kill)
end
exit({:shutdown, 1})
end end
end end
@@ -197,7 +207,7 @@ defmodule Kernel.ParallelCompiler do
end end
end end
defp print_failure(_file, {:compiled, _}) do defp print_failure(_file, {:shutdown, _}) do
:ok :ok
end end
@@ -211,7 +221,8 @@ defmodule Kernel.ParallelCompiler do
IO.puts Exception.format(:exit, reason, []) IO.puts Exception.format(:exit, reason, [])
end 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 defp prune_stacktrace([{mod, _, _, _}|t]) when mod in @elixir_internals do
prune_stacktrace(t) prune_stacktrace(t)
+25 -50
View File
@@ -114,7 +114,7 @@ defmodule Kernel.SpecialForms do
defmodule User do defmodule User do
def __struct__ do def __struct__ do
%{name: "josé", age: 27} %{name: "john", age: 27}
end end
end end
@@ -122,7 +122,7 @@ defmodule Kernel.SpecialForms do
`Kernel.defstruct/1` macro: `Kernel.defstruct/1` macro:
defmodule User do defmodule User do
defstruct name: "josé", age: 27 defstruct name: "john", age: 27
end end
Now a struct can be created as follows: Now a struct can be created as follows:
@@ -132,13 +132,13 @@ defmodule Kernel.SpecialForms do
Underneath a struct is just a map with a `__struct__` field Underneath a struct is just a map with a `__struct__` field
pointing to the `User` module: pointing to the `User` module:
%User{} == %{__struct__: User, name: "josé", age: 27} %User{} == %{__struct__: User, name: "john", age: 27}
A struct also validates that the given keys are part of the defined A struct also validates that the given keys are part of the defined
struct. The example below will fail because there is no key struct. The example below will fail because there is no key
`:full_name` in the `User` struct: `:full_name` in the `User` struct:
%User{full_name: "José Valim"} %User{full_name: "john doe"}
Note that a struct specifies a minimum set of keys required Note that a struct specifies a minimum set of keys required
for operations. Other keys can be added to structs via the for operations. Other keys can be added to structs via the
@@ -242,14 +242,14 @@ defmodule Kernel.SpecialForms do
Here, `val` is interpreted as unsigned. Here, `val` is interpreted as unsigned.
The endianness of a segment can be big, little or native (the The endianness of a segment can be big, little or native (the
latter meaning it will be resolved at VM load time). Passing latter meaning it will be resolved at VM load time). Many options
many options can be done by giving a list: can be given by using `-` as separator:
<<102 :: [integer, native], rest :: binary>> <<102 :: integer-native, rest :: binary>>
Or: Or:
<<102 :: [unsigned, big, integer], rest :: binary>> <<102 :: unsigned-big-integer, rest :: binary>>
And so on. And so on.
@@ -271,7 +271,7 @@ defmodule Kernel.SpecialForms do
We can also match by specifying size and unit explicitly: We can also match by specifying size and unit explicitly:
iex> <<102, _rest :: [size(2), unit(8)]>> = "foo" iex> <<102, _rest :: size(2)-unit(8)>> = "foo"
"foo" "foo"
However, if we expect a size of 32, it won't match: However, if we expect a size of 32, it won't match:
@@ -286,12 +286,15 @@ defmodule Kernel.SpecialForms do
in a binary match can use the default size (all others must in a binary match can use the default size (all others must
have their size specified explicitly). have their size specified explicitly).
Size can also be specified using a syntax shortcut. Instead of Size and unit can also be specified using a syntax shortcut
writing `size(8)`, one can write just `8` and it will be interpreted when passing integer values:
as `size(8)`
iex> << 1 :: 3 >> == << 1 :: size(3) >> << x :: 8 >> == << x :: size(8) >>
true << x :: 8 * 4 >> == << x :: size(8)-unit(4) >>
<< x :: _ * 4 >> == << x :: unit(4) >>
This syntax reflects the fact the effective size is given by
multiplying the size by the unit.
For floats, `size * unit` must result in 32 or 64, corresponding For floats, `size * unit` must result in 32 or 64, corresponding
to binary32 and binary64, respectively. to binary32 and binary64, respectively.
@@ -340,28 +343,9 @@ defmodule Kernel.SpecialForms do
Note that `Kernel."HELLO"` will be treated as a remote call and not an alias. Note that `Kernel."HELLO"` will be treated as a remote call and not an alias.
This choice was done so every time single- or double-quotes are used, we have This choice was done so every time single- or double-quotes are used, we have
a remote call irregardless of the quote contents. This decision is also reflected a remote call regardless of the quote contents. This decision is also reflected
in the quoted expressions discussed below. in the quoted expressions discussed below.
## Runtime (dynamic) behaviour
The result returned by `.` is always specified by the right-side:
iex> x = String
iex> x.downcase("FOO")
"foo"
iex> x.Sample
String.Sample
In case the right-side is also dynamic, `.`'s behaviour can be reproduced
at runtime via `apply/3` and `Module.concat/2`:
iex> apply(:erlang, :+, [1,2])
3
iex> Module.concat(Kernel, Sample)
Kernel.Sample
## Quoted expression ## Quoted expression
When `.` is used, the quoted expression may take two distinct When `.` is used, the quoted expression may take two distinct
@@ -1109,7 +1093,7 @@ defmodule Kernel.SpecialForms do
## Examples ## Examples
Imagine the situation you have a variable `name` and Imagine the situation you have a variable `value` and
you want to inject it inside some quote. The first attempt you want to inject it inside some quote. The first attempt
would be: would be:
@@ -1171,10 +1155,10 @@ defmodule Kernel.SpecialForms do
Note generators can also be used to filter as it removes any value Note generators can also be used to filter as it removes any value
that doesn't match the left side of `<-`: that doesn't match the left side of `<-`:
iex> for {:user, name} <- [user: "jose", admin: "john", user: "eric"] do iex> for {:user, name} <- [user: "john", admin: "john", user: "meg"] do
...> String.upcase(name) ...> String.upcase(name)
...> end ...> end
["JOSE", "ERIC"] ["JOHN", "MEG"]
Bitstring generators are also supported and are very useful when you Bitstring generators are also supported and are very useful when you
need to organize bitstring streams: need to organize bitstring streams:
@@ -1324,22 +1308,13 @@ defmodule Kernel.SpecialForms do
On the other hand, aliases holds some properties: On the other hand, aliases holds some properties:
1. The head element of aliases can be any term. 1. The head element of aliases can be any term that must expand to
an atom at compilation time.
2. The tail elements of aliases are guaranteed to always be atoms. 2. The tail elements of aliases are guaranteed to always be atoms.
3. When the head element of aliases is the atom `:Elixir`, no expansion happen. 3. When the head element of aliases is the atom `:Elixir`, no expansion happen.
4. When the head element of aliases is not an atom, it is expanded at runtime:
quote do: some_var.Foo
{:__aliases__, [], [{:some_var, [], Elixir}, :Foo]}
Since `some_var` is not available at compilation time, the compiler
expands such expression to:
Module.concat [some_var, Foo]
""" """
defmacro __aliases__(args) defmacro __aliases__(args)
@@ -1513,9 +1488,9 @@ defmodule Kernel.SpecialForms do
The catch clause can be used to catch throws values and exits. The catch clause can be used to catch throws values and exits.
try do try do
exit(1) exit(:shutdown)
catch catch
:exit, 1 -> IO.puts "Exited with 1" :exit, :shutdown -> IO.puts "Exited with shutdown reason"
end end
try do try do
+180 -55
View File
@@ -1,5 +1,5 @@
defmodule Kernel.Typespec do defmodule Kernel.Typespec do
@moduledoc """ @moduledoc ~S"""
Provides macros and functions for working with typespecs. Provides macros and functions for working with typespecs.
Elixir comes with a notation for declaring types and specifications. Elixir is Elixir comes with a notation for declaring types and specifications. Elixir is
@@ -38,6 +38,7 @@ defmodule Kernel.Typespec do
| Fun | Fun
| Integer | Integer
| List | List
| Map
| Tuple | Tuple
| Union | Union
| UserDefined # Described in section "Defining a type" | UserDefined # Described in section "Defining a type"
@@ -65,16 +66,33 @@ defmodule Kernel.Typespec do
| nonempty_list(Type) # proper non-empty list | nonempty_list(Type) # proper non-empty list
| [] # empty list | [] # empty list
| [Type] # shorthand for list(Type) | [Type] # shorthand for list(Type)
| [...] # shorthand for nonempty_list()
| [Type, ...] # shorthand for nonempty_list(Type) | [Type, ...] # shorthand for nonempty_list(Type)
| [Keyword]
Tuple :: tuple # a tuple of any size Map :: map() # map of any size
| {} # empty tuple | %{} # map of any size
| %Struct{} # struct (see defstruct/1)
| %Struct{Keyword}
| %{Keyword}
| %{Pairs}
Tuple :: tuple # a tuple of any size
| {} # empty tuple
| {TList} | {TList}
| record(Atom) # record (see Record)
| record(Atom, Keyword)
Keyword :: ElixirAtom: Type
| ElixirAtom: Type, Keyword
Pairs :: Type => Type
| Type => Type, Pairs
TList :: Type TList :: Type
| Type, TList | Type, TList
Union :: Type1 | Type2 Union :: Type | Type
### Bit strings ### Bit strings
@@ -104,36 +122,38 @@ defmodule Kernel.Typespec do
## Built-in types ## Built-in types
Built-in type | Defined as Built-in type | Defined as
:-------------------- | :--------- :-------------------- | :---------
`term` | `any` `term` | `any`
`binary` | `<< _ :: _ * 8 >>` `binary` | `<< _ :: _ * 8 >>`
`bitstring` | `<< _ :: _ * 1 >>` `bitstring` | `<< _ :: _ * 1 >>`
`boolean` | `false` &#124; `true` `boolean` | `false` \| `true`
`byte` | `0..255` `byte` | `0..255`
`char` | `0..0xffff` `char` | `0..0x10ffff`
`number` | `integer` &#124; `float` `number` | `integer` \| `float`
`list` | `[any]` `char_list` | `[char]`
`maybe_improper_list` | `maybe_improper_list(any, any)` `list` | `[any]`
`nonempty_list` | `nonempty_list(any)` `maybe_improper_list` | `maybe_improper_list(any, any)`
`iodata` | `iolist` &#124; `binary` `nonempty_list` | `nonempty_list(any)`
`iolist` | `maybe_improper_list(byte` &#124; `binary` &#124; `iolist, binary` &#124; `[])` `iodata` | `iolist` \| `binary`
`module` | `atom` `iolist` | `maybe_improper_list(byte` \| `binary` \| `iolist, binary` \| `[])`
`mfa` | `{atom, atom, arity}` `module` | `atom` \| `tuple`
`arity` | `0..255` `mfa` | `{atom, atom, arity}`
`node` | `atom` `arity` | `0..255`
`timeout` | `:infinity` &#124; `non_neg_integer` `node` | `atom`
`no_return` | `none` `timeout` | `:infinity` \| `non_neg_integer`
`fun` | `(... -> any)` `no_return` | `none`
`fun` | `(... -> any)`
Some built-in types cannot be expressed with valid syntax according to the Some built-in types cannot be expressed with valid syntax according to the
language defined above. language defined above.
Built-in type | Can be interpreted as Built-in type | Can be interpreted as
:---------------- | :-------------------- :---------------- | :--------------------
`non_neg_integer` | `0..` `non_neg_integer` | `0..`
`pos_integer` | `1..` `pos_integer` | `1..`
`neg_integer` | `..-1` `neg_integer` | `..-1`
Types defined in other modules are referred to as "remote types", they are Types defined in other modules are referred to as "remote types", they are
referenced as `Module.type_name` (ex. `Enum.t` or `String.t`). referenced as `Module.type_name` (ex. `Enum.t` or `String.t`).
@@ -488,9 +508,9 @@ defmodule Kernel.Typespec do
do: type_to_signature(other) do: type_to_signature(other)
@doc false @doc false
def type_to_signature({:::, _, [{name, _, nil}, _]}), def type_to_signature({:::, _, [{name, _, context}, _]}) when is_atom(name) and is_atom(context),
do: {name, 0} do: {name, 0}
def type_to_signature({:::, _, [{name, _, args}, _]}), def type_to_signature({:::, _, [{name, _, args}, _]}) when is_atom(name),
do: {name, length(args)} do: {name, length(args)}
## Macro callbacks ## Macro callbacks
@@ -536,7 +556,7 @@ defmodule Kernel.Typespec do
if not export and doc do if not export and doc do
:elixir_errors.warn(caller.line, caller.file, "type #{name}/#{arity} is private, " <> :elixir_errors.warn(caller.line, caller.file, "type #{name}/#{arity} is private, " <>
"@typedoc's are always discarded for private types\n") "@typedoc's are always discarded for private types")
end end
{{kind, {name, arity}, type}, caller.line, export, doc} {{kind, {name, arity}, type}, caller.line, export, doc}
@@ -624,6 +644,10 @@ defmodule Kernel.Typespec do
[] []
end 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 defp typespec_to_ast({:type, line, :tuple, :any}) do
{:tuple, [line: line], []} {:tuple, [line: line], []}
end end
@@ -640,13 +664,30 @@ defmodule Kernel.Typespec do
end end
end end
defp typespec_to_ast({:type, _line, :list, args}) do defp typespec_to_ast({:type, line, :list, []}) do
for arg <- args, do: typespec_to_ast(arg) {:list, [line: line], []}
end
defp typespec_to_ast({:type, _line, :list, [arg]}) do
[typespec_to_ast(arg)]
end
defp typespec_to_ast({:type, line, :nonempty_list, []}) do
[{:..., [line: line], nil}]
end
defp typespec_to_ast({:type, line, :nonempty_list, [arg]}) do
[typespec_to_ast(arg), {:..., [line: line], nil}]
end end
defp typespec_to_ast({:type, line, :map, fields}) do defp typespec_to_ast({:type, line, :map, fields}) do
fields = Enum.map fields, fn {:type, _, :map_field_assoc, k, v} -> fields = Enum.map fields, fn
{typespec_to_ast(k), typespec_to_ast(v)} # 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 end
{struct, fields} = Keyword.pop(fields, :__struct__) {struct, fields} = Keyword.pop(fields, :__struct__)
@@ -690,7 +731,11 @@ defmodule Kernel.Typespec do
end end
defp typespec_to_ast({:type, line, :range, [left, right]}) do defp typespec_to_ast({:type, line, :range, [left, right]}) do
{:"..", [line: line], [typespec_to_ast(left), typespec_to_ast(right)]} {:.., [line: line], [typespec_to_ast(left), typespec_to_ast(right)]}
end
defp typespec_to_ast({:type, _line, nil, []}) do
[]
end end
defp typespec_to_ast({:type, line, name, args}) do defp typespec_to_ast({:type, line, name, args}) do
@@ -747,9 +792,9 @@ defmodule Kernel.Typespec do
defp erl_to_ex_var(var) do defp erl_to_ex_var(var) do
case Atom.to_string(var) do case Atom.to_string(var) do
<<"_", c :: [binary, size(1)], rest :: binary>> -> <<"_", c :: binary-size(1), rest :: binary>> ->
String.to_atom("_#{String.downcase(c)}#{rest}") String.to_atom("_#{String.downcase(c)}#{rest}")
<<c :: [binary, size(1)], rest :: binary>> -> <<c :: binary-size(1), rest :: binary>> ->
String.to_atom("#{String.downcase(c)}#{rest}") String.to_atom("#{String.downcase(c)}#{rest}")
end end
end end
@@ -785,19 +830,79 @@ defmodule Kernel.Typespec do
## Handle maps and structs ## Handle maps and structs
defp typespec({:%{}, meta, fields}, vars, caller) do defp typespec({:%{}, meta, fields}, vars, caller) do
fields = :lists.map(fn {k, v} -> fields =
{:type, line(meta), :map_field_assoc, typespec(k, vars, caller), typespec(v, vars, caller)} if :erlang.system_info(:otp_release) >= '18' do
end, fields) :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} {:type, line(meta), :map, fields}
end end
defp typespec({:%, _, [name, {:%{}, meta, fields}]}, vars, caller) do defp typespec({:%, _, [name, {:%{}, meta, fields}]}, vars, caller) do
typespec({:%{}, meta, [{:__struct__, name}|fields]}, vars, caller) module = Macro.expand(name, caller)
struct =
if module == caller.module do
Module.get_attribute(module, :struct) ||
compile_error(caller, "struct is not defined for #{Macro.to_string(name)}")
else
module.__struct__
end
struct =
:lists.map(fn {field, _} ->
{field, quote do: term()}
end, Map.to_list(struct))
:lists.foreach(fn {field, _} ->
unless Keyword.has_key?(struct, field) do
compile_error(caller, "undefined field #{field} on struct #{Macro.to_string(name)}")
end
end, fields)
fields = Keyword.merge(struct, [__struct__: module] ++ fields)
typespec({:%{}, meta, fields}, vars, caller)
end
# Handle records
defp typespec({:record, meta, [atom]}, vars, caller) do
typespec({:record, meta, [atom, []]}, vars, caller)
end
defp typespec({:record, meta, [atom, fields]}, vars, caller) do
case Macro.expand({atom, [], [{atom, [], []}]}, caller) do
keyword when is_list(keyword) ->
keyword =
:lists.map(fn {field, _} ->
{field, quote do: term()}
end, keyword)
:lists.foreach(fn {field, _} ->
unless Keyword.has_key?(keyword, field) do
compile_error(caller, "undefined field #{field} on record #{inspect atom}")
end
end, fields)
fields = Keyword.merge(keyword, fields)
types = Keyword.values(fields)
typespec({:{}, meta, [atom|types]}, vars, caller)
_ ->
compile_error(caller, "unknown record #{inspect atom}")
end
end end
# Handle ranges # Handle ranges
defp typespec({:.., meta, args}, vars, caller) do 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 end
# Handle special forms # Handle special forms
@@ -866,11 +971,11 @@ defmodule Kernel.Typespec do
end end
# Handle local calls # Handle local calls
defp typespec({:string, meta, arguments}, vars, caller) do defp typespec({type, meta, arguments}, vars, caller) when type in [:string, :nonempty_string] do
:elixir_errors.warn caller.line, caller.file, "string() type use is discouraged. For character lists, use " <> :elixir_errors.warn caller.line, caller.file, "#{type}() type use is discouraged. For character lists, use " <>
"char_list() type, for strings, String.t()\n#{Exception.format_stacktrace(Macro.Env.stacktrace(caller))}" "char_list() type, for strings, String.t()\n#{Exception.format_stacktrace(Macro.Env.stacktrace(caller))}"
arguments = for arg <- arguments, do: typespec(arg, vars, caller) arguments = for arg <- arguments, do: typespec(arg, vars, caller)
{:type, line(meta), :string, arguments} {:type, line(meta), type, arguments}
end end
defp typespec({:char_list, _meta, []}, vars, caller) do defp typespec({:char_list, _meta, []}, vars, caller) do
@@ -881,9 +986,21 @@ defmodule Kernel.Typespec do
typespec((quote do: :elixir.as_boolean(unquote(arg))), vars, caller) typespec((quote do: :elixir.as_boolean(unquote(arg))), vars, caller)
end 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 defp typespec({name, meta, arguments}, vars, caller) do
arguments = for arg <- arguments, do: typespec(arg, vars, caller) 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 end
# Handle literals # Handle literals
@@ -899,15 +1016,19 @@ defmodule Kernel.Typespec do
typespec({nil, [], []}, vars, caller) typespec({nil, [], []}, vars, caller)
end end
defp typespec([{:..., _, atom}], vars, caller) when is_atom(atom) do
typespec({:nonempty_list, [], []}, vars, caller)
end
defp typespec([spec, {:..., _, atom}], vars, caller) when is_atom(atom) do
typespec({:nonempty_list, [], [spec]}, vars, caller)
end
defp typespec([spec], vars, caller) do defp typespec([spec], vars, caller) do
typespec({:list, [], [spec]}, vars, caller) typespec({:list, [], [spec]}, vars, caller)
end end
defp typespec([spec, {:"...", _, quoted}], vars, caller) when is_atom(quoted) do defp typespec(list, vars, caller) when is_list(list) do
typespec({:nonempty_list, [], [spec]}, vars, caller)
end
defp typespec(list, vars, caller) do
[h|t] = :lists.reverse(list) [h|t] = :lists.reverse(list)
union = :lists.foldl(fn(x, acc) -> union = :lists.foldl(fn(x, acc) ->
{:|, [], [validate_kw(x, list, caller), acc]} {:|, [], [validate_kw(x, list, caller), acc]}
@@ -915,6 +1036,10 @@ defmodule Kernel.Typespec do
typespec({:list, [], [union]}, vars, caller) typespec({:list, [], [union]}, vars, caller)
end end
defp typespec(other, _vars, caller) do
compile_error(caller, "unexpected expression in typespec: #{Macro.to_string other}")
end
## Helpers ## Helpers
defp compile_error(caller, desc) do defp compile_error(caller, desc) do
@@ -941,7 +1066,7 @@ defmodule Kernel.Typespec do
end end
end end
defp fn_args(meta, [{:"...", _, _}], _vars, _caller) do defp fn_args(meta, [{:..., _, _}], _vars, _caller) do
{:type, line(meta), :any} {:type, line(meta), :any}
end end
+46 -33
View File
@@ -9,19 +9,19 @@ defmodule Keyword do
behave exactly as a dictionary and mimic the API defined behave exactly as a dictionary and mimic the API defined
by the `Dict` behaviour. by the `Dict` behaviour.
For example, `Keyword.get` will get the first entry matching For example, `Keyword.get/3` will get the first entry matching
the given key, regardless if duplicated entries exist. the given key, regardless if duplicated entries exist.
Similarly, `Keyword.put` and `Keyword.delete` ensure all Similarly, `Keyword.put/3` and `Keyword.delete/3` ensure all
duplicated entries for a given key are removed when invoked. duplicated entries for a given key are removed when invoked.
A handful of functions exist to handle duplicated keys, in A handful of functions exist to handle duplicated keys, in
particular, `from_enum` allows creating a new keywords without particular, `Enum.into/2` allows creating new keywords without
removing duplicated keys, `get_values` returns all values for removing duplicated keys, `get_values/2` returns all values for
a given key and `delete_first` deletes just one of the existing a given key and `delete_first/2` deletes just one of the existing
entries. entries.
Since a keyword list is simply a list, all the operations defined Since a keyword list is simply a list, all the operations defined
in `Enum` and `List` can also be applied. in `Enum` and `List` can be applied.
""" """
@compile :inline_list_funcs @compile :inline_list_funcs
@@ -34,7 +34,7 @@ defmodule Keyword do
@type t(value) :: [{key, value}] @type t(value) :: [{key, value}]
@doc """ @doc """
Checks if the given argument is a keywords list or not. Checks if the given argument is a keyword list or not.
""" """
@spec keyword?(term) :: boolean @spec keyword?(term) :: boolean
def keyword?([{key, _value} | rest]) when is_atom(key) do def keyword?([{key, _value} | rest]) when is_atom(key) do
@@ -56,7 +56,7 @@ defmodule Keyword do
Creates a keyword from an enumerable. Creates a keyword from an enumerable.
Duplicated entries are removed, the latest one prevails. Duplicated entries are removed, the latest one prevails.
I.e. differently from `Enum.into(enumerable, [])`, Unlike `Enum.into(enumerable, [])`,
`Keyword.new(enumerable)` guarantees the keys are unique. `Keyword.new(enumerable)` guarantees the keys are unique.
## Examples ## Examples
@@ -76,7 +76,7 @@ defmodule Keyword do
Creates a keyword from an enumerable via the transformation function. Creates a keyword from an enumerable via the transformation function.
Duplicated entries are removed, the latest one prevails. Duplicated entries are removed, the latest one prevails.
I.e. differently from `Enum.into(enumerable, [], fun)`, Unlike `Enum.into(enumerable, [], fun)`,
`Keyword.new(enumerable, fun)` guarantees the keys are unique. `Keyword.new(enumerable, fun)` guarantees the keys are unique.
## Examples ## Examples
@@ -96,7 +96,7 @@ defmodule Keyword do
@doc """ @doc """
Gets the value for a specific `key`. Gets the value for a specific `key`.
If `key` does not exist, return default value (`nil` if no default value). If `key` does not exist, return the default value (`nil` if no default value).
If duplicated entries exist, the first one is returned. If duplicated entries exist, the first one is returned.
Use `get_values/2` to retrieve all entries. Use `get_values/2` to retrieve all entries.
@@ -124,6 +124,7 @@ defmodule Keyword do
@doc """ @doc """
Fetches the value for a specific `key` and returns it in a tuple. Fetches the value for a specific `key` and returns it in a tuple.
If the `key` does not exist, returns `:error`. If the `key` does not exist, returns `:error`.
## Examples ## Examples
@@ -135,7 +136,7 @@ defmodule Keyword do
:error :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 def fetch(keywords, key) when is_list(keywords) and is_atom(key) do
case :lists.keyfind(key, 1, keywords) do case :lists.keyfind(key, 1, keywords) do
{^key, value} -> {:ok, value} {^key, value} -> {:ok, value}
@@ -144,8 +145,9 @@ defmodule Keyword do
end end
@doc """ @doc """
Fetches the value for specific `key`. If `key` does not exist, Fetches the value for specific `key`.
a `KeyError` is raised.
If `key` does not exist, a `KeyError` is raised.
## Examples ## Examples
@@ -184,8 +186,9 @@ defmodule Keyword do
end end
@doc """ @doc """
Returns all keys from the keyword list. Duplicated Returns all keys from the keyword list.
keys appear duplicated in the final list of keys.
Duplicated keys appear duplicated in the final list of keys.
## Examples ## Examples
@@ -217,6 +220,7 @@ defmodule Keyword do
@doc """ @doc """
Deletes the entries in the keyword list for a `key` with `value`. Deletes the entries in the keyword list for a `key` with `value`.
If no `key` with `value` exists, returns the keyword list unchanged. If no `key` with `value` exists, returns the keyword list unchanged.
## Examples ## Examples
@@ -238,8 +242,9 @@ defmodule Keyword do
@doc """ @doc """
Deletes the entries in the keyword list for a specific `key`. Deletes the entries in the keyword list for a specific `key`.
If the `key` does not exist, returns the keyword list unchanged. If the `key` does not exist, returns the keyword list unchanged.
Use `delete_first` to delete just the first entry in case of Use `delete_first/2` to delete just the first entry in case of
duplicated keys. duplicated keys.
## Examples ## Examples
@@ -261,6 +266,7 @@ defmodule Keyword do
@doc """ @doc """
Deletes the first entry in the keyword list for a specific `key`. Deletes the first entry in the keyword list for a specific `key`.
If the `key` does not exist, returns the keyword list unchanged. If the `key` does not exist, returns the keyword list unchanged.
## Examples ## Examples
@@ -319,7 +325,9 @@ defmodule Keyword do
end end
@doc """ @doc """
Checks if two keywords are equal. I.e. they contain Checks if two keywords are equal.
Two keywords are considered to be equal if they contain
the same keys and those keys contain the same values. the same keys and those keys contain the same values.
## Examples ## Examples
@@ -334,8 +342,9 @@ defmodule Keyword do
end end
@doc """ @doc """
Merges two keyword lists into one. If they have duplicated Merges two keyword lists into one.
entries, the one given as second argument wins.
If they have duplicated keys, the one given in the second argument wins.
## Examples ## Examples
@@ -350,8 +359,9 @@ defmodule Keyword do
end end
@doc """ @doc """
Merges two keyword lists into one. If they have duplicated Merges two keyword lists into one.
entries, the given function is invoked to solve conflicts.
If they have duplicated keys, the given function is invoked to solve conflicts.
## Examples ## Examples
@@ -392,10 +402,11 @@ defmodule Keyword do
end end
@doc """ @doc """
Updates the `key` with the given function. If the `key` does Updates the `key` with the given function.
not exist, raises `KeyError`.
If the `key` does not exist, raises `KeyError`.
If there are duplicated entries, they are all removed and only the first one If there are duplicated keys, they are all removed and only the first one
is updated. is updated.
## Examples ## Examples
@@ -425,10 +436,11 @@ defmodule Keyword do
end end
@doc """ @doc """
Updates the `key` with the given function. If the `key` does Updates the `key` with the given function.
not exist, inserts the given `initial` value.
If the `key` does not exist, inserts the given `initial` value.
If there are duplicated entries, they are all removed and only the first one If there are duplicated keys, they are all removed and only the first one
is updated. is updated.
## Examples ## Examples
@@ -455,8 +467,9 @@ defmodule Keyword do
@doc """ @doc """
Takes all entries corresponding to the given keys and extracts them into a Takes all entries corresponding to the given keys and extracts them into a
separate keyword list. Returns a tuple with the new list and the old list separate keyword list.
with removed keys.
Returns a tuple with the new list and the old list with removed keys.
Keys for which there are no entires in the keyword list are ignored. Keys for which there are no entires in the keyword list are ignored.
@@ -508,7 +521,7 @@ defmodule Keyword do
end end
@doc """ @doc """
Drops the given keys from the dict. Drops the given keys from the keyword list.
Duplicated keys are preserved in the new keyword list. Duplicated keys are preserved in the new keyword list.
@@ -531,7 +544,7 @@ defmodule Keyword do
Returns the first value associated with `key` in the keyword Returns the first value associated with `key` in the keyword
list as well as the keyword list without `key`. list as well as the keyword list without `key`.
All duplicated entries are removed. See `pop_first/3` for All duplicated keys are removed. See `pop_first/3` for
removing only the first entry. removing only the first entry.
## Examples ## Examples
@@ -558,10 +571,10 @@ defmodule Keyword do
@doc """ @doc """
Returns the first value associated with `key` in the keyword Returns the first value associated with `key` in the keyword
list as well as the keyword list without that particular ocurrence list as well as the keyword list without that particular occurrence
of `key`. of `key`.
Duplicated entries are not removed. Duplicated keys are not removed.
## Examples ## Examples
+5 -21
View File
@@ -247,7 +247,7 @@ defmodule List do
[a: 1, b: 2, c: 3] [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 def keystore(list, key, position, new_tuple) do
:lists.keystore(key, position + 1, list, new_tuple) :lists.keystore(key, position + 1, list, new_tuple)
end end
@@ -307,6 +307,8 @@ defmodule List do
@doc """ @doc """
Zips corresponding elements from each list in `list_of_lists`. Zips corresponding elements from each list in `list_of_lists`.
The zipping finishes as soon as any list terminates.
## Examples ## Examples
iex> List.zip([[1, 2], [3, 4], [5, 6]]) iex> List.zip([[1, 2], [3, 4], [5, 6]])
@@ -322,24 +324,6 @@ defmodule List do
do_zip(list_of_lists, []) do_zip(list_of_lists, [])
end end
@doc """
Unzips the given list of lists or tuples into separate lists and returns a
list of lists.
## 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 """ @doc """
Returns a list with `value` inserted at the specified `index`. Returns a list with `value` inserted at the specified `index`.
Note that `index` is capped at the list length. Negative indices Note that `index` is capped at the list length. Negative indices
@@ -528,7 +512,7 @@ defmodule List do
1023 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 def to_integer(char_list, base) do
:erlang.list_to_integer(char_list, base) :erlang.list_to_integer(char_list, base)
end end
@@ -566,7 +550,7 @@ defmodule List do
"abc" "abc"
""" """
@spec to_string(:unicode.char_list) :: String.t @spec to_string(:unicode.charlist) :: String.t
def to_string(list) when is_list(list) do def to_string(list) when is_list(list) do
case :unicode.characters_to_binary(list) do case :unicode.characters_to_binary(list) do
result when is_binary(result) -> result when is_binary(result) ->
+85 -25
View File
@@ -16,7 +16,8 @@ defmodule Macro do
:&&, :||, :<>, :++, :--, :\\, :::, :<-, :.., :|>, :=~, :&&, :||, :<>, :++, :--, :\\, :::, :<-, :.., :|>, :=~,
:<, :>, :->, :<, :>, :->,
:+, :-, :*, :/, :=, :|, :., :+, :-, :*, :/, :=, :|, :.,
:and, :or, :xor, :when, :in, :and, :or, :when, :in,
:~>>, :<<~, :~>, :<~, :<~>, :<|>,
:<<<, :>>>, :|||, :&&&, :^^^, :~~~] :<<<, :>>>, :|||, :&&&, :^^^, :~~~]
@doc false @doc false
@@ -35,11 +36,12 @@ defmodule Macro do
::: -> {:right, 60} ::: -> {:right, 60}
:| -> {:right, 70} :| -> {:right, 70}
:= -> {:right, 90} := -> {:right, 90}
o when o in [:||, :|||, :or, :xor] -> {:left, 130} o when o in [:||, :|||, :or] -> {:left, 130}
o when o in [:&&, :&&&, :and] -> {:left, 140} o when o in [:&&, :&&&, :and] -> {:left, 140}
o when o in [:==, :!=, :=~, :===, :!==] -> {:left, 150} o when o in [:==, :!=, :=~, :===, :!==] -> {:left, 150}
o when o in [:<, :<=, :>=, :>] -> {:left, 160} o when o in [:<, :<=, :>=, :>] -> {:left, 160}
o when o in [:|>, :<<<, :>>>] -> {:left, 170} o when o in [:|>, :<<<, :>>>, :<~, :~>,
:<<~, :~>>, :<~>, :<|>] -> {:left, 170}
:in -> {:left, 180} :in -> {:left, 180}
o when o in [:++, :--, :.., :<>] -> {:right, 200} o when o in [:++, :--, :.., :<>] -> {:right, 200}
o when o in [:+, :-] -> {:left, 210} o when o in [:+, :-] -> {:left, 210}
@@ -74,7 +76,7 @@ defmodule Macro do
def pipe(expr, call_args, position) def pipe(expr, call_args, position)
def pipe(expr, {:&, _, _} = call_args, _integer) do def pipe(expr, {:&, _, _} = call_args, _integer) do
raise ArgumentError, "cannot pipe #{to_string expr} into #{to_string call_args}" bad_pipe(expr, call_args)
end end
def pipe(expr, {call, line, atom}, integer) when is_atom(atom) do def pipe(expr, {call, line, atom}, integer) when is_atom(atom) do
@@ -86,7 +88,12 @@ defmodule Macro do
end end
def pipe(expr, call_args, _integer) do def pipe(expr, call_args, _integer) do
raise ArgumentError, "cannot pipe #{to_string expr} into #{to_string call_args}" bad_pipe(expr, call_args)
end
defp bad_pipe(expr, call_args) do
raise ArgumentError, "cannot pipe #{to_string expr} into #{to_string call_args}, " <>
"can only pipe into local calls foo(), remote calls Foo.bar() or anonymous functions calls foo.()"
end end
@doc """ @doc """
@@ -300,6 +307,40 @@ defmodule Macro do
elem(:elixir_quote.escape(expr, Keyword.get(opts, :unquote, false)), 0) elem(:elixir_quote.escape(expr, Keyword.get(opts, :unquote, false)), 0)
end end
@doc """
Validates the given expressions are valid quoted expressions.
Check the `type:Macro.t` for the specification of a valid
quoted expression.
"""
@spec validate(term) :: :ok | {:error, term}
def validate(expr) do
find_invalid(expr) || :ok
end
defp find_invalid({left, right}), do:
find_invalid(left) || find_invalid(right)
defp find_invalid({left, meta, right}) when is_list(meta) and (is_atom(right) or is_list(right)), do:
find_invalid(left) || find_invalid(right)
defp find_invalid(list) when is_list(list), do:
Enum.find_value(list, &find_invalid/1)
defp find_invalid(pid) when is_pid(pid), do: nil
defp find_invalid(atom) when is_atom(atom), do: nil
defp find_invalid(num) when is_number(num), do: nil
defp find_invalid(bin) when is_binary(bin), do: nil
defp find_invalid(fun) when is_function(fun) do
unless :erlang.fun_info(fun, :env) == {:env, []} and
:erlang.fun_info(fun, :type) == {:type, :external} do
{:error, fun}
end
end
defp find_invalid(other), do: {:error, other}
@doc ~S""" @doc ~S"""
Unescape the given chars. Unescape the given chars.
@@ -307,9 +348,9 @@ defmodule Macro do
single- and double-quoted strings. Check `unescape_string/2` single- and double-quoted strings. Check `unescape_string/2`
for information on how to customize the escaping map. for information on how to customize the escaping map.
In this setup, Elixir will escape the following: `\a`, `\b`, In this setup, Elixir will escape the following: `\0`, `\a`, `\b`,
`\d`, `\e`, `\f`, `\n`, `\r`, `\s`, `\t` and `\v`. Octals are `\d`, `\e`, `\f`, `\n`, `\r`, `\s`, `\t` and `\v`. Hexadecimals
also escaped according to the latin1 set they represent. are also supported via `\xNN` and `\x{NN...}` syntax.
This function is commonly used on sigil implementations This function is commonly used on sigil implementations
(like `~r`, `~s` and others) which receive a raw, unescaped (like `~r`, `~s` and others) which receive a raw, unescaped
@@ -340,6 +381,7 @@ defmodule Macro do
representing the codepoint of the character it wants to unescape. representing the codepoint of the character it wants to unescape.
Here is the default mapping function implemented by Elixir: Here is the default mapping function implemented by Elixir:
def unescape_map(?0), do: ?0
def unescape_map(?a), do: ?\a def unescape_map(?a), do: ?\a
def unescape_map(?b), do: ?\b def unescape_map(?b), do: ?\b
def unescape_map(?d), do: ?\d def unescape_map(?d), do: ?\d
@@ -350,20 +392,14 @@ defmodule Macro do
def unescape_map(?s), do: ?\s def unescape_map(?s), do: ?\s
def unescape_map(?t), do: ?\t def unescape_map(?t), do: ?\t
def unescape_map(?v), do: ?\v def unescape_map(?v), do: ?\v
def unescape_map(?x), do: true
def unescape_map(e), do: e def unescape_map(e), do: e
If the `unescape_map` function returns `false`. The char is If the `unescape_map` function returns `false`. The char is
not escaped and `\` is kept in the char list. not escaped and `\` is kept in the char list.
## Octals Hexadecimals will be escaped if the map function returns `true`
for `?x`.
Octals will by default be escaped unless the map function
returns `false` for `?0`.
## Hex
Hexadecimals will by default be escaped unless the map function
returns `false` for `?x`.
## Examples ## Examples
@@ -525,11 +561,15 @@ defmodule Macro do
# All other calls # All other calls
def to_string({target, _, args} = ast, fun) when is_list(args) do def to_string({target, _, args} = ast, fun) when is_list(args) do
{list, last} = :elixir_utils.split_last(args) if sigil = sigil_call(ast, fun) do
fun.(ast, case kw_blocks?(last) do sigil
true -> call_to_string_with_args(target, list, fun) <> kw_blocks_to_string(last, fun) else
false -> call_to_string_with_args(target, args, fun) {list, last} = :elixir_utils.split_last(args)
end) fun.(ast, case kw_blocks?(last) do
true -> call_to_string_with_args(target, list, fun) <> kw_blocks_to_string(last, fun)
false -> call_to_string_with_args(target, args, fun)
end)
end
end end
# Two-item tuples # Two-item tuples
@@ -565,6 +605,26 @@ defmodule Macro do
defp module_to_string(atom, _fun) when is_atom(atom), do: inspect(atom, []) defp module_to_string(atom, _fun) when is_atom(atom), do: inspect(atom, [])
defp module_to_string(other, fun), do: call_to_string(other, fun) defp module_to_string(other, fun), do: call_to_string(other, fun)
defp sigil_call({func, _, [{:<<>>, _, [string]}, args]} = ast, fun) when is_list(args) do
sigil =
case Atom.to_string(func) do
<<"sigil_", name>> ->
"~" <> <<name>> <>
fun.(string, inspect(string, [])) <>
sigil_args(args, fun)
_ ->
nil
end
fun.(ast, sigil)
end
defp sigil_call(_other, _fun) do
nil
end
defp sigil_args([], _fun), do: ""
defp sigil_args(args, fun), do: fun.(args, List.to_string(args))
defp call_to_string(atom, _fun) when is_atom(atom), do: Atom.to_string(atom) defp call_to_string(atom, _fun) when is_atom(atom), do: Atom.to_string(atom)
defp call_to_string({:., _, [arg]}, fun), do: module_to_string(arg, fun) <> "." defp call_to_string({:., _, [arg]}, fun), do: module_to_string(arg, fun) <> "."
defp call_to_string({:., _, [left, right]}, fun), do: module_to_string(left, fun) <> "." <> call_to_string(right, fun) defp call_to_string({:., _, [left, right]}, fun), do: module_to_string(left, fun) <> "." <> call_to_string(right, fun)
@@ -773,7 +833,7 @@ defmodule Macro do
:elixir_lexical.record_remote(receiver, env.lexical_tracker) :elixir_lexical.record_remote(receiver, env.lexical_tracker)
{receiver, true} {receiver, true}
aliases -> 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 case :lists.all(&is_atom/1, aliases) do
true -> true ->
@@ -838,7 +898,7 @@ defmodule Macro do
end end
expand = :elixir_dispatch.expand_import(meta, {atom, length(args)}, args, expand = :elixir_dispatch.expand_import(meta, {atom, length(args)}, args,
env, extra) env, extra, true)
case expand do case expand do
{:ok, receiver, quoted} -> {:ok, receiver, quoted} ->
@@ -879,7 +939,7 @@ defmodule Macro do
be expanded. be expanded.
This function uses `expand_once/2` under the hood. Check This function uses `expand_once/2` under the hood. Check
`expand_once/2` for more information and exmaples. `expand_once/2` for more information and examples.
""" """
def expand(tree, env) do def expand(tree, env) do
expand_until({tree, true}, env) expand_until({tree, true}, env)
+21 -6
View File
@@ -4,7 +4,22 @@ defmodule Macro.Env do
The current environment can be accessed at any time as The current environment can be accessed at any time as
`__ENV__`. Inside macros, the caller environment can be `__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 * `module` - the current module name
* `file` - the current file name as a binary * `file` - the current file name as a binary
@@ -29,7 +44,7 @@ defmodule Macro.Env do
* `local` - the module to expand local functions to * `local` - the module to expand local functions to
""" """
@type name_arity :: {atom, non_neg_integer} @type name_arity :: {atom, arity}
@type file :: binary @type file :: binary
@type line :: non_neg_integer @type line :: non_neg_integer
@type aliases :: [{module, module}] @type aliases :: [{module, module}]
@@ -42,10 +57,10 @@ defmodule Macro.Env do
@type vars :: [{atom, atom | non_neg_integer}] @type vars :: [{atom, atom | non_neg_integer}]
@type export_vars :: vars | nil @type export_vars :: vars | nil
@type lexical_tracker :: pid @type lexical_tracker :: pid
@type local :: module | nil @type local :: atom | nil
@type t :: %{__struct__: __MODULE__, @type t :: %{__struct__: __MODULE__,
module: module, module: atom,
file: file, file: file,
line: line, line: line,
function: name_arity | nil, function: name_arity | nil,
@@ -105,9 +120,9 @@ defmodule Macro.Env do
""" """
def stacktrace(%{__struct__: Macro.Env} = env) do def stacktrace(%{__struct__: Macro.Env} = env) do
cond do cond do
nil?(env.module) -> is_nil(env.module) ->
[{:elixir_compiler, :__FILE__, 1, relative_location(env)}] [{:elixir_compiler, :__FILE__, 1, relative_location(env)}]
nil?(env.function) -> is_nil(env.function) ->
[{env.module, :__MODULE__, 0, relative_location(env)}] [{env.module, :__MODULE__, 0, relative_location(env)}]
true -> true ->
{name, arity} = env.function {name, arity} = env.function
+27
View File
@@ -38,5 +38,32 @@ defmodule Map do
end, map1, map2 end, map1, map2
end end
@doc """
Converts a struct to map.
It accepts the struct module or a struct itself and
simply removes the `__struct__` field from the struct.
## Example
defmodule User do
defstruct [:name]
end
Map.from_struct(User)
#=> %{name: nil}
Map.from_struct(%User{name: "john"})
#=> %{name: "john"}
"""
def from_struct(struct) when is_atom(struct) do
:maps.remove(:__struct__, struct.__struct__)
end
def from_struct(%{__struct__: _} = struct) do
:maps.remove(:__struct__, struct)
end
def equal?(%{} = map1, %{} = map2), do: map1 === map2 def equal?(%{} = map1, %{} = map2), do: map1 === map2
end end
+75 -89
View File
@@ -85,6 +85,9 @@ defmodule Module do
See http://www.erlang.org/doc/man/compile.html for the list of supported See http://www.erlang.org/doc/man/compile.html for the list of supported
options. options.
Several uses of `@compile` will accumulate instead of overriding
previous ones.
### Example ### Example
defmodule M do defmodule M do
@@ -476,19 +479,6 @@ defmodule Module do
:elixir_aliases.safe_concat([left, right]) :elixir_aliases.safe_concat([left, right])
end end
@doc """
Gets an anonymous function from the given module, function
and arity. The module and function are not verified to exist.
iex> fun = Module.function(Kernel, :is_atom, 1)
iex> fun.(:hello)
true
"""
def function(mod, fun, arity) do
:erlang.make_fun(mod, fun, arity)
end
@doc """ @doc """
Attaches documentation to a given function or type. It expects Attaches documentation to a given function or type. It expects
the module the function/type belongs to, the line (a non negative the module the function/type belongs to, the line (a non negative
@@ -514,23 +504,23 @@ defmodule Module do
def add_doc(module, line, kind, tuple, signature, doc) when 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 kind in [:def, :defmacro, :type, :opaque] and (is_binary(doc) or is_boolean(doc) or doc == nil) do
assert_not_compiled!(:add_doc, module) assert_not_compiled!(:add_doc, module)
table = docs_table_for(module) table = data_table_for(module)
{signature, _} = :lists.mapfoldl fn(x, acc) -> {signature, _} = :lists.mapfoldl fn(x, acc) ->
{simplify_signature(x, acc), acc + 1} {simplify_signature(x, acc), acc + 1}
end, 1, signature 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 :ok
[{tuple, line, _old_kind, old_sign, old_doc}] -> [{doc_tuple, line, _old_kind, old_sign, old_doc}] ->
:ets.insert(table, { :ets.insert(table, {
tuple, doc_tuple,
line, line,
kind, kind,
merge_signatures(old_sign, signature, 1), merge_signatures(old_sign, signature, 1),
if(nil?(doc), do: old_doc, else: doc) if(is_nil(doc), do: old_doc, else: doc)
}) })
:ok :ok
end end
@@ -622,7 +612,7 @@ defmodule Module do
""" """
def defines?(module, tuple) when is_tuple(tuple) do def defines?(module, tuple) when is_tuple(tuple) do
assert_not_compiled!(:defines?, module) assert_not_compiled!(:defines?, module)
table = function_table_for(module) table = defs_table_for(module)
:ets.lookup(table, tuple) != [] :ets.lookup(table, tuple) != []
end end
@@ -642,7 +632,7 @@ defmodule Module do
""" """
def defines?(module, tuple, kind) do def defines?(module, tuple, kind) do
assert_not_compiled!(:defines?, module) assert_not_compiled!(:defines?, module)
table = function_table_for(module) table = defs_table_for(module)
case :ets.lookup(table, tuple) do case :ets.lookup(table, tuple) do
[{_, ^kind, _, _, _, _, _}] -> true [{_, ^kind, _, _, _, _, _}] -> true
_ -> false _ -> false
@@ -662,8 +652,8 @@ defmodule Module do
""" """
def definitions_in(module) do def definitions_in(module) do
assert_not_compiled!(:definitions_in, module) assert_not_compiled!(:definitions_in, module)
table = function_table_for(module) table = defs_table_for(module)
for {tuple, _, _, _, _, _, _} <- :ets.tab2list(table), do: tuple :lists.concat :ets.match(table, {:'$1', :_, :_, :_, :_, :_, :_})
end end
@doc """ @doc """
@@ -681,8 +671,8 @@ defmodule Module do
""" """
def definitions_in(module, kind) do def definitions_in(module, kind) do
assert_not_compiled!(:definitions_in, module) assert_not_compiled!(:definitions_in, module)
table = function_table_for(module) table = defs_table_for(module)
for {tuple, stored_kind, _, _, _, _, _} <- :ets.tab2list(table), stored_kind == kind, do: tuple :lists.concat :ets.match(table, {:'$1', kind, :_, :_, :_, :_, :_})
end end
@doc """ @doc """
@@ -694,7 +684,7 @@ defmodule Module do
def make_overridable(module, tuples) do def make_overridable(module, tuples) do
assert_not_compiled!(:make_overridable, module) assert_not_compiled!(:make_overridable, module)
for tuple <- tuples do :lists.foreach(fn tuple ->
case :elixir_def.lookup_definition(module, tuple) do case :elixir_def.lookup_definition(module, tuple) do
false -> false ->
{name, arity} = tuple {name, arity} = tuple
@@ -702,27 +692,26 @@ defmodule Module do
clause -> clause ->
:elixir_def.delete_definition(module, tuple) :elixir_def.delete_definition(module, tuple)
neighbours = if loaded?(Module.LocalsTracker) do neighbours = if :elixir_compiler.get_opt(:internal) do
Module.LocalsTracker.yank(module, tuple)
else
[] []
else
Module.LocalsTracker.yank(module, tuple)
end end
old = get_attribute(module, :__overridable) old = :elixir_def_overridable.overridable(module)
merged = :orddict.update(tuple, fn({count, _, _, _}) -> merged = :orddict.update(tuple, fn({count, _, _, _}) ->
{count + 1, clause, neighbours, false} {count + 1, clause, neighbours, false}
end, {1, clause, neighbours, false}, old) end, {1, clause, neighbours, false}, old)
:elixir_def_overridable.overridable(module, merged)
put_attribute(module, :__overridable, merged)
end end
end end, tuples)
end end
@doc """ @doc """
Returns `true` if `tuple` in `module` is marked as overridable. Returns `true` if `tuple` in `module` is marked as overridable.
""" """
def overridable?(module, tuple) do def overridable?(module, tuple) do
!!List.keyfind(get_attribute(module, :__overridable), tuple, 0) !!List.keyfind(:elixir_def_overridable.overridable(module), tuple, 0)
end end
@doc """ @doc """
@@ -741,7 +730,7 @@ defmodule Module do
assert_not_compiled!(:put_attribute, module) assert_not_compiled!(:put_attribute, module)
table = data_table_for(module) table = data_table_for(module)
value = normalize_attribute(key, value) value = normalize_attribute(key, value)
acc = :ets.lookup_element(table, :__acc_attributes, 2) acc = :ets.lookup_element(table, {:elixir, :acc_attributes}, 2)
new = new =
if :lists.member(key, acc) do if :lists.member(key, acc) do
@@ -766,15 +755,9 @@ defmodule Module do
@foo @foo
Expands to: Expands close to:
Module.get_attribute(__MODULE__, :foo, true) Module.get_attribute(__MODULE__, :foo)
Notice the third argument may be given to indicate a stacktrace
to be emitted when the attribute was not previously defined.
The default value for `warn` is nil for direct calls but the `@foo`
macro sets it to the proper stacktrace automatically, warning
every time `@foo` is used but not set previously.
## Examples ## Examples
@@ -788,37 +771,9 @@ defmodule Module do
end end
""" """
@spec get_attribute(module, atom, warn :: nil | [tuple]) :: term @spec get_attribute(atom, atom) :: term
def get_attribute(module, key, warn \\ nil) when def get_attribute(module, key) do
is_atom(key) and (is_list(warn) or nil?(warn)) do get_attribute(module, key, nil)
assert_not_compiled!(:get_attribute, module)
table = data_table_for(module)
case :ets.lookup(table, key) do
[{^key, val}] -> val
[] ->
acc = :ets.lookup_element(table, :__acc_attributes, 2)
cond do
:lists.member(key, acc) ->
[]
is_list(warn) ->
:elixir_errors.warn warn_info(warn), "undefined module attribute @#{key}, " <>
"please remove access to @#{key} or explicitly set it to nil before access\n"
nil
true ->
nil
end
end
end
defp warn_info([entry|_]) do
opts = elem(entry, tuple_size(entry) - 1)
Exception.format_file_line(Keyword.get(opts, :file), Keyword.get(opts, :line)) <> " "
end
defp warn_info([]) do
""
end end
@doc """ @doc """
@@ -832,10 +787,12 @@ defmodule Module do
end end
""" """
@spec delete_attribute(atom, atom) :: :ok
def delete_attribute(module, key) when is_atom(key) do def delete_attribute(module, key) when is_atom(key) do
assert_not_compiled!(:delete_attribute, module) assert_not_compiled!(:delete_attribute, module)
table = data_table_for(module) table = data_table_for(module)
:ets.delete(table, key) :ets.delete(table, key)
:ok
end end
@doc """ @doc """
@@ -874,13 +831,13 @@ defmodule Module do
table = data_table_for(module) table = data_table_for(module)
if Keyword.get(opts, :persist) do if Keyword.get(opts, :persist) do
old = :ets.lookup_element(table, :__persisted_attributes, 2) old = :ets.lookup_element(table, {:elixir, :persisted_attributes}, 2)
:ets.insert(table, {:__persisted_attributes, [new|old]}) :ets.insert(table, {{:elixir, :persisted_attributes}, [new|old]})
end end
if Keyword.get(opts, :accumulate) do if Keyword.get(opts, :accumulate) do
old = :ets.lookup_element(table, :__acc_attributes, 2) old = :ets.lookup_element(table, {:elixir, :acc_attributes}, 2)
:ets.insert(table, {:__acc_attributes, [new|old]}) :ets.insert(table, {{:elixir, :acc_attributes}, [new|old]})
end end
end end
@@ -911,7 +868,9 @@ defmodule Module do
:ok -> :ok ->
:ok :ok
{:error, :private_doc} -> {:error, :private_doc} ->
:elixir_errors.warn line, env.file, "function #{name}/#{arity} is private, @doc's are always discarded for private functions\n" :elixir_errors.warn line, env.file,
"function #{name}/#{arity} is private, " <>
"@doc's are always discarded for private functions"
end end
delete_attribute(module, :doc) delete_attribute(module, :doc)
@@ -933,6 +892,38 @@ defmodule Module do
:ets.insert(table, {key, new}) :ets.insert(table, {key, new})
end end
@doc false
def get_attribute(module, key, warn) when is_atom(key) and (is_list(warn) or is_nil(warn)) do
assert_not_compiled!(:get_attribute, module)
table = data_table_for(module)
case :ets.lookup(table, key) do
[{^key, val}] -> val
[] ->
acc = :ets.lookup_element(table, {:elixir, :acc_attributes}, 2)
cond do
:lists.member(key, acc) ->
[]
is_list(warn) ->
:elixir_errors.warn warn_info(warn), "undefined module attribute @#{key}, " <>
"please remove access to @#{key} or explicitly set it to nil before access"
nil
true ->
nil
end
end
end
defp warn_info([entry|_]) do
opts = elem(entry, tuple_size(entry) - 1)
Exception.format_file_line(Keyword.get(opts, :file), Keyword.get(opts, :line)) <> " "
end
defp warn_info([]) do
""
end
## Helpers ## Helpers
defp normalize_attribute(:on_load, atom) when is_atom(atom) do defp normalize_attribute(:on_load, atom) when is_atom(atom) do
@@ -940,7 +931,8 @@ defmodule Module do
end end
defp normalize_attribute(:behaviour, atom) when is_atom(atom) do defp normalize_attribute(:behaviour, atom) when is_atom(atom) do
Code.ensure_compiled(atom) # Attempt to compile behaviour but ignore failure (will warn later)
_ = Code.ensure_compiled(atom)
atom atom
end end
@@ -963,15 +955,11 @@ defmodule Module do
end end
defp data_table_for(module) do defp data_table_for(module) do
module :elixir_module.data_table(module)
end end
defp function_table_for(module) do defp defs_table_for(module) do
:elixir_def.table(module) :elixir_module.defs_table(module)
end
defp docs_table_for(module) do
:elixir_module.docs_table(module)
end end
defp assert_not_compiled!(fun, module) do defp assert_not_compiled!(fun, module) do
@@ -979,6 +967,4 @@ defmodule Module do
raise ArgumentError, raise ArgumentError,
"could not call #{fun} on module #{inspect module} because it was already compiled" "could not call #{fun} on module #{inspect module} because it was already compiled"
end end
defp loaded?(module), do: is_tuple :code.is_loaded(module)
end end
+68 -30
View File
@@ -65,22 +65,25 @@ defmodule Module.LocalsTracker do
""" """
@spec reachable(ref) :: [local] @spec reachable(ref) :: [local]
def reachable(ref) do def reachable(ref) do
d = :gen_server.call(to_pid(ref), :digraph, @timeout) reachable_from(:gen_server.call(to_pid(ref), :digraph, @timeout), :local)
reduce_reachable(d, :local, []) end
defp reachable_from(d, starting) do
:sets.to_list(reduce_reachable(d, starting, :sets.new))
end end
defp reduce_reachable(d, vertex, vertices) do defp reduce_reachable(d, vertex, vertices) do
neighbours = :digraph.out_neighbours(d, vertex) neighbours = :digraph.out_neighbours(d, vertex)
neighbours = (for {_, _} = t <- neighbours, do: t) |> :ordsets.from_list neighbours = (for {_, _} = t <- neighbours, do: t) |> :sets.from_list
remaining = :ordsets.subtract(neighbours, vertices) remaining = :sets.subtract(neighbours, vertices)
vertices = :ordsets.union(neighbours, vertices) vertices = :sets.union(neighbours, vertices)
:lists.foldl(&reduce_reachable(d, &1, &2), vertices, remaining) :sets.fold(&reduce_reachable(d, &1, &2), vertices, remaining)
end end
defp to_pid(pid) when is_pid(pid), do: pid defp to_pid(pid) when is_pid(pid), do: pid
defp to_pid(mod) when is_atom(mod) do defp to_pid(mod) when is_atom(mod) do
table = :elixir_module.data_table(mod) table = :elixir_module.data_table(mod)
[{_, val}] = :ets.lookup(table, :__locals_tracker) [{_, val}] = :ets.lookup(table, {:elixir, :locals_tracker})
val val
end end
@@ -89,8 +92,7 @@ defmodule Module.LocalsTracker do
# Starts the tracker and returns its pid. # Starts the tracker and returns its pid.
@doc false @doc false
def start_link do def start_link do
{:ok, pid} = :gen_server.start_link(__MODULE__, [], []) :gen_server.start_link(__MODULE__, [], [])
pid
end end
# Adds a definition into the tracker. A public # Adds a definition into the tracker. A public
@@ -134,9 +136,7 @@ defmodule Module.LocalsTracker do
# Reattach a previously yanked node # Reattach a previously yanked node
@doc false @doc false
def reattach(pid, kind, tuple, neighbours) do def reattach(pid, kind, tuple, neighbours) do
pid = to_pid(pid) :gen_server.cast(to_pid(pid), {:reattach, kind, tuple, neighbours})
add_definition(pid, kind, tuple)
:gen_server.cast(pid, {:reattach, tuple, neighbours})
end end
# Collecting all conflicting imports with the given functions # Collecting all conflicting imports with the given functions
@@ -156,12 +156,42 @@ defmodule Module.LocalsTracker do
# given also accounting the expected amount of default # given also accounting the expected amount of default
# clauses a private function have. # clauses a private function have.
@doc false @doc false
def collect_unused_locals(pid, private) do def collect_unused_locals(ref, private) do
reachable = reachable(pid) d = :gen_server.call(to_pid(ref), :digraph, @timeout)
:lists.foldl(&collect_unused_locals(&1, &2, reachable), [], private) {unreachable(d, private), collect_warnings(d, private)}
end end
defp collect_unused_locals({tuple, kind, 0}, acc, reachable) do defp unreachable(d, private) do
unreachable = for {tuple, _, _} <- private, do: tuple
private =
for {tuple, :defp, _} <- private do
neighbours = :digraph.in_neighbours(d, tuple)
neighbours = for {_, _} = t <- neighbours, do: t
{tuple, :sets.from_list(neighbours)}
end
reduce_unreachable(private, [], :sets.from_list(unreachable))
end
defp reduce_unreachable([{vertex, callers}|t], acc, unreachable) do
if :sets.is_subset(callers, unreachable) do
reduce_unreachable(t, [{vertex, callers}|acc], unreachable)
else
reduce_unreachable(acc ++ t, [], :sets.del_element(vertex, unreachable))
end
end
defp reduce_unreachable([], _acc, unreachable) do
:sets.to_list(unreachable)
end
defp collect_warnings(d, private) do
reachable = reachable_from(d, :local)
:lists.foldl(&collect_warnings(&1, &2, reachable), [], private)
end
defp collect_warnings({tuple, kind, 0}, acc, reachable) do
if :lists.member(tuple, reachable) do if :lists.member(tuple, reachable) do
acc acc
else else
@@ -169,7 +199,7 @@ defmodule Module.LocalsTracker do
end end
end end
defp collect_unused_locals({tuple, kind, default}, acc, reachable) when default > 0 do defp collect_warnings({tuple, kind, default}, acc, reachable) when default > 0 do
{name, arity} = tuple {name, arity} = tuple
min = arity - default min = arity - default
max = arity max = arity
@@ -211,6 +241,7 @@ defmodule Module.LocalsTracker do
{:ok, {d, []}} {:ok, {d, []}}
end end
@doc false
def handle_call({:cache_env, env}, _from, {d, cache}) do def handle_call({:cache_env, env}, _from, {d, cache}) do
case cache do case cache do
[{i,^env}|_] -> [{i,^env}|_] ->
@@ -227,10 +258,9 @@ defmodule Module.LocalsTracker do
end end
def handle_call({:yank, local}, _from, {d, _} = state) do def handle_call({:yank, local}, _from, {d, _} = state) do
in_vertices = :digraph.in_neighbours(d, local)
out_vertices = :digraph.out_neighbours(d, local) out_vertices = :digraph.out_neighbours(d, local)
:digraph.del_vertex(d, local) :digraph.del_edges(d, :digraph.out_edges(d, local))
{:reply, {in_vertices, out_vertices}, state} {:reply, {[], out_vertices}, state}
end end
def handle_call(:digraph, _from, {d, _} = state) do def handle_call(:digraph, _from, {d, _} = state) do
@@ -241,6 +271,7 @@ defmodule Module.LocalsTracker do
{:stop, {:bad_call, request}, state} {:stop, {:bad_call, request}, state}
end end
@doc false
def handle_info(_msg, state) do def handle_info(_msg, state) do
{:noreply, state} {:noreply, state}
end end
@@ -268,9 +299,17 @@ defmodule Module.LocalsTracker do
{:noreply, state} {:noreply, state}
end end
def handle_cast({:reattach, tuple, {in_neigh, out_neigh}}, {d, _} = state) do def handle_cast({:reattach, _kind, tuple, {in_neigh, out_neigh}}, {d, _} = state) do
for from <- in_neigh, do: replace_edge(d, from, tuple) for from <- in_neigh do
for to <- out_neigh, do: replace_edge(d, tuple, to) :digraph.add_vertex(d, from)
replace_edge!(d, from, tuple)
end
for to <- out_neigh do
:digraph.add_vertex(d, to)
replace_edge!(d, tuple, to)
end
{:noreply, state} {:noreply, state}
end end
@@ -282,10 +321,12 @@ defmodule Module.LocalsTracker do
{:stop, {:bad_cast, msg}, state} {:stop, {:bad_cast, msg}, state}
end end
@doc false
def terminate(_reason, _state) do def terminate(_reason, _state) do
:ok :ok
end end
@doc false
def code_change(_old, state, _extra) do def code_change(_old, state, _extra) do
{:ok, state} {:ok, state}
end end
@@ -300,6 +341,8 @@ defmodule Module.LocalsTracker do
if function != nil do if function != nil do
replace_edge!(d, function, tuple) replace_edge!(d, function, tuple)
end end
:ok
end end
defp handle_add_local(d, from, to) do defp handle_add_local(d, from, to) do
@@ -317,14 +360,9 @@ defmodule Module.LocalsTracker do
end end
defp replace_edge!(d, from, to) do 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) [:"$e"|_] = :digraph.add_edge(d, from, to)
end end
end :ok
defp replace_edge(d, from, to) do
unless :lists.member(to, :digraph.out_neighbours(d, from)) do
:digraph.add_edge(d, from, to)
end
end end
end end
+1 -1
View File
@@ -30,7 +30,7 @@ defmodule Node do
returns `{:error, :not_allowed}`. Returns `{:error, :not_found}` if the returns `{:error, :not_allowed}`. Returns `{:error, :not_found}` if the
local node is not alive. local node is not alive.
""" """
@spec stop() :: :ok | {:error, term} @spec stop() :: :ok | {:error, :not_allowed | :not_found}
def stop() do def stop() do
:net_kernel.stop() :net_kernel.stop()
end end
+122 -59
View File
@@ -45,7 +45,7 @@ defmodule OptionParser do
* `:strict` - the switches are strict. Any switch that does not * `:strict` - the switches are strict. Any switch that does not
exist in the switch list is treated as an error. exist in the switch list is treated as an error.
* `:switches` - configure some switches. Switches that does not * `:switches` - defines some switches. Switches that does not
exist in the switch list are still attempted to be parsed. exist in the switch list are still attempted to be parsed.
Note only `:strict` or `:switches` may be given at once. Note only `:strict` or `:switches` may be given at once.
@@ -59,7 +59,7 @@ defmodule OptionParser do
* `:float` - parses the switch as a float. * `:float` - parses the switch as a float.
* `:string` - returns the switch as a string. * `:string` - returns the switch as a string.
If a switch can't be parsed or is not specfied in the strict case, If a switch can't be parsed or is not specified in the strict case,
the option is returned in the invalid options list (third element the option is returned in the invalid options list (third element
of the returned tuple). of the returned tuple).
@@ -91,14 +91,8 @@ defmodule OptionParser do
## Negation switches ## Negation switches
All switches starting with `--no-` are considered to be booleans and never In case a switch is declared as boolean, it may be passed as `--no-SWITCH`
parse the next value: which will set the option to false:
iex> OptionParser.parse(["--no-op", "path/to/file"])
{[no_op: true], ["path/to/file"], []}
However, in case the base switch exists, it sets that particular switch to
false:
iex> OptionParser.parse(["--no-op", "path/to/file"], switches: [op: :boolean]) iex> OptionParser.parse(["--no-op", "path/to/file"], switches: [op: :boolean])
{[op: false], ["path/to/file"], []} {[op: false], ["path/to/file"], []}
@@ -113,8 +107,7 @@ defmodule OptionParser do
""" """
@spec parse(argv, options) :: {parsed, argv, errors} @spec parse(argv, options) :: {parsed, argv, errors}
def parse(argv, opts \\ []) when is_list(argv) and is_list(opts) do def parse(argv, opts \\ []) when is_list(argv) and is_list(opts) do
config = compile_config(opts, true) do_parse(argv, compile_config(opts), [], [], [], true)
do_parse(argv, config, [], [], [])
end end
@doc """ @doc """
@@ -134,37 +127,36 @@ defmodule OptionParser do
""" """
@spec parse_head(argv, options) :: {parsed, argv, errors} @spec parse_head(argv, options) :: {parsed, argv, errors}
def parse_head(argv, opts \\ []) when is_list(argv) and is_list(opts) do def parse_head(argv, opts \\ []) when is_list(argv) and is_list(opts) do
config = compile_config(opts, false) do_parse(argv, compile_config(opts), [], [], [], false)
do_parse(argv, config, [], [], [])
end end
defp do_parse([], _config, opts, args, invalid) do defp do_parse([], _config, opts, args, invalid, _all?) do
{Enum.reverse(opts), Enum.reverse(args), Enum.reverse(invalid)} {Enum.reverse(opts), Enum.reverse(args), Enum.reverse(invalid)}
end end
defp do_parse(argv, {aliases, switches, strict, all}=config, opts, args, invalid) do defp do_parse(argv, {aliases, switches, strict}=config, opts, args, invalid, all?) do
case next(argv, aliases, switches, strict) do case next(argv, aliases, switches, strict) do
{:ok, option, value, rest} -> {:ok, option, value, rest} ->
# the option exist and it was successfully parsed # the option exist and it was successfully parsed
kinds = List.wrap Keyword.get(switches, option) kinds = List.wrap Keyword.get(switches, option)
new_opts = do_store_option(opts, option, value, kinds) new_opts = do_store_option(opts, option, value, kinds)
do_parse(rest, config, new_opts, args, invalid) do_parse(rest, config, new_opts, args, invalid, all?)
{:invalid, option, value, rest} -> {:invalid, option, value, rest} ->
# the option exist but it has wrong value # the option exist but it has wrong value
do_parse(rest, config, opts, args, [{option, value}|invalid]) do_parse(rest, config, opts, args, [{option, value}|invalid], all?)
{:undefined, option, _value, rest} -> {:undefined, option, _value, rest} ->
# the option does not exist (for strict cases) # the option does not exist (for strict cases)
do_parse(rest, config, opts, args, [{option, nil}|invalid]) do_parse(rest, config, opts, args, [{option, nil}|invalid], all?)
{:error, ["--"|rest]} -> {:error, ["--"|rest]} ->
{Enum.reverse(opts), Enum.reverse(args, rest), Enum.reverse(invalid)} {Enum.reverse(opts), Enum.reverse(args, rest), Enum.reverse(invalid)}
{:error, [arg|rest]=remaining_args} -> {:error, [arg|rest]=remaining_args} ->
# there is no option # there is no option
if all do if all? do
do_parse(rest, config, opts, [arg|args], invalid) do_parse(rest, config, opts, [arg|args], invalid, all?)
else else
{Enum.reverse(opts), Enum.reverse(args, remaining_args), Enum.reverse(invalid)} {Enum.reverse(opts), Enum.reverse(args, remaining_args), Enum.reverse(invalid)}
end end
@@ -193,12 +185,12 @@ defmodule OptionParser do
@spec next(argv, options) :: @spec next(argv, options) ::
{:ok, key :: atom, value :: term, argv} | {:ok, key :: atom, value :: term, argv} |
{:invalid, key :: atom, value :: term, argv} | {:invalid, String.t, String.t | nil, argv} |
{:undefined, key :: atom, value :: term, argv} | {:undefined, String.t, String.t | nil, argv} |
{:error, argv} {:error, argv}
def next(argv, opts \\ []) when is_list(argv) and is_list(opts) do def next(argv, opts \\ []) when is_list(argv) and is_list(opts) do
{aliases, switches, strict, _} = compile_config(opts, true) {aliases, switches, strict} = compile_config(opts)
next(argv, aliases, switches, strict) next(argv, aliases, switches, strict)
end end
@@ -221,7 +213,7 @@ defmodule OptionParser do
defp next(["-" <> option|rest], aliases, switches, strict) do defp next(["-" <> option|rest], aliases, switches, strict) do
{option, value} = split_option(option) {option, value} = split_option(option)
opt_name_bin = "-" <> option opt_name_bin = "-" <> option
tagged = tag_option(option, value, switches, aliases) tagged = tag_option(option, switches, aliases)
if strict and not option_defined?(tagged, switches) do if strict and not option_defined?(tagged, switches) do
{:undefined, opt_name_bin, value, rest} {:undefined, opt_name_bin, value, rest}
@@ -239,9 +231,95 @@ defmodule OptionParser do
{:error, argv} {:error, argv}
end end
@doc """
Receives a key-value enumerable and convert it to argv.
Keys must be atoms. Keys with nil value are discarded,
boolean values are converted to `--key` or `--no-key`
and all other values are converted using `to_string/1`.
## Examples
iex> OptionParser.to_argv([foo_bar: "baz"])
["--foo-bar", "baz"]
iex> OptionParser.to_argv([bool: true, bool: false, discarded: nil])
["--bool", "--no-bool"]
"""
@spec to_argv(Enumerable.t) :: argv
def to_argv(enum) do
Enum.flat_map(enum, fn
{_key, nil} -> []
{key, true} -> [to_switch(key)]
{key, false} -> [to_switch(key, "--no-")]
{key, value} -> [to_switch(key), to_string(value)]
end)
end
defp to_switch(key, prefix \\ "--") when is_atom(key) do
prefix <> String.replace(Atom.to_string(key), "_", "-")
end
@doc ~S"""
Splits a string into argv chunks.
## Examples
iex> OptionParser.split("foo bar")
["foo", "bar"]
iex> OptionParser.split("foo \"bar baz\"")
["foo", "bar baz"]
"""
@spec split(String.t) :: argv
def split(string) do
do_split(strip_leading_spaces(string), "", [], nil)
end
# If we have a escaped quote, simply remove the escape
defp do_split(<<?\\, quote, t :: binary>>, buffer, acc, quote),
do: do_split(t, <<buffer::binary, quote>>, acc, quote)
# If we have a quote and we were not in a quote, start one
defp do_split(<<quote, t :: binary>>, buffer, acc, nil) when quote in [?", ?'],
do: do_split(t, buffer, acc, quote)
# If we have a quote and we were inside it, close it
defp do_split(<<quote, t :: binary>>, buffer, acc, quote),
do: do_split(t, buffer, acc, nil)
# If we have a escaped quote/space, simply remove the escape as long as we are not inside a quote
defp do_split(<<?\\, h, t :: binary>>, buffer, acc, nil) when h in [?\s, ?', ?"],
do: do_split(t, <<buffer::binary, h>>, acc, nil)
# If we have space and we are outside of a quote, start new segment
defp do_split(<<?\s, t :: binary>>, buffer, acc, nil),
do: do_split(strip_leading_spaces(t), "", [buffer|acc], nil)
# All other characters are moved to buffer
defp do_split(<<h, t::binary>>, buffer, acc, quote) do
do_split(t, <<buffer::binary, h>>, acc, quote)
end
# Finish the string expecting a nil marker
defp do_split(<<>>, "", acc, nil),
do: Enum.reverse(acc)
defp do_split(<<>>, buffer, acc, nil),
do: Enum.reverse([buffer|acc])
# Otherwise raise
defp do_split(<<>>, _, _acc, marker) do
raise "argv string did not terminate properly, a #{<<marker>>} was opened but never closed"
end
defp strip_leading_spaces(" " <> t), do: strip_leading_spaces(t)
defp strip_leading_spaces(t), do: t
## Helpers ## Helpers
defp compile_config(opts, all) do defp compile_config(opts) do
aliases = opts[:aliases] || [] aliases = opts[:aliases] || []
{switches, strict} = cond do {switches, strict} = cond do
@@ -253,7 +331,7 @@ defmodule OptionParser do
{[], false} {[], false}
end end
{aliases, switches, strict, all} {aliases, switches, strict}
end end
defp validate_option(value, kinds) do defp validate_option(value, kinds) do
@@ -296,11 +374,11 @@ defmodule OptionParser do
end end
end end
defp tag_option(<<?-, option :: binary>>, value, switches, _aliases) do defp tag_option(<<?-, option :: binary>>, switches, _aliases) do
get_negated(option, value, switches) get_negated(option, switches)
end end
defp tag_option(option, _value, _switches, aliases) when is_binary(option) do defp tag_option(option, _switches, aliases) when is_binary(option) do
opt = get_option(option) opt = get_option(option)
if alias = aliases[opt] do if alias = aliases[opt] do
{:default, alias} {:default, alias}
@@ -325,23 +403,14 @@ defmodule OptionParser do
{nil, [:invalid], value} {nil, [:invalid], value}
end end
defp normalize_option({:negated, option}, nil, switches) do defp normalize_option({:negated, option}, value, switches) do
kinds = List.wrap(switches[option]) if value do
{option, [:invalid], value}
cond do else
:boolean in kinds -> {option, List.wrap(switches[option]), false}
{option, kinds, false}
kinds == [] ->
{option, kinds, true}
true ->
{reverse_negated(option), [:invalid], nil}
end end
end end
defp normalize_option({:negated, option}, value, _switches) do
{option, [:invalid], value}
end
defp normalize_option({:default, option}, value, switches) do defp normalize_option({:default, option}, value, switches) do
{option, List.wrap(switches[option]), value} {option, List.wrap(switches[option]), value}
end end
@@ -396,24 +465,18 @@ defmodule OptionParser do
end end
end end
defp reverse_negated(negated) do defp get_negated("no-" <> rest = original, switches) do
String.to_atom("no_" <> Atom.to_string(negated)) cond do
end (negated = get_option(rest)) && :boolean in List.wrap(switches[negated]) ->
{:negated, negated}
defp get_negated("no-" <> rest = option, value, switches) do option = get_option(original) ->
if negated = get_option(rest) do {:default, option}
option = if Keyword.has_key?(switches, negated) and value == nil do true ->
negated :unknown
else
get_option(option)
end
{:negated, option}
else
:unknown
end end
end end
defp get_negated(rest, _value, _switches) do defp get_negated(rest, _switches) do
if option = get_option(rest) do if option = get_option(rest) do
{:default, option} {:default, option}
else else
+129 -77
View File
@@ -60,40 +60,70 @@ defmodule Path do
def absname(path, relative_to) do def absname(path, relative_to) do
path = IO.chardata_to_string(path) path = IO.chardata_to_string(path)
case type(path) do case type(path) do
:relative -> join(relative_to, path) :relative -> absname_join(relative_to, path)
:absolute -> :absolute -> absname_join([path])
cond do
path == "/" ->
path
:binary.last(path) == ?/ ->
binary_part(path, 0, byte_size(path) - 1)
true ->
path
end
:volumerelative -> :volumerelative ->
relative_to = IO.chardata_to_string(relative_to) relative_to = IO.chardata_to_string(relative_to)
absname_vr(split(path), split(relative_to), relative_to) absname_vr(split(path), split(relative_to), relative_to)
end end
end end
## Absolute path on current drive # Absolute path on current drive
defp absname_vr(["/"|rest], [volume|_], _relative), defp absname_vr(["/"|rest], [volume|_], _relative),
do: join([volume|rest]) do: absname_join([volume|rest])
## Relative to current directory on current drive. # Relative to current directory on current drive.
defp absname_vr([<<x, ?:>>|rest], [<<x, _ :: binary>>|_], relative), defp absname_vr([<<x, ?:>>|rest], [<<x, _ :: binary>>|_], relative),
do: absname(join(rest), relative) do: absname(absname_join(rest), relative)
## Relative to current directory on another drive. # Relative to current directory on another drive.
defp absname_vr([<<x, ?:>>|name], _, _relative) do defp absname_vr([<<x, ?:>>|name], _, _relative) do
cwd = cwd =
case :file.get_cwd([x, ?:]) do case :file.get_cwd([x, ?:]) do
{:ok, dir} -> IO.chardata_to_string(dir) {:ok, dir} -> IO.chardata_to_string(dir)
{:error, _} -> <<x, ?:, ?/>> {:error, _} -> <<x, ?:, ?/>>
end end
absname(join(name), cwd) absname(absname_join(name), cwd)
end end
# Joins a list
defp absname_join([name1, name2|rest]), do:
absname_join([absname_join(name1, name2)|rest])
defp absname_join([name]), do:
do_absname_join(IO.chardata_to_string(name), <<>>, [], major_os_type())
# Joins two paths
defp absname_join(left, right),
do: do_absname_join(IO.chardata_to_string(left), relative(right), [], major_os_type())
defp do_absname_join(<<uc_letter, ?:, rest :: binary>>, relativename, [], :win32) when uc_letter in ?A..?Z, do:
do_absname_join(rest, relativename, [?:, uc_letter+?a-?A], :win32)
defp do_absname_join(<<?\\, rest :: binary>>, relativename, result, :win32), do:
do_absname_join(<<?/, rest :: binary>>, relativename, result, :win32)
defp do_absname_join(<<?/, rest :: binary>>, relativename, [?., ?/|result], os_type), do:
do_absname_join(rest, relativename, [?/|result], os_type)
defp do_absname_join(<<?/, rest :: binary>>, relativename, [?/|result], os_type), do:
do_absname_join(rest, relativename, [?/|result], os_type)
defp do_absname_join(<<>>, <<>>, result, os_type), do:
IO.iodata_to_binary(reverse_maybe_remove_dirsep(result, os_type))
defp do_absname_join(<<>>, relativename, [?:|rest], :win32), do:
do_absname_join(relativename, <<>>, [?:|rest], :win32)
defp do_absname_join(<<>>, relativename, [?/|result], os_type), do:
do_absname_join(relativename, <<>>, [?/|result], os_type)
defp do_absname_join(<<>>, relativename, result, os_type), do:
do_absname_join(relativename, <<>>, [?/|result], os_type)
defp do_absname_join(<<char, rest :: binary>>, relativename, result, os_type), do:
do_absname_join(rest, relativename, [char|result], os_type)
defp reverse_maybe_remove_dirsep([?/, ?:, letter], :win32), do:
[letter, ?:, ?/]
defp reverse_maybe_remove_dirsep([?/], _), do:
[?/]
defp reverse_maybe_remove_dirsep([?/|name], _), do:
:lists.reverse(name)
defp reverse_maybe_remove_dirsep(name, _), do:
:lists.reverse(name)
@doc """ @doc """
Converts the path to an absolute one and expands Converts the path to an absolute one and expands
any `.` and `..` characters and a leading `~`. any `.` and `..` characters and a leading `~`.
@@ -106,7 +136,7 @@ defmodule Path do
""" """
@spec expand(t) :: binary @spec expand(t) :: binary
def expand(path) do def expand(path) do
normalize absname(expand_home(path), System.cwd!) expand_dot absname(expand_home(path), System.cwd!)
end end
@doc """ @doc """
@@ -133,7 +163,7 @@ defmodule Path do
""" """
@spec expand(t, t) :: binary @spec expand(t, t) :: binary
def expand(path, relative_to) do def expand(path, relative_to) do
normalize absname(absname(expand_home(path), expand_home(relative_to)), System.cwd!) expand_dot absname(absname(expand_home(path), expand_home(relative_to)), System.cwd!)
end end
@doc """ @doc """
@@ -157,10 +187,7 @@ defmodule Path do
""" """
@spec type(t) :: :absolute | :relative | :volumerelative @spec type(t) :: :absolute | :relative | :volumerelative
def type(name) when is_list(name) or is_binary(name) do def type(name) when is_list(name) or is_binary(name) do
case :os.type() do pathtype(name, major_os_type) |> elem(0)
{:win32, _} -> win32_pathtype(name)
_ -> unix_pathtype(name)
end |> elem(0)
end end
@doc """ @doc """
@@ -182,10 +209,20 @@ defmodule Path do
""" """
@spec relative(t) :: binary @spec relative(t) :: binary
def relative(name) do def relative(name) do
case :os.type() do relative(name, major_os_type())
{:win32, _} -> win32_pathtype(name) end
_ -> unix_pathtype(name)
end |> elem(1) |> IO.chardata_to_string defp relative(name, os_type) do
pathtype(name, os_type)
|> elem(1)
|> IO.chardata_to_string
end
defp pathtype(name, os_type) do
case os_type do
:win32 -> win32_pathtype(name)
_ -> unix_pathtype(name)
end
end end
defp unix_pathtype(<<?/, relative :: binary>>), do: defp unix_pathtype(<<?/, relative :: binary>>), do:
@@ -390,7 +427,7 @@ defmodule Path do
end end
@doc """ @doc """
Returns a string with one or more path components joined by the path separator. Joins a list of strings.
This function should be used to convert a list of strings to a path. This function should be used to convert a list of strings to a path.
Note that any trailing slash is removed on join. Note that any trailing slash is removed on join.
@@ -411,11 +448,14 @@ defmodule Path do
def join([name1, name2|rest]), do: def join([name1, name2|rest]), do:
join([join(name1, name2)|rest]) join([join(name1, name2)|rest])
def join([name]), do: def join([name]), do:
do_join(IO.chardata_to_string(name), <<>>, [], major_os_type()) name
@doc """ @doc """
Joins two paths. Joins two paths.
The right path will always be expanded to its relative format
and any trailing slash is removed on join.
## Examples ## Examples
iex> Path.join("foo", "bar") iex> Path.join("foo", "bar")
@@ -423,44 +463,34 @@ defmodule Path do
""" """
@spec join(t, t) :: binary @spec join(t, t) :: binary
def join(left, right), def join(left, right) do
do: do_join(IO.chardata_to_string(left), relative(right), [], major_os_type()) left = IO.chardata_to_string(left)
os_type = major_os_type()
defp major_os_type do do_join(left, right, os_type) |> remove_dirsep(os_type)
:os.type |> elem(0)
end end
defp do_join(<<uc_letter, ?:, rest :: binary>>, relativename, [], :win32) when uc_letter in ?A..?Z, do: defp do_join("", right, os_type), do: relative(right, os_type)
do_join(rest, relativename, [?:, uc_letter+?a-?A], :win32) defp do_join(left, "", _os_type), do: left
defp do_join(<<?\\, rest :: binary>>, relativename, result, :win32), do: defp do_join(left, right, os_type), do: remove_dirsep(left, os_type) <> "/" <> relative(right, os_type)
do_join(<<?/, rest :: binary>>, relativename, result, :win32)
defp do_join(<<?/, rest :: binary>>, relativename, [?., ?/|result], os_type), do:
do_join(rest, relativename, [?/|result], os_type)
defp do_join(<<?/, rest :: binary>>, relativename, [?/|result], os_type), do:
do_join(rest, relativename, [?/|result], os_type)
defp do_join(<<>>, <<>>, result, os_type), do:
IO.iodata_to_binary(maybe_remove_dirsep(result, os_type))
defp do_join(<<>>, relativename, [?:|rest], :win32), do:
do_join(relativename, <<>>, [?:|rest], :win32)
defp do_join(<<>>, relativename, [?/|result], os_type), do:
do_join(relativename, <<>>, [?/|result], os_type)
defp do_join(<<>>, relativename, result, os_type), do:
do_join(relativename, <<>>, [?/|result], os_type)
defp do_join(<<char, rest :: binary>>, relativename, result, os_type), do:
do_join(rest, relativename, [char|result], os_type)
defp maybe_remove_dirsep([?/, ?:, letter], :win32), do: defp remove_dirsep("", _os_type), do: ""
[letter, ?:, ?/] defp remove_dirsep(bin, os_type) do
defp maybe_remove_dirsep([?/], _), do: last = :binary.last(bin)
[?/] if last == ?/ or (last == ?\\ and os_type == :win32) do
defp maybe_remove_dirsep([?/|name], _), do: binary_part(bin, 0, byte_size(bin) - 1)
:lists.reverse(name) else
defp maybe_remove_dirsep(name, _), do: bin
:lists.reverse(name) end
end
@doc """ @doc ~S"""
Returns a list with the path split by the path separator. Splits the path into a list at the path separator.
If an empty string is given, returns the root path.
If an empty string is given, returns an empty list.
On Windows, path is split on both "\" and "/" separators
and the driver letter, if there is one, is always returned
in lowercase.
## Examples ## Examples
@@ -486,6 +516,11 @@ defmodule Path do
defmodule Wildcard do defmodule Wildcard do
@moduledoc false @moduledoc false
def read_link_info(file) do
call({:read_link_info, file})
end
# For compatibility with buggy Erlang 17.1.
def read_file_info(file) do def read_file_info(file) do
call({:read_link_info, file}) call({:read_link_info, file})
end end
@@ -558,7 +593,7 @@ defmodule Path do
|> Enum.map(&IO.chardata_to_string/1) |> Enum.map(&IO.chardata_to_string/1)
end end
# Normalize the given path by expanding "..", "." and "~". # expand_dot the given path by expanding "..", "." and "~".
defp chardata_to_list(chardata) do defp chardata_to_list(chardata) do
case :unicode.characters_to_list(chardata) do case :unicode.characters_to_list(chardata) do
@@ -575,34 +610,51 @@ defmodule Path do
defp expand_home(type) do defp expand_home(type) do
case IO.chardata_to_string(type) do case IO.chardata_to_string(type) do
"~" <> rest -> System.user_home! <> rest "~" <> rest -> resolve_home(rest)
rest -> rest rest -> rest
end end
end end
defp normalize(path), do: normalize(split(path), []) defp resolve_home(""), do: System.user_home!
defp normalize([".."|t], ["/"|_] = acc) do defp resolve_home(rest) do
normalize t, acc case {rest, major_os_type} do
{"\\" <> _, :win32} ->
System.user_home! <> rest
{"/" <> _, _} ->
System.user_home! <> rest
_ -> rest
end
end end
defp normalize([".."|t], [<<letter, ?:, ?/>>|_] = acc) when letter in ?a..?z do defp expand_dot(<<"/../", rest::binary>>),
normalize t, acc do: expand_dot("/" <> rest)
defp expand_dot(<<letter, ":/../", rest::binary>>) when letter in ?a..?z,
do: expand_dot(<<letter, ":/", rest::binary>>)
defp expand_dot("/.."),
do: "/"
defp expand_dot(<<letter, ":/..">>) when letter in ?a..?z,
do: expand_dot(<<letter, ":/">>)
defp expand_dot(path),
do: expand_dot(:binary.split(path, "/", [:global]), [])
defp expand_dot([".."|t], [_, _|acc]) do
expand_dot t, acc
end end
defp normalize([".."|t], [_|acc]) do defp expand_dot(["."|t], acc) do
normalize t, acc expand_dot t, acc
end end
defp normalize(["."|t], acc) do defp expand_dot([h|t], acc) do
normalize t, acc expand_dot t, ["/", h|acc]
end end
defp normalize([h|t], acc) do defp expand_dot([], ["/"|acc]) do
normalize t, [h|acc] IO.iodata_to_binary(:lists.reverse(acc))
end end
defp normalize([], acc) do defp major_os_type do
join :lists.reverse(acc) :os.type |> elem(0)
end end
end end
+18
View File
@@ -5,6 +5,8 @@ defmodule Port do
@doc """ @doc """
See http://www.erlang.org/doc/man/erlang.html#open_port-2. See http://www.erlang.org/doc/man/erlang.html#open_port-2.
Inlined by the compiler.
""" """
def open(name, settings) do def open(name, settings) do
:erlang.open_port(name, settings) :erlang.open_port(name, settings)
@@ -12,6 +14,8 @@ defmodule Port do
@doc """ @doc """
See http://www.erlang.org/doc/man/erlang.html#port_close-1. See http://www.erlang.org/doc/man/erlang.html#port_close-1.
Inlined by the compiler.
""" """
def close(port) do def close(port) do
:erlang.port_close(port) :erlang.port_close(port)
@@ -19,6 +23,8 @@ defmodule Port do
@doc """ @doc """
See http://www.erlang.org/doc/man/erlang.html#port_command-2. See http://www.erlang.org/doc/man/erlang.html#port_command-2.
Inlined by the compiler.
""" """
def command(port, data, options \\ []) do def command(port, data, options \\ []) do
:erlang.port_command(port, data, options) :erlang.port_command(port, data, options)
@@ -26,6 +32,8 @@ defmodule Port do
@doc """ @doc """
See http://www.erlang.org/doc/man/erlang.html#port_connect-2. See http://www.erlang.org/doc/man/erlang.html#port_connect-2.
Inlined by the compiler.
""" """
def connect(port, pid) do def connect(port, pid) do
:erlang.port_connect(port, pid) :erlang.port_connect(port, pid)
@@ -33,6 +41,8 @@ defmodule Port do
@doc """ @doc """
See http://www.erlang.org/doc/man/erlang.html#port_control-3. See http://www.erlang.org/doc/man/erlang.html#port_control-3.
Inlined by the compiler.
""" """
def control(port, operation, data) do def control(port, operation, data) do
:erlang.port_control(port, operation, data) :erlang.port_control(port, operation, data)
@@ -40,6 +50,8 @@ defmodule Port do
@doc """ @doc """
See http://www.erlang.org/doc/man/erlang.html#port_call-3. See http://www.erlang.org/doc/man/erlang.html#port_call-3.
Inlined by the compiler.
""" """
def call(port, operation, data) do def call(port, operation, data) do
:erlang.port_call(port, operation, data) :erlang.port_call(port, operation, data)
@@ -47,6 +59,8 @@ defmodule Port do
@doc """ @doc """
See http://www.erlang.org/doc/man/erlang.html#port_info-1. See http://www.erlang.org/doc/man/erlang.html#port_info-1.
Inlined by the compiler.
""" """
def info(port) do def info(port) do
:erlang.port_info(port) :erlang.port_info(port)
@@ -54,6 +68,8 @@ defmodule Port do
@doc """ @doc """
See http://www.erlang.org/doc/man/erlang.html#port_info-2. See http://www.erlang.org/doc/man/erlang.html#port_info-2.
Inlined by the compiler.
""" """
def info(port, item) do def info(port, item) do
:erlang.port_info(port, item) :erlang.port_info(port, item)
@@ -61,6 +77,8 @@ defmodule Port do
@doc """ @doc """
See http://www.erlang.org/doc/man/erlang.html#ports-0. See http://www.erlang.org/doc/man/erlang.html#ports-0.
Inlined by the compiler.
""" """
def list do def list do
:erlang.ports :erlang.ports
+24 -24
View File
@@ -34,7 +34,7 @@ defmodule Process do
end end
@doc """ @doc """
Returns the value for the given key. Returns the value for the given `key`.
""" """
@spec get(term) :: term @spec get(term) :: term
@spec get(term, default :: term) :: term @spec get(term, default :: term) :: term
@@ -64,7 +64,7 @@ defmodule Process do
end end
@doc """ @doc """
Deletes the given key from the dictionary. Deletes the given `key` from the dictionary.
""" """
@spec delete(term) :: term | nil @spec delete(term) :: term | nil
def delete(key) do def delete(key) do
@@ -79,11 +79,11 @@ defmodule Process do
1. If pid is not trapping exits, pid will exit with the given reason. 1. If pid is not trapping exits, pid will exit with the given reason.
2. If pid is trapping exits, the exit signal is transformed into a message 2. If pid is trapping exits, the exit signal is transformed into a message
{:EXIT, from, reason} and delivered to the message queue of pid. `{:EXIT, from, reason}` and delivered to the message queue of pid.
3. If reason is the atom `:normal`, pid will not exit. If it is trapping 3. If reason is the atom `:normal`, pid will not exit. If it is trapping
exits, the exit signal is transformed into a message {:EXIT, from, exits, the exit signal is transformed into a message `{:EXIT, from,
:normal} and delivered to its message queue. :normal}` and delivered to its message queue.
4. If reason is the atom `:kill`, that is if `exit(pid, :kill)` is called, 4. If reason is the atom `:kill`, that is if `exit(pid, :kill)` is called,
an untrappable exit signal is sent to pid which will unconditionally an untrappable exit signal is sent to pid which will unconditionally
@@ -129,10 +129,10 @@ defmodule Process do
end end
@doc """ @doc """
Sends `msg` to `dest` after `time` millisecons. Sends `msg` to `dest` after `time` milliseconds.
If `dest` is a pid, it has to be a pid of a local process, dead or alive. If `dest` is a pid, it must be the pid of a local process, dead or alive.
If `dest` is an atom, it is supposed to be the name of a registered process If `dest` is an atom, it must be the name of a registered process
which is looked up at the time of delivery. No error is given if the name does which is looked up at the time of delivery. No error is given if the name does
not refer to a process. not refer to a process.
@@ -208,8 +208,8 @@ defmodule Process do
end end
@doc """ @doc """
If monitor_ref is a reference which the calling process If `monitor_ref` is a reference which the calling process
obtained by calling monitor/1, this monitoring is turned off. obtained by calling `monitor/1`, this monitoring is turned off.
If the monitoring is already turned off, nothing happens. If the monitoring is already turned off, nothing happens.
See http://www.erlang.org/doc/man/erlang.html#demonitor-2 for more info. See http://www.erlang.org/doc/man/erlang.html#demonitor-2 for more info.
@@ -227,7 +227,7 @@ defmodule Process do
processes currently existing on the local node. processes currently existing on the local node.
Note that a process that is exiting, exists but is not alive, i.e., Note that a process that is exiting, exists but is not alive, i.e.,
alive?/1 will return false for a process that is exiting, `alive?/1` will return `false` for a process that is exiting,
but its process identifier will be part of the result returned. but its process identifier will be part of the result returned.
See http://www.erlang.org/doc/man/erlang.html#processes-0 for more info. See http://www.erlang.org/doc/man/erlang.html#processes-0 for more info.
@@ -252,7 +252,7 @@ defmodule Process do
@doc """ @doc """
Removes the link, if there is one, between the calling process and Removes the link, if there is one, between the calling process and
the process or port referred to by `pid`. Returns true and does not the process or port referred to by `pid`. Returns `true` and does not
fail, even if there is no link or `id` does not exist fail, even if there is no link or `id` does not exist
See http://www.erlang.org/doc/man/erlang.html#unlink-1 for more info. See http://www.erlang.org/doc/man/erlang.html#unlink-1 for more info.
@@ -265,13 +265,13 @@ defmodule Process do
end end
@doc """ @doc """
Associates the name with a pid or a port identifier. name, which must Associates the name with a pid or a port identifier. `name`, which must
be an atom, can be used instead of the pid / port identifier with the be an atom, can be used instead of the pid / port identifier with the
`Kernel.send/2` function. `Kernel.send/2` function.
`Process.register/2` will fail with `ArgumentError` if the pid supplied `Process.register/2` will fail with `ArgumentError` if the pid supplied
is no longer alive, (check with `alive?/1`) or if the name is is no longer alive, (check with `alive?/1`) or if the name is
already registered (check with `registered?/1`). already registered (check with `whereis/1`).
""" """
@spec register(pid | port, atom) :: true @spec register(pid | port, atom) :: true
def register(pid, name) when not name in [nil, false, true] do def register(pid, name) when not name in [nil, false, true] do
@@ -290,7 +290,7 @@ defmodule Process do
@doc """ @doc """
Returns the pid or port identifier with the registered name. Returns the pid or port identifier with the registered name.
Returns nil if the name is not registered. Returns `nil` if the name is not registered.
See http://www.erlang.org/doc/man/erlang.html#whereis-1 for more info. See http://www.erlang.org/doc/man/erlang.html#whereis-1 for more info.
""" """
@@ -309,7 +309,7 @@ defmodule Process do
@doc """ @doc """
Sets the group leader of `pid` to `leader`. Typically, this is used when a processes Sets the group leader of `pid` to `leader`. Typically, this is used when a processes
started from a certain shell should have another group leader than `:init`. started from a certain shell should have a group leader other than `:init`.
""" """
@spec group_leader(pid, leader :: pid) :: true @spec group_leader(pid, leader :: pid) :: true
def group_leader(pid, leader) do def group_leader(pid, leader) do
@@ -317,7 +317,7 @@ defmodule Process do
end end
@doc """ @doc """
Returns a list of names which have been registered using register/2. Returns a list of names which have been registered using `register/2`.
""" """
@spec registered :: [atom] @spec registered :: [atom]
def registered do def registered do
@@ -339,19 +339,19 @@ defmodule Process do
end end
@doc """ @doc """
Sets certain flags for the process Pid, in the same manner as flag/2. Sets certain flags for the process `pid`, in the same manner as `flag/2`.
Returns the old value of the flag. The allowed values for Flag are Returns the old value of the flag. The allowed values for `flag` are
only a subset of those allowed in flag/2, namely: save_calls. only a subset of those allowed in `flag/2`, namely: `save_calls`.
See http://www.erlang.org/doc/man/erlang.html#process_flag-3 for more info. 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 def flag(pid, flag, value) do
:erlang.process_flag(pid, flag, value) :erlang.process_flag(pid, flag, value)
end end
@doc """ @doc """
Returns information about the process identified by pid or nil if the process Returns information about the process identified by `pid` or `nil` if the process
is not alive. is not alive.
Use this only for debugging information. Use this only for debugging information.
@@ -363,8 +363,8 @@ defmodule Process do
end end
@doc """ @doc """
Returns information about the process identified by pid Returns information about the process identified by `pid`
or nil if the process is not alive. or `nil` if the process is not alive.
See http://www.erlang.org/doc/man/erlang.html#process_info-2 for more info. See http://www.erlang.org/doc/man/erlang.html#process_info-2 for more info.
""" """
+48 -25
View File
@@ -19,11 +19,12 @@ defmodule Protocol do
defmacro def({name, _, args}) when is_atom(name) and is_list(args) do defmacro def({name, _, args}) when is_atom(name) and is_list(args) do
arity = length(args) 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] type_args = [quote(do: t) | type_args]
call_args = for i <- :lists.seq(2, arity), call_args = :lists.map(fn i -> {String.to_atom(<<?x, i + 64>>), [], __MODULE__} end,
do: {String.to_atom(<<?x, i + 64>>), [], __MODULE__} :lists.seq(2, arity))
call_args = [quote(do: t) | call_args] call_args = [quote(do: t) | call_args]
quote do quote do
@@ -85,11 +86,13 @@ defmodule Protocol do
Returns `:ok` if so, otherwise raises ArgumentError. Returns `:ok` if so, otherwise raises ArgumentError.
""" """
@spec assert_impl!(module, module) :: :ok | no_return @spec assert_impl!(module, module) :: :ok | no_return
def assert_impl!(protocol, impl) do def assert_impl!(protocol, base) do
assert_impl!(protocol, impl, "") assert_impl!(protocol, base, "")
end end
defp assert_impl!(protocol, impl, extra) do defp assert_impl!(protocol, base, extra) do
impl = Module.concat(protocol, base)
case Code.ensure_compiled(impl) do case Code.ensure_compiled(impl) do
{:module, ^impl} -> :ok {:module, ^impl} -> :ok
_ -> raise ArgumentError, _ -> raise ArgumentError,
@@ -287,7 +290,7 @@ defmodule Protocol do
defp beam_file(module) when is_atom(module) do defp beam_file(module) when is_atom(module) do
case :code.which(module) do case :code.which(module) do
:non_existing -> module atom when is_atom(atom) -> module
file -> file file -> file
end end
end end
@@ -426,7 +429,7 @@ defmodule Protocol do
defp after_defprotocol do defp after_defprotocol do
quote bind_quoted: [builtin: builtin] do quote bind_quoted: [builtin: builtin] do
@spec impl_for(term) :: module | nil @spec impl_for(term) :: atom() | nil
Kernel.def impl_for(data) Kernel.def impl_for(data)
# Define the implementation for structs. # Define the implementation for structs.
@@ -438,7 +441,7 @@ defmodule Protocol do
end end
# Define the implementation for builtins. # Define the implementation for builtins.
for {guard, mod} <- builtin do :lists.foreach(fn {guard, mod} ->
target = Module.concat(__MODULE__, mod) target = Module.concat(__MODULE__, mod)
Kernel.def impl_for(data) when :erlang.unquote(guard)(data) do Kernel.def impl_for(data) when :erlang.unquote(guard)(data) do
@@ -447,9 +450,9 @@ defmodule Protocol do
false -> any_impl_for false -> any_impl_for
end end
end end
end end, builtin)
@spec impl_for!(term) :: module | no_return @spec impl_for!(term) :: atom() | no_return()
Kernel.def impl_for!(data) do Kernel.def impl_for!(data) do
impl_for(data) || raise(Protocol.UndefinedError, protocol: __MODULE__, value: data) impl_for(data) || raise(Protocol.UndefinedError, protocol: __MODULE__, value: data)
end end
@@ -494,12 +497,19 @@ defmodule Protocol do
@protocol [fallback_to_any: !!@fallback_to_any, consolidated: false] @protocol [fallback_to_any: !!@fallback_to_any, consolidated: false]
@doc false @doc false
@spec __protocol__(atom) :: term @spec __protocol__(:name) :: __MODULE__
Kernel.def __protocol__(:name), do: __MODULE__ @spec __protocol__(:functions) :: unquote(Protocol.__functions_spec__(@functions))
Kernel.def __protocol__(:name), do: __MODULE__
Kernel.def __protocol__(:functions), do: unquote(:lists.sort(@functions)) Kernel.def __protocol__(:functions), do: unquote(:lists.sort(@functions))
end end
end end
@doc false
def __functions_spec__([]),
do: []
def __functions_spec__([h|t]),
do: [:lists.foldl(&{:|, [], [&1, &2]}, h, t), quote(do: ...)]
@doc false @doc false
def __impl__(protocol, opts) do def __impl__(protocol, opts) do
do_defimpl(protocol, :lists.keysort(1, opts)) do_defimpl(protocol, :lists.keysort(1, opts))
@@ -510,6 +520,20 @@ defmodule Protocol do
end end
defp do_defimpl(protocol, [do: block, for: for]) do defp do_defimpl(protocol, [do: block, for: for]) do
# Unquote the implementation just later
# when all variables will already be injected
# into the module body.
__impl__ =
quote unquote: false do
@doc false
@spec __impl__(:for) :: unquote(for)
@spec __impl__(:target) :: __MODULE__
@spec __impl__(:protocol) :: unquote(protocol)
def __impl__(:for), do: unquote(for)
def __impl__(:target), do: __MODULE__
def __impl__(:protocol), do: unquote(protocol)
end
quote do quote do
protocol = unquote(protocol) protocol = unquote(protocol)
for = unquote(for) for = unquote(for)
@@ -527,11 +551,7 @@ defmodule Protocol do
Module.register_attribute(__MODULE__, :impl, persist: true) Module.register_attribute(__MODULE__, :impl, persist: true)
@impl [protocol: @protocol, for: @for] @impl [protocol: @protocol, for: @for]
@doc false unquote(__impl__)
@spec __impl__(atom) :: term
def __impl__(:target), do: __MODULE__
def __impl__(:protocol), do: @protocol
def __impl__(:for), do: @for
end end
end end
end end
@@ -557,14 +577,14 @@ defmodule Protocol do
end end
defp derive(protocol, for, struct, opts, env) do defp derive(protocol, for, struct, opts, env) do
impl = Module.concat(protocol, Map)
extra = ", cannot derive #{inspect protocol} for #{inspect for}" extra = ", cannot derive #{inspect protocol} for #{inspect for}"
assert_protocol!(protocol, extra) assert_protocol!(protocol, extra)
assert_impl!(protocol, impl, extra) assert_impl!(protocol, Map, extra)
# Clean up variables from eval context # Clean up variables from eval context
env = %{env | vars: [], export_vars: nil} env = %{env | vars: [], export_vars: nil}
args = [for, struct, opts] args = [for, struct, opts]
impl = Module.concat(protocol, Map)
:elixir_module.expand_callback(env.line, impl, :__deriving__, args, env, fn :elixir_module.expand_callback(env.line, impl, :__deriving__, args, env, fn
mod, fun, args -> mod, fun, args ->
@@ -576,7 +596,9 @@ defmodule Protocol do
@impl [protocol: unquote(protocol), for: unquote(for)] @impl [protocol: unquote(protocol), for: unquote(for)]
@doc false @doc false
@spec __impl__(atom) :: term @spec __impl__(:target) :: unquote(impl)
@spec __impl__(:protocol) :: unquote(protocol)
@spec __impl__(:for) :: unquote(for)
def __impl__(:target), do: unquote(impl) def __impl__(:target), do: unquote(impl)
def __impl__(:protocol), do: unquote(protocol) def __impl__(:protocol), do: unquote(protocol)
def __impl__(:for), do: unquote(for) def __impl__(:for), do: unquote(for)
@@ -588,16 +610,17 @@ defmodule Protocol do
@doc false @doc false
def __spec__?(module, name, arity) do def __spec__?(module, name, arity) do
signature = {name, arity} signature = {name, arity}
specs = Module.get_attribute(module, :spec)
specs = Module.get_attribute(module, :spec)
found = found =
for {:spec, expr, caller} <- specs, :lists.map(fn {:spec, expr, caller} ->
Kernel.Typespec.spec_to_signature(expr) == signature do if Kernel.Typespec.spec_to_signature(expr) == signature do
Kernel.Typespec.define_spec(:callback, expr, caller) Kernel.Typespec.define_spec(:callback, expr, caller)
true true
end end
end, specs)
found != [] :lists.any(& &1 == true, found)
end end
## Helpers ## Helpers
+3 -2
View File
@@ -2,7 +2,7 @@ defmodule Range do
@moduledoc """ @moduledoc """
Defines a Range. Defines a Range.
A Range are represented internally as a struct. However, A Range is represented internally as a struct. However,
the most common form of creating and matching on ranges the most common form of creating and matching on ranges
is via the `../2` macro, auto-imported from Kernel: is via the `../2` macro, auto-imported from Kernel:
@@ -18,7 +18,8 @@ defmodule Range do
defstruct first: nil, last: nil defstruct first: nil, last: nil
@type t(first, last) :: %{__struct__: Range, first: first, last: last} @type t :: %Range{}
@type t(first, last) :: %Range{first: first, last: last}
@doc """ @doc """
Creates a new range. Creates a new range.
+73 -20
View File
@@ -4,7 +4,7 @@ defmodule Record do
Records are simply tuples where the first element is an atom: Records are simply tuples where the first element is an atom:
iex> Record.record? {User, "jose", 27} iex> Record.is_record {User, "john", 27}
true true
This module provides conveniences for working with records at This module provides conveniences for working with records at
@@ -20,6 +20,20 @@ defmodule Record do
The macros `defrecord/3` and `defrecordp/3` can be used to create The macros `defrecord/3` and `defrecordp/3` can be used to create
records while `extract/2` can be used to extract records from Erlang records while `extract/2` can be used to extract records from Erlang
files. files.
## Types
Types can be defined for tuples with the `record/2` macro (only available
in typespecs). Like with the generated record macros it will expand to
a tuple.
defmodule MyModule do
require Record
Record.defrecord :user name: "john", age: 25
@type user :: record(:user, name: String.t, age: integer)
# expands to: `@type user :: {:user, String.t, integer}`
end
""" """
@doc """ @doc """
@@ -49,12 +63,12 @@ defmodule Record do
## Examples ## Examples
iex> record = {User, "jose", 27} iex> record = {User, "john", 27}
iex> Record.record?(record, User) iex> Record.is_record(record, User)
true true
""" """
defmacro record?(data, kind) do defmacro is_record(data, kind) do
case Macro.Env.in_guard?(__CALLER__) do case Macro.Env.in_guard?(__CALLER__) do
true -> true ->
quote do quote do
@@ -77,15 +91,15 @@ defmodule Record do
## Examples ## Examples
iex> record = {User, "jose", 27} iex> record = {User, "john", 27}
iex> Record.record?(record) iex> Record.is_record(record)
true true
iex> tuple = {} iex> tuple = {}
iex> Record.record?(tuple) iex> Record.is_record(tuple)
false false
""" """
defmacro record?(data) do defmacro is_record(data) do
case Macro.Env.in_guard?(__CALLER__) do case Macro.Env.in_guard?(__CALLER__) do
true -> true ->
quote do quote do
@@ -111,26 +125,31 @@ defmodule Record do
## Examples ## Examples
defmodule User do defmodule User do
Record.defrecord :user, [name: "José", age: "25"] require Record
Record.defrecord :user, [name: "meg", age: "25"]
end end
In the example above, a set of macros named `user` but with different In the example above, a set of macros named `user` but with different
arities will be defined to manipulate the underlying record: arities will be defined to manipulate the underlying record:
# To create records # To create records
user() #=> {:user, "José", 25} record = user() #=> {:user, "meg", 25}
user(age: 26) #=> {:user, "José", 26} record = user(age: 26) #=> {:user, "meg", 26}
# To get a field from the record # To get a field from the record
user(record, :name) #=> "José" user(record, :name) #=> "meg"
# To update the record # To update the record
user(record, age: 26) #=> {:user, "José", 26} user(record, age: 26) #=> {:user, "meg", 26}
# Convert a record to a keyword list
user(record) #=> [name: "meg", age: 26]
By default, Elixir uses the record name as the first element of By default, Elixir uses the record name as the first element of
the tuple (the tag). But it can be changed to something else: the tuple (the tag). But it can be changed to something else:
defmodule User do defmodule User do
require Record
Record.defrecord :user, User, name: nil Record.defrecord :user, User, name: nil
end end
@@ -141,7 +160,7 @@ defmodule Record do
defmacro defrecord(name, tag \\ nil, kv) do defmacro defrecord(name, tag \\ nil, kv) do
quote bind_quoted: [name: name, tag: tag, kv: kv] do quote bind_quoted: [name: name, tag: tag, kv: kv] do
tag = tag || name tag = tag || name
fields = Macro.escape Record.__fields__(:defrecord, kv) fields = Record.__fields__(:defrecord, kv)
defmacro(unquote(name)(args \\ [])) do defmacro(unquote(name)(args \\ [])) do
Record.__access__(unquote(tag), unquote(fields), args, __CALLER__) Record.__access__(unquote(tag), unquote(fields), args, __CALLER__)
@@ -159,7 +178,7 @@ defmodule Record do
defmacro defrecordp(name, tag \\ nil, kv) do defmacro defrecordp(name, tag \\ nil, kv) do
quote bind_quoted: [name: name, tag: tag, kv: kv] do quote bind_quoted: [name: name, tag: tag, kv: kv] do
tag = tag || name tag = tag || name
fields = Macro.escape Record.__fields__(:defrecordp, kv) fields = Record.__fields__(:defrecordp, kv)
defmacrop(unquote(name)(args \\ [])) do defmacrop(unquote(name)(args \\ [])) do
Record.__access__(unquote(tag), unquote(fields), args, __CALLER__) Record.__access__(unquote(tag), unquote(fields), args, __CALLER__)
@@ -175,9 +194,19 @@ defmodule Record do
@doc false @doc false
def __fields__(type, fields) do def __fields__(type, fields) do
:lists.map(fn :lists.map(fn
{ key, _ } = pair when is_atom(key) -> pair {key, val} when is_atom(key) ->
key when is_atom(key) -> { key, nil } try do
other -> raise ArgumentError, "#{type} fields must be atoms, got: #{inspect other}" Macro.escape(val)
rescue
e in [ArgumentError] ->
raise ArgumentError, "invalid value for record field #{key}, " <> Exception.message(e)
else
val -> {key, val}
end
key when is_atom(key) ->
{key, nil}
other ->
raise ArgumentError, "#{type} fields must be atoms, got: #{inspect other}"
end, fields) end, fields)
end end
@@ -190,8 +219,15 @@ defmodule Record do
Keyword.keyword?(args) -> Keyword.keyword?(args) ->
create(atom, fields, args, caller) create(atom, fields, args, caller)
true -> true ->
msg = "expected arguments to be a compile time atom or keywords, got: #{Macro.to_string args}" case Macro.expand(args, caller) do
raise ArgumentError, msg {:{}, _, [^atom|list]} when length(list) == length(fields) ->
record = List.to_tuple([atom|list])
Macro.escape(Record.__keyword__(atom, fields, record))
{^atom, arg} when length(fields) == 1 ->
Macro.escape(Record.__keyword__(atom, fields, {atom, arg}))
_ ->
quote do: Record.__keyword__(unquote(atom), unquote(fields), unquote(args))
end
end end
end end
@@ -279,4 +315,21 @@ defmodule Record do
defp find_index([{k, _}|_], k, i), do: i + 2 defp find_index([{k, _}|_], k, i), do: i + 2
defp find_index([{_, _}|t], k, i), do: find_index(t, k, i + 1) defp find_index([{_, _}|t], k, i), do: find_index(t, k, i + 1)
defp find_index([], _k, _i), do: nil defp find_index([], _k, _i), do: nil
# Returns a keyword list of the record
@doc false
def __keyword__(atom, fields, record) do
if is_record(record, atom) do
[_tag|values] = Tuple.to_list(record)
join_keyword(fields, values, [])
else
msg = "expected argument to be a literal atom, literal keyword or a #{inspect atom} record, got runtime: #{inspect record}"
raise ArgumentError, msg
end
end
defp join_keyword([{field, _default}|fields], [value|values], acc),
do: join_keyword(fields, values, [{field, value}| acc])
defp join_keyword([], [], acc),
do: :lists.reverse(acc)
end end
+92 -38
View File
@@ -22,8 +22,9 @@ defmodule Regex do
The modifiers available when creating a Regex are: The modifiers available when creating a Regex are:
* `unicode` (u) - enables unicode specific patterns like `\p`; it expects * `unicode` (u) - enables unicode specific patterns like `\p` and changes
valid unicode strings to be given on match modifiers like `\w`, `\W`, `\s` and friends to also match on unicode.
It expects valid unicode strings to be given on match
* `caseless` (i) - add case insensitivity * `caseless` (i) - add case insensitivity
@@ -75,7 +76,9 @@ defmodule Regex do
""" """
defstruct re_pattern: nil :: term, source: "" :: binary, opts: "" :: binary defstruct re_pattern: nil, source: "", opts: ""
@type t :: %__MODULE__{re_pattern: term, source: binary, opts: binary}
defmodule CompileError do defmodule CompileError do
defexception message: "regex could not be compiled" defexception message: "regex could not be compiled"
@@ -104,7 +107,7 @@ defmodule Regex do
def compile(source, options \\ "") def compile(source, options \\ "")
def compile(source, options) when is_binary(options) do def compile(source, options) when is_binary(options) do
case translate_options(options) do case translate_options(options, []) do
{:error, rest} -> {:error, rest} ->
{:error, {:invalid_option, rest}} {:error, {:invalid_option, rest}}
@@ -130,6 +133,7 @@ defmodule Regex do
Compiles the regular expression according to the given options. Compiles the regular expression according to the given options.
Fails with `Regex.CompileError` if the regex cannot be compiled. Fails with `Regex.CompileError` if the regex cannot be compiled.
""" """
@spec compile(binary, binary | [term]) :: t
def compile!(source, options \\ "") do def compile!(source, options \\ "") do
case compile(source, options) do case compile(source, options) do
{:ok, regex} -> regex {:ok, regex} -> regex
@@ -149,6 +153,7 @@ defmodule Regex do
false false
""" """
@spec match?(t, String.t) :: boolean
def match?(%Regex{re_pattern: compiled}, string) when is_binary(string) do def match?(%Regex{re_pattern: compiled}, string) when is_binary(string) do
:re.run(string, compiled, [{:capture, :none}]) == :match :re.run(string, compiled, [{:capture, :none}]) == :match
end end
@@ -165,6 +170,8 @@ defmodule Regex do
false false
""" """
@spec regex?(t) :: true
@spec regex?(any) :: false
def regex?(%Regex{}), do: true def regex?(%Regex{}), do: true
def regex?(_), do: false def regex?(_), do: false
@@ -190,6 +197,7 @@ defmodule Regex do
[{2,2},{3,1}] [{2,2},{3,1}]
""" """
@spec run(t, binary, [term]) :: nil | [binary] | [{integer, integer}]
def run(regex, string, options \\ []) def run(regex, string, options \\ [])
def run(%Regex{re_pattern: compiled}, string, options) when is_binary(string) do def run(%Regex{re_pattern: compiled}, string, options) when is_binary(string) do
@@ -220,6 +228,7 @@ defmodule Regex do
nil nil
""" """
@spec named_captures(t, String.t, [term]) :: map | nil
def named_captures(regex, string, options \\ []) when is_binary(string) do def named_captures(regex, string, options \\ []) when is_binary(string) do
names = names(regex) names = names(regex)
options = Keyword.put(options, :capture, names) options = Keyword.put(options, :capture, names)
@@ -230,6 +239,7 @@ defmodule Regex do
@doc """ @doc """
Returns the underlying `re_pattern` in the regular expression. Returns the underlying `re_pattern` in the regular expression.
""" """
@spec re_pattern(t) :: term
def re_pattern(%Regex{re_pattern: compiled}) do def re_pattern(%Regex{re_pattern: compiled}) do
compiled compiled
end end
@@ -243,6 +253,7 @@ defmodule Regex do
"foo" "foo"
""" """
@spec source(t) :: String.t
def source(%Regex{source: source}) do def source(%Regex{source: source}) do
source source
end end
@@ -256,6 +267,7 @@ defmodule Regex do
"m" "m"
""" """
@spec opts(t) :: String.t
def opts(%Regex{opts: opts}) do def opts(%Regex{opts: opts}) do
opts opts
end end
@@ -269,6 +281,7 @@ defmodule Regex do
["foo"] ["foo"]
""" """
@spec names(t) :: [String.t]
def names(%Regex{re_pattern: re_pattern}) do def names(%Regex{re_pattern: re_pattern}) do
{:namelist, names} = :re.inspect(re_pattern, :namelist) {:namelist, names} = :re.inspect(re_pattern, :namelist)
names names
@@ -298,6 +311,7 @@ defmodule Regex do
[] []
""" """
@spec scan(t, String.t, [term]) :: [[String.t]]
def scan(regex, string, options \\ []) def scan(regex, string, options \\ [])
def scan(%Regex{re_pattern: compiled}, string, options) when is_binary(string) do def scan(%Regex{re_pattern: compiled}, string, options) when is_binary(string) do
@@ -318,12 +332,17 @@ defmodule Regex do
## Options ## Options
* `:parts` - when specified, splits the string into the given number of * `:parts` - when specified, splits the string into the given number of
parts. If not specified, `:parts` is defaulted to `:infinity`, which will parts. If not specified, `:parts` defaults to `:infinity`, which will
split the string into the maximum number of parts possible based on the split the string into the maximum number of parts possible based on the
given pattern. given pattern.
* `:trim` - when true, remove blank strings from the result. * `:trim` - when true, remove blank strings from the result.
* `:on` - specifies which captures and order to split the string
on. Check the moduledoc for `Regex` to see the possible capture
values. Defaults to `:first` which means captures inside the
Regex does not affect the split result.
## Examples ## Examples
iex> Regex.split(~r/-/, "a-b-c") iex> Regex.split(~r/-/, "a-b-c")
@@ -338,27 +357,61 @@ defmodule Regex do
iex> Regex.split(~r//, "abc") iex> Regex.split(~r//, "abc")
["a", "b", "c", ""] ["a", "b", "c", ""]
iex> Regex.split(~r//, "abc", trim: true) iex> Regex.split(~r/a(?<second>b)c/, "abc")
["a", "b", "c"] ["", ""]
iex> Regex.split(~r/a(?<second>b)c/, "abc", on: [:second])
["a", "c"]
""" """
@spec split(t, String.t, [term]) :: [String.t]
def split(regex, string, options \\ []) def split(regex, string, options \\ [])
def split(%Regex{re_pattern: compiled}, string, options) when is_binary(string) do def split(%Regex{}, "", _opts), do: [""]
parts = Keyword.get(options, :parts, :infinity)
opts = [return: :binary, parts: zero_to_infinity(parts)]
splits = :re.split(string, compiled, opts)
if Keyword.get(options, :trim, false) do def split(%Regex{re_pattern: compiled}, string, opts) when is_binary(string) do
for split <- splits, split != "", do: split on = Keyword.get(opts, :on, :first)
else case :re.run(string, compiled, [:global, capture: on]) do
splits {:match, matches} ->
do_split(matches, string, 0,
parts_to_index(Keyword.get(opts, :parts, :infinity)),
Keyword.get(opts, :trim, false))
:match ->
[string]
:nomatch ->
[string]
end end
end end
defp zero_to_infinity(0), do: :infinity defp parts_to_index(:infinity), do: 0
defp zero_to_infinity(n), do: n defp parts_to_index(n) when is_integer(n) and n > 0, do: n
defp do_split(_, string, offset, _counter, true) when byte_size(string) <= offset,
do: []
defp do_split(_, string, offset, 1, _trim),
do: [binary_part(string, offset, byte_size(string) - offset)]
defp do_split([], string, offset, _counter, _trim),
do: [binary_part(string, offset, byte_size(string) - offset)]
defp do_split([[{pos, _}|h]|t], string, offset, counter, trim) when pos - offset < 0,
do: do_split([h|t], string, offset, counter, trim)
defp do_split([[]|t], string, offset, counter, trim),
do: do_split(t, string, offset, counter, trim)
defp do_split([[{pos, length}|h]|t], string, offset, counter, trim) do
new_offset = pos + length
keep = pos - offset
if keep == 0 and (length == 0 or trim) do
do_split([h|t], string, new_offset, counter, trim)
else
<<_::binary-size(offset), part::binary-size(keep), _::binary>> = string
[part|do_split([h|t], string, new_offset, counter - 1, trim)]
end
end
@doc ~S""" @doc ~S"""
Receives a regex, a binary and a replacement, returns a new Receives a regex, a binary and a replacement, returns a new
@@ -366,8 +419,8 @@ defmodule Regex do
The replacement can be either a string or a function. The string The replacement can be either a string or a function. The string
is used as a replacement for every match and it allows specific is used as a replacement for every match and it allows specific
captures to be accessed via `\N`, where `N` is the capture. In captures to be accessed via `\N` or `\g{N}`, where `N` is the
case `\0` is used, the whole match is inserted. capture. In case `\0` is used, the whole match is inserted.
When the replacement is a function, the function may have arity When the replacement is a function, the function may have arity
N where each argument maps to a capture, with the first argument N where each argument maps to a capture, with the first argument
@@ -397,6 +450,7 @@ defmodule Regex do
"[b][d]" "[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 \\ [])
def replace(regex, string, replacement, options) when is_binary(replacement) do def replace(regex, string, replacement, options) when is_binary(replacement) do
@@ -425,6 +479,11 @@ defmodule Regex do
defp precompile_replacement(""), defp precompile_replacement(""),
do: [] 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 defp precompile_replacement(<<?\\, x, rest :: binary>>) when x < ?0 or x > ?9 do
case precompile_replacement(rest) do case precompile_replacement(rest) do
[head | t] when is_binary(head) -> [head | t] when is_binary(head) ->
@@ -471,12 +530,12 @@ defmodule Regex do
defp apply_list(whole, string, pos, replacement, [[{mpos, _} | _] | _] = list) when mpos > pos do defp apply_list(whole, string, pos, replacement, [[{mpos, _} | _] | _] = list) when mpos > pos do
length = mpos - pos length = mpos - pos
<<untouched :: [size(length), binary], rest :: binary>> = string <<untouched :: binary-size(length), rest :: binary>> = string
[untouched | apply_list(whole, rest, mpos, replacement, list)] [untouched | apply_list(whole, rest, mpos, replacement, list)]
end end
defp apply_list(whole, string, pos, replacement, [[{mpos, length} | _] = head | tail]) when mpos == pos do defp apply_list(whole, string, pos, replacement, [[{mpos, length} | _] = head | tail]) when mpos == pos do
<<_ :: [size(length), binary], rest :: binary>> = string <<_ :: size(length)-binary, rest :: binary>> = string
new_data = apply_replace(whole, replacement, head) new_data = apply_replace(whole, replacement, head)
[new_data | apply_list(whole, rest, pos + length, replacement, tail)] [new_data | apply_list(whole, rest, pos + length, replacement, tail)]
end end
@@ -496,7 +555,7 @@ defmodule Regex do
cond do cond do
is_binary(part) -> is_binary(part) ->
part part
part > tuple_size(indexes) -> part >= tuple_size(indexes) ->
"" ""
true -> true ->
get_index(string, elem(indexes, part)) get_index(string, elem(indexes, part))
@@ -509,7 +568,7 @@ defmodule Regex do
end end
defp get_index(string, {pos, len}) do defp get_index(string, {pos, len}) do
<<_ :: [size(pos), binary], res :: [size(len), binary], _ :: binary>> = string <<_ :: size(pos)-binary, res :: size(len)-binary, _ :: binary>> = string
res res
end end
@@ -559,18 +618,13 @@ defmodule Regex do
# Private Helpers # Private Helpers
defp translate_options(<<?g, t :: binary>>) do defp translate_options(<<?u, t :: binary>>, acc), do: translate_options(t, [:unicode, :ucp|acc])
IO.write :stderr, "The /g flag for regular expressions is no longer needed\n#{Exception.format_stacktrace}" defp translate_options(<<?i, t :: binary>>, acc), do: translate_options(t, [:caseless|acc])
translate_options(t) defp translate_options(<<?x, t :: binary>>, acc), do: translate_options(t, [:extended|acc])
end defp translate_options(<<?f, t :: binary>>, acc), do: translate_options(t, [:firstline|acc])
defp translate_options(<<?r, t :: binary>>, acc), do: translate_options(t, [:ungreedy|acc])
defp translate_options(<<?u, t :: binary>>), do: [:unicode|translate_options(t)] defp translate_options(<<?s, t :: binary>>, acc), do: translate_options(t, [:dotall, {:newline, :anycrlf}|acc])
defp translate_options(<<?i, t :: binary>>), do: [:caseless|translate_options(t)] defp translate_options(<<?m, t :: binary>>, acc), do: translate_options(t, [:multiline|acc])
defp translate_options(<<?x, t :: binary>>), do: [:extended|translate_options(t)] defp translate_options(<<>>, acc), do: acc
defp translate_options(<<?f, t :: binary>>), do: [:firstline|translate_options(t)] defp translate_options(rest, _acc), do: {:error, rest}
defp translate_options(<<?r, t :: binary>>), do: [:ungreedy|translate_options(t)]
defp translate_options(<<?s, t :: binary>>), do: [:dotall, {:newline, :anycrlf}|translate_options(t)]
defp translate_options(<<?m, t :: binary>>), do: [:multiline|translate_options(t)]
defp translate_options(<<>>), do: []
defp translate_options(rest), do: {:error, rest}
end end
+1 -1
View File
@@ -198,7 +198,7 @@ defmodule Set do
if target1 == target2 do if target1 == target2 do
target1.intersection(set1, set2) target1.intersection(set1, set2)
else else
target1.reduce(set1, {:cont, Collectable.empty(set1)}, fn v, acc -> target1.reduce(set1, {:cont, target1.new}, fn v, acc ->
{:cont, if(target2.member?(set2, v), do: target1.put(acc, v), else: acc)} {:cont, if(target2.member?(set2, v), do: target1.put(acc, v), else: acc)}
end) |> elem(1) end) |> elem(1)
end end
+176 -57
View File
@@ -89,12 +89,14 @@ defmodule Stream do
returns an anonymous function may return a struct in future releases. returns an anonymous function may return a struct in future releases.
""" """
@doc false
defstruct enum: nil, funs: [], accs: [], done: nil defstruct enum: nil, funs: [], accs: [], done: nil
@type acc :: any @type acc :: any
@type element :: any @type element :: any
@type index :: non_neg_integer @type index :: non_neg_integer
@type default :: any @type default :: any
@type t :: %__MODULE__{}
# Require Stream.Reducers and its callbacks # Require Stream.Reducers and its callbacks
require Stream.Reducers, as: R require Stream.Reducers, as: R
@@ -154,18 +156,22 @@ defmodule Stream do
@spec chunk(Enumerable.t, non_neg_integer, non_neg_integer, Enumerable.t | nil) :: Enumerable.t @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 def chunk(enum, n, step, pad \\ nil) when n > 0 and step > 0 do
limit = :erlang.max(n, step) limit = :erlang.max(n, step)
lazy enum, {[], 0}, if is_nil(pad) do
fn(f1) -> R.chunk(n, step, limit, f1) end, lazy enum, {[], 0}, fn(f1) -> R.chunk(n, step, limit, f1) end
fn(f1) -> &do_chunk(&1, n, pad, f1) end else
lazy enum, {[], 0},
fn(f1) -> R.chunk(n, step, limit, f1) end,
&do_chunk(&1, n, pad, &2)
end
end end
defp do_chunk(acc(h, {buffer, count} = old, t) = acc, n, pad, f1) do defp do_chunk(acc(_, {_, 0}, _) = acc, _, _, _) do
if nil?(pad) || count == 0 do {:cont, acc}
{:cont, acc} end
else
buffer = :lists.reverse(buffer) ++ Enum.take(pad, n - count) defp do_chunk(acc(h, {buffer, count} = old, t), n, pad, f1) do
cont_with_acc(f1, buffer, h, old, t) buffer = :lists.reverse(buffer) ++ Enum.take(pad, n - count)
end cont_with_acc(f1, buffer, h, old, t)
end end
@doc """ @doc """
@@ -184,7 +190,7 @@ defmodule Stream do
def chunk_by(enum, fun) do def chunk_by(enum, fun) do
lazy enum, nil, lazy enum, nil,
fn(f1) -> R.chunk_by(fun, f1) end, fn(f1) -> R.chunk_by(fun, f1) end,
fn(f1) -> &do_chunk_by(&1, f1) end &do_chunk_by(&1, &2)
end end
defp do_chunk_by(acc(_, nil, _) = acc, _f1) do defp do_chunk_by(acc(_, nil, _) = acc, _f1) do
@@ -336,6 +342,25 @@ defmodule Stream do
lazy enum, fn(f1) -> R.filter_map(filter, mapper, f1) end lazy enum, fn(f1) -> R.filter_map(filter, mapper, f1) end
end end
@doc """
Creates a stream that emits a value every `n` milliseconds.
The values emitted are an increasing counter starting at `0`.
## Examples
iex> Stream.interval(10) |> Enum.take(10)
[0,1,2,3,4,5,6,7,8,9]
"""
@spec interval(non_neg_integer) :: Enumerable.t
def interval(n) do
unfold 0, fn (count) ->
:timer.sleep(n)
{count, count + 1}
end
end
@doc """ @doc """
Injects the stream values into the given collectable as a side-effect. Injects the stream values into the given collectable as a side-effect.
@@ -427,7 +452,7 @@ defmodule Stream do
""" """
@spec run(Enumerable.t) :: :ok @spec run(Enumerable.t) :: :ok
def run(stream) do def run(stream) do
Enumerable.reduce(stream, {:cont, nil}, fn(_, _) -> {:cont, nil} end) _ = Enumerable.reduce(stream, {:cont, nil}, fn(_, _) -> {:cont, nil} end)
:ok :ok
end end
@@ -561,6 +586,22 @@ defmodule Stream do
lazy enum, fn(f1) -> R.take_while(fun, f1) end lazy enum, fn(f1) -> R.take_while(fun, f1) end
end end
@doc """
Creates a stream that emits a single value after `n` milliseconds.
The value emitted is `0`.
## Examples
iex> Stream.timer(10) |> Enum.to_list
[0]
"""
@spec timer(non_neg_integer) :: Enumerable.t
def timer(n) do
take(interval(n), 1)
end
@doc """ @doc """
Transforms an existing stream. Transforms an existing stream.
@@ -575,7 +616,7 @@ defmodule Stream do
## Examples ## 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 many of the functions defined in this module. For example, we can implement
`Stream.take(enum, n)` as follows: `Stream.take(enum, n)` as follows:
@@ -616,12 +657,14 @@ defmodule Stream do
{[], user_acc} -> {[], user_acc} ->
do_transform(user_acc, user, fun, next_acc, next, inner_acc, inner) do_transform(user_acc, user, fun, next_acc, next, inner_acc, inner)
{list, user_acc} when is_list(list) -> {list, user_acc} when is_list(list) ->
do_list_transform(user_acc, user, fun, next_acc, next, inner_acc, inner, &Enumerable.List.reduce(list, &1, fun)) do_list_transform(user_acc, user, fun, next_acc, next, inner_acc, inner,
&Enumerable.List.reduce(list, &1, fun))
{:halt, _user_acc} -> {:halt, _user_acc} ->
next.({:halt, next_acc}) next.({:halt, next_acc})
{:halted, elem(inner_acc, 1)} {:halted, elem(inner_acc, 1)}
{other, user_acc} -> {other, user_acc} ->
do_other_transform(user_acc, user, fun, next_acc, next, inner_acc, inner, &Enumerable.reduce(other, &1, inner)) do_enum_transform(user_acc, user, fun, next_acc, next, inner_acc, inner,
&Enumerable.reduce(other, &1, inner))
end end
{reason, _} -> {reason, _} ->
{reason, elem(inner_acc, 1)} {reason, elem(inner_acc, 1)}
@@ -633,8 +676,9 @@ defmodule Stream do
reduce.(inner_acc) reduce.(inner_acc)
catch catch
kind, reason -> kind, reason ->
stacktrace = System.stacktrace
next.({:halt, next_acc}) next.({:halt, next_acc})
:erlang.raise(kind, reason, :erlang.get_stacktrace) :erlang.raise(kind, reason, stacktrace)
else else
{:done, acc} -> {:done, acc} ->
do_transform(user_acc, user, fun, next_acc, next, {:cont, acc}, inner) do_transform(user_acc, user, fun, next_acc, next, {:cont, acc}, inner)
@@ -646,29 +690,31 @@ defmodule Stream do
end end
end end
defp do_other_transform(user_acc, user, fun, next_acc, next, inner_acc, inner, reduce) do defp do_enum_transform(user_acc, user, fun, next_acc, next, {op, inner_acc}, inner, reduce) do
try do try do
reduce.(inner_acc) reduce.({op, [:outer|inner_acc]})
catch catch
{:stream_transform, h} ->
next.({:halt, next_acc})
{:halted, h}
kind, reason -> kind, reason ->
stacktrace = System.stacktrace stacktrace = System.stacktrace
next.({:halt, next_acc}) next.({:halt, next_acc})
:erlang.raise(kind, reason, stacktrace) :erlang.raise(kind, reason, stacktrace)
else else
{_, acc} -> {:halted, [:outer|acc]} ->
do_transform(user_acc, user, fun, next_acc, next, {:cont, acc}, inner) do_transform(user_acc, user, fun, next_acc, next, {:cont, acc}, inner)
{:suspended, acc, c} -> {:halted, [:inner|acc]} ->
{:suspended, acc, &do_other_transform(user_acc, user, fun, next_acc, next, &1, inner, c)} next.({:halt, next_acc})
{:halted, acc}
{:done, [_|acc]} ->
do_transform(user_acc, user, fun, next_acc, next, {:cont, acc}, inner)
{:suspended, [_|acc], c} ->
{:suspended, acc, &do_enum_transform(user_acc, user, fun, next_acc, next, &1, inner, c)}
end end
end end
defp do_transform_each(x, acc, f) do defp do_transform_each(x, [:outer|acc], f) do
case f.(x, acc) do case f.(x, acc) do
{:halt, h} -> throw({:stream_transform, h}) {:halt, res} -> {:halt, [:inner|res]}
{_, _} = o -> o {op, res} -> {op, [:outer|res]}
end end
end end
@@ -840,6 +886,15 @@ defmodule Stream do
""" """
@spec cycle(Enumerable.t) :: Enumerable.t @spec cycle(Enumerable.t) :: Enumerable.t
def cycle(enumerable)
def cycle(enumerable) when is_list(enumerable) do
unfold {enumerable, enumerable}, fn
{source, [h | t]} -> {h, {source, t}}
{source = [h | t], []} -> {h, {source, t}}
end
end
def cycle(enumerable) do def cycle(enumerable) do
fn acc, fun -> fn acc, fun ->
inner = &do_cycle_each(&1, &2, fun) inner = &do_cycle_each(&1, &2, fun)
@@ -927,14 +982,15 @@ defmodule Stream do
@doc """ @doc """
Emits a sequence of values for the given resource. Emits a sequence of values for the given resource.
Similar to `unfold/2` but the initial value is computed lazily via Similar to `transform/2` but the initial accumulated value is
`start_fun` and executes an `after_fun` at the end of enumeration computed lazily via `start_fun` and executes an `after_fun` at
(both in cases of success and failure). the end of enumeration (both in cases of success and failure).
Successive values are generated by calling `next_fun` with the Successive values are generated by calling `next_fun` with the
previous accumulator (the initial value being the result returned previous accumulator (the initial value being the result returned
by `start_fun`) and it must return a tuple with the current and by `start_fun`) and it must return a tuple containing a list
next accumulator. The enumeration finishes if it returns `nil`. of items to be emitted and the next accumulator. The enumeration
finishes if it returns `{:halt, acc}`.
As the name says, this function is useful to stream values from As the name says, this function is useful to stream values from
resources. resources.
@@ -944,8 +1000,8 @@ defmodule Stream do
Stream.resource(fn -> File.open!("sample") end, Stream.resource(fn -> File.open!("sample") end,
fn file -> fn file ->
case IO.read(file, :line) do case IO.read(file, :line) do
data when is_binary(data) -> {data, file} data when is_binary(data) -> {[data], file}
_ -> nil _ -> {:halt, file}
end end
end, end,
fn file -> File.close(file) end) fn file -> File.close(file) end)
@@ -967,9 +1023,11 @@ defmodule Stream do
defp do_resource(next_acc, next_fun, {:cont, acc}, fun, after_fun) do defp do_resource(next_acc, next_fun, {:cont, acc}, fun, after_fun) do
try do try do
# Optimize the most common cases
case next_fun.(next_acc) do case next_fun.(next_acc) do
nil -> nil {[], next_acc} -> {:opt, {:cont, acc}, next_acc}
{v, next_acc} -> {fun.(v, acc), next_acc} {[v], next_acc} -> {:opt, fun.(v, acc), next_acc}
{_, _} = other -> other
end end
catch catch
kind, reason -> kind, reason ->
@@ -977,11 +1035,62 @@ defmodule Stream do
after_fun.(next_acc) after_fun.(next_acc)
:erlang.raise(kind, reason, stacktrace) :erlang.raise(kind, reason, stacktrace)
else else
nil -> {:opt, acc, next_acc} ->
after_fun.(next_acc)
{:done, acc}
{acc, next_acc} ->
do_resource(next_acc, next_fun, acc, fun, after_fun) do_resource(next_acc, next_fun, acc, fun, after_fun)
{:halt, next_acc} ->
do_resource(next_acc, next_fun, {:halt, acc}, fun, after_fun)
{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))
{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
defp do_list_resource(next_acc, next_fun, acc, fun, after_fun, reduce) do
try do
reduce.(acc)
catch
kind, reason ->
stacktrace = System.stacktrace
after_fun.(next_acc)
:erlang.raise(kind, reason, stacktrace)
else
{:done, acc} ->
do_resource(next_acc, next_fun, {:cont, acc}, fun, after_fun)
{:halted, acc} ->
do_resource(next_acc, next_fun, {:halt, acc}, fun, after_fun)
{:suspended, acc, c} ->
{:suspended, acc, &do_list_resource(next_acc, next_fun, &1, fun, after_fun, c)}
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
end end
@@ -1022,12 +1131,12 @@ defmodule Stream do
@compile {:inline, lazy: 2, lazy: 3, lazy: 4} @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] } do: %{lazy | funs: [fun|funs] }
defp lazy(enum, fun), defp lazy(enum, fun),
do: %Stream{enum: enum, funs: [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] } do: %{lazy | funs: [fun|funs], accs: [acc|accs] }
defp lazy(enum, acc, fun), defp lazy(enum, acc, fun),
do: %Stream{enum: enum, funs: [fun], accs: [acc]} do: %Stream{enum: enum, funs: [fun], accs: [acc]}
@@ -1066,19 +1175,29 @@ defimpl Enumerable, for: Stream do
case reduce.({command, [acc|accs]}) do case reduce.({command, [acc|accs]}) do
{:suspended, [acc|accs], continuation} -> {:suspended, [acc|accs], continuation} ->
{:suspended, acc, &do_each(continuation, done, accs, &1)} {:suspended, acc, &do_each(continuation, done, accs, &1)}
{:halted, [acc|_]} -> {:halted, accs} ->
{:halted, acc} do_done {:halted, accs}, done
{:done, [acc|_] = accs} -> {:done, accs} ->
case done do do_done {:done, accs}, done
nil -> end
{:done, acc} end
{done, fun} ->
case done.(fun).(accs) do defp do_done({reason, [acc|_]}, nil), do: {reason, acc}
{:cont, [acc|_]} -> {:done, acc} defp do_done({reason, [acc|t]}, {done, fun}) do
{:halt, [acc|_]} -> {:halted, acc} [h|_] = Enum.reverse(t)
{:suspend, [acc|_]} -> {:suspended, acc, &({:done, elem(&1, 1)})} case done.([acc, h], fun) do
end {:cont, [acc|_]} -> {reason, acc}
end {:halt, [acc|_]} -> {:halted, acc}
{:suspend, [acc|_]} -> {:suspended, acc, &({:done, elem(&1, 1)})}
end end
end end
end end
defimpl Inspect, for: Stream do
import Inspect.Algebra
def inspect(%{enum: enum, funs: funs}, opts) do
inner = [enum: enum, funs: Enum.reverse(funs)]
concat ["#Stream<", to_doc(inner, opts), ">"]
end
end
+210 -114
View File
@@ -4,28 +4,12 @@ defmodule String do
@moduledoc ~S""" @moduledoc ~S"""
A String in Elixir is a UTF-8 encoded binary. A String in Elixir is a UTF-8 encoded binary.
## String and binary operations
The functions in this module act according to the
Unicode Standard, version 6.3.0. For example,
`capitalize/1`, `downcase/1`, `strip/1` are provided by this
module.
In addition to this module, Elixir provides more low-level
operations that work directly with binaries. Some
of those can be found in the `Kernel` module, as:
* `Kernel.binary_part/3` - retrieves part of the binary
* `Kernel.bit_size/1` and `Kernel.byte_size/1` - size related functions
* `Kernel.is_bitstring/1` and `Kernel.is_binary/1` - type checking function
* Plus a number of functions for working with binaries (bytes)
[in the `:binary` module](http://erlang.org/doc/man/binary.html)
## Codepoints and graphemes ## Codepoints and graphemes
As per the Unicode Standard, a codepoint is an Unicode The functions in this module act according to the Unicode
Character, which may be represented by one or more bytes. Standard, version 6.3.0.As per the standard, a codepoint is
For example, the character "é" is represented with two an Unicode Character, which may be represented by one or more
bytes. For example, the character "é" is represented with two
bytes: bytes:
iex> byte_size("é") iex> byte_size("é")
@@ -66,34 +50,110 @@ defmodule String do
This current Elixir version implements Extended Grapheme Cluster This current Elixir version implements Extended Grapheme Cluster
algorithm. algorithm.
## String and binary operations
To act accordingly to the Unicode Standard, many functions
in this module runs in linear time, as it needs to traverse
the whole string considering the proper Unicode codepoints.
For example, `String.length/1` is going to take longer as
the input grows. On the other hand, `byte_size/1` always runs
in constant time (i.e. regardless of the input size).
This means often there are performance costs in using the
functions in this module, compared to the more low-level
operations that work directly with binaries:
* `Kernel.binary_part/3` - retrieves part of the binary
* `Kernel.bit_size/1` and `Kernel.byte_size/1` - size related functions
* `Kernel.is_bitstring/1` and `Kernel.is_binary/1` - type checking function
* Plus a number of functions for working with binaries (bytes)
[in the `:binary` module](http://erlang.org/doc/man/binary.html)
There are many situations where using the `String` module can
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 ->
...> base = String.length(prefix)
...> String.slice(full, base..-1)
...> end
...> take_prefix.("Mr. John", "Mr. ")
"John"
While this is much better (we don't traverse `full` twice),
it could still be improved. In this case, since we want to
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)
...> end
...> take_prefix.("Mr. John", "Mr. ")
"John"
Or simply used pattern matching:
iex> take_prefix = fn full, prefix ->
...> base = byte_size(prefix)
...> <<_ :: binary-size(base), rest :: binary>> = full
...> rest
...> end
...> take_prefix.("Mr. John", "Mr. ")
"John"
On the other hand, if you want to dynamically slice a string
based on an integer value, then using `String.slice/3` is the
best option as it guarantees we won't incorrectly split a valid
codepoint in multiple bytes.
## Integer codepoints ## Integer codepoints
Although codepoints could be represented as integers, this Although codepoints could be represented as integers, this
module represents all codepoints as strings. For example: module represents all codepoints as strings. For example:
iex> String.codepoints("josé") iex> String.codepoints("olá")
["j", "o", "s", "é"] ["o", "l", "á"]
There are a couple of ways to retrieve a character integer There are a couple of ways to retrieve a character integer
codepoint. One may use the `?` special macro: codepoint. One may use the `?` construct:
iex> ?j iex> ?o
106 111
iex> ?é iex> ?á
233 225
Or also via pattern matching: Or also via pattern matching:
iex> << eacute :: utf8 >> = "é" iex> << eacute :: utf8 >> = "á"
iex> eacute iex> eacute
233 225
As we have seen above, codepoints can be inserted into As we have seen above, codepoints can be inserted into
a string by their hexadecimal code: a string by their hexadecimal code:
"jos\x{0065}\x{0301}" #=> "ol\x{0061}\x{0301}" #=>
"josé" "olá"
## Self-synchronization ## Self-synchronization
@@ -129,7 +189,7 @@ defmodule String do
@spec printable?(t) :: boolean @spec printable?(t) :: boolean
def printable?(<< h :: utf8, t :: binary >>) def printable?(<< h :: utf8, t :: binary >>)
when h in ?\040..?\176 when h in 0x20..0x7E
when h in 0xA0..0xD7FF when h in 0xA0..0xD7FF
when h in 0xE000..0xFFFD when h in 0xE000..0xFFFD
when h in 0x10000..0x10FFFF do when h in 0x10000..0x10FFFF do
@@ -227,40 +287,53 @@ defmodule String do
""" """
@spec split(t, t | [t] | Regex.t) :: [t] @spec split(t, t | [t] | Regex.t) :: [t]
@spec split(t, t | [t] | Regex.t, Keyword.t) :: [t] @spec split(t, t | [t] | Regex.t, Keyword.t) :: [t]
def split(binary, pattern, options \\ []) def split(string, pattern, options \\ [])
def split("", _pattern, _options), do: [""] def split(string, "", options) do
parts = Keyword.get(options, :parts, :infinity)
split_codepoints(string, parts_to_index(parts), Keyword.get(options, :trim, false))
end
def split(binary, "", options), do: split(binary, ~r""u, options) def split(string, pattern, options) do
def split(binary, pattern, options) do
if Regex.regex?(pattern) do if Regex.regex?(pattern) do
Regex.split(pattern, binary, options) Regex.split(pattern, string, options)
else else
splits = parts = Keyword.get(options, :parts, :infinity)
case Keyword.get(options, :parts, :infinity) do trim = Keyword.get(options, :trim, false)
num when is_number(num) and num > 0 -> if parts == :infinity and trim == false do
split_parts(binary, pattern, num - 1) :binary.split(string, pattern, [:global])
_ ->
:binary.split(binary, pattern, [:global])
end
if Keyword.get(options, :trim, false) do
for split <- splits, split != "", do: split
else else
splits split_parts(string, pattern, parts_to_index(parts), trim)
end end
end end
end end
defp split_parts("", _pattern, _num), do: [""] defp parts_to_index(:infinity), do: 0
defp split_parts(binary, pattern, num), do: split_parts(binary, pattern, num, []) defp parts_to_index(n) when is_integer(n) and n > 0, do: n
defp split_parts("", _pattern, _num, parts), do: Enum.reverse([""|parts])
defp split_parts(binary, _pattern, 0, parts), do: Enum.reverse([binary|parts]) defp split_codepoints(binary, 1, _trim), do: [binary]
defp split_parts(binary, pattern, num, parts) do defp split_codepoints(<<h :: utf8, t :: binary>>, count, trim),
case :binary.split(binary, pattern) do do: [<<h :: utf8>>|split_codepoints(t, count - 1, trim)]
[head] -> Enum.reverse([head|parts]) defp split_codepoints(<<h, t :: binary>>, count, trim),
[head, rest] -> split_parts(rest, pattern, num - 1, [head|parts]) do: [<<h>>|split_codepoints(t, count - 1, trim)]
defp split_codepoints(<<>>, _, true), do: []
defp split_codepoints(<<>>, _, false), do: [""]
defp split_parts("", _pattern, _num, true), do: []
defp split_parts("", _pattern, _num, _trim), do: [""]
defp split_parts(string, _pattern, 1, _trim), do: [string]
defp split_parts(string, pattern, num, trim) do
case :binary.split(string, pattern) do
[""] when trim ->
[]
[head] ->
[head]
[head, tail] ->
if trim and head == "" do
split_parts(tail, pattern, num, trim)
else
[head|split_parts(tail, pattern, num-1, trim)]
end
end end
end end
@@ -322,8 +395,8 @@ defmodule String do
iex> String.upcase("ab 123 xpto") iex> String.upcase("ab 123 xpto")
"AB 123 XPTO" "AB 123 XPTO"
iex> String.upcase("josé") iex> String.upcase("olá")
"JOSÉ" "OLÁ"
""" """
@spec upcase(t) :: t @spec upcase(t) :: t
@@ -340,8 +413,8 @@ defmodule String do
iex> String.downcase("AB 123 XPTO") iex> String.downcase("AB 123 XPTO")
"ab 123 xpto" "ab 123 xpto"
iex> String.downcase("JOSÉ") iex> String.downcase("OLÁ")
"josé" "olá"
""" """
@spec downcase(t) :: t @spec downcase(t) :: t
@@ -364,8 +437,8 @@ defmodule String do
iex> String.capitalize("fin") iex> String.capitalize("fin")
"Fin" "Fin"
iex> String.capitalize("josé") iex> String.capitalize("olá")
"José" "Olá"
""" """
@spec capitalize(t) :: t @spec capitalize(t) :: t
@@ -405,7 +478,7 @@ defmodule String do
# does not traverse the whole binary). # does not traverse the whole binary).
def rstrip(string, char) when char in 0..127 do def rstrip(string, char) when char in 0..127 do
if :binary.last(string) == char do if :binary.last(string) == char do
do_rstrip(string, "", char) rstrip(binary_part(string, 0, byte_size(string) - 1), char)
else else
string string
end end
@@ -577,13 +650,14 @@ defmodule String do
iex> String.replace("a,b,c", ",", "-", global: false) iex> String.replace("a,b,c", ",", "-", global: false)
"a-b,c" "a-b,c"
The pattern can also be a regex. In those cases, one can give `\N` The pattern can also be a regex. In those cases, one can give `\N` or
in the `replacement` string to access a specific capture in the regex: `\g{N}` in the `replacement` string to access a specific capture in the
regex:
iex> String.replace("a,b,c", ~r/,(.)/, ",\\1\\1") iex> String.replace("a,b,c", ~r/,(.)/, ",\\1\\1")
"a,bb,cc" "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. one can inject the whole matched pattern in the replacement string.
When strings are used as a pattern, a developer can also use the When strings are used as a pattern, a developer can also use the
@@ -599,8 +673,8 @@ defmodule String do
"a[,,]b[,,]c" "a[,,]b[,,]c"
""" """
@spec replace(t, t, t) :: t @spec replace(t, t | Regex.t, t) :: t
@spec replace(t, t, t, Keyword.t) :: t @spec replace(t, t | Regex.t, t, Keyword.t) :: t
def replace(subject, pattern, replacement, options \\ []) when is_binary(replacement) do def replace(subject, pattern, replacement, options \\ []) when is_binary(replacement) do
if Regex.regex?(pattern) do if Regex.regex?(pattern) do
@@ -672,8 +746,8 @@ defmodule String do
## Examples ## Examples
iex> String.codepoints("josé") iex> String.codepoints("olá")
["j", "o", "s", "é"] ["o", "l", "á"]
iex> String.codepoints("оптими зации") iex> String.codepoints("оптими зации")
["о","п","т","и","м","и"," ","з","а","ц","и","и"] ["о","п","т","и","м","и"," ","з","а","ц","и","и"]
@@ -699,8 +773,8 @@ defmodule String do
## Examples ## Examples
iex> String.next_codepoint("josé") iex> String.next_codepoint("olá")
{"j", "osé"} {"o", "lá"}
""" """
@compile {:inline, next_codepoint: 1} @compile {:inline, next_codepoint: 1}
@@ -786,10 +860,10 @@ defmodule String do
## Examples ## Examples
iex> String.chunk(<<?a, ?b, ?c, 0>>, :valid) iex> String.chunk(<<?a, ?b, ?c, 0>>, :valid)
["abc\000"] ["abc\0"]
iex> String.chunk(<<?a, ?b, ?c, 0, 0x0ffff::utf8>>, :valid) iex> String.chunk(<<?a, ?b, ?c, 0, 0x0ffff::utf8>>, :valid)
["abc\000", <<0x0ffff::utf8>>] ["abc\0", <<0x0ffff::utf8>>]
iex> String.chunk(<<?a, ?b, ?c, 0, 0x0ffff::utf8>>, :printable) iex> String.chunk(<<?a, ?b, ?c, 0, 0x0ffff::utf8>>, :printable)
["abc", <<0, 0x0ffff::utf8>>] ["abc", <<0, 0x0ffff::utf8>>]
@@ -849,8 +923,8 @@ defmodule String do
## Examples ## Examples
iex> String.next_grapheme("josé") iex> String.next_grapheme("olá")
{"j", "osé"} {"o", "lá"}
""" """
@compile {:inline, next_grapheme: 1} @compile {:inline, next_grapheme: 1}
@@ -974,8 +1048,13 @@ defmodule String do
@doc """ @doc """
Returns a substring starting at the offset given by the first, and Returns a substring starting at the offset given by the first, and
a length given by the second. a length given by the second.
If the offset is greater than string length, then it returns `""`. If the offset is greater than string length, then it returns `""`.
Remember this function works with unicode codepoints and consider
the slices to represent codepoints offsets. If you want to split
on raw bytes, check `Kernel.binary_part/3` instead.
## Examples ## Examples
iex> String.slice("elixir", 1, 3) iex> String.slice("elixir", 1, 3)
@@ -1010,17 +1089,29 @@ defmodule String do
end end
def slice(string, start, len) when start >= 0 and len >= 0 do def slice(string, start, len) when start >= 0 and len >= 0 do
do_slice(next_grapheme(string), start, start + len - 1, 0, "") case do_count_bytes(next_grapheme(string), start, 0) do
{nil, _} -> ""
{next, start_bytes} ->
{_, len_bytes} = do_count_bytes(next, len, 0)
binary_part(string, start_bytes, len_bytes)
end
end end
def slice(string, start, len) when start < 0 and len >= 0 do def slice(string, start, len) when start < 0 and len >= 0 do
real_start_pos = length(string) - abs(start) start = length(string) + start
case real_start_pos >= 0 do case start >= 0 do
true -> do_slice(next_grapheme(string), real_start_pos, real_start_pos + len - 1, 0, "") true -> slice(string, start, len)
false -> "" false -> ""
end end
end end
defp do_count_bytes(next, 0, acc), do: {next, acc}
defp do_count_bytes(nil, _, acc), do: {nil, acc}
defp do_count_bytes({char, rest}, counter, acc) do
do_count_bytes(next_grapheme(rest), counter - 1, acc + byte_size(char))
end
@doc """ @doc """
Returns a substring from the offset given by the start of the Returns a substring from the offset given by the start of the
range to the offset given by the end of the range. range to the offset given by the end of the range.
@@ -1028,6 +1119,14 @@ defmodule String do
If the start of the range is not a valid offset for the given If the start of the range is not a valid offset for the given
string or if the range is in reverse order, returns `""`. string or if the range is in reverse order, returns `""`.
If the start or end of the range are negative, the whole string
is traversed first in order to convert the negative indexes into
positive ones.
Remember this function works with unicode codepoints and consider
the slices to represent codepoints offsets. If you want to split
on raw bytes, check `Kernel.binary_part/3` instead.
## Examples ## Examples
iex> String.slice("elixir", 1..3) iex> String.slice("elixir", 1..3)
@@ -1060,54 +1159,51 @@ defmodule String do
iex> String.slice("a", 1..1500) iex> String.slice("a", 1..1500)
"" ""
iex> String.slice("a", 2..1500)
""
""" """
@spec slice(t, Range.t) :: t @spec slice(t, Range.t) :: t
def slice(string, range) def slice(string, range)
def slice(string, first..last) when first >= 0 and last >= 0 do def slice("", _.._), do: ""
do_slice(next_grapheme(string), first, last, 0, "")
def slice(string, first..-1) when first >= 0 do
case do_count_bytes(next_grapheme(string), first, 0) do
{nil, _} -> ""
{_, start_bytes} -> binary_part(string, start_bytes, byte_size(string) - start_bytes)
end
end end
def slice(string, first..last) do def slice(string, first..last) when first >= 0 and last >= 0 do
total = length(string) if last >= first do
slice(string, first, last - first + 1)
if first < 0 do
first = total + first
end
if last < 0 do
last = total + last
end
if first >= 0 do
do_slice(next_grapheme(string), first, last, 0, "")
else else
"" ""
end end
end end
defp do_slice(_, start_pos, last_pos, _, _) when start_pos > last_pos do def slice(string, first..last) do
"" {bytes, length} = do_acc_bytes(next_grapheme(string), [], 0)
if first < 0, do: first = length + first
if last < 0, do: last = length + last
if first < 0 or first > last or first > length do
""
else
last = min(last + 1, length)
bytes = Enum.drop(bytes, length - last)
first = last - first
{length_bytes, start_bytes} = Enum.split(bytes, first)
binary_part(string, Enum.sum(start_bytes), Enum.sum(length_bytes))
end
end end
defp do_slice({_, rest}, start_pos, last_pos, current_pos, acc) when current_pos < start_pos do defp do_acc_bytes({char, rest}, bytes, length) do
do_slice(next_grapheme(rest), start_pos, last_pos, current_pos + 1, acc) do_acc_bytes(next_grapheme(rest), [byte_size(char)|bytes], length + 1)
end end
defp do_slice({char, rest}, start_pos, last_pos, current_pos, acc) when current_pos >= start_pos and current_pos < last_pos do defp do_acc_bytes(nil, bytes, length) do
do_slice(next_grapheme(rest), start_pos, last_pos, current_pos + 1, acc <> char) {bytes, length}
end
defp do_slice({char, _}, start_pos, last_pos, current_pos, acc) when current_pos >= start_pos and current_pos == last_pos do
acc <> char
end
defp do_slice(nil, _, _, _, acc) do
acc
end end
@doc """ @doc """
@@ -1328,7 +1424,7 @@ defmodule String do
1023 1023
""" """
@spec to_integer(String.t, pos_integer) :: integer @spec to_integer(String.t, 2..36) :: integer
def to_integer(string, base) do def to_integer(string, base) do
:erlang.binary_to_integer(string, base) :erlang.binary_to_integer(string, base)
end end
+5 -5
View File
@@ -36,7 +36,7 @@ defmodule StringIO do
""" """
@spec open(binary, Keyword.t) :: {:ok, pid} @spec open(binary, Keyword.t) :: {:ok, pid}
def open(string, options \\ []) when is_binary(string) do def open(string, options \\ []) when is_binary(string) do
:gen_server.start_link(__MODULE__, {string, options}, []) GenServer.start_link(__MODULE__, {string, options}, [])
end end
@doc """ @doc """
@@ -52,7 +52,7 @@ defmodule StringIO do
""" """
@spec contents(pid) :: {binary, binary} @spec contents(pid) :: {binary, binary}
def contents(pid) when is_pid(pid) do def contents(pid) when is_pid(pid) do
:gen_server.call(pid, :contents) GenServer.call(pid, :contents)
end end
@doc """ @doc """
@@ -68,7 +68,7 @@ defmodule StringIO do
""" """
@spec close(pid) :: {:ok, {binary, binary}} @spec close(pid) :: {:ok, {binary, binary}}
def close(pid) when is_pid(pid) do def close(pid) when is_pid(pid) do
:gen_server.call(pid, :close) GenServer.call(pid, :close)
end end
## callbacks ## callbacks
@@ -199,7 +199,7 @@ defmodule StringIO do
end end
defp do_get_chars(input, :latin1, n) do defp do_get_chars(input, :latin1, n) do
<<chars :: [binary, size(n)], rest :: binary>> = input <<chars :: binary-size(n), rest :: binary>> = input
{chars, rest} {chars, rest}
end end
@@ -209,7 +209,7 @@ defmodule StringIO do
{buf_count, split_pos} when buf_count < n or split_pos == :none -> {buf_count, split_pos} when buf_count < n or split_pos == :none ->
{input, ""} {input, ""}
{_buf_count, split_pos} -> {_buf_count, split_pos} ->
<<chars :: [binary, size(split_pos)], rest :: binary>> = input <<chars :: binary-size(split_pos), rest :: binary>> = input
{chars, rest} {chars, rest}
end end
catch catch
+26 -10
View File
@@ -28,7 +28,7 @@ defmodule Supervisor do
{:reply, h, t} {:reply, h, t}
end end
def handle_cast({:push, h}, _from, t) do def handle_cast({:push, h}, t) do
{:noreply, [h|t]} {:noreply, [h|t]}
end end
end end
@@ -132,7 +132,7 @@ defmodule Supervisor do
* `:simple_one_for_one` - similar to `:one_for_one` but suits better * `:simple_one_for_one` - similar to `:one_for_one` but suits better
when dynamically attaching children. This strategy requires the 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 in this module behave slightly differently when this strategy is
used. used.
@@ -268,7 +268,9 @@ defmodule Supervisor do
and child specification. and child specification.
""" """
@spec start_child(supervisor, Supervisor.Spec.spec | [term]) :: on_start_child @spec start_child(supervisor, Supervisor.Spec.spec | [term]) :: on_start_child
defdelegate start_child(supervisor, child_spec_or_args), to: :supervisor def start_child(supervisor, child_spec_or_args) do
call(supervisor, {:start_child, child_spec_or_args})
end
@doc """ @doc """
Terminates the given pid or child id. Terminates the given pid or child id.
@@ -290,7 +292,9 @@ defmodule Supervisor do
""" """
@spec terminate_child(supervisor, pid | Supervisor.Spec.child_id) :: :ok | {:error, error} @spec terminate_child(supervisor, pid | Supervisor.Spec.child_id) :: :ok | {:error, error}
when error: :not_found | :simple_one_for_one when error: :not_found | :simple_one_for_one
defdelegate terminate_child(supervisor, pid_or_child_id), to: :supervisor def terminate_child(supervisor, pid_or_child_id) do
call(supervisor, {:terminate_child, pid_or_child_id})
end
@doc """ @doc """
Deletes the child specification identified by `child_id`. Deletes the child specification identified by `child_id`.
@@ -306,7 +310,9 @@ defmodule Supervisor do
""" """
@spec delete_child(supervisor, Supervisor.Spec.child_id) :: :ok | {:error, error} @spec delete_child(supervisor, Supervisor.Spec.child_id) :: :ok | {:error, error}
when error: :not_found | :simple_one_for_one | :running | :restarting when error: :not_found | :simple_one_for_one | :running | :restarting
defdelegate delete_child(supervisor, child_id), to: :supervisor def delete_child(supervisor, child_id) do
call(supervisor, {:delete_child, child_id})
end
@doc """ @doc """
Restarts a child process identified by `child_id`. Restarts a child process identified by `child_id`.
@@ -335,7 +341,9 @@ defmodule Supervisor do
@spec restart_child(supervisor, Supervisor.Spec.child_id) :: @spec restart_child(supervisor, Supervisor.Spec.child_id) ::
{:ok, child} | {:ok, child, term} | {:error, error} {:ok, child} | {:ok, child, term} | {:error, error}
when error: :not_found | :simple_one_for_one | :running | :restarting | term when error: :not_found | :simple_one_for_one | :running | :restarting | term
defdelegate restart_child(supervisor, child_id), to: :supervisor def restart_child(supervisor, child_id) do
call(supervisor, {:restart_child, child_id})
end
@doc """ @doc """
Returns a list with information about all children. Returns a list with information about all children.
@@ -361,7 +369,9 @@ defmodule Supervisor do
child | :restarting, child | :restarting,
Supervisor.Spec.worker, Supervisor.Spec.worker,
Supervisor.Spec.modules}] Supervisor.Spec.modules}]
defdelegate which_children(supervisor), to: :supervisor def which_children(supervisor) do
call(supervisor, :which_children)
end
@doc """ @doc """
Returns a map containing count values for the supervisor. Returns a map containing count values for the supervisor.
@@ -381,9 +391,15 @@ defmodule Supervisor do
""" """
@spec count_children(supervisor) :: @spec count_children(supervisor) ::
[specs: non_neg_integer, active: non_neg_integer, %{specs: non_neg_integer, active: non_neg_integer,
supervisors: non_neg_integer, workers: non_neg_integer] supervisors: non_neg_integer, workers: non_neg_integer}
def count_children(supervisor) do def count_children(supervisor) do
:supervisor.count_children(supervisor) |> :maps.from_list call(supervisor, :count_children) |> :maps.from_list
end
@compile {:inline, call: 2}
defp call(supervisor, req) do
GenServer.call(supervisor, req, :infinity)
end end
end end
+3 -3
View File
@@ -145,12 +145,12 @@ defmodule Supervisor.Spec do
in the `Supervisor` module docs. in the `Supervisor` module docs.
* `:max_restarts` - the maximum amount of restarts allowed in * `: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. * `:max_seconds` - the time frame in which `:max_restarts` applies.
Defaults to 5. 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 are allowed within 5 seconds. Please check the `Supervisor` module for
a complete description of the available strategies. a complete description of the available strategies.
""" """
@@ -162,7 +162,7 @@ defmodule Supervisor.Spec do
raise ArgumentError, "expected :strategy option to be given" raise ArgumentError, "expected :strategy option to be given"
end end
maxR = Keyword.get(options, :max_restarts, 5) maxR = Keyword.get(options, :max_restarts, 3)
maxS = Keyword.get(options, :max_seconds, 5) maxS = Keyword.get(options, :max_seconds, 5)
assert_unique_ids(Enum.map(children, &elem(&1, 0))) assert_unique_ids(Enum.map(children, &elem(&1, 0)))
+143 -41
View File
@@ -72,7 +72,7 @@ defmodule System do
""" """
@spec argv() :: [String.t] @spec argv() :: [String.t]
def argv do def argv do
:elixir_code_server.call :argv :elixir_config.get(:argv)
end end
@doc """ @doc """
@@ -83,7 +83,7 @@ defmodule System do
""" """
@spec argv([String.t]) :: :ok @spec argv([String.t]) :: :ok
def argv(args) do def argv(args) do
:elixir_code_server.cast({:argv, args}) :elixir_config.put(:argv, args)
end end
@doc """ @doc """
@@ -94,11 +94,20 @@ defmodule System do
""" """
def cwd do def cwd do
case :file.get_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 _ -> nil
end end
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 """ @doc """
Current working directory, exception on error. Current working directory, exception on error.
@@ -202,59 +211,32 @@ defmodule System do
end end
@doc """ @doc """
Register a program exit handler function. Registers a program exit handler function.
Registers a function that will be invoked Registers a function that will be invoked at the end of program execution.
at the end of program execution. Useful for Useful for invoking a hook in "script" mode.
invoking a hook in "script" mode.
The function must receive the exit status code The handler always executes in a different process from the one it was
as an argument. registered in. As a consequence, any resources managed by the calling process
(ETS tables, open files, etc.) won't be available by the time the handler
function is invoked.
The function must receive the exit status code as an argument.
""" """
def at_exit(fun) when is_function(fun, 1) do 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 end
@doc """ @doc """
Execute a system command. Locates an executable on the system.
Executes `command` in a command shell of the target OS,
captures the standard output of the command and returns
the result as a binary.
If `command` is a char list, a char list is returned.
Otherwise a string, correctly encoded in UTF-8, is expected.
"""
@spec cmd(String.t) :: String.t
@spec cmd(char_list) :: char_list
def cmd(command) when is_list(command) do
:os.cmd(command)
end
def cmd(command) when is_binary(command) do
List.to_string :os.cmd(String.to_char_list(command))
end
@doc """
Locate an executable on the system.
This function looks up an executable program given This function looks up an executable program given
its name using the environment variable PATH on Unix its name using the environment variable PATH on Unix
and Windows. It also considers the proper executable and Windows. It also considers the proper executable
extension for each OS, so for Windows it will try to extension for each OS, so for Windows it will try to
lookup files with `.com`, `.cmd` or similar extensions. lookup files with `.com`, `.cmd` or similar extensions.
If `program` is a char list, a char list is returned.
Returns a binary otherwise.
""" """
@spec find_executable(binary) :: binary | nil @spec find_executable(binary) :: binary | nil
@spec find_executable(char_list) :: char_list | nil
def find_executable(program) when is_list(program) do
:os.find_executable(program) || nil
end
def find_executable(program) when is_binary(program) do def find_executable(program) when is_binary(program) do
case :os.find_executable(String.to_char_list(program)) do case :os.find_executable(String.to_char_list(program)) do
false -> nil false -> nil
@@ -387,4 +369,124 @@ defmodule System do
def halt(status) when is_binary(status) do def halt(status) when is_binary(status) do
:erlang.halt(String.to_char_list(status)) :erlang.halt(String.to_char_list(status))
end end
@doc """
Executes the given `command` with `args`.
`command` is expected to be an executable available in PATH
unless an absolute path is given.
`args` must be a list of strings which are not expanded
in any way. For example, this means wildcard expansion will
not happen unless `Path.wildcard/2` is used. On Windows though,
wildcard expansion is up to the program.
A set of options are also supported and described below.
## Options
* `:into` - injects the result into the given collectable, defaults to `""`
* `:cd` - the directory to run the command in
* `:env` - an enumerable of tuples containing environment key-value as binary
* `:arg0` - set the command arg0
* `:stderr_to_stdout` - redirects stderr to stdout when true
* `:parallelism` - when true, the VM will schedule port tasks to improve
parallelism in the system. If set to false, the VM will try to perform
commands immediately, improving latency at the expense of parallelism.
The default can be set on system startup by passing the "+spp" argument
to `--erl`.
## Error reasons
If invalid arguments are given, `ArgumentError` is raised by
`System.cmd/3`. `System.cmd/3` also expects a strict set of
options and will raise if unknown or invalid options are given.
Furthermore, `System.cmd/3` may fail with one of the POSIX reasons
detailed below:
* `:system_limit` - all available ports in the Erlang emulator are in use
* `:enomem` - there was not enough memory to create the port
* `:eagain` - there are no more available operating system processes
* `:enametoolong` - the external command given was too long
* `:emfile` - there are no more available file descriptors
(for the operating system process that the Erlang emulator runs in)
* `:enfile` - the file table is full (for the entire operating system)
* `:eacces` - the command does not point to an executable file
* `:enoent` - the command does not point to an existing file
## Shell commands
If you desire to execute a trusted command inside a shell, with pipes,
redirecting and so on, please check
[Erlang's :os.cmd/1 function](http://www.erlang.org/doc/man/os.html#cmd-1).
"""
@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
cmd = String.to_char_list(command)
cmd =
if Path.type(cmd) == :absolute do
cmd
else
: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, cmd}, opts), initial, fun
end
defp do_cmd(port, acc, fun) do
receive do
{^port, {:data, data}} ->
do_cmd(port, fun.(acc, {:cont, data}), fun)
{^port, {:exit_status, status}} ->
{fun.(acc, :done), status}
end
end
defp cmd_opts([{:into, any}|t], opts, _into),
do: cmd_opts(t, opts, any)
defp cmd_opts([{:cd, bin}|t], opts, into) when is_binary(bin),
do: cmd_opts(t, [{:cd, bin}|opts], into)
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, 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)
defp cmd_opts([{:env, enum}|t], opts, into),
do: cmd_opts(t, [{:env, validate_env(enum)}|opts], into)
defp cmd_opts([{key, val}|_], _opts, _into),
do: raise(ArgumentError, "invalid option #{inspect key} with value #{inspect val}")
defp cmd_opts([], opts, into),
do: {into, opts}
defp validate_env(enum) do
Enum.map enum, fn
{k, v} ->
{String.to_char_list(k), String.to_char_list(v)}
other ->
raise ArgumentError, "invalid environment key-value #{inspect other}"
end
end
end end
+39 -5
View File
@@ -16,7 +16,7 @@ defmodule Task do
They are implemented by spawning a process that sends a message They are implemented by spawning a process that sends a message
to the caller once the given computation is performed. to the caller once the given computation is performed.
Besides `async/1` and `await/2`, tasks can also be Besides `async/1` and `await/2`, tasks can also be
started as part of supervision trees and dynamically spawned started as part of supervision trees and dynamically spawned
in remote nodes. We will explore all three scenarios next. in remote nodes. We will explore all three scenarios next.
@@ -58,7 +58,7 @@ defmodule Task do
that dynamically supervise tasks: that dynamically supervise tasks:
{:ok, pid} = Task.Supervisor.start_link() {:ok, pid} = Task.Supervisor.start_link()
Task.Supervisor.async(pid, fn -> do_work() end) Task.Supervisor.async(pid, MyMod, :my_fun, [arg1, arg2, arg3])
`Task.Supervisor` also makes it possible to spawn tasks in remote nodes as `Task.Supervisor` also makes it possible to spawn tasks in remote nodes as
long as the supervisor is registered locally or globally: long as the supervisor is registered locally or globally:
@@ -67,7 +67,7 @@ defmodule Task do
Task.Supervisor.start_link(name: :tasks_sup) Task.Supervisor.start_link(name: :tasks_sup)
# In the client # In the client
Task.Supervisor.async({:tasks_sup, :remote@local}, fn -> do_work() end) Task.Supervisor.async({:tasks_sup, :remote@local}, MyMod, :my_fun, [arg1, arg2, arg3])
`Task.Supervisor` is more often started in your supervision tree as: `Task.Supervisor` is more often started in your supervision tree as:
@@ -77,7 +77,15 @@ defmodule Task do
supervisor(Task.Supervisor, [[name: :tasks_sup]]) supervisor(Task.Supervisor, [[name: :tasks_sup]])
] ]
Check `Task.Supervisor` for other operations supported by the Task supervisor. Note that, when working with distributed tasks, one should use the `async/3` API,
that expects explicit module, function and arguments, instead of `async/1` that
works with anonymous functions. That's because the anonymous function API expects
the same module version to exist on all involved nodes. Check the `Agent` module
documentation for more information on distributed processes, as the limitations
described in the agents documentation apply to the whole ecosystem.
Finally, check `Task.Supervisor` for other operations supported by the Task
supervisor.
""" """
@doc """ @doc """
@@ -93,6 +101,8 @@ defmodule Task do
""" """
defstruct pid: nil, ref: nil defstruct pid: nil, ref: nil
@type t :: %__MODULE__{}
@doc """ @doc """
Starts a task as part of a supervision tree. Starts a task as part of a supervision tree.
""" """
@@ -109,6 +119,30 @@ defmodule Task do
Task.Supervised.start_link(get_info(self), {mod, fun, args}) Task.Supervised.start_link(get_info(self), {mod, fun, args})
end end
@doc """
Starts a task.
This is only used when the task is used for side-effects
(i.e. no interest in its return result) and it should not
be linked to the current process.
"""
@spec start(fun) :: {:ok, pid}
def start(fun) do
start(:erlang, :apply, [fun, []])
end
@doc """
Starts a task.
This is only used when the task is used for side-effects
(i.e. no interest in its return result) and it should not
be linked to the current process.
"""
@spec start(module, atom, [term]) :: {:ok, pid}
def start(mod, fun, args) do
Task.Supervised.start(get_info(self), {mod, fun, args})
end
@doc """ @doc """
Starts a task that can be awaited on. Starts a task that can be awaited on.
@@ -150,7 +184,7 @@ defmodule Task do
end end
@doc """ @doc """
Awaits for a task reply. Awaits a task reply.
A timeout, in milliseconds, can be given with default value A timeout, in milliseconds, can be given with default value
of `5000`. In case the task process dies, this function will of `5000`. In case the task process dies, this function will
+10 -6
View File
@@ -1,6 +1,10 @@
defmodule Task.Supervised do defmodule Task.Supervised do
@moduledoc false @moduledoc false
def start(info, fun) do
{:ok, :proc_lib.spawn(__MODULE__, :noreply, [info, fun])}
end
def start_link(info, fun) do def start_link(info, fun) do
{:ok, :proc_lib.spawn_link(__MODULE__, :noreply, [info, fun])} {:ok, :proc_lib.spawn_link(__MODULE__, :noreply, [info, fun])}
end end
@@ -89,12 +93,12 @@ defmodule Task.Supervised do
{fun, args} = get_running(mfa) {fun, args} = get_running(mfa)
:error_logger.format( :error_logger.format(
"** Task ~p terminating~n" <> '** Task ~p terminating~n' ++
"** Started from ~p~n" <> '** Started from ~p~n' ++
"** When function == ~p~n" <> '** When function == ~p~n' ++
"** arguments == ~p~n" <> '** arguments == ~p~n' ++
"** Reason for termination == ~n" <> '** Reason for termination == ~n' ++
"** ~p~n", [self, get_from(info), fun, args, reason]) '** ~p~n', [self, get_from(info), fun, args, reason])
exit(reason) exit(reason)
end end
+10 -1
View File
@@ -25,14 +25,23 @@ defmodule Task.Supervisor do
described under the `Name Registration` section in the `GenServer` module described under the `Name Registration` section in the `GenServer` module
docs; 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 * `:shutdown` - `:brutal_kill` if the tasks must be killed directly on shutdown
or an integer indicating the timeout value, defaults to 5000 milliseconds; 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 @spec start_link(Supervisor.options) :: Supervisor.on_start
def start_link(opts \\ []) do def start_link(opts \\ []) do
import Supervisor.Spec import Supervisor.Spec
{restart, opts} = Keyword.pop(opts, :restart, :temporary)
{shutdown, opts} = Keyword.pop(opts, :shutdown, 5000) {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) Supervisor.start_link(children, [strategy: :simple_one_for_one] ++ opts)
end end
+71 -42
View File
@@ -7,20 +7,21 @@ defmodule URI do
fragment: nil, authority: nil, fragment: nil, authority: nil,
userinfo: nil, host: nil, port: nil userinfo: nil, host: nil, port: nil
@type t :: %__MODULE__{}
import Bitwise import Bitwise
@ports %{ @schemes [
"ftp" => 21, "ftp",
"http" => 80, "http",
"https" => 443, "https",
"ldap" => 389, "ldap",
"sftp" => 22, "sftp",
"tftp" => 69, "tftp",
} ]
Enum.each @ports, fn {scheme, port} -> Enum.each @schemes, fn scheme ->
def normalize_scheme(unquote(scheme)), do: unquote(scheme) def normalize_scheme(unquote(scheme)), do: unquote(scheme)
def default_port(unquote(scheme)), do: unquote(port)
end end
@doc """ @doc """
@@ -45,8 +46,7 @@ defmodule URI do
""" """
def default_port(scheme) when is_binary(scheme) do def default_port(scheme) when is_binary(scheme) do
{:ok, dict} = Application.fetch_env(:elixir, :uri) :elixir_config.get({:uri, scheme})
Map.get(dict, scheme)
end end
@doc """ @doc """
@@ -57,8 +57,7 @@ defmodule URI do
new URIs. new URIs.
""" """
def default_port(scheme, port) when is_binary(scheme) and port > 0 do def default_port(scheme, port) when is_binary(scheme) and port > 0 do
{:ok, dict} = Application.fetch_env(:elixir, :uri) :elixir_config.put({:uri, scheme}, port)
Application.put_env(:elixir, :uri, Map.put(dict, scheme, port), persistent: true)
end end
@doc """ @doc """
@@ -66,7 +65,7 @@ defmodule URI do
Takes an enumerable (containing a sequence of two-item tuples) Takes an enumerable (containing a sequence of two-item tuples)
and returns a string of the form "key1=value1&key2=value2..." where and returns a string of the form "key1=value1&key2=value2..." where
keys and values are URL encoded as per `encode/1`. keys and values are URL encoded as per `encode/2`.
Keys and values can be any term that implements the `String.Chars` Keys and values can be any term that implements the `String.Chars`
protocol, except lists which are explicitly forbidden. protocol, except lists which are explicitly forbidden.
@@ -96,7 +95,10 @@ defmodule URI do
""" """
def decode_query(q, dict \\ %{}) when is_binary(q) 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 end
@doc """ @doc """
@@ -105,19 +107,19 @@ defmodule URI do
## Examples ## 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"}] [{"foo", "1"}, {"bar", "2"}]
""" """
def query_decoder(q) when is_binary(q) do def query_decoder(q) when is_binary(q) do
Stream.unfold(q, &do_decoder/1) Stream.unfold(q, &do_decode_query/1)
end end
defp do_decoder("") do defp do_decode_query("") do
nil nil
end end
defp do_decoder(q) do defp do_decode_query(q) do
{first, next} = {first, next} =
case :binary.split(q, "&") do case :binary.split(q, "&") do
[first, rest] -> {first, rest} [first, rest] -> {first, rest}
@@ -232,7 +234,7 @@ defmodule URI do
""" """
def decode(uri) do def decode(uri) do
unpercent(uri) unpercent(uri, "", false)
catch catch
:malformed_uri -> :malformed_uri ->
raise ArgumentError, "malformed URI #{inspect uri}" raise ArgumentError, "malformed URI #{inspect uri}"
@@ -248,23 +250,25 @@ defmodule URI do
""" """
def decode_www_form(str) do def decode_www_form(str) do
String.split(str, "+") |> Enum.map_join(" ", &unpercent/1) unpercent(str, "", true)
catch catch
:malformed_uri -> :malformed_uri ->
raise ArgumentError, "malformed URI #{inspect str}" raise ArgumentError, "malformed URI #{inspect str}"
end end
defp unpercent(<<?%, hex_1, hex_2, tail :: binary>>) do defp unpercent(<<?+, tail::binary>>, acc, spaces = true) do
<<bsl(hex_to_dec(hex_1), 4) + hex_to_dec(hex_2)>> <> unpercent(tail) unpercent(tail, <<acc::binary, ?\s>>, spaces)
end
defp unpercent(<<?%, _>>), do: throw(:malformed_uri)
defp unpercent(<<?%>>), do: throw(:malformed_uri)
defp unpercent(<<head, tail :: binary>>) do
<<head>> <> unpercent(tail)
end 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
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
@@ -272,12 +276,17 @@ defmodule URI do
defp hex_to_dec(_n), do: throw(:malformed_uri) defp hex_to_dec(_n), do: throw(:malformed_uri)
@doc """ @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 Note this function expects a well-formed URI and does not perform
scheme of the URI. For example, http and https have different any validation. See the examples section below of how `URI.parse/1`
default ports. Such values can be accessed and registered via can be used to parse a wide range of relative URIs.
`URI.default_port/1` and `URI.default_port/2`.
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 ## Examples
@@ -286,12 +295,24 @@ defmodule URI do
authority: "elixir-lang.org", userinfo: nil, authority: "elixir-lang.org", userinfo: nil,
host: "elixir-lang.org", port: 80} 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(%URI{} = uri), do: uri
def parse(s) when is_binary(s) do def parse(s) when is_binary(s) do
# From http://tools.ietf.org/html/rfc3986#appendix-B # 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)) parts = nillify(Regex.run(regex, s))
destructure [_, _, scheme, _, authority, path, _, query, _, fragment], parts destructure [_, _, scheme, _, authority, path, _, query, _, fragment], parts
@@ -307,7 +328,7 @@ defmodule URI do
scheme = normalize_scheme(scheme) scheme = normalize_scheme(scheme)
if nil?(port) and not nil?(scheme) do if is_nil(port) and not is_nil(scheme) do
port = default_port(scheme) port = default_port(scheme)
end end
@@ -347,12 +368,20 @@ defimpl String.Chars, for: URI do
if uri.port == port, do: uri = %{uri | port: nil} if uri.port == port, do: uri = %{uri | port: nil}
end end
# Based on http://tools.ietf.org/html/rfc3986#section-5.3
if uri.host do
authority = uri.host
if uri.userinfo, do: authority = uri.userinfo <> "@" <> authority
if uri.port, do: authority = authority <> ":" <> Integer.to_string(uri.port)
else
authority = uri.authority
end
result = "" result = ""
if uri.scheme, do: result = result <> uri.scheme <> "://" if uri.scheme, do: result = result <> uri.scheme <> ":"
if uri.userinfo, do: result = result <> uri.userinfo <> "@" if authority, do: result = result <> "//" <> authority
if uri.host, do: result = result <> uri.host
if uri.port, do: result = result <> ":" <> Integer.to_string(uri.port)
if uri.path, do: result = result <> uri.path if uri.path, do: result = result <> uri.path
if uri.query, do: result = result <> "?" <> uri.query if uri.query, do: result = result <> "?" <> uri.query
if uri.fragment, do: result = result <> "#" <> uri.fragment if uri.fragment, do: result = result <> "#" <> uri.fragment
+21 -7
View File
@@ -58,14 +58,26 @@ defmodule Version do
@type version :: String.t | t @type version :: String.t | t
@type requirement :: String.t | Version.Requirement.t @type requirement :: String.t | Version.Requirement.t
@type matchable :: {major :: String.t | non_neg_integer, @type major :: String.t | non_neg_integer
minor :: non_neg_integer | nil, @type minor :: non_neg_integer | nil
patch :: non_neg_integer | nil, @type patch :: non_neg_integer | nil
pre :: [String.t]} @type pre :: [String.t]
@type build :: String.t | nil
@type matchable :: {major :: major,
minor :: minor,
patch :: patch,
pre :: pre}
@type t :: %__MODULE__{
major: major,
minor: minor,
patch: patch,
pre: pre,
build: build}
defmodule Requirement do defmodule Requirement do
@moduledoc false @moduledoc false
defstruct [:source, :matchspec] defstruct [:source, :matchspec]
@type t :: %__MODULE__{}
end end
defmodule InvalidRequirementError do defmodule InvalidRequirementError do
@@ -157,7 +169,8 @@ defmodule Version do
## Examples ## Examples
iex> Version.parse("2.0.1-alpha1") |> elem(1) iex> {:ok, version} = Version.parse("2.0.1-alpha1")
iex> version
#Version<2.0.1-alpha1> #Version<2.0.1-alpha1>
iex> Version.parse("2.0-alpha1") iex> Version.parse("2.0-alpha1")
@@ -181,7 +194,8 @@ defmodule Version do
## Examples ## Examples
iex> Version.parse_requirement("== 2.0.1") |> elem(1) iex> {:ok, req} = Version.parse_requirement("== 2.0.1")
iex> req
#Version.Requirement<== 2.0.1> #Version.Requirement<== 2.0.1>
iex> Version.parse_requirement("== == 2.0.1") iex> Version.parse_requirement("== == 2.0.1")
@@ -310,7 +324,7 @@ defmodule Version do
patch = nillify(patch) patch = nillify(patch)
pre = nillify(pre) pre = nillify(pre)
if nil?(minor) or (nil?(patch) and not approximate?) do if is_nil(minor) or (is_nil(patch) and not approximate?) do
:error :error
else else
major = String.to_integer(major) major = String.to_integer(major)
+1 -4
View File
@@ -4,9 +4,6 @@ defmodule Elixir.Mixfile do
def project do def project do
[app: :elixir, [app: :elixir,
version: System.version, version: System.version,
build_per_environment: false, build_per_environment: false]
escript_embed_elixir: false,
escript_main_module: :elixir,
escript_emu_args: "%%! -noshell\n"]
end end
end end
+50 -16
View File
@@ -2,7 +2,7 @@
%% private to the Elixir compiler and reserved to be used by Elixir only. %% private to the Elixir compiler and reserved to be used by Elixir only.
-module(elixir). -module(elixir).
-behaviour(application). -behaviour(application).
-export([main/1, start_cli/0, -export([start_cli/0,
string_to_quoted/4, 'string_to_quoted!'/4, string_to_quoted/4, 'string_to_quoted!'/4,
env_for_eval/1, env_for_eval/2, quoted_to_erl/2, quoted_to_erl/3, env_for_eval/1, env_for_eval/2, quoted_to_erl/2, quoted_to_erl/3,
eval/2, eval/3, eval_forms/3, eval_forms/4, eval_quoted/3]). eval/2, eval/3, eval_forms/3, eval_forms/4, eval_quoted/3]).
@@ -28,8 +28,13 @@ start(_Type, _Args) ->
error -> [binary] error -> [binary]
end, end,
io:setopts(standard_io, Opts), ok = io:setopts(standard_io, Opts),
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 case file:native_name_encoding() of
latin1 -> latin1 ->
io:format(standard_error, io:format(standard_error,
@@ -40,24 +45,48 @@ start(_Type, _Args) ->
ok ok
end, 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) -> config_change(_Changed, _New, _Remove) ->
ok. ok.
%% escript entry point
main(Args) ->
application:start(?MODULE),
'Elixir.Kernel.CLI':main(Args).
%% Boot and process given options. Invoked by Elixir's script. %% Boot and process given options. Invoked by Elixir's script.
start_cli() -> start_cli() ->
application:start(?MODULE), {ok, _} = application:ensure_all_started(?MODULE),
%% We start the Logger so tools that depend on Elixir
%% always have the Logger directly accessible. However
%% Logger is not a dependency of the Elixir application,
%% which means releases that want to use Logger must
%% always list it as part of its applications.
_ = case code:ensure_loaded('Elixir.Logger') of
{module, _} -> application:start(logger);
{error, _} -> ok
end,
'Elixir.Kernel.CLI':main(init:get_plain_arguments()). 'Elixir.Kernel.CLI':main(init:get_plain_arguments()).
%% EVAL HOOKS %% EVAL HOOKS
@@ -153,14 +182,19 @@ eval_forms(Tree, Binding, Env, Scope) ->
{atom, _, Atom} -> {atom, _, Atom} ->
{Atom, Binding, NewEnv, NewScope}; {Atom, Binding, NewEnv, NewScope};
_ -> _ ->
{value, Value, NewBinding} = erl_eval(Erl, ParsedBinding), {value, Value, NewBinding} = erl_eval(Erl, ParsedBinding, Env),
{Value, elixir_scope:dump_binding(NewBinding, NewScope), NewEnv, NewScope} {Value, elixir_scope:dump_binding(NewBinding, NewScope), NewEnv, NewScope}
end. end.
erl_eval(Erl, ParsedBinding) -> erl_eval(Erl, ParsedBinding, E) ->
case erl_eval:check_command([Erl], ParsedBinding) of
ok -> ok;
{error, Desc} -> elixir_errors:handle_file_error(?m(E, file), Desc)
end,
% Below must be all one line for locations to be the same when the stacktrace % Below must be all one line for locations to be the same when the stacktrace
% needs to be extended to the full stacktrace. % needs to be extended to the full stacktrace.
try erl_eval:expr(Erl, ParsedBinding) catch Class:Exception -> erlang:raise(Class, Exception, get_stacktrace()) end. try erl_eval:expr(Erl, ParsedBinding, none, none, none) catch Class:Exception -> erlang:raise(Class, Exception, get_stacktrace()) end.
get_stacktrace() -> get_stacktrace() ->
Stacktrace = erlang:get_stacktrace(), Stacktrace = erlang:get_stacktrace(),
@@ -21,7 +21,7 @@
-record(elixir_quote, { -record(elixir_quote, {
line=false, line=false,
keep=false, file=nil,
context=nil, context=nil,
vars_hygiene=true, vars_hygiene=true,
aliases_hygiene=true, aliases_hygiene=true,
@@ -41,28 +41,29 @@
%% Used in tokenization and interpolation %% Used in tokenization and interpolation
%% Numbers %% Numbers
-define(is_hex(S), ?is_digit(S) orelse (S >= $A andalso S =< $F) orelse (S >= $a andalso S =< $f)). -define(is_hex(S), (?is_digit(S) orelse (S >= $A andalso S =< $F) orelse (S >= $a andalso S =< $f))).
-define(is_bin(S), S >= $0 andalso S =< $1). -define(is_bin(S), (S >= $0 andalso S =< $1)).
-define(is_octal(S), S >= $0 andalso S =< $7). -define(is_octal(S), (S >= $0 andalso S =< $7)).
-define(is_leading_octal(S), S >= $0 andalso S =< $3). -define(is_leading_octal(S), (S >= $0 andalso S =< $3)).
%% Digits and letters %% Digits and letters
-define(is_digit(S), S >= $0 andalso S =< $9). -define(is_digit(S), (S >= $0 andalso S =< $9)).
-define(is_upcase(S), S >= $A andalso S =< $Z). -define(is_upcase(S), (S >= $A andalso S =< $Z)).
-define(is_downcase(S), S >= $a andalso S =< $z). -define(is_downcase(S), (S >= $a andalso S =< $z)).
%% Atoms %% Atoms
-define(is_atom_start(S), ?is_quote(S) orelse ?is_upcase(S) orelse ?is_downcase(S) orelse (S == $_)). -define(is_atom_start(S), (?is_quote(S) orelse ?is_upcase(S) orelse ?is_downcase(S) orelse (S == $_))).
-define(is_atom(S), ?is_identifier(S) orelse (S == $@)). -define(is_atom(S), (?is_identifier(S) orelse (S == $@))).
-define(is_identifier(S), ?is_digit(S) orelse ?is_upcase(S) orelse ?is_downcase(S) orelse (S == $_)). -define(is_identifier(S), (?is_digit(S) orelse ?is_upcase(S) orelse ?is_downcase(S) orelse (S == $_))).
-define(is_sigil(S), (S == $/) orelse (S == $<) orelse (S == $") orelse (S == $') orelse -define(is_sigil(S), ((S == $/) orelse (S == $<) orelse (S == $") orelse (S == $') orelse
(S == $[) orelse (S == $() orelse (S == ${) orelse (S == $|)). (S == $[) orelse (S == $() orelse (S == ${) orelse (S == $|))).
%% Quotes %% Quotes
-define(is_quote(S), S == $" orelse S == $'). -define(is_quote(S), (S == $" orelse S == $')).
%% Spaces %% Spaces
-define(is_horizontal_space(S), (S == $\s) orelse (S == $\t)). -define(is_horizontal_space(S), ((S == $\s) orelse (S == $\t))).
-define(is_vertical_space(S), (S == $\r) orelse (S == $\n)). -define(is_vertical_space(S), ((S == $\r) orelse (S == $\n))).
-define(is_space(S), ?is_horizontal_space(S) orelse ?is_vertical_space(S)). -define(is_space(S), (?is_horizontal_space(S) orelse ?is_vertical_space(S))).
-define(is_invalid_space(S), (S == 16#A0)).
+93 -67
View File
@@ -35,70 +35,99 @@ expand_bitstr(Fun, [H|T], Acc, E) ->
%% Expand bit info %% Expand bit info
expand_bit_info(Meta, Info, E) when is_list(Info) ->
expand_bit_info(Meta, Info, default, [], E);
expand_bit_info(Meta, Info, E) -> expand_bit_info(Meta, Info, E) ->
expand_bit_info(Meta, [Info], E). expand_bit_info(Meta, unpack_bit_info(Info, []), default, [], E).
expand_bit_info(Meta, [{Expr, ExprMeta, Args}|T], Size, Types, E) when is_atom(Expr) -> expand_bit_info(Meta, [{size, _, [_]=Args}|T], Size, Types, E) ->
ListArgs = if is_atom(Args) -> []; is_list(Args) -> Args end, case Size of
case expand_bit_type_or_size(Expr, ListArgs) of default ->
type -> {[EArg], EE} = elixir_exp:expand_args(Args, E),
{EArgs, EE} = elixir_exp:expand_args(ListArgs, E),
expand_bit_info(Meta, T, Size, [{Expr, [], EArgs}|Types], EE); case EArg of
size -> {Var, _, Context} when is_atom(Var) and is_atom(Context) ->
case Size of ok;
default -> ok; _ when is_integer(EArg) ->
_ -> elixir_errors:compile_error(Meta, ?m(E, file), "duplicated size definition in bitstring") ok;
_ ->
elixir_errors:compile_error(Meta, ?m(E, file),
"size in bitstring expects an integer or a variable as argument, got: ~ts",
['Elixir.Macro':to_string(EArg)])
end, end,
{EArgs, EE} = elixir_exp:expand_args(ListArgs, E),
expand_bit_info(Meta, T, {Expr, [], EArgs}, Types, EE); expand_bit_info(Meta, T, {size, [], [EArg]}, Types, EE);
none -> _ ->
handle_unknown_bit_info(Meta, {Expr, ExprMeta, ListArgs}, T, Size, Types, E) elixir_errors:compile_error(Meta, ?m(E, file),
"duplicated size definition in bitstring")
end; end;
expand_bit_info(Meta, [Int|T], Size, Types, E) when is_integer(Int) -> expand_bit_info(Meta, [{Expr, ExprMeta, Args}|T], Size, Types, E) when is_atom(Expr) ->
expand_bit_info(Meta, [{size, [], [Int]}|T], Size, Types, E); case expand_bit_type(Expr, Args) of
type ->
{EArgs, EE} = elixir_exp:expand_args(Args, E),
validate_bit_type_args(Meta, Expr, EArgs, EE),
expand_bit_info(Meta, T, Size, [{Expr, [], EArgs}|Types], EE);
none ->
handle_unknown_bit_info(Meta, {Expr, ExprMeta, Args}, T, Size, Types, E)
end;
expand_bit_info(Meta, [Expr|_], _Size, _Types, E) -> expand_bit_info(Meta, [Expr|_], _Size, _Types, E) ->
elixir_errors:compile_error(Meta, ?m(E, file), elixir_errors:compile_error(Meta, ?m(E, file),
"unknown bitstring specifier ~ts", ['Elixir.Kernel':inspect(Expr)]); "unknown bitstring specifier ~ts", ['Elixir.Kernel':inspect(Expr)]);
expand_bit_info(_Meta, [], Size, Types, _) -> expand_bit_info(Meta, [], Size, Types, _) ->
case Size of [H|T] = case Size of
default -> lists:reverse(Types); default -> lists:reverse(Types);
_ -> [Size|lists:reverse(Types)] _ -> lists:reverse(Types, [Size])
end. end,
lists:foldl(fun(I, Acc) -> {'-', Meta, [Acc, I]} end, H, T).
expand_bit_type_or_size(binary, []) -> type; expand_bit_type(binary, []) -> type;
expand_bit_type_or_size(integer, []) -> type; expand_bit_type(integer, []) -> type;
expand_bit_type_or_size(float, []) -> type; expand_bit_type(float, []) -> type;
expand_bit_type_or_size(bitstring, []) -> type; expand_bit_type(bitstring, []) -> type;
expand_bit_type_or_size(bytes, []) -> type; expand_bit_type(bytes, []) -> type;
expand_bit_type_or_size(bits, []) -> type; expand_bit_type(bits, []) -> type;
expand_bit_type_or_size(utf8, []) -> type; expand_bit_type(utf8, []) -> type;
expand_bit_type_or_size(utf16, []) -> type; expand_bit_type(utf16, []) -> type;
expand_bit_type_or_size(utf32, []) -> type; expand_bit_type(utf32, []) -> type;
expand_bit_type_or_size(signed, []) -> type; expand_bit_type(signed, []) -> type;
expand_bit_type_or_size(unsigned, []) -> type; expand_bit_type(unsigned, []) -> type;
expand_bit_type_or_size(big, []) -> type; expand_bit_type(big, []) -> type;
expand_bit_type_or_size(little, []) -> type; expand_bit_type(little, []) -> type;
expand_bit_type_or_size(native, []) -> type; expand_bit_type(native, []) -> type;
expand_bit_type_or_size(unit, [_]) -> type; expand_bit_type(unit, [_]) -> type;
expand_bit_type_or_size(size, [_]) -> size; expand_bit_type(_, _) -> none.
expand_bit_type_or_size(_, _) -> none.
handle_unknown_bit_info(Meta, {_, ExprMeta, _} = Expr, T, Size, Types, E) -> validate_bit_type_args(Meta, unit, [Unit], E) when not is_integer(Unit) ->
case 'Elixir.Macro':expand(Expr, elixir_env:linify({?line(ExprMeta), E})) of elixir_errors:compile_error(Meta, ?m(E, file),
"unit in bitstring expects an integer as argument, got: ~ts",
['Elixir.Macro':to_string(Unit)]);
validate_bit_type_args(_Meta, _Expr, _Args, _E) ->
ok.
handle_unknown_bit_info(Meta, Expr, T, Size, Types, E) ->
case 'Elixir.Macro':expand(Expr, elixir_env:linify({?line(Meta), E})) of
Expr -> Expr ->
elixir_errors:compile_error(ExprMeta, ?m(E, file), elixir_errors:compile_error(Meta, ?m(E, file),
"unknown bitstring specifier ~ts", ['Elixir.Macro':to_string(Expr)]); "unknown bitstring specifier ~ts", ['Elixir.Macro':to_string(Expr)]);
Other -> Info ->
List = case is_list(Other) of true -> Other; false -> [Other] end, expand_bit_info(Meta, unpack_bit_info(Info, []) ++ T, Size, Types, E)
expand_bit_info(Meta, List ++ T, Size, Types, E)
end. end.
unpack_bit_info({'-', _, [H, T]}, Acc) ->
unpack_bit_info(H, unpack_bit_info(T, Acc));
unpack_bit_info({'*', _, [{'_', _, Atom}, Unit]}, Acc) when is_atom(Atom) and is_integer(Unit) ->
[{unit, [], [Unit]}|Acc];
unpack_bit_info({'*', _, [Size, Unit]}, Acc) when is_integer(Size) and is_integer(Unit) ->
[{size, [], [Size]}, {unit, [], [Unit]}|Acc];
unpack_bit_info(Size, Acc) when is_integer(Size) ->
[{size, [], [Size]}|Acc];
unpack_bit_info({Expr, Meta, Args}, Acc) when is_atom(Expr) ->
ListArgs = if is_atom(Args) -> []; is_list(Args) -> Args end,
[{Expr, Meta, ListArgs}|Acc];
unpack_bit_info(Other, Acc) ->
[Other|Acc].
%% Translation %% Translation
has_size({bin, _, Elements}) -> has_size({bin, _, Elements}) ->
@@ -124,7 +153,7 @@ build_bitstr_each(_Fun, [], _Meta, S, Acc) ->
{Acc, S}; {Acc, S};
build_bitstr_each(Fun, [{'::',_,[H,V]}|T], Meta, S, Acc) -> build_bitstr_each(Fun, [{'::',_,[H,V]}|T], Meta, S, Acc) ->
{Size, Types} = extract_bit_info(Meta, V, S#elixir_scope{context=nil}), {Size, Types} = extract_bit_info(V, S#elixir_scope{context=nil}),
build_bitstr_each(Fun, T, Meta, S, Acc, H, Size, Types); build_bitstr_each(Fun, T, Meta, S, Acc, H, Size, Types);
build_bitstr_each(Fun, [H|T], Meta, S, Acc) -> build_bitstr_each(Fun, [H|T], Meta, S, Acc) ->
@@ -201,23 +230,20 @@ unit_size([], Guess) -> Guess.
%% Extra bitstring specifiers %% Extra bitstring specifiers
extract_bit_info(Meta, [{size, _, [Arg]}|T], S) -> extract_bit_info({'-', _, [L, {size, _, [Size]}]}, S) ->
case elixir_translator:translate(Arg, S) of {extract_bit_size(Size, S), extract_bit_type(L, [])};
{{Kind, _, _} = Size, _} when Kind == integer; Kind == var -> extract_bit_info({size, _, [Size]}, S) ->
{Size, extract_bit_type(Meta, T, S)}; {extract_bit_size(Size, S), []};
_ -> extract_bit_info(L, _S) ->
elixir_errors:compile_error(Meta, S#elixir_scope.file, {default, extract_bit_type(L, [])}.
"size in bitstring expects an integer or a variable as argument, got: ~ts", ['Elixir.Macro':to_string(Arg)])
end;
extract_bit_info(Meta, T, S) ->
{default, extract_bit_type(Meta, T, S)}.
extract_bit_type(Meta, [{unit, _, [Arg]}|T], S) when is_integer(Arg) -> extract_bit_size(Size, S) ->
[{unit, Arg}|extract_bit_type(Meta, T, S)]; {TSize, _} = elixir_translator:translate(Size, S),
extract_bit_type(Meta, [{unit, _, [Arg]}|_], S) -> TSize.
elixir_errors:compile_error(Meta, S#elixir_scope.file,
"unit in bitstring expects an integer as argument, got: ~ts", ['Elixir.Macro':to_string(Arg)]); extract_bit_type({'-', _, [L, R]}, Acc) ->
extract_bit_type(Meta, [{Other, _, []}|T], S) -> extract_bit_type(L, extract_bit_type(R, Acc));
[Other|extract_bit_type(Meta, T, S)]; extract_bit_type({unit, _, [Arg]}, Acc) ->
extract_bit_type(_Meta, [], _S) -> [{unit, Arg}|Acc];
[]. extract_bit_type({Other, _, []}, Acc) ->
[Other|Acc].
+1 -1
View File
@@ -44,7 +44,7 @@ define({Line,E}, Kind, Call, Expr) ->
unless_loaded(Fun, Args, Callback) -> unless_loaded(Fun, Args, Callback) ->
case code:is_loaded(?kernel) of case code:is_loaded(?kernel) of
{_, _} -> apply(?kernel, Fun, Args); {_, _} -> apply(?kernel, Fun, Args);
false -> Callback() false -> Callback()
end. end.
env() -> env() ->
+50 -66
View File
@@ -6,13 +6,11 @@
-define(timeout, 30000). -define(timeout, 30000).
-record(elixir_code_server, { -record(elixir_code_server, {
compilation_status=[],
argv=[],
loaded=[], loaded=[],
at_exit=[], paths={[],[]},
pool={[],0}, mod_pool={[],0},
compiler_options=[{docs,true},{debug_info,true},{warnings_as_errors,false}], mod_ets=dict:new(),
erl_compiler_options=nil compilation_status=[]
}). }).
call(Args) -> call(Args) ->
@@ -27,11 +25,22 @@ start_link() ->
gen_server:start_link({local, ?MODULE}, ?MODULE, ok, []). gen_server:start_link({local, ?MODULE}, ?MODULE, ok, []).
init(ok) -> init(ok) ->
code:ensure_loaded('Elixir.Macro.Env'), %% We attempt to load those modules here so throughout
code:ensure_loaded('Elixir.Module.LocalsTracker'), %% the codebase we can avoid code:is_loaded/1 checks.
code:ensure_loaded('Elixir.Kernel.LexicalTracker'), _ = code:ensure_loaded('Elixir.Macro.Env'),
%% The table where we store module definitions
_ = ets:new(elixir_modules, [set, protected, named_table, {read_concurrency, true}]),
{ok, #elixir_code_server{}}. {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) -> handle_call({acquire, Path}, From, Config) ->
Current = Config#elixir_code_server.loaded, Current = Config#elixir_code_server.loaded,
case orddict:find(Path, Current) of case orddict:find(Path, Current) of
@@ -48,58 +57,32 @@ handle_call({acquire, Path}, From, Config) ->
handle_call(loaded, _From, Config) -> handle_call(loaded, _From, Config) ->
{reply, [F || {F, true} <- Config#elixir_code_server.loaded], 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) -> handle_call({compilation_status, CompilerPid}, _From, Config) ->
CompilationStatusList = Config#elixir_code_server.compilation_status, CompilationStatusList = Config#elixir_code_server.compilation_status,
CompilationStatusListNew = orddict:erase(CompilerPid, CompilationStatusList), CompilationStatusListNew = orddict:erase(CompilerPid, CompilationStatusList),
CompilationStatus = orddict:fetch(CompilerPid, CompilationStatusList), CompilationStatus = orddict:fetch(CompilerPid, CompilationStatusList),
{reply, CompilationStatus, Config#elixir_code_server{compilation_status=CompilationStatusListNew}}; {reply, CompilationStatus,
Config#elixir_code_server{compilation_status=CompilationStatusListNew}};
handle_call(retrieve_module_name, _From, Config) -> 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} -> {[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} -> {[], Counter} ->
{reply, module_tuple(Counter), Config#elixir_code_server{pool={[],Counter+1}}} {reply, module_tuple(Counter), Config#elixir_code_server{mod_pool={[],Counter+1}}}
end; end;
handle_call(erl_compiler_options, _From, Config) -> handle_call(paths, _From, Config) ->
case Config#elixir_code_server.erl_compiler_options of {reply, Config#elixir_code_server.paths, Config};
nil ->
Opts = erl_compiler_options(),
{reply, Opts, Config#elixir_code_server{erl_compiler_options=Opts}};
Opts ->
{reply, Opts, Config}
end;
handle_call(Request, _From, Config) -> handle_call(Request, _From, Config) ->
{stop, {badcall, Request}, 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) -> handle_cast({register_warning, CompilerPid}, Config) ->
CompilationStatusCurrent = Config#elixir_code_server.compilation_status, CompilationStatusCurrent = Config#elixir_code_server.compilation_status,
CompilationStatusNew = orddict:store(CompilerPid, error, CompilationStatusCurrent), 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}}; {ok, true} -> {noreply, Config#elixir_code_server{compilation_status=CompilationStatusNew}};
_ -> {noreply, Config} _ -> {noreply, Config}
end; end;
@@ -115,7 +98,7 @@ handle_cast({loaded, Path}, Config) ->
{ok, true} -> {ok, true} ->
{noreply, Config}; {noreply, Config};
{ok, {Ref, List}} when is_list(List), is_reference(Ref) -> {ok, {Ref, List}} when is_list(List), is_reference(Ref) ->
[Pid ! {elixir_code_server, Ref, loaded} || {Pid, _Tag} <- lists:reverse(List)], _ = [Pid ! {elixir_code_server, Ref, loaded} || {Pid, _Tag} <- lists:reverse(List)],
Done = orddict:store(Path, true, Current), Done = orddict:store(Path, true, Current),
{noreply, Config#elixir_code_server{loaded=Done}}; {noreply, Config#elixir_code_server{loaded=Done}};
error -> error ->
@@ -128,13 +111,19 @@ handle_cast({unload_files, Files}, Config) ->
Unloaded = lists:foldl(fun(File, Acc) -> orddict:erase(File, Acc) end, Current, Files), Unloaded = lists:foldl(fun(File, Acc) -> orddict:erase(File, Acc) end, Current, Files),
{noreply, Config#elixir_code_server{loaded=Unloaded}}; {noreply, Config#elixir_code_server{loaded=Unloaded}};
handle_cast({return_module_name, H}, #elixir_code_server{pool={T,Counter}} = Config) -> handle_cast({return_module_name, H}, #elixir_code_server{mod_pool={T,Counter}} = Config) ->
{noreply, Config#elixir_code_server{pool={[H|T],Counter}}}; {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}}};
handle_cast(Request, Config) -> handle_cast(Request, Config) ->
{stop, {badcast, 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}. {noreply, Config}.
terminate(_Reason, _Config) -> terminate(_Reason, _Config) ->
@@ -146,22 +135,17 @@ code_change(_Old, Config, _Extra) ->
module_tuple(I) -> module_tuple(I) ->
{list_to_atom("elixir_compiler_" ++ integer_to_list(I)), I}. {list_to_atom("elixir_compiler_" ++ integer_to_list(I)), I}.
erl_compiler_options() -> defmodule(Pid, Tuple, #elixir_code_server{mod_ets=ModEts} = Config) ->
Key = "ERL_COMPILER_OPTIONS", ets:insert(elixir_modules, Tuple),
case os:getenv(Key) of Ref = erlang:monitor(process, Pid),
false -> []; Mod = erlang:element(1, Tuple),
Str when is_list(Str) -> {Ref, Config#elixir_code_server{mod_ets=dict:store(Ref, Mod, ModEts)}}.
case erl_scan:string(Str) of
{ok,Tokens,_} -> undefmodule(Ref, #elixir_code_server{mod_ets=ModEts} = Config) ->
case erl_parse:parse_term(Tokens ++ [{dot, 1}]) of case dict:find(Ref, ModEts) of
{ok,List} when is_list(List) -> List; {ok, Mod} ->
{ok,Term} -> [Term]; ets:delete(elixir_modules, Mod),
{error,_Reason} -> Config#elixir_code_server{mod_ets=dict:erase(Ref, ModEts)};
io:format("Ignoring bad term in ~ts\n", [Key]), error ->
[] Config
end;
{error, {_,_,_Reason}, _} ->
io:format("Ignoring bad term in ~ts\n", [Key]),
[]
end
end. end.
+79 -27
View File
@@ -1,12 +1,12 @@
-module(elixir_compiler). -module(elixir_compiler).
-export([get_opt/1, string/2, quoted/2, file/1, file_to_path/2]). -export([get_opt/1, string/2, quoted/2, file/1, file/2, file_to_path/2]).
-export([core/0, module/4, eval_forms/3]). -export([core/0, module/4, eval_forms/3]).
-include("elixir.hrl"). -include("elixir.hrl").
%% Public API %% Public API
get_opt(Key) -> get_opt(Key) ->
Dict = elixir_code_server:call(compiler_options), Dict = elixir_config:get(compiler_options),
case lists:keyfind(Key, 1, Dict) of case lists:keyfind(Key, 1, Dict) of
false -> false; false -> false;
{Key, Value} -> Value {Key, Value} -> Value
@@ -15,15 +15,19 @@ get_opt(Key) ->
%% Compilation entry points. %% Compilation entry points.
string(Contents, File) when is_list(Contents), is_binary(File) -> string(Contents, File) when is_list(Contents), is_binary(File) ->
string(Contents, File, nil).
string(Contents, File, Dest) ->
Forms = elixir:'string_to_quoted!'(Contents, 1, File, []), Forms = elixir:'string_to_quoted!'(Contents, 1, File, []),
quoted(Forms, File). quoted(Forms, File, Dest).
quoted(Forms, File) when is_binary(File) -> quoted(Forms, File) when is_binary(File) ->
quoted(Forms, File, nil).
quoted(Forms, File, Dest) ->
Previous = get(elixir_compiled), Previous = get(elixir_compiled),
try try
put(elixir_compiled, []), put(elixir_compiled, []),
elixir_lexical:run(File, fun elixir_lexical:run(File, Dest, fun
(Pid) -> (Pid) ->
Env = elixir:env_for_eval([{line,1},{file,File}]), Env = elixir:env_for_eval([{line,1},{file,File}]),
eval_forms(Forms, [], Env#{lexical_tracker := Pid}) eval_forms(Forms, [], Env#{lexical_tracker := Pid})
@@ -34,14 +38,20 @@ quoted(Forms, File) when is_binary(File) ->
end. end.
file(Relative) when is_binary(Relative) -> file(Relative) when is_binary(Relative) ->
file(Relative, nil).
file(Relative, Dest) ->
File = filename:absname(Relative), File = filename:absname(Relative),
{ok, Bin} = file:read_file(File), {ok, Bin} = file:read_file(File),
string(elixir_utils:characters_to_list(Bin), File). string(elixir_utils:characters_to_list(Bin), File, case Dest of
nil -> Dest;
_ -> filename:absname(Dest)
end).
file_to_path(File, Path) when is_binary(File), is_binary(Path) -> file_to_path(File, Dest) when is_binary(File), is_binary(Dest) ->
Lists = file(File), Comp = file(File, Dest),
[binary_to_path(X, Path) || X <- Lists], Abs = filename:absname(Dest),
Lists. _ = [binary_to_path(X, Abs) || X <- Comp],
Comp.
%% Evaluation %% Evaluation
@@ -68,9 +78,9 @@ code_loading_compilation(Forms, Vars, #{line := Line} = E) ->
%% Pass {native, false} to speed up bootstrap %% Pass {native, false} to speed up bootstrap
%% process when native is set to true %% process when native is set to true
AllOpts = elixir_code_server:call(erl_compiler_options), AllOpts = options(),
FinalOpts = AllOpts -- [native, warn_missing_spec], FinalOpts = AllOpts -- [native, warn_missing_spec],
module(Form, ?m(E, file), FinalOpts, true, fun(_, Binary) -> inner_module(Form, FinalOpts, true, E, fun(_, Binary) ->
%% If we have labeled locals, anonymous functions %% If we have labeled locals, anonymous functions
%% were created and therefore we cannot ditch the %% were created and therefore we cannot ditch the
%% module %% module
@@ -82,6 +92,36 @@ code_loading_compilation(Forms, Vars, #{line := Line} = E) ->
dispatch_loaded(Module, Fun, Args, Purgeable, I, EE) dispatch_loaded(Module, Fun, Args, Purgeable, I, EE)
end). 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) -> dispatch_loaded(Module, Fun, Args, Purgeable, I, E) ->
Res = Module:Fun(Args), Res = Module:Fun(Args),
code:delete(Module), code:delete(Module),
@@ -128,43 +168,55 @@ allows_fast_compilation(_) -> false.
%% Compile the module by forms based on the scope information %% Compile the module by forms based on the scope information
%% executes the callback in case of success. This automatically %% executes the callback in case of success. This automatically
%% handles errors and warnings. Used by this module and elixir_module. %% handles errors and warnings. Used by this module and elixir_module.
module(Forms, File, Opts, Callback) -> module(Forms, Opts, E, Callback) ->
Final = Final =
case (get_opt(debug_info) == true) orelse case (get_opt(debug_info) == true) orelse
lists:member(debug_info, Opts) of lists:member(debug_info, Opts) of
true -> [debug_info] ++ elixir_code_server:call(erl_compiler_options); true -> [debug_info] ++ options();
false -> elixir_code_server:call(erl_compiler_options) false -> options()
end, end,
module(Forms, File, Final, false, Callback). inner_module(Forms, Final, false, E, Callback).
module(Forms, File, Options, Bootstrap, Callback) when inner_module(Forms, Options, Bootstrap, #{file := File} = E, Callback) when
is_binary(File), is_list(Forms), is_list(Options), is_boolean(Bootstrap), is_function(Callback) -> is_list(Forms), is_list(Options), is_boolean(Bootstrap), is_function(Callback) ->
Listname = elixir_utils:characters_to_list(File), Source = elixir_utils:characters_to_list(File),
case compile:noenv_forms([no_auto_import()|Forms], [return,{source,Listname}|Options]) of case compile:noenv_forms([no_auto_import()|Forms], [return,{source,Source}|Options]) of
{ok, ModuleName, Binary, Warnings} -> {ok, Module, Binary, Warnings} ->
format_warnings(Bootstrap, Warnings), format_warnings(Bootstrap, Warnings),
code:load_binary(ModuleName, Listname, Binary), {module, Module} = code:load_binary(Module, beam_location(E), Binary),
Callback(ModuleName, Binary); Callback(Module, Binary);
{error, Errors, Warnings} -> {error, Errors, Warnings} ->
format_warnings(Bootstrap, Warnings), format_warnings(Bootstrap, Warnings),
format_errors(Errors) format_errors(Errors)
end. end.
beam_location(#{module := nil}) -> in_memory;
beam_location(#{lexical_tracker := Pid, module := Module}) ->
case elixir_lexical:dest(Pid) of
nil -> in_memory;
Dest ->
filename:join(elixir_utils:characters_to_list(Dest),
atom_to_list(Module) ++ ".beam")
end.
no_auto_import() -> no_auto_import() ->
{attribute, 0, compile, no_auto_import}. {attribute, 0, compile, no_auto_import}.
%% CORE HANDLING %% CORE HANDLING
core() -> core() ->
application:start(elixir), {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) || File <- core_main()].
core_file(File) -> core_file(File) ->
try try
Lists = file(File), Lists = file(File),
[binary_to_path(X, "lib/elixir/ebin") || X <- Lists], _ = [binary_to_path(X, "lib/elixir/ebin") || X <- Lists],
io:format("Compiled ~ts~n", [File]) io:format("Compiled ~ts~n", [File])
catch catch
Kind:Reason -> Kind:Reason ->
@@ -214,11 +266,11 @@ format_errors([]) ->
format_errors(Errors) -> format_errors(Errors) ->
lists:foreach(fun ({File, Each}) -> lists:foreach(fun ({File, Each}) ->
BinFile = elixir_utils:characters_to_binary(File), BinFile = elixir_utils:characters_to_binary(File),
lists:foreach(fun (Error) -> elixir_errors:handle_file_error(BinFile, Error) end, Each) lists:foreach(fun(Error) -> elixir_errors:handle_file_error(BinFile, Error) end, Each)
end, Errors). end, Errors).
format_warnings(Bootstrap, Warnings) -> format_warnings(Bootstrap, Warnings) ->
lists:foreach(fun ({File, Each}) -> lists:foreach(fun ({File, Each}) ->
BinFile = elixir_utils:characters_to_binary(File), BinFile = elixir_utils:characters_to_binary(File),
lists:foreach(fun (Warning) -> elixir_errors:handle_file_warning(Bootstrap, BinFile, Warning) end, Each) lists:foreach(fun(Warning) -> elixir_errors:handle_file_warning(Bootstrap, BinFile, Warning) end, Each)
end, Warnings). end, Warnings).
+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]). handle_info/2, terminate/2, code_change/3, next/0]).
-behaviour(gen_server). -behaviour(gen_server).
-define(timeout, 1000). %% 1 second -define(timeout, 30000). %% 30 seconds
-define(limit, 4294967296). %% 2^32 -define(limit, 4294967295). %% 2^32 - 1
next() -> next() ->
gen_server:call(?MODULE, next, ?timeout). gen_server:call(?MODULE, next, ?timeout).
+185 -132
View File
@@ -1,49 +1,35 @@
% Holds the logic responsible for function definitions (def(p) and defmacro(p)). % Holds the logic responsible for function definitions (def(p) and defmacro(p)).
-module(elixir_def). -module(elixir_def).
-export([table/1, clauses_table/1, setup/1, -export([setup/1, reset_last/1, lookup_definition/2,
cleanup/1, reset_last/1, lookup_definition/2, delete_definition/2, store_definition/6, unwrap_definitions/2,
delete_definition/2, store_definition/6, unwrap_definitions/1, store_each/7, format_error/1]).
store_each/8, format_error/1]).
-include("elixir.hrl"). -include("elixir.hrl").
-define(attr, '__def_table'). -define(last_def, {elixir, last_def}).
-define(clauses_attr, '__clauses_table'). -define(attr, {elixir, def_table}).
-define(clauses_attr, {elixir, clauses_table}).
%% Table management functions. Called internally. %% 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) -> setup(Module) ->
ets:insert(Module, {?attr, ets:new(Module, [set, public])}),
ets:insert(Module, {?clauses_attr, ets:new(Module, [bag, public])}),
reset_last(Module), reset_last(Module),
ok. ok.
cleanup(Module) ->
ets:delete(table(Module)),
ets:delete(clauses_table(Module)).
%% Reset the last item. Useful when evaling code.
reset_last(Module) -> 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) -> lookup_definition(Module, Tuple) ->
case ets:lookup(table(Module), Tuple) of case ets:lookup(elixir_module:defs_table(Module), Tuple) of
[Result] -> [Result] ->
CTable = clauses_table(Module), CTable = elixir_module:clas_table(Module),
{Result, [Clause || {_, Clause} <- ets:lookup(CTable, Tuple)]}; {Result, [Clause || {_, Clause} <- ets:lookup(CTable, Tuple)]};
_ -> _ ->
false false
end. end.
delete_definition(Module, Tuple) -> delete_definition(Module, Tuple) ->
ets:delete(table(Module), Tuple), ets:delete(elixir_module:defs_table(Module), Tuple),
ets:delete(clauses_table(Module), Tuple). ets:delete(elixir_module:clas_table(Module), Tuple).
% Invoked by the wrap definition with the function abstract tree. % Invoked by the wrap definition with the function abstract tree.
% Each function is then added to the function table. % Each function is then added to the function table.
@@ -55,7 +41,7 @@ store_definition(Line, Kind, CheckClauses, Call, Body, Pos) ->
{Name, Args} = case NameAndArgs of {Name, Args} = case NameAndArgs of
{N, _, A} when is_atom(N), is_atom(A) -> {N, []}; {N, _, A} when is_atom(N), is_atom(A) -> {N, []};
{N, _, A} when is_atom(N), is_list(A) -> {N, A}; {N, _, A} when is_atom(N), is_list(A) -> {N, A};
_ -> elixir_errors:form_error(Line, ?m(E, file), ?MODULE, {invalid_def, Kind, NameAndArgs}) _ -> elixir_errors:form_error([{line, Line}], ?m(E, file), ?MODULE, {invalid_def, Kind, NameAndArgs})
end, end,
%% Now that we have verified the call format, %% Now that we have verified the call format,
@@ -64,11 +50,17 @@ store_definition(Line, Kind, CheckClauses, Call, Body, Pos) ->
DoCheckClauses = (not lists:keymember(context, 1, Meta)) andalso (CheckClauses), DoCheckClauses = (not lists:keymember(context, 1, Meta)) andalso (CheckClauses),
%% Check if there is a file information in the definition. %% Check if there is a file information in the definition.
%% If so, we assume this come from another source and we need %% If so, we assume this come from another source and
%% to linify taking into account keep line numbers. %% we need to linify taking into account keep line numbers.
{File, Key} = case lists:keyfind(file, 1, Meta) of {Location, Key} = case lists:keyfind(file, 1, Meta) of
{file, Bin} when is_binary(Bin) -> {Bin, keep}; {file, KeepFile} when is_binary(KeepFile) ->
_ -> {nil, line} case lists:keyfind(keep, 1, Meta) of
{keep, KeepLine} when is_integer(KeepLine) -> KeepLine;
_ -> KeepLine = 0
end,
{{KeepFile, KeepLine}, keep};
_ ->
{nil, line}
end, end,
LinifyArgs = elixir_quote:linify(Line, Key, Args), LinifyArgs = elixir_quote:linify(Line, Key, Args),
@@ -76,31 +68,28 @@ store_definition(Line, Kind, CheckClauses, Call, Body, Pos) ->
LinifyBody = elixir_quote:linify(Line, Key, Body), LinifyBody = elixir_quote:linify(Line, Key, Body),
assert_no_aliases_name(Line, Name, Args, E), assert_no_aliases_name(Line, Name, Args, E),
assert_valid_name(Line, Kind, Name, Args, E),
store_definition(Line, Kind, DoCheckClauses, Name, store_definition(Line, Kind, DoCheckClauses, Name,
LinifyArgs, LinifyGuards, LinifyBody, File, E). LinifyArgs, LinifyGuards, LinifyBody, Location, E).
store_definition(Line, Kind, CheckClauses, Name, Args, Guards, Body, MetaFile, #{module := Module} = ER) -> store_definition(Line, Kind, CheckClauses, Name, Args, Guards, Body, KeepLocation, #{module := Module} = ER) ->
Arity = length(Args), Arity = length(Args),
Tuple = {Name, Arity}, Tuple = {Name, Arity},
E = ER#{function := Tuple}, E = ER#{function := Tuple},
elixir_locals:record_definition(Tuple, Kind, Module), elixir_locals:record_definition(Tuple, Kind, Module),
Location = retrieve_location(Line, MetaFile, Module), Location = retrieve_location(KeepLocation, Module),
{Function, Defaults, Super} = translate_definition(Kind, Line, Module, Name, Args, Guards, Body, E), {Function, Defaults, Super} = translate_definition(Kind, Line, Module, Name, Args, Guards, Body, E),
DefaultsLength = length(Defaults), DefaultsLength = length(Defaults),
elixir_locals:record_defaults(Tuple, Kind, Module, DefaultsLength), elixir_locals:record_defaults(Tuple, Kind, Module, DefaultsLength),
File = ?m(E, file), File = ?m(E, file),
Table = table(Module),
CTable = clauses_table(Module),
compile_super(Module, Super, E), 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, store_each(CheckClauses, Kind, File, Location, Module, DefaultsLength, Function),
Table, CTable, DefaultsLength, Function), [store_each(false, Kind, File, Location, Module, 0,
[store_each(false, Kind, File, Location, Table, CTable, 0,
default_function_for(Kind, Name, Default)) || Default <- Defaults], default_function_for(Kind, Name, Default)) || Default <- Defaults],
make_struct_available(Kind, Module, Name, Args), make_struct_available(Kind, Module, Name, Args),
@@ -115,31 +104,36 @@ run_on_definition_callbacks(Kind, Line, Module, Name, Args, Guards, Expr, E) ->
_ -> _ ->
Env = elixir_env:linify({Line, E}), Env = elixir_env:linify({Line, E}),
Callbacks = 'Elixir.Module':get_attribute(Module, on_definition), Callbacks = 'Elixir.Module':get_attribute(Module, on_definition),
[Mod:Fun(Env, Kind, Name, Args, Guards, Expr) || {Mod, Fun} <- Callbacks] _ = [Mod:Fun(Env, Kind, Name, Args, Guards, Expr) || {Mod, Fun} <- Callbacks],
ok
end. end.
make_struct_available(def, Module, '__struct__', []) -> make_struct_available(def, Module, '__struct__', []) ->
case erlang:get(elixir_compiler_pid) of case erlang:get(elixir_compiler_pid) of
undefined -> ok; undefined -> ok;
Pid -> Pid ! {struct_available, Module} Pid ->
Pid ! {struct_available, Module},
ok
end; end;
make_struct_available(_, _, _, _) -> make_struct_available(_, _, _, _) ->
ok. ok.
%% Retrieve location from meta file or @file, otherwise nil %% Retrieve location from meta file (if Key == keep)
%% or @file, otherwise nil
retrieve_location(Line, File, Module) -> retrieve_location(Location, Module) ->
case get_location_attribute(Module) of case get_location_attribute(Module) of
nil when not is_binary(File) -> nil when is_tuple(Location) ->
nil; {File, Line} = Location,
nil ->
{normalize_location(File), Line}; {normalize_location(File), Line};
X when is_binary(X) -> nil ->
nil;
File when is_binary(File) ->
'Elixir.Module':delete_attribute(Module, file), 'Elixir.Module':delete_attribute(Module, file),
{normalize_location(X), 0}; {normalize_location(File), 0};
{X, L} when is_binary(X) andalso is_integer(L) -> {File, Line} when is_binary(File) andalso is_integer(Line) ->
'Elixir.Module':delete_attribute(Module, file), 'Elixir.Module':delete_attribute(Module, file),
{normalize_location(X), L} {normalize_location(File), Line}
end. end.
get_location_attribute(Module) -> get_location_attribute(Module) ->
@@ -148,13 +142,13 @@ get_location_attribute(Module) ->
false -> 'Elixir.Module':get_attribute(Module, file) false -> 'Elixir.Module':get_attribute(Module, file)
end. end.
normalize_location(X) -> normalize_location(File) ->
elixir_utils:characters_to_list(elixir_utils:relative_to_cwd(X)). elixir_utils:characters_to_list(elixir_utils:relative_to_cwd(File)).
%% Compile super %% Compile super
compile_super(Module, true, #{function := Function}) -> compile_super(Module, true, #{function := Function}) ->
elixir_def_overridable:store(Module, Function, true); elixir_def_overridable:super(Module, Function);
compile_super(_Module, _, _E) -> ok. compile_super(_Module, _, _E) -> ok.
%% Translate the given call and expression given %% Translate the given call and expression given
@@ -164,7 +158,7 @@ translate_definition(Kind, Line, Module, Name, Args, Guards, Body, E) when is_in
Arity = length(Args), Arity = length(Args),
{EArgs, EGuards, EBody, _} = elixir_exp_clauses:def(fun elixir_def_defaults:expand/2, {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), Body == nil andalso check_args_for_bodyless_clause(Line, EArgs, E),
@@ -179,7 +173,7 @@ translate_definition(Kind, Line, Module, Name, Args, Guards, Body, E) when is_in
translate_clause(nil, _Line, _Kind, _Args, [], _Body, _S) -> translate_clause(nil, _Line, _Kind, _Args, [], _Body, _S) ->
{[], false}; {[], false};
translate_clause(nil, Line, Kind, _Args, _Guards, _Body, #elixir_scope{file=File}) -> translate_clause(nil, Line, Kind, _Args, _Guards, _Body, #elixir_scope{file=File}) ->
elixir_errors:form_error(Line, File, ?MODULE, {missing_do, Kind}); elixir_errors:form_error([{line, Line}], File, ?MODULE, {missing_do, Kind});
translate_clause(_, Line, Kind, Args, Guards, Body, S) -> translate_clause(_, Line, Kind, Args, Guards, Body, S) ->
{TClause, TS} = elixir_clauses:clause(Line, {TClause, TS} = elixir_clauses:clause(Line,
fun elixir_translator:translate_args/2, Args, Body, Guards, true, S), fun elixir_translator:translate_args/2, Args, Body, Guards, true, S),
@@ -216,120 +210,167 @@ is_macro(_) -> false.
% Unwrap the functions stored in the functions table. % Unwrap the functions stored in the functions table.
% It returns a list of all functions to be exported, plus the macros, % It returns a list of all functions to be exported, plus the macros,
% and the body of all functions. % and the body of all functions.
unwrap_definitions(Module) -> unwrap_definitions(File, Module) ->
Table = table(Module), Table = elixir_module:defs_table(Module),
CTable = clauses_table(Module), CTable = elixir_module:clas_table(Module),
ets:delete(Table, last),
unwrap_definition(ets:tab2list(Table), CTable, [], [], [], [], [], [], []).
unwrap_definition([Fun|T], CTable, All, Exports, Private, Def, Defmacro, Functions, Tail) -> {All, Private} = unwrap_definition(ets:tab2list(Table), File, Module, CTable, [], []),
Tuple = element(1, Fun), Unreachable = elixir_locals:warn_unused_local(File, Module, Private),
Clauses = [Clause || {_, Clause} <- ets:lookup(CTable, Tuple)], split_definition(All, Unreachable, [], [], [], [], [], {[], []}).
{NewFun, NewExports, NewPrivate, NewDef, NewDefmacro} = unwrap_definition([Fun|T], File, Module, CTable, All, Private) ->
case Clauses of {Tuple, Kind, Line, _, Check, Location, {Defaults, _, _}} = Fun,
[] -> {false, Exports, Private, Def, Defmacro}; Export = export(Kind, Tuple),
_ -> unwrap_definition(element(2, Fun), Tuple, Fun, Exports, Private, Def, Defmacro)
end,
{NewFunctions, NewTail} = case NewFun of case [Clause || {_, Clause} <- ets:lookup(CTable, Tuple)] of
[] ->
warn_bodyless_function(Line, File, Module, Kind, Tuple),
unwrap_definition(T, File, Module, CTable, All, Private);
Clauses ->
Unwrapped = {Tuple, Kind, Line, Location,
function_for_stored_definition(Line, Export, Clauses)},
NewPrivate =
if
Kind == defp; Kind == defmacrop ->
[{Tuple, Kind, Line, Check, Defaults}|Private];
true ->
Private
end,
unwrap_definition(T, File, Module, CTable, [Unwrapped|All], NewPrivate)
end;
unwrap_definition([], _File, _Module, _CTable, All, Private) ->
{All, Private}.
split_definition([{Tuple, def, Line, Location, Body}|T], Unreachable,
Def, Defp, Defmacro, Defmacrop, Exports, Functions) ->
split_definition(T, Unreachable, [Tuple|Def], Defp, Defmacro, Defmacrop,
[export(def, Tuple)|Exports],
add_definition(Line, Location, Body, Functions));
split_definition([{Tuple, defp, Line, Location, Body}|T], Unreachable,
Def, Defp, Defmacro, Defmacrop, Exports, Functions) ->
case lists:member(Tuple, Unreachable) of
false -> false ->
NewAll = All, split_definition(T, Unreachable, Def, [Tuple|Defp], Defmacro, Defmacrop,
{Functions, Tail}; Exports, add_definition(Line, Location, Body, Functions));
_ -> true ->
NewAll = [Tuple|All], split_definition(T, Unreachable, Def, [Tuple|Defp], Defmacro, Defmacrop,
function_for_stored_definition(NewFun, Clauses, Functions, Tail) Exports, Functions)
end, end;
unwrap_definition(T, CTable, NewAll, NewExports, NewPrivate, split_definition([{Tuple, defmacro, Line, Location, Body}|T], Unreachable,
NewDef, NewDefmacro, NewFunctions, NewTail); Def, Defp, Defmacro, Defmacrop, Exports, Functions) ->
unwrap_definition([], _CTable, All, Exports, Private, Def, Defmacro, Functions, Tail) -> split_definition(T, Unreachable, Def, Defp, [Tuple|Defmacro], Defmacrop,
{All, Exports, Private, ordsets:from_list(Def), [export(defmacro, Tuple)|Exports],
ordsets:from_list(Defmacro), lists:reverse(Tail ++ Functions)}. add_definition(Line, Location, Body, Functions));
unwrap_definition(def, Tuple, Fun, Exports, Private, Def, Defmacro) -> split_definition([{Tuple, defmacrop, _Line, _Location, _Body}|T], Unreachable,
{Fun, [Tuple|Exports], Private, [Tuple|Def], Defmacro}; Def, Defp, Defmacro, Defmacrop, Exports, Functions) ->
unwrap_definition(defmacro, {Name, Arity} = Tuple, Fun, Exports, Private, Def, Defmacro) -> split_definition(T, Unreachable, Def, Defp, Defmacro, [Tuple|Defmacrop],
Macro = {elixir_utils:macro_name(Name), Arity + 1}, Exports, Functions);
{setelement(1, Fun, Macro), [Macro|Exports], Private, Def, [Tuple|Defmacro]};
unwrap_definition(defp, Tuple, Fun, Exports, Private, Def, Defmacro) -> split_definition([], _Unreachable, Def, Defp, Defmacro, Defmacrop, Exports, {Head, Tail}) ->
%% {Name, Arity}, Kind, Line, Check, Defaults {Def, Defp, Defmacro, Defmacrop, Exports, Head ++ Tail}.
Info = {Tuple, defp, element(3, Fun), element(5, Fun), element(7, Fun)},
{Fun, Exports, [Info|Private], Def, Defmacro};
unwrap_definition(defmacrop, Tuple, Fun, Exports, Private, Def, Defmacro) ->
%% {Name, Arity}, Kind, Line, Check, Defaults
Info = {Tuple, defmacrop, element(3, Fun), element(5, Fun), element(7, Fun)},
{false, Exports, [Info|Private], Def, Defmacro}.
%% Helpers %% Helpers
function_for_stored_definition({{Name,Arity}, _, Line, _, _, nil, _}, Clauses, Functions, Tail) -> export(Kind, {Name, Arity}) when Kind == defmacro; Kind == defmacrop ->
{[{function, Line, Name, Arity, Clauses}|Functions], Tail}; {elixir_utils:macro_name(Name), Arity + 1};
export(Kind, {Name, Arity}) when Kind == def; Kind == defp ->
{Name, Arity}.
function_for_stored_definition({{Name,Arity}, _, Line, _, _, Location, _}, Clauses, Functions, Tail) -> function_for_stored_definition(Line, {Name,Arity}, Clauses) ->
{Functions, [ {function, Line, Name, Arity, Clauses}.
{function, Line, Name, Arity, Clauses},
{attribute, Line, file, Location} | Tail add_definition(_Line, nil, Body, {Head, Tail}) ->
]}. {[Body|Head], Tail};
add_definition(Line, Location, Body, {Head, Tail}) ->
{Head,
[{attribute, Line, file, Location}, Body|Tail]}.
default_function_for(Kind, Name, {clause, Line, Args, _Guards, _Exprs} = Clause) default_function_for(Kind, Name, {clause, Line, Args, _Guards, _Exprs} = Clause)
when Kind == defmacro; Kind == defmacrop -> when Kind == defmacro; Kind == defmacrop ->
{function, Line, Name, length(Args) - 1, [Clause]}; {function, Line, Name, length(Args) - 1, [Clause]};
default_function_for(_, Name, {clause, Line, Args, _Guards, _Exprs} = Clause) -> default_function_for(_, Name, {clause, Line, Args, _Guards, _Exprs} = Clause) ->
{function, Line, Name, length(Args), [Clause]}. {function, Line, Name, length(Args), [Clause]}.
warn_bodyless_function(_Line, _File, Special, _Kind, _Tuple)
when Special == 'Elixir.Kernel.SpecialForms'; Special == 'Elixir.Module' ->
ok;
warn_bodyless_function(Line, File, _Module, Kind, Tuple) ->
elixir_errors:form_warn([{line, Line}], File, ?MODULE, {bodyless_fun, Kind, Tuple}),
ok.
%% Store each definition in the table. %% Store each definition in the table.
%% This function also checks and emit warnings in case %% This function also checks and emit warnings in case
%% the kind, of the visibility of the function changes. %% the kind, of the visibility of the function changes.
store_each(Check, Kind, File, Location, Table, CTable, Defaults, {function, Line, Name, Arity, Clauses}) -> store_each(Check, Kind, File, Location, Module, Defaults, {function, Line, Name, Arity, Clauses}) ->
Tuple = {Name, Arity}, Data = elixir_module:data_table(Module),
case ets:lookup(Table, Tuple) of Defs = elixir_module:defs_table(Module),
[{Tuple, StoredKind, StoredLine, StoredFile, StoredCheck, StoredLocation, StoredDefaults}] -> Clas = elixir_module:clas_table(Module),
Tuple = {Name, Arity},
HasBody = Clauses =/= [],
case ets:lookup(Defs, Tuple) of
[{Tuple, StoredKind, StoredLine, StoredFile, StoredCheck,
StoredLocation, {StoredDefaults, LastHasBody, LastDefaults}}] ->
FinalLine = StoredLine, FinalLine = StoredLine,
FinalLocation = StoredLocation, FinalLocation = StoredLocation,
FinalDefaults = max(Defaults, StoredDefaults), FinalDefaults = {max(Defaults, StoredDefaults), HasBody, Defaults},
check_valid_kind(Line, File, Name, Arity, Kind, StoredKind), check_valid_kind(Line, File, Name, Arity, Kind, StoredKind),
(Check and StoredCheck) andalso (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, StoredDefaults); check_valid_defaults(Line, File, Name, Arity, Kind, Defaults, LastDefaults, LastHasBody);
[] -> [] ->
FinalLine = Line, FinalLine = Line,
FinalLocation = Location, FinalLocation = Location,
FinalDefaults = Defaults FinalDefaults = {Defaults, HasBody, Defaults}
end, end,
Check andalso ets:insert(Table, {last, {Name, Arity}}),
ets:insert(CTable, [{Tuple, Clause} || Clause <- Clauses ]), Check andalso ets:insert(Data, {?last_def, {Name, Arity}}),
ets:insert(Table, {Tuple, Kind, FinalLine, File, Check, FinalLocation, FinalDefaults}). ets:insert(Clas, [{Tuple, Clause} || Clause <- Clauses]),
ets:insert(Defs, {Tuple, Kind, FinalLine, File, Check, FinalLocation, FinalDefaults}).
%% Validations %% Validations
check_valid_kind(_Line, _File, _Name, _Arity, Kind, Kind) -> []; check_valid_kind(_Line, _File, _Name, _Arity, Kind, Kind) -> [];
check_valid_kind(Line, File, Name, Arity, Kind, StoredKind) -> check_valid_kind(Line, File, Name, Arity, Kind, StoredKind) ->
elixir_errors:form_error(Line, File, ?MODULE, elixir_errors:form_error([{line, Line}], File, ?MODULE,
{changed_kind, {Name, Arity, StoredKind, Kind}}). {changed_kind, {Name, Arity, StoredKind, Kind}}).
check_valid_clause(Line, File, Name, Arity, Kind, Table, StoredLine, StoredFile) -> check_valid_clause(Line, File, Name, Arity, Kind, Data, StoredLine, StoredFile) ->
case ets:lookup_element(Table, last, 2) of case ets:lookup_element(Data, ?last_def, 2) of
{Name,Arity} -> []; {Name,Arity} -> [];
[] -> []; [] -> [];
_ -> _ ->
Relative = elixir_utils:relative_to_cwd(StoredFile), Relative = elixir_utils:relative_to_cwd(elixir_utils:relative_to_cwd(StoredFile)),
elixir_errors:handle_file_warning(File, {Line, ?MODULE, elixir_errors:form_warn([{line, Line}], File, ?MODULE,
{ungrouped_clause, {Kind, Name, Arity, StoredLine, Relative}}}) {ungrouped_clause, {Kind, Name, Arity, StoredLine, Relative}})
end. end.
check_valid_defaults(_Line, _File, _Name, _Arity, _Kind, 0, _) -> []; % Any clause after clause with defaults (body less does not count)
check_valid_defaults(Line, File, Name, Arity, Kind, _, 0) -> check_valid_defaults(Line, File, Name, Arity, Kind, _, StoredDefaults, true) when StoredDefaults > 0 ->
elixir_errors:handle_file_warning(File, {Line, ?MODULE, {out_of_order_defaults, {Kind, Name, Arity}}}); elixir_errors:form_error([{line, Line}], File, ?MODULE,
check_valid_defaults(Line, File, Name, Arity, Kind, _, _) -> {clauses_with_defaults, {Kind, Name, Arity}});
elixir_errors:form_error(Line, File, ?MODULE, {clauses_with_defaults, {Kind, Name, Arity}}). % Clause without defaults
check_valid_defaults(_Line, _File, _Name, _Arity, _Kind, 0, _, _) -> [];
% Clause with defaults after clause without defaults
check_valid_defaults(Line, File, Name, Arity, Kind, _, 0, _) ->
elixir_errors:form_warn([{line, Line}], File, ?MODULE, {out_of_order_defaults, {Kind, Name, Arity}});
% Clause with defaults after clause with defaults
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) -> check_previous_defaults(Line, Module, Name, Arity, Kind, Defaults, E) ->
Matches = ets:match(Table, {{Name, '$2'}, '$1', '_', '_', '_', '_', '$3'}), Matches = ets:match(elixir_module:defs_table(Module), {{Name, '$2'}, '$1', '_', '_', '_', '_', {'$3', '_', '_'}}),
[ begin [ begin
elixir_errors:form_error(Line, ?m(E, file), ?MODULE, elixir_errors:form_error([{line, Line}], ?m(E, file), ?MODULE,
{defs_with_defaults, Name, {Kind, Arity}, {K, A}}) {defs_with_defaults, Name, {Kind, Arity}, {K, A}})
end || [K, A, D] <- Matches, A /= Arity, D /= 0, defaults_conflict(A, D, Arity, Defaults)]. end || [K, A, D] <- Matches, A /= Arity, D /= 0, defaults_conflict(A, D, Arity, Defaults)].
@@ -339,7 +380,8 @@ defaults_conflict(A, D, Arity, Defaults) ->
check_args_for_bodyless_clause(Line, Args, E) -> check_args_for_bodyless_clause(Line, Args, E) ->
[ begin [ begin
elixir_errors:form_error(Line, ?m(E, file), ?MODULE, invalid_args_for_bodyless_clause) elixir_errors:form_error([{line, Line}], ?m(E, file), ?MODULE,
invalid_args_for_bodyless_clause)
end || Arg <- Args, invalid_arg(Arg) ]. end || Arg <- Args, invalid_arg(Arg) ].
invalid_arg({Name, _, Kind}) when is_atom(Name), is_atom(Kind) -> invalid_arg({Name, _, Kind}) when is_atom(Name), is_atom(Kind) ->
@@ -350,13 +392,20 @@ invalid_arg(_) ->
true. true.
assert_no_aliases_name(Line, '__aliases__', [Atom], #{file := File}) when is_atom(Atom) -> assert_no_aliases_name(Line, '__aliases__', [Atom], #{file := File}) when is_atom(Atom) ->
elixir_errors:form_error(Line, File, ?MODULE, {no_alias, Atom}); elixir_errors:form_error([{line, Line}], File, ?MODULE, {no_alias, Atom});
assert_no_aliases_name(_Line, _Aliases, _Args, _S) ->
ok.
assert_no_aliases_name(_Meta, _Aliases, _Args, _S) -> assert_valid_name(Line, Kind, is_record, [_, _], #{file := File}) when Kind == defp; Kind == def ->
elixir_errors:form_error([{line, Line}], File, ?MODULE, {is_record, Kind});
assert_valid_name(_Line, _Kind, _Name, _Args, _S) ->
ok. ok.
%% Format errors %% Format errors
format_error({bodyless_fun, Kind, {Name, Arity}}) ->
io_lib:format("bodyless clause provided for nonexistent ~ts ~ts/~B", [Kind, Name, Arity]);
format_error({no_module,{Kind,Name,Arity}}) -> format_error({no_module,{Kind,Name,Arity}}) ->
io_lib:format("cannot define function outside module, invalid scope for ~ts ~ts/~B", [Kind, Name, Arity]); io_lib:format("cannot define function outside module, invalid scope for ~ts ~ts/~B", [Kind, Name, Arity]);
@@ -391,5 +440,9 @@ format_error({invalid_def, Kind, NameAndArgs}) ->
format_error(invalid_args_for_bodyless_clause) -> format_error(invalid_args_for_bodyless_clause) ->
"can use only variables and \\\\ as arguments of bodyless clause"; "can use only variables and \\\\ as arguments of bodyless clause";
format_error({is_record, Kind}) ->
io_lib:format("cannot define function named ~ts is_record/2 due to compability "
"issues with the Erlang compiler (it is a known bug)", [Kind]);
format_error({missing_do, Kind}) -> format_error({missing_do, Kind}) ->
io_lib:format("missing do keyword in ~ts", [Kind]). io_lib:format("missing do keyword in ~ts", [Kind]).
+2 -1
View File
@@ -4,10 +4,11 @@
-include("elixir.hrl"). -include("elixir.hrl").
expand(Args, E) -> expand(Args, E) ->
NoContext = E#{context := nil},
lists:mapfoldl(fun lists:mapfoldl(fun
({'\\\\', Meta, [Left, Right]}, Acc) -> ({'\\\\', Meta, [Left, Right]}, Acc) ->
{ELeft, EL} = elixir_exp:expand(Left, 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}; {{'\\\\', Meta, [ELeft, ERight]}, EL};
(Left, Acc) -> (Left, Acc) ->
elixir_exp:expand(Left, Acc) elixir_exp:expand(Left, Acc)
+19 -14
View File
@@ -1,14 +1,18 @@
% Holds the logic responsible for defining overridable functions and handling super. % Holds the logic responsible for defining overridable functions and handling super.
-module(elixir_def_overridable). -module(elixir_def_overridable).
-export([store_pending/1, ensure_defined/4, -export([setup/1, overridable/1, overridable/2, name/2, super/2, store_pending/1,
name/2, store/3, format_error/1]). ensure_defined/4, format_error/1]).
-include("elixir.hrl"). -include("elixir.hrl").
-define(attr, {elixir, overridable}).
setup(Module) ->
overridable(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) -> 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. %% Check if an overridable function is defined.
@@ -36,30 +40,31 @@ store(Module, Function, GenerateName) ->
{_Count, _Clause, _Neighbours, true} -> ok; {_Count, _Clause, _Neighbours, true} -> ok;
{Count, Clause, Neighbours, false} -> {Count, Clause, Neighbours, false} ->
overridable(Module, orddict:store(Function, {Count, Clause, Neighbours, true}, Overridable)), overridable(Module, orddict:store(Function, {Count, Clause, Neighbours, true}, Overridable)),
{{{Name, Arity}, Kind, Line, File, _Check, Location, Defaults}, Clauses} = Clause, {{{Name, Arity}, Kind, Line, File, _Check, Location, {Defaults, _HasBody, _LastDefaults}}, Clauses} = Clause,
{FinalKind, FinalName} = case GenerateName of {FinalKind, FinalName} = case GenerateName of
true -> {defp, name(Module, Function, Overridable)}; true -> {defp, name(Module, Function, Overridable)};
false -> {Kind, Name} false -> {Kind, Name}
end, 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); 'Elixir.Module.LocalsTracker':reattach(Module, Kind, {Name, Arity}, Neighbours);
_ -> true ->
ok ok
end, end,
Def = {function, Line, FinalName, Arity, Clauses}, Def = {function, Line, FinalName, Arity, Clauses},
elixir_def:store_each(false, FinalKind, File, Location, elixir_def:store_each(false, FinalKind, File, Location, Module, Defaults, Def)
elixir_def:table(Module), elixir_def:clauses_table(Module), Defaults, Def)
end. end.
%% Store pending declarations that were not manually made concrete. super(Module, Function) ->
store(Module, Function, true).
store_pending(Module) -> store_pending(Module) ->
[store(Module, X, false) || {X, {_, _, _, false}} <- overridable(Module), _ = [store(Module, X, false) || {X, {_, _, _, false}} <- overridable(Module),
not 'Elixir.Module':'defines?'(Module, X)]. not 'Elixir.Module':'defines?'(Module, X)],
ok.
%% Error handling %% Error handling
@@ -72,4 +77,4 @@ format_error({no_super, Module, {Name, Arity}}) ->
format_fa({Name, Arity}) -> format_fa({Name, Arity}) ->
A = atom_to_binary(Name, utf8), A = atom_to_binary(Name, utf8),
B = integer_to_binary(Arity), B = integer_to_binary(Arity),
<< A/binary, $/, B/binary >>. <<A/binary, $/, B/binary>>.
+21 -190
View File
@@ -4,24 +4,12 @@
-module(elixir_dispatch). -module(elixir_dispatch).
-export([dispatch_import/5, dispatch_require/6, -export([dispatch_import/5, dispatch_require/6,
require_function/5, import_function/4, require_function/5, import_function/4,
expand_import/5, expand_require/5, expand_import/6, expand_require/5,
default_functions/0, default_macros/0, default_requires/0, default_functions/0, default_macros/0, default_requires/0,
find_import/4, format_error/1]). find_import/4, format_error/1]).
-include("elixir.hrl"). -include("elixir.hrl").
-import(ordsets, [is_element/2]). -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(kernel, 'Elixir.Kernel').
-define(list, 'Elixir.List').
-define(map, 'Elixir.Map').
-define(node, 'Elixir.Node').
-define(process, 'Elixir.Process').
-define(string, 'Elixir.String').
-define(system, 'Elixir.System').
-define(tuple, 'Elixir.Tuple').
default_functions() -> default_functions() ->
[{?kernel, elixir_imported_functions()}]. [{?kernel, elixir_imported_functions()}].
@@ -30,17 +18,20 @@ default_macros() ->
default_requires() -> default_requires() ->
['Elixir.Kernel', 'Elixir.Kernel.Typespec']. ['Elixir.Kernel', 'Elixir.Kernel.Typespec'].
%% This is used by elixir_quote. Note we don't record the
%% import locally because at that point there is no
%% ambiguity.
find_import(Meta, Name, Arity, E) -> find_import(Meta, Name, Arity, E) ->
Tuple = {Name, Arity}, Tuple = {Name, Arity},
case find_dispatch(Meta, Tuple, [], E) of case find_dispatch(Meta, Tuple, [], E) of
{function, Receiver} -> {function, Receiver} ->
elixir_lexical:record_import(Receiver, ?m(E, lexical_tracker)), elixir_lexical:record_import(Receiver, ?m(E, lexical_tracker)),
elixir_locals:record_import(Tuple, Receiver, ?m(E, module), ?m(E, function)), %% elixir_locals:record_import(Tuple, Receiver, ?m(E, module), ?m(E, function)),
Receiver; Receiver;
{macro, Receiver} -> {macro, Receiver} ->
elixir_lexical:record_import(Receiver, ?m(E, lexical_tracker)), elixir_lexical:record_import(Receiver, ?m(E, lexical_tracker)),
elixir_locals:record_import(Tuple, Receiver, ?m(E, module), ?m(E, function)), %% elixir_locals:record_import(Tuple, Receiver, ?m(E, module), ?m(E, function)),
Receiver; Receiver;
_ -> _ ->
false false
@@ -78,7 +69,7 @@ remote_function(Meta, Receiver, Name, Arity, E) ->
check_deprecation(Meta, Receiver, Name, Arity, E), check_deprecation(Meta, Receiver, Name, Arity, E),
elixir_lexical:record_remote(Receiver, ?m(E, lexical_tracker)), 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}; {AR, AN} -> {remote, AR, AN, Arity};
false -> {remote, Receiver, Name, Arity} false -> {remote, Receiver, Name, Arity}
end. end.
@@ -87,7 +78,7 @@ remote_function(Meta, Receiver, Name, Arity, E) ->
dispatch_import(Meta, Name, Args, E, Callback) -> dispatch_import(Meta, Name, Args, E, Callback) ->
Arity = length(Args), Arity = length(Args),
case expand_import(Meta, {Name, Arity}, Args, E, []) of case expand_import(Meta, {Name, Arity}, Args, E, [], false) of
{ok, Receiver, Quoted} -> {ok, Receiver, Quoted} ->
expand_quoted(Meta, Receiver, Name, Arity, Quoted, E); expand_quoted(Meta, Receiver, Name, Arity, Quoted, E);
{ok, Receiver, NewName, NewArgs} -> {ok, Receiver, NewName, NewArgs} ->
@@ -99,9 +90,9 @@ dispatch_import(Meta, Name, Args, E, Callback) ->
dispatch_require(Meta, Receiver, Name, Args, E, Callback) when is_atom(Receiver) -> dispatch_require(Meta, Receiver, Name, Args, E, Callback) when is_atom(Receiver) ->
Arity = length(Args), Arity = length(Args),
case rewrite(Receiver, Name, Args, Arity) of case elixir_rewrite:inline(Receiver, Name, Arity) of
{ok, AR, AN, AA} -> {AR, AN} ->
Callback(AR, AN, AA); Callback(AR, AN, Args);
false -> false ->
case expand_require(Meta, Receiver, {Name, Arity}, Args, E) of case expand_require(Meta, Receiver, {Name, Arity}, Args, E) of
{ok, Receiver, Quoted} -> expand_quoted(Meta, Receiver, Name, Arity, Quoted, E); {ok, Receiver, Quoted} -> expand_quoted(Meta, Receiver, Name, Arity, Quoted, E);
@@ -114,12 +105,12 @@ dispatch_require(_Meta, Receiver, Name, Args, _E, Callback) ->
%% Macros expansion %% Macros expansion
expand_import(Meta, {Name, Arity} = Tuple, Args, E, Extra) -> expand_import(Meta, {Name, Arity} = Tuple, Args, E, Extra, External) ->
Module = ?m(E, module), Module = ?m(E, module),
Dispatch = find_dispatch(Meta, Tuple, Extra, E), Dispatch = find_dispatch(Meta, Tuple, Extra, E),
Function = ?m(E, function), Function = ?m(E, function),
Local = (Function /= nil) andalso (Function /= Tuple) andalso AllowLocals = External orelse ((Function /= nil) andalso (Function /= Tuple)),
elixir_locals:macro_for(Module, Name, Arity), Local = AllowLocals andalso elixir_locals:macro_for(Module, Name, Arity),
case Dispatch of case Dispatch of
%% In case it is an import, we dispatch the import. %% In case it is an import, we dispatch the import.
@@ -147,11 +138,7 @@ do_expand_import(Meta, {Name, Arity} = Tuple, Args, Module, E, Result) ->
{function, Receiver} -> {function, Receiver} ->
elixir_lexical:record_import(Receiver, ?m(E, lexical_tracker)), elixir_lexical:record_import(Receiver, ?m(E, lexical_tracker)),
elixir_locals:record_import(Tuple, Receiver, Module, ?m(E, function)), elixir_locals:record_import(Tuple, Receiver, Module, ?m(E, function)),
{ok, Receiver, Name, Args};
case rewrite(Receiver, Name, Args, Arity) of
{ok, _, _, _} = Res -> Res;
false -> {ok, Receiver, Name, Args}
end;
{macro, Receiver} -> {macro, Receiver} ->
check_deprecation(Meta, Receiver, Name, Arity, E), check_deprecation(Meta, Receiver, Name, Arity, E),
elixir_lexical:record_import(Receiver, ?m(E, lexical_tracker)), elixir_lexical:record_import(Receiver, ?m(E, lexical_tracker)),
@@ -163,8 +150,8 @@ do_expand_import(Meta, {Name, Arity} = Tuple, Args, Module, E, Result) ->
error -> {ok, Receiver, Name, Args} error -> {ok, Receiver, Name, Args}
end; end;
false when Module == ?kernel -> false when Module == ?kernel ->
case rewrite(Module, Name, Args, Arity) of case elixir_rewrite:inline(Module, Name, Arity) of
{ok, _, _, _} = Res -> Res; {AR, AN} -> {ok, AR, AN, Args};
false -> error false -> error
end; end;
false -> false ->
@@ -228,7 +215,7 @@ expand_quoted(Meta, Receiver, Name, Arity, Quoted, E) ->
end. end.
caller(Line, #{module := nil} = E) -> 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) -> caller(Line, #{module := Module, function := nil} = E) ->
{Module, '__MODULE__', 0, location(Line, E)}; {Module, '__MODULE__', 0, location(Line, E)};
caller(Line, #{module := Module, function := {Name, Arity}} = E) -> caller(Line, #{module := Module, function := {Name, Arity}} = E) ->
@@ -338,152 +325,6 @@ elixir_imported_macros() ->
error:undef -> [] error:undef -> []
end. 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(?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}) -> check_deprecation(Meta, Receiver, Name, Arity, #{file := File}) ->
case deprecation(Receiver, Name, Arity) of case deprecation(Receiver, Name, Arity) of
false -> ok; false -> ok;
@@ -507,15 +348,5 @@ deprecation_message(Warning, Message) ->
Message -> Warning ++ ", " ++ Message Message -> Warning ++ ", " ++ Message
end. end.
deprecation('Elixir.Mix.Generator', 'from_file', _) ->
"instead pass [from_file: file] to embed_text/2 and embed_template/2 macros. "
"Note that [from_file: file] expects paths relative to the current working "
"directory and not to the current file";
deprecation('Elixir.EEx.TransformerEngine', '__using__', _) ->
"check EEx.SmartEngine for how to build custom engines";
deprecation('Elixir.EEx.AssignsEngine', '__using__', _) ->
"check EEx.SmartEngine for how to build custom engines";
deprecation('Elixir.Kernel', 'xor', _) ->
true; %% Remember to remove xor operator from tokenizer
deprecation(_, _, _) -> deprecation(_, _, _) ->
false. false.
+114 -63
View File
@@ -2,36 +2,37 @@
% This is not exposed in the Elixir language. % This is not exposed in the Elixir language.
-module(elixir_errors). -module(elixir_errors).
-export([compile_error/3, compile_error/4, -export([compile_error/3, compile_error/4,
form_error/4, parse_error/4, warn/2, warn/3, form_error/4, form_warn/4, parse_error/4, warn/2, warn/3,
handle_file_warning/2, handle_file_warning/3, handle_file_error/2]). handle_file_warning/2, handle_file_warning/3, handle_file_error/2]).
-include("elixir.hrl"). -include("elixir.hrl").
-type line_or_meta() :: integer() | list(). -spec warn(non_neg_integer(), binary(), unicode:chardata()) -> ok.
warn(Warning) -> warn(Line, File, Warning) when is_integer(Line), is_binary(File) ->
CompilerPid = get(elixir_compiler_pid), warn(file_format(Line, File), Warning).
if
CompilerPid =/= undefined -> -spec warn(unicode:chardata(), unicode:chardata()) -> ok.
elixir_code_server:cast({register_warning, CompilerPid});
true -> false
end,
io:put_chars(standard_error, Warning).
warn(Caller, Warning) -> warn(Caller, Warning) ->
warn([Caller, "warning: ", Warning]). do_warn([Caller, "warning: ", Warning, $\n]).
warn(Line, File, Warning) when is_integer(Line) -> %% General forms handling.
warn(file_format(Line, File, "warning: " ++ Warning)).
%% Raised during expansion/translation/compilation. -spec form_error(list(), binary(), module(), any()) -> no_return().
-spec form_error(line_or_meta(), binary(), module(), any()) -> no_return().
form_error(Meta, File, Module, Desc) -> form_error(Meta, File, Module, Desc) ->
compile_error(Meta, File, format_error(Module, Desc)). compile_error(Meta, File, format_error(Module, Desc)).
-spec compile_error(line_or_meta(), binary(), iolist()) -> no_return(). -spec form_warn(list(), binary(), module(), any()) -> ok.
-spec compile_error(line_or_meta(), binary(), iolist(), list()) -> no_return().
form_warn(Meta, File, Module, Desc) when is_list(Meta) ->
{MetaLine, MetaFile} = meta_location(Meta, File),
warn(MetaLine, MetaFile, format_error(Module, Desc)).
%% Compilation error.
-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) -> compile_error(Meta, File, Message) when is_list(Message) ->
raise(Meta, File, 'Elixir.CompileError', elixir_utils:characters_to_binary(Message)). raise(Meta, File, 'Elixir.CompileError', elixir_utils:characters_to_binary(Message)).
@@ -39,23 +40,39 @@ compile_error(Meta, File, Message) when is_list(Message) ->
compile_error(Meta, File, Format, Args) when is_list(Format) -> compile_error(Meta, File, Format, Args) when is_list(Format) ->
compile_error(Meta, File, io_lib:format(Format, Args)). compile_error(Meta, File, io_lib:format(Format, Args)).
%% Raised on tokenizing/parsing %% Tokenization parsing/errors.
-spec parse_error(line_or_meta(), binary(), binary(), binary()) -> no_return(). -spec parse_error(non_neg_integer(), binary(), binary(), binary()) -> no_return().
parse_error(Meta, File, Error, <<>>) -> parse_error(Line, File, Error, <<>>) ->
Message = case Error of Message = case Error of
<<"syntax error before: ">> -> <<"syntax error: expression is incomplete">>; <<"syntax error before: ">> -> <<"syntax error: expression is incomplete">>;
_ -> Error _ -> Error
end, end,
raise(Meta, File, 'Elixir.TokenMissingError', Message); do_raise(Line, File, 'Elixir.TokenMissingError', Message);
%% Show a nicer message for missing end tokens %% Show a nicer message for missing end tokens
parse_error(Meta, File, <<"syntax error before: ">>, <<"'end'">>) -> parse_error(Line, File, <<"syntax error before: ">>, <<"'end'">>) ->
raise(Meta, File, 'Elixir.SyntaxError', <<"unexpected token: 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(Meta, File, Error, <<"[", _/binary>> = Full) when is_binary(Error) -> 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 = Rest =
case binary:split(Full, <<"<<">>) of case binary:split(Full, <<"<<">>) of
[Lead, Token] -> [Lead, Token] ->
@@ -66,27 +83,27 @@ parse_error(Meta, File, Error, <<"[", _/binary>> = Full) when is_binary(Error) -
[_] -> [_] ->
<<$">> <<$">>
end, end,
raise(Meta, File, 'Elixir.SyntaxError', <<Error/binary, Rest/binary >>); do_raise(Line, File, 'Elixir.SyntaxError', <<Error/binary, Rest/binary >>);
%% Everything else is fine as is %% Everything else is fine as is
parse_error(Meta, File, Error, Token) when is_binary(Error), is_binary(Token) -> parse_error(Line, File, Error, Token) when is_binary(Error), is_binary(Token) ->
Message = <<Error / binary, Token / binary >>, Message = <<Error/binary, Token/binary >>,
raise(Meta, File, 'Elixir.SyntaxError', Message). do_raise(Line, File, 'Elixir.SyntaxError', Message).
%% Handle warnings and errors (called during module compilation) %% Handle warnings and errors from Erlang land (called during module compilation)
%% Ignore on bootstrap %% Ignore on bootstrap
handle_file_warning(true, _File, {_Line, sys_core_fold, nomatch_guard}) -> []; handle_file_warning(true, _File, {_Line, sys_core_fold, nomatch_guard}) -> ok;
handle_file_warning(true, _File, {_Line, sys_core_fold, {nomatch_shadow, _}}) -> []; handle_file_warning(true, _File, {_Line, sys_core_fold, {nomatch_shadow, _}}) -> ok;
%% Ignore always %% Ignore always
handle_file_warning(_, _File, {_Line, sys_core_fold, useless_building}) -> []; handle_file_warning(_, _File, {_Line, sys_core_fold, useless_building}) -> ok;
%% This is an Erlang bug, it considers {tuple, _}.call to always fail %% This is an Erlang bug, it considers {tuple, _}.call to always fail
handle_file_warning(_, _File, {_Line, v3_kernel, bad_call}) -> []; handle_file_warning(_, _File, {_Line, v3_kernel, bad_call}) -> ok;
%% We handle unused local warnings ourselves %% We handle unused local warnings ourselves
handle_file_warning(_, _File, {_Line, erl_lint, {unused_function, _}}) -> []; handle_file_warning(_, _File, {_Line, erl_lint, {unused_function, _}}) -> ok;
%% Make no_effect clauses pretty %% Make no_effect clauses pretty
handle_file_warning(_, File, {Line, sys_core_fold, {no_effect, {erlang, F, A}}}) -> handle_file_warning(_, File, {Line, sys_core_fold, {no_effect, {erlang, F, A}}}) ->
@@ -95,7 +112,7 @@ handle_file_warning(_, File, {Line, sys_core_fold, {no_effect, {erlang, F, A}}})
false -> false ->
case erl_internal:bif(F, A) of case erl_internal:bif(F, A) of
false -> {"the call to :erlang.~ts/~B has no effect", [F,A]}; false -> {"the call to :erlang.~ts/~B has no effect", [F,A]};
true -> {"the call to ~ts/~B has no effect", [F,A]} true -> {"the call to ~ts/~B has no effect", [F,A]}
end end
end, end,
Message = io_lib:format(Fmt, Args), Message = io_lib:format(Fmt, Args),
@@ -116,7 +133,7 @@ handle_file_warning(_, File, {Line,erl_lint,{undefined_behaviour_func,{Fun,Arity
handle_file_warning(_, File, {Line,erl_lint,{undefined_behaviour,Module}}) -> handle_file_warning(_, File, {Line,erl_lint,{undefined_behaviour,Module}}) ->
case elixir_compiler:get_opt(internal) of case elixir_compiler:get_opt(internal) of
true -> []; true -> ok;
false -> false ->
Message = io_lib:format("behaviour ~ts undefined", [elixir_aliases:inspect(Module)]), Message = io_lib:format("behaviour ~ts undefined", [elixir_aliases:inspect(Module)]),
warn(Line, File, Message) warn(Line, File, Message)
@@ -124,17 +141,29 @@ handle_file_warning(_, File, {Line,erl_lint,{undefined_behaviour,Module}}) ->
%% Ignore unused vars at "weird" lines (<= 0) %% Ignore unused vars at "weird" lines (<= 0)
handle_file_warning(_, _File, {Line,erl_lint,{unused_var,_Var}}) when Line =< 0 -> 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}}) -> 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");
%% Properly format other unused vars %% Properly format other unused vars
handle_file_warning(_, File, {Line,erl_lint,{unused_var,Var}}) -> handle_file_warning(_, File, {Line,erl_lint,{unused_var,Var}}) ->
Message = format_error(erl_lint, {unused_var, format_var(Var)}), Message = format_error(erl_lint, {unused_var, format_var(Var)}),
warn(Line, File, Message); 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 %% Default behaviour
handle_file_warning(_, File, {Line,Module,Desc}) -> handle_file_warning(_, File, {Line,Module,Desc}) ->
Message = format_error(Module, Desc), Message = format_error(Module, Desc),
@@ -152,39 +181,27 @@ handle_file_error(File, {Line,erl_lint,{unsafe_var,Var,{In,_Where}}}) ->
_ -> In _ -> In
end, end,
Message = io_lib:format("cannot define variable ~ts inside ~ts", [format_var(Var), Translated]), Message = io_lib:format("cannot define variable ~ts inside ~ts", [format_var(Var), Translated]),
raise(Line, File, 'Elixir.CompileError', iolist_to_binary(Message)); do_raise(Line, File, 'Elixir.CompileError', elixir_utils:characters_to_binary(Message));
handle_file_error(File, {Line,erl_lint,{spec_fun_undefined,{M,F,A}}}) -> handle_file_error(File, {Line,erl_lint,{spec_fun_undefined,{M,F,A}}}) ->
Message = io_lib:format("spec for undefined function ~ts.~ts/~B", [elixir_aliases:inspect(M), F, A]), Message = io_lib:format("spec for undefined function ~ts.~ts/~B", [elixir_aliases:inspect(M), F, A]),
raise(Line, File, 'Elixir.CompileError', iolist_to_binary(Message)); do_raise(Line, File, 'Elixir.CompileError', elixir_utils:characters_to_binary(Message));
handle_file_error(File, {Line,Module,Desc}) -> handle_file_error(File, {Line,Module,Desc}) ->
form_error(Line, File, Module, Desc). Message = format_error(Module, Desc),
do_raise(Line, File, 'Elixir.CompileError', elixir_utils:characters_to_binary(Message)).
%% Helpers %% Helpers
raise(Meta, File, Kind, Message) when is_list(Meta) -> raise(Meta, File, Kind, Message) when is_list(Meta) ->
raise(?line(Meta), File, Kind, Message); {MetaLine, MetaFile} = meta_location(Meta, File),
do_raise(MetaLine, MetaFile, Kind, Message).
raise(none, File, Kind, Message) -> file_format(0, File) ->
raise(0, File, Kind, Message); io_lib:format("~ts: ", [elixir_utils:relative_to_cwd(File)]);
raise(Line, File, Kind, Message) when is_integer(Line), is_binary(File) -> file_format(Line, File) ->
%% Populate the stacktrace so we can raise it io_lib:format("~ts:~w: ", [elixir_utils:relative_to_cwd(File), Line]).
try
throw(ok)
catch
ok -> ok
end,
Stacktrace = erlang:get_stacktrace(),
Exception = Kind:exception([{description, Message}, {file, File}, {line, Line}]),
erlang:raise(error, Exception, tl(Stacktrace)).
file_format(0, File, Message) when is_binary(File) ->
io_lib:format("~ts: ~ts~n", [elixir_utils:relative_to_cwd(File), Message]);
file_format(Line, File, Message) when is_binary(File) ->
io_lib:format("~ts:~w: ~ts~n", [elixir_utils:relative_to_cwd(File), Line, Message]).
format_var(Var) -> format_var(Var) ->
list_to_atom(lists:takewhile(fun(X) -> X /= $@ end, atom_to_list(Var))). list_to_atom(lists:takewhile(fun(X) -> X /= $@ end, atom_to_list(Var))).
@@ -215,3 +232,37 @@ translate_comp_op('=<') -> '<=';
translate_comp_op('=:=') -> '==='; translate_comp_op('=:=') -> '===';
translate_comp_op('=/=') -> '!=='; translate_comp_op('=/=') -> '!==';
translate_comp_op(Other) -> Other. translate_comp_op(Other) -> Other.
meta_location(Meta, File) ->
case lists:keyfind(file, 1, Meta) of
{file, MetaFile} when is_binary(MetaFile) ->
case lists:keyfind(keep, 1, Meta) of
{keep, MetaLine} when is_integer(MetaLine) -> ok;
_ -> MetaLine = 0
end,
{MetaLine, MetaFile};
_ ->
{?line(Meta), File}
end.
do_warn(Warning) ->
CompilerPid = get(elixir_compiler_pid),
if
CompilerPid =/= undefined ->
elixir_code_server:cast({register_warning, CompilerPid});
true -> false
end,
io:put_chars(standard_error, Warning),
ok.
do_raise(none, File, Kind, Message) ->
do_raise(0, File, Kind, Message);
do_raise(Line, File, Kind, Message) when is_integer(Line), is_binary(File), is_binary(Message) ->
try
throw(ok)
catch
ok -> ok
end,
Stacktrace = erlang:get_stacktrace(),
Exception = Kind:exception([{description, Message}, {file, File}, {line, Line}]),
erlang:raise(error, Exception, tl(Stacktrace)).
+27 -18
View File
@@ -66,6 +66,8 @@ expand({'__aliases__', _, _} = Alias, E) ->
elixir_lexical:record_remote(Receiver, ?m(E, lexical_tracker)), elixir_lexical:record_remote(Receiver, ?m(E, lexical_tracker)),
{Receiver, EA}; {Receiver, EA};
false -> false ->
elixir_errors:warn(?m(E, line), ?m(E, file), io_lib:format("an alias must expand to an atom "
"at compilation time but got ~ts", ['Elixir.Macro':to_string(hd(EAliases))])),
{{{'.', [], [elixir_aliases, concat]}, [], [EAliases]}, EA} {{{'.', [], [elixir_aliases, concat]}, [], [EAliases]}, EA}
end end
end; end;
@@ -169,7 +171,7 @@ expand({quote, Meta, [KV, Do]}, E) when is_list(Do) ->
false -> compile_error(Meta, E#elixir_scope.file, "missing do keyword in quote") false -> compile_error(Meta, E#elixir_scope.file, "missing do keyword in quote")
end, end,
ValidOpts = [context, location, line, unquote, bind_quoted], ValidOpts = [context, location, line, file, unquote, bind_quoted],
{EKV, ET} = expand_opts(Meta, quote, ValidOpts, KV, E), {EKV, ET} = expand_opts(Meta, quote, ValidOpts, KV, E),
Context = case lists:keyfind(context, 1, EKV) of Context = case lists:keyfind(context, 1, EKV) of
@@ -185,8 +187,20 @@ expand({quote, Meta, [KV, Do]}, E) when is_list(Do) ->
end end
end, end,
Keep = lists:keyfind(location, 1, EKV) == {location, keep}, {File, Line} = case lists:keyfind(location, 1, EKV) of
Line = proplists:get_value(line, EKV, false), {location, keep} ->
{elixir_utils:relative_to_cwd(?m(E, file)), false};
false ->
{ case lists:keyfind(file, 1, EKV) of
{file, F} -> F;
false -> nil
end,
case lists:keyfind(line, 1, EKV) of
{line, L} -> L;
false -> false
end }
end,
{Binding, DefaultUnquote} = case lists:keyfind(bind_quoted, 1, EKV) of {Binding, DefaultUnquote} = case lists:keyfind(bind_quoted, 1, EKV) of
{bind_quoted, BQ} -> {BQ, false}; {bind_quoted, BQ} -> {BQ, false};
@@ -194,11 +208,11 @@ expand({quote, Meta, [KV, Do]}, E) when is_list(Do) ->
end, end,
Unquote = case lists:keyfind(unquote, 1, EKV) of Unquote = case lists:keyfind(unquote, 1, EKV) of
{unquote, Bool} when is_boolean(Bool) -> Bool; {unquote, U} when is_boolean(U) -> U;
false -> DefaultUnquote false -> DefaultUnquote
end, end,
Q = #elixir_quote{line=Line, keep=Keep, unquote=Unquote, context=Context}, Q = #elixir_quote{line=Line, file=File, unquote=Unquote, context=Context},
{Quoted, _Q} = elixir_quote:quote(Exprs, Binding, Q, ET), {Quoted, _Q} = elixir_quote:quote(Exprs, Binding, Q, ET),
expand(Quoted, ET); expand(Quoted, ET);
@@ -296,13 +310,8 @@ expand({Name, Meta, Kind} = Var, #{vars := Vars} = E) when is_atom(Name), is_ato
VarMeta = lists:keyfind(var, 1, Meta), VarMeta = lists:keyfind(var, 1, Meta),
if if
VarMeta == {var, true} -> VarMeta == {var, true} ->
Extra = case Kind of compile_error(Meta, ?m(E, file), "expected var \"~ts\"~ts to expand to an existing variable "
nil -> ""; "or be part of a match", [Name, elixir_scope:context_info(Kind)]);
_ -> io_lib:format(" (context ~ts)", [elixir_aliases:inspect(Kind)])
end,
compile_error(Meta, ?m(E, file), "expected var ~ts~ts to expand to an existing "
"variable or be a part of a match", [Name, Extra]);
true -> true ->
expand({Name, Meta, []}, E) expand({Name, Meta, []}, E)
end end
@@ -347,7 +356,7 @@ expand({_, Meta, Args} = Invalid, E) when is_list(Meta) and is_list(Args) ->
expand({_, _, _} = Tuple, E) -> expand({_, _, _} = Tuple, E) ->
compile_error([{line,0}], ?m(E, file), "invalid quoted expression: ~ts", compile_error([{line,0}], ?m(E, file), "invalid quoted expression: ~ts",
['Elixir.Kernel':inspect(Tuple, [{records,false}])]); ['Elixir.Kernel':inspect(Tuple, [])]);
%% Literals %% Literals
@@ -449,8 +458,7 @@ assert_no_ambiguous_op(_Atom, _Meta, _Args, _E) ->
ok. ok.
expand_local(Meta, Name, Args, #{local := nil, function := nil} = E) -> expand_local(Meta, Name, Args, #{local := nil, function := nil} = E) ->
{EArgs, EA} = expand_args(Args, E), compile_error(Meta, ?m(E, file), "undefined function ~ts/~B", [Name, length(Args)]);
{{Name, Meta, EArgs}, EA};
expand_local(Meta, Name, Args, #{local := nil, module := Module, function := Function} = E) -> expand_local(Meta, Name, Args, #{local := nil, module := Module, function := Function} = E) ->
elixir_locals:record_local({Name, length(Args)}, Module, Function), elixir_locals:record_local({Name, length(Args)}, Module, Function),
{EArgs, EA} = expand_args(Args, E), {EArgs, EA} = expand_args(Args, E),
@@ -466,7 +474,8 @@ expand_remote(Receiver, DotMeta, Right, Meta, Args, E, EL) ->
true -> ok true -> ok
end, end,
{EArgs, EA} = expand_args(Args, E), {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 %% Lexical helpers
@@ -481,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]) "unsupported option ~ts given to ~s", ['Elixir.Kernel':inspect(Key), Kind])
end || {Key, _} <- Opts, not lists:member(Key, Allowed)]; end || {Key, _} <- Opts, not lists:member(Key, Allowed)];
validate_opts(Meta, Kind, _Allowed, _Opts, S) -> validate_opts(Meta, Kind, _Allowed, _Opts, E) ->
compile_error(Meta, S#elixir_scope.file, "invalid options for ~s, expected a keyword list", [Kind]). compile_error(Meta, ?m(E, file), "invalid options for ~s, expected a keyword list", [Kind]).
no_alias_opts(KV) when is_list(KV) -> no_alias_opts(KV) when is_list(KV) ->
case lists:keyfind(as, 1, KV) of case lists:keyfind(as, 1, KV) of
+3 -1
View File
@@ -101,7 +101,9 @@ do_receive(Meta, {Key, _}, _Acc, E) ->
%% Try %% Try
'try'(Meta, [], E) -> '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) -> 'try'(Meta, KV, E) when not is_list(KV) ->
elixir_errors:compile_error(Meta, ?m(E, file), "invalid arguments for try"); elixir_errors:compile_error(Meta, ?m(E, file), "invalid arguments for try");
'try'(Meta, KV, E) -> 'try'(Meta, KV, E) ->

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