Compare commits

...
Author SHA1 Message Date
José Valim 9033b217d9 Support canonical URLs 2016-02-21 21:33:30 +01:00
José Valim 1d9548fd28 Merge pull request #3475 from siscia/fixIntTake
Ensure integer in guards in Enum/Stream.take
2015-07-12 17:58:49 +02:00
siscia 9a6c195880 add test for integer passed as argument in {Enum,Stream}.take #3473 2015-07-12 17:40:16 +02:00
José Valim 3eb938a0ba Release v1.0.5 2015-06-29 09:56:28 +02:00
José Valim 66f1e74e0c Run badkey and badmap only on 18
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2015-06-25 18:01:05 +02:00
José Valim 7c382b94af Ensure we also normalize badmap and badkey in rescue 2015-06-25 16:49:30 +02:00
Boris Mühmer 9297a9167e fix for Erlang R18.0 changes in maps module
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2015-06-25 16:12:34 +02:00
James Fish 0410a1cec9 Fix logger translating process exit 2015-06-25 13:06:38 +01:00
James Fish b545a1e491 Translate emulator logged process crashes in 18.0+
Signed-off-by: James Fish <james@fishcakez.com>
2015-06-25 11:57:49 +01:00
José Valim c08b5b11c6 Move tests to Erlang 18 which has proper logger behaviour
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2015-06-25 13:01:04 +02:00
José Valim 8939b39fc2 Update to Erlang 18 2015-06-25 12:47:55 +02:00
José Valim e12a0c561e Backport improvements to <<>>' docs 2015-06-22 13:47:27 +02:00
José Valim 61c3e82123 Improve wording
Signed-off-by: James Fish <james@fishcakez.com>
2015-06-18 16:14:58 +01:00
José Valim d43b41695d Also document exit reasons
Signed-off-by: James Fish <james@fishcakez.com>
2015-06-18 16:14:34 +01:00
José Valim fdca8b2657 Add docs for simple one for one
Signed-off-by: James Fish <james@fishcakez.com>
2015-06-18 16:14:03 +01:00
José Valim dec06d8c82 Update CHANGELOG 2015-06-13 18:47:53 +02:00
José Valim 11c84cbd2e Delegate to :erlang.binary_to_float on Float.parse/1
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2015-06-13 18:45:45 +02:00
José Valim 67501bcddc Avoid non-fixed table errors on autocomplete 2015-06-08 16:56:22 +02:00
José Valim 1f05e7ce73 Rely on loaded_applications instead of running ones
If the application is loaded, it is enough for its
modules to be available for autocompletion. This also
simplifies the code and makes completion faster.

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2015-06-08 16:08:43 +02:00
James Fish 02c279af12 Really fix race condition in logger translator test
Signed-off-by: James Fish <james@fishcakez.com>
2015-06-08 12:08:26 +01:00
José Valim 2c2fb820ab Update CHANGELOG 2015-06-08 09:45:01 +02:00
James Fish 1ad1d6d6b0 Merge pull request #3382 from lexmag/string_io-fixes
Fix `StringIO` processes leakage in the `ExUnit.CaptureIO`

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2015-06-08 09:33:46 +02:00
James Fish 83d8161959 Fix race condition in logger translator test 2015-06-07 22:07:36 +01:00
Eric Meadows-Jönsson b87ea61713 Join pre-release parts with "."
Signed-off-by: Eric Meadows-Jönsson <eric.meadows.jonsson@gmail.com>
2015-06-07 22:13:05 +02:00
Eric Meadows-Jönsson c355ab6482 Fix to_string for Version with numeric pre
Signed-off-by: Eric Meadows-Jönsson <eric.meadows.jonsson@gmail.com>
2015-06-07 22:12:43 +02:00
Eric Meadows-Jönsson f0ed2d48ce Fix Version.pre typespec
Signed-off-by: Eric Meadows-Jönsson <eric.meadows.jonsson@gmail.com>
2015-06-07 22:10:35 +02:00
Eric Meadows-Jönsson d23a46fa9f Add more documentation for ~> requirements
Signed-off-by: Eric Meadows-Jönsson <eric.meadows.jonsson@gmail.com>
2015-06-07 22:10:31 +02:00
Alexei Sholik 6c0cbbd73e Handle :undefined arity in Logger.Translator
We get :undefined when a temporary worker of a simple_one_for_one
supervisor crashes.

Thanks to @fishcakez for feedback
2015-06-07 23:03:54 +03:00
José Valim d58ef57e13 Ensure take/1 does not consume more than required
Kudos to @hamiltop for the idea and initial work on this fix.

Closes #3381 #3379

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2015-06-06 19:09:04 +02:00
James Fish 1a9493534e Fix GenEvent detecting module that isn't loaded
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2015-06-06 19:07:41 +02:00
Eric Meadows-Jönsson 45fe1cb084 Expand attributes and macros in record extractor
Achieved by using :epp.parse_file/2 instead of
:epp_dodger.quick_parse_file/1.

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2015-06-05 22:52:49 +02:00
José Valim 3f0e7c5613 Only consider outer halts in flat_map when the op is not halt itself
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2015-06-05 22:51:36 +02:00
José Valim 49e76833d6 Ensure blocks do not clobber eex buffer
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2015-06-05 13:17:05 +02:00
José Valim 7963e44a5e Speed up upcase and downcase for large strings
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2015-06-05 13:16:15 +02:00
José Valim 3ba5e44dae Ensure we properly underscore acronyms followed by paths
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2015-06-05 13:15:56 +02:00
José Valim 1d74ddac78 Handle corner cases for small strings in rstrp
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2015-06-05 13:15:43 +02:00
José Valim c1e3d4f976 Optimize rstrip
This new implementation is no longer linear without affecting
smaller samples. For a string that is 100 bytes long, it is
25x faster than the previous implementation.

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2015-06-05 13:15:12 +02:00
Michael Sokolnicki b4fc2b35fc Add unit test for IO.binread with :all option
Unit test checks correct handling of 0x0D0A (CR+LF) when reading entire
binary file

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2015-06-05 13:14:22 +02:00
Michael Sokolnicki 18a5d8affb Read 4K blocks instead of lines in IO.binread
Fix for incorrect handling of 0x0D0A (CR+LF) sequence when reading
binary files with option :all

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2015-06-05 13:14:15 +02:00
José Valim cd80994a70 Ensure we escape config before injecting into escript
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2015-04-14 15:23:13 +02:00
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
335 changed files with 16010 additions and 6715 deletions
+2 -3
View File
@@ -1,6 +1,6 @@
/.eunit
/.release
/docs
/doc
/ebin
/lib/*/ebin/*
/lib/*/tmp
@@ -10,5 +10,4 @@
/lib/elixir/test/ebin
/rel/elixir
erl_crash.dump
.dialyzer_plt
.dialyzer.base_plt
.elixir.plt
+2
View File
@@ -7,3 +7,5 @@ notifications:
- eric.meadows.jonsson@gmail.com
otp_release:
- 17.0
- 17.1
- 17.3
+295 -7
View File
@@ -1,18 +1,306 @@
# Changelog
## v0.14.3-dev
## v1.0.5 (2015-06-29)
* Enhancements
* [Elixir] Support 18.0
* [IEx] Rely only on loaded applications for autocompletion on IEx
* [Record] Expand attributes and macros in record extractor
* [String] `Optimize String.rstrip/1`
* [String] `Optimize String.downcase/1`
* [String] `Optimize String.upcase/1`
* Bug fixes
* [Kernel] `|>`, `<<<`, `>>>` and `^^^` were made left associative in operator table
* [Kernel] `<`, `>`, `<=`, `>=` were given higher precedence than comparison ones (`==`, `!=`, etc) in operator table
* [EEx] Ensure blocks do not clobber EEx buffers
* [Enum] Ensure `Enum.take/2` does not consume one extra item when halting on the last emittable item
* [ExUnit] Fix `StringIO` processes leakage in the `ExUnit.CaptureIO` when there are errors inside the `capture_io` block
* [Float] Avoid rounding errors on `Float.parse/1`
* [GenEvent] Fix `GenEvent` detection of modules that aren't loaded
* [IO] Read 4K blocks instead of lines in `IO.binread/2`. This fixes a bug where CRLF were being ignored and is also going to improve performance
* [Logger] Handle `:undefined` arity in `Logger.Translator` (we get :undefined when a temporary worker of a `simple_one_for_one` supervisor crashes)
* [Mix] Ensure config is escaped before being injected into escripts. This fixes a bug where escripts failed to build when containing values like maps in config files
* [Mix] Ensure we properly underscore acronyms followed by paths, for example, `HTTP.Foo`
* [Stream] Ensure `Stream.flat_map/2` does not consume more items than necessary when piped to another `Stream.flat_map/2` that halts in the inner stream
* [Version] Fix `to_string` for versions with numeric pre releases
* Soft deprecations (no warnings emitted)
## v1.0.4 (2015-04-07)
* Deprecations
* Enhancements
* [Elixir] Support Erlang 17.5 and 18.0-rc
* [Mix] Support "--search PATTERN" in "mix help"
* [Mix] Support `:start_permanent` that starts the application as permanent
* [Mix] Support `:build_embedded` that compile protocols, avoid symlinks and ensure protocols are loaded on boot
* [Mix] Make "mix app.start" public and support "--permanent" and "--temporary" options
* [URI] Speed decoding operations
* Bug fixes
* [Elixir] Produce clearer error messages for syntax errors before a sigil
* [Elixir] Return value when matching on `_` in the last line of a block
* [Elixir] Ensure changes in child deps forces the parent to recompile
* [Kernel] Fix a bug where `=~/2` returned false when matching on an empty string. The current behaviour was changed to match the behaviour of `String.contains?/2`
* [Mix] Store relative paths in erlang manifests
* [Mix] Fix httpc proxy option profile for downloading hex and other safe packges
* [Mix] Fix missing behaviour warning when compiling Erlang modules
## v1.0.3 (2015-02-12)
* Enhancements
* [Elixir] Do not show exported vars warning
* [Elixir] Improve error messages when using invalid expressions inside maps
* [Elixir] Skip aliases and import warnings when code cannot compile
* [IEx] Improve autocompletion for Erlang/Elixir based on loaded applications
* [IO] Make `IO.ANSI.Docs` readable on white background
* [Mix] Provide clearer error message when updating archives on Windows
* [Mix] Don't go through hex.pm API server to install Hex
* Bug fixes
* [Elixir] Ensure all Elixir applications work with code upgrades
* [Elixir] Don't leave spurious processes and stale data when the parallel compiler fails
* [Inspect] Keep original stacktrace on Inspect errors
* [IO] Fix `IO.ANSI.Docs` handling of `*`, `**`, `_` where they would be activated in the middle of words
* [Logger] Fix logging with `metadata[:function]`
* [Mix] Don't show hex update message on mix `local.hex`
* [Mix] Ensure tasks are properly underscored and camelized
* [Mix] Ensure Mix does not use ANSI escapes on Windows unless ANSI is enabled
* [Mix] Only shutdown Logger if the Mix application is being actually started
* [Mix] Ensure priv/include are copied on Windows even if source did not change
* [Mix] Ensure rebar dependencies work when user path has a space on Windows
* [Regex] Fix `Regex.replace/4` failures when the number of substitutions in pattern is more than the number of matches and support `\g{...}` patterns
* [String] Fix LFCR being treated as a grapheme instead of CRLF
* [URI] Do more strict matching on URI schema
## v1.0.2 (2014-10-21)
* Bug fixes
* [EEx] Remove invalid warning when attempting to inline code in EEx
* [EEx] Unify syntax errors to always include `file:line:`
* [IEx] Set null prompt function in dumb term mode
* [Kernel] Do not define an ets table with the same name as the module as it clashes with user code
* [Kernel] Fail compiling try expressions if they lack one of catch/after/rescue/else clauses
* [Kernel] Improve error messages when parsing fails on aliases
* [Kernel] Improve warnings coming from compile-time evaluation of arguments
* [String] Fix `String.replace/4` typespec
## v1.0.1 (2014-10-07)
* Enhancements
* [Mix] Add MIX_ENV output to archive.build (as on escript.build)
* Bug fixes
* [CLI] Fix Elixir CLI when running on Cygwin
* [ExUnit] Include stacktraces in ExUnit timeouts
* [Mix] Load dependencies before `deps.check` compiles dependencies
* [Mix] Ensure `Mix.Config` is deep merged
* [Mix] Change version requirement in new apps to `~> MAJOR.MINOR`
* [Mix] Report correct location if `local.hex` fails and give higher priority to powershell install on Windows
* [Mix] Ensure `--elixirc-paths` option for `compile.elixir` is a subset of the project `:elixirc_paths` configuration and it does not remove skipped entries from the manifest
* [Mix] Ensure `compile.elixir` writes to manifest when files are removed
* [Mix] Ensure `compile.elixir` purges and deletes modules before compiling
* [Mix] Do not crash on malformed proxy env var
* [Stream] Ensure `chunk/4` works correctly when halted
* [System] Do not allow relative paths in `System.cmd/3` as documented
* [System] Fix `:stderr_to_stdout` option in `System.cmd/3`
## v1.0.0 (2014-09-10)
* Enhancements
* [Logger] Add `Logger.enable/1` and `Logger.disable/1`
* Backwards incompatible changes
* [GenEvent] No longer support `{:swap_handler, ...}` as callback return value
* [List] `List.unzip/2` was removed
## v1.0.0-rc2 (2014-09-07)
* Enhancements
* [ExUnit] Print process mailbox on failing `assert_receive`
* [ExUnit] Tag all doctests with the `:doctest` tag
* [Kernel] Friendlier `unquote_splicing` error on inline quote
* [Kernel] Add `<~`, `~>`, `<<~`, `~>>`, `<~>`, `<|>` as operators
* [Mix] `mix deps.unlock` warns when given a missing dependency
* [Mix] Fetch hex using HTTPS
* Bug fixes
* [File] Ensure `File.cwd` generates lowercase drive letters on Windows
* [GenEvent] Ensure the event manager does not crash on `:sys.replace_state/3`
* [GenEvent] Do not consider swap handlers to be failures in a stream
* [Logger] Fix `Logger.log/2` not to raise on Logger exit
* [Logger] Recompute sync/async mode on Logger configuration
* [System] Ensure `System.cwd` generates lowercase drive letters on Windows
* Deprecations
* [GenEvent] The `:monitor` option in `add_handler/4` and `swap_handler/6` is deprecated in favor of `add_mon_handler/3` and `swap_mon_handler/5`
* Backwards incompatible changes
* [Supervisor] Make `max_restarts` default to 3 (closer to upcoming Erlang defaults)
## v1.0.0-rc1 (2014-08-30)
* Enhancements
* [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)
@@ -28,7 +316,7 @@
* Bug fixes
* [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`
* [Kernel] Ensure derived protocols are defined with a file
* [Kernel] Change precedence of `&` to not special case `/`
@@ -70,7 +358,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)
* Deprecations
* [Mix] `:escript_*` options were moved into a single `:escript` group
* [Mix] `:escript_*` options moved into a single `:escript` group
* Backwards incompatible changes
* [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)
* [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)
* [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).
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
difficulty of the issue, making it easier for developers to
[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
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
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:
* 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/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");
you may not use this file except in compliance with the License.
+52 -28
View File
@@ -1,4 +1,6 @@
REBAR := rebar
DOCS := v1.0
CANONICAL := stable
ELIXIRC := bin/elixirc --verbose --ignore-module-conflict
ERLC := erlc -I lib/elixir/include
ERL := erl -I lib/elixir/include -noshell -pa lib/elixir/ebin
@@ -11,7 +13,7 @@ INSTALL_DIR = $(INSTALL) -m755 -d
INSTALL_DATA = $(INSTALL) -m644
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
#==> Functions
@@ -32,7 +34,7 @@ $(1): lib/$(1)/ebin/Elixir.$(2).beam lib/$(1)/ebin/$(1).app
lib/$(1)/ebin/$(1).app: lib/$(1)/mix.exs
$(Q) mkdir -p 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) rm -rf lib/$(1)/_build
@@ -68,10 +70,10 @@ erlang:
# Since Mix depends on EEx and EEx depends on
# Mix, we first compile EEx without the .app
# file, then mix and then compile EEx fully
elixir: 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
$(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 \
echo "==> bootstrap (compile)"; \
$(ERL) -s elixir_compiler core -s erlang halt; \
@@ -86,10 +88,11 @@ $(KERNEL): lib/elixir/lib/*.ex lib/elixir/lib/*/*.ex
unicode: $(UNICODE)
$(UNICODE): lib/elixir/unicode/*
@ 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;
$(eval $(call APP_TEMPLATE,ex_unit,ExUnit))
$(eval $(call APP_TEMPLATE,logger,Logger))
$(eval $(call APP_TEMPLATE,eex,EEx))
$(eval $(call APP_TEMPLATE,mix,Mix))
$(eval $(call APP_TEMPLATE,iex,IEx))
@@ -123,16 +126,18 @@ clean_exbeam:
#==> Release tasks
SOURCE_REF = $(shell head="$$(git rev-parse HEAD)" tag="$$(git tag --points-at $$head | tail -1)" ; echo "$${tag:-$$head}\c")
DOCS = bin/elixir ../ex_doc/bin/ex_doc "$(1)" "$(VERSION)" "lib/$(2)/ebin" -m "$(3)" -u "https://github.com/elixir-lang/elixir" --source-ref "$(call SOURCE_REF)" -o docs/$(2) -p http://elixir-lang.org/docs.html
LOGO_PATH = $(shell test -f ../docs/logo.png && echo "--logo ../docs/logo.png")
SOURCE_REF = $(shell tag="$(call GIT_TAG)" revision="$(call GIT_REVISION)"; echo "$${tag:-$$revision}\c")
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 "v$(VERSION)" $(call LOGO_PATH) -o doc/$(2) -a http://elixir-lang.org/docs/$(CANONICAL)/$(2)/ -p http://elixir-lang.org/docs.html
docs: compile ../ex_doc/bin/ex_doc
$(Q) rm -rf docs
$(call DOCS,Elixir,elixir,Kernel)
$(call DOCS,EEx,eex,EEx)
$(call DOCS,Mix,mix,Mix)
$(call DOCS,IEx,iex,IEx)
$(call DOCS,ExUnit,ex_unit,ExUnit)
$(Q) rm -rf doc
$(call COMPILE_DOCS,Elixir,elixir,Kernel)
$(call COMPILE_DOCS,EEx,eex,EEx)
$(call COMPILE_DOCS,Mix,mix,Mix)
$(call COMPILE_DOCS,IEx,iex,IEx)
$(call COMPILE_DOCS,ExUnit,ex_unit,ExUnit)
$(call COMPILE_DOCS,Logger,logger,Logger)
../ex_doc/bin/ex_doc:
@ echo "ex_doc is not found in ../ex_doc as expected. See README for more information."
@@ -140,12 +145,21 @@ docs: compile ../ex_doc/bin/ex_doc
release_zip: compile
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
cd ../docs
rm -rf ../docs/master
mv docs ../docs/master
rm -rf ../docs/$(DOCS)/*/
mv doc/* ../docs/$(DOCS)
# This task requires aws-cli to be installed and set up for access to s3.hex.pm
# See: http://docs.aws.amazon.com/cli/latest/userguide/cli-chap-getting-set-up.html
publish_mix: compile
cd lib/mix && MIX_ENV=prod mix escript.build
aws s3 cp lib/mix/mix s3://s3.hex.pm/builds/mix/v$(VERSION)/mix --acl public-read
aws s3 cp lib/mix/mix s3://s3.hex.pm/builds/mix/mix --acl public-read
rm lib/mix/mix
rm -rf lib/mix/_build
#==> Tests tasks
@@ -164,7 +178,7 @@ $(TEST_EBIN)/%.beam: $(TEST_ERL)/%.erl
$(Q) mkdir -p $(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
@ echo "==> doctest (exunit)"
@@ -173,16 +187,26 @@ test_doc_test: compile
test_stdlib: compile
@ echo "==> elixir (exunit)"
$(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:
@ 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
#==> Dialyzer tasks
dialyze: .dialyzer.base_plt
$(Q) rm -f .dialyzer_plt
$(Q) cp .dialyzer.base_plt .dialyzer_plt
@ echo "==> Adding Elixir to 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
DIALYZER_OPTS = --no_check_plt --fullpath -Werror_handling -Wunmatched_returns -Wunderspecs
PLT = .elixir.plt
$(PLT):
@ 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..."
$(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
$ 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 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
## All releases
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
* VERSION
* CHANGELOG
* src/elixir.app.src
* VERSION (make sure there is no newline in this file)
* CHANGELOG.md
* src/elixir.app.src (not lib/elixir/src/elixir.app.src)
+1 -1
View File
@@ -1 +1 @@
0.14.3-dev
1.0.5
+8 -1
View File
@@ -80,7 +80,14 @@ done
SELF=$(readlink_f "$0")
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 [ -f "$SCRIPT_PATH/../releases/RELEASES" ] && [ -f "$SCRIPT_PATH/erl" ]; then
+31 -20
View File
@@ -1,4 +1,5 @@
@echo off
setlocal
if "%1"=="" goto documentation
if "%1"=="--help" 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 --hidden Makes a hidden node
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.
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 can be passed to the erlang runtime using ELIXIR_ERL_OPTIONS or --erl
goto :EOF
goto end
:parseopts
@@ -46,6 +48,9 @@ set beforeExtra=
rem Flag which determines whether or not to use werl vs erl
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
:startloop
set par="%1"
@@ -59,23 +64,24 @@ if "%par%"=="""" (
goto :expand_erl_libs
)
rem ******* EXECUTION OPTIONS **********************
IF "%par%"==""+iex"" (Set useWerl=1)
rem ******* ERLANG PARAMETERS **********************
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)
IF "%par%"==""--werl"" (Set useWerl=1)
IF "%par%"==""+iex"" (Set runMode="iex")
rem ******* elixir parameters **********************
rem Note: we don't have to do anything with options that don't take an argument
IF NOT "%par%"=="%par:-e=%" (shift)
IF NOT "%par%"=="%par:-r=%" (shift)
IF NOT "%par%"=="%par:-pr=%" (shift)
IF NOT "%par%"=="%par:-pa=%" (shift)
IF NOT "%par%"=="%par:-pz=%" (shift)
IF NOT "%par%"=="%par:--app=%" (shift)
IF NOT "%par%"=="%par:--remsh=%" (shift)
IF """"=="%par:-e=%" (shift)
IF """"=="%par:-r=%" (shift)
IF """"=="%par:-pr=%" (shift)
IF """"=="%par:-pa=%" (shift)
IF """"=="%par:-pz=%" (shift)
IF """"=="%par:--app=%" (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
rem ******* assume all pre-params are parsed ********************
@@ -88,8 +94,13 @@ for /d %%d in ("%originPath%..\lib\*.") do (
)
SETLOCAL disabledelayedexpansion
:run
IF %useWerl% EQU 1 (
werl.exe %ext_libs% %ELIXIR_ERL_OPTIONS% %parsErlang% -s elixir start_cli %beforeExtra% -extra %*
) ELSE (
erl.exe %ext_libs% -noshell %ELIXIR_ERL_OPTIONS% %parsErlang% -s elixir start_cli %beforeExtra% -extra %*
IF NOT %runMode% == "iex" (
set beforeExtra=-s elixir start_cli %beforeExtra%
)
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")
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
# Reminder: apply any changes in this file to bin\mix.bat
Mix.start
Mix.CLI.main
+1 -1
View File
@@ -1,2 +1,2 @@
@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
$toCmd = '' + (Resolve-Path -relative (Split-Path $MyInvocation.MyCommand.Path)) + '\mix.bat'
# Store path to mix.bat as a FileInfo object
$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 ($arg -is [array])
if ($args[$i] -is [array])
{
# 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]
if ($i -ne ($arg.length - 1))
{
$toCmd += ', '
}
$newArgs += ($args[$i][$j] + ',')
}
$newArgs += $args[$i][-1]
}
else
{
$toCmd += $arg
$newArgs += $args[$i]
}
}
# Corrected arguments are ready to pass to batch file
cmd /c $toCmd
& $mixBatPath $newArgs
+6 -3
View File
@@ -1,5 +1,9 @@
defmodule EEx.SyntaxError do
defexception [:message]
defexception [:message, :file, :line]
def message(exception) do
"#{exception.file}:#{exception.line}: #{exception.message}"
end
end
defmodule EEx do
@@ -36,8 +40,7 @@ defmodule EEx do
* `: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 template is read from or to "nofile" when compiling
from a string.
file the template is read from or to "nofile" when compiling from a string.
* `:engine` - the EEx engine to be used for compilation.
## Engine
+10 -5
View File
@@ -12,10 +12,14 @@ defmodule EEx.Compiler do
def compile(source, opts) do
file = opts[:file] || "nofile"
line = opts[:line] || 1
tokens = EEx.Tokenizer.tokenize(source, line)
case EEx.Tokenizer.tokenize(source, line) do
{:ok, tokens} ->
state = %{engine: opts[:engine] || @default_engine,
file: file, line: line, quoted: [], start_line: nil}
generate_buffer(tokens, "", [], state)
{:error, line, message} ->
raise EEx.SyntaxError, line: line, file: file, message: message
end
end
# Generates the buffers by handling each expression from the tokenizer
@@ -51,16 +55,17 @@ defmodule EEx.Compiler do
{buffer, t}
end
defp generate_buffer([{:end_expr, line, _, chars}|_], _buffer, [], _state) do
raise EEx.SyntaxError, message: "unexpected token: #{inspect chars} at line #{inspect line}"
defp generate_buffer([{:end_expr, line, _, chars}|_], _buffer, [], state) do
raise EEx.SyntaxError, message: "unexpected token #{inspect chars}", file: state.file, line: line
end
defp generate_buffer([], buffer, [], state) do
state.engine.handle_body(buffer)
end
defp generate_buffer([], _buffer, _scope, _state) do
raise EEx.SyntaxError, message: "unexpected end of string. expecting a closing <% end %>."
defp generate_buffer([], _buffer, _scope, state) do
raise EEx.SyntaxError, message: "unexpected end of string, expected a closing '<% end %>'",
file: state.file, line: state.line
end
# Creates a placeholder and wrap it inside the expression block
+6 -5
View File
@@ -64,7 +64,8 @@ defmodule EEx.Engine do
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))
end
@@ -97,16 +98,16 @@ defmodule EEx.Engine do
"""
def handle_expr(buffer, "=", expr) do
quote do
tmp = unquote(buffer)
tmp <> to_string(unquote(expr))
tmp1 = unquote(buffer)
tmp1 <> String.Chars.to_string(unquote(expr))
end
end
def handle_expr(buffer, "", expr) do
quote do
tmp = unquote(buffer)
tmp2 = unquote(buffer)
unquote(expr)
tmp
tmp2
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
@moduledoc """
The default engine used by EEx.
+25 -14
View File
@@ -3,7 +3,8 @@ defmodule EEx.Tokenizer do
@doc """
Tokenizes the given char list or binary.
It returns 4 different types of tokens as result:
It returns {:ok, list} with the following tokens:
* `{:text, contents}`
* `{:expr, line, marker, contents}`
@@ -11,34 +12,44 @@ defmodule EEx.Tokenizer do
* `{:middle_expr, line, marker, contents}`
* `{:end_expr, line, marker, contents}`
Or `{:error, line, error}` in case of errors.
"""
def tokenize(bin, line) when is_binary(bin) do
tokenize(String.to_char_list(bin), line)
end
def tokenize(list, line) do
Enum.reverse(tokenize(list, line, [], []))
tokenize(list, line, [], [])
end
defp tokenize('<%%' ++ t, line, buffer, acc) do
{buffer, new_line, rest} = tokenize_expr t, line, [?%, ?<|buffer]
case expr(t, line, [?%, ?<|buffer]) do
{:error, _, _} = error -> error
{:ok, buffer, new_line, rest} ->
tokenize rest, new_line, [?>, ?%|buffer], acc
end
end
defp tokenize('<%#' ++ t, line, buffer, acc) do
{_, new_line, rest} = tokenize_expr t, line, []
case expr(t, line, []) do
{:error, _, _} = error -> error
{:ok, _, new_line, rest} ->
tokenize rest, new_line, buffer, acc
end
end
defp tokenize('<%' ++ t, line, buffer, acc) do
{marker, t} = retrieve_marker(t)
{expr, new_line, rest} = tokenize_expr t, line, []
case expr(t, line, []) do
{:error, _, _} = error -> error
{:ok, expr, new_line, rest} ->
token = token_name(expr)
acc = tokenize_text(buffer, acc)
final = {token, line, marker, Enum.reverse(expr)}
tokenize rest, new_line, [], [final | acc]
end
end
defp tokenize('\n' ++ t, line, buffer, acc) do
tokenize t, line + 1, [?\n|buffer], acc
@@ -49,7 +60,7 @@ defmodule EEx.Tokenizer do
end
defp tokenize([], _line, buffer, acc) do
tokenize_text(buffer, acc)
{:ok, Enum.reverse(tokenize_text(buffer, acc))}
end
# Retrieve marker for <%
@@ -64,20 +75,20 @@ defmodule EEx.Tokenizer do
# Tokenize an expression until we find %>
defp tokenize_expr([?%, ?>|t], line, buffer) do
{buffer, line, t}
defp expr([?%, ?>|t], line, buffer) do
{:ok, buffer, line, t}
end
defp tokenize_expr('\n' ++ t, line, buffer) do
tokenize_expr t, line + 1, [?\n|buffer]
defp expr('\n' ++ t, line, buffer) do
expr t, line + 1, [?\n|buffer]
end
defp tokenize_expr([h|t], line, buffer) do
tokenize_expr t, line, [h|buffer]
defp expr([h|t], line, buffer) do
expr t, line, [h|buffer]
end
defp tokenize_expr([], _line, _buffer) do
raise EEx.SyntaxError, message: "missing token: %>"
defp expr([], line, _buffer) do
{:error, line, "missing token '%>'"}
end
# Receive an expression content and check
+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 %>"
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
result = EEx.eval_string(actual, binding, file: __ENV__.file)
assert result == expected
+25 -24
View File
@@ -5,23 +5,26 @@ defmodule EEx.TokenizerTest do
require EEx.Tokenizer, as: T
test "simple chars lists" do
assert T.tokenize('foo', 1) == [ {:text, 'foo'} ]
assert T.tokenize('foo', 1) == {:ok, [{:text, 'foo'}]}
end
test "simple strings" do
assert T.tokenize("foo", 1) == [ {:text, 'foo'} ]
assert T.tokenize("foo", 1) == {:ok, [{:text, 'foo'}]}
end
test "strings with embedded code" do
assert T.tokenize('foo <% bar %>', 1) == [ {:text, 'foo '}, {:expr, 1, "", ' bar '} ]
assert T.tokenize('foo <% bar %>', 1) ==
{:ok, [{:text, 'foo '}, {:expr, 1, "", ' bar '}]}
end
test "strings with embedded equals code" do
assert T.tokenize('foo <%= bar %>', 1) == [ {:text, 'foo '}, {:expr, 1, "=", ' bar '} ]
assert T.tokenize('foo <%= bar %>', 1) ==
{:ok, [{:text, 'foo '}, {:expr, 1, "=", ' bar '}]}
end
test "strings with more than one line" do
assert T.tokenize('foo\n<%= bar %>', 1) == [ {:text, 'foo\n'}, {:expr, 2, "=", ' bar '} ]
assert T.tokenize('foo\n<%= bar %>', 1) ==
{:ok, [{:text, 'foo\n'}, {:expr, 2, "=", ' bar '}]}
end
test "strings with more than one line and expression with more than one line" do
@@ -32,50 +35,50 @@ baz %>
<% foo %>
'''
assert T.tokenize(string, 1) == [
assert T.tokenize(string, 1) == {:ok, [
{:text, 'foo '},
{:expr, 1, "=", ' bar\n\nbaz '},
{:text, '\n'},
{:expr, 4, "", ' foo '},
{:text, '\n'}
]
]}
end
test "quotation" do
assert T.tokenize('foo <%% true %>', 1) == [
assert T.tokenize('foo <%% true %>', 1) == {:ok, [
{:text, 'foo <% true %>'}
]
]}
end
test "quotation with do/end" do
assert T.tokenize('foo <%% true do %>bar<%% end %>', 1) == [
assert T.tokenize('foo <%% true do %>bar<%% end %>', 1) == {:ok, [
{:text, 'foo <% true do %>bar<% end %>'}
]
]}
end
test "comments" do
assert T.tokenize('foo <%# true %>', 1) == [
assert T.tokenize('foo <%# true %>', 1) == {:ok, [
{:text, 'foo '}
]
]}
end
test "comments with do/end" do
assert T.tokenize('foo <%# true do %>bar<%# end %>', 1) == [
assert T.tokenize('foo <%# true do %>bar<%# end %>', 1) == {:ok, [
{:text, 'foo bar'}
]
]}
end
test "strings with embedded do end" do
assert T.tokenize('foo <% if true do %>bar<% end %>', 1) == [
assert T.tokenize('foo <% if true do %>bar<% end %>', 1) == {:ok, [
{:text, 'foo '},
{:start_expr, 1, "", ' if true do '},
{:text, 'bar'},
{:end_expr, 1, "", ' end '}
]
]}
end
test "strings with embedded -> end" do
assert T.tokenize('foo <% cond do %><% false -> %>bar<% true -> %>baz<% end %>', 1) == [
assert T.tokenize('foo <% cond do %><% false -> %>bar<% true -> %>baz<% end %>', 1) == {:ok, [
{:text, 'foo '},
{:start_expr, 1, "", ' cond do '},
{:middle_expr, 1, "", ' false -> '},
@@ -83,23 +86,21 @@ baz %>
{:middle_expr, 1, "", ' true -> '},
{:text, 'baz'},
{:end_expr, 1, "", ' end '}
]
]}
end
test "strings with embedded keywords blocks" do
assert T.tokenize('foo <% if true do %>bar<% else %>baz<% end %>', 1) == [
assert T.tokenize('foo <% if true do %>bar<% else %>baz<% end %>', 1) == {:ok, [
{:text, 'foo '},
{:start_expr, 1, "", ' if true do '},
{:text, 'bar'},
{:middle_expr, 1, "", ' else '},
{:text, 'baz'},
{:end_expr, 1, "", ' end '}
]
]}
end
test "raise syntax error when there is start mark and no end mark" do
assert_raise EEx.SyntaxError, "missing token: %>", fn ->
T.tokenize('foo <% :bar', 1)
end
assert T.tokenize('foo <% :bar', 1) == {:error, 1, "missing token '%>'"}
end
end
+21 -8
View File
@@ -50,7 +50,8 @@ defmodule EExTest do
use ExUnit.Case, async: true
doctest EEx
doctest EEx.AssignsEngine
doctest EEx.Engine
doctest EEx.SmartEngine
test "evaluates simple string" do
assert_eval "foo bar", "foo bar"
@@ -110,25 +111,25 @@ defmodule EExTest do
end
test "raises a syntax error when the token is invalid" do
assert_raise EEx.SyntaxError, "missing token: %>", fn ->
assert_raise EEx.SyntaxError, "nofile:1: missing token '%>'", fn ->
EEx.compile_string "foo <%= bar"
end
end
test "raises a syntax error when end expression is found without a start expression" do
assert_raise EEx.SyntaxError, "unexpected token: ' end ' at line 1", fn ->
assert_raise EEx.SyntaxError, "nofile:1: unexpected token ' end '", fn ->
EEx.compile_string "foo <% end %>"
end
end
test "raises a syntax error when start expression is found without an end expression" do
assert_raise EEx.SyntaxError, "unexpected end of string. expecting a closing <% end %>.", fn ->
EEx.compile_string "foo <% if true do %>"
assert_raise EEx.SyntaxError, "nofile:2: unexpected end of string, expected a closing '<% end %>'", fn ->
EEx.compile_string "foo\n<% if true do %>"
end
end
test "raises a syntax error when nested end expression is found without an start expression" do
assert_raise EEx.SyntaxError, "unexpected token: ' end ' at line 1", fn ->
test "raises a syntax error when nested end expression is found without a start expression" do
assert_raise EEx.SyntaxError, "nofile:1: unexpected token ' end '", fn ->
EEx.compile_string "foo <% if true do %><% end %><% end %>"
end
end
@@ -251,7 +252,7 @@ Number <%= x %>
assert_eval expected, string
end
test "do not consider already finished functions" do
test "properly handle functions on the left side of clauses" do
expected = """
foo
@@ -286,6 +287,18 @@ foo
assert_eval "\n\n Good\n \n", string
end
test "evaluates expressions with buffers" do
string = """
<%= 123 %>
<% if true do %>
<%= 456 %>
<% end %>
<%= 789 %>
"""
assert_eval "123\n\n789\n", string
end
test "for comprehensions" do
string = """
<%= for _name <- packages || [] do %>
+4 -3
View File
@@ -119,7 +119,7 @@ defimpl Access, for: Map do
def get_and_update!(other, key, _fun) do
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
@@ -132,8 +132,9 @@ defimpl Access, for: Atom do
undefined(atom)
end
def get_and_update(nil, _, fun) do
fun.(nil)
def get_and_update(nil, key, _fun) do
raise ArgumentError,
"could not put/update key #{inspect key} on a nil value"
end
def get_and_update(atom, _key, _fun) do
+89 -18
View File
@@ -68,22 +68,22 @@ defmodule Agent do
## A word on distributed agents
It is important to consider the limitations of distributed agents. Agents
work by sending anonymous functions between the caller and the agent.
In a distributed setup with multiple nodes, agents only work if the caller
(client) and the agent have the same version of a given module.
provides two APIs, one that works with anonymous functions and another
that expects explicit module, function and arguments.
This setup may exhibit issues when doing "rolling upgrades". By rolling
upgrades we mean the following situation: you wish 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; this may cause agents to
crash. That said, if you plan to run in distributed environments, agents
should likely be avoided.
In a distributed setup with multiple nodes, the API that accepts anonymous
functions only works if the caller (client) and the agent have the same
version of the caller module.
Note, however, that agents work fine if you want to perform hot code
swapping, as it keeps both the old and new versions of a given module.
We detail how to do hot code swapping with agents in the next section.
Keep in mind this issue also shows up when performing "rolling upgrades"
with agents. By rolling upgrades we mean the following situation: you wish
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
@@ -111,7 +111,7 @@ defmodule Agent do
@type state :: term
@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.
@@ -149,6 +149,18 @@ defmodule Agent do
GenServer.start_link(Agent.Server, fun, options)
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 """
Starts an agent process without links (outside of a supervision tree).
@@ -160,7 +172,18 @@ defmodule Agent do
end
@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
passing the agent state. The result of the function invocation is
@@ -173,6 +196,18 @@ defmodule Agent do
GenServer.call(agent, {:get, fun}, timeout)
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 """
Gets and updates the agent state in one operation.
@@ -188,6 +223,18 @@ defmodule Agent do
GenServer.call(agent, {:get_and_update, fun}, timeout)
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 """
Updates the agent state.
@@ -197,11 +244,23 @@ defmodule Agent do
A timeout can also be specified (it has a default value of 5000).
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
GenServer.call(agent, {:update, fun}, timeout)
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 """
Performs a cast (fire and forget) operation on the agent state.
@@ -213,7 +272,19 @@ defmodule Agent do
"""
@spec cast(agent, (state -> state)) :: :ok
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
@doc """
+11 -8
View File
@@ -4,20 +4,20 @@ defmodule Agent.Server do
use GenServer
def init(fun) do
{:ok, fun.()}
{:ok, run(fun, [])}
end
def handle_call({:get, fun}, _from, state) do
{:reply, fun.(state), state}
{:reply, run(fun, [state]), state}
end
def handle_call({:get_and_update, fun}, _from, state) do
{reply, state} = fun.(state)
{reply, state} = run(fun, [state])
{:reply, reply, state}
end
def handle_call({:update, fun}, _from, state) do
{:reply, :ok, fun.(state)}
{:reply, :ok, run(fun, [state])}
end
def handle_call(:stop, _from, state) do
@@ -28,16 +28,16 @@ defmodule Agent.Server do
super(msg, from, state)
end
def handle_cast(fun, state) when is_function(fun, 1) do
{:noreply, fun.(state)}
def handle_cast({:cast, fun}, state) do
{:noreply, run(fun, [state])}
end
def handle_cast(msg, state) do
super(msg, state)
end
def code_change(_old, state, { m, f, a }) do
{:ok, apply(m, f, [state|a])}
def code_change(_old, state, fun) do
{:ok, run(fun, [state])}
end
def terminate(_reason, _state) do
@@ -50,4 +50,7 @@ defmodule Agent.Server do
end
:ok
end
defp run({m, f, a}, extra), do: apply(m, f, extra ++ a)
defp run(fun, extra), do: apply(fun, extra)
end
+2 -2
View File
@@ -201,7 +201,7 @@ defmodule Application do
`:applications` in the `.app` file in case they were not previously
started.
"""
@spec ensure_all_started(app, start_type) :: {:ok, [app]} | {:error, term}
@spec ensure_all_started(app, start_type) :: {:ok, [app]} | {:error, {app, term}}
def ensure_all_started(app, type \\ :temporary) when is_atom(app) do
:application.ensure_all_started(app, type)
end
@@ -322,7 +322,7 @@ defmodule Application do
@doc """
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.
"""
@spec format_error(any) :: String.t
+4 -4
View File
@@ -499,7 +499,7 @@ defmodule Base do
defp do_encode64(<<>>, _), do: <<>>
defp do_encode64(data, enc) do
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>>
case rest do
<<c1::6, c2::6, c3::4>> ->
@@ -514,7 +514,7 @@ defmodule Base do
defp do_decode64(<<>>, _), do: <<>>
defp do_decode64(string, dec) when rem(byte_size(string), 4) == 0 do
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>>
case rest do
<<c1::8, c2::8, ?=, ?=>> ->
@@ -534,7 +534,7 @@ defmodule Base do
defp do_encode32(<<>>, _), do: <<>>
defp do_encode32(data, enc) do
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>>
case rest do
<<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(string, dec) when rem(byte_size(string), 8) == 0 do
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>>
case rest do
<<c1::8, c2::8, ?=, ?=, ?=, ?=, ?=, ?=>> ->
+12 -3
View File
@@ -51,6 +51,14 @@ defmodule Code do
:code.del_path(to_char_list(Path.expand path))
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 """
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.
While `load_file` loads a file and returns the loaded modules and their
byte code, `eval_file` simply evalutes the file contents and returns the
byte code, `eval_file` simply evaluates the file contents and returns the
evaluation result and its bindings.
"""
def eval_file(file, relative_to \\ nil) do
@@ -308,7 +316,7 @@ defmodule Code do
Check `compiler_options/1` for more information.
"""
def compiler_options do
:elixir_code_server.call :compiler_options
:elixir_config.get :compiler_options
end
@doc """
@@ -347,7 +355,8 @@ defmodule Code do
bad = bad |> Keyword.keys |> Enum.join(", ")
raise ArgumentError, message: "unknown compiler options: #{bad}"
end
:elixir_code_server.cast {:compiler_options, opts}
update = &:orddict.merge(fn(_, _, value) -> value end, &1, opts)
:elixir_config.update :compiler_options, update
end
@doc """
+5 -45
View File
@@ -8,12 +8,6 @@ defprotocol Collectable do
iex> Enum.into([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?
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.
The `Collectable` module was designed to fill the gap left by the
`Enumerable` protocol. It provides two functions: `into/1` and `empty/1`.
`into/1` can be seen as the opposite of `Enumerable.reduce/3`. If
`Enumerable` is about taking values out, `Collectable.into/1` is about
collecting those values into a structure.
`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.
`Enumerable` protocol. `into/1` can be seen as the opposite of
`Enumerable.reduce/3`. If `Enumerable` is about taking values out,
`Collectable.into/1` is about collecting those values into a structure.
"""
@type command :: {:cont, term} | :done | :halt
@doc """
Receives a collectable structure and returns an empty one.
"""
@spec empty(t) :: t
def empty(collectable)
@doc """
Returns a function that collects values alongside
the initial accumulation value.
@@ -65,10 +47,6 @@ defprotocol Collectable do
end
defimpl Collectable, for: List do
def empty(_list) do
[]
end
def into(original) do
{[], fn
list, {:cont, x} -> [x|list]
@@ -79,34 +57,16 @@ defimpl Collectable, for: List do
end
defimpl Collectable, for: BitString do
def empty(_bitstring) do
""
end
def into(original) do
{original, fn
bitstring, {:cont, x} -> <<bitstring :: bits, x :: bits>>
bitstring, :done -> bitstring
acc, {:cont, x} when is_bitstring(x) -> [acc|x]
acc, :done -> IO.iodata_to_binary(acc)
_, :halt -> :ok
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
def empty(_map) do
%{}
end
def into(original) do
{original, fn
map, {:cont, {k, v}} -> :maps.put(k, v, map)
+78 -78
View File
@@ -288,8 +288,8 @@ defmodule Dict do
## Examples
iex> d = Enum.into([a: 1, b: 2], dict_impl.new)
iex> Enum.sort(Dict.keys(d))
iex> dict = Enum.into([a: 1, b: 2], dict_impl.new)
iex> Enum.sort(Dict.keys(dict))
[:a,:b]
"""
@@ -304,8 +304,8 @@ defmodule Dict do
## Examples
iex> d = Enum.into([a: 1, b: 2], dict_impl.new)
iex> Enum.sort(Dict.values(d))
iex> dict = Enum.into([a: 1, b: 2], dict_impl.new)
iex> Enum.sort(Dict.values(dict))
[1,2]
"""
@@ -319,8 +319,8 @@ defmodule Dict do
## Examples
iex> d = Enum.into([a: 1, b: 2], dict_impl.new)
iex> Dict.size(d)
iex> dict = Enum.into([a: 1, b: 2], dict_impl.new)
iex> Dict.size(dict)
2
"""
@@ -334,10 +334,10 @@ defmodule Dict do
## Examples
iex> d = Enum.into([a: 1], dict_impl.new)
iex> Dict.has_key?(d, :a)
iex> dict = Enum.into([a: 1], dict_impl.new)
iex> Dict.has_key?(dict, :a)
true
iex> Dict.has_key?(d, :b)
iex> Dict.has_key?(dict, :b)
false
"""
@@ -352,12 +352,12 @@ defmodule Dict do
## Examples
iex> d = Enum.into([a: 1], dict_impl.new)
iex> Dict.get(d, :a)
iex> dict = Enum.into([a: 1], dict_impl.new)
iex> Dict.get(dict, :a)
1
iex> Dict.get(d, :b)
iex> Dict.get(dict, :b)
nil
iex> Dict.get(d, :b, 3)
iex> Dict.get(dict, :b, 3)
3
"""
@spec get(t, key, value) :: value
@@ -371,10 +371,10 @@ defmodule Dict do
## Examples
iex> d = Enum.into([a: 1], dict_impl.new)
iex> Dict.fetch(d, :a)
iex> dict = Enum.into([a: 1], dict_impl.new)
iex> Dict.fetch(dict, :a)
{:ok, 1}
iex> Dict.fetch(d, :b)
iex> Dict.fetch(dict, :b)
:error
"""
@@ -389,8 +389,8 @@ defmodule Dict do
## Examples
iex> d = Enum.into([a: 1], dict_impl.new)
iex> Dict.fetch!(d, :a)
iex> dict = Enum.into([a: 1], dict_impl.new)
iex> Dict.fetch!(dict, :a)
1
"""
@@ -405,9 +405,9 @@ defmodule Dict do
## Examples
iex> d = Enum.into([a: 1, b: 2], dict_impl.new)
iex> d = Dict.put(d, :a, 3)
iex> Dict.get(d, :a)
iex> dict = Enum.into([a: 1, b: 2], dict_impl.new)
iex> dict = Dict.put(dict, :a, 3)
iex> Dict.get(dict, :a)
3
"""
@@ -421,9 +421,9 @@ defmodule Dict do
## Examples
iex> d = Enum.into([a: 1, b: 2], dict_impl.new)
iex> d = Dict.put_new(d, :a, 3)
iex> Dict.get(d, :a)
iex> dict = Enum.into([a: 1, b: 2], dict_impl.new)
iex> dict = Dict.put_new(dict, :a, 3)
iex> Dict.get(dict, :a)
1
"""
@@ -438,13 +438,13 @@ defmodule Dict do
## Examples
iex> d = Enum.into([a: 1, b: 2], dict_impl.new)
iex> d = Dict.delete(d, :a)
iex> Dict.get(d, :a)
iex> dict = Enum.into([a: 1, b: 2], dict_impl.new)
iex> dict = Dict.delete(dict, :a)
iex> Dict.get(dict, :a)
nil
iex> d = Enum.into([b: 2], dict_impl.new)
iex> Dict.delete(d, :a) == d
iex> dict = Enum.into([b: 2], dict_impl.new)
iex> Dict.delete(dict, :a) == dict
true
"""
@@ -454,10 +454,10 @@ defmodule Dict do
end
@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`,
the functions in entries in `b` have higher precedence unless a
If one of the `dict2` entries already exists in `dict1`, the
functions in entries in `dict2` have higher precedence unless a
function is given to resolve conflicts.
Notice this function is polymorphic as it merges dicts of any
@@ -466,18 +466,18 @@ defmodule Dict do
## Examples
iex> d1 = Enum.into([a: 1, b: 2], dict_impl.new)
iex> d2 = Enum.into([a: 3, d: 4], dict_impl.new)
iex> d = Dict.merge(d1, d2)
iex> [a: Dict.get(d, :a), b: Dict.get(d, :b), d: Dict.get(d, :d)]
iex> dict1 = Enum.into([a: 1, b: 2], dict_impl.new)
iex> dict2 = Enum.into([a: 3, d: 4], dict_impl.new)
iex> dict = Dict.merge(dict1, dict2)
iex> [a: Dict.get(dict, :a), b: Dict.get(dict, :b), d: Dict.get(dict, :d)]
[a: 3, b: 2, d: 4]
iex> d1 = Enum.into([a: 1, b: 2], dict_impl.new)
iex> d2 = Enum.into([a: 3, d: 4], dict_impl.new)
iex> d = Dict.merge(d1, d2, fn(_k, v1, v2) ->
iex> dict1 = Enum.into([a: 1, b: 2], dict_impl.new)
iex> dict2 = Enum.into([a: 3, d: 4], dict_impl.new)
iex> dict = Dict.merge(dict1, dict2, fn(_k, v1, v2) ->
...> v1 + v2
...> 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]
"""
@@ -502,18 +502,18 @@ defmodule Dict do
## Examples
iex> dict = Enum.into([a: 1], dict_impl.new)
iex> {v, d} = Dict.pop dict, :a
iex> {v, Enum.sort(d)}
iex> {v, dict} = Dict.pop dict, :a
iex> {v, Enum.sort(dict)}
{1,[]}
iex> dict = Enum.into([a: 1], dict_impl.new)
iex> {v, d} = Dict.pop dict, :b
iex> {v, Enum.sort(d)}
iex> {v, dict} = Dict.pop dict, :b
iex> {v, Enum.sort(dict)}
{nil,[a: 1]}
iex> dict = Enum.into([a: 1], dict_impl.new)
iex> {v, d} = Dict.pop dict, :b, 3
iex> {v, Enum.sort(d)}
iex> {v, dict} = Dict.pop dict, :b, 3
iex> {v, Enum.sort(dict)}
{3,[a: 1]}
"""
@@ -528,9 +528,9 @@ defmodule Dict do
## Examples
iex> d = Enum.into([a: 1, b: 2], dict_impl.new)
iex> d = Dict.update!(d, :a, fn(val) -> -val end)
iex> Dict.get(d, :a)
iex> dict = Enum.into([a: 1, b: 2], dict_impl.new)
iex> dict = Dict.update!(dict, :a, fn(val) -> -val end)
iex> Dict.get(dict, :a)
-1
"""
@@ -546,9 +546,9 @@ defmodule Dict do
## Examples
iex> d = Enum.into([a: 1, b: 2], dict_impl.new)
iex> d = Dict.update(d, :c, 3, fn(val) -> -val end)
iex> Dict.get(d, :c)
iex> dict = Enum.into([a: 1, b: 2], dict_impl.new)
iex> dict = Dict.update(dict, :c, 3, fn(val) -> -val end)
iex> Dict.get(dict, :c)
3
"""
@@ -566,19 +566,19 @@ defmodule Dict do
## Examples
iex> d = Enum.into([a: 1, b: 2, c: 3, d: 4], dict_impl.new)
iex> {d1, d2} = Dict.split(d, [:a, :c, :e])
iex> {Dict.to_list(d1) |> Enum.sort, Dict.to_list(d2) |> Enum.sort}
iex> dict = Enum.into([a: 1, b: 2, c: 3, d: 4], dict_impl.new)
iex> {dict1, dict2} = Dict.split(dict, [:a, :c, :e])
iex> {Dict.to_list(dict1) |> Enum.sort, Dict.to_list(dict2) |> Enum.sort}
{[a: 1, c: 3], [b: 2, d: 4]}
iex> d = Enum.into([], dict_impl.new)
iex> {d1, d2} = Dict.split(d, [:a, :c])
iex> {Dict.to_list(d1), Dict.to_list(d2)}
iex> dict = Enum.into([], dict_impl.new)
iex> {dict1, dict2} = Dict.split(dict, [:a, :c])
iex> {Dict.to_list(dict1), Dict.to_list(dict2)}
{[], []}
iex> d = Enum.into([a: 1, b: 2], dict_impl.new)
iex> {d1, d2} = Dict.split(d, [:a, :b, :c])
iex> {Dict.to_list(d1) |> Enum.sort, Dict.to_list(d2)}
iex> dict = Enum.into([a: 1, b: 2], dict_impl.new)
iex> {dict1, dict2} = Dict.split(dict, [:a, :b, :c])
iex> {Dict.to_list(dict1) |> Enum.sort, Dict.to_list(dict2)}
{[a: 1, b: 2], []}
"""
@@ -593,14 +593,14 @@ defmodule Dict do
## Examples
iex> d = Enum.into([a: 1, b: 2], dict_impl.new)
iex> d = Dict.drop(d, [:a, :c, :d])
iex> Dict.to_list(d)
iex> dict = Enum.into([a: 1, b: 2], dict_impl.new)
iex> dict = Dict.drop(dict, [:a, :c, :d])
iex> Dict.to_list(dict)
[b: 2]
iex> d = Enum.into([a: 1, b: 2], dict_impl.new)
iex> d = Dict.drop(d, [:c, :d])
iex> Dict.to_list(d) |> Enum.sort
iex> dict = Enum.into([a: 1, b: 2], dict_impl.new)
iex> dict = Dict.drop(dict, [:c, :d])
iex> Dict.to_list(dict) |> Enum.sort
[a: 1, b: 2]
"""
@@ -616,12 +616,12 @@ defmodule Dict do
## Examples
iex> d = Enum.into([a: 1, b: 2], dict_impl.new)
iex> d = Dict.take(d, [:a, :c, :d])
iex> Dict.to_list(d)
iex> dict = Enum.into([a: 1, b: 2], dict_impl.new)
iex> dict = Dict.take(dict, [:a, :c, :d])
iex> Dict.to_list(dict)
[a: 1]
iex> d = Dict.take(d, [:c, :d])
iex> Dict.to_list(d)
iex> dict = Dict.take(dict, [:c, :d])
iex> Dict.to_list(dict)
[]
"""
@@ -645,14 +645,14 @@ defmodule Dict do
## Examples
iex> a = Enum.into([a: 2, b: 3, f: 5, c: 123], dict_impl.new)
iex> b = [a: 2, b: 3, f: 5, c: 123]
iex> Dict.equal?(a, b)
iex> dict1 = Enum.into([a: 2, b: 3, f: 5, c: 123], dict_impl.new)
iex> dict2 = [a: 2, b: 3, f: 5, c: 123]
iex> Dict.equal?(dict1, dict2)
true
iex> a = Enum.into([a: 2, b: 3, f: 5, c: 123], dict_impl.new)
iex> b = []
iex> Dict.equal?(a, b)
iex> dict1 = Enum.into([a: 2, b: 3, f: 5, c: 123], dict_impl.new)
iex> dict2 = []
iex> Dict.equal?(dict1, dict2)
false
"""
+76 -39
View File
@@ -270,8 +270,13 @@ defmodule Enum do
@doc """
Finds the element at the given index (zero-based).
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
iex> Enum.at([2, 4, 6], 0)
@@ -287,7 +292,6 @@ defmodule Enum do
:none
"""
@spec at(t, integer) :: element | nil
@spec at(t, integer, default) :: element | default
def at(collection, n, default \\ nil) do
case fetch(collection, n) do
@@ -330,7 +334,6 @@ defmodule Enum do
[[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]
def chunk(coll, n, step, pad \\ nil) when n > 0 and step > 0 do
limit = :erlang.max(n, step)
@@ -338,7 +341,7 @@ defmodule Enum do
{_, {acc, {buffer, i}}} =
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)
else
buffer = :lists.reverse(buffer) ++ take(pad, n - i)
@@ -566,12 +569,17 @@ defmodule Enum do
@doc """
Finds the element at the given index (zero-based).
Returns `{:ok, element}` if found, otherwise `:error`.
A negative index can be passed, which means the collection is
enumerated once and the index is counted from the end (i.e.
`-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
iex> Enum.fetch([2, 4, 6], 0)
@@ -585,11 +593,11 @@ defmodule Enum do
"""
@spec fetch(t, integer) :: {:ok, element} | :error
def fetch(collection, n) when is_list(collection) and n >= 0 do
def fetch(collection, n) when is_list(collection) and is_integer(n) and n >= 0 do
do_fetch(collection, n)
end
def fetch(collection, n) when n >= 0 do
def fetch(collection, n) when is_integer(n) and n >= 0 do
res =
Enumerable.reduce(collection, {:cont, 0}, fn(entry, acc) ->
if acc == n do
@@ -605,15 +613,20 @@ defmodule Enum do
end
end
def fetch(collection, n) when n < 0 do
def fetch(collection, n) when is_integer(n) and n < 0 do
do_fetch(reverse(collection), abs(n + 1))
end
@doc """
Finds the element at the given index (zero-based).
Raises `OutOfBoundsError` if the given position
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
iex> Enum.fetch!([2, 4, 6], 0)
@@ -689,7 +702,6 @@ defmodule Enum do
3
"""
@spec find(t, (element -> any)) :: element | nil
@spec find(t, default, (element -> any)) :: element | default
def find(collection, ifnone \\ nil, fun)
@@ -716,7 +728,6 @@ defmodule Enum do
true
"""
@spec find_value(t, (element -> any)) :: any | :nil
@spec find_value(t, any, (element -> any)) :: any | :nil
def find_value(collection, ifnone \\ nil, fun)
@@ -931,7 +942,6 @@ defmodule Enum do
"1 = 2 = 3"
"""
@spec join(t) :: String.t
@spec join(t, String.t) :: String.t
def join(collection, joiner \\ "")
@@ -990,7 +1000,6 @@ defmodule Enum do
"2 = 4 = 6"
"""
@spec map_join(t, (element -> any)) :: String.t
@spec map_join(t, String.t, (element -> any)) :: String.t
def map_join(collection, joiner \\ "", mapper)
@@ -1252,6 +1261,15 @@ defmodule Enum do
:lists.foldl(fun, acc, collection)
end
def reduce(%{__struct__: _} = collection, acc, fun) do
Enumerable.reduce(collection, {:cont, acc},
fn x, acc -> {:cont, fun.(x, acc)} end) |> elem(1)
end
def reduce(%{} = collection, acc, fun) do
:maps.fold(fn k, v, acc -> fun.({k, v}, acc) end, acc, collection)
end
def reduce(collection, acc, fun) do
Enumerable.reduce(collection, {:cont, acc},
fn x, acc -> {:cont, fun.(x, acc)} end) |> elem(1)
@@ -1396,7 +1414,7 @@ defmodule Enum do
result. For example, one could do the following to set a seed
dynamically:
:random.seed(:erlang.now)
:random.seed(:os.timestamp)
## Examples
@@ -1568,6 +1586,46 @@ defmodule Enum do
reduce(collection, [], &sort_reducer(&1, &2, fun)) |> sort_terminator(fun)
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 """
Splits the enumerable into two collections, leaving `count`
elements in the first one. If `count` is a negative number,
@@ -1673,23 +1731,23 @@ defmodule Enum do
[]
end
def take(collection, count) when is_list(collection) and count > 0 do
def take(collection, count) when is_list(collection) and is_integer(count) and count > 0 do
do_take(collection, count)
end
def take(collection, count) when count > 0 do
def take(collection, count) when count > 0 and is_integer(count) do
{_, {res, _}} =
Enumerable.reduce(collection, {:cont, {[], count}}, fn(entry, {list, count}) ->
if count > 1 do
{:cont, {[entry|list], count - 1}}
else
{:halt, {[entry|list], count}}
case count do
0 -> {:halt, {list, count}}
1 -> {:halt, {[entry|list], count - 1}}
_ -> {:cont, {[entry|list], count - 1}}
end
end)
:lists.reverse(res)
end
def take(collection, count) when count < 0 do
def take(collection, count) when count < 0 and is_integer(count) do
Stream.take(collection, count).({:cont, []}, &{:cont, [&1|&2]})
|> elem(1) |> :lists.reverse
end
@@ -1749,27 +1807,6 @@ defmodule Enum do
reverse(collection) |> :lists.reverse
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 """
Enumerates the collection, removing all duplicated items.
+49 -11
View File
@@ -5,16 +5,16 @@ defmodule Exception do
Note that stacktraces in Elixir are updated on throw,
errors and exits. For example, at any given moment,
`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`
functions in this module. They may be changed in future releases
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.
"""
@typedoc "The exception type (as generated by defexception)"
@typedoc "The exception type"
@type t :: %{__struct__: module, __exception__: true}
@typedoc "The kind handled by formatting functions"
@@ -482,6 +482,7 @@ end
defmodule RuntimeError do
defexception message: "runtime error"
@spec exception(String.t) :: Exception.t
def exception(msg) when is_binary(msg) do
%RuntimeError{message: msg}
end
@@ -494,6 +495,7 @@ end
defmodule ArgumentError do
defexception message: "argument error"
@spec exception(String.t) :: Exception.t
def exception(msg) when is_binary(msg) do
%ArgumentError{message: msg}
end
@@ -562,6 +564,14 @@ defmodule BadStructError do
end
end
defmodule BadMapError do
defexception [term: nil]
def message(exception) do
"expected a map, got: #{inspect(exception.term)}"
end
end
defmodule MatchError do
defexception [term: nil]
@@ -611,12 +621,17 @@ defmodule BadArityError do
end
defmodule UndefinedFunctionError do
defexception [module: nil, function: nil, arity: nil]
defexception [module: nil, function: nil, arity: nil, self: false]
def message(exception) do
if exception.function do
formatted = Exception.format_mfa exception.module, exception.function, exception.arity
"undefined function: #{formatted}"
def message(%{function: function, module: module, arity: arity, self: self}) do
if function do
formatted = Exception.format_mfa module, function, arity
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
"undefined function"
end
@@ -662,7 +677,12 @@ defmodule KeyError do
defexception key: nil, term: nil
def message(exception) do
"key #{inspect exception.key} not found in: #{inspect exception.term}"
msg = "key #{inspect exception.key} not found"
if exception.term != nil do
msg <> " in: #{inspect exception.term}"
else
msg
end
end
end
@@ -761,6 +781,20 @@ defmodule ErlangError do
%MatchError{term: term}
end
def normalize({:badmap, term}, _stacktrace) do
%BadMapError{term: term}
end
def normalize({:badkey, key}, stacktrace) do
term =
case stacktrace || :erlang.get_stacktrace do
[{:maps, :update, [_, _, map], _}|_] -> map
[{:maps, :get, [_, map], _}|_] -> map
_ -> nil
end
%KeyError{key: key, term: term}
end
def normalize({:case_clause, term}, _stacktrace) do
%CaseClauseError{term: term}
end
@@ -770,8 +804,9 @@ defmodule ErlangError do
end
def normalize(:undef, stacktrace) do
{mod, fun, arity} = from_stacktrace(stacktrace || :erlang.get_stacktrace)
%UndefinedFunctionError{module: mod, function: fun, arity: arity}
stacktrace = stacktrace || :erlang.get_stacktrace
{mod, fun, arity} = from_stacktrace(stacktrace)
%UndefinedFunctionError{module: mod, function: fun, arity: arity, self: from_self(stacktrace)}
end
def normalize(:function_clause, stacktrace) do
@@ -798,4 +833,7 @@ defmodule ErlangError do
defp from_stacktrace(_) do
{nil, nil, nil}
end
defp from_self([{module, _, _, _}, {module, _, _, _}|_]), do: true
defp from_self(_), do: false
end
+52 -24
View File
@@ -75,6 +75,12 @@ defmodule File do
@type posix :: :file.posix()
@type io_device :: :file.io_device()
@type stat_options :: [time: :local | :universal | :posix]
@type mode :: :append | :binary | :compressed | :delayed_write | :exclusive |
:raw | :read | :read_ahead | :sync | :write |
{:encoding , :latin1 | :unicode | :utf16 | :utf32 | :utf8 |
{:utf16, :big | :little} | {:utf32, :big | :little}} |
{:read_ahead, pos_integer} |
{:delayed_write, non_neg_integer, non_neg_integer}
@doc """
Returns `true` if the path is a regular file.
@@ -308,11 +314,15 @@ defmodule File do
def touch(path, time \\ :calendar.local_time) do
path = IO.chardata_to_string(path)
case F.change_time(path, time) do
{:error, :enoent} ->
write(path, "")
F.change_time(path, time)
other ->
other
{:error, :enoent} -> touch_new(path, time)
other -> other
end
end
defp touch_new(path, time) do
case write(path, "", [:append]) do
:ok -> F.change_time(path, time)
{:error, _reason} = error -> error
end
end
@@ -548,7 +558,8 @@ defmodule File do
[dest|acc]
{:error, :eexist} ->
if callback.(src, dest) do
rm(dest)
# If rm/1 fails, copy/2 will fail
_ = rm(dest)
case copy(src, dest) do
{:ok, _} ->
copy_file_mode!(src, dest)
@@ -569,7 +580,8 @@ defmodule File do
[dest|acc]
{:error, :eexist} ->
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
:ok -> [dest|acc]
{:error, reason} -> {:error, reason, src}
@@ -606,7 +618,7 @@ defmodule File do
Check `File.open/2` for other available options.
"""
@spec write(Path.t, iodata, list) :: :ok | {:error, posix}
@spec write(Path.t, iodata, [mode]) :: :ok | {:error, posix}
def write(path, content, modes \\ []) do
F.write_file(IO.chardata_to_string(path), content, modes)
end
@@ -614,7 +626,7 @@ defmodule File do
@doc """
Same as `write/3` but raises an exception if it fails, returns `:ok` otherwise.
"""
@spec write!(Path.t, iodata, list) :: :ok | no_return
@spec write!(Path.t, iodata, [mode]) :: :ok | no_return
def write!(path, content, modes \\ []) do
path = IO.chardata_to_string(path)
case F.write_file(path, content, modes) do
@@ -665,14 +677,20 @@ defmodule File do
if match? {:win32, _}, :os.type do
case F.read_file_info(IO.chardata_to_string(path)) do
{:ok, file_info} when elem(file_info, 3) in [:read, :none] ->
File.chmod(path, (elem(file_info, 7) + 0200))
F.delete(path)
change_mode_windows(path, file_info)
_ ->
nil
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 """
Same as `rm/1`, but raises an exception in case of failure. Otherwise `:ok`.
"""
@@ -890,7 +908,8 @@ defmodule File do
File.close(file)
"""
@spec open(Path.t, list) :: {:ok, io_device} | {:error, posix}
@spec open(Path.t, [mode | :ram]) :: {:ok, io_device} | {:error, posix}
@spec open(Path.t, (io_device -> res)) :: {:ok, res} | {:error, posix} when res: var
def open(path, modes \\ [])
def open(path, modes) when is_list(modes) do
@@ -923,7 +942,7 @@ defmodule File do
end)
"""
@spec open(Path.t, list, (io_device -> res)) :: {:ok, res} | {:error, posix} when res: var
@spec open(Path.t, [mode | :ram], (io_device -> res)) :: {:ok, res} | {:error, posix} when res: var
def open(path, modes, function) do
case open(path, modes) do
{:ok, device} ->
@@ -941,7 +960,7 @@ defmodule File do
Returns the `io_device` otherwise.
"""
@spec open!(Path.t, list) :: io_device | no_return
@spec open!(Path.t, [mode]) :: io_device | no_return
def open!(path, modes \\ []) do
path = IO.chardata_to_string(path)
case open(path, modes) do
@@ -956,7 +975,7 @@ defmodule File do
Returns the function result otherwise.
"""
@spec open!(Path.t, list, (io_device -> res)) :: res | no_return when res: var
@spec open!(Path.t, [mode | :ram], (io_device -> res)) :: res | no_return when res: var
def open!(path, modes, function) do
path = IO.chardata_to_string(path)
case open(path, modes, function) do
@@ -977,18 +996,27 @@ defmodule File do
@spec cwd() :: {:ok, binary} | {:error, posix}
def cwd() do
case F.get_cwd do
{:ok, base} -> {:ok, IO.chardata_to_string(base)}
{:ok, base} -> {:ok, IO.chardata_to_string(fix_drive_letter(base))}
{:error, _} = error -> error
end
end
defp fix_drive_letter([l, ?:, ?/ | rest] = original) when l in ?A..?Z do
case :os.type() do
{:win32, _} -> [l+?a-?A, ?:, ?/ | rest]
_ -> original
end
end
defp fix_drive_letter(original), do: original
@doc """
The same as `cwd/0`, but raises an exception if it fails.
"""
@spec cwd!() :: binary | no_return
def cwd!() do
case F.get_cwd do
{:ok, cwd} -> IO.chardata_to_string(cwd)
case cwd() do
{:ok, cwd} -> cwd
{:error, reason} ->
raise File.Error, reason: reason, action: "get current working directory"
end
@@ -1112,7 +1140,7 @@ defmodule File do
Returns `:ok` on success, or `{:error, reason}`
on failure.
"""
@spec chmod(Path.t, integer) :: :ok | {:error, posix}
@spec chmod(Path.t, non_neg_integer) :: :ok | {:error, posix}
def chmod(path, mode) do
F.change_mode(IO.chardata_to_string(path), mode)
end
@@ -1120,7 +1148,7 @@ defmodule File do
@doc """
Same as `chmod/2`, but raises an exception in case of failure. Otherwise `:ok`.
"""
@spec chmod!(Path.t, integer) :: :ok | no_return
@spec chmod!(Path.t, non_neg_integer) :: :ok | no_return
def chmod!(path, mode) do
path = IO.chardata_to_string(path)
case chmod(path, mode) do
@@ -1135,7 +1163,7 @@ defmodule File do
for a given `file`. Returns `:ok` on success, or
`{:error, reason}` on failure.
"""
@spec chgrp(Path.t, integer) :: :ok | {:error, posix}
@spec chgrp(Path.t, non_neg_integer) :: :ok | {:error, posix}
def chgrp(path, gid) do
F.change_group(IO.chardata_to_string(path), gid)
end
@@ -1143,7 +1171,7 @@ defmodule File do
@doc """
Same as `chgrp/2`, but raises an exception in case of failure. Otherwise `:ok`.
"""
@spec chgrp!(Path.t, integer) :: :ok | no_return
@spec chgrp!(Path.t, non_neg_integer) :: :ok | no_return
def chgrp!(path, gid) do
path = IO.chardata_to_string(path)
case chgrp(path, gid) do
@@ -1158,7 +1186,7 @@ defmodule File do
for a given `file`. Returns `:ok` on success,
or `{:error, reason}` on failure.
"""
@spec chown(Path.t, integer) :: :ok | {:error, posix}
@spec chown(Path.t, non_neg_integer) :: :ok | {:error, posix}
def chown(path, uid) do
F.change_owner(IO.chardata_to_string(path), uid)
end
@@ -1166,7 +1194,7 @@ defmodule File do
@doc """
Same as `chown/2`, but raises an exception in case of failure. Otherwise `:ok`.
"""
@spec chown!(Path.t, integer) :: :ok | no_return
@spec chown!(Path.t, non_neg_integer) :: :ok | no_return
def chown!(path, uid) do
path = IO.chardata_to_string(path)
case chown(path, uid) do
+1
View File
@@ -57,6 +57,7 @@ defmodule File.Stat do
pairs = :lists.zip(keys, vals)
defstruct keys
@type t :: %__MODULE__{}
@doc """
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
@type t :: %__MODULE__{}
@doc false
def __build__(path, modes, line_or_bytes) do
raw = :lists.keyfind(:encoding, 1, modes) == false
@@ -55,10 +57,14 @@ defmodule File.Stream do
false -> IO.write(device, x)
end
: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
: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
+69 -83
View File
@@ -1,3 +1,5 @@
import Kernel, except: [round: 1]
defmodule Float do
@moduledoc """
Functions for working with floating point numbers.
@@ -26,130 +28,108 @@ defmodule Float do
"""
@spec parse(binary) :: {float, binary} | :error
def parse("-" <> binary) do
case parse_unsign(binary) do
case parse_unsigned(binary) do
:error -> :error
{number, remainder} -> {-number, remainder}
end
end
def parse(binary) do
parse_unsign(binary)
parse_unsigned(binary)
end
defp parse_unsign("-" <> _), do: :error
defp parse_unsign(binary) when is_binary(binary) do
case Integer.parse binary do
:error -> :error
{integer_part, after_integer} -> parse_unsign after_integer, integer_part
end
end
defp parse_unsigned(<<char, rest::binary>>) when char in ?0..?9, do:
parse_unsigned(rest, false, false, <<char>>)
# Dot followed by digit is required afterwards or we are done
defp parse_unsign(<< ?., char, rest :: binary >>, int) when char in ?0..?9 do
parse_unsign(rest, char - ?0, 1, int)
end
defp parse_unsigned(binary) when is_binary(binary), do:
:error
defp parse_unsign(rest, int) do
{:erlang.float(int), rest}
end
defp parse_unsigned(<<char, rest :: binary>>, dot?, e?, acc) when char in ?0..?9, do:
parse_unsigned(rest, dot?, e?, <<acc::binary, char>>)
# Handle decimal points
defp parse_unsign(<< char, rest :: binary >>, float, decimal, int) when char in ?0..?9 do
parse_unsign rest, 10 * float + (char - ?0), decimal + 1, int
end
defp parse_unsigned(<<?., char, rest :: binary>>, false, false, acc) when char in ?0..?9, do:
parse_unsigned(rest, true, false, <<acc::binary, ?., char>>)
defp parse_unsign(<< ?e, after_e :: binary >>, float, decimal, int) do
case Integer.parse after_e do
:error ->
# Note we rebuild the binary here instead of breaking it apart at
# the function clause because the current approach copies a binary
# just on this branch. If we broke it apart in the function clause,
# the copy would happen when calling Integer.parse/1.
{floatify(int, float, decimal), << ?e, after_e :: binary >>}
{exponential, after_exponential} ->
{floatify(int, float, decimal, exponential), after_exponential}
end
end
defp parse_unsigned(<<?e, char, rest :: binary>>, dot?, false, acc) when char in ?0..?9, do:
parse_unsigned(rest, true, true, <<add_dot(acc, dot?)::binary, ?e, char>>)
defp parse_unsign(bitstring, float, decimal, int) do
{floatify(int, float, decimal), bitstring}
end
defp parse_unsigned(<<?e, ?-, char, rest :: binary>>, dot?, false, acc) when char in ?0..?9, do:
parse_unsigned(rest, true, true, <<add_dot(acc, dot?)::binary, ?e, ?-, char>>)
defp floatify(int, float, decimal, exponential \\ 0) do
multiplier = if int < 0, do: -1.0, else: 1.0
defp parse_unsigned(rest, dot?, _e?, acc), do:
{:erlang.binary_to_float(add_dot(acc, dot?)), rest}
# Try to ensure the minimum amount of rounding errors
result = multiplier * (abs(int) * :math.pow(10, decimal) + float) * :math.pow(10, exponential - decimal)
# Try avoiding stuff like this:
# iex(1)> 0.0001 * 75
# 0.007500000000000001
# Due to IEEE 754 floating point standard
# http://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html
final_decimal_places = decimal - exponential
if final_decimal_places > 0 do
decimal_power_round = :math.pow(10, final_decimal_places)
trunc(result * decimal_power_round) / decimal_power_round
else
result
end
end
defp add_dot(acc, true), do: acc
defp add_dot(acc, false), do: acc <> ".0"
@doc """
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
iex> Float.floor(34)
34
iex> Float.floor(34.25)
34
34.0
iex> Float.floor(-56.5)
-57
-57.0
iex> Float.floor(34.253, 2)
34.25
"""
@spec floor(float | integer) :: integer
def floor(num) when is_integer(num), do: num
def floor(num) when is_float(num) do
truncated = :erlang.trunc(num)
case :erlang.abs(num - truncated) do
x when x > 0 and num < 0 -> truncated - 1
_ -> truncated
end
@spec floor(float, 0..15) :: float
def floor(number, precision \\ 0) when is_float(number) and precision in 0..15 do
power = power_of_10(precision)
number = number * power
truncated = trunc(number)
variance = if number - truncated < 0, do: -1.0, else: 0.0
(truncated + variance) / power
end
@doc """
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
iex> Float.ceil(34)
34
iex> Float.ceil(34.25)
35
35.0
iex> Float.ceil(-56.5)
-56
-56.0
iex> Float.ceil(34.253, 2)
34.26
"""
@spec ceil(float | integer) :: integer
def ceil(num) when is_integer(num), do: num
def ceil(num) when is_float(num) do
truncated = :erlang.trunc(num)
case :erlang.abs(num - truncated) do
x when x > 0 and num > 0 -> truncated + 1
_ -> truncated
end
@spec ceil(float, 0..15) :: float
def ceil(number, precision \\ 0) when is_float(number) and precision in 0..15 do
power = power_of_10(precision)
number = number * power
truncated = trunc(number)
variance = if number - truncated > 0, do: 1.0, else: 0.0
(truncated + variance) / power
end
@doc """
Rounds a floating point value to an arbitrary number of fractional digits
(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
iex> Float.round(5.5674, 3)
@@ -165,9 +145,15 @@ defmodule Float do
-5.568
"""
@spec round(float, integer) :: float
def round(number, precision) when is_float(number) and is_integer(precision) and precision in 0..15 do
Kernel.round(number * :math.pow(10, precision)) / :math.pow(10, precision)
@spec round(float, 0..15) :: float
def round(number, precision \\ 0) when is_float(number) and precision in 0..15 do
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
@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
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.
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
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
@doc """
Sends an asynchronous request to the `server`.
This function returns `:ok` immediately, regardless of whether the
destination node or server does exists. `handle_cast/2` will be called on the
server to handle the request.
This function returns `:ok` immediately, regardless of
whether the destination node or server does exists, unless
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
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 """
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.
"""
@spec abcast([node], name :: atom, term) :: :abcast
def abcast(nodes \\ nodes(), name, request) do
:gen_server.abcast(nodes, name, request)
def abcast(nodes \\ nodes(), name, request) when is_list(nodes) and is_atom(name) do
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
@doc """
+1 -1
View File
@@ -21,7 +21,7 @@ defmodule HashDict do
@node_size 8
@node_template :erlang.make_tuple(@node_size, [])
@opaque t :: map
@opaque t :: %__MODULE__{size: non_neg_integer, root: term}
@doc false
defstruct size: 0, root: @node_template
+1 -1
View File
@@ -21,7 +21,7 @@ defmodule HashSet do
@node_size 8
@node_template :erlang.make_tuple(@node_size, [])
@opaque t :: map
@opaque t :: %__MODULE__{size: non_neg_integer, root: term}
@doc false
defstruct size: 0, root: @node_template
+39 -39
View File
@@ -212,29 +212,28 @@ defimpl Inspect, for: BitString do
@doc false
# Also used by Regex
def escape_char(char) when char in ?\000..?\377,
do: octify(char)
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 >>
def escape_char(0) do
<<?\\, ?0>>
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)>>
<<?\\, ?x, ?{, to_hex(a), to_hex(b), to_hex(c), to_hex(d), ?}>>
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)>>
<<?\\, ?x, ?{, to_hex(a), to_hex(b), to_hex(c),
to_hex(d), to_hex(e), to_hex(f), ?}>>
end
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(<<>>, binary), do: binary
@@ -272,25 +271,6 @@ defimpl Inspect, for: BitString do
end
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(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)) ->
<< ?', Inspect.BitString.escape(IO.chardata_to_string(thing), ?') :: binary, ?' >>
keyword?(thing) ->
surround_many("[", thing, "]", opts.limit, &keyword(&1, opts))
surround_many("[", thing, "]", opts, &keyword/2)
true ->
surround_many("[", thing, "]", opts.limit, &to_doc(&1, opts))
surround_many("[", thing, "]", opts, &to_doc/2)
end
end
@@ -347,7 +327,7 @@ defimpl Inspect, for: Tuple do
def inspect({}, _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
@@ -358,14 +338,14 @@ defimpl Inspect, for: Map do
def inspect(map, name, opts) do
map = :maps.to_list(map)
surround_many("%" <> name <> "{", map, "}", opts.limit, traverse_fun(map, opts))
surround_many("%" <> name <> "{", map, "}", opts, traverse_fun(map))
end
defp traverse_fun(list, opts) do
defp traverse_fun(list) do
if Inspect.List.keyword?(list) do
&Inspect.List.keyword(&1, opts)
&Inspect.List.keyword/2
else
&to_map(&1, opts)
&to_map/2
end
end
@@ -378,8 +358,28 @@ defimpl Inspect, for: Map do
end
defimpl Inspect, for: Integer do
def inspect(thing, _opts) do
Integer.to_string(thing)
def inspect(thing, %Inspect.Opts{base: base}) do
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
+126 -95
View File
@@ -29,15 +29,30 @@ defmodule Inspect.Opts do
* `: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,
binaries: :infer :: :infer | :as_binaries | :as_strings,
char_lists: :infer :: :infer | :as_lists | :as_char_lists,
limit: 50 :: pos_integer,
width: 80 :: pos_integer | :infinity,
pretty: false :: boolean
defstruct structs: true,
binaries: :infer,
char_lists: :infer,
limit: 50,
width: 80,
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
defmodule Inspect.Algebra do
@@ -58,8 +73,8 @@ defmodule Inspect.Algebra do
elements together and render them:
iex> doc = Inspect.Algebra.concat(Inspect.Algebra.empty, "foo")
iex> Inspect.Algebra.pretty(doc, 80)
"foo"
iex> Inspect.Algebra.format(doc, 80)
["foo"]
The functions `nest/2`, `space/2` and `line/2` help you put the
document together into a rigid structure. However, the document
@@ -69,15 +84,15 @@ defmodule Inspect.Algebra do
render it:
iex> doc = Inspect.Algebra.glue("a", " ", "b")
iex> Inspect.Algebra.pretty(doc, 80)
"a b"
iex> Inspect.Algebra.format(doc, 80)
["a", " ", "b"]
Notice the break was represented as is, because we haven't reached
a line limit. Once we do, it is replaced by a newline:
iex> doc = Inspect.Algebra.glue(String.duplicate("a", 20), " ", "b")
iex> Inspect.Algebra.pretty(doc, 10)
"aaaaaaaaaaaaaaaaaaaa\nb"
iex> Inspect.Algebra.format(doc, 10)
["aaaaaaaaaaaaaaaaaaaa", "\n", "b"]
Finally, this module also contains Elixir related functions, a bit
tied to Elixir formatting, namely `surround/3` and `surround_many/5`.
@@ -170,10 +185,29 @@ defmodule Inspect.Algebra do
Inspect.inspect(map, opts)
rescue
e ->
stacktrace = System.stacktrace
# Because we try to raise a nice error message in case
# we can't inspect a struct, there is a chance the error
# message itself relies on the struct being printed, so
# 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)
raise ArgumentError,
formatted = IO.iodata_to_binary(format(res, :infinity))
reraise ArgumentError,
"Got #{inspect e.__struct__} with message " <>
"\"#{Exception.message(e)}\" while inspecting #{pretty(res, opts.width)}"
"\"#{Exception.message(e)}\" while inspecting #{formatted}",
stacktrace
after
Process.delete(:inspect_trap)
end
end
end
else
Inspect.Map.inspect(map, opts)
@@ -201,9 +235,9 @@ defmodule Inspect.Algebra do
## Examples
iex> doc = Inspect.Algebra.concat "Tasteless", "Artosis"
iex> Inspect.Algebra.pretty(doc, 80)
"TastelessArtosis"
iex> doc = Inspect.Algebra.concat "hello", "world"
iex> Inspect.Algebra.format(doc, 80)
["hello", "world"]
"""
@spec concat(t, t) :: doc_cons
@@ -227,8 +261,8 @@ defmodule Inspect.Algebra do
## Examples
iex> doc = Inspect.Algebra.nest(Inspect.Algebra.glue("hello", "world"), 5)
iex> Inspect.Algebra.pretty(doc, 5)
"hello\n world"
iex> Inspect.Algebra.format(doc, 5)
["hello", "\n ", "world"]
"""
@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:
iex> doc = Inspect.Algebra.glue("a", " ", "b")
iex> Inspect.Algebra.pretty(doc, 80)
"a b"
iex> Inspect.Algebra.format(doc, 80)
["a", " ", "b"]
Notice the break was represented as is, because we haven't reached
a line limit. Once we do, it is replaced by a newline:
iex> doc = Inspect.Algebra.glue(String.duplicate("a", 20), " ", "b")
iex> Inspect.Algebra.pretty(doc, 10)
"aaaaaaaaaaaaaaaaaaaa\nb"
iex> Inspect.Algebra.format(doc, 10)
["aaaaaaaaaaaaaaaaaaaa", "\n", "b"]
"""
@spec break(binary) :: doc_break
@@ -303,10 +337,10 @@ defmodule Inspect.Algebra do
...> "B"
...> )
...> ))
iex> Inspect.Algebra.pretty(doc, 80)
"Hello, A B"
iex> Inspect.Algebra.pretty(doc, 6)
"Hello,\nA B"
iex> Inspect.Algebra.format(doc, 80)
["Hello,", " ", "A", " ", "B"]
iex> Inspect.Algebra.format(doc, 6)
["Hello,", "\n", "A", " ", "B"]
"""
@spec group(t) :: doc_group
@@ -320,8 +354,8 @@ defmodule Inspect.Algebra do
## Examples
iex> doc = Inspect.Algebra.space "Hughes", "Wadler"
iex> Inspect.Algebra.pretty(doc, 80)
"Hughes Wadler"
iex> Inspect.Algebra.format(doc, 80)
["Hughes", " ", "Wadler"]
"""
@spec space(t, t) :: doc_cons
@@ -333,8 +367,8 @@ defmodule Inspect.Algebra do
## Examples
iex> doc = Inspect.Algebra.line "Hughes", "Wadler"
iex> Inspect.Algebra.pretty(doc, 80)
"Hughes\nWadler"
iex> Inspect.Algebra.format(doc, 80)
["Hughes", "\n", "Wadler"]
"""
@spec line(t, t) :: doc_cons
@@ -350,8 +384,8 @@ defmodule Inspect.Algebra do
iex> doc = Inspect.Algebra.folddoc(doc, fn(x,y) ->
...> Inspect.Algebra.concat [x, "!", y]
...> end)
iex> Inspect.Algebra.pretty(doc, 80)
"A!B"
iex> Inspect.Algebra.format(doc, 80)
["A", "!", "B"]
"""
@spec folddoc([t], ((t, t) -> t)) :: t
@@ -371,8 +405,8 @@ defmodule Inspect.Algebra do
## Examples
iex> doc = Inspect.Algebra.surround "[", Inspect.Algebra.glue("a", "b"), "]"
iex> Inspect.Algebra.pretty(doc, 3)
"[a\n b]"
iex> Inspect.Algebra.format(doc, 3)
["[", "a", "\n ", "b", "]"]
"""
@spec surround(binary, t, binary) :: t
@@ -381,55 +415,63 @@ defmodule Inspect.Algebra do
end
@doc ~S"""
Maps and glues a collection of items together using the given separator
and surrounds them. A limit can be passed which, once reached, stops
gluing and outputs "..." instead.
Maps and glues a collection of items.
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
iex> doc = Inspect.Algebra.surround_many("[", Enum.to_list(1..5), "]", :infinity, &Integer.to_string(&1))
iex> Inspect.Algebra.pretty(doc, 5)
iex> doc = Inspect.Algebra.surround_many("[", Enum.to_list(1..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]"
iex> doc = Inspect.Algebra.surround_many("[", Enum.to_list(1..5), "]", 3, &Integer.to_string(&1))
iex> Inspect.Algebra.pretty(doc, 20)
iex> doc = Inspect.Algebra.surround_many("[", Enum.to_list(1..5), "]",
...> %Inspect.Opts{limit: 3}, fn i, _opts -> to_string(i) end)
iex> Inspect.Algebra.format(doc, 20) |> IO.iodata_to_binary
"[1, 2, 3, ...]"
iex> doc = Inspect.Algebra.surround_many("[", Enum.to_list(1..5), "]", 3, &Integer.to_string(&1), "!")
iex> Inspect.Algebra.pretty(doc, 20)
iex> doc = Inspect.Algebra.surround_many("[", Enum.to_list(1..5), "]",
...> %Inspect.Opts{limit: 3}, fn i, _opts -> to_string(i) end, "!")
iex> Inspect.Algebra.format(doc, 20) |> IO.iodata_to_binary
"[1! 2! 3! ...]"
"""
@spec surround_many(binary, [any], binary, integer | :infinity, (term -> t), binary) :: t
def surround_many(left, docs, right, limit, fun, separator \\ @surround_separator)
@spec surround_many(binary, [any], binary, Inspect.Opts.t, (term, Inspect.Opts.t -> t), binary) :: t
def surround_many(left, docs, right, opts, fun, separator \\ @surround_separator) do
do_surround_many(left, docs, right, opts.limit, opts, fun, separator)
end
def surround_many(left, [], right, _, _fun, _) do
defp do_surround_many(left, [], right, _, _opts, _fun, _) do
concat(left, right)
end
def surround_many(left, docs, right, limit, fun, sep) do
surround(left, surround_many(docs, limit, fun, sep), right)
defp do_surround_many(left, docs, right, limit, _opts, fun, sep) do
surround(left, do_surround_many(docs, limit, _opts, fun, sep), right)
end
defp surround_many(_, 0, _fun, _sep) do
defp do_surround_many(_, 0, _opts, _fun, _sep) do
"..."
end
defp surround_many([h], _limit, fun, _sep) do
fun.(h)
defp do_surround_many([h], limit, opts, fun, _sep) do
fun.(h, %{opts | limit: limit})
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(
concat(fun.(h), sep),
surround_many(t, decrement(limit), fun, sep)
concat(fun.(h, %{opts | limit: limit}), sep),
do_surround_many(t, limit, opts, fun, sep)
)
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(
concat(fun.(h), @tail_separator),
fun.(t)
concat(fun.(h, %{opts | limit: limit}), @tail_separator),
fun.(t, %{opts | limit: limit})
)
end
@@ -437,50 +479,45 @@ defmodule Inspect.Algebra do
defp decrement(counter), do: counter - 1
@doc """
The pretty printing function.
The formatting function.
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.
"""
@spec pretty(t, non_neg_integer | :infinity) :: binary
def pretty(d, w) do
sdoc = format w, 0, [{0, default_mode(w), doc_group(d)}]
render(sdoc)
@spec format(t, non_neg_integer | :infinity) :: iodata
def format(d, w) do
format(w, 0, [{0, default_mode(w), doc_group(d)}])
end
defp default_mode(:infinity), do: :flat
defp default_mode(_), do: :break
# Rendering and internal helpers
# Record representing the document mode to be rendered: flat or broken
@typep mode :: :flat | :break
@doc false
@spec fits?(integer, [{integer, mode, t}]) :: boolean
def fits?(w, _) when w < 0, do: false
def fits?(_, []), do: true
def fits?(_, [{_, _, :doc_line} | _]), do: true
def 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]])
def 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])
def 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)
def fits?(_, [{_, :break, doc_break(_)} | _]), do: true
defp fits?(w, _) when w < 0, do: false
defp fits?(_, []), do: true
defp fits?(_, [{_, _, :doc_line} | _]), do: true
defp fits?(w, [{_, _, :doc_nil} | t]), do: fits?(w, t)
defp 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_nest(x, j)} | t]), do: fits?(w, [{i + j, m, x} | t])
defp fits?(w, [{i, _, doc_group(x)} | t]), do: fits?(w, [{i, :flat, x} | t])
defp fits?(w, [{_, _, s} | t]) when is_binary(s), do: fits?((w - byte_size s), t)
defp fits?(w, [{_, :flat, doc_break(s)} | t]), do: fits?((w - byte_size s), t)
defp fits?(_, [{_, :break, doc_break(_)} | _]), do: true
@doc false
@spec format(integer | :infinity, integer, [{integer, mode, t}]) :: [binary]
def format(_, _, []), do: []
def 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)
def 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])
def 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)]
def 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(_, _, []), do: []
defp format(w, _, [{i, _, :doc_line} | t]), do: [indent(i) | format(w, i, t)]
defp format(w, k, [{_, _, :doc_nil} | t]), do: format(w, k, t)
defp 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_nest(x, j)} | t]), do: format(w, k, [{i + j, m, x} | t])
defp format(w, k, [{i, m, doc_group(x)} | t]), do: format(w, k, [{i, m, x} | t])
defp format(w, k, [{_, _, s} | t]) when is_binary(s), 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)]
defp format(w, k, [{i, :break, doc_break(s)} | t]) do
k = k + byte_size(s)
if w == :infinity or fits?(w - k, t) do
@@ -492,10 +529,4 @@ defmodule Inspect.Algebra do
defp indent(0), do: @newline
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
+9 -7
View File
@@ -9,9 +9,10 @@ defmodule Integer do
Determines if an integer is odd.
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
end
@@ -19,9 +20,10 @@ defmodule Integer do
Determines if an integer is even.
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
end
@@ -99,7 +101,7 @@ defmodule Integer do
"64"
"""
@spec to_string(integer, pos_integer) :: String.t
@spec to_string(integer, 2..36) :: String.t
def to_string(some_integer, base) do
:erlang.integer_to_binary(some_integer, base)
end
@@ -115,7 +117,7 @@ defmodule Integer do
'7'
"""
@spec to_char_list(integer) :: list
@spec to_char_list(integer) :: char_list
def to_char_list(number) do
:erlang.integer_to_list(number)
end
@@ -132,7 +134,7 @@ defmodule Integer do
'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
:erlang.integer_to_list(number, base)
end
+61 -20
View File
@@ -43,8 +43,10 @@ defmodule IO do
end
@doc """
Reads `count` characters from the IO device or until
the end of the line if `:line` is given. It returns:
Reads `count` characters from the IO device, a whole
`:line` or the whole device with `:all`.
It returns:
* `data` - the input characters
@@ -53,10 +55,17 @@ defmodule IO do
* `{:error, reason}` - other (rare) error condition;
for instance, `{:error, :estale}` if reading from an
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, :all) do
do_read_all(map_dev(device), "")
end
def read(device, :line) do
:io.get_line(map_dev(device), '')
end
@@ -65,9 +74,19 @@ defmodule IO do
:io.get_chars(map_dev(device), '', count)
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 """
Reads `count` bytes from the IO device or until
the end of the line if `:line` is given. It returns:
Reads `count` characters from the IO device, a whole
`:line` or the whole device with `:all`.
It returns:
* `data` - the input characters
@@ -76,10 +95,20 @@ defmodule IO do
* `{:error, reason}` - other (rare) error condition;
for instance, `{:error, :estale}` if reading from an
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, :all) do
do_binread_all(map_dev(device), "")
end
def binread(device, :line) do
case :file.read_line(map_dev(device)) do
{:ok, data} -> data
@@ -94,6 +123,15 @@ defmodule IO do
end
end
@read_all_size 4096
defp do_binread_all(mapped_dev, acc) do
case :file.read(mapped_dev, @read_all_size) do
{:ok, data} -> do_binread_all(mapped_dev, acc <> data)
:eof -> acc
other -> other
end
end
@doc """
Writes the given argument to the given device.
@@ -119,6 +157,9 @@ defmodule IO do
as a binary, no unicode conversion happens.
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}
def binwrite(device \\ group_leader(), item) when is_iodata(item) do
@@ -139,17 +180,13 @@ defmodule IO do
@doc """
Inspects and writes the given argument to the device.
It sets by default pretty printing to true and returns
the item itself.
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.
It enables pretty printing by default with width of
80 characters. The width can be changed by explicitly
passing the `:width` option.
## Examples
IO.inspect Process.list
IO.inspect Process.list, width: 40
"""
@spec inspect(term, Keyword.t) :: term
@@ -162,8 +199,9 @@ defmodule IO do
"""
@spec inspect(device, term, Keyword.t) :: term
def inspect(device, item, opts) when is_list(opts) do
opts = Keyword.put_new(opts, :pretty, true)
puts device, Kernel.inspect(item, opts)
opts = struct(Inspect.Opts, opts)
iodata = Inspect.Algebra.format(Inspect.Algebra.to_doc(item, opts), opts.width)
puts device, iodata
item
end
@@ -263,6 +301,9 @@ defmodule IO do
Note that an IO stream has side effects and every time
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
def binstream(device, line_or_bytes) do
@@ -354,11 +395,11 @@ defmodule IO do
def each_stream(device, what) do
case read(device, what) do
:eof ->
nil
{:halt, device}
{:error, reason} ->
raise IO.StreamError, reason: reason
data ->
{data, device}
{[data], device}
end
end
@@ -366,11 +407,11 @@ defmodule IO do
def each_binstream(device, what) do
case binread(device, what) do
:eof ->
nil
{:halt, device}
{:error, reason} ->
raise IO.StreamError, reason: reason
data ->
{data, device}
{[data], device}
end
end
+49 -68
View File
@@ -7,7 +7,7 @@ defmodule IO.ANSI.Sequence do
"\e[#{unquote(code)}#{unquote(terminator)}"
end
defp escape_sequence(unquote(Atom.to_char_list(name))) do
defp format_sequence(unquote(name)) do
unquote(name)()
end
end
@@ -24,19 +24,21 @@ defmodule IO.ANSI do
import IO.ANSI.Sequence
@doc """
Checks whether the default I/O device is a terminal or a file.
@typep ansicode :: atom()
@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
be displayed as intended. This is checked by sending a message to
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).
@doc """
Checks if ANSI coloring is supported and enabled on this machine.
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 terminal?(:io.device) :: boolean
def terminal?(device \\ :erlang.group_leader) do
!match?({:win32, _}, :os.type()) and
match?({:ok, _}, :io.columns(device))
@spec enabled? :: boolean
def enabled? do
Application.get_env(:elixir, :ansi_enabled, false)
end
@doc "Resets all attributes"
@@ -130,99 +132,78 @@ defmodule IO.ANSI do
@doc "Clear screen"
defsequence :clear, "2", "J"
defp escape_sequence(other) do
defp format_sequence(other) do
raise ArgumentError, "invalid ANSI sequence specification: #{other}"
end
@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
multiple sequences).
The named sequences are represented by atoms.
It will also append a `%{reset}` to the string. If you don't want this
behaviour, use `escape_fragment/2`.
It will also append an `IO.ANSI.reset` to the chardata when a conversion is
performed. If you don't want this behaviour, use `format_fragment/2`.
An optional boolean parameter can be passed to enable or disable
emitting actual ANSI codes. When `false`, no ANSI codes will emitted.
By default, standard output will be checked if it is a terminal capable
of handling these sequences (using `terminal?/1` function)
By default checks if ANSI is enabled using the `enabled?/0` function.
## Examples
iex> IO.ANSI.escape("Hello %{red,bright,green}yes", true)
"Hello \e[31m\e[1m\e[32myes\e[0m"
iex> IO.ANSI.format(["Hello, ", :red, :bright, "world!"], true)
[[[[[[], "Hello, "] | "\e[31m"] | "\e[1m"], "world!"] | "\e[0m"]
"""
@spec escape(String.t, emit :: boolean) :: String.t
def escape(string, emit \\ terminal?) when is_binary(string) and is_boolean(emit) do
{rendered, emitted} = do_escape(string, emit, false, nil, [])
if emitted do
rendered <> reset
else
rendered
end
def format(chardata, emit \\ enabled?) when is_boolean(emit) do
do_format(chardata, [], [], emit, :maybe)
end
@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
multiple sequences).
The named sequences are represented by atoms.
An optional boolean parameter can be passed to enable or disable
emitting actual ANSI codes. When `false`, no ANSI codes will emitted.
By default, standard output will be checked if it is a terminal capable
of handling these sequences (using `terminal?/1` function)
By default checks if ANSI is enabled using the `enabled?/0` function.
## Examples
iex> IO.ANSI.escape_fragment("Hello %{red,bright,green}yes", true)
"Hello \e[31m\e[1m\e[32myes"
iex> IO.ANSI.escape_fragment("%{reset}bye", true)
"\e[0mbye"
iex> IO.ANSI.format_fragment([:bright, 'Word'], true)
[[[[[[] | "\e[1m"], 87], 111], 114], 100]
"""
@spec escape_fragment(String.t, emit :: boolean) :: String.t
def escape_fragment(string, emit \\ terminal?) when is_binary(string) and is_boolean(emit) do
{escaped, _emitted} = do_escape(string, emit, false, nil, [])
escaped
def format_fragment(chardata, emit \\ enabled?) when is_boolean(emit) do
do_format(chardata, [], [], emit, false)
end
defp do_escape(<<?}, t :: binary>>, emit, emitted, buffer, acc) when is_list(buffer) do
sequences =
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
defp do_format([term | rest], rem, acc, emit, append_reset) do
do_format(term, [rest | rem], acc, emit, append_reset)
end
defp do_escape(<<h, t :: binary>>, emit, emitted, buffer, acc) when is_list(buffer) do
do_escape(t, emit, emitted, [h|buffer], acc)
defp do_format(term, rem, acc, true, append_reset) when is_atom(term) do
do_format([], rem, [acc | format_sequence(term)], true, !!append_reset)
end
defp do_escape(<<>>, _emit, _emitted, buffer, _acc) when is_list(buffer) do
buffer = IO.iodata_to_binary Enum.reverse(buffer)
raise ArgumentError, "missing } for escape fragment #{buffer}"
defp do_format(term, rem, acc, false, append_reset) when is_atom(term) do
do_format([], rem, acc, false, append_reset)
end
defp do_escape(<<?%, ?{, t :: binary>>, emit, emitted, nil, acc) do
do_escape(t, emit, emitted, [], acc)
defp do_format(term, rem, acc, emit, append_reset) when not is_list(term) do
do_format([], rem, [acc | [term]], emit, append_reset)
end
defp do_escape(<<h, t :: binary>>, emit, emitted, nil, acc) do
do_escape(t, emit, emitted, nil, [h|acc])
defp do_format([], [next | rest], acc, emit, append_reset) do
do_format(next, rest, acc, emit, append_reset)
end
defp do_escape(<<>>, _emit, emitted, nil, acc) do
{IO.iodata_to_binary(Enum.reverse(acc)), emitted}
defp do_format([], [], acc, true, true) do
[acc | IO.ANSI.reset]
end
defp do_format([], [], acc, _emit, _append_reset) do
acc
end
end
+282 -136
View File
@@ -9,11 +9,12 @@ defmodule IO.ANSI.Docs do
The supported values are:
* `:enabled` - toggles coloring on and off (true)
* `:doc_code` - code blocks (cyan, bright)
* `:doc_inline_code` - inline code (cyan)
* `:doc_headings` - h1 and h2 headings (yellow, bright)
* `:doc_title` - top level heading (reverse, yellow, bright)
* `:doc_bold` - bold text (bright)
* `:doc_code` - code blocks (cyan, bright)
* `:doc_headings` - h1 and h2 headings (yellow, bright)
* `:doc_inline_code` - inline code (cyan)
* `:doc_table_heading` - style for table headings
* `:doc_title` - top level heading (reverse, yellow, bright)
* `:doc_underline` - underlined text (underline)
* `:width` - the width to format the text (80)
@@ -22,12 +23,13 @@ defmodule IO.ANSI.Docs do
"""
def default_options do
[enabled: true,
doc_code: "cyan,bright",
doc_inline_code: "cyan",
doc_headings: "yellow,bright",
doc_title: "reverse,yellow,bright",
doc_bold: "bright",
doc_underline: "underline",
doc_bold: [:bright],
doc_code: [:cyan, :bright],
doc_headings: [:yellow],
doc_inline_code: [:cyan],
doc_table_heading: [:reverse],
doc_title: [:reverse, :yellow],
doc_underline: [:underline],
width: 80]
end
@@ -43,12 +45,13 @@ defmodule IO.ANSI.Docs do
padding = div(width + String.length(heading), 2)
heading = heading |> String.rjust(padding) |> String.ljust(width)
write(:doc_title, heading, options)
newline_after_block
end
@doc """
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`.
"""
def print(doc, options \\ []) do
@@ -56,50 +59,61 @@ defmodule IO.ANSI.Docs do
doc
|> String.split(["\r\n","\n"], trim: false)
|> Enum.map(&String.rstrip/1)
|> process("", options)
|> process([], "", options)
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)
process(rest, "", options)
process(rest, [], "", options)
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)
process(rest, "", options)
process(rest, [], "", options)
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)
process(rest, indent, options)
process(rest, [], "", options)
end
defp process(["" | rest], indent, options) do
process(rest, indent, options)
defp process(["" | rest], text, indent, options) do
write_text(text, indent, options)
process(rest, [], indent, options)
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)
end
defp process([line | rest], indent, options) do
{stripped, count} = strip_spaces(line, 0)
defp process(all=[line | rest], text, indent, options) do
{stripped, count} = strip_spaces(line, 0, :infinity)
if is_table_line?(stripped) and rest != [] and is_table_line?(hd(rest)) do
write_text(text, indent, options)
process_table(all, indent, options)
else
case stripped do
<<bullet, ?\s, item :: binary>> when bullet in @bullets ->
process_list(item, rest, count, indent, options)
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_text(rest, [line], indent, false, options)
process(rest, [stripped | text], indent, options)
end
end
defp strip_spaces(" " <> line, acc) do
strip_spaces(line, acc + 1)
end
defp strip_spaces(rest, acc) do
{rest, acc}
end
## Headings
@@ -110,91 +124,81 @@ defmodule IO.ANSI.Docs do
defp write_h2(heading, options) do
write(:doc_headings, heading, options)
newline_after_block
end
defp write_h3(heading, indent, options) do
IO.write(indent)
write(:doc_headings, heading, options)
newline_after_block
end
## Lists
defp process_list(line, rest, count, indent, options) do
IO.write indent <> "• "
{contents, rest, done} = process_list_next(rest, count, false, [])
process_text(contents, [line], indent <> " ", true, options)
if done, do: IO.puts(IO.ANSI.reset)
process(rest, indent, options)
defp process_list(entry, line, rest, count, indent, options) do
# The first list always win some extra padding
if indent == "", do: entry = " " <> entry
new_indent = indent <> String.duplicate(" ", String.length(entry))
{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
# Process the thing after a list item entry. It can be either:
#
# * Continuation of the list
# * A nested list
# * The end of the list
#
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])
defp process_list_next([line | rest], count, max, acc) do
{stripped, next_count} = strip_spaces(line, 0, max)
case process_list_next_kind(stripped, rest, count, next_count) do
:next -> process_list_next(rest, count, max, [stripped | acc])
:done -> {Enum.reverse(acc), [line | rest], true}
:list -> {Enum.reverse(acc), [line | rest], false}
end
end
defp process_list_next([<<bullet, ?\s, _ :: binary>> | _] = rest, _count, _done, acc) when bullet in @bullets do
{Enum.reverse(acc), rest, false}
defp process_list_next([], _count, _max, acc) do
{Enum.reverse(acc), [], true}
end
defp process_list_next(["" | rest], count, _done, acc) do
process_list_next(rest, count, true, [""|acc])
end
defp process_list_next(rest, _count, done, acc) do
{Enum.reverse(acc), rest, done}
end
defp list_next(<<bullet, ?\s, _ :: binary>>, 0) when bullet in @bullets, do: :done
defp list_next(line, 0), do: chop(line, 2)
defp list_next(" " <> line, acc), do: list_next(line, acc - 1)
defp list_next(line, _acc), do: line
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)
defp process_list_next_kind(stripped, rest, count, next_count) do
case {stripped, rest} do
{<<bullet, ?\s, _ :: binary>>, _} when bullet in @bullets and next_count <= count ->
:list
{<<d1, ?., ?\s, _ :: binary>>, _} when d1 in ?0..?9 and next_count <= count ->
:list
{<<d1, d2, ?., ?\s, _ :: binary>>, _} when d1 in ?0..?9 and d2 in ?0..?9 and next_count <= count ->
:list
{"", [" " <> _ | _]} ->
:next
{"", _} ->
:done
_ ->
process_text(rest, [line | para], indent, true, options)
:next
end
end
defp process_text([line | rest], para, indent, from_list, options) do
process_text(rest, [line | para], indent, from_list, options)
## Text
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
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
|> Enum.join(" ")
|> handle_links
|> handle_inline(nil, [], [], options)
|> handle_inline(options)
|> 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
## Code blocks
@@ -214,18 +218,114 @@ defmodule IO.ANSI.Docs do
defp process_code(rest, code, indent, options) do
write_code(code, indent, options)
process(rest, indent, options)
process(rest, [], indent, options)
end
defp write_code(code, indent, options) do
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
## 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
IO.puts color(style, options) <> string <> IO.ANSI.reset
IO.puts IO.ANSI.reset
IO.puts [color(style, options), string, IO.ANSI.reset]
end
defp write_with_wrap([], _available, _indent, _first) do
@@ -283,10 +383,8 @@ defmodule IO.ANSI.Docs do
defp escape_underlines_in_link(text) do
case Regex.match?(~r{.*(https?\S*)}, text) do
true ->
Regex.replace(~r{_}, text, "\\\\_")
_ ->
text
true -> Regex.replace(~r{_}, text, "\\\\_")
_ -> text
end
end
@@ -294,61 +392,104 @@ defmodule IO.ANSI.Docs do
Regex.replace(~r{\[(.*?)\]\((.*?)\)}, text, "\\1 (\\2)")
end
# Single inline quotes.
@single [?`, ?_, ?*]
# ` does not require space in between
@spaced [?_, ?*]
# We have four entries: **, *, _ and `.
#
# The first three behave the same while the last one is simpler
# when it comes to delimiters. But, since the first has two
# characters, we need to handle 3 cases:
#
# 1. **
# 2. _ and *
# 3. `
#
# Where the first two should have the same code but match differently.
@single [?_, ?*]
# Clauses for handling spaces
defp handle_inline(<<?*, ?*, ?\s, rest :: binary>>, nil, buffer, acc, options) do
handle_inline(rest, nil, [?\s, ?*, ?*|buffer], acc, options)
# Characters that can mark the beginning or the end of a word.
# Only support the most common ones at this moment.
@delimiters [?\s, ?', ?", ?!, ?@, ?#, ?$, ?%, ?^, ?&, ?-, ?+, ?(, ?), ?[, ?], ?{, ?}, ?<, ?>, ?.]
# Inline start
defp handle_inline(<<?*, ?*, rest :: binary>>, options) do
handle_inline(rest, ?d, ["**"], [], options)
end
defp handle_inline(<<mark, ?\s, rest :: binary>>, nil, buffer, acc, options) when mark in @spaced do
handle_inline(rest, nil, [?\s, mark|buffer], acc, options)
defp handle_inline(<<mark, rest :: binary>>, options) when mark in @single do
handle_inline(rest, mark, [<<mark>>], [], options)
end
defp handle_inline(<<?\s, ?*, ?*, rest :: binary>>, limit, buffer, acc, options) do
handle_inline(rest, limit, [?*, ?*, ?\s|buffer], acc, options)
defp handle_inline(rest, options) do
handle_inline(rest, nil, [], [], options)
end
defp handle_inline(<<?\s, mark, rest :: binary>>, limit, buffer, acc, options) when mark in @spaced do
handle_inline(rest, limit, [mark, ?\s|buffer], acc, options)
# Inline delimiters
defp handle_inline(<<delimiter, ?*, ?*, rest :: binary>>, nil, buffer, acc, options)
when rest != "" and delimiter in @delimiters do
handle_inline(rest, ?d, ["**"], [delimiter, Enum.reverse(buffer)|acc], options)
end
defp handle_inline(<<delimiter, mark, rest :: binary>>, nil, buffer, acc, options)
when rest != "" and delimiter in @delimiters and mark in @single do
handle_inline(rest, mark, [<<mark>>], [delimiter, Enum.reverse(buffer)|acc], options)
end
defp handle_inline(<<?`, rest :: binary>>, nil, buffer, acc, options)
when rest != "" do
handle_inline(rest, ?`, ["`"], [Enum.reverse(buffer)|acc], options)
end
# Clauses for handling escape
defp handle_inline(<<?\\, ?\\, ?*, ?*, rest :: binary>>, nil, buffer, acc, options)
when rest != "" do
handle_inline(rest, ?d, ["**"], [?\\, Enum.reverse(buffer)|acc], options)
end
defp handle_inline(<<?\\, ?\\, mark, rest :: binary>>, nil, buffer, acc, options)
when rest != "" and mark in @single do
handle_inline(rest, mark, [<<mark>>], [?\\, Enum.reverse(buffer)|acc], options)
end
defp handle_inline(<<?\\, ?\\, rest :: binary>>, limit, buffer, acc, options) do
handle_inline(rest, limit, [?\\|buffer], acc, options)
end
defp handle_inline(<<?\\, ?*, ?*, rest :: binary>>, limit, buffer, acc, options) do
handle_inline(rest, limit, [?*, ?*|buffer], acc, options)
end
# A escape is not valid inside `
# An escape is not valid inside `
defp handle_inline(<<?\\, mark, rest :: binary>>, limit, buffer, acc, options)
when mark in [?_, ?*, ?`] and not(mark == limit and mark == ?`) do
when not(mark == limit and mark == ?`) do
handle_inline(rest, limit, [mark|buffer], acc, options)
end
# Inline start
defp handle_inline(<<?*, ?*, rest :: binary>>, nil, buffer, acc, options) when rest != "" do
handle_inline(rest, ?d, ["**"], [Enum.reverse(buffer)|acc], options)
end
defp handle_inline(<<mark, rest :: binary>>, nil, buffer, acc, options) when rest != "" and mark in @single do
handle_inline(rest, mark, [<<mark>>], [Enum.reverse(buffer)|acc], options)
end
# Inline end
defp handle_inline(<<?*, ?*, rest :: binary>>, ?d, buffer, acc, options) do
defp handle_inline(<<?*, ?*, delimiter, rest :: binary>>, ?d, buffer, acc, options)
when delimiter in @delimiters do
handle_inline(<<delimiter, rest :: binary>>, nil, [], [inline_buffer(buffer, options)|acc], options)
end
defp handle_inline(<<mark, delimiter, rest :: binary>>, mark, buffer, acc, options)
when delimiter in @delimiters and mark in @single do
handle_inline(<<delimiter, rest :: binary>>, nil, [], [inline_buffer(buffer, options)|acc], options)
end
defp handle_inline(<<?*, ?*, rest:: binary>>, ?d, buffer, acc, options)
when rest == "" do
handle_inline(<<>>, nil, [], [inline_buffer(buffer, options)|acc], options)
end
defp handle_inline(<<mark, rest :: binary>>, mark, buffer, acc, options)
when rest == "" and mark in @single do
handle_inline(<<>>, nil, [], [inline_buffer(buffer, options)|acc], options)
end
defp handle_inline(<<?`, rest :: binary>>, ?`, buffer, acc, options) do
handle_inline(rest, nil, [], [inline_buffer(buffer, options)|acc], options)
end
defp handle_inline(<<mark, rest :: binary>>, mark, buffer, acc, options) when mark in @single do
handle_inline(rest, nil, [], [inline_buffer(buffer, options)|acc], options)
end
# Catch all
defp handle_inline(<<char, rest :: binary>>, mark, buffer, acc, options) do
handle_inline(rest, mark, [char|buffer], acc, options)
@@ -363,14 +504,19 @@ defmodule IO.ANSI.Docs do
[color_for(h, options)|t]
end
defp color_for("`", colors), do: color(:doc_inline_code, colors)
defp color_for("_", colors), do: color(:doc_underline, colors)
defp color_for("*", colors), do: color(:doc_bold, colors)
defp color_for("**", colors), do: color(:doc_bold, colors)
defp color_for(mark, colors) do
case mark do
"`" -> color(:doc_inline_code, colors)
"_" -> color(:doc_underline, colors)
"*" -> color(:doc_bold, colors)
"**" -> color(:doc_bold, colors)
end
end
defp color(style, colors) do
color = colors[style]
enabled = colors[:enabled]
IO.ANSI.escape_fragment("%{#{color}}", enabled)
IO.ANSI.format_fragment(color, colors[:enabled])
end
defp newline_after_block, do: IO.puts(IO.ANSI.reset)
end
+3 -1
View File
@@ -22,6 +22,8 @@ defmodule IO.Stream do
defstruct device: nil, raw: true, line_or_bytes: :line
@type t :: %__MODULE__{}
@doc false
def __build__(device, raw, line_or_bytes) do
%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)
false -> &IO.each_stream(&1, line_or_bytes)
end
Stream.unfold(device, next_fun).(acc, fun)
Stream.resource(fn -> device end, next_fun, &(&1)).(acc, fun)
end
def count(_stream) do
+289 -243
View File
@@ -150,9 +150,9 @@ defmodule Kernel do
3
"""
@spec byte_size(bitstring) :: non_neg_integer
def byte_size(bitstring) do
:erlang.byte_size(bitstring)
@spec byte_size(binary) :: non_neg_integer
def byte_size(binary) do
:erlang.byte_size(binary)
end
@doc """
@@ -183,9 +183,56 @@ defmodule Kernel do
## 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)
In case something goes wrong, you can also use `exit/1` with
a different reason:
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
def exit(reason) do
@@ -838,11 +885,6 @@ defmodule Kernel do
:erlang.--(left, right)
end
@doc false
def left xor right do
:erlang.xor(left, right)
end
@doc """
Boolean not. Argument must be a boolean.
@@ -1344,7 +1386,14 @@ defmodule Kernel do
iex> "abcd" =~ "ad"
false
iex> "abcd" =~ ""
true
"""
@spec (String.t =~ (String.t | Regex.t)) :: boolean
def left =~ "" when is_binary(left), do: true
def left =~ right when is_binary(left) and is_binary(right) do
:binary.match(left, right) != :nomatch
end
@@ -1372,14 +1421,26 @@ defmodule Kernel do
iex> inspect [1, 2, 3, 4, 5], limit: 3
"[1, 2, 3, ...]"
iex> inspect("josé" <> <<0>>)
"<<106, 111, 115, 195, 169, 0>>"
iex> inspect("olá" <> <<0>>)
"<<111, 108, 195, 161, 0>>"
iex> inspect("josé" <> <<0>>, binaries: :as_strings)
"\"josé\\000\""
iex> inspect("olá" <> <<0>>, binaries: :as_strings)
"\"olá\\0\""
iex> inspect("josé", binaries: :as_binaries)
"<<106, 111, 115, 195, 169>>"
iex> inspect("olá", binaries: :as_binaries)
"<<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
representation of an Elixir term. In such cases, the inspected result
@@ -1396,7 +1457,9 @@ defmodule Kernel do
true -> opts.width
false -> :infinity
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
@doc """
@@ -1415,18 +1478,18 @@ defmodule Kernel do
## Example
defmodule User do
defstruct name: "jose"
defstruct name: "john"
end
struct(User)
#=> %User{name: "jose"}
#=> %User{name: "john"}
opts = [name: "eric"]
opts = [name: "meg"]
user = struct(User, opts)
#=> %User{name: "eric"}
#=> %User{name: "meg"}
struct(user, unknown: "value")
#=> %User{name: "eric"}
#=> %User{name: "meg"}
"""
@spec struct(module | map, Enum.t) :: map
@@ -1453,47 +1516,77 @@ defmodule Kernel do
Gets a value from a nested structure.
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
iex> users = %{"josé" => %{age: 27}, "eric" => %{age: 23}}
iex> get_in(users, ["josé", :age])
iex> users = %{"john" => %{age: 27}, "meg" => %{age: 23}}
iex> get_in(users, ["john", :age])
27
In case any of entries in the middle returns `nil`, `nil` will be returned
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])
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
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|t]), do: get_in(Access.get(data, h), t)
def get_in(data, [h]) when is_function(h),
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 """
Puts a value in a nested structure.
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
iex> users = %{"josé" => %{age: 27}, "eric" => %{age: 23}}
iex> put_in(users, ["josé", :age], 28)
%{"josé" => %{age: 28}, "eric" => %{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}}
iex> users = %{"john" => %{age: 27}, "meg" => %{age: 23}}
iex> put_in(users, ["john", :age], 28)
%{"john" => %{age: 28}, "meg" => %{age: 23}}
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
def put_in(data, keys, value) do
@@ -1504,21 +1597,18 @@ defmodule Kernel do
Updates a key in a nested structure.
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
iex> users = %{"josé" => %{age: 27}, "eric" => %{age: 23}}
iex> update_in(users, ["josé", :age], &(&1 + 1))
%{"josé" => %{age: 28}, "eric" => %{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}}
iex> users = %{"john" => %{age: 27}, "meg" => %{age: 23}}
iex> update_in(users, ["john", :age], &(&1 + 1))
%{"john" => %{age: 28}, "meg" => %{age: 23}}
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
def update_in(data, keys, fun) do
@@ -1528,9 +1618,20 @@ defmodule Kernel do
@doc """
Gets a value and updates a nested structure.
It expects a tuple to be returned, containing the value retrieved
and the update one. Uses the `Access` protocol to traverse the
structures according to the given `keys`.
It expects a tuple to be returned, containing the value
retrieved and the update one.
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
@@ -1539,27 +1640,38 @@ defmodule Kernel do
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:
iex> users = %{"josé" => %{age: 27}, "eric" => %{age: 23}}
iex> get_and_update_in(users, ["josé", :age], &{&1, &1 + 1})
{27, %{"josé" => %{age: 28}, "eric" => %{age: 23}}}
iex> users = %{"john" => %{age: 27}, "meg" => %{age: 23}}
iex> get_and_update_in(users, ["john", :age], &{&1, &1 + 1})
{27, %{"john" => %{age: 28}, "meg" => %{age: 23}}}
In case any of entries in the middle returns `nil`, a map is dynamically
created:
When one of the keys is a function, the function is invoked.
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> get_and_update_in(users, ["dave", :age], &{&1, 13})
{nil, %{"josé" => %{age: 27}, "eric" => %{age: 23}, "dave" => %{age: 13}}}
iex> users = [%{name: "john", age: 27}, %{name: "meg", age: 23}]
iex> all = fn :get_and_update, data, next ->
...> 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),
(term -> {get, term})) :: {get, Access.t} when get: var
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), 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))
end
def get_and_update_in(data, [h], fun) when is_function(h),
do: h.(:get_and_update, data, fun)
def get_and_update_in(data, [h|t], fun) when is_function(h),
do: h.(:get_and_update, data, &get_and_update_in(&1, t, fun))
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 """
Puts a value in a nested structure via the given `path`.
@@ -1579,13 +1691,13 @@ defmodule Kernel do
## Examples
iex> users = %{"josé" => %{age: 27}, "eric" => %{age: 23}}
iex> put_in(users["josé"][:age], 28)
%{"josé" => %{age: 28}, "eric" => %{age: 23}}
iex> users = %{"john" => %{age: 27}, "meg" => %{age: 23}}
iex> put_in(users["john"][:age], 28)
%{"john" => %{age: 28}, "meg" => %{age: 23}}
iex> users = %{"josé" => %{age: 27}, "eric" => %{age: 23}}
iex> put_in(users["josé"].age, 28)
%{"josé" => %{age: 28}, "eric" => %{age: 23}}
iex> users = %{"john" => %{age: 27}, "meg" => %{age: 23}}
iex> put_in(users["john"].age, 28)
%{"john" => %{age: 28}, "meg" => %{age: 23}}
"""
defmacro put_in(path, value) do
@@ -1612,13 +1724,13 @@ defmodule Kernel do
## Examples
iex> users = %{"josé" => %{age: 27}, "eric" => %{age: 23}}
iex> update_in(users["josé"][:age], &(&1 + 1))
%{"josé" => %{age: 28}, "eric" => %{age: 23}}
iex> users = %{"john" => %{age: 27}, "meg" => %{age: 23}}
iex> update_in(users["john"][:age], &(&1 + 1))
%{"john" => %{age: 28}, "meg" => %{age: 23}}
iex> users = %{"josé" => %{age: 27}, "eric" => %{age: 23}}
iex> update_in(users["josé"].age, &(&1 + 1))
%{"josé" => %{age: 28}, "eric" => %{age: 23}}
iex> users = %{"john" => %{age: 27}, "meg" => %{age: 23}}
iex> update_in(users["john"].age, &(&1 + 1))
%{"john" => %{age: 28}, "meg" => %{age: 23}}
"""
defmacro update_in(path, fun) do
@@ -1644,9 +1756,9 @@ defmodule Kernel do
## Examples
iex> users = %{"josé" => %{age: 27}, "eric" => %{age: 23}}
iex> get_and_update_in(users["josé"][:age], &{&1, &1 + 1})
{27, %{"josé" => %{age: 28}, "eric" => %{age: 23}}}
iex> users = %{"john" => %{age: 27}, "meg" => %{age: 23}}
iex> get_and_update_in(users["john"].age, &{&1, &1 + 1})
{27, %{"john" => %{age: 28}, "meg" => %{age: 23}}}
## Paths
@@ -1661,15 +1773,15 @@ defmodule Kernel do
Here are some valid paths:
users["josé"][:age]
users["josé"].age
User.all["josé"].age
all_users()["josé"].age
users["john"][:age]
users["john"].age
User.all["john"].age
all_users()["john"].age
Here are some invalid ones:
# 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
users
@@ -1690,7 +1802,7 @@ defmodule Kernel do
defp nest_get_and_update_in(h, [{:access, key}|t], fun) do
quote do
Access.get_and_update(
case(unquote(h), do: (nil -> %{}; o -> o)),
unquote(h),
unquote(key),
unquote(nest_get_and_update_in(t, fun))
)
@@ -1708,13 +1820,15 @@ defmodule Kernel do
end
defp unnest({{:., _, [expr, key]}, _, []}, acc, kind)
when is_tuple(expr) and elem(expr, 0) != :__aliases__ and elem(expr, 0) != :__MODULE__ do
when is_tuple(expr) and
:erlang.element(1, expr) != :__aliases__ and
:erlang.element(1, expr) != :__MODULE__ do
unnest(expr, [{:map, key}|acc], kind)
end
defp unnest(other, [], kind) do
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
defp unnest(other, acc, kind) do
@@ -1723,14 +1837,14 @@ defmodule Kernel do
false ->
raise ArgumentError,
"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
defp proper_start?({{:., _, [expr, _]}, _, _args})
when is_atom(expr)
when elem(expr, 0) == :__aliases__
when elem(expr, 0) == :__MODULE__, do: true
when :erlang.element(1, expr) == :__aliases__
when :erlang.element(1, expr) == :__MODULE__, do: true
defp proper_start?({atom, _, _args})
when is_atom(atom), do: true
@@ -1776,20 +1890,20 @@ defmodule Kernel do
## Examples
iex> nil?(1)
iex> is_nil(1)
false
iex> nil?(nil)
iex> is_nil(nil)
true
"""
defmacro nil?(x) do
defmacro is_nil(x) do
quote do: unquote(x) == nil
end
@doc """
A convenient macro that checks if the right side matches
the left side. The left side is allowed to be a match pattern.
A convenience macro that checks if the right side (an expression)
matches the left side (a pattern).
## Examples
@@ -1894,10 +2008,7 @@ defmodule Kernel do
case is_list(args) and length(args) == 1 and typespec(name) do
false ->
case name == :typedoc and not bootstraped?(Kernel.Typespec) do
true -> nil
false -> do_at(args, name, function?, __CALLER__)
end
do_at(args, name, function?, __CALLER__)
macro ->
case bootstraped?(Kernel.Typespec) do
false -> nil
@@ -1911,7 +2022,7 @@ defmodule Kernel do
defp do_at([arg], name, function?, env) do
case function? do
true ->
raise ArgumentError, "cannot dynamically set attribute @#{name} inside function"
raise ArgumentError, "cannot set attribute @#{name} inside function/macro"
false ->
case name do
:behavior ->
@@ -1932,7 +2043,14 @@ defmodule Kernel do
case function? do
true ->
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 ->
escaped = case stack do
[] -> []
@@ -1948,7 +2066,7 @@ defmodule Kernel do
end
defp warn_info([entry|_]) do
opts = elem(entry, tuple_size(entry) - 1)
opts = :erlang.element(tuple_size(entry), entry)
Exception.format_file_line(Keyword.get(opts, :file), Keyword.get(opts, :line)) <> " "
end
@@ -1964,8 +2082,9 @@ defmodule Kernel do
defp typespec(_), do: false
@doc """
Returns the binding as a keyword list where the variable name
is the key and the variable value is the value.
Returns the binding for the given context as a keyword list.
The variable name is the key and the variable value is the value.
## Examples
@@ -1976,59 +2095,18 @@ defmodule Kernel do
iex> binding()
[x: 2]
"""
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(:foo)
[]
iex> binding([:x, :y], :foo)
iex> var!(x, :foo) = 1
1
iex> binding(:foo)
[x: 1]
"""
defmacro binding(list, context) when is_list(list) and is_atom(context) do
do_binding(list, context, __CALLER__.vars, Macro.Env.in_match?(__CALLER__))
end
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})}
defmacro binding(context \\ nil) do
in_match? = Macro.Env.in_match?(__CALLER__)
for {v, c} <- __CALLER__.vars, c == context do
{v, wrap_binding(in_match?, {v, [], c})}
end
end
@@ -2310,7 +2388,7 @@ defmodule Kernel do
it is not loaded. Check `Code.ensure_loaded/1` for more
information.
"""
@spec function_exported?(atom | tuple, atom, integer) :: boolean
@spec function_exported?(atom | tuple, atom, arity) :: boolean
def function_exported?(module, function, arity) do
:erlang.function_exported(module, function, arity)
end
@@ -2561,7 +2639,7 @@ defmodule Kernel do
# Generate the alias for this module definition
{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]]}}
false ->
@@ -2819,97 +2897,59 @@ defmodule Kernel do
## Types
`defstruct` automatically generates a type `t` unless one exists.
The following definition:
It is recommended to define types for structs, by convention this type
is called `t`. To define a struct in a type the struct literal syntax
is used:
defmodule User do
defstruct name: "José" :: String.t,
age: 25 :: integer
defstruct name: "john", age: 25
@type t :: %User{name: String.t, age: integer}
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}
In case a struct does not declare a field type, it defaults to `term`.
Private structs that are not used outside its module should use the private
type attribute `@typep`. Public structs whose internal structure is private
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
{fields, types} = split_fields_and_types(kv)
fields =
defmacro defstruct(fields) do
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}"
{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)
@struct :maps.put(:__struct__, __MODULE__, :maps.from_list(fields))
case Module.get_attribute(__MODULE__, :derive) do
[] ->
:ok
derive ->
Protocol.__derive__(derive, __MODULE__, __ENV__)
[] -> :ok
derive -> Protocol.__derive__(derive, __MODULE__, __ENV__)
end
@spec __struct__() :: t
@spec __struct__() :: %__MODULE__{}
def __struct__() do
@struct
end
end
types =
case bootstraped?(Kernel.Typespec) do
true when types == [] ->
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
quote do
unquote(fields)
unquote(types)
fields
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"""
Defines an exception.
@@ -2970,7 +3010,7 @@ defmodule Kernel do
@behaviour Exception
fields = defstruct unquote(fields)
@spec exception(term) :: t
@spec exception(Keyword.t) :: Exception.t
def exception(args) when is_list(args) do
Kernel.struct(__struct__, args)
end
@@ -2978,7 +3018,7 @@ defmodule Kernel do
defoverridable exception: 1
if Keyword.has_key?(fields, :message) do
@spec message(t) :: String.t
@spec message(Exception.t) :: String.t
def message(exception) do
exception.message
end
@@ -3309,7 +3349,7 @@ defmodule Kernel do
## Sigils
@doc """
@doc ~S"""
Handles the sigil ~S. It simply returns a string
without escaping characters and without interpolations.
@@ -3318,15 +3358,15 @@ defmodule Kernel do
iex> ~S(foo)
"foo"
iex> ~S(f\#{o}o)
"f\\\#{o}o"
iex> ~S(f#{o}o)
"f\#{o}o"
"""
defmacro sigil_S(string, []) do
string
end
@doc """
@doc ~S"""
Handles the sigil ~s. It returns a string as if it was double quoted
string, unescaping characters and replacing interpolations.
@@ -3335,15 +3375,18 @@ defmodule Kernel do
iex> ~s(foo)
"foo"
iex> ~s(f\#{:o}o)
iex> ~s(f#{:o}o)
"foo"
iex> ~s(f\#{:o}o)
"f\#{:o}o"
"""
defmacro sigil_s({:<<>>, line, pieces}, []) do
{:<<>>, line, Macro.unescape_tokens(pieces)}
end
@doc """
@doc ~S"""
Handles the sigil ~C. It simply returns a char list
without escaping characters and without interpolations.
@@ -3352,15 +3395,15 @@ defmodule Kernel do
iex> ~C(foo)
'foo'
iex> ~C(f\#{o}o)
'f\\\#{o}o'
iex> ~C(f#{o}o)
'f\#{o}o'
"""
defmacro sigil_C({:<<>>, _line, [string]}, []) when is_binary(string) do
String.to_char_list(string)
end
@doc """
@doc ~S"""
Handles the sigil ~c. It returns a char list as if it were a single
quoted string, unescaping characters and replacing interpolations.
@@ -3369,9 +3412,12 @@ defmodule Kernel do
iex> ~c(foo)
'foo'
iex> ~c(f\#{:o}o)
iex> ~c(f#{:o}o)
'foo'
iex> ~c(f\#{:o}o)
'f\#{:o}o'
"""
# 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)))
end
@doc """
@doc ~S"""
Handles the sigil ~R. It returns a Regex pattern without escaping
nor interpreting interpolations.
## Examples
iex> Regex.match?(~R(f\#{1,3}o), "f\#o")
iex> Regex.match?(~R(f#{1,3}o), "f#o")
true
"""
@@ -3420,7 +3466,7 @@ defmodule Kernel do
Macro.escape(regex)
end
@doc """
@doc ~S"""
Handles the sigil ~w. It returns a list of "words" split by whitespace.
## Modifiers
@@ -3431,7 +3477,7 @@ defmodule Kernel do
## Examples
iex> ~w(foo \#{:bar} baz)
iex> ~w(foo #{:bar} baz)
["foo", "bar", "baz"]
iex> ~w(--source test/enum_test.exs)
@@ -3451,7 +3497,7 @@ defmodule Kernel do
split_words(binary, modifiers)
end
@doc """
@doc ~S"""
Handles the sigil ~W. It returns a list of "words" split by whitespace
without escaping nor interpreting interpolations.
@@ -3463,8 +3509,8 @@ defmodule Kernel do
## Examples
iex> ~W(foo \#{bar} baz)
["foo", "\\\#{bar}", "baz"]
iex> ~W(foo #{bar} baz)
["foo", "\#{bar}", "baz"]
"""
defmacro sigil_W({:<<>>, _line, [string]}, modifiers) when is_binary(string) do
+61 -48
View File
@@ -3,7 +3,7 @@ defmodule Kernel.CLI do
@blank_config %{commands: [], output: ".", compile: [],
halt: true, compiler_options: [], errors: [],
verbose_compile: false}
pa: [], pz: [], verbose_compile: false}
@doc """
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)
{config, argv} = parse_argv(argv)
:elixir_code_server.cast({:paths, config.pa, config.pz})
System.argv(argv)
run fn ->
run fn _ ->
errors = process_commands(config)
if errors != [] do
@@ -33,37 +34,19 @@ defmodule Kernel.CLI do
by escripts generated by Elixir.
"""
def run(fun, halt \\ true) do
try do
fun.()
if halt do
at_exit(0)
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)
res = exec_fun(fun, {:ok, 0})
if elem(res, 0) == :shutdown or halt do
{_, int} = at_exit(res)
System.halt(int)
end
end
@doc """
Parses ARGV returning the CLI config and trailing args.
"""
@doc false
def parse_argv(argv) do
parse_argv(argv, @blank_config)
end
@doc """
Process commands according to the parsed config from `parse_argv/1`.
Returns all errors.
"""
@doc false
def process_commands(config) do
results = Enum.map(Enum.reverse(config.commands), &process_command(&1, config))
errors = for {:error, msg} <- results, do: msg
@@ -72,21 +55,48 @@ defmodule Kernel.CLI do
## Helpers
defp at_exit(status) do
hooks = :elixir_code_server.call(:flush_at_exit)
defp at_exit(res) do
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
parent = self()
{pid, ref} =
spawn_monitor fn ->
try do
hook.(status)
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 ->
print_error(kind, reason, System.stacktrace)
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
# If an at_exit callback adds a
# new hook we need to invoke it.
unless hooks == [], do: at_exit(status)
receive do
{^pid, res} ->
:erlang.demonitor(ref, [:flush])
res
{:DOWN, ^ref, _, _, other} ->
print_error({:EXIT, pid}, other, [])
{:shutdown, 1}
end
end
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))
end
@elixir_internals [:elixir_compiler, :elixir_module, :elixir_translator, :elixir_expand]
@elixir_internals [:elixir, :elixir_exp, :elixir_compiler, :elixir_module, :elixir_translator,
:elixir_expand, :elixir_lexical]
defp prune_stacktrace([{mod, _, _, _}|t]) when mod in @elixir_internals do
prune_stacktrace(t)
@@ -129,13 +140,15 @@ defmodule Kernel.CLI do
end
defp parse_shared(["-pa", h|t], config) do
add_code_path(h, &Code.prepend_path/1)
parse_shared t, config
paths = expand_code_path(h)
Enum.each(paths, &:code.add_patha/1)
parse_shared t, %{config | pa: config.pa ++ paths}
end
defp parse_shared(["-pz", h|t], config) do
add_code_path(h, &Code.append_path/1)
parse_shared t, config
paths = expand_code_path(h)
Enum.each(paths, &:code.add_pathz/1)
parse_shared t, %{config | pz: config.pz ++ paths}
end
defp parse_shared(["--app", h|t], config) do
@@ -162,7 +175,7 @@ defmodule Kernel.CLI do
parse_shared t, config
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
end
@@ -170,12 +183,11 @@ defmodule Kernel.CLI do
{list, config}
end
defp add_code_path(path, fun) do
defp expand_code_path(path) do
path = Path.expand(path)
case Path.wildcard(path) do
[] -> fun.(path)
list -> Enum.each(list, fun)
[] -> [to_char_list(path)]
list -> Enum.map(list, &to_char_list/1)
end
end
@@ -352,7 +364,8 @@ defmodule Kernel.CLI do
end
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
{:ok, []} ->
@@ -396,7 +409,7 @@ defmodule Kernel.CLI do
end
defp wrapper(fun) do
fun.()
_ = fun.()
:ok
end
@@ -407,8 +420,8 @@ defmodule Kernel.CLI do
# the actual Elixir executable.
case :os.type() do
{:win32, _} ->
exec = Path.rootname(exec)
if File.regular?(exec), do: exec
base = Path.rootname(exec)
if File.regular?(base), do: base, else: exec
_ ->
exec
end
+2 -2
View File
@@ -25,11 +25,11 @@ defmodule Kernel.ErrorHandler do
defp ensure_loaded(module) do
case Code.ensure_loaded(module) do
{:module, _} -> []
{:module, _} -> :ok
{:error, _} ->
parent = :erlang.get(:elixir_compiler_pid)
ref = :erlang.make_ref
send parent, {:waiting, module, self(), ref, module}
send parent, {:waiting, :module, self(), ref, module}
:erlang.garbage_collect(self)
receive do
{^ref, :ready} -> :ok
+44 -36
View File
@@ -18,20 +18,22 @@ defmodule Kernel.LexicalTracker do
Returns all remotes linked to in this lexical scope.
"""
def remotes(arg) do
# If the module is compiled from a function, its lexical
# scope may be long gone, so it has no associated PID.
if pid = to_pid(arg) do
ets = :gen_server.call(pid, :ets, @timeout)
ets = :gen_server.call(to_pid(arg), :ets, @timeout)
:ets.match(ets, {:"$1", :_, :_}) |> List.flatten
else
[]
end
@doc """
Gets the destination the lexical scope is meant to
compile to.
"""
def dest(arg) do
:gen_server.call(to_pid(arg), :dest, @timeout)
end
defp to_pid(pid) when is_pid(pid), do: pid
defp to_pid(mod) when is_atom(mod) do
table = :elixir_module.data_table(mod)
[{_, val}] = :ets.lookup(table, :__lexical_tracker)
[{_, val}] = :ets.lookup(table, {:elixir, :lexical_tracker})
val
end
@@ -39,9 +41,8 @@ defmodule Kernel.LexicalTracker do
# Starts the tracker and returns its pid.
@doc false
def start_link do
{:ok, pid} = :gen_server.start_link(__MODULE__, [], [])
pid
def start_link(dest) do
:gen_server.start_link(__MODULE__, dest, [])
end
@doc false
@@ -96,62 +97,69 @@ defmodule Kernel.LexicalTracker do
# Callbacks
def init([]) do
{:ok, :ets.new(:lexical, [:protected])}
def init(dest) do
{:ok, {:ets.new(:lexical, [:protected]), dest}}
end
def handle_call(:ets, _from, d) do
{:reply, d, d}
@doc false
def handle_call(:ets, _from, {d, dest}) do
{:reply, d, {d, dest}}
end
def handle_call(request, _from, d) do
{:stop, {:bad_call, request}, d}
def handle_call(:dest, _from, {d, dest}) do
{:reply, dest, {d, dest}}
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)
{:noreply, d}
{:noreply, {d, dest}}
end
def handle_cast({:import_dispatch, module}, d) do
def handle_cast({:import_dispatch, module}, {d, dest}) do
add_dispatch(d, module, @import)
{:noreply, d}
{:noreply, {d, dest}}
end
def handle_cast({:alias_dispatch, module}, d) do
def handle_cast({:alias_dispatch, module}, {d, dest}) do
add_dispatch(d, module, @alias)
{:noreply, d}
{:noreply, {d, dest}}
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)
{:noreply, d}
{:noreply, {d, dest}}
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)
{:noreply, d}
{:noreply, {d, dest}}
end
def handle_cast(:stop, d) do
{:stop, :normal, d}
def handle_cast(:stop, {d, dest}) do
{:stop, :normal, {d, dest}}
end
def handle_cast(msg, d) do
{:stop, {:bad_cast, msg}, d}
def handle_cast(msg, {d, dest}) do
{:stop, {:bad_cast, msg}, {d, dest}}
end
def handle_info(_msg, d) do
{:noreply, d}
@doc false
def handle_info(_msg, {d, dest}) do
{:noreply, {d, dest}}
end
def terminate(_reason, _d) do
@doc false
def terminate(_reason, _state) do
:ok
end
def code_change(_old, d, _extra) do
{:ok, d}
@doc false
def code_change(_old, state, _extra) do
{:ok, state}
end
# 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
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
file
@@ -23,57 +23,61 @@ defmodule Kernel.ParallelCompiler do
* `:each_module` - for each module compiled, invokes the callback passing
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.
"""
def files(files, callbacks \\ [])
def files(files, options \\ [])
def files(files, callbacks) when is_list(callbacks) do
spawn_compilers(files, nil, callbacks)
def files(files, options) when is_list(options) do
spawn_compilers(files, nil, options)
end
@doc """
Compiles the given files to the given path.
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
spawn_compilers(files, path, callbacks)
def files_to_path(files, path, options) when is_binary(path) and is_list(options) do
spawn_compilers(files, path, options)
end
defp spawn_compilers(files, path, callbacks) do
Code.ensure_loaded(Kernel.ErrorHandler)
defp spawn_compilers(files, path, options) do
true = Code.ensure_loaded?(Kernel.ErrorHandler)
compiler_pid = self()
:elixir_code_server.cast({:reset_warnings, compiler_pid})
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,
# compilation status will be set to error and we fail with CompileError
case :elixir_code_server.call({:compilation_status, compiler_pid}) do
:ok -> result
:error -> exit(1)
:error -> exit({:shutdown, 1})
end
end
# 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
wait_for_messages(entries, original, output, callbacks, waiting, queued, schedulers, result)
wait_for_messages(entries, original, output, options, waiting, queued, schedulers, result)
end
# 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)
send h, {ref, :ready}
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
# 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()
{pid, ref} =
@@ -87,43 +91,43 @@ defmodule Kernel.ParallelCompiler do
:erlang.process_flag(:error_handler, Kernel.ErrorHandler)
exit(try do
if output do
_ = if output do
:elixir_compiler.file_to_path(h, output)
else
:elixir_compiler.file(h)
:elixir_compiler.file(h, Keyword.get(options, :dest))
end
{:compiled, h}
{:shutdown, h}
catch
kind, reason ->
{:failure, kind, reason, System.stacktrace}
end)
end
spawn_compilers(t, original, output, callbacks, waiting,
spawn_compilers(t, original, output, options, waiting,
[{pid, ref, h}|queued], schedulers, result)
end
# 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
end
# 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, _, _} ->
{_kind, ^child, ref, _module} = List.keyfind(waiting, child, 1)
send child, {ref, :release}
end
wait_for_messages([], original, output, callbacks, waiting, queued, schedulers, result)
wait_for_messages([], original, output, options, waiting, queued, schedulers, result)
end
# 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
wait_for_messages([], original, output, callbacks, waiting, queued, schedulers, result)
defp spawn_compilers([], original, output, options, waiting, queued, schedulers, result) do
wait_for_messages([], original, output, options, waiting, queued, schedulers, result)
end
# 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
{:struct_available, module} ->
available = for {:struct, pid, _, waiting_module} <- waiting,
@@ -131,11 +135,11 @@ defmodule Kernel.ParallelCompiler do
not pid in entries,
do: pid
spawn_compilers(available ++ entries, original, output, callbacks,
spawn_compilers(available ++ entries, original, output, options,
waiting, queued, schedulers, [{:struct, module}|result])
{: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)
end
@@ -147,7 +151,7 @@ defmodule Kernel.ParallelCompiler do
not pid in entries,
do: pid
spawn_compilers(available ++ entries, original, output, callbacks,
spawn_compilers(available ++ entries, original, output, options,
waiting, queued, schedulers, [{:module, module}|result])
{:waiting, kind, child, ref, on} ->
@@ -160,10 +164,10 @@ defmodule Kernel.ParallelCompiler do
waiting = [{kind, child, ref, on}|waiting]
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}} ->
if callback = Keyword.get(callbacks, :each_file) do
{:DOWN, _down_ref, :process, down_pid, {:shutdown, file}} ->
if callback = Keyword.get(options, :each_file) do
callback.(file)
end
@@ -172,21 +176,27 @@ defmodule Kernel.ParallelCompiler do
new_entries = List.delete(entries, down_pid)
new_queued = List.keydelete(queued, down_pid, 0)
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} ->
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
defp handle_failure(ref, reason, entries, waiting, queued) do
if file = find_failure(ref, queued) do
print_failure(file, reason)
if all_missing?(entries, waiting, queued) do
collect_failures(queued, length(queued) - 1)
end
exit(1)
Enum.each queued, fn {child, _, _} ->
Process.exit(child, :kill)
end
exit({:shutdown, 1})
end
end
@@ -197,7 +207,7 @@ defmodule Kernel.ParallelCompiler do
end
end
defp print_failure(_file, {:compiled, _}) do
defp print_failure(_file, {:shutdown, _}) do
:ok
end
@@ -211,7 +221,8 @@ defmodule Kernel.ParallelCompiler do
IO.puts Exception.format(:exit, reason, [])
end
@elixir_internals [:elixir_compiler, :elixir_module, :elixir_translator, :elixir_expand]
@elixir_internals [:elixir, :elixir_exp, :elixir_compiler, :elixir_module, :elixir_translator,
:elixir_expand, :elixir_lexical]
defp prune_stacktrace([{mod, _, _, _}|t]) when mod in @elixir_internals do
prune_stacktrace(t)
+158 -120
View File
@@ -114,7 +114,7 @@ defmodule Kernel.SpecialForms do
defmodule User do
def __struct__ do
%{name: "josé", age: 27}
%{name: "john", age: 27}
end
end
@@ -122,7 +122,7 @@ defmodule Kernel.SpecialForms do
`Kernel.defstruct/1` macro:
defmodule User do
defstruct name: "josé", age: 27
defstruct name: "john", age: 27
end
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
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
struct. The example below will fail because there is no key
`: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
for operations. Other keys can be added to structs via the
@@ -172,10 +172,22 @@ defmodule Kernel.SpecialForms do
iex> << 1, 2, 3 >>
<< 1, 2, 3 >>
## Bitstring types
## Types
A bitstring is made of many segments. Each segment has a
type, which defaults to integer:
A bitstring is made of many segments and each segment has a
type. There are 9 types used in bitstrings:
- `integer`
- `float`
- `bits` (alias for bitstring)
- `bitstring`
- `binary`
- `bytes` (alias for binary)
- `utf8`
- `utf16`
- `utf32`
When no type is specified, the default is `integer`:
iex> <<1, 2, 3>>
<<1, 2, 3>>
@@ -186,14 +198,7 @@ defmodule Kernel.SpecialForms do
iex> <<0, "foo">>
<<0, 102, 111, 111>>
Any other type needs to be explicitly tagged. For example,
in order to store a float type in the binary, one has to do:
iex> <<3.14 :: float>>
<<64, 9, 30, 184, 81, 235, 133, 31>>
This also means that variables need to be explicitly tagged,
otherwise Elixir defaults to integer:
Variables or any other type need to be explicitly tagged:
iex> rest = "oo"
iex> <<102, rest>>
@@ -201,100 +206,161 @@ defmodule Kernel.SpecialForms do
We can solve this by explicitly tagging it as a binary:
<<102, rest :: binary>>
The type can be integer, float, bitstring/bits, binary/bytes,
utf8, utf16 or utf32, e.g.:
<<102 :: float, rest :: binary>>
An integer can be any arbitrary precision integer. A float is an
IEEE 754 binary32 or binary64 floating point number. A bitstring
is an arbitrary series of bits. A binary is a special case of
bitstring that has a total size divisible by 8.
iex> rest = "oo"
iex> <<102, rest :: binary>>
"foo"
The utf8, utf16, and utf32 types are for unicode codepoints. They
can also be applied to literal strings and char lists:
iex> <<"foo" :: utf16>>
<<0, 102, 0, 111, 0, 111>>
iex> <<"foo" :: utf32>>
<<0, 0, 0, 102, 0, 0, 0, 111, 0, 0, 0, 111>>
The bits type is an alias for bitstring. The bytes type is an
alias for binary.
## Options
The signedness can also be given as signed or unsigned. The
signedness only matters for matching and relevant only for
integers. If unspecified, it defaults to unsigned. Example:
Many options can be given by using `-` as separator. Order is
arbitrary, so the following are all equivalent:
<<102 :: integer-native, rest :: binary>>
<<102 :: native-integer, rest :: binary>>
<<102 :: unsigned-big-integer, rest :: binary>>
<<102 :: unsigned-big-integer-size(8), rest :: binary>>
<<102 :: unsigned-big-integer-8, rest :: binary>>
<<102 :: 8-integer-big-unsigned, rest :: binary>>
<<102, rest :: binary>>
### Unit and Size
The length of the match is equal to the `unit` (a number of bits) times the
`size` (the number of repeated segnments of length `unit`).
Type | Default Unit
--------- | ------------
`integer` | 1 bit
`float` | 1 bit
`binary` | 8 bits
Sizes for types are a bit more nuanced. The default size for integers is 8.
For floats, it is 64. For floats, `size * unit` must result in 32 or 64,
corresponding to [IEEE 754](http://en.wikipedia.org/wiki/IEEE_floating_point)
binary32 and binary64, respectively.
For binaries, the default is the size of the binary. Only the last binary in a
match can use the default size. All others must have their size specified
explicitly, even if the match is unambiguous. For example:
iex> <<name::binary-size(5), " the ", species::binary>> = <<"Frank the Walrus">>
"Frank the Walrus"
iex> {name, species}
{"Frank", "Walrus"}
Failing to specify the size for the non-last causes compilation to fail:
<<name::binary, " the ", species::binary>> = <<"Frank the Walrus">>
** (CompileError): a binary field without size is only allowed at the end of a binary pattern
#### Shortcut Syntax
Size and unit can also be specified using a syntax shortcut
when passing integer values:
iex> x = 1
iex> << x :: 8 >> == << x :: size(8) >>
true
iex> << x :: 8 * 4 >> == << x :: size(8)-unit(4) >>
true
This syntax reflects the fact the effective size is given by
multiplying the size by the unit.
### Modifiers
Some types have associated modifiers to clear up ambiguity in byte
representation.
Modifier | Relevant Type(s)
-------------------- | ----------------
`signed` | `integer`
`unsigned` (default) | `integer`
`little` | `integer`, `utf16`, `utf32`
`big` (default) | `integer`, `utf16`, `utf32`
`native` | `integer`, `utf16`, `utf32`
### Sign
Integers can be `signed` or `unsigned`, defaulting to `unsigned`.
iex> <<int::integer>> = <<-100>>
<<156>>
iex> int
156
iex> <<int::integer-signed>> = <<-100>>
<<156>>
iex> int
-100
`signed` and `unsigned` are only used for matching binaries (see below) and
are only used for integers.
iex> <<-100 :: signed, _rest :: binary>> = <<-100, "foo">>
<<156, 102, 111, 111>>
This match would have failed if we did not specify that the
value -100 is signed. If we're matching into a variable instead
of a value, the signedness won't be checked; rather, the number
will simply be interpreted as having the given (or implied)
signedness, e.g.:
### Endianness
iex> <<val, _rest :: binary>> = <<-100, "foo">>
iex> val
156
Elixir has three options for endianness: `big`, `little`, and `native`.
The default is `big`. `native` is determined by the VM at startup.
Here, `val` is interpreted as unsigned.
iex> <<number::little-integer-size(16)>> = <<0, 1>>
<<0, 1>>
iex> number
256
iex> <<number::big-integer-size(16)>> = <<0, 1>>
<<0, 1>>
iex> number
1
iex> <<number::native-integer-size(16)>> = <<0, 1>>
<<0, 1>>
iex> number
256
The endianness of a segment can be big, little or native (the
latter meaning it will be resolved at VM load time). Passing
many options can be done by giving a list:
## Binary/Bitstring Matching
<<102 :: [integer, native], rest :: binary>>
Binary matching is a powerful feature in Elixir that is useful for extracting
information from binaries as well as pattern matching.
Or:
Binary matching can be used by itself to extract information from binaries:
<<102 :: [unsigned, big, integer], rest :: binary>>
iex> <<"Hello, ", place::binary>> = "Hello, World"
"Hello, World"
iex> place
"World"
And so on.
Or as a part of function definitions to pattern match:
Endianness only makes sense for integers and some UTF code
point types (utf16 and utf32).
defmodule ImageTyper
@png_signature <<137::size(8), 80::size(8), 78::size(8), 71::size(8),
13::size(8), 10::size(8), 26::size(8), 10::size(8)>>
@jpg_signature <<255::size(8), 216::size(8)>>
Finally, we can also specify size and unit for each segment. The
unit is multiplied by the size to give the effective size of
the segment in bits. The default unit for integers, floats,
and bitstrings is 1. For binaries, it is 8.
def type(<<@png_signature, rest::binary>>), do: :png
def type(<<@jpg_signature, rest::binary>>), do: :jpg
def type(_), do :unknown
end
Since integers are default, the default unit is 1. The example below
matches because the string "foo" takes 24 bits and we match it
against a segment of 24 bits, 8 of which are taken by the integer
102 and the remaining 16 bits are specified on the rest.
### Performance & Optimizations
iex> <<102, _rest :: size(16)>> = "foo"
"foo"
The Erlang compiler can provide a number of optimizations on binary creation
and matching. To see optimization output, set the `bin_opt_info` compiler
option:
We can also match by specifying size and unit explicitly:
ERL_COMPILER_OPTIONS=bin_opt_info mix compile
iex> <<102, _rest :: [size(2), unit(8)]>> = "foo"
"foo"
However, if we expect a size of 32, it won't match:
iex> <<102, _rest :: size(32)>> = "foo"
** (MatchError) no match of right hand side value: "foo"
Size and unit are not applicable to utf8, utf16, and utf32.
The default size for integers is 8. For floats, it is 64. For
binaries, it is the size of the binary. Only the last binary
in a binary match can use the default size (all others must
have their size specified explicitly).
Size can also be specified using a syntax shortcut. Instead of
writing `size(8)`, one can write just `8` and it will be interpreted
as `size(8)`
iex> << 1 :: 3 >> == << 1 :: size(3) >>
true
For floats, `size * unit` must result in 32 or 64, corresponding
to binary32 and binary64, respectively.
To learn more about specific optimizations and performance considerations,
check out
[Erlang's Efficiency Guide on handling binaries](http://www.erlang.org/doc/efficiency_guide/binaryhandling.html).
"""
defmacro unquote(:<<>>)(args)
@@ -340,28 +406,9 @@ defmodule Kernel.SpecialForms do
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
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.
## 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
When `.` is used, the quoted expression may take two distinct
@@ -1109,7 +1156,7 @@ defmodule Kernel.SpecialForms do
## 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
would be:
@@ -1171,10 +1218,10 @@ defmodule Kernel.SpecialForms do
Note generators can also be used to filter as it removes any value
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)
...> end
["JOSE", "ERIC"]
["JOHN", "MEG"]
Bitstring generators are also supported and are very useful when you
need to organize bitstring streams:
@@ -1324,22 +1371,13 @@ defmodule Kernel.SpecialForms do
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.
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)
@@ -1513,9 +1551,9 @@ defmodule Kernel.SpecialForms do
The catch clause can be used to catch throws values and exits.
try do
exit(1)
exit(:shutdown)
catch
:exit, 1 -> IO.puts "Exited with 1"
:exit, :shutdown -> IO.puts "Exited with shutdown reason"
end
try do
+155 -30
View File
@@ -1,5 +1,5 @@
defmodule Kernel.Typespec do
@moduledoc """
@moduledoc ~S"""
Provides macros and functions for working with typespecs.
Elixir comes with a notation for declaring types and specifications. Elixir is
@@ -38,6 +38,7 @@ defmodule Kernel.Typespec do
| Fun
| Integer
| List
| Map
| Tuple
| Union
| UserDefined # Described in section "Defining a type"
@@ -65,16 +66,33 @@ defmodule Kernel.Typespec do
| nonempty_list(Type) # proper non-empty list
| [] # empty list
| [Type] # shorthand for list(Type)
| [...] # shorthand for nonempty_list()
| [Type, ...] # shorthand for nonempty_list(Type)
| [Keyword]
Map :: map() # map of any size
| %{} # map of any size
| %Struct{} # struct (see defstruct/1)
| %Struct{Keyword}
| %{Keyword}
| %{Pairs}
Tuple :: tuple # a tuple of any size
| {} # empty tuple
| {TList}
| record(Atom) # record (see Record)
| record(Atom, Keyword)
Keyword :: ElixirAtom: Type
| ElixirAtom: Type, Keyword
Pairs :: Type => Type
| Type => Type, Pairs
TList :: Type
| Type, TList
Union :: Type1 | Type2
Union :: Type | Type
### Bit strings
@@ -109,23 +127,25 @@ defmodule Kernel.Typespec do
`term` | `any`
`binary` | `<< _ :: _ * 8 >>`
`bitstring` | `<< _ :: _ * 1 >>`
`boolean` | `false` &#124; `true`
`boolean` | `false` \| `true`
`byte` | `0..255`
`char` | `0..0xffff`
`number` | `integer` &#124; `float`
`char` | `0..0x10ffff`
`number` | `integer` \| `float`
`char_list` | `[char]`
`list` | `[any]`
`maybe_improper_list` | `maybe_improper_list(any, any)`
`nonempty_list` | `nonempty_list(any)`
`iodata` | `iolist` &#124; `binary`
`iolist` | `maybe_improper_list(byte` &#124; `binary` &#124; `iolist, binary` &#124; `[])`
`module` | `atom`
`iodata` | `iolist` \| `binary`
`iolist` | `maybe_improper_list(byte` \| `binary` \| `iolist, binary` \| `[])`
`module` | `atom` \| `tuple`
`mfa` | `{atom, atom, arity}`
`arity` | `0..255`
`node` | `atom`
`timeout` | `:infinity` &#124; `non_neg_integer`
`timeout` | `:infinity` \| `non_neg_integer`
`no_return` | `none`
`fun` | `(... -> any)`
Some built-in types cannot be expressed with valid syntax according to the
language defined above.
@@ -488,9 +508,9 @@ defmodule Kernel.Typespec do
do: type_to_signature(other)
@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}
def type_to_signature({:::, _, [{name, _, args}, _]}),
def type_to_signature({:::, _, [{name, _, args}, _]}) when is_atom(name),
do: {name, length(args)}
## Macro callbacks
@@ -536,7 +556,7 @@ defmodule Kernel.Typespec do
if not export and doc do
: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
{{kind, {name, arity}, type}, caller.line, export, doc}
@@ -624,6 +644,10 @@ defmodule Kernel.Typespec do
[]
end
defp typespec_to_ast({:user_type, line, name, args}) do
typespec_to_ast({:type, line, name, args})
end
defp typespec_to_ast({:type, line, :tuple, :any}) do
{:tuple, [line: line], []}
end
@@ -640,12 +664,29 @@ defmodule Kernel.Typespec do
end
end
defp typespec_to_ast({:type, _line, :list, args}) do
for arg <- args, do: typespec_to_ast(arg)
defp typespec_to_ast({:type, line, :list, []}) do
{: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
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
# 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
@@ -690,7 +731,11 @@ defmodule Kernel.Typespec do
end
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
defp typespec_to_ast({:type, line, name, args}) do
@@ -747,9 +792,9 @@ defmodule Kernel.Typespec do
defp erl_to_ex_var(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}")
<<c :: [binary, size(1)], rest :: binary>> ->
<<c :: binary-size(1), rest :: binary>> ->
String.to_atom("#{String.downcase(c)}#{rest}")
end
end
@@ -785,19 +830,79 @@ defmodule Kernel.Typespec do
## Handle maps and structs
defp typespec({:%{}, meta, fields}, vars, caller) do
fields = :lists.map(fn {k, v} ->
fields =
if :erlang.system_info(:otp_release) >= '18' do
:lists.map(fn {k, v} ->
{:type, line(meta), :map_field_assoc, [typespec(k, vars, caller), typespec(v, vars, caller)]}
end, fields)
else
:lists.map(fn {k, v} ->
{:type, line(meta), :map_field_assoc, typespec(k, vars, caller), typespec(v, vars, caller)}
end, fields)
end
{:type, line(meta), :map, fields}
end
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
# Handle ranges
defp typespec({:.., meta, args}, vars, caller) do
typespec({:range, meta, args}, vars, caller)
args = for arg <- args, do: typespec(arg, vars, caller)
{:type, line(meta), :range, args}
end
# Handle special forms
@@ -866,11 +971,11 @@ defmodule Kernel.Typespec do
end
# Handle local calls
defp typespec({:string, meta, arguments}, vars, caller) do
:elixir_errors.warn caller.line, caller.file, "string() type use is discouraged. For character lists, use " <>
defp typespec({type, meta, arguments}, vars, caller) when type in [:string, :nonempty_string] do
: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))}"
arguments = for arg <- arguments, do: typespec(arg, vars, caller)
{:type, line(meta), :string, arguments}
{:type, line(meta), type, arguments}
end
defp typespec({:char_list, _meta, []}, vars, caller) do
@@ -881,10 +986,22 @@ defmodule Kernel.Typespec do
typespec((quote do: :elixir.as_boolean(unquote(arg))), vars, caller)
end
defp typespec({:fun, meta, args}, vars, caller) do
args = for arg <- args, do: typespec(arg, vars, caller)
{:type, line(meta), :fun, args}
end
defp typespec({name, meta, arguments}, vars, caller) do
arguments = for arg <- arguments, do: typespec(arg, vars, caller)
if :erlang.system_info(:otp_release) >= '18' do
arity = length(arguments)
type = if :erl_internal.is_type(name, arity), do: :type, else: :user_type
{type, line(meta), name, arguments}
else
{:type, line(meta), name, arguments}
end
end
# Handle literals
defp typespec(atom, _, _) when is_atom(atom) do
@@ -899,15 +1016,19 @@ defmodule Kernel.Typespec do
typespec({nil, [], []}, vars, caller)
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
typespec({:list, [], [spec]}, vars, caller)
end
defp typespec([spec, {:"...", _, quoted}], vars, caller) when is_atom(quoted) do
typespec({:nonempty_list, [], [spec]}, vars, caller)
end
defp typespec(list, vars, caller) do
defp typespec(list, vars, caller) when is_list(list) do
[h|t] = :lists.reverse(list)
union = :lists.foldl(fn(x, acc) ->
{:|, [], [validate_kw(x, list, caller), acc]}
@@ -915,6 +1036,10 @@ defmodule Kernel.Typespec do
typespec({:list, [], [union]}, vars, caller)
end
defp typespec(other, _vars, caller) do
compile_error(caller, "unexpected expression in typespec: #{Macro.to_string other}")
end
## Helpers
defp compile_error(caller, desc) do
@@ -941,7 +1066,7 @@ defmodule Kernel.Typespec do
end
end
defp fn_args(meta, [{:"...", _, _}], _vars, _caller) do
defp fn_args(meta, [{:..., _, _}], _vars, _caller) do
{:type, line(meta), :any}
end
+46 -33
View File
@@ -9,19 +9,19 @@ defmodule Keyword do
behave exactly as a dictionary and mimic the API defined
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.
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.
A handful of functions exist to handle duplicated keys, in
particular, `from_enum` allows creating a new keywords without
removing duplicated keys, `get_values` returns all values for
a given key and `delete_first` deletes just one of the existing
particular, `Enum.into/2` allows creating new keywords without
removing duplicated keys, `get_values/2` returns all values for
a given key and `delete_first/2` deletes just one of the existing
entries.
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
@@ -34,7 +34,7 @@ defmodule Keyword do
@type t(value) :: [{key, value}]
@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
def keyword?([{key, _value} | rest]) when is_atom(key) do
@@ -56,7 +56,7 @@ defmodule Keyword do
Creates a keyword from an enumerable.
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.
## Examples
@@ -76,7 +76,7 @@ defmodule Keyword do
Creates a keyword from an enumerable via the transformation function.
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.
## Examples
@@ -96,7 +96,7 @@ defmodule Keyword do
@doc """
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.
Use `get_values/2` to retrieve all entries.
@@ -124,6 +124,7 @@ defmodule Keyword do
@doc """
Fetches the value for a specific `key` and returns it in a tuple.
If the `key` does not exist, returns `:error`.
## Examples
@@ -135,7 +136,7 @@ defmodule Keyword do
:error
"""
@spec fetch(t, key) :: {:ok, value}
@spec fetch(t, key) :: {:ok, value} | :error
def fetch(keywords, key) when is_list(keywords) and is_atom(key) do
case :lists.keyfind(key, 1, keywords) do
{^key, value} -> {:ok, value}
@@ -144,8 +145,9 @@ defmodule Keyword do
end
@doc """
Fetches the value for specific `key`. If `key` does not exist,
a `KeyError` is raised.
Fetches the value for specific `key`.
If `key` does not exist, a `KeyError` is raised.
## Examples
@@ -184,8 +186,9 @@ defmodule Keyword do
end
@doc """
Returns all keys from the keyword list. Duplicated
keys appear duplicated in the final list of keys.
Returns all keys from the keyword list.
Duplicated keys appear duplicated in the final list of keys.
## Examples
@@ -217,6 +220,7 @@ defmodule Keyword do
@doc """
Deletes the entries in the keyword list for a `key` with `value`.
If no `key` with `value` exists, returns the keyword list unchanged.
## Examples
@@ -238,8 +242,9 @@ defmodule Keyword do
@doc """
Deletes the entries in the keyword list for a specific `key`.
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.
## Examples
@@ -261,6 +266,7 @@ defmodule Keyword do
@doc """
Deletes the first entry in the keyword list for a specific `key`.
If the `key` does not exist, returns the keyword list unchanged.
## Examples
@@ -319,7 +325,9 @@ defmodule Keyword do
end
@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.
## Examples
@@ -334,8 +342,9 @@ defmodule Keyword do
end
@doc """
Merges two keyword lists into one. If they have duplicated
entries, the one given as second argument wins.
Merges two keyword lists into one.
If they have duplicated keys, the one given in the second argument wins.
## Examples
@@ -350,8 +359,9 @@ defmodule Keyword do
end
@doc """
Merges two keyword lists into one. If they have duplicated
entries, the given function is invoked to solve conflicts.
Merges two keyword lists into one.
If they have duplicated keys, the given function is invoked to solve conflicts.
## Examples
@@ -392,10 +402,11 @@ defmodule Keyword do
end
@doc """
Updates the `key` with the given function. If the `key` does
not exist, raises `KeyError`.
Updates the `key` with the given function.
If there are duplicated entries, they are all removed and only the first one
If the `key` does not exist, raises `KeyError`.
If there are duplicated keys, they are all removed and only the first one
is updated.
## Examples
@@ -425,10 +436,11 @@ defmodule Keyword do
end
@doc """
Updates the `key` with the given function. If the `key` does
not exist, inserts the given `initial` value.
Updates the `key` with the given function.
If there are duplicated entries, they are all removed and only the first one
If the `key` does not exist, inserts the given `initial` value.
If there are duplicated keys, they are all removed and only the first one
is updated.
## Examples
@@ -455,8 +467,9 @@ defmodule Keyword do
@doc """
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
with removed keys.
separate keyword list.
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.
@@ -508,7 +521,7 @@ defmodule Keyword do
end
@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.
@@ -531,7 +544,7 @@ defmodule Keyword do
Returns the first value associated with `key` in the keyword
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.
## Examples
@@ -558,10 +571,10 @@ defmodule Keyword do
@doc """
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`.
Duplicated entries are not removed.
Duplicated keys are not removed.
## Examples
+5 -21
View File
@@ -247,7 +247,7 @@ defmodule List do
[a: 1, b: 2, c: 3]
"""
@spec keystore([tuple], any, non_neg_integer, tuple) :: [tuple]
@spec keystore([tuple], any, non_neg_integer, tuple) :: [tuple, ...]
def keystore(list, key, position, new_tuple) do
:lists.keystore(key, position + 1, list, new_tuple)
end
@@ -307,6 +307,8 @@ defmodule List do
@doc """
Zips corresponding elements from each list in `list_of_lists`.
The zipping finishes as soon as any list terminates.
## Examples
iex> List.zip([[1, 2], [3, 4], [5, 6]])
@@ -322,24 +324,6 @@ defmodule List do
do_zip(list_of_lists, [])
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 """
Returns a list with `value` inserted at the specified `index`.
Note that `index` is capped at the list length. Negative indices
@@ -528,7 +512,7 @@ defmodule List do
1023
"""
@spec to_integer(char_list, non_neg_integer) :: integer
@spec to_integer(char_list, 2..36) :: integer
def to_integer(char_list, base) do
:erlang.list_to_integer(char_list, base)
end
@@ -566,7 +550,7 @@ defmodule List do
"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
case :unicode.characters_to_binary(list) do
result when is_binary(result) ->
+80 -20
View File
@@ -16,7 +16,8 @@ defmodule Macro do
:&&, :||, :<>, :++, :--, :\\, :::, :<-, :.., :|>, :=~,
:<, :>, :->,
:+, :-, :*, :/, :=, :|, :.,
:and, :or, :xor, :when, :in,
:and, :or, :when, :in,
:~>>, :<<~, :~>, :<~, :<~>, :<|>,
:<<<, :>>>, :|||, :&&&, :^^^, :~~~]
@doc false
@@ -35,11 +36,12 @@ defmodule Macro do
::: -> {:right, 60}
:| -> {:right, 70}
:= -> {: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 [:==, :!=, :=~, :===, :!==] -> {:left, 150}
o when o in [:<, :<=, :>=, :>] -> {:left, 160}
o when o in [:|>, :<<<, :>>>] -> {:left, 170}
o when o in [:|>, :<<<, :>>>, :<~, :~>,
:<<~, :~>>, :<~>, :<|>] -> {:left, 170}
:in -> {:left, 180}
o when o in [:++, :--, :.., :<>] -> {:right, 200}
o when o in [:+, :-] -> {:left, 210}
@@ -74,7 +76,7 @@ defmodule Macro do
def pipe(expr, call_args, position)
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
def pipe(expr, {call, line, atom}, integer) when is_atom(atom) do
@@ -86,7 +88,12 @@ defmodule Macro do
end
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
@doc """
@@ -300,6 +307,40 @@ defmodule Macro do
elem(:elixir_quote.escape(expr, Keyword.get(opts, :unquote, false)), 0)
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"""
Unescape the given chars.
@@ -307,9 +348,9 @@ defmodule Macro do
single- and double-quoted strings. Check `unescape_string/2`
for information on how to customize the escaping map.
In this setup, Elixir will escape the following: `\a`, `\b`,
`\d`, `\e`, `\f`, `\n`, `\r`, `\s`, `\t` and `\v`. Octals are
also escaped according to the latin1 set they represent.
In this setup, Elixir will escape the following: `\0`, `\a`, `\b`,
`\d`, `\e`, `\f`, `\n`, `\r`, `\s`, `\t` and `\v`. Hexadecimals
are also supported via `\xNN` and `\x{NN...}` syntax.
This function is commonly used on sigil implementations
(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.
Here is the default mapping function implemented by Elixir:
def unescape_map(?0), do: ?0
def unescape_map(?a), do: ?\a
def unescape_map(?b), do: ?\b
def unescape_map(?d), do: ?\d
@@ -350,20 +392,14 @@ defmodule Macro do
def unescape_map(?s), do: ?\s
def unescape_map(?t), do: ?\t
def unescape_map(?v), do: ?\v
def unescape_map(?x), do: true
def unescape_map(e), do: e
If the `unescape_map` function returns `false`. The char is
not escaped and `\` is kept in the char list.
## Octals
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`.
Hexadecimals will be escaped if the map function returns `true`
for `?x`.
## Examples
@@ -525,12 +561,16 @@ defmodule Macro do
# All other calls
def to_string({target, _, args} = ast, fun) when is_list(args) do
if sigil = sigil_call(ast, fun) do
sigil
else
{list, last} = :elixir_utils.split_last(args)
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
# Two-item tuples
def to_string({left, right}, fun) do
@@ -565,6 +605,26 @@ defmodule Macro do
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 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({:., _, [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)
@@ -773,7 +833,7 @@ defmodule Macro do
:elixir_lexical.record_remote(receiver, env.lexical_tracker)
{receiver, true}
aliases ->
aliases = for alias <- aliases, do: elem(do_expand_once(alias, env), 0)
aliases = :lists.map(&elem(do_expand_once(&1, env), 0), aliases)
case :lists.all(&is_atom/1, aliases) do
true ->
@@ -838,7 +898,7 @@ defmodule Macro do
end
expand = :elixir_dispatch.expand_import(meta, {atom, length(args)}, args,
env, extra)
env, extra, true)
case expand do
{:ok, receiver, quoted} ->
@@ -879,7 +939,7 @@ defmodule Macro do
be expanded.
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
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
`__ENV__`. Inside macros, the caller environment can be
accessed as `__CALLER__`. It contains the following fields:
accessed as `__CALLER__`.
An instance of `Macro.Env` must not be modified by hand. If you need to
create a custom environment to pass to `Code.eval_quoted/3`, use the
following trick:
def make_custom_env do
import SomeModule, only: [some_function: 2]
alias A.B.C
__ENV__
end
You may then call `make_custom_env()` to get a struct with the desired
imports and aliases included.
It contains the following fields:
* `module` - the current module name
* `file` - the current file name as a binary
@@ -29,7 +44,7 @@ defmodule Macro.Env do
* `local` - the module to expand local functions to
"""
@type name_arity :: {atom, non_neg_integer}
@type name_arity :: {atom, arity}
@type file :: binary
@type line :: non_neg_integer
@type aliases :: [{module, module}]
@@ -42,10 +57,10 @@ defmodule Macro.Env do
@type vars :: [{atom, atom | non_neg_integer}]
@type export_vars :: vars | nil
@type lexical_tracker :: pid
@type local :: module | nil
@type local :: atom | nil
@type t :: %{__struct__: __MODULE__,
module: module,
module: atom,
file: file,
line: line,
function: name_arity | nil,
@@ -105,9 +120,9 @@ defmodule Macro.Env do
"""
def stacktrace(%{__struct__: Macro.Env} = env) do
cond do
nil?(env.module) ->
is_nil(env.module) ->
[{:elixir_compiler, :__FILE__, 1, relative_location(env)}]
nil?(env.function) ->
is_nil(env.function) ->
[{env.module, :__MODULE__, 0, relative_location(env)}]
true ->
{name, arity} = env.function
+27
View File
@@ -38,5 +38,32 @@ defmodule Map do
end, map1, map2
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
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
options.
Several uses of `@compile` will accumulate instead of overriding
previous ones.
### Example
defmodule M do
@@ -476,19 +479,6 @@ defmodule Module do
:elixir_aliases.safe_concat([left, right])
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 """
Attaches documentation to a given function or type. It expects
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
kind in [:def, :defmacro, :type, :opaque] and (is_binary(doc) or is_boolean(doc) or doc == nil) do
assert_not_compiled!(:add_doc, module)
table = docs_table_for(module)
table = data_table_for(module)
{signature, _} = :lists.mapfoldl fn(x, acc) ->
{simplify_signature(x, acc), acc + 1}
end, 1, signature
case :ets.lookup(table, tuple) do
case :ets.lookup(table, {:doc, tuple}) do
[] ->
:ets.insert(table, {tuple, line, kind, signature, doc})
:ets.insert(table, {{:doc, tuple}, line, kind, signature, doc})
:ok
[{tuple, line, _old_kind, old_sign, old_doc}] ->
[{doc_tuple, line, _old_kind, old_sign, old_doc}] ->
:ets.insert(table, {
tuple,
doc_tuple,
line,
kind,
merge_signatures(old_sign, signature, 1),
if(nil?(doc), do: old_doc, else: doc)
if(is_nil(doc), do: old_doc, else: doc)
})
:ok
end
@@ -622,7 +612,7 @@ defmodule Module do
"""
def defines?(module, tuple) when is_tuple(tuple) do
assert_not_compiled!(:defines?, module)
table = function_table_for(module)
table = defs_table_for(module)
:ets.lookup(table, tuple) != []
end
@@ -642,7 +632,7 @@ defmodule Module do
"""
def defines?(module, tuple, kind) do
assert_not_compiled!(:defines?, module)
table = function_table_for(module)
table = defs_table_for(module)
case :ets.lookup(table, tuple) do
[{_, ^kind, _, _, _, _, _}] -> true
_ -> false
@@ -662,8 +652,8 @@ defmodule Module do
"""
def definitions_in(module) do
assert_not_compiled!(:definitions_in, module)
table = function_table_for(module)
for {tuple, _, _, _, _, _, _} <- :ets.tab2list(table), do: tuple
table = defs_table_for(module)
:lists.concat :ets.match(table, {:'$1', :_, :_, :_, :_, :_, :_})
end
@doc """
@@ -681,8 +671,8 @@ defmodule Module do
"""
def definitions_in(module, kind) do
assert_not_compiled!(:definitions_in, module)
table = function_table_for(module)
for {tuple, stored_kind, _, _, _, _, _} <- :ets.tab2list(table), stored_kind == kind, do: tuple
table = defs_table_for(module)
:lists.concat :ets.match(table, {:'$1', kind, :_, :_, :_, :_, :_})
end
@doc """
@@ -694,7 +684,7 @@ defmodule Module do
def make_overridable(module, tuples) do
assert_not_compiled!(:make_overridable, module)
for tuple <- tuples do
:lists.foreach(fn tuple ->
case :elixir_def.lookup_definition(module, tuple) do
false ->
{name, arity} = tuple
@@ -702,27 +692,26 @@ defmodule Module do
clause ->
:elixir_def.delete_definition(module, tuple)
neighbours = if loaded?(Module.LocalsTracker) do
Module.LocalsTracker.yank(module, tuple)
else
neighbours = if :elixir_compiler.get_opt(:internal) do
[]
else
Module.LocalsTracker.yank(module, tuple)
end
old = get_attribute(module, :__overridable)
old = :elixir_def_overridable.overridable(module)
merged = :orddict.update(tuple, fn({count, _, _, _}) ->
{count + 1, clause, neighbours, false}
end, {1, clause, neighbours, false}, old)
put_attribute(module, :__overridable, merged)
end
:elixir_def_overridable.overridable(module, merged)
end
end, tuples)
end
@doc """
Returns `true` if `tuple` in `module` is marked as overridable.
"""
def overridable?(module, tuple) do
!!List.keyfind(get_attribute(module, :__overridable), tuple, 0)
!!List.keyfind(:elixir_def_overridable.overridable(module), tuple, 0)
end
@doc """
@@ -741,7 +730,7 @@ defmodule Module do
assert_not_compiled!(:put_attribute, module)
table = data_table_for(module)
value = normalize_attribute(key, value)
acc = :ets.lookup_element(table, :__acc_attributes, 2)
acc = :ets.lookup_element(table, {:elixir, :acc_attributes}, 2)
new =
if :lists.member(key, acc) do
@@ -766,15 +755,9 @@ defmodule Module do
@foo
Expands to:
Expands close to:
Module.get_attribute(__MODULE__, :foo, true)
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.
Module.get_attribute(__MODULE__, :foo)
## Examples
@@ -788,37 +771,9 @@ defmodule Module do
end
"""
@spec get_attribute(module, atom, warn :: nil | [tuple]) :: term
def get_attribute(module, key, warn \\ nil) when
is_atom(key) and (is_list(warn) or 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, :__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
""
@spec get_attribute(atom, atom) :: term
def get_attribute(module, key) do
get_attribute(module, key, nil)
end
@doc """
@@ -832,10 +787,12 @@ defmodule Module do
end
"""
@spec delete_attribute(atom, atom) :: :ok
def delete_attribute(module, key) when is_atom(key) do
assert_not_compiled!(:delete_attribute, module)
table = data_table_for(module)
:ets.delete(table, key)
:ok
end
@doc """
@@ -874,13 +831,13 @@ defmodule Module do
table = data_table_for(module)
if Keyword.get(opts, :persist) do
old = :ets.lookup_element(table, :__persisted_attributes, 2)
:ets.insert(table, {:__persisted_attributes, [new|old]})
old = :ets.lookup_element(table, {:elixir, :persisted_attributes}, 2)
:ets.insert(table, {{:elixir, :persisted_attributes}, [new|old]})
end
if Keyword.get(opts, :accumulate) do
old = :ets.lookup_element(table, :__acc_attributes, 2)
:ets.insert(table, {:__acc_attributes, [new|old]})
old = :ets.lookup_element(table, {:elixir, :acc_attributes}, 2)
:ets.insert(table, {{:elixir, :acc_attributes}, [new|old]})
end
end
@@ -911,7 +868,9 @@ defmodule Module do
:ok ->
:ok
{: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
delete_attribute(module, :doc)
@@ -933,6 +892,38 @@ defmodule Module do
:ets.insert(table, {key, new})
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
defp normalize_attribute(:on_load, atom) when is_atom(atom) do
@@ -940,7 +931,8 @@ defmodule Module do
end
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
end
@@ -963,15 +955,11 @@ defmodule Module do
end
defp data_table_for(module) do
module
:elixir_module.data_table(module)
end
defp function_table_for(module) do
:elixir_def.table(module)
end
defp docs_table_for(module) do
:elixir_module.docs_table(module)
defp defs_table_for(module) do
:elixir_module.defs_table(module)
end
defp assert_not_compiled!(fun, module) do
@@ -979,6 +967,4 @@ defmodule Module do
raise ArgumentError,
"could not call #{fun} on module #{inspect module} because it was already compiled"
end
defp loaded?(module), do: is_tuple :code.is_loaded(module)
end
+68 -30
View File
@@ -65,22 +65,25 @@ defmodule Module.LocalsTracker do
"""
@spec reachable(ref) :: [local]
def reachable(ref) do
d = :gen_server.call(to_pid(ref), :digraph, @timeout)
reduce_reachable(d, :local, [])
reachable_from(:gen_server.call(to_pid(ref), :digraph, @timeout), :local)
end
defp reachable_from(d, starting) do
:sets.to_list(reduce_reachable(d, starting, :sets.new))
end
defp reduce_reachable(d, vertex, vertices) do
neighbours = :digraph.out_neighbours(d, vertex)
neighbours = (for {_, _} = t <- neighbours, do: t) |> :ordsets.from_list
remaining = :ordsets.subtract(neighbours, vertices)
vertices = :ordsets.union(neighbours, vertices)
:lists.foldl(&reduce_reachable(d, &1, &2), vertices, remaining)
neighbours = (for {_, _} = t <- neighbours, do: t) |> :sets.from_list
remaining = :sets.subtract(neighbours, vertices)
vertices = :sets.union(neighbours, vertices)
:sets.fold(&reduce_reachable(d, &1, &2), vertices, remaining)
end
defp to_pid(pid) when is_pid(pid), do: pid
defp to_pid(mod) when is_atom(mod) do
table = :elixir_module.data_table(mod)
[{_, val}] = :ets.lookup(table, :__locals_tracker)
[{_, val}] = :ets.lookup(table, {:elixir, :locals_tracker})
val
end
@@ -89,8 +92,7 @@ defmodule Module.LocalsTracker do
# Starts the tracker and returns its pid.
@doc false
def start_link do
{:ok, pid} = :gen_server.start_link(__MODULE__, [], [])
pid
:gen_server.start_link(__MODULE__, [], [])
end
# Adds a definition into the tracker. A public
@@ -134,9 +136,7 @@ defmodule Module.LocalsTracker do
# Reattach a previously yanked node
@doc false
def reattach(pid, kind, tuple, neighbours) do
pid = to_pid(pid)
add_definition(pid, kind, tuple)
:gen_server.cast(pid, {:reattach, tuple, neighbours})
:gen_server.cast(to_pid(pid), {:reattach, kind, tuple, neighbours})
end
# Collecting all conflicting imports with the given functions
@@ -156,12 +156,42 @@ defmodule Module.LocalsTracker do
# given also accounting the expected amount of default
# clauses a private function have.
@doc false
def collect_unused_locals(pid, private) do
reachable = reachable(pid)
:lists.foldl(&collect_unused_locals(&1, &2, reachable), [], private)
def collect_unused_locals(ref, private) do
d = :gen_server.call(to_pid(ref), :digraph, @timeout)
{unreachable(d, private), collect_warnings(d, private)}
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
acc
else
@@ -169,7 +199,7 @@ defmodule Module.LocalsTracker do
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
min = arity - default
max = arity
@@ -211,6 +241,7 @@ defmodule Module.LocalsTracker do
{:ok, {d, []}}
end
@doc false
def handle_call({:cache_env, env}, _from, {d, cache}) do
case cache do
[{i,^env}|_] ->
@@ -227,10 +258,9 @@ defmodule Module.LocalsTracker do
end
def handle_call({:yank, local}, _from, {d, _} = state) do
in_vertices = :digraph.in_neighbours(d, local)
out_vertices = :digraph.out_neighbours(d, local)
:digraph.del_vertex(d, local)
{:reply, {in_vertices, out_vertices}, state}
:digraph.del_edges(d, :digraph.out_edges(d, local))
{:reply, {[], out_vertices}, state}
end
def handle_call(:digraph, _from, {d, _} = state) do
@@ -241,6 +271,7 @@ defmodule Module.LocalsTracker do
{:stop, {:bad_call, request}, state}
end
@doc false
def handle_info(_msg, state) do
{:noreply, state}
end
@@ -268,9 +299,17 @@ defmodule Module.LocalsTracker do
{:noreply, state}
end
def handle_cast({:reattach, tuple, {in_neigh, out_neigh}}, {d, _} = state) do
for from <- in_neigh, do: replace_edge(d, from, tuple)
for to <- out_neigh, do: replace_edge(d, tuple, to)
def handle_cast({:reattach, _kind, tuple, {in_neigh, out_neigh}}, {d, _} = state) do
for from <- in_neigh do
: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}
end
@@ -282,10 +321,12 @@ defmodule Module.LocalsTracker do
{:stop, {:bad_cast, msg}, state}
end
@doc false
def terminate(_reason, _state) do
:ok
end
@doc false
def code_change(_old, state, _extra) do
{:ok, state}
end
@@ -300,6 +341,8 @@ defmodule Module.LocalsTracker do
if function != nil do
replace_edge!(d, function, tuple)
end
:ok
end
defp handle_add_local(d, from, to) do
@@ -317,14 +360,9 @@ defmodule Module.LocalsTracker do
end
defp replace_edge!(d, from, to) do
unless :lists.member(to, :digraph.out_neighbours(d, from)) do
_ = unless :lists.member(to, :digraph.out_neighbours(d, from)) do
[:"$e"|_] = :digraph.add_edge(d, from, to)
end
end
defp replace_edge(d, from, to) do
unless :lists.member(to, :digraph.out_neighbours(d, from)) do
:digraph.add_edge(d, from, to)
end
:ok
end
end
+1 -1
View File
@@ -30,7 +30,7 @@ defmodule Node do
returns `{:error, :not_allowed}`. Returns `{:error, :not_found}` if the
local node is not alive.
"""
@spec stop() :: :ok | {:error, term}
@spec stop() :: :ok | {:error, :not_allowed | :not_found}
def stop() do
:net_kernel.stop()
end
+121 -58
View File
@@ -45,7 +45,7 @@ defmodule OptionParser do
* `:strict` - the switches are strict. Any switch that does not
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.
Note only `:strict` or `:switches` may be given at once.
@@ -59,7 +59,7 @@ defmodule OptionParser do
* `:float` - parses the switch as a float.
* `: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
of the returned tuple).
@@ -91,14 +91,8 @@ defmodule OptionParser do
## Negation switches
All switches starting with `--no-` are considered to be booleans and never
parse the next value:
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:
In case a switch is declared as boolean, it may be passed as `--no-SWITCH`
which will set the option to false:
iex> OptionParser.parse(["--no-op", "path/to/file"], switches: [op: :boolean])
{[op: false], ["path/to/file"], []}
@@ -113,8 +107,7 @@ defmodule OptionParser do
"""
@spec parse(argv, options) :: {parsed, argv, errors}
def parse(argv, opts \\ []) when is_list(argv) and is_list(opts) do
config = compile_config(opts, true)
do_parse(argv, config, [], [], [])
do_parse(argv, compile_config(opts), [], [], [], true)
end
@doc """
@@ -134,37 +127,36 @@ defmodule OptionParser do
"""
@spec parse_head(argv, options) :: {parsed, argv, errors}
def parse_head(argv, opts \\ []) when is_list(argv) and is_list(opts) do
config = compile_config(opts, false)
do_parse(argv, config, [], [], [])
do_parse(argv, compile_config(opts), [], [], [], false)
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)}
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
{:ok, option, value, rest} ->
# the option exist and it was successfully parsed
kinds = List.wrap Keyword.get(switches, option)
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} ->
# 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} ->
# 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]} ->
{Enum.reverse(opts), Enum.reverse(args, rest), Enum.reverse(invalid)}
{:error, [arg|rest]=remaining_args} ->
# there is no option
if all do
do_parse(rest, config, opts, [arg|args], invalid)
if all? do
do_parse(rest, config, opts, [arg|args], invalid, all?)
else
{Enum.reverse(opts), Enum.reverse(args, remaining_args), Enum.reverse(invalid)}
end
@@ -193,12 +185,12 @@ defmodule OptionParser do
@spec next(argv, options) ::
{:ok, key :: atom, value :: term, argv} |
{:invalid, key :: atom, value :: term, argv} |
{:undefined, key :: atom, value :: term, argv} |
{:invalid, String.t, String.t | nil, argv} |
{:undefined, String.t, String.t | nil, argv} |
{:error, argv}
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)
end
@@ -221,7 +213,7 @@ defmodule OptionParser do
defp next(["-" <> option|rest], aliases, switches, strict) do
{option, value} = split_option(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
{:undefined, opt_name_bin, value, rest}
@@ -239,9 +231,95 @@ defmodule OptionParser do
{:error, argv}
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
defp compile_config(opts, all) do
defp compile_config(opts) do
aliases = opts[:aliases] || []
{switches, strict} = cond do
@@ -253,7 +331,7 @@ defmodule OptionParser do
{[], false}
end
{aliases, switches, strict, all}
{aliases, switches, strict}
end
defp validate_option(value, kinds) do
@@ -296,11 +374,11 @@ defmodule OptionParser do
end
end
defp tag_option(<<?-, option :: binary>>, value, switches, _aliases) do
get_negated(option, value, switches)
defp tag_option(<<?-, option :: binary>>, switches, _aliases) do
get_negated(option, switches)
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)
if alias = aliases[opt] do
{:default, alias}
@@ -325,21 +403,12 @@ defmodule OptionParser do
{nil, [:invalid], value}
end
defp normalize_option({:negated, option}, nil, switches) do
kinds = List.wrap(switches[option])
cond do
:boolean in kinds ->
{option, kinds, false}
kinds == [] ->
{option, kinds, true}
true ->
{reverse_negated(option), [:invalid], nil}
end
end
defp normalize_option({:negated, option}, value, _switches) do
defp normalize_option({:negated, option}, value, switches) do
if value do
{option, [:invalid], value}
else
{option, List.wrap(switches[option]), false}
end
end
defp normalize_option({:default, option}, value, switches) do
@@ -396,24 +465,18 @@ defmodule OptionParser do
end
end
defp reverse_negated(negated) do
String.to_atom("no_" <> Atom.to_string(negated))
end
defp get_negated("no-" <> rest = option, value, switches) do
if negated = get_option(rest) do
option = if Keyword.has_key?(switches, negated) and value == nil do
negated
else
get_option(option)
end
{:negated, option}
else
defp get_negated("no-" <> rest = original, switches) do
cond do
(negated = get_option(rest)) && :boolean in List.wrap(switches[negated]) ->
{:negated, negated}
option = get_option(original) ->
{:default, option}
true ->
:unknown
end
end
defp get_negated(rest, _value, _switches) do
defp get_negated(rest, _switches) do
if option = get_option(rest) do
{:default, option}
else
+128 -76
View File
@@ -60,40 +60,70 @@ defmodule Path do
def absname(path, relative_to) do
path = IO.chardata_to_string(path)
case type(path) do
:relative -> join(relative_to, path)
:absolute ->
cond do
path == "/" ->
path
:binary.last(path) == ?/ ->
binary_part(path, 0, byte_size(path) - 1)
true ->
path
end
:relative -> absname_join(relative_to, path)
:absolute -> absname_join([path])
:volumerelative ->
relative_to = IO.chardata_to_string(relative_to)
absname_vr(split(path), split(relative_to), relative_to)
end
end
## Absolute path on current drive
# Absolute path on current drive
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),
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
cwd =
case :file.get_cwd([x, ?:]) do
{:ok, dir} -> IO.chardata_to_string(dir)
{:error, _} -> <<x, ?:, ?/>>
end
absname(join(name), cwd)
absname(absname_join(name), cwd)
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 """
Converts the path to an absolute one and expands
any `.` and `..` characters and a leading `~`.
@@ -106,7 +136,7 @@ defmodule Path do
"""
@spec expand(t) :: binary
def expand(path) do
normalize absname(expand_home(path), System.cwd!)
expand_dot absname(expand_home(path), System.cwd!)
end
@doc """
@@ -133,7 +163,7 @@ defmodule Path do
"""
@spec expand(t, t) :: binary
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
@doc """
@@ -157,10 +187,7 @@ defmodule Path do
"""
@spec type(t) :: :absolute | :relative | :volumerelative
def type(name) when is_list(name) or is_binary(name) do
case :os.type() do
{:win32, _} -> win32_pathtype(name)
_ -> unix_pathtype(name)
end |> elem(0)
pathtype(name, major_os_type) |> elem(0)
end
@doc """
@@ -182,10 +209,20 @@ defmodule Path do
"""
@spec relative(t) :: binary
def relative(name) do
case :os.type() do
{:win32, _} -> win32_pathtype(name)
relative(name, major_os_type())
end
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 |> elem(1) |> IO.chardata_to_string
end
end
defp unix_pathtype(<<?/, relative :: binary>>), do:
@@ -390,7 +427,7 @@ defmodule Path do
end
@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.
Note that any trailing slash is removed on join.
@@ -411,11 +448,14 @@ defmodule Path do
def join([name1, name2|rest]), do:
join([join(name1, name2)|rest])
def join([name]), do:
do_join(IO.chardata_to_string(name), <<>>, [], major_os_type())
name
@doc """
Joins two paths.
The right path will always be expanded to its relative format
and any trailing slash is removed on join.
## Examples
iex> Path.join("foo", "bar")
@@ -423,44 +463,34 @@ defmodule Path do
"""
@spec join(t, t) :: binary
def join(left, right),
do: do_join(IO.chardata_to_string(left), relative(right), [], major_os_type())
defp major_os_type do
:os.type |> elem(0)
def join(left, right) do
left = IO.chardata_to_string(left)
os_type = major_os_type()
do_join(left, right, os_type) |> remove_dirsep(os_type)
end
defp do_join(<<uc_letter, ?:, rest :: binary>>, relativename, [], :win32) when uc_letter in ?A..?Z, do:
do_join(rest, relativename, [?:, uc_letter+?a-?A], :win32)
defp do_join(<<?\\, rest :: binary>>, relativename, result, :win32), do:
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 do_join("", right, os_type), do: relative(right, os_type)
defp do_join(left, "", _os_type), do: left
defp do_join(left, right, os_type), do: remove_dirsep(left, os_type) <> "/" <> relative(right, os_type)
defp maybe_remove_dirsep([?/, ?:, letter], :win32), do:
[letter, ?:, ?/]
defp maybe_remove_dirsep([?/], _), do:
[?/]
defp maybe_remove_dirsep([?/|name], _), do:
:lists.reverse(name)
defp maybe_remove_dirsep(name, _), do:
:lists.reverse(name)
defp remove_dirsep("", _os_type), do: ""
defp remove_dirsep(bin, os_type) do
last = :binary.last(bin)
if last == ?/ or (last == ?\\ and os_type == :win32) do
binary_part(bin, 0, byte_size(bin) - 1)
else
bin
end
end
@doc """
Returns a list with the path split by the path separator.
If an empty string is given, returns the root path.
@doc ~S"""
Splits the path into a list at the path separator.
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
@@ -486,6 +516,11 @@ defmodule Path do
defmodule Wildcard do
@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
call({:read_link_info, file})
end
@@ -558,7 +593,7 @@ defmodule Path do
|> Enum.map(&IO.chardata_to_string/1)
end
# Normalize the given path by expanding "..", "." and "~".
# expand_dot the given path by expanding "..", "." and "~".
defp chardata_to_list(chardata) do
case :unicode.characters_to_list(chardata) do
@@ -575,34 +610,51 @@ defmodule Path do
defp expand_home(type) do
case IO.chardata_to_string(type) do
"~" <> rest -> System.user_home! <> rest
"~" <> rest -> resolve_home(rest)
rest -> rest
end
end
defp normalize(path), do: normalize(split(path), [])
defp resolve_home(""), do: System.user_home!
defp normalize([".."|t], ["/"|_] = acc) do
normalize t, acc
defp resolve_home(rest) do
case {rest, major_os_type} do
{"\\" <> _, :win32} ->
System.user_home! <> rest
{"/" <> _, _} ->
System.user_home! <> rest
_ -> rest
end
end
defp normalize([".."|t], [<<letter, ?:, ?/>>|_] = acc) when letter in ?a..?z do
normalize t, acc
defp expand_dot(<<"/../", rest::binary>>),
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
defp normalize([".."|t], [_|acc]) do
normalize t, acc
defp expand_dot(["."|t], acc) do
expand_dot t, acc
end
defp normalize(["."|t], acc) do
normalize t, acc
defp expand_dot([h|t], acc) do
expand_dot t, ["/", h|acc]
end
defp normalize([h|t], acc) do
normalize t, [h|acc]
defp expand_dot([], ["/"|acc]) do
IO.iodata_to_binary(:lists.reverse(acc))
end
defp normalize([], acc) do
join :lists.reverse(acc)
defp major_os_type do
:os.type |> elem(0)
end
end
+18
View File
@@ -5,6 +5,8 @@ defmodule Port do
@doc """
See http://www.erlang.org/doc/man/erlang.html#open_port-2.
Inlined by the compiler.
"""
def open(name, settings) do
:erlang.open_port(name, settings)
@@ -12,6 +14,8 @@ defmodule Port do
@doc """
See http://www.erlang.org/doc/man/erlang.html#port_close-1.
Inlined by the compiler.
"""
def close(port) do
:erlang.port_close(port)
@@ -19,6 +23,8 @@ defmodule Port do
@doc """
See http://www.erlang.org/doc/man/erlang.html#port_command-2.
Inlined by the compiler.
"""
def command(port, data, options \\ []) do
:erlang.port_command(port, data, options)
@@ -26,6 +32,8 @@ defmodule Port do
@doc """
See http://www.erlang.org/doc/man/erlang.html#port_connect-2.
Inlined by the compiler.
"""
def connect(port, pid) do
:erlang.port_connect(port, pid)
@@ -33,6 +41,8 @@ defmodule Port do
@doc """
See http://www.erlang.org/doc/man/erlang.html#port_control-3.
Inlined by the compiler.
"""
def control(port, operation, data) do
:erlang.port_control(port, operation, data)
@@ -40,6 +50,8 @@ defmodule Port do
@doc """
See http://www.erlang.org/doc/man/erlang.html#port_call-3.
Inlined by the compiler.
"""
def call(port, operation, data) do
:erlang.port_call(port, operation, data)
@@ -47,6 +59,8 @@ defmodule Port do
@doc """
See http://www.erlang.org/doc/man/erlang.html#port_info-1.
Inlined by the compiler.
"""
def info(port) do
:erlang.port_info(port)
@@ -54,6 +68,8 @@ defmodule Port do
@doc """
See http://www.erlang.org/doc/man/erlang.html#port_info-2.
Inlined by the compiler.
"""
def info(port, item) do
:erlang.port_info(port, item)
@@ -61,6 +77,8 @@ defmodule Port do
@doc """
See http://www.erlang.org/doc/man/erlang.html#ports-0.
Inlined by the compiler.
"""
def list do
:erlang.ports
+24 -24
View File
@@ -34,7 +34,7 @@ defmodule Process do
end
@doc """
Returns the value for the given key.
Returns the value for the given `key`.
"""
@spec get(term) :: term
@spec get(term, default :: term) :: term
@@ -64,7 +64,7 @@ defmodule Process do
end
@doc """
Deletes the given key from the dictionary.
Deletes the given `key` from the dictionary.
"""
@spec delete(term) :: term | nil
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.
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
exits, the exit signal is transformed into a message {:EXIT, from,
:normal} and delivered to its message queue.
exits, the exit signal is transformed into a message `{:EXIT, from,
:normal}` and delivered to its message queue.
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
@@ -129,10 +129,10 @@ defmodule Process do
end
@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 an atom, it is supposed to be the name of a registered process
If `dest` is a pid, it must be the pid of a local process, dead or alive.
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
not refer to a process.
@@ -208,8 +208,8 @@ defmodule Process do
end
@doc """
If monitor_ref is a reference which the calling process
obtained by calling monitor/1, this monitoring is turned off.
If `monitor_ref` is a reference which the calling process
obtained by calling `monitor/1`, this monitoring is turned off.
If the monitoring is already turned off, nothing happens.
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.
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.
See http://www.erlang.org/doc/man/erlang.html#processes-0 for more info.
@@ -252,7 +252,7 @@ defmodule Process do
@doc """
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
See http://www.erlang.org/doc/man/erlang.html#unlink-1 for more info.
@@ -265,13 +265,13 @@ defmodule Process do
end
@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
`Kernel.send/2` function.
`Process.register/2` will fail with `ArgumentError` if the pid supplied
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
def register(pid, name) when not name in [nil, false, true] do
@@ -290,7 +290,7 @@ defmodule Process do
@doc """
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.
"""
@@ -309,7 +309,7 @@ defmodule Process do
@doc """
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
def group_leader(pid, leader) do
@@ -317,7 +317,7 @@ defmodule Process do
end
@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]
def registered do
@@ -339,19 +339,19 @@ defmodule Process do
end
@doc """
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
only a subset of those allowed in flag/2, namely: save_calls.
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
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.
"""
@spec flag(pid, process_flag, term) :: term
@spec flag(pid, :save_calls, non_neg_integer) :: non_neg_integer
def flag(pid, flag, value) do
:erlang.process_flag(pid, flag, value)
end
@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.
Use this only for debugging information.
@@ -363,8 +363,8 @@ defmodule Process do
end
@doc """
Returns information about the process identified by pid
or nil if the process is not alive.
Returns information about the process identified by `pid`
or `nil` if the process is not alive.
See http://www.erlang.org/doc/man/erlang.html#process_info-2 for more info.
"""
+47 -24
View File
@@ -19,11 +19,12 @@ defmodule Protocol do
defmacro def({name, _, args}) when is_atom(name) and is_list(args) do
arity = length(args)
type_args = for _ <- :lists.seq(2, arity), do: quote(do: term)
type_args = :lists.map(fn _ -> quote(do: term) end,
:lists.seq(2, arity))
type_args = [quote(do: t) | type_args]
call_args = for i <- :lists.seq(2, arity),
do: {String.to_atom(<<?x, i + 64>>), [], __MODULE__}
call_args = :lists.map(fn i -> {String.to_atom(<<?x, i + 64>>), [], __MODULE__} end,
:lists.seq(2, arity))
call_args = [quote(do: t) | call_args]
quote do
@@ -85,11 +86,13 @@ defmodule Protocol do
Returns `:ok` if so, otherwise raises ArgumentError.
"""
@spec assert_impl!(module, module) :: :ok | no_return
def assert_impl!(protocol, impl) do
assert_impl!(protocol, impl, "")
def assert_impl!(protocol, base) do
assert_impl!(protocol, base, "")
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
{:module, ^impl} -> :ok
_ -> raise ArgumentError,
@@ -287,7 +290,7 @@ defmodule Protocol do
defp beam_file(module) when is_atom(module) do
case :code.which(module) do
:non_existing -> module
atom when is_atom(atom) -> module
file -> file
end
end
@@ -426,7 +429,7 @@ defmodule Protocol do
defp after_defprotocol do
quote bind_quoted: [builtin: builtin] do
@spec impl_for(term) :: module | nil
@spec impl_for(term) :: atom() | nil
Kernel.def impl_for(data)
# Define the implementation for structs.
@@ -438,7 +441,7 @@ defmodule Protocol do
end
# Define the implementation for builtins.
for {guard, mod} <- builtin do
:lists.foreach(fn {guard, mod} ->
target = Module.concat(__MODULE__, mod)
Kernel.def impl_for(data) when :erlang.unquote(guard)(data) do
@@ -447,9 +450,9 @@ defmodule Protocol do
false -> any_impl_for
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
impl_for(data) || raise(Protocol.UndefinedError, protocol: __MODULE__, value: data)
end
@@ -494,12 +497,19 @@ defmodule Protocol do
@protocol [fallback_to_any: !!@fallback_to_any, consolidated: false]
@doc false
@spec __protocol__(atom) :: term
@spec __protocol__(:name) :: __MODULE__
@spec __protocol__(:functions) :: unquote(Protocol.__functions_spec__(@functions))
Kernel.def __protocol__(:name), do: __MODULE__
Kernel.def __protocol__(:functions), do: unquote(:lists.sort(@functions))
end
end
@doc false
def __functions_spec__([]),
do: []
def __functions_spec__([h|t]),
do: [:lists.foldl(&{:|, [], [&1, &2]}, h, t), quote(do: ...)]
@doc false
def __impl__(protocol, opts) do
do_defimpl(protocol, :lists.keysort(1, opts))
@@ -510,6 +520,20 @@ defmodule Protocol do
end
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
protocol = unquote(protocol)
for = unquote(for)
@@ -527,11 +551,7 @@ defmodule Protocol do
Module.register_attribute(__MODULE__, :impl, persist: true)
@impl [protocol: @protocol, for: @for]
@doc false
@spec __impl__(atom) :: term
def __impl__(:target), do: __MODULE__
def __impl__(:protocol), do: @protocol
def __impl__(:for), do: @for
unquote(__impl__)
end
end
end
@@ -557,14 +577,14 @@ defmodule Protocol do
end
defp derive(protocol, for, struct, opts, env) do
impl = Module.concat(protocol, Map)
extra = ", cannot derive #{inspect protocol} for #{inspect for}"
assert_protocol!(protocol, extra)
assert_impl!(protocol, impl, extra)
assert_impl!(protocol, Map, extra)
# Clean up variables from eval context
env = %{env | vars: [], export_vars: nil}
args = [for, struct, opts]
impl = Module.concat(protocol, Map)
:elixir_module.expand_callback(env.line, impl, :__deriving__, args, env, fn
mod, fun, args ->
@@ -576,7 +596,9 @@ defmodule Protocol do
@impl [protocol: unquote(protocol), for: unquote(for)]
@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__(:protocol), do: unquote(protocol)
def __impl__(:for), do: unquote(for)
@@ -588,16 +610,17 @@ defmodule Protocol do
@doc false
def __spec__?(module, name, arity) do
signature = {name, arity}
specs = Module.get_attribute(module, :spec)
specs = Module.get_attribute(module, :spec)
found =
for {:spec, expr, caller} <- specs,
Kernel.Typespec.spec_to_signature(expr) == signature do
:lists.map(fn {:spec, expr, caller} ->
if Kernel.Typespec.spec_to_signature(expr) == signature do
Kernel.Typespec.define_spec(:callback, expr, caller)
true
end
end, specs)
found != []
:lists.any(& &1 == true, found)
end
## Helpers
+3 -2
View File
@@ -2,7 +2,7 @@ defmodule Range do
@moduledoc """
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
is via the `../2` macro, auto-imported from Kernel:
@@ -18,7 +18,8 @@ defmodule Range do
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 """
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:
iex> Record.record? {User, "jose", 27}
iex> Record.is_record {User, "john", 27}
true
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
records while `extract/2` can be used to extract records from Erlang
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 """
@@ -49,12 +63,12 @@ defmodule Record do
## Examples
iex> record = {User, "jose", 27}
iex> Record.record?(record, User)
iex> record = {User, "john", 27}
iex> Record.is_record(record, User)
true
"""
defmacro record?(data, kind) do
defmacro is_record(data, kind) do
case Macro.Env.in_guard?(__CALLER__) do
true ->
quote do
@@ -77,15 +91,15 @@ defmodule Record do
## Examples
iex> record = {User, "jose", 27}
iex> Record.record?(record)
iex> record = {User, "john", 27}
iex> Record.is_record(record)
true
iex> tuple = {}
iex> Record.record?(tuple)
iex> Record.is_record(tuple)
false
"""
defmacro record?(data) do
defmacro is_record(data) do
case Macro.Env.in_guard?(__CALLER__) do
true ->
quote do
@@ -111,26 +125,31 @@ defmodule Record do
## Examples
defmodule User do
Record.defrecord :user, [name: "José", age: "25"]
require Record
Record.defrecord :user, [name: "meg", age: "25"]
end
In the example above, a set of macros named `user` but with different
arities will be defined to manipulate the underlying record:
# To create records
user() #=> {:user, "José", 25}
user(age: 26) #=> {:user, "José", 26}
record = user() #=> {:user, "meg", 25}
record = user(age: 26) #=> {:user, "meg", 26}
# To get a field from the record
user(record, :name) #=> "José"
user(record, :name) #=> "meg"
# 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
the tuple (the tag). But it can be changed to something else:
defmodule User do
require Record
Record.defrecord :user, User, name: nil
end
@@ -141,7 +160,7 @@ defmodule Record do
defmacro defrecord(name, tag \\ nil, kv) do
quote bind_quoted: [name: name, tag: tag, kv: kv] do
tag = tag || name
fields = Macro.escape Record.__fields__(:defrecord, kv)
fields = Record.__fields__(:defrecord, kv)
defmacro(unquote(name)(args \\ [])) do
Record.__access__(unquote(tag), unquote(fields), args, __CALLER__)
@@ -159,7 +178,7 @@ defmodule Record do
defmacro defrecordp(name, tag \\ nil, kv) do
quote bind_quoted: [name: name, tag: tag, kv: kv] do
tag = tag || name
fields = Macro.escape Record.__fields__(:defrecordp, kv)
fields = Record.__fields__(:defrecordp, kv)
defmacrop(unquote(name)(args \\ [])) do
Record.__access__(unquote(tag), unquote(fields), args, __CALLER__)
@@ -175,9 +194,19 @@ defmodule Record do
@doc false
def __fields__(type, fields) do
:lists.map(fn
{ key, _ } = pair when is_atom(key) -> pair
key when is_atom(key) -> { key, nil }
other -> raise ArgumentError, "#{type} fields must be atoms, got: #{inspect other}"
{key, val} when is_atom(key) ->
try do
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
@@ -190,8 +219,15 @@ defmodule Record do
Keyword.keyword?(args) ->
create(atom, fields, args, caller)
true ->
msg = "expected arguments to be a compile time atom or keywords, got: #{Macro.to_string args}"
raise ArgumentError, msg
case Macro.expand(args, caller) do
{:{}, _, [^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
@@ -279,4 +315,21 @@ defmodule Record do
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([], _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
+4 -3
View File
@@ -45,10 +45,11 @@ defmodule Record.Extractor do
end
# Read a file and return its abstract syntax form that also
# includes record and other preprocessor modules. This is done
# by using Erlang's epp_dodger.
# includes record but with macros and other attributes expanded,
# such as `-include(...)` and `-include_lib(...)`. This is done
# by using Erlang's epp.
defp read_file(file) do
case :epp_dodger.quick_parse_file(file) do
case :epp.parse_file(file, []) do
{:ok, form} ->
form
other ->
+92 -38
View File
@@ -22,8 +22,9 @@ defmodule Regex do
The modifiers available when creating a Regex are:
* `unicode` (u) - enables unicode specific patterns like `\p`; it expects
valid unicode strings to be given on match
* `unicode` (u) - enables unicode specific patterns like `\p` and changes
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
@@ -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
defexception message: "regex could not be compiled"
@@ -104,7 +107,7 @@ defmodule Regex do
def compile(source, options \\ "")
def compile(source, options) when is_binary(options) do
case translate_options(options) do
case translate_options(options, []) do
{:error, rest} ->
{:error, {:invalid_option, rest}}
@@ -130,6 +133,7 @@ defmodule Regex do
Compiles the regular expression according to the given options.
Fails with `Regex.CompileError` if the regex cannot be compiled.
"""
@spec compile(binary, binary | [term]) :: t
def compile!(source, options \\ "") do
case compile(source, options) do
{:ok, regex} -> regex
@@ -149,6 +153,7 @@ defmodule Regex do
false
"""
@spec match?(t, String.t) :: boolean
def match?(%Regex{re_pattern: compiled}, string) when is_binary(string) do
:re.run(string, compiled, [{:capture, :none}]) == :match
end
@@ -165,6 +170,8 @@ defmodule Regex do
false
"""
@spec regex?(t) :: true
@spec regex?(any) :: false
def regex?(%Regex{}), do: true
def regex?(_), do: false
@@ -190,6 +197,7 @@ defmodule Regex do
[{2,2},{3,1}]
"""
@spec run(t, binary, [term]) :: nil | [binary] | [{integer, integer}]
def run(regex, string, options \\ [])
def run(%Regex{re_pattern: compiled}, string, options) when is_binary(string) do
@@ -220,6 +228,7 @@ defmodule Regex do
nil
"""
@spec named_captures(t, String.t, [term]) :: map | nil
def named_captures(regex, string, options \\ []) when is_binary(string) do
names = names(regex)
options = Keyword.put(options, :capture, names)
@@ -230,6 +239,7 @@ defmodule Regex do
@doc """
Returns the underlying `re_pattern` in the regular expression.
"""
@spec re_pattern(t) :: term
def re_pattern(%Regex{re_pattern: compiled}) do
compiled
end
@@ -243,6 +253,7 @@ defmodule Regex do
"foo"
"""
@spec source(t) :: String.t
def source(%Regex{source: source}) do
source
end
@@ -256,6 +267,7 @@ defmodule Regex do
"m"
"""
@spec opts(t) :: String.t
def opts(%Regex{opts: opts}) do
opts
end
@@ -269,6 +281,7 @@ defmodule Regex do
["foo"]
"""
@spec names(t) :: [String.t]
def names(%Regex{re_pattern: re_pattern}) do
{:namelist, names} = :re.inspect(re_pattern, :namelist)
names
@@ -298,6 +311,7 @@ defmodule Regex do
[]
"""
@spec scan(t, String.t, [term]) :: [[String.t]]
def scan(regex, string, options \\ [])
def scan(%Regex{re_pattern: compiled}, string, options) when is_binary(string) do
@@ -318,12 +332,17 @@ defmodule Regex do
## Options
* `: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
given pattern.
* `: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
iex> Regex.split(~r/-/, "a-b-c")
@@ -338,36 +357,70 @@ defmodule Regex do
iex> Regex.split(~r//, "abc")
["a", "b", "c", ""]
iex> Regex.split(~r//, "abc", trim: true)
["a", "b", "c"]
iex> Regex.split(~r/a(?<second>b)c/, "abc")
["", ""]
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{re_pattern: compiled}, string, options) when is_binary(string) do
parts = Keyword.get(options, :parts, :infinity)
opts = [return: :binary, parts: zero_to_infinity(parts)]
splits = :re.split(string, compiled, opts)
def split(%Regex{}, "", _opts), do: [""]
if Keyword.get(options, :trim, false) do
for split <- splits, split != "", do: split
def split(%Regex{re_pattern: compiled}, string, opts) when is_binary(string) do
on = Keyword.get(opts, :on, :first)
case :re.run(string, compiled, [:global, capture: on]) do
{: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
defp parts_to_index(:infinity), do: 0
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
splits
<<_::binary-size(offset), part::binary-size(keep), _::binary>> = string
[part|do_split([h|t], string, new_offset, counter - 1, trim)]
end
end
defp zero_to_infinity(0), do: :infinity
defp zero_to_infinity(n), do: n
@doc ~S"""
Receives a regex, a binary and a replacement, returns a new
binary where the all matches are replaced by replacement.
The replacement can be either a string or a function. The string
is used as a replacement for every match and it allows specific
captures to be accessed via `\N`, where `N` is the capture. In
case `\0` is used, the whole match is inserted.
captures to be accessed via `\N` or `\g{N}`, where `N` is the
capture. In case `\0` is used, the whole match is inserted.
When the replacement is a function, the function may have arity
N where each argument maps to a capture, with the first argument
@@ -397,6 +450,7 @@ defmodule Regex do
"[b][d]"
"""
@spec replace(t, String.t, String.t | (... -> String.t), [term]) :: String.t
def replace(regex, string, replacement, options \\ [])
def replace(regex, string, replacement, options) when is_binary(replacement) do
@@ -425,6 +479,11 @@ defmodule Regex do
defp precompile_replacement(""),
do: []
defp precompile_replacement(<<?\\, ?g, ?{, rest :: binary>>) when byte_size(rest) > 0 do
{ns, <<?}, rest :: binary>>} = pick_int(rest)
[List.to_integer(ns) | precompile_replacement(rest)]
end
defp precompile_replacement(<<?\\, x, rest :: binary>>) when x < ?0 or x > ?9 do
case precompile_replacement(rest) do
[head | t] when is_binary(head) ->
@@ -471,12 +530,12 @@ defmodule Regex do
defp apply_list(whole, string, pos, replacement, [[{mpos, _} | _] | _] = list) when mpos > pos do
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)]
end
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_list(whole, rest, pos + length, replacement, tail)]
end
@@ -496,7 +555,7 @@ defmodule Regex do
cond do
is_binary(part) ->
part
part > tuple_size(indexes) ->
part >= tuple_size(indexes) ->
""
true ->
get_index(string, elem(indexes, part))
@@ -509,7 +568,7 @@ defmodule Regex do
end
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
end
@@ -559,18 +618,13 @@ defmodule Regex do
# Private Helpers
defp translate_options(<<?g, t :: binary>>) do
IO.write :stderr, "The /g flag for regular expressions is no longer needed\n#{Exception.format_stacktrace}"
translate_options(t)
end
defp translate_options(<<?u, t :: binary>>), do: [:unicode|translate_options(t)]
defp translate_options(<<?i, t :: binary>>), do: [:caseless|translate_options(t)]
defp translate_options(<<?x, t :: binary>>), do: [:extended|translate_options(t)]
defp translate_options(<<?f, t :: binary>>), do: [:firstline|translate_options(t)]
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}
defp translate_options(<<?u, t :: binary>>, acc), do: translate_options(t, [:unicode, :ucp|acc])
defp translate_options(<<?i, t :: binary>>, acc), do: translate_options(t, [:caseless|acc])
defp translate_options(<<?x, t :: binary>>, acc), do: translate_options(t, [:extended|acc])
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(<<?s, t :: binary>>, acc), do: translate_options(t, [:dotall, {:newline, :anycrlf}|acc])
defp translate_options(<<?m, t :: binary>>, acc), do: translate_options(t, [:multiline|acc])
defp translate_options(<<>>, acc), do: acc
defp translate_options(rest, _acc), do: {:error, rest}
end
+1 -1
View File
@@ -198,7 +198,7 @@ defmodule Set do
if target1 == target2 do
target1.intersection(set1, set2)
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)}
end) |> elem(1)
end
+169 -48
View File
@@ -89,12 +89,14 @@ defmodule Stream do
returns an anonymous function may return a struct in future releases.
"""
@doc false
defstruct enum: nil, funs: [], accs: [], done: nil
@type acc :: any
@type element :: any
@type index :: non_neg_integer
@type default :: any
@type t :: %__MODULE__{}
# Require Stream.Reducers and its callbacks
require Stream.Reducers, as: R
@@ -154,19 +156,23 @@ defmodule Stream do
@spec chunk(Enumerable.t, non_neg_integer, non_neg_integer, Enumerable.t | nil) :: Enumerable.t
def chunk(enum, n, step, pad \\ nil) when n > 0 and step > 0 do
limit = :erlang.max(n, step)
if is_nil(pad) do
lazy enum, {[], 0}, fn(f1) -> R.chunk(n, step, limit, f1) end
else
lazy enum, {[], 0},
fn(f1) -> R.chunk(n, step, limit, f1) end,
fn(f1) -> &do_chunk(&1, n, pad, f1) end
&do_chunk(&1, n, pad, &2)
end
end
defp do_chunk(acc(h, {buffer, count} = old, t) = acc, n, pad, f1) do
if nil?(pad) || count == 0 do
defp do_chunk(acc(_, {_, 0}, _) = acc, _, _, _) do
{:cont, acc}
else
end
defp do_chunk(acc(h, {buffer, count} = old, t), n, pad, f1) do
buffer = :lists.reverse(buffer) ++ Enum.take(pad, n - count)
cont_with_acc(f1, buffer, h, old, t)
end
end
@doc """
Chunks the `enum` by buffering elements for which `fun` returns
@@ -184,7 +190,7 @@ defmodule Stream do
def chunk_by(enum, fun) do
lazy enum, nil,
fn(f1) -> R.chunk_by(fun, f1) end,
fn(f1) -> &do_chunk_by(&1, f1) end
&do_chunk_by(&1, &2)
end
defp do_chunk_by(acc(_, nil, _) = acc, _f1) do
@@ -336,6 +342,25 @@ defmodule Stream do
lazy enum, fn(f1) -> R.filter_map(filter, mapper, f1) 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 """
Injects the stream values into the given collectable as a side-effect.
@@ -427,7 +452,7 @@ defmodule Stream do
"""
@spec run(Enumerable.t) :: :ok
def run(stream) do
Enumerable.reduce(stream, {:cont, nil}, fn(_, _) -> {:cont, nil} end)
_ = Enumerable.reduce(stream, {:cont, nil}, fn(_, _) -> {:cont, nil} end)
:ok
end
@@ -493,11 +518,11 @@ defmodule Stream do
@spec take(Enumerable.t, non_neg_integer) :: Enumerable.t
def take(_enum, 0), do: %Stream{enum: []}
def take(enum, n) when n > 0 do
def take(enum, n) when n > 0 and is_integer(n) do
lazy enum, n, fn(f1) -> R.take(f1) end
end
def take(enum, n) when n < 0 do
def take(enum, n) when n < 0 and is_integer(n) do
&do_take(enum, abs(n), &1, &2)
end
@@ -561,6 +586,22 @@ defmodule Stream do
lazy enum, fn(f1) -> R.take_while(fun, f1) 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 """
Transforms an existing stream.
@@ -575,7 +616,7 @@ defmodule Stream do
## Examples
`Stream.transform/3` is a useful as it can be used as basis to implement
`Stream.transform/3` is useful as it can be used as the basis to implement
many of the functions defined in this module. For example, we can implement
`Stream.take(enum, n)` as follows:
@@ -616,12 +657,14 @@ defmodule Stream do
{[], user_acc} ->
do_transform(user_acc, user, fun, next_acc, next, inner_acc, inner)
{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} ->
next.({:halt, next_acc})
{:halted, elem(inner_acc, 1)}
{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
{reason, _} ->
{reason, elem(inner_acc, 1)}
@@ -633,8 +676,9 @@ defmodule Stream do
reduce.(inner_acc)
catch
kind, reason ->
stacktrace = System.stacktrace
next.({:halt, next_acc})
:erlang.raise(kind, reason, :erlang.get_stacktrace)
:erlang.raise(kind, reason, stacktrace)
else
{:done, acc} ->
do_transform(user_acc, user, fun, next_acc, next, {:cont, acc}, inner)
@@ -646,29 +690,33 @@ defmodule Stream do
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
reduce.(inner_acc)
reduce.({op, [:outer|inner_acc]})
catch
{:stream_transform, h} ->
next.({:halt, next_acc})
{:halted, h}
kind, reason ->
stacktrace = System.stacktrace
next.({:halt, next_acc})
:erlang.raise(kind, reason, stacktrace)
else
{_, acc} ->
# Only take into account outer halts when the op is not halt itself.
# Otherwise, we were the ones wishing to halt, so we should just stop.
{:halted, [:outer|acc]} when op != :halt ->
do_transform(user_acc, user, fun, next_acc, next, {:cont, acc}, inner)
{:suspended, acc, c} ->
{:suspended, acc, &do_other_transform(user_acc, user, fun, next_acc, next, &1, inner, c)}
{:halted, [_|acc]} ->
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
defp do_transform_each(x, acc, f) do
defp do_transform_each(x, [:outer|acc], f) do
case f.(x, acc) do
{:halt, h} -> throw({:stream_transform, h})
{_, _} = o -> o
{:halt, res} -> {:halt, [:inner|res]}
{op, res} -> {op, [:outer|res]}
end
end
@@ -840,6 +888,15 @@ defmodule Stream do
"""
@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
fn acc, fun ->
inner = &do_cycle_each(&1, &2, fun)
@@ -927,14 +984,15 @@ defmodule Stream do
@doc """
Emits a sequence of values for the given resource.
Similar to `unfold/2` but the initial value is computed lazily via
`start_fun` and executes an `after_fun` at the end of enumeration
(both in cases of success and failure).
Similar to `transform/2` but the initial accumulated value is
computed lazily via `start_fun` and executes an `after_fun` at
the end of enumeration (both in cases of success and failure).
Successive values are generated by calling `next_fun` with the
previous accumulator (the initial value being the result returned
by `start_fun`) and it must return a tuple with the current and
next accumulator. The enumeration finishes if it returns `nil`.
by `start_fun`) and it must return a tuple containing a list
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
resources.
@@ -944,8 +1002,8 @@ defmodule Stream do
Stream.resource(fn -> File.open!("sample") end,
fn file ->
case IO.read(file, :line) do
data when is_binary(data) -> {data, file}
_ -> nil
data when is_binary(data) -> {[data], file}
_ -> {:halt, file}
end
end,
fn file -> File.close(file) end)
@@ -967,9 +1025,11 @@ defmodule Stream do
defp do_resource(next_acc, next_fun, {:cont, acc}, fun, after_fun) do
try do
# Optimize the most common cases
case next_fun.(next_acc) do
nil -> nil
{v, next_acc} -> {fun.(v, acc), next_acc}
{[], next_acc} -> {:opt, {:cont, acc}, next_acc}
{[v], next_acc} -> {:opt, fun.(v, acc), next_acc}
{_, _} = other -> other
end
catch
kind, reason ->
@@ -977,11 +1037,62 @@ defmodule Stream do
after_fun.(next_acc)
:erlang.raise(kind, reason, stacktrace)
else
nil ->
after_fun.(next_acc)
{:done, acc}
{acc, next_acc} ->
{:opt, acc, next_acc} ->
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
@@ -1022,12 +1133,12 @@ defmodule Stream do
@compile {:inline, lazy: 2, lazy: 3, lazy: 4}
defp lazy(%Stream{funs: funs} = lazy, fun),
defp lazy(%Stream{done: nil, funs: funs} = lazy, fun),
do: %{lazy | funs: [fun|funs] }
defp lazy(enum, fun),
do: %Stream{enum: enum, funs: [fun]}
defp lazy(%Stream{funs: funs, accs: accs} = lazy, acc, fun),
defp lazy(%Stream{done: nil, funs: funs, accs: accs} = lazy, acc, fun),
do: %{lazy | funs: [fun|funs], accs: [acc|accs] }
defp lazy(enum, acc, fun),
do: %Stream{enum: enum, funs: [fun], accs: [acc]}
@@ -1066,19 +1177,29 @@ defimpl Enumerable, for: Stream do
case reduce.({command, [acc|accs]}) do
{:suspended, [acc|accs], continuation} ->
{:suspended, acc, &do_each(continuation, done, accs, &1)}
{:halted, [acc|_]} ->
{:halted, acc}
{:done, [acc|_] = accs} ->
case done do
nil ->
{:done, acc}
{done, fun} ->
case done.(fun).(accs) do
{:cont, [acc|_]} -> {:done, acc}
{:halted, accs} ->
do_done {:halted, accs}, done
{:done, accs} ->
do_done {:done, accs}, done
end
end
defp do_done({reason, [acc|_]}, nil), do: {reason, acc}
defp do_done({reason, [acc|t]}, {done, fun}) do
[h|_] = Enum.reverse(t)
case done.([acc, h], fun) do
{:cont, [acc|_]} -> {reason, acc}
{:halt, [acc|_]} -> {:halted, acc}
{:suspend, [acc|_]} -> {:suspended, acc, &({:done, elem(&1, 1)})}
end
end
end
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
+9 -3
View File
@@ -132,10 +132,16 @@ defmodule Stream.Reducers do
defmacro take(f \\ nil) do
quote do
fn(entry, acc(h, n, t) = orig) ->
if n >= 1 do
cont_with_acc(unquote(f), entry, h, n-1, t)
else
case n do
0 ->
{:halt, orig}
1 ->
case cont_with_acc(unquote(f), entry, h, n-1, t) do
{:cont, acc} -> {:halt, acc}
reason -> reason
end
_ ->
cont_with_acc(unquote(f), entry, h, n-1, t)
end
end
end
+210 -114
View File
@@ -4,28 +4,12 @@ defmodule String do
@moduledoc ~S"""
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
As per the Unicode Standard, a codepoint is an Unicode
Character, which may be represented by one or more bytes.
For example, the character "é" is represented with two
The functions in this module act according to the Unicode
Standard, version 6.3.0.As per the standard, a codepoint is
an Unicode Character, which may be represented by one or more
bytes. For example, the character "é" is represented with two
bytes:
iex> byte_size("é")
@@ -66,34 +50,110 @@ defmodule String do
This current Elixir version implements Extended Grapheme Cluster
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
Although codepoints could be represented as integers, this
module represents all codepoints as strings. For example:
iex> String.codepoints("josé")
["j", "o", "s", "é"]
iex> String.codepoints("olá")
["o", "l", "á"]
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
106
iex> ?o
111
iex> ?é
233
iex> ?á
225
Or also via pattern matching:
iex> << eacute :: utf8 >> = "é"
iex> << eacute :: utf8 >> = "á"
iex> eacute
233
225
As we have seen above, codepoints can be inserted into
a string by their hexadecimal code:
"jos\x{0065}\x{0301}" #=>
"josé"
"ol\x{0061}\x{0301}" #=>
"olá"
## Self-synchronization
@@ -129,7 +189,7 @@ defmodule String do
@spec printable?(t) :: boolean
def printable?(<< h :: utf8, t :: binary >>)
when h in ?\040..?\176
when h in 0x20..0x7E
when h in 0xA0..0xD7FF
when h in 0xE000..0xFFFD
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, 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(binary, pattern, options) do
def split(string, pattern, options) do
if Regex.regex?(pattern) do
Regex.split(pattern, binary, options)
Regex.split(pattern, string, options)
else
splits =
case Keyword.get(options, :parts, :infinity) do
num when is_number(num) and num > 0 ->
split_parts(binary, pattern, num - 1)
_ ->
:binary.split(binary, pattern, [:global])
end
if Keyword.get(options, :trim, false) do
for split <- splits, split != "", do: split
parts = Keyword.get(options, :parts, :infinity)
trim = Keyword.get(options, :trim, false)
if parts == :infinity and trim == false do
:binary.split(string, pattern, [:global])
else
splits
split_parts(string, pattern, parts_to_index(parts), trim)
end
end
end
defp split_parts("", _pattern, _num), do: [""]
defp split_parts(binary, pattern, num), do: split_parts(binary, pattern, num, [])
defp split_parts("", _pattern, _num, parts), do: Enum.reverse([""|parts])
defp split_parts(binary, _pattern, 0, parts), do: Enum.reverse([binary|parts])
defp split_parts(binary, pattern, num, parts) do
case :binary.split(binary, pattern) do
[head] -> Enum.reverse([head|parts])
[head, rest] -> split_parts(rest, pattern, num - 1, [head|parts])
defp parts_to_index(:infinity), do: 0
defp parts_to_index(n) when is_integer(n) and n > 0, do: n
defp split_codepoints(binary, 1, _trim), do: [binary]
defp split_codepoints(<<h :: utf8, t :: binary>>, count, trim),
do: [<<h :: utf8>>|split_codepoints(t, count - 1, trim)]
defp split_codepoints(<<h, t :: binary>>, count, trim),
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
@@ -322,8 +395,8 @@ defmodule String do
iex> String.upcase("ab 123 xpto")
"AB 123 XPTO"
iex> String.upcase("josé")
"JOSÉ"
iex> String.upcase("olá")
"OLÁ"
"""
@spec upcase(t) :: t
@@ -340,8 +413,8 @@ defmodule String do
iex> String.downcase("AB 123 XPTO")
"ab 123 xpto"
iex> String.downcase("JOSÉ")
"josé"
iex> String.downcase("OLÁ")
"olá"
"""
@spec downcase(t) :: t
@@ -364,8 +437,8 @@ defmodule String do
iex> String.capitalize("fin")
"Fin"
iex> String.capitalize("josé")
"José"
iex> String.capitalize("olá")
"Olá"
"""
@spec capitalize(t) :: t
@@ -405,7 +478,7 @@ defmodule String do
# does not traverse the whole binary).
def rstrip(string, char) when char in 0..127 do
if :binary.last(string) == char do
do_rstrip(string, "", char)
rstrip(binary_part(string, 0, byte_size(string) - 1), char)
else
string
end
@@ -577,13 +650,14 @@ defmodule String do
iex> String.replace("a,b,c", ",", "-", global: false)
"a-b,c"
The pattern can also be a regex. In those cases, one can give `\N`
in the `replacement` string to access a specific capture in the regex:
The pattern can also be a regex. In those cases, one can give `\N` or
`\g{N}` in the `replacement` string to access a specific capture in the
regex:
iex> String.replace("a,b,c", ~r/,(.)/, ",\\1\\1")
"a,bb,cc"
Notice we had to escape the escape character `\`. By giving `&`,
Notice we had to escape the escape character `\`. By giving `\0`,
one can inject the whole matched pattern in the replacement string.
When strings are used as a pattern, a developer can also use the
@@ -599,8 +673,8 @@ defmodule String do
"a[,,]b[,,]c"
"""
@spec replace(t, t, t) :: t
@spec replace(t, t, t, Keyword.t) :: t
@spec replace(t, t | Regex.t, t) :: t
@spec replace(t, t | Regex.t, t, Keyword.t) :: t
def replace(subject, pattern, replacement, options \\ []) when is_binary(replacement) do
if Regex.regex?(pattern) do
@@ -672,8 +746,8 @@ defmodule String do
## Examples
iex> String.codepoints("josé")
["j", "o", "s", "é"]
iex> String.codepoints("olá")
["o", "l", "á"]
iex> String.codepoints("оптими зации")
["о","п","т","и","м","и"," ","з","а","ц","и","и"]
@@ -699,8 +773,8 @@ defmodule String do
## Examples
iex> String.next_codepoint("josé")
{"j", "osé"}
iex> String.next_codepoint("olá")
{"o", "lá"}
"""
@compile {:inline, next_codepoint: 1}
@@ -786,10 +860,10 @@ defmodule String do
## Examples
iex> String.chunk(<<?a, ?b, ?c, 0>>, :valid)
["abc\000"]
["abc\0"]
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)
["abc", <<0, 0x0ffff::utf8>>]
@@ -849,8 +923,8 @@ defmodule String do
## Examples
iex> String.next_grapheme("josé")
{"j", "osé"}
iex> String.next_grapheme("olá")
{"o", "lá"}
"""
@compile {:inline, next_grapheme: 1}
@@ -974,8 +1048,13 @@ defmodule String do
@doc """
Returns a substring starting at the offset given by the first, and
a length given by the second.
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
iex> String.slice("elixir", 1, 3)
@@ -1010,17 +1089,29 @@ defmodule String do
end
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
def slice(string, start, len) when start < 0 and len >= 0 do
real_start_pos = length(string) - abs(start)
case real_start_pos >= 0 do
true -> do_slice(next_grapheme(string), real_start_pos, real_start_pos + len - 1, 0, "")
start = length(string) + start
case start >= 0 do
true -> slice(string, start, len)
false -> ""
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 """
Returns a substring from the offset given by the start of the
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
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
iex> String.slice("elixir", 1..3)
@@ -1060,54 +1159,51 @@ defmodule String do
iex> String.slice("a", 1..1500)
""
iex> String.slice("a", 2..1500)
""
"""
@spec slice(t, Range.t) :: t
def slice(string, range)
def slice("", _.._), do: ""
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
def slice(string, first..last) when first >= 0 and last >= 0 do
do_slice(next_grapheme(string), first, last, 0, "")
end
def slice(string, first..last) do
total = length(string)
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, "")
if last >= first do
slice(string, first, last - first + 1)
else
""
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
defp do_slice({_, rest}, start_pos, last_pos, current_pos, acc) when current_pos < start_pos do
do_slice(next_grapheme(rest), start_pos, last_pos, current_pos + 1, acc)
defp do_acc_bytes({char, rest}, bytes, length) do
do_acc_bytes(next_grapheme(rest), [byte_size(char)|bytes], length + 1)
end
defp do_slice({char, rest}, start_pos, last_pos, current_pos, acc) when current_pos >= start_pos and current_pos < last_pos do
do_slice(next_grapheme(rest), start_pos, last_pos, current_pos + 1, acc <> char)
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
defp do_acc_bytes(nil, bytes, length) do
{bytes, length}
end
@doc """
@@ -1328,7 +1424,7 @@ defmodule String do
1023
"""
@spec to_integer(String.t, pos_integer) :: integer
@spec to_integer(String.t, 2..36) :: integer
def to_integer(string, base) do
:erlang.binary_to_integer(string, base)
end
+5 -5
View File
@@ -36,7 +36,7 @@ defmodule StringIO do
"""
@spec open(binary, Keyword.t) :: {:ok, pid}
def open(string, options \\ []) when is_binary(string) do
:gen_server.start_link(__MODULE__, {string, options}, [])
GenServer.start_link(__MODULE__, {string, options}, [])
end
@doc """
@@ -52,7 +52,7 @@ defmodule StringIO do
"""
@spec contents(pid) :: {binary, binary}
def contents(pid) when is_pid(pid) do
:gen_server.call(pid, :contents)
GenServer.call(pid, :contents)
end
@doc """
@@ -68,7 +68,7 @@ defmodule StringIO do
"""
@spec close(pid) :: {:ok, {binary, binary}}
def close(pid) when is_pid(pid) do
:gen_server.call(pid, :close)
GenServer.call(pid, :close)
end
## callbacks
@@ -199,7 +199,7 @@ defmodule StringIO do
end
defp do_get_chars(input, :latin1, n) do
<<chars :: [binary, size(n)], rest :: binary>> = input
<<chars :: binary-size(n), rest :: binary>> = input
{chars, rest}
end
@@ -209,7 +209,7 @@ defmodule StringIO do
{buf_count, split_pos} when buf_count < n or split_pos == :none ->
{input, ""}
{_buf_count, split_pos} ->
<<chars :: [binary, size(split_pos)], rest :: binary>> = input
<<chars :: binary-size(split_pos), rest :: binary>> = input
{chars, rest}
end
catch
+98 -17
View File
@@ -20,15 +20,15 @@ defmodule Supervisor do
defmodule Stack do
use GenServer
def start_link(state) do
GenServer.start_link(__MODULE__, state, [name: :sup_stack])
def start_link(state, opts) do
GenServer.start_link(__MODULE__, state, opts)
end
def handle_call(:pop, _from, [h|t]) do
{:reply, h, t}
end
def handle_cast({:push, h}, _from, t) do
def handle_cast({:push, h}, t) do
{:noreply, [h|t]}
end
end
@@ -38,16 +38,17 @@ defmodule Supervisor do
# Import helpers for defining supervisors
import Supervisor.Spec
# We are going to supervise the Stack server which will
# be started with a single argument [:hello]
# We are going to supervise the Stack server which
# will be started with a single argument [:hello]
# and the default name of :sup_stack.
children = [
worker(Stack, [[:hello]])
worker(Stack, [[:hello], [name: :sup_stack]])
]
# Start the supervisor with our one child
{:ok, pid} = Supervisor.start_link(children, strategy: :one_for_one)
Notice that when starting the GenServer, we have registered it
Notice that when starting the GenServer, we are registering it
with name `:sup_stack`, which allows us to call it directly and
get what is on the stack:
@@ -65,7 +66,7 @@ defmodule Supervisor do
Let's try it:
GenServer.call(:sup_stack, :pop)
=ERROR REPORT====
** (exit) exited in: GenServer.call(:sup_stack, :pop, 5000)
Luckily, since the server is being supervised by a supervisor, the
supervisor will automatically start a new one, with the default stack
@@ -132,10 +133,74 @@ defmodule Supervisor do
* `:simple_one_for_one` - similar to `:one_for_one` but suits better
when dynamically attaching children. This strategy requires the
supervisor specification to contain only one children. Many functions
supervisor specification to contain only one child. Many functions
in this module behave slightly differently when this strategy is
used.
## Simple one for one
The simple one for one supervisor is useful when you want to dynamically
start and stop supervisor children. For example, imagine you want to
dynamically create multiple stacks. We can do so by defining a simple one
for one supervisor:
# Import helpers for defining supervisors
import Supervisor.Spec
# This time, we don't pass any argument because
# the argument will be given when we start the child
children = [
worker(Stack, [], restart: :transient)
]
# Start the supervisor with our one child
{:ok, sup_pid} = Supervisor.start_link(children, strategy: :simple_one_for_one)
There are a couple differences here:
* The simple one for one specification can define only one child which
works as a template for when we call `start_child/2`
* We have define the child to have restart strategy of transient. This
means that, if the child process exits due to a `:normal`, `:shutdown`
or `{:shutdown, term}` reason, it won't be restarted. This is useful
as it allows our workers to politely shutdown and be removed from the
simple one for one supervisor, without being restarted. You can find
more information about restart strategies on `Supervisor.Spec`
With the supervisor defined, let's dynamically start stacks:
{:ok, pid} = Supervisor.start_child(sup_pid, [[:hello, :world], []])
GenServer.call(pid, :pop) #=> :hello
GenServer.call(pid, :pop) #=> :world
{:ok, pid} = Supervisor.start_child(sup_pid, [[:something, :else], []])
GenServer.call(pid, :pop) #=> :something
GenServer.call(pid, :pop) #=> :else
Supervisor.count_children(sup_pid)
#=> %{active: 2, specs: 1, supervisors: 0, workers: 2}
## Exit reasons
From the example above, you may have noticed that the transient restart
strategy for the worker does not restart the child in case it crashes with
reason `:normal`, `:shutdown` or `{:shutdown, term}`.
So one may ask: which exit reason should I choose when existing my worker?
There are three options:
* `:normal` - on such cases, the exit won't be logged, there is no restart
on transient mode and linked processes do not exit
* `:shutdown` or `{:shutdown, term}` - on such cases, the exit won't be
logged, there is no restart on transient mode and linked processes exit
with the same reason unless trapping exits
* any other term - on such cases, the exit will be logged, there are
restarts on transient mode and linked processes exit with the same reason
unless trapping exits
## Name Registration
A supervisor is bound to the same name registration rules as a `GenServer`.
@@ -268,7 +333,9 @@ defmodule Supervisor do
and child specification.
"""
@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 """
Terminates the given pid or child id.
@@ -290,7 +357,9 @@ defmodule Supervisor do
"""
@spec terminate_child(supervisor, pid | Supervisor.Spec.child_id) :: :ok | {:error, error}
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 """
Deletes the child specification identified by `child_id`.
@@ -306,7 +375,9 @@ defmodule Supervisor do
"""
@spec delete_child(supervisor, Supervisor.Spec.child_id) :: :ok | {:error, error}
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 """
Restarts a child process identified by `child_id`.
@@ -335,7 +406,9 @@ defmodule Supervisor do
@spec restart_child(supervisor, Supervisor.Spec.child_id) ::
{:ok, child} | {:ok, child, term} | {:error, error}
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 """
Returns a list with information about all children.
@@ -361,7 +434,9 @@ defmodule Supervisor do
child | :restarting,
Supervisor.Spec.worker,
Supervisor.Spec.modules}]
defdelegate which_children(supervisor), to: :supervisor
def which_children(supervisor) do
call(supervisor, :which_children)
end
@doc """
Returns a map containing count values for the supervisor.
@@ -381,9 +456,15 @@ defmodule Supervisor do
"""
@spec count_children(supervisor) ::
[specs: non_neg_integer, active: non_neg_integer,
supervisors: non_neg_integer, workers: non_neg_integer]
%{specs: non_neg_integer, active: non_neg_integer,
supervisors: non_neg_integer, workers: non_neg_integer}
def count_children(supervisor) do
: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
+3 -3
View File
@@ -145,12 +145,12 @@ defmodule Supervisor.Spec do
in the `Supervisor` module docs.
* `:max_restarts` - the maximum amount of restarts allowed in
a time frame. Defaults to 5.
a time frame. Defaults to 3.
* `:max_seconds` - the time frame in which `:max_restarts` applies.
Defaults to 5.
The `:strategy` option is required and by default maximum 5 restarts
The `:strategy` option is required and by default maximum 3 restarts
are allowed within 5 seconds. Please check the `Supervisor` module for
a complete description of the available strategies.
"""
@@ -162,7 +162,7 @@ defmodule Supervisor.Spec do
raise ArgumentError, "expected :strategy option to be given"
end
maxR = Keyword.get(options, :max_restarts, 5)
maxR = Keyword.get(options, :max_restarts, 3)
maxS = Keyword.get(options, :max_seconds, 5)
assert_unique_ids(Enum.map(children, &elem(&1, 0)))
+143 -41
View File
@@ -72,7 +72,7 @@ defmodule System do
"""
@spec argv() :: [String.t]
def argv do
:elixir_code_server.call :argv
:elixir_config.get(:argv)
end
@doc """
@@ -83,7 +83,7 @@ defmodule System do
"""
@spec argv([String.t]) :: :ok
def argv(args) do
:elixir_code_server.cast({:argv, args})
:elixir_config.put(:argv, args)
end
@doc """
@@ -94,11 +94,20 @@ defmodule System do
"""
def cwd do
case :file.get_cwd do
{:ok, base} -> IO.chardata_to_string(base)
{:ok, base} -> IO.chardata_to_string(fix_drive_letter(base))
_ -> nil
end
end
defp fix_drive_letter([l, ?:, ?/ | rest] = original) when l in ?A..?Z do
case :os.type() do
{:win32, _} -> [l+?a-?A, ?:, ?/ | rest]
_ -> original
end
end
defp fix_drive_letter(original), do: original
@doc """
Current working directory, exception on error.
@@ -202,59 +211,32 @@ defmodule System do
end
@doc """
Register a program exit handler function.
Registers a program exit handler function.
Registers a function that will be invoked
at the end of program execution. Useful for
invoking a hook in "script" mode.
Registers a function that will be invoked at the end of program execution.
Useful for invoking a hook in "script" mode.
The function must receive the exit status code
as an argument.
The handler always executes in a different process from the one it was
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
:elixir_code_server.cast {:at_exit, fun}
:elixir_config.update :at_exit, &[fun|&1]
end
@doc """
Execute a system command.
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.
Locates an executable on the system.
This function looks up an executable program given
its name using the environment variable PATH on Unix
and Windows. It also considers the proper executable
extension for each OS, so for Windows it will try to
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(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
case :os.find_executable(String.to_char_list(program)) do
false -> nil
@@ -387,4 +369,124 @@ defmodule System do
def halt(status) when is_binary(status) do
:erlang.halt(String.to_char_list(status))
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
+38 -4
View File
@@ -58,7 +58,7 @@ defmodule Task do
that dynamically supervise tasks:
{: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
long as the supervisor is registered locally or globally:
@@ -67,7 +67,7 @@ defmodule Task do
Task.Supervisor.start_link(name: :tasks_sup)
# 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:
@@ -77,7 +77,15 @@ defmodule Task do
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 """
@@ -93,6 +101,8 @@ defmodule Task do
"""
defstruct pid: nil, ref: nil
@type t :: %__MODULE__{}
@doc """
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})
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 """
Starts a task that can be awaited on.
@@ -150,7 +184,7 @@ defmodule Task do
end
@doc """
Awaits for a task reply.
Awaits a task reply.
A timeout, in milliseconds, can be given with default value
of `5000`. In case the task process dies, this function will
+10 -6
View File
@@ -1,6 +1,10 @@
defmodule Task.Supervised do
@moduledoc false
def start(info, fun) do
{:ok, :proc_lib.spawn(__MODULE__, :noreply, [info, fun])}
end
def start_link(info, fun) do
{:ok, :proc_lib.spawn_link(__MODULE__, :noreply, [info, fun])}
end
@@ -89,12 +93,12 @@ defmodule Task.Supervised do
{fun, args} = get_running(mfa)
:error_logger.format(
"** Task ~p terminating~n" <>
"** Started from ~p~n" <>
"** When function == ~p~n" <>
"** arguments == ~p~n" <>
"** Reason for termination == ~n" <>
"** ~p~n", [self, get_from(info), fun, args, reason])
'** Task ~p terminating~n' ++
'** Started from ~p~n' ++
'** When function == ~p~n' ++
'** arguments == ~p~n' ++
'** Reason for termination == ~n' ++
'** ~p~n', [self, get_from(info), fun, args, reason])
exit(reason)
end
+10 -1
View File
@@ -25,14 +25,23 @@ defmodule Task.Supervisor do
described under the `Name Registration` section in the `GenServer` module
docs;
* `:restart` - the restart strategy, may be `:temporary` (the default),
`:transient` or `:permanent`. Check `Supervisor.Spec` for more info.
Defaults to temporary as most tasks can't be effectively restarted after
a crash;
* `:shutdown` - `:brutal_kill` if the tasks must be killed directly on shutdown
or an integer indicating the timeout value, defaults to 5000 milliseconds;
* `:max_restarts` and `:max_seconds` - as specified in `Supervisor.Spec.supervise/2`;
"""
@spec start_link(Supervisor.options) :: Supervisor.on_start
def start_link(opts \\ []) do
import Supervisor.Spec
{restart, opts} = Keyword.pop(opts, :restart, :temporary)
{shutdown, opts} = Keyword.pop(opts, :shutdown, 5000)
children = [worker(Task.Supervised, [], restart: :temporary, shutdown: shutdown)]
children = [worker(Task.Supervised, [], restart: restart, shutdown: shutdown)]
Supervisor.start_link(children, [strategy: :simple_one_for_one] ++ opts)
end
+71 -42
View File
@@ -7,20 +7,21 @@ defmodule URI do
fragment: nil, authority: nil,
userinfo: nil, host: nil, port: nil
@type t :: %__MODULE__{}
import Bitwise
@ports %{
"ftp" => 21,
"http" => 80,
"https" => 443,
"ldap" => 389,
"sftp" => 22,
"tftp" => 69,
}
@schemes [
"ftp",
"http",
"https",
"ldap",
"sftp",
"tftp",
]
Enum.each @ports, fn {scheme, port} ->
Enum.each @schemes, fn scheme ->
def normalize_scheme(unquote(scheme)), do: unquote(scheme)
def default_port(unquote(scheme)), do: unquote(port)
end
@doc """
@@ -45,8 +46,7 @@ defmodule URI do
"""
def default_port(scheme) when is_binary(scheme) do
{:ok, dict} = Application.fetch_env(:elixir, :uri)
Map.get(dict, scheme)
:elixir_config.get({:uri, scheme})
end
@doc """
@@ -57,8 +57,7 @@ defmodule URI do
new URIs.
"""
def default_port(scheme, port) when is_binary(scheme) and port > 0 do
{:ok, dict} = Application.fetch_env(:elixir, :uri)
Application.put_env(:elixir, :uri, Map.put(dict, scheme, port), persistent: true)
:elixir_config.put({:uri, scheme}, port)
end
@doc """
@@ -66,7 +65,7 @@ defmodule URI do
Takes an enumerable (containing a sequence of two-item tuples)
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`
protocol, except lists which are explicitly forbidden.
@@ -96,7 +95,10 @@ defmodule URI do
"""
def decode_query(q, dict \\ %{}) when is_binary(q) do
Enum.reduce query_decoder(q), dict, fn({k, v}, acc) -> Dict.put(acc, k, v) end
case do_decode_query(q) do
nil -> dict
{{k, v}, q} -> decode_query(q, Dict.put(dict, k, v))
end
end
@doc """
@@ -105,19 +107,19 @@ defmodule URI do
## Examples
iex> URI.query_decoder("foo=1&bar=2") |> Enum.map &(&1)
iex> URI.query_decoder("foo=1&bar=2") |> Enum.map(&(&1))
[{"foo", "1"}, {"bar", "2"}]
"""
def query_decoder(q) when is_binary(q) do
Stream.unfold(q, &do_decoder/1)
Stream.unfold(q, &do_decode_query/1)
end
defp do_decoder("") do
defp do_decode_query("") do
nil
end
defp do_decoder(q) do
defp do_decode_query(q) do
{first, next} =
case :binary.split(q, "&") do
[first, rest] -> {first, rest}
@@ -232,7 +234,7 @@ defmodule URI do
"""
def decode(uri) do
unpercent(uri)
unpercent(uri, "", false)
catch
:malformed_uri ->
raise ArgumentError, "malformed URI #{inspect uri}"
@@ -248,23 +250,25 @@ defmodule URI do
"""
def decode_www_form(str) do
String.split(str, "+") |> Enum.map_join(" ", &unpercent/1)
unpercent(str, "", true)
catch
:malformed_uri ->
raise ArgumentError, "malformed URI #{inspect str}"
end
defp unpercent(<<?%, hex_1, hex_2, tail :: binary>>) do
<<bsl(hex_to_dec(hex_1), 4) + hex_to_dec(hex_2)>> <> unpercent(tail)
end
defp unpercent(<<?%, _>>), do: throw(:malformed_uri)
defp unpercent(<<?%>>), do: throw(:malformed_uri)
defp unpercent(<<head, tail :: binary>>) do
<<head>> <> unpercent(tail)
defp unpercent(<<?+, tail::binary>>, acc, spaces = true) do
unpercent(tail, <<acc::binary, ?\s>>, spaces)
end
defp unpercent(<<>>), do: <<>>
defp unpercent(<<?%, hex_1, hex_2, tail::binary>>, acc, spaces) do
unpercent(tail, <<acc::binary, bsl(hex_to_dec(hex_1), 4) + hex_to_dec(hex_2)>>, spaces)
end
defp unpercent(<<?%, _::binary>>, _acc, _spaces), do: throw(:malformed_uri)
defp unpercent(<<head, tail::binary>>, acc, spaces) do
unpercent(tail, <<acc::binary, head>>, spaces)
end
defp unpercent(<<>>, acc, _spaces), do: acc
defp hex_to_dec(n) when n in ?A..?F, do: n - ?A + 10
defp hex_to_dec(n) when n in ?a..?f, do: n - ?a + 10
@@ -272,12 +276,17 @@ defmodule URI do
defp hex_to_dec(_n), do: throw(:malformed_uri)
@doc """
Parses a URI into components.
Parses a well-formed URI reference into its components.
URIs have portions that are handled specially for the particular
scheme of the URI. For example, http and https have different
default ports. Such values can be accessed and registered via
`URI.default_port/1` and `URI.default_port/2`.
Note this function expects a well-formed URI and does not perform
any validation. See the examples section below of how `URI.parse/1`
can be used to parse a wide range of relative URIs.
This function uses the parsing regular expression as defined
in the Appendix B of RFC3986.
When a URI is given without a port, the values registered via
`URI.default_port/1` and `URI.default_port/2` are used.
## Examples
@@ -286,12 +295,24 @@ defmodule URI do
authority: "elixir-lang.org", userinfo: nil,
host: "elixir-lang.org", port: 80}
iex> URI.parse("//elixir-lang.org/")
%URI{authority: "elixir-lang.org", fragment: nil, host: "elixir-lang.org",
path: "/", port: nil, query: nil, scheme: nil, userinfo: nil}
iex> URI.parse("/foo/bar")
%URI{authority: nil, fragment: nil, host: nil, path: "/foo/bar",
port: nil, query: nil, scheme: nil, userinfo: nil}
iex> URI.parse("foo/bar")
%URI{authority: nil, fragment: nil, host: nil, path: "foo/bar",
port: nil, query: nil, scheme: nil, userinfo: nil}
"""
def parse(%URI{} = uri), do: uri
def parse(s) when is_binary(s) do
# From http://tools.ietf.org/html/rfc3986#appendix-B
regex = ~r/^(([^:\/?#]+):)?(\/\/([^\/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?/
regex = ~r/^(([a-z][a-z0-9\+\-\.]*):)?(\/\/([^\/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?/i
parts = nillify(Regex.run(regex, s))
destructure [_, _, scheme, _, authority, path, _, query, _, fragment], parts
@@ -307,7 +328,7 @@ defmodule URI do
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)
end
@@ -347,12 +368,20 @@ defimpl String.Chars, for: URI do
if uri.port == port, do: uri = %{uri | port: nil}
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 = ""
if uri.scheme, do: result = result <> uri.scheme <> "://"
if uri.userinfo, do: result = result <> uri.userinfo <> "@"
if uri.host, do: result = result <> uri.host
if uri.port, do: result = result <> ":" <> Integer.to_string(uri.port)
if uri.scheme, do: result = result <> uri.scheme <> ":"
if authority, do: result = result <> "//" <> authority
if uri.path, do: result = result <> uri.path
if uri.query, do: result = result <> "?" <> uri.query
if uri.fragment, do: result = result <> "#" <> uri.fragment
+47 -8
View File
@@ -51,6 +51,19 @@ defmodule Version do
"~> 2.0.0"
`~>` will never include pre-release versions of its upper bound.
It can also be used to set an upper bound on only the major
version part. See the table below for `~>` requirements and
their corresponding translation.
`~>` | Translation
:------------- | :---------------------
`~> 2.0.0` | `>= 2.0.0 and < 2.1.0`
`~> 2.1.2` | `>= 2.1.2 and < 2.2.0`
`~> 2.1.3-dev` | `>= 2.1.3-dev and < 2.2.0`
`~> 2.0` | `>= 2.0.0 and < 3.0.0`
`~> 2.1` | `>= 2.1.0 and < 3.0.0`
"""
import Kernel, except: [match?: 2]
@@ -58,14 +71,26 @@ defmodule Version do
@type version :: String.t | t
@type requirement :: String.t | Version.Requirement.t
@type matchable :: {major :: String.t | non_neg_integer,
minor :: non_neg_integer | nil,
patch :: non_neg_integer | nil,
pre :: [String.t]}
@type major :: String.t | non_neg_integer
@type minor :: non_neg_integer | nil
@type patch :: non_neg_integer | nil
@type pre :: [String.t | non_neg_integer]
@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
@moduledoc false
defstruct [:source, :matchspec]
@type t :: %__MODULE__{}
end
defmodule InvalidRequirementError do
@@ -157,7 +182,8 @@ defmodule Version do
## 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>
iex> Version.parse("2.0-alpha1")
@@ -181,7 +207,8 @@ defmodule Version do
## 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>
iex> Version.parse_requirement("== == 2.0.1")
@@ -310,7 +337,7 @@ defmodule Version do
patch = nillify(patch)
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
else
major = String.to_integer(major)
@@ -492,10 +519,22 @@ end
defimpl String.Chars, for: Version do
def to_string(version) do
pre = unless Enum.empty?(pre = version.pre), do: "-#{pre}"
pre = pre(version.pre)
build = if build = version.build, do: "+#{build}"
"#{version.major}.#{version.minor}.#{version.patch}#{pre}#{build}"
end
defp pre([]) do
""
end
defp pre(pre) do
"-" <>
Enum.map_join(pre, ".", fn
int when is_integer(int) -> Integer.to_string(int)
string when is_binary(string) -> string
end)
end
end
defimpl Inspect, for: Version do
+1 -4
View File
@@ -4,9 +4,6 @@ defmodule Elixir.Mixfile do
def project do
[app: :elixir,
version: System.version,
build_per_environment: false,
escript_embed_elixir: false,
escript_main_module: :elixir,
escript_emu_args: "%%! -noshell\n"]
build_per_environment: false]
end
end
+50 -16
View File
@@ -2,7 +2,7 @@
%% private to the Elixir compiler and reserved to be used by Elixir only.
-module(elixir).
-behaviour(application).
-export([main/1, start_cli/0,
-export([start_cli/0,
string_to_quoted/4, 'string_to_quoted!'/4,
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]).
@@ -28,8 +28,13 @@ start(_Type, _Args) ->
error -> [binary]
end,
io:setopts(standard_io, Opts),
io:setopts(standard_error, [{unicode,true}]),
ok = io:setopts(standard_io, Opts),
ok = case io:setopts(standard_error, [{encoding,utf8}]) of
ok -> ok;
{error, _} -> io:setopts(standard_error, [{unicode,true}]) %% R17.3 and earlier
end,
case file:native_name_encoding() of
latin1 ->
io:format(standard_error,
@@ -40,24 +45,48 @@ start(_Type, _Args) ->
ok
end,
elixir_sup:start_link().
URIs = [{<<"ftp">>, 21},
{<<"sftp">>, 22},
{<<"tftp">>, 69},
{<<"http">>, 80},
{<<"https">>, 443},
{<<"ldap">>, 389}],
URIConfig = [{{uri, Scheme}, Port} || {Scheme, Port} <- URIs],
CompilerOpts = [{docs,true},{debug_info,true},{warnings_as_errors,false}],
Config = [{at_exit, []},
{compiler_options, orddict:from_list(CompilerOpts)}
| URIConfig],
Tab = elixir_config:new(Config),
case elixir_sup:start_link() of
{ok, Sup} ->
{ok, Sup, Tab};
{error, _Reason} = Error ->
elixir_config:delete(Tab),
Error
end.
stop(Tab) ->
elixir_config:delete(Tab).
stop(_S) ->
ok.
config_change(_Changed, _New, _Remove) ->
ok.
%% escript entry point
main(Args) ->
application:start(?MODULE),
'Elixir.Kernel.CLI':main(Args).
%% Boot and process given options. Invoked by Elixir's script.
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()).
%% EVAL HOOKS
@@ -153,14 +182,19 @@ eval_forms(Tree, Binding, Env, Scope) ->
{atom, _, Atom} ->
{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}
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
% 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() ->
Stacktrace = erlang:get_stacktrace(),
@@ -21,7 +21,7 @@
-record(elixir_quote, {
line=false,
keep=false,
file=nil,
context=nil,
vars_hygiene=true,
aliases_hygiene=true,
@@ -41,28 +41,29 @@
%% Used in tokenization and interpolation
%% Numbers
-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_octal(S), S >= $0 andalso S =< $7).
-define(is_leading_octal(S), S >= $0 andalso S =< $3).
-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_octal(S), (S >= $0 andalso S =< $7)).
-define(is_leading_octal(S), (S >= $0 andalso S =< $3)).
%% Digits and letters
-define(is_digit(S), S >= $0 andalso S =< $9).
-define(is_upcase(S), S >= $A andalso S =< $Z).
-define(is_downcase(S), S >= $a andalso S =< $z).
-define(is_digit(S), (S >= $0 andalso S =< $9)).
-define(is_upcase(S), (S >= $A andalso S =< $Z)).
-define(is_downcase(S), (S >= $a andalso S =< $z)).
%% Atoms
-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_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_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
(S == $[) orelse (S == $() orelse (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
(S == $[) orelse (S == $() orelse (S == ${) orelse (S == $|))).
%% Quotes
-define(is_quote(S), S == $" orelse S == $').
-define(is_quote(S), (S == $" orelse S == $')).
%% Spaces
-define(is_horizontal_space(S), (S == $\s) orelse (S == $\t)).
-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_horizontal_space(S), ((S == $\s) orelse (S == $\t))).
-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_invalid_space(S), (S == 16#A0)).
+92 -66
View File
@@ -35,70 +35,99 @@ expand_bitstr(Fun, [H|T], Acc, E) ->
%% 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, unpack_bit_info(Info, []), default, [], E).
expand_bit_info(Meta, [{Expr, ExprMeta, Args}|T], Size, Types, E) when is_atom(Expr) ->
ListArgs = if is_atom(Args) -> []; is_list(Args) -> Args end,
case expand_bit_type_or_size(Expr, ListArgs) of
type ->
{EArgs, EE} = elixir_exp:expand_args(ListArgs, E),
expand_bit_info(Meta, T, Size, [{Expr, [], EArgs}|Types], EE);
size ->
expand_bit_info(Meta, [{size, _, [_]=Args}|T], Size, Types, E) ->
case Size of
default -> ok;
_ -> elixir_errors:compile_error(Meta, ?m(E, file), "duplicated size definition in bitstring")
default ->
{[EArg], EE} = elixir_exp:expand_args(Args, E),
case EArg of
{Var, _, Context} when is_atom(Var) and is_atom(Context) ->
ok;
_ when is_integer(EArg) ->
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,
{EArgs, EE} = elixir_exp:expand_args(ListArgs, E),
expand_bit_info(Meta, T, {Expr, [], EArgs}, Types, EE);
none ->
handle_unknown_bit_info(Meta, {Expr, ExprMeta, ListArgs}, T, Size, Types, E)
expand_bit_info(Meta, T, {size, [], [EArg]}, Types, EE);
_ ->
elixir_errors:compile_error(Meta, ?m(E, file),
"duplicated size definition in bitstring")
end;
expand_bit_info(Meta, [Int|T], Size, Types, E) when is_integer(Int) ->
expand_bit_info(Meta, [{size, [], [Int]}|T], Size, Types, E);
expand_bit_info(Meta, [{Expr, ExprMeta, Args}|T], Size, Types, E) when is_atom(Expr) ->
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) ->
elixir_errors:compile_error(Meta, ?m(E, file),
"unknown bitstring specifier ~ts", ['Elixir.Kernel':inspect(Expr)]);
expand_bit_info(_Meta, [], Size, Types, _) ->
case Size of
expand_bit_info(Meta, [], Size, Types, _) ->
[H|T] = case Size of
default -> lists:reverse(Types);
_ -> [Size|lists:reverse(Types)]
end.
_ -> lists:reverse(Types, [Size])
end,
lists:foldl(fun(I, Acc) -> {'-', Meta, [Acc, I]} end, H, T).
expand_bit_type_or_size(binary, []) -> type;
expand_bit_type_or_size(integer, []) -> type;
expand_bit_type_or_size(float, []) -> type;
expand_bit_type_or_size(bitstring, []) -> type;
expand_bit_type_or_size(bytes, []) -> type;
expand_bit_type_or_size(bits, []) -> type;
expand_bit_type_or_size(utf8, []) -> type;
expand_bit_type_or_size(utf16, []) -> type;
expand_bit_type_or_size(utf32, []) -> type;
expand_bit_type_or_size(signed, []) -> type;
expand_bit_type_or_size(unsigned, []) -> type;
expand_bit_type_or_size(big, []) -> type;
expand_bit_type_or_size(little, []) -> type;
expand_bit_type_or_size(native, []) -> type;
expand_bit_type_or_size(unit, [_]) -> type;
expand_bit_type_or_size(size, [_]) -> size;
expand_bit_type_or_size(_, _) -> none.
expand_bit_type(binary, []) -> type;
expand_bit_type(integer, []) -> type;
expand_bit_type(float, []) -> type;
expand_bit_type(bitstring, []) -> type;
expand_bit_type(bytes, []) -> type;
expand_bit_type(bits, []) -> type;
expand_bit_type(utf8, []) -> type;
expand_bit_type(utf16, []) -> type;
expand_bit_type(utf32, []) -> type;
expand_bit_type(signed, []) -> type;
expand_bit_type(unsigned, []) -> type;
expand_bit_type(big, []) -> type;
expand_bit_type(little, []) -> type;
expand_bit_type(native, []) -> type;
expand_bit_type(unit, [_]) -> type;
expand_bit_type(_, _) -> none.
handle_unknown_bit_info(Meta, {_, ExprMeta, _} = Expr, T, Size, Types, E) ->
case 'Elixir.Macro':expand(Expr, elixir_env:linify({?line(ExprMeta), E})) of
validate_bit_type_args(Meta, unit, [Unit], E) when not is_integer(Unit) ->
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 ->
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)]);
Other ->
List = case is_list(Other) of true -> Other; false -> [Other] end,
expand_bit_info(Meta, List ++ T, Size, Types, E)
Info ->
expand_bit_info(Meta, unpack_bit_info(Info, []) ++ T, Size, Types, E)
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
has_size({bin, _, Elements}) ->
@@ -124,7 +153,7 @@ build_bitstr_each(_Fun, [], _Meta, S, Acc) ->
{Acc, S};
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, [H|T], Meta, S, Acc) ->
@@ -201,23 +230,20 @@ unit_size([], Guess) -> Guess.
%% Extra bitstring specifiers
extract_bit_info(Meta, [{size, _, [Arg]}|T], S) ->
case elixir_translator:translate(Arg, S) of
{{Kind, _, _} = Size, _} when Kind == integer; Kind == var ->
{Size, extract_bit_type(Meta, T, S)};
_ ->
elixir_errors:compile_error(Meta, S#elixir_scope.file,
"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_info({'-', _, [L, {size, _, [Size]}]}, S) ->
{extract_bit_size(Size, S), extract_bit_type(L, [])};
extract_bit_info({size, _, [Size]}, S) ->
{extract_bit_size(Size, S), []};
extract_bit_info(L, _S) ->
{default, extract_bit_type(L, [])}.
extract_bit_type(Meta, [{unit, _, [Arg]}|T], S) when is_integer(Arg) ->
[{unit, Arg}|extract_bit_type(Meta, T, S)];
extract_bit_type(Meta, [{unit, _, [Arg]}|_], S) ->
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(Meta, [{Other, _, []}|T], S) ->
[Other|extract_bit_type(Meta, T, S)];
extract_bit_type(_Meta, [], _S) ->
[].
extract_bit_size(Size, S) ->
{TSize, _} = elixir_translator:translate(Size, S),
TSize.
extract_bit_type({'-', _, [L, R]}, Acc) ->
extract_bit_type(L, extract_bit_type(R, Acc));
extract_bit_type({unit, _, [Arg]}, Acc) ->
[{unit, Arg}|Acc];
extract_bit_type({Other, _, []}, Acc) ->
[Other|Acc].
+50 -66
View File
@@ -6,13 +6,11 @@
-define(timeout, 30000).
-record(elixir_code_server, {
compilation_status=[],
argv=[],
loaded=[],
at_exit=[],
pool={[],0},
compiler_options=[{docs,true},{debug_info,true},{warnings_as_errors,false}],
erl_compiler_options=nil
paths={[],[]},
mod_pool={[],0},
mod_ets=dict:new(),
compilation_status=[]
}).
call(Args) ->
@@ -27,11 +25,22 @@ start_link() ->
gen_server:start_link({local, ?MODULE}, ?MODULE, ok, []).
init(ok) ->
code:ensure_loaded('Elixir.Macro.Env'),
code:ensure_loaded('Elixir.Module.LocalsTracker'),
code:ensure_loaded('Elixir.Kernel.LexicalTracker'),
%% We attempt to load those modules here so throughout
%% the codebase we can avoid code:is_loaded/1 checks.
_ = 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{}}.
handle_call({defmodule, Pid, Tuple}, _From, Config) ->
{Ref, New} = defmodule(Pid, Tuple, Config),
{reply, Ref, New};
handle_call({undefmodule, Ref}, _From, Config) ->
{reply, ok, undefmodule(Ref, Config)};
handle_call({acquire, Path}, From, Config) ->
Current = Config#elixir_code_server.loaded,
case orddict:find(Path, Current) of
@@ -48,58 +57,32 @@ handle_call({acquire, Path}, From, Config) ->
handle_call(loaded, _From, Config) ->
{reply, [F || {F, true} <- Config#elixir_code_server.loaded], Config};
handle_call(at_exit, _From, Config) ->
{reply, Config#elixir_code_server.at_exit, Config};
handle_call(flush_at_exit, _From, Config) ->
{reply, Config#elixir_code_server.at_exit, Config#elixir_code_server{at_exit=[]}};
handle_call(argv, _From, Config) ->
{reply, Config#elixir_code_server.argv, Config};
handle_call(compiler_options, _From, Config) ->
{reply, Config#elixir_code_server.compiler_options, Config};
handle_call({compilation_status, CompilerPid}, _From, Config) ->
CompilationStatusList = Config#elixir_code_server.compilation_status,
CompilationStatusListNew = orddict:erase(CompilerPid, CompilationStatusList),
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) ->
case Config#elixir_code_server.pool of
case Config#elixir_code_server.mod_pool of
{[H|T], Counter} ->
{reply, module_tuple(H), Config#elixir_code_server{pool={T,Counter}}};
{reply, module_tuple(H), Config#elixir_code_server{mod_pool={T,Counter}}};
{[], Counter} ->
{reply, module_tuple(Counter), Config#elixir_code_server{pool={[],Counter+1}}}
{reply, module_tuple(Counter), Config#elixir_code_server{mod_pool={[],Counter+1}}}
end;
handle_call(erl_compiler_options, _From, Config) ->
case Config#elixir_code_server.erl_compiler_options of
nil ->
Opts = erl_compiler_options(),
{reply, Opts, Config#elixir_code_server{erl_compiler_options=Opts}};
Opts ->
{reply, Opts, Config}
end;
handle_call(paths, _From, Config) ->
{reply, Config#elixir_code_server.paths, Config};
handle_call(Request, _From, Config) ->
{stop, {badcall, Request}, Config}.
handle_cast({at_exit, AtExit}, Config) ->
{noreply, Config#elixir_code_server{at_exit=[AtExit|Config#elixir_code_server.at_exit]}};
handle_cast({argv, Argv}, Config) ->
{noreply, Config#elixir_code_server{argv=Argv}};
handle_cast({compiler_options, Options}, Config) ->
Final = orddict:merge(fun(_,_,V) -> V end, Config#elixir_code_server.compiler_options, Options),
{noreply, Config#elixir_code_server{compiler_options=Final}};
handle_cast({register_warning, CompilerPid}, Config) ->
CompilationStatusCurrent = Config#elixir_code_server.compilation_status,
CompilationStatusNew = orddict:store(CompilerPid, error, CompilationStatusCurrent),
case orddict:find(warnings_as_errors, Config#elixir_code_server.compiler_options) of
CompilerOptions = elixir_config:get(compiler_options),
case orddict:find(warnings_as_errors, CompilerOptions) of
{ok, true} -> {noreply, Config#elixir_code_server{compilation_status=CompilationStatusNew}};
_ -> {noreply, Config}
end;
@@ -115,7 +98,7 @@ handle_cast({loaded, Path}, Config) ->
{ok, true} ->
{noreply, Config};
{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),
{noreply, Config#elixir_code_server{loaded=Done}};
error ->
@@ -128,13 +111,19 @@ handle_cast({unload_files, Files}, Config) ->
Unloaded = lists:foldl(fun(File, Acc) -> orddict:erase(File, Acc) end, Current, Files),
{noreply, Config#elixir_code_server{loaded=Unloaded}};
handle_cast({return_module_name, H}, #elixir_code_server{pool={T,Counter}} = Config) ->
{noreply, Config#elixir_code_server{pool={[H|T],Counter}}};
handle_cast({return_module_name, H}, #elixir_code_server{mod_pool={T,Counter}} = Config) ->
{noreply, Config#elixir_code_server{mod_pool={[H|T],Counter}}};
handle_cast({paths, PA, PZ}, #elixir_code_server{} = Config) ->
{noreply, Config#elixir_code_server{paths={PA,PZ}}};
handle_cast(Request, Config) ->
{stop, {badcast, Request}, Config}.
handle_info(_Request, Config) ->
handle_info({'DOWN', Ref, process, _Pid, _Reason}, Config) ->
{noreply, undefmodule(Ref, Config)};
handle_info(_Msg, Config) ->
{noreply, Config}.
terminate(_Reason, _Config) ->
@@ -146,22 +135,17 @@ code_change(_Old, Config, _Extra) ->
module_tuple(I) ->
{list_to_atom("elixir_compiler_" ++ integer_to_list(I)), I}.
erl_compiler_options() ->
Key = "ERL_COMPILER_OPTIONS",
case os:getenv(Key) of
false -> [];
Str when is_list(Str) ->
case erl_scan:string(Str) of
{ok,Tokens,_} ->
case erl_parse:parse_term(Tokens ++ [{dot, 1}]) of
{ok,List} when is_list(List) -> List;
{ok,Term} -> [Term];
{error,_Reason} ->
io:format("Ignoring bad term in ~ts\n", [Key]),
[]
end;
{error, {_,_,_Reason}, _} ->
io:format("Ignoring bad term in ~ts\n", [Key]),
[]
end
defmodule(Pid, Tuple, #elixir_code_server{mod_ets=ModEts} = Config) ->
ets:insert(elixir_modules, Tuple),
Ref = erlang:monitor(process, Pid),
Mod = erlang:element(1, Tuple),
{Ref, Config#elixir_code_server{mod_ets=dict:store(Ref, Mod, ModEts)}}.
undefmodule(Ref, #elixir_code_server{mod_ets=ModEts} = Config) ->
case dict:find(Ref, ModEts) of
{ok, Mod} ->
ets:delete(elixir_modules, Mod),
Config#elixir_code_server{mod_ets=dict:erase(Ref, ModEts)};
error ->
Config
end.
+77 -25
View File
@@ -1,12 +1,12 @@
-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]).
-include("elixir.hrl").
%% Public API
get_opt(Key) ->
Dict = elixir_code_server:call(compiler_options),
Dict = elixir_config:get(compiler_options),
case lists:keyfind(Key, 1, Dict) of
false -> false;
{Key, Value} -> Value
@@ -15,15 +15,19 @@ get_opt(Key) ->
%% Compilation entry points.
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, []),
quoted(Forms, File).
quoted(Forms, File, Dest).
quoted(Forms, File) when is_binary(File) ->
quoted(Forms, File, nil).
quoted(Forms, File, Dest) ->
Previous = get(elixir_compiled),
try
put(elixir_compiled, []),
elixir_lexical:run(File, fun
elixir_lexical:run(File, Dest, fun
(Pid) ->
Env = elixir:env_for_eval([{line,1},{file,File}]),
eval_forms(Forms, [], Env#{lexical_tracker := Pid})
@@ -34,14 +38,20 @@ quoted(Forms, File) when is_binary(File) ->
end.
file(Relative) when is_binary(Relative) ->
file(Relative, nil).
file(Relative, Dest) ->
File = filename:absname(Relative),
{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) ->
Lists = file(File),
[binary_to_path(X, Path) || X <- Lists],
Lists.
file_to_path(File, Dest) when is_binary(File), is_binary(Dest) ->
Comp = file(File, Dest),
Abs = filename:absname(Dest),
_ = [binary_to_path(X, Abs) || X <- Comp],
Comp.
%% Evaluation
@@ -68,9 +78,9 @@ code_loading_compilation(Forms, Vars, #{line := Line} = E) ->
%% Pass {native, false} to speed up bootstrap
%% process when native is set to true
AllOpts = elixir_code_server:call(erl_compiler_options),
AllOpts = options(),
FinalOpts = AllOpts -- [native, warn_missing_spec],
module(Form, ?m(E, file), FinalOpts, true, fun(_, Binary) ->
inner_module(Form, FinalOpts, true, E, fun(_, Binary) ->
%% If we have labeled locals, anonymous functions
%% were created and therefore we cannot ditch the
%% module
@@ -82,6 +92,36 @@ code_loading_compilation(Forms, Vars, #{line := Line} = E) ->
dispatch_loaded(Module, Fun, Args, Purgeable, I, EE)
end).
options() ->
case elixir_config:get(erl_compiler_options) of
nil ->
elixir_config:update(erl_compiler_options, fun options/1);
Opts ->
Opts
end.
options(nil) ->
Key = "ERL_COMPILER_OPTIONS",
case os:getenv(Key) of
false -> [];
Str when is_list(Str) ->
case erl_scan:string(Str) of
{ok,Tokens,_} ->
case erl_parse:parse_term(Tokens ++ [{dot, 1}]) of
{ok,List} when is_list(List) -> List;
{ok,Term} -> [Term];
{error,_Reason} ->
io:format("Ignoring bad term in ~ts\n", [Key]),
[]
end;
{error, {_,_,_Reason}, _} ->
io:format("Ignoring bad term in ~ts\n", [Key]),
[]
end
end;
options(Opts) ->
Opts.
dispatch_loaded(Module, Fun, Args, Purgeable, I, E) ->
Res = Module:Fun(Args),
code:delete(Module),
@@ -128,43 +168,55 @@ allows_fast_compilation(_) -> false.
%% Compile the module by forms based on the scope information
%% executes the callback in case of success. This automatically
%% handles errors and warnings. Used by this module and elixir_module.
module(Forms, File, Opts, Callback) ->
module(Forms, Opts, E, Callback) ->
Final =
case (get_opt(debug_info) == true) orelse
lists:member(debug_info, Opts) of
true -> [debug_info] ++ elixir_code_server:call(erl_compiler_options);
false -> elixir_code_server:call(erl_compiler_options)
true -> [debug_info] ++ options();
false -> options()
end,
module(Forms, File, Final, false, Callback).
inner_module(Forms, Final, false, E, Callback).
module(Forms, File, Options, Bootstrap, Callback) when
is_binary(File), is_list(Forms), is_list(Options), is_boolean(Bootstrap), is_function(Callback) ->
Listname = elixir_utils:characters_to_list(File),
inner_module(Forms, Options, Bootstrap, #{file := File} = E, Callback) when
is_list(Forms), is_list(Options), is_boolean(Bootstrap), is_function(Callback) ->
Source = elixir_utils:characters_to_list(File),
case compile:noenv_forms([no_auto_import()|Forms], [return,{source,Listname}|Options]) of
{ok, ModuleName, Binary, Warnings} ->
case compile:noenv_forms([no_auto_import()|Forms], [return,{source,Source}|Options]) of
{ok, Module, Binary, Warnings} ->
format_warnings(Bootstrap, Warnings),
code:load_binary(ModuleName, Listname, Binary),
Callback(ModuleName, Binary);
{module, Module} = code:load_binary(Module, beam_location(E), Binary),
Callback(Module, Binary);
{error, Errors, Warnings} ->
format_warnings(Bootstrap, Warnings),
format_errors(Errors)
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() ->
{attribute, 0, compile, no_auto_import}.
%% CORE HANDLING
core() ->
application:start(elixir),
elixir_code_server:cast({compiler_options, [{docs,false},{internal,true}]}),
{ok, _} = application:ensure_all_started(elixir),
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) ->
try
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])
catch
Kind:Reason ->
+65
View File
@@ -0,0 +1,65 @@
-module(elixir_config).
-compile({no_auto_import, [get/1]}).
-export([new/1, delete/1, put/2, get/1, update/2, get_and_put/2]).
-export([start_link/0, init/1, handle_call/3, handle_cast/2,
handle_info/2, code_change/3, terminate/2]).
-behaviour(gen_server).
%% public api
new(Opts) ->
Tab = ets:new(?MODULE, [named_table, public, {read_concurrency, true}]),
true = ets:insert_new(?MODULE, Opts),
Tab.
delete(?MODULE) ->
ets:delete(?MODULE).
put(Key, Value) ->
gen_server:call(?MODULE, {put, Key, Value}).
get(Key) ->
case ets:lookup(?MODULE, Key) of
[{_, Value}] -> Value;
[] -> nil
end.
update(Key, Fun) ->
gen_server:call(?MODULE, {update, Key, Fun}).
get_and_put(Key, Value) ->
gen_server:call(?MODULE, {get_and_put, Key, Value}).
start_link() ->
gen_server:start_link({local, ?MODULE}, ?MODULE, ?MODULE, []).
%% gen_server api
init(Tab) ->
%% Ets table must be writable
public = ets:info(Tab, protection),
{ok, Tab}.
handle_call({put, Key, Value}, _From, Tab) ->
ets:insert(Tab, {Key, Value}),
{reply, ok, Tab};
handle_call({update, Key, Fun}, _From, Tab) ->
Value = Fun(get(Key)),
ets:insert(Tab, {Key, Value}),
{reply, Value, Tab};
handle_call({get_and_put, Key, Value}, _From, Tab) ->
OldValue = get(Key),
ets:insert(Tab, {Key, Value}),
{reply, OldValue, Tab}.
handle_cast(Cast, Tab) ->
{stop, {bad_cast, Cast}, Tab}.
handle_info(_Msg, Tab) ->
{noreply, Tab}.
code_change(_OldVsn, Tab, _Extra) ->
{ok, Tab}.
terminate(_Reason, _Tab) ->
ok.
+2 -2
View File
@@ -3,8 +3,8 @@
handle_info/2, terminate/2, code_change/3, next/0]).
-behaviour(gen_server).
-define(timeout, 1000). %% 1 second
-define(limit, 4294967296). %% 2^32
-define(timeout, 30000). %% 30 seconds
-define(limit, 4294967295). %% 2^32 - 1
next() ->
gen_server:call(?MODULE, next, ?timeout).
+181 -128
View File
@@ -1,49 +1,35 @@
% Holds the logic responsible for function definitions (def(p) and defmacro(p)).
-module(elixir_def).
-export([table/1, clauses_table/1, setup/1,
cleanup/1, reset_last/1, lookup_definition/2,
delete_definition/2, store_definition/6, unwrap_definitions/1,
store_each/8, format_error/1]).
-export([setup/1, reset_last/1, lookup_definition/2,
delete_definition/2, store_definition/6, unwrap_definitions/2,
store_each/7, format_error/1]).
-include("elixir.hrl").
-define(attr, '__def_table').
-define(clauses_attr, '__clauses_table').
-define(last_def, {elixir, last_def}).
-define(attr, {elixir, def_table}).
-define(clauses_attr, {elixir, clauses_table}).
%% Table management functions. Called internally.
table(Module) ->
ets:lookup_element(Module, ?attr, 2).
clauses_table(Module) ->
ets:lookup_element(Module, ?clauses_attr, 2).
setup(Module) ->
ets:insert(Module, {?attr, ets:new(Module, [set, public])}),
ets:insert(Module, {?clauses_attr, ets:new(Module, [bag, public])}),
reset_last(Module),
ok.
cleanup(Module) ->
ets:delete(table(Module)),
ets:delete(clauses_table(Module)).
%% Reset the last item. Useful when evaling code.
reset_last(Module) ->
ets:insert(table(Module), {last, []}).
ets:insert(elixir_module:data_table(Module), {?last_def, []}).
%% Looks up a definition from the database.
lookup_definition(Module, Tuple) ->
case ets:lookup(table(Module), Tuple) of
case ets:lookup(elixir_module:defs_table(Module), Tuple) of
[Result] ->
CTable = clauses_table(Module),
CTable = elixir_module:clas_table(Module),
{Result, [Clause || {_, Clause} <- ets:lookup(CTable, Tuple)]};
_ ->
false
end.
delete_definition(Module, Tuple) ->
ets:delete(table(Module), Tuple),
ets:delete(clauses_table(Module), Tuple).
ets:delete(elixir_module:defs_table(Module), Tuple),
ets:delete(elixir_module:clas_table(Module), Tuple).
% Invoked by the wrap definition with the function abstract tree.
% Each function is then added to the function table.
@@ -55,7 +41,7 @@ store_definition(Line, Kind, CheckClauses, Call, Body, Pos) ->
{Name, Args} = case NameAndArgs of
{N, _, A} when is_atom(N), is_atom(A) -> {N, []};
{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,
%% 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),
%% Check if there is a file information in the definition.
%% If so, we assume this come from another source and we need
%% to linify taking into account keep line numbers.
{File, Key} = case lists:keyfind(file, 1, Meta) of
{file, Bin} when is_binary(Bin) -> {Bin, keep};
_ -> {nil, line}
%% If so, we assume this come from another source and
%% we need to linify taking into account keep line numbers.
{Location, Key} = case lists:keyfind(file, 1, Meta) of
{file, KeepFile} when is_binary(KeepFile) ->
case lists:keyfind(keep, 1, Meta) of
{keep, KeepLine} when is_integer(KeepLine) -> KeepLine;
_ -> KeepLine = 0
end,
{{KeepFile, KeepLine}, keep};
_ ->
{nil, line}
end,
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),
assert_no_aliases_name(Line, Name, Args, E),
assert_valid_name(Line, Kind, Name, Args, E),
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),
Tuple = {Name, Arity},
E = ER#{function := Tuple},
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),
DefaultsLength = length(Defaults),
elixir_locals:record_defaults(Tuple, Kind, Module, DefaultsLength),
File = ?m(E, file),
Table = table(Module),
CTable = clauses_table(Module),
compile_super(Module, Super, E),
check_previous_defaults(Table, Line, Name, Arity, Kind, DefaultsLength, E),
check_previous_defaults(Line, Module, Name, Arity, Kind, DefaultsLength, E),
store_each(CheckClauses, Kind, File, Location,
Table, CTable, DefaultsLength, Function),
[store_each(false, Kind, File, Location, Table, CTable, 0,
store_each(CheckClauses, Kind, File, Location, Module, DefaultsLength, Function),
[store_each(false, Kind, File, Location, Module, 0,
default_function_for(Kind, Name, Default)) || Default <- Defaults],
make_struct_available(Kind, Module, Name, Args),
@@ -115,31 +104,36 @@ run_on_definition_callbacks(Kind, Line, Module, Name, Args, Guards, Expr, E) ->
_ ->
Env = elixir_env:linify({Line, E}),
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.
make_struct_available(def, Module, '__struct__', []) ->
case erlang:get(elixir_compiler_pid) of
undefined -> ok;
Pid -> Pid ! {struct_available, Module}
Pid ->
Pid ! {struct_available, Module},
ok
end;
make_struct_available(_, _, _, _) ->
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
nil when not is_binary(File) ->
nil;
nil ->
nil when is_tuple(Location) ->
{File, Line} = Location,
{normalize_location(File), Line};
X when is_binary(X) ->
nil ->
nil;
File when is_binary(File) ->
'Elixir.Module':delete_attribute(Module, file),
{normalize_location(X), 0};
{X, L} when is_binary(X) andalso is_integer(L) ->
{normalize_location(File), 0};
{File, Line} when is_binary(File) andalso is_integer(Line) ->
'Elixir.Module':delete_attribute(Module, file),
{normalize_location(X), L}
{normalize_location(File), Line}
end.
get_location_attribute(Module) ->
@@ -148,13 +142,13 @@ get_location_attribute(Module) ->
false -> 'Elixir.Module':get_attribute(Module, file)
end.
normalize_location(X) ->
elixir_utils:characters_to_list(elixir_utils:relative_to_cwd(X)).
normalize_location(File) ->
elixir_utils:characters_to_list(elixir_utils:relative_to_cwd(File)).
%% Compile super
compile_super(Module, true, #{function := Function}) ->
elixir_def_overridable:store(Module, Function, true);
elixir_def_overridable:super(Module, Function);
compile_super(_Module, _, _E) -> ok.
%% Translate the given call and expression given
@@ -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) ->
{[], false};
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) ->
{TClause, TS} = elixir_clauses:clause(Line,
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.
% It returns a list of all functions to be exported, plus the macros,
% and the body of all functions.
unwrap_definitions(Module) ->
Table = table(Module),
CTable = clauses_table(Module),
ets:delete(Table, last),
unwrap_definition(ets:tab2list(Table), CTable, [], [], [], [], [], [], []).
unwrap_definitions(File, Module) ->
Table = elixir_module:defs_table(Module),
CTable = elixir_module:clas_table(Module),
unwrap_definition([Fun|T], CTable, All, Exports, Private, Def, Defmacro, Functions, Tail) ->
Tuple = element(1, Fun),
Clauses = [Clause || {_, Clause} <- ets:lookup(CTable, Tuple)],
{All, Private} = unwrap_definition(ets:tab2list(Table), File, Module, CTable, [], []),
Unreachable = elixir_locals:warn_unused_local(File, Module, Private),
split_definition(All, Unreachable, [], [], [], [], [], {[], []}).
{NewFun, NewExports, NewPrivate, NewDef, NewDefmacro} =
case Clauses of
[] -> {false, Exports, Private, Def, Defmacro};
_ -> unwrap_definition(element(2, Fun), Tuple, Fun, Exports, Private, Def, Defmacro)
unwrap_definition([Fun|T], File, Module, CTable, All, Private) ->
{Tuple, Kind, Line, _, Check, Location, {Defaults, _, _}} = Fun,
Export = export(Kind, Tuple),
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,
{NewFunctions, NewTail} = case NewFun of
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 ->
NewAll = All,
{Functions, Tail};
_ ->
NewAll = [Tuple|All],
function_for_stored_definition(NewFun, Clauses, Functions, Tail)
end,
split_definition(T, Unreachable, Def, [Tuple|Defp], Defmacro, Defmacrop,
Exports, add_definition(Line, Location, Body, Functions));
true ->
split_definition(T, Unreachable, Def, [Tuple|Defp], Defmacro, Defmacrop,
Exports, Functions)
end;
unwrap_definition(T, CTable, NewAll, NewExports, NewPrivate,
NewDef, NewDefmacro, NewFunctions, NewTail);
unwrap_definition([], _CTable, All, Exports, Private, Def, Defmacro, Functions, Tail) ->
{All, Exports, Private, ordsets:from_list(Def),
ordsets:from_list(Defmacro), lists:reverse(Tail ++ Functions)}.
split_definition([{Tuple, defmacro, Line, Location, Body}|T], Unreachable,
Def, Defp, Defmacro, Defmacrop, Exports, Functions) ->
split_definition(T, Unreachable, Def, Defp, [Tuple|Defmacro], Defmacrop,
[export(defmacro, Tuple)|Exports],
add_definition(Line, Location, Body, Functions));
unwrap_definition(def, Tuple, Fun, Exports, Private, Def, Defmacro) ->
{Fun, [Tuple|Exports], Private, [Tuple|Def], Defmacro};
unwrap_definition(defmacro, {Name, Arity} = Tuple, Fun, Exports, Private, Def, Defmacro) ->
Macro = {elixir_utils:macro_name(Name), Arity + 1},
{setelement(1, Fun, Macro), [Macro|Exports], Private, Def, [Tuple|Defmacro]};
unwrap_definition(defp, Tuple, Fun, Exports, Private, Def, Defmacro) ->
%% {Name, Arity}, Kind, Line, Check, Defaults
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}.
split_definition([{Tuple, defmacrop, _Line, _Location, _Body}|T], Unreachable,
Def, Defp, Defmacro, Defmacrop, Exports, Functions) ->
split_definition(T, Unreachable, Def, Defp, Defmacro, [Tuple|Defmacrop],
Exports, Functions);
split_definition([], _Unreachable, Def, Defp, Defmacro, Defmacrop, Exports, {Head, Tail}) ->
{Def, Defp, Defmacro, Defmacrop, Exports, Head ++ Tail}.
%% Helpers
function_for_stored_definition({{Name,Arity}, _, Line, _, _, nil, _}, Clauses, Functions, Tail) ->
{[{function, Line, Name, Arity, Clauses}|Functions], Tail};
export(Kind, {Name, Arity}) when Kind == defmacro; Kind == defmacrop ->
{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) ->
{Functions, [
{function, Line, Name, Arity, Clauses},
{attribute, Line, file, Location} | Tail
]}.
function_for_stored_definition(Line, {Name,Arity}, Clauses) ->
{function, Line, Name, Arity, Clauses}.
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)
when Kind == defmacro; Kind == defmacrop ->
{function, Line, Name, length(Args) - 1, [Clause]};
default_function_for(_, Name, {clause, Line, Args, _Guards, _Exprs} = 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.
%% This function also checks and emit warnings in case
%% the kind, of the visibility of the function changes.
store_each(Check, Kind, File, Location, Table, CTable, Defaults, {function, Line, Name, Arity, Clauses}) ->
store_each(Check, Kind, File, Location, Module, Defaults, {function, Line, Name, Arity, Clauses}) ->
Data = elixir_module:data_table(Module),
Defs = elixir_module:defs_table(Module),
Clas = elixir_module:clas_table(Module),
Tuple = {Name, Arity},
case ets:lookup(Table, Tuple) of
[{Tuple, StoredKind, StoredLine, StoredFile, StoredCheck, StoredLocation, StoredDefaults}] ->
HasBody = Clauses =/= [],
case ets:lookup(Defs, Tuple) of
[{Tuple, StoredKind, StoredLine, StoredFile, StoredCheck,
StoredLocation, {StoredDefaults, LastHasBody, LastDefaults}}] ->
FinalLine = StoredLine,
FinalLocation = StoredLocation,
FinalDefaults = max(Defaults, StoredDefaults),
FinalDefaults = {max(Defaults, StoredDefaults), HasBody, Defaults},
check_valid_kind(Line, File, Name, Arity, Kind, StoredKind),
(Check and StoredCheck) andalso
check_valid_clause(Line, File, Name, Arity, Kind, Table, StoredLine, StoredFile),
check_valid_defaults(Line, File, Name, Arity, Kind, Defaults, StoredDefaults);
check_valid_clause(Line, File, Name, Arity, Kind, Data, StoredLine, StoredFile),
check_valid_defaults(Line, File, Name, Arity, Kind, Defaults, LastDefaults, LastHasBody);
[] ->
FinalLine = Line,
FinalLocation = Location,
FinalDefaults = Defaults
FinalDefaults = {Defaults, HasBody, Defaults}
end,
Check andalso ets:insert(Table, {last, {Name, Arity}}),
ets:insert(CTable, [{Tuple, Clause} || Clause <- Clauses ]),
ets:insert(Table, {Tuple, Kind, FinalLine, File, Check, FinalLocation, FinalDefaults}).
Check andalso ets:insert(Data, {?last_def, {Name, Arity}}),
ets:insert(Clas, [{Tuple, Clause} || Clause <- Clauses]),
ets:insert(Defs, {Tuple, Kind, FinalLine, File, Check, FinalLocation, FinalDefaults}).
%% Validations
check_valid_kind(_Line, _File, _Name, _Arity, Kind, Kind) -> [];
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}}).
check_valid_clause(Line, File, Name, Arity, Kind, Table, StoredLine, StoredFile) ->
case ets:lookup_element(Table, last, 2) of
check_valid_clause(Line, File, Name, Arity, Kind, Data, StoredLine, StoredFile) ->
case ets:lookup_element(Data, ?last_def, 2) of
{Name,Arity} -> [];
[] -> [];
_ ->
Relative = elixir_utils:relative_to_cwd(StoredFile),
elixir_errors:handle_file_warning(File, {Line, ?MODULE,
{ungrouped_clause, {Kind, Name, Arity, StoredLine, Relative}}})
Relative = elixir_utils:relative_to_cwd(elixir_utils:relative_to_cwd(StoredFile)),
elixir_errors:form_warn([{line, Line}], File, ?MODULE,
{ungrouped_clause, {Kind, Name, Arity, StoredLine, Relative}})
end.
check_valid_defaults(_Line, _File, _Name, _Arity, _Kind, 0, _) -> [];
check_valid_defaults(Line, File, Name, Arity, Kind, _, 0) ->
elixir_errors:handle_file_warning(File, {Line, ?MODULE, {out_of_order_defaults, {Kind, Name, Arity}}});
check_valid_defaults(Line, File, Name, Arity, Kind, _, _) ->
elixir_errors:form_error(Line, File, ?MODULE, {clauses_with_defaults, {Kind, Name, Arity}}).
% Any clause after clause with defaults (body less does not count)
check_valid_defaults(Line, File, Name, Arity, Kind, _, StoredDefaults, true) when StoredDefaults > 0 ->
elixir_errors:form_error([{line, 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) ->
Matches = ets:match(Table, {{Name, '$2'}, '$1', '_', '_', '_', '_', '$3'}),
check_previous_defaults(Line, Module, Name, Arity, Kind, Defaults, E) ->
Matches = ets:match(elixir_module:defs_table(Module), {{Name, '$2'}, '$1', '_', '_', '_', '_', {'$3', '_', '_'}}),
[ begin
elixir_errors:form_error(Line, ?m(E, file), ?MODULE,
elixir_errors:form_error([{line, Line}], ?m(E, file), ?MODULE,
{defs_with_defaults, Name, {Kind, Arity}, {K, A}})
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) ->
[ 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) ].
invalid_arg({Name, _, Kind}) when is_atom(Name), is_atom(Kind) ->
@@ -350,13 +392,20 @@ invalid_arg(_) ->
true.
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.
%% 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}}) ->
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) ->
"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}) ->
io_lib:format("missing do keyword in ~ts", [Kind]).
+2 -1
View File
@@ -4,10 +4,11 @@
-include("elixir.hrl").
expand(Args, E) ->
NoContext = E#{context := nil},
lists:mapfoldl(fun
({'\\\\', Meta, [Left, Right]}, Acc) ->
{ELeft, EL} = elixir_exp:expand(Left, Acc),
{ERight, _} = elixir_exp:expand(Right, Acc#{context := nil}),
{ERight, _} = elixir_exp:expand(Right, NoContext),
{{'\\\\', Meta, [ELeft, ERight]}, EL};
(Left, Acc) ->
elixir_exp:expand(Left, Acc)
+18 -13
View File
@@ -1,14 +1,18 @@
% Holds the logic responsible for defining overridable functions and handling super.
-module(elixir_def_overridable).
-export([store_pending/1, ensure_defined/4,
name/2, store/3, format_error/1]).
-export([setup/1, overridable/1, overridable/2, name/2, super/2, store_pending/1,
ensure_defined/4, format_error/1]).
-include("elixir.hrl").
-define(attr, {elixir, overridable}).
setup(Module) ->
overridable(Module, []).
overridable(Module) ->
ets:lookup_element(elixir_module:data_table(Module), '__overridable', 2).
ets:lookup_element(elixir_module:data_table(Module), ?attr, 2).
overridable(Module, Value) ->
ets:insert(elixir_module:data_table(Module), {'__overridable', Value}).
ets:insert(elixir_module:data_table(Module), {?attr, Value}).
%% Check if an overridable function is defined.
@@ -36,30 +40,31 @@ store(Module, Function, GenerateName) ->
{_Count, _Clause, _Neighbours, true} -> ok;
{Count, Clause, Neighbours, false} ->
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
true -> {defp, name(Module, Function, Overridable)};
false -> {Kind, Name}
end,
case code:is_loaded('Elixir.Module.LocalsTracker') of
{_, _} ->
case elixir_compiler:get_opt(internal) of
false ->
'Elixir.Module.LocalsTracker':reattach(Module, Kind, {Name, Arity}, Neighbours);
_ ->
true ->
ok
end,
Def = {function, Line, FinalName, Arity, Clauses},
elixir_def:store_each(false, FinalKind, File, Location,
elixir_def:table(Module), elixir_def:clauses_table(Module), Defaults, Def)
elixir_def:store_each(false, FinalKind, File, Location, Module, Defaults, Def)
end.
%% Store pending declarations that were not manually made concrete.
super(Module, Function) ->
store(Module, Function, true).
store_pending(Module) ->
[store(Module, X, false) || {X, {_, _, _, false}} <- overridable(Module),
not 'Elixir.Module':'defines?'(Module, X)].
_ = [store(Module, X, false) || {X, {_, _, _, false}} <- overridable(Module),
not 'Elixir.Module':'defines?'(Module, X)],
ok.
%% Error handling
+18 -187
View File
@@ -4,24 +4,12 @@
-module(elixir_dispatch).
-export([dispatch_import/5, dispatch_require/6,
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,
find_import/4, format_error/1]).
-include("elixir.hrl").
-import(ordsets, [is_element/2]).
-define(atom, 'Elixir.Atom').
-define(float, 'Elixir.Float').
-define(io, 'Elixir.IO').
-define(integer, 'Elixir.Integer').
-define(kernel, 'Elixir.Kernel').
-define(list, 'Elixir.List').
-define(map, 'Elixir.Map').
-define(node, 'Elixir.Node').
-define(process, 'Elixir.Process').
-define(string, 'Elixir.String').
-define(system, 'Elixir.System').
-define(tuple, 'Elixir.Tuple').
default_functions() ->
[{?kernel, elixir_imported_functions()}].
@@ -30,17 +18,20 @@ default_macros() ->
default_requires() ->
['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) ->
Tuple = {Name, Arity},
case find_dispatch(Meta, Tuple, [], E) of
{function, Receiver} ->
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;
{macro, Receiver} ->
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;
_ ->
false
@@ -78,7 +69,7 @@ remote_function(Meta, Receiver, Name, Arity, E) ->
check_deprecation(Meta, Receiver, Name, Arity, E),
elixir_lexical:record_remote(Receiver, ?m(E, lexical_tracker)),
case inline(Receiver, Name, Arity) of
case elixir_rewrite:inline(Receiver, Name, Arity) of
{AR, AN} -> {remote, AR, AN, Arity};
false -> {remote, Receiver, Name, Arity}
end.
@@ -87,7 +78,7 @@ remote_function(Meta, Receiver, Name, Arity, E) ->
dispatch_import(Meta, Name, Args, E, Callback) ->
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} ->
expand_quoted(Meta, Receiver, Name, Arity, Quoted, E);
{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) ->
Arity = length(Args),
case rewrite(Receiver, Name, Args, Arity) of
{ok, AR, AN, AA} ->
Callback(AR, AN, AA);
case elixir_rewrite:inline(Receiver, Name, Arity) of
{AR, AN} ->
Callback(AR, AN, Args);
false ->
case expand_require(Meta, Receiver, {Name, Arity}, Args, E) of
{ok, Receiver, Quoted} -> expand_quoted(Meta, Receiver, Name, Arity, Quoted, E);
@@ -114,12 +105,12 @@ dispatch_require(_Meta, Receiver, Name, Args, _E, Callback) ->
%% 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),
Dispatch = find_dispatch(Meta, Tuple, Extra, E),
Function = ?m(E, function),
Local = (Function /= nil) andalso (Function /= Tuple) andalso
elixir_locals:macro_for(Module, Name, Arity),
AllowLocals = External orelse ((Function /= nil) andalso (Function /= Tuple)),
Local = AllowLocals andalso elixir_locals:macro_for(Module, Name, Arity),
case Dispatch of
%% 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} ->
elixir_lexical:record_import(Receiver, ?m(E, lexical_tracker)),
elixir_locals:record_import(Tuple, Receiver, Module, ?m(E, function)),
case rewrite(Receiver, Name, Args, Arity) of
{ok, _, _, _} = Res -> Res;
false -> {ok, Receiver, Name, Args}
end;
{ok, Receiver, Name, Args};
{macro, Receiver} ->
check_deprecation(Meta, Receiver, Name, Arity, E),
elixir_lexical:record_import(Receiver, ?m(E, lexical_tracker)),
@@ -163,8 +150,8 @@ do_expand_import(Meta, {Name, Arity} = Tuple, Args, Module, E, Result) ->
error -> {ok, Receiver, Name, Args}
end;
false when Module == ?kernel ->
case rewrite(Module, Name, Args, Arity) of
{ok, _, _, _} = Res -> Res;
case elixir_rewrite:inline(Module, Name, Arity) of
{AR, AN} -> {ok, AR, AN, Args};
false -> error
end;
false ->
@@ -228,7 +215,7 @@ expand_quoted(Meta, Receiver, Name, Arity, Quoted, E) ->
end.
caller(Line, #{module := nil} = E) ->
{elixir_compiler, '__FILE__', 2, location(Line, E)};
{elixir_compiler_0, '__FILE__', 1, location(Line, E)};
caller(Line, #{module := Module, function := nil} = E) ->
{Module, '__MODULE__', 0, location(Line, E)};
caller(Line, #{module := Module, function := {Name, Arity}} = E) ->
@@ -338,152 +325,6 @@ elixir_imported_macros() ->
error:undef -> []
end.
rewrite(?atom, to_string, [Arg], _) ->
{ok, erlang, atom_to_binary, [Arg, utf8]};
rewrite(?kernel, elem, [Tuple, Index], _) ->
{ok, erlang, element, [increment(Index), Tuple]};
rewrite(?kernel, put_elem, [Tuple, Index, Value], _) ->
{ok, erlang, setelement, [increment(Index), Tuple, Value]};
rewrite(?map, 'has_key?', [Map, Key], _) ->
{ok, maps, is_key, [Key, Map]};
rewrite(?map, fetch, [Map, Key], _) ->
{ok, maps, find, [Key, Map]};
rewrite(?map, put, [Map, Key, Value], _) ->
{ok, maps, put, [Key, Value, Map]};
rewrite(?map, delete, [Map, Key], _) ->
{ok, maps, remove, [Key, Map]};
rewrite(?process, monitor, [Arg], _) ->
{ok, erlang, monitor, [process, Arg]};
rewrite(?string, to_atom, [Arg], _) ->
{ok, erlang, binary_to_atom, [Arg, utf8]};
rewrite(?string, to_existing_atom, [Arg], _) ->
{ok, erlang, binary_to_existing_atom, [Arg, utf8]};
rewrite(?tuple, insert_at, [Tuple, Index, Term], _) ->
{ok, erlang, insert_element, [increment(Index), Tuple, Term]};
rewrite(?tuple, delete_at, [Tuple, Index], _) ->
{ok, erlang, delete_element, [increment(Index), Tuple]};
rewrite(?tuple, duplicate, [Data, Size], _) ->
{ok, erlang, make_tuple, [Size, Data]};
rewrite(Receiver, Name, Args, Arity) ->
case inline(Receiver, Name, Arity) of
{AR, AN} -> {ok, AR, AN, Args};
false -> false
end.
increment(Number) when is_number(Number) ->
Number + 1;
increment(Other) ->
{{'.', [], [erlang, '+']}, [], [Other, 1]}.
inline(?atom, to_char_list, 1) -> {erlang, atom_to_list};
inline(?io, iodata_length, 1) -> {erlang, iolist_size};
inline(?io, iodata_to_binary, 1) -> {erlang, iolist_to_binary};
inline(?integer, to_string, 1) -> {erlang, integer_to_binary};
inline(?integer, to_string, 2) -> {erlang, integer_to_binary};
inline(?integer, to_char_list, 1) -> {erlang, integer_to_list};
inline(?integer, to_char_list, 2) -> {erlang, integer_to_list};
inline(?float, to_string, 1) -> {erlang, float_to_binary};
inline(?float, to_char_list, 1) -> {erlang, float_to_list};
inline(?list, to_atom, 1) -> {erlang, list_to_atom};
inline(?list, to_existing_atom, 1) -> {erlang, list_to_existing_atom};
inline(?list, to_float, 1) -> {erlang, list_to_float};
inline(?list, to_integer, 1) -> {erlang, list_to_integer};
inline(?list, to_integer, 2) -> {erlang, list_to_integer};
inline(?list, to_tuple, 1) -> {erlang, list_to_tuple};
inline(?kernel, '+', 2) -> {erlang, '+'};
inline(?kernel, '-', 2) -> {erlang, '-'};
inline(?kernel, '+', 1) -> {erlang, '+'};
inline(?kernel, '-', 1) -> {erlang, '-'};
inline(?kernel, '*', 2) -> {erlang, '*'};
inline(?kernel, '/', 2) -> {erlang, '/'};
inline(?kernel, '++', 2) -> {erlang, '++'};
inline(?kernel, '--', 2) -> {erlang, '--'};
inline(?kernel, 'not', 1) -> {erlang, 'not'};
inline(?kernel, '<', 2) -> {erlang, '<'};
inline(?kernel, '>', 2) -> {erlang, '>'};
inline(?kernel, '<=', 2) -> {erlang, '=<'};
inline(?kernel, '>=', 2) -> {erlang, '>='};
inline(?kernel, '==', 2) -> {erlang, '=='};
inline(?kernel, '!=', 2) -> {erlang, '/='};
inline(?kernel, '===', 2) -> {erlang, '=:='};
inline(?kernel, '!==', 2) -> {erlang, '=/='};
inline(?kernel, abs, 1) -> {erlang, abs};
inline(?kernel, apply, 2) -> {erlang, apply};
inline(?kernel, apply, 3) -> {erlang, apply};
inline(?kernel, binary_part, 3) -> {erlang, binary_part};
inline(?kernel, bit_size, 1) -> {erlang, bit_size};
inline(?kernel, byte_size, 1) -> {erlang, byte_size};
inline(?kernel, 'div', 2) -> {erlang, 'div'};
inline(?kernel, exit, 1) -> {erlang, exit};
inline(?kernel, hd, 1) -> {erlang, hd};
inline(?kernel, is_atom, 1) -> {erlang, is_atom};
inline(?kernel, is_binary, 1) -> {erlang, is_binary};
inline(?kernel, is_bitstring, 1) -> {erlang, is_bitstring};
inline(?kernel, is_boolean, 1) -> {erlang, is_boolean};
inline(?kernel, is_float, 1) -> {erlang, is_float};
inline(?kernel, is_function, 1) -> {erlang, is_function};
inline(?kernel, is_function, 2) -> {erlang, is_function};
inline(?kernel, is_integer, 1) -> {erlang, is_integer};
inline(?kernel, is_list, 1) -> {erlang, is_list};
inline(?kernel, is_map, 1) -> {erlang, is_map};
inline(?kernel, is_number, 1) -> {erlang, is_number};
inline(?kernel, is_pid, 1) -> {erlang, is_pid};
inline(?kernel, is_port, 1) -> {erlang, is_port};
inline(?kernel, is_reference, 1) -> {erlang, is_reference};
inline(?kernel, is_tuple, 1) -> {erlang, is_tuple};
inline(?kernel, length, 1) -> {erlang, length};
inline(?kernel, make_ref, 0) -> {erlang, make_ref};
inline(?kernel, map_size, 1) -> {erlang, map_size};
inline(?kernel, max, 2) -> {erlang, max};
inline(?kernel, min, 2) -> {erlang, min};
inline(?kernel, node, 0) -> {erlang, node};
inline(?kernel, node, 1) -> {erlang, node};
inline(?kernel, 'rem', 2) -> {erlang, 'rem'};
inline(?kernel, round, 1) -> {erlang, round};
inline(?kernel, self, 0) -> {erlang, self};
inline(?kernel, send, 2) -> {erlang, send};
inline(?kernel, spawn, 1) -> {erlang, spawn};
inline(?kernel, spawn, 3) -> {erlang, spawn};
inline(?kernel, spawn_link, 1) -> {erlang, spawn_link};
inline(?kernel, spawn_link, 3) -> {erlang, spawn_link};
inline(?kernel, spawn_monitor, 1) -> {erlang, spawn_monitor};
inline(?kernel, spawn_monitor, 3) -> {erlang, spawn_monitor};
inline(?kernel, throw, 1) -> {erlang, throw};
inline(?kernel, tl, 1) -> {erlang, tl};
inline(?kernel, trunc, 1) -> {erlang, trunc};
inline(?kernel, tuple_size, 1) -> {erlang, tuple_size};
inline(?map, keys, 1) -> {maps, keys};
inline(?map, merge, 2) -> {maps, merge};
inline(?map, size, 1) -> {maps, size};
inline(?map, values, 1) -> {maps, values};
inline(?map, to_list, 1) -> {maps, to_list};
inline(?node, spawn, 2) -> {erlang, spawn};
inline(?node, spawn, 3) -> {erlang, spawn_opt};
inline(?node, spawn, 4) -> {erlang, spawn};
inline(?node, spawn, 5) -> {erlang, spawn_opt};
inline(?node, spawn_link, 2) -> {erlang, spawn_link};
inline(?node, spawn_link, 4) -> {erlang, spawn_link};
inline(?process, exit, 2) -> {erlang, exit};
inline(?process, spawn, 2) -> {erlang, spawn_opt};
inline(?process, spawn, 4) -> {erlang, spawn_opt};
inline(?process, demonitor, 1) -> {erlang, demonitor};
inline(?process, demonitor, 2) -> {erlang, demonitor};
inline(?process, link, 1) -> {erlang, link};
inline(?process, unlink, 1) -> {erlang, unlink};
inline(?string, to_float, 1) -> {erlang, binary_to_float};
inline(?string, to_integer, 1) -> {erlang, binary_to_integer};
inline(?string, to_integer, 2) -> {erlang, binary_to_integer};
inline(?system, stacktrace, 0) -> {erlang, get_stacktrace};
inline(?tuple, to_list, 1) -> {erlang, tuple_to_list};
inline(_, _, _) -> false.
check_deprecation(Meta, Receiver, Name, Arity, #{file := File}) ->
case deprecation(Receiver, Name, Arity) of
false -> ok;
@@ -507,15 +348,5 @@ deprecation_message(Warning, Message) ->
Message -> Warning ++ ", " ++ Message
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(_, _, _) ->
false.
+112 -61
View File
@@ -2,36 +2,37 @@
% This is not exposed in the Elixir language.
-module(elixir_errors).
-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]).
-include("elixir.hrl").
-type line_or_meta() :: integer() | list().
-spec warn(non_neg_integer(), binary(), unicode:chardata()) -> ok.
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).
warn(Line, File, Warning) when is_integer(Line), is_binary(File) ->
warn(file_format(Line, File), Warning).
-spec warn(unicode:chardata(), unicode:chardata()) -> ok.
warn(Caller, Warning) ->
warn([Caller, "warning: ", Warning]).
do_warn([Caller, "warning: ", Warning, $\n]).
warn(Line, File, Warning) when is_integer(Line) ->
warn(file_format(Line, File, "warning: " ++ Warning)).
%% General forms handling.
%% Raised during expansion/translation/compilation.
-spec form_error(line_or_meta(), binary(), module(), any()) -> no_return().
-spec form_error(list(), binary(), module(), any()) -> no_return().
form_error(Meta, File, Module, Desc) ->
compile_error(Meta, File, format_error(Module, Desc)).
-spec compile_error(line_or_meta(), binary(), iolist()) -> no_return().
-spec compile_error(line_or_meta(), binary(), iolist(), list()) -> no_return().
-spec form_warn(list(), binary(), module(), any()) -> ok.
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) ->
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, 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
<<"syntax error before: ">> -> <<"syntax error: expression is incomplete">>;
_ -> Error
end,
raise(Meta, File, 'Elixir.TokenMissingError', Message);
do_raise(Line, File, 'Elixir.TokenMissingError', Message);
%% Show a nicer message for missing end tokens
parse_error(Meta, File, <<"syntax error before: ">>, <<"'end'">>) ->
raise(Meta, File, 'Elixir.SyntaxError', <<"unexpected token: end">>);
parse_error(Line, File, <<"syntax error before: ">>, <<"'end'">>) ->
do_raise(Line, File, 'Elixir.SyntaxError', <<"unexpected token: end">>);
%% Binaries are wrapped in [<<...>>], so we need to unwrap them
parse_error(Meta, File, Error, <<"[", _/binary>> = Full) when is_binary(Error) ->
%% Produce a human-readable message for errors before a sigil
parse_error(Line, File, <<"syntax error before: ">>, <<"{sigil,", _Rest/binary>> = Full) ->
{ok, Tokens, _} = erl_scan:string(binary_to_list(Full)),
{ok, {sigil, _, Sigil, [Content], _}} = erl_parse:parse_term(Tokens ++ [{dot, 1}]),
Message = <<"syntax error before: sigil ~", Sigil," with content '", Content/binary, "'">>,
do_raise(Line, File, 'Elixir.SyntaxError', Message);
%% Aliases are wrapped in ['']
parse_error(Line, File, Error, <<"['", Token/binary>>) when is_binary(Error) ->
Rest =
case binary:split(Token, <<"'">>) of
[Part, _] -> Part;
_ -> <<>>
end,
do_raise(Line, File, 'Elixir.SyntaxError', <<Error/binary, Rest/binary>>);
%% Binaries (and interpolation) are wrapped in [<<...>>]
parse_error(Line, File, Error, <<"[", _/binary>> = Full) when is_binary(Error) ->
Rest =
case binary:split(Full, <<"<<">>) of
[Lead, Token] ->
@@ -66,27 +83,27 @@ parse_error(Meta, File, Error, <<"[", _/binary>> = Full) when is_binary(Error) -
[_] ->
<<$">>
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
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 >>,
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
handle_file_warning(true, _File, {_Line, sys_core_fold, nomatch_guard}) -> [];
handle_file_warning(true, _File, {_Line, sys_core_fold, {nomatch_shadow, _}}) -> [];
handle_file_warning(true, _File, {_Line, sys_core_fold, nomatch_guard}) -> ok;
handle_file_warning(true, _File, {_Line, sys_core_fold, {nomatch_shadow, _}}) -> ok;
%% 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
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
handle_file_warning(_, _File, {_Line, erl_lint, {unused_function, _}}) -> [];
handle_file_warning(_, _File, {_Line, erl_lint, {unused_function, _}}) -> ok;
%% Make no_effect clauses pretty
handle_file_warning(_, File, {Line, sys_core_fold, {no_effect, {erlang, F, A}}}) ->
@@ -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}}) ->
case elixir_compiler:get_opt(internal) of
true -> [];
true -> ok;
false ->
Message = io_lib:format("behaviour ~ts undefined", [elixir_aliases:inspect(Module)]),
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)
handle_file_warning(_, _File, {Line,erl_lint,{unused_var,_Var}}) when Line =< 0 ->
[];
ok;
%% Ignore shadowed vars as we guarantee no conflicts ourselves
%% Ignore shadowed and exported vars as we guarantee no conflicts ourselves
handle_file_warning(_, _File, {_Line,erl_lint,{shadowed_var,_Var,_Where}}) ->
[];
ok;
handle_file_warning(_, _File, {_Line,erl_lint,{exported_var,_Var,_Where}}) ->
ok;
%% Rewrite nomatch_guard to be more generic it can happen inside if, unless, etc
handle_file_warning(_, File, {Line, sys_core_fold, nomatch_guard}) ->
warn(Line, File, "this check/guard will always yield the same result");
%% Properly format other unused vars
handle_file_warning(_, File, {Line,erl_lint,{unused_var,Var}}) ->
Message = format_error(erl_lint, {unused_var, format_var(Var)}),
warn(Line, File, Message);
%% Handle literal eval failures
handle_file_warning(_, File, {Line,sys_core_fold,{eval_failure, Error}}) ->
#{'__struct__' := Struct} = 'Elixir.Exception':normalize(error, Error),
warn(Line, File, ["this expression will fail with ", elixir_aliases:inspect(Struct)]);
%% Default behaviour
handle_file_warning(_, File, {Line,Module,Desc}) ->
Message = format_error(Module, Desc),
@@ -152,39 +181,27 @@ handle_file_error(File, {Line,erl_lint,{unsafe_var,Var,{In,_Where}}}) ->
_ -> In
end,
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}}}) ->
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}) ->
form_error(Line, File, Module, Desc).
Message = format_error(Module, Desc),
do_raise(Line, File, 'Elixir.CompileError', elixir_utils:characters_to_binary(Message)).
%% Helpers
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) ->
raise(0, File, Kind, Message);
file_format(0, File) ->
io_lib:format("~ts: ", [elixir_utils:relative_to_cwd(File)]);
raise(Line, File, Kind, Message) when is_integer(Line), is_binary(File) ->
%% Populate the stacktrace so we can raise it
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]).
file_format(Line, File) ->
io_lib:format("~ts:~w: ", [elixir_utils:relative_to_cwd(File), Line]).
format_var(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(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)),
{Receiver, EA};
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}
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")
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),
Context = case lists:keyfind(context, 1, EKV) of
@@ -185,8 +187,20 @@ expand({quote, Meta, [KV, Do]}, E) when is_list(Do) ->
end
end,
Keep = lists:keyfind(location, 1, EKV) == {location, keep},
Line = proplists:get_value(line, EKV, false),
{File, Line} = case lists:keyfind(location, 1, EKV) of
{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
{bind_quoted, BQ} -> {BQ, false};
@@ -194,11 +208,11 @@ expand({quote, Meta, [KV, Do]}, E) when is_list(Do) ->
end,
Unquote = case lists:keyfind(unquote, 1, EKV) of
{unquote, Bool} when is_boolean(Bool) -> Bool;
{unquote, U} when is_boolean(U) -> U;
false -> DefaultUnquote
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),
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),
if
VarMeta == {var, true} ->
Extra = case Kind of
nil -> "";
_ -> 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]);
compile_error(Meta, ?m(E, file), "expected var \"~ts\"~ts to expand to an existing variable "
"or be part of a match", [Name, elixir_scope:context_info(Kind)]);
true ->
expand({Name, Meta, []}, E)
end
@@ -347,7 +356,7 @@ expand({_, Meta, Args} = Invalid, E) when is_list(Meta) and is_list(Args) ->
expand({_, _, _} = Tuple, E) ->
compile_error([{line,0}], ?m(E, file), "invalid quoted expression: ~ts",
['Elixir.Kernel':inspect(Tuple, [{records,false}])]);
['Elixir.Kernel':inspect(Tuple, [])]);
%% Literals
@@ -449,8 +458,7 @@ assert_no_ambiguous_op(_Atom, _Meta, _Args, _E) ->
ok.
expand_local(Meta, Name, Args, #{local := nil, function := nil} = E) ->
{EArgs, EA} = expand_args(Args, E),
{{Name, Meta, EArgs}, EA};
compile_error(Meta, ?m(E, file), "undefined function ~ts/~B", [Name, length(Args)]);
expand_local(Meta, Name, Args, #{local := nil, module := Module, function := Function} = E) ->
elixir_locals:record_local({Name, length(Args)}, Module, Function),
{EArgs, EA} = expand_args(Args, E),
@@ -466,7 +474,8 @@ expand_remote(Receiver, DotMeta, Right, Meta, Args, E, EL) ->
true -> ok
end,
{EArgs, EA} = expand_args(Args, E),
{{{'.', DotMeta, [Receiver, Right]}, Meta, EArgs}, elixir_env:mergev(EL, EA)}.
{elixir_rewrite:rewrite(Receiver, DotMeta, Right, Meta, EArgs),
elixir_env:mergev(EL, EA)}.
%% Lexical helpers
@@ -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])
end || {Key, _} <- Opts, not lists:member(Key, Allowed)];
validate_opts(Meta, Kind, _Allowed, _Opts, S) ->
compile_error(Meta, S#elixir_scope.file, "invalid options for ~s, expected a keyword list", [Kind]).
validate_opts(Meta, Kind, _Allowed, _Opts, E) ->
compile_error(Meta, ?m(E, file), "invalid options for ~s, expected a keyword list", [Kind]).
no_alias_opts(KV) when is_list(KV) ->
case lists:keyfind(as, 1, KV) of

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