Compare commits

...
465 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
285 changed files with 9039 additions and 4255 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
/.eunit
/.release
/docs
/doc
/ebin
/lib/*/ebin/*
/lib/*/tmp
+1
View File
@@ -8,3 +8,4 @@ notifications:
otp_release:
- 17.0
- 17.1
- 17.3
+208
View File
@@ -1,5 +1,213 @@
# Changelog
## 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
* [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
## v1.0.4 (2015-04-07)
* 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
+2 -22
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:
+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.
+27 -15
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
@@ -86,7 +88,7 @@ $(KERNEL): lib/elixir/lib/*.ex 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))
@@ -124,17 +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)
$(call DOCS,Logger,logger,Logger)
$(Q) rm -rf doc
$(call COMPILE_DOCS,Elixir,elixir,Kernel)
$(call COMPILE_DOCS,EEx,eex,EEx)
$(call COMPILE_DOCS,Mix,mix,Mix)
$(call COMPILE_DOCS,IEx,iex,IEx)
$(call COMPILE_DOCS,ExUnit,ex_unit,ExUnit)
$(call COMPILE_DOCS,Logger,logger,Logger)
../ex_doc/bin/ex_doc:
@ echo "ex_doc is not found in ../ex_doc as expected. See README for more information."
@@ -142,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
@@ -183,7 +195,7 @@ test_stdlib: compile
#==> Dialyzer tasks
DIALYZER_OPTS = --no_check_plt --fullpath -Werror_handling -Wunmatched_returns -Wrace_conditions -Wunderspecs
DIALYZER_OPTS = --no_check_plt --fullpath -Werror_handling -Wunmatched_returns -Wunderspecs
PLT = .elixir.plt
$(PLT):
+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.
+23 -11
View File
@@ -1,28 +1,40 @@
# Release process
## All releases
This document simply outlines the release process:
1. Remove all `-dev` extension from versions (see below for all files)
1. Ensure you are running on the oldest supported Erlang version
2. Ensure CHANGELOG is updated and add current date
2. Remove all `-dev` extension from versions (see below for all files)
3. Commit changes above with title "Release vVERSION" and generate new tag
3. Ensure CHANGELOG is updated and add current date
4. Run `make clean test` to ensure all tests pass from scratch and the CI is green
4. Commit changes above with title "Release vVERSION" and generate new tag
5. Push master and the new tag
5. Run `make clean test` to ensure all tests pass from scratch and the CI is green
6. Release new docs with `make release_docs`, move docs to `docs/stable`
6. Ensure minimum supported Hex works with new release (instructions upcoming)
7. Release new zip with `make release_zip`, push new zip to GitHub Releases, name it Precompiled.zip
7. Push branch and the new tag
8. Fast-forward merge master into stable branch and push it
8. Release new docs with `make release_docs`, copy docs to `docs/stable` if appropriate, and push
9. After release, bump versions, add `-dev` back and commit
9. Release new zip with `make release_zip`, push `Precompiled.zip` to GitHub Releases
10. `make release_docs` once again and push it to `elixir-lang/docs`
10. Add the release to `elixir.csv` file in `elixir-lang/elixir-lang.github.com`
11. Add the release to `elixir.csv` file in `elixir-lang/elixir-lang.github.com`
11. Build and push standalone Mix with `make publish_mix` (requires AWS credentials)
## New vMAJOR.MINOR releases
12. Create a new branch "vMAJOR.MINOR"
13. Move docs generation to `docs/vMAJOR.MINOR` and copy them from `docs/stable`
14. In master, bump versions, start new CHANGELOG, add `-dev` back and commit "Start vVERSION+1"
15. `make release_docs` and push it to `elixir-lang/docs`
## Places where version is mentioned
+1 -1
View File
@@ -1 +1 @@
0.15.0
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
+9 -2
View File
@@ -48,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"
@@ -62,6 +65,7 @@ if "%par%"=="""" (
)
rem ******* EXECUTION OPTIONS **********************
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 """"=="%par:-e=%" (shift)
@@ -90,10 +94,13 @@ for /d %%d in ("%originPath%..\lib\*.") do (
)
SETLOCAL disabledelayedexpansion
:run
IF NOT %runMode% == "iex" (
set beforeExtra=-s elixir start_cli %beforeExtra%
)
IF %useWerl% EQU 1 (
start werl.exe %ext_libs% %ELIXIR_ERL_OPTIONS% %parsErlang% -s elixir start_cli %beforeExtra% -extra %*
start werl.exe %ext_libs% %ELIXIR_ERL_OPTIONS% %parsErlang% %beforeExtra% -extra %*
) ELSE (
erl.exe %ext_libs% -noshell %ELIXIR_ERL_OPTIONS% %parsErlang% -s elixir start_cli %beforeExtra% -extra %*
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 "$@"
+8 -5
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
@@ -34,10 +38,9 @@ defmodule EEx do
All functions in this module accepts EEx-related options.
They are:
* `: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.
* `: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.
* `:engine` - the EEx engine to be used for compilation.
## Engine
+13 -8
View File
@@ -12,10 +12,14 @@ defmodule EEx.Compiler do
def compile(source, opts) do
file = opts[:file] || "nofile"
line = opts[:line] || 1
tokens = EEx.Tokenizer.tokenize(source, line)
state = %{engine: opts[:engine] || @default_engine,
file: file, line: line, quoted: [], start_line: nil}
generate_buffer(tokens, "", [], state)
case EEx.Tokenizer.tokenize(source, line) do
{:ok, tokens} ->
state = %{engine: opts[:engine] || @default_engine,
file: file, line: line, quoted: [], start_line: nil}
generate_buffer(tokens, "", [], state)
{:error, line, message} ->
raise EEx.SyntaxError, line: line, file: file, message: message
end
end
# Generates the buffers by handling each expression from the tokenizer
@@ -51,16 +55,17 @@ defmodule EEx.Compiler do
{buffer, t}
end
defp generate_buffer([{:end_expr, line, _, chars}|_], _buffer, [], _state) do
raise EEx.SyntaxError, message: "unexpected token: #{inspect chars} at line #{inspect line}"
defp generate_buffer([{:end_expr, line, _, chars}|_], _buffer, [], state) do
raise EEx.SyntaxError, message: "unexpected token #{inspect chars}", file: state.file, line: line
end
defp generate_buffer([], buffer, [], state) do
state.engine.handle_body(buffer)
end
defp generate_buffer([], _buffer, _scope, _state) do
raise EEx.SyntaxError, message: "unexpected end of string. expecting a closing <% end %>."
defp generate_buffer([], _buffer, _scope, state) do
raise EEx.SyntaxError, message: "unexpected end of string, expected a closing '<% end %>'",
file: state.file, line: state.line
end
# Creates a placeholder and wrap it inside the expression block
+4 -7
View File
@@ -98,19 +98,16 @@ defmodule EEx.Engine do
"""
def handle_expr(buffer, "=", expr) do
quote do
tmp = unquote(buffer)
tmp <> (case unquote(expr) do
bin when is_binary(bin) -> bin
oth -> String.Chars.to_string(oth)
end)
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
+31 -20
View File
@@ -3,7 +3,8 @@ defmodule EEx.Tokenizer do
@doc """
Tokenizes the given char list or binary.
It returns 4 different types of tokens as result:
It returns {:ok, list} with the following tokens:
* `{:text, contents}`
* `{:expr, line, marker, contents}`
@@ -11,33 +12,43 @@ defmodule EEx.Tokenizer do
* `{:middle_expr, line, marker, contents}`
* `{:end_expr, line, marker, contents}`
Or `{:error, line, error}` in case of errors.
"""
def tokenize(bin, line) when is_binary(bin) do
tokenize(String.to_char_list(bin), line)
end
def tokenize(list, line) do
Enum.reverse(tokenize(list, line, [], []))
tokenize(list, line, [], [])
end
defp tokenize('<%%' ++ t, line, buffer, acc) do
{buffer, new_line, rest} = tokenize_expr t, line, [?%, ?<|buffer]
tokenize rest, new_line, [?>, ?%|buffer], acc
case expr(t, line, [?%, ?<|buffer]) do
{:error, _, _} = error -> error
{:ok, buffer, new_line, rest} ->
tokenize rest, new_line, [?>, ?%|buffer], acc
end
end
defp tokenize('<%#' ++ t, line, buffer, acc) do
{_, new_line, rest} = tokenize_expr t, line, []
tokenize rest, new_line, buffer, acc
case expr(t, line, []) do
{:error, _, _} = error -> error
{:ok, _, new_line, rest} ->
tokenize rest, new_line, buffer, acc
end
end
defp tokenize('<%' ++ t, line, buffer, acc) do
{marker, t} = retrieve_marker(t)
{expr, new_line, rest} = tokenize_expr t, line, []
token = token_name(expr)
acc = tokenize_text(buffer, acc)
final = {token, line, marker, Enum.reverse(expr)}
tokenize rest, new_line, [], [final | acc]
case expr(t, line, []) do
{:error, _, _} = error -> error
{:ok, expr, new_line, rest} ->
token = token_name(expr)
acc = tokenize_text(buffer, acc)
final = {token, line, marker, Enum.reverse(expr)}
tokenize rest, new_line, [], [final | acc]
end
end
defp tokenize('\n' ++ t, line, buffer, acc) do
@@ -49,7 +60,7 @@ defmodule EEx.Tokenizer do
end
defp tokenize([], _line, buffer, acc) do
tokenize_text(buffer, acc)
{:ok, Enum.reverse(tokenize_text(buffer, acc))}
end
# Retrieve marker for <%
@@ -64,20 +75,20 @@ defmodule EEx.Tokenizer do
# Tokenize an expression until we find %>
defp tokenize_expr([?%, ?>|t], line, buffer) do
{buffer, line, t}
defp expr([?%, ?>|t], line, buffer) do
{:ok, buffer, line, t}
end
defp tokenize_expr('\n' ++ t, line, buffer) do
tokenize_expr t, line + 1, [?\n|buffer]
defp expr('\n' ++ t, line, buffer) do
expr t, line + 1, [?\n|buffer]
end
defp tokenize_expr([h|t], line, buffer) do
tokenize_expr t, line, [h|buffer]
defp expr([h|t], line, buffer) do
expr t, line, [h|buffer]
end
defp tokenize_expr([], _line, _buffer) do
raise EEx.SyntaxError, message: "missing token: %>"
defp expr([], line, _buffer) do
{:error, line, "missing token '%>'"}
end
# Receive an expression content and check
+25 -24
View File
@@ -5,23 +5,26 @@ defmodule EEx.TokenizerTest do
require EEx.Tokenizer, as: T
test "simple chars lists" do
assert T.tokenize('foo', 1) == [ {:text, 'foo'} ]
assert T.tokenize('foo', 1) == {:ok, [{:text, 'foo'}]}
end
test "simple strings" do
assert T.tokenize("foo", 1) == [ {:text, 'foo'} ]
assert T.tokenize("foo", 1) == {:ok, [{:text, 'foo'}]}
end
test "strings with embedded code" do
assert T.tokenize('foo <% bar %>', 1) == [ {:text, 'foo '}, {:expr, 1, "", ' bar '} ]
assert T.tokenize('foo <% bar %>', 1) ==
{:ok, [{:text, 'foo '}, {:expr, 1, "", ' bar '}]}
end
test "strings with embedded equals code" do
assert T.tokenize('foo <%= bar %>', 1) == [ {:text, 'foo '}, {:expr, 1, "=", ' bar '} ]
assert T.tokenize('foo <%= bar %>', 1) ==
{:ok, [{:text, 'foo '}, {:expr, 1, "=", ' bar '}]}
end
test "strings with more than one line" do
assert T.tokenize('foo\n<%= bar %>', 1) == [ {:text, 'foo\n'}, {:expr, 2, "=", ' bar '} ]
assert T.tokenize('foo\n<%= bar %>', 1) ==
{:ok, [{:text, 'foo\n'}, {:expr, 2, "=", ' bar '}]}
end
test "strings with more than one line and expression with more than one line" do
@@ -32,50 +35,50 @@ baz %>
<% foo %>
'''
assert T.tokenize(string, 1) == [
assert T.tokenize(string, 1) == {:ok, [
{:text, 'foo '},
{:expr, 1, "=", ' bar\n\nbaz '},
{:text, '\n'},
{:expr, 4, "", ' foo '},
{:text, '\n'}
]
]}
end
test "quotation" do
assert T.tokenize('foo <%% true %>', 1) == [
assert T.tokenize('foo <%% true %>', 1) == {:ok, [
{:text, 'foo <% true %>'}
]
]}
end
test "quotation with do/end" do
assert T.tokenize('foo <%% true do %>bar<%% end %>', 1) == [
assert T.tokenize('foo <%% true do %>bar<%% end %>', 1) == {:ok, [
{:text, 'foo <% true do %>bar<% end %>'}
]
]}
end
test "comments" do
assert T.tokenize('foo <%# true %>', 1) == [
assert T.tokenize('foo <%# true %>', 1) == {:ok, [
{:text, 'foo '}
]
]}
end
test "comments with do/end" do
assert T.tokenize('foo <%# true do %>bar<%# end %>', 1) == [
assert T.tokenize('foo <%# true do %>bar<%# end %>', 1) == {:ok, [
{:text, 'foo bar'}
]
]}
end
test "strings with embedded do end" do
assert T.tokenize('foo <% if true do %>bar<% end %>', 1) == [
assert T.tokenize('foo <% if true do %>bar<% end %>', 1) == {:ok, [
{:text, 'foo '},
{:start_expr, 1, "", ' if true do '},
{:text, 'bar'},
{:end_expr, 1, "", ' end '}
]
]}
end
test "strings with embedded -> end" do
assert T.tokenize('foo <% cond do %><% false -> %>bar<% true -> %>baz<% end %>', 1) == [
assert T.tokenize('foo <% cond do %><% false -> %>bar<% true -> %>baz<% end %>', 1) == {:ok, [
{:text, 'foo '},
{:start_expr, 1, "", ' cond do '},
{:middle_expr, 1, "", ' false -> '},
@@ -83,23 +86,21 @@ baz %>
{:middle_expr, 1, "", ' true -> '},
{:text, 'baz'},
{:end_expr, 1, "", ' end '}
]
]}
end
test "strings with embedded keywords blocks" do
assert T.tokenize('foo <% if true do %>bar<% else %>baz<% end %>', 1) == [
assert T.tokenize('foo <% if true do %>bar<% else %>baz<% end %>', 1) == {:ok, [
{:text, 'foo '},
{:start_expr, 1, "", ' if true do '},
{:text, 'bar'},
{:middle_expr, 1, "", ' else '},
{:text, 'baz'},
{:end_expr, 1, "", ' end '}
]
]}
end
test "raise syntax error when there is start mark and no end mark" do
assert_raise EEx.SyntaxError, "missing token: %>", fn ->
T.tokenize('foo <% :bar', 1)
end
assert T.tokenize('foo <% :bar', 1) == {:error, 1, "missing token '%>'"}
end
end
+19 -7
View File
@@ -111,25 +111,25 @@ defmodule EExTest do
end
test "raises a syntax error when the token is invalid" do
assert_raise EEx.SyntaxError, "missing token: %>", fn ->
assert_raise EEx.SyntaxError, "nofile:1: missing token '%>'", fn ->
EEx.compile_string "foo <%= bar"
end
end
test "raises a syntax error when end expression is found without a start expression" do
assert_raise EEx.SyntaxError, "unexpected token: ' end ' at line 1", fn ->
assert_raise EEx.SyntaxError, "nofile:1: unexpected token ' end '", fn ->
EEx.compile_string "foo <% end %>"
end
end
test "raises a syntax error when start expression is found without an end expression" do
assert_raise EEx.SyntaxError, "unexpected end of string. expecting a closing <% end %>.", fn ->
EEx.compile_string "foo <% if true do %>"
assert_raise EEx.SyntaxError, "nofile:2: unexpected end of string, expected a closing '<% end %>'", fn ->
EEx.compile_string "foo\n<% if true do %>"
end
end
test "raises a syntax error when nested end expression is found without an start expression" do
assert_raise EEx.SyntaxError, "unexpected token: ' end ' at line 1", fn ->
test "raises a syntax error when nested end expression is found without a start expression" do
assert_raise EEx.SyntaxError, "nofile:1: unexpected token ' end '", fn ->
EEx.compile_string "foo <% if true do %><% end %><% end %>"
end
end
@@ -252,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
@@ -287,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 %>
+1 -1
View File
@@ -201,7 +201,7 @@ defmodule Application do
`:applications` in the `.app` file in case they were not previously
started.
"""
@spec ensure_all_started(app, start_type) :: {:ok, [app]} | {:error, term}
@spec ensure_all_started(app, start_type) :: {:ok, [app]} | {:error, {app, term}}
def ensure_all_started(app, type \\ :temporary) when is_atom(app) do
:application.ensure_all_started(app, type)
end
+5 -13
View File
@@ -51,16 +51,7 @@ defmodule Code do
:code.del_path(to_char_list(Path.expand path))
end
@doc """
Re-add paths given to the command line to keep their position
on the overall code path.
Some tools may change the code path by prepending new items but
still want the paths given by the user to have higher priority.
Calling this function guarantees the paths are re-added on
top of the user given ones.
"""
@spec readd_paths() :: :ok
@doc false
def readd_paths() do
{pa, pz} = :elixir_code_server.call(:paths)
:code.add_pathsa(pa)
@@ -260,7 +251,7 @@ defmodule Code do
Accepts `relative_to` as an argument to tell where the file is located.
While `load_file` loads a file and returns the loaded modules and their
byte code, `eval_file` simply evalutes the file contents and returns the
byte code, `eval_file` simply evaluates the file contents and returns the
evaluation result and its bindings.
"""
def eval_file(file, relative_to \\ nil) do
@@ -325,7 +316,7 @@ defmodule Code do
Check `compiler_options/1` for more information.
"""
def compiler_options do
:elixir_code_server.call :compiler_options
:elixir_config.get :compiler_options
end
@doc """
@@ -364,7 +355,8 @@ defmodule Code do
bad = bad |> Keyword.keys |> Enum.join(", ")
raise ArgumentError, message: "unknown compiler options: #{bad}"
end
:elixir_code_server.cast {:compiler_options, opts}
update = &:orddict.merge(fn(_, _, value) -> value end, &1, opts)
:elixir_config.update :compiler_options, update
end
@doc """
+3 -43
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,10 +57,6 @@ defimpl Collectable, for: List do
end
defimpl Collectable, for: BitString do
def empty(_bitstring) do
""
end
def into(original) do
{original, fn
acc, {:cont, x} when is_bitstring(x) -> [acc|x]
@@ -92,21 +66,7 @@ defimpl Collectable, for: BitString do
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)
+20 -32
View File
@@ -341,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)
@@ -593,11 +593,11 @@ defmodule Enum do
"""
@spec fetch(t, integer) :: {:ok, element} | :error
def fetch(collection, n) when is_list(collection) and n >= 0 do
def fetch(collection, n) when is_list(collection) and is_integer(n) and n >= 0 do
do_fetch(collection, n)
end
def fetch(collection, n) when n >= 0 do
def fetch(collection, n) when is_integer(n) and n >= 0 do
res =
Enumerable.reduce(collection, {:cont, 0}, fn(entry, acc) ->
if acc == n do
@@ -613,7 +613,7 @@ defmodule Enum do
end
end
def fetch(collection, n) when n < 0 do
def fetch(collection, n) when is_integer(n) and n < 0 do
do_fetch(reverse(collection), abs(n + 1))
end
@@ -1261,6 +1261,15 @@ defmodule Enum do
:lists.foldl(fun, acc, collection)
end
def reduce(%{__struct__: _} = collection, acc, fun) do
Enumerable.reduce(collection, {:cont, acc},
fn x, acc -> {:cont, fun.(x, acc)} end) |> elem(1)
end
def reduce(%{} = collection, acc, fun) do
:maps.fold(fn k, v, acc -> fun.({k, v}, acc) end, acc, collection)
end
def reduce(collection, acc, fun) do
Enumerable.reduce(collection, {:cont, acc},
fn x, acc -> {:cont, fun.(x, acc)} end) |> elem(1)
@@ -1722,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
@@ -1798,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.
+44 -8
View File
@@ -564,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]
@@ -613,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
@@ -664,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
@@ -763,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
@@ -772,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
@@ -800,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
+33 -17
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.
@@ -126,12 +132,12 @@ defmodule File do
Typical error reasons are:
* `:eacces` - missing search or write permissions for the parent
directories of `path`
directories of `path`
* `:eexist` - there is already a file or directory named `path`
* `:enoent` - a component of `path` does not exist
* `:enospc` - there is a no space left on the device
* `:enotdir` - a component of `path` is not a directory;
on some platforms, `:enoent` is returned instead
on some platforms, `:enoent` is returned instead
"""
@spec mkdir(Path.t) :: :ok | {:error, posix}
def mkdir(path) do
@@ -612,7 +618,7 @@ defmodule File do
Check `File.open/2` for other available options.
"""
@spec write(Path.t, iodata, list) :: :ok | {:error, posix}
@spec write(Path.t, iodata, [mode]) :: :ok | {:error, posix}
def write(path, content, modes \\ []) do
F.write_file(IO.chardata_to_string(path), content, modes)
end
@@ -620,7 +626,7 @@ defmodule File do
@doc """
Same as `write/3` but raises an exception if it fails, returns `:ok` otherwise.
"""
@spec write!(Path.t, iodata, list) :: :ok | no_return
@spec write!(Path.t, iodata, [mode]) :: :ok | no_return
def write!(path, content, modes \\ []) do
path = IO.chardata_to_string(path)
case F.write_file(path, content, modes) do
@@ -902,7 +908,8 @@ defmodule File do
File.close(file)
"""
@spec open(Path.t, list) :: {:ok, io_device} | {:error, posix}
@spec open(Path.t, [mode | :ram]) :: {:ok, io_device} | {:error, posix}
@spec open(Path.t, (io_device -> res)) :: {:ok, res} | {:error, posix} when res: var
def open(path, modes \\ [])
def open(path, modes) when is_list(modes) do
@@ -935,7 +942,7 @@ defmodule File do
end)
"""
@spec open(Path.t, list, (io_device -> res)) :: {:ok, res} | {:error, posix} when res: var
@spec open(Path.t, [mode | :ram], (io_device -> res)) :: {:ok, res} | {:error, posix} when res: var
def open(path, modes, function) do
case open(path, modes) do
{:ok, device} ->
@@ -953,7 +960,7 @@ defmodule File do
Returns the `io_device` otherwise.
"""
@spec open!(Path.t, list) :: io_device | no_return
@spec open!(Path.t, [mode]) :: io_device | no_return
def open!(path, modes \\ []) do
path = IO.chardata_to_string(path)
case open(path, modes) do
@@ -968,7 +975,7 @@ defmodule File do
Returns the function result otherwise.
"""
@spec open!(Path.t, list, (io_device -> res)) :: res | no_return when res: var
@spec open!(Path.t, [mode | :ram], (io_device -> res)) :: res | no_return when res: var
def open!(path, modes, function) do
path = IO.chardata_to_string(path)
case open(path, modes, function) do
@@ -989,18 +996,27 @@ defmodule File do
@spec cwd() :: {:ok, binary} | {:error, posix}
def cwd() do
case F.get_cwd do
{:ok, base} -> {:ok, IO.chardata_to_string(base)}
{:ok, base} -> {:ok, IO.chardata_to_string(fix_drive_letter(base))}
{:error, _} = error -> error
end
end
defp fix_drive_letter([l, ?:, ?/ | rest] = original) when l in ?A..?Z do
case :os.type() do
{:win32, _} -> [l+?a-?A, ?:, ?/ | rest]
_ -> original
end
end
defp fix_drive_letter(original), do: original
@doc """
The same as `cwd/0`, but raises an exception if it fails.
"""
@spec cwd!() :: binary | no_return
def cwd!() do
case F.get_cwd do
{:ok, cwd} -> IO.chardata_to_string(cwd)
case cwd() do
{:ok, cwd} -> cwd
{:error, reason} ->
raise File.Error, reason: reason, action: "get current working directory"
end
@@ -1124,7 +1140,7 @@ defmodule File do
Returns `:ok` on success, or `{:error, reason}`
on failure.
"""
@spec chmod(Path.t, integer) :: :ok | {:error, posix}
@spec chmod(Path.t, non_neg_integer) :: :ok | {:error, posix}
def chmod(path, mode) do
F.change_mode(IO.chardata_to_string(path), mode)
end
@@ -1132,7 +1148,7 @@ defmodule File do
@doc """
Same as `chmod/2`, but raises an exception in case of failure. Otherwise `:ok`.
"""
@spec chmod!(Path.t, integer) :: :ok | no_return
@spec chmod!(Path.t, non_neg_integer) :: :ok | no_return
def chmod!(path, mode) do
path = IO.chardata_to_string(path)
case chmod(path, mode) do
@@ -1147,7 +1163,7 @@ defmodule File do
for a given `file`. Returns `:ok` on success, or
`{:error, reason}` on failure.
"""
@spec chgrp(Path.t, integer) :: :ok | {:error, posix}
@spec chgrp(Path.t, non_neg_integer) :: :ok | {:error, posix}
def chgrp(path, gid) do
F.change_group(IO.chardata_to_string(path), gid)
end
@@ -1155,7 +1171,7 @@ defmodule File do
@doc """
Same as `chgrp/2`, but raises an exception in case of failure. Otherwise `:ok`.
"""
@spec chgrp!(Path.t, integer) :: :ok | no_return
@spec chgrp!(Path.t, non_neg_integer) :: :ok | no_return
def chgrp!(path, gid) do
path = IO.chardata_to_string(path)
case chgrp(path, gid) do
@@ -1170,7 +1186,7 @@ defmodule File do
for a given `file`. Returns `:ok` on success,
or `{:error, reason}` on failure.
"""
@spec chown(Path.t, integer) :: :ok | {:error, posix}
@spec chown(Path.t, non_neg_integer) :: :ok | {:error, posix}
def chown(path, uid) do
F.change_owner(IO.chardata_to_string(path), uid)
end
@@ -1178,7 +1194,7 @@ defmodule File do
@doc """
Same as `chown/2`, but raises an exception in case of failure. Otherwise `:ok`.
"""
@spec chown!(Path.t, integer) :: :ok | no_return
@spec chown!(Path.t, non_neg_integer) :: :ok | no_return
def chown!(path, uid) do
path = IO.chardata_to_string(path)
case chown(path, uid) do
+18 -54
View File
@@ -28,75 +28,39 @@ 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`.
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
+57 -7
View File
@@ -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 """
+22 -21
View File
@@ -271,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
@@ -377,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
+16 -49
View File
@@ -31,6 +31,10 @@ defmodule Inspect.Opts do
* `: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,
@@ -38,6 +42,7 @@ defmodule Inspect.Opts do
char_lists: :infer,
limit: 50,
width: 80,
base: :decimal,
pretty: false
@type t :: %__MODULE__{
@@ -46,6 +51,7 @@ defmodule Inspect.Opts do
char_lists: :infer | :as_lists | :as_char_lists,
limit: pos_integer,
width: pos_integer | :infinity,
base: :decimal | :binary | :hex | :octal,
pretty: boolean}
end
@@ -179,6 +185,8 @@ defmodule Inspect.Algebra do
Inspect.inspect(map, opts)
rescue
e ->
stacktrace = System.stacktrace
# Because we try to raise a nice error message in case
# we can't inspect a struct, there is a chance the error
# message itself relies on the struct being printed, so
@@ -192,9 +200,10 @@ defmodule Inspect.Algebra do
Process.put(:inspect_trap, true)
res = Inspect.Map.inspect(map, opts)
formatted = IO.iodata_to_binary(format(res, :infinity))
raise ArgumentError,
reraise ArgumentError,
"Got #{inspect e.__struct__} with message " <>
"\"#{Exception.message(e)}\" while inspecting #{formatted}"
"\"#{Exception.message(e)}\" while inspecting #{formatted}",
stacktrace
after
Process.delete(:inspect_trap)
end
@@ -428,24 +437,17 @@ defmodule Inspect.Algebra do
...> %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
@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
if is_integer(opts) do
IO.write :stderr, "Inspect.Algebra.surround_many/6 with an integer limit is deprecated, " <>
"please pass Inspect.Opts instead\n#{Exception.format_stacktrace}"
old_surround_many(left, docs, right, opts, fun, separator)
else
do_surround_many(left, docs, right, opts.limit, opts, fun, separator)
end
do_surround_many(left, docs, right, opts.limit, opts, fun, separator)
end
def do_surround_many(left, [], right, _, _opts, _fun, _) do
defp do_surround_many(left, [], right, _, _opts, _fun, _) do
concat(left, right)
end
def do_surround_many(left, docs, right, limit, _opts, fun, sep) do
defp do_surround_many(left, docs, right, limit, _opts, fun, sep) do
surround(left, do_surround_many(docs, limit, _opts, fun, sep), right)
end
@@ -473,36 +475,6 @@ defmodule Inspect.Algebra do
)
end
def old_surround_many(left, [], right, _, _fun, _) do
concat(left, right)
end
def old_surround_many(left, docs, right, limit, fun, sep) do
surround(left, old_surround_many(docs, limit, fun, sep), right)
end
defp old_surround_many(_, 0, _fun, _sep) do
"..."
end
defp old_surround_many([h], _limit, fun, _sep) do
fun.(h)
end
defp old_surround_many([h|t], limit, fun, sep) when is_list(t) do
glue(
concat(fun.(h), sep),
old_surround_many(t, decrement(limit), fun, sep)
)
end
defp old_surround_many([h|t], _limit, fun, _sep) do
glue(
concat(fun.(h), @tail_separator),
fun.(t)
)
end
defp decrement(:infinity), do: :infinity
defp decrement(counter), do: counter - 1
@@ -513,16 +485,11 @@ defmodule Inspect.Algebra do
and returns an IO data representation of the best layout for the
document to fit in the given width.
"""
@spec format(t, non_neg_integer | :infinity) :: binary
@spec format(t, non_neg_integer | :infinity) :: iodata
def format(d, w) do
format(w, 0, [{0, default_mode(w), doc_group(d)}])
end
@doc false
def pretty(d, w) do
format(d, w) |> IO.iodata_to_binary
end
defp default_mode(:infinity), do: :flat
defp default_mode(_), do: :break
+8 -6
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
@@ -132,7 +134,7 @@ defmodule Integer do
'3FF'
"""
@spec to_char_list(integer, pos_integer) :: char_list
@spec to_char_list(integer, 2..36) :: char_list
def to_char_list(number, base) do
:erlang.integer_to_list(number, base)
end
+7 -6
View File
@@ -123,8 +123,9 @@ defmodule IO do
end
end
@read_all_size 4096
defp do_binread_all(mapped_dev, acc) do
case :file.read_line(mapped_dev) do
case :file.read(mapped_dev, @read_all_size) do
{:ok, data} -> do_binread_all(mapped_dev, acc <> data)
:eof -> acc
other -> other
@@ -180,7 +181,7 @@ defmodule IO do
Inspects and writes the given argument to the device.
It enables pretty printing by default with width of
80 characters. Th width can be changed by explicitly
80 characters. The width can be changed by explicitly
passing the `:width` option.
## Examples
@@ -394,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
@@ -406,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
+5 -73
View File
@@ -7,10 +7,6 @@ defmodule IO.ANSI.Sequence do
"\e[#{unquote(code)}#{unquote(terminator)}"
end
defp escape_sequence(unquote(Atom.to_char_list(name))) do
unquote(name)()
end
defp format_sequence(unquote(name)) do
unquote(name)()
end
@@ -32,25 +28,17 @@ defmodule IO.ANSI do
@typep ansilist :: maybe_improper_list(char() | ansicode() | binary() | ansilist(), binary() | ansicode() | [])
@type ansidata :: ansilist() | ansicode() | binary()
@doc false
def terminal?(device \\ :erlang.group_leader) do
!match?({:win32, _}, :os.type()) and
match?({:ok, _}, :io.columns(device))
end
@doc """
Checks if ANSI coloring is supported and enabled on this machine.
By default, ANSI is only enabled on UNIX machines. Enabling or
disabling ANSI escapes can be done by configuring the
`:ansi_enabled` value in the `:elixir` application.
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 enabled? :: boolean
def enabled? do
case Application.fetch_env(:elixir, :ansi_enabled) do
{:ok, boolean} when is_boolean(boolean) -> boolean
:error -> !match?({:win32, _}, :os.type())
end
Application.get_env(:elixir, :ansi_enabled, false)
end
@doc "Resets all attributes"
@@ -144,10 +132,6 @@ defmodule IO.ANSI do
@doc "Clear screen"
defsequence :clear, "2", "J"
defp escape_sequence(other) do
raise ArgumentError, "invalid ANSI sequence specification: #{other}"
end
defp format_sequence(other) do
raise ArgumentError, "invalid ANSI sequence specification: #{other}"
end
@@ -222,56 +206,4 @@ defmodule IO.ANSI do
defp do_format([], [], acc, _emit, _append_reset) do
acc
end
@doc false
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
end
@doc false
def escape_fragment(string, emit \\ terminal?) when is_binary(string) and is_boolean(emit) do
{escaped, _emitted} = do_escape(string, emit, false, nil, [])
escaped
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
end
defp do_escape(<<h, t :: binary>>, emit, emitted, buffer, acc) when is_list(buffer) do
do_escape(t, emit, emitted, [h|buffer], acc)
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}"
end
defp do_escape(<<?%, ?{, t :: binary>>, emit, emitted, nil, acc) do
do_escape(t, emit, emitted, [], acc)
end
defp do_escape(<<h, t :: binary>>, emit, emitted, nil, acc) do
do_escape(t, emit, emitted, nil, [h|acc])
end
defp do_escape(<<>>, _emit, emitted, nil, acc) do
{IO.iodata_to_binary(Enum.reverse(acc)), emitted}
end
end
+174 -143
View File
@@ -25,10 +25,10 @@ defmodule IO.ANSI.Docs do
[enabled: true,
doc_bold: [:bright],
doc_code: [:cyan, :bright],
doc_headings: [:yellow, :bright],
doc_headings: [:yellow],
doc_inline_code: [:cyan],
doc_table_heading: [:reverse],
doc_title: [:reverse, :yellow, :bright],
doc_title: [:reverse, :yellow],
doc_underline: [:underline],
width: 80]
end
@@ -59,56 +59,63 @@ 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(all=[line | rest], indent, options) do
{stripped, count} = strip_spaces(line, 0)
if is_table_line?(stripped) && length(rest) > 0 && is_table_line?(hd(rest)) do
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)
<<bullet, ?\s, item :: binary>> when bullet in @bullets ->
write_text(text, indent, options)
process_list("• ", item, rest, count, indent, options)
<<d1, ?., ?\s, item :: binary>> when d1 in ?0..?9 ->
write_text(text, indent, options)
process_list(<<d1, ?., ?\s>>, item, rest, count, indent, options)
<<d1, d2, ?., ?\s, item :: binary>> when d1 in ?0..?9 and d2 in ?0..?9 ->
write_text(text, indent, options)
process_list(<<d1, d2, ?., ?\s>>, item, rest, count, indent, options)
_ ->
process_text(rest, [line], indent, false, options)
process(rest, [stripped | text], indent, options)
end
end
end
defp strip_spaces(" " <> line, acc) do
strip_spaces(line, acc + 1)
end
defp strip_spaces(rest, acc) do
{rest, acc}
end
## Headings
defp write_h1(heading, options) do
@@ -128,82 +135,70 @@ defmodule IO.ANSI.Docs do
## 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: newline_after_block
unless no_wrap, do: newline_after_block()
end
## Code blocks
@@ -213,17 +208,17 @@ defmodule IO.ANSI.Docs do
end
# Blank line between code blocks
defp process_code([ "", " " <> line | rest ], code, indent, options) do
defp process_code(["", " " <> line | rest], code, indent, options) do
process_code(rest, [line, "" | code], indent, options)
end
defp process_code([ " " <> line | rest ], code, indent, options) do
defp process_code([" " <> line | rest], code, indent, options) do
process_code(rest, [line|code], indent, options)
end
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
@@ -237,7 +232,7 @@ defmodule IO.ANSI.Docs do
{table, rest} = Enum.split_while(lines, &is_table_line?/1)
table_lines(table, options)
newline_after_block
process(rest, indent, options)
process(rest, [], indent, options)
end
defp table_lines(lines, options) do
@@ -267,13 +262,8 @@ defmodule IO.ANSI.Docs do
col = col
|> String.replace(~r/\\ \|/x, "|")
|> handle_links
|> handle_inline(nil, [], [], options)
len = col
|> String.replace(~r/\e\[\d+m/, "")
|> String.length
{col, len}
|> handle_inline(options)
{col, length_without_escape(col, 0)}
end
defp pad_to_number_of_columns(cols, col_count),
@@ -319,15 +309,20 @@ defmodule IO.ANSI.Docs do
end
end
## Helpers
@table_line_re ~r'''
defp is_table_line?(line) do
Regex.match?(~r'''
( ^ \s{0,3} \| (?: [^|]+ \|)+ \s* $ )
|
(\s \| \s)
'''x
'''x, line)
end
defp is_table_line?(line), do: Regex.match?(@table_line_re, line)
## 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]
@@ -375,7 +370,7 @@ defmodule IO.ANSI.Docs do
defp length_without_escape(rest, count) do
case String.next_grapheme(rest) do
{_, rest} -> length_without_escape(rest, count + 1)
{_, rest} -> length_without_escape(rest, count + 1)
nil -> count
end
end
@@ -388,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
@@ -399,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)
@@ -479,11 +515,6 @@ defmodule IO.ANSI.Docs do
defp color(style, colors) do
color = colors[style]
if is_binary(color) do
IO.puts :stderr, "warning: ANSI colors as strings is deprecated, " <>
"they now must be a list of atoms, got #{inspect color} for #{inspect style}"
color = String.split(color, ",") |> Enum.map(&String.to_atom/1)
end
IO.ANSI.format_fragment(color, colors[:enabled])
end
+1 -1
View File
@@ -57,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
+65 -33
View File
@@ -1386,7 +1386,14 @@ defmodule Kernel do
iex> "abcd" =~ "ad"
false
iex> "abcd" =~ ""
true
"""
@spec (String.t =~ (String.t | Regex.t)) :: boolean
def left =~ "" when is_binary(left), do: true
def left =~ right when is_binary(left) and is_binary(right) do
:binary.match(left, right) != :nomatch
end
@@ -1423,6 +1430,18 @@ defmodule Kernel do
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
must start with `#`. For example, inspecting a function will return:
@@ -1631,7 +1650,7 @@ defmodule Kernel do
iex> users = [%{name: "john", age: 27}, %{name: "meg", age: 23}]
iex> all = fn :get_and_update, data, next ->
...> Enum.map(data, next) |> List.unzip() |> List.to_tuple()
...> Enum.map(data, next) |> :lists.unzip
...> end
iex> get_and_update_in(users, [all, :age], &{&1, &1 + 1})
{[27, 23], [%{name: "john", age: 28}, %{name: "meg", age: 24}]}
@@ -1801,7 +1820,9 @@ defmodule Kernel do
end
defp unnest({{:., _, [expr, key]}, _, []}, acc, kind)
when is_tuple(expr) and elem(expr, 0) != :__aliases__ and elem(expr, 0) != :__MODULE__ do
when is_tuple(expr) and
:erlang.element(1, expr) != :__aliases__ and
:erlang.element(1, expr) != :__MODULE__ do
unnest(expr, [{:map, key}|acc], kind)
end
@@ -1822,8 +1843,8 @@ defmodule Kernel do
defp proper_start?({{:., _, [expr, _]}, _, _args})
when is_atom(expr)
when elem(expr, 0) == :__aliases__
when elem(expr, 0) == :__MODULE__, do: true
when :erlang.element(1, expr) == :__aliases__
when :erlang.element(1, expr) == :__MODULE__, do: true
defp proper_start?({atom, _, _args})
when is_atom(atom), do: true
@@ -1869,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
@@ -2001,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 ->
@@ -2022,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
[] -> []
@@ -2038,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
@@ -2360,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
@@ -2889,31 +2917,35 @@ defmodule Kernel do
structure is public should use `@type`.
"""
defmacro defstruct(fields) do
fields =
quote bind_quoted: [fields: fields] do
fields = :lists.map(fn
{key, _} = pair when is_atom(key) -> pair
key when is_atom(key) -> {key, nil}
other -> raise ArgumentError, "struct field names must be atoms, got: #{inspect other}"
end, fields)
quote bind_quoted: [fields: fields] do
fields = :lists.map(fn
{key, val} when is_atom(key) ->
try do
Macro.escape(val)
rescue
e in [ArgumentError] ->
raise ArgumentError, "invalid value for struct field #{key}, " <> Exception.message(e)
else
_ -> {key, val}
end
key when is_atom(key) ->
{key, nil}
other ->
raise ArgumentError, "struct field names must be atoms, got: #{inspect other}"
end, fields)
@struct :maps.put(:__struct__, __MODULE__, :maps.from_list(fields))
@struct :maps.put(:__struct__, __MODULE__, :maps.from_list(fields))
case Module.get_attribute(__MODULE__, :derive) do
[] ->
:ok
derive ->
Protocol.__derive__(derive, __MODULE__, __ENV__)
end
case Module.get_attribute(__MODULE__, :derive) do
[] -> :ok
derive -> Protocol.__derive__(derive, __MODULE__, __ENV__)
end
@spec __struct__() :: %__MODULE__{}
def __struct__() do
@struct
end
@spec __struct__() :: %__MODULE__{}
def __struct__() do
@struct
end
quote do
unquote(fields)
fields
end
end
+4 -3
View File
@@ -56,7 +56,7 @@ defmodule Kernel.CLI do
## Helpers
defp at_exit(res) do
hooks = :elixir_code_server.call(:flush_at_exit)
hooks = :elixir_config.get_and_put(:at_exit, [])
res = Enum.reduce(hooks, res, &exec_fun/2)
if hooks == [], do: res, else: at_exit(res)
end
@@ -113,7 +113,8 @@ defmodule Kernel.CLI do
IO.puts :stderr, Exception.format(kind, reason, prune_stacktrace(trace))
end
@elixir_internals [:elixir_compiler, :elixir_module, :elixir_translator, :elixir_expand]
@elixir_internals [:elixir, :elixir_exp, :elixir_compiler, :elixir_module, :elixir_translator,
:elixir_expand, :elixir_lexical]
defp prune_stacktrace([{mod, _, _, _}|t]) when mod in @elixir_internals do
prune_stacktrace(t)
@@ -408,7 +409,7 @@ defmodule Kernel.CLI do
end
defp wrapper(fun) do
fun.()
_ = fun.()
:ok
end
+45 -40
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.match(ets, {:"$1", :_, :_}) |> List.flatten
else
[]
end
ets = :gen_server.call(to_pid(arg), :ets, @timeout)
:ets.match(ets, {:"$1", :_, :_}) |> List.flatten
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,65 +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
@doc false
def handle_call(:ets, _from, d) do
{:reply, d, d}
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}
end
def handle_info(_msg, d) do
{:noreply, d}
def handle_cast(msg, {d, dest}) do
{:stop, {:bad_cast, msg}, {d, dest}}
end
@doc false
def terminate(_reason, _d) do
def handle_info(_msg, {d, dest}) do
{:noreply, {d, dest}}
end
@doc false
def terminate(_reason, _state) do
:ok
end
@doc false
def code_change(_old, d, _extra) do
{:ok, d}
def code_change(_old, state, _extra) do
{:ok, state}
end
# Callbacks helpers
+45 -34
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,32 +23,36 @@ 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
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
@@ -59,21 +63,21 @@ defmodule Kernel.ParallelCompiler do
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} =
@@ -90,40 +94,40 @@ defmodule Kernel.ParallelCompiler 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,20 +176,26 @@ 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
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)
+147 -112
View File
@@ -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,103 +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>>
<<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:
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.:
iex> <<val, _rest :: binary>> = <<-100, "foo">>
iex> val
156
Here, `val` is interpreted as unsigned.
The endianness of a segment can be big, little or native (the
latter meaning it will be resolved at VM load time). Many options
can be given by using `-` as separator:
Many options can be given by using `-` as separator. Order is
arbitrary, so the following are all equivalent:
<<102 :: integer-native, rest :: binary>>
Or:
<<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>>
And so on.
### Unit and Size
Endianness only makes sense for integers and some UTF code
point types (utf16 and utf32).
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`).
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.
Type | Default Unit
--------- | ------------
`integer` | 1 bit
`float` | 1 bit
`binary` | 8 bits
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.
Sizes for types are a bit more nuanced. The default size for integers is 8.
iex> <<102, _rest :: size(16)>> = "foo"
"foo"
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.
We can also match by specifying size and unit explicitly:
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> <<102, _rest :: size(2)-unit(8)>> = "foo"
"foo"
iex> <<name::binary-size(5), " the ", species::binary>> = <<"Frank the Walrus">>
"Frank the Walrus"
iex> {name, species}
{"Frank", "Walrus"}
However, if we expect a size of 32, it won't match:
Failing to specify the size for the non-last causes compilation to fail:
iex> <<102, _rest :: size(32)>> = "foo"
** (MatchError) no match of right hand side value: "foo"
<<name::binary, " the ", species::binary>> = <<"Frank the Walrus">>
** (CompileError): a binary field without size is only allowed at the end of a binary pattern
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).
#### Shortcut Syntax
Size and unit can also be specified using a syntax shortcut
when passing integer values:
<< x :: 8 >> == << x :: size(8) >>
<< x :: 8 * 4 >> == << x :: size(8)-unit(4) >>
<< x :: _ * 4 >> == << x :: unit(4) >>
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.
For floats, `size * unit` must result in 32 or 64, corresponding
to binary32 and binary64, respectively.
### 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>>
### Endianness
Elixir has three options for endianness: `big`, `little`, and `native`.
The default is `big`. `native` is determined by the VM at startup.
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
## Binary/Bitstring Matching
Binary matching is a powerful feature in Elixir that is useful for extracting
information from binaries as well as pattern matching.
Binary matching can be used by itself to extract information from binaries:
iex> <<"Hello, ", place::binary>> = "Hello, World"
"Hello, World"
iex> place
"World"
Or as a part of function definitions to pattern match:
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)>>
def type(<<@png_signature, rest::binary>>), do: :png
def type(<<@jpg_signature, rest::binary>>), do: :jpg
def type(_), do :unknown
end
### Performance & Optimizations
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:
ERL_COMPILER_OPTIONS=bin_opt_info mix compile
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)
@@ -343,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
@@ -1112,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:
@@ -1327,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)
+42 -8
View File
@@ -644,6 +644,10 @@ defmodule Kernel.Typespec do
[]
end
defp typespec_to_ast({:user_type, line, name, args}) do
typespec_to_ast({:type, line, name, args})
end
defp typespec_to_ast({:type, line, :tuple, :any}) do
{:tuple, [line: line], []}
end
@@ -677,8 +681,13 @@ defmodule Kernel.Typespec do
end
defp typespec_to_ast({:type, line, :map, fields}) do
fields = Enum.map fields, fn {:type, _, :map_field_assoc, k, v} ->
{typespec_to_ast(k), typespec_to_ast(v)}
fields = Enum.map fields, fn
# OTP 18
{:type, _, :map_field_assoc, [k, v]} ->
{typespec_to_ast(k), typespec_to_ast(v)}
# OTP 17
{:type, _, :map_field_assoc, k, v} ->
{typespec_to_ast(k), typespec_to_ast(v)}
end
{struct, fields} = Keyword.pop(fields, :__struct__)
@@ -821,9 +830,17 @@ defmodule Kernel.Typespec do
## Handle maps and structs
defp typespec({:%{}, meta, fields}, vars, caller) do
fields = :lists.map(fn {k, v} ->
{:type, line(meta), :map_field_assoc, typespec(k, vars, caller), typespec(v, vars, caller)}
end, fields)
fields =
if :erlang.system_info(:otp_release) >= '18' do
:lists.map(fn {k, v} ->
{:type, line(meta), :map_field_assoc, [typespec(k, vars, caller), typespec(v, vars, caller)]}
end, fields)
else
:lists.map(fn {k, v} ->
{:type, line(meta), :map_field_assoc, typespec(k, vars, caller), typespec(v, vars, caller)}
end, fields)
end
{:type, line(meta), :map, fields}
end
@@ -884,7 +901,8 @@ defmodule Kernel.Typespec do
# Handle ranges
defp typespec({:.., meta, args}, vars, caller) do
typespec({:range, meta, args}, vars, caller)
args = for arg <- args, do: typespec(arg, vars, caller)
{:type, line(meta), :range, args}
end
# Handle special forms
@@ -968,9 +986,21 @@ defmodule Kernel.Typespec do
typespec((quote do: :elixir.as_boolean(unquote(arg))), vars, caller)
end
defp typespec({:fun, meta, args}, vars, caller) do
args = for arg <- args, do: typespec(arg, vars, caller)
{:type, line(meta), :fun, args}
end
defp typespec({name, meta, arguments}, vars, caller) do
arguments = for arg <- arguments, do: typespec(arg, vars, caller)
{:type, line(meta), name, arguments}
if :erlang.system_info(:otp_release) >= '18' do
arity = length(arguments)
type = if :erl_internal.is_type(name, arity), do: :type, else: :user_type
{type, line(meta), name, arguments}
else
{:type, line(meta), name, arguments}
end
end
# Handle literals
@@ -998,7 +1028,7 @@ defmodule Kernel.Typespec do
typespec({: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]}
@@ -1006,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
+1 -1
View File
@@ -136,7 +136,7 @@ defmodule Keyword do
:error
"""
@spec fetch(t, key) :: {:ok, value}
@spec fetch(t, key) :: {:ok, value} | :error
def fetch(keywords, key) when is_list(keywords) and is_atom(key) do
case :lists.keyfind(key, 1, keywords) do
{^key, value} -> {:ok, value}
+2 -22
View File
@@ -247,7 +247,7 @@ defmodule List do
[a: 1, b: 2, c: 3]
"""
@spec keystore([tuple], any, non_neg_integer, tuple) :: [tuple]
@spec keystore([tuple], any, non_neg_integer, tuple) :: [tuple, ...]
def keystore(list, key, position, new_tuple) do
:lists.keystore(key, position + 1, list, new_tuple)
end
@@ -324,26 +324,6 @@ defmodule List do
do_zip(list_of_lists, [])
end
@doc """
Unzips the given list of tuples into a list of lists.
The number of unzipped elements is equal to the size
of the smallest tuple in the list.
## Examples
iex> List.unzip([{1, 2}, {3, 4}])
[[1, 3], [2, 4]]
iex> List.unzip([{1, :a, "apple"}, {2, :b, "banana"}, {3, :c}])
[[1, 2, 3], [:a, :b, :c]]
"""
@spec unzip([tuple]) :: [list]
def unzip(list) when is_list(list) do
:lists.map &Tuple.to_list/1, zip(list)
end
@doc """
Returns a list with `value` inserted at the specified `index`.
Note that `index` is capped at the list length. Negative indices
@@ -532,7 +512,7 @@ defmodule List do
1023
"""
@spec to_integer(char_list, non_neg_integer) :: integer
@spec to_integer(char_list, 2..36) :: integer
def to_integer(char_list, base) do
:erlang.list_to_integer(char_list, base)
end
+39 -3
View File
@@ -17,6 +17,7 @@ defmodule Macro do
:<, :>, :->,
:+, :-, :*, :/, :=, :|, :.,
:and, :or, :when, :in,
:~>>, :<<~, :~>, :<~, :<~>, :<|>,
:<<<, :>>>, :|||, :&&&, :^^^, :~~~]
@doc false
@@ -39,7 +40,8 @@ defmodule Macro do
o when o in [:&&, :&&&, :and] -> {:left, 140}
o when o in [:==, :!=, :=~, :===, :!==] -> {:left, 150}
o when o in [:<, :<=, :>=, :>] -> {:left, 160}
o when o in [:|>, :<<<, :>>>] -> {:left, 170}
o when o in [:|>, :<<<, :>>>, :<~, :~>,
:<<~, :~>>, :<~>, :<|>] -> {:left, 170}
:in -> {:left, 180}
o when o in [:++, :--, :.., :<>] -> {:right, 200}
o when o in [:+, :-] -> {:left, 210}
@@ -305,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.
@@ -797,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 ->
@@ -903,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)
+18 -3
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
@@ -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
+33 -50
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)
:elixir_def_overridable.overridable(module, merged)
end
end
end, tuples)
end
@doc """
Returns `true` if `tuple` in `module` is marked as overridable.
"""
def overridable?(module, tuple) do
!!List.keyfind(get_attribute(module, :__overridable), tuple, 0)
!!List.keyfind(:elixir_def_overridable.overridable(module), tuple, 0)
end
@doc """
@@ -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
@@ -842,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
@@ -904,14 +893,14 @@ defmodule Module do
end
@doc false
def get_attribute(module, key, warn) when is_atom(key) and (is_list(warn) or nil?(warn)) do
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, :__acc_attributes, 2)
acc = :ets.lookup_element(table, {:elixir, :acc_attributes}, 2)
cond do
:lists.member(key, acc) ->
@@ -966,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
@@ -982,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
+4 -4
View File
@@ -83,7 +83,7 @@ defmodule Module.LocalsTracker do
defp to_pid(pid) when is_pid(pid), do: pid
defp to_pid(mod) when is_atom(mod) do
table = :elixir_module.data_table(mod)
[{_, val}] = :ets.lookup(table, :__locals_tracker)
[{_, val}] = :ets.lookup(table, {:elixir, :locals_tracker})
val
end
@@ -92,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
@@ -272,6 +271,7 @@ defmodule Module.LocalsTracker do
{:stop, {:bad_call, request}, state}
end
@doc false
def handle_info(_msg, state) do
{:noreply, state}
end
@@ -360,7 +360,7 @@ defmodule Module.LocalsTracker do
end
defp replace_edge!(d, from, to) do
unless :lists.member(to, :digraph.out_neighbours(d, from)) do
_ = unless :lists.member(to, :digraph.out_neighbours(d, from)) do
[:"$e"|_] = :digraph.add_edge(d, from, to)
end
:ok
+1 -1
View File
@@ -30,7 +30,7 @@ defmodule Node do
returns `{:error, :not_allowed}`. Returns `{:error, :not_found}` if the
local node is not alive.
"""
@spec stop() :: :ok | {:error, term}
@spec stop() :: :ok | {:error, :not_allowed | :not_found}
def stop() do
:net_kernel.stop()
end
+105 -75
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)
_ -> unix_pathtype(name)
end |> elem(1) |> IO.chardata_to_string
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
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,40 +463,25 @@ 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 ~S"""
Splits the path into a list at the path separator.
@@ -568,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
@@ -602,29 +627,34 @@ defmodule Path do
end
end
defp normalize(path), do: normalize(split(path), [])
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 normalize([".."|t], ["/"|_] = acc) do
normalize t, acc
defp expand_dot([".."|t], [_, _|acc]) do
expand_dot t, acc
end
defp normalize([".."|t], [<<letter, ?:, ?/>>|_] = acc) when letter in ?a..?z 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(["."|t], acc) do
normalize t, acc
defp expand_dot([], ["/"|acc]) do
IO.iodata_to_binary(:lists.reverse(acc))
end
defp normalize([h|t], acc) do
normalize t, [h|acc]
end
defp normalize([], acc) do
join :lists.reverse(acc)
defp major_os_type do
:os.type |> elem(0)
end
end
+1 -1
View File
@@ -345,7 +345,7 @@ defmodule Process do
See http://www.erlang.org/doc/man/erlang.html#process_flag-3 for more info.
"""
@spec flag(pid, process_flag, term) :: term
@spec flag(pid, :save_calls, non_neg_integer) :: non_neg_integer
def flag(pid, flag, value) do
:erlang.process_flag(pid, flag, value)
end
+15 -13
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
@@ -289,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
@@ -440,7 +441,7 @@ defmodule Protocol do
end
# Define the implementation for builtins.
for {guard, mod} <- builtin do
:lists.foreach(fn {guard, mod} ->
target = Module.concat(__MODULE__, mod)
Kernel.def impl_for(data) when :erlang.unquote(guard)(data) do
@@ -449,7 +450,7 @@ defmodule Protocol do
false -> any_impl_for
end
end
end
end, builtin)
@spec impl_for!(term) :: atom() | no_return()
Kernel.def impl_for!(data) do
@@ -609,16 +610,17 @@ defmodule Protocol do
@doc false
def __spec__?(module, name, arity) do
signature = {name, arity}
specs = Module.get_attribute(module, :spec)
specs = Module.get_attribute(module, :spec)
found =
for {:spec, expr, caller} <- specs,
Kernel.Typespec.spec_to_signature(expr) == signature do
Kernel.Typespec.define_spec(:callback, expr, caller)
true
end
:lists.map(fn {:spec, expr, caller} ->
if Kernel.Typespec.spec_to_signature(expr) == signature do
Kernel.Typespec.define_spec(:callback, expr, caller)
true
end
end, specs)
found != []
:lists.any(& &1 == true, found)
end
## Helpers
+22 -12
View File
@@ -4,7 +4,7 @@ defmodule Record do
Records are simply tuples where the first element is an atom:
iex> Record.record? {User, "john", 27}
iex> Record.is_record {User, "john", 27}
true
This module provides conveniences for working with records at
@@ -64,11 +64,11 @@ defmodule Record do
## Examples
iex> record = {User, "john", 27}
iex> Record.record?(record, User)
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
@@ -92,14 +92,14 @@ defmodule Record do
## Examples
iex> record = {User, "john", 27}
iex> Record.record?(record)
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
@@ -160,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__)
@@ -178,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__)
@@ -194,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
@@ -309,7 +319,7 @@ defmodule Record do
# Returns a keyword list of the record
@doc false
def __keyword__(atom, fields, record) do
if record?(record, atom) do
if is_record(record, atom) do
[_tag|values] = Tuple.to_list(record)
join_keyword(fields, values, [])
else
+7 -6
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 ->
@@ -80,11 +81,11 @@ defmodule Record.Extractor do
defp eval_record(cons, form) do
form = form ++
[ {:function, 0, :hello, 0, [
{:clause, 0, [], [], [ cons ]} ]} ]
[{:function, 0, :hello, 0, [
{:clause, 0, [], [], [cons]}]}]
{:function, 0, :hello, 0, [
{:clause, 0, [], [], [ record_ast ]} ]} = :erl_expand_records.module(form, []) |> List.last
{:clause, 0, [], [], [record_ast]}]} = :erl_expand_records.module(form, []) |> List.last
{:value, record, _} = :erl_eval.expr(record_ast, [])
record
+22 -5
View File
@@ -78,7 +78,7 @@ defmodule Regex do
defstruct re_pattern: nil, source: "", opts: ""
@opaque t :: %__MODULE__{re_pattern: term, source: binary, opts: binary}
@type t :: %__MODULE__{re_pattern: term, source: binary, opts: binary}
defmodule CompileError do
defexception message: "regex could not be compiled"
@@ -133,6 +133,7 @@ defmodule Regex do
Compiles the regular expression according to the given options.
Fails with `Regex.CompileError` if the regex cannot be compiled.
"""
@spec compile(binary, binary | [term]) :: t
def compile!(source, options \\ "") do
case compile(source, options) do
{:ok, regex} -> regex
@@ -152,6 +153,7 @@ defmodule Regex do
false
"""
@spec match?(t, String.t) :: boolean
def match?(%Regex{re_pattern: compiled}, string) when is_binary(string) do
:re.run(string, compiled, [{:capture, :none}]) == :match
end
@@ -168,6 +170,8 @@ defmodule Regex do
false
"""
@spec regex?(t) :: true
@spec regex?(any) :: false
def regex?(%Regex{}), do: true
def regex?(_), do: false
@@ -193,6 +197,7 @@ defmodule Regex do
[{2,2},{3,1}]
"""
@spec run(t, binary, [term]) :: nil | [binary] | [{integer, integer}]
def run(regex, string, options \\ [])
def run(%Regex{re_pattern: compiled}, string, options) when is_binary(string) do
@@ -223,6 +228,7 @@ defmodule Regex do
nil
"""
@spec named_captures(t, String.t, [term]) :: map | nil
def named_captures(regex, string, options \\ []) when is_binary(string) do
names = names(regex)
options = Keyword.put(options, :capture, names)
@@ -233,6 +239,7 @@ defmodule Regex do
@doc """
Returns the underlying `re_pattern` in the regular expression.
"""
@spec re_pattern(t) :: term
def re_pattern(%Regex{re_pattern: compiled}) do
compiled
end
@@ -246,6 +253,7 @@ defmodule Regex do
"foo"
"""
@spec source(t) :: String.t
def source(%Regex{source: source}) do
source
end
@@ -259,6 +267,7 @@ defmodule Regex do
"m"
"""
@spec opts(t) :: String.t
def opts(%Regex{opts: opts}) do
opts
end
@@ -272,6 +281,7 @@ defmodule Regex do
["foo"]
"""
@spec names(t) :: [String.t]
def names(%Regex{re_pattern: re_pattern}) do
{:namelist, names} = :re.inspect(re_pattern, :namelist)
names
@@ -301,6 +311,7 @@ defmodule Regex do
[]
"""
@spec scan(t, String.t, [term]) :: [[String.t]]
def scan(regex, string, options \\ [])
def scan(%Regex{re_pattern: compiled}, string, options) when is_binary(string) do
@@ -353,7 +364,7 @@ defmodule Regex do
["a", "c"]
"""
@spec split(t, String.t, [term]) :: [String.t]
def split(regex, string, options \\ [])
def split(%Regex{}, "", _opts), do: [""]
@@ -408,8 +419,8 @@ defmodule Regex do
The replacement can be either a string or a function. The string
is used as a replacement for every match and it allows specific
captures to be accessed via `\N`, where `N` is the capture. In
case `\0` is used, the whole match is inserted.
captures to be accessed via `\N` or `\g{N}`, where `N` is the
capture. In case `\0` is used, the whole match is inserted.
When the replacement is a function, the function may have arity
N where each argument maps to a capture, with the first argument
@@ -439,6 +450,7 @@ defmodule Regex do
"[b][d]"
"""
@spec replace(t, String.t, String.t | (... -> String.t), [term]) :: String.t
def replace(regex, string, replacement, options \\ [])
def replace(regex, string, replacement, options) when is_binary(replacement) do
@@ -467,6 +479,11 @@ defmodule Regex do
defp precompile_replacement(""),
do: []
defp precompile_replacement(<<?\\, ?g, ?{, rest :: binary>>) when byte_size(rest) > 0 do
{ns, <<?}, rest :: binary>>} = pick_int(rest)
[List.to_integer(ns) | precompile_replacement(rest)]
end
defp precompile_replacement(<<?\\, x, rest :: binary>>) when x < ?0 or x > ?9 do
case precompile_replacement(rest) do
[head | t] when is_binary(head) ->
@@ -538,7 +555,7 @@ defmodule Regex do
cond do
is_binary(part) ->
part
part > tuple_size(indexes) ->
part >= tuple_size(indexes) ->
""
true ->
get_index(string, elem(indexes, part))
+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
+149 -44
View File
@@ -156,18 +156,22 @@ defmodule Stream do
@spec chunk(Enumerable.t, non_neg_integer, non_neg_integer, Enumerable.t | nil) :: Enumerable.t
def chunk(enum, n, step, pad \\ nil) when n > 0 and step > 0 do
limit = :erlang.max(n, step)
lazy enum, {[], 0},
fn(f1) -> R.chunk(n, step, limit, f1) end,
fn(f1) -> &do_chunk(&1, n, pad, f1) end
if is_nil(pad) do
lazy enum, {[], 0}, fn(f1) -> R.chunk(n, step, limit, f1) end
else
lazy enum, {[], 0},
fn(f1) -> R.chunk(n, step, limit, f1) end,
&do_chunk(&1, n, pad, &2)
end
end
defp do_chunk(acc(h, {buffer, count} = old, t) = acc, n, pad, f1) do
if nil?(pad) || count == 0 do
{:cont, acc}
else
buffer = :lists.reverse(buffer) ++ Enum.take(pad, n - count)
cont_with_acc(f1, buffer, h, old, t)
end
defp do_chunk(acc(_, {_, 0}, _) = acc, _, _, _) do
{:cont, acc}
end
defp do_chunk(acc(h, {buffer, count} = old, t), n, pad, f1) do
buffer = :lists.reverse(buffer) ++ Enum.take(pad, n - count)
cont_with_acc(f1, buffer, h, old, t)
end
@doc """
@@ -186,7 +190,7 @@ defmodule Stream do
def chunk_by(enum, fun) do
lazy enum, nil,
fn(f1) -> R.chunk_by(fun, f1) end,
fn(f1) -> &do_chunk_by(&1, f1) end
&do_chunk_by(&1, &2)
end
defp do_chunk_by(acc(_, nil, _) = acc, _f1) do
@@ -338,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.
@@ -495,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
@@ -563,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.
@@ -577,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:
@@ -660,9 +699,11 @@ defmodule Stream do
next.({:halt, next_acc})
:erlang.raise(kind, reason, stacktrace)
else
{:halted, [:outer|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)
{:halted, [:inner|acc]} ->
{:halted, [_|acc]} ->
next.({:halt, next_acc})
{:halted, acc}
{:done, [_|acc]} ->
@@ -847,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)
@@ -934,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.
@@ -951,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)
@@ -974,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 ->
@@ -984,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
@@ -1029,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]}
@@ -1073,19 +1177,20 @@ defimpl Enumerable, for: Stream do
case reduce.({command, [acc|accs]}) do
{:suspended, [acc|accs], continuation} ->
{:suspended, acc, &do_each(continuation, done, accs, &1)}
{:halted, [acc|_]} ->
{:halted, acc}
{:done, [acc|_] = accs} ->
case done do
nil ->
{:done, acc}
{done, fun} ->
case done.(fun).(accs) do
{:cont, [acc|_]} -> {:done, acc}
{:halt, [acc|_]} -> {:halted, acc}
{:suspend, [acc|_]} -> {:suspended, acc, &({:done, elem(&1, 1)})}
end
end
{:halted, accs} ->
do_done {:halted, accs}, done
{:done, accs} ->
do_done {:done, accs}, done
end
end
defp do_done({reason, [acc|_]}, nil), do: {reason, acc}
defp do_done({reason, [acc|t]}, {done, fun}) do
[h|_] = Enum.reverse(t)
case done.([acc, h], fun) do
{:cont, [acc|_]} -> {reason, acc}
{:halt, [acc|_]} -> {:halted, acc}
{:suspend, [acc|_]} -> {:suspended, acc, &({:done, elem(&1, 1)})}
end
end
end
+10 -4
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
{:halt, orig}
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
+87 -26
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,6 +50,82 @@ 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
@@ -590,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
@@ -612,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
@@ -1363,7 +1424,7 @@ defmodule String do
1023
"""
@spec to_integer(String.t, pos_integer) :: integer
@spec to_integer(String.t, 2..36) :: integer
def to_integer(string, base) do
:erlang.binary_to_integer(string, base)
end
+3 -3
View File
@@ -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
+97 -16
View File
@@ -20,8 +20,8 @@ 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
@@ -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)))
+23 -20
View File
@@ -72,7 +72,7 @@ defmodule System do
"""
@spec argv() :: [String.t]
def argv do
:elixir_code_server.call :argv
:elixir_config.get(:argv)
end
@doc """
@@ -83,7 +83,7 @@ defmodule System do
"""
@spec argv([String.t]) :: :ok
def argv(args) do
:elixir_code_server.cast({:argv, args})
:elixir_config.put(:argv, args)
end
@doc """
@@ -94,11 +94,20 @@ defmodule System do
"""
def cwd do
case :file.get_cwd do
{:ok, base} -> IO.chardata_to_string(base)
{:ok, base} -> IO.chardata_to_string(fix_drive_letter(base))
_ -> nil
end
end
defp fix_drive_letter([l, ?:, ?/ | rest] = original) when l in ?A..?Z do
case :os.type() do
{:win32, _} -> [l+?a-?A, ?:, ?/ | rest]
_ -> original
end
end
defp fix_drive_letter(original), do: original
@doc """
Current working directory, exception on error.
@@ -215,16 +224,7 @@ defmodule System do
The function must receive the exit status code as an argument.
"""
def at_exit(fun) when is_function(fun, 1) do
:elixir_code_server.cast {:at_exit, fun}
end
@doc false
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))
:elixir_config.update :at_exit, &[fun|&1]
end
@doc """
@@ -431,18 +431,18 @@ defmodule System do
@spec cmd(binary, [binary], Keyword.t) ::
{Collectable.t, exit_status :: non_neg_integer}
def cmd(command, args, opts \\ []) when is_binary(command) and is_list(args) do
command = String.to_char_list(command)
cmd = String.to_char_list(command)
command =
if Path.type(command) == :absolute do
command
cmd =
if Path.type(cmd) == :absolute do
cmd
else
:os.find_executable(command) || command
:os.find_executable(cmd) || :erlang.error(:enoent, [command, args, opts])
end
{into, opts} = cmd_opts(opts, [:use_stdio, :exit_status, :binary, :hide, args: args], "")
{initial, fun} = Collectable.into(into)
do_cmd Port.open({:spawn_executable, command}, opts), initial, fun
do_cmd Port.open({:spawn_executable, cmd}, opts), initial, fun
end
defp do_cmd(port, acc, fun) do
@@ -463,9 +463,12 @@ defmodule System do
defp cmd_opts([{:arg0, bin}|t], opts, into) when is_binary(bin),
do: cmd_opts(t, [{:arg0, bin}|opts], into)
defp cmd_opts([{:stderr_to_stdout, bool}|t], opts, into) when is_boolean(bool),
defp cmd_opts([{:stderr_to_stdout, true}|t], opts, into),
do: cmd_opts(t, [:stderr_to_stdout|opts], into)
defp cmd_opts([{:stderr_to_stdout, false}|t], opts, into),
do: cmd_opts(t, opts, into)
defp cmd_opts([{:parallelism, bool}|t], opts, into) when is_boolean(bool),
do: cmd_opts(t, [{:parallelism, bool}|opts], into)
+27 -3
View File
@@ -77,8 +77,8 @@ defmodule Task do
supervisor(Task.Supervisor, [[name: :tasks_sup]])
]
Note that, when working with distributed tasks, one should use the `async/4` API,
that expects explicit module, function and arguments, instead of `async/2` that
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
@@ -119,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.
@@ -160,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
+4
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
+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
+69 -42
View File
@@ -11,18 +11,17 @@ defmodule URI do
import Bitwise
@ports %{
"ftp" => 21,
"http" => 80,
"https" => 443,
"ldap" => 389,
"sftp" => 22,
"tftp" => 69,
}
@schemes [
"ftp",
"http",
"https",
"ldap",
"sftp",
"tftp",
]
Enum.each @ports, fn {scheme, port} ->
Enum.each @schemes, fn scheme ->
def normalize_scheme(unquote(scheme)), do: unquote(scheme)
def default_port(unquote(scheme)), do: unquote(port)
end
@doc """
@@ -47,8 +46,7 @@ defmodule URI do
"""
def default_port(scheme) when is_binary(scheme) do
{:ok, dict} = Application.fetch_env(:elixir, :uri)
Map.get(dict, scheme)
:elixir_config.get({:uri, scheme})
end
@doc """
@@ -59,8 +57,7 @@ defmodule URI do
new URIs.
"""
def default_port(scheme, port) when is_binary(scheme) and port > 0 do
{:ok, dict} = Application.fetch_env(:elixir, :uri)
Application.put_env(:elixir, :uri, Map.put(dict, scheme, port), persistent: true)
:elixir_config.put({:uri, scheme}, port)
end
@doc """
@@ -68,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.
@@ -98,7 +95,10 @@ defmodule URI do
"""
def decode_query(q, dict \\ %{}) when is_binary(q) do
Enum.reduce query_decoder(q), dict, fn({k, v}, acc) -> Dict.put(acc, k, v) end
case do_decode_query(q) do
nil -> dict
{{k, v}, q} -> decode_query(q, Dict.put(dict, k, v))
end
end
@doc """
@@ -107,19 +107,19 @@ defmodule URI do
## Examples
iex> URI.query_decoder("foo=1&bar=2") |> Enum.map &(&1)
iex> URI.query_decoder("foo=1&bar=2") |> Enum.map(&(&1))
[{"foo", "1"}, {"bar", "2"}]
"""
def query_decoder(q) when is_binary(q) do
Stream.unfold(q, &do_decoder/1)
Stream.unfold(q, &do_decode_query/1)
end
defp do_decoder("") do
defp do_decode_query("") do
nil
end
defp do_decoder(q) do
defp do_decode_query(q) do
{first, next} =
case :binary.split(q, "&") do
[first, rest] -> {first, rest}
@@ -234,7 +234,7 @@ defmodule URI do
"""
def decode(uri) do
unpercent(uri)
unpercent(uri, "", false)
catch
:malformed_uri ->
raise ArgumentError, "malformed URI #{inspect uri}"
@@ -250,23 +250,25 @@ defmodule URI do
"""
def decode_www_form(str) do
String.split(str, "+") |> Enum.map_join(" ", &unpercent/1)
unpercent(str, "", true)
catch
:malformed_uri ->
raise ArgumentError, "malformed URI #{inspect str}"
end
defp unpercent(<<?%, hex_1, hex_2, tail :: binary>>) do
<<bsl(hex_to_dec(hex_1), 4) + hex_to_dec(hex_2)>> <> unpercent(tail)
end
defp unpercent(<<?%, _>>), do: throw(:malformed_uri)
defp unpercent(<<?%>>), do: throw(:malformed_uri)
defp unpercent(<<head, tail :: binary>>) do
<<head>> <> unpercent(tail)
defp unpercent(<<?+, tail::binary>>, acc, spaces = true) do
unpercent(tail, <<acc::binary, ?\s>>, spaces)
end
defp unpercent(<<>>), do: <<>>
defp unpercent(<<?%, hex_1, hex_2, tail::binary>>, acc, spaces) do
unpercent(tail, <<acc::binary, bsl(hex_to_dec(hex_1), 4) + hex_to_dec(hex_2)>>, spaces)
end
defp unpercent(<<?%, _::binary>>, _acc, _spaces), do: throw(:malformed_uri)
defp unpercent(<<head, tail::binary>>, acc, spaces) do
unpercent(tail, <<acc::binary, head>>, spaces)
end
defp unpercent(<<>>, acc, _spaces), do: acc
defp hex_to_dec(n) when n in ?A..?F, do: n - ?A + 10
defp hex_to_dec(n) when n in ?a..?f, do: n - ?a + 10
@@ -274,12 +276,17 @@ defmodule URI do
defp hex_to_dec(_n), do: throw(:malformed_uri)
@doc """
Parses a URI into components.
Parses a well-formed URI reference into its components.
URIs have portions that are handled specially for the particular
scheme of the URI. For example, http and https have different
default ports. Such values can be accessed and registered via
`URI.default_port/1` and `URI.default_port/2`.
Note this function expects a well-formed URI and does not perform
any validation. See the examples section below of how `URI.parse/1`
can be used to parse a wide range of relative URIs.
This function uses the parsing regular expression as defined
in the Appendix B of RFC3986.
When a URI is given without a port, the values registered via
`URI.default_port/1` and `URI.default_port/2` are used.
## Examples
@@ -288,12 +295,24 @@ defmodule URI do
authority: "elixir-lang.org", userinfo: nil,
host: "elixir-lang.org", port: 80}
iex> URI.parse("//elixir-lang.org/")
%URI{authority: "elixir-lang.org", fragment: nil, host: "elixir-lang.org",
path: "/", port: nil, query: nil, scheme: nil, userinfo: nil}
iex> URI.parse("/foo/bar")
%URI{authority: nil, fragment: nil, host: nil, path: "/foo/bar",
port: nil, query: nil, scheme: nil, userinfo: nil}
iex> URI.parse("foo/bar")
%URI{authority: nil, fragment: nil, host: nil, path: "foo/bar",
port: nil, query: nil, scheme: nil, userinfo: nil}
"""
def parse(%URI{} = uri), do: uri
def parse(s) when is_binary(s) do
# From http://tools.ietf.org/html/rfc3986#appendix-B
regex = ~r/^(([^:\/?#]+):)?(\/\/([^\/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?/
regex = ~r/^(([a-z][a-z0-9\+\-\.]*):)?(\/\/([^\/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?/i
parts = nillify(Regex.run(regex, s))
destructure [_, _, scheme, _, authority, path, _, query, _, fragment], parts
@@ -309,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
@@ -349,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
+28 -3
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]
@@ -61,7 +74,7 @@ defmodule Version do
@type major :: String.t | non_neg_integer
@type minor :: non_neg_integer | nil
@type patch :: non_neg_integer | nil
@type pre :: [String.t]
@type pre :: [String.t | non_neg_integer]
@type build :: String.t | nil
@type matchable :: {major :: major,
minor :: minor,
@@ -324,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)
@@ -506,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
+34 -7
View File
@@ -29,9 +29,12 @@ start(_Type, _Args) ->
end,
ok = io:setopts(standard_io, Opts),
%% Must use undocument {unicode, true} to set unicode on standard_error, more
%% info: http://erlang.org/pipermail/erlang-bugs/2014-April/004310.html
ok = io:setopts(standard_error, [{unicode,true}]),
ok = case io:setopts(standard_error, [{encoding,utf8}]) of
ok -> ok;
{error, _} -> io:setopts(standard_error, [{unicode,true}]) %% R17.3 and earlier
end,
case file:native_name_encoding() of
latin1 ->
io:format(standard_error,
@@ -42,10 +45,29 @@ start(_Type, _Args) ->
ok
end,
elixir_sup:start_link().
URIs = [{<<"ftp">>, 21},
{<<"sftp">>, 22},
{<<"tftp">>, 69},
{<<"http">>, 80},
{<<"https">>, 443},
{<<"ldap">>, 389}],
URIConfig = [{{uri, Scheme}, Port} || {Scheme, Port} <- URIs],
CompilerOpts = [{docs,true},{debug_info,true},{warnings_as_errors,false}],
Config = [{at_exit, []},
{compiler_options, orddict:from_list(CompilerOpts)}
| URIConfig],
Tab = elixir_config:new(Config),
case elixir_sup:start_link() of
{ok, Sup} ->
{ok, Sup, Tab};
{error, _Reason} = Error ->
elixir_config:delete(Tab),
Error
end.
stop(Tab) ->
elixir_config:delete(Tab).
stop(_S) ->
ok.
config_change(_Changed, _New, _Remove) ->
ok.
@@ -54,12 +76,17 @@ config_change(_Changed, _New, _Remove) ->
start_cli() ->
{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.
_ = application:start(logger),
_ = case code:ensure_loaded('Elixir.Logger') of
{module, _} -> application:start(logger);
{error, _} -> ok
end,
'Elixir.Kernel.CLI':main(init:get_plain_arguments()).
%% EVAL HOOKS
@@ -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)).
+2 -11
View File
@@ -36,7 +36,7 @@ expand_bitstr(Fun, [H|T], Acc, E) ->
%% Expand bit info
expand_bit_info(Meta, Info, E) ->
expand_bit_info(Meta, unpack_bit_info(Meta, Info, E), default, [], E).
expand_bit_info(Meta, unpack_bit_info(Info, []), default, [], E).
expand_bit_info(Meta, [{size, _, [_]=Args}|T], Size, Types, E) ->
case Size of
@@ -111,18 +111,9 @@ handle_unknown_bit_info(Meta, Expr, T, Size, Types, E) ->
elixir_errors:compile_error(Meta, ?m(E, file),
"unknown bitstring specifier ~ts", ['Elixir.Macro':to_string(Expr)]);
Info ->
expand_bit_info(Meta, unpack_bit_info(Meta, Info, E) ++ T, Size, Types, E)
expand_bit_info(Meta, unpack_bit_info(Info, []) ++ T, Size, Types, E)
end.
%% We can remove unpack_bit_info/3 once deprecated.
unpack_bit_info(Meta, [H|T], E) ->
elixir_errors:warn(?line(Meta), ?m(E, file), "passing a list of bitstring modifiers is deprecated, "
"please separate them with - instead"),
Dashed = lists:foldl(fun(I, Acc) -> {'-', Meta, [Acc, I]} end, H, T),
unpack_bit_info(Dashed, []);
unpack_bit_info(_Meta, Expr, _E) ->
unpack_bit_info(Expr, []).
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) ->
+1 -1
View File
@@ -44,7 +44,7 @@ define({Line,E}, Kind, Call, Expr) ->
unless_loaded(Fun, Args, Callback) ->
case code:is_loaded(?kernel) of
{_, _} -> apply(?kernel, Fun, Args);
false -> Callback()
false -> Callback()
end.
env() ->
+39 -67
View File
@@ -6,14 +6,11 @@
-define(timeout, 30000).
-record(elixir_code_server, {
compilation_status=[],
argv=[],
loaded=[],
at_exit=[],
pool={[],0},
paths={[],[]},
compiler_options=[{docs,true},{debug_info,true},{warnings_as_errors,false}],
erl_compiler_options=nil
mod_pool={[],0},
mod_ets=dict:new(),
compilation_status=[]
}).
call(Args) ->
@@ -29,14 +26,21 @@ start_link() ->
init(ok) ->
%% We attempt to load those modules here so throughout
%% the codebase we can use a code:is_loaded/1 check on
%% bootstrap instead of using the more expensive
%% code:ensure_loaded/1 multiple times.
%% the codebase we can avoid code:is_loaded/1 checks.
_ = code:ensure_loaded('Elixir.Macro.Env'),
_ = code:ensure_loaded('Elixir.Module.LocalsTracker'),
_ = code:ensure_loaded('Elixir.Kernel.LexicalTracker'),
%% The table where we store module definitions
_ = ets:new(elixir_modules, [set, protected, named_table, {read_concurrency, true}]),
{ok, #elixir_code_server{}}.
handle_call({defmodule, Pid, Tuple}, _From, Config) ->
{Ref, New} = defmodule(Pid, Tuple, Config),
{reply, Ref, New};
handle_call({undefmodule, Ref}, _From, Config) ->
{reply, ok, undefmodule(Ref, Config)};
handle_call({acquire, Path}, From, Config) ->
Current = Config#elixir_code_server.loaded,
case orddict:find(Path, Current) of
@@ -53,18 +57,6 @@ handle_call({acquire, Path}, From, Config) ->
handle_call(loaded, _From, Config) ->
{reply, [F || {F, true} <- Config#elixir_code_server.loaded], Config};
handle_call(at_exit, _From, Config) ->
{reply, Config#elixir_code_server.at_exit, Config};
handle_call(flush_at_exit, _From, Config) ->
{reply, Config#elixir_code_server.at_exit, Config#elixir_code_server{at_exit=[]}};
handle_call(argv, _From, Config) ->
{reply, Config#elixir_code_server.argv, Config};
handle_call(compiler_options, _From, Config) ->
{reply, Config#elixir_code_server.compiler_options, Config};
handle_call({compilation_status, CompilerPid}, _From, Config) ->
CompilationStatusList = Config#elixir_code_server.compilation_status,
CompilationStatusListNew = orddict:erase(CompilerPid, CompilationStatusList),
@@ -73,20 +65,11 @@ handle_call({compilation_status, CompilerPid}, _From, Config) ->
Config#elixir_code_server{compilation_status=CompilationStatusListNew}};
handle_call(retrieve_module_name, _From, Config) ->
case Config#elixir_code_server.pool of
case Config#elixir_code_server.mod_pool of
{[H|T], Counter} ->
{reply, module_tuple(H), Config#elixir_code_server{pool={T,Counter}}};
{reply, module_tuple(H), Config#elixir_code_server{mod_pool={T,Counter}}};
{[], Counter} ->
{reply, module_tuple(Counter), Config#elixir_code_server{pool={[],Counter+1}}}
end;
handle_call(erl_compiler_options, _From, Config) ->
case Config#elixir_code_server.erl_compiler_options of
nil ->
Opts = erl_compiler_options(),
{reply, Opts, Config#elixir_code_server{erl_compiler_options=Opts}};
Opts ->
{reply, Opts, Config}
{reply, module_tuple(Counter), Config#elixir_code_server{mod_pool={[],Counter+1}}}
end;
handle_call(paths, _From, Config) ->
@@ -95,20 +78,11 @@ handle_call(paths, _From, Config) ->
handle_call(Request, _From, Config) ->
{stop, {badcall, Request}, Config}.
handle_cast({at_exit, AtExit}, Config) ->
{noreply, Config#elixir_code_server{at_exit=[AtExit|Config#elixir_code_server.at_exit]}};
handle_cast({argv, Argv}, Config) ->
{noreply, Config#elixir_code_server{argv=Argv}};
handle_cast({compiler_options, Options}, Config) ->
Final = orddict:merge(fun(_,_,V) -> V end, Config#elixir_code_server.compiler_options, Options),
{noreply, Config#elixir_code_server{compiler_options=Final}};
handle_cast({register_warning, CompilerPid}, Config) ->
CompilationStatusCurrent = Config#elixir_code_server.compilation_status,
CompilationStatusNew = orddict:store(CompilerPid, error, CompilationStatusCurrent),
case orddict:find(warnings_as_errors, Config#elixir_code_server.compiler_options) of
CompilerOptions = elixir_config:get(compiler_options),
case orddict:find(warnings_as_errors, CompilerOptions) of
{ok, true} -> {noreply, Config#elixir_code_server{compilation_status=CompilationStatusNew}};
_ -> {noreply, Config}
end;
@@ -137,8 +111,8 @@ handle_cast({unload_files, Files}, Config) ->
Unloaded = lists:foldl(fun(File, Acc) -> orddict:erase(File, Acc) end, Current, Files),
{noreply, Config#elixir_code_server{loaded=Unloaded}};
handle_cast({return_module_name, H}, #elixir_code_server{pool={T,Counter}} = Config) ->
{noreply, Config#elixir_code_server{pool={[H|T],Counter}}};
handle_cast({return_module_name, H}, #elixir_code_server{mod_pool={T,Counter}} = Config) ->
{noreply, Config#elixir_code_server{mod_pool={[H|T],Counter}}};
handle_cast({paths, PA, PZ}, #elixir_code_server{} = Config) ->
{noreply, Config#elixir_code_server{paths={PA,PZ}}};
@@ -146,7 +120,10 @@ handle_cast({paths, PA, PZ}, #elixir_code_server{} = Config) ->
handle_cast(Request, Config) ->
{stop, {badcast, Request}, Config}.
handle_info(_Request, Config) ->
handle_info({'DOWN', Ref, process, _Pid, _Reason}, Config) ->
{noreply, undefmodule(Ref, Config)};
handle_info(_Msg, Config) ->
{noreply, Config}.
terminate(_Reason, _Config) ->
@@ -158,22 +135,17 @@ code_change(_Old, Config, _Extra) ->
module_tuple(I) ->
{list_to_atom("elixir_compiler_" ++ integer_to_list(I)), I}.
erl_compiler_options() ->
Key = "ERL_COMPILER_OPTIONS",
case os:getenv(Key) of
false -> [];
Str when is_list(Str) ->
case erl_scan:string(Str) of
{ok,Tokens,_} ->
case erl_parse:parse_term(Tokens ++ [{dot, 1}]) of
{ok,List} when is_list(List) -> List;
{ok,Term} -> [Term];
{error,_Reason} ->
io:format("Ignoring bad term in ~ts\n", [Key]),
[]
end;
{error, {_,_,_Reason}, _} ->
io:format("Ignoring bad term in ~ts\n", [Key]),
[]
end
defmodule(Pid, Tuple, #elixir_code_server{mod_ets=ModEts} = Config) ->
ets:insert(elixir_modules, Tuple),
Ref = erlang:monitor(process, Pid),
Mod = erlang:element(1, Tuple),
{Ref, Config#elixir_code_server{mod_ets=dict:store(Ref, Mod, ModEts)}}.
undefmodule(Ref, #elixir_code_server{mod_ets=ModEts} = Config) ->
case dict:find(Ref, ModEts) of
{ok, Mod} ->
ets:delete(elixir_modules, Mod),
Config#elixir_code_server{mod_ets=dict:erase(Ref, ModEts)};
error ->
Config
end.
+75 -23
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,29 +168,38 @@ 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),
{module, ModuleName} = 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}.
@@ -158,7 +207,10 @@ no_auto_import() ->
core() ->
{ok, _} = application:ensure_all_started(elixir),
elixir_code_server:cast({compiler_options, [{docs,false},{internal,true}]}),
New = orddict:from_list([{docs,false},{internal,true}]),
Merge = fun(_, _, Value) -> Value end,
Update = fun(Old) -> orddict:merge(Merge, Old, New) end,
_ = elixir_config:update(compiler_options, Update),
[core_file(File) || File <- core_main()].
core_file(File) ->
+65
View File
@@ -0,0 +1,65 @@
-module(elixir_config).
-compile({no_auto_import, [get/1]}).
-export([new/1, delete/1, put/2, get/1, update/2, get_and_put/2]).
-export([start_link/0, init/1, handle_call/3, handle_cast/2,
handle_info/2, code_change/3, terminate/2]).
-behaviour(gen_server).
%% public api
new(Opts) ->
Tab = ets:new(?MODULE, [named_table, public, {read_concurrency, true}]),
true = ets:insert_new(?MODULE, Opts),
Tab.
delete(?MODULE) ->
ets:delete(?MODULE).
put(Key, Value) ->
gen_server:call(?MODULE, {put, Key, Value}).
get(Key) ->
case ets:lookup(?MODULE, Key) of
[{_, Value}] -> Value;
[] -> nil
end.
update(Key, Fun) ->
gen_server:call(?MODULE, {update, Key, Fun}).
get_and_put(Key, Value) ->
gen_server:call(?MODULE, {get_and_put, Key, Value}).
start_link() ->
gen_server:start_link({local, ?MODULE}, ?MODULE, ?MODULE, []).
%% gen_server api
init(Tab) ->
%% Ets table must be writable
public = ets:info(Tab, protection),
{ok, Tab}.
handle_call({put, Key, Value}, _From, Tab) ->
ets:insert(Tab, {Key, Value}),
{reply, ok, Tab};
handle_call({update, Key, Fun}, _From, Tab) ->
Value = Fun(get(Key)),
ets:insert(Tab, {Key, Value}),
{reply, Value, Tab};
handle_call({get_and_put, Key, Value}, _From, Tab) ->
OldValue = get(Key),
ets:insert(Tab, {Key, Value}),
{reply, OldValue, Tab}.
handle_cast(Cast, Tab) ->
{stop, {bad_cast, Cast}, Tab}.
handle_info(_Msg, Tab) ->
{noreply, Tab}.
code_change(_OldVsn, Tab, _Extra) ->
{ok, Tab}.
terminate(_Reason, _Tab) ->
ok.
+2 -2
View File
@@ -3,8 +3,8 @@
handle_info/2, terminate/2, code_change/3, next/0]).
-behaviour(gen_server).
-define(timeout, 1000). %% 1 second
-define(limit, 4294967296). %% 2^32
-define(timeout, 30000). %% 30 seconds
-define(limit, 4294967295). %% 2^32 - 1
next() ->
gen_server:call(?MODULE, next, ?timeout).
+47 -50
View File
@@ -1,49 +1,35 @@
% Holds the logic responsible for function definitions (def(p) and defmacro(p)).
-module(elixir_def).
-export([table/1, clauses_table/1, setup/1,
cleanup/1, reset_last/1, lookup_definition/2,
-export([setup/1, reset_last/1, lookup_definition/2,
delete_definition/2, store_definition/6, unwrap_definitions/2,
store_each/8, format_error/1]).
store_each/7, format_error/1]).
-include("elixir.hrl").
-define(attr, '__def_table').
-define(clauses_attr, '__clauses_table').
-define(last_def, {elixir, last_def}).
-define(attr, {elixir, def_table}).
-define(clauses_attr, {elixir, clauses_table}).
%% Table management functions. Called internally.
table(Module) ->
ets:lookup_element(Module, ?attr, 2).
clauses_table(Module) ->
ets:lookup_element(Module, ?clauses_attr, 2).
setup(Module) ->
ets:insert(Module, {?attr, ets:new(Module, [set, public])}),
ets:insert(Module, {?clauses_attr, ets:new(Module, [bag, public])}),
reset_last(Module),
ok.
cleanup(Module) ->
ets:delete(table(Module)),
ets:delete(clauses_table(Module)).
%% Reset the last item. Useful when evaling code.
reset_last(Module) ->
ets:insert(table(Module), {last, []}).
ets:insert(elixir_module:data_table(Module), {?last_def, []}).
%% Looks up a definition from the database.
lookup_definition(Module, Tuple) ->
case ets:lookup(table(Module), Tuple) of
case ets:lookup(elixir_module:defs_table(Module), Tuple) of
[Result] ->
CTable = clauses_table(Module),
CTable = elixir_module:clas_table(Module),
{Result, [Clause || {_, Clause} <- ets:lookup(CTable, Tuple)]};
_ ->
false
end.
delete_definition(Module, Tuple) ->
ets:delete(table(Module), Tuple),
ets:delete(clauses_table(Module), Tuple).
ets:delete(elixir_module:defs_table(Module), Tuple),
ets:delete(elixir_module:clas_table(Module), Tuple).
% Invoked by the wrap definition with the function abstract tree.
% Each function is then added to the function table.
@@ -82,6 +68,7 @@ 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, Location, E).
@@ -97,16 +84,12 @@ store_definition(Line, Kind, CheckClauses, Name, Args, Guards, Body, KeepLocatio
DefaultsLength = length(Defaults),
elixir_locals:record_defaults(Tuple, Kind, Module, DefaultsLength),
File = ?m(E, file),
Table = table(Module),
CTable = clauses_table(Module),
File = ?m(E, file),
compile_super(Module, Super, E),
check_previous_defaults(Table, Line, Name, Arity, Kind, DefaultsLength, E),
check_previous_defaults(Line, Module, Name, Arity, Kind, DefaultsLength, E),
store_each(CheckClauses, Kind, File, Location,
Table, CTable, DefaultsLength, Function),
[store_each(false, Kind, File, Location, Table, CTable, 0,
store_each(CheckClauses, Kind, File, Location, Module, DefaultsLength, Function),
[store_each(false, Kind, File, Location, Module, 0,
default_function_for(Kind, Name, Default)) || Default <- Defaults],
make_struct_available(Kind, Module, Name, Args),
@@ -165,7 +148,7 @@ normalize_location(File) ->
%% Compile super
compile_super(Module, true, #{function := Function}) ->
elixir_def_overridable:store(Module, Function, true);
elixir_def_overridable:super(Module, Function);
compile_super(_Module, _, _E) -> ok.
%% Translate the given call and expression given
@@ -175,7 +158,7 @@ translate_definition(Kind, Line, Module, Name, Args, Guards, Body, E) when is_in
Arity = length(Args),
{EArgs, EGuards, EBody, _} = elixir_exp_clauses:def(fun elixir_def_defaults:expand/2,
Args, Guards, expr_from_body(Line, Body), E),
Args, Guards, expr_from_body(Line, Body), E),
Body == nil andalso check_args_for_bodyless_clause(Line, EArgs, E),
@@ -228,9 +211,8 @@ is_macro(_) -> false.
% It returns a list of all functions to be exported, plus the macros,
% and the body of all functions.
unwrap_definitions(File, Module) ->
Table = table(Module),
CTable = clauses_table(Module),
ets:delete(Table, last),
Table = elixir_module:defs_table(Module),
CTable = elixir_module:clas_table(Module),
{All, Private} = unwrap_definition(ets:tab2list(Table), File, Module, CTable, [], []),
Unreachable = elixir_locals:warn_unused_local(File, Module, Private),
@@ -326,26 +308,33 @@ warn_bodyless_function(Line, File, _Module, Kind, Tuple) ->
%% This function also checks and emit warnings in case
%% the kind, of the visibility of the function changes.
store_each(Check, Kind, File, Location, Table, CTable, Defaults, {function, Line, Name, Arity, Clauses}) ->
Tuple = {Name, Arity},
store_each(Check, Kind, File, Location, Module, Defaults, {function, Line, Name, Arity, Clauses}) ->
Data = elixir_module:data_table(Module),
Defs = elixir_module:defs_table(Module),
Clas = elixir_module:clas_table(Module),
Tuple = {Name, Arity},
HasBody = Clauses =/= [],
case ets:lookup(Table, Tuple) of
[{Tuple, StoredKind, StoredLine, StoredFile, StoredCheck, StoredLocation, {StoredDefaults, LastHasBody, LastDefaults}}] ->
case ets:lookup(Defs, Tuple) of
[{Tuple, StoredKind, StoredLine, StoredFile, StoredCheck,
StoredLocation, {StoredDefaults, LastHasBody, LastDefaults}}] ->
FinalLine = StoredLine,
FinalLocation = StoredLocation,
FinalDefaults = {max(Defaults, StoredDefaults), HasBody, Defaults},
check_valid_kind(Line, File, Name, Arity, Kind, StoredKind),
(Check and StoredCheck) andalso
check_valid_clause(Line, File, Name, Arity, Kind, Table, StoredLine, StoredFile),
check_valid_clause(Line, File, Name, Arity, Kind, Data, StoredLine, StoredFile),
check_valid_defaults(Line, File, Name, Arity, Kind, Defaults, LastDefaults, LastHasBody);
[] ->
FinalLine = Line,
FinalLocation = Location,
FinalDefaults = {Defaults, HasBody, Defaults}
end,
Check andalso ets:insert(Table, {last, {Name, Arity}}),
ets:insert(CTable, [{Tuple, Clause} || Clause <- Clauses ]),
ets:insert(Table, {Tuple, Kind, FinalLine, File, Check, FinalLocation, FinalDefaults}).
Check andalso ets:insert(Data, {?last_def, {Name, Arity}}),
ets:insert(Clas, [{Tuple, Clause} || Clause <- Clauses]),
ets:insert(Defs, {Tuple, Kind, FinalLine, File, Check, FinalLocation, FinalDefaults}).
%% Validations
@@ -354,8 +343,8 @@ check_valid_kind(Line, File, Name, Arity, Kind, StoredKind) ->
elixir_errors:form_error([{line, Line}], File, ?MODULE,
{changed_kind, {Name, Arity, StoredKind, Kind}}).
check_valid_clause(Line, File, Name, Arity, Kind, Table, StoredLine, StoredFile) ->
case ets:lookup_element(Table, last, 2) of
check_valid_clause(Line, File, Name, Arity, Kind, Data, StoredLine, StoredFile) ->
case ets:lookup_element(Data, ?last_def, 2) of
{Name,Arity} -> [];
[] -> [];
_ ->
@@ -378,8 +367,8 @@ check_valid_defaults(Line, File, Name, Arity, Kind, _, _, _) ->
elixir_errors:form_error([{line, Line}], File, ?MODULE,
{clauses_with_defaults, {Kind, Name, Arity}}).
check_previous_defaults(Table, Line, Name, Arity, Kind, Defaults, E) ->
Matches = ets:match(Table, {{Name, '$2'}, '$1', '_', '_', '_', '_', {'$3', '_', '_'}}),
check_previous_defaults(Line, Module, Name, Arity, Kind, Defaults, E) ->
Matches = ets:match(elixir_module:defs_table(Module), {{Name, '$2'}, '$1', '_', '_', '_', '_', {'$3', '_', '_'}}),
[ begin
elixir_errors:form_error([{line, Line}], ?m(E, file), ?MODULE,
{defs_with_defaults, Name, {Kind, Arity}, {K, A}})
@@ -404,8 +393,12 @@ invalid_arg(_) ->
assert_no_aliases_name(Line, '__aliases__', [Atom], #{file := File}) when is_atom(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
@@ -447,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)
+15 -11
View File
@@ -1,14 +1,18 @@
% Holds the logic responsible for defining overridable functions and handling super.
-module(elixir_def_overridable).
-export([store_pending/1, ensure_defined/4,
name/2, store/3, format_error/1]).
-export([setup/1, overridable/1, overridable/2, name/2, super/2, store_pending/1,
ensure_defined/4, format_error/1]).
-include("elixir.hrl").
-define(attr, {elixir, overridable}).
setup(Module) ->
overridable(Module, []).
overridable(Module) ->
ets:lookup_element(elixir_module:data_table(Module), '__overridable', 2).
ets:lookup_element(elixir_module:data_table(Module), ?attr, 2).
overridable(Module, Value) ->
ets:insert(elixir_module:data_table(Module), {'__overridable', Value}).
ets:insert(elixir_module:data_table(Module), {?attr, Value}).
%% Check if an overridable function is defined.
@@ -43,19 +47,19 @@ store(Module, Function, GenerateName) ->
false -> {Kind, Name}
end,
case code:is_loaded('Elixir.Module.LocalsTracker') of
{_, _} ->
case elixir_compiler:get_opt(internal) of
false ->
'Elixir.Module.LocalsTracker':reattach(Module, Kind, {Name, Arity}, Neighbours);
_ ->
true ->
ok
end,
Def = {function, Line, FinalName, Arity, Clauses},
elixir_def:store_each(false, FinalKind, File, Location,
elixir_def:table(Module), elixir_def:clauses_table(Module), Defaults, Def)
elixir_def:store_each(false, FinalKind, File, Location, Module, Defaults, Def)
end.
%% Store pending declarations that were not manually made concrete.
super(Module, Function) ->
store(Module, Function, true).
store_pending(Module) ->
_ = [store(Module, X, false) || {X, {_, _, _, false}} <- overridable(Module),
@@ -73,4 +77,4 @@ format_error({no_super, Module, {Name, Arity}}) ->
format_fa({Name, Arity}) ->
A = atom_to_binary(Name, utf8),
B = integer_to_binary(Arity),
<< A/binary, $/, B/binary >>.
<<A/binary, $/, B/binary>>.
+13 -194
View File
@@ -9,20 +9,7 @@
find_import/4, format_error/1]).
-include("elixir.hrl").
-import(ordsets, [is_element/2]).
-define(atom, 'Elixir.Atom').
-define(float, 'Elixir.Float').
-define(io, 'Elixir.IO').
-define(integer, 'Elixir.Integer').
-define(kernel, 'Elixir.Kernel').
-define(list, 'Elixir.List').
-define(map, 'Elixir.Map').
-define(node, 'Elixir.Node').
-define(port, 'Elixir.Port').
-define(process, 'Elixir.Process').
-define(string, 'Elixir.String').
-define(system, 'Elixir.System').
-define(tuple, 'Elixir.Tuple').
default_functions() ->
[{?kernel, elixir_imported_functions()}].
@@ -31,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
@@ -79,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.
@@ -100,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);
@@ -148,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)),
@@ -164,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 ->
@@ -229,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) ->
@@ -339,163 +325,6 @@ elixir_imported_macros() ->
error:undef -> []
end.
rewrite(?atom, to_string, [Arg], _) ->
{ok, erlang, atom_to_binary, [Arg, utf8]};
rewrite(?kernel, elem, [Tuple, Index], _) ->
{ok, erlang, element, [increment(Index), Tuple]};
rewrite(?kernel, put_elem, [Tuple, Index, Value], _) ->
{ok, erlang, setelement, [increment(Index), Tuple, Value]};
rewrite(?map, 'has_key?', [Map, Key], _) ->
{ok, maps, is_key, [Key, Map]};
rewrite(?map, fetch, [Map, Key], _) ->
{ok, maps, find, [Key, Map]};
rewrite(?map, put, [Map, Key, Value], _) ->
{ok, maps, put, [Key, Value, Map]};
rewrite(?map, delete, [Map, Key], _) ->
{ok, maps, remove, [Key, Map]};
rewrite(?process, monitor, [Arg], _) ->
{ok, erlang, monitor, [process, Arg]};
rewrite(?string, to_atom, [Arg], _) ->
{ok, erlang, binary_to_atom, [Arg, utf8]};
rewrite(?string, to_existing_atom, [Arg], _) ->
{ok, erlang, binary_to_existing_atom, [Arg, utf8]};
rewrite(?tuple, insert_at, [Tuple, Index, Term], _) ->
{ok, erlang, insert_element, [increment(Index), Tuple, Term]};
rewrite(?tuple, delete_at, [Tuple, Index], _) ->
{ok, erlang, delete_element, [increment(Index), Tuple]};
rewrite(?tuple, duplicate, [Data, Size], _) ->
{ok, erlang, make_tuple, [Size, Data]};
rewrite(Receiver, Name, Args, Arity) ->
case inline(Receiver, Name, Arity) of
{AR, AN} -> {ok, AR, AN, Args};
false -> false
end.
increment(Number) when is_number(Number) ->
Number + 1;
increment(Other) ->
{{'.', [], [erlang, '+']}, [], [Other, 1]}.
inline(?atom, to_char_list, 1) -> {erlang, atom_to_list};
inline(?io, iodata_length, 1) -> {erlang, iolist_size};
inline(?io, iodata_to_binary, 1) -> {erlang, iolist_to_binary};
inline(?integer, to_string, 1) -> {erlang, integer_to_binary};
inline(?integer, to_string, 2) -> {erlang, integer_to_binary};
inline(?integer, to_char_list, 1) -> {erlang, integer_to_list};
inline(?integer, to_char_list, 2) -> {erlang, integer_to_list};
inline(?float, to_string, 1) -> {erlang, float_to_binary};
inline(?float, to_char_list, 1) -> {erlang, float_to_list};
inline(?list, to_atom, 1) -> {erlang, list_to_atom};
inline(?list, to_existing_atom, 1) -> {erlang, list_to_existing_atom};
inline(?list, to_float, 1) -> {erlang, list_to_float};
inline(?list, to_integer, 1) -> {erlang, list_to_integer};
inline(?list, to_integer, 2) -> {erlang, list_to_integer};
inline(?list, to_tuple, 1) -> {erlang, list_to_tuple};
inline(?kernel, '+', 2) -> {erlang, '+'};
inline(?kernel, '-', 2) -> {erlang, '-'};
inline(?kernel, '+', 1) -> {erlang, '+'};
inline(?kernel, '-', 1) -> {erlang, '-'};
inline(?kernel, '*', 2) -> {erlang, '*'};
inline(?kernel, '/', 2) -> {erlang, '/'};
inline(?kernel, '++', 2) -> {erlang, '++'};
inline(?kernel, '--', 2) -> {erlang, '--'};
inline(?kernel, 'not', 1) -> {erlang, 'not'};
inline(?kernel, '<', 2) -> {erlang, '<'};
inline(?kernel, '>', 2) -> {erlang, '>'};
inline(?kernel, '<=', 2) -> {erlang, '=<'};
inline(?kernel, '>=', 2) -> {erlang, '>='};
inline(?kernel, '==', 2) -> {erlang, '=='};
inline(?kernel, '!=', 2) -> {erlang, '/='};
inline(?kernel, '===', 2) -> {erlang, '=:='};
inline(?kernel, '!==', 2) -> {erlang, '=/='};
inline(?kernel, abs, 1) -> {erlang, abs};
inline(?kernel, apply, 2) -> {erlang, apply};
inline(?kernel, apply, 3) -> {erlang, apply};
inline(?kernel, binary_part, 3) -> {erlang, binary_part};
inline(?kernel, bit_size, 1) -> {erlang, bit_size};
inline(?kernel, byte_size, 1) -> {erlang, byte_size};
inline(?kernel, 'div', 2) -> {erlang, 'div'};
inline(?kernel, exit, 1) -> {erlang, exit};
inline(?kernel, hd, 1) -> {erlang, hd};
inline(?kernel, is_atom, 1) -> {erlang, is_atom};
inline(?kernel, is_binary, 1) -> {erlang, is_binary};
inline(?kernel, is_bitstring, 1) -> {erlang, is_bitstring};
inline(?kernel, is_boolean, 1) -> {erlang, is_boolean};
inline(?kernel, is_float, 1) -> {erlang, is_float};
inline(?kernel, is_function, 1) -> {erlang, is_function};
inline(?kernel, is_function, 2) -> {erlang, is_function};
inline(?kernel, is_integer, 1) -> {erlang, is_integer};
inline(?kernel, is_list, 1) -> {erlang, is_list};
inline(?kernel, is_map, 1) -> {erlang, is_map};
inline(?kernel, is_number, 1) -> {erlang, is_number};
inline(?kernel, is_pid, 1) -> {erlang, is_pid};
inline(?kernel, is_port, 1) -> {erlang, is_port};
inline(?kernel, is_reference, 1) -> {erlang, is_reference};
inline(?kernel, is_tuple, 1) -> {erlang, is_tuple};
inline(?kernel, length, 1) -> {erlang, length};
inline(?kernel, make_ref, 0) -> {erlang, make_ref};
inline(?kernel, map_size, 1) -> {erlang, map_size};
inline(?kernel, max, 2) -> {erlang, max};
inline(?kernel, min, 2) -> {erlang, min};
inline(?kernel, node, 0) -> {erlang, node};
inline(?kernel, node, 1) -> {erlang, node};
inline(?kernel, 'rem', 2) -> {erlang, 'rem'};
inline(?kernel, round, 1) -> {erlang, round};
inline(?kernel, self, 0) -> {erlang, self};
inline(?kernel, send, 2) -> {erlang, send};
inline(?kernel, spawn, 1) -> {erlang, spawn};
inline(?kernel, spawn, 3) -> {erlang, spawn};
inline(?kernel, spawn_link, 1) -> {erlang, spawn_link};
inline(?kernel, spawn_link, 3) -> {erlang, spawn_link};
inline(?kernel, spawn_monitor, 1) -> {erlang, spawn_monitor};
inline(?kernel, spawn_monitor, 3) -> {erlang, spawn_monitor};
inline(?kernel, throw, 1) -> {erlang, throw};
inline(?kernel, tl, 1) -> {erlang, tl};
inline(?kernel, trunc, 1) -> {erlang, trunc};
inline(?kernel, tuple_size, 1) -> {erlang, tuple_size};
inline(?map, keys, 1) -> {maps, keys};
inline(?map, merge, 2) -> {maps, merge};
inline(?map, size, 1) -> {maps, size};
inline(?map, values, 1) -> {maps, values};
inline(?map, to_list, 1) -> {maps, to_list};
inline(?node, spawn, 2) -> {erlang, spawn};
inline(?node, spawn, 3) -> {erlang, spawn_opt};
inline(?node, spawn, 4) -> {erlang, spawn};
inline(?node, spawn, 5) -> {erlang, spawn_opt};
inline(?node, spawn_link, 2) -> {erlang, spawn_link};
inline(?node, spawn_link, 4) -> {erlang, spawn_link};
inline(?process, exit, 2) -> {erlang, exit};
inline(?process, spawn, 2) -> {erlang, spawn_opt};
inline(?process, spawn, 4) -> {erlang, spawn_opt};
inline(?process, demonitor, 1) -> {erlang, demonitor};
inline(?process, demonitor, 2) -> {erlang, demonitor};
inline(?process, link, 1) -> {erlang, link};
inline(?process, unlink, 1) -> {erlang, unlink};
inline(?port, open, 2) -> {erlang, open_port};
inline(?port, call, 3) -> {erlang, port_call};
inline(?port, close, 1) -> {erlang, port_close};
inline(?port, command, 2) -> {erlang, port_command};
inline(?port, command, 3) -> {erlang, port_command};
inline(?port, connect, 2) -> {erlang, port_connect};
inline(?port, control, 3) -> {erlang, port_control};
inline(?port, info, 1) -> {erlang, port_info};
inline(?port, info, 2) -> {erlang, port_info};
inline(?port, list, 0) -> {erlang, ports};
inline(?string, to_float, 1) -> {erlang, binary_to_float};
inline(?string, to_integer, 1) -> {erlang, binary_to_integer};
inline(?string, to_integer, 2) -> {erlang, binary_to_integer};
inline(?system, stacktrace, 0) -> {erlang, get_stacktrace};
inline(?tuple, to_list, 1) -> {erlang, tuple_to_list};
inline(_, _, _) -> false.
check_deprecation(Meta, Receiver, Name, Arity, #{file := File}) ->
case deprecation(Receiver, Name, Arity) of
false -> ok;
@@ -519,15 +348,5 @@ deprecation_message(Warning, Message) ->
Message -> Warning ++ ", " ++ Message
end.
deprecation('Elixir.System', 'cmd', 1) ->
"use System.cmd/3 instead";
deprecation('Elixir.Inspect.Algebra', 'pretty', 2) ->
"use Inspect.Algebra.format/2 instead";
deprecation('Elixir.IO.ANSI', 'escape', _) ->
"use the new API in IO.ANSI.format/2 instead";
deprecation('Elixir.IO.ANSI', 'escape_fragment', _) ->
"use the new API in IO.ANSI.format_fragment/2 instead";
deprecation('Elixir.IO.ANSI', 'terminal?', _) ->
"use IO.ANSI.enabled?/0 instead";
deprecation(_, _, _) ->
false.
+31 -7
View File
@@ -6,12 +6,12 @@
handle_file_warning/2, handle_file_warning/3, handle_file_error/2]).
-include("elixir.hrl").
-spec warn(non_neg_integer(), binary(), iolist()) -> ok.
-spec warn(non_neg_integer(), binary(), unicode:chardata()) -> ok.
warn(Line, File, Warning) when is_integer(Line), is_binary(File) ->
warn(file_format(Line, File), Warning).
-spec warn(iolist(), iolist()) -> ok.
-spec warn(unicode:chardata(), unicode:chardata()) -> ok.
warn(Caller, Warning) ->
do_warn([Caller, "warning: ", Warning, $\n]).
@@ -31,8 +31,8 @@ form_warn(Meta, File, Module, Desc) when is_list(Meta) ->
%% Compilation error.
-spec compile_error(list(), binary(), iolist()) -> no_return().
-spec compile_error(list(), binary(), iolist(), list()) -> no_return().
-spec compile_error(list(), binary(), unicode:charlist()) -> no_return().
-spec compile_error(list(), binary(), string(), list()) -> no_return().
compile_error(Meta, File, Message) when is_list(Message) ->
raise(Meta, File, 'Elixir.CompileError', elixir_utils:characters_to_binary(Message)).
@@ -55,7 +55,23 @@ parse_error(Line, File, Error, <<>>) ->
parse_error(Line, File, <<"syntax error before: ">>, <<"'end'">>) ->
do_raise(Line, File, 'Elixir.SyntaxError', <<"unexpected token: end">>);
%% Binaries are wrapped in [<<...>>], so we need to unwrap them
%% Produce a human-readable message for errors before a sigil
parse_error(Line, File, <<"syntax error before: ">>, <<"{sigil,", _Rest/binary>> = Full) ->
{ok, Tokens, _} = erl_scan:string(binary_to_list(Full)),
{ok, {sigil, _, Sigil, [Content], _}} = erl_parse:parse_term(Tokens ++ [{dot, 1}]),
Message = <<"syntax error before: sigil ~", Sigil," with content '", Content/binary, "'">>,
do_raise(Line, File, 'Elixir.SyntaxError', Message);
%% Aliases are wrapped in ['']
parse_error(Line, File, Error, <<"['", Token/binary>>) when is_binary(Error) ->
Rest =
case binary:split(Token, <<"'">>) of
[Part, _] -> Part;
_ -> <<>>
end,
do_raise(Line, File, 'Elixir.SyntaxError', <<Error/binary, Rest/binary>>);
%% Binaries (and interpolation) are wrapped in [<<...>>]
parse_error(Line, File, Error, <<"[", _/binary>> = Full) when is_binary(Error) ->
Rest =
case binary:split(Full, <<"<<">>) of
@@ -127,10 +143,13 @@ handle_file_warning(_, File, {Line,erl_lint,{undefined_behaviour,Module}}) ->
handle_file_warning(_, _File, {Line,erl_lint,{unused_var,_Var}}) when Line =< 0 ->
ok;
%% Ignore shadowed vars as we guarantee no conflicts ourselves
%% Ignore shadowed and exported vars as we guarantee no conflicts ourselves
handle_file_warning(_, _File, {_Line,erl_lint,{shadowed_var,_Var,_Where}}) ->
ok;
handle_file_warning(_, _File, {_Line,erl_lint,{exported_var,_Var,_Where}}) ->
ok;
%% Rewrite nomatch_guard to be more generic it can happen inside if, unless, etc
handle_file_warning(_, File, {Line, sys_core_fold, nomatch_guard}) ->
warn(Line, File, "this check/guard will always yield the same result");
@@ -140,6 +159,11 @@ handle_file_warning(_, File, {Line,erl_lint,{unused_var,Var}}) ->
Message = format_error(erl_lint, {unused_var, format_var(Var)}),
warn(Line, File, Message);
%% Handle literal eval failures
handle_file_warning(_, File, {Line,sys_core_fold,{eval_failure, Error}}) ->
#{'__struct__' := Struct} = 'Elixir.Exception':normalize(error, Error),
warn(Line, File, ["this expression will fail with ", elixir_aliases:inspect(Struct)]);
%% Default behaviour
handle_file_warning(_, File, {Line,Module,Desc}) ->
Message = format_error(Module, Desc),
@@ -241,4 +265,4 @@ do_raise(Line, File, Kind, Message) when is_integer(Line), is_binary(File), is_b
end,
Stacktrace = erlang:get_stacktrace(),
Exception = Kind:exception([{description, Message}, {file, File}, {line, Line}]),
erlang:raise(error, Exception, tl(Stacktrace)).
erlang:raise(error, Exception, tl(Stacktrace)).
+8 -6
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;
@@ -354,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
@@ -456,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),
@@ -473,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
@@ -488,8 +490,8 @@ validate_opts(Meta, Kind, Allowed, Opts, E) when is_list(Opts) ->
"unsupported option ~ts given to ~s", ['Elixir.Kernel':inspect(Key), Kind])
end || {Key, _} <- Opts, not lists:member(Key, Allowed)];
validate_opts(Meta, Kind, _Allowed, _Opts, S) ->
compile_error(Meta, S#elixir_scope.file, "invalid options for ~s, expected a keyword list", [Kind]).
validate_opts(Meta, Kind, _Allowed, _Opts, E) ->
compile_error(Meta, ?m(E, file), "invalid options for ~s, expected a keyword list", [Kind]).
no_alias_opts(KV) when is_list(KV) ->
case lists:keyfind(as, 1, KV) of
+3 -1
View File
@@ -101,7 +101,9 @@ do_receive(Meta, {Key, _}, _Acc, E) ->
%% Try
'try'(Meta, [], E) ->
compile_error(Meta, ?m(E, file), "missing do keywords in try");
compile_error(Meta, ?m(E, file), "missing do keyword in try");
'try'(Meta, [{do, _}], E) ->
compile_error(Meta, ?m(E, file), "missing catch/rescue/after/else keyword in try");
'try'(Meta, KV, E) when not is_list(KV) ->
elixir_errors:compile_error(Meta, ?m(E, file), "invalid arguments for try");
'try'(Meta, KV, E) ->
+5 -33
View File
@@ -126,29 +126,6 @@ collect_filters([H|T], Acc) ->
collect_filters([], Acc) ->
{Acc, []}.
%% If all we have is one enum generator, we check if it is a list
%% for optimization otherwise fallback to the reduce generator.
build_inline(Line, [{enum, Meta, Left, Right, Filters}] = Orig, Expr, Into, Var, Acc, S) ->
case Right of
{cons, _, _, _} ->
build_comprehension(Line, Orig, Expr, Into);
{Other, _, _} when Other == tuple; Other == map ->
build_reduce(Orig, Expr, Into, Acc, S);
_ ->
Clauses = [{enum, Meta, Left, Var, Filters}],
{'case', -1, Right, [
{clause, -1,
[Var],
[[elixir_utils:erl_call(Line, erlang, is_list, [Var])]],
[build_comprehension(Line, Clauses, Expr, Into)]},
{clause, -1,
[Var],
[],
[build_reduce(Clauses, Expr, Into, Acc, S)]}
]}
end;
build_inline(Line, Clauses, Expr, Into, _Var, Acc, S) ->
case lists:all(fun(Clause) -> element(1, Clause) == bin end, Clauses) of
true -> build_comprehension(Line, Clauses, Expr, Into);
@@ -165,7 +142,7 @@ build_into(Line, Clauses, Expr, Into, Fun, Acc, S) ->
MatchExpr = {match, Line,
{tuple, Line, [Acc, Fun]},
elixir_utils:erl_call(Line, 'Elixir.Collectable', into, [Into])
},
},
TryExpr =
{'try', Line,
@@ -199,10 +176,8 @@ build_reduce(Clauses, Expr, {bin, _, _} = Into, Acc, S) ->
build_reduce_clause([{enum, Meta, Left, Right, Filters}|T], Expr, Arg, Acc, S) ->
Line = ?line(Meta),
Inner = build_reduce_clause(T, Expr, Acc, Acc, S),
True = pair(Line, cont, Inner),
False = pair(Line, cont, Acc),
True = build_reduce_clause(T, Expr, Acc, Acc, S),
False = Acc,
Clauses0 =
case is_var(Left) of
@@ -218,11 +193,8 @@ build_reduce_clause([{enum, Meta, Left, Right, Filters}|T], Expr, Arg, Acc, S) -
[Left, Acc], [],
[join_filters(Line, Filters, True, False)]}|Clauses0],
Args = [Right, pair(Line, cont, Arg), {'fun', Line, {clauses, Clauses1}}],
Tuple = elixir_utils:erl_call(Line, 'Elixir.Enumerable', reduce, Args),
%% Use -1 because in case of no returns we don't care about the result
elixir_utils:erl_call(-1, erlang, element, [{integer, Line, 2}, Tuple]);
Args = [Right, Arg, {'fun', Line, {clauses, Clauses1}}],
elixir_utils:erl_call(Line, 'Elixir.Enum', reduce, Args);
build_reduce_clause([{bin, Meta, Left, Right, Filters}|T], Expr, Arg, Acc, S) ->
Line = ?line(Meta),
+26 -79
View File
@@ -74,104 +74,51 @@ unescape_chars(String) ->
unescape_chars(String, fun unescape_map/1).
unescape_chars(String, Map) ->
Octals = Map($0) /= false,
Hex = Map($x) == true,
unescape_chars(String, Map, Octals, Hex, <<>>).
unescape_chars(String, Map, Map($x) == true, <<>>).
%% Deprecated
unescape_chars(<<$\\,$x,A,B,Rest/binary>>, Map, true, Acc) when ?is_hex(A), ?is_hex(B) ->
append_escaped(Rest, Map, [A,B], true, Acc, 16);
unescape_chars(<<$\\,A,B,C,Rest/binary>>, Map, true, Hex, Acc) when ?is_octal(A), A =< $3, ?is_octal(B), ?is_octal(C) ->
io:format(standard_error, "warning: octals inside strings/sigils/chars are deprecated, got: \\~ts~n", [<<A, B, C>>]),
append_escaped(Rest, Map, [A,B,C], true, Hex, Acc, 8);
unescape_chars(<<$\\,$x,A,Rest/binary>>, Map, true, Acc) when ?is_hex(A) ->
append_escaped(Rest, Map, [A], true, Acc, 16);
unescape_chars(<<$\\,A,B,Rest/binary>>, Map, true, Hex, Acc) when ?is_octal(A), ?is_octal(B) ->
io:format(standard_error, "warning: octals inside strings/sigils/chars are deprecated, got: \\~ts~n", [<<A, B>>]),
append_escaped(Rest, Map, [A,B], true, Hex, Acc, 8);
unescape_chars(<<$\\,$x,${,A,$},Rest/binary>>, Map, true, Acc) when ?is_hex(A) ->
append_escaped(Rest, Map, [A], true, Acc, 16);
unescape_chars(<<$\\,$0,Rest/binary>>, Map, true, Hex, Acc) ->
append_escaped(Rest, Map, [$0], true, Hex, Acc, 8);
unescape_chars(<<$\\,$x,${,A,B,$},Rest/binary>>, Map, true, Acc) when ?is_hex(A), ?is_hex(B) ->
append_escaped(Rest, Map, [A,B], true, Acc, 16);
unescape_chars(<<$\\,A,Rest/binary>>, Map, true, Hex, Acc) when ?is_octal(A) ->
io:format(standard_error, "warning: octals inside strings/sigils/chars are deprecated, got: \\~ts~n", [<<A>>]),
append_escaped(Rest, Map, [A], true, Hex, Acc, 8);
unescape_chars(<<$\\,$x,${,A,B,C,$},Rest/binary>>, Map, true, Acc) when ?is_hex(A), ?is_hex(B), ?is_hex(C) ->
append_escaped(Rest, Map, [A,B,C], true, Acc, 16);
unescape_chars(<<$\\,$X,A,B,Rest/binary>>, Map, Octal, true, Acc) when ?is_hex(A), ?is_hex(B) ->
io:format(standard_error, "warning: \\X inside strings/sigils/chars is deprecated, please use \\x instead~n", []),
append_escaped(Rest, Map, [A,B], Octal, true, Acc, 16);
unescape_chars(<<$\\,$x,${,A,B,C,D,$},Rest/binary>>, Map, true, Acc) when ?is_hex(A), ?is_hex(B), ?is_hex(C), ?is_hex(D) ->
append_escaped(Rest, Map, [A,B,C,D], true, Acc, 16);
unescape_chars(<<$\\,$X,A,Rest/binary>>, Map, Octal, true, Acc) when ?is_hex(A) ->
io:format(standard_error, "warning: \\X inside strings/sigils/chars is deprecated, please use \\x instead~n", []),
append_escaped(Rest, Map, [A], Octal, true, Acc, 16);
unescape_chars(<<$\\,$x,${,A,B,C,D,E,$},Rest/binary>>, Map, true, Acc) when ?is_hex(A), ?is_hex(B), ?is_hex(C), ?is_hex(D), ?is_hex(E) ->
append_escaped(Rest, Map, [A,B,C,D,E], true, Acc, 16);
unescape_chars(<<$\\,$X,${,A,$},Rest/binary>>, Map, Octal, true, Acc) when ?is_hex(A) ->
io:format(standard_error, "warning: \\X inside strings/sigils/chars is deprecated, please use \\x instead~n", []),
append_escaped(Rest, Map, [A], Octal, true, Acc, 16);
unescape_chars(<<$\\,$x,${,A,B,C,D,E,F,$},Rest/binary>>, Map, true, Acc) when ?is_hex(A), ?is_hex(B), ?is_hex(C), ?is_hex(D), ?is_hex(E), ?is_hex(F) ->
append_escaped(Rest, Map, [A,B,C,D,E,F], true, Acc, 16);
unescape_chars(<<$\\,$X,${,A,B,$},Rest/binary>>, Map, Octal, true, Acc) when ?is_hex(A), ?is_hex(B) ->
io:format(standard_error, "warning: \\X inside strings/sigils/chars is deprecated, please use \\x instead~n", []),
append_escaped(Rest, Map, [A,B], Octal, true, Acc, 16);
unescape_chars(<<$\\,$X,${,A,B,C,$},Rest/binary>>, Map, Octal, true, Acc) when ?is_hex(A), ?is_hex(B), ?is_hex(C) ->
io:format(standard_error, "warning: \\X inside strings/sigils/chars is deprecated, please use \\x instead~n", []),
append_escaped(Rest, Map, [A,B,C], Octal, true, Acc, 16);
unescape_chars(<<$\\,$X,${,A,B,C,D,$},Rest/binary>>, Map, Octal, true, Acc) when ?is_hex(A), ?is_hex(B), ?is_hex(C), ?is_hex(D) ->
io:format(standard_error, "warning: \\X inside strings/sigils/chars is deprecated, please use \\x instead~n", []),
append_escaped(Rest, Map, [A,B,C,D], Octal, true, Acc, 16);
unescape_chars(<<$\\,$X,${,A,B,C,D,E,$},Rest/binary>>, Map, Octal, true, Acc) when ?is_hex(A), ?is_hex(B), ?is_hex(C), ?is_hex(D), ?is_hex(E) ->
io:format(standard_error, "warning: \\X inside strings/sigils/chars is deprecated, please use \\x instead~n", []),
append_escaped(Rest, Map, [A,B,C,D,E], Octal, true, Acc, 16);
unescape_chars(<<$\\,$X,${,A,B,C,D,E,F,$},Rest/binary>>, Map, Octal, true, Acc) when ?is_hex(A), ?is_hex(B), ?is_hex(C), ?is_hex(D), ?is_hex(E), ?is_hex(F) ->
io:format(standard_error, "warning: \\X inside strings/sigils/chars is deprecated, please use \\x instead~n", []),
append_escaped(Rest, Map, [A,B,C,D,E,F], Octal, true, Acc, 16);
%% End of deprecated
unescape_chars(<<$\\,$x,A,B,Rest/binary>>, Map, Octal, true, Acc) when ?is_hex(A), ?is_hex(B) ->
append_escaped(Rest, Map, [A,B], Octal, true, Acc, 16);
unescape_chars(<<$\\,$x,A,Rest/binary>>, Map, Octal, true, Acc) when ?is_hex(A) ->
append_escaped(Rest, Map, [A], Octal, true, Acc, 16);
unescape_chars(<<$\\,$x,${,A,$},Rest/binary>>, Map, Octal, true, Acc) when ?is_hex(A) ->
append_escaped(Rest, Map, [A], Octal, true, Acc, 16);
unescape_chars(<<$\\,$x,${,A,B,$},Rest/binary>>, Map, Octal, true, Acc) when ?is_hex(A), ?is_hex(B) ->
append_escaped(Rest, Map, [A,B], Octal, true, Acc, 16);
unescape_chars(<<$\\,$x,${,A,B,C,$},Rest/binary>>, Map, Octal, true, Acc) when ?is_hex(A), ?is_hex(B), ?is_hex(C) ->
append_escaped(Rest, Map, [A,B,C], Octal, true, Acc, 16);
unescape_chars(<<$\\,$x,${,A,B,C,D,$},Rest/binary>>, Map, Octal, true, Acc) when ?is_hex(A), ?is_hex(B), ?is_hex(C), ?is_hex(D) ->
append_escaped(Rest, Map, [A,B,C,D], Octal, true, Acc, 16);
unescape_chars(<<$\\,$x,${,A,B,C,D,E,$},Rest/binary>>, Map, Octal, true, Acc) when ?is_hex(A), ?is_hex(B), ?is_hex(C), ?is_hex(D), ?is_hex(E) ->
append_escaped(Rest, Map, [A,B,C,D,E], Octal, true, Acc, 16);
unescape_chars(<<$\\,$x,${,A,B,C,D,E,F,$},Rest/binary>>, Map, Octal, true, Acc) when ?is_hex(A), ?is_hex(B), ?is_hex(C), ?is_hex(D), ?is_hex(E), ?is_hex(F) ->
append_escaped(Rest, Map, [A,B,C,D,E,F], Octal, true, Acc, 16);
unescape_chars(<<$\\,$x,_/binary>>, _Map, _Octal, true, _Acc) ->
unescape_chars(<<$\\,$x,_/binary>>, _Map, true, _Acc) ->
Msg = <<"missing hex sequence after \\x">>,
error('Elixir.ArgumentError':exception([{message,Msg}]));
unescape_chars(<<$\\,Escaped,Rest/binary>>, Map, Octals, Hex, Acc) ->
unescape_chars(<<$\\,Escaped,Rest/binary>>, Map, Hex, Acc) ->
case Map(Escaped) of
false -> unescape_chars(Rest, Map, Octals, Hex, <<Acc/binary, $\\, Escaped>>);
Other -> unescape_chars(Rest, Map, Octals, Hex, <<Acc/binary, Other>>)
false -> unescape_chars(Rest, Map, Hex, <<Acc/binary, $\\, Escaped>>);
Other -> unescape_chars(Rest, Map, Hex, <<Acc/binary, Other>>)
end;
unescape_chars(<<Char, Rest/binary>>, Map, Octals, Hex, Acc) ->
unescape_chars(Rest, Map, Octals, Hex, <<Acc/binary, Char>>);
unescape_chars(<<Char, Rest/binary>>, Map, Hex, Acc) ->
unescape_chars(Rest, Map, Hex, <<Acc/binary, Char>>);
unescape_chars(<<>>, _Map, _Octals, _Hex, Acc) -> Acc.
unescape_chars(<<>>, _Map, _Hex, Acc) -> Acc.
append_escaped(Rest, Map, List, Octal, Hex, Acc, Base) ->
append_escaped(Rest, Map, List, Hex, Acc, Base) ->
Codepoint = list_to_integer(List, Base),
try <<Acc/binary, Codepoint/utf8>> of
Binary -> unescape_chars(Rest, Map, Octal, Hex, Binary)
Binary -> unescape_chars(Rest, Map, Hex, Binary)
catch
error:badarg ->
Msg = <<"invalid or reserved unicode codepoint ", (integer_to_binary(Codepoint))/binary>>,
+17 -12
View File
@@ -1,6 +1,6 @@
%% Module responsible for tracking lexical information.
-module(elixir_lexical).
-export([run/2,
-export([run/3, dest/1,
record_alias/4, record_alias/2,
record_import/4, record_import/2,
record_remote/2, format_error/1
@@ -9,21 +9,26 @@
-define(tracker, 'Elixir.Kernel.LexicalTracker').
run(File, Callback) ->
case code:is_loaded(?tracker) of
{file, _} ->
Pid = ?tracker:start_link(),
try
Callback(Pid)
after
warn_unused_aliases(File, Pid),
warn_unused_imports(File, Pid),
unlink(Pid), ?tracker:stop(Pid)
end;
run(File, Dest, Callback) ->
case elixir_compiler:get_opt(internal) of
false ->
{ok, Pid} = ?tracker:start_link(Dest),
try Callback(Pid) of
Res ->
warn_unused_aliases(File, Pid),
warn_unused_imports(File, Pid),
Res
after
unlink(Pid),
?tracker:stop(Pid)
end;
true ->
Callback(nil)
end.
dest(nil) -> nil;
dest(Pid) -> ?tracker:dest(Pid).
%% RECORD
record_alias(Module, Line, Warn, Ref) ->
+10 -6
View File
@@ -9,7 +9,7 @@
-export([macro_for/3, local_for/3, local_for/4]).
-include("elixir.hrl").
-define(attr, '__locals_tracker').
-define(attr, {elixir, locals_tracker}).
-define(tracker, 'Elixir.Module.LocalsTracker').
macro_for(Module, Name, Arity) ->
@@ -72,9 +72,13 @@ rewrite_clause(Else, _) -> Else.
%% TRACKING
setup(Module) ->
case code:is_loaded(?tracker) of
{file, _} -> ets:insert(Module, {?attr, ?tracker:start_link()}), ok;
false -> ok
case elixir_compiler:get_opt(internal) of
false ->
{ok, Pid} = ?tracker:start_link(),
ets:insert(elixir_module:data_table(Module), {?attr, Pid}),
ok;
true ->
ok
end.
cleanup(Module) ->
@@ -104,7 +108,7 @@ if_tracker(Module, Callback) ->
if_tracker(Module, ok, Callback).
if_tracker(Module, Default, Callback) ->
try ets:lookup_element(Module, ?attr, 2) of
try ets:lookup_element(elixir_module:data_table(Module), ?attr, 2) of
Pid -> Callback(Pid)
catch
error:badarg -> Default
@@ -114,7 +118,7 @@ if_tracker(Module, Default, Callback) ->
cache_env(#{module := Module} = RE) ->
E = RE#{line := nil,vars := []},
try ets:lookup_element(Module, ?attr, 2) of
try ets:lookup_element(elixir_module:data_table(Module), ?attr, 2) of
Pid ->
{Pid, ?tracker:cache_env(Pid, E)}
catch
+18 -11
View File
@@ -5,9 +5,11 @@
expand_map(Meta, [{'|', UpdateMeta, [Left, Right]}], E) ->
{[ELeft|ERight], EA} = elixir_exp:expand_args([Left|Right], E),
validate_kv(Meta, ERight, Right, E),
{{'%{}', Meta, [{'|', UpdateMeta, [ELeft, ERight]}]}, EA};
expand_map(Meta, Args, E) ->
{EArgs, EA} = elixir_exp:expand_args(Args, E),
validate_kv(Meta, EArgs, Args, E),
{{'%{}', Meta, EArgs}, EA}.
expand_struct(Meta, Left, Right, E) ->
@@ -35,6 +37,15 @@ expand_struct(Meta, Left, Right, E) ->
{{'%', EMeta, [ELeft, ERight]}, EE}.
validate_kv(Meta, KV, Original, E) ->
lists:foldl(fun
({_K, _V}, Acc) -> Acc + 1;
(_, Acc) ->
compile_error(Meta, ?m(E, file),
"expected key-value pairs in a map, got: ~ts",
['Elixir.Macro':to_string(lists:nth(Acc, Original))])
end, 1, KV).
translate_map(Meta, Args, S) ->
{Assocs, TUpdate, US} = extract_assoc_update(Args, S),
translate_map(Meta, Assocs, TUpdate, US).
@@ -101,11 +112,11 @@ load_struct(Meta, Name, S) ->
error:undef ->
Inspected = elixir_aliases:inspect(Name),
case Context of
case Context andalso (S#elixir_scope.function == nil) of
true ->
compile_error(Meta, S#elixir_scope.file,
"cannot access struct ~ts in the same context that defines it as "
"the struct fields are not yet accessible", [Inspected]);
"cannot access struct ~ts, the struct was not yet defined or the struct is being "
"accessed in the same context that defines it", [Inspected]);
false ->
compile_error(Meta, S#elixir_scope.file, "~ts.__struct__/0 is undefined, "
"cannot expand struct ~ts", [Inspected, Inspected])
@@ -139,14 +150,10 @@ translate_map(Meta, Assocs, TUpdate, #elixir_scope{extra=Extra} = S) ->
Line = ?line(Meta),
{TArgs, SA} = lists:mapfoldl(fun
({Key, Value}, Acc) ->
{TKey, Acc1} = KeyFun(Key, Acc),
{TValue, Acc2} = ValFun(Value, Acc1#elixir_scope{extra=Extra}),
{{Op, ?line(Meta), TKey, TValue}, Acc2};
(Other, _Acc) ->
compile_error(Meta, S#elixir_scope.file, "expected key-value pairs in map, got: ~ts",
['Elixir.Macro':to_string(Other)])
{TArgs, SA} = lists:mapfoldl(fun({Key, Value}, Acc) ->
{TKey, Acc1} = KeyFun(Key, Acc),
{TValue, Acc2} = ValFun(Value, Acc1#elixir_scope{extra=Extra}),
{{Op, ?line(Meta), TKey, TValue}, Acc2}
end, S, Assocs),
build_map(Line, TUpdate, TArgs, SA).
+97 -98
View File
@@ -1,25 +1,27 @@
-module(elixir_module).
-export([compile/4, data_table/1, docs_table/1, is_open/1,
expand_callback/6, add_beam_chunk/3, format_error/1]).
-export([data_table/1, defs_table/1, clas_table/1, is_open/1,
compile/4, expand_callback/6, add_beam_chunk/3, format_error/1]).
-include("elixir.hrl").
-define(acc_attr, '__acc_attributes').
-define(docs_attr, '__docs_table').
-define(lexical_attr, '__lexical_tracker').
-define(persisted_attr, '__persisted_attributes').
-define(overridable_attr, '__overridable').
-define(location_attr, '__location').
-define(acc_attr, {elixir, acc_attributes}).
-define(lexical_attr, {elixir, lexical_tracker}).
-define(persisted_attr, {elixir, persisted_attributes}).
-define(overridable_attr, {elixir, overridable}).
-define(location_attr, {elixir, location}).
%% TABLE METHODS
data_table(Module) ->
Module.
ets:lookup_element(elixir_modules, Module, 2).
docs_table(Module) ->
ets:lookup_element(Module, ?docs_attr, 2).
defs_table(Module) ->
ets:lookup_element(elixir_modules, Module, 3).
clas_table(Module) ->
ets:lookup_element(elixir_modules, Module, 4).
is_open(Module) ->
Module == ets:info(Module, name).
ets:lookup(elixir_modules, Module) /= [].
%% Compilation hook
@@ -34,7 +36,7 @@ compile(Module, Block, Vars, #{line := Line} = Env) when is_atom(Module) ->
case ?m(LexEnv, lexical_tracker) of
nil ->
elixir_lexical:run(?m(LexEnv, file), fun(Pid) ->
elixir_lexical:run(?m(LexEnv, file), nil, fun(Pid) ->
do_compile(Line, Module, Block, Vars, LexEnv#{lexical_tracker := Pid})
end);
_ ->
@@ -49,12 +51,12 @@ do_compile(Line, Module, Block, Vars, E) ->
check_module_availability(Line, File, Module),
Docs = elixir_compiler:get_opt(docs),
build(Line, File, Module, Docs, ?m(E, lexical_tracker)),
{Data, Defs, Clas, Ref} = build(Line, File, Module, Docs, ?m(E, lexical_tracker)),
try
{Result, NE} = eval_form(Line, Module, Block, Vars, E),
{Result, NE} = eval_form(Line, Module, Data, Block, Vars, E),
_ = case ets:lookup(data_table(Module), 'on_load') of
_ = case ets:lookup(Data, 'on_load') of
[] -> ok;
[{on_load,OnLoad}] ->
[elixir_locals:record_local(Tuple, Module) || Tuple <- OnLoad]
@@ -65,14 +67,14 @@ do_compile(Line, Module, Block, Vars, E) ->
{All, Forms0} = functions_form(Line, File, Module, Def, Defp,
Defmacro, Defmacrop, Exports, Functions),
Forms1 = specs_form(Module, Defmacro, Defmacrop, Forms0),
Forms2 = types_form(Line, File, Module, Forms1),
Forms3 = attributes_form(Line, File, Module, Forms2),
Forms1 = specs_form(Data, Defmacro, Defmacrop, Forms0),
Forms2 = types_form(Line, File, Data, Forms1),
Forms3 = attributes_form(Line, File, Data, Forms2),
elixir_locals:ensure_no_import_conflict(Line, File, Module, All),
case Docs of
true -> warn_unused_docs(Line, File, Module, doc);
true -> warn_unused_docs(Line, File, Data, doc);
false -> false
end,
@@ -83,65 +85,68 @@ do_compile(Line, Module, Block, Vars, E) ->
{attribute, Line, module, Module} | Forms3
],
Binary = load_form(Line, Final, compile_opts(Module), NE),
Binary = load_form(Line, Data, Final, compile_opts(Module), NE),
{module, Module, Binary, Result}
after
elixir_locals:cleanup(Module),
elixir_def:cleanup(Module),
ets:delete(docs_table(Module)),
ets:delete(data_table(Module))
ets:delete(Data),
ets:delete(Defs),
ets:delete(Clas),
elixir_code_server:call({undefmodule, Ref})
end.
%% Hook that builds both attribute and functions and set up common hooks.
build(Line, File, Module, Docs, Lexical) ->
%% Table with meta information about the module.
DataTable = data_table(Module),
OldTable = ets:info(DataTable, name),
case OldTable == DataTable of
true ->
[{OldFile, OldLine}] = ets:lookup_element(OldTable, ?location_attr, 2),
case ets:lookup(elixir_modules, Module) of
[{Module, _, _, _, OldLine, OldFile}] ->
Error = {module_in_definition, Module, OldFile, OldLine},
elixir_errors:form_error([{line, Line}], File, ?MODULE, Error);
false ->
_ ->
[]
end,
DataTable = ets:new(DataTable, [set, named_table, public]),
ets:insert(DataTable, {before_compile, []}),
ets:insert(DataTable, {after_compile, []}),
Data = ets:new(Module, [set, public]),
Defs = ets:new(Module, [set, public]),
Clas = ets:new(Module, [bag, public]),
Ref = elixir_code_server:call({defmodule, self(),
{Module, Data, Defs, Clas, Line, File}}),
ets:insert(Data, {before_compile, []}),
ets:insert(Data, {after_compile, []}),
ets:insert(Data, {moduledoc, nil}),
case Docs of
true -> ets:insert(DataTable, {on_definition, [{'Elixir.Module', compile_doc}]});
_ -> ets:insert(DataTable, {on_definition, []})
true -> ets:insert(Data, {on_definition, [{'Elixir.Module', compile_doc}]});
_ -> ets:insert(Data, {on_definition, []})
end,
Attributes = [behaviour, on_load, spec, type, typep, opaque, callback, compile, external_resource],
ets:insert(DataTable, {?acc_attr, [before_compile, after_compile, on_definition, derive|Attributes]}),
ets:insert(DataTable, {?persisted_attr, [vsn|Attributes]}),
ets:insert(DataTable, {?docs_attr, ets:new(DataTable, [ordered_set, public])}),
ets:insert(DataTable, {?lexical_attr, Lexical}),
ets:insert(DataTable, {?overridable_attr, []}),
ets:insert(DataTable, {?location_attr, [{File, Line}]}),
Attributes = [behaviour, on_load, compile, external_resource],
ets:insert(Data, {?acc_attr, [before_compile, after_compile, on_definition, derive,
spec, type, typep, opaque, callback|Attributes]}),
ets:insert(Data, {?persisted_attr, [vsn|Attributes]}),
ets:insert(Data, {?lexical_attr, Lexical}),
%% Setup other modules
%% Setup definition related modules
elixir_def:setup(Module),
elixir_locals:setup(Module),
ok.
elixir_def_overridable:setup(Module),
{Data, Defs, Clas, Ref}.
%% Receives the module representation and evaluates it.
eval_form(Line, Module, Block, Vars, E) ->
eval_form(Line, Module, Data, Block, Vars, E) ->
{Value, EE} = elixir_compiler:eval_forms(Block, Vars, E),
elixir_def_overridable:store_pending(Module),
EV = elixir_env:linify({Line, EE#{vars := [], export_vars := nil}}),
EC = eval_callbacks(Line, Module, before_compile, [EV], EV),
EC = eval_callbacks(Line, Data, before_compile, [EV], EV),
elixir_def_overridable:store_pending(Module),
{Value, EC}.
eval_callbacks(Line, Module, Name, Args, E) ->
Callbacks = lists:reverse(ets:lookup_element(data_table(Module), Name, 2)),
eval_callbacks(Line, Data, Name, Args, E) ->
Callbacks = lists:reverse(ets:lookup_element(Data, Name, 2)),
lists:foldl(fun({M,F}, Acc) ->
expand_callback(Line, M, F, Args, Acc#{vars := [], export_vars := nil},
@@ -160,13 +165,11 @@ functions_form(Line, File, Module, Def, Defp, Defmacro, Defmacrop, Exports, Body
%% Add attributes handling to the form
attributes_form(Line, File, Module, Current) ->
Table = data_table(Module),
attributes_form(Line, File, Data, Current) ->
AccAttrs = ets:lookup_element(Data, ?acc_attr, 2),
PersistedAttrs = ets:lookup_element(Data, ?persisted_attr, 2),
AccAttrs = ets:lookup_element(Table, '__acc_attributes', 2),
PersistedAttrs = ets:lookup_element(Table, '__persisted_attributes', 2),
Transform = fun({Key, Value}, Acc) ->
Transform = fun({Key, Value}, Acc) when is_atom(Key) ->
case lists:member(Key, PersistedAttrs) of
false -> Acc;
true ->
@@ -182,7 +185,8 @@ attributes_form(Line, File, Module, Current) ->
end
end,
ets:foldl(Transform, Current, Table).
Results = ets:select(Data, [{{'$1', '_'}, [{is_atom, '$1'}], ['$_']}]),
lists:foldl(Transform, Current, Results).
process_attribute(Line, File, external_resource, Values) ->
lists:usort([process_external_resource(Line, File, Value) || Value <- Values]);
@@ -197,27 +201,21 @@ process_external_resource(Line, File, Value) ->
%% Types
types_form(Line, File, Module, Forms0) ->
case code:ensure_loaded('Elixir.Kernel.Typespec') of
{module, 'Elixir.Kernel.Typespec'} ->
Types0 = 'Elixir.Module':get_attribute(Module, type) ++
'Elixir.Module':get_attribute(Module, typep) ++
'Elixir.Module':get_attribute(Module, opaque),
types_form(Line, File, Data, Forms0) ->
case elixir_compiler:get_opt(internal) of
false ->
Types0 = get_typespec(Data, type) ++ get_typespec(Data, typep)
++ get_typespec(Data, opaque),
Types1 = ['Elixir.Kernel.Typespec':translate_type(Kind, Expr, Doc, Caller) ||
{Kind, Expr, Doc, Caller} <- Types0],
'Elixir.Module':delete_attribute(Module, type),
'Elixir.Module':delete_attribute(Module, typep),
'Elixir.Module':delete_attribute(Module, opaque),
warn_unused_docs(Line, File, Module, typedoc),
warn_unused_docs(Line, File, Data, typedoc),
Forms1 = types_attributes(Types1, Forms0),
Forms2 = export_types_attributes(Types1, Forms1),
typedocs_attributes(Types1, Forms2);
{error, _} ->
true ->
Forms0
end.
@@ -247,23 +245,17 @@ typedocs_attributes(Types, Forms) ->
%% Specs
specs_form(Module, Defmacro, Defmacrop, Forms) ->
case code:ensure_loaded('Elixir.Kernel.Typespec') of
{module, 'Elixir.Kernel.Typespec'} ->
Specs0 = 'Elixir.Module':get_attribute(Module, spec) ++
'Elixir.Module':get_attribute(Module, callback),
specs_form(Data, Defmacro, Defmacrop, Forms) ->
case elixir_compiler:get_opt(internal) of
false ->
Specs0 = get_typespec(Data, spec) ++ get_typespec(Data, callback),
Specs1 = ['Elixir.Kernel.Typespec':translate_spec(Kind, Expr, Caller) ||
{Kind, Expr, Caller} <- Specs0],
Specs2 = lists:flatmap(fun(Spec) ->
translate_macro_spec(Spec, Defmacro, Defmacrop)
end, Specs1),
'Elixir.Module':delete_attribute(Module, spec),
'Elixir.Module':delete_attribute(Module, callback),
specs_attributes(Forms, Specs2);
{error, _} ->
true ->
Forms
end.
@@ -307,11 +299,11 @@ compile_opts(Module) ->
[] -> []
end.
load_form(Line, Forms, Opts, #{file := File} = E) ->
elixir_compiler:module(Forms, File, Opts, fun(Module, Binary0) ->
load_form(Line, Data, Forms, Opts, E) ->
elixir_compiler:module(Forms, Opts, E, fun(Module, Binary0) ->
Docs = elixir_compiler:get_opt(docs),
Binary = add_docs_chunk(Binary0, Module, Line, Docs),
eval_callbacks(Line, Module, after_compile, [E, Binary], E),
Binary = add_docs_chunk(Binary0, Data, Line, Docs),
eval_callbacks(Line, Data, after_compile, [E, Binary], E),
case get(elixir_compiled) of
Current when is_list(Current) ->
@@ -321,7 +313,7 @@ load_form(Line, Forms, Opts, #{file := File} = E) ->
undefined -> ok;
PID ->
Ref = make_ref(),
PID ! {module_available, self(), Ref, File, Module, Binary},
PID ! {module_available, self(), Ref, ?m(E, file), Module, Binary},
receive {Ref, ack} -> ok end
end;
_ ->
@@ -331,23 +323,30 @@ load_form(Line, Forms, Opts, #{file := File} = E) ->
Binary
end).
add_docs_chunk(Bin, Module, Line, true) ->
add_docs_chunk(Bin, Data, Line, true) ->
ChunkData = term_to_binary({elixir_docs_v1, [
{docs, get_docs(Module)},
{moduledoc, get_moduledoc(Line, Module)}
]}),
{docs, get_docs(Data)},
{moduledoc, get_moduledoc(Line, Data)}
]}),
add_beam_chunk(Bin, "ExDc", ChunkData);
add_docs_chunk(Bin, _, _, _) -> Bin.
get_docs(Module) ->
ordsets:from_list(
get_docs(Data) ->
Match = ets:match(Data, {{doc, '$1'}, '$2', '$3', '$4', '$5'}),
lists:usort(
[{Tuple, Line, Kind, Sig, Doc} ||
{Tuple, Line, Kind, Sig, Doc} <- ets:tab2list(docs_table(Module)),
Kind =/= type, Kind =/= opaque]).
[Tuple, Line, Kind, Sig, Doc] <- Match,
Kind =/= type, Kind =/= opaque]).
get_moduledoc(Line, Module) ->
{Line, 'Elixir.Module':get_attribute(Module, moduledoc)}.
get_moduledoc(Line, Data) ->
{Line, ets:lookup_element(Data, moduledoc, 2)}.
get_typespec(Data, Key) ->
case ets:lookup(Data, Key) of
[{Key, Value}] -> Value;
[] -> []
end.
check_module_availability(Line, File, Module) ->
Reserved = ['Elixir.Any', 'Elixir.BitString', 'Elixir.Function', 'Elixir.PID',
@@ -370,8 +369,8 @@ check_module_availability(Line, File, Module) ->
ok
end.
warn_unused_docs(Line, File, Module, Attribute) ->
case ets:member(data_table(Module), Attribute) of
warn_unused_docs(Line, File, Data, Attribute) ->
case ets:member(Data, Attribute) of
true ->
elixir_errors:form_warn([{line, Line}], File, ?MODULE, {unused_doc, Attribute});
_ ->
+38 -30
View File
@@ -7,7 +7,7 @@ Nonterminals
add_op_eol mult_op_eol hat_op_eol two_op_eol pipe_op_eol stab_op_eol
arrow_op_eol match_op_eol when_op_eol in_op_eol in_match_op_eol
type_op_eol rel_op_eol
open_paren close_paren empty_paren
open_paren close_paren empty_paren eoe
list list_args open_bracket close_bracket
tuple open_curly close_curly
bit_string open_bit close_bit
@@ -16,11 +16,11 @@ Nonterminals
container_args_base container_args call_args_parens_base call_args_parens parens_call
call_args_no_parens_one call_args_no_parens_expr call_args_no_parens_comma_expr
call_args_no_parens_all call_args_no_parens_many call_args_no_parens_many_strict
stab stab_eol stab_expr stab_maybe_expr stab_parens_many
stab stab_eoe stab_expr stab_maybe_expr stab_parens_many
kw_eol kw_base kw call_args_no_parens_kw_expr call_args_no_parens_kw
dot_op dot_alias dot_identifier dot_op_identifier dot_do_identifier
dot_paren_identifier dot_bracket_identifier
do_block fn_eol do_eol end_eol block_eol block_item block_list
do_block fn_eoe do_eoe end_eoe block_eoe block_item block_list
.
Terminals
@@ -32,13 +32,13 @@ Terminals
comp_op at_op unary_op and_op or_op arrow_op match_op in_op in_match_op
type_op dual_op add_op mult_op hat_op two_op pipe_op stab_op when_op assoc_op
capture_op rel_op
'true' 'false' 'nil' 'do' eol ',' '.'
'true' 'false' 'nil' 'do' eol ';' ',' '.'
'(' ')' '[' ']' '{' '}' '<<' '>>' '%{}' '%'
.
Rootsymbol grammar.
%% There are two shift/reduce conflicts coming from call_args_parens.
%% Two shift/reduce conflicts coming from call_args_parens.
Expect 2.
%% Changes in ops and precedence should be reflected on lib/elixir/lib/macro.ex
@@ -58,12 +58,12 @@ Left 130 or_op_eol. %% ||, |||, or
Left 140 and_op_eol. %% &&, &&&, and
Left 150 comp_op_eol. %% ==, !=, =~, ===, !==
Left 160 rel_op_eol. %% <, >, <=, >=
Left 170 arrow_op_eol. %% < (op), (op) > (e.g |>, <<<, >>>)
Left 170 arrow_op_eol. %% < (op), (op) > (|>, <<<, >>>, ~>>, <<~, ~>, <~, <~>, <|>)
Left 180 in_op_eol. %% in
Right 200 two_op_eol. %% ++, --, .., <>
Left 210 add_op_eol. %% + (op), - (op)
Left 220 mult_op_eol. %% * (op), / (op)
Left 250 hat_op_eol. %% ^ (op) (e.g ^^^)
Left 250 hat_op_eol. %% ^ (op) (^^^)
Nonassoc 300 unary_op_eol. %% +, -, !, ^, not, ~~~
Left 310 dot_call_op.
Left 310 dot_op. %% .
@@ -72,16 +72,16 @@ Nonassoc 330 dot_identifier.
%%% MAIN FLOW OF EXPRESSIONS
grammar -> eol : nil.
grammar -> eoe : nil.
grammar -> expr_list : to_block('$1').
grammar -> eol expr_list : to_block('$2').
grammar -> expr_list eol : to_block('$1').
grammar -> eol expr_list eol : to_block('$2').
grammar -> eoe expr_list : to_block('$2').
grammar -> expr_list eoe : to_block('$1').
grammar -> eoe expr_list eoe : to_block('$2').
grammar -> '$empty' : nil.
% Note expressions are on reverse order
expr_list -> expr : ['$1'].
expr_list -> expr_list eol expr : ['$3'|'$1'].
expr_list -> expr_list eoe expr : ['$3'|'$1'].
expr -> empty_paren : nil.
expr -> matched_expr : '$1'.
@@ -202,8 +202,12 @@ access_expr -> bracket_expr : '$1'.
access_expr -> at_op_eol number : build_unary_op('$1', ?exprs('$2')).
access_expr -> unary_op_eol number : build_unary_op('$1', ?exprs('$2')).
access_expr -> capture_op_eol number : build_unary_op('$1', ?exprs('$2')).
access_expr -> fn_eol stab end_eol : build_fn('$1', build_stab(reverse('$2'))).
access_expr -> fn_eoe stab end_eoe : build_fn('$1', build_stab(reverse('$2'))).
access_expr -> open_paren stab close_paren : build_stab(reverse('$2')).
access_expr -> open_paren stab ';' close_paren : build_stab(reverse('$2')).
access_expr -> open_paren ';' stab ';' close_paren : build_stab(reverse('$3')).
access_expr -> open_paren ';' stab close_paren : build_stab(reverse('$3')).
access_expr -> open_paren ';' close_paren : build_stab([]).
access_expr -> number : ?exprs('$1').
access_expr -> signed_number : {element(4, '$1'), meta('$1'), ?exprs('$1')}.
access_expr -> list : element(1, '$1').
@@ -240,28 +244,32 @@ bracket_at_expr -> at_op_eol access_expr bracket_arg :
%% Blocks
do_block -> do_eol 'end' : [[{do,nil}]].
do_block -> do_eol stab end_eol : [[{do, build_stab(reverse('$2'))}]].
do_block -> do_eol block_list 'end' : [[{do, nil}|'$2']].
do_block -> do_eol stab_eol block_list 'end' : [[{do, build_stab(reverse('$2'))}|'$3']].
do_block -> do_eoe 'end' : [[{do,nil}]].
do_block -> do_eoe stab end_eoe : [[{do, build_stab(reverse('$2'))}]].
do_block -> do_eoe block_list 'end' : [[{do, nil}|'$2']].
do_block -> do_eoe stab_eoe block_list 'end' : [[{do, build_stab(reverse('$2'))}|'$3']].
fn_eol -> 'fn' : '$1'.
fn_eol -> 'fn' eol : '$1'.
eoe -> eol : '$1'.
eoe -> ';' : '$1'.
eoe -> eol ';' : '$1'.
do_eol -> 'do' : '$1'.
do_eol -> 'do' eol : '$1'.
fn_eoe -> 'fn' : '$1'.
fn_eoe -> 'fn' eoe : '$1'.
end_eol -> 'end' : '$1'.
end_eol -> eol 'end' : '$2'.
do_eoe -> 'do' : '$1'.
do_eoe -> 'do' eoe : '$1'.
block_eol -> block_identifier : '$1'.
block_eol -> block_identifier eol : '$1'.
end_eoe -> 'end' : '$1'.
end_eoe -> eoe 'end' : '$2'.
block_eoe -> block_identifier : '$1'.
block_eoe -> block_identifier eoe : '$1'.
stab -> stab_expr : ['$1'].
stab -> stab eol stab_expr : ['$3'|'$1'].
stab -> stab eoe stab_expr : ['$3'|'$1'].
stab_eol -> stab : '$1'.
stab_eol -> stab eol : '$1'.
stab_eoe -> stab : '$1'.
stab_eoe -> stab eoe : '$1'.
stab_expr -> expr : '$1'.
stab_expr -> stab_op_eol stab_maybe_expr : build_op('$1', [], '$2').
@@ -275,8 +283,8 @@ stab_expr -> stab_parens_many when_op expr stab_op_eol stab_maybe_expr :
stab_maybe_expr -> 'expr' : '$1'.
stab_maybe_expr -> '$empty' : nil.
block_item -> block_eol stab_eol : {?exprs('$1'), build_stab(reverse('$2'))}.
block_item -> block_eol : {?exprs('$1'), nil}.
block_item -> block_eoe stab_eoe : {?exprs('$1'), build_stab(reverse('$2'))}.
block_item -> block_eoe : {?exprs('$1'), nil}.
block_list -> block_item : ['$1'].
block_list -> block_item block_list : ['$1'|'$2'].
+36 -2
View File
@@ -151,6 +151,10 @@ escape(Expr, Unquote) ->
%% Quotes an expression and return its quoted Elixir AST.
quote({unquote_splicing, _, [_]}, _Binding, #elixir_quote{unquote=true}, _) ->
argument_error(<<"unquote_splicing only works inside arguments and block contexts, "
"wrap it in parens if you want it to work with one-liners">>);
quote(Expr, nil, Q, E) ->
do_quote(Expr, Q, E);
@@ -242,6 +246,15 @@ do_quote({Left, Right}, Q, E) ->
{TRight, RQ} = do_quote(Right, LQ, E),
{{TLeft, TRight}, RQ};
do_quote(BitString, #elixir_quote{escape=true} = Q, _) when is_bitstring(BitString) ->
case bit_size(BitString) rem 8 of
0 ->
{BitString, Q};
Size ->
<<Bits:Size, Bytes/binary>> = BitString,
{{'<<>>', [], [{'::', [], [Bits, Size]}, Bytes]}, Q}
end;
do_quote(Map, #elixir_quote{escape=true} = Q, E) when is_map(Map) ->
{TT, TQ} = do_quote(maps:to_list(Map), Q, E),
{{'%{}', [], TT}, TQ};
@@ -251,7 +264,7 @@ do_quote(Tuple, #elixir_quote{escape=true} = Q, E) when is_tuple(Tuple) ->
{{'{}', [], TT}, TQ};
do_quote(List, #elixir_quote{escape=true} = Q, E) when is_list(List) ->
% The improper case is pretty inefficient, but improper lists are are.
%% The improper case is a bit inefficient, but improper lists are rare.
case reverse_improper(List) of
{L} -> do_splice(L, Q, E);
{L, R} ->
@@ -259,14 +272,35 @@ do_quote(List, #elixir_quote{escape=true} = Q, E) when is_list(List) ->
{TR, QR} = do_quote(R, QL, E),
{update_last(TL, fun(X) -> {'|', [], [X, TR]} end), QR}
end;
do_quote(Other, #elixir_quote{escape=true} = Q, _)
when is_number(Other); is_pid(Other); is_atom(Other) ->
{Other, Q};
do_quote(Fun, #elixir_quote{escape=true} = Q, _) when is_function(Fun) ->
case (erlang:fun_info(Fun, env) == {env, []}) andalso
(erlang:fun_info(Fun, type) == {type, external}) of
true -> {Fun, Q};
false -> bad_escape(Fun)
end;
do_quote(Other, #elixir_quote{escape=true}, _) ->
bad_escape(Other);
do_quote(List, Q, E) when is_list(List) ->
do_splice(lists:reverse(List), Q, E);
do_splice(lists:reverse(List), Q, E);
do_quote(Other, Q, _) ->
{Other, Q}.
%% Quote helpers
bad_escape(Arg) ->
Msg = <<"cannot escape ", ('Elixir.Kernel':inspect(Arg, []))/binary, ". ",
"The supported values are: lists, tuples, maps, atoms, numbers, bitstrings, ",
"pids and remote functions in the format &Mod.fun/arity">>,
error('Elixir.ArgumentError':exception([{message,Msg}])).
do_quote_call(Left, Meta, Expr, Args, Q, E) ->
All = [meta(Meta, Q), Left, {unquote, Meta, [Expr]}, Args,
Q#elixir_quote.context],
+224
View File
@@ -0,0 +1,224 @@
-module(elixir_rewrite).
-export([rewrite/5, inline/3]).
%% Convenience variables
-define(hidden, [{line,-1}]).
-define(atom, 'Elixir.Atom').
-define(enum, 'Elixir.Enum').
-define(float, 'Elixir.Float').
-define(io, 'Elixir.IO').
-define(integer, 'Elixir.Integer').
-define(kernel, 'Elixir.Kernel').
-define(list, 'Elixir.List').
-define(map, 'Elixir.Map').
-define(node, 'Elixir.Node').
-define(port, 'Elixir.Port').
-define(process, 'Elixir.Process').
-define(string, 'Elixir.String').
-define(string_chars, 'Elixir.String.Chars').
-define(system, 'Elixir.System').
-define(tuple, 'Elixir.Tuple').
%% Inline
%% Inline rules are straight-forward, they keep the same
%% number and order of arguments and show up on captures.
inline(?atom, to_char_list, 1) -> {erlang, atom_to_list};
inline(?io, iodata_length, 1) -> {erlang, iolist_size};
inline(?io, iodata_to_binary, 1) -> {erlang, iolist_to_binary};
inline(?integer, to_string, 1) -> {erlang, integer_to_binary};
inline(?integer, to_string, 2) -> {erlang, integer_to_binary};
inline(?integer, to_char_list, 1) -> {erlang, integer_to_list};
inline(?integer, to_char_list, 2) -> {erlang, integer_to_list};
inline(?float, to_string, 1) -> {erlang, float_to_binary};
inline(?float, to_char_list, 1) -> {erlang, float_to_list};
inline(?list, to_atom, 1) -> {erlang, list_to_atom};
inline(?list, to_existing_atom, 1) -> {erlang, list_to_existing_atom};
inline(?list, to_float, 1) -> {erlang, list_to_float};
inline(?list, to_integer, 1) -> {erlang, list_to_integer};
inline(?list, to_integer, 2) -> {erlang, list_to_integer};
inline(?list, to_tuple, 1) -> {erlang, list_to_tuple};
inline(?kernel, '+', 2) -> {erlang, '+'};
inline(?kernel, '-', 2) -> {erlang, '-'};
inline(?kernel, '+', 1) -> {erlang, '+'};
inline(?kernel, '-', 1) -> {erlang, '-'};
inline(?kernel, '*', 2) -> {erlang, '*'};
inline(?kernel, '/', 2) -> {erlang, '/'};
inline(?kernel, '++', 2) -> {erlang, '++'};
inline(?kernel, '--', 2) -> {erlang, '--'};
inline(?kernel, 'not', 1) -> {erlang, 'not'};
inline(?kernel, '<', 2) -> {erlang, '<'};
inline(?kernel, '>', 2) -> {erlang, '>'};
inline(?kernel, '<=', 2) -> {erlang, '=<'};
inline(?kernel, '>=', 2) -> {erlang, '>='};
inline(?kernel, '==', 2) -> {erlang, '=='};
inline(?kernel, '!=', 2) -> {erlang, '/='};
inline(?kernel, '===', 2) -> {erlang, '=:='};
inline(?kernel, '!==', 2) -> {erlang, '=/='};
inline(?kernel, abs, 1) -> {erlang, abs};
inline(?kernel, apply, 2) -> {erlang, apply};
inline(?kernel, apply, 3) -> {erlang, apply};
inline(?kernel, binary_part, 3) -> {erlang, binary_part};
inline(?kernel, bit_size, 1) -> {erlang, bit_size};
inline(?kernel, byte_size, 1) -> {erlang, byte_size};
inline(?kernel, 'div', 2) -> {erlang, 'div'};
inline(?kernel, exit, 1) -> {erlang, exit};
inline(?kernel, hd, 1) -> {erlang, hd};
inline(?kernel, is_atom, 1) -> {erlang, is_atom};
inline(?kernel, is_binary, 1) -> {erlang, is_binary};
inline(?kernel, is_bitstring, 1) -> {erlang, is_bitstring};
inline(?kernel, is_boolean, 1) -> {erlang, is_boolean};
inline(?kernel, is_float, 1) -> {erlang, is_float};
inline(?kernel, is_function, 1) -> {erlang, is_function};
inline(?kernel, is_function, 2) -> {erlang, is_function};
inline(?kernel, is_integer, 1) -> {erlang, is_integer};
inline(?kernel, is_list, 1) -> {erlang, is_list};
inline(?kernel, is_map, 1) -> {erlang, is_map};
inline(?kernel, is_number, 1) -> {erlang, is_number};
inline(?kernel, is_pid, 1) -> {erlang, is_pid};
inline(?kernel, is_port, 1) -> {erlang, is_port};
inline(?kernel, is_reference, 1) -> {erlang, is_reference};
inline(?kernel, is_tuple, 1) -> {erlang, is_tuple};
inline(?kernel, length, 1) -> {erlang, length};
inline(?kernel, make_ref, 0) -> {erlang, make_ref};
inline(?kernel, map_size, 1) -> {erlang, map_size};
inline(?kernel, max, 2) -> {erlang, max};
inline(?kernel, min, 2) -> {erlang, min};
inline(?kernel, node, 0) -> {erlang, node};
inline(?kernel, node, 1) -> {erlang, node};
inline(?kernel, 'rem', 2) -> {erlang, 'rem'};
inline(?kernel, round, 1) -> {erlang, round};
inline(?kernel, self, 0) -> {erlang, self};
inline(?kernel, send, 2) -> {erlang, send};
inline(?kernel, spawn, 1) -> {erlang, spawn};
inline(?kernel, spawn, 3) -> {erlang, spawn};
inline(?kernel, spawn_link, 1) -> {erlang, spawn_link};
inline(?kernel, spawn_link, 3) -> {erlang, spawn_link};
inline(?kernel, spawn_monitor, 1) -> {erlang, spawn_monitor};
inline(?kernel, spawn_monitor, 3) -> {erlang, spawn_monitor};
inline(?kernel, throw, 1) -> {erlang, throw};
inline(?kernel, tl, 1) -> {erlang, tl};
inline(?kernel, trunc, 1) -> {erlang, trunc};
inline(?kernel, tuple_size, 1) -> {erlang, tuple_size};
inline(?map, keys, 1) -> {maps, keys};
inline(?map, merge, 2) -> {maps, merge};
inline(?map, size, 1) -> {maps, size};
inline(?map, values, 1) -> {maps, values};
inline(?map, to_list, 1) -> {maps, to_list};
inline(?node, spawn, 2) -> {erlang, spawn};
inline(?node, spawn, 3) -> {erlang, spawn_opt};
inline(?node, spawn, 4) -> {erlang, spawn};
inline(?node, spawn, 5) -> {erlang, spawn_opt};
inline(?node, spawn_link, 2) -> {erlang, spawn_link};
inline(?node, spawn_link, 4) -> {erlang, spawn_link};
inline(?process, exit, 2) -> {erlang, exit};
inline(?process, spawn, 2) -> {erlang, spawn_opt};
inline(?process, spawn, 4) -> {erlang, spawn_opt};
inline(?process, demonitor, 1) -> {erlang, demonitor};
inline(?process, demonitor, 2) -> {erlang, demonitor};
inline(?process, link, 1) -> {erlang, link};
inline(?process, unlink, 1) -> {erlang, unlink};
inline(?port, open, 2) -> {erlang, open_port};
inline(?port, call, 3) -> {erlang, port_call};
inline(?port, close, 1) -> {erlang, port_close};
inline(?port, command, 2) -> {erlang, port_command};
inline(?port, command, 3) -> {erlang, port_command};
inline(?port, connect, 2) -> {erlang, port_connect};
inline(?port, control, 3) -> {erlang, port_control};
inline(?port, info, 1) -> {erlang, port_info};
inline(?port, info, 2) -> {erlang, port_info};
inline(?port, list, 0) -> {erlang, ports};
inline(?string, to_float, 1) -> {erlang, binary_to_float};
inline(?string, to_integer, 1) -> {erlang, binary_to_integer};
inline(?string, to_integer, 2) -> {erlang, binary_to_integer};
inline(?system, stacktrace, 0) -> {erlang, get_stacktrace};
inline(?tuple, to_list, 1) -> {erlang, tuple_to_list};
inline(_, _, _) -> false.
%% Rewrite rules
%% Rewrite rules are more complex than regular inlining code.
%% It receives all remote call arguments and return quoted
%% expressions with the new environment.
%% Complex rewrite rules
rewrite(?string_chars, _DotMeta, 'to_string', _Meta, [String]) when is_binary(String) ->
String;
rewrite(?string_chars, DotMeta, 'to_string', Meta, [String]) ->
Var = {'_rewrite', ?hidden, nil},
Guard = {{'.', ?hidden, [erlang, is_binary]}, ?hidden, [Var]},
Slow = remote(?string_chars, DotMeta, 'to_string', Meta, [Var]),
Fast = Var,
{'case', ?hidden, [String, [{do,
[{'->', ?hidden, [[{'when', ?hidden, [Var, Guard]}], Fast]},
{'->', ?hidden, [[Var], Slow]}]
}]]};
rewrite(?enum, DotMeta, 'reverse', Meta, [List]) when is_list(List) ->
remote(lists, DotMeta, 'reverse', Meta, [List]);
rewrite(?enum, DotMeta, 'reverse', Meta, [List]) ->
Var = {'_rewrite', ?hidden, nil},
Guard = {{'.', ?hidden, [erlang, is_list]}, ?hidden, [Var]},
Slow = remote(?enum, DotMeta, 'reverse', Meta, [Var]),
Fast = remote(lists, DotMeta, 'reverse', Meta, [Var]),
{'case', ?hidden, [List, [{do,
[{'->', ?hidden, [[{'when', ?hidden, [Var, Guard]}], Fast]},
{'->', ?hidden, [[Var], Slow]}]
}]]};
rewrite(Receiver, DotMeta, Right, Meta, Args) ->
{EReceiver, ERight, EArgs} = rewrite(Receiver, Right, Args),
remote(EReceiver, DotMeta, ERight, Meta, EArgs).
%% Simple rewrite rules
rewrite(?atom, to_string, [Arg]) ->
{erlang, atom_to_binary, [Arg, utf8]};
rewrite(?kernel, elem, [Tuple, Index]) ->
{erlang, element, [increment(Index), Tuple]};
rewrite(?kernel, put_elem, [Tuple, Index, Value]) ->
{erlang, setelement, [increment(Index), Tuple, Value]};
rewrite(?map, 'has_key?', [Map, Key]) ->
{maps, is_key, [Key, Map]};
rewrite(?map, fetch, [Map, Key]) ->
{maps, find, [Key, Map]};
rewrite(?map, put, [Map, Key, Value]) ->
{maps, put, [Key, Value, Map]};
rewrite(?map, delete, [Map, Key]) ->
{maps, remove, [Key, Map]};
rewrite(?process, monitor, [Arg]) ->
{erlang, monitor, [process, Arg]};
rewrite(?string, to_atom, [Arg]) ->
{erlang, binary_to_atom, [Arg, utf8]};
rewrite(?string, to_existing_atom, [Arg]) ->
{erlang, binary_to_existing_atom, [Arg, utf8]};
rewrite(?tuple, insert_at, [Tuple, Index, Term]) ->
{erlang, insert_element, [increment(Index), Tuple, Term]};
rewrite(?tuple, delete_at, [Tuple, Index]) ->
{erlang, delete_element, [increment(Index), Tuple]};
rewrite(?tuple, duplicate, [Data, Size]) ->
{erlang, make_tuple, [Size, Data]};
rewrite(Receiver, Fun, Args) ->
{Receiver, Fun, Args}.
%% Rewrite helpers
remote(Receiver, DotMeta, Right, Meta, Args) ->
{{'.', DotMeta, [Receiver, Right]}, Meta, Args}.
increment(Number) when is_number(Number) ->
Number + 1;
increment(Other) ->
{{'.', [], [erlang, '+']}, [], [Other, 1]}.
+10
View File
@@ -7,6 +7,16 @@ start_link() ->
init(ok) ->
Workers = [
{
elixir_config,
{elixir_config, start_link, []},
permanent, % Restart = permanent | transient | temporary
2000, % Shutdown = brutal_kill | int() >= 0 | infinity
worker, % Type = worker | supervisor
[elixir_config] % Modules = [Module] | dynamic
},
{
elixir_code_server,
{elixir_code_server, start_link, []},
+52 -96
View File
@@ -33,10 +33,16 @@
-define(arrow_op3(T1, T2, T3),
T1 == $<, T2 == $<, T3 == $<;
T1 == $>, T2 == $>, T3 == $>).
T1 == $>, T2 == $>, T3 == $>;
T1 == $~, T2 == $>, T3 == $>;
T1 == $<, T2 == $<, T3 == $~;
T1 == $<, T2 == $~, T3 == $>;
T1 == $<, T2 == $|, T3 == $>).
-define(arrow_op(T1, T2),
T1 == $|, T2 == $>).
T1 == $|, T2 == $>;
T1 == $~, T2 == $>;
T1 == $<, T2 == $~).
-define(rel_op(T),
T == $<;
@@ -80,7 +86,7 @@
-define(type_op(T1, T2),
T1 == $:, T2 == $:).
-define(pipe_op(T1),
-define(pipe_op(T),
T == $|).
tokenize(String, Line, #elixir_tokenizer{} = Scope) ->
@@ -130,28 +136,6 @@ tokenize([$0,$o,H|T], Line, Scope, Tokens) when ?is_octal(H) ->
{Rest, Number} = tokenize_octal([H|T], []),
tokenize(Rest, Line, Scope, [{number, Line, Number}|Tokens]);
%% Deprecated
tokenize([$0,$X,H|T], Line, Scope, Tokens) when ?is_hex(H) ->
io:format("~ts:~w: warning: 0X for hexadecimals is deprecated, please use 0x instead~n",
[Scope#elixir_tokenizer.file, Line]),
{Rest, Number} = tokenize_hex([H|T], []),
tokenize(Rest, Line, Scope, [{number, Line, Number}|Tokens]);
tokenize([$0,$B,H|T], Line, Scope, Tokens) when ?is_bin(H) ->
io:format("~ts:~w: warning: 0B for binaries is deprecated, please use 0b instead~n",
[Scope#elixir_tokenizer.file, Line]),
{Rest, Number} = tokenize_bin([H|T], []),
tokenize(Rest, Line, Scope, [{number, Line, Number}|Tokens]);
tokenize([$0,H|T], Line, Scope, Tokens) when ?is_octal(H) ->
io:format("~ts:~w: warning: octals starting with leading 0 is deprecated, please use 0o instead, got octal start with: ~ts~n",
[Scope#elixir_tokenizer.file, Line, <<$0, H>>]),
{Rest, Number} = tokenize_octal([H|T], []),
tokenize(Rest, Line, Scope, [{number, Line, Number}|Tokens]);
%% End of deprecated
% Comments
tokenize([$#|String], Line, Scope, Tokens) ->
@@ -221,73 +205,19 @@ tokenize([$?,$\\,$x,A|T], Line, Scope, Tokens)
Char = escape_char([$\\,$x,A]),
tokenize(T, Line, Scope, [{number, Line, Char}|Tokens]);
%% Deprecated
tokenize([$?,$\\,$X,${,A,B,C,D,E,F,$}|T], Line, Scope, Tokens)
when ?is_hex(A), ?is_hex(B), ?is_hex(C), ?is_hex(D), ?is_hex(E), ?is_hex(F) ->
Char = escape_char([$\\,$X,${,A,B,C,D,E,F,$}]),
tokenize(T, Line, Scope, [{number, Line, Char}|Tokens]);
tokenize([$?,$\\,$X,${,A,B,C,D,E,$}|T], Line, Scope, Tokens)
when ?is_hex(A), ?is_hex(B), ?is_hex(C), ?is_hex(D), ?is_hex(E) ->
Char = escape_char([$\\,$X,${,A,B,C,D,E,$}]),
tokenize(T, Line, Scope, [{number, Line, Char}|Tokens]);
tokenize([$?,$\\,$X,${,A,B,C,D,$}|T], Line, Scope, Tokens)
when ?is_hex(A), ?is_hex(B), ?is_hex(C), ?is_hex(D) ->
Char = escape_char([$\\,$X,${,A,B,C,D,$}]),
tokenize(T, Line, Scope, [{number, Line, Char}|Tokens]);
tokenize([$?,$\\,$X,${,A,B,C,$}|T], Line, Scope, Tokens)
when ?is_hex(A), ?is_hex(B), ?is_hex(C) ->
Char = escape_char([$\\,$X,${,A,B,C,$}]),
tokenize(T, Line, Scope, [{number, Line, Char}|Tokens]);
tokenize([$?,$\\,$X,${,A,B,$}|T], Line, Scope, Tokens)
when ?is_hex(A), ?is_hex(B) ->
Char = escape_char([$\\,$X,${,A,B,$}]),
tokenize(T, Line, Scope, [{number, Line, Char}|Tokens]);
tokenize([$?,$\\,$X,${,A,$}|T], Line, Scope, Tokens)
when ?is_hex(A) ->
Char = escape_char([$\\,$X,${,A,$}]),
tokenize(T, Line, Scope, [{number, Line, Char}|Tokens]);
tokenize([$?,$\\,$X,A,B|T], Line, Scope, Tokens)
when ?is_hex(A), ?is_hex(B) ->
Char = escape_char([$\\,$X,A,B]),
tokenize(T, Line, Scope, [{number, Line, Char}|Tokens]);
tokenize([$?,$\\,$X,A|T], Line, Scope, Tokens)
when ?is_hex(A) ->
Char = escape_char([$\\,$X,A]),
tokenize(T, Line, Scope, [{number, Line, Char}|Tokens]);
tokenize([$?,$\\,A,B,C|T], Line, Scope, Tokens)
when ?is_octal(A), A =< $3,?is_octal(B), ?is_octal(C) ->
Char = escape_char([$\\,A,B,C]),
tokenize(T, Line, Scope, [{number, Line, Char}|Tokens]);
tokenize([$?,$\\,A,B|T], Line, Scope, Tokens)
when ?is_octal(A), ?is_octal(B) ->
Char = escape_char([$\\,A,B]),
tokenize(T, Line, Scope, [{number, Line, Char}|Tokens]);
tokenize([$?,$\\,$0|T], Line, Scope, Tokens) ->
tokenize(T, Line, Scope, [{number, Line, 0}|Tokens]);
tokenize([$?,$\\,A|T], Line, Scope, Tokens)
when ?is_octal(A) ->
Char = escape_char([$\\,A]),
tokenize(T, Line, Scope, [{number, Line, Char}|Tokens]);
%% End of deprecation
tokenize([$?,$\\,H|T], Line, Scope, Tokens) ->
Char = elixir_interpolation:unescape_map(H),
tokenize(T, Line, Scope, [{number, Line, Char}|Tokens]);
tokenize([$?,Char|T], Line, Scope, Tokens) ->
case handle_char(Char) of
{Escape, Name} ->
Msg = io_lib:format("found ? followed by codepoint 0x~.16B (~ts), please use ~ts instead",
[Char, Name, Escape]),
elixir_errors:warn(Line, Scope#elixir_tokenizer.file, Msg);
false ->
ok
end,
tokenize(T, Line, Scope, [{number, Line, Char}|Tokens]);
% Heredocs
@@ -375,10 +305,19 @@ tokenize([$:,T|Rest], Line, Scope, Tokens) when
% End of line
tokenize(";" ++ Rest, Line, Scope, []) ->
tokenize(Rest, Line, Scope, eol(Line, ';', []));
tokenize(Rest, Line, Scope, [{';', Line}]);
tokenize(";" ++ Rest, Line, Scope, [Top|Tokens]) when element(1, Top) /= eol ->
tokenize(Rest, Line, Scope, eol(Line, ';', [Top|Tokens]));
tokenize(";" ++ Rest, Line, Scope, [Top|_] = Tokens) when element(1, Top) /= ';' ->
tokenize(Rest, Line, Scope, [{';', Line}|Tokens]);
tokenize("\\" = Original, Line, _Scope, Tokens) ->
{error, {Line, "invalid escape \\ at end of file", []}, Original, Tokens};
tokenize("\\\n" = Original, Line, _Scope, Tokens) ->
{error, {Line, "invalid escape \\ at end of file", []}, Original, Tokens};
tokenize("\\\r\n" = Original, Line, _Scope, Tokens) ->
{error, {Line, "invalid escape \\ at end of file", []}, Original, Tokens};
tokenize("\\\n" ++ Rest, Line, Scope, Tokens) ->
tokenize(Rest, Line + 1, Scope, Tokens);
@@ -387,10 +326,10 @@ tokenize("\\\r\n" ++ Rest, Line, Scope, Tokens) ->
tokenize(Rest, Line + 1, Scope, Tokens);
tokenize("\n" ++ Rest, Line, Scope, Tokens) ->
tokenize(Rest, Line + 1, Scope, eol(Line, newline, Tokens));
tokenize(Rest, Line + 1, Scope, eol(Line, Tokens));
tokenize("\r\n" ++ Rest, Line, Scope, Tokens) ->
tokenize(Rest, Line + 1, Scope, eol(Line, newline, Tokens));
tokenize(Rest, Line + 1, Scope, eol(Line, Tokens));
% Stand-alone tokens
@@ -534,6 +473,10 @@ tokenize([H|_] = String, Line, Scope, Tokens) when ?is_downcase(H); H == $_ ->
tokenize([T|Rest], Line, Scope, Tokens) when ?is_horizontal_space(T) ->
handle_space_sensitive_tokens(strip_horizontal_space(Rest), Line, Scope, Tokens);
tokenize([T|Rest] = Original, Line, _Scope, Tokens) when ?is_invalid_space(T) ->
Message = io_lib:format("invalid space character U+~.16B before: ", [T]),
{error, {Line, lists:flatten(Message), until_eol(Rest)}, Original, Tokens};
tokenize(T, Line, _Scope, Tokens) ->
{error, {Line, "invalid token: ", until_eol(T)}, T, Tokens}.
@@ -554,6 +497,18 @@ until_eol("\n" ++ _) -> [];
until_eol([]) -> [];
until_eol([H|T]) -> [H|until_eol(T)].
handle_char(7) -> {"\\a", "alert"};
handle_char($\b) -> {"\\b", "backspace"};
handle_char($\d) -> {"\\d", "delete"};
handle_char($\e) -> {"\\e", "escape"};
handle_char($\f) -> {"\\f", "form feed"};
handle_char($\n) -> {"\\n", "newline"};
handle_char($\r) -> {"\\r", "carriage return"};
handle_char($\s) -> {"\\s", "space"};
handle_char($\t) -> {"\\t", "tab"};
handle_char($\v) -> {"\\v", "vertical tab"};
handle_char(_) -> false.
escape_char(List) ->
<<Char/utf8>> = elixir_interpolation:unescape_chars(list_to_binary(List)),
Char.
@@ -662,9 +617,10 @@ handle_space_sensitive_tokens(String, Line, Scope, Tokens) ->
%% Helpers
eol(_Line, _Mod, [{',',_}|_] = Tokens) -> Tokens;
eol(_Line, _Mod, [{eol,_,_}|_] = Tokens) -> Tokens;
eol(Line, Mod, Tokens) -> [{eol,Line,Mod}|Tokens].
eol(_Line, [{';',_}|_] = Tokens) -> Tokens;
eol(_Line, [{',',_}|_] = Tokens) -> Tokens;
eol(_Line, [{eol,_}|_] = Tokens) -> Tokens;
eol(Line, Tokens) -> [{eol,Line}|Tokens].
unsafe_to_atom(Part, Line, #elixir_tokenizer{}) when
is_binary(Part) andalso size(Part) > 255;
@@ -891,7 +847,7 @@ check_call_identifier(_Kind, Line, Atom, [$(|_]) -> {paren_identifier, Line, Ato
check_call_identifier(_Kind, Line, Atom, [$[|_]) -> {bracket_identifier, Line, Atom};
check_call_identifier(Kind, Line, Atom, _Rest) -> {Kind, Line, Atom}.
add_token_with_nl(Left, [{eol,_,newline}|T]) -> [Left|T];
add_token_with_nl(Left, [{eol,_}|T]) -> [Left|T];
add_token_with_nl(Left, T) -> [Left|T].
%% Error handling
+6 -5
View File
@@ -10,7 +10,7 @@
translate({'=', Meta, [Left, Right]}, S) ->
Return = case Left of
{'_', _, Atom} when is_atom(Atom) -> false;
{'_', _, Atom} when is_atom(Atom) -> S#elixir_scope.return;
_ -> true
end,
@@ -195,18 +195,16 @@ translate({Name, Meta, Args}, S) when is_atom(Name), is_list(Meta), is_list(Args
if
S#elixir_scope.context == match ->
compile_error(Meta, S#elixir_scope.file,
"cannot invoke function ~ts/~B inside match", [Name, length(Args)]);
"cannot invoke local ~ts/~B inside match", [Name, length(Args)]);
S#elixir_scope.context == guard ->
Arity = length(Args),
File = S#elixir_scope.file,
case Arity of
0 -> compile_error(Meta, File, "unknown variable ~ts or cannot invoke "
"function ~ts/~B inside guard", [Name, Name, Arity]);
"local ~ts/~B inside guard", [Name, Name, Arity]);
_ -> compile_error(Meta, File, "cannot invoke local ~ts/~B inside guard",
[Name, Arity])
end;
S#elixir_scope.function == nil ->
compile_error(Meta, S#elixir_scope.file, "undefined function ~ts/~B", [Name, length(Args)]);
true ->
Line = ?line(Meta),
{TArgs, NS} = translate_args(Args, S),
@@ -259,6 +257,9 @@ translate({{'.', _, [Left, Right]}, Meta, Args}, S)
%% the is_map/1 guard instead of matching on map. Hopefully
%% we can use a match on 17.1.
%%
%% In the future, we could also use maps:get/2 instead
%% of pattern match, reducing the AST footprint.
%%
%% http://erlang.org/pipermail/erlang-bugs/2014-April/004338.html
{{'case', -1, TLeft, [
{clause, -1,
+10
View File
@@ -156,6 +156,16 @@ erl_rescue_guard_for(Meta, Var, 'Elixir.BadStructError') ->
erl_tuple_size(Meta, Var, 3),
erl_record_compare(Meta, Var, badstruct));
erl_rescue_guard_for(Meta, Var, 'Elixir.BadMapError') ->
erl_and(Meta,
erl_tuple_size(Meta, Var, 2),
erl_record_compare(Meta, Var, badmap));
erl_rescue_guard_for(Meta, Var, 'Elixir.KeyError') ->
erl_and(Meta,
erl_tuple_size(Meta, Var, 2),
erl_record_compare(Meta, Var, badkey));
erl_rescue_guard_for(Meta, Var, 'Elixir.ArgumentError') ->
erl_or(Meta,
{erl(Meta, '=='), Meta, [Var, badarg]},
-2
View File
@@ -381,8 +381,6 @@ defmodule DictTest.Common do
dict = new_dict([{1}, {2}, {3}], fn {x} -> {<<x + 64>>, x} end)
assert Dict.size(dict) == 3
assert Enum.sort(dict) == [{"A", 1}, {"B", 2}, {"C", 3}]
assert Collectable.empty(new_dict) == new_dict([])
end
test "is zippable" do
-10
View File
@@ -189,11 +189,6 @@ defmodule EnumTest.List do
assert Enum.into([a: 1, b: 2], %{c: 3}) == %{a: 1, b: 2, c: 3}
assert Enum.into(%{a: 1, b: 2}, []) == [a: 1, b: 2]
assert Enum.into([1, 2, 3], "numbers: ", &to_string/1) == "numbers: 123"
assert Enum.into([1, 2, 3], fn
func, {:cont, x} when is_function(func) -> [x]
list, {:cont, x} -> [x|list]
list, _ -> list
end) == [3, 2, 1]
end
test :intersperse do
@@ -368,11 +363,6 @@ defmodule EnumTest.List do
assert Enum.to_list(1 .. 3) == [1, 2, 3]
end
test :traverse do
assert Enum.traverse([1, 2, 3], &(&1 * &1)) == [1, 4, 9]
assert Enum.traverse(%{a: 1, b: 2}, fn {k, v} -> {k, v*2} end) == %{a: 2, b: 4}
end
test :uniq do
assert Enum.uniq([1, 2, 3, 2, 1]) == [1, 2, 3]
assert Enum.uniq([1, 2, 3, 2, 1], fn x -> x end) == [1, 2, 3]
+20 -2
View File
@@ -291,7 +291,7 @@ defmodule Kernel.ExceptionTest do
test "format_fa" do
assert Exception.format_fa(fn -> end, 1) =~
~r"#Function<\d\.\d+/0 in Kernel\.ExceptionTest\.test format_fa/1>/1"
~r"#Function<\d+\.\d+/0 in Kernel\.ExceptionTest\.test format_fa/1>/1"
end
import Exception, only: [message: 1]
@@ -308,8 +308,10 @@ defmodule Kernel.ExceptionTest do
test "undefined function message" do
assert %UndefinedFunctionError{} |> message == "undefined function"
assert %UndefinedFunctionError{module: Kernel, function: :bar, arity: 1} |> message ==
"undefined function: Kernel.bar/1"
assert %UndefinedFunctionError{module: Foo, function: :bar, arity: 1} |> message ==
"undefined function: Foo.bar/1"
"undefined function: Foo.bar/1 (module Foo is not available)"
assert %UndefinedFunctionError{module: nil, function: :bar, arity: 0} |> message ==
"undefined function: nil.bar/0"
end
@@ -325,4 +327,20 @@ defmodule Kernel.ExceptionTest do
assert %ErlangError{original: :sample} |> message ==
"erlang error: :sample"
end
test "format_stacktrace from file" do
assert_raise ArgumentError, fn ->
Code.eval_string("def foo do end", [], file: "myfile")
end
assert Exception.format_stacktrace(System.stacktrace) =~ "myfile:1: (file)"
end
test "format_stacktrace from module" do
assert_raise ArgumentError, fn ->
Code.eval_string("defmodule Foo do raise ArgumentError, ~s(oops) end", [], file: "myfile")
end
assert Exception.format_stacktrace(System.stacktrace) =~ "myfile:1: (module)"
end
end
@@ -1 +1,6 @@
defmodule CompileSample, do: nil
defmodule CompileSample, do: nil
# Assert that modules compiled are loaded into memory
# with the proper destination for the BEAM files.
dest = __DIR__ |> Path.join("../../../tmp/#{CompileSample}.beam") |> Path.expand
^dest = CompileSample |> :code.which() |> List.to_string
+4
View File
@@ -0,0 +1,4 @@
LF
CR CRLF
LFCR

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