Compare commits

...
465 Commits
Author SHA1 Message Date
José Valim 44b0412042 Release v0.14.2 2014-06-29 11:39:33 +02:00
José Valim d984a82587 Merge pull request #2463 from hamiltop/master
Update documentation on Kernel.var!/2
2014-06-29 11:13:40 +02:00
hamiltop e59d50a891 Update documentation on Kernel.var!/2
The documentation is no longer accurate since passing atoms to `Kernel.var!/2` has been deprecated and removed.
2014-06-28 22:00:29 -07:00
Eric Meadows-Jönsson 7bf568d0b3 Bump Hex version requirement 2014-06-28 16:47:30 +02:00
José Valim f95cafe5ca Fix regression when case is used inside cond head 2014-06-28 16:31:03 +02:00
José Valim f0e1443f21 Use iodata_to_binary in join/2 and map_join/3
Everything is previously converted to a string using
the String.Chars protocol, so we can just treat it
as iodata.
2014-06-28 14:33:20 +02:00
José Valim cf619585a6 No need to reverse a list when building an iolist 2014-06-28 12:21:17 +02:00
José Valim c639b01146 Update CHANGELOG 2014-06-28 12:02:58 +02:00
José Valim b4415f2a14 Change precedence of & to not special case / 2014-06-28 11:16:51 +02:00
José Valim 538b51c20a Remove usage of :filelib functions
This is to work around a regression in 17.1 where many
:filelib functions were broken. More information read:

http://erlang.org/pipermail/erlang-bugs/2014-June/004465.html
2014-06-27 23:00:18 +02:00
José Valim c8a0158791 Add @external_resource attribute, closes #2455 2014-06-27 22:16:34 +02:00
José Valim b088d4b90e Also filter out .ps1 files 2014-06-26 20:10:47 +02:00
José Valim 02a415facd Add MIX_ARCHIVES env variable 2014-06-26 19:48:32 +02:00
José Valim 5cee19067d Enum.join/2 and Enum.map_join/3 now uses iolists 2014-06-26 18:41:12 +02:00
José Valim 3496681236 Merge pull request #2457 from mururu/args-for-bodyless
Ensure we can only use variables and `\\` as arguments of bodyless clause
2014-06-26 17:07:00 +02:00
Yuki Ito a7645a2c8b Ensure we can only use variables and \\ as arguments of bodyless clause, closes #2374 2014-06-26 23:06:49 +09:00
José Valim d9423627f3 Document the role of MIX_HOME more explicitly 2014-06-26 14:48:44 +02:00
José Valim 5a8520fb0f Ensure we show a consistent error when we cannot evaluate .iex.exs 2014-06-26 14:41:23 +02:00
José Valim 4458ee1232 Remove --gen-debug option 2014-06-26 13:35:54 +02:00
José Valim a6b1cca91f Soft deprecate Mix.Generator.from_file/1 2014-06-26 13:35:54 +02:00
José Valim e2535731c8 Merge pull request #2453 from jwarwick/specialform_typo
Fixed typos in Kernel.SpecialForms docs
2014-06-25 22:01:35 +02:00
John Warwick 393ff69b1e Fixed typos in Kernel.SpecialForms docs 2014-06-25 15:49:16 -04:00
José Valim 033747ad63 Fix changelog message typo 2014-06-25 13:15:01 +02:00
José Valim 730d043631 Update CHANGELOG 2014-06-25 12:38:35 +02:00
José Valim c819bdbf74 Raise CondClauseError for cond clause mismatch 2014-06-25 01:46:56 +02:00
José Valim 64f8541ee6 Make cond a special form so it does not leak vars on head 2014-06-25 01:46:56 +02:00
Eric Meadows-Jönsson d1f4e2d49b Simplify .git directory check 2014-06-25 01:16:53 +02:00
Eric Meadows-Jönsson 5a13aecbae Error if git executable is not available 2014-06-25 00:15:11 +02:00
Eric Meadows-Jönsson 61904ee24c Remove git rev-parse check for valid .git directory
Instead pass `—git-dir=.git` to all git commands to ensure that the
command will only affect the local directory.
2014-06-25 00:14:35 +02:00
José Valim 5c7fdcc244 Soft deprecate EEx.TransformerEngine and EEx.AssignsEngine 2014-06-24 13:51:17 +02:00
José Valim f772877039 Update CHANGELOG 2014-06-24 13:28:41 +02:00
José Valim e571218917 Sigils no longer balance start and end tokens, closes #2284 2014-06-24 13:17:43 +02:00
José Valim aa01dd1fc1 Ensure derived protocols are defined with a file 2014-06-24 11:28:27 +02:00
José Valim 30971c9cb3 Merge pull request #2447 from jwarwick/task_docs
Fixing doc typos in Task
2014-06-23 20:16:09 +02:00
John Warwick abfc69a6e2 Fixing doc typos in Task 2014-06-23 14:02:31 -04:00
José Valim ac1fc484e4 Registered name returns an empty list, as in Erlang, to avoid ambiguity 2014-06-23 17:20:28 +02:00
José Valim 889bfebe4e Convert build_info to a map 2014-06-22 23:38:20 +02:00
José Valim e9899a5462 Fix local.install description 2014-06-22 18:00:47 +02:00
José Valim ec92bb118d Merge pull request #2444 from alco/kernel-cli-paths
Support paths inside archives in -pa and -pz options. Closes #2442
2014-06-22 14:38:55 +02:00
Alexei Sholik fd9ca435b7 Support paths inside archives in -pa and -pz options. Closes #2442 2014-06-22 15:26:36 +03:00
José Valim 3ec0d20621 Only set encoding when there is noshell, closes #2432 2014-06-22 14:16:40 +02:00
José Valim 16f3b928a1 Do not set -noshell if we are going to override it afterwards 2014-06-22 14:16:40 +02:00
José Valim fb20b6ddf6 Merge pull request #2443 from alco/doc-typo
Remove a stray XML tag from the path docs
2014-06-22 14:15:53 +02:00
Alexei Sholik d77a4d6c3f Remove a stray XML tag from the path docs 2014-06-22 14:44:58 +03:00
José Valim d7900239c2 Improve docs and defaults for File 2014-06-22 12:13:21 +02:00
José Valim babd98be3f Use mfa when starting remote nodes so it works with any elixir version 2014-06-21 20:43:16 +02:00
José Valim 9a85b1556b Merge pull request #2440 from tallakt/stream_unfold_typo
Fix unclear doc in Stream.unfold
2014-06-21 16:15:06 +02:00
Tallak Tveide d1da200af5 Fix unclear doc in Stream.unfold 2014-06-21 15:05:20 +02:00
José Valim e260b92f89 Merge pull request #2431 from alco/escript-name
Mention the fact that :escript_name is also used as a module name
2014-06-21 11:12:55 +02:00
José Valim e09e5c3f41 Merge pull request #2438 from jwarwick/helper_warning
Cleaning up IEx.Helpers.Flush warning
2014-06-21 11:12:01 +02:00
John Warwick a76a281309 Cleaning up IEx.Helpers.Flush warning 2014-06-20 22:09:28 -04:00
Alexei Sholik be42d00067 Update escript options: :name, :comment, :emu_args 2014-06-21 01:07:34 +03:00
José Valim f21a998f81 Merge pull request #2437 from lexmag/uri-parse
Let `URI.parse/1` work with `%URI{}`
2014-06-20 23:01:41 +02:00
Aleksei Magusev 89e0018d58 Let URI.parse/1 work with %URI{} 2014-06-21 00:37:16 +04:00
José Valim 21b691cd4e Do not expect options to have a certain format in the environment 2014-06-20 21:14:02 +02:00
José Valim a8a91c2282 Merge pull request #2435 from chyndman/makefile-win-epmd
workaround 'epmd -daemon' not working on Windows
2014-06-20 20:10:55 +02:00
José Valim 5275646966 Merge pull request #2433 from chyndman/contrib-win-update
Add extra Path directory, remove PowerShell warning
2014-06-20 20:10:34 +02:00
Chris Hyndman e60cd6a484 workaround 'epmd -daemon' not working on Windows 2014-06-20 10:53:23 -07:00
José Valim 552006bea3 Merge pull request #2434 from jwarwick/agent_doc
Fixed doc typos in Agent and File.Stream
2014-06-20 19:51:30 +02:00
John Warwick 2077b584ad Fixed doc typos in Agent and File.Stream 2014-06-20 13:22:25 -04:00
Chris Hyndman 29f05fda35 Add extra Path directory, remove PowerShell warning 2014-06-20 10:09:55 -07:00
Eric Meadows-Jönsson cbb7c545e0 Fix size/1 warnings 2014-06-20 14:24:36 +02:00
José Valim c909267077 Merge pull request #2429 from jwarwick/application_doc
Fixing typos in Application doc
2014-06-19 22:01:56 +02:00
John Warwick a9bbabfabc Fixing typos in Application doc 2014-06-19 15:49:31 -04:00
José Valim c4d666a4ba Fix typo on Access docs 2014-06-19 16:24:13 +02:00
José Valim 8e824d3da1 Merge pull request #2427 from fishcakez/task_init_call
Set :proc_lib initial call on task
2014-06-19 11:09:39 +02:00
Eric Meadows-Jönsson 41caa0d635 Merge pull request #2428 from knewter/feature/deriving_typofix
Fix typo in deriving docs
2014-06-19 01:39:28 +02:00
Josh Adams 3a0108edf8 Fix typo in deriving docs 2014-06-18 18:22:05 -05:00
James Fish 7123fb9d09 Set :proc_lib initial call on task
Sets the :proc_lib initial call to the {mod, fun, arity} of the task.

This allows :proc_lib.translate_initial_call(task.pid) to show the mfa
of a task process when debugging. Tools such as :observer and the erlang
shell make use of this function when getting information on a process.

This gives a useful initial call in the crash report sent to the error
logger when a task raises.
2014-06-18 22:58:20 +01:00
José Valim 6d8e118192 Check if Makefile.win is available before running nmake 2014-06-18 23:17:56 +02:00
José Valim 846703b31c Merge pull request #2426 from chyndman/mix-make-manager-win-nmake
make manager calls nmake /F Makefile.win on Windows
2014-06-18 23:07:52 +02:00
Chris Hyndman 46905b39a7 make manager calls nmake /F Makefile.win on Windows 2014-06-18 12:20:11 -07:00
Eric Meadows-Jönsson 0f43aca5bc Merge pull request #2424 from ericmj/delay-typespecs
Delay typespec compilation to after expansion
2014-06-18 20:01:17 +02:00
Eric Meadows-Jönsson 587c83cbff Delay typespec compilation to after expansion 2014-06-18 19:49:57 +02:00
José Valim e8194b1764 Fix wrong changelog entry 2014-06-18 16:57:20 +02:00
José Valim feb9cb645e Remove previously deprecated behaviour 2014-06-18 16:56:59 +02:00
José Valim 29f9a4fd67 Start v0.14.2-dev 2014-06-18 16:16:18 +02:00
José Valim 22f3a50eba Release v0.14.1 2014-06-18 16:09:05 +02:00
José Valim 470e5424bb Update CHANGELOG 2014-06-18 13:42:51 +02:00
José Valim 8f272c4cc8 Start epmd, clean up warnings 2014-06-18 10:50:48 +02:00
José Valim 46c23b1547 Merge pull request #2423 from fishcakez/genevent_stream_stop
Improve GenEvent.stream/2 cleanup
2014-06-18 10:26:31 +02:00
James Fish 4d607ace08 Improve GenEvent.stream/2 cleanup
Makes the cleanup of the Stream resource more efficient by reusing the
monitored process and its link to ensure the handler has been removed.

The flushing of messages, after removing the handler, is also only done
when needed.
2014-06-18 02:05:10 +01:00
José Valim 78adb5b200 Ensure handler does not get stuck and properly clean up mailbox 2014-06-17 23:12:02 +02:00
José Valim 311efb66a9 Add GenEvent.stream modes and make it sync by default 2014-06-17 20:20:50 +02:00
José Valim 272be2c088 Work around and document for mix test on windows, closes #2375 2014-06-17 19:12:55 +02:00
José Valim 911acc3173 Attempt to start epmd before running tests 2014-06-17 17:48:59 +02:00
José Valim 182cbfe541 Update to Unicode 7.0.0 2014-06-17 17:17:32 +02:00
José Valim c43bc6a06d Attempt to fix race condition on test by depending on last compiled file 2014-06-17 17:00:46 +02:00
José Valim 38aa5c57e5 Add Node.start/3 and Node.stop/0, closes #2406 2014-06-17 16:56:50 +02:00
José Valim 4f93dae442 Merge pull request #2415 from alco/list-format
Reformat unordered lists in all docstrings
2014-06-17 16:39:43 +02:00
Alexei Sholik 41c74e9682 Fix ordered list formatting
The rules are similar to those for the unordered lists (see previous
commits). Subsequent lines and paragraphs should be aligned to the
beginning of the sentence on the first line, like this:

  1. This is a list item,
     it has a second line.

     Also paragraph.

  2. Second list item.

         code fragment

Short, one-sentence items that can be read as part of the surrounding
paragraph can start with a non-capital letter
2014-06-17 16:34:20 +03:00
Alexei Sholik e0f5dd506a Apply uniform capitalization and punctuation rules to all lists
* Never use capital letters after `-`. Sentences start right after
    the bullet and continue to the first period.

  * When list elements are one or two sentences, use non-capitalized
    format without terminators.

    The two sentences are converted into one, separated by a semicolon.
    This mainly applies to the cases with short sentences or when only
    one of many list elements needs to have two sentences.

    Example:

        * `:opt_a` - basic description, no terminator
        * `:opt_b` - also short description; this used to be second
          sentence

  * When at least one list element needs to have real sentences, 
    the whole list is formatted like that, with a period at the end
    of each element:

        * `:opt_a` - this is still short.

        * `:opt_b` or `:opt_c` - but this is longer. May have multiple 
          sentences.

          Or even paragraphs.
2014-06-17 16:25:16 +03:00
José Valim f90eeaa024 Fix map printing to indent 2 levels, closes #2165 2014-06-17 13:57:21 +02:00
José Valim 9451e69996 Improve task log based on @fishcakez feedback 2014-06-17 13:26:22 +02:00
José Valim 924350868a Deprecate previously soft deprecated conversion functions 2014-06-17 13:21:34 +02:00
José Valim ee91be519a Update CHANGELOG 2014-06-17 13:03:38 +02:00
José Valim 90951be944 Remove deprecated on_exit callbacks 2014-06-17 12:37:08 +02:00
José Valim 65cf31d32d Do not match files starting with . in Path.wildcard/2 by default 2014-06-17 11:33:25 +02:00
José Valim e14c9ea6ce Merge pull request #2419 from lexmag/uri-binaries
Make `URI.encode*` to work only with binaries
2014-06-17 10:43:15 +02:00
Aleksei Magusev 8635fcc869 Make URI.encode* to work only with binaries 2014-06-17 12:41:01 +04:00
José Valim 65809a5644 Merge pull request #2416 from chyndman/win-volume-path-normalize
Add normalize dispatch for Windows volume letters
2014-06-17 10:35:09 +02:00
José Valim d53a315069 Merge pull request #2414 from christhekeele/empty-range-slice
Ensures Enum.slice always returns a list.
2014-06-17 10:31:27 +02:00
José Valim 8d2199c3f3 Merge pull request #2418 from jw2013/master
fix typo in docstring of String.at and String.slice
2014-06-17 10:29:52 +02:00
jw2013 54e69c023a fix typo in docstring of Enum.at and Enum.slice 2014-06-16 21:48:39 -04:00
Alexei Sholik 55503239a4 Add missing backticks 2014-06-17 03:00:00 +03:00
Chris Hyndman 19d8446ac0 Improved match and guard 2014-06-16 16:50:11 -07:00
Chris Hyndman 5a94906f1d Add normalize dispatch for Windows volume letters 2014-06-16 16:19:57 -07:00
Alexei Sholik 915ba1bb29 Reformat unordered lists in all docstrings
List items are marked with a star (*). Bullets marking list elements
must have a 2-space indent. The continuation lines should be indented
by 2 more spaces.

The second and subsequent paragraphs need to be indented 4 spaces
from the beginning of the line to be included in the list item.

The code fragments have to be indented 8 spaces from the beginning
of the line (or 4 spaces from the beginning of the preceding paragraph).

Sublists are marked with a hyphen (-). They follow the rules above
with the difference that their initial indent will be greater
(8 spaces from the beginning of the line).
2014-06-17 02:01:27 +03:00
Chris Keele d35d03a8ee Ensures Enum.slice always returns a list.
Both Enum.slice/3 and Enum.slice/2 now will return empty lists for
impossible or out of bound ranges, rather than `nil`.

Fixes #2410.
2014-06-16 17:22:01 -05:00
José Valim b959049c77 Properly indent tables 2014-06-16 23:57:14 +02:00
José Valim 24c4bc3009 Merge pull request #2412 from christhekeele/empty-string-slice
Ensures String.slice always returns a string
2014-06-16 23:09:36 +02:00
Chris Keele 000a174353 Ensures String.slice returns a string.
Both String.slice/3 and String.slice/2 now will return empty strings for
impossible or out of bound ranges.
2014-06-16 15:45:28 -05:00
José Valim b126e3b089 Merge pull request #2408 from ericmj/base-case
Add options for which case to use in Base functions
2014-06-16 22:04:15 +02:00
José Valim 319957f5df Merge pull request #2409 from alco/escriptize-config
Put escript options under :escript in the project config
2014-06-16 22:03:34 +02:00
Alexei Sholik cb2eb7d376 Add deprecation warnings for old escript options 2014-06-16 22:52:34 +03:00
Alexei Sholik 1857267872 Put escript options under :escript in the project config 2014-06-16 22:52:27 +03:00
Eric Meadows-Jönsson 91faec5f65 Add options for which case to use in Base functions 2014-06-16 21:36:26 +02:00
Eric Meadows-Jönsson 0f0a91faef Bump hex version requirement to 0.2.5 2014-06-16 21:22:42 +02:00
Eric Meadows-Jönsson baec57637e Fix consolidate task example 2014-06-16 17:21:00 +02:00
José Valim 0c8c6f03e0 Clarify quote docs 2014-06-16 16:42:05 +02:00
José Valim 7bdbefb277 Improve compile.protocols docs and make it public 2014-06-16 16:07:31 +02:00
José Valim ec0e7d8056 Remove get_in/1 2014-06-16 15:49:19 +02:00
José Valim c2e912bdfd --quiet is no longer supported 2014-06-15 17:45:04 +02:00
José Valim 14e1d410f1 Do not tag mix exceptions if in root project 2014-06-15 17:28:23 +02:00
José Valim 15925573e8 Ensure we can measure a timestamp difference on tests 2014-06-15 17:12:04 +02:00
José Valim f32ede4e3f Ensure umbrella is recompiled when a child dependency changes 2014-06-15 16:35:25 +02:00
José Valim 1dc9193e69 Rely on print_app for mix deps.compile and ensure path deps are compiled 2014-06-14 20:57:07 +02:00
Eric Meadows-Jönsson dacb30ad05 Fix path normalization of "/.."
Closes #2404.
2014-06-14 00:51:59 +02:00
José Valim 362df17316 Update CHANGELOG 2014-06-13 17:11:48 +02:00
José Valim d54d0f6a6b Log when tasks go down 2014-06-13 17:11:47 +02:00
José Valim 72d2f38cb5 Merge pull request #2402 from lexmag/encode-www-form
Introduce `URI.encode(decode)_www_form/2` functions
2014-06-13 14:51:43 +02:00
Aleksei Magusev b3cc527126 Introduce URI.decode_www_form/1 function 2014-06-13 16:31:26 +04:00
Aleksei Magusev 6b45855f0e Fix edge cases for URI.decode/1 2014-06-13 16:30:16 +04:00
Aleksei Magusev 40835556da Introduce URI.encode_www_form/1 function 2014-06-13 16:30:16 +04:00
José Valim dc9a47d259 Do not warn on case template teardown/teardown_all definition 2014-06-13 09:20:08 +02:00
José Valim 2d1cf2ebc1 Merge pull request #2400 from alco/patch-3
Fix a typo in Kernel docs
2014-06-13 09:07:00 +02:00
Alexei Sholik 9a56e5ef5a Fix a typo in Kernel docs 2014-06-13 01:08:00 +03:00
José Valim ab148551ba Merge pull request #2399 from chyndman/win-disambiguate-exe
Disambiguate (w)erl to (w)erl.exe
2014-06-12 21:30:26 +02:00
chyndman 4d7c75f2ca Disambiguate (w)erl to (w)erl.exe 2014-06-12 12:23:31 -07:00
José Valim e7f3db9fd4 Merge pull request #2397 from alco/option-parser-fix
Do not allow underscores in option names. Closes #2391
2014-06-12 19:52:11 +02:00
José Valim 4682511923 Make test and test cases exit with reason :shutdown
This guarantees OTP processes are properly shutdown when
the test finishes.
2014-06-12 19:21:37 +02:00
Alexei Sholik e942c07f34 Do not allow underscores in option names. Closes #2391 2014-06-12 19:26:47 +03:00
José Valim dc403e4311 Merge pull request #2395 from pwoolcoc/mix-help-new-typo
fix typo in `mix help new`
2014-06-11 22:19:15 +02:00
Paul b3c6bc2bc5 fix typo in mix help new 2014-06-11 15:42:30 -04:00
José Valim c377483715 Ensure mix test print app name for umbrella 2014-06-11 13:27:30 +02:00
José Valim 3ded858534 Remember users umbrella deps are not shared 2014-06-11 12:41:45 +02:00
José Valim aa09b1e2d2 Improve docs for URI 2014-06-11 11:26:24 +02:00
José Valim 8cb4c719f9 Merge pull request #2392 from lexmag/improve-uri
Make `URI.encode/1` to conform to RFC 3986. Introduce `URI.encode/2` function.
2014-06-11 11:15:14 +02:00
Aleksei Magusev ffa2abd8c6 Introduce URI.encode/2 function
`URI.encode/1` escapes character if it
is not satisfied `URI.char_unescaped?/1`
2014-06-10 19:56:23 +04:00
Aleksei Magusev 14722ba102 Make URI.encode/1 to conform to RFC 3986 2014-06-10 16:20:44 +04:00
Aleksei Magusev 7f4437ece5 Improve function naming in URI 2014-06-10 16:20:43 +04:00
José Valim afc7644fe0 Guarantee we get a keyword on mix deps specs 2014-06-10 12:41:19 +02:00
José Valim 7de880495b Remove previously deprecated code, enable new deprecations 2014-06-10 12:35:07 +02:00
José Valim 29addf9e93 Update Access docs, closes #2388 2014-06-10 12:26:12 +02:00
José Valim 6e9f9f254f Kill test process only after on_exit runs
We do this to avoid links created during the test from
crashing when the test exits which happened before the on_exit
callbacks run.

This was particularly troubling with GenServer, GenEvent
and Supervisor, in which the first does not crash when
a linked process exits with normal reason, but the other
two do. Due to this behaviour, before this change there was
a possibility of race conditions causing tests to crash on
on_exit.

In case the test process exits with a non-normal reason,
all links will be broken, so the problem doesn't manifest
in such cases.
2014-06-10 12:10:31 +02:00
José Valim db25e24eb8 Merge pull request #2390 from benjamintanweihao/patch-3
Update IEx.pry documentation
2014-06-10 12:10:25 +02:00
Benjamin Tan Wei Hao 8a184a6b3d Update IEx.pry documentation
Modified example that works with the current version of Elixir. Fix some typos in the pry console.
2014-06-10 17:54:44 +08:00
José Valim fcd6213c4c Merge pull request #2386 from alco/patch-2
Fix a typo
2014-06-09 17:34:22 +02:00
Alexei Sholik 5409b7e707 Fix a typo 2014-06-09 18:28:45 +03:00
José Valim 815772811f Start v0.14.1-dev 2014-06-08 21:14:54 +02:00
José Valim 9cff61493e Release v0.14.0 2014-06-08 21:09:14 +02:00
José Valim 65a3434bd4 Do not support Macro.var/1 2014-06-08 12:17:23 +02:00
José Valim 4ff9f9aa29 Update CHANGELOG 2014-06-08 11:15:27 +02:00
José Valim 3d982fd1a9 Deprecate var!(:atom), add Macro.var/1 2014-06-08 11:05:39 +02:00
José Valim 396b1e68a8 Make the exit conditions for GenServer/GenEvent/Supervisor clear 2014-06-07 18:32:31 +02:00
José Valim 9c549b3465 Fix help for elixirc executables, closes #2380 2014-06-06 20:59:43 +02:00
José Valim 04194a189d Support --config on mix run 2014-06-06 17:25:36 +02:00
José Valim 35e04d0ea5 Allow mix loadconfig to be called multiple times 2014-06-06 16:40:29 +02:00
José Valim 1038018e84 Migrate from size to byte_size and tuple_size 2014-06-06 13:33:02 +02:00
José Valim 2b3e53670e Provide Protocol.derive/3 2014-06-05 22:54:10 +02:00
José Valim dff93a44c2 Soft deprecate size/1
Closes #2356
2014-06-05 21:02:10 +02:00
José Valim f76c8fec7b Support E in scientific notation for floats 2014-06-05 13:18:25 +02:00
José Valim 8b19b0b3f4 Merge pull request #2379 from jw2013/master
Fix broken promise in Kernel.SpecialForms
2014-06-05 13:09:25 +02:00
jw2013 77fad29a1b Fix broken promise in Kernel.SpecialForms 2014-06-05 06:53:15 -04:00
José Valim 5bab9cae49 Avoid race conditions on ExUnit test suite 2014-06-05 12:16:37 +02:00
José Valim 05507d1f3a Merge pull request #2378 from scrogson/patch-1
Grammar fix.
2014-06-05 10:29:21 +02:00
Sonny Scroggin e369900b06 Grammar fix. 2014-06-04 22:56:02 -05:00
José Valim a629cfce86 Merge pull request #2376 from chyndman/initial-mix-powershell-script
Initial PowerShell script addressing mix commas
2014-06-04 18:33:44 +02:00
chyndman 1a93d35d9c Initial PowerShell script addressing mix commas 2014-06-03 16:45:24 -07:00
José Valim 06cc1c183f Get rid of Mix.Sup 2014-06-03 20:31:47 +02:00
José Valim cb5806bbf3 Deprecate teardown and teardown_all 2014-06-03 18:56:47 +02:00
José Valim db70fd5a5f Also support on_exit on setup_all 2014-06-03 18:28:04 +02:00
José Valim 513ed0bbae Move on handler functionality to its own module 2014-06-03 18:23:42 +02:00
José Valim 3bbae0a368 Merge pull request #2365 from MSch/exunit-on-exit
Support on_exit for registering callbacks in ExUnit.
2014-06-03 17:26:23 +02:00
José Valim 2134db3a0d Merge pull request #2373 from jwarwick/cli
Fixed reading .mix/config.exs
2014-06-03 10:25:50 +02:00
John Warwick 80f35eab7d Fixed reading .mix/config.exs 2014-06-02 22:58:37 -07:00
Eric Meadows-Jönsson ab8a2d195f Only import select functions from Mix.Config 2014-06-02 23:34:57 +02:00
José Valim a7096b5061 Merge pull request #2372 from chyndman/eacces-posix-typo
:eaccess becomes :eacces
2014-06-03 01:19:20 +09:00
chyndman 8552886a8a :eaccess becomes :eacces 2014-06-02 09:16:26 -07:00
José Valim 789a657490 Merge pull request #2371 from zambal/master
Fix operator precedence bug in Mix.Compilers.Elixir.clean/1
2014-06-02 23:33:14 +09:00
zambal 2ee44eba29 Fix operator precedence bug in Mix.Compilers.Elixir.clean/1 2014-06-02 16:18:19 +02:00
José Valim e4a9fae0fd Remove dependency on manifest files 2014-06-02 14:13:00 +02:00
José Valim 0030b282c1 Clean up Path and Task code 2014-06-01 18:52:39 +02:00
José Valim 9c90955863 Merge pull request #2370 from keithseahus/master
Fix Path.absname("/") and Path.expand("/") to return the absolute path "/".
2014-06-01 17:12:36 +02:00
Keisuke Takahashi 61e626bdb8 Add tests for Path.expand("/") to be "/" (the issue is already fixed). 2014-06-01 21:37:12 +09:00
Keisuke Takahashi 3c40de4c0c Fix Path.absname("/") to return the absolute path "/", not "". 2014-06-01 21:23:25 +09:00
José Valim 86a8a6ee88 Tidy up previous commits 2014-06-01 11:32:33 +02:00
José Valim 4d5696e074 Merge pull request #2367 from chyndman/file-rm-windows-read-write
File.rm makes files writeable on Windows before deletion
2014-06-01 09:39:57 +02:00
José Valim 892f211550 Merge pull request #2368 from bkochendorfer/add_proxy_support
Add http and https proxy options
2014-06-01 09:37:23 +02:00
Brett K 2caa16ac22 Add http and https proxy options 2014-06-01 00:10:02 -05:00
chyndman 32c5694551 Corrected octal/decimal handling 2014-05-31 10:43:04 -07:00
Eric Meadows-Jönsson 1aeb7ae7d0 Fix :io.columns calls 2014-05-31 18:40:28 +02:00
chyndman fe8f3464b6 Added rm test for read-only files 2014-05-31 08:44:13 -07:00
chyndman 8941d2821d chmod does not require conversion of bases 2014-05-31 08:36:34 -07:00
José Valim 7ba3c3cfb7 Revert "EEx tests aware of CR+LF newline on Windows"
This reverts commit f60d3668ed.
2014-05-31 09:58:29 +02:00
chyndman 5cc2724b40 File.rm makes files writeable on Windows before deletion 2014-05-30 17:15:23 -07:00
Martin Schürrer 40dff7ebe8 Support on_exit for registering callbacks in ExUnit. 2014-05-30 17:55:52 +02:00
José Valim 5158a19fbd Add a CHANGELOG entry to task change 2014-05-30 16:16:49 +02:00
José Valim fd936bb326 Merge pull request #2350 from ericmj/keyword-error
Raise if giving non-keywords to Keyword
2014-05-30 16:03:43 +02:00
José Valim 3e54d33481 Do not catch failures on tasks
Closes #2345
2014-05-30 16:01:08 +02:00
José Valim 0fe1e686b3 Revert "Set up the monitor only on await for tasks"
This reverts commit 85a04fe919.
2014-05-30 15:59:17 +02:00
José Valim 9e80f3b82b Add guard to impl of Enumerable for Function
This improves the stacktrace in case of wrong dispatch.
2014-05-30 15:59:17 +02:00
José Valim 01bda975c3 Add missing |> Stream.run/1 to its documentation 2014-05-30 15:59:17 +02:00
José Valim 05a8ce56a3 Merge pull request #2364 from pwoolcoc/minor-mix-help-fix
Document the additional `--names` argument that `mix help` can take
2014-05-30 15:34:54 +02:00
Paul d498a89267 Document the additional --names argument that mix help can take 2014-05-30 07:59:00 -04:00
José Valim 46db0d4304 Fix typo on CLI --app parsing 2014-05-29 23:06:36 +02:00
José Valim f2ec77886a Merge pull request #2361 from pwoolcoc/mix-help-names
Mix help names
2014-05-29 20:27:46 +02:00
Paul 4e8ae3abdb add test for mix help --names 2014-05-29 14:07:28 -04:00
Paul dc7ffae763 combine loops 2014-05-29 14:07:19 -04:00
Paul b7897b224d enable passing --names to mix help to only display command names 2014-05-29 14:07:12 -04:00
José Valim c80a65b657 Improve formatting 2014-05-29 19:43:18 +02:00
José Valim f02cd8437b Use new Mix.Config in generated files 2014-05-29 19:37:35 +02:00
José Valim 7d87e84803 Define Mix.Config 2014-05-29 19:11:03 +02:00
José Valim b4a0b4cc19 Provide Mix.raise/1 that attaches mix info to Mix exceptions
Closes #1744
2014-05-29 13:35:26 +02:00
José Valim 1940f490b2 Merge pull request #2358 from chyndman/win-contributing-edit
qualify that PowerShell doesn't work yet
2014-05-28 20:46:46 +02:00
chyndman 69098968be qualify that PowerShell doesn't work yet 2014-05-28 11:40:09 -07:00
José Valim 85a04fe919 Set up the monitor only on await for tasks
Closes #2345
2014-05-28 20:10:43 +02:00
José Valim 54db13ae56 Merge pull request #2354 from chyndman/eex-test-crlf-newlines
EEx tests aware of CR+LF newline on Windows
2014-05-28 19:30:53 +02:00
chyndman f60d3668ed EEx tests aware of CR+LF newline on Windows 2014-05-28 10:25:16 -07:00
Eric Meadows-Jönsson 3471898364 Raise if giving non-keywords to Keyword 2014-05-28 18:28:57 +02:00
José Valim bf7d172f11 Make available_compiler_options public and improve error message 2014-05-28 14:12:29 +02:00
José Valim 28457563ce Merge pull request #2351 from ericmj/compiler_opts
Do not allow custom compiler options
2014-05-28 14:08:38 +02:00
José Valim 280a5ceedc Fix get_and_update_in/2 docs 2014-05-28 13:54:59 +02:00
José Valim a696cf41dc Improve protocol docs 2014-05-28 13:48:35 +02:00
José Valim f3f0ec09b7 Add get_and_update_in/* ops and update CHANGELOG 2014-05-28 13:38:51 +02:00
José Valim e040688bd9 Require get_and_update/3 instead of update/3 in Access 2014-05-28 11:05:58 +02:00
José Valim 9d84e1c007 Merge pull request #2353 from scrogson/patch-1
Supervisor documentation updates
2014-05-28 10:20:50 +02:00
José Valim 2e6a83dff9 Merge pull request #2352 from chyndman/mix-shell-tests-win
Mix tests aware of Windows' esoteric newline behavior
2014-05-28 10:17:31 +02:00
Sonny Scroggin d866a26ac1 supervise vs. supervisor 2014-05-28 02:18:14 -05:00
Sonny Scroggin 3453c30f4c import happens already with use Supervisor 2014-05-28 02:17:35 -05:00
Sonny Scroggin 50a7b0e1bc Small typo 2014-05-28 02:01:30 -05:00
chyndman 7ce790035d Mix.CmdTest, Mix.ShellTest and Windows newlines (take 2) 2014-05-27 14:55:02 -07:00
chyndman 606cd2ff69 Defined os_newline in test_helper for Mix 2014-05-27 14:36:22 -07:00
José Valim 9c9d2e1ea0 Fix Kernel bootstrap 2014-05-27 23:18:06 +02:00
José Valim 6e7baa42ac Test and assert valid paths 2014-05-27 23:12:57 +02:00
chyndman 633f92edbe Mix.CmdTest, Mix.ShellTest and Windows newlines (take 2) 2014-05-27 14:06:59 -07:00
chyndman b23f2fc360 Mix.ShellTest aware of Windows newline behavior 2014-05-27 12:39:48 -07:00
chyndman 5b31ec421d Mix.CmdTest aware of Windows newline behavior 2014-05-27 12:31:55 -07:00
José Valim 2d38f20b43 Add get_in/1, put_in/2 and update_in/2 2014-05-27 13:56:18 +02:00
Eric Meadows-Jönsson 9f6f92b0f2 Do not allow custom compiler options 2014-05-27 12:46:09 +02:00
José Valim 4a183b462c Fix nested updates bootstrapping 2014-05-27 10:47:58 +02:00
José Valim 4d53d8c710 Add get_in/2, put_in/3 and update_in/3 2014-05-27 10:14:19 +02:00
José Valim a09a0c2a1a Add Access.update/3 2014-05-27 10:14:19 +02:00
José Valim 6a42ab0797 Merge pull request #2348 from chyndman/mix-build-project-and-tests-win
Mix.Project.build_structure tested for copying fallback
2014-05-27 08:54:58 +02:00
chyndman 183ed3ffdd Mix.Project.build_structure test asserts according to symlink_or_copy 2014-05-26 14:43:23 -07:00
José Valim 8a8779fcbc Merge pull request #2349 from chyndman/mix-cli-test-win-stderr
Mix.CLITest redirects stderr to stdout for testing on Windows
2014-05-26 23:28:56 +02:00
chyndman bcb8cd763e Mix.CLITest redirects stderr to stdout for testing on Windows 2014-05-26 12:37:13 -07:00
chyndman 97c0809845 Mix.Utils.build_project returns :ok always 2014-05-26 11:05:27 -07:00
José Valim e33a9557aa Merge pull request #2346 from alco/md-fixes
Fix formatting in .md files
2014-05-26 14:43:57 +02:00
José Valim 43706a4623 mix new no longer generates a supevision tree by default
Please pass `--sup` instead, closes #2074.
2014-05-26 14:41:56 +02:00
Alexei Sholik b816c98877 Fix formatting in .md files 2014-05-26 15:34:52 +03:00
José Valim 23f0ff81c4 Write consolidated files to _build 2014-05-26 12:27:39 +02:00
José Valim 6aada7fe3c Update CHANGELOG and fix tasks printing 2014-05-26 12:12:15 +02:00
José Valim e24cef0126 Add protocol consolidation with mix compile.protocols 2014-05-26 11:45:22 +02:00
José Valim b161070345 Merge pull request #2344 from madlep/fix_agent_start_link_timeout_option_doc
fix documentation of `:timeout` option in `start_link/2`
2014-05-26 09:48:34 +02:00
José Valim 0b4f9ad486 Merge pull request #2343 from alco/string-doc-null
Escape invalid characters in the docstring for String.chunk
2014-05-26 09:48:19 +02:00
Julian Doherty d484ca8ae2 fix documentation of :timeout option in start_link/2
added line break to make it clearer that `:timeout` is a different
option to `:name`.
2014-05-26 15:39:26 +10:00
Alexei Sholik d77c1a34f4 Escape invalid characters in the docstring for String.chunk 2014-05-26 04:44:57 +03:00
José Valim f6b0f29f5d Add docs for @derive 2014-05-25 19:18:02 +02:00
José Valim 436f563ad3 Ensure variables do not leak on callbacks 2014-05-25 14:22:23 +02:00
José Valim 81b80d5b9c No need to name the agent 2014-05-25 13:33:56 +02:00
José Valim 425cea5a84 Merge pull request #2341 from pma/master
Convert Mix.Tasks.Compile.Elixir.ManifestCompiler from GenServer to Agent
2014-05-25 05:27:29 -06:00
José Valim b4c0826467 Update repo links 2014-05-25 12:41:57 +02:00
José Valim d94491841a Generate docs per app 2014-05-25 12:26:52 +02:00
José Valim bb1093ef2d Improve IEx description 2014-05-25 11:59:58 +02:00
Paulo Almeida 863e22a7e2 Convert Mix.Tasks.Compile.Elixir.ManifestCompiler from a GenServer to an Agent 2014-05-25 10:30:48 +01:00
José Valim aa682a4704 Merge pull request #2340 from chyndman/win-contributing-howto
Instructions for contribution from Windows (first draft)
2014-05-25 16:21:09 +08:00
chyndman 01d5f99437 Instructions for contribution from Windows (first draft) 2014-05-24 23:03:08 -07:00
José Valim 34c2da2c65 Initial work on @derive for structs 2014-05-25 01:43:58 +02:00
José Valim a3d5be91f2 Remove deprecated quote options 2014-05-25 01:43:58 +02:00
José Valim bbb77e0102 Merge pull request #2339 from pma/master
Convert Mix.ProjectStack from a GenServer to an Agent
2014-05-25 01:43:13 +02:00
Paulo Almeida 19164d2e55 Convert Mix.ProjectStack from a GenServer to an Agent 2014-05-24 19:57:11 +01:00
José Valim a950d4fda1 Merge pull request #2337 from patrickgombert/master
Convert Mix.TasksServer from a GenServer to an Agent
2014-05-25 03:01:35 +09:00
Patrick Gombert 33ac879521 Convert Mix.TasksServer from a GenServer to an Agent 2014-05-24 12:37:30 -05:00
José Valim 6ef4c80724 Merge pull request #2338 from jwarwick/autolinks
Fixing doc links
2014-05-25 02:27:36 +09:00
John Warwick 3843ee6ce5 Fixing doc links 2014-05-24 19:09:23 +02:00
José Valim 3ab1db6740 Move Protocol consolidation up to the Protocol module 2014-05-24 18:28:48 +02:00
José Valim 1f2dc0f716 Mark trie structs as private 2014-05-24 14:11:11 +02:00
José Valim 83fa2fbbbf Update CHANGELOG 2014-05-24 13:58:51 +02:00
José Valim cc969f8d01 Remove wrong record wording 2014-05-24 13:53:11 +02:00
José Valim 3de70b909a Merge pull request #2331 from alco/string-chunk
Add String.chunk/2
2014-05-24 20:52:37 +09:00
José Valim 7782227182 Remove duplication on utils test assertion 2014-05-24 13:49:26 +02:00
Alexei Sholik 58cd9e5c11 Add String.chunk/2 2014-05-24 14:45:28 +03:00
José Valim 862ab01f94 Merge pull request #2327 from chyndman/win-copy-always
Mix.Utils.symlink_or_copy always copies on Windows
2014-05-24 20:42:55 +09:00
José Valim 2c630ff33c Enable previously set deprecations 2014-05-24 13:13:30 +02:00
José Valim 007571e979 Remove deprecated records 2014-05-24 12:59:01 +02:00
José Valim 53552d434d Merge pull request #2335 from alco/genserver-doc
Genserver doc
2014-05-24 19:46:03 +09:00
José Valim eb32937858 Get rid of Kernel.access/2 in favor of Access.get/2 2014-05-24 12:45:33 +02:00
José Valim 6720cb6830 Simplify beam writing mechanism 2014-05-24 12:32:39 +02:00
Alexei Sholik 16c0262122 Consistent naming of the “Name Registration” section across docs 2014-05-24 13:29:52 +03:00
Alexei Sholik 2606f47138 Proof-read GenServer docs 2014-05-24 13:28:00 +03:00
José Valim 9337d50775 Merge pull request #2334 from alco/supervisor-doc
Proof-read Supervisor docs
2014-05-24 19:22:00 +09:00
Alexei Sholik 069415595a Proof-read Supervisor docs 2014-05-24 13:13:12 +03:00
José Valim 4e88d82871 Merge pull request #2333 from alco/genevent-doc
Fix formatting and proof-read GenEvent docs
2014-05-24 18:57:05 +09:00
Alexei Sholik 5e8d948e39 Fix formatting and proof-read GenEvent docs 2014-05-24 12:55:31 +03:00
José Valim 893700f65e Start v0.14.0-dev 2014-05-24 11:26:21 +02:00
José Valim c3f0929a77 Merge pull request #2326 from alco/docs-beam-chunks
Docs beam chunks
2014-05-24 18:26:11 +09:00
José Valim 3c48099718 Release v0.13.3 2014-05-24 09:59:49 +02:00
José Valim 419e2a1d2a Merge pull request #2332 from devinus/stream-struct
Change Stream's struct to just %Stream{}
2014-05-24 07:20:44 +02:00
Devin Torres 39c84f4324 Change Stream's struct to just %Stream{} 2014-05-24 00:09:32 -05:00
Alexei Sholik 8448513c8b Update tests that need to read module docs to write BEAM code to disk 2014-05-24 04:33:41 +03:00
Alexei Sholik bc65fc0276 Preserve docs chunk during protocol consolidation 2014-05-24 03:45:30 +03:00
Alexei Sholik b1f0d3ed99 Add version tag to Elixir’s beam chunk
This will allow us to change the layout of the chunk in the future
2014-05-24 03:45:29 +03:00
Alexei Sholik 211d9428b4 Add Code.get_docs/2 function 2014-05-24 03:45:29 +03:00
Alexei Sholik 8a3f2cfc2f Remove :docs and :moduledoc clauses from the __info__ function
Instead, we keep the docs in a custom chunk within the .beam file;
this will allow us to lazily load them
2014-05-24 03:45:29 +03:00
chyndman fff7a85769 Mix.Utils.symlink_or_copy: return types for copying and corresponding tests 2014-05-23 12:47:57 -07:00
José Valim 8652ee5838 Do not allow keyword on containers without pos args 2014-05-23 20:59:38 +02:00
José Valim beb1bbeb3d Ensure config/config.exs is really persisted 2014-05-23 19:22:00 +02:00
chyndman 377c64d1b0 Mix.Utils.symlink_or_copy always copies on Windows 2014-05-23 10:18:11 -07:00
José Valim cc9783a437 Update CHANGELOG 2014-05-23 17:07:39 +02:00
José Valim e566d7cda0 Merge pull request #2320 from alco/regex-literal
Draft implementation of custom inspect algorithm for regexes
2014-05-23 23:41:49 +09:00
José Valim 5c43548b0f Remove unused reduce/3 from map 2014-05-23 16:35:05 +02:00
José Valim 82518c3376 Ensure doctests failures are properly tested 2014-05-23 16:35:05 +02:00
José Valim 8ab8663370 Change help heading on mix 2014-05-23 16:35:05 +02:00
Eric Meadows-Jönsson d44d71cce1 Do not install rebar bundled with elixir 2014-05-23 13:39:46 +02:00
Alexei Sholik 4d66ac72e1 Dedicated inspect implementation for regexes. Fix #2250
We no longer share the implementation with BitString because regex
literals have slightly different escaping rules
2014-05-23 14:36:56 +03:00
José Valim fb49aa1e99 Deprecate Dict.Behaviour in favor of Dict 2014-05-23 00:09:36 +02:00
José Valim a6b9d2ed72 Merge pull request #2324 from chyndman/elixir-bat-fix
elixir.bat fixed to prevent eating quotes
2014-05-22 23:07:31 +02:00
chyndman df1366c1d6 elixir.bat fixed to prevent eating quotes 2014-05-22 14:02:28 -07:00
José Valim 0bcc02d816 Merge pull request #2321 from chyndman/mix-bat-help
elixir.bat defers to mix for handling help options
2014-05-23 04:02:52 +09:00
chyndman 1c9dff1fee elixir.bat only triggers help at first argument 2014-05-22 11:57:25 -07:00
José Valim ed4ee3fe5c Merge pull request #2322 from alco/agent-doc
Proof-read Agent docs
2014-05-23 03:48:06 +09:00
Alexei Sholik cc8f6a4ead Proof-read Agent docs 2014-05-22 20:23:37 +03:00
chyndman 82f800e1c5 elixir.bat defers to mix for handling help options 2014-05-22 09:51:28 -07:00
José Valim 18aeb78547 Exit with proper test code on test_erlang, closes #2313 2014-05-22 12:21:08 +02:00
José Valim 83ccde4db6 Update CHANGELOG, fix tests and typespecs 2014-05-22 11:29:42 +02:00
José Valim 59900260a1 Merge pull request #2312 from alco/option-parser-fixes
Return invalid options as strings
2014-05-22 18:19:52 +09:00
José Valim 6bfcf2f33c \d is a printable character 2014-05-22 10:37:09 +02:00
José Valim 45bd25057f Merge pull request #2317 from vicmargar/fix-exception-doc
Fix doc example for exception/1
2014-05-22 10:30:42 +02:00
José Valim 3f6e6101d8 Merge pull request #2315 from chyndman/gsoc-windows
Mix.CLITest: explicitly declare environment variables (Windows-friendly)
2014-05-22 10:27:54 +02:00
Victor Martinez eb17182ed4 Fix example for exception/1 2014-05-22 00:40:48 +01:00
chyndman 5b08851a8d Mix.CLITest: explicitly declare environment variables (Windows-friendly) 2014-05-21 16:19:37 -07:00
Eric Meadows-Jönsson bc0adc2f4f Merge pull request #2316 from alco/string-to_char_list
String to char list
2014-05-22 00:48:31 +02:00
Alexei Sholik 67f2be2eb9 Fix example for String.to_char_list 2014-05-22 01:18:17 +03:00
Alexei Sholik fe5d8e6483 Mention removal of String.to_char_list! in the CHANGELOG 2014-05-22 01:17:37 +03:00
Alexei Sholik 4cc28c2e8c Return invalid options as strings 2014-05-22 00:05:41 +03:00
Eric Meadows-Jönsson 78453597c2 Bump Hex requirement 2014-05-21 22:06:46 +02:00
José Valim 927594bb14 Always return nil for undefined options (#2308) 2014-05-21 21:09:01 +02:00
José Valim fab45f58c6 Merge pull request #2311 from alco/option-parser-fixes
Do not assume invalid options are of boolean type
2014-05-21 20:41:14 +02:00
Alexei Sholik a9375903ae Do not assume invalid options are of boolean type 2014-05-21 17:36:19 +03:00
José Valim 44d4b5a7bf Merge pull request #2298 from alco/mix-run-errors
Report errors about missing files in the mix run task
2014-05-21 15:20:13 +02:00
José Valim ed582c3977 Merge pull request #2309 from alco/option-parser-tests
Fix parsing of "-" in OptionParser
2014-05-21 15:19:04 +02:00
Alexei Sholik 9d4231c92c Add tests for non-existent file errors in mix run 2014-05-21 14:07:50 +03:00
Alexei Sholik ecdb3e9aa9 Report errors about missing files in the mix run task 2014-05-21 14:03:00 +03:00
Alexei Sholik fb2686fe37 Parse "-" as a plain argument 2014-05-21 13:55:28 +03:00
José Valim 55e885f5c5 Ensure we don't leak erlang terms on failure, closes #2303 2014-05-21 12:44:12 +02:00
José Valim f92d8393da (Soft) deprecate set_elem/3 in favor of put_elem/3, closes #2259 2014-05-21 12:04:22 +02:00
José Valim cb0c3d8a2a Support :strict option on parse and improve return types 2014-05-21 11:50:11 +02:00
José Valim d9ece7edb0 Merge pull request #2290 from alco/option-parser-next
Tentative implementation of the `next` function
2014-05-21 11:09:50 +02:00
Andrey Pavlov 24a256686a Description for mix test --seed option
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2014-05-21 11:05:53 +02:00
José Valim ce9b381f67 Unify mix run tests 2014-05-21 11:04:37 +02:00
José Valim a0d42b47b6 Merge pull request #2304 from knewter/feature/agent_doc_typo_fix
Fix typo in Agent docs
2014-05-21 10:33:24 +02:00
José Valim e3e5b9b85b Merge pull request #2305 from benjamintanweihao/patch-1
Fix typo
2014-05-21 10:33:10 +02:00
José Valim 5f4180a898 Merge pull request #2306 from benjamintanweihao/patch-2
Trailing whitespace.
2014-05-21 10:32:57 +02:00
Benjamin Tan Wei Hao 184cb36e00 Trailing whitespace. 2014-05-21 11:33:47 +08:00
Benjamin Tan Wei Hao 48a0211156 Fix typo 2014-05-21 11:24:46 +08:00
Josh Adams 9dc7172ec5 Fix typo in Agent docs
Replace reference to GenEvent with reference to Agent
2014-05-20 20:46:13 -05:00
José Valim 00db100bd4 Merge pull request #2301 from alco/mix-readme-template
Make the title in the Readme generated by mix new more prominent
2014-05-21 00:59:29 +02:00
Alexei Sholik 1804db48c8 Make the title in the Readme generated by mix new more prominent 2014-05-21 01:36:33 +03:00
José Valim b92368104e Merge pull request #2300 from lexmag/mix-run-warn-fix
Fix match in `Mix.Tasks.Run` test
2014-05-20 23:28:13 +02:00
Aleksei Magusev 6f0c0269d2 Fix match in Mix.Tasks.Run test 2014-05-21 01:14:37 +04:00
José Valim 9b431a8831 Speed up CLI tests by avoiding booting Elixir 2014-05-20 22:42:01 +02:00
José Valim d53509efd2 Merge pull request #2299 from lucasweiblen/fix-typos
Fix typos
2014-05-20 22:25:21 +02:00
Lucas Weiblen ea0226b3da Fix typo on ExUnit 2014-05-20 16:42:48 -03:00
Lucas Weiblen 4346a41811 Fix typos on Mix 2014-05-20 16:42:33 -03:00
José Valim b342739dba Tag wrong heredoc start as SyntaxError instead of TokenMissingError 2014-05-20 20:25:31 +02:00
José Valim b570850faf Fix Stream.cycle/1 with inner halting stream, closes #2285 2014-05-20 16:51:00 +02:00
José Valim 5882cf5fb6 Merge pull request #2297 from alco/mix-run-test
Small fix in the run task tests
2014-05-20 16:44:09 +02:00
Alexei Sholik 4dde1b076b Small fix in the run task tests 2014-05-20 17:36:52 +03:00
Alexei Sholik 142ebc3ecd Fix handling of boolean options with bad values 2014-05-20 17:32:12 +03:00
Alexei Sholik 339c57f1c6 Fix value normalization in strict mode 2014-05-20 17:32:12 +03:00
Alexei Sholik 76260b057f Refactor option parsing internals to support strict mode 2014-05-20 17:32:12 +03:00
Alexei Sholik 40a384aef6 Tentative implementation of the next function 2014-05-20 17:32:12 +03:00
José Valim e7fcaff839 Merge pull request #2289 from lexmag/master
Change `OptionParser` to remove `--`. Fix command-line arguments handling.
2014-05-20 16:14:04 +02:00
José Valim a4b01b2f97 Fix race condition on supervisor test 2014-05-20 15:57:18 +02:00
Aleksei Magusev ee561bd473 Fix raising Code.LoadError 2014-05-20 17:10:46 +04:00
Aleksei Magusev 6696133abb Fix command-line arguments handling in Kernel.CLI 2014-05-20 17:10:46 +04:00
Aleksei Magusev 42b0c24f6a Fix command-line arguments handling in Mix.Tasks.Run 2014-05-20 17:10:33 +04:00
José Valim b8d02e94b2 Merge pull request #2296 from alco/record-doc
Fix example of using `defrecord`
2014-05-20 14:16:37 +02:00
Alexei Sholik d9a8bbb65e Fix example of using defrecord 2014-05-20 14:20:23 +03:00
Aleksei Magusev c0bc64fb19 Change OptionParser to remove the end of command options (--) 2014-05-20 12:49:10 +04:00
Aleksei Magusev 0445647d63 Fix indentation 2014-05-20 12:37:20 +04:00
José Valim da03542309 Merge pull request #2294 from ericmj/reraise
reraise should work like raise
2014-05-20 10:31:55 +02:00
Eric Meadows-Jönsson 2e24e228d1 reraise should work like raise 2014-05-20 10:29:07 +02:00
José Valim f83eb30975 Merge pull request #2295 from kiela/master
Add links to LEGAL and LICENSE files in README.md
2014-05-20 10:00:14 +02:00
Kamil Kieliszczyk 80d81ed47a Add links to LEGAL and LICENSE files in README.md 2014-05-20 02:31:51 +02:00
José Valim dc9803de6d Remove inlist and inbits from keywords list 2014-05-19 23:12:45 +02:00
José Valim 4e5c851c44 Update CHANGELOG 2014-05-19 18:56:34 +02:00
José Valim b76d39e9d4 Ensure map_join also works for empty binaries 2014-05-19 18:54:13 +02:00
José Valim be34a75124 Disabling :hygiene in quote is deprecated 2014-05-19 18:25:56 +02:00
José Valim 78c16c0e89 Do not allow nil context to be set on quote 2014-05-19 15:41:09 +02:00
José Valim 860ca5b5cb Add Macro.prewalk/2/3 and Macro.postwalk/2/3 2014-05-19 15:31:58 +02:00
José Valim ce8733fc6f Merge pull request #2293 from liveforeverx/fix_enum
fix Enum.join for empty elements in the begining of the collection
2014-05-19 12:44:12 +02:00
Dima Aleksandrov 24109337fe fix Enum.join for empty elements in the begining of the collection 2014-05-19 12:35:52 +02:00
José Valim 90c2959ed7 Update record.ex docs, closes #2291 2014-05-19 11:42:20 +02:00
José Valim 70e3be3ccf Merge pull request #2292 from mururu/fix-doc_test-docs
Fix ExUnit.DocTest docs
2014-05-19 11:37:24 +02:00
Yuki Ito eeb796107e Fix ExUnit.DocTest docs 2014-05-19 17:04:47 +09:00
José Valim 166bbb17f9 Reduce examples of disabling macros hygiene 2014-05-18 00:51:45 +02:00
José Valim 4ed84f86cd Update CHANGELOG 2014-05-17 17:10:30 +02:00
José Valim 540c763c85 Move binary and list conversions to String and List 2014-05-17 17:02:46 +02:00
José Valim 3d4877ffda Get rid of BitString module and bitstring conversions 2014-05-17 16:34:26 +02:00
José Valim 84262257ef Move Integer/Float conversions to their respective modules 2014-05-17 16:29:49 +02:00
José Valim 68dd952b45 More deprecations around tuples and records 2014-05-17 16:13:02 +02:00
José Valim a068b5a4b0 Move List and String deprecations to new mechanism 2014-05-17 16:02:56 +02:00
José Valim c6da7b56ba Optimize cond expansion 2014-05-17 15:56:47 +02:00
Eric Meadows-Jönsson dbe918c911 Merge pull request #2288 from ericmj/compile-deprecations
Make deprecations compiler warnings
2014-05-17 15:53:53 +02:00
Eric Meadows-Jönsson 4977ec8c05 Make deprecations compiler warnings 2014-05-17 15:52:00 +02:00
José Valim bf8f5c4bb8 Add String.to_char_list and List.to_string 2014-05-17 13:59:17 +02:00
José Valim 3bb8e17e9e Add iodata_to_binary and chardata_to_string to IO 2014-05-17 13:06:40 +02:00
José Valim 6b755112ce Add a test for mix config files 2014-05-17 12:09:10 +02:00
José Valim 9650344c76 Do faster expansion on rewrites 2014-05-16 23:49:12 +02:00
José Valim ca6788113e Deprecations should go to standard_error 2014-05-16 22:39:56 +02:00
José Valim 095e4a7d2e Improve docs for string, process and supervisor 2014-05-16 22:28:20 +02:00
José Valim 23475f9590 Make exception specs match the ones defined in callback 2014-05-16 18:53:06 +02:00
José Valim e69a35a85b Merge pull request #2281 from bitwalker/please-the-dialyzer-gods
Add typespecs for builtins, __record__,  __struct__, exception
2014-05-16 18:51:58 +02:00
Paul Schoenfelder a6392a8220 Add typespecs for builtins, __record__, and __struct__
Added typespecs for:

- __FILE__/1
- __MODULE__/1
- __info__/1
- __record__/1, __record__/2, __record__/3
- __struct__/0
2014-05-16 11:45:47 -05:00
José Valim 21399ebfa6 Fix false positives when inspecting records, closes #2287 2014-05-16 18:20:21 +02:00
José Valim deb0191a21 Add Atom.to_string, Atom.to_char_list and BitString.to_list 2014-05-16 14:38:43 +02:00
José Valim 489b1e04c7 Deprecate defrecord/3 2014-05-16 13:35:34 +02:00
José Valim a80f9d9ae1 Remove dependency on Record.Backend 2014-05-16 12:21:36 +02:00
José Valim 22eb707aab Reduce bootstrap list 2014-05-16 00:53:40 +02:00
José Valim c22e8e85dc Convert exceptions to structs 2014-05-16 00:01:49 +02:00
José Valim 98b1acfe28 Prepare elixir_try to be able to handle upcoming map clauses 2014-05-16 00:01:49 +02:00
José Valim cd8f52aa92 Disallow variables on the right side of in 2014-05-16 00:01:48 +02:00
José Valim efc6963e79 Fix race condition on agent tests 2014-05-16 00:01:48 +02:00
José Valim dcf3692722 Merge pull request #2282 from bitwalker/macro-to-string-fixes
Properly handle atom keys in kw_list_to_string
2014-05-15 22:28:12 +02:00
Paul Schoenfelder 30294998e5 Properly handle atom keys in kw_list_to_string 2014-05-15 10:53:16 -05:00
José Valim 48863afca4 Merge pull request #2283 from edgurgel/patch-1
Update String moduledoc
2014-05-15 18:18:43 +09:00
Eduardo Gurgel 8622ed53e2 Update String moduledoc
`Kernel.binary_to_term` does not exist anymore

[ci skip]
2014-05-15 20:36:48 +12:00
José Valim 5e34d0a7f1 Allow h +/1 as valid syntax 2014-05-13 14:21:51 +02:00
José Valim 5d775f2c24 Remove warning from new applications 2014-05-13 13:56:01 +02:00
José Valim 9acf4b2df9 Merge pull request #2279 from alco/keyword-doc-fixes
Keyword doc fixes
2014-05-13 16:52:48 +09:00
Alexei Sholik 45ee18578b Small doc fixes for Keyword 2014-05-13 00:26:21 +03:00
Alexei Sholik fafdf40687 Better docs and tests for Keyword.update and Keyword.update! 2014-05-13 00:26:07 +03:00
José Valim 22206f87bf Merge pull request #2276 from alco/option-parser-fix
Option parser fix
2014-05-13 03:44:06 +09:00
José Valim 2e7d938707 Merge pull request #2277 from alco/elixir-translator-warning
Remove unused import in elixir_translator.erl
2014-05-12 20:37:00 +02:00
Alexei Sholik 57ccb29bc4 Remove unused import in elixir_translator.erl 2014-05-12 21:35:21 +03:00
Alexei Sholik 79bb046ad6 Fix unused variable warning 2014-05-12 21:33:04 +03:00
Alexei Sholik 24c17c350c Format OptionParser tests more nicely 2014-05-12 21:27:44 +03:00
Alexei Sholik 2187db5415 Don’t ignore values for ‘--no-*’ options 2014-05-12 21:24:31 +03:00
Alexei Sholik 84cdbe3562 Add more sanity-check tests for OptionParser 2014-05-12 21:24:31 +03:00
Alexei Sholik 95ed007b1c Properly handle non-boolean values for boolean options 2014-05-12 21:24:31 +03:00
José Valim 55bd1d4e5a core -> stdlib in makefile 2014-05-12 19:53:09 +02:00
José Valim e6e1af7d84 Merge pull request #2275 from alco/makefile-rename-kernel
Rename some Makefile targets. Closes #2274
2014-05-12 19:50:50 +02:00
Alexei Sholik 1019917f1f Rename some Makefile targets. Closes #2274
kernel -> core
test_kernel -> test_stdlib
2014-05-12 20:41:06 +03:00
José Valim 9f0a30de43 Merge xgen and soft deprecate existing behaviours 2014-05-12 19:10:50 +02:00
José Valim ef67a9527a List all soft deprecations that have become deprecated 2014-05-12 12:37:03 +02:00
José Valim ff04acd3a6 Update exit signals in Exception to {:EXIT, pid} 2014-05-12 12:35:43 +02:00
José Valim a79f7c291f Merge pull request #2270 from fishcakez/format_banner_EXIT
Format EXIT signals consistently
2014-05-12 12:13:06 +02:00
José Valim 10ea9bdd40 Mention ERL_COMPILER_OPTIONS and rename ELIXIR_ERL_OPTS to ELIXIR_ERL_OPTIONS
Closes #2271
2014-05-12 09:47:37 +02:00
James Fish ba62dc9522 Format EXIT signals consistently 2014-05-11 18:01:13 +01:00
Eric Meadows-Jönsson d9bb48f691 Always filter children of remote dependencies 2014-05-11 15:13:09 +02:00
José Valim 580107d29c Deprecate code that was previously soft-deprecated 2014-05-11 14:19:05 +02:00
José Valim bb8f3aaa8d Remove deprecated code 2014-05-11 13:55:11 +02:00
José Valim f4b436cbc3 Start v0.13.3-dev 2014-05-11 12:30:45 +02:00
310 changed files with 17450 additions and 10089 deletions
-1
View File
@@ -4,7 +4,6 @@
/ebin
/lib/*/ebin/*
/lib/*/tmp
/lib/*/test/tmp
/lib/elixir/src/elixir.app.src
/lib/elixir/src/*_lexer.erl
/lib/elixir/src/*_parser.erl
+186 -31
View File
@@ -1,4 +1,160 @@
# v0.13.2 (2014-05-11)
# Changelog
## v0.14.2
* Enhancements
* [Enum] Improve performance of `Enum.join/2` and `Enum.map_join/3` by using iolists
* [Kernel] Ensure compatibility with Erlang 17.1
* [Kernel] Support `@external_resource` attribute to external dependencies to a module
* [Mix] Allow built Git dependencies to run on a system without Git by passing `--no-deps-check`
* [Mix] Add `MIX_ARCHIVES` env variable (it is recommended for Elixir build tools to swap this environment)
* [Task] Set `:proc_lib` initial call on task to aid debugging
* [Typespec] Delay typespec compilation to after expansion
* [URI] Allow `parse/1` now accepts `%URI{}` as argument and return the uri itself
* Bug fixes
* [CLI] Support paths inside archives in `-pa` and `-pz` options
* [IEx] Remove dely when printing data from the an application start callback
* [IEx] Ensure we show a consistent error when we cannot evaluate `.iex.exs`
* [Kernel] Ensure derived protocols are defined with a file
* [Kernel] Change precedence of `&` to not special case `/`
* [Kernel] Ensure we can only use variables and `\\` as arguments of bodyless clause
* Soft deprecations (no warnings emitted)
* [EEx] Using `EEx.TransformerEngine` and `EEx.AssignsEngine` are deprecated in favor of function composition with `Macro.prewalk/1` (see `EEx.SmartEngine` for an example)
* [Mix] `Mix.Generator.from_file/1` is deprecated in favor of passing `from_file: file` option to `embed_text/2` and `embed_template/2` (note though that `from_file/1` expects a path relative to the current file while the `from_file: file` expects one relative to the current working directory)
* Deprecations
* [Kernel] `size/1` is deprecated in favor of `byte_size/1` and `tuple_size/1` (this change was soft deprecated two releases ago)
* Backwards incompatible changes
* [CLI] Remove support for the `--gen-debug` option as its usage is not documented by OTP
* [Kernel] Sigils no longer balance start and end tokens, e.g. the sigil `~s(f(o)o)` is no longer valid as it finishes in the first closing `)`
* [Kernel] Variables set in `cond` clause heads are no longer available outside of that particular `cond` clause (this is the behaviour also found in `case`, `receive` and friends)
* [System] `build_info/0` now returns a map
## v0.14.1 (2014-06-18)
* Enhancements
* [Base] Decoding and encoding functions now accept the `:case` as an option
* [ExUnit] The test process now exits with `:shutdown` reason
* [GenEvent] `GenEvent.stream/2` now accepts `:sync` and `:async` modes
* [Node] Add `Node.start/3` and `Node.stop/0`
* [String] Updated Unicode database to 7.0
* [Task] Log when tasks crash
* Bug fixes
* [Enum] `Enum.slice/2` and `Enum.slice/3` always returns a list (and never nil)
* [Kernel] Disambiguate (w)erl to (w)erl.exe
* [Mix] Ensure umbrella project is recompiled when a dependency inside an umbrella child changes
* [OptionParser] Do not allow underscores in option names
* [Path] Fix path expansion of `"/.."`
* [Path] Do not match files starting with `.` in `Path.wildcard/2` by default
* [Process] `Process.info(pid, :registered_name)` returns `{:registered_name, nil}` if there is no registered name
* [String] `String.slice/2` and `String.slice/3` always returns a list (and never nil)
* [URI] `encode/1` does not escape reserved/unreserved characters by default nor encodes whitespace as `+` (check `URI.encode_www_form/1` and `URI.decode_www_form/1` for previous behaviour)
* Deprecations
* [Mix] `:escript_*` options were moved into a single `:escript` group
* Backwards incompatible changes
* [GenEvent] `GenEvent.stream/2` defaults to `:sync` mode
* [Kernel] Remove `get_in/1`
## v0.14.0 (2014-06-08)
* Enhancements
* [ExUnit] Add `on_exit/1` callbacks that are guaranteed to run once the test process exits and always in another process
* [Kernel] Store documentation in the abstract code to avoid loading them when the module is loaded
* [Kernel] Add `get_in/2`, `put_in/3`, `update_in/3` and `get_and_update_in/3` to handle nested data structure operations
* [Kernel] Add `get_in/1`, `put_in/2`, `update_in/2` and `get_and_update_in/2` to handle nested data structure operations via paths
* [Mix] Add `Mix.Config` to ease definition of configuration files
* [Mix] Add `mix loadconfig` task that can be called multiple times to load external configs
* [Mix] Support `--config` option on `mix run`
* [Mix] Support `HTTP_PROXY` and `HTTPS_PROXY` on Mix url commands
* [Mix] Support `--names` options in `mix help` which emit only names (useful for autocompletion)
* [Protocol] Add `Protocol.consolidate/2`, `Protocol.consolidated?/1` and a `mix compile.protocols` task for protocol consolidation
* [Protocol] Add `Protocol.derive/3` for runtime deriving of a struct
* [String] Add `String.chunk/2`
* [Struct] Add support for `@derive` before `defstruct/2` definitions
* Bug fixes
* [File] `File.rm` now consistently deletes read-only across operating systems
* [Kernel] Ensure Mix `_build` structure works on Windows when copying projects
* [Kernel] Ensure `1.0E10` (with uppercase E) is also valid syntax
* [Mix] Fix `mix do` task for Windows' powershell users
* [Path] Fix `Path.absname("/")` and `Path.expand("/")` to return the absolute path `"/"`.
* Soft deprecations (no warnings emitted)
* [Kernel] `size/1` is deprecated, please use `byte_size/1` or `tuple_size/1` instead
* [ExUnit] `teardown/2` and `teardown_all/2` are deprecated in favor of `on_exit/1` callbacks
* Deprecations
* [Access] `Access.access/2` is deprecated in favor of `Access.get/2`
* [Dict] `Dict.Behaviour` is deprecated in favor of `Dict`
* [Kernel] `Application.Behaviour`, `GenEvent.Behaviour`, `GenServer.Behaviour` and `Supervisor.Behaviour` are deprecated in favor of `Application`, `GenEvent`, `GenServer` and `Supervisor`
* [Kernel] `defexception/3` is deprecated in favor of `defexception/1`
* [Kernel] `raise/3` is deprecated in favor of `reraise/2`
* [Kernel] `set_elem/3` is deprecated in favor of `put_elem/3`
* [Kernel] Passing an atom `var!/1` is deprecated, variables can be built dynamically with `Macro.var/2`
* [Mix] Exceptions that define a `:mix_error` field to be compatible with Mix are no longer supported. Instead please provide a `:mix` field and use `Mix.raise/1` and `Mix.raise/2`
* Backwards incompatible changes
* [Access] `Kernel.access/2` no longer exists and the `Access` protocol now requires `get/2` (instead of `access/2`) and `get_and_update/3` to be implemented
* [Kernel] Retrieving docs as `module.__info__(:docs)` is no longer supported, please use `Code.get_docs/2` instead
* [Kernel] `Code.compiler_options/1` no longer accepts custom options, only the ones specified by Elixir (use mix config instead)
* [Mix] `mix new` no longer generates a supevision tree by default, please pass `--sup` instead
* [Task] Tasks are automatically linked to callers and a failure in the task will crash the caller directly
## v0.13.3 (2014-05-24)
* Enhancements
* [OptionParser] Add `:strict` option that only parses known switches
* [OptionParser] Add `next/2` useful for manual parsing of options
* [Macro] Add `Macro.prewalk/2/3` and `Macro.postwalk/2/3`
* [Kernel] `GenEvent`, `GenServer`, `Supervisor`, `Agent` and `Task` modules added
* [Kernel] Make deprecations compiler warnings to avoid the same deprecation being printed multiple times
* Bug fixes
* [Enum] Fix `Enum.join/2` and `Enum.map_join/3` for empty binaries at the beginning of the collection
* [ExUnit] Ensure the formatter doesn't error when printing :EXITs
* [Kernel] Rename `ELIXIR_ERL_OPTS` to `ELIXIR_ERL_OPTIONS` for consistency with `ERL_COMPILER_OPTIONS`
* [OptionParser] Parse `-` as a plain argument
* [OptionParser] `--` is always removed from argument list on `parse/2` and when it is the leading entry on `parse_head/2`
* [Regex] Properly escape regex (previously regex controls were double escaped)
* Soft deprecations (no warnings emitted)
* [Dict] `Dict.Behaviour` is deprecated in favor of `Dict`
* [Kernel] `Application.Behaviour`, `GenEvent.Behaviour`, `GenServer.Behaviour` and `Supervisor.Behaviour` are deprecated in favor of `Application`, `GenEvent`, `GenServer` and `Supervisor`
* [Kernel] `defexception/3` is deprecated in favor of `defexception/1`
* [Kernel] `raise/3` is deprecated in favor of `reraise/2`
* [Kernel] `set_elem/3` is deprecated in favor of `put_elem/3`
* Soft deprecations for conversions (no warnings emitted)
* [Kernel] `atom_to_binary/1` and `atom_to_list/1` are deprecated in favor of `Atom.to_string/1` and `Atom.to_char_list/1`
* [Kernel] `bitstring_to_list/1` and `list_to_bitstring/1` are deprecated in favor of the `:erlang` ones
* [Kernel] `binary_to_atom/1`, `binary_to_existing_atom/1`, `binary_to_float/1`, `binary_to_integer/1` and `binary_to_integer/2` are deprecated in favor of conversion functions in `String`
* [Kernel] `float_to_binary/*` and `float_to_list/*` are deprecated in favor of `Float.to_string/*` and `Float.to_char_list/*`
* [Kernel] `integer_to_binary/*` and `integer_to_list/*` are deprecated in favor of `Integer.to_string/*` and `Integer.to_char_list/*`
* [Kernel] `iodata_to_binary/1` and `iodata_length/1` are deprecated `IO.iodata_to_binary/1` and `IO.iodata_length/1`
* [Kernel] `list_to_atom/1`, `list_to_existing_atom/1`, `list_to_float/1`, `list_to_integer/1`, `list_to_integer/2` and `list_to_tuple/1` are deprecated in favor of conversion functions in `List`
* [Kernel] `tuple_to_list/1` is deprecated in favor of `Tuple.to_list/1`
* [List] `List.from_char_data/1` and `List.from_char_data!/1` deprecated in favor of `String.to_char_list/1`
* [String] `String.from_char_data/1` and `String.from_char_data!/1` deprecated in favor of `List.to_string/1`
* Deprecations
* [Kernel] `is_exception/1`, `is_record/1` and `is_record/2` are deprecated in favor of `Exception.exception?1`, `Record.record?/1` and `Record.record?/2`
* [Kernel] `defrecord/3` is deprecated in favor of structs
* [Kernel] `:hygiene` in `quote` is deprecated
* [Mix] `Mix.project/0` is deprecated in favor of `Mix.Project.config/0`
* [Process] `Process.spawn/1`, `Process.spawn/3`, `Process.spawn_link/1`, `Process.spawn_link/3`, `Process.spawn_monitor/1`, `Process.spawn_monitor/3`, `Process.send/2` and `Process.self/0` are deprecated in favor of the ones in `Kernel`
* Backwards incompatible changes
* [Exception] Exceptions now generate structs instead of records
* [OptionParser] Errors on parsing returns the switch and value as binaries (unparsed)
* [String] `String.to_char_list/1` (previously deprecated) no longer returns a tuple but the char list only and raises in case of failure
## v0.13.2 (2014-05-11)
* Enhancements
* [Application] Add an Application module with common functions to work with OTP applications
@@ -7,7 +163,7 @@
* [Mix] `mix deps.clean` now works accross environments
* [Mix] Support line numbers in `mix test`, e.g. test/some/file_test.exs:12
* [Mix] Use `@file` attributes to detect dependencies in between `.ex` and external files. This means changing an `.eex` file will no longer recompile the whole project only the files that depend directly on it
* [Mix] Support application configurations in `config/config.exs` which can be customized by specifying your own `:config_path`
* [Mix] Support application configurations in `config/config.exs`
* [Mix] Support user-wide configuration with `~/.mix/config.exs`
* [Mix] `mix help` now uses ANSI formatting to print guides
* [Regex] Support functions in `Regex.replace/4`
@@ -30,7 +186,6 @@
* [System] Convert remaining functions in System to rely on char data
* Soft deprecations (no warnings emitted)
* [Application] `use Application.Behaviour` is deprecated in favor of `use Application`
* [Exception] `exception.message` is deprecated in favor `Exception.message/1` for retrieving exception messages
* [Kernel] `is_exception/1`, `is_record/1` and `is_record/2` are deprecated in favor of `Exception.exception?1`, `Record.record?/1` and `Record.record?/2`
* [Mix] `Mix.project/0` is deprecated in favor of `Mix.Project.config/0`
@@ -51,7 +206,7 @@
* [URI] The `URI.Info` record has now become the `URI` struct
* [Version] The `Version.Schema` record has now become the `Version` struct
# v0.13.1 (2014-04-27)
## v0.13.1 (2014-04-27)
* Enhancements
* [Mix] Support `MIX_EXS` as configuration for running the current mix.exs file
@@ -77,7 +232,7 @@
* [Path] Atoms are no longer supported in Path functions
* [Regex] Regexes are no longer unicode by default. Instead, they must be explicitly marked with the `u` option
# v0.13.0 (2014-04-20)
## v0.13.0 (2014-04-20)
* Enhancements
* [Base] Add `Base` module which does conversions to bases 16, 32, hex32, 64 and url64
@@ -128,7 +283,7 @@
* Backwards incompatible changes
* [ExUnit] Formatters are now required to be a GenEvent and `ExUnit.run/2` returns a map with results
# v0.12.5 (2014-03-09)
## v0.12.5 (2014-03-09)
* Bug fixes
* [Kernel] Ensure `try` does not generate an after clause. Generating an after clause forbade clauses in the `else` part from being tail recursive. This should improve performance and memory consumption of `Stream` functions
@@ -146,7 +301,7 @@
* Backwards incompatible changes
* [Mix] Remove `MIX_GIT_FORCE_HTTPS` as Git itself already provides mechanisms for doing so
# v0.12.4 (2014-02-12)
## v0.12.4 (2014-02-12)
* Enhancements
* [Mix] `mix deps.get` and `mix deps.update` no longer compile dependencies afterwards. Instead, they mark the dependencies which are going to be automatically compiled next time `deps.check` is invoked (which is done automatically by most mix tasks). This means users should have a better workflow when migrating in between environments
@@ -160,7 +315,7 @@
* [ExUnit] `CaptureIO` returns an empty string instead of nil when there is no capture
* [Version] The `Version` module now only works with SemVer. The functions `Version.parse/1` and `Version.parse_requirement/1` now return `{:ok,res} | :error` for the cases you want to handle non SemVer cases manually. All other functions will trigger errors on non semantics versions
# v0.12.3 (2014-02-02)
## v0.12.3 (2014-02-02)
* Enhancements
* [Kernel] Warnings now are explicitly tagged with "warning:" in messages
@@ -193,7 +348,7 @@
* [Range] `Range` is no longer a record, instead use `first .. last` if you need pattern matching
* [Set] Implementations of `difference/2`, `disjoint?/2`, `equal?/2`, `intersection/2`, `subset?/2` and `union/2` in `HashSet` are no longer polymorphic. To get polymorphism, use the functions in `Set` instead
# v0.12.2 (2014-01-15)
## v0.12.2 (2014-01-15)
* Enhancements
* [EEx] Allow `EEx.AssignsEngine` to accept any Dict
@@ -222,7 +377,7 @@
* Backwards incompatible changes
* [String] Change `String.next_grapheme/1` and `String.next_codepoint/1` to return `nil` on string end
# v0.12.1 (2014-01-04)
## v0.12.1 (2014-01-04)
* Enhancements
* [ExUnit] Support `:include` and `:exclude` configuration options to filter which tests should run based on their tags. Those options are also supported via `mix test` as `--include` and `--exclude`
@@ -246,7 +401,7 @@
* [GenServer] GenServer now stops on unknown call/cast requests
* [Kernel] Change how `->` is represented in AST. Now each clause is represented by its own AST node which makes composition easier. See commit 51aef55 for more information.
# v0.12.0 (2013-12-15)
## v0.12.0 (2013-12-15)
* Enhancements
* [Exception] Allow `exception/1` to be overridden and promote it as the main mechanism to customize exceptions
@@ -283,7 +438,7 @@
* [Stream] The `Stream.Lazy` structure has changed to accumulate functions and accumulators as we go (its inspected representation has also changed)
* [Typespec] `when` clauses were moved to the outer part of the spec and should be in the keywords format. So `add(a, b) when is_subtype(a, integer) and is_subtype(b, integer) :: integer` should now be written as `add(a, b) :: integer when a: integer, b: integer`
# v0.11.2 (2013-11-14)
## v0.11.2 (2013-11-14)
* Enhancements
* [Mix] Add `mix iex` that redirects users to the proper `iex -S mix` command
@@ -292,7 +447,7 @@
* Backwards incompatible changes
* [Mix] Mix now compiles files to `_build`. Projects should update just fine, however documentation and books may want to update to the latest information
# v0.11.1 (2013-11-07)
## v0.11.1 (2013-11-07)
* Enhancements
* [Mix] Improve dependency convergence by explicitly checking each requirement instead of expecting all requirements to be equal
@@ -310,7 +465,7 @@
* Backwards incompatible changes
* [Mix] Setting `:load_paths` in your project configuration is deprecated
# v0.11.0 (2013-11-02)
## v0.11.0 (2013-11-02)
* Enhancements
* [Code] Eval now returns variables from other contexts
@@ -357,7 +512,7 @@
* [Process] `Process.group_leader/2` args have been reversed so the "subject" comes first
* [Protocol] Protocol no longer dispatches to `Number`, but to `Integer` and `Float`
# v0.10.3 (2013-10-02)
## v0.10.3 (2013-10-02)
* Enhancements
* [Enum] Add `Enum.take_every/2`
@@ -392,7 +547,7 @@
* [Mix] `File.IteratorError` was renamed to `IO.StreamError`
* [Mix] `mix new` now defaults to the `--sup` option, use `--bare` to get the previous behaviour
# v0.10.2 (2013-09-03)
## v0.10.2 (2013-09-03)
* Enhancements
* [CLI] Add `--verbose` to elixirc, which now is non-verbose by default
@@ -455,7 +610,7 @@
* [Kernel] The previous ambiguous import syntax `import :functions, Foo` was removed in favor of `import Foo, only: :functions`
* [OptionParser] `parse` and `parse_head` now returns a tuple with three elements instead of two
# v0.10.1 (2013-08-03)
## v0.10.1 (2013-08-03)
* Enhancements
* [Behaviour] Add support for `defmacrocallback/1`
@@ -505,7 +660,7 @@
* [Regex] `Regex.scan/3` now always returns a list of lists, normalizing the result, instead of list with mixed lists and binaries
* [System] `System.halt/2` was removed since the current Erlang implementation of such function is bugged
# v0.10.0 (2013-07-15)
## v0.10.0 (2013-07-15)
* Enhancements
* [ExUnit] Support `trace: true` option which gives detailed reporting on test runs
@@ -549,7 +704,7 @@
* [Record] First element of a record via `defrecordp` is now the `defrecordp` name and no longer the current atom
* [URI] Remove custom URI parsers in favor of `URI.default_port/2`
# v0.9.3 (2013-06-23)
## v0.9.3 (2013-06-23)
* Enhancements
* [File] Add `File.chgrp`, `File.chmod` and `File.chown`
@@ -587,7 +742,7 @@
* [Macro] `Macro.expand/2` now expands until final form. Although this is backwards incompatible, it is very likely you do not need to change your code, since expansion until its final form is recommended, particularly if you are expecting an atom out of it
* [Mix] No longer support beam files on `mix local`
# v0.9.2 (2013-06-13)
## v0.9.2 (2013-06-13)
* Enhancements
* [ExUnit] `capture_io` now captures prompt by default
@@ -612,7 +767,7 @@
* Backwards incompatible changes
* [Kernel] The `=~` operator now returns true or false instead of an index
# v0.9.1 (2013-05-30)
## v0.9.1 (2013-05-30)
* Enhancements
* [IEx] Limit the number of entries kept in history and allow it to be configured
@@ -628,7 +783,7 @@
* [Record] Fix a bug where nested records cannot be defined
* [Record] Fix a bug where a record named Record cannot be defined
# v0.9.0 (2013-05-23)
## v0.9.0 (2013-05-23)
* Enhancements
* [ExUnit] `ExUnit.CaptureIO` now accepts an input to be used during capture
@@ -641,7 +796,7 @@
* [Kernel] Erlang R15 is no longer supported
* [Kernel] Elixir modules are now represented as `Elixir.ModuleName` (using `.` instead of `-` as separator)
# v0.8.3 (2013-05-22)
## v0.8.3 (2013-05-22)
* Enhancements
* [CLI] Flags `-p` and `-pr` fails if pattern match no files
@@ -693,7 +848,7 @@
* [Kernel] Precedence of `|>` has changed to lower to support constructs like `1..5 |> Enum.to_list`
* [Mix] `mix escriptize` now receives arguments as binaries
# v0.8.2 (2013-04-20)
## v0.8.2 (2013-04-20)
* Enhancements
* [ExUnit] Use ANSI escape codes in CLI output
@@ -733,7 +888,7 @@
unquote(Macro.escape(x))
end
# v0.8.1 (2013-02-17)
## v0.8.1 (2013-02-17)
* Enhancements
* [ExUnit] Tests can now receive metadata set on setup/teardown callbacks
@@ -760,7 +915,7 @@
* [Kernel] Old `:local.(args)` syntax is deprecated
* [Process] `Process.self` is deprecated in favor `Kernel.self`
# v0.8.0 (2013-01-28)
## v0.8.0 (2013-01-28)
* Enhancements
* [Binary] Support `<< "string" :: utf8 >>` as in Erlang
@@ -802,7 +957,7 @@
* [Mix] `mix iex` is no longer functional, please use `iex -S mix`
* [OptionParser] `:flags` option was deprecated in favor of `:switches` to support many types
# v0.7.2 (2012-12-04)
## v0.7.2 (2012-12-04)
* Enhancements
* [CLI] `--debug-info` is now true by default
@@ -828,7 +983,7 @@
* [Record] Default-based generated functions are deprecated
* [Typespec] Enhancements and deprecations to the `@spec/@callback` and the fun type syntax
# v0.7.1 (2012-11-18)
## v0.7.1 (2012-11-18)
* Enhancements
* [IEx] Only show documented functions and also show docs for default generated functions
@@ -859,7 +1014,7 @@
* [Enum] `Enum.times` is deprecated in favor of using ranges
* [System] `halt` moved to `System` module
# v0.7.0 (2012-10-20)
## v0.7.0 (2012-10-20)
* Enhancements
* [Behaviour] Add Behaviour with a simple callback DSL to define callbacks
@@ -908,7 +1063,7 @@
* [Kernel] `Erlang.` syntax is deprecated in favor of simply using atoms
* [Module] `Module.read_attribute` and `Module.add_attribute` deprecated in favor of `Module.get_attribute` and `Module.put_attribute` which mimics Dict API
# v0.6.0 (2012-08-01)
## v0.6.0 (2012-08-01)
* Backwards incompatible changes
* [Kernel] Compile files now follow `Elixir-ModuleName` convention to solve issues with Erlang embedded mode. This removes the `__MAIN__` pseudo-variable as modules are now located inside `Elixir` namespace
@@ -958,6 +1113,6 @@
* [Regex] Back references are now properly supported
* [System] Add `System.find_executable`
# v0.5.0 (2012-05-24)
## v0.5.0 (2012-05-24)
* First official release
+21 -1
View File
@@ -71,7 +71,7 @@ Please provide as much detail and context as possible.
## Contributing
We incentivate everyone to contribute to Elixir and help us tackle
We incentivize everyone to contribute to Elixir and help us tackle
existing issues! To do so, there are a few things you need to know
about the code. First, Elixir code is divided in applications inside
the `lib` folder:
@@ -107,6 +107,26 @@ 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:
+25 -16
View File
@@ -32,7 +32,7 @@ $(1): lib/$(1)/ebin/Elixir.$(2).beam lib/$(1)/ebin/$(1).app
lib/$(1)/ebin/$(1).app: lib/$(1)/mix.exs
$(Q) mkdir -p lib/$(1)/_build/shared/lib/$(1)
$(Q) cp -R lib/$(1)/ebin lib/$(1)/_build/shared/lib/$(1)/
$(Q) cd lib/$(1) && ../../bin/elixir -e "Mix.Sup.start_link()" -r mix.exs -e "Mix.Task.run('compile.app')"
$(Q) cd lib/$(1) && ../../bin/elixir -e "Mix.start(:permanent, [])" -r mix.exs -e "Mix.Task.run('compile.app')"
$(Q) cp lib/$(1)/_build/shared/lib/$(1)/ebin/$(1).app lib/$(1)/ebin/$(1).app
$(Q) rm -rf lib/$(1)/_build
@@ -67,10 +67,10 @@ erlang:
# Since Mix depends on EEx and EEx depends on
# Mix, we first compile EEx without the .app
# file, then mix and then compile eex fully
elixir: kernel lib/eex/ebin/Elixir.EEx.beam mix ex_unit eex iex
# file, then mix and then compile EEx fully
elixir: stdlib lib/eex/ebin/Elixir.EEx.beam mix ex_unit eex iex
kernel: $(KERNEL) VERSION
stdlib: $(KERNEL) VERSION
$(KERNEL): lib/elixir/lib/*.ex lib/elixir/lib/*/*.ex
$(Q) if [ ! -f $(KERNEL) ]; then \
echo "==> bootstrap (compile)"; \
@@ -101,7 +101,7 @@ install: compile
$(INSTALL_DATA) $$dir/ebin/* "$(DESTDIR)$(PREFIX)/$(LIBDIR)/elixir/$$dir/ebin"; \
done
$(Q) $(INSTALL_DIR) "$(DESTDIR)$(PREFIX)/$(LIBDIR)/elixir/bin"
$(Q) $(INSTALL_PROGRAM) $(filter-out %.bat, $(wildcard bin/*)) "$(DESTDIR)$(PREFIX)/$(LIBDIR)/elixir/bin"
$(Q) $(INSTALL_PROGRAM) $(filter-out %.ps1, $(filter-out %.bat, $(wildcard bin/*))) "$(DESTDIR)$(PREFIX)/$(LIBDIR)/elixir/bin"
$(Q) $(INSTALL_DIR) "$(DESTDIR)$(PREFIX)/bin"
$(Q) for file in "$(DESTDIR)$(PREFIX)"/$(LIBDIR)/elixir/bin/* ; do \
ln -sf "../$(LIBDIR)/elixir/bin/$${file##*/}" "$(DESTDIR)$(PREFIX)/bin/" ; \
@@ -124,13 +124,15 @@ 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
docs: compile ../ex_doc/bin/ex_doc
mkdir -p ebin
rm -rf docs
cp -R -f lib/*/ebin/*.beam ./ebin
bin/elixir ../ex_doc/bin/ex_doc "Elixir" "$(VERSION)" "./ebin" -m Kernel -u "https://github.com/elixir-lang/elixir" --source-ref "$(call SOURCE_REF)"
rm -rf ebin
$(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)
../ex_doc/bin/ex_doc:
@ echo "ex_doc is not found in ../ex_doc as expected. See README for more information."
@@ -149,21 +151,28 @@ release_docs: docs
test: test_erlang test_elixir
test_erlang: compile
TEST_ERL = lib/elixir/test/erlang
TEST_EBIN = lib/elixir/test/ebin
TEST_ERLS = $(addprefix $(TEST_EBIN)/, $(addsuffix .beam, $(basename $(notdir $(wildcard $(TEST_ERL)/*.erl)))))
test_erlang: compile $(TEST_ERLS)
@ echo "==> elixir (eunit)"
$(Q) mkdir -p lib/elixir/test/ebin
$(Q) $(ERLC) -pa lib/elixir/ebin -o lib/elixir/test/ebin lib/elixir/test/erlang/*.erl
$(Q) $(ERL) -pa lib/elixir/test/ebin -s test_helper test -s erlang halt;
$(Q) $(ERL) -pa $(TEST_EBIN) -s test_helper test;
@ echo ""
test_elixir: test_kernel test_ex_unit test_doc_test test_mix test_eex test_iex
$(TEST_EBIN)/%.beam: $(TEST_ERL)/%.erl
$(Q) mkdir -p $(TEST_EBIN)
$(Q) $(ERLC) -o $(TEST_EBIN) $<
test_elixir: test_stdlib test_ex_unit test_doc_test test_mix test_eex test_iex
test_doc_test: compile
@ echo "==> doctest (exunit)"
$(Q) cd lib/elixir && ../../bin/elixir -r "test/doc_test.exs";
test_kernel: compile
test_stdlib: compile
@ echo "==> elixir (exunit)"
$(Q) exec epmd & exit
$(Q) cd lib/elixir && ../../bin/elixir -r "test/elixir/test_helper.exs" -pr "test/elixir/**/*_test.exs";
.dialyzer.base_plt:
+2 -2
View File
@@ -37,7 +37,7 @@ We appreciate any contribution to Elixir, so check out our [CONTRIBUTING.md](CON
## Important links
* #elixir-lang on freenode IRC
* \#elixir-lang on freenode IRC
* [Website][1]
* [Issue tracker][2]
* [elixir-talk Mailing list (questions)][3]
@@ -54,4 +54,4 @@ We appreciate any contribution to Elixir, so check out our [CONTRIBUTING.md](CON
Elixir source code is released under Apache 2 License with some parts under Erlang's license (EPL).
Check LEGAL and LICENSE files for more information.
Check [LEGAL](LEGAL) and [LICENSE](LICENSE) files for more information.
+13 -11
View File
@@ -1,26 +1,28 @@
## Release process
# Release process
This document simply outlines the release process:
1) Remove `-dev` extension from VERSION
1. Remove `-dev` extension from VERSION
2) Ensure CHANGELOG is updated and timestamp
2. Ensure CHANGELOG is updated and timestamp
3) Commit changes above with title "Release vVERSION" and generate new tag
3. Commit changes above with title "Release vVERSION" and generate new tag
4) Run `make clean test` to ensure all tests pass from scratch and the CI is green
4. Run `make clean test` to ensure all tests pass from scratch and the CI is green
5) Push master and tags
5. Push master and tags
6) Release new docs with `make release_docs`, move docs to `docs/stable`
6. Release new docs with `make release_docs`, move docs to `docs/stable`
7) Release new zip with `make release_zip`, push new zip to GitHub Releases
7. Release new zip with `make release_zip`, push new zip to GitHub Releases
8) Merge master into stable branch and push it
8. Merge master into stable branch and push it
9) After release, bump versions, add `-dev` back and commit
9. After release, bump versions, add `-dev` back and commit
10) `make release_docs` once again and push it to `elixir-lang.github.com`
10. `make release_docs` once again and push it to `elixir-lang.org`
11. Also update `release` file in `elixir-lang.org`
## Places where version is mentioned
+1 -1
View File
@@ -1 +1 @@
0.13.2
0.14.2
+3 -7
View File
@@ -17,11 +17,10 @@ if [ $# -eq 0 ] || [ "$1" = "--help" ] || [ "$1" = "-h" ]; then
--hidden Makes a hidden node
--detached Starts the Erlang VM detached from console
--no-halt Does not halt the Erlang VM after execution
--gen-debug Turns on default debugging for all GenServers
** Options marked with (*) can be given more than once
** Options given after the .exs file or -- are passed down to the executed code
** Options can be passed to the erlang runtime using ELIXIR_ERL_OPTS or --erl" >&2
** Options can be passed to the erlang runtime using ELIXIR_ERL_OPTIONS or --erl" >&2
exit 1
fi
@@ -72,9 +71,6 @@ while [ $I -le $# ]; do
eval "VAL=\${$I}"
ERL="$ERL "$VAL""
;;
--gen-debug)
ERL="$ERL -generic_debug"
;;
*)
break
;;
@@ -84,7 +80,7 @@ done
SELF=$(readlink_f "$0")
SCRIPT_PATH=$(dirname "$SELF")
if [ "$MODE" != "iex" ]; then ERL="$ERL -s elixir start_cli"; fi
if [ "$MODE" != "iex" ]; then ERL="$ERL -noshell -s elixir start_cli"; fi
if [ -z "$ERL_PATH" ]; then
if [ -f "$SCRIPT_PATH/../releases/RELEASES" ] && [ -f "$SCRIPT_PATH/erl" ]; then
@@ -94,4 +90,4 @@ if [ -z "$ERL_PATH" ]; then
fi
fi
exec "$ERL_PATH" -pa "$SCRIPT_PATH"/../lib/*/ebin -noshell $ELIXIR_ERL_OPTS $ERL -extra "$@"
exec "$ERL_PATH" -pa "$SCRIPT_PATH"/../lib/*/ebin $ELIXIR_ERL_OPTIONS $ERL -extra "$@"
+7 -13
View File
@@ -1,12 +1,8 @@
@echo off
set argc=0
for %%A in (%*) do (
if "%%A"=="--help" goto documentation
if "%%A"=="-h" goto documentation
if "%%A"=="/h" goto documentation
set /A argc+=1
)
if %argc%==0 goto documentation
if "%1"=="" goto documentation
if "%1"=="--help" goto documentation
if "%1"=="-h" goto documentation
if "%1"=="/h" goto documentation
goto parseopts
:documentation
@@ -27,11 +23,10 @@ echo --cookie cookie Sets a cookie for this distributed node
echo --hidden Makes a hidden node
echo --detached Starts the Erlang VM detached from console
echo --no-halt Does not halt the Erlang VM after execution
echo --gen-debug Turns on default debugging for all GenServers
echo.
echo ** Options marked with (*) can be given more than once
echo ** Options given after the .exs file or -- are passed down to the executed code
echo ** Options can be passed to the erlang runtime using ELIXIR_ERL_OPTS or --erl
echo ** Options can be passed to the erlang runtime using ELIXIR_ERL_OPTIONS or --erl
goto :EOF
:parseopts
@@ -72,7 +67,6 @@ IF NOT "%par%"=="%par:--cookie=%" (Set parsErlang=%parsErlang% -setcookie %1 &
IF NOT "%par%"=="%par:--sname=%" (Set parsErlang=%parsErlang% -sname %1 && shift)
IF NOT "%par%"=="%par:--name=%" (Set parsErlang=%parsErlang% -name %1 && shift)
IF NOT "%par%"=="%par:--erl=%" (Set beforeExtra=%beforeExtra% %~1 && shift)
IF NOT "%par%"=="%par:--gen-debug=%" (Set parsErlang=%parsErlang% -generic_debug)
rem ******* elixir parameters **********************
rem Note: we don't have to do anything with options that don't take an argument
IF NOT "%par%"=="%par:-e=%" (shift)
@@ -95,7 +89,7 @@ for /d %%d in ("%originPath%..\lib\*.") do (
SETLOCAL disabledelayedexpansion
:run
IF %useWerl% EQU 1 (
werl %ext_libs% -noshell %ELIXIR_ERL_OPTS% %parsErlang% -s elixir start_cli %beforeExtra% -extra %*
werl.exe %ext_libs% %ELIXIR_ERL_OPTIONS% %parsErlang% -s elixir start_cli %beforeExtra% -extra %*
) ELSE (
erl %ext_libs% -noshell %ELIXIR_ERL_OPTS% %parsErlang% -s elixir start_cli %beforeExtra% -extra %*
erl.exe %ext_libs% -noshell %ELIXIR_ERL_OPTIONS% %parsErlang% -s elixir start_cli %beforeExtra% -extra %*
)
+2 -1
View File
@@ -10,7 +10,8 @@ if [ $# -eq 0 ] || [ "$1" = "--help" ] || [ "$1" = "-h" ]; then
--verbose Print informational messages.
** Options given after -- are passed down to the executed code
** Options can be passed to the erlang runtime using ELIXIR_ERL_OPTS" >&2
** Options can be passed to the erlang runtime using ELIXIR_ERL_OPTIONS
** Options can be passed to the erlang compiler using ERL_COMPILER_OPTIONS" >&2
exit 1
fi
+2 -2
View File
@@ -19,8 +19,8 @@ echo --ignore-module-conflict
echo --warnings-as-errors Treat warnings as errors and return non-zero exit code
echo --verbose Print informational messages.
echo.
echo ** Options marked with (*) can be given more than once
echo ** Options given after -- are passed down to the executed code
echo ** Options can be passed to the erlang runtime using ELIXIR_ERL_OPTS" >&2
echo ** Options can be passed to the erlang runtime using ELIXIR_ERL_OPTIONS
echo ** Options can be passed to the erlang compiler using ERL_COMPILER_OPTIONS >&2
:run
call "%~dp0\elixir.bat" +elixirc %*
+1 -2
View File
@@ -16,14 +16,13 @@ if [ $# -gt 0 ] && ([ "$1" = "--help" ] || [ "$1" = "-h" ]); then
--cookie \"cookie\" Sets a cookie for this distributed node
--hidden Makes a hidden node
--detached Starts the Erlang VM detached from console
--gen-debug Turns on default debugging for all GenServers
--remsh \"name\" Connects to a node using a remote shell
--dot-iex \"path\" Overrides default .iex.exs file and uses path instead;
path can be empty, then no file will be loaded
** Options marked with (*) can be given more than once
** Options given after the .exs file or -- are passed down to the executed code
** Options can be passed to the VM using ELIXIR_ERL_OPTS or --erl" >&2
** Options can be passed to the VM using ELIXIR_ERL_OPTIONS or --erl" >&2
exit 1
fi
+27
View File
@@ -0,0 +1,27 @@
# Initialize with path to mix.bat relative to caller's working directory
$toCmd = '' + (Resolve-Path -relative (Split-Path $MyInvocation.MyCommand.Path)) + '\mix.bat'
foreach ($arg in $args)
{
$toCmd += ' '
if ($arg -is [array])
{
# Commas created the array so we need to reintroduce those commas
for ($i = 0; $i -lt $arg.length; $i++)
{
$toCmd += $arg[$i]
if ($i -ne ($arg.length - 1))
{
$toCmd += ', '
}
}
}
else
{
$toCmd += $arg
}
}
# Corrected arguments are ready to pass to batch file
cmd /c $toCmd
+24 -20
View File
@@ -1,4 +1,6 @@
defexception EEx.SyntaxError, message: nil
defmodule EEx.SyntaxError do
defexception [:message]
end
defmodule EEx do
@moduledoc ~S"""
@@ -12,32 +14,31 @@ defmodule EEx do
This module provides 3 main APIs for you to use:
1) Evaluate a string (`eval_string`) or a file (`eval_file`)
directly. This is the simplest API to use but also the
slowest, since the code is evaluated and not compiled before;
1. Evaluate a string (`eval_string`) or a file (`eval_file`)
directly. This is the simplest API to use but also the
slowest, since the code is evaluated and not compiled before.
2) Define a function from a string (`function_from_string`)
or a file (`function_from_file`). This allows you to embed
the template as a function inside a module which will then
be compiled. This is the preferred API if you have access
to the template at compilation time;
2. Define a function from a string (`function_from_string`)
or a file (`function_from_file`). This allows you to embed
the template as a function inside a module which will then
be compiled. This is the preferred API if you have access
to the template at compilation time.
3) Compile a string (`compile_string`) or a file (`compile_file`)
into Elixir syntax tree. This is the API used by both functions
above and is available to you if you want to provide your own
ways of handling the compiled template.
3. Compile a string (`compile_string`) or a file (`compile_file`)
into Elixir syntax tree. This is the API used by both functions
above and is available to you if you want to provide your own
ways of handling the compiled template.
## Options
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;
* `:engine` - the EEx engine to be used for compilation.
* `: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
@@ -81,7 +82,7 @@ defmodule EEx do
iex> EEx.eval_string "<%= @foo %>", assigns: [foo: 1]
"1"
In other words, <%= @foo %> is simply translated to:
In other words, `<%= @foo %>` is simply translated to:
<%= Dict.get assigns, :foo %>
@@ -91,6 +92,7 @@ defmodule EEx do
@doc """
Generates a function definition from the string.
The kind (`:def` or `:defp`) must be given, the
function name, its arguments and the compilation options.
@@ -119,6 +121,7 @@ defmodule EEx do
@doc """
Generates a function definition from the file contents.
The kind (`:def` or `:defp`) must be given, the
function name, its arguments and the compilation options.
@@ -146,6 +149,7 @@ defmodule EEx do
args = Enum.map args, fn arg -> {arg, [line: 1], nil} end
compiled = EEx.compile_file(file, info)
@external_resource file
@file file
case kind do
:def -> def(unquote(name)(unquote_splicing(args)), do: unquote(compiled))
+3 -3
View File
@@ -1,7 +1,7 @@
defmodule EEx.Compiler do
@moduledoc false
# when changing this setting, don't forget to update the docs for EEx
# When changing this setting, don't forget to update the docs for EEx
@default_engine EEx.SmartEngine
@doc """
@@ -21,7 +21,7 @@ defmodule EEx.Compiler do
# Generates the buffers by handling each expression from the tokenizer
defp generate_buffer([{:text, chars}|t], buffer, scope, state) do
buffer = state.engine.handle_text(buffer, String.from_char_data!(chars))
buffer = state.engine.handle_text(buffer, IO.chardata_to_string(chars))
generate_buffer(t, buffer, scope, state)
end
@@ -68,7 +68,7 @@ defmodule EEx.Compiler do
defp wrap_expr(current, line, buffer, chars, state) do
new_lines = List.duplicate(?\n, line - state.line)
key = length(state.quoted)
placeholder = '__EEX__(' ++ integer_to_list(key) ++ ');'
placeholder = '__EEX__(' ++ Integer.to_char_list(key) ++ ');'
{current ++ placeholder ++ new_lines ++ chars,
%{state | quoted: [{key, buffer}|state.quoted]}}
end
+57 -15
View File
@@ -1,27 +1,28 @@
defmodule EEx.Engine do
@moduledoc ~S"""
This is the basic EEx engine that ships with Elixir.
Basic EEx engine that ships with Elixir.
An engine needs to implement three functions:
* `handle_body(quoted)` - receives the final built quoted
expression, should do final post-processing and return a
quoted expression;
* `handle_body(quoted)` - receives the final built quoted
expression, should do final post-processing and return a
quoted expression.
* `handle_text(buffer, text)` - it receives the buffer,
the text and must return a new quoted expression;
* `handle_text(buffer, text)` - it receives the buffer,
the text and must return a new quoted expression.
* `handle_expr(buffer, marker, expr)` - it receives the buffer,
the marker, the expr and must return a new quoted expression;
* `handle_expr(buffer, marker, expr)` - it receives the buffer,
the marker, the expr and must return a new quoted expression.
The marker is what follows exactly after `<%`. For example,
`<% foo %>` has an empty marker, but `<%= foo %>` has `"="`
as marker. The allowed markers so far are:
The marker is what follows exactly after `<%`. For example,
`<% foo %>` has an empty marker, but `<%= foo %>` has `"="`
as marker. The allowed markers so far are: `""` and `"="`.
* `""`
* `"="`
Read `handle_expr/3` below for more information about the markers
implemented by default by this engine.
Read `handle_expr/3` below for more information about the markers
implemented by default by this engine.
`EEx.Engine` can be used directly if one desires to use the
default implementations for the functions above.
"""
use Behaviour
@@ -30,6 +31,47 @@ defmodule EEx.Engine do
defcallback handle_text(Macro.t, binary) :: Macro.t
defcallback handle_expr(Macro.t, binary, Macro.t) :: Macro.t
@doc false
defmacro __using__(_) do
quote do
@behaviour EEx.Engine
def handle_body(body) do
EEx.Engine.handle_body(body)
end
def handle_text(buffer, text) do
EEx.Engine.handle_text(buffer, text)
end
def handle_expr(buffer, mark, expr) do
EEx.Engine.handle_expr(buffer, mark, expr)
end
defoverridable [handle_body: 1, handle_expr: 3, handle_text: 2]
end
end
@doc """
Handles assigns in quoted expressions.
This can be added to any custom engine by invoking
`handle_assign/3` with `Macro.prewalk/1`:
def handle_expr(buffer, token, expr) do
expr = Macro.prewalk(expr, &EEx.Engine.handle_assign/1)
EEx.Engine.handle_expr(buffer, token, expr)
end
"""
def handle_assign({:@, line, [{name, _, atom}]}) when is_atom(name) and is_atom(atom) do
quote line: line, do: Dict.get(var!(assigns), unquote(name))
end
def handle_assign(arg) do
arg
end
@doc """
The default implementation implementation simply returns the
given expression.
+28 -43
View File
@@ -1,14 +1,5 @@
defmodule EEx.TransformerEngine do
@moduledoc """
An abstract engine that is meant to be used and
built upon in other modules. This engine implements
the `EEx.Engine` behaviour and provides a `transform`
overridable directive that allows a developer to
customize the expression returned by the engine.
Check `EEx.AssignsEngine` and `EEx.SmartEngine` for
examples of using this module.
"""
@moduledoc false
@doc false
defmacro __using__(_) do
@@ -49,20 +40,33 @@ defmodule EEx.TransformerEngine do
end
defmodule EEx.AssignsEngine do
@moduledoc """
An abstract engine that, when used with the
`TransformerEngine`, allows a developer to access
assigns using `@` as syntax.
@moduledoc false
This engine is included by default on the SmartEngine.
@doc false
defmacro __using__(_) do
quote unquote: false do
defp transform({:@, line, [{name, _, atom}]}) when is_atom(name) and is_atom(atom) do
quote do: Dict.get(var!(assigns), unquote(name))
end
defp transform(arg) do
super(arg)
end
defoverridable [transform: 1]
end
end
end
defmodule EEx.SmartEngine do
@moduledoc """
The default engine used by EEx.
It includes assigns (like `@foo`) and possibly other
conveniences in the future.
## Examples
defmodule MyEngine do
use EEx.TransformerEngine
use EEx.AssignsEngine
end
iex> EEx.eval_string("<%= @foo %>", assigns: [foo: 1])
"1"
@@ -88,29 +92,10 @@ defmodule EEx.AssignsEngine do
"""
@doc false
defmacro __using__(_) do
quote unquote: false do
defp transform({:@, line, [{name, _, atom}]}) when is_atom(name) and is_atom(atom) do
quote do: Dict.get(var!(assigns), unquote(name))
end
use EEx.Engine
defp transform(arg) do
super(arg)
end
defoverridable [transform: 1]
end
def handle_expr(buffer, mark, expr) do
expr = Macro.prewalk(expr, &EEx.Engine.handle_assign/1)
super(buffer, mark, expr)
end
end
defmodule EEx.SmartEngine do
use EEx.TransformerEngine
use EEx.AssignsEngine
@moduledoc """
An engine meant for end-user usage that includes
`EEx.AssignsEngine` and other conveniences. Read
`EEx.AssignsEngine` for examples.
"""
end
+6 -6
View File
@@ -5,15 +5,15 @@ defmodule EEx.Tokenizer do
Tokenizes the given char list or binary.
It returns 4 different types of tokens as result:
* {:text, contents}
* {:expr, line, marker, contents}
* {:start_expr, line, marker, contents}
* {:middle_expr, line, marker, contents}
* {:end_expr, line, marker, contents}
* `{:text, contents}`
* `{:expr, line, marker, contents}`
* `{:start_expr, line, marker, contents}`
* `{:middle_expr, line, marker, contents}`
* `{:end_expr, line, marker, contents}`
"""
def tokenize(bin, line) when is_binary(bin) do
tokenize(List.from_char_data!(bin), line)
tokenize(String.to_char_list(bin), line)
end
def tokenize(list, line) do
+24 -14
View File
@@ -2,16 +2,20 @@ Code.require_file "test_helper.exs", __DIR__
require EEx
defmodule EExText.Compiled do
defmodule EExTest.Compiled do
def before_compile do
fill_in_stacktrace
{__ENV__.line, hd(tl(System.stacktrace))}
end
{:erlang, 1, 2}.tuple_to_list
EEx.function_from_string :def, :string_sample, "<%= a + b %>", [:a, :b]
filename = Path.join(__DIR__, "fixtures/eex_template_with_bindings.eex")
EEx.function_from_file :defp, :private_file_sample, filename, [:bar]
filename = Path.join(__DIR__, "fixtures/eex_template_with_bindings.eex")
EEx.function_from_file :def, :public_file_sample, filename, [:bar]
def file_sample(arg), do: private_file_sample(arg)
def after_compile do
@@ -319,39 +323,45 @@ foo
end
end
test "sets external resource attribute" do
assert EExTest.Compiled.__info__(:attributes)[:external_resource] ==
[Path.join(__DIR__, "fixtures/eex_template_with_bindings.eex")]
end
test "defined from string" do
assert EExText.Compiled.string_sample(1, 2) == "3"
assert EExTest.Compiled.string_sample(1, 2) == "3"
end
test "defined from file" do
assert EExText.Compiled.file_sample(1) == "foo 1\n"
assert EExTest.Compiled.file_sample(1) == "foo 1\n"
assert EExTest.Compiled.public_file_sample(1) == "foo 1\n"
end
test "defined from file do not affect backtrace" do
assert EExText.Compiled.before_compile ==
assert EExTest.Compiled.before_compile ==
{8,
{EExText.Compiled,
{EExTest.Compiled,
:before_compile,
0,
[file: to_char_list(Path.relative_to_cwd(__ENV__.file)), line: 7]
}
}
assert EExText.Compiled.after_compile ==
{19,
{EExText.Compiled,
assert EExTest.Compiled.after_compile ==
{23,
{EExTest.Compiled,
:after_compile,
0,
[file: to_char_list(Path.relative_to_cwd(__ENV__.file)), line: 18]
[file: to_char_list(Path.relative_to_cwd(__ENV__.file)), line: 22]
}
}
assert EExText.Compiled.unknown ==
{25,
{EExText.Compiled,
assert EExTest.Compiled.unknown ==
{29,
{EExTest.Compiled,
:unknown,
0,
[file: 'unknown', line: 24]
[file: 'unknown', line: 28]
}
}
end
+102 -20
View File
@@ -1,13 +1,14 @@
import Kernel, except: [access: 2]
defprotocol Access do
@moduledoc """
The Access protocol is the underlying protocol invoked
when the brackets syntax is used. For instance, `foo[bar]`
is translated to `access foo, bar` which, by default,
invokes the `Access.access` protocol.
The Access protocol is used by `foo[bar]` and also
empowers the nested update functions in Kernel.
This protocol is implemented by default for Lists, Maps
For instance, `foo[bar]` translates `Access.get(foo, bar)`.
`Kernel.get_in/2`, `Kernel.put_in/3`, `Kernel.update_in/3` and
`Kernel.get_and_update_in/3` are also all powered by the Access
protocol.
This protocol is implemented by default for keywords, maps
and dictionary like types:
iex> keywords = [a: 1, b: 2]
@@ -22,46 +23,127 @@ defprotocol Access do
iex> star_ratings[1.5]
"★☆"
The key access must be implemented using the `===` operator.
This protocol is limited and is implemented only for the
following built-in types: keywords, records and functions.
The key comparison must be implemented using the `===` operator.
"""
@doc """
Receives the element being accessed and the access item.
Accesses the given key in the container.
"""
def access(container, key)
@spec get(t, term) :: t
def get(container, key)
@doc """
Gets a value and updates the given `key` in one pass.
The function must receive the value for the given `key`
(or `nil` if the key doesn't exist in `container`) and
the function must return a tuple containing the `get`
value and the new value to be stored in the `container`.
"""
@spec get_and_update(t, term, (term -> {get, term})) :: {get, t} when get: var
def get_and_update(container, key, fun)
end
defimpl Access, for: List do
def access(dict, key) when is_atom(key) do
def get(dict, key) when is_atom(key) do
case :lists.keyfind(key, 1, dict) do
{^key, value} -> value
false -> nil
end
end
def access(_dict, key) do
raise ArgumentError, message: "the access protocol for lists expect the key to be an atom, got: #{inspect key}"
def get(_dict, key) do
raise ArgumentError,
"the access protocol for lists expect the key to be an atom, got: #{inspect key}"
end
def get_and_update(dict, key, fun) when is_atom(key) do
get_and_update(dict, [], key, fun)
end
defp get_and_update([{key, value}|t], acc, key, fun) do
{get, update} = fun.(value)
{get, :lists.reverse(acc, [{key, update}|t])}
end
defp get_and_update([h|t], acc, key, fun) do
get_and_update(t, [h|acc], key, fun)
end
defp get_and_update([], acc, key, fun) do
{get, update} = fun.(nil)
{get, [{key, update}|:lists.reverse(acc)]}
end
end
defimpl Access, for: Map do
def access(map, key) do
def get(map, key) do
case :maps.find(key, map) do
{:ok, value} -> value
:error -> nil
end
end
def get_and_update(map, key, fun) do
value =
case :maps.find(key, map) do
{:ok, value} -> value
:error -> nil
end
{get, update} = fun.(value)
{get, :maps.put(key, update, map)}
end
def get!(%{} = map, key) do
case :maps.find(key, map) do
{:ok, value} -> value
:error -> raise KeyError, key: key, term: map
end
end
def get!(other, key) do
raise ArgumentError,
"could not get key #{inspect key}. Expected map/struct, got: #{inspect other}"
end
def get_and_update!(%{} = map, key, fun) do
case :maps.find(key, map) do
{:ok, value} ->
{get, update} = fun.(value)
{get, :maps.put(key, update, map)}
:error ->
raise KeyError, key: key, term: map
end
end
def get_and_update!(other, key, _fun) do
raise ArgumentError,
"could not update key #{inspect key}. Expected map/struct, got: #{inspect other}"
end
end
defimpl Access, for: Atom do
def access(nil, _) do
def get(nil, _) do
nil
end
def access(atom, _) do
raise "The access protocol can only be invoked for atoms at " <>
"compilation time, tried to invoke it for #{inspect atom}"
def get(atom, _) do
undefined(atom)
end
def get_and_update(nil, _, fun) do
fun.(nil)
end
def get_and_update(atom, _key, _fun) do
undefined(atom)
end
defp undefined(atom) do
raise Protocol.UndefinedError,
protocol: @protocol,
value: atom,
description: "only the nil atom is supported"
end
end
+228
View File
@@ -0,0 +1,228 @@
defmodule Agent do
@moduledoc """
Agents are a simple abstraction around state.
Often in Elixir there is a need to share or store state that
must be accessed from different processes or by the same process
at different points in time.
The Agent module provides a basic server implementation that
allows state to be retrieved and updated via a simple API.
## Examples
For example, in the Mix tool that ships with Elixir, we need
to keep a set of all tasks executed by a given project. Since
this set is shared, we can implement it with an Agent:
defmodule Mix.TasksServer do
def start_link do
Agent.start_link(fn -> HashSet.new end, name: __MODULE__)
end
@doc "Checks if the task has already executed"
def executed?(task, project) do
item = {task, project}
Agent.get(__MODULE__, fn set ->
item in set
end)
end
@doc "Marks a task as executed"
def put_task(task, project) do
item = {task, project}
Agent.update(__MODULE__, &Set.put(&1, item))
end
end
Note that agents still provide a segregation between the
client and server APIs, as seen in GenServers. In particular,
all code inside the function passed to the agent is executed
by the agent. This distinction is important because you may
want to avoid expensive operations inside the agent, as it will
effectively block the agent until the request is fulfilled.
Consider these two examples:
# Compute in the agent/server
def get_something(agent) do
Agent.get(agent, fn state -> do_something_expensive(state) end)
end
# Compute in the agent/client
def get_something(agent) do
Agent.get(agent, &(&1)) |> do_something_expensive()
end
The first one blocks the agent while the second one copies
all the state to the client and executes the operation in the client.
The trade-off here is exactly if the data is small enough to be
sent to the client cheaply or large enough to require processing on
the server (or at least some initial processing).
## Name Registration
An Agent is bound to the same name registration rules as GenServers.
Read more about it in the `GenServer` docs.
## A word on distributed agents
It is important to consider the limitations of distributed agents. Agents
work by sending anonymous functions between the caller and the agent.
In a distributed setup with multiple nodes, agents only work if the caller
(client) and the agent have the same version of a given module.
This setup may exhibit issues when doing "rolling upgrades". By rolling
upgrades we mean the following situation: you wish to deploy a new version of
your software by *shutting down* some of your nodes and replacing them with
nodes running a new version of the software. In this setup, part of your
environment will have one version of a given module and the other part
another version (the newer one) of the same module; this may cause agents to
crash. That said, if you plan to run in distributed environments, agents
should likely be avoided.
Note, however, that agents work fine if you want to perform hot code
swapping, as it keeps both the old and new versions of a given module.
We detail how to do hot code swapping with agents in the next section.
## Hot code swapping
An agent can have its code hot swapped live by simply passing a module,
function and args tuple to the update instruction. For example, imagine
you have an agent named `:sample` and you want to convert its inner state
from some dict structure to a map. It can be done with the following
instruction:
{:update, :sample, {:advanced, {Enum, :into, [%{}]}}}
The agent's state will be added to the given list as the first argument.
"""
@typedoc "Return values of `start*` functions"
@type on_start :: {:ok, pid} | {:error, {:already_started, pid} | term}
@typedoc "The agent name"
@type name :: atom | {:global, term} | {:via, module, term}
@typedoc "The agent reference"
@type agent :: pid | {atom, node} | name
@typedoc "The agent state"
@type state :: term
@doc """
Starts an agent linked to the current process.
This is often used to start the agent as part of a supervision tree.
Once the agent is spawned, the given function is invoked and its return
value is used as the agent state. Note that `start_link` does not return
until the given function has returned.
## Options
The `:name` option is used for registration as described in the module
documentation.
If the `:timeout` option is present, the agent is allowed to spend at most
the given number of milliseconds on initialization or it will be terminated
and the start function will return `{:error, :timeout}`.
If the `:debug` option is present, the corresponding function in the
[`:sys` module](http://www.erlang.org/doc/man/sys.html) will be invoked.
If the `:spawn_opt` option is present, its value will be passed as options
to the underlying process as in `Process.spawn/4`.
## Return values
If the server is successfully created and initialized, the function returns
`{:ok, pid}`, where `pid` is the pid of the server. If there already exists
an agent with the specified name, the function returns
`{:error, {:already_started, pid}}` with the pid of that process.
If the given function callback fails with `reason`, the function returns
`{:error, reason}`.
"""
@spec start_link((() -> term), GenServer.options) :: on_start
def start_link(fun, options \\ []) when is_function(fun, 0) do
GenServer.start_link(Agent.Server, fun, options)
end
@doc """
Starts an agent process without links (outside of a supervision tree).
See `start_link/2` for more information.
"""
@spec start((() -> term), GenServer.options) :: on_start
def start(fun, options \\ []) when is_function(fun, 0) do
GenServer.start(Agent.Server, fun, options)
end
@doc """
Gets the agent value and executes the given function.
The function `fun` is sent to the `agent` which invokes the function
passing the agent state. The result of the function invocation is
returned.
A timeout can also be specified (it has a default value of 5000).
"""
@spec get(agent, (state -> a), timeout) :: a when a: var
def get(agent, fun, timeout \\ 5000) when is_function(fun, 1) do
GenServer.call(agent, {:get, fun}, timeout)
end
@doc """
Gets and updates the agent state in one operation.
The function `fun` is sent to the `agent` which invokes the function
passing the agent state. The function must return a tuple with two
elements, the first being the value to return (i.e. the `get` value)
and the second one is the new state.
A timeout can also be specified (it has a default value of 5000).
"""
@spec get_and_update(agent, (state -> {a, state}), timeout) :: a when a: var
def get_and_update(agent, fun, timeout \\ 5000) when is_function(fun, 1) do
GenServer.call(agent, {:get_and_update, fun}, timeout)
end
@doc """
Updates the agent state.
The function `fun` is sent to the `agent` which invokes the function
passing the agent state. The function must return the new state.
A timeout can also be specified (it has a default value of 5000).
This function always returns `:ok`.
"""
@spec update(agent, (state -> state)) :: :ok
def update(agent, fun, timeout \\ 5000) when is_function(fun, 1) do
GenServer.call(agent, {:update, fun}, timeout)
end
@doc """
Performs a cast (fire and forget) operation on the agent state.
The function `fun` is sent to the `agent` which invokes the function
passing the agent state. The function must return the new state.
Note that `cast` returns `:ok` immediately, regardless of whether the
destination node or agent exists.
"""
@spec cast(agent, (state -> state)) :: :ok
def cast(agent, fun) when is_function(fun, 1) do
GenServer.cast(agent, fun)
end
@doc """
Stops the agent.
Returns `:ok` if the agent is stopped within the given `timeout`.
"""
@spec stop(agent, timeout) :: :ok
def stop(agent, timeout \\ 5000) do
GenServer.call(agent, :stop, timeout)
end
end
+53
View File
@@ -0,0 +1,53 @@
defmodule Agent.Server do
@moduledoc false
use GenServer
def init(fun) do
{:ok, fun.()}
end
def handle_call({:get, fun}, _from, state) do
{:reply, fun.(state), state}
end
def handle_call({:get_and_update, fun}, _from, state) do
{reply, state} = fun.(state)
{:reply, reply, state}
end
def handle_call({:update, fun}, _from, state) do
{:reply, :ok, fun.(state)}
end
def handle_call(:stop, _from, state) do
{:stop, :normal, :ok, state}
end
def handle_call(msg, from, state) do
super(msg, from, state)
end
def handle_cast(fun, state) when is_function(fun, 1) do
{:noreply, fun.(state)}
end
def handle_cast(msg, state) do
super(msg, state)
end
def code_change(_old, state, { m, f, a }) do
{:ok, apply(m, f, [state|a])}
end
def terminate(_reason, _state) do
# There is a race condition if the agent is
# restarted too fast and it is registered.
try do
self |> :erlang.process_info(:registered_name) |> elem(1) |> Process.unregister
rescue
_ -> :ok
end
:ok
end
end
+19 -17
View File
@@ -13,7 +13,7 @@ defmodule Application do
In Elixir, Mix is responsible for compiling your source code and
generating your application `.app` file. Furthermore, Mix is also
responsible for configuring, starting and stoping your application
responsible for configuring, starting and stopping your application
and its dependencies. For this reason, this documentation will focus
on the remaining aspects of your application: the application environment,
and the application callback module.
@@ -48,7 +48,7 @@ defmodule Application do
developers to configure the environment of their dependencies.
Keep in mind that each application is responsible for its environment.
Do not use the fucntions in this module for directly access or modify
Do not use the functions in this module for directly access or modify
the environment of other application (as it may lead to inconsistent
data in the application environment).
@@ -83,8 +83,8 @@ defmodule Application do
setup where applications takeover and failovers are configured. This particular
aspect of applications can be read with more detail in the OTP documentation:
* http://www.erlang.org/doc/man/application.html
* http://www.erlang.org/doc/design_principles/applications.html
* http://www.erlang.org/doc/man/application.html
* http://www.erlang.org/doc/design_principles/applications.html
A developer may also implement the `stop/1` callback (automatically defined
by `use Application`) which does any application cleanup. It receives the
@@ -152,9 +152,8 @@ defmodule Application do
## Options
* `:timeout` - the timeout for the change (defaults to 5000ms);
* `:persistent` - persists the given value on application load and reloads;
* `:timeout` - the timeout for the change (defaults to 5000ms)
* `:persistent` - persists the given value on application load and reloads
If `put_env/4` is called before the application is loaded, the application
environment values specified in the `.app` file will override the ones
@@ -223,13 +222,16 @@ defmodule Application do
The `type` argument specifies the type of the application:
* `:permanent` - if `app` terminates, all other applications and the entire
node are also terminated;
* `:transient` - if `app` terminates with `:normal` reason, it is reported
but no other applications are terminated. If a transient application terminates
abnormally, all other applications and the entire node are also terminated;
* `:temporary` - if `app` termiantes, it is reported but no other applications
are terminated (the default);
* `:permanent` - if `app` terminates, all other applications and the entire
node are also terminated.
* `:transient` - if `app` terminates with `:normal` reason, it is reported
but no other applications are terminated. If a transient application
terminates abnormally, all other applications and the entire node are
also terminated.
* `:temporary` - if `app` terminates, it is reported but no other
applications are terminated (the default).
Note that it is always possible to stop an application explicitly by calling
`stop/1`. Regardless of the type of the application, no other applications will
@@ -246,7 +248,7 @@ defmodule Application do
@doc """
Stops the given `app`.
When stopped, the application is still loaded.
When stopped, the application is still loaded.
"""
@spec stop(app) :: :ok | {:error, term}
def stop(app) do
@@ -305,8 +307,8 @@ defmodule Application do
@spec app_dir(app) :: String.t
def app_dir(app) when is_atom(app) do
case :code.lib_dir(app) do
lib when is_list(lib) -> String.from_char_data!(lib)
{:error, :bad_name} -> raise ArgumentError, message: "unknown application: #{inspect app}"
lib when is_list(lib) -> IO.chardata_to_string(lib)
{:error, :bad_name} -> raise ArgumentError, "unknown application: #{inspect app}"
end
end
-17
View File
@@ -1,17 +0,0 @@
defmodule Application.Behaviour do
@moduledoc false
defmacro __using__(_) do
# IO.write :stderr, "use Application.Behaviour is deprecated, please use Application instead\n#{Exception.format_stacktrace}"
quote location: :keep do
@behaviour :application
@doc false
def stop(_state) do
:ok
end
defoverridable [stop: 1]
end
end
end
+25
View File
@@ -0,0 +1,25 @@
defmodule Atom do
@doc """
Convenience functions for working with atoms.
"""
@doc """
Converts an atom to string.
Inlined by the compiler.
"""
@spec to_string(atom) :: String.t
def to_string(atom) do
:erlang.atom_to_binary(atom, :utf8)
end
@doc """
Converts an atom to a char list.
Inlined by the compiler.
"""
@spec to_char_list(atom) :: char_list
def to_char_list(atom) do
:erlang.atom_to_list(atom)
end
end
+230 -102
View File
@@ -5,8 +5,91 @@ defmodule Base do
This module provides data encoding and decoding functions
according to [RFC 4648](http://tools.ietf.org/html/rfc4648).
This document defines the commonly used base 64, base 32, and base
16 encoding schemes.
This document defines the commonly used base 16, base 32, and base
64 encoding schemes.
## Base 16 alphabet
| Value | Encoding | Value | Encoding | Value | Encoding | Value | Encoding |
|------:|---------:|------:|---------:|------:|---------:|------:|---------:|
| 0| 0| 4| 4| 8| 8| 12| C|
| 1| 1| 5| 5| 9| 9| 13| D|
| 2| 2| 6| 6| 10| A| 14| E|
| 3| 3| 7| 7| 11| B| 15| F|
## Base 32 alphabet
| Value | Encoding | Value | Encoding | Value | Encoding | Value | Encoding |
|------:|---------:|------:|---------:|------:|---------:|------:|---------:|
| 0| A| 9| J| 18| S| 27| 3|
| 1| B| 10| K| 19| T| 28| 4|
| 2| C| 11| L| 20| U| 29| 5|
| 3| D| 12| M| 21| V| 30| 6|
| 4| E| 13| N| 22| W| 31| 7|
| 5| F| 14| O| 23| X| | |
| 6| G| 15| P| 24| Y| (pad)| =|
| 7| H| 16| Q| 25| Z| | |
| 8| I| 17| R| 26| 2| | |
## Base 32 (extended hex) alphabet
| Value | Encoding | Value | Encoding | Value | Encoding | Value | Encoding |
|------:|---------:|------:|---------:|------:|---------:|------:|---------:|
| 0| 0| 9| 9| 18| I| 27| R|
| 1| 1| 10| A| 19| J| 28| S|
| 2| 2| 11| B| 20| K| 29| T|
| 3| 3| 12| C| 21| L| 30| U|
| 4| 4| 13| D| 22| M| 31| V|
| 5| 5| 14| E| 23| N| | |
| 6| 6| 15| F| 24| O| (pad)| =|
| 7| 7| 16| G| 25| P| | |
| 8| 8| 17| H| 26| Q| | |
## Base 64 alphabet
| Value | Encoding | Value | Encoding | Value | Encoding | Value | Encoding |
|------:|---------:|------:|---------:|------:|---------:|------:|---------:|
| 0| A| 17| R| 34| i| 51| z|
| 1| B| 18| S| 35| j| 52| 0|
| 2| C| 19| T| 36| k| 53| 1|
| 3| D| 20| U| 37| l| 54| 2|
| 4| E| 21| V| 38| m| 55| 3|
| 5| F| 22| W| 39| n| 56| 4|
| 6| G| 23| X| 40| o| 57| 5|
| 7| H| 24| Y| 41| p| 58| 6|
| 8| I| 25| Z| 42| q| 59| 7|
| 9| J| 26| a| 43| r| 60| 8|
| 10| K| 27| b| 44| s| 61| 9|
| 11| L| 28| c| 45| t| 62| +|
| 12| M| 29| d| 46| u| 63| /|
| 13| N| 30| e| 47| v| | |
| 14| O| 31| f| 48| w| (pad)| =|
| 15| P| 32| g| 49| x| | |
| 16| Q| 33| h| 50| y| | |
## Base 64 (URL and filename safe) alphabet
| Value | Encoding | Value | Encoding | Value | Encoding | Value | Encoding |
|------:|---------:|------:|---------:|------:|---------:|------:|---------:|
| 0| A| 17| R| 34| i| 51| z|
| 1| B| 18| S| 35| j| 52| 0|
| 2| C| 19| T| 36| k| 53| 1|
| 3| D| 20| U| 37| l| 54| 2|
| 4| E| 21| V| 38| m| 55| 3|
| 5| F| 22| W| 39| n| 56| 4|
| 6| G| 23| X| 40| o| 57| 5|
| 7| H| 24| Y| 41| p| 58| 6|
| 8| I| 25| Z| 42| q| 59| 7|
| 9| J| 26| a| 43| r| 60| 8|
| 10| K| 27| b| 44| s| 61| 9|
| 11| L| 28| c| 45| t| 62| -|
| 12| M| 29| d| 46| u| 63| _|
| 13| N| 30| e| 47| v| | |
| 14| O| 31| f| 48| w| (pad)| =|
| 15| P| 32| g| 49| x| | |
| 16| Q| 33| h| 50| y| | |
"""
b16_alphabet = Enum.with_index '0123456789ABCDEF'
@@ -25,45 +108,86 @@ defmodule Base do
defp unquote(dec)(unquote(encoding)), do: unquote(value)
end
defp unquote(dec)(c) do
raise ArgumentError, message: "non-alphabet digit found: #{<<c>>}"
raise ArgumentError, "non-alphabet digit found: #{<<c>>}"
end
end
defp encode_case(:upper, func),
do: func
defp encode_case(:lower, func),
do: &to_lower(func.(&1))
defp decode_case(:upper, func),
do: func
defp decode_case(:lower, func),
do: &func.(from_lower(&1))
defp decode_case(:mixed, func),
do: &func.(from_mixed(&1))
defp to_lower(char) when char in ?A..?Z,
do: char + (?a - ?A)
defp to_lower(char),
do: char
defp from_lower(char) when char in ?a..?z,
do: char - (?a - ?A)
defp from_lower(char) when not char in ?A..?Z,
do: char
defp from_lower(char),
do: raise(ArgumentError, "non-alphabet digit found: #{<<char>>}")
defp from_mixed(char) when char in ?a..?z,
do: char - (?a - ?A)
defp from_mixed(char),
do: char
@doc """
Encodes a binary string into a base 16 encoded string.
Accepts an atom `:upper` (default) for encoding to upper case characters or
`:lower` for lower case characters.
## Examples
iex> Base.encode16("foobar")
"666F6F626172"
iex> Base.encode16("foobar", case: :lower)
"666f6f626172"
"""
@spec encode16(binary) :: binary
def encode16(data) when is_binary(data) do
do_encode16(data, &enc16/1)
@spec encode16(binary, Keyword.t) :: binary
def encode16(data, opts \\ []) when is_binary(data) do
case = Keyword.get(opts, :case, :upper)
do_encode16(data, encode_case(case, &enc16/1))
end
@doc """
Decodes a base 16 encoded string into a binary string.
The following alphabet is used both for encoding and decoding:
| Value | Encoding | Value | Encoding | Value | Encoding | Value | Encoding |
|------:|---------:|------:|---------:|------:|---------:|------:|---------:|
| 0| 0| 4| 4| 8| 8| 12| C|
| 1| 1| 5| 5| 9| 9| 13| D|
| 2| 2| 6| 6| 10| A| 14| E|
| 3| 3| 7| 7| 11| B| 15| F|
Accepts an atom `:upper` (default) for decoding from upper case characters or
`:lower` for lower case characters. `:mixed` can be given for mixed case
characters.
## Examples
iex> Base.decode16("666F6F626172")
{:ok, "foobar"}
iex> Base.decode16("666f6f626172", case: :lower)
{:ok, "foobar"}
iex> Base.decode16("666f6F626172", case: :mixed)
{:ok, "foobar"}
"""
@spec decode16(binary) :: {:ok, binary} | :error
def decode16(string) when is_binary(string) do
{:ok, decode16!(string)}
@spec decode16(binary, Keyword.t) :: {:ok, binary} | :error
def decode16(string, opts \\ []) when is_binary(string) do
case = Keyword.get(opts, :case, :upper)
{:ok, do_decode16(string, decode_case(case, &dec16/1))}
rescue
ArgumentError -> :error
end
@@ -71,6 +195,10 @@ defmodule Base do
@doc """
Decodes a base 16 encoded string into a binary string.
Accepts an atom `:upper` (default) for decoding from upper case characters or
`:lower` for lower case characters. `:mixed` can be given for mixed case
characters.
An `ArgumentError` exception is raised if the padding is incorrect or
a non-alphabet character is present in the string.
@@ -79,10 +207,18 @@ defmodule Base do
iex> Base.decode16!("666F6F626172")
"foobar"
iex> Base.decode16!("666f6f626172", case: :lower)
"foobar"
iex> Base.decode16!("666f6F626172", case: :mixed)
"foobar"
"""
@spec decode16!(binary) :: binary
def decode16!(string) when is_binary(string) do
do_decode16(string, &dec16/1)
@spec decode16!(binary, Keyword.t) :: binary
def decode16!(string, opts \\ []) when is_binary(string) do
case = Keyword.get(opts, :case, :upper)
do_decode16(string, decode_case(case, &dec16/1))
end
@doc """
@@ -102,28 +238,6 @@ defmodule Base do
@doc """
Decodes a base 64 encoded string into a binary string.
The following alphabet is used both for encoding and decoding:
| Value | Encoding | Value | Encoding | Value | Encoding | Value | Encoding |
|------:|---------:|------:|---------:|------:|---------:|------:|---------:|
| 0| A| 17| R| 34| i| 51| z|
| 1| B| 18| S| 35| j| 52| 0|
| 2| C| 19| T| 36| k| 53| 1|
| 3| D| 20| U| 37| l| 54| 2|
| 4| E| 21| V| 38| m| 55| 3|
| 5| F| 22| W| 39| n| 56| 4|
| 6| G| 23| X| 40| o| 57| 5|
| 7| H| 24| Y| 41| p| 58| 6|
| 8| I| 25| Z| 42| q| 59| 7|
| 9| J| 26| a| 43| r| 60| 8|
| 10| K| 27| b| 44| s| 61| 9|
| 11| L| 28| c| 45| t| 62| +|
| 12| M| 29| d| 46| u| 63| /|
| 13| N| 30| e| 47| v| | |
| 14| O| 31| f| 48| w| (pad)| =|
| 15| P| 32| g| 49| x| | |
| 16| Q| 33| h| 50| y| | |
## Examples
iex> Base.decode64("Zm9vYmFy")
@@ -175,28 +289,6 @@ defmodule Base do
Decodes a base 64 encoded string with URL and filename safe alphabet
into a binary string.
The following alphabet is used both for encoding and decoding:
| Value | Encoding | Value | Encoding | Value | Encoding | Value | Encoding |
|------:|---------:|------:|---------:|------:|---------:|------:|---------:|
| 0| A| 17| R| 34| i| 51| z|
| 1| B| 18| S| 35| j| 52| 0|
| 2| C| 19| T| 36| k| 53| 1|
| 3| D| 20| U| 37| l| 54| 2|
| 4| E| 21| V| 38| m| 55| 3|
| 5| F| 22| W| 39| n| 56| 4|
| 6| G| 23| X| 40| o| 57| 5|
| 7| H| 24| Y| 41| p| 58| 6|
| 8| I| 25| Z| 42| q| 59| 7|
| 9| J| 26| a| 43| r| 60| 8|
| 10| K| 27| b| 44| s| 61| 9|
| 11| L| 28| c| 45| t| 62| -|
| 12| M| 29| d| 46| u| 63| _|
| 13| N| 30| e| 47| v| | |
| 14| O| 31| f| 48| w| (pad)| =|
| 15| P| 32| g| 49| x| | |
| 16| Q| 33| h| 50| y| | |
## Examples
iex> Base.url_decode64("_3_-_A==")
@@ -231,43 +323,49 @@ defmodule Base do
@doc """
Encodes a binary string into a base 32 encoded string.
Accepts an atom `:upper` (default) for encoding to upper case characters or
`:lower` for lower case characters.
## Examples
iex> Base.encode32("foobar")
"MZXW6YTBOI======"
iex> Base.encode32("foobar", case: :lower)
"mzxw6ytboi======"
"""
@spec encode32(binary) :: binary
def encode32(data) when is_binary(data) do
do_encode32(data, &enc32/1)
@spec encode32(binary, Keyword.t) :: binary
def encode32(data, opts \\ []) when is_binary(data) do
case = Keyword.get(opts, :case, :upper)
do_encode32(data, encode_case(case, &enc32/1))
end
@doc """
Decodes a base 32 encoded string into a binary string.
The following alphabet is used both for encoding and decoding:
| Value | Encoding | Value | Encoding | Value | Encoding | Value | Encoding |
|------:|---------:|------:|---------:|------:|---------:|------:|---------:|
| 0| A| 9| J| 18| S| 27| 3|
| 1| B| 10| K| 19| T| 28| 4|
| 2| C| 11| L| 20| U| 29| 5|
| 3| D| 12| M| 21| V| 30| 6|
| 4| E| 13| N| 22| W| 31| 7|
| 5| F| 14| O| 23| X| | |
| 6| G| 15| P| 24| Y| (pad)| =|
| 7| H| 16| Q| 25| Z| | |
| 8| I| 17| R| 26| 2| | |
Accepts an atom `:upper` (default) for decoding from upper case characters or
`:lower` for lower case characters. `:mixed` can be given for mixed case
characters.
## Examples
iex> Base.decode32("MZXW6YTBOI======")
{:ok, "foobar"}
iex> Base.decode32("mzxw6ytboi======", case: :lower)
{:ok, "foobar"}
iex> Base.decode32("mzXW6ytBOi======", case: :mixed)
{:ok, "foobar"}
"""
@spec decode32(binary) :: {:ok, binary} | :error
def decode32(string) do
{:ok, do_decode32(string, &dec32/1)}
@spec decode32(binary, Keyword.t) :: {:ok, binary} | :error
def decode32(string, opts \\ []) do
case = Keyword.get(opts, :case, :upper)
{:ok, do_decode32(string, decode_case(case, &dec32/1))}
rescue
ArgumentError -> :error
end
@@ -275,6 +373,10 @@ defmodule Base do
@doc """
Decodes a base 32 encoded string into a binary string.
Accepts an atom `:upper` (default) for decoding from upper case characters or
`:lower` for lower case characters. `:mixed` can be given for mixed case
characters.
An `ArgumentError` exception is raised if the padding is incorrect or
a non-alphabet character is present in the string.
@@ -283,54 +385,68 @@ defmodule Base do
iex> Base.decode32!("MZXW6YTBOI======")
"foobar"
iex> Base.decode32!("mzxw6ytboi======", case: :lower)
"foobar"
iex> Base.decode32!("mzXW6ytBOi======", case: :mixed)
"foobar"
"""
@spec decode32!(binary) :: binary
def decode32!(string) do
do_decode32(string, &dec32/1)
@spec decode32!(binary, Keyword.t) :: binary
def decode32!(string, opts \\ []) do
case = Keyword.get(opts, :case, :upper)
do_decode32(string, decode_case(case, &dec32/1))
end
@doc """
Encodes a binary string into a base 32 encoded string with an
extended hexadecimal alphabet.
Accepts an atom `:upper` (default) for encoding to upper case characters or
`:lower` for lower case characters.
## Examples
iex> Base.hex_encode32("foobar")
"CPNMUOJ1E8======"
iex> Base.hex_encode32("foobar", case: :lower)
"cpnmuoj1e8======"
"""
@spec hex_encode32(binary) :: binary
def hex_encode32(data) when is_binary(data) do
do_encode32(data, &enc32hex/1)
@spec hex_encode32(binary, Keyword.t) :: binary
def hex_encode32(data, opts \\ []) when is_binary(data) do
case = Keyword.get(opts, :case, :upper)
do_encode32(data, encode_case(case, &enc32hex/1))
end
@doc """
Decodes a base 32 encoded string with extended hexadecimal alphabet
into a binary string.
The following alphabet is used both for encoding and decoding:
| Value | Encoding | Value | Encoding | Value | Encoding | Value | Encoding |
|------:|---------:|------:|---------:|------:|---------:|------:|---------:|
| 0| 0| 9| 9| 18| I| 27| R|
| 1| 1| 10| A| 19| J| 28| S|
| 2| 2| 11| B| 20| K| 29| T|
| 3| 3| 12| C| 21| L| 30| U|
| 4| 4| 13| D| 22| M| 31| V|
| 5| 5| 14| E| 23| N| | |
| 6| 6| 15| F| 24| O| (pad)| =|
| 7| 7| 16| G| 25| P| | |
| 8| 8| 17| H| 26| Q| | |
Accepts an atom `:upper` (default) for decoding from upper case characters or
`:lower` for lower case characters. `:mixed` can be given for mixed case
characters.
## Examples
iex> Base.hex_decode32("CPNMUOJ1E8======")
{:ok, "foobar"}
iex> Base.hex_decode32("cpnmuoj1e8======", case: :lower)
{:ok, "foobar"}
iex> Base.hex_decode32("cpnMuOJ1E8======", case: :mixed)
{:ok, "foobar"}
"""
@spec hex_decode32(binary) :: {:ok, binary} | :error
def hex_decode32(string) when is_binary(string) do
{:ok, do_decode32(string, &dec32hex/1)}
@spec hex_decode32(binary, Keyword.t) :: {:ok, binary} | :error
def hex_decode32(string, opts \\ []) when is_binary(string) do
case = Keyword.get(opts, :case, :upper)
{:ok, do_decode32(string, decode_case(case, &dec32hex/1))}
rescue
ArgumentError -> :error
end
@@ -339,6 +455,10 @@ defmodule Base do
Decodes a base 32 encoded string with extended hexadecimal alphabet
into a binary string.
Accepts an atom `:upper` (default) for decoding from upper case characters or
`:lower` for lower case characters. `:mixed` can be given for mixed case
characters.
An `ArgumentError` exception is raised if the padding is incorrect or
a non-alphabet character is present in the string.
@@ -347,10 +467,18 @@ defmodule Base do
iex> Base.hex_decode32!("CPNMUOJ1E8======")
"foobar"
iex> Base.hex_decode32!("cpnmuoj1e8======", case: :lower)
"foobar"
iex> Base.hex_decode32!("cpnMuOJ1E8======", case: :mixed)
"foobar"
"""
@spec hex_decode32!(binary) :: binary
def hex_decode32!(string) when is_binary(string) do
do_decode32(string, &dec32hex/1)
@spec hex_decode32!(binary, Keyword.t) :: binary
def hex_decode32!(string, opts \\ []) when is_binary(string) do
case = Keyword.get(opts, :case, :upper)
do_decode32(string, decode_case(case, &dec32hex/1))
end
defp do_encode16(<<>>, _), do: <<>>
@@ -365,7 +493,7 @@ defmodule Base do
end
end
defp do_decode16(_, _) do
raise ArgumentError, message: "odd-length string"
raise ArgumentError, "odd-length string"
end
defp do_encode64(<<>>, _), do: <<>>
@@ -400,7 +528,7 @@ defmodule Base do
end
end
defp do_decode64(_, _) do
raise ArgumentError, message: "incorrect padding"
raise ArgumentError, "incorrect padding"
end
defp do_encode32(<<>>, _), do: <<>>
@@ -458,7 +586,7 @@ defmodule Base do
end
end
defp do_decode32(_, _) do
raise ArgumentError, message: "incorrect padding"
raise ArgumentError, "incorrect padding"
end
end
+3 -3
View File
@@ -78,7 +78,7 @@ defmodule Behaviour do
{name, args} ->
do_callback(:def, name, args, name, length(args), args, return, guards, caller)
_ ->
raise ArgumentError, message: "invalid syntax in defcallback #{Macro.to_string(spec)}"
raise ArgumentError, "invalid syntax in defcallback #{Macro.to_string(spec)}"
end
end
@@ -88,7 +88,7 @@ defmodule Behaviour do
do_callback(:defmacro, :"MACRO-#{name}", [quote(do: env :: Macro.Env.t)|args],
name, length(args), args, return, guards, caller)
_ ->
raise ArgumentError, message: "invalid syntax in defmacrocallback #{Macro.to_string(spec)}"
raise ArgumentError, "invalid syntax in defmacrocallback #{Macro.to_string(spec)}"
end
end
@@ -111,7 +111,7 @@ defmodule Behaviour do
end
defp ensure_not_default({:\\, _, [_, _]}) do
raise ArgumentError, message: "default arguments \\\\ not supported in defcallback/defmacrocallback"
raise ArgumentError, "default arguments \\\\ not supported in defcallback/defmacrocallback"
end
defp ensure_not_default(_), do: :ok
+2 -2
View File
@@ -24,8 +24,8 @@ defmodule Bitwise do
Allow a developer to use this module in their programs with
the following options:
* `:only_operators` - Include only operators;
* `:skip_operators` - Skip operators;
* `:only_operators` - include only operators
* `:skip_operators` - skip operators
"""
defmacro __using__(options) do
+104 -39
View File
@@ -1,11 +1,4 @@
defmodule Code do
defexception LoadError, [:file, :message] do
def exception(opts) do
file = opts[:file]
LoadError[message: "could not load #{file}", file: file]
end
end
@moduledoc """
Utilities for managing code compilation, code evaluation and code loading.
@@ -66,21 +59,24 @@ defmodule Code do
Those options can be:
* `:file` - the file to be considered in the evaluation
* `:line` - the line on which the script starts
* `:delegate_locals_to` - delegate local calls to the given module,
the default is to not delegate
* `:file` - the file to be considered in the evaluation
* `:line` - the line on which the script starts
* `:delegate_locals_to` - delegate local calls to the given module,
the default is to not delegate
Additionally, the following scope values can be configured:
* `:aliases` - a list of tuples with the alias and its target
* `:requires` - a list of modules required
* `:functions` - a list of tuples where the first element is a module
and the second a list of imported function names and arity. The list
of function names and arity must be sorted
* `:macros` - a list of tuples where the first element is a module
and the second a list of imported macro names and arity. The list
of function names and arity must be sorted
* `:aliases` - a list of tuples with the alias and its target
* `:requires` - a list of modules required
* `:functions` - a list of tuples where the first element is a module
and the second a list of imported function names and arity; the list
of function names and arity must be sorted
* `:macros` - a list of tuples where the first element is a module
and the second a list of imported macro names and arity; the list
of function names and arity must be sorted
Notice that setting any of the values above overrides Elixir's default
values. For example, setting `:requires` to `[]`, will no longer
@@ -135,14 +131,14 @@ defmodule Code do
## Examples
iex> contents = quote(hygiene: [vars: false], do: a + b)
iex> contents = quote(do: var!(a) + var!(b))
iex> Code.eval_quoted(contents, [a: 1, b: 2], file: __ENV__.file, line: __ENV__.line)
{3, [a: 1, b: 2]}
For convenience, you can pass `__ENV__` as the `opts` argument and
all options will be automatically extracted from the current environment:
iex> contents = quote(hygiene: [vars: false], do: a + b)
iex> contents = quote(do: var!(a) + var!(b))
iex> Code.eval_quoted(contents, [a: 1, b: 2], __ENV__)
{3, [a: 1, b: 2]}
@@ -171,7 +167,7 @@ defmodule Code do
valid = is_list(requires) and Enum.all?(requires, &is_atom(&1))
unless valid do
raise ArgumentError, message: "expected :#{kind} option given to eval in the format: [module]"
raise ArgumentError, "expected :#{kind} option given to eval in the format: [module]"
end
end
@@ -181,7 +177,7 @@ defmodule Code do
end)
unless valid do
raise ArgumentError, message: "expected :#{kind} option given to eval in the format: [{module, module}]"
raise ArgumentError, "expected :#{kind} option given to eval in the format: [{module, module}]"
end
end
@@ -193,7 +189,7 @@ defmodule Code do
end)
unless valid do
raise ArgumentError, message: "expected :#{kind} option given to eval in the format: [{module, [{name, arity}]}]"
raise ArgumentError, "expected :#{kind} option given to eval in the format: [{module, [{name, arity}]}]"
end
end
@@ -205,13 +201,13 @@ defmodule Code do
## Options
* `:file` - The filename to be used in stacktraces
and the file reported in the `__ENV__` variable.
* `:file` - the filename to be used in stacktraces
and the file reported in the `__ENV__` variable
* `:line` - The line reported in the `__ENV__` variable.
* `:line` - the line reported in the `__ENV__` variable
* `:existing_atoms_only` - When `true`, raises an error
when non-existing atoms are found by the tokenizer.
* `:existing_atoms_only` - when `true`, raises an error
when non-existing atoms are found by the tokenizer
## Macro.to_string/2
@@ -252,7 +248,7 @@ defmodule Code do
"""
def eval_file(file, relative_to \\ nil) do
file = find_file(file, relative_to)
eval_string File.read!(file), [], []
eval_string File.read!(file), [], [file: file, line: 1]
end
@doc """
@@ -315,6 +311,15 @@ defmodule Code do
:elixir_code_server.call :compiler_options
end
@doc """
Returns a list with the available compiler options.
See `Code.compiler_options/1` for more info.
"""
def available_compiler_options do
[:docs, :debug_info, :ignore_module_conflict, :warnings_as_errors]
end
@doc """
Sets compilation options.
@@ -322,20 +327,26 @@ defmodule Code do
Available options are:
* `:docs` - when `true`, retain documentation in the compiled module,
`true` by default;
* `:docs` - when `true`, retain documentation in the compiled module,
`true` by default
* `:debug_info` - when `true`, retain debug information in the compiled module.
This allows a developer to reconstruct the original source
code, `false` by default;
* `:debug_info` - when `true`, retain debug information in the compiled
module; this allows a developer to reconstruct the original source
code, `false` by default
* `:ignore_module_conflict` - when `true`, override modules that were already defined
without raising errors, `false` by default;
* `:ignore_module_conflict` - when `true`, override modules that were
already defined without raising errors, `false` by default
* `:warnings_as_errors` - cause compilation to fail when warnings are generated;
* `:warnings_as_errors` - cause compilation to fail when warnings are
generated
"""
def compiler_options(opts) do
{opts, bad} = Keyword.split(opts, available_compiler_options)
if bad != [] do
bad = bad |> Keyword.keys |> Enum.join(", ")
raise ArgumentError, message: "unknown compiler options: #{bad}"
end
:elixir_code_server.cast {:compiler_options, opts}
end
@@ -455,6 +466,60 @@ defmodule Code do
match?({:module, ^module}, ensure_compiled(module))
end
@doc """
Returns the docs for the given module.
When given a module name, it finds its BEAM code and reads the docs from it.
When given a path to a .beam file, it will load the docs directly from that
file.
The return value depends on the `kind` value:
* `:docs` - list of all docstrings attached to functions and macros
using the `@doc` attribute
* `:moduledoc` - tuple `{<line>, <doc>}` where `line` is the line on
which module definition starts and `doc` is the string
attached to the module using the `@moduledoc` attribute
* `:all` - a keyword list with both `:docs` and `:moduledoc`
"""
def get_docs(module, kind) when is_atom(module) do
case :code.get_object_code(module) do
{_module, bin, _beam_path} ->
do_get_docs(bin, kind)
:error -> nil
end
end
def get_docs(binpath, kind) when is_binary(binpath) do
do_get_docs(String.to_char_list(binpath), kind)
end
@docs_chunk 'ExDc'
defp do_get_docs(bin_or_path, kind) do
case :beam_lib.chunks(bin_or_path, [@docs_chunk]) do
{:ok, {_module, [{@docs_chunk, bin}]}} ->
lookup_docs(:erlang.binary_to_term(bin), kind)
{:error, :beam_lib, {:missing_chunk, _, @docs_chunk}} -> nil
end
end
defp lookup_docs({:elixir_docs_v1, docs}, kind),
do: do_lookup_docs(docs, kind)
# unsupported chunk version
defp lookup_docs(_, _), do: nil
defp do_lookup_docs(docs, :all), do: docs
defp do_lookup_docs(docs, kind) when kind in [:docs, :moduledoc],
do: Keyword.get(docs, kind)
## Helpers
# Finds the file given the relative_to path.
@@ -470,7 +535,7 @@ defmodule Code do
if File.regular?(file) do
file
else
raise LoadError, file: file
raise Code.LoadError, file: file
end
end
end
+205 -4
View File
@@ -31,6 +31,70 @@ defmodule Dict do
Dictionaries are required to implement all operations
using the match (`===`) operator.
## Default implementation
Default implementations for some functions in the `Dict` module
are provided via `use Dict`.
For example:
defmodule MyDict do
use Dict
# implement required functions (see below)
# override default implementations if optimization
# is needed
end
The client module must contain the following functions:
* `delete/2`
* `fetch/2`
* `put/3`
* `reduce/3`
* `size/1`
All functions, except `reduce/3`, are required by the Dict behaviour.
`reduce/3` must be implemtented as per the Enumerable protocol.
Based on these functions, `Dict` generates default implementations
for the following functions:
* `drop/2`
* `equal?/2`
* `fetch!/2`
* `get/2`
* `get/3`
* `has_key?/2`
* `keys/1`
* `merge/2`
* `merge/3`
* `pop/2`
* `pop/3`
* `put_new/3`
* `split/2`
* `take/2`
* `to_list/1`
* `update/4`
* `update!/3`
* `values/1`
All of these functions are defined as overridable, so you can provide
your own implementation if needed.
Note you can also test your custom module via `Dict`'s doctests:
defmodule MyDict do
# ...
end
defmodule MyTests do
use ExUnit.Case
doctest Dict
defp dict_impl, do: MyDict
end
"""
use Behaviour
@@ -63,6 +127,146 @@ defmodule Dict do
defcallback update!(t, key, (value -> value)) :: t | no_return
defcallback values(t) :: list(value)
defmacro __using__(_) do
# Use this import to guarantee proper code expansion
import Kernel, except: [size: 1]
quote do
@behaviour Dict
def get(dict, key, default \\ nil) do
case fetch(dict, key) do
{:ok, value} -> value
:error -> default
end
end
def fetch!(dict, key) do
case fetch(dict, key) do
{:ok, value} -> value
:error -> raise KeyError, key: key, term: dict
end
end
def has_key?(dict, key) do
match? {:ok, _}, fetch(dict, key)
end
def put_new(dict, key, value) do
case has_key?(dict, key) do
true -> dict
false -> put(dict, key, value)
end
end
def drop(dict, keys) do
Enum.reduce(keys, dict, &delete(&2, &1))
end
def take(dict, keys) do
Enum.reduce(keys, new, fn key, acc ->
case fetch(dict, key) do
{:ok, value} -> put(acc, key, value)
:error -> acc
end
end)
end
def to_list(dict) do
reduce(dict, {:cont, []}, fn
kv, acc -> {:cont, [kv|acc]}
end) |> elem(1) |> :lists.reverse
end
def keys(dict) do
reduce(dict, {:cont, []}, fn
{k, _}, acc -> {:cont, [k|acc]}
end) |> elem(1) |> :lists.reverse
end
def values(dict) do
reduce(dict, {:cont, []}, fn
{_, v}, acc -> {:cont, [v|acc]}
end) |> elem(1) |> :lists.reverse
end
def equal?(dict1, dict2) do
# Use this import to avoid conflicts in the user code
import Kernel, except: [size: 1]
case size(dict1) == size(dict2) do
false -> false
true ->
reduce(dict1, {:cont, true}, fn({k, v}, _acc) ->
case fetch(dict2, k) do
{:ok, ^v} -> {:cont, true}
_ -> {:halt, false}
end
end) |> elem(1)
end
end
def merge(dict1, dict2, fun \\ fn(_k, _v1, v2) -> v2 end) do
# Use this import to avoid conflicts in the user code
import Kernel, except: [size: 1]
if size(dict1) < size(dict2) do
reduce(dict1, {:cont, dict2}, fn {k, v1}, acc ->
{:cont, update(acc, k, v1, &fun.(k, v1, &1))}
end)
else
reduce(dict2, {:cont, dict1}, fn {k, v2}, acc ->
{:cont, update(acc, k, v2, &fun.(k, &1, v2))}
end)
end |> elem(1)
end
def update(dict, key, initial, fun) do
case fetch(dict, key) do
{:ok, value} ->
put(dict, key, fun.(value))
:error ->
put(dict, key, initial)
end
end
def update!(dict, key, fun) do
case fetch(dict, key) do
{:ok, value} ->
put(dict, key, fun.(value))
:error ->
raise KeyError, key: key, term: dict
end
end
def pop(dict, key, default \\ nil) do
case fetch(dict, key) do
{:ok, value} ->
{value, delete(dict, key)}
:error ->
{default, dict}
end
end
def split(dict, keys) do
Enum.reduce(keys, {new, dict}, fn key, {inc, exc} = acc ->
case fetch(exc, key) do
{:ok, value} ->
{put(inc, key, value), delete(exc, key)}
:error ->
acc
end
end)
end
defoverridable merge: 2, merge: 3, equal?: 2, to_list: 1, keys: 1,
values: 1, take: 2, drop: 2, get: 2, get: 3, fetch!: 2,
has_key?: 2, put_new: 3, pop: 2, pop: 3, split: 2,
update: 4, update!: 3
end
end
defmacrop target(dict) do
quote do
case unquote(dict) do
@@ -70,9 +274,6 @@ defmodule Dict do
x
%{} ->
Map
x when is_tuple(x) ->
IO.write :stderr, "Using records with the Dict module is deprecated, please use structs instead\n#{Exception.format_stacktrace}"
elem(x, 0)
x when is_list(x) ->
Keyword
x ->
@@ -487,6 +688,6 @@ defmodule Dict do
end
defp unsupported_dict(dict) do
raise ArgumentError, message: "unsupported dict: #{inspect dict}"
raise ArgumentError, "unsupported dict: #{inspect dict}"
end
end
-199
View File
@@ -1,199 +0,0 @@
defmodule Dict.Behaviour do
@moduledoc ~S"""
Default implementations for some required functions in the `Dict` module.
This module makes it easier to create your own `Dict` compliant
module, by providing default implementations for some required functions.
## Example
defmodule MyDict do
use Dict.Behaviour
# implement required functions (see below)
# override default implementations if optimization is needed
end
The client module must contain the following functions:
* `delete/2`
* `fetch/2`
* `put/3`
* `reduce/3`
* `size/1`
All functions, except `reduce/3`, are required by the Dict behaviour.
`reduce/3` must be implemtented as per the Enumerable protocol.
Based on these functions, `Dict.Behaviour` generates default implementations
for the following functions:
* `drop/2`
* `equal?/2`
* `fetch!/2`
* `get/2`
* `get/3`
* `has_key?/2`
* `keys/1`
* `merge/2`
* `merge/3`
* `pop/2`
* `pop/3`
* `put_new/3`
* `split/2`
* `take/2`
* `to_list/1`
* `values/1`
* `update/4`
* `update!/3`
All of these functions are defined as overridable, so you can provide your own
implementation if needed.
Note you can also test your custom module via `Dict`'s doctests:
defmodule MyDict do
def new(keywords \\ []) do
...
end
end
defmodule MyTests do
use ExUnit.Case
doctest Dict
defp dict_impl, do: MyDict
end
"""
defmacro __using__(_) do
# Use this import to guarantee proper code expansion
import Kernel, except: [size: 1]
quote do
@behaviour Dict
def get(dict, key, default \\ nil) do
case fetch(dict, key) do
{:ok, value} -> value
:error -> default
end
end
def fetch!(dict, key) do
case fetch(dict, key) do
{:ok, value} -> value
:error -> raise KeyError, key: key, term: dict
end
end
def has_key?(dict, key) do
match? {:ok, _}, fetch(dict, key)
end
def put_new(dict, key, value) do
update(dict, key, value, fn(v) -> v end)
end
def drop(dict, keys) do
Enum.reduce keys, dict, &delete(&2, &1)
end
def take(dict, keys) do
Enum.reduce keys, new, fn key, acc ->
case fetch(dict, key) do
{:ok, value} -> put(acc, key, value)
:error -> acc
end
end
end
def to_list(dict) do
reduce(dict, {:cont, []}, fn
kv, acc -> {:cont, [kv|acc]}
end) |> elem(1) |> :lists.reverse
end
def keys(dict) do
reduce(dict, {:cont, []}, fn
{k, _}, acc -> {:cont, [k|acc]}
end) |> elem(1) |> :lists.reverse
end
def values(dict) do
reduce(dict, {:cont, []}, fn
{_, v}, acc -> {:cont, [v|acc]}
end) |> elem(1) |> :lists.reverse
end
def equal?(dict1, dict2) do
# Use this import to avoid conflicts in the user code
import Kernel, except: [size: 1]
case size(dict1) == size(dict2) do
false -> false
true ->
reduce(dict1, {:cont, true}, fn({k, v}, _acc) ->
case fetch(dict2, k) do
{:ok, ^v} -> {:cont, true}
_ -> {:halt, false}
end
end) |> elem(1)
end
end
def merge(dict1, dict2, fun \\ fn(_k, _v1, v2) -> v2 end) do
reduce(dict1, {:cont, dict2}, fn {k, v1}, acc ->
{:cont, update(acc, k, v1, &fun.(k, v1, &1))}
end) |> elem(1)
end
def update(dict, key, initial, fun) do
case fetch(dict, key) do
{ :ok, value } ->
put(dict, key, fun.(value))
:error ->
put(dict, key, initial)
end
end
def update!(dict, key, fun) do
case fetch(dict, key) do
{ :ok, value } ->
put(dict, key, fun.(value))
:error ->
raise KeyError, key: key, term: dict
end
end
def pop(dict, key, default \\ nil) do
case fetch(dict, key) do
{ :ok, value } ->
{ value, delete(dict, key) }
:error ->
{ default, dict }
end
end
def split(dict, keys) do
Enum.reduce keys, { new, dict }, fn key, { inc, exc } = acc ->
case fetch(exc, key) do
{ :ok, value } ->
{ put(inc, key, value), delete(exc, key) }
:error ->
acc
end
end
end
defoverridable merge: 2, merge: 3, equal?: 2, to_list: 1, keys: 1,
values: 1, take: 2, drop: 2, get: 2, get: 3, fetch!: 2,
has_key?: 2, put_new: 3, pop: 2, pop: 3, update: 4, update!: 3,
split: 2
end
end
end
+62 -32
View File
@@ -39,9 +39,9 @@ defprotocol Enumerable do
It must be a tagged tuple with one of the following "tags":
* `:cont` - the enumeration should continue
* `:halt` - the enumeration should halt immediately
* `:suspend` - the enumeration should be suspended immediately
* `:cont` - the enumeration should continue
* `:halt` - the enumeration should halt immediately
* `:suspend` - the enumeration should be suspended immediately
Depending on the accumulator value, the result returned by
`Enumerable.reduce/3` will change. Please check the `result`
@@ -936,10 +936,15 @@ defmodule Enum do
def join(collection, joiner \\ "")
def join(collection, joiner) when is_binary(joiner) do
reduce(collection, "", fn
entry, "" -> to_string(entry)
entry, acc -> acc <> joiner <> to_string(entry)
reduced = reduce(collection, :first, fn
entry, :first -> enum_to_string(entry)
entry, acc -> [acc, joiner|enum_to_string(entry)]
end)
if reduced == :first do
""
else
IO.iodata_to_binary reduced
end
end
@doc """
@@ -990,10 +995,16 @@ defmodule Enum do
def map_join(collection, joiner \\ "", mapper)
def map_join(collection, joiner, mapper) when is_binary(joiner) do
reduce(collection, "", fn
entry, "" -> to_string(mapper, entry)
entry, acc -> acc <> joiner <> to_string(mapper, entry)
reduced = reduce(collection, :first, fn
entry, :first -> enum_to_string(mapper.(entry))
entry, acc -> [acc, joiner|enum_to_string(mapper.(entry))]
end)
if reduced == :first do
""
else
IO.iodata_to_binary reduced
end
end
@doc """
@@ -1406,18 +1417,33 @@ defmodule Enum do
@doc """
Returns a subset list of the given collection. Drops elements
until element position `start`, then takes `count` elements.
If the count is greater than collection length, it returns as
much as possible. If zero, then it returns `[]`.
## Examples
iex> Enum.slice(1..100, 5, 10)
[6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
iex> Enum.slice(1..10, 5, 100)
[6, 7, 8, 9, 10]
iex> Enum.slice(1..10, 5, 0)
[]
"""
@spec slice(t, integer, non_neg_integer) :: list
def slice(_coll, _start, 0), do: []
def slice(coll, start, count) when start < 0 do
{list, new_start} = enumerate_and_count(coll, start)
if new_start >= 0, do: slice(list, new_start, count)
if new_start >= 0 do
slice(list, new_start, count)
else
[]
end
end
def slice(coll, start, count) when is_list(coll) and start >= 0 and count > 0 do
@@ -1425,7 +1451,7 @@ defmodule Enum do
end
def slice(coll, start, count) when start >= 0 and count > 0 do
{start, _, list} = Enumerable.reduce(coll, {:cont, {start, count, []}}, fn
{_, _, list} = Enumerable.reduce(coll, {:cont, {start, count, []}}, fn
_entry, {start, count, _list} when start > 0 ->
{:cont, {start-1, count, []}}
entry, {start, count, list} when count > 1 ->
@@ -1434,15 +1460,7 @@ defmodule Enum do
{:halt, {start, count, [entry|list]}}
end) |> elem(1)
if start <= 0, do: :lists.reverse(list)
end
def slice(coll, start, 0) do
res =
Enumerable.reduce(coll, {:cont, start}, fn _, start ->
if start > 0, do: {:cont, start-1}, else: {:halt, []}
end) |> elem(1)
if is_list(res), do: res
:lists.reverse(list)
end
@doc """
@@ -1457,17 +1475,31 @@ defmodule Enum do
The first position (after adding count to negative positions) must be smaller
or equal to the last position.
If the start of the range is not a valid offset for the given
collection or if the range is in reverse order, returns `[]`.
## Examples
iex> Enum.slice(1..100, 5..10)
[6, 7, 8, 9, 10, 11]
iex> Enum.slice(1..10, 5..20)
[6, 7, 8, 9, 10]
iex> Enum.slice(1..10, 11..20)
[]
iex> Enum.slice(1..10, 6..5)
[]
"""
@spec slice(t, Range.t) :: list
def slice(coll, first..last) when first >= 0 and last >= 0 do
# Simple case, which works on infinite collections
if last - first >= 0 do
slice(coll, first, last - first + 1)
else
[]
end
end
@@ -1478,6 +1510,8 @@ defmodule Enum do
length = corr_last - corr_first + 1
if corr_first >= 0 and length > 0 do
slice(list, corr_first, length)
else
[]
end
end
@@ -1805,7 +1839,7 @@ defmodule Enum do
## Helpers
@compile {:inline, to_string: 2}
@compile {:inline, enum_to_string: 1}
defp enumerate_and_count(collection, count) when is_list(collection) do
{collection, length(collection) - abs(count)}
@@ -1815,12 +1849,8 @@ defmodule Enum do
map_reduce(collection, -abs(count), fn(x, acc) -> {x, acc + 1} end)
end
defp to_string(mapper, entry) do
case mapper.(entry) do
x when is_binary(x) -> x
o -> String.Chars.to_string(o)
end
end
defp enum_to_string(entry) when is_binary(entry), do: entry
defp enum_to_string(entry), do: String.Chars.to_string(entry)
## Implementations
@@ -2131,12 +2161,12 @@ defmodule Enum do
## slice
defp do_slice([], start, _count) do
if start == 0, do: []
defp do_slice([], _start, _count) do
[]
end
defp do_slice(list, start, 0) do
if start < length(list), do: []
defp do_slice(_list, _start, 0) do
[]
end
defp do_slice([h|t], 0, count) do
@@ -2184,7 +2214,7 @@ defimpl Enumerable, for: Map do
end
defimpl Enumerable, for: Function do
def reduce(function, acc, fun),
def reduce(function, acc, fun) when is_function(function, 2),
do: function.(acc, fun)
def member?(_function, _value),
do: {:error, __MODULE__}
+368 -244
View File
@@ -1,180 +1,26 @@
# Some exceptions implement `message/1` instead of `exception/1` mostly
# for bootstrap reasons. It is recommended for applications to implement
# `exception/1` instead of `message/1` as described in `defexception/3` docs.
defexception RuntimeError, message: "runtime error"
defexception ArgumentError, message: "argument error"
defexception ArithmeticError, message: "bad argument in arithmetic expression"
defexception SystemLimitError, message: "a system limit has been reached"
defexception SyntaxError, [file: nil, line: nil, description: "syntax error"] do
def message(exception) do
Exception.format_file_line(Path.relative_to_cwd(exception.file), exception.line) <>
" " <> exception.description
end
end
defexception TokenMissingError, [file: nil, line: nil, description: "expression is incomplete"] do
def message(exception) do
Exception.format_file_line(Path.relative_to_cwd(exception.file), exception.line) <>
" " <> exception.description
end
end
defexception CompileError, [file: nil, line: nil, description: "compile error"] do
def message(exception) do
Exception.format_file_line(Path.relative_to_cwd(exception.file), exception.line) <>
" " <> exception.description
end
end
defexception BadFunctionError, [term: nil] do
def message(exception) do
"expected a function, got: #{inspect(exception.term)}"
end
end
defexception BadStructError, [struct: nil, term: nil] do
def message(exception) do
"expected a struct named #{inspect(exception.struct)}, got: #{inspect(exception.term)}"
end
end
defexception MatchError, [term: nil] do
def message(exception) do
"no match of right hand side value: #{inspect(exception.term)}"
end
end
defexception CaseClauseError, [term: nil] do
def message(exception) do
"no case clause matching: #{inspect(exception.term)}"
end
end
defexception TryClauseError, [term: nil] do
def message(exception) do
"no try clause matching: #{inspect(exception.term)}"
end
end
defexception BadArityError, [function: nil, args: nil] do
def message(exception) do
fun = exception.function
args = exception.args
insp = Enum.map_join(args, ", ", &inspect/1)
{:arity, arity} = :erlang.fun_info(fun, :arity)
"#{inspect(fun)} with arity #{arity} called with #{count(length(args), insp)}"
end
defp count(0, _insp), do: "no arguments"
defp count(1, insp), do: "1 argument (#{insp})"
defp count(x, insp), do: "#{x} arguments (#{insp})"
end
defexception UndefinedFunctionError, [module: nil, function: nil, arity: nil] do
def message(exception) do
if exception.function do
formatted = Exception.format_mfa exception.module, exception.function, exception.arity
"undefined function: #{formatted}"
else
"undefined function"
end
end
end
defexception FunctionClauseError, [module: nil, function: nil, arity: nil] do
def message(exception) do
if exception.function do
formatted = Exception.format_mfa exception.module, exception.function, exception.arity
"no function clause matching in #{formatted}"
else
"no function clause matches"
end
end
end
defexception Protocol.UndefinedError, [protocol: nil, value: nil, description: nil] do
def message(exception) do
msg = "protocol #{inspect exception.protocol} not implemented for #{inspect exception.value}"
if exception.description do
msg <> ", " <> exception.description
else
msg
end
end
end
defexception ErlangError, [original: nil] do
def message(exception) do
"erlang error: #{inspect(exception.original)}"
end
end
defexception KeyError, key: nil, term: nil do
def message(exception) do
"key #{inspect exception.key} not found in: #{inspect exception.term}"
end
end
defexception UnicodeConversionError, [:encoded, :message] do
def exception(opts) do
UnicodeConversionError[
encoded: opts[:encoded],
message: "#{opts[:kind]} #{detail(opts[:rest])}"
]
end
defp detail(rest) when is_binary(rest) do
"encoding starting at #{inspect rest}"
end
defp detail([h|_]) do
"code point #{h}"
end
end
defexception Enum.OutOfBoundsError, message: "out of bounds error"
defexception Enum.EmptyError, message: "empty error"
defexception File.Error, [reason: nil, action: "", path: nil] do
def message(exception) do
formatted = iodata_to_binary(:file.format_error(reason exception))
"could not #{action exception} #{path exception}: #{formatted}"
end
end
defexception File.CopyError, [reason: nil, action: "", source: nil, destination: nil, on: nil] do
def message(exception) do
formatted = iodata_to_binary(:file.format_error(reason exception))
location = if on = on(exception), do: ". #{on}", else: ""
"could not #{action exception} from #{source exception} to " <>
"#{destination exception}#{location}: #{formatted}"
end
end
defmodule Exception do
@moduledoc """
Functions to work with and pretty print exceptions.
Functions to format throw/catch/exit and exceptions.
Note that stacktraces in Elixir are updated on throw,
errors and exits. For example, at any given moment,
`System.stacktrace` will return the stacktrace for the
last throw/error/exit that ocurred in the current process.
Finally note developers should not rely on the particular
format of the `format` functions provided by this module.
They may be changed in future releases in order to better
suit Elixir's tool chain. In other words, by using the
functions in this module it is guarantee you will format
exceptions as in the current Elixir version being used.
Do not rely on the particular format returned by the `format`
functions in this module. They may be changed in future releases
in order to better suit Elixir's tool chain. In other words,
by using the functions in this module it is guarantee you will
format exceptions as in the current Elixir version being used.
"""
@type t :: tuple
@typedoc "The exception type (as generated by defexception)"
@type t :: %{__struct__: module, __exception__: true}
@typedoc "The kind handled by formatting functions"
@type kind :: :error | :exit | :throw | {:EXIT, pid}
@type stacktrace :: [stacktrace_entry]
@type process ::
pid | atom | {atom, node} | {:global, term} | {:via, module, term}
@type stacktrace_entry ::
{module, function, arity_or_args, location} |
{function, arity_or_args, location}
@@ -182,24 +28,25 @@ defmodule Exception do
@typep arity_or_args :: non_neg_integer | list
@typep location :: Keyword.t
@callback exception(term) :: t
@callback message(t) :: String.t
@doc """
Returns true if the given argument is an exception.
"""
def exception?(exception) do
is_tuple(exception) and tuple_size(exception) > 1 and
:erlang.element(2, exception) == :__exception__
end
def exception?(%{__struct__: struct, __exception__: true}) when is_atom(struct), do: true
def exception?(_), do: false
@doc """
Gets the message for an exception.
"""
def message(exception) do
def message(%{__struct__: module, __exception__: true} = exception) when is_atom(module) do
try do
exception.message
module.message(exception)
rescue
e ->
raise ArgumentError, message:
"Got #{inspect e.__record__(:name)} with message " <>
raise ArgumentError,
"Got #{inspect e.__struct__} with message " <>
"\"#{message(e)}\" while retrieving message for #{inspect(exception)}"
end
end
@@ -220,16 +67,17 @@ defmodule Exception do
an empty stacktrace, `[]`, must be used.
"""
@spec normalize(:error, any, stacktrace) :: t
@spec normalize(:exit | :throw, any, stacktrace) :: any
# Generating a stacktrace is expensive, default to nil to only fetch it when
# needed.
@spec normalize(kind, payload, stacktrace) :: payload when payload: var
# Generating a stacktrace is expensive, default to nil
# to only fetch it when needed.
def normalize(kind, payload, stacktrace \\ nil)
def normalize(:error, exception, stacktrace) do
if exception?(exception) do
exception
else
normalize_error(exception, stacktrace)
ErlangError.normalize(exception, stacktrace)
end
end
@@ -237,60 +85,6 @@ defmodule Exception do
payload
end
defp normalize_error(:badarg, _stacktrace) do
ArgumentError[]
end
defp normalize_error(:badarith, _stacktrace) do
ArithmeticError[]
end
defp normalize_error(:system_limit, _stacktrace) do
SystemLimitError[]
end
defp normalize_error({:badarity, {fun, args}}, _stacktrace) do
BadArityError[function: fun, args: args]
end
defp normalize_error({:badfun, term}, _stacktrace) do
BadFunctionError[term: term]
end
defp normalize_error({:badstruct, struct, term}, _stacktrace) do
BadStructError[struct: struct, term: term]
end
defp normalize_error({:badmatch, term}, _stacktrace) do
MatchError[term: term]
end
defp normalize_error({:case_clause, term}, _stacktrace) do
CaseClauseError[term: term]
end
defp normalize_error({:try_clause, term}, _stacktrace) do
TryClauseError[term: term]
end
defp normalize_error(:undef, stacktrace) do
{mod, fun, arity} = from_stacktrace(stacktrace || :erlang.get_stacktrace)
UndefinedFunctionError[module: mod, function: fun, arity: arity]
end
defp normalize_error(:function_clause, stacktrace) do
{mod, fun, arity} = from_stacktrace(stacktrace || :erlang.get_stacktrace)
FunctionClauseError[module: mod, function: fun, arity: arity]
end
defp normalize_error({:badarg, payload}, _stacktrace) do
ArgumentError[message: "argument error: #{inspect(payload)}"]
end
defp normalize_error(other, _stacktrace) do
ErlangError[original: other]
end
@doc """
Normalizes and formats any throw, error and exit.
@@ -304,12 +98,12 @@ defmodule Exception do
not return the stacktrace corresponding to the exception
an empty stacktrace, `[]`, must be used.
"""
@spec format_banner(:error | :exit | :throw, any, stacktrace) :: String.t
@spec format_banner(kind, any, stacktrace | nil) :: String.t
def format_banner(kind, exception, stacktrace \\ nil)
def format_banner(:error, exception, stacktrace) do
exception = normalize(:error, exception, stacktrace)
"** (" <> inspect(exception.__record__(:name)) <> ") " <> message(exception)
"** (" <> inspect(exception.__struct__) <> ") " <> message(exception)
end
def format_banner(:throw, reason, _stacktrace) do
@@ -320,18 +114,34 @@ defmodule Exception do
"** (exit) " <> format_exit(reason, <<"\n ">>)
end
@doc """
Normalizes and formats an exception and stacktrace.
def format_banner({:EXIT, pid}, reason, _stacktrace) do
"** (EXIT from #{inspect pid}) " <> format_exit(reason, <<"\n ">>)
end
Behaves the same as `format_banner/3` but also includes
the formatted stacktrace.
@doc """
Normalizes and formats throw/errors/exits and stacktrace.
It relies on `format_banner/3` and `format_stacktrace/1`
to generate the final format.
Note that `{:EXIT, pid}` do not generate a stacktrace though
(as they are retrieved as messages without stacktraces).
"""
@spec format(:error | :exit | :throw, any, stacktrace) :: String.t
def format(kind, exception, stacktrace \\ :erlang.get_stacktrace()) do
message = format_banner(kind, exception, stacktrace)
@spec format(kind, any, stacktrace | nil) :: String.t
def format(kind, payload, stacktrace \\ nil)
def format({:EXIT, _} = kind, any, _) do
format_banner(kind, any)
end
def format(kind, payload, stacktrace) do
stacktrace = stacktrace || System.stacktrace
message = format_banner(kind, payload, stacktrace)
case stacktrace do
[] -> message
_ -> message <> "\n" <> format_stacktrace(stacktrace)
_ -> message <> "\n" <> format_stacktrace(stacktrace)
end
end
@@ -547,7 +357,7 @@ defmodule Exception do
defp format_application(module) do
case :application.get_application(module) do
{:ok, app} -> "(" <> atom_to_binary(app) <> ") "
{:ok, app} -> "(" <> Atom.to_string(app) <> ") "
:undefined -> ""
end
end
@@ -624,7 +434,7 @@ defmodule Exception do
end
defp format_arity(arity) when is_integer(arity) do
"/" <> integer_to_binary(arity)
"/" <> Integer.to_string(arity)
end
@doc """
@@ -662,6 +472,320 @@ defmodule Exception do
defp format_location(opts) when is_list(opts) do
format_file_line Keyword.get(opts, :file), Keyword.get(opts, :line), " "
end
end
# Some exceptions implement `message/1` instead of `exception/1` mostly
# for bootstrap reasons. It is recommended for applications to implement
# `exception/1` instead of `message/1` as described in `defexception/1`
# docs.
defmodule RuntimeError do
defexception message: "runtime error"
def exception(msg) when is_binary(msg) do
%RuntimeError{message: msg}
end
def exception(arg) do
super(arg)
end
end
defmodule ArgumentError do
defexception message: "argument error"
def exception(msg) when is_binary(msg) do
%ArgumentError{message: msg}
end
def exception(arg) do
super(arg)
end
end
defmodule ArithmeticError do
defexception []
def message(_) do
"bad argument in arithmetic expression"
end
end
defmodule SystemLimitError do
defexception []
def message(_) do
"a system limit has been reached"
end
end
defmodule SyntaxError do
defexception [file: nil, line: nil, description: "syntax error"]
def message(exception) do
Exception.format_file_line(Path.relative_to_cwd(exception.file), exception.line) <>
" " <> exception.description
end
end
defmodule TokenMissingError do
defexception [file: nil, line: nil, description: "expression is incomplete"]
def message(exception) do
Exception.format_file_line(Path.relative_to_cwd(exception.file), exception.line) <>
" " <> exception.description
end
end
defmodule CompileError do
defexception [file: nil, line: nil, description: "compile error"]
def message(exception) do
Exception.format_file_line(Path.relative_to_cwd(exception.file), exception.line) <>
" " <> exception.description
end
end
defmodule BadFunctionError do
defexception [term: nil]
def message(exception) do
"expected a function, got: #{inspect(exception.term)}"
end
end
defmodule BadStructError do
defexception [struct: nil, term: nil]
def message(exception) do
"expected a struct named #{inspect(exception.struct)}, got: #{inspect(exception.term)}"
end
end
defmodule MatchError do
defexception [term: nil]
def message(exception) do
"no match of right hand side value: #{inspect(exception.term)}"
end
end
defmodule CaseClauseError do
defexception [term: nil]
def message(exception) do
"no case clause matching: #{inspect(exception.term)}"
end
end
defmodule CondClauseError do
defexception []
def message(_exception) do
"no cond clause evaluated to a true value"
end
end
defmodule TryClauseError do
defexception [term: nil]
def message(exception) do
"no try clause matching: #{inspect(exception.term)}"
end
end
defmodule BadArityError do
defexception [function: nil, args: nil]
def message(exception) do
fun = exception.function
args = exception.args
insp = Enum.map_join(args, ", ", &inspect/1)
{:arity, arity} = :erlang.fun_info(fun, :arity)
"#{inspect(fun)} with arity #{arity} called with #{count(length(args), insp)}"
end
defp count(0, _insp), do: "no arguments"
defp count(1, insp), do: "1 argument (#{insp})"
defp count(x, insp), do: "#{x} arguments (#{insp})"
end
defmodule UndefinedFunctionError do
defexception [module: nil, function: nil, arity: nil]
def message(exception) do
if exception.function do
formatted = Exception.format_mfa exception.module, exception.function, exception.arity
"undefined function: #{formatted}"
else
"undefined function"
end
end
end
defmodule FunctionClauseError do
defexception [module: nil, function: nil, arity: nil]
def message(exception) do
if exception.function do
formatted = Exception.format_mfa exception.module, exception.function, exception.arity
"no function clause matching in #{formatted}"
else
"no function clause matches"
end
end
end
defmodule Code.LoadError do
defexception [:file, :message]
def exception(opts) do
file = Keyword.fetch!(opts, :file)
%Code.LoadError{message: "could not load #{file}", file: file}
end
end
defmodule Protocol.UndefinedError do
defexception [protocol: nil, value: nil, description: nil]
def message(exception) do
msg = "protocol #{inspect exception.protocol} not implemented for #{inspect exception.value}"
if exception.description do
msg <> ", " <> exception.description
else
msg
end
end
end
defmodule KeyError do
defexception key: nil, term: nil
def message(exception) do
"key #{inspect exception.key} not found in: #{inspect exception.term}"
end
end
defmodule UnicodeConversionError do
defexception [:encoded, :message]
def exception(opts) do
%UnicodeConversionError{
encoded: Keyword.fetch!(opts, :encoded),
message: "#{Keyword.fetch!(opts, :kind)} #{detail Keyword.fetch!(opts, :rest)}"
}
end
defp detail(rest) when is_binary(rest) do
"encoding starting at #{inspect rest}"
end
defp detail([h|_]) do
"code point #{h}"
end
end
defmodule Enum.OutOfBoundsError do
defexception []
def message(_) do
"out of bounds error"
end
end
defmodule Enum.EmptyError do
defexception []
def message(_) do
"empty error"
end
end
defmodule File.Error do
defexception [reason: nil, action: "", path: nil]
def message(exception) do
formatted = IO.iodata_to_binary(:file.format_error(exception.reason))
"could not #{exception.action} #{exception.path}: #{formatted}"
end
end
defmodule File.CopyError do
defexception [reason: nil, action: "", source: nil, destination: nil, on: nil]
def message(exception) do
formatted = IO.iodata_to_binary(:file.format_error(exception.reason))
location = if on = exception.on, do: ". #{on}", else: ""
"could not #{exception.action} from #{exception.source} to " <>
"#{exception.destination}#{location}: #{formatted}"
end
end
defmodule ErlangError do
defexception [original: nil]
def message(exception) do
"erlang error: #{inspect(exception.original)}"
end
@doc false
def normalize(:badarg, _stacktrace) do
%ArgumentError{}
end
def normalize(:badarith, _stacktrace) do
%ArithmeticError{}
end
def normalize(:system_limit, _stacktrace) do
%SystemLimitError{}
end
def normalize(:cond_clause, _stacktrace) do
%CondClauseError{}
end
def normalize({:badarity, {fun, args}}, _stacktrace) do
%BadArityError{function: fun, args: args}
end
def normalize({:badfun, term}, _stacktrace) do
%BadFunctionError{term: term}
end
def normalize({:badstruct, struct, term}, _stacktrace) do
%BadStructError{struct: struct, term: term}
end
def normalize({:badmatch, term}, _stacktrace) do
%MatchError{term: term}
end
def normalize({:case_clause, term}, _stacktrace) do
%CaseClauseError{term: term}
end
def normalize({:try_clause, term}, _stacktrace) do
%TryClauseError{term: term}
end
def normalize(:undef, stacktrace) do
{mod, fun, arity} = from_stacktrace(stacktrace || :erlang.get_stacktrace)
%UndefinedFunctionError{module: mod, function: fun, arity: arity}
end
def normalize(:function_clause, stacktrace) do
{mod, fun, arity} = from_stacktrace(stacktrace || :erlang.get_stacktrace)
%FunctionClauseError{module: mod, function: fun, arity: arity}
end
def normalize({:badarg, payload}, _stacktrace) do
%ArgumentError{message: "argument error: #{inspect(payload)}"}
end
def normalize(other, _stacktrace) do
%ErlangError{original: other}
end
defp from_stacktrace([{module, function, args, _}|_]) when is_list(args) do
{module, function, length(args)}
+178 -272
View File
@@ -1,144 +1,3 @@
require Record
defmodule File.Stat do
@moduledoc """
A struct responsible to hold file information.
In Erlang, this struct is represented by a `:file_info` record.
Therefore this module also provides functions for converting
in between the Erlang record and the Elixir struct.
Its fields are:
* `size` - Size of file in bytes.
* `type` - `:device`, `:directory`, `:regular`, `:other`. The type of the file.
* `access` - `:read`, `:write`, `:read_write`, `:none`. The current system access to
the file.
* `atime` - The last time the file was read.
* `mtime` - The last time the file was written.
* `ctime` - The interpretation of this time field depends on the operating
system. On Unix, it is the last time the file or the inode was
changed. In Windows, it is the create time.
* `mode` - The file permissions.
* `links` - The number of links to this file. This is always 1 for file
systems which have no concept of links.
* `major_device` - Identifies the file system where the file is located.
In windows, the number indicates a drive as follows:
0 means A:, 1 means B:, and so on.
* `minor_device` - Only valid for character devices on Unix. In all other
cases, this field is zero.
* `inode` - Gives the inode number. On non-Unix file systems, this field
will be zero.
* `uid` - Indicates the owner of the file.
* `gid` - Gives the group that the owner of the file belongs to. Will be
zero for non-Unix file systems.
The time type returned in `atime`, `mtime`, and `ctime` is dependent on the
time type set in options. `{:time, type}` where type can be `:local`,
`:universal`, or `:posix`. Default is `:local`.
"""
record = Record.extract(:file_info, from_lib: "kernel/include/file.hrl")
keys = :lists.map(&elem(&1, 0), record)
vals = :lists.map(&{&1, [], nil}, keys)
pairs = :lists.zip(keys, vals)
defstruct keys
@doc """
Converts a `File.Stat` struct to a `:file_info` record.
"""
def to_record(%File.Stat{unquote_splicing(pairs)}) do
{:file_info, unquote_splicing(vals)}
end
@doc """
Converts a `:file_info` record into a `File.Stat`.
"""
def from_record({:file_info, unquote_splicing(vals)}) do
%File.Stat{unquote_splicing(pairs)}
end
end
defmodule File.Stream do
@moduledoc """
Defines a `File.Stream` struct returned by `File.stream!/2`.
The following fields are public:
* `path` - the file path
* `modes` - the file modes
* `raw` - a boolean indicating if bin functions should be used
* `line_or_bytes` - if reading should read lines or a given amount of bytes
"""
defstruct path: nil, modes: [], line_or_bytes: :line, raw: true
defimpl Collectable do
def empty(stream) do
stream
end
def into(%{path: path, modes: modes, raw: raw} = stream) do
modes = for mode <- modes, not mode in [:read], do: mode
case :file.open(path, [:write|modes]) do
{:ok, device} ->
{:ok, into(device, stream, raw)}
{:error, reason} ->
raise File.Error, reason: reason, action: "stream", path: path
end
end
defp into(device, stream, raw) do
fn
:ok, {:cont, x} ->
case raw do
true -> IO.binwrite(device, x)
false -> IO.write(device, x)
end
:ok, :done ->
:file.close(device)
stream
:ok, :halt ->
:file.close(device)
end
end
end
defimpl Enumerable do
def reduce(%{path: path, modes: modes, line_or_bytes: line_or_bytes, raw: raw}, acc, fun) do
modes = for mode <- modes, not mode in [:write, :append], do: mode
start_fun =
fn ->
case :file.open(path, modes) do
{:ok, device} -> device
{:error, reason} ->
raise File.Error, reason: reason, action: "stream", path: path
end
end
next_fun =
case raw do
true -> &IO.each_binstream(&1, line_or_bytes)
false -> &IO.each_stream(&1, line_or_bytes)
end
Stream.resource(start_fun, next_fun, &:file.close/1).(acc, fun)
end
def count(_stream) do
{:error, __MODULE__}
end
def member?(_stream, _term) do
{:error, __MODULE__}
end
end
end
defmodule File do
@moduledoc ~S"""
This module contains functions to manipulate files.
@@ -211,8 +70,7 @@ defmodule File do
about such options and other performance considerations.
"""
alias :file, as: F
alias :filelib, as: FL
alias :file, as: F
@type posix :: :file.posix()
@type io_device :: :file.io_device()
@@ -228,7 +86,7 @@ defmodule File do
"""
@spec regular?(Path.t) :: boolean
def regular?(path) do
FL.is_regular(String.from_char_data!(path))
:elixir_utils.read_file_type(IO.chardata_to_string(path)) == {:ok, :regular}
end
@doc """
@@ -236,7 +94,7 @@ defmodule File do
"""
@spec dir?(Path.t) :: boolean
def dir?(path) do
FL.is_dir(String.from_char_data!(path))
:elixir_utils.read_file_type(IO.chardata_to_string(path)) == {:ok, :directory}
end
@doc """
@@ -258,7 +116,7 @@ defmodule File do
"""
@spec exists?(Path.t) :: boolean
def exists?(path) do
match?({:ok, _}, F.read_file_info(String.from_char_data!(path)))
match?({:ok, _}, F.read_file_info(IO.chardata_to_string(path)))
end
@doc """
@@ -267,16 +125,17 @@ defmodule File do
Typical error reasons are:
* :eacces - Missing search or write permissions for the parent 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.
* `:eacces` - missing search or write permissions for the parent
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
"""
@spec mkdir(Path.t) :: :ok | {:error, posix}
def mkdir(path) do
F.make_dir(String.from_char_data!(path))
F.make_dir(IO.chardata_to_string(path))
end
@doc """
@@ -284,7 +143,7 @@ defmodule File do
"""
@spec mkdir!(Path.t) :: :ok | no_return
def mkdir!(path) do
path = String.from_char_data!(path)
path = IO.chardata_to_string(path)
case mkdir(path) do
:ok -> :ok
{:error, reason} ->
@@ -298,13 +157,38 @@ defmodule File do
Typical error reasons are:
* :eacces - Missing search or write permissions for the parent directories of `path`.
* :enospc - There is a no space left on the device.
* :enotdir - A component of `path` is not a directory.
* `:eacces` - missing search or write permissions for the parent
directories of `path`
* `:enospc` - there is a no space left on the device
* `:enotdir` - a component of `path` is not a directory
"""
@spec mkdir_p(Path.t) :: :ok | {:error, posix}
def mkdir_p(path) do
FL.ensure_dir(Path.join(path, "."))
do_mkdir_p(IO.chardata_to_string(path))
end
defp do_mkdir_p("/") do
:ok
end
defp do_mkdir_p(path) do
if dir?(path) do
:ok
else
parent = Path.dirname(path)
if parent == path do
# Protect against infinite loop
{:error, :einval}
else
_ = do_mkdir_p(parent)
case :file.make_dir(path) do
{:error, :eexist} = error ->
if dir?(path), do: :ok, else: error
other ->
other
end
end
end
end
@doc """
@@ -312,7 +196,7 @@ defmodule File do
"""
@spec mkdir_p!(Path.t) :: :ok | no_return
def mkdir_p!(path) do
path = String.from_char_data!(path)
path = IO.chardata_to_string(path)
case mkdir_p(path) do
:ok -> :ok
{:error, reason} ->
@@ -326,19 +210,19 @@ defmodule File do
Typical error reasons:
* :enoent - The file does not exist.
* :eacces - Missing permission for reading the file,
or for searching one of the parent directories.
* :eisdir - The named file is a directory.
* :enotdir - A component of the file name is not a directory.
On some platforms, `:enoent` is returned instead.
* :enomem - There is not enough memory for the contents of the file.
* `:enoent` - the file does not exist
* `:eacces` - missing permission for reading the file,
or for searching one of the parent directories
* `:eisdir` - the named file is a directory
* `:enotdir` - a component of the file name is not a directory;
on some platforms, `:enoent` is returned instead
* `:enomem` - there is not enough memory for the contents of the file
You can use `:file.format_error/1` to get a descriptive string of the error.
"""
@spec read(Path.t) :: {:ok, binary} | {:error, posix}
def read(path) do
F.read_file(String.from_char_data!(path))
F.read_file(IO.chardata_to_string(path))
end
@doc """
@@ -347,7 +231,7 @@ defmodule File do
"""
@spec read!(Path.t) :: binary | no_return
def read!(path) do
path = String.from_char_data!(path)
path = IO.chardata_to_string(path)
case read(path) do
{:ok, binary} ->
binary
@@ -359,20 +243,19 @@ defmodule File do
@doc """
Returns information about the `path`. If it exists, it
returns a `{:ok, info}` tuple, where info is a
`File.Stat` record. Returns `{:error, reason}` with
`File.Stat` struct. Returns `{:error, reason}` with
the same reasons as `read/1` if a failure occurs.
## Options
The accepted options are:
* `:time` if the time should be `:local`, `:universal` or `:posix`.
Default is `:local`.
* `:time` - `:local | :universal | :posix`; default: `:local`
"""
@spec stat(Path.t, stat_options) :: {:ok, File.Stat.t} | {:error, posix}
def stat(path, opts \\ []) do
case F.read_file_info(String.from_char_data!(path), opts) do
case F.read_file_info(IO.chardata_to_string(path), opts) do
{:ok, fileinfo} ->
{:ok, File.Stat.from_record(fileinfo)}
error ->
@@ -386,7 +269,7 @@ defmodule File do
"""
@spec stat!(Path.t, stat_options) :: File.Stat.t | no_return
def stat!(path, opts \\ []) do
path = String.from_char_data!(path)
path = IO.chardata_to_string(path)
case stat(path, opts) do
{:ok, info} -> info
{:error, reason} ->
@@ -400,7 +283,7 @@ defmodule File do
"""
@spec write_stat(Path.t, File.Stat.t, stat_options) :: :ok | {:error, posix}
def write_stat(path, stat, opts \\ []) do
F.write_file_info(String.from_char_data!(path), File.Stat.to_record(stat), opts)
F.write_file_info(IO.chardata_to_string(path), File.Stat.to_record(stat), opts)
end
@doc """
@@ -409,7 +292,7 @@ defmodule File do
"""
@spec write_stat!(Path.t, File.Stat.t, stat_options) :: :ok | no_return
def write_stat!(path, stat, opts \\ []) do
path = String.from_char_data!(path)
path = IO.chardata_to_string(path)
case write_stat(path, stat, opts) do
:ok -> :ok
{:error, reason} ->
@@ -423,7 +306,7 @@ defmodule File do
"""
@spec touch(Path.t, :calendar.datetime) :: :ok | {:error, posix}
def touch(path, time \\ :calendar.local_time) do
path = String.from_char_data!(path)
path = IO.chardata_to_string(path)
case F.change_time(path, time) do
{:error, :enoent} ->
write(path, "")
@@ -439,7 +322,7 @@ defmodule File do
"""
@spec touch!(Path.t, :calendar.datetime) :: :ok | no_return
def touch!(path, time \\ :calendar.local_time) do
path = String.from_char_data!(path)
path = IO.chardata_to_string(path)
case touch(path, time) do
:ok -> :ok
{:error, reason} ->
@@ -482,7 +365,7 @@ defmodule File do
"""
@spec copy(Path.t, Path.t, pos_integer | :infinity) :: {:ok, non_neg_integer} | {:error, posix}
def copy(source, destination, bytes_count \\ :infinity) do
F.copy(String.from_char_data!(source), String.from_char_data!(destination), bytes_count)
F.copy(IO.chardata_to_string(source), IO.chardata_to_string(destination), bytes_count)
end
@doc """
@@ -491,8 +374,8 @@ defmodule File do
"""
@spec copy!(Path.t, Path.t, pos_integer | :infinity) :: non_neg_integer | no_return
def copy!(source, destination, bytes_count \\ :infinity) do
source = String.from_char_data!(source)
destination = String.from_char_data!(destination)
source = IO.chardata_to_string(source)
destination = IO.chardata_to_string(destination)
case copy(source, destination, bytes_count) do
{:ok, bytes_count} -> bytes_count
{:error, reason} ->
@@ -522,8 +405,8 @@ defmodule File do
"""
@spec cp(Path.t, Path.t, (Path.t, Path.t -> boolean)) :: :ok | {:error, posix}
def cp(source, destination, callback \\ fn(_, _) -> true end) do
source = String.from_char_data!(source)
destination = String.from_char_data!(destination)
source = IO.chardata_to_string(source)
destination = IO.chardata_to_string(destination)
case do_cp_file(source, destination, callback, []) do
{:error, reason, _} -> {:error, reason}
@@ -537,8 +420,8 @@ defmodule File do
"""
@spec cp!(Path.t, Path.t, (Path.t, Path.t -> boolean)) :: :ok | no_return
def cp!(source, destination, callback \\ fn(_, _) -> true end) do
source = String.from_char_data!(source)
destination = String.from_char_data!(destination)
source = IO.chardata_to_string(source)
destination = IO.chardata_to_string(destination)
case cp(source, destination, callback) do
:ok -> :ok
@@ -593,8 +476,8 @@ defmodule File do
"""
@spec cp_r(Path.t, Path.t, (Path.t, Path.t -> boolean)) :: {:ok, [binary]} | {:error, posix, binary}
def cp_r(source, destination, callback \\ fn(_, _) -> true end) when is_function(callback) do
source = String.from_char_data!(source)
destination = String.from_char_data!(destination)
source = IO.chardata_to_string(source)
destination = IO.chardata_to_string(destination)
case do_cp_r(source, destination, callback, []) do
{:error, _, _} = error -> error
@@ -608,8 +491,8 @@ defmodule File do
"""
@spec cp_r!(Path.t, Path.t, (Path.t, Path.t -> boolean)) :: [binary] | no_return
def cp_r!(source, destination, callback \\ fn(_, _) -> true end) do
source = String.from_char_data!(source)
destination = String.from_char_data!(destination)
source = IO.chardata_to_string(source)
destination = IO.chardata_to_string(destination)
case cp_r(source, destination, callback) do
{:ok, files} -> files
@@ -622,7 +505,7 @@ defmodule File do
# src may be a file or a directory, dest is definitely
# a directory. Returns nil unless an error is found.
defp do_cp_r(src, dest, callback, acc) when is_list(acc) do
case :elixir_utils.file_type(src) do
case :elixir_utils.read_link_type(src) do
{:ok, :regular} ->
do_cp_file(src, dest, callback, acc)
{:ok, :symlink} ->
@@ -713,19 +596,19 @@ defmodule File do
Typical error reasons are:
* :enoent - A component of the file name does not exist.
* :enotdir - A component of the file name is not a directory.
On some platforms, enoent is returned instead.
* :enospc - There is a no space left on the device.
* :eacces - Missing permission for writing the file or searching one of the parent directories.
* :eisdir - The named file is a directory.
* `:enoent` - a component of the file name does not exist
* `:enotdir` - a component of the file name is not a directory;
on some platforms, enoent is returned instead
* `:enospc` - there is a no space left on the device
* `:eacces` - missing permission for writing the file or searching one of
the parent directories
* `:eisdir` - the named file is a directory
The writing is automatically done in `:raw` mode. Check
`File.open/2` for other available options.
Check `File.open/2` for other available options.
"""
@spec write(Path.t, iodata, list) :: :ok | {:error, posix}
def write(path, content, modes \\ []) do
F.write_file(String.from_char_data!(path), content, [:raw|modes])
F.write_file(IO.chardata_to_string(path), content, modes)
end
@doc """
@@ -733,7 +616,7 @@ defmodule File do
"""
@spec write!(Path.t, iodata, list) :: :ok | no_return
def write!(path, content, modes \\ []) do
path = String.from_char_data!(path)
path = IO.chardata_to_string(path)
case F.write_file(path, content, modes) do
:ok -> :ok
{:error, reason} ->
@@ -743,16 +626,18 @@ defmodule File do
@doc """
Tries to delete the file `path`.
Returns `:ok` if successful, or `{:error, reason}` if an error occurs.
Note the file is deleted even if in read-only mode.
Typical error reasons are:
* :enoent - The file does not exist.
* :eacces - Missing permission for the file or one of its parents.
* :eperm - The file is a directory and user is not super-user.
* :enotdir - A component of the file name is not a directory.
On some platforms, enoent is returned instead.
* :einval - Filename had an improper type, such as tuple.
* `:enoent` - the file does not exist
* `:eacces` - missing permission for the file or one of its parents
* `:eperm` - the file is a directory and user is not super-user
* `:enotdir` - a component of the file name is not a directory;
on some platforms, enoent is returned instead
* `:einval` - filename had an improper type, such as tuple
## Examples
@@ -765,7 +650,27 @@ defmodule File do
"""
@spec rm(Path.t) :: :ok | {:error, posix}
def rm(path) do
F.delete(String.from_char_data!(path))
path = IO.chardata_to_string(path)
case F.delete(path) do
:ok ->
:ok
{:error, :eacces} = e ->
change_mode_windows(path) || e
{:error, _} = e ->
e
end
end
defp change_mode_windows(path) do
if match? {:win32, _}, :os.type do
case F.read_file_info(IO.chardata_to_string(path)) do
{:ok, file_info} when elem(file_info, 3) in [:read, :none] ->
File.chmod(path, (elem(file_info, 7) + 0200))
F.delete(path)
_ ->
nil
end
end
end
@doc """
@@ -773,7 +678,7 @@ defmodule File do
"""
@spec rm!(Path.t) :: :ok | no_return
def rm!(path) do
path = String.from_char_data!(path)
path = IO.chardata_to_string(path)
case rm(path) do
:ok -> :ok
{:error, reason} ->
@@ -796,7 +701,7 @@ defmodule File do
"""
@spec rmdir(Path.t) :: :ok | {:error, posix}
def rmdir(path) do
F.del_dir(String.from_char_data!(path))
F.del_dir(IO.chardata_to_string(path))
end
@doc """
@@ -804,7 +709,7 @@ defmodule File do
"""
@spec rmdir!(Path.t) :: :ok | {:error, posix}
def rmdir!(path) do
path = String.from_char_data!(path)
path = IO.chardata_to_string(path)
case rmdir(path) do
:ok -> :ok
{:error, reason} ->
@@ -832,7 +737,7 @@ defmodule File do
"""
@spec rm_rf(Path.t) :: {:ok, [binary]} | {:error, posix, binary}
def rm_rf(path) do
do_rm_rf(String.from_char_data!(path), {:ok, []})
do_rm_rf(IO.chardata_to_string(path), {:ok, []})
end
defp do_rm_rf(path, {:ok, _} = entry) do
@@ -886,9 +791,9 @@ defmodule File do
end
defp safe_list_dir(path) do
case :elixir_utils.file_type(path) do
case :elixir_utils.read_link_type(path) do
{:ok, :symlink} ->
case :elixir_utils.file_type(path, :read_file_info) do
case :elixir_utils.read_file_type(path) do
{:ok, :directory} -> {:ok, :directory}
_ -> {:ok, :regular}
end
@@ -907,7 +812,7 @@ defmodule File do
"""
@spec rm_rf!(Path.t) :: [binary] | no_return
def rm_rf!(path) do
path = String.from_char_data!(path)
path = IO.chardata_to_string(path)
case rm_rf(path) do
{:ok, files} -> files
{:error, reason, _} ->
@@ -928,43 +833,55 @@ defmodule File do
The allowed modes:
* `:read` - The file, which must exist, is opened for reading.
* `:read` - the file, which must exist, is opened for reading.
* `:write` - The file is opened for writing. It is created if it does not exist.
If the file exists, and if write is not combined with read, the file will be truncated.
* `:write` - the file is opened for writing. It is created if it does not
exist.
* `:append` - The file will be opened for writing, and it will be created if it does not exist.
Every write operation to a file opened with append will take place at the end of the file.
If the file does exists, and if write is not combined with read, the file
will be truncated.
* `:exclusive` - The file, when opened for writing, is created if it does not exist.
If the file exists, open will return `{:error, :eexist}`.
* `:append` - the file will be opened for writing, and it will be created
if it does not exist. Every write operation to a file opened with append
will take place at the end of the file.
* `:char_list` - When this term is given, read operations on the file will return char lists rather than binaries;
* `:exclusive` - the file, when opened for writing, is created if it does
not exist. If the file exists, open will return `{:error, :eexist}`.
* `:compressed` - Makes it possible to read or write gzip compressed files.
The compressed option must be combined with either read or write, but not both.
Note that the file size obtained with `stat/1` will most probably not
match the number of bytes that can be read from a compressed file.
* `:char_list` - when this term is given, read operations on the file will
return char lists rather than binaries.
* `:utf8` - This option denotes how data is actually stored in the disk file and
makes the file perform automatic translation of characters to and from utf-8.
If data is sent to a file in a format that cannot be converted to the utf-8
or if data is read by a function that returns data in a format that cannot cope
with the character range of the data, an error occurs and the file will be closed.
* `:compressed` - makes it possible to read or write gzip compressed files.
The compressed option must be combined with either read or write, but not
both. Note that the file size obtained with `stat/1` will most probably
not match the number of bytes that can be read from a compressed file.
* `:utf8` - this option denotes how data is actually stored in the disk
file and makes the file perform automatic translation of characters to
and from utf-8.
If data is sent to a file in a format that cannot be converted to the
utf-8 or if data is read by a function that returns data in a format that
cannot cope with the character range of the data, an error occurs and the
file will be closed.
Check http://www.erlang.org/doc/man/file.html#open-2 for more information about
other options like `:read_ahead` and `:delayed_write`.
This function returns:
* `{:ok, io_device}` - The file has been opened in the requested mode.
`io_device` is actually the pid of the process which handles the file.
This process is linked to the process which originally opened the file.
If any process to which the `io_device` is linked terminates, the file will
be closed and the process itself will be terminated. An `io_device` returned
from this call can be used as an argument to the `IO` module functions.
* `{:ok, io_device}` - the file has been opened in the requested mode.
* `{:error, reason}` - The file could not be opened.
`io_device` is actually the pid of the process which handles the file.
This process is linked to the process which originally opened the file.
If any process to which the `io_device` is linked terminates, the file
will be closed and the process itself will be terminated.
An `io_device` returned from this call can be used as an argument to the
`IO` module functions.
* `{:error, reason}` - the file could not be opened.
## Examples
@@ -977,7 +894,7 @@ defmodule File do
def open(path, modes \\ [])
def open(path, modes) when is_list(modes) do
F.open(String.from_char_data!(path), open_defaults(modes, true))
F.open(IO.chardata_to_string(path), open_defaults(modes, true))
end
def open(path, function) when is_function(function) do
@@ -989,14 +906,15 @@ defmodule File do
The file is opened, given to the function as argument and
automatically closed after the function returns, regardless
if there was an error or not.
if there was an error when executing the function.
It returns `{:ok, function_result}` in case of success,
`{:error, reason}` otherwise.
Do not use this function with `:delayed_write` option
since automatically closing the file may fail
(as writes are delayed).
This function expects the file to be closed with success,
which is usually the case unless the `:delayed_write` option
is given. For this reason, we do not recommend passing
`:delayed_write` to this function.
## Examples
@@ -1020,11 +938,12 @@ defmodule File do
@doc """
Same as `open/2` but raises an error if file could not be opened.
Returns the `io_device` otherwise.
"""
@spec open!(Path.t, list) :: io_device | no_return
def open!(path, modes \\ []) do
path = String.from_char_data!(path)
path = IO.chardata_to_string(path)
case open(path, modes) do
{:ok, device} -> device
{:error, reason} ->
@@ -1034,11 +953,12 @@ defmodule File do
@doc """
Same as `open/3` but raises an error if file could not be opened.
Returns the function result otherwise.
"""
@spec open!(Path.t, list, (io_device -> res)) :: res | no_return when res: var
def open!(path, modes, function) do
path = String.from_char_data!(path)
path = IO.chardata_to_string(path)
case open(path, modes, function) do
{:ok, device} -> device
{:error, reason} ->
@@ -1057,7 +977,7 @@ defmodule File do
@spec cwd() :: {:ok, binary} | {:error, posix}
def cwd() do
case F.get_cwd do
{:ok, base} -> {:ok, String.from_char_data!(base)}
{:ok, base} -> {:ok, IO.chardata_to_string(base)}
{:error, _} = error -> error
end
end
@@ -1068,7 +988,7 @@ defmodule File do
@spec cwd!() :: binary | no_return
def cwd!() do
case F.get_cwd do
{:ok, cwd} -> String.from_char_data!(cwd)
{:ok, cwd} -> IO.chardata_to_string(cwd)
{:error, reason} ->
raise File.Error, reason: reason, action: "get current working directory"
end
@@ -1081,7 +1001,7 @@ defmodule File do
"""
@spec cd(Path.t) :: :ok | {:error, posix}
def cd(path) do
F.set_cwd(String.from_char_data!(path))
F.set_cwd(IO.chardata_to_string(path))
end
@doc """
@@ -1089,7 +1009,7 @@ defmodule File do
"""
@spec cd!(Path.t) :: :ok | no_return
def cd!(path) do
path = String.from_char_data!(path)
path = IO.chardata_to_string(path)
case F.set_cwd(path) do
:ok -> :ok
{:error, reason} ->
@@ -1124,8 +1044,8 @@ defmodule File do
"""
@spec ls(Path.t) :: {:ok, [binary]} | {:error, posix}
def ls(path \\ ".") do
case F.list_dir(String.from_char_data!(path)) do
{:ok, file_list} -> {:ok, Enum.map(file_list, &String.from_char_data!/1)}
case F.list_dir(IO.chardata_to_string(path)) do
{:ok, file_list} -> {:ok, Enum.map(file_list, &IO.chardata_to_string/1)}
{:error, _} = error -> error
end
end
@@ -1136,7 +1056,7 @@ defmodule File do
"""
@spec ls!(Path.t) :: [binary] | no_return
def ls!(path \\ ".") do
path = String.from_char_data!(path)
path = IO.chardata_to_string(path)
case ls(path) do
{:ok, value} -> value
{:error, reason} ->
@@ -1184,21 +1104,7 @@ defmodule File do
"""
def stream!(path, modes \\ [], line_or_bytes \\ :line) do
modes = open_defaults(modes, true)
raw = :lists.keyfind(:encoding, 1, modes) == false
modes =
if raw do
if :lists.keyfind(:read_ahead, 1, modes) do
[:raw|modes]
else
[:raw, :read_ahead|modes]
end
else
modes
end
%File.Stream{path: String.from_char_data!(path), modes: modes,
raw: raw, line_or_bytes: line_or_bytes}
File.Stream.__build__(IO.chardata_to_string(path), modes, line_or_bytes)
end
@doc """
@@ -1208,7 +1114,7 @@ defmodule File do
"""
@spec chmod(Path.t, integer) :: :ok | {:error, posix}
def chmod(path, mode) do
F.change_mode(String.from_char_data!(path), mode)
F.change_mode(IO.chardata_to_string(path), mode)
end
@doc """
@@ -1216,7 +1122,7 @@ defmodule File do
"""
@spec chmod!(Path.t, integer) :: :ok | no_return
def chmod!(path, mode) do
path = String.from_char_data!(path)
path = IO.chardata_to_string(path)
case chmod(path, mode) do
:ok -> :ok
{:error, reason} ->
@@ -1231,7 +1137,7 @@ defmodule File do
"""
@spec chgrp(Path.t, integer) :: :ok | {:error, posix}
def chgrp(path, gid) do
F.change_group(String.from_char_data!(path), gid)
F.change_group(IO.chardata_to_string(path), gid)
end
@doc """
@@ -1239,7 +1145,7 @@ defmodule File do
"""
@spec chgrp!(Path.t, integer) :: :ok | no_return
def chgrp!(path, gid) do
path = String.from_char_data!(path)
path = IO.chardata_to_string(path)
case chgrp(path, gid) do
:ok -> :ok
{:error, reason} ->
@@ -1254,7 +1160,7 @@ defmodule File do
"""
@spec chown(Path.t, integer) :: :ok | {:error, posix}
def chown(path, uid) do
F.change_owner(String.from_char_data!(path), uid)
F.change_owner(IO.chardata_to_string(path), uid)
end
@doc """
@@ -1262,7 +1168,7 @@ defmodule File do
"""
@spec chown!(Path.t, integer) :: :ok | no_return
def chown!(path, uid) do
path = String.from_char_data!(path)
path = IO.chardata_to_string(path)
case chown(path, uid) do
:ok -> :ok
{:error, reason} ->
+74
View File
@@ -0,0 +1,74 @@
require Record
defmodule File.Stat do
@moduledoc """
A struct responsible to hold file information.
In Erlang, this struct is represented by a `:file_info` record.
Therefore this module also provides functions for converting
in between the Erlang record and the Elixir struct.
Its fields are:
* `size` - size of file in bytes.
* `type` - `:device | :directory | :regular | :other`; the type of the
file.
* `access` - `:read | :write | :read_write | :none`; the current system
access to the file.
* `atime` - the last time the file was read.
* `mtime` - the last time the file was written.
* `ctime` - the interpretation of this time field depends on the operating
system. On Unix, it is the last time the file or the inode was changed.
In Windows, it is the time of creation.
* `mode` - the file permissions.
* `links` - the number of links to this file. This is always 1 for file
systems which have no concept of links.
* `major_device` - identifies the file system where the file is located.
In windows, the number indicates a drive as follows: 0 means A:, 1 means
B:, and so on.
* `minor_device` - only valid for character devices on Unix. In all other
cases, this field is zero.
* `inode` - gives the inode number. On non-Unix file systems, this field
will be zero.
* `uid` - indicates the owner of the file.
* `gid` - gives the group that the owner of the file belongs to. Will be
zero for non-Unix file systems.
The time type returned in `atime`, `mtime`, and `ctime` is dependent on the
time type set in options. `{:time, type}` where type can be `:local`,
`:universal`, or `:posix`. Default is `:local`.
"""
record = Record.extract(:file_info, from_lib: "kernel/include/file.hrl")
keys = :lists.map(&elem(&1, 0), record)
vals = :lists.map(&{&1, [], nil}, keys)
pairs = :lists.zip(keys, vals)
defstruct keys
@doc """
Converts a `File.Stat` struct to a `:file_info` record.
"""
def to_record(%File.Stat{unquote_splicing(pairs)}) do
{:file_info, unquote_splicing(vals)}
end
@doc """
Converts a `:file_info` record into a `File.Stat`.
"""
def from_record({:file_info, unquote_splicing(vals)}) do
%File.Stat{unquote_splicing(pairs)}
end
end
+96
View File
@@ -0,0 +1,96 @@
defmodule File.Stream do
@moduledoc """
Defines a `File.Stream` struct returned by `File.stream!/3`.
The following fields are public:
* `path` - the file path
* `modes` - the file modes
* `raw` - a boolean indicating if bin functions should be used
* `line_or_bytes` - if reading should read lines or a given amount of bytes
"""
defstruct path: nil, modes: [], line_or_bytes: :line, raw: true
@doc false
def __build__(path, modes, line_or_bytes) do
raw = :lists.keyfind(:encoding, 1, modes) == false
modes =
if raw do
if :lists.keyfind(:read_ahead, 1, modes) == {:read_ahead, false} do
[:raw|modes]
else
[:raw, :read_ahead|modes]
end
else
modes
end
%File.Stream{path: path, modes: modes, raw: raw, line_or_bytes: line_or_bytes}
end
defimpl Collectable do
def empty(stream) do
stream
end
def into(%{path: path, modes: modes, raw: raw} = stream) do
modes = for mode <- modes, not mode in [:read], do: mode
case :file.open(path, [:write|modes]) do
{:ok, device} ->
{:ok, into(device, stream, raw)}
{:error, reason} ->
raise File.Error, reason: reason, action: "stream", path: path
end
end
defp into(device, stream, raw) do
fn
:ok, {:cont, x} ->
case raw do
true -> IO.binwrite(device, x)
false -> IO.write(device, x)
end
:ok, :done ->
:file.close(device)
stream
:ok, :halt ->
:file.close(device)
end
end
end
defimpl Enumerable do
def reduce(%{path: path, modes: modes, line_or_bytes: line_or_bytes, raw: raw}, acc, fun) do
modes = for mode <- modes, not mode in [:write, :append], do: mode
start_fun =
fn ->
case :file.open(path, modes) do
{:ok, device} -> device
{:error, reason} ->
raise File.Error, reason: reason, action: "stream", path: path
end
end
next_fun =
case raw do
true -> &IO.each_binstream(&1, line_or_bytes)
false -> &IO.each_stream(&1, line_or_bytes)
end
Stream.resource(start_fun, next_fun, &:file.close/1).(acc, fun)
end
def count(_stream) do
{:error, __MODULE__}
end
def member?(_stream, _term) do
{:error, __MODULE__}
end
end
end
+82
View File
@@ -169,4 +169,86 @@ defmodule Float do
def round(number, precision) when is_float(number) and is_integer(precision) and precision in 0..15 do
Kernel.round(number * :math.pow(10, precision)) / :math.pow(10, precision)
end
@doc """
Returns a char list which corresponds to the text representation of the given float.
Inlined by the compiler.
## Examples
iex> Float.to_char_list(7.0)
'7.00000000000000000000e+00'
"""
@spec to_char_list(float) :: char_list
def to_char_list(number) do
:erlang.float_to_list(number)
end
@doc """
Returns a list which corresponds to the text representation
of `float`.
## Options
* `:decimals` — number of decimal points to show
* `:scientific` — number of decimal points to show, in scientific format
* `:compact` — when true, use the most compact representation (ignored
with the `scientific` option)
## Examples
iex> Float.to_char_list 7.1, [decimals: 2, compact: true]
'7.1'
"""
@spec to_char_list(float, list) :: char_list
def to_char_list(float, options) do
:erlang.float_to_list(float, expand_compact(options))
end
@doc """
Returns a binary which corresponds to the text representation
of `some_float`.
Inlined by the compiler.
## Examples
iex> Float.to_string(7.0)
"7.00000000000000000000e+00"
"""
@spec to_string(float) :: String.t
def to_string(some_float) do
:erlang.float_to_binary(some_float)
end
@doc """
Returns a binary which corresponds to the text representation
of `float`.
## Options
* `:decimals` — number of decimal points to show
* `:scientific` — number of decimal points to show, in scientific format
* `:compact` — when true, use the most compact representation (ignored
with the `scientific` option)
## Examples
iex> Float.to_string 7.1, [decimals: 2, compact: true]
"7.1"
"""
@spec to_string(float, list) :: String.t
def to_string(float, options) do
:erlang.float_to_binary(float, expand_compact(options))
end
defp expand_compact([{:compact, false}|t]), do: expand_compact(t)
defp expand_compact([{:compact, true}|t]), do: [:compact|expand_compact(t)]
defp expand_compact([h|t]), do: [h|expand_compact(t)]
defp expand_compact([]), do: []
end
+664
View File
@@ -0,0 +1,664 @@
defmodule GenEvent do
@moduledoc """
A behaviour module for implementing event handling functionality.
The event handling model consists of a generic event manager
process with an arbitrary number of event handlers which are
added and deleted dynamically.
An event manager implemented using this module will have a standard
set of interface functions and include functionality for tracing and
error reporting. It will also fit into an supervision tree.
## Example
There are many use cases for event handlers. For example, a logging
system can be built using event handlers where which log message is
an event and different event handlers can be plugged to handle the
log messages. One handler may print error messages on the terminal,
another can write it to a file, while a third one can keep the
messages in memory (like a buffer) until they are read.
As an example, let's have a GenEvent that accumulates messages until
they are collected by an explicit call.
defmodule LoggerHandler do
use GenEvent
# Callbacks
def handle_event({:log, x}, messages) do
{:ok, [x|messages]}
end
def handle_call(:messages, messages) do
{:ok, Enum.reverse(messages), []}
end
end
{:ok, pid} = GenEvent.start_link()
GenEvent.add_handler(pid, LoggerHandler, [])
#=> :ok
GenEvent.notify(pid, {:log, 1})
#=> :ok
GenEvent.notify(pid, {:log, 2})
#=> :ok
GenEvent.call(pid, LoggerHandler, :messages)
#=> [1, 2]
GenEvent.call(pid, LoggerHandler, :messages)
#=> []
We start a new event manager by calling `GenEvent.start_link/0`.
Notifications can be sent to the event manager which will then
invoke `handle_event/0` for each registered handler.
We can add new handlers with `add_handler/4`. Calls can also
be made to specific handlers by using `call/3`.
## Callbacks
There are 6 callbacks required to be implemented in a `GenEvent`. By
adding `use GenEvent` to your module, Elixir will automatically define
all 6 callbacks for you, leaving it up to you to implement the ones
you want to customize. The callbacks are:
* `init(args)` - invoked when the event handler is added.
It must return:
- `{:ok, state}`
- `{:ok, state, :hibernate}`
- `{:error, reason}`
* `handle_event(msg, state)` - invoked whenever an event is sent via
`notify/2` or `sync_notify/2`.
It must return:
- `{:ok, new_state}`
- `{:ok, new_state, :hibernate}`
- `{:swap_handler, args1, new_state, handler2, args2}`
- `:remove_handler`
* `handle_call(msg, state)` - invoked when a `call/3` is done to a specific
handler.
It must return:
- `{:ok, reply, new_state}`
- `{:ok, reply, new_state, :hibernate}`
- `{:swap_handler, reply, args1, new_state, handler2, args2}`
- `{:remove_handler, reply}`
* `handle_info(msg, state)` - invoked to handle all other messages which
are received by the process. Must return the same values as
`handle_event/2`.
It must return:
- `{:noreply, state}`
- `{:noreply, state, timeout}`
- `{:stop, reason, state}`
* `terminate(reason, state)` - called when the event handler is removed or
the event manager is terminating. It can return any term.
* `code_change(old_vsn, state, extra)` - called when the application
code is being upgraded live (hot code swapping).
It must return:
- `{:ok, new_state}`
## Name Registration
A GenEvent is bound to the same name registration rules as a `GenServer`.
Read more about it in the `GenServer` docs.
## Streaming
`GenEvent`s can be streamed from and streamed with the help of `stream/2`.
Here are some examples:
stream = GenEvent.stream(pid)
# Take the next 10 events
Enum.take(stream, 10)
# Print all remaining events
for event <- stream do
IO.inspect event
end
A stream may also be given an id, which allows all streams with the given
id to be cancelled at any moment via `cancel_streams/1`.
## Learn more
If you wish to find out more about gen events, Elixir getting started
guides provide a tutorial-like introduction. The documentation and links
in Erlang can also provide extra insight.
* http://elixir-lang.org/getting_started/mix/1.html
* http://www.erlang.org/doc/man/gen_event.html
* http://learnyousomeerlang.com/event-handlers
"""
@typedoc "Return values of `start*` functions"
@type on_start :: {:ok, pid} | {:error, {:already_started, pid}}
@typedoc "The GenEvent manager name"
@type name :: atom | {:global, term} | {:via, module, term}
@typedoc "Options used by the `start*` functions"
@type options :: [name: name]
@typedoc "The event manager reference"
@type manager :: pid | name | {atom, node}
@typedoc "Supported values for new handlers"
@type handler :: module | {module, term}
@doc """
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`
* `:id` - the event stream id for cancellation
* `:timeout` - the timeout in between events, defaults to `:infinity`
* `:duration` - the duration of the subscription, defaults to `:infinity`
* `:mode` - if the subscription mode is sync or async, defaults to `:sync`
"""
defstruct manager: nil, id: nil, timeout: :infinity, duration: :infinity, mode: :sync
@doc false
defmacro __using__(_) do
quote location: :keep do
@behaviour :gen_event
@doc false
def init(args) do
{:ok, args}
end
@doc false
def handle_event(_event, state) do
{:ok, state}
end
@doc false
def handle_call(_request, state) do
{:ok, {:error, :bad_call}, state}
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
defoverridable [init: 1, handle_event: 2, handle_call: 2,
handle_info: 2, terminate: 2, code_change: 3]
end
end
@doc """
Starts an event manager linked to the current process.
This is often used to start the `GenEvent` as part of a supervision tree.
It accepts the `:name` option which is described under the `Name Registration`
section in the `GenServer` module docs.
If the event manager is successfully created and initialized, the function
returns `{:ok, pid}`, where pid is the pid of the server. If there already
exists a process with the specified server name, the function returns
`{:error, {:already_started, pid}}` with the pid of that process.
Note that a `GenEvent` started with `start_link/1` is linked to the
parent process and will exit not only on crashes but also if the parent
process exits with `:normal` reason.
"""
@spec start_link(options) :: on_start
def start_link(options \\ []) when is_list(options) do
do_start(:link, options)
end
@doc """
Starts an event manager process without links (outside of a supervision tree).
See `start_link/1` for more information.
"""
@spec start(options) :: on_start
def start(options \\ []) when is_list(options) do
do_start(:nolink, options)
end
defp do_start(mode, options) do
case Keyword.get(options, :name) do
nil ->
:gen.start(:gen_event, mode, :"no callback module", [], [])
atom when is_atom(atom) ->
:gen.start(:gen_event, mode, {:local, atom}, :"no callback module", [], [])
other when is_tuple(other) ->
:gen.start(:gen_event, mode, other, :"no callback module", [], [])
end
end
@doc """
Returns a stream that consumes and notifies events to the `manager`.
The stream is a `GenEvent` struct that implements the `Enumerable`
protocol. The supported options are:
* `:id` - an id to identify all live stream instances; when an `:id` is
given, existing streams can be called with via `cancel_streams`.
* `:timeout` (Enumerable) - raises if no event arrives in X milliseconds.
* `:duration` (Enumerable) - only consume events during the X milliseconds
from the streaming start.
* `:mode` - the mode to consume events, can be `:sync` (default) or
`:async`. On sync, the event manager waits for the event to be consumed
before moving on to the next event handler.
"""
def stream(manager, options \\ []) do
%GenEvent{manager: manager,
id: Keyword.get(options, :id),
timeout: Keyword.get(options, :timeout, :infinity),
duration: Keyword.get(options, :duration, :infinity),
mode: Keyword.get(options, :mode, :sync)}
end
@doc """
Adds a new event handler to the event `manager`.
The event manager will call the `init/1` callback with `args` to
initiate the event handler and its internal state.
If `init/1` returns a correct value indicating successful completion,
the event manager adds the event handler and this function returns
`:ok`. If the callback fails with `reason` or returns `{:error, reason}`,
the event handler is ignored and this function returns `{:EXIT, reason}`
or `{:error, reason}`, respectively.
## Linked handlers
When adding a handler, a `:link` option with value `true` can be given.
This means the event handler and the calling process are now linked.
If the calling process later terminates with `reason`, the event manager
will delete the event handler by calling the `terminate/2` callback with
`{:stop, reason}` as argument. If the event handler later is deleted,
the event manager sends a message `{:gen_event_EXIT, handler, reason}`
to the calling process. Reason is one of the following:
* `:normal` - if the event handler has been removed due to a call to
`remove_handler/3`, or `:remove_handler` has been returned by a callback
function
* `:shutdown` - if the event handler has been removed because the event
manager is terminating
* `{:swapped, new_handler, pid}` - if the process pid has replaced the
event handler by another
* a term - if the event handler is removed due to an error. Which term
depends on the error
"""
@spec add_handler(manager, handler, term, [link: boolean]) :: :ok | {:EXIT, term} | {:error, term}
def add_handler(manager, handler, args, options \\ []) do
case Keyword.get(options, :link, false) do
true -> :gen_event.add_sup_handler(manager, handler, args)
false -> :gen_event.add_handler(manager, handler, args)
end
end
@doc """
Sends an event notification to the event `manager`.
The event manager will call `handle_event/2` for each installed event handler.
`notify` is asynchronous and will return immediately after the notification is
sent. `notify` will not fail even if the specified event manager does not exist,
unless it is specified as `name` (atom).
"""
@spec notify(manager, term) :: :ok
defdelegate notify(manager, event), to: :gen_event
@doc """
Sends a sync event notification to the event `manager`.
In other words, this function only returns `:ok` after the event manager
invokes the `handle_event/2` on each installed event handler.
See `notify/2` for more info.
"""
@spec sync_notify(manager, term) :: :ok
defdelegate sync_notify(manager, event), to: :gen_event
@doc """
Makes a synchronous call to the event `handler` installed in `manager`.
The given `request` is sent and the caller waits until a reply arrives or
a timeout occurs. The event manager will call `handle_call/2` to handle
the request.
The return value `reply` is defined in the return value of `handle_call/2`.
If the specified event handler is not installed, the function returns
`{:error, :bad_module}`.
"""
@spec call(manager, handler, term, timeout) :: term | {:error, term}
def call(manager, handler, request, timeout \\ 5000) do
:gen_event.call(manager, handler, request, timeout)
end
@doc """
Cancels all streams currently running with the given `:id`.
In order for a stream to be cancelled, an `:id` must be passed
when the stream is created via `stream/2`. Passing a stream without
an id leads to an argument error.
"""
@spec cancel_streams(t) :: :ok
def cancel_streams(%GenEvent{id: nil}) do
raise ArgumentError, "cannot cancel streams without an id"
end
def cancel_streams(%GenEvent{manager: manager, id: id}) do
handlers = :gen_event.which_handlers(manager)
for {Enumerable.GenEvent, {handler_id, _}} = ref <- handlers,
handler_id === id do
:gen_event.delete_handler(manager, ref, :remove_handler)
end
:ok
end
@doc """
Removes an event handler from the event `manager`.
The event manager will call `terminate/2` to terminate the event handler
and return the callback value. If the specified event handler is not
installed, the function returns `{:error, :module_not_found}`.
"""
@spec remove_handler(manager, handler, term) :: term | {:error, term}
def remove_handler(manager, handler, args) do
:gen_event.delete_handler(manager, handler, args)
end
@doc """
Replaces an old event handler with a new one in the event `manager`.
First, the old event handler is deleted by calling `terminate/2` with
the given `args1` and collects the return value. Then the new event handler
is added and initiated by calling `init({args2, term}), where term is the
return value of calling `terminate/2` in the old handler. This makes it
possible to transfer information from one handler to another.
The new handler will be added even if the specified old event handler
is not installed in which case `term = :error` or if the handler fails to
terminate with a given reason.
If there was a linked connection between handler1 and a process pid, there
will be a link connection between handler2 and pid instead. A new link in
between the caller process and the new handler can also be set with by
giving `link: true` as option. See `add_handler/4` for more information.
If `init/1` in the second handler returns a correct value, this function
returns `:ok`.
"""
@spec swap_handler(manager, handler, term, handler, term, [link: boolean]) :: :ok | {:error, term}
def swap_handler(manager, handler1, args1, handler2, args2, options \\ []) do
case Keyword.get(options, :link, false) do
true -> :gen_event.swap_sup_handler(manager, {handler1, args1}, {handler2, args2})
false -> :gen_event.swap_handler(manager, {handler1, args1}, {handler2, args2})
end
end
@doc """
Returns a list of all event handlers installed in the `manager`.
"""
@spec which_handlers(manager) :: [handler]
defdelegate which_handlers(manager), to: :gen_event
@doc """
Terminates the event `manager`.
Before terminating, the event manager will call `terminate(:stop, ...)`
for each installed event handler.
"""
@spec stop(manager) :: :ok
defdelegate stop(manager), to: :gen_event
end
defimpl Enumerable, for: GenEvent do
use GenEvent
@doc false
def init({_mode, mon_pid, _pid, ref} = state) do
# Tell the mon_pid we are good to go, and send self() so that this handler
# can be removed later without using the managers name.
send(mon_pid, {:UP, ref, self()})
{:ok, state}
end
@doc false
def handle_event(event, {:sync, mon_pid, pid, ref} = state) do
sync = Process.monitor(mon_pid)
send pid, {ref, sync, event}
receive do
{^sync, :done} ->
Process.demonitor(sync, [:flush])
:remove_handler
{^sync, :next} ->
Process.demonitor(sync, [:flush])
{:ok, state}
{:DOWN, ^sync, _, _, _} ->
{:ok, state}
end
end
def handle_event(event, {:async, _mon_pid, pid, ref} = state) do
send pid, {ref, nil, event}
{:ok, state}
end
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
{nil, _manager, event}, acc ->
fun.(event, acc)
{ref, manager, event}, acc ->
try do
fun.(event, acc)
after
send manager, {ref, :next}
end
end
end
defp start(%{manager: manager, id: id, duration: duration, mode: mode} = stream) do
{mon_pid, mon_ref} = add_handler(mode, manager, id, duration)
send mon_pid, {:UP, mon_ref, self()}
receive do
# The subscription process gave us a go.
{:UP, ^mon_ref, manager_pid} ->
{mon_ref, mon_pid, manager_pid}
# The subscription process died due to an abnormal reason.
{:DOWN, ^mon_ref, _, _, reason} ->
exit({reason, {__MODULE__, :start, [stream]}})
end
end
defp next(%{timeout: timeout} = stream, {mon_ref, mon_pid, manager_pid} = acc) do
# If :DOWN is received must resend it to self so that stop/2 can receive it
# and know that the handler has been removed.
receive do
{:DOWN, ^mon_ref, _, _, :normal} ->
send(self(), {:DOWN, mon_ref, :process, mon_pid, :normal})
nil
{:DOWN, ^mon_ref, _, _, reason} ->
send(self(), {:DOWN, mon_ref, :process, mon_pid, :normal})
exit({reason, {__MODULE__, :next, [stream, acc]}})
{^mon_ref, sync_ref, event} ->
{{sync_ref, manager_pid, event}, acc}
after
timeout ->
exit({:timeout, {__MODULE__, :next, [stream, acc]}})
end
end
defp stop(%{mode: mode} = stream, {mon_ref, mon_pid, manager_pid} = acc) do
case remove_handler(mon_ref, mon_pid, manager_pid) do
:ok when mode == :async ->
flush_events(mon_ref)
:ok ->
:ok
{:error, reason} ->
exit({reason, {__MODULE__, :stop, [stream, acc]}})
end
end
defp add_handler(mode, manager, id, duration) do
parent = self()
# The subscription is managed by another process, that dies if
# the handler dies, and is killed when there is a need to remove
# the subscription.
spawn_monitor(fn ->
# It is possible that the handler could be removed, and then the GenEvent
# could exit before this process has exited normally. Because the removal
# does not cause an unlinking this process would exit with the same
# reason. Trapping exits ensures that no errors is raised in this case.
Process.flag(:trap_exit, true)
parent_ref = Process.monitor(parent)
# Receive the notification from the parent, unless it died.
mon_ref = receive do
{:UP, ref, ^parent} -> ref
{:DOWN, ^parent_ref, _, _, _} -> exit(:normal)
end
cancel = cancel_ref(id, mon_ref)
:ok = :gen_event.add_sup_handler(manager, {__MODULE__, cancel},
{mode, self(), parent, mon_ref})
receive do
# This message is already in the mailbox if we got this far.
{:UP, ^mon_ref, manager_pid} ->
send(parent, {:UP, mon_ref, manager_pid})
receive do
# The stream has finished, remove the handler.
{:DONE, ^mon_ref} ->
exit_handler(manager_pid, parent_ref, cancel)
# If the parent died, we can exit normally.
{:DOWN, ^parent_ref, _, _, _} ->
exit(:normal)
# reason should be normal unless the handler is swapped.
{:gen_event_EXIT, {__MODULE__, ^cancel}, reason} ->
exit(reason)
# Exit if the manager dies, so the streamer is notified.
{:EXIT, ^manager_pid, :noconnection} ->
exit({:nodedown, node(manager_pid)})
{:EXIT, ^manager_pid, reason} ->
exit(reason)
after
# Our time is over, notify the parent.
duration -> exit(:normal)
end
end
end)
end
defp cancel_ref(nil, mon_ref), do: mon_ref
defp cancel_ref(id, mon_ref), do: {id, mon_ref}
defp exit_handler(manager_pid, parent_ref, cancel) do
# Send exit signal so manager removes handler.
Process.exit(manager_pid, :shutdown)
receive do
# If the parent died, we can exit normally.
{:DOWN, ^parent_ref, _, _, _} ->
exit(:normal)
# Probably the reason is :shutdown, which occurs when the manager receives
# an exit signal from a handler supervising process. However whatever the
# reason the handler has been removed so it is ok.
{:gen_event_EXIT, {__MODULE__, ^cancel}, _} ->
exit(:normal)
# The connection broke, perhaps the handler might try to forward events
# before it removes the handler, so must exit abnormally.
{:EXIT, ^manager_pid, :noconnection} ->
exit({:nodedown, node(manager_pid)})
# The manager has exited but don't exit abnormally as the handler has died
# with the manager and all expected events have been handled. This is ok.
{:EXIT, ^manager_pid, _} ->
exit(:normal)
end
end
defp remove_handler(mon_ref, mon_pid, manager_pid) do
send(mon_pid, {:DONE, mon_ref})
receive do
{^mon_ref, sync, _} when sync != nil ->
send(manager_pid, {sync, :done})
Process.demonitor(mon_ref, [:flush])
:ok
{:DOWN, ^mon_ref, _, _, :normal} ->
:ok
{:DOWN, ^mon_ref, _, _, reason} ->
{:error, reason}
end
end
defp flush_events(mon_ref) do
receive do
{^mon_ref, _, _} ->
flush_events(mon_ref)
after
0 -> :ok
end
end
end
-100
View File
@@ -1,100 +0,0 @@
defmodule GenEvent.Behaviour do
@moduledoc """
This module is a convenience for defining GenEvent callbacks in Elixir.
GenEvent is an OTP behaviour that encapsulates event handling functionality.
## Example
Below is an example of a GenEvent that stores notifications
until they are fetched:
defmodule MyEventHandler do
use GenEvent.Behaviour
# Callbacks
def init(_) do
{:ok, []}
end
def handle_event({:notification, x}, notifications) do
{:ok, [x|notifications]}
end
def handle_call(:notifications, notifications) do
{:ok, Enum.reverse(notifications), []}
end
end
{:ok, pid} = :gen_event.start_link
#=> {:ok,#PID<0.42.0>}
:gen_event.add_handler(pid, MyEventHandler, [])
#=> :ok
:gen_event.notify(pid, {:notification, 1})
#=> :ok
:gen_event.notify(pid, {:notification, 2})
#=> :ok
:gen_event.call(pid, MyEventHandler, :notifications)
#=> [1, 2]
:gen_event.call(pid, MyEventHandler, :notifications)
#=> []
Notice we never call the server callbacks directly, they are called
by OTP whenever we interact with the server.
Starting and sending messages to the GenEvent is done
via Erlang's `:gen_event` module. For more information,
please refer to the following:
* http://www.erlang.org/doc/man/gen_event.html
* http://learnyousomeerlang.com/event-handlers
"""
@doc false
defmacro __using__(_) do
quote location: :keep do
@behaviour :gen_event
@doc false
def init(args) do
{:ok, args}
end
@doc false
def handle_event(_event, state) do
{:ok, state}
end
@doc false
def handle_call(_request, state) do
{:ok, :ok, state}
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
defoverridable [init: 1,
handle_event: 2,
handle_call: 2, handle_info: 2,
terminate: 2, code_change: 3]
end
end
end
+430
View File
@@ -0,0 +1,430 @@
defmodule GenServer do
@moduledoc """
A behaviour module for implementing the server of a client-server relation.
A GenServer is a process as any other Elixir process and it can be used
to keep state, execute code asynchronously and so on. The advantage of using
a generic server process (GenServer) implemented using this module is that it
will have a standard set of interface functions and include functionality for
tracing and error reporting. It will also fit into a supervision tree.
## Example
The GenServer behaviour abstracts the common client-server interaction.
Developer are only required to implement the callbacks and functionality they are
interested in.
Let's start with a code example and then explore the available callbacks.
Imagine we want a GenServer that works like a stack, allowing us to push
and pop items:
defmodule Stack do
use GenServer
# Callbacks
def handle_call(:pop, _from, [h|t]) do
{:reply, h, t}
end
def handle_cast({:push, item}, state) do
{:noreply, [item|state]}
end
end
# Start the server
{:ok, pid} = GenServer.start_link(Stack, [:hello])
# This is the client
GenServer.call(pid, :pop)
#=> :hello
GenServer.cast(pid, {:push, :world})
#=> :ok
GenServer.call(pid, :pop)
#=> :world
We start our `Stack` by calling `start_link/3`, passing the module
with the server implementation and its initial argument (a list
representing the stack containing the item `:hello`). We can primarily
interact with the server by sending two types of messages. **call**
messages expect a reply from the server (and are therefore synchronous)
while **cast** messages do not.
Every time you do a `GenServer.call/3`, the client will send a message
that must be handled by the `handle_call/3` callback in the GenServer.
A `cast/2` message must be handled by `handle_cast/2`.
## Callbacks
There are 6 callbacks required to be implemented in a `GenServer`. By
adding `use GenServer` to your module, Elixir will automatically define
all 6 callbacks for you, leaving it up to you to implement the ones
you want to customize. The callbacks are:
* `init(args)` - invoked when the server is started.
It must return:
- `{:ok, state}`
- `{:ok, state, timeout}`
- `:ignore`
- `{:stop, reason}`
* `handle_call(msg, {from, ref}, state)` and `handle_cast(msg, state)` -
invoked to handle call (sync) and cast (async) messages.
It must return:
- `{:reply, reply, new_state}`
- `{:reply, reply, new_state, timeout}`
- `{:reply, reply, new_state, :hibernate}`
- `{:noreply, new_state}`
- `{:noreply, new_state, timeout}`
- `{:noreply, new_state, :hibernate}`
- `{:stop, reason, new_state}`
- `{:stop, reason, reply, new_state}`
* `handle_info(msg, state)` - invoked to handle all other messages which
are received by the process.
It must return:
- `{:noreply, state}`
- `{:noreply, state, timeout}`
- `{:stop, reason, state}`
* `terminate(reason, state)` - called when the server is about to
terminate, useful for cleaning up. It must return `:ok`.
* `code_change(old_vsn, state, extra)` - called when the application
code is being upgraded live (hot code swapping).
It must return:
- `{:ok, new_state}`
- `{:error, reason}`
## Name Registration
Both `start_link/3` and `start/3` support the `GenServer` to register
a name on start via the `:name` option. Registered names are also
automatically cleaned up on termination. The supported values are:
* an atom - the GenServer is registered locally with the given name
using `Process.register/2`.
* `{:global, term}`- the GenServer is registered globally with the given
term using the functions in the `:global` module.
* `{:via, module, term}` - the GenServer is registered with the given
mechanism and name. The `:via` option expects a module name to control
the registration mechanism alongside a name which can be any term.
For example, we could start and register our Stack server locally as follows:
# Start the server and register it locally with name MyStack
{:ok, _} = GenServer.start_link(Stack, [:hello], name: MyStack)
# Now messages can be sent directly to MyStack
GenServer.call(MyStack, :pop) #=> :hello
Once the server is started, the remaining functions in this module (`call/3`,
`cast/2`, and friends) will also accept an atom, or any `:global` or `:via`
tuples. In general, the following formats are supported:
* a `pid`
* an `atom` if the server is locally registered
* `{atom, node}` if the server is locally registered at another node
* `{:global, term}` if the server is globally registered
* `{:via, module, name}` if the server is registered through an alternative
registry
## Client / Server APIs
Although in the example above we have used `GenServer.start_link/3` and
friends to directly start and communicate with the server, most of the
time we don't call the `GenServer` functions directly. Instead, we wrap
the calls in new functions representing the public API of the server.
Here is a better implementation of our Stack module:
defmodule Stack do
use GenServer
# Client
def start_link(default) do
GenServer.start_link(__MODULE__, default)
end
def push(pid, item) do
GenServer.cast(pid, {:push, item})
end
def pop(pid) do
GenServer.call(pid, :pop)
end
# Server (callbacks)
def handle_call(:pop, _from, [h|t]) do
{:reply, h, t}
end
def handle_call(request, from, state) do
# Call the default implementation from GenServer
super(request, from, state)
end
def handle_cast({:push, item}, state) do
{:noreply, [item|state]}
end
def handle_cast(request, state) do
super(request, state)
end
end
In practice, it is common to have both server and client functions in
the same module. If the server and/or client implementations are growing
complex, you may want to have them in different modules.
## Learn more
If you wish to find out more about gen servers, Elixir getting started
guides provide a tutorial-like introduction. The documentation and links
in Erlang can also provide extra insight.
* http://elixir-lang.org/getting_started/mix/1.html
* http://www.erlang.org/doc/man/gen_server.html
* http://www.erlang.org/doc/design_principles/gen_server_concepts.html
* http://learnyousomeerlang.com/clients-and-servers
"""
@typedoc "Return values of `start*` functions"
@type on_start :: {:ok, pid} | :ignore | {:error, {:already_started, pid} | term}
@typedoc "The GenServer name"
@type name :: atom | {:global, term} | {:via, module, term}
@typedoc "Options used by the `start*` functions"
@type options :: [debug: debug,
name: name,
timeout: timeout,
spawn_opt: Process.spawn_opt]
@typedoc "debug options supported by the `start*` functions"
@type debug :: [:trace | :log | :statistics | {:log_to_file, Path.t}]
@typedoc "The server reference"
@type server :: pid | name | {atom, node}
@doc false
defmacro __using__(_) do
quote location: :keep do
@behaviour :gen_server
@doc false
def init(args) do
{:ok, args}
end
@doc false
def handle_call(msg, _from, state) do
{:stop, {:bad_call, msg}, state}
end
@doc false
def handle_info(_msg, state) do
{:noreply, state}
end
@doc false
def handle_cast(msg, state) do
{:stop, {:bad_cast, msg}, state}
end
@doc false
def terminate(_reason, _state) do
:ok
end
@doc false
def code_change(_old, state, _extra) do
{:ok, state}
end
defoverridable [init: 1, handle_call: 3, handle_info: 2,
handle_cast: 2, terminate: 2, code_change: 3]
end
end
@doc """
Starts a `GenServer` process linked to the current process.
This is often used to start the `GenServer` as part of a supervision tree.
Once the server is started, it calls the `init/1` function in the given `module`
passing the given `args` to initialize it. To ensure a synchronized start-up
procedure, this function does not return until `init/1` has returned.
Note that a `GenServer` started with `start_link/3` is linked to the
parent process and will exit in case of crashes. The GenServer will also
exit due to the `:normal` reasons in case it is configured to trap exits
in the `init/1` callback.
## Options
The `:name` option is used for name registration as described in the module
documentation. If the option `:timeout` option is present, the server is
allowed to spend the given milliseconds initializing or it will be
terminated and the start function will return `{:error, :timeout}`.
If the `:debug` option is present, the corresponding function in the
[`:sys` module](http://www.erlang.org/doc/man/sys.html) will be invoked.
If the `:spawn_opt` option is present, its value will be passed as options
to the underlying process as in `Process.spawn/4`.
## Return values
If the server is successfully created and initialized, the function returns
`{:ok, pid}`, where pid is the pid of the server. If there already exists a
process with the specified server name, the function returns
`{:error, {:already_started, pid}}` with the pid of that process.
If the `init/1` callback fails with `reason`, the function returns
`{:error, reason}`. Otherwise, if it returns `{:stop, reason}`
or `:ignore`, the process is terminated and the function returns
`{:error, reason}` or `:ignore`, respectively.
"""
@spec start_link(module, any, options) :: on_start
def start_link(module, args, options \\ []) when is_atom(module) and is_list(options) do
do_start(:link, module, args, options)
end
@doc """
Starts a `GenServer` process without links (outside of a supervision tree).
See `start_link/3` for more information.
"""
@spec start(module, any, options) :: on_start
def start(module, args, options \\ []) when is_atom(module) and is_list(options) do
do_start(:nolink, module, args, options)
end
defp do_start(link, module, args, options) do
case Keyword.pop(options, :name) do
{nil, opts} ->
:gen.start(:gen_server, link, module, args, opts)
{atom, opts} when is_atom(atom) ->
:gen.start(:gen_server, link, {:local, atom}, module, args, opts)
{other, opts} when is_tuple(other) ->
:gen.start(:gen_server, link, other, module, args, opts)
end
end
@doc """
Makes a synchronous call to the `server` and waits for its reply.
The client sends the given `request` to the server and waits until a reply
arrives or a timeout occurs. `handle_call/3` will be called on the server
to handle the request.
The server can be any of the values described in the `Name Registration`
section of the module documentation.
## Timeouts
The `timeout` is an integer greater than zero which specifies how many
milliseconds to wait for a reply, or the atom `:infinity` to wait
indefinitely. The default value is 5000. If no reply is received within
the specified time, the function call fails. If the caller catches the
failure and continues running, and the server is just late with the reply,
it may arrive at any time later into the caller's message queue. The caller
must in this case be prepared for this and discard any such garbage messages
that are two element tuples with a reference as the first element.
"""
@spec call(server, term, timeout) :: term
def call(server, request, timeout \\ 5000) do
:gen_server.call(server, request, timeout)
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.
"""
@spec cast(server, term) :: :ok
defdelegate cast(server, request), to: :gen_server
@doc """
Casts all servers locally registered as `name` at the specified nodes.
The function returns immediately and ignores nodes that do not exist, or where the
server name does not exist.
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)
end
@doc """
Calls all servers locally registered as `name` at the specified `nodes`.
The `request` is first sent to every node and then we wait for the
replies. This function returns a tuple containing the node and its reply
as first element and all bad nodes as second element. The bad nodes is a
list of nodes that either did not exist, or where a server with the given
`name` did not exist or did not reply.
Nodes is a list of node names to which the request is sent. The default
value is the list of all known nodes.
To avoid that late answers (after the timeout) pollute the caller's message
queue, a middleman process is used to do the actual calls. Late answers will
then be discarded when they arrive to a terminated process.
"""
@spec multi_call([node], name :: atom, term, timeout) ::
{replies :: [{node, term}], bad_nodes :: [node]}
def multi_call(nodes \\ nodes(), name, request, timeout \\ :infinity) do
:gen_server.multi_call(nodes, name, request, timeout)
end
@doc """
Replies to a client.
This function can be used by a server to explicitly send a reply to a
client that called `call/3` or `multi_call/4`. When the reply cannot be
defined in the return value of `handle_call/3`.
The `client` must be the `from` argument (the second argument) received
in `handle_call/3` callbacks. Reply is an arbitrary term which will be
given back to the client as the return value of the call.
This function always returns `:ok`.
"""
@spec reply({pid, reference}, term) :: :ok
def reply(client, reply)
def reply({to, tag}, reply) do
try do
send(to, {tag, reply})
:ok
catch
_, _ -> :ok
end
end
@compile {:inline, [nodes: 0]}
defp nodes do
[node()|:erlang.nodes()]
end
end
-125
View File
@@ -1,125 +0,0 @@
defmodule GenServer.Behaviour do
@moduledoc """
This module is a convenience for defining GenServer callbacks in Elixir.
A server is responsible for reacting to messages received from a client.
A GenServer is an OTP behaviour that encapsulates common server
functionalities.
## Example
Below is an example of a GenServer that pushes and pops items
onto a stack:
defmodule MyServer do
use GenServer.Behaviour
# Callbacks
def handle_call(:pop, _from, [h|t]) do
{:reply, h, t}
end
def handle_call(request, from, config) do
# Call the default implementation from GenServer.Behaviour
super(request, from, config)
end
def handle_cast({:push, item}, config) do
{:noreply, [item|config]}
end
def handle_cast(request, config) do
super(request, config)
end
end
{:ok, pid} = :gen_server.start_link(MyServer, [:hello], [])
:gen_server.call(pid, :pop)
#=> :hello
:gen_server.cast(pid, {:push, :world})
#=> :ok
:gen_server.call(pid, :pop)
#=> :world
Notice we never call the server callbacks directly, they are called
by OTP whenever we interact with the server. **cast** messages are
asynchronous while **call** ones are synchronous. For a
GenServer, there are 8 different values a callback such as
`handle_call` or `handle_cast` can return:
{:reply, reply, new_state}
{:reply, reply, new_state, timeout}
{:reply, reply, new_state, :hibernate}
{:noreply, new_state}
{:noreply, new_state, timeout}
{:noreply, new_state, :hibernate}
{:stop, reason, new_state}
{:stop, reason, reply, new_state}
There are 6 callbacks required to be implemented in a GenServer. The
`GenServer.Behaviour` module defines all of them automatically, but
allows us to customize the ones we need. The required callbacks are:
* `init(args)` - invoked when the server is started;
* `handle_call(msg, from, state)` - invoked to handle call messages;
* `handle_cast(msg, state)` - invoked to handle cast messages;
* `handle_info(msg, state)` - handle all other messages which are
normally received by processes;
* `terminate(reason, state)` - called when the server is about to
terminate, useful for cleaning up;
* `code_change(old_vsn, state, extra)` - called when the application
code is being upgraded live (hot code swap);
Starting and sending messages to the GenServer is done
via Erlang's `:gen_server` module. For more information,
please refer to the following:
* http://www.erlang.org/doc/man/gen_server.html
* http://www.erlang.org/doc/design_principles/gen_server_concepts.html
* http://learnyousomeerlang.com/clients-and-servers
"""
@doc false
defmacro __using__(_) do
quote location: :keep do
@behaviour :gen_server
@doc false
def init(args) do
{:ok, args}
end
@doc false
def handle_call(request, _from, state) do
{:stop, {:bad_call, request}, state}
end
@doc false
def handle_info(_msg, state) do
{:noreply, state}
end
@doc false
def handle_cast(msg, state) do
{:stop, {:bad_cast, msg}, state}
end
@doc false
def terminate(_reason, _state) do
:ok
end
@doc false
def code_change(_old, state, _extra) do
{:ok, state}
end
defoverridable [init: 1, handle_call: 3, handle_info: 2,
handle_cast: 2, terminate: 2, code_change: 3]
end
end
end
+42 -46
View File
@@ -14,7 +14,7 @@ defmodule HashDict do
functions and their APIs, please consult the `Dict` module.
"""
use Dict.Behaviour
use Dict
@node_bitmap 0b111
@node_shift 3
@@ -22,6 +22,7 @@ defmodule HashDict do
@node_template :erlang.make_tuple(@node_size, [])
@opaque t :: map
@doc false
defstruct size: 0, root: @node_template
# Inline common instructions
@@ -43,7 +44,7 @@ defmodule HashDict do
def update!(%HashDict{root: root, size: size} = dict, key, fun) when is_function(fun, 1) do
{root, counter} = do_update(root, key, fn -> raise KeyError, key: key, term: dict end,
fun, key_hash(key))
fun, key_hash(key))
%HashDict{root: root, size: size + counter}
end
@@ -59,14 +60,14 @@ defmodule HashDict do
def delete(dict, key) do
case dict_delete(dict, key) do
{dict, _value} -> dict
:error -> dict
:error -> dict
end
end
def pop(dict, key, default \\ nil) do
case dict_delete(dict, key) do
{dict, value} -> {value, dict}
:error -> {default, dict}
:error -> {default, dict}
end
end
@@ -83,33 +84,13 @@ defmodule HashDict do
end)
end
def split(dict, keys) do
Enum.reduce keys, {new, dict}, fn key, {inc, exc} = acc ->
case dict_delete(exc, key) do
{exc, value} -> {put(inc, key, value), exc}
:error -> acc
end
end
end
def merge(%HashDict{size: size1} = dict1, %HashDict{size: size2} = dict2, callback) when size1 < size2 do
reduce(dict1, {:cont, dict2}, fn {k, v1}, acc ->
{:cont, update(acc, k, v1, &callback.(k, v1, &1))}
end) |> elem(1)
end
def merge(%HashDict{} = dict1, %HashDict{} = dict2, callback) do
reduce(dict2, {:cont, dict1}, fn {k, v2}, acc ->
{:cont, update(acc, k, v2, &callback.(k, &1, v2))}
end) |> elem(1)
end
## General helpers
defp dict_delete(%HashDict{root: root, size: size}, key) do
@doc false
def dict_delete(%HashDict{root: root, size: size}, key) do
case do_delete(root, key, key_hash(key)) do
{root, value} -> {%HashDict{root: root, size: size - 1}, value}
:error -> :error
:error -> :error
end
end
@@ -129,17 +110,17 @@ defmodule HashDict do
index = key_mask(hash)
case elem(node, index) do
[] ->
{set_elem(node, index, [key|value]), 1}
{put_elem(node, index, [key|value]), 1}
[^key|_] ->
{set_elem(node, index, [key|value]), 0}
{put_elem(node, index, [key|value]), 0}
[k|v] ->
n = set_elem(@node_template, key_mask(key_shift(hash)), [key|value])
{set_elem(node, index, {k, v, n}), 1}
n = put_elem(@node_template, key_mask(key_shift(hash)), [key|value])
{put_elem(node, index, {k, v, n}), 1}
{^key, _, n} ->
{set_elem(node, index, {key, value, n}), 0}
{put_elem(node, index, {key, value, n}), 0}
{k, v, n} ->
{n, counter} = do_put(n, key, value, key_shift(hash))
{set_elem(node, index, {k, v, n}), counter}
{put_elem(node, index, {k, v, n}), counter}
end
end
@@ -147,17 +128,17 @@ defmodule HashDict do
index = key_mask(hash)
case elem(node, index) do
[] ->
{set_elem(node, index, [key|initial.()]), 1}
{put_elem(node, index, [key|initial.()]), 1}
[^key|value] ->
{set_elem(node, index, [key|fun.(value)]), 0}
{put_elem(node, index, [key|fun.(value)]), 0}
[k|v] ->
n = set_elem(@node_template, key_mask(key_shift(hash)), [key|initial.()])
{set_elem(node, index, {k, v, n}), 1}
n = put_elem(@node_template, key_mask(key_shift(hash)), [key|initial.()])
{put_elem(node, index, {k, v, n}), 1}
{^key, value, n} ->
{set_elem(node, index, {key, fun.(value), n}), 0}
{put_elem(node, index, {key, fun.(value), n}), 0}
{k, v, n} ->
{n, counter} = do_update(n, key, initial, fun, key_shift(hash))
{set_elem(node, index, {k, v, n}), counter}
{put_elem(node, index, {k, v, n}), counter}
end
end
@@ -167,17 +148,17 @@ defmodule HashDict do
[] ->
:error
[^key|value] ->
{set_elem(node, index, []), value}
{put_elem(node, index, []), value}
[_|_] ->
:error
{^key, value, n} ->
{set_elem(node, index, do_compact_node(n)), value}
{put_elem(node, index, do_compact_node(n)), value}
{k, v, n} ->
case do_delete(n, key, key_shift(hash)) do
{@node_template, value} ->
{set_elem(node, index, [k|v]), value}
{put_elem(node, index, [k|v]), value}
{n, value} ->
{set_elem(node, index, {k, v, n}), value}
{put_elem(node, index, {k, v, n}), value}
:error ->
:error
end
@@ -188,12 +169,12 @@ defmodule HashDict do
defp do_compact_node(node) when elem(node, unquote(index)) != [] do
case elem(node, unquote(index)) do
[k|v] ->
case set_elem(node, unquote(index), []) do
case put_elem(node, unquote(index), []) do
@node_template -> [k|v]
n -> {k, v, n}
end
{k, v, n} ->
{k, v, set_elem(node, unquote(index), do_compact_node(n))}
{k, v, put_elem(node, unquote(index), do_compact_node(n))}
end
end
end
@@ -253,7 +234,14 @@ defimpl Enumerable, for: HashDict do
end
defimpl Access, for: HashDict do
def access(dict, key), do: HashDict.get(dict, key, nil)
def get(dict, key) do
HashDict.get(dict, key, nil)
end
def get_and_update(dict, key, fun) do
{get, update} = fun.(HashDict.get(dict, key, nil))
{get, HashDict.put(dict, key, update)}
end
end
defimpl Collectable, for: HashDict do
@@ -269,3 +257,11 @@ defimpl Collectable, for: HashDict do
end}
end
end
defimpl Inspect, for: HashDict do
import Inspect.Algebra
def inspect(dict, opts) do
concat ["#HashDict<", Inspect.List.inspect(HashDict.to_list(dict), opts), ">"]
end
end
+19 -10
View File
@@ -22,6 +22,7 @@ defmodule HashSet do
@node_template :erlang.make_tuple(@node_size, [])
@opaque t :: map
@doc false
defstruct size: 0, root: @node_template
# Inline common instructions
@@ -134,15 +135,15 @@ defmodule HashSet do
index = key_mask(hash)
case elem(node, index) do
[] ->
{set_elem(node, index, [term]), 1}
{put_elem(node, index, [term]), 1}
[^term|_] ->
{node, 0}
[t] ->
n = set_elem(@node_template, key_mask(key_shift(hash)), [term])
{set_elem(node, index, [t|n]), 1}
n = put_elem(@node_template, key_mask(key_shift(hash)), [term])
{put_elem(node, index, [t|n]), 1}
[t|n] ->
{n, counter} = do_put(n, term, key_shift(hash))
{set_elem(node, index, [t|n]), counter}
{put_elem(node, index, [t|n]), counter}
end
end
@@ -152,17 +153,17 @@ defmodule HashSet do
[] ->
:error
[^term] ->
{:ok, set_elem(node, index, [])}
{:ok, put_elem(node, index, [])}
[_] ->
:error
[^term|n] ->
{:ok, set_elem(node, index, do_compact_node(n))}
{:ok, put_elem(node, index, do_compact_node(n))}
[t|n] ->
case do_delete(n, term, key_shift(hash)) do
{:ok, @node_template} ->
{:ok, set_elem(node, index, [t])}
{:ok, put_elem(node, index, [t])}
{:ok, n} ->
{:ok, set_elem(node, index, [t|n])}
{:ok, put_elem(node, index, [t|n])}
:error ->
:error
end
@@ -173,12 +174,12 @@ defmodule HashSet do
defp do_compact_node(node) when elem(node, unquote(index)) != [] do
case elem(node, unquote(index)) do
[t] ->
case set_elem(node, unquote(index), []) do
case put_elem(node, unquote(index), []) do
@node_template -> [t]
n -> [t|n]
end
[t|n] ->
[t|set_elem(node, unquote(index), do_compact_node(n))]
[t|put_elem(node, unquote(index), do_compact_node(n))]
end
end
end
@@ -264,3 +265,11 @@ defimpl Collectable, for: HashSet do
end}
end
end
defimpl Inspect, for: HashSet do
import Inspect.Algebra
def inspect(set, opts) do
concat ["#HashSet<", Inspect.List.inspect(HashSet.to_list(set), opts), ">"]
end
end
+93 -87
View File
@@ -8,7 +8,7 @@ defprotocol Inspect do
formatted, either in pretty printing format or a regular one.
The `inspect/2` function receives the entity to be inspected
followed by the inspecting options, represented by the record
followed by the inspecting options, represented by the struct
`Inspect.Opts`.
Inspection is done using the functions available in `Inspect.Algebra`.
@@ -70,7 +70,7 @@ defimpl Inspect, for: Atom do
def inspect(:""), do: ":\"\""
def inspect(atom) do
binary = atom_to_binary(atom)
binary = Atom.to_string(atom)
cond do
valid_ref_identifier?(binary) ->
@@ -142,7 +142,7 @@ end
defimpl Inspect, for: BitString do
def inspect(thing, %Inspect.Opts{binaries: bins} = opts) when is_binary(thing) do
if bins == :as_strings or (bins == :infer and String.printable?(thing)) do
<< ?", escape(thing, ?") :: binary, ?" >>
<<?", escape(thing, ?") :: binary, ?">>
else
inspect_bitstring(thing, opts)
end
@@ -202,19 +202,40 @@ defimpl Inspect, for: BitString do
else
<< byte :: size(8), h :: binary >> = head
t = << h :: binary, t :: binary >>
escape(t, char, << binary :: binary, octify(byte) :: binary >>)
escape(t, char, << binary :: binary, escape_char(byte) :: binary >>)
end
end
defp escape(<<h, t :: binary>>, char, binary) do
escape(t, char, << binary :: binary, octify(h) :: binary >>)
escape(t, char, << binary :: binary, escape_char(h) :: binary >>)
end
defp escape(<<>>, _char, binary), do: binary
@doc false
# Also used by Regex
def escape_char(char) when char in ?\000..?\377,
do: octify(char)
def escape_char(char), do: hexify(char)
defp octify(byte) do
<< hi :: size(2), mi :: size(3), lo :: size(3) >> = << byte >>
<< ?\\, ?0 + hi, ?0 + mi, ?0 + lo >>
end
defp hexify(char) when char < 0x10000 do
<<a::4, b::4, c::4, d::4>> = <<char::size(16)>>
<<?\\, ?x, ?{, to_hex(a), to_hex(b), to_hex(c), to_hex(d), ?}>>
end
defp hexify(char) when char < 0x1000000 do
<<a::4, b::4, c::4, d::4, e::4, f::4>> = <<char::size(24)>>
<<?\\, ?x, ?{, to_hex(a), to_hex(b), to_hex(c),
to_hex(d), to_hex(e), to_hex(f), ?}>>
end
defp to_hex(c) when c in 0..9, do: ?0+c
defp to_hex(c) when c in 10..15, do: ?a+c-10
defp append(<<h, t :: binary>>, binary), do: append(t, << binary :: binary, h >>)
defp append(<<>>, binary), do: binary
@@ -229,11 +250,11 @@ defimpl Inspect, for: BitString do
end
defp each_bit(<<h, t :: bitstring>>, counter, acc) when t != <<>> do
each_bit(t, decrement(counter), acc <> integer_to_binary(h) <> ", ")
each_bit(t, decrement(counter), acc <> Integer.to_string(h) <> ", ")
end
defp each_bit(<<h :: size(8)>>, _counter, acc) do
acc <> integer_to_binary(h)
acc <> Integer.to_string(h)
end
defp each_bit(<<>>, _counter, acc) do
@@ -243,7 +264,7 @@ defimpl Inspect, for: BitString do
defp each_bit(bitstring, _counter, acc) do
size = bit_size(bitstring)
<<h :: size(size)>> = bitstring
acc <> integer_to_binary(h) <> "::size(" <> integer_to_binary(size) <> ")"
acc <> Integer.to_string(h) <> "::size(" <> Integer.to_string(size) <> ")"
end
defp decrement(:infinity), do: :infinity
@@ -275,7 +296,7 @@ defimpl Inspect, for: List do
def inspect(thing, %Inspect.Opts{char_lists: lists} = opts) do
cond do
lists == :as_char_lists or (lists == :infer and printable?(thing)) ->
<< ?', Inspect.BitString.escape(String.from_char_data!(thing), ?') :: binary, ?' >>
<< ?', Inspect.BitString.escape(IO.chardata_to_string(thing), ?') :: binary, ?' >>
keyword?(thing) ->
surround_many("[", thing, "]", opts.limit, &keyword(&1, opts))
true ->
@@ -291,7 +312,7 @@ defimpl Inspect, for: List do
end
def keyword?([{key, _value} | rest]) when is_atom(key) do
case atom_to_list(key) do
case Atom.to_char_list(key) do
'Elixir.' ++ _ -> false
_ -> keyword?(rest)
end
@@ -326,63 +347,13 @@ defimpl Inspect, for: Tuple do
def inspect({}, _opts), do: "{}"
def inspect(tuple, opts) do
if opts.records do
record_inspect(tuple, opts)
else
surround_many("{", tuple_to_list(tuple), "}", opts.limit, &to_doc(&1, opts))
end
end
## Helpers
defp record_inspect(record, opts) do
[name|tail] = tuple_to_list(record)
if is_atom(name) && (fields = record_fields(name)) && (length(fields) == size(record) - 1) do
surround_record(name, fields, tail, opts)
else
surround_many("{", [name|tail], "}", opts.limit, &to_doc(&1, opts))
end
end
defp record_fields(name) do
case atom_to_binary(name) do
"Elixir." <> _ ->
try do
name.__record__(:fields)
rescue
_ -> nil
end
_ -> nil
end
end
defp surround_record(name, fields, tail, opts) do
concat(
Inspect.Atom.inspect(name, opts),
surround_many("[", zip_fields(fields, tail), "]", opts.limit, &keyword(&1, opts))
)
end
defp zip_fields([{key, _}|tk], [value|tv]) do
case atom_to_binary(key) do
"_" <> _ -> zip_fields(tk, tv)
key -> [{key, value}|zip_fields(tk, tv)]
end
end
defp zip_fields([], []) do
[]
end
defp keyword({k, v}, opts) do
concat(k <> ": ", to_doc(v, opts))
surround_many("{", Tuple.to_list(tuple), "}", opts.limit, &to_doc(&1, opts))
end
end
defimpl Inspect, for: Map do
def inspect(map, opts) do
inspect(map, "", opts)
nest inspect(map, "", opts), 1
end
def inspect(map, name, opts) do
@@ -408,20 +379,67 @@ end
defimpl Inspect, for: Integer do
def inspect(thing, _opts) do
integer_to_binary(thing)
Integer.to_string(thing)
end
end
defimpl Inspect, for: Float do
def inspect(thing, _opts) do
iodata_to_binary(:io_lib_format.fwrite_g(thing))
IO.iodata_to_binary(:io_lib_format.fwrite_g(thing))
end
end
defimpl Inspect, for: Regex do
def inspect(regex, opts) do
concat ["~r", to_doc(regex.source, opts), regex.opts]
def inspect(regex, _opts) do
delim = ?/
concat ["~r",
<<delim, escape(regex.source, delim)::binary, delim>>,
regex.opts]
end
defp escape(bin, term),
do: escape(bin, <<>>, term)
defp escape(<<?\\, term>> <> rest, buf, term),
do: escape(rest, buf <> <<?\\, term>>, term)
defp escape(<<term>> <> rest, buf, term),
do: escape(rest, buf <> <<?\\, term>>, term)
# the list of characters is from `String.printable?` impl
# minus characters treated specially by regex: \s, \d, \b, \e
defp escape(<<?\n>> <> rest, buf, term),
do: escape(rest, <<buf::binary, ?\\, ?n>>, term)
defp escape(<<?\r>> <> rest, buf, term),
do: escape(rest, <<buf::binary, ?\\, ?r>>, term)
defp escape(<<?\t>> <> rest, buf, term),
do: escape(rest, <<buf::binary, ?\\, ?t>>, term)
defp escape(<<?\v>> <> rest, buf, term),
do: escape(rest, <<buf::binary, ?\\, ?v>>, term)
defp escape(<<?\f>> <> rest, buf, term),
do: escape(rest, <<buf::binary, ?\\, ?f>>, term)
defp escape(<<?\a>> <> rest, buf, term),
do: escape(rest, <<buf::binary, ?\\, ?a>>, term)
defp escape(<<c::utf8>> <> rest, buf, term) do
charstr = <<c::utf8>>
if String.printable?(charstr) and not c in [?\d, ?\b, ?\e] do
escape(rest, buf <> charstr, term)
else
escape(rest, buf <> Inspect.BitString.escape_char(c), term)
end
end
defp escape(<<c>> <> rest, buf, term),
do: escape(rest, <<buf::binary, Inspect.BitString.escape_char(c)>>, term)
defp escape(<<>>, buf, _), do: buf
end
defimpl Inspect, for: Function do
@@ -432,7 +450,7 @@ defimpl Inspect, for: Function do
if fun_info[:type] == :external and fun_info[:env] == [] do
"&#{Inspect.Atom.inspect(mod)}.#{fun_info[:name]}/#{fun_info[:arity]}"
else
case atom_to_list(mod) do
case Atom.to_char_list(mod) do
'elixir_compiler_' ++ _ ->
if function_exported?(mod, :__RELATIVE__, 0) do
"#Function<#{uniq(fun_info)} in file:#{mod.__RELATIVE__}>"
@@ -455,7 +473,7 @@ defimpl Inspect, for: Function do
end
defp extract_name(name) do
name = atom_to_binary(name)
name = Atom.to_string(name)
case :binary.split(name, "-", [:global]) do
["", name | _] -> "." <> name
_ -> "." <> name
@@ -463,27 +481,27 @@ defimpl Inspect, for: Function do
end
defp uniq(fun_info) do
integer_to_binary(fun_info[:new_index]) <> "." <>
integer_to_binary(fun_info[:uniq])
Integer.to_string(fun_info[:new_index]) <> "." <>
Integer.to_string(fun_info[:uniq])
end
end
defimpl Inspect, for: PID do
def inspect(pid, _opts) do
"#PID" <> iodata_to_binary(:erlang.pid_to_list(pid))
"#PID" <> IO.iodata_to_binary(:erlang.pid_to_list(pid))
end
end
defimpl Inspect, for: Port do
def inspect(port, _opts) do
iodata_to_binary :erlang.port_to_list(port)
IO.iodata_to_binary :erlang.port_to_list(port)
end
end
defimpl Inspect, for: Reference do
def inspect(ref, _opts) do
'#Ref' ++ rest = :erlang.ref_to_list(ref)
"#Reference" <> iodata_to_binary(rest)
"#Reference" <> IO.iodata_to_binary(rest)
end
end
@@ -496,23 +514,11 @@ defimpl Inspect, for: Any do
else
dunder ->
if :maps.keys(dunder) == :maps.keys(map) do
Inspect.Map.inspect(:maps.remove(:__struct__, map),
Inspect.Atom.inspect(struct, opts), opts)
pruned = :maps.remove(:__exception__, :maps.remove(:__struct__, map))
Inspect.Map.inspect(pruned, Inspect.Atom.inspect(struct, opts), opts)
else
Inspect.Map.inspect(map, opts)
end
end
end
end
defimpl Inspect, for: HashDict do
def inspect(dict, opts) do
concat ["#HashDict<", Inspect.List.inspect(HashDict.to_list(dict), opts), ">"]
end
end
defimpl Inspect, for: HashSet do
def inspect(set, opts) do
concat ["#HashSet<", Inspect.List.inspect(HashSet.to_list(set), opts), ">"]
end
end
+80 -86
View File
@@ -4,27 +4,32 @@ defmodule Inspect.Opts do
The following fields are available:
* `:structs` - when false, structs are not formatted by the inspect protocol,
they are instead printed as maps, defaults to true;
* `:structs` - when false, structs are not formatted by the inspect
protocol, they are instead printed as maps, defaults to true.
* `:binaries` - when `:as_strings` all binaries will be printed as strings,
non-printable bytes will be escaped; when `:as_binaries` all
binaries will be printed in bit syntax; when the default
`:infer`, the binary will be printed as a string if it is
printable, otherwise in bit syntax;
* `:binaries` - when `:as_strings` all binaries will be printed as strings,
non-printable bytes will be escaped.
* `:char_lists` - when `:as_char_lists` all lists will be printed as char lists,
non-printable elements will be escaped; when `:as_lists` all
lists will be printed as lists; when the default `:infer`, the
list will be printed as a char list if it is printable,
otherwise as list;
When `:as_binaries` all binaries will be printed in bit syntax.
* `:limit` - limits the number of items that are printed for tuples, bitstrings,
and lists, does not apply to strings nor char lists, defaults to 50;
When the default `:infer`, the binary will be printed as a string if it
is printable, otherwise in bit syntax.
* `:pretty` - if set to true enables pretty printing, defaults to false;
* `:char_lists` - when `:as_char_lists` all lists will be printed as char
lists, non-printable elements will be escaped.
* `:width` - defaults to the 80 characters;
When `:as_lists` all lists will be printed as lists.
When the default `:infer`, the list will be printed as a char list if it
is printable, otherwise as list.
* `:limit` - limits the number of items that are printed for tuples,
bitstrings, and lists, does not apply to strings nor char lists, defaults
to 50.
* `:pretty` - if set to true enables pretty printing, defaults to false.
* `:width` - defaults to the 80 characters.
"""
defstruct structs: true :: boolean,
@@ -32,8 +37,7 @@ defmodule Inspect.Opts do
char_lists: :infer :: :infer | :as_lists | :as_char_lists,
limit: 50 :: pos_integer,
width: 80 :: pos_integer | :infinity,
pretty: false :: boolean,
records: true :: boolean
pretty: false :: boolean
end
defmodule Inspect.Algebra do
@@ -114,19 +118,25 @@ defmodule Inspect.Algebra do
@type t :: :doc_nil | :doc_line | doc_cons | doc_nest | doc_break | doc_group | binary
require Record
@typep doc_cons :: {:doc_cons, t, t}
Record.defrecordp :doc_cons, left: :doc_nil, right: :doc_nil
defmacrop doc_cons(left, right) do
quote do: {:doc_cons, unquote(left), unquote(right)}
end
@typep doc_nest :: {:doc_nest, non_neg_integer, t}
Record.defrecordp :doc_nest, indent: 1, doc: :doc_nil
@typep doc_nest :: {:doc_nest, t, non_neg_integer}
defmacrop doc_nest(doc, indent) do
quote do: {:doc_nest, unquote(doc), unquote(indent) }
end
@typep doc_break :: {:doc_break, binary}
Record.defrecordp :doc_break, str: " "
defmacrop doc_break(break) do
quote do: {:doc_break, unquote(break)}
end
@typep doc_group :: {:doc_group, t}
Record.defrecordp :doc_group, doc: :doc_nil
defmacrop doc_group(group) do
quote do: {:doc_group, unquote(group)}
end
defmacrop is_doc(doc) do
if Macro.Env.in_guard?(__CALLER__) do
@@ -154,23 +164,6 @@ defmodule Inspect.Algebra do
according to the inspect protocol.
"""
@spec to_doc(any, Inspect.Opts.t) :: t
def to_doc(arg, %Inspect.Opts{} = opts) when is_record(arg) do
# IO.write :stderr, "inspect records is deprecated (just remove this code when protocols+records are removed)\n#{Exception.format_stacktrace}"
if opts.records do
try do
Inspect.inspect(arg, opts)
rescue
e ->
res = Inspect.Tuple.inspect(arg, opts)
raise ArgumentError, message:
"Got #{inspect e.__record__(:name)} with message " <>
"#{Exception.message(e)} while inspecting #{pretty(res, opts.width)}"
end
else
Inspect.Tuple.inspect(arg, opts)
end
end
def to_doc(%{__struct__: struct} = map, %Inspect.Opts{} = opts) when is_atom(struct) do
if opts.structs do
try do
@@ -178,8 +171,8 @@ defmodule Inspect.Algebra do
rescue
e ->
res = Inspect.Map.inspect(map, opts)
raise ArgumentError, message:
"Got #{inspect e.__record__(:name)} with message " <>
raise ArgumentError,
"Got #{inspect e.__struct__} with message " <>
"\"#{Exception.message(e)}\" while inspecting #{pretty(res, opts.width)}"
end
else
@@ -192,8 +185,7 @@ defmodule Inspect.Algebra do
end
@doc """
Returns `:doc_nil` which is a document entity used to represent
nothingness. Takes no arguments.
Returns a document entity used to represent nothingness.
## Examples
@@ -205,8 +197,7 @@ defmodule Inspect.Algebra do
def empty, do: :doc_nil
@doc """
Concatenates two document entities. Takes two arguments:
left doc and right doc. Returns a DocCons doc
Concatenates two document entities.
## Examples
@@ -217,7 +208,7 @@ defmodule Inspect.Algebra do
"""
@spec concat(t, t) :: doc_cons
def concat(x, y) when is_doc(x) and is_doc(y) do
doc_cons(left: x, right: y)
doc_cons(x, y)
end
@doc """
@@ -228,15 +219,16 @@ defmodule Inspect.Algebra do
folddoc(docs, &concat(&1, &2))
end
@doc """
Nests document entity `x` positions deep. Nesting will be
appended to the line breaks.
@doc ~S"""
Nests document entity `x` positions deep.
Nesting will be appended to the line breaks.
## Examples
iex> doc = Inspect.Algebra.nest(Inspect.Algebra.concat(Inspect.Algebra.break, "6"), 5)
iex> Inspect.Algebra.pretty(doc, 80)
" 6"
iex> doc = Inspect.Algebra.nest(Inspect.Algebra.glue("hello", "world"), 5)
iex> Inspect.Algebra.pretty(doc, 5)
"hello\n world"
"""
@spec nest(t, non_neg_integer) :: doc_nest
@@ -245,13 +237,15 @@ defmodule Inspect.Algebra do
end
def nest(x, i) when is_doc(x) and is_integer(i) do
doc_nest(indent: i, doc: x)
doc_nest(x, i)
end
@doc ~S"""
Document entity representing a break. This break can
be rendered as a linebreak or as spaces, depending on the
`mode` of the chosen layout or the provided separator.
Document entity representing a break.
This break can be rendered as a linebreak or as spaces,
depending on the `mode` of the chosen layout or the provided
separator.
## Examples
@@ -270,10 +264,10 @@ defmodule Inspect.Algebra do
"""
@spec break(binary) :: doc_break
def break(s) when is_binary(s), do: doc_break(str: s)
def break(s) when is_binary(s), do: doc_break(s)
@spec break() :: doc_break
def break(), do: doc_break(str: @break)
def break(), do: doc_break(@break)
@doc """
Inserts a break between two docs. See `break/1` for more info.
@@ -317,7 +311,7 @@ defmodule Inspect.Algebra do
"""
@spec group(t) :: doc_group
def group(d) when is_doc(d) do
doc_group(doc: d)
doc_group(d)
end
@doc """
@@ -393,15 +387,15 @@ defmodule Inspect.Algebra do
## Examples
iex> doc = Inspect.Algebra.surround_many("[", Enum.to_list(1..5), "]", :infinity, &integer_to_binary(&1))
iex> doc = Inspect.Algebra.surround_many("[", Enum.to_list(1..5), "]", :infinity, &Integer.to_string(&1))
iex> Inspect.Algebra.pretty(doc, 5)
"[1,\n 2,\n 3,\n 4,\n 5]"
iex> doc = Inspect.Algebra.surround_many("[", Enum.to_list(1..5), "]", 3, &integer_to_binary(&1))
iex> doc = Inspect.Algebra.surround_many("[", Enum.to_list(1..5), "]", 3, &Integer.to_string(&1))
iex> Inspect.Algebra.pretty(doc, 20)
"[1, 2, 3, ...]"
iex> doc = Inspect.Algebra.surround_many("[", Enum.to_list(1..5), "]", 3, &integer_to_binary(&1), "!")
iex> doc = Inspect.Algebra.surround_many("[", Enum.to_list(1..5), "]", 3, &Integer.to_string(&1), "!")
iex> Inspect.Algebra.pretty(doc, 20)
"[1! 2! 3! ...]"
@@ -451,7 +445,7 @@ defmodule Inspect.Algebra do
"""
@spec pretty(t, non_neg_integer | :infinity) :: binary
def pretty(d, w) do
sdoc = format w, 0, [{0, default_mode(w), doc_group(doc: d)}]
sdoc = format w, 0, [{0, default_mode(w), doc_group(d)}]
render(sdoc)
end
@@ -465,28 +459,28 @@ defmodule Inspect.Algebra do
@doc false
@spec fits?(integer, [{integer, mode, t}]) :: boolean
def fits?(w, _) when w < 0, do: false
def fits?(_, []), do: true
def fits?(_, [{_, _, :doc_line} | _]), do: true
def fits?(w, [{_, _, :doc_nil} | t]), do: fits?(w, t)
def fits?(w, [{i, m, doc_cons(left: x, right: y)} | t]), do: fits?(w, [{i, m, x} | [{i, m, y} | t]])
def fits?(w, [{i, m, doc_nest(indent: j, doc: x)} | t]), do: fits?(w, [{i + j, m, x} | t])
def fits?(w, [{i, _, doc_group(doc: x)} | t]), do: fits?(w, [{i, :flat, x} | t])
def fits?(w, [{_, _, s} | t]) when is_binary(s), do: fits?((w - byte_size s), t)
def fits?(w, [{_, :flat, doc_break(str: s)} | t]), do: fits?((w - byte_size s), t)
def fits?(_, [{_, :break, doc_break(str: _)} | _]), do: true
def fits?(w, _) when w < 0, do: false
def fits?(_, []), do: true
def fits?(_, [{_, _, :doc_line} | _]), do: true
def fits?(w, [{_, _, :doc_nil} | t]), do: fits?(w, t)
def fits?(w, [{i, m, doc_cons(x, y)} | t]), do: fits?(w, [{i, m, x} | [{i, m, y} | t]])
def fits?(w, [{i, m, doc_nest(x, j)} | t]), do: fits?(w, [{i + j, m, x} | t])
def fits?(w, [{i, _, doc_group(x)} | t]), do: fits?(w, [{i, :flat, x} | t])
def fits?(w, [{_, _, s} | t]) when is_binary(s), do: fits?((w - byte_size s), t)
def fits?(w, [{_, :flat, doc_break(s)} | t]), do: fits?((w - byte_size s), t)
def fits?(_, [{_, :break, doc_break(_)} | _]), do: true
@doc false
@spec format(integer | :infinity, integer, [{integer, mode, t}]) :: [binary]
def format(_, _, []), do: []
def format(w, _, [{i, _, :doc_line} | t]), do: [indent(i) | format(w, i, t)]
def format(w, k, [{_, _, :doc_nil} | t]), do: format(w, k, t)
def format(w, k, [{i, m, doc_cons(left: x, right: y)} | t]), do: format(w, k, [{i, m, x} | [{i, m, y} | t]])
def format(w, k, [{i, m, doc_nest(indent: j, doc: x)} | t]), do: format(w, k, [{i + j, m, x} | t])
def format(w, k, [{i, m, doc_group(doc: x)} | t]), do: format(w, k, [{i, m, x} | t])
def format(w, k, [{_, _, s} | t]) when is_binary(s), do: [s | format(w, (k + byte_size s), t)]
def format(w, k, [{_, :flat, doc_break(str: s)} | t]), do: [s | format(w, (k + byte_size s), t)]
def format(w, k, [{i, :break, doc_break(str: s)} | t]) do
def format(_, _, []), do: []
def format(w, _, [{i, _, :doc_line} | t]), do: [indent(i) | format(w, i, t)]
def format(w, k, [{_, _, :doc_nil} | t]), do: format(w, k, t)
def format(w, k, [{i, m, doc_cons(x, y)} | t]), do: format(w, k, [{i, m, x} | [{i, m, y} | t]])
def format(w, k, [{i, m, doc_nest(x, j)} | t]), do: format(w, k, [{i + j, m, x} | t])
def format(w, k, [{i, m, doc_group(x)} | t]), do: format(w, k, [{i, m, x} | t])
def format(w, k, [{_, _, s} | t]) when is_binary(s), do: [s | format(w, (k + byte_size s), t)]
def format(w, k, [{_, :flat, doc_break(s)} | t]), do: [s | format(w, (k + byte_size s), t)]
def format(w, k, [{i, :break, doc_break(s)} | t]) do
k = k + byte_size(s)
if w == :infinity or fits?(w - k, t) do
@@ -502,6 +496,6 @@ defmodule Inspect.Algebra do
@doc false
@spec render([binary]) :: binary
def render(sdoc) do
iodata_to_binary sdoc
IO.iodata_to_binary sdoc
end
end
+67
View File
@@ -69,4 +69,71 @@ defmodule Integer do
defp do_parse(bitstring, acc) do
{acc, bitstring}
end
@doc """
Returns a binary which corresponds to the text representation
of `some_integer`.
Inlined by the compiler.
## Examples
iex> Integer.to_string(123)
"123"
"""
@spec to_string(integer) :: String.t
def to_string(some_integer) do
:erlang.integer_to_binary(some_integer)
end
@doc """
Returns a binary which corresponds to the text representation
of `some_integer` in base `base`.
Inlined by the compiler.
## Examples
iex> Integer.to_string(100, 16)
"64"
"""
@spec to_string(integer, pos_integer) :: String.t
def to_string(some_integer, base) do
:erlang.integer_to_binary(some_integer, base)
end
@doc """
Returns a char list which corresponds to the text representation of the given integer.
Inlined by the compiler.
## Examples
iex> Integer.to_char_list(7)
'7'
"""
@spec to_char_list(integer) :: list
def to_char_list(number) do
:erlang.integer_to_list(number)
end
@doc """
Returns a char list which corresponds to the text representation of the
given integer in the given case.
Inlined by the compiler.
## Examples
iex> Integer.to_char_list(1023, 16)
'3FF'
"""
@spec to_char_list(integer, pos_integer) :: list
def to_char_list(number, base) do
:erlang.integer_to_list(number, base)
end
end
+124 -105
View File
@@ -1,66 +1,3 @@
defexception IO.StreamError, [:reason, :message] do
def exception(opts) do
reason = opts[:reason]
formatted = iodata_to_binary(:file.format_error(reason))
IO.StreamError[message: "error during streaming: #{formatted}", reason: reason]
end
end
defmodule IO.Stream do
@moduledoc """
Defines a `IO.Stream` struct returned by `IO.stream/2` and `IO.binstream/2`.
The following fields are public:
* `device` - the IO device
* `raw` - a boolean indicating if bin functions should be used
* `line_or_bytes` - if reading should read lines or a given amount of bytes
"""
defstruct device: nil, raw: true, line_or_bytes: :line
defimpl Collectable do
def empty(stream) do
stream
end
def into(%{device: device, raw: raw} = stream) do
{:ok, into(stream, device, raw)}
end
defp into(stream, device, raw) do
fn
:ok, {:cont, x} ->
case raw do
true -> IO.binwrite(device, x)
false -> IO.write(device, x)
end
:ok, _ -> stream
end
end
end
defimpl Enumerable do
def reduce(%{device: device, raw: raw, line_or_bytes: line_or_bytes}, acc, fun) do
next_fun =
case raw 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)
end
def count(_stream) do
{:error, __MODULE__}
end
def member?(_stream, _term) do
{:error, __MODULE__}
end
end
end
defmodule IO do
@moduledoc """
Functions handling IO.
@@ -84,17 +21,18 @@ defmodule IO do
the atom must be the name of a registered process. However,
there are three exceptions for this rule:
* `:standard_io` - when the `:standard_io` atom is given,
it is treated as a shortcut for `Process.group_leader`
* `:standard_io` - when the `:standard_io` atom is given,
it is treated as a shortcut for `Process.group_leader`
* `:stdio` - is a shortcut for `:standard_io`
* `:stdio` - is a shortcut for `:standard_io`
* `:stderr` - is a shortcut for `:standard_error`
* `:stderr` - is a shortcut for `:standard_error`
"""
@type device :: atom | pid
@type nodata :: {:error, term} | :eof
@type chardata() :: :unicode.chardata()
import :erlang, only: [group_leader: 0]
@@ -108,15 +46,15 @@ defmodule IO do
Reads `count` characters from the IO device or until
the end of the line if `:line` is given. It returns:
* `data` - The input characters.
* `data` - the input characters
* `:eof` - End of file was encountered.
* `:eof` - end of file was encountered
* `{:error, reason}` - Other (rare) error condition,
for instance `{:error, :estale}` if reading from an
NFS file system.
* `{:error, reason}` - other (rare) error condition;
for instance, `{:error, :estale}` if reading from an
NFS volume
"""
@spec read(device, :line | non_neg_integer) :: char_data | nodata
@spec read(device, :line | non_neg_integer) :: chardata | nodata
def read(device \\ group_leader, chars_or_line)
def read(device, :line) do
@@ -131,13 +69,13 @@ defmodule IO do
Reads `count` bytes from the IO device or until
the end of the line if `:line` is given. It returns:
* `data` - The input characters.
* `data` - the input characters
* `:eof` - End of file was encountered.
* `:eof` - end of file was encountered
* `{:error, reason}` - Other (rare) error condition,
for instance `{:error, :estale}` if reading from an
NFS file system.
* `{:error, reason}` - other (rare) error condition;
for instance, `{:error, :estale}` if reading from an
NFS volume
"""
@spec binread(device, :line | non_neg_integer) :: iodata | nodata
def binread(device \\ group_leader, chars_or_line)
@@ -171,9 +109,9 @@ defmodule IO do
#=> "error"
"""
@spec write(device, char_data | String.Chars.t) :: :ok
@spec write(device, chardata | String.Chars.t) :: :ok
def write(device \\ group_leader(), item) do
:io.put_chars map_dev(device), to_char_data(item)
:io.put_chars map_dev(device), to_chardata(item)
end
@doc """
@@ -190,12 +128,12 @@ defmodule IO do
@doc """
Writes the argument to the device, similar to `write/2`,
but adds a newline at the end. The argument is expected
to be a char_data.
to be a chardata.
"""
@spec puts(device, char_data | String.Chars.t) :: :ok
@spec puts(device, chardata | String.Chars.t) :: :ok
def puts(device \\ group_leader(), item) do
erl_dev = map_dev(device)
:io.put_chars erl_dev, [to_char_data(item), ?\n]
:io.put_chars erl_dev, [to_chardata(item), ?\n]
end
@doc """
@@ -236,16 +174,16 @@ defmodule IO do
Otherwise, `count` is the number of raw bytes to be retrieved.
It returns:
* `data` - The input characters.
* `data` - the input characters
* `:eof` - End of file was encountered.
* `:eof` - end of file was encountered
* `{:error, reason}` - Other (rare) error condition,
for instance `{:error, :estale}` if reading from an
NFS file system.
* `{:error, reason}` - other (rare) error condition;
for instance, `{:error, :estale}` if reading from an
NFS volume
"""
@spec getn(char_data | String.Chars.t, pos_integer) :: char_data | nodata
@spec getn(device, char_data | String.Chars.t) :: char_data | nodata
@spec getn(chardata | String.Chars.t, pos_integer) :: chardata | nodata
@spec getn(device, chardata | String.Chars.t) :: chardata | nodata
def getn(prompt, count \\ 1)
def getn(prompt, count) when is_integer(count) do
@@ -262,26 +200,26 @@ defmodule IO do
the number of unicode codepoints to be retrieved.
Otherwise, `count` is the number of raw bytes to be retrieved.
"""
@spec getn(device, char_data | String.Chars.t, pos_integer) :: char_data | nodata
@spec getn(device, chardata | String.Chars.t, pos_integer) :: chardata | nodata
def getn(device, prompt, count) do
:io.get_chars(map_dev(device), to_char_data(prompt), count)
:io.get_chars(map_dev(device), to_chardata(prompt), count)
end
@doc """
Reads a line from the IO device. It returns:
* `data` - The characters in the line terminated
by a LF (or end of file).
* `data` - the characters in the line terminated
by a LF (or end of file)
* `:eof` - End of file was encountered.
* `:eof` - end of file was encountered
* `{:error, reason}` - Other (rare) error condition,
for instance `{:error, :estale}` if reading from an
NFS file system.
* `{:error, reason}` - other (rare) error condition;
for instance, `{:error, :estale}` if reading from an
NFS volume
"""
@spec gets(device, char_data | String.Chars.t) :: char_data | nodata
@spec gets(device, chardata | String.Chars.t) :: chardata | nodata
def gets(device \\ group_leader(), prompt) do
:io.get_line(map_dev(device), to_char_data(prompt))
:io.get_line(map_dev(device), to_chardata(prompt))
end
@doc """
@@ -310,7 +248,7 @@ defmodule IO do
"""
@spec stream(device, :line | pos_integer) :: Enumerable.t
def stream(device, line_or_codepoints) do
%IO.Stream{device: map_dev(device), raw: false, line_or_bytes: line_or_codepoints}
IO.Stream.__build__(map_dev(device), false, line_or_codepoints)
end
@doc """
@@ -328,7 +266,88 @@ defmodule IO do
"""
@spec binstream(device, :line | pos_integer) :: Enumerable.t
def binstream(device, line_or_bytes) do
%IO.Stream{device: map_dev(device), raw: true, line_or_bytes: line_or_bytes}
IO.Stream.__build__(map_dev(device), true, line_or_bytes)
end
@doc """
Converts chardata (a list of integers representing codepoints,
lists and strings) into a string.
In case the conversion fails, it raises a `UnicodeConversionError`.
If a string is given, returns the string itself.
## Examples
iex> IO.chardata_to_string([0x00E6, 0x00DF])
"æß"
iex> IO.chardata_to_string([0x0061, "bc"])
"abc"
"""
@spec chardata_to_string(chardata) :: String.t | no_return
def chardata_to_string(string) when is_binary(string) do
string
end
def chardata_to_string(list) when is_list(list) do
case :unicode.characters_to_binary(list) do
result when is_binary(result) ->
result
{:error, encoded, rest} ->
raise UnicodeConversionError, encoded: encoded, rest: rest, kind: :invalid
{:incomplete, encoded, rest} ->
raise UnicodeConversionError, encoded: encoded, rest: rest, kind: :incomplete
end
end
@doc """
Converts iodata (a list of integers representing bytes, lists
and binaries) into a binary.
Notice that this function treats lists of integers as raw bytes
and does not perform any kind of encoding conversion. If you want
to convert from a char list to a string (UTF-8 encoded), please
use `chardata_to_string/1` instead.
If this function receives a binary, the same binary is returned.
Inlined by the compiler.
## Examples
iex> bin1 = <<1, 2, 3>>
iex> bin2 = <<4, 5>>
iex> bin3 = <<6>>
iex> IO.iodata_to_binary([bin1, 1, [2, 3, bin2], 4|bin3])
<<1,2,3,1,2,3,4,5,4,6>>
iex> bin = <<1, 2, 3>>
iex> IO.iodata_to_binary(bin)
<<1,2,3>>
"""
@spec iodata_to_binary(iodata) :: binary
def iodata_to_binary(item) do
:erlang.iolist_to_binary(item)
end
@doc """
Returns the size of an iodata.
Inlined by the compiler.
## Examples
iex> IO.iodata_length([1, 2|<<3, 4>>])
4
"""
@spec iodata_length(iodata) :: non_neg_integer
def iodata_length(item) do
:erlang.iolist_size(item)
end
@doc false
@@ -355,13 +374,13 @@ defmodule IO do
end
end
@compile {:inline, map_dev: 1, to_char_data: 1}
@compile {:inline, map_dev: 1, to_chardata: 1}
# Map the Elixir names for standard io and error to Erlang names
defp map_dev(:stdio), do: :standard_io
defp map_dev(:stderr), do: :standard_error
defp map_dev(other) when is_atom(other) or is_pid(other) or is_tuple(other), do: other
defp to_char_data(list) when is_list(list), do: list
defp to_char_data(other), do: to_string(other)
defp to_chardata(list) when is_list(list), do: list
defp to_chardata(other), do: to_string(other)
end
+7 -7
View File
@@ -7,7 +7,7 @@ defmodule IO.ANSI.Sequence do
"\e[#{unquote(code)}#{unquote(terminator)}"
end
defp escape_sequence(unquote(atom_to_list(name))) do
defp escape_sequence(unquote(Atom.to_char_list(name))) do
unquote(name)()
end
end
@@ -131,7 +131,7 @@ defmodule IO.ANSI do
defsequence :clear, "2", "J"
defp escape_sequence(other) do
raise ArgumentError, message: "invalid ANSI sequence specification: #{other}"
raise ArgumentError, "invalid ANSI sequence specification: #{other}"
end
@doc ~S"""
@@ -155,7 +155,7 @@ defmodule IO.ANSI do
"""
@spec escape(String.t, emit :: boolean) :: String.t
def escape(string, emit \\ terminal?) do
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
@@ -185,7 +185,7 @@ defmodule IO.ANSI do
"""
@spec escape_fragment(String.t, emit :: boolean) :: String.t
def escape_fragment(string, emit \\ terminal?) do
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
@@ -210,8 +210,8 @@ defmodule IO.ANSI do
end
defp do_escape(<<>>, _emit, _emitted, buffer, _acc) when is_list(buffer) do
buffer = iodata_to_binary Enum.reverse(buffer)
raise ArgumentError, message: "missing } for escape fragment #{buffer}"
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
@@ -223,6 +223,6 @@ defmodule IO.ANSI do
end
defp do_escape(<<>>, _emit, emitted, nil, acc) do
{iodata_to_binary(Enum.reverse(acc)), emitted}
{IO.iodata_to_binary(Enum.reverse(acc)), emitted}
end
end
+10 -10
View File
@@ -8,14 +8,14 @@ defmodule IO.ANSI.Docs do
The supported values are:
* `:enabled` - toggles coloring on and off (true)
* `:doc_code` - code blocks (cyan, bright)
* `:doc_inline_code` - inline code (cyan)
* `:doc_headings` - h1 and h2 headings (yellow, bright)
* `:doc_title` - top level heading (reverse, yellow, bright)
* `:doc_bold` - bold text (bright)
* `:doc_underline` - underlined text (underline)
* `:width` - the width to format the text (80)
* `:enabled` - toggles coloring on and off (true)
* `:doc_code` - code blocks (cyan, bright)
* `:doc_inline_code` - inline code (cyan)
* `:doc_headings` - h1 and h2 headings (yellow, bright)
* `:doc_title` - top level heading (reverse, yellow, bright)
* `:doc_bold` - bold text (bright)
* `:doc_underline` - underlined text (underline)
* `:width` - the width to format the text (80)
Values for the color settings are strings with
comma-separated ANSI values.
@@ -192,7 +192,7 @@ defmodule IO.ANSI.Docs do
|> handle_links
|> handle_inline(nil, [], [], options)
|> String.split(~r{\s})
|> write_with_wrap(options[:width] - size(indent), indent, from_list)
|> write_with_wrap(options[:width] - byte_size(indent), indent, from_list)
unless from_list, do: IO.puts(IO.ANSI.reset)
end
@@ -355,7 +355,7 @@ defmodule IO.ANSI.Docs do
end
defp handle_inline(<<>>, _mark, buffer, acc, _options) do
iodata_to_binary Enum.reverse([Enum.reverse(buffer)|acc])
IO.iodata_to_binary Enum.reverse([Enum.reverse(buffer)|acc])
end
defp inline_buffer(buffer, options) do
+69
View File
@@ -0,0 +1,69 @@
defmodule IO.StreamError do
defexception [:reason, :message]
def exception(opts) do
reason = opts[:reason]
formatted = IO.iodata_to_binary(:file.format_error(reason))
%IO.StreamError{message: "error during streaming: #{formatted}", reason: reason}
end
end
defmodule IO.Stream do
@moduledoc """
Defines a `IO.Stream` struct returned by `IO.stream/2` and `IO.binstream/2`.
The following fields are public:
* `device` - the IO device
* `raw` - a boolean indicating if bin functions should be used
* `line_or_bytes` - if reading should read lines or a given amount of bytes
"""
defstruct device: nil, raw: true, line_or_bytes: :line
@doc false
def __build__(device, raw, line_or_bytes) do
%IO.Stream{device: device, raw: raw, line_or_bytes: line_or_bytes}
end
defimpl Collectable do
def empty(stream) do
stream
end
def into(%{device: device, raw: raw} = stream) do
{:ok, into(stream, device, raw)}
end
defp into(stream, device, raw) do
fn
:ok, {:cont, x} ->
case raw do
true -> IO.binwrite(device, x)
false -> IO.write(device, x)
end
:ok, _ -> stream
end
end
end
defimpl Enumerable do
def reduce(%{device: device, raw: raw, line_or_bytes: line_or_bytes}, acc, fun) do
next_fun =
case raw 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)
end
def count(_stream) do
{:error, __MODULE__}
end
def member?(_stream, _term) do
{:error, __MODULE__}
end
end
end
+594 -992
View File
File diff suppressed because it is too large Load Diff
+111 -85
View File
@@ -9,15 +9,13 @@ defmodule Kernel.CLI do
This is the API invoked by Elixir boot process.
"""
def main(argv) do
argv = for arg <- argv, do: String.from_char_data!(arg)
argv = for arg <- argv, do: IO.chardata_to_string(arg)
{config, argv} = process_argv(argv, @blank_config)
{config, argv} = parse_argv(argv)
System.argv(argv)
run fn ->
command_results = Enum.map(Enum.reverse(config.commands), &process_command(&1, config))
command_errors = for {:error, msg} <- command_results, do: msg
errors = Enum.reverse(config.errors) ++ command_errors
errors = process_commands(config)
if errors != [] do
Enum.each(errors, &IO.puts(:stderr, &1))
@@ -55,6 +53,23 @@ defmodule Kernel.CLI do
end
end
@doc """
Parses ARGV returning the CLI config and trailing args.
"""
def parse_argv(argv) do
parse_argv(argv, @blank_config)
end
@doc """
Process commands according to the parsed config from `parse_argv/1`.
Returns all errors.
"""
def process_commands(config) do
results = Enum.map(Enum.reverse(config.commands), &process_command(&1, config))
errors = for {:error, msg} <- results, do: msg
Enum.reverse(config.errors, errors)
end
## Helpers
defp at_exit(status) do
@@ -75,7 +90,7 @@ defmodule Kernel.CLI do
end
defp shared_option?(list, config, callback) do
case process_shared(list, config) do
case parse_shared(list, config) do
{[h|hs], _} when h == hd(list) ->
new_config = %{config | errors: ["#{h} : Unknown option" | config.errors]}
callback.(hs, new_config)
@@ -106,160 +121,173 @@ defmodule Kernel.CLI do
[]
end
# Process shared options
# Parse shared options
defp process_shared([opt|_t], _config) when opt in ["-v", "--version"] do
defp parse_shared([opt|_t], _config) when opt in ["-v", "--version"] do
IO.puts "Elixir #{System.version}"
System.halt 0
end
defp process_shared(["-pa", h|t], config) do
Enum.each Path.wildcard(Path.expand(h)), &Code.prepend_path(&1)
process_shared t, config
defp parse_shared(["-pa", h|t], config) do
add_code_path(h, &Code.prepend_path/1)
parse_shared t, config
end
defp process_shared(["-pz", h|t], config) do
Enum.each Path.wildcard(Path.expand(h)), &Code.append_path(&1)
process_shared t, config
defp parse_shared(["-pz", h|t], config) do
add_code_path(h, &Code.append_path/1)
parse_shared t, config
end
defp process_shared(["--app", h|t], config) do
process_shared t, %{config | commands: &[{:app, h}|&1]}
defp parse_shared(["--app", h|t], config) do
parse_shared t, %{config | commands: [{:app, h} | config.commands]}
end
defp process_shared(["--no-halt"|t], config) do
process_shared t, %{config | halt: false}
defp parse_shared(["--no-halt"|t], config) do
parse_shared t, %{config | halt: false}
end
defp process_shared(["-e", h|t], config) do
process_shared t, %{config | commands: [{:eval, h} | config.commands]}
defp parse_shared(["-e", h|t], config) do
parse_shared t, %{config | commands: [{:eval, h} | config.commands]}
end
defp process_shared(["-r", h|t], config) do
process_shared t, %{config | commands: [{:require, h} | config.commands]}
defp parse_shared(["-r", h|t], config) do
parse_shared t, %{config | commands: [{:require, h} | config.commands]}
end
defp process_shared(["-pr", h|t], config) do
process_shared t, %{config | commands: [{:parallel_require, h} | config.commands]}
defp parse_shared(["-pr", h|t], config) do
parse_shared t, %{config | commands: [{:parallel_require, h} | config.commands]}
end
defp process_shared([erl, _|t], config) when erl in ["--erl", "--sname", "--name", "--cookie"] do
process_shared t, config
defp parse_shared([erl, _|t], config) when erl in ["--erl", "--sname", "--name", "--cookie"] do
parse_shared t, config
end
defp process_shared([erl|t], config) when erl in ["--detached", "--hidden", "--gen-debug"] do
process_shared t, config
defp parse_shared([erl|t], config) when erl in ["--detached", "--hidden"] do
parse_shared t, config
end
defp process_shared(list, config) do
defp parse_shared(list, config) do
{list, config}
end
defp add_code_path(path, fun) do
path = Path.expand(path)
case Path.wildcard(path) do
[] -> fun.(path)
list -> Enum.each(list, fun)
end
end
# Process init options
defp process_argv(["--"|t], config) do
defp parse_argv(["--"|t], config) do
{config, t}
end
defp process_argv(["+elixirc"|t], config) do
process_compiler t, config
defp parse_argv(["+elixirc"|t], config) do
parse_compiler t, config
end
defp process_argv(["+iex"|t], config) do
process_iex t, config
defp parse_argv(["+iex"|t], config) do
parse_iex t, config
end
defp process_argv(["-S", h|t], config) do
defp parse_argv(["-S", h|t], config) do
{%{config | commands: [{:script, h} | config.commands]}, t}
end
defp process_argv([h|t] = list, config) do
defp parse_argv([h|t] = list, config) do
case h do
"-" <> _ ->
shared_option? list, config, &process_argv(&1, &2)
shared_option? list, config, &parse_argv(&1, &2)
_ ->
{%{config | commands: [{:file, h} | config.commands]}, t}
if Keyword.has_key?(config.commands, :eval) do
{config, list}
else
{%{config | commands: [{:file, h} | config.commands]}, t}
end
end
end
defp process_argv([], config) do
defp parse_argv([], config) do
{config, []}
end
# Process compiler options
# Parse compiler options
defp process_compiler(["--"|t], config) do
defp parse_compiler(["--"|t], config) do
{config, t}
end
defp process_compiler(["-o", h|t], config) do
process_compiler t, %{config | output: h}
defp parse_compiler(["-o", h|t], config) do
parse_compiler t, %{config | output: h}
end
defp process_compiler(["--no-docs"|t], config) do
process_compiler t, %{config | compiler_options: [{:docs, false} | config.compiler_options]}
defp parse_compiler(["--no-docs"|t], config) do
parse_compiler t, %{config | compiler_options: [{:docs, false} | config.compiler_options]}
end
defp process_compiler(["--no-debug-info"|t], config) do
process_compiler t, %{config | compiler_options: [{:debug_info, false} | config.compiler_options]}
defp parse_compiler(["--no-debug-info"|t], config) do
parse_compiler t, %{config | compiler_options: [{:debug_info, false} | config.compiler_options]}
end
defp process_compiler(["--ignore-module-conflict"|t], config) do
process_compiler t, %{config | compiler_options: [{:ignore_module_conflict, true} | config.compiler_options]}
defp parse_compiler(["--ignore-module-conflict"|t], config) do
parse_compiler t, %{config | compiler_options: [{:ignore_module_conflict, true} | config.compiler_options]}
end
defp process_compiler(["--warnings-as-errors"|t], config) do
process_compiler t, %{config | compiler_options: [{:warnings_as_errors, true} | config.compiler_options]}
defp parse_compiler(["--warnings-as-errors"|t], config) do
parse_compiler t, %{config | compiler_options: [{:warnings_as_errors, true} | config.compiler_options]}
end
defp process_compiler(["--verbose"|t], config) do
process_compiler t, %{config | verbose_compile: true}
defp parse_compiler(["--verbose"|t], config) do
parse_compiler t, %{config | verbose_compile: true}
end
defp process_compiler([h|t] = list, config) do
defp parse_compiler([h|t] = list, config) do
case h do
"-" <> _ ->
shared_option? list, config, &process_compiler(&1, &2)
shared_option? list, config, &parse_compiler(&1, &2)
_ ->
pattern = if :filelib.is_dir(h), do: "#{h}/**/*.ex", else: h
process_compiler t, %{config | compile: [pattern | config.compile]}
pattern = if File.dir?(h), do: "#{h}/**/*.ex", else: h
parse_compiler t, %{config | compile: [pattern | config.compile]}
end
end
defp process_compiler([], config) do
defp parse_compiler([], config) do
{%{config | commands: [{:compile, config.compile}|config.commands]}, []}
end
# Process iex options
# Parse iex options
defp process_iex(["--"|t], config) do
defp parse_iex(["--"|t], config) do
{config, t}
end
# This clause is here so that Kernel.CLI does not error out with "unknown
# option"
defp process_iex(["--dot-iex", _|t], config) do
process_iex t, config
# This clause is here so that Kernel.CLI does not
# error out with "unknown option"
defp parse_iex(["--dot-iex", _|t], config) do
parse_iex t, config
end
defp process_iex([opt, _|t], config) when opt in ["--remsh"] do
process_iex t, config
defp parse_iex([opt, _|t], config) when opt in ["--remsh"] do
parse_iex t, config
end
defp process_iex(["-S", h|t], config) do
defp parse_iex(["-S", h|t], config) do
{%{config | commands: [{:script, h} | config.commands]}, t}
end
defp process_iex([h|t] = list, config) do
defp parse_iex([h|t] = list, config) do
case h do
"-" <> _ ->
shared_option? list, config, &process_iex(&1, &2)
shared_option? list, config, &parse_iex(&1, &2)
_ ->
{%{config | commands: [{:file, h} | config.commands]}, t}
end
end
defp process_iex([], config) do
defp parse_iex([], config) do
{config, []}
end
@@ -267,7 +295,7 @@ defmodule Kernel.CLI do
defp process_command({:cookie, h}, _config) do
if Node.alive? do
wrapper fn -> Node.set_cookie(binary_to_atom(h)) end
wrapper fn -> Node.set_cookie(String.to_atom(h)) end
else
{:error, "--cookie : Cannot set cookie if the node is not alive (set --name or --sname)"}
end
@@ -278,7 +306,7 @@ defmodule Kernel.CLI do
end
defp process_command({:app, app}, _config) when is_binary(app) do
case Application.ensure_all_started(binary_to_atom(app)) do
case Application.ensure_all_started(String.to_atom(app)) do
{:error, {app, reason}} ->
{:error, "--app : Could not start application #{app}: " <>
Application.format_error(reason)}
@@ -296,7 +324,7 @@ defmodule Kernel.CLI do
end
defp process_command({:file, file}, _config) when is_binary(file) do
if :filelib.is_regular(file) do
if File.regular?(file) do
wrapper fn -> Code.require_file(file) end
else
{:error, "No file named #{file}"}
@@ -304,9 +332,7 @@ defmodule Kernel.CLI do
end
defp process_command({:require, pattern}, _config) when is_binary(pattern) do
files = Path.wildcard(pattern)
files = Enum.uniq(files)
files = Enum.filter files, &:filelib.is_regular(&1)
files = filter_patterns(pattern)
if files != [] do
wrapper fn -> Enum.map files, &Code.require_file(&1) end
@@ -316,9 +342,7 @@ defmodule Kernel.CLI do
end
defp process_command({:parallel_require, pattern}, _config) when is_binary(pattern) do
files = Path.wildcard(pattern)
files = Enum.uniq(files)
files = Enum.filter files, &:filelib.is_regular(&1)
files = filter_patterns(pattern)
if files != [] do
wrapper fn -> Kernel.ParallelRequire.files(files) end
@@ -330,7 +354,7 @@ defmodule Kernel.CLI do
defp process_command({:compile, patterns}, config) do
:filelib.ensure_dir(:filename.join(config.output, "."))
case match_regular_files(patterns) do
case filter_multiple_patterns(patterns) do
{:ok, []} ->
{:error, "No files matched provided patterns"}
{:ok, files} ->
@@ -344,9 +368,13 @@ defmodule Kernel.CLI do
end
end
defp match_regular_files(patterns) do
defp filter_patterns(pattern) do
Enum.filter(Enum.uniq(Path.wildcard(pattern)), &File.regular?(&1))
end
defp filter_multiple_patterns(patterns) do
matched_files = Enum.map patterns, fn(pattern) ->
case Path.wildcard(pattern) do
case filter_patterns(pattern) do
[] -> {:missing, pattern}
files -> {:ok, files}
end
@@ -361,9 +389,7 @@ defmodule Kernel.CLI do
&elem(&1, 1)
if missing_patterns == [] do
files = Enum.uniq(Enum.concat(files))
files = Enum.filter files, &:filelib.is_regular(&1)
{:ok, files}
{:ok, Enum.uniq(Enum.concat(files))}
else
{:missing, Enum.uniq(missing_patterns)}
end
+2 -1
View File
@@ -7,7 +7,8 @@
# Note that since this is required for bootstrap, we can't use
# any of the `GenServer.Behaviour` conveniences.
defmodule Kernel.LexicalTracker do
@timeout 30_000
@moduledoc false
@timeout 30_000
@behaviour :gen_server
@import 2
+5 -3
View File
@@ -17,9 +17,11 @@ defmodule Kernel.ParallelCompiler do
This function receives a set of callbacks as options:
* `:each_file` - for each file compiled, invokes the callback passing the file
* `:each_module` - for each module compiled, invokes the callback
passing the file, module and the module bytecode
* `:each_file` - for each file compiled, invokes the callback passing the
file
* `: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.
Returns the modules generated by each compiled file.
+82 -48
View File
@@ -29,7 +29,7 @@ defmodule Kernel.SpecialForms do
Conveniences for manipulating tuples can be found in the
`Tuple` module. Some functions for working with tuples are
also available in `Kernel`, namely `Kernel.elem/2`,
`Kernel.set_elem/3` and `Kernel.tuple_size/1`.
`Kernel.put_elem/3` and `Kernel.tuple_size/1`.
## Examples
@@ -118,18 +118,25 @@ defmodule Kernel.SpecialForms do
end
end
Now a struct can be created as follow:
In practice though, structs are usually defined with the
`Kernel.defstruct/1` macro:
defmodule User do
defstruct name: "josé", age: 27
end
Now a struct can be created as follows:
%User{}
Underneath, a struct is just a map with a `__struct__` field
pointing to the User module:
Underneath a struct is just a map with a `__struct__` field
pointing to the `User` module:
%User{} == %{__struct__: User, name: "josé", age: 27}
A struct also validates the given keys are part of the defined
A struct also validates that the given keys are part of the defined
struct. The example below will fail because there is no key
`:full_name` in the user struct:
`:full_name` in the `User` struct:
%User{full_name: "José Valim"}
@@ -148,8 +155,10 @@ defmodule Kernel.SpecialForms do
compilation time and it will guarantee at runtime the given
argument is a struct, failing with `BadStructError` otherwise.
Check `Kernel.defprotocol/2` for more information on how structs
can be used with protocols for polymorphic dispatch. Also,
Although structs are maps, by default structs do not implement
any of the protocols implemented for maps. Check
`Kernel.defprotocol/2` for more information on how structs
can be used with protocols for polymorphic dispatch. Also
see `Kernel.struct/2` for examples on how to create and update
structs dynamically.
"""
@@ -580,7 +589,7 @@ defmodule Kernel.SpecialForms do
defmacro import(module, opts)
@doc """
Returns the current environment information as a `Macro.Env[]` record.
Returns the current environment information as a `Macro.Env` struct.
In the environment you can access the current filename,
line numbers, set up aliases, the current function and others.
@@ -603,6 +612,14 @@ defmodule Kernel.SpecialForms do
"""
defmacro __DIR__
@doc """
Returns the current calling environment as a `Macro.Env` struct.
In the environment you can access the filename, line numbers,
set up aliases, the function and others.
"""
defmacro __CALLER__
@doc """
Accesses an already bound variable in match clauses.
@@ -653,24 +670,28 @@ defmodule Kernel.SpecialForms do
The tuple above represents a function call to `sum` passing 1, 2 and
3 as arguments. The tuple elements are:
* The first element of the tuple is always an atom or
another tuple in the same representation;
* The second element of the tuple represents metadata;
* The third element of the tuple are the arguments for the
function call. The third argument may be an atom, which is
usually a variable (or a local call);
* The first element of the tuple is always an atom or
another tuple in the same representation.
* The second element of the tuple represents metadata.
* The third element of the tuple are the arguments for the
function call. The third argument may be an atom, which is
usually a variable (or a local call).
## Options
* `:unquote` - When false, disables unquoting. Useful when you have a quote
inside another quote and want to control what quote is
able to unquote;
* `:location` - When set to `:keep`, keeps the current line and file from quote.
Read the Stacktrace information section below for more information;
* `:hygiene` - Allows a developer to disable hygiene selectively;
* `:context` - Sets the resolution context;
* `:bind_quoted` - Passes a binding to the macro. Whenever a binding is given,
`unquote` is automatically disabled;
* `:unquote` - when false, disables unquoting. Useful when you have a quote
inside another quote and want to control what quote is able to unquote.
* `:location` - when set to `:keep`, keeps the current line and file from
quote. Read the Stacktrace information section below for more
information.
* `:context` - sets the resolution context.
* `:bind_quoted` - passes a binding to the macro. Whenever a binding is
given, `unquote` is automatically disabled.
## Quote literals
@@ -864,10 +885,6 @@ defmodule Kernel.SpecialForms do
ContextHygiene.read
#=> 1
Hygiene for variables can be disabled overall as:
quote hygiene: [vars: false], do: x
## Hygiene in aliases
Aliases inside quote are hygienic by default.
@@ -987,9 +1004,6 @@ defmodule Kernel.SpecialForms do
Lazy.return_size #=> 2
As in aliases, import expansion can be explicitly disabled
via the `hygiene: [imports: false]` option.
## Stacktrace information
When defining functions via macros, developers have the option of
@@ -1055,9 +1069,9 @@ defmodule Kernel.SpecialForms do
we are passing the representation of the variable `kv`, our
code fails.
This is actually a common pitfall when developing macros. In
practice, we want to avoid doing work at compilation time as
much as possible. That said, let's attempt to improve our macro:
This is actually a common pitfall when developing macros. We are
assuming a particular shape in the macro. We can work around it
by unquoting the variable inside the quoted expression:
defmacro defkv(kv) do
quote do
@@ -1313,21 +1327,21 @@ 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.
2) The tail elements of aliases are guaranteed to always be atoms;
2. The tail elements of aliases are guaranteed to always be atoms.
3) When the head element of aliases is the atom `:Elixir`, no expansion happen;
3. When the head element of aliases is the atom `:Elixir`, no expansion happen.
4) When the head element of aliases is not an atom, it is expanded at runtime:
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]}
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:
Since `some_var` is not available at compilation time, the compiler
expands such expression to:
Module.concat [some_var, Foo]
Module.concat [some_var, Foo]
"""
defmacro __aliases__(args)
@@ -1383,7 +1397,27 @@ defmodule Kernel.SpecialForms do
case, clauses that do not explicitly bind a value have the variable
bound to nil.
"""
defmacro case(condition, blocks)
defmacro case(condition, clauses)
@doc """
Evaluates the expression corresponding to the first clause that
evaluates to truth value.
Raises an error if all conditions evaluate to to nil or false.
## Examples
cond do
1 + 1 == 1 ->
"This will never match"
2 * 2 != 4 ->
"Nor this"
true ->
"This will"
end
"""
defmacro cond(clauses)
@doc ~S"""
Evaluate the given expressions and handle any error, exit
@@ -1642,11 +1676,11 @@ defmodule Kernel.SpecialForms do
The `after` clause can be specified even if there are no match clauses.
There are two special cases for the timeout value given to `after`
* `:infinity` - The process should wait indefinitely for a matching
message, this is the same as not using a timeout.
* `:infinity` - the process should wait indefinitely for a matching
message, this is the same as not using a timeout
* 0 - if there is no matching message in the mailbox, the timeout
will occur immediately.
* 0 - if there is no matching message in the mailbox, the timeout
will occur immediately
## Variables handling
+118 -129
View File
@@ -104,36 +104,36 @@ defmodule Kernel.Typespec do
## Built-in types
Built-in type | Defined as
:-------------------- | :---------
`term` | `any`
`binary` | `<< _ :: _ * 8 >>`
`bitstring` | `<< _ :: _ * 1 >>`
`boolean` | `false` &#124; `true`
`byte` | `0..255`
`char` | `0..0xffff`
`number` | `integer` &#124; `float`
`list` | `[any]`
`maybe_improper_list` | `maybe_improper_list(any, any)`
`nonempty_list` | `nonempty_list(any)`
`iodata` | `iolist` &#124; `binary`
`iolist` | `maybe_improper_list(byte` &#124; `binary` &#124; `iolist, binary` &#124; `[])`
`module` | `atom`
`mfa` | `{atom, atom, arity}`
`arity` | `0..255`
`node` | `atom`
`timeout` | `:infinity` &#124; `non_neg_integer`
`no_return` | `none`
`fun` | `(... -> any)`
Built-in type | Defined as
:-------------------- | :---------
`term` | `any`
`binary` | `<< _ :: _ * 8 >>`
`bitstring` | `<< _ :: _ * 1 >>`
`boolean` | `false` &#124; `true`
`byte` | `0..255`
`char` | `0..0xffff`
`number` | `integer` &#124; `float`
`list` | `[any]`
`maybe_improper_list` | `maybe_improper_list(any, any)`
`nonempty_list` | `nonempty_list(any)`
`iodata` | `iolist` &#124; `binary`
`iolist` | `maybe_improper_list(byte` &#124; `binary` &#124; `iolist, binary` &#124; `[])`
`module` | `atom`
`mfa` | `{atom, atom, arity}`
`arity` | `0..255`
`node` | `atom`
`timeout` | `:infinity` &#124; `non_neg_integer`
`no_return` | `none`
`fun` | `(... -> any)`
Some built-in types cannot be expressed with valid syntax according to the
language defined above.
Built-in type | Can be interpreted as
:---------------- | :--------------------
`non_neg_integer` | `0..`
`pos_integer` | `1..`
`neg_integer` | `..-1`
Built-in type | Can be interpreted as
:---------------- | :--------------------
`non_neg_integer` | `0..`
`pos_integer` | `1..`
`neg_integer` | `..-1`
Types defined in other modules are referred to as "remote types", they are
referenced as `Module.type_name` (ex. `Enum.t` or `String.t`).
@@ -153,8 +153,6 @@ defmodule Kernel.Typespec do
@type dict(key, value) :: [{key, value}]
Types can also be defined for records, see `defrecord/3`.
## Defining a specification
@spec function_name(type1, type2) :: return_type
@@ -264,60 +262,18 @@ defmodule Kernel.Typespec do
end
end
## Helpers
@doc """
Defines a `type`, `typep` or `opaque` by receiving Erlang's typespec.
Defines a `type`, `typep` or `opaque` by receiving a typespec expression.
"""
def define_type(caller, kind, {name, _, vars} = type) when kind in [:type, :typep, :opaque] do
{kind, export} =
case kind do
:type -> {:type, true}
:typep -> {:type, false}
:opaque -> {:opaque, true}
end
module = caller.module
arity = length(vars)
Module.compile_typespec module, kind, type
if export do
Module.compile_typespec(module, :export_type, [{name, arity}])
end
define_doc(caller, kind, name, arity, export)
type
end
defp define_doc(caller, kind, name, arity, export) do
module = caller.module
doc = Module.get_attribute(module, :typedoc)
if doc do
if export do
Module.add_doc(module, caller.line, kind, {name, arity}, doc)
else
:elixir_errors.warn caller.line, caller.file, "type #{name}/#{arity} is private, " <>
"@typedoc's are always discarded for private types\n"
end
end
Module.delete_attribute(module, :typedoc)
def define_type(kind, expr, doc \\ nil, env) do
Module.store_typespec(env.module, kind, {kind, expr, doc, env})
end
@doc """
Defines a `spec` by receiving Erlang's typespec.
Defines a `spec` by receiving a typespec expression.
"""
def define_spec(module, tuple, definition) do
Module.compile_typespec module, :spec, {tuple, definition}
end
@doc """
Defines a `callback` by receiving Erlang's typespec.
"""
def define_callback(module, tuple, definition) do
Module.compile_typespec module, :callback, {tuple, definition}
def define_spec(kind, expr, env) do
Module.store_typespec(env.module, kind, {kind, expr, env})
end
@doc """
@@ -326,9 +282,12 @@ defmodule Kernel.Typespec do
for modules being compiled.
"""
def defines_type?(module, name, arity) do
finder = &match?({^name, _, vars} when length(vars) == arity, &1)
Enum.any?(Module.get_attribute(module, :type), finder) or
Enum.any?(Module.get_attribute(module, :opaque), finder)
finder = fn {_kind, expr, _doc, _caller} ->
type_to_signature(expr) == {name, arity}
end
:lists.any(finder, Module.get_attribute(module, :type)) or
:lists.any(finder, Module.get_attribute(module, :opaque))
end
@doc """
@@ -336,8 +295,10 @@ defmodule Kernel.Typespec do
This function is only available for modules being compiled.
"""
def defines_spec?(module, name, arity) do
tuple = {name, arity}
Enum.any?(Module.get_attribute(module, :spec), &match?(^tuple, &1))
finder = fn {_kind, expr, _caller} ->
spec_to_signature(expr) == {name, arity}
end
:lists.any(finder, Module.get_attribute(module, :spec))
end
@doc """
@@ -345,8 +306,10 @@ defmodule Kernel.Typespec do
This function is only available for modules being compiled.
"""
def defines_callback?(module, name, arity) do
tuple = {name, arity}
Enum.any?(Module.get_attribute(module, :callback), &match?(^tuple, &1))
finder = fn {_kind, expr, _caller} ->
spec_to_signature(expr) == {name, arity}
end
:lists.any(finder, Module.get_attribute(module, :callback))
end
@doc """
@@ -425,7 +388,7 @@ defmodule Kernel.Typespec do
case abstract_code(module) do
{:ok, abstract_code} ->
type_docs = for {:attribute, _, :typedoc, tup} <- abstract_code, do: tup
List.flatten(type_docs)
:lists.flatten(type_docs)
_ ->
nil
end
@@ -445,7 +408,7 @@ defmodule Kernel.Typespec do
case abstract_code(module) do
{:ok, abstract_code} ->
exported_types = for {:attribute, _, :export_type, types} <- abstract_code, do: types
exported_types = List.flatten(exported_types)
exported_types = :lists.flatten(exported_types)
for {:attribute, _, kind, {name, _, args} = type} <- abstract_code, kind in [:opaque, :type] do
cond do
@@ -516,10 +479,40 @@ defmodule Kernel.Typespec do
binary
end
## Helpers
@doc false
def spec_to_signature({:when, _, [spec, _]}),
do: type_to_signature(spec)
def spec_to_signature(other),
do: type_to_signature(other)
@doc false
def type_to_signature({:::, _, [{name, _, nil}, _]}),
do: {name, 0}
def type_to_signature({:::, _, [{name, _, args}, _]}),
do: {name, length(args)}
## Macro callbacks
@doc false
def deftype(kind, {:::, _, [{name, _, args}, definition]}, caller) when is_atom(name) and name != ::: do
def defspec(kind, expr, caller) do
Module.store_typespec(caller.module, kind, {kind, expr, caller})
end
@doc false
def deftype(kind, expr, caller) do
module = caller.module
doc = Module.get_attribute(module, :typedoc)
Module.delete_attribute(module, :typedoc)
Module.store_typespec(module, kind, {kind, expr, doc, caller})
end
## Translation from Elixir AST to typespec AST
@doc false
def translate_type(kind, {:::, _, [{name, _, args}, definition]}, doc, caller) when is_atom(name) and name != ::: do
args =
if is_atom(args) do
[]
@@ -527,31 +520,43 @@ defmodule Kernel.Typespec do
for(arg <- args, do: variable(arg))
end
vars = for {:var, _, var} <- args, do: var
spec = typespec(definition, vars, caller)
vars = for {:var, _, var} <- args, do: var
spec = typespec(definition, vars, caller)
vars = for {:var, _, _} = var <- args, do: var
type = {name, spec, vars}
vars = for {:var, _, _} = var <- args, do: var
type = {name, spec, vars}
arity = length(vars)
define_type(caller, kind, type)
{kind, export} =
case kind do
:type -> {:type, true}
:typep -> {:type, false}
:opaque -> {:opaque, true}
end
if not export and doc do
:elixir_errors.warn(caller.line, caller.file, "type #{name}/#{arity} is private, " <>
"@typedoc's are always discarded for private types\n")
end
{{kind, {name, arity}, type}, caller.line, export, doc}
end
def deftype(_kind, other, caller) do
def translate_type(_kind, other, _doc, caller) do
type_spec = Macro.to_string(other)
compile_error caller, "invalid type specification: #{type_spec}"
end
@doc false
def defspec(type, {:when, _meta, [spec, guard]}, caller) do
defspec(type, spec, guard, caller)
def translate_spec(kind, {:when, _meta, [spec, guard]}, caller) do
translate_spec(kind, spec, guard, caller)
end
def defspec(type, spec, caller) do
defspec(type, spec, [], caller)
def translate_spec(kind, spec, caller) do
translate_spec(kind, spec, [], caller)
end
defp defspec(type, {:::, meta, [{name, _, args}, return]}, guard, caller) when is_atom(name) and name != ::: do
defp translate_spec(kind, {:::, meta, [{name, _, args}, return]}, guard, caller) when is_atom(name) and name != ::: do
if is_atom(args), do: args = []
unless Keyword.keyword?(guard) do
@@ -567,12 +572,11 @@ defmodule Kernel.Typespec do
spec = {:type, line(meta), :bounded_fun, [spec, constraints]}
end
code = {{name, Kernel.length(args)}, spec}
Module.compile_typespec(caller.module, type, code)
code
arity = length(args)
{{kind, {name, arity}, spec}, caller.line}
end
defp defspec(_type, spec, _guard, caller) do
defp translate_spec(_kind, spec, _guard, caller) do
spec = Macro.to_string(spec)
compile_error caller, "invalid function type specification: #{spec}"
end
@@ -580,14 +584,14 @@ defmodule Kernel.Typespec do
defp guard_to_constraints(guard, vars, meta, caller) do
line = line(meta)
Enum.reduce(guard, [], fn
:lists.foldl(fn
{_name, {:var, _, context}}, acc when is_atom(context) ->
acc
{name, type}, acc ->
constraint = [{:atom, line, :is_subtype}, [{:var, line, name}, typespec(type, vars, caller)]]
type = {:type, line, :constraint, constraint}
[type|acc]
end) |> Enum.reverse
end, [], guard) |> :lists.reverse
end
## To AST conversion
@@ -707,10 +711,6 @@ defmodule Kernel.Typespec do
typespec_to_ast({:type, line, :char_list, []})
end
defp typespec_to_ast({:remote_type, line, [{:atom, _, :elixir}, {:atom, _, :char_data}, []]}) do
typespec_to_ast({:type, line, :char_data, []})
end
defp typespec_to_ast({:remote_type, line, [{:atom, _, :elixir}, {:atom, _, :as_boolean}, [arg]]}) do
typespec_to_ast({:type, line, :as_boolean, [arg]})
end
@@ -746,15 +746,15 @@ defmodule Kernel.Typespec do
defp typespec_to_ast(other), do: other
defp erl_to_ex_var(var) do
case atom_to_binary(var) do
case Atom.to_string(var) do
<<"_", c :: [binary, size(1)], rest :: binary>> ->
binary_to_atom("_#{String.downcase(c)}#{rest}")
String.to_atom("_#{String.downcase(c)}#{rest}")
<<c :: [binary, size(1)], rest :: binary>> ->
binary_to_atom("#{String.downcase(c)}#{rest}")
String.to_atom("#{String.downcase(c)}#{rest}")
end
end
## From AST conversion
## To typespec conversion
defp line(meta) do
case :lists.keyfind(:line, 1, meta) do
@@ -785,9 +785,9 @@ defmodule Kernel.Typespec do
## Handle maps and structs
defp typespec({:%{}, meta, fields}, vars, caller) do
fields = Enum.map(fields, fn {k, v} ->
fields = :lists.map(fn {k, v} ->
{:type, line(meta), :map_field_assoc, typespec(k, vars, caller), typespec(v, vars, caller)}
end)
end, fields)
{:type, line(meta), :map, fields}
end
@@ -828,13 +828,6 @@ defmodule Kernel.Typespec do
{:op, line(meta), op, {:integer, line(meta), integer}}
end
# Handle access macro
defp typespec({{:., meta, [Kernel, :access]}, meta1, [target, args]}, vars, caller) do
access = {{:., meta, [Kernel, :access]}, meta1,
[target, args ++ [_: {:any, [], []}]]}
typespec(Macro.expand(access, caller), vars, caller)
end
# Handle remote calls
defp typespec({{:., meta, [remote, name]}, _, args} = orig, vars, caller) do
remote = Macro.expand remote, caller
@@ -865,7 +858,7 @@ defmodule Kernel.Typespec do
# Handle variables or local calls
defp typespec({name, meta, atom}, vars, caller) when is_atom(atom) do
if name in vars do
if :lists.member(name, vars) do
{:var, line(meta), name}
else
typespec({name, meta, []}, vars, caller)
@@ -884,10 +877,6 @@ defmodule Kernel.Typespec do
typespec((quote do: :elixir.char_list()), vars, caller)
end
defp typespec({:char_data, _meta, []}, vars, caller) do
typespec((quote do: :elixir.char_data()), vars, caller)
end
defp typespec({:as_boolean, _meta, [arg]}, vars, caller) do
typespec((quote do: :elixir.as_boolean(unquote(arg))), vars, caller)
end
@@ -919,10 +908,10 @@ defmodule Kernel.Typespec do
end
defp typespec(list, vars, caller) do
[h|t] = Enum.reverse(list)
union = Enum.reduce(t, validate_kw(h, list, caller), fn(x, acc) ->
[h|t] = :lists.reverse(list)
union = :lists.foldl(fn(x, acc) ->
{:|, [], [validate_kw(x, list, caller), acc]}
end)
end, validate_kw(h, list, caller), t)
typespec({:list, [], [union]}, vars, caller)
end
+41 -19
View File
@@ -6,13 +6,14 @@ defmodule Keyword do
A keyword may have duplicated keys so it is not strictly
a dictionary. However most of the functions in this module
behaves exactly as a dictionary and mimic the API defined
behave exactly as a dictionary and mimic the API defined
by the `Dict` behaviour.
For example, `Keyword.get` will get the first entry matching
the given key, regardless if duplicated entries exist.
Similarly, `Keyword.put` and `Keyword.delete` ensure all
duplicated entries for a given key are removed when invoked.
A handful of functions exist to handle duplicated keys, in
particular, `from_enum` allows creating a new keywords without
removing duplicated keys, `get_values` returns all values for
@@ -23,6 +24,7 @@ defmodule Keyword do
in `Enum` and `List` can also be applied.
"""
@compile :inline_list_funcs
@behaviour Dict
@type key :: atom
@@ -173,7 +175,12 @@ defmodule Keyword do
"""
@spec get_values(t, key) :: [value]
def get_values(keywords, key) when is_list(keywords) and is_atom(key) do
for {k, v} <- keywords, key == k, do: v
fun = fn
{k, v} when k === key -> {true, v}
{_, _} -> false
end
:lists.filtermap(fun, keywords)
end
@doc """
@@ -191,7 +198,7 @@ defmodule Keyword do
"""
@spec keys(t) :: [key]
def keys(keywords) when is_list(keywords) do
for {key, _} <- keywords, do: key
:lists.map(fn {k, _} -> k end, keywords)
end
@doc """
@@ -205,11 +212,11 @@ defmodule Keyword do
"""
@spec values(t) :: [value]
def values(keywords) when is_list(keywords) do
for {_, value} <- keywords, do: value
:lists.map(fn {_, v} -> v end, keywords)
end
@doc """
Deletes the entry in the keyword list for a `key` with `value`.
Deletes the entries in the keyword list for a `key` with `value`.
If no `key` with `value` exists, returns the keyword list unchanged.
## Examples
@@ -226,11 +233,11 @@ defmodule Keyword do
"""
@spec delete(t, key, value) :: t
def delete(keywords, key, value) when is_list(keywords) and is_atom(key) do
for {k, v} = tuple <- keywords, key != k or value != v, do: tuple
:lists.filter(fn {k, v} -> k != key or v != value end, keywords)
end
@doc """
Deletes all entries in the keyword list for a specific `key`.
Deletes the entries in the keyword list for a specific `key`.
If the `key` does not exist, returns the keyword list unchanged.
Use `delete_first` to delete just the first entry in case of
duplicated keys.
@@ -249,7 +256,7 @@ defmodule Keyword do
"""
@spec delete(t, key) :: t
def delete(keywords, key) when is_list(keywords) and is_atom(key) do
for {k, _} = tuple <- keywords, key != k, do: tuple
:lists.filter(fn {k, _} -> k != key end, keywords)
end
@doc """
@@ -338,7 +345,8 @@ defmodule Keyword do
"""
@spec merge(t, t) :: t
def merge(d1, d2) when is_list(d1) and is_list(d2) do
d2 ++ for({k, _} = tuple <- d1, not has_key?(d2, k), do: tuple)
fun = fn {k, _v} -> not has_key?(d2, k) end
d2 ++ :lists.filter(fun, d1)
end
@doc """
@@ -387,6 +395,9 @@ defmodule Keyword do
Updates the `key` with the given function. If the `key` does
not exist, raises `KeyError`.
If there are duplicated entries, they are all removed and only the first one
is updated.
## Examples
iex> Keyword.update!([a: 1], :a, &(&1 * 2))
@@ -417,6 +428,9 @@ defmodule Keyword do
Updates the `key` with the given function. If the `key` does
not exist, inserts the given `initial` value.
If there are duplicated entries, they are all removed and only the first one
is updated.
## Examples
iex> Keyword.update([a: 1], :a, 13, &(&1 * 2))
@@ -440,8 +454,13 @@ defmodule Keyword do
end
@doc """
Splits the given keywords in two given the given keys.
Duplicated keys are preserved in the split keyword list.
Takes all entries corresponding to the given keys and extracts them into a
separate keyword list. Returns a tuple with the new list and the old list
with removed keys.
Keys for which there are no entires in the keyword list are ignored.
Entries with duplicated keys end up in the same keyword list.
## Examples
@@ -455,20 +474,22 @@ defmodule Keyword do
"""
def split(keywords, keys) when is_list(keywords) do
acc = {[], []}
{take, drop} = Enum.reduce keywords, acc, fn({k, v}, {take, drop}) ->
fun = fn {k, v}, {take, drop} ->
case k in keys do
true -> {[{k, v}|take], drop}
false -> {take, [{k, v}|drop]}
end
end
{Enum.reverse(take), Enum.reverse(drop)}
acc = {[], []}
{take, drop} = :lists.foldl(fun, acc, keywords)
{:lists.reverse(take), :lists.reverse(drop)}
end
@doc """
Takes the given keys from the dict.
Takes all entries corresponding to the given keys and returns them in a new
keyword list.
Duplicated keys are preserved in the new keyword list.
## Examples
@@ -483,11 +504,12 @@ defmodule Keyword do
"""
def take(keywords, keys) when is_list(keywords) do
for {k, _} = tuple <- keywords, k in keys, do: tuple
:lists.filter(fn {k, _} -> k in keys end, keywords)
end
@doc """
Drops the given keys from the dict.
Duplicated keys are preserved in the new keyword list.
## Examples
@@ -496,13 +518,13 @@ defmodule Keyword do
iex> Keyword.drop(d, [:b, :d])
[a: 1, c: 3]
iex> d = [a: 1, b: 2, c: 3, d: 4, a: 5]
iex> d = [a: 1, b: 2, b: 3, c: 3, d: 4, a: 5]
iex> Keyword.drop(d, [:b, :d])
[a: 1, c: 3, a: 5]
"""
def drop(keywords, keys) when is_list(keywords) do
for {k, _} = tuple <- keywords, not k in keys, do: tuple
:lists.filter(fn {k, _} -> not k in keys end, keywords)
end
@doc """
+103 -37
View File
@@ -337,7 +337,7 @@ defmodule List do
"""
@spec unzip([tuple]) :: [list]
def unzip(list) when is_list(list) do
:lists.map &tuple_to_list/1, zip(list)
:lists.map &Tuple.to_list/1, zip(list)
end
@doc """
@@ -454,56 +454,122 @@ defmodule List do
end
@doc """
Converts char data into a char list.
Converts a char list to an atom.
A char data is a string or a list of integers and strings and it
is converted into a list with each codepoint represented as its
respective integer value.
Currently Elixir does not support conversions from char lists
which contains Unicode codepoints greater than 0xFF.
Inlined by the compiler.
## Examples
iex> List.from_char_data("æß")
{:ok, 'æß'}
iex> List.from_char_data([?a, "bc"])
{:ok, 'abc'}
iex> List.to_atom('elixir')
:elixir
"""
@spec from_char_data(char_data) :: {:ok, char_list} | {:error, list, binary} | {:incomplete, list, binary}
def from_char_data(char_data) do
case :unicode.characters_to_list(char_data) do
result when is_list(result) ->
{:ok, result}
{:error, _, _} = error ->
error
{:incomplete, _, _} = incomplete ->
incomplete
end
@spec to_atom(char_list) :: atom
def to_atom(char_list) do
:erlang.list_to_atom(char_list)
end
@doc """
Converts char data into a char list.
Converts a char list to an existing atom.
A char data is a string or a list of integers and strings and it
is converted into a list with each codepoint represented as its
respective integer value.
Currently Elixir does not support conversions from char lists
which contains Unicode codepoints greater than 0xFF.
In case the conversion fails or is incomplete, it raises a
`UnicodeConversionError`.
Inlined by the compiler.
"""
@spec to_existing_atom(char_list) :: atom
def to_existing_atom(char_list) do
:erlang.list_to_existing_atom(char_list)
end
@doc """
Returns the float whose text representation is `char_list`.
Inlined by the compiler.
## Examples
iex> List.from_char_data!("æß")
'æß'
iex> List.from_char_data!("abc")
'abc'
iex> List.to_float('2.2017764e+0')
2.2017764
"""
@spec from_char_data!(char_data) :: char_list | no_return
def from_char_data!(char_data) do
case :unicode.characters_to_list(char_data) do
result when is_list(result) ->
@spec to_float(char_list) :: float
def to_float(char_list) do
:erlang.list_to_float(char_list)
end
@doc """
Returns an integer whose text representation is `char_list`.
Inlined by the compiler.
## Examples
iex> List.to_integer('123')
123
"""
@spec to_integer(char_list) :: integer
def to_integer(char_list) do
:erlang.list_to_integer(char_list)
end
@doc """
Returns an integer whose text representation is `char_list` in base `base`.
Inlined by the compiler.
## Examples
iex> List.to_integer('3FF', 16)
1023
"""
@spec to_integer(char_list, non_neg_integer) :: integer
def to_integer(char_list, base) do
:erlang.list_to_integer(char_list, base)
end
@doc """
Converts a list to a tuple.
Inlined by the compiler.
## Examples
iex> List.to_tuple([:share, [:elixir, 163]])
{:share, [:elixir, 163]}
"""
@spec to_tuple(list) :: tuple
def to_tuple(list) do
:erlang.list_to_tuple(list)
end
@doc """
Converts a list of integers representing codepoints, lists or
strings into a string.
Notice that this function expect a list of integer representing
UTF-8 codepoints. If you have a list of bytes, you must instead use
[the `:binary` module](http://erlang.org/doc/man/binary.html).
## Examples
iex> List.to_string([0x00E6, 0x00DF])
"æß"
iex> List.to_string([0x0061, "bc"])
"abc"
"""
@spec to_string(:unicode.char_list) :: String.t
def to_string(list) when is_list(list) do
case :unicode.characters_to_binary(list) do
result when is_binary(result) ->
result
{:error, encoded, rest} ->
@@ -592,7 +658,7 @@ defmodule List do
case heads do
nil -> :lists.reverse acc
_ -> do_zip mlist, [list_to_tuple(:lists.reverse(heads))|acc]
_ -> do_zip mlist, [:erlang.list_to_tuple(:lists.reverse(heads))|acc]
end
end
@@ -608,6 +674,6 @@ defmodule List do
{nil, nil}
end
defp to_list(tuple) when is_tuple(tuple), do: tuple_to_list(tuple)
defp to_list(tuple) when is_tuple(tuple), do: Tuple.to_list(tuple)
defp to_list(list) when is_list(list), do: list
end
+5 -5
View File
@@ -13,7 +13,7 @@ defprotocol List.Chars do
end
defimpl List.Chars, for: Atom do
def to_char_list(atom), do: atom_to_list(atom)
def to_char_list(atom), do: Atom.to_char_list(atom)
end
defimpl List.Chars, for: BitString do
@@ -21,7 +21,7 @@ defimpl List.Chars, for: BitString do
Returns the given binary converted to a char list.
"""
def to_char_list(thing) when is_binary(thing) do
List.from_char_data!(thing)
String.to_char_list(thing)
end
def to_char_list(thing) do
@@ -38,7 +38,7 @@ end
defimpl List.Chars, for: Integer do
def to_char_list(thing) do
integer_to_list(thing)
Integer.to_char_list(thing)
end
end
@@ -47,10 +47,10 @@ defimpl List.Chars, for: Float do
@limit :math.pow(10, @digits)
def to_char_list(thing) when thing > @limit do
float_to_list(thing, scientific: @digits)
Float.to_char_list(thing, scientific: @digits)
end
def to_char_list(thing) do
float_to_list(thing, compact: true, decimals: @digits)
Float.to_char_list(thing, compact: true, decimals: @digits)
end
end
+155 -65
View File
@@ -16,7 +16,7 @@ defmodule Macro do
:&&, :||, :<>, :++, :--, :\\, :::, :<-, :.., :|>, :=~,
:<, :>, :->,
:+, :-, :*, :/, :=, :|, :.,
:and, :or, :xor, :when, :in, :inlist, :inbits,
:and, :or, :xor, :when, :in,
:<<<, :>>>, :|||, :&&&, :^^^, :~~~]
@doc false
@@ -30,7 +30,7 @@ defmodule Macro do
@spec binary_op_props(atom) :: {:left | :right, precedence :: integer}
defp binary_op_props(o) do
case o do
o when o in [:<-, :inlist, :inbits, :\\, :::] -> {:left, 40}
o when o in [:<-, :\\, :::] -> {:left, 40}
:| -> {:right, 50}
:when -> {:right, 70}
:= -> {:right, 80}
@@ -68,7 +68,7 @@ defmodule Macro do
def pipe(expr, call_args, position)
def pipe(expr, {:&, _, _} = call_args, _integer) do
raise ArgumentError, message: "cannot pipe #{to_string expr} into #{to_string call_args}"
raise ArgumentError, "cannot pipe #{to_string expr} into #{to_string call_args}"
end
def pipe(expr, {call, line, atom}, integer) when is_atom(atom) do
@@ -80,17 +80,15 @@ defmodule Macro do
end
def pipe(expr, call_args, _integer) do
raise ArgumentError,
message: "cannot pipe #{to_string expr} into #{to_string call_args}"
raise ArgumentError, "cannot pipe #{to_string expr} into #{to_string call_args}"
end
@doc """
Recurs the quoted expression applying the given function to
each metadata node.
Applies the given function to the node metadata if it contains one.
This is often useful to remove information like lines and
hygienic counters from the expression for either storage or
comparison.
This is often useful when used with `Macro.prewalk/1` to remove
information like lines and hygienic counters from the expression
for either storage or comparison.
## Examples
@@ -104,21 +102,135 @@ defmodule Macro do
def update_meta(quoted, fun)
def update_meta({left, meta, right}, fun) when is_list(meta) do
{update_meta(left, fun), fun.(meta), update_meta(right, fun)}
end
def update_meta({left, right}, fun) do
{update_meta(left, fun), update_meta(right, fun)}
end
def update_meta(list, fun) when is_list(list) do
for x <- list, do: update_meta(x, fun)
{left, fun.(meta), right}
end
def update_meta(other, _fun) do
other
end
@doc """
Genrates a AST node representing the variable given
by the atoms `var` and `context`.
## Examples
In order to build a variable, a context is expected.
Most of the times, in order to preserve hygiene, the
context must be `__MODULE__`:
iex> Macro.var(:foo, __MODULE__)
{:foo, [], __MODULE__}
However, if there is a need to access the user variable,
nil can be given:
iex> Macro.var(:foo, nil)
{:foo, [], nil}
"""
@spec var(var, context) :: {var, [], context} when var: atom, context: atom
def var(var, context) when is_atom(var) and is_atom(context) do
{var, [], context}
end
@doc """
Performs a depth-first, pre-order traversal of quoted expressions.
"""
@spec prewalk(t, (t -> t)) :: t
def prewalk(ast, fun) when is_function(fun, 1) do
elem(prewalk(ast, nil, fn x, nil -> {fun.(x), nil} end), 0)
end
@doc """
Performs a depth-first, pre-order traversal of quoted expressions
using an accumulator.
"""
@spec prewalk(t, any, (t, any -> {t, any})) :: {t, any}
def prewalk(ast, acc, fun) when is_function(fun, 2) do
{ast, acc} = fun.(ast, acc)
do_prewalk(ast, acc, fun)
end
defp do_prewalk({form, meta, args}, acc, fun) do
unless is_atom(form) do
{form, acc} = fun.(form, acc)
{form, acc} = do_prewalk(form, acc, fun)
end
unless is_atom(args) do
{args, acc} = Enum.map_reduce(args, acc, fn x, acc ->
{x, acc} = fun.(x, acc)
do_prewalk(x, acc, fun)
end)
end
{{form, meta, args}, acc}
end
defp do_prewalk({left, right}, acc, fun) do
{left, acc} = fun.(left, acc)
{left, acc} = do_prewalk(left, acc, fun)
{right, acc} = fun.(right, acc)
{right, acc} = do_prewalk(right, acc, fun)
{{left, right}, acc}
end
defp do_prewalk(list, acc, fun) when is_list(list) do
Enum.map_reduce(list, acc, fn x, acc ->
{x, acc} = fun.(x, acc)
do_prewalk(x, acc, fun)
end)
end
defp do_prewalk(x, acc, _fun) do
{x, acc}
end
@doc """
Performs a depth-first, post-order traversal of quoted expressions.
"""
@spec postwalk(t, (t -> t)) :: t
def postwalk(ast, fun) when is_function(fun, 1) do
elem(postwalk(ast, nil, fn x, nil -> {fun.(x), nil} end), 0)
end
@doc """
Performs a depth-first, post-order traversal of quoted expressions
using an accumulator.
"""
@spec postwalk(t, any, (t, any -> {t, any})) :: {t, any}
def postwalk(ast, acc, fun) when is_function(fun, 2) do
do_postwalk(ast, acc, fun)
end
defp do_postwalk({form, meta, args}, acc, fun) do
unless is_atom(form) do
{form, acc} = do_postwalk(form, acc, fun)
end
unless is_atom(args) do
{args, acc} = Enum.map_reduce(args, acc, &do_postwalk(&1, &2, fun))
end
fun.({form, meta, args}, acc)
end
defp do_postwalk({left, right}, acc, fun) do
{left, acc} = do_postwalk(left, acc, fun)
{right, acc} = do_postwalk(right, acc, fun)
fun.({left, right}, acc)
end
defp do_postwalk(list, acc, fun) when is_list(list) do
{list, acc} = Enum.map_reduce(list, acc, &do_postwalk(&1, &2, fun))
fun.(list, acc)
end
defp do_postwalk(x, acc, fun) do
fun.(x, acc)
end
@doc """
Decomposes a local or remote call into its remote part (when provided),
function name and argument list.
@@ -300,7 +412,7 @@ defmodule Macro do
# Variables
def to_string({var, _, atom} = ast, fun) when is_atom(atom) do
fun.(ast, atom_to_binary(var))
fun.(ast, Atom.to_string(var))
end
# Aliases
@@ -389,7 +501,7 @@ defmodule Macro do
# Unary ops
def to_string({unary, _, [{binary, _, [_, _]} = arg]} = ast, fun)
when unary in unquote(@unary_ops) and binary in unquote(@binary_ops) do
fun.(ast, atom_to_binary(unary) <> "(" <> to_string(arg, fun) <> ")")
fun.(ast, Atom.to_string(unary) <> "(" <> to_string(arg, fun) <> ")")
end
def to_string({:not, _, [arg]} = ast, fun) do
@@ -397,12 +509,12 @@ defmodule Macro do
end
def to_string({op, _, [arg]} = ast, fun) when op in unquote(@unary_ops) do
fun.(ast, atom_to_binary(op) <> to_string(arg, fun))
fun.(ast, Atom.to_string(op) <> to_string(arg, fun))
end
# Access
def to_string({{:., _, [Kernel, :access]}, _, [left, right]} = ast, fun) do
fun.(ast, to_string(left, fun) <> to_string(right, fun))
def to_string({{:., _, [Access, :get]}, _, [left, right]} = ast, fun) do
fun.(ast, to_string(left, fun) <> to_string([right], fun))
end
# All other calls
@@ -425,7 +537,7 @@ defmodule Macro do
list == [] ->
"[]"
:io_lib.printable_list(list) ->
"'" <> Inspect.BitString.escape(String.from_char_data!(list), ?') <> "'"
"'" <> Inspect.BitString.escape(IO.chardata_to_string(list), ?') <> "'"
Keyword.keyword?(list) ->
"[" <> kw_list_to_string(list, fun) <> "]"
true ->
@@ -434,7 +546,7 @@ defmodule Macro do
end
# All other structures
def to_string(other, fun), do: fun.(other, inspect(other, records: false))
def to_string(other, fun), do: fun.(other, inspect(other, []))
# Block keywords
@kw_keywords [:do, :catch, :rescue, :after, :else]
@@ -444,12 +556,12 @@ defmodule Macro do
end
defp kw_blocks?(_), do: false
defp module_to_string(atom, _fun) when is_atom(atom), do: inspect(atom, records: false)
defp module_to_string(atom, _fun) when is_atom(atom), do: inspect(atom, [])
defp module_to_string(other, fun), do: call_to_string(other, fun)
defp call_to_string(atom, _fun) when is_atom(atom), do: atom_to_binary(atom)
defp call_to_string({:., _, [arg]}, fun), do: module_to_string(arg, fun) <> "."
defp call_to_string({:., _, [left, right]}, fun), do: module_to_string(left, fun) <> "." <> call_to_string(right, fun)
defp call_to_string(atom, _fun) when is_atom(atom), do: Atom.to_string(atom)
defp call_to_string({:., _, [arg]}, fun), do: module_to_string(arg, fun) <> "."
defp call_to_string({:., _, [left, right]}, fun), do: module_to_string(left, fun) <> "." <> call_to_string(right, fun)
defp call_to_string(other, fun), do: to_string(other, fun)
defp call_to_string_with_args(target, args, fun) do
@@ -481,7 +593,7 @@ defmodule Macro do
defp kw_block_to_string(key, value, fun) do
block = adjust_new_lines block_to_string(value, fun), "\n "
atom_to_binary(key) <> "\n " <> block <> "\n"
Atom.to_string(key) <> "\n " <> block <> "\n"
end
defp block_to_string([{:->, _, _}|_] = block, fun) do
@@ -510,7 +622,11 @@ defmodule Macro do
defp kw_list_to_string(list, fun) do
Enum.map_join(list, ", ", fn {key, value} ->
atom_to_binary(key) <> ": " <> to_string(value, fun)
atom_name = case Inspect.Atom.inspect(key) do
":" <> rest -> rest
other -> other
end
atom_name <> ": " <> to_string(value, fun)
end)
end
@@ -570,10 +686,10 @@ defmodule Macro do
The following contents are expanded:
* Macros (local or remote);
* Aliases are expanded (if possible) and return atoms;
* Pseudo-variables (`__ENV__`, `__MODULE__` and `__DIR__`);
* Module attributes reader (`@foo`);
* Macros (local or remote)
* Aliases are expanded (if possible) and return atoms
* Pseudo-variables (`__ENV__`, `__MODULE__` and `__DIR__`)
* Module attributes reader (`@foo`)
If the expression cannot be expanded, it returns the expression
itself. Notice that `expand_once/2` performs the expansion just
@@ -590,7 +706,7 @@ defmodule Macro do
Consider the implementation below:
defmacro defmodule_with_length(name, do: block) do
length = length(atom_to_list(name))
length = length(Atom.to_char_list(name))
quote do
defmodule unquote(name) do
@@ -630,7 +746,7 @@ defmodule Macro do
defmacro defmodule_with_length(name, do: block) do
expanded = Macro.expand(name, __CALLER__)
length = length(atom_to_list(expanded))
length = length(Atom.to_char_list(expanded))
quote do
defmodule unquote(name) do
@@ -722,7 +838,7 @@ defmodule Macro do
{:ok, receiver, quoted} ->
next = :elixir_counter.next
{:elixir_quote.linify_with_context_counter(0, {receiver, next}, quoted), true}
{:ok, _receiver} ->
{:ok, _receiver, _name, _args} ->
{original, false}
:error ->
{original, false}
@@ -770,30 +886,4 @@ defmodule Macro do
defp expand_until({tree, false}, _env) do
tree
end
@doc """
Recursively traverses the quoted expression checking if all sub-terms are
safe.
Terms are considered safe if they represent data structures and don't actually
evaluate code. Returns `:ok` unless a given term is unsafe,
which is returned as `{:unsafe, term}`.
"""
def safe_term(terms) do
IO.write :stderr, "Macro.safe_term/1 is deprecated\n#{Exception.format_stacktrace}"
do_safe_term(terms) || :ok
end
defp do_safe_term({local, _, terms}) when local in [:{}, :%{}, :__aliases__] do
do_safe_term(terms)
end
defp do_safe_term({unary, _, [term]}) when unary in [:+, :-] do
do_safe_term(term)
end
defp do_safe_term({left, right}), do: do_safe_term(left) || do_safe_term(right)
defp do_safe_term(terms) when is_list(terms), do: Enum.find_value(terms, &do_safe_term(&1))
defp do_safe_term(terms) when is_tuple(terms), do: {:unsafe, terms}
defp do_safe_term(_), do: nil
end
+21 -19
View File
@@ -6,25 +6,27 @@ defmodule Macro.Env do
`__ENV__`. Inside macros, the caller environment can be
accessed as `__CALLER__`. It contains the following fields:
* `module` - the current module name.
* `file` - the current file name as a binary
* `line` - the current line as an integer
* `function` - a tuple as `{atom, integer`}, where the first
element is the function name and the seconds its arity. Returns
`nil` if not inside a function
* `context` - the context of the environment. It can be nil
(default context), inside a guard or inside an assign
* `aliases` - a list of two item tuples, where the first
item is the aliased name and the second the actual name
* `requires` - the list of required modules
* `functions` - a list of functions imported from each module
* `macros` - a list of macros imported from each module
* `macro_aliases` - a list of aliases defined inside the current macro
* `context_modules` - a list of modules defined in the current context
* `vars` - a list keeping all defined variables as {var, context}
* `export_vars` - a list keeping all variables to be exported in a construct (may be nil)
* `lexical_tracker` - PID to the lexical tracker which is responsible to keep user info
* `local` - the module to expand local functions to
* `module` - the current module name
* `file` - the current file name as a binary
* `line` - the current line as an integer
* `function` - a tuple as `{atom, integer`}, where the first
element is the function name and the seconds its arity; returns
`nil` if not inside a function
* `context` - the context of the environment; it can be `nil`
(default context), inside a guard or inside an assign
* `aliases` - a list of two item tuples, where the first
item is the aliased name and the second the actual name
* `requires` - the list of required modules
* `functions` - a list of functions imported from each module
* `macros` - a list of macros imported from each module
* `macro_aliases` - a list of aliases defined inside the current macro
* `context_modules` - a list of modules defined in the current context
* `vars` - a list keeping all defined variables as `{var, context}`
* `export_vars` - a list keeping all variables to be exported in a
construct (may be `nil`)
* `lexical_tracker` - PID of the lexical tracker which is responsible to
keep user info
* `local` - the module to expand local functions to
"""
@type name_arity :: {atom, non_neg_integer}
+4 -44
View File
@@ -11,10 +11,12 @@ defmodule Map do
their APIs, please consult the `Dict` module.
"""
use Dict.Behaviour
use Dict
defdelegate [keys(map), values(map), size(map), merge(map1, map2), to_list(map)], to: :maps
@compile {:inline, fetch: 2, put: 3, delete: 2, has_key?: 2}
@doc """
Returns a new empty map.
"""
@@ -24,21 +26,10 @@ defmodule Map do
def fetch(map, key), do: :maps.find(key, map)
def pop(map, key, default \\ nil) do
{get(map, key, default), delete(map, key)}
end
def put(map, key, val) do
:maps.put(key, val, map)
end
def put_new(map, key, val) do
case has_key?(map, key) do
true -> map
false -> :maps.put(key, val, map)
end
end
def delete(map, key), do: :maps.remove(key, map)
def merge(map1, map2, callback) do
@@ -47,36 +38,5 @@ defmodule Map do
end, map1, map2
end
def split(map, keys) do
acc = {%{}, %{}}
:maps.fold fn k, v, {take, drop} ->
if k in keys do
{put(take, k, v), drop}
else
{take, put(drop, k, v)}
end
end, acc, map
end
def update!(map, key, fun) do
case :maps.find(key, map) do
:error ->
raise(KeyError, key: key, term: map)
{:ok, val} ->
:maps.put(key, fun.(val), map)
end
end
def update(map, key, initial, fun) do
case :maps.find(key, map) do
:error ->
:maps.put(key, initial, map)
{:ok, val} ->
:maps.put(key, fun.(val), map)
end
end
def equal?(map, map), do: true
def equal?(_, _), do: false
def equal?(%{} = map1, %{} = map2), do: map1 === map2
end
+53 -44
View File
@@ -14,7 +14,7 @@ defmodule Module do
Each module can be decorated with one or more attributes. The following ones
are currently defined by Elixir:
* `@after_compile`
* `@after_compile`
A hook that will be invoked right after the current module is compiled.
@@ -33,7 +33,7 @@ defmodule Module do
end
end
* `@before_compile`
* `@before_compile`
A hook that will be invoked before the module is compiled.
@@ -63,7 +63,7 @@ defmodule Module do
@before_compile A
end
* `@behaviour` (notice the British spelling)
* `@behaviour` (notice the British spelling)
Specify an OTP or user-defined behaviour.
@@ -75,7 +75,7 @@ defmodule Module do
# ...
end
* `@compile`
* `@compile`
Define options for module compilation that are passed to the Erlang
compiler.
@@ -95,7 +95,7 @@ defmodule Module do
end
end
* `@doc`
* `@doc`
Provide documentation for the function or macro that follows the
attribute.
@@ -122,7 +122,7 @@ defmodule Module do
end
end
* `@file`
* `@file`
Change the filename used in stacktraces for the function or macro that
follows the attribute.
@@ -139,7 +139,7 @@ defmodule Module do
end
end
* `@moduledoc`
* `@moduledoc`
Provide documentation for the current module.
@@ -156,7 +156,7 @@ defmodule Module do
end
* `@on_definition`
* `@on_definition`
A hook that will be invoked when each function or macro in the current
module is defined. Useful when annotating functions.
@@ -211,7 +211,7 @@ defmodule Module do
end
end
* `@on_load`
* `@on_load`
A hook that will be invoked whenever the module is loaded.
@@ -237,7 +237,7 @@ defmodule Module do
end
end
* `@vsn`
* `@vsn`
Specify the module version. Accepts any valid Elixir value.
@@ -247,14 +247,25 @@ defmodule Module do
@vsn "1.0"
end
* `@external_resource`
Specify an external resource to the current module.
Many times a module embeds information from an external file. This
attribute allows the module to annotate which external resources
have been used.
Tools like Mix may use this information to ensure the module is
recompiled in case any of the external resources change.
The following attributes are part of typespecs and are also reserved by
Elixir (see `Kernel.Typespec` for more information about typespecs):
* `@type` - defines a type to be used in `@spec`
* `@typep` - defines a private type to be used in `@spec`
* `@opaque` - defines an opaque type to be used in `@spec`
* `@spec` - provides a specification for a function
* `@callback` - provides a specification for the behaviour callback
* `@type` - defines a type to be used in `@spec`
* `@typep` - defines a private type to be used in `@spec`
* `@opaque` - defines an opaque type to be used in `@spec`
* `@spec` - provides a specification for a function
* `@callback` - provides a specification for the behaviour callback
In addition to the built-in attributes outlined above, custom attributes may
also be added. A custom attribute is any valid identifier prefixed with an
@@ -280,18 +291,11 @@ defmodule Module do
Each module gets an `__info__/1` function when it's compiled. The function
takes one of the following atoms:
* `:functions` - keyword list of public functions along with their arities
* `:functions` - keyword list of public functions along with their arities
* `:macros` - keyword list of public macros along with their arities
* `:macros` - keyword list of public macros along with their arities
* `:docs` - list of all docstrings attached to functions and macros
using the `@doc` attribute
* `:moduledoc` - tuple `{<line>, <doc>}` where `line` is the line on
which module definition starts and `doc` is the string
attached to the module using the `@moduledoc` attribute
* `:module` - module name (`Module == Module.__info__(:module)`)
* `:module` - module name (`Module == Module.__info__(:module)`)
In addition to the above, you may also pass to `__info__/1` any atom supported
by Erlang's `module_info` function which also gets defined for each compiled
@@ -356,8 +360,10 @@ defmodule Module do
@doc """
Creates a module with the given name and defined by
the given quoted expressions. The line where the module
is defined and its file can be passed as options.
the given quoted expressions.
The line where the module is defined and its file **must**
be passed as options.
## Examples
@@ -383,13 +389,16 @@ defmodule Module do
when defining the module, while `defmodule` automatically
shares the same environment.
"""
def create(module, quoted, opts \\ [])
def create(module, quoted, opts)
def create(module, quoted, %Macro.Env{} = env) do
create(module, quoted, Map.to_list(env))
end
def create(module, quoted, opts) when is_atom(module) do
def create(module, quoted, opts) when is_atom(module) and is_list(opts) do
unless Keyword.has_key?(opts, :file) do
raise ArgumentError, "expected :file to be given as option"
end
:elixir_module.compile(module, quoted, [], :elixir.env_for_eval(opts))
end
@@ -507,9 +516,9 @@ defmodule Module do
assert_not_compiled!(:add_doc, module)
table = docs_table_for(module)
{signature, _} = Enum.map_reduce signature, 1, fn(x, acc) ->
{signature, _} = :lists.mapfoldl fn(x, acc) ->
{simplify_signature(x, acc), acc + 1}
end
end, 1, signature
case :ets.lookup(table, tuple) do
[] ->
@@ -534,8 +543,8 @@ defmodule Module do
end
defp simplify_signature({:%, _, [left, _]}, _i) when is_atom(left) do
last = List.last(String.split(atom_to_binary(left), "."))
atom = binary_to_atom(downcase(last))
last = List.last(String.split(Atom.to_string(left), "."))
atom = String.to_atom(downcase(last))
{atom, [], nil}
end
@@ -544,8 +553,8 @@ defmodule Module do
end
defp simplify_signature({var, _, atom}, _i) when is_atom(atom) do
case atom_to_binary(var) do
"_" <> rest -> {binary_to_atom(rest), [], Elixir}
case Atom.to_string(var) do
"_" <> rest -> {String.to_atom(rest), [], Elixir}
_ -> {var, [], nil}
end
end
@@ -804,7 +813,7 @@ defmodule Module do
end
defp warn_info([entry|_]) do
opts = elem(entry, size(entry) - 1)
opts = elem(entry, tuple_size(entry) - 1)
Exception.format_file_line(Keyword.get(opts, :file), Keyword.get(opts, :line)) <> " "
end
@@ -838,12 +847,12 @@ defmodule Module do
When registering an attribute, two options can be given:
* `:accumulate` - Several calls to the same attribute will
accumulate instead of override the previous one. New attributes
are always added to the top of the accumulated list.
* `:accumulate` - several calls to the same attribute will
accumulate instead of override the previous one. New attributes
are always added to the top of the accumulated list.
* `:persist` - The attribute will be persisted in the Erlang
Abstract Format. Useful when interfacing with Erlang libraries.
* `:persist` - the attribute will be persisted in the Erlang
Abstract Format. Useful when interfacing with Erlang libraries.
By default, both options are `false`.
@@ -911,7 +920,7 @@ defmodule Module do
@doc false
# Used internally to compile types. This function
# is private and must be used only internally.
def compile_typespec(module, key, value) when is_atom(key) do
def store_typespec(module, key, value) when is_atom(key) do
assert_not_compiled!(:put_attribute, module)
table = data_table_for(module)
@@ -945,7 +954,7 @@ defmodule Module do
end
defp normalize_attribute(key, _value) when key in [:type, :typep, :export_type, :opaque, :callback] do
raise ArgumentError, message: "attributes type, typep, export_type, opaque and callback " <>
raise ArgumentError, "attributes type, typep, export_type, opaque and callback " <>
"must be set via Kernel.Typespec"
end
@@ -968,7 +977,7 @@ defmodule Module do
defp assert_not_compiled!(fun, module) do
open?(module) ||
raise ArgumentError,
message: "could not call #{fun} on module #{inspect module} because it was already compiled"
"could not call #{fun} on module #{inspect module} because it was already compiled"
end
defp loaded?(module), do: is_tuple :code.is_loaded(module)
+27 -2
View File
@@ -10,6 +10,31 @@ defmodule Node do
@type t :: node
@doc """
Turns a non-distributed node into a distributed node.
This functionality starts the `:net_kernel` and other
related processes.
"""
@spec start(node, :longnames | :shortnames, non_neg_integer) ::
{:ok, pid} | {:error, term}
def start(name, type \\ :longnames, tick_time \\ 15000) do
:net_kernel.start([name, type, tick_time])
end
@doc """
Turns a distributed node into a non-distributed node.
For other nodes in the network, this is the same as the node going down.
Only possible when the node was started with `Node.start/3`, otherwise
returns `{:error, :not_allowed}`. Returns `{:error, :not_found}` if the
local node is not alive.
"""
@spec stop() :: :ok | {:error, term}
def stop() do
:net_kernel.stop()
end
@doc """
Returns the current node.
@@ -49,8 +74,8 @@ defmodule Node do
See http://www.erlang.org/doc/man/erlang.html#nodes-1 for more info.
"""
@typep list_arg :: :visible | :hidden | :connected | :this | :known
@spec list(list_arg | [list_arg]) :: [t]
@typep state :: :visible | :hidden | :connected | :this | :known
@spec list(state | [state]) :: [t]
def list(args) do
:erlang.nodes(args)
end
+262 -99
View File
@@ -3,6 +3,11 @@ defmodule OptionParser do
This module contains functions to parse command line arguments.
"""
@type argv :: [String.t]
@type parsed :: Keyword.t
@type errors :: [{String.t, String.t | nil}]
@type options :: [switches: Keyword.t, strict: Keyword.t, aliases: Keyword.t]
@doc """
Parses `argv` into a keywords list.
@@ -27,41 +32,62 @@ defmodule OptionParser do
Note Elixir also converts the switches to underscore atoms, as
`--source-path` becomes `:source_path`, to better suit Elixir
conventions.
conventions. This means that option names on the command line cannot contain
underscores; such options will be reported as `:undefined` (in strict mode)
or `:invalid` (in basic mode).
## Switches
Many times though, it is better to explicitly list the available
switches and their formats. The following types are supported:
switches and their formats. The switches can be specified via two
different options:
* `:boolean` - Marks the given switch as a boolean. Boolean switches
never consume the following value unless it is
`true` or `false`;
* `:integer` - Parses the switch as an integer;
* `:float` - Parses the switch as a float;
* `:string` - Returns the switch as a string;
* `:strict` - the switches are strict. Any switch that does not
exist in the switch list is treated as an error.
If a switch can't be parsed, the option is returned in the invalid
options list (third element of the returned tuple).
* `:switches` - configure some switches. Switches that does not
exist in the switch list are still attempted to be parsed.
Note only `:strict` or `:switches` may be given at once.
For each switch, the following types are supported:
* `:boolean` - marks the given switch as a boolean. Boolean switches
never consume the following value unless it is `true` or
`false`.
* `:integer` - parses the switch as an integer.
* `:float` - parses the switch as a float.
* `:string` - returns the switch as a string.
If a switch can't be parsed or is not specfied in the strict case,
the option is returned in the invalid options list (third element
of the returned tuple).
The following extra "types" are supported:
* `:keep` - Keeps duplicated items in the list instead of overriding;
* `:keep` - keeps duplicated items in the list instead of overriding
Examples:
iex> OptionParser.parse(["--unlock", "path/to/file"], switches: [unlock: :boolean])
iex> OptionParser.parse(["--unlock", "path/to/file"], strict: [unlock: :boolean])
{[unlock: true], ["path/to/file"], []}
iex> OptionParser.parse(["--unlock", "--limit", "0", "path/to/file"],
...> switches: [unlock: :boolean, limit: :integer])
...> strict: [unlock: :boolean, limit: :integer])
{[unlock: true, limit: 0], ["path/to/file"], []}
iex> OptionParser.parse(["--limit", "3"], switches: [limit: :integer])
iex> OptionParser.parse(["--limit", "3"], strict: [limit: :integer])
{[limit: 3], [], []}
iex> OptionParser.parse(["--limit", "yyz"], switches: [limit: :integer])
{[], [], [limit: "yyz"]}
iex> OptionParser.parse(["--limit", "xyz"], strict: [limit: :integer])
{[], [], [{"--limit", "xyz"}]}
iex> OptionParser.parse(["--unknown", "xyz"], strict: [])
{[], ["xyz"], [{"--unknown", nil}]}
iex> OptionParser.parse(["--limit", "3", "--unknown", "xyz"],
...> switches: [limit: :integer])
{[limit: 3, unknown: "xyz"], [], []}
## Negation switches
@@ -85,8 +111,10 @@ defmodule OptionParser do
{[debug: true], [], []}
"""
@spec parse(argv, options) :: {parsed, argv, errors}
def parse(argv, opts \\ []) when is_list(argv) and is_list(opts) do
parse(argv, opts, true)
config = compile_config(opts, true)
do_parse(argv, config, [], [], [])
end
@doc """
@@ -104,71 +132,158 @@ defmodule OptionParser do
{[verbose: true, source: "lib"], ["test/enum_test.exs", "--unlock"], []}
"""
@spec parse_head(argv, options) :: {parsed, argv, errors}
def parse_head(argv, opts \\ []) when is_list(argv) and is_list(opts) do
parse(argv, opts, false)
config = compile_config(opts, false)
do_parse(argv, config, [], [], [])
end
defp do_parse([], _config, opts, args, invalid) do
{Enum.reverse(opts), Enum.reverse(args), Enum.reverse(invalid)}
end
defp do_parse(argv, {aliases, switches, strict, all}=config, opts, args, invalid) do
case next(argv, aliases, switches, strict) do
{:ok, option, value, rest} ->
# the option exist and it was successfully parsed
kinds = List.wrap Keyword.get(switches, option)
new_opts = do_store_option(opts, option, value, kinds)
do_parse(rest, config, new_opts, args, invalid)
{:invalid, option, value, rest} ->
# the option exist but it has wrong value
do_parse(rest, config, opts, args, [{option, value}|invalid])
{:undefined, option, _value, rest} ->
# the option does not exist (for strict cases)
do_parse(rest, config, opts, args, [{option, nil}|invalid])
{:error, ["--"|rest]} ->
{Enum.reverse(opts), Enum.reverse(args, rest), Enum.reverse(invalid)}
{:error, [arg|rest]=remaining_args} ->
# there is no option
if all do
do_parse(rest, config, opts, [arg|args], invalid)
else
{Enum.reverse(opts), Enum.reverse(args, remaining_args), Enum.reverse(invalid)}
end
end
end
@doc """
Low-level function that parses one option.
It accepts the same options as `parse/2` and `parse_head/2`
as both functions are built on top of next. This function
may return:
* `{:ok, key, value, rest}` - the option `key` with `value` was
successfully parsed
* `{:invalid, key, value, rest}` - the option `key` is invalid with `value`
(returned when the switch type does not match the one given via the
command line)
* `{:undefined, key, value, rest}` - the option `key` is undefined
(returned on strict cases and the switch is unknown)
* `{:error, rest}` - there are no switches at the top of the given argv
"""
@spec next(argv, options) ::
{:ok, key :: atom, value :: term, argv} |
{:invalid, key :: atom, value :: term, argv} |
{:undefined, key :: atom, value :: term, argv} |
{:error, argv}
def next(argv, opts \\ []) when is_list(argv) and is_list(opts) do
{aliases, switches, strict, _} = compile_config(opts, true)
next(argv, aliases, switches, strict)
end
defp next([], _aliases, _switches, _strict) do
{:error, []}
end
defp next(["--"|_]=argv, _aliases, _switches, _strict) do
{:error, argv}
end
defp next(["-"|_]=argv, _aliases, _switches, _strict) do
{:error, argv}
end
defp next(["- " <> _|_]=argv, _aliases, _switches, _strict) do
{:error, argv}
end
defp next(["-" <> option|rest], aliases, switches, strict) do
{option, value} = split_option(option)
opt_name_bin = "-" <> option
tagged = tag_option(option, value, switches, aliases)
if strict and not option_defined?(tagged, switches) do
{:undefined, opt_name_bin, value, rest}
else
{opt_name, kinds, value} = normalize_option(tagged, value, switches)
{value, kinds, rest} = normalize_value(value, kinds, rest, strict)
case validate_option(value, kinds) do
{:ok, new_value} -> {:ok, opt_name, new_value, rest}
:invalid -> {:invalid, opt_name_bin, value, rest}
end
end
end
defp next(argv, _aliases, _switches, _strict) do
{:error, argv}
end
## Helpers
defp parse(argv, opts, bool) do
aliases = opts[:aliases] || []
switches = opts[:switches] || []
parse(argv, aliases, switches, bool)
defp compile_config(opts, all) do
aliases = opts[:aliases] || []
{switches, strict} = cond do
s = opts[:switches] ->
{s, false}
s = opts[:strict] ->
{s, true}
true ->
{[], false}
end
{aliases, switches, strict, all}
end
defp parse(argv, aliases, switches, all) do
parse(argv, aliases, switches, [], [], [], all)
end
defp validate_option(value, kinds) do
{is_invalid, value} = cond do
:invalid in kinds ->
{true, value}
:boolean in kinds ->
case value do
t when t in [true, "true"] -> {nil, true}
f when f in [false, "false"] -> {nil, false}
_ -> {true, value}
end
:integer in kinds ->
case Integer.parse(value) do
{value, ""} -> {nil, value}
_ -> {true, value}
end
:float in kinds ->
case Float.parse(value) do
{value, ""} -> {nil, value}
_ -> {true, value}
end
true ->
{nil, value}
end
defp parse(["--"|_] = value, _aliases, _switches, dict, args, invalid, _all) do
{Enum.reverse(dict), Enum.reverse(args, value), Enum.reverse(invalid)}
end
defp parse(["-" <> option|t], aliases, switches, dict, args, invalid, all) do
{option, value} = split_option(option)
{option, kinds, value} = normalize_option(option, value, switches, aliases)
{value, kinds, t} = normalize_value(value, kinds, t)
{dict, invalid} = store_option(dict, invalid, option, value, kinds)
parse(t, aliases, switches, dict, args, invalid, all)
end
defp parse([h|t], aliases, switches, dict, args, invalid, true) do
parse(t, aliases, switches, dict, [h|args], invalid, true)
end
defp parse([], _, _switches, dict, args, invalid, true) do
{Enum.reverse(dict), Enum.reverse(args), Enum.reverse(invalid)}
end
defp parse(value, _, _switches, dict, _args, invalid, false) do
{Enum.reverse(dict), value, Enum.reverse(invalid)}
end
defp store_option(dict, invalid, option, value, kinds) do
{invalid_option, value} =
cond do
:invalid in kinds ->
{option, value}
:boolean in kinds ->
{nil, value in [true, "true"]}
:integer in kinds ->
case Integer.parse(value) do
{value, ""} -> {nil, value}
_ -> {option, value}
end
:float in kinds ->
case Float.parse(value) do
{value, ""} -> {nil, value}
_ -> {option, value}
end
true ->
{nil, value}
end
if invalid_option do
{dict, [{option, value}|invalid]}
if is_invalid do
:invalid
else
{do_store_option(dict, option, value, kinds), invalid}
{:ok, value}
end
end
@@ -181,20 +296,36 @@ defmodule OptionParser do
end
end
defp normalize_option(<<?-, option :: binary>>, value, switches, _aliases) do
normalize_option(get_negated(option, switches), value, switches)
defp tag_option(<<?-, option :: binary>>, value, switches, _aliases) do
get_negated(option, value, switches)
end
defp normalize_option(option, value, switches, aliases) do
option = get_option(option)
if alias = aliases[option] do
normalize_option({:default, alias}, value, switches)
defp tag_option(option, _value, _switches, aliases) when is_binary(option) do
opt = get_option(option)
if alias = aliases[opt] do
{:default, alias}
else
{option, [:invalid], value}
:unknown
end
end
defp normalize_option({:negated, option}, _value, switches) do
defp option_defined?(:unknown, _switches) do
false
end
defp option_defined?({:negated, option}, switches) do
Keyword.has_key?(switches, option)
end
defp option_defined?({:default, option}, switches) do
Keyword.has_key?(switches, option)
end
defp normalize_option(:unknown, value, _switches) do
{nil, [:invalid], value}
end
defp normalize_option({:negated, option}, nil, switches) do
kinds = List.wrap(switches[option])
cond do
@@ -203,15 +334,20 @@ defmodule OptionParser do
kinds == [] ->
{option, kinds, true}
true ->
{option, [:invalid], false}
{reverse_negated(option), [:invalid], nil}
end
end
defp normalize_option({:negated, option}, value, _switches) do
{option, [:invalid], value}
end
defp normalize_option({:default, option}, value, switches) do
{option, List.wrap(switches[option]), value}
end
defp normalize_value(nil, kinds, t) do
defp normalize_value(nil, kinds, t, strict) do
nil_or_true = if strict, do: nil, else: true
cond do
:boolean in kinds ->
{true, kinds, t}
@@ -219,19 +355,21 @@ defmodule OptionParser do
[h|t] = t
{h, kinds, t}
kinds == [] ->
{true, kinds, t}
{nil_or_true, kinds, t}
true ->
{true, [:invalid], t}
{nil, [:invalid], t}
end
end
defp normalize_value(value, kinds, t) do
defp normalize_value(value, kinds, t, _) do
{value, kinds, t}
end
defp value_in_tail?(["-" <> _|_]), do: false
defp value_in_tail?([]), do: false
defp value_in_tail?(_), do: true
defp value_in_tail?(["-"|_]), do: true
defp value_in_tail?(["- " <> _|_]), do: true
defp value_in_tail?(["-" <> _|_]), do: false
defp value_in_tail?([]), do: false
defp value_in_tail?(_), do: true
defp split_option(option) do
case :binary.split(option, "=") do
@@ -240,21 +378,46 @@ defmodule OptionParser do
end
end
defp to_underscore(option) do
for <<c <- option>>, into: "", do: << if(c == ?-, do: ?_, else: c) >>
end
defp to_underscore(option), do: to_underscore(option, <<>>)
defp to_underscore("_" <> _rest, _acc), do: nil
defp to_underscore("-" <> rest, acc),
do: to_underscore(rest, acc <> "_")
defp to_underscore(<<c>> <> rest, acc),
do: to_underscore(rest, <<acc::binary, c>>)
defp to_underscore(<<>>, acc), do: acc
defp get_option(option) do
option |> to_underscore |> binary_to_atom
if str = to_underscore(option) do
String.to_atom(str)
end
end
defp get_negated("no-" <> rest = option, switches) do
negated = get_option(rest)
option = if Keyword.has_key?(switches, negated), do: negated, else: get_option(option)
{:negated, option}
defp reverse_negated(negated) do
String.to_atom("no_" <> Atom.to_string(negated))
end
defp get_negated(rest, _switches) do
{:default, get_option(rest)}
defp get_negated("no-" <> rest = option, value, switches) do
if negated = get_option(rest) do
option = if Keyword.has_key?(switches, negated) and value == nil do
negated
else
get_option(option)
end
{:negated, option}
else
:unknown
end
end
defp get_negated(rest, _value, _switches) do
if option = get_option(rest) do
{:default, option}
else
:unknown
end
end
end
+93 -34
View File
@@ -13,7 +13,7 @@ defmodule Path do
"""
alias :filename, as: FN
@type t :: char_data
@type t :: :unicode.chardata()
@doc """
Converts the given path to an absolute one. Unlike
@@ -58,17 +58,20 @@ defmodule Path do
"""
@spec absname(t, t) :: binary
def absname(path, relative_to) do
path = String.from_char_data!(path)
path = IO.chardata_to_string(path)
case type(path) do
:relative -> join(relative_to, path)
:absolute ->
if :binary.last(path) == ?/ do
binary_part(path, 0, byte_size(path) - 1)
else
path
cond do
path == "/" ->
path
:binary.last(path) == ?/ ->
binary_part(path, 0, byte_size(path) - 1)
true ->
path
end
:volumerelative ->
relative_to = String.from_char_data!(relative_to)
relative_to = IO.chardata_to_string(relative_to)
absname_vr(split(path), split(relative_to), relative_to)
end
end
@@ -85,7 +88,7 @@ defmodule Path do
defp absname_vr([<<x, ?:>>|name], _, _relative) do
cwd =
case :file.get_cwd([x, ?:]) do
{:ok, dir} -> String.from_char_data!(dir)
{:ok, dir} -> IO.chardata_to_string(dir)
{:error, _} -> <<x, ?:, ?/>>
end
absname(join(name), cwd)
@@ -138,6 +141,7 @@ defmodule Path do
## Unix examples
Path.type("/") #=> :absolute
Path.type("/usr/local/bin") #=> :absolute
Path.type("usr/local/bin") #=> :relative
Path.type("../usr/local/bin") #=> :relative
@@ -181,7 +185,7 @@ defmodule Path do
case :os.type() do
{:win32, _} -> win32_pathtype(name)
_ -> unix_pathtype(name)
end |> elem(1) |> String.from_char_data!
end |> elem(1) |> IO.chardata_to_string
end
defp unix_pathtype(<<?/, relative :: binary>>), do:
@@ -245,7 +249,7 @@ defmodule Path do
"""
@spec relative_to(t, t) :: binary
def relative_to(path, from) do
path = String.from_char_data!(path)
path = IO.chardata_to_string(path)
relative_to(split(path), split(from), path)
end
@@ -269,7 +273,7 @@ defmodule Path do
@spec relative_to_cwd(t) :: binary
def relative_to_cwd(path) do
case :file.get_cwd do
{:ok, base} -> relative_to(path, String.from_char_data!(base))
{:ok, base} -> relative_to(path, IO.chardata_to_string(base))
_ -> path
end
end
@@ -292,7 +296,7 @@ defmodule Path do
"""
@spec basename(t) :: binary
def basename(path) do
FN.basename(String.from_char_data!(path))
FN.basename(IO.chardata_to_string(path))
end
@doc """
@@ -314,7 +318,7 @@ defmodule Path do
"""
@spec basename(t, t) :: binary
def basename(path, extension) do
FN.basename(String.from_char_data!(path), String.from_char_data!(extension))
FN.basename(IO.chardata_to_string(path), IO.chardata_to_string(extension))
end
@doc """
@@ -330,7 +334,7 @@ defmodule Path do
"""
@spec dirname(t) :: binary
def dirname(path) do
FN.dirname(String.from_char_data!(path))
FN.dirname(IO.chardata_to_string(path))
end
@doc """
@@ -347,7 +351,7 @@ defmodule Path do
"""
@spec extname(t) :: binary
def extname(path) do
FN.extension(String.from_char_data!(path))
FN.extension(IO.chardata_to_string(path))
end
@doc """
@@ -364,7 +368,7 @@ defmodule Path do
"""
@spec rootname(t) :: binary
def rootname(path) do
FN.rootname(String.from_char_data!(path))
FN.rootname(IO.chardata_to_string(path))
end
@doc """
@@ -382,7 +386,7 @@ defmodule Path do
"""
@spec rootname(t, t) :: binary
def rootname(path, extension) do
FN.rootname(String.from_char_data!(path), String.from_char_data!(extension))
FN.rootname(IO.chardata_to_string(path), IO.chardata_to_string(extension))
end
@doc """
@@ -407,7 +411,7 @@ defmodule Path do
def join([name1, name2|rest]), do:
join([join(name1, name2)|rest])
def join([name]), do:
do_join(String.from_char_data!(name), <<>>, [], major_os_type())
do_join(IO.chardata_to_string(name), <<>>, [], major_os_type())
@doc """
Joins two paths.
@@ -420,7 +424,7 @@ defmodule Path do
"""
@spec join(t, t) :: binary
def join(left, right),
do: do_join(String.from_char_data!(left), relative(right), [], major_os_type())
do: do_join(IO.chardata_to_string(left), relative(right), [], major_os_type())
defp major_os_type do
:os.type |> elem(0)
@@ -435,7 +439,7 @@ defmodule Path do
defp do_join(<<?/, rest :: binary>>, relativename, [?/|result], os_type), do:
do_join(rest, relativename, [?/|result], os_type)
defp do_join(<<>>, <<>>, result, os_type), do:
iodata_to_binary(maybe_remove_dirsep(result, os_type))
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:
@@ -476,7 +480,33 @@ defmodule Path do
def split(""), do: []
def split(path) do
FN.split(String.from_char_data!(path))
FN.split(IO.chardata_to_string(path))
end
defmodule Wildcard do
@moduledoc false
def read_file_info(file) do
call({:read_link_info, file})
end
def list_dir(dir) do
case call({:list_dir, dir}) do
{:ok, files} ->
{:ok, for(file <- files, hd(file) != ?., do: file)}
other ->
other
end
end
@compile {:inline, call: 1}
defp call(tuple) do
x = :erlang.dt_spread_tag(true)
y = :gen_server.call(:file_server_2, tuple)
:erlang.dt_restore_tag(x)
y
end
end
@doc """
@@ -486,19 +516,26 @@ defmodule Path do
"wildcard characters" are interpreted in a special way. The
following characters are special:
* `?` - Matches one character.
* `*` - Matches any number of characters up to the end of
the filename, the next dot, or the next slash.
* `**` - Two adjacent <c>*</c>'s used as a single pattern will
match all files and zero or more directories and subdirectories.
* `[char1,char2,...]` - Matches any of the characters listed. Two characters
separated by a hyphen will match a range of characters.
* `{item1,item2,...}` - Matches one of the alternatives.
* `?` - matches one character
* `*` - matches any number of characters up to the end of the filename, the
next dot, or the next slash
* `**` - two adjacent `*`'s used as a single pattern will match all
files and zero or more directories and subdirectories
* `[char1,char2,...]` - matches any of the characters listed; two
characters separated by a hyphen will match a range of characters
* `{item1,item2,...}` - matches one of the alternatives
Other characters represent themselves. Only paths that have
exactly the same character in the same position will match. Note
that matching is case-sensitive; i.e. "a" will not match "A".
By default, the patterns `*` and `?` do not match files starting
with a dot `.` unless `match_dot: true` is given.
## Examples
Imagine you have a directory called `projects` with three Elixir projects
@@ -513,17 +550,31 @@ defmodule Path do
"""
@spec wildcard(t) :: [binary]
def wildcard(glob) do
def wildcard(glob, opts \\ []) do
mod = if Keyword.get(opts, :match_dot), do: :file, else: Path.Wildcard
glob
|> List.from_char_data!
|> :filelib.wildcard
|> Enum.map(&String.from_char_data!/1)
|> chardata_to_list()
|> :filelib.wildcard(mod)
|> Enum.map(&IO.chardata_to_string/1)
end
# Normalize the given path by expanding "..", "." and "~".
defp chardata_to_list(chardata) do
case :unicode.characters_to_list(chardata) do
result when is_list(result) ->
result
{:error, encoded, rest} ->
raise UnicodeConversionError, encoded: encoded, rest: rest, kind: :invalid
{:incomplete, encoded, rest} ->
raise UnicodeConversionError, encoded: encoded, rest: rest, kind: :incomplete
end
end
defp expand_home(type) do
case String.from_char_data!(type) do
case IO.chardata_to_string(type) do
"~" <> rest -> System.user_home! <> rest
rest -> rest
end
@@ -531,6 +582,14 @@ defmodule Path do
defp normalize(path), do: normalize(split(path), [])
defp normalize([".."|t], ["/"|_] = acc) do
normalize t, acc
end
defp normalize([".."|t], [<<letter, ?:, ?/>>|_] = acc) when letter in ?a..?z do
normalize t, acc
end
defp normalize([".."|t], [_|acc]) do
normalize t, acc
end
+32 -73
View File
@@ -2,10 +2,16 @@ defmodule Process do
@moduledoc """
Conveniences for working with processes and the process dictionary.
Some of the functions in this module are inlined by the compiler,
similar to functions in the `Kernel` module and they are explicitly
marked in their docs as "inlined by the compiler". For more information
about inlined functions, check out the `Kernel` module.
Besides the functions available in this module, the `Kernel` module
exposes and auto-imports some basic functionality related to processes
available through the functions:
* `Kernel.spawn/1` and `Kernel.spawn/3`
* `Kernel.spawn_link/1` and `Kernel.spawn_link/3`
* `Kernel.spawn_monitor/1` and `Kernel.spawn_monitor/3`
* `Kernel.self/0`
* `Kernel.send/2`
"""
@doc """
@@ -57,15 +63,6 @@ defmodule Process do
nillify :erlang.put(key, value)
end
@doc """
Deletes all items in the dictionary.
"""
@spec delete :: [{term, term}]
def delete() do
IO.write :stderr, "Process.delete/0 is deprecated\n#{Exception.format_stacktrace}"
:erlang.erase()
end
@doc """
Deletes the given key from the dictionary.
"""
@@ -79,18 +76,18 @@ defmodule Process do
The following behaviour applies if reason is any term except `:normal` or `:kill`:
1) If pid is not trapping exits, pid will exit with the given reason;
1. If pid is not trapping exits, pid will exit with the given reason.
2) If pid is trapping exits, the exit signal is transformed into a message
{:EXIT, from, reason} and delivered to the message queue of pid;
2. If pid is trapping exits, the exit signal is transformed into a message
{:EXIT, from, reason} and delivered to the message queue of pid.
3) If reason is the atom `:normal`, pid will not exit. If it is trapping exits,
the exit signal is transformed into a message {:EXIT, from, :normal} and
delivered to its message queue;
3. If reason is the atom `:normal`, pid will not exit. If it is trapping
exits, the exit signal is transformed into a message {:EXIT, from,
:normal} and delivered to its message queue.
4) If reason is the atom `:kill`, that is if `exit(pid, :kill)` is called, an
untrappable exit signal is sent to pid which will unconditionally exit with
exit reason `:killed`.
4. If reason is the atom `:kill`, that is if `exit(pid, :kill)` is called,
an untrappable exit signal is sent to pid which will unconditionally
exit with exit reason `:killed`.
Inlined by the compiler.
@@ -197,54 +194,6 @@ defmodule Process do
:erlang.spawn_opt(mod, fun, args, opts)
end
@doc false
def spawn(fun) do
# IO.write :stderr, "Process.spawn/1 is deprecated, use the one in Kernel instead\n#{Exception.format_stacktrace}"
:erlang.spawn(fun)
end
@doc false
def spawn(mod, fun, args) do
# IO.write :stderr, "Process.spawn/3 is deprecated, use the one in Kernel instead\n#{Exception.format_stacktrace}"
:erlang.spawn(mod, fun, args)
end
@doc false
def spawn_link(fun) do
# IO.write :stderr, "Process.spawn_link/1 is deprecated, use the one in Kernel instead\n#{Exception.format_stacktrace}"
:erlang.spawn_link(fun)
end
@doc false
def spawn_link(mod, fun, args) do
# IO.write :stderr, "Process.spawn_link/3 is deprecated, use the one in Kernel instead\n#{Exception.format_stacktrace}"
:erlang.spawn_link(mod, fun, args)
end
@doc false
def spawn_monitor(fun) do
# IO.write :stderr, "Process.spawn_monitor/1 is deprecated, use the one in Kernel instead\n#{Exception.format_stacktrace}"
:erlang.spawn_monitor(fun)
end
@doc false
def spawn_monitor(mod, fun, args) do
# IO.write :stderr, "Process.spawn_monitor/3 is deprecated, use the one in Kernel instead\n#{Exception.format_stacktrace}"
:erlang.spawn_monitor(mod, fun, args)
end
@doc false
def self() do
# IO.write :stderr, "Process.self/0 is deprecated, use the one in Kernel instead\n#{Exception.format_stacktrace}"
:erlang.self()
end
@doc false
def send(dest, msg) do
# IO.write :stderr, "Process.send/2 is deprecated, use the one in Kernel instead\n#{Exception.format_stacktrace}"
:erlang.send(dest, msg)
end
@doc """
The calling process starts monitoring the item given.
It returns the monitor reference.
@@ -325,7 +274,7 @@ defmodule Process do
already registered (check with `registered?/1`).
"""
@spec register(pid | port, atom) :: true
def register(pid, name) do
def register(pid, name) when not name in [nil, false, true] do
:erlang.register(name, pid)
end
@@ -419,8 +368,18 @@ defmodule Process do
See http://www.erlang.org/doc/man/erlang.html#process_info-2 for more info.
"""
@spec info(pid, atom) :: {atom, term}
def info(pid, spec) do
@spec info(pid, atom) :: {atom, term} | nil
def info(pid, spec)
def info(pid, :registered_name) do
case :erlang.process_info(pid, :registered_name) do
:undefined -> nil
[] -> {:registered_name, []}
other -> other
end
end
def info(pid, spec) when is_atom(spec) do
nillify :erlang.process_info(pid, spec)
end
+570 -231
View File
@@ -1,226 +1,19 @@
defmodule Protocol do
@moduledoc false
@moduledoc """
Functions for working with protocols.
"""
# Callback for defprotocol.
@doc false
def defprotocol(name, [do: block]) do
quote do
defmodule unquote(name) do
# We don't allow function definition inside protocols
import Kernel, except: [
defmacrop: 1, defmacrop: 2, defmacro: 1, defmacro: 2,
defp: 1, defp: 2, def: 1, def: 2
]
@doc """
Defines a new protocol function.
# Import the new dsl that holds the new def
import Protocol.DSL, only: :macros
Protocols do not allow functions to be defined directly, instead, the
regular `Kernel.def/*` macros are replaced by this macro which
defines the protocol functions with the appropriate callbacks.
"""
defmacro def(signature)
# Compile with debug info for consolidation
@compile :debug_info
# Set up a clear slate to store defined functions
@functions []
@fallback_to_any false
# Invoke the user given block
unquote(block)
# Finalize expansion
unquote(after_defprotocol)
end
end
end
defp after_defprotocol do
quote unquote: false do
# == Deprecated records handling ==
{arg, impl} = Protocol.rec_impl_for(__MODULE__)
Kernel.def impl_for(unquote(arg)) when Kernel.is_record(unquote(arg)), do: unquote(impl)
# == Deprecated records handling ==
@spec impl_for(term) :: module | nil
Kernel.def impl_for(data)
# Define the implementation for structs.
#
# It simply delegates to struct_impl_for which is then
# optimized during protocol consolidation.
Kernel.def impl_for(%{__struct__: struct}) when :erlang.is_atom(struct) do
struct_impl_for(struct)
end
# Define the implementation for builtins.
for {guard, mod} <- Protocol.builtin do
target = Module.concat(__MODULE__, mod)
Kernel.def impl_for(data) when :erlang.unquote(guard)(data) do
case impl_for?(unquote(target)) do
true -> unquote(target).__impl__(:name)
false -> any_impl_for
end
end
end
@spec impl_for!(term) :: module | no_return
Kernel.def impl_for!(data) do
impl_for(data) || raise(Protocol.UndefinedError, protocol: __MODULE__, value: data)
end
# Internal handler for Any
if @fallback_to_any do
Kernel.defp any_impl_for do
case impl_for?(__MODULE__.Any) do
true -> __MODULE__.Any.__impl__(:name)
false -> nil
end
end
else
Kernel.defp any_impl_for, do: nil
end
# Internal handler for Structs
Kernel.defp struct_impl_for(struct) do
target = Module.concat(__MODULE__, struct)
case impl_for?(target) do
true -> target.__impl__(:name)
false -> any_impl_for
end
end
# Check if compilation is available internally
Kernel.defp impl_for?(target) do
Code.ensure_compiled?(target) and
function_exported?(target, :__impl__, 1)
end
# Inline any and struct implementations
@compile {:inline, any_impl_for: 0, struct_impl_for: 1, impl_for?: 1}
if :code.ensure_loaded(Kernel.Typespec) == {:module, Kernel.Typespec} and
not Kernel.Typespec.defines_type?(__MODULE__, :t, 0) do
@type t :: term
end
# Store information as an attribute so it
# can be read without loading the module.
Module.register_attribute(__MODULE__, :protocol, persist: true)
@protocol [fallback_to_any: !!@fallback_to_any, consolidated: false]
@doc false
Kernel.def __protocol__(:name), do: __MODULE__
Kernel.def __protocol__(:functions), do: unquote(:lists.sort(@functions))
end
end
# Callback for defimpl.
@doc false
def defimpl(protocol, opts) do
do_defimpl(protocol, :lists.keysort(1, opts))
end
defp do_defimpl(protocol, [do: block, for: for]) when is_list(for) do
for f <- for, do: do_defimpl(protocol, [do: block, for: f])
end
defp do_defimpl(protocol, [do: block, for: for]) do
quote do
protocol = unquote(protocol)
for = unquote(for)
name = Module.concat(protocol, for)
Protocol.assert_protocol(protocol)
defmodule name do
@behaviour unquote(protocol)
@protocol unquote(protocol)
@for unquote(for)
unquote(block)
Module.register_attribute(__MODULE__, :impl, persist: true)
@impl [protocol: @protocol, for: @for]
@doc false
def __impl__(:name), do: __MODULE__
def __impl__(:protocol), do: @protocol
def __impl__(:for), do: @for
end
end
end
# Check if the given module is a protocol. Raises an error
# if not loaded or not a protocol.
@doc false
def assert_protocol(module) do
case Code.ensure_compiled(module) do
{:module, ^module} -> nil
_ -> raise ArgumentError, message: "#{inspect module} is not loaded"
end
try do
module.__protocol__(:name)
rescue
UndefinedFunctionError ->
raise ArgumentError, message: "#{inspect module} is not a protocol"
end
end
# Builtin types.
@doc false
def builtin do
[is_tuple: Tuple,
is_atom: Atom,
is_list: List,
is_map: Map,
is_bitstring: BitString,
is_integer: Integer,
is_float: Float,
is_function: Function,
is_pid: PID,
is_port: Port,
is_reference: Reference]
end
# Implements the function that detects the protocol and
# returns the module to dispatch to.
@doc false
def rec_impl_for(current) do
all = [Any] ++ for {_guard, mod} <- builtin, do: mod
arg = quote do: arg
target = Module.concat(current, Tuple)
fallback = quote do
case impl_for?(unquote(target)) do
true -> unquote(target).__impl__(:name)
false -> any_impl_for
end
end
impl_for = quote do
atom = :erlang.element(1, unquote(arg))
case not(atom in unquote(all)) and match?('Elixir.' ++ _, atom_to_list(atom)) do
true ->
target = Module.concat(unquote(current), atom)
case impl_for?(target) do
true -> target.__impl__(:name)
false -> unquote(fallback)
end
false ->
unquote(fallback)
end
end
{arg, impl_for}
end
end
defmodule Protocol.DSL do
@moduledoc false
@doc false
defmacro def({_, _, args}) when args == [] or is_atom(args) do
raise ArgumentError, message: "protocol functions expect at least one argument"
raise ArgumentError, "protocol functions expect at least one argument"
end
defmacro def({name, _, args}) when is_atom(name) and is_list(args) do
@@ -230,7 +23,7 @@ defmodule Protocol.DSL do
type_args = [quote(do: t) | type_args]
call_args = for i <- :lists.seq(2, arity),
do: {binary_to_atom(<<?x, i + 64>>), [], __MODULE__}
do: {String.to_atom(<<?x, i + 64>>), [], __MODULE__}
call_args = [quote(do: t) | call_args]
quote do
@@ -250,30 +43,576 @@ defmodule Protocol.DSL do
# Convert the spec to callback if possible,
# otherwise generate a dummy callback
Protocol.DSL.__spec__?(__MODULE__, name, arity) ||
Protocol.__spec__?(__MODULE__, name, arity) ||
@callback unquote(name)(unquote_splicing(type_args)) :: term
end
end
defmacro def(_) do
raise ArgumentError, message: "invalid args for def inside defprotocol"
raise ArgumentError, "invalid args for def inside defprotocol"
end
@doc """
Checks if the given module is loaded and is protocol.
Returns `:ok` if so, otherwise raises ArgumentError.
"""
@spec assert_protocol!(module) :: :ok | no_return
def assert_protocol!(module) do
assert_protocol!(module, "")
end
defp assert_protocol!(module, extra) do
case Code.ensure_compiled(module) do
{:module, ^module} -> :ok
_ -> raise ArgumentError, "#{inspect module} is not available" <> extra
end
try do
module.__protocol__(:name)
rescue
UndefinedFunctionError ->
raise ArgumentError, "#{inspect module} is not a protocol" <> extra
end
:ok
end
@doc """
Checks if the given module is loaded and is an implementation
of the given protocol.
Returns `:ok` if so, otherwise raises ArgumentError.
"""
@spec assert_impl!(module, module) :: :ok | no_return
def assert_impl!(protocol, impl) do
assert_impl!(protocol, impl, "")
end
defp assert_impl!(protocol, impl, extra) do
case Code.ensure_compiled(impl) do
{:module, ^impl} -> :ok
_ -> raise ArgumentError,
"#{inspect impl} is not available" <> extra
end
try do
impl.__impl__(:protocol)
rescue
UndefinedFunctionError ->
raise ArgumentError,
"#{inspect impl} is not an implementation of a protocol" <> extra
else
^protocol ->
:ok
other ->
raise ArgumentError,
"expected #{inspect impl} to be an implementation of #{inspect protocol}, got: #{inspect other}" <> extra
end
end
@doc """
Derive the `protocol` for `module` with the given options.
"""
defmacro derive(protocol, module, options \\ []) do
quote do
module = unquote(module)
Protocol.__derive__([{unquote(protocol), unquote(options)}], module, __ENV__)
end
end
## Consolidation
@doc """
Extract all protocols from the given paths.
The paths can be either a char list or a string. Internally
they are worked on as char lists, so passing them as lists
avoid extra conversion.
Does not load any of the protocols.
## Examples
# Get Elixir's ebin and retrieve all protocols
iex> path = :code.lib_dir(:elixir, :ebin)
iex> mods = Protocol.extract_protocols([path])
iex> Enumerable in mods
true
"""
@spec extract_protocols([char_list | String.t]) :: [atom]
def extract_protocols(paths) do
extract_matching_by_attribute paths, 'Elixir.',
fn module, attributes ->
case attributes[:protocol] do
[fallback_to_any: _, consolidated: _] -> module
_ -> nil
end
end
end
@doc """
Extract all types implemented for the given protocol from
the given paths.
The paths can be either a char list or a string. Internally
they are worked on as char lists, so passing them as lists
avoid extra conversion.
Does not load any of the implementations.
## Examples
# Get Elixir's ebin and retrieve all protocols
iex> path = :code.lib_dir(:elixir, :ebin)
iex> mods = Protocol.extract_impls(Enumerable, [path])
iex> List in mods
true
"""
@spec extract_impls(module, [char_list | String.t]) :: [atom]
def extract_impls(protocol, paths) when is_atom(protocol) do
prefix = Atom.to_char_list(protocol) ++ '.'
extract_matching_by_attribute paths, prefix, fn
_mod, attributes ->
case attributes[:impl] do
[protocol: ^protocol, for: for] -> for
_ -> nil
end
end
end
defp extract_matching_by_attribute(paths, prefix, callback) do
for path <- paths,
file <- list_dir(path),
mod = extract_from_file(path, file, prefix, callback),
do: mod
end
defp list_dir(path) when is_list(path) do
case :file.list_dir(path) do
{:ok, files} -> files
_ -> []
end
end
defp list_dir(path), do: list_dir(to_char_list(path))
defp extract_from_file(path, file, prefix, callback) do
if :lists.prefix(prefix, file) and :filename.extension(file) == '.beam' do
extract_from_beam(:filename.join(path, file), callback)
end
end
defp extract_from_beam(file, callback) do
case :beam_lib.chunks(file, [:attributes]) do
{:ok, {module, [attributes: attributes]}} ->
callback.(module, attributes)
_ ->
nil
end
end
defmacrop if_ok(expr, call) do
quote do
case unquote(expr) do
{:ok, var} -> unquote(Macro.pipe(quote(do: var), call, 0))
other -> other
end
end
end
@doc """
Returns true if the protocol was consolidated.
"""
@spec consolidated?(module) :: boolean
def consolidated?(protocol) do
protocol.__info__(:attributes)[:protocol][:consolidated]
end
@doc """
Receives a protocol and a list of implementations and
consolidates the given protocol.
Consolidation happens by changing the protocol `impl_for`
in the abstract format to have fast lookup rules. Usually
the list of implementations to use during consolidation
are retrieved with the help of `extract_impls/2`.
It returns the updated version of the protocol bytecode.
A given bytecode or protocol implementation can be checked
to be consolidated or not by analyzing the protocol
attribute:
Protocol.consolidated?(Enumerable)
If the first element of the tuple is true, it means
the protocol was consolidated.
This function does not load the protocol at any point
nor loads the new bytecode for the compiled module.
However each implementation must be available and
it will be loaded.
"""
@spec consolidate(module, [module]) ::
{:ok, binary} |
{:error, :not_a_protocol} |
{:error, :no_beam_info}
def consolidate(protocol, types) when is_atom(protocol) do
beam_protocol(protocol)
|> if_ok(change_debug_info types)
|> if_ok(compile)
end
@docs_chunk 'ExDc'
defp beam_protocol(protocol) do
chunk_ids = [:abstract_code, :attributes, @docs_chunk]
opts = [:allow_missing_chunks]
case :beam_lib.chunks(beam_file(protocol), chunk_ids, opts) do
{:ok, {^protocol, [{:abstract_code, {_raw, abstract_code}},
{:attributes, attributes},
{@docs_chunk, docs}]}} ->
case attributes[:protocol] do
[fallback_to_any: any, consolidated: _] ->
{:ok, {protocol, any, abstract_code, docs}}
_ ->
{:error, :not_a_protocol}
end
_ ->
{:error, :no_beam_info}
end
end
defp beam_file(module) when is_atom(module) do
case :code.which(module) do
:non_existing -> module
file -> file
end
end
# Change the debug information to the optimized
# impl_for/1 dispatch version.
defp change_debug_info({protocol, any, code, docs}, types) do
types = if any, do: types, else: List.delete(types, Any)
all = [Any] ++ for {_guard, mod} <- builtin, do: mod
structs = types -- all
case change_impl_for(code, protocol, types, structs, false, []) do
{:ok, ret} -> {:ok, {ret, docs}}
other -> other
end
end
defp change_impl_for([{:attribute, line, :protocol, opts}|t], protocol, types, structs, _, acc) do
opts = [fallback_to_any: opts[:fallback_to_any], consolidated: true]
change_impl_for(t, protocol, types, structs, true,
[{:attribute, line, :protocol, opts}|acc])
end
defp change_impl_for([{:function, line, :impl_for, 1, _}|t], protocol, types, structs, is_protocol, acc) do
fallback = if Any in types, do: load_impl(protocol, Any), else: nil
clauses = for {guard, mod} <- builtin,
mod in types,
do: builtin_clause_for(mod, guard, protocol, line)
clauses = [struct_clause_for(line)|clauses] ++
[fallback_clause_for(fallback, protocol, line)]
change_impl_for(t, protocol, types, structs, is_protocol,
[{:function, line, :impl_for, 1, clauses}|acc])
end
defp change_impl_for([{:function, line, :struct_impl_for, 1, _}|t], protocol, types, structs, is_protocol, acc) do
fallback = if Any in types, do: load_impl(protocol, Any), else: nil
clauses = for struct <- structs, do: each_struct_clause_for(struct, protocol, line)
clauses = clauses ++ [fallback_clause_for(fallback, protocol, line)]
change_impl_for(t, protocol, types, structs, is_protocol,
[{:function, line, :struct_impl_for, 1, clauses}|acc])
end
defp change_impl_for([h|t], protocol, info, types, is_protocol, acc) do
change_impl_for(t, protocol, info, types, is_protocol, [h|acc])
end
defp change_impl_for([], protocol, _info, _types, is_protocol, acc) do
if is_protocol do
{:ok, {protocol, Enum.reverse(acc)}}
else
{:error, :not_a_protocol}
end
end
defp builtin_clause_for(mod, guard, protocol, line) do
{:clause, line,
[{:var, line, :x}],
[[{:call, line,
{:remote, line, {:atom, line, :erlang}, {:atom, line, guard}},
[{:var, line, :x}],
}]],
[{:atom, line, load_impl(protocol, mod)}]}
end
defp struct_clause_for(line) do
{:clause, line,
[{:map, line, [
{:map_field_exact, line, {:atom, line, :__struct__}, {:var, line, :x}}
]}],
[[{:call, line,
{:remote, line, {:atom, line, :erlang}, {:atom, line, :is_atom}},
[{:var, line, :x}],
}]],
[{:call, line,
{:atom, line, :struct_impl_for},
[{:var, line, :x}]}]}
end
defp each_struct_clause_for(other, protocol, line) do
{:clause, line, [{:atom, line, other}], [],
[{:atom, line, load_impl(protocol, other)}]}
end
defp fallback_clause_for(value, _protocol, line) do
{:clause, line, [{:var, line, :_}], [],
[{:atom, line, value}]}
end
defp load_impl(protocol, for) do
Module.concat(protocol, for).__impl__(:target)
end
# Finally compile the module and emit its bytecode.
defp compile({{protocol, code}, docs}) do
opts = if Code.compiler_options[:debug_info], do: [:debug_info], else: []
{:ok, ^protocol, binary, _warnings} = :compile.forms(code, [:return|opts])
unless docs == :missing_chunk do
binary = :elixir_module.add_beam_chunk(binary, @docs_chunk, docs)
end
{:ok, binary}
end
## Definition callbacks
@doc false
def __protocol__(name, [do: block]) do
quote do
defmodule unquote(name) do
# We don't allow function definition inside protocols
import Kernel, except: [
defmacrop: 1, defmacrop: 2, defmacro: 1, defmacro: 2,
defp: 1, defp: 2, def: 1, def: 2
]
# Import the new dsl that holds the new def
import Protocol, only: [def: 1]
# Compile with debug info for consolidation
@compile :debug_info
# Set up a clear slate to store defined functions
@functions []
@fallback_to_any false
# Invoke the user given block
unquote(block)
# Finalize expansion
unquote(after_defprotocol)
end
end
end
defp after_defprotocol do
quote bind_quoted: [builtin: builtin] do
@spec impl_for(term) :: module | nil
Kernel.def impl_for(data)
# Define the implementation for structs.
#
# It simply delegates to struct_impl_for which is then
# optimized during protocol consolidation.
Kernel.def impl_for(%{__struct__: struct}) when :erlang.is_atom(struct) do
struct_impl_for(struct)
end
# Define the implementation for builtins.
for {guard, mod} <- builtin do
target = Module.concat(__MODULE__, mod)
Kernel.def impl_for(data) when :erlang.unquote(guard)(data) do
case impl_for?(unquote(target)) do
true -> unquote(target).__impl__(:target)
false -> any_impl_for
end
end
end
@spec impl_for!(term) :: module | no_return
Kernel.def impl_for!(data) do
impl_for(data) || raise(Protocol.UndefinedError, protocol: __MODULE__, value: data)
end
# Internal handler for Any
if @fallback_to_any do
Kernel.defp any_impl_for do
case impl_for?(__MODULE__.Any) do
true -> __MODULE__.Any.__impl__(:target)
false -> nil
end
end
else
Kernel.defp any_impl_for, do: nil
end
# Internal handler for Structs
Kernel.defp struct_impl_for(struct) do
target = Module.concat(__MODULE__, struct)
case impl_for?(target) do
true -> target.__impl__(:target)
false -> any_impl_for
end
end
# Check if compilation is available internally
Kernel.defp impl_for?(target) do
Code.ensure_compiled?(target) and
function_exported?(target, :__impl__, 1)
end
# Inline any and struct implementations
@compile {:inline, any_impl_for: 0, struct_impl_for: 1, impl_for?: 1}
unless Kernel.Typespec.defines_type?(__MODULE__, :t, 0) do
@type t :: term
end
# Store information as an attribute so it
# can be read without loading the module.
Module.register_attribute(__MODULE__, :protocol, persist: true)
@protocol [fallback_to_any: !!@fallback_to_any, consolidated: false]
@doc false
@spec __protocol__(atom) :: term
Kernel.def __protocol__(:name), do: __MODULE__
Kernel.def __protocol__(:functions), do: unquote(:lists.sort(@functions))
end
end
@doc false
def __impl__(protocol, opts) do
do_defimpl(protocol, :lists.keysort(1, opts))
end
defp do_defimpl(protocol, [do: block, for: for]) when is_list(for) do
for f <- for, do: do_defimpl(protocol, [do: block, for: f])
end
defp do_defimpl(protocol, [do: block, for: for]) do
quote do
protocol = unquote(protocol)
for = unquote(for)
name = Module.concat(protocol, for)
Protocol.assert_protocol!(protocol)
defmodule name do
@behaviour protocol
@protocol protocol
@for for
unquote(block)
Module.register_attribute(__MODULE__, :impl, persist: true)
@impl [protocol: @protocol, for: @for]
@doc false
@spec __impl__(atom) :: term
def __impl__(:target), do: __MODULE__
def __impl__(:protocol), do: @protocol
def __impl__(:for), do: @for
end
end
end
@doc false
def __derive__(derives, for, %Macro.Env{} = env) when is_atom(for) do
struct =
if for == env.module do
Module.get_attribute(for, :struct) ||
raise "struct is not defined for #{inspect for}"
else
for.__struct__
end
:lists.foreach(fn
proto when is_atom(proto) ->
derive(proto, for, struct, [], env)
{proto, opts} when is_atom(proto) ->
derive(proto, for, struct, opts, env)
end, :lists.flatten(derives))
:ok
end
defp derive(protocol, for, struct, opts, env) do
impl = Module.concat(protocol, Map)
extra = ", cannot derive #{inspect protocol} for #{inspect for}"
assert_protocol!(protocol, extra)
assert_impl!(protocol, impl, extra)
# Clean up variables from eval context
env = %{env | vars: [], export_vars: nil}
args = [for, struct, opts]
:elixir_module.expand_callback(env.line, impl, :__deriving__, args, env, fn
mod, fun, args ->
if function_exported?(mod, fun, length(args)) do
apply(mod, fun, args)
else
Module.create(Module.concat(protocol, for), quote do
Module.register_attribute(__MODULE__, :impl, persist: true)
@impl [protocol: unquote(protocol), for: unquote(for)]
@doc false
@spec __impl__(atom) :: term
def __impl__(:target), do: unquote(impl)
def __impl__(:protocol), do: unquote(protocol)
def __impl__(:for), do: unquote(for)
end, Macro.Env.location(env))
end
end)
end
@doc false
def __spec__?(module, name, arity) do
case :code.ensure_loaded(Kernel.Typespec) do
{:module, Kernel.Typespec} ->
tuple = {name, arity}
specs = Module.get_attribute(module, :spec)
signature = {name, arity}
specs = Module.get_attribute(module, :spec)
found = for {k, v} <- specs, k == tuple do
Kernel.Typespec.define_callback(module, tuple, v)
found =
for {:spec, expr, caller} <- specs,
Kernel.Typespec.spec_to_signature(expr) == signature do
Kernel.Typespec.define_spec(:callback, expr, caller)
true
end
found != []
{:error, _} ->
true
end
found != []
end
## Helpers
defp builtin do
[is_tuple: Tuple,
is_atom: Atom,
is_list: List,
is_map: Map,
is_bitstring: BitString,
is_integer: Integer,
is_float: Float,
is_function: Function,
is_pid: PID,
is_port: Port,
is_reference: Reference]
end
end
-249
View File
@@ -1,249 +0,0 @@
defmodule Protocol.Consolidation do
@moduledoc """
Module responsible for consolidating protocols and helpers for
extracting protocols and implementations from code paths for
consolidation.
"""
@doc """
Extract all protocols from the given paths.
The paths can be either a char list or a string. Internally
they are worked on as char lists, so passing them as lists
avoid extra conversion.
## Examples
# Get Elixir's ebin and retrieve all protocols
iex> path = :code.lib_dir(:elixir, :ebin)
iex> mods = Protocol.Consolidation.extract_protocols([path])
iex> Enumerable in mods
true
"""
@spec extract_protocols([char_list | String.t]) :: [atom]
def extract_protocols(paths) do
extract_matching_by_attribute paths, 'Elixir.',
fn module, attributes ->
case attributes[:protocol] do
[fallback_to_any: _, consolidated: _] -> module
_ -> nil
end
end
end
@doc """
Extract all types implemented for the given protocol from
the given paths.
The paths can be either a char list or a string. Internally
they are worked on as char lists, so passing them as lists
avoid extra conversion.
## Examples
# Get Elixir's ebin and retrieve all protocols
iex> path = :code.lib_dir(:elixir, :ebin)
iex> mods = Protocol.Consolidation.extract_impls(Enumerable, [path])
iex> List in mods
true
"""
@spec extract_impls(module, [char_list | String.t]) :: [atom]
def extract_impls(protocol, paths) when is_atom(protocol) do
prefix = atom_to_list(protocol) ++ '.'
extract_matching_by_attribute paths, prefix, fn
_mod, attributes ->
case attributes[:impl] do
[protocol: ^protocol, for: for] -> for
_ -> nil
end
end
end
defp extract_matching_by_attribute(paths, prefix, callback) do
for path <- paths,
file <- list_dir(path),
mod = extract_from_file(path, file, prefix, callback),
do: mod
end
defp list_dir(path) when is_list(path) do
case :file.list_dir(path) do
{:ok, files} -> files
_ -> []
end
end
defp list_dir(path), do: list_dir(to_char_list(path))
defp extract_from_file(path, file, prefix, callback) do
if :lists.prefix(prefix, file) and :filename.extension(file) == '.beam' do
extract_from_beam(:filename.join(path, file), callback)
end
end
defp extract_from_beam(file, callback) do
case :beam_lib.chunks(file, [:attributes]) do
{:ok, {module, [attributes: attributes]}} ->
callback.(module, attributes)
_ ->
nil
end
end
defmacrop if_ok(expr, call) do
quote do
case unquote(expr) do
{:ok, var} -> unquote(Macro.pipe(quote(do: var), call, 0))
other -> other
end
end
end
@doc """
Receives a protocol and a list of implementations and
consolidates the given protocol. Consolidation happens
by changing the protocol `impl_for` in the abstract
format to have fast lookup rules.
It returns the updated version of the protocol bytecode.
A given bytecode or protocol implementation can be checked
to be consolidated or not by analyzing the protocol
attribute:
Enumerable.__info__(:attributes)[:protocol]
If the first element of the tuple is true, it means
the protocol was consolidated.
This function does not load the protocol at any point
nor loads the new bytecode for the compiled module.
"""
@spec apply_to(module, [module]) ::
{:ok, binary} |
{:error, :not_a_protocol} |
{:error, :no_beam_info}
def apply_to(protocol, types) when is_atom(protocol) do
raise ArgumentError, message: "consolidation is disabled as we can't consolidate records " <>
"and structs at once. Consolidation will be added back once " <>
"polymorphic records are removed"
ensure_protocol(protocol)
|> if_ok(change_debug_info types)
|> if_ok(compile)
end
# Ensure the given module is loaded and is a protocol.
defp ensure_protocol(protocol) do
case :beam_lib.chunks(beam_file(protocol), [:abstract_code, :attributes]) do
{:ok, {^protocol, [abstract_code: {_raw, abstract_code},
attributes: attributes]}} ->
case attributes[:protocol] do
[fallback_to_any: any, consolidated: _] ->
{:ok, {protocol, any, abstract_code}}
_ ->
{:error, :not_a_protocol}
end
_ ->
{:error, :no_beam_info}
end
end
defp beam_file(module) when is_atom(module) do
case :code.which(module) do
:non_existing -> module
file -> file
end
end
# Change the debug information to the optimized
# impl_for/1 dispatch version.
defp change_debug_info({protocol, any, code}, types) do
types = if any, do: types, else: List.delete(types, Any)
all = [Any] ++ for {_guard, mod} <- Protocol.builtin, do: mod
structs = types -- all
change_impl_for(code, protocol, types, structs, false, [])
end
defp change_impl_for([{:attribute, line, :protocol, opts}|t], protocol, types, structs, _, acc) do
opts = [fallback_to_any: opts[:fallback_to_any], consolidated: true]
change_impl_for(t, protocol, types, structs, true,
[{:attribute, line, :protocol, opts}|acc])
end
defp change_impl_for([{:function, line, :impl_for, 1, _}|t], protocol, types, structs, is_protocol, acc) do
fallback = if Any in types, do: Module.concat(protocol, Any), else: nil
clauses = for {guard, mod} <- Protocol.builtin,
mod in types,
do: builtin_clause_for(mod, guard, protocol, line)
clauses = [struct_clause_for(line)|clauses] ++
[fallback_clause_for(fallback, protocol, line)]
change_impl_for(t, protocol, types, structs, is_protocol,
[{:function, line, :impl_for, 1, clauses}|acc])
end
defp change_impl_for([{:function, line, :struct_impl_for, 1, _}|t], protocol, types, structs, is_protocol, acc) do
fallback = if Any in types, do: Module.concat(protocol, Any), else: nil
clauses = for struct <- structs, do: each_struct_clause_for(struct, protocol, line)
clauses = clauses ++ [fallback_clause_for(fallback, protocol, line)]
change_impl_for(t, protocol, types, structs, is_protocol,
[{:function, line, :struct_impl_for, 1, clauses}|acc])
end
defp change_impl_for([h|t], protocol, info, types, is_protocol, acc) do
change_impl_for(t, protocol, info, types, is_protocol, [h|acc])
end
defp change_impl_for([], protocol, _info, _types, is_protocol, acc) do
if is_protocol do
{:ok, {protocol, Enum.reverse(acc)}}
else
{:error, :not_a_protocol}
end
end
defp builtin_clause_for(mod, guard, protocol, line) do
{:clause, line,
[{:var, line, :x}],
[[{:call, line,
{:remote, line, {:atom, line, :erlang}, {:atom, line, guard}},
[{:var, line, :x}],
}]],
[{:atom, line, Module.concat(protocol, mod)}]}
end
defp struct_clause_for(line) do
{:clause, line,
[{:map, line, [
{:map_field_exact, line, {:atom, line, :__struct__}, {:var, line, :x}}
]}],
[[{:call, line,
{:remote, line, {:atom, line, :erlang}, {:atom, line, :is_atom}},
[{:var, line, :x}],
}]],
[{:call, line,
{:atom, line, :struct_impl_for},
[{:var, line, :x}]}]}
end
defp each_struct_clause_for(other, protocol, line) do
{:clause, line, [{:atom, line, other}], [],
[{:atom, line, Module.concat(protocol, other)}]}
end
defp fallback_clause_for(value, _protocol, line) do
{:clause, line, [{:var, line, :_}], [],
[{:atom, line, value}]}
end
# Finally compile the module and emit its bytecode.
defp compile({protocol, code}) do
opts = if Code.compiler_options[:debug_info], do: [:debug_info], else: []
{:ok, ^protocol, binary, _warnings} = :compile.forms(code, [:return|opts])
{:ok, binary}
end
end
+120 -27
View File
@@ -12,12 +12,10 @@ defmodule Record do
manipulate the tuples, providing fast operations on top of
the tuples compact structure.
In Elixir, records are used in two situations:
In Elixir, records are used mostly in two situations:
1. To work with short, internal data. See `Inspect.Algebra`
implementation for a good example;
2. To interface with Erlang records;
1. to work with short, internal data
2. to interface with Erlang records
The macros `defrecord/3` and `defrecordp/3` can be used to create
records while `extract/2` can be used to extract records from Erlang
@@ -103,21 +101,6 @@ defmodule Record do
end
end
@doc false
def defmacros(name, values, env, tag \\ nil) do
Record.Deprecated.defmacros(name, values, env, tag)
end
@doc false
def deftypes(values, types, env) do
Record.Deprecated.deftypes(values, types, env)
end
@doc false
def deffunctions(values, env) do
Record.Deprecated.deffunctions(values, env)
end
@doc """
Defines a set of macros to create and access a record.
@@ -151,20 +134,21 @@ defmodule Record do
Record.defrecord :user, User, name: nil
end
user() #=> {User, nil}
require User
User.user() #=> {User, nil}
"""
defmacro defrecord(name, tag \\ nil, kv) do
quote bind_quoted: [name: name, tag: tag, kv: kv] do
tag = tag || name
fields = Macro.escape Record.Backend.default_fields(:record, kv)
fields = Macro.escape Record.__fields__(:defrecord, kv)
defmacro(unquote(name)(args \\ [])) do
Record.Backend.access(unquote(tag), unquote(fields), args, __CALLER__)
Record.__access__(unquote(tag), unquote(fields), args, __CALLER__)
end
defmacro(unquote(name)(record, args)) do
Record.Backend.access(unquote(tag), unquote(fields), record, args, __CALLER__)
Record.__access__(unquote(tag), unquote(fields), record, args, __CALLER__)
end
end
end
@@ -175,15 +159,124 @@ 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.Backend.default_fields(:record, kv)
fields = Macro.escape Record.__fields__(:defrecordp, kv)
defmacrop(unquote(name)(args \\ [])) do
Record.Backend.access(unquote(tag), unquote(fields), args, __CALLER__)
Record.__access__(unquote(tag), unquote(fields), args, __CALLER__)
end
defmacrop(unquote(name)(record, args)) do
Record.Backend.access(unquote(tag), unquote(fields), record, args, __CALLER__)
Record.__access__(unquote(tag), unquote(fields), record, args, __CALLER__)
end
end
end
# Normalizes of record fields to have default values.
@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}"
end, fields)
end
# Callback invoked from record/0 and record/1 macros.
@doc false
def __access__(atom, fields, args, caller) do
cond do
is_atom(args) ->
index(atom, fields, args)
Keyword.keyword?(args) ->
create(atom, fields, args, caller)
true ->
msg = "expected arguments to be a compile time atom or keywords, got: #{Macro.to_string args}"
raise ArgumentError, msg
end
end
# Callback invoked from the record/2 macro.
@doc false
def __access__(atom, fields, record, args, caller) do
cond do
is_atom(args) ->
get(atom, fields, record, args)
Keyword.keyword?(args) ->
update(atom, fields, record, args, caller)
true ->
msg = "expected arguments to be a compile time atom or keywords, got: #{Macro.to_string args}"
raise ArgumentError, msg
end
end
# Gets the index of field.
defp index(atom, fields, field) do
if index = find_index(fields, field, 0) do
index - 1 # Convert to Elixir index
else
raise ArgumentError, "record #{inspect atom} does not have the key: #{inspect field}"
end
end
# Creates a new record with the given default fields and keyword values.
defp create(atom, fields, keyword, caller) do
in_match = Macro.Env.in_match?(caller)
{match, remaining} =
Enum.map_reduce(fields, keyword, fn({field, default}, each_keyword) ->
new_fields =
case Keyword.has_key?(each_keyword, field) do
true -> Keyword.get(each_keyword, field)
false ->
case in_match do
true -> {:_, [], nil}
false -> Macro.escape(default)
end
end
{new_fields, Keyword.delete(each_keyword, field)}
end)
case remaining do
[] ->
{:{}, [], [atom|match]}
_ ->
keys = for {key, _} <- remaining, do: key
raise ArgumentError, "record #{inspect atom} does not have the key: #{inspect hd(keys)}"
end
end
# Updates a record given by var with the given keyword.
defp update(atom, fields, var, keyword, caller) do
if Macro.Env.in_match?(caller) do
raise ArgumentError, "cannot invoke update style macro inside match"
end
Enum.reduce keyword, var, fn({key, value}, acc) ->
index = find_index(fields, key, 0)
if index do
quote do
:erlang.setelement(unquote(index), unquote(acc), unquote(value))
end
else
raise ArgumentError, "record #{inspect atom} does not have the key: #{inspect key}"
end
end
end
# Gets a record key from the given var.
defp get(atom, fields, var, key) do
index = find_index(fields, key, 0)
if index do
quote do
:erlang.element(unquote(index), unquote(var))
end
else
raise ArgumentError, "record #{inspect atom} does not have the key: #{inspect key}"
end
end
defp find_index([{k, _}|_], k, i), do: i + 2
defp find_index([{_, _}|t], k, i), do: find_index(t, k, i + 1)
defp find_index([], _k, _i), do: nil
end
-153
View File
@@ -1,153 +0,0 @@
defmodule Record.Backend do
# Callback functions invoked by defrecord, defrecordp and friends.
@moduledoc false
@doc """
Normalizes a list of record or struct fields to have default values.
"""
def default_fields(type, fields) do
Enum.map(fields, fn
{ key, _ } = pair when is_atom(key) -> pair
key when is_atom(key) -> { key, nil }
other -> raise ArgumentError, message: "#{type} fields must be atoms, got: #{inspect other}"
end)
end
@doc """
Splits a keywords list into fields and types.
This logic is shared by records and structs.
"""
def split_fields_and_types(kv) do
if Keyword.keyword?(kv) do
split_fields_and_types(kv, [], [])
else
{kv, []}
end
end
defp split_fields_and_types([{field, {:::, _, [default, type]}}|t], fields, types) do
split_fields_and_types(t, [{field, default}|fields], [{field, type}|types])
end
defp split_fields_and_types([{field, default}|t], fields, types) do
split_fields_and_types(t, [{field, default}|fields], [{field, quote(do: term)}|types])
end
defp split_fields_and_types([field|t], fields, types) do
split_fields_and_types(t, [field|fields], [{field, quote(do: term)}|types])
end
defp split_fields_and_types([], fields, types) do
{:lists.reverse(fields), :lists.reverse(types)}
end
@doc """
Callback invoked from record/0 and record/1 macros.
"""
def access(atom, fields, args, caller) do
cond do
is_atom(args) ->
index(atom, fields, args)
Keyword.keyword?(args) ->
create(atom, fields, args, caller)
true ->
raise ArgumentError,
message: "expected arguments to be a compile time atom or keywords, got: #{Macro.to_string args}"
end
end
@doc """
Callback invoked from the record/2 macro.
"""
def access(atom, fields, record, args, caller) do
cond do
is_atom(args) ->
get(atom, fields, record, args)
Keyword.keyword?(args) ->
update(atom, fields, record, args, caller)
true ->
raise ArgumentError,
message: "expected arguments to be a compile time atom or keywords, got: #{Macro.to_string args}"
end
end
@doc """
Gets the index of field.
"""
def index(atom, fields, field) do
if index = find_index(fields, field, 0) do
index - 1 # Convert to Elixir index
else
raise ArgumentError, message: "record #{inspect atom} does not have the key: #{inspect field}"
end
end
@doc """
Creates a new record with the given default fields and keyword values.
"""
def create(atom, fields, keyword, caller) do
in_match = Macro.Env.in_match?(caller)
{match, remaining} =
Enum.map_reduce(fields, keyword, fn({field, default}, each_keyword) ->
new_fields =
case Keyword.has_key?(each_keyword, field) do
true -> Keyword.get(each_keyword, field)
false ->
case in_match do
true -> {:_, [], nil}
false -> Macro.escape(default)
end
end
{new_fields, Keyword.delete(each_keyword, field)}
end)
case remaining do
[] ->
{:{}, [], [atom|match]}
_ ->
keys = for {key, _} <- remaining, do: key
raise ArgumentError, message: "record #{inspect atom} does not have the key: #{inspect hd(keys)}"
end
end
@doc """
Updates a record given by var with the given keyword.
"""
def update(atom, fields, var, keyword, caller) do
if Macro.Env.in_match?(caller) do
raise ArgumentError, message: "cannot invoke update style macro inside match"
end
Enum.reduce keyword, var, fn({key, value}, acc) ->
index = find_index(fields, key, 0)
if index do
quote do
:erlang.setelement(unquote(index), unquote(acc), unquote(value))
end
else
raise ArgumentError, message: "record #{inspect atom} does not have the key: #{inspect key}"
end
end
end
@doc """
Gets a record key from the given var.
"""
def get(atom, fields, var, key) do
index = find_index(fields, key, 0)
if index do
quote do
:erlang.element(unquote(index), unquote(var))
end
else
raise ArgumentError, message: "record #{inspect atom} does not have the key: #{inspect key}"
end
end
defp find_index([{k, _}|_], k, i), do: i + 2
defp find_index([{_, _}|t], k, i), do: find_index(t, k, i + 1)
defp find_index([], _k, _i), do: nil
end
-526
View File
@@ -1,526 +0,0 @@
defmodule Record.Deprecated do
@moduledoc false
def defrecord(name, fields, opts) do
block = Keyword.get(opts, :do, nil)
record_check!(fields)
quote do
unquoted_fields = unquote(fields)
defmodule unquote(name) do
import Record.DSL
@record_fields []
@record_types []
Record.Deprecated.deffunctions(unquoted_fields, __ENV__)
value = unquote(block)
Record.Deprecated.deftypes(@record_fields, @record_types, __ENV__)
value
end
end
end
defp record_check!([{field, {:::, _, [_, _]}}|_]) when is_atom(field) do
raise ArgumentError, message: "typespecs are not supported inlined with defrecord, " <>
"please use record_type instead"
end
defp record_check!([_|t]), do: record_check!(t)
defp record_check!(_), do: :ok
def defrecordp(name, tag, fields) do
case recordp_split(fields, [], [], false) do
{:ok, fields, types, def_type} ->
types = Macro.escape(types)
# bind_quoted isn't available when bootstrapping record
quoted = quote [unquote: false] do
Record.Deprecated.defmacros(name, fields, __ENV__, tag)
if def_type do
type = binary_to_atom(atom_to_binary(name) <> "_t")
@typep unquote(type)() :: {unquote(tag || name), unquote_splicing(types)}
end
end
quote do
def_type = unquote(def_type)
fields = unquote(fields)
types = unquote(types)
tag = unquote(tag)
name = unquote(name)
unquote(quoted)
end
:error ->
quote do
name = unquote(name)
Record.Deprecated.defmacros(name, unquote(fields), __ENV__, unquote(tag))
end
end
end
defp recordp_split([{field, {:::, _, [default, type]}}|t], defaults, types, _) do
recordp_split(t, [{field, default}|defaults], [type|types], true)
end
defp recordp_split([other|t], defaults, types, def_type) do
recordp_split(t, [other|defaults], [quote(do: term)|types], def_type)
end
defp recordp_split([], defaults, types, def_type) do
{:ok, :lists.reverse(defaults), :lists.reverse(types), def_type}
end
defp recordp_split(_, _, _, _) do
:error
end
def deffunctions(values, env) do
values = for value <- values, do: convert_value(value)
escaped = Macro.escape(values)
contents = [
reflection(escaped),
initializer(escaped),
conversions(values),
record_optimizable(),
updater(values),
accessors(values, 1),
switch_recorder()
]
contents = [quote(do: @record_fields unquote(escaped))|contents]
# Special case for bootstrapping purposes
if env == Macro.Env do
Module.eval_quoted(env, contents, [], [])
else
Module.eval_quoted(env.module, contents, [], Macro.Env.location(env))
end
end
def deftypes(values, types, env) do
types = types || []
values = for value <- values do
{name, default} = convert_value(value)
{name, default, find_spec(types, name)}
end
contents = [
core_specs(values),
accessor_specs(values, 1, [])
]
# We need to handle bootstraping
cond do
:code.ensure_loaded(Kernel.Typespec) != {:module, Kernel.Typespec} ->
nil
env == Macro.Env ->
Module.eval_quoted(env, contents, [], [])
true ->
Module.eval_quoted(env.module, contents, [], Macro.Env.location(env))
end
end
def defmacros(name, values, env, tag \\ nil)
when is_atom(name) and is_list(values) and is_atom(tag) do
escaped = for value <- values do
{key, value} = convert_value(value)
{key, Macro.escape(value)}
end
tag = tag || name
contents = quote do
defmacrop unquote(name)() do
Record.Deprecated.access(unquote(tag), unquote(escaped), [], __CALLER__)
end
defmacrop unquote(name)(args) do
Record.Deprecated.access(unquote(tag), unquote(escaped), args, __CALLER__)
end
defmacrop unquote(name)(record, args) do
Record.Deprecated.dispatch(unquote(tag), unquote(escaped), record, args, __CALLER__)
end
end
Module.eval_quoted(env.module, contents, [], Macro.Env.location(env))
end
## Callbacks
defmacro __before_compile__(_) do
quote do
def __record__(:optimizable), do: @record_optimizable
end
end
def __on_definition__(env, kind, name, args, _guards, _body) do
tuple = {name, length(args)}
module = env.module
functions = Module.get_attribute(module, :record_optimizable)
functions =
if kind in [:def] and Module.get_attribute(module, :record_optimized) do
[tuple|functions]
else
List.delete(functions, tuple)
end
Module.put_attribute(module, :record_optimizable, functions)
end
def access(atom, fields, arg, _caller) when is_atom(arg) do
if index = find_index(fields, arg, 0) do
index + 1
else
raise ArgumentError, message: "record #{inspect atom} does not have the key: #{inspect arg}"
end
end
def access(atom, fields, keyword, caller) do
unless is_keyword(keyword) do
raise ArgumentError, message: "expected contents inside brackets to be a keyword list or an atom, got: #{inspect keyword}"
end
in_match = Macro.Env.in_match?(caller)
has_underscore_value = Keyword.has_key?(keyword, :_)
underscore_value = Keyword.get(keyword, :_, {:_, [], nil})
keyword = Keyword.delete keyword, :_
iterator = fn({field, default}, each_keyword) ->
new_fields =
case Keyword.has_key?(each_keyword, field) do
true -> Keyword.get(each_keyword, field)
false ->
case in_match or has_underscore_value do
true -> underscore_value
false -> Macro.escape(default)
end
end
{new_fields, Keyword.delete(each_keyword, field)}
end
{match, remaining} = :lists.mapfoldl(iterator, keyword, fields)
case remaining do
[] ->
{:{}, [], [atom|match]}
_ ->
keys = for {key, _} <- remaining, do: key
raise ArgumentError, message: "record #{inspect atom} does not have the key: #{inspect hd(keys)}"
end
end
def dispatch(atom, fields, record, args, caller) do
cond do
is_atom(args) ->
get(atom, fields, record, args)
is_keyword(args) ->
update(atom, fields, record, args, caller)
true ->
raise ArgumentError, message: "expected arguments to be a compile time atom or keywords"
end
end
defp update(atom, fields, var, keyword, caller) do
unless is_keyword(keyword) do
raise ArgumentError, message: "expected arguments to be compile time keywords"
end
if Macro.Env.in_match?(caller) do
raise ArgumentError, message: "cannot invoke update style macro inside match context"
end
Enum.reduce keyword, var, fn({key, value}, acc) ->
index = find_index(fields, key, 0)
if index do
quote do
:erlang.setelement(unquote(index + 2), unquote(acc), unquote(value))
end
else
raise ArgumentError, message: "record #{inspect atom} does not have the key: #{inspect key}"
end
end
end
defp get(atom, fields, var, key) do
index = find_index(fields, key, 0)
if index do
quote do
:erlang.element(unquote(index + 2), unquote(var))
end
else
raise ArgumentError, message: "record #{inspect atom} does not have the key: #{inspect key}"
end
end
## Function generation
defp reflection(values) do
quoted = for {k, _} <- values do
index = find_index(values, k, 0)
quote do
def __record__(:index, unquote(k)), do: unquote(index + 1)
end
end
quote do
unquote(quoted)
@doc false
def __record__(:index, _), do: nil
@doc false
def __record__(:index, arg, _), do: __record__(:index, arg)
@doc false
def __record__(kind, _), do: __record__(kind)
@doc false
def __record__(:name), do: __MODULE__
def __record__(:fields), do: unquote(values)
end
end
defp initializer(values) do
defaults = for {_, value} <- values, do: value
# For each value, define a piece of code that will receive
# an ordered dict of options (opts) and it will try to fetch
# the given key from the ordered dict, falling back to the
# default value if one does not exist.
atom_selective = for {k, v} <- values, do: initialize_lookup(k, v)
string_selective = for {k, v} <- values, do: initialize_lookup(atom_to_binary(k), v)
quote do
@doc false
def new(), do: new([])
@doc false
def new([]), do: {__MODULE__, unquote_splicing(defaults)}
def new([{key, _}|_] = opts) when is_atom(key), do: {__MODULE__, unquote_splicing(atom_selective)}
def new([{key, _}|_] = opts) when is_binary(key), do: {__MODULE__, unquote_splicing(string_selective)}
end
end
defp initialize_lookup(k, v) do
quote do
case :lists.keyfind(unquote(k), 1, opts) do
false -> unquote(v)
{_, v} -> v
end
end
end
defp conversions(values) do
sorted = for {k, _} <- values do
index = find_index(values, k, 0)
{k, quote(do: :erlang.element(unquote(index + 2), record))}
end
quote do
@doc false
def to_keywords(record) do
unquote(sorted)
end
end
end
defp accessors([{:__exception__, _}|t], 1) do
accessors(t, 2)
end
defp accessors([{key, _default}|t], i) do
update = binary_to_atom "update_" <> atom_to_binary(key)
contents = quote do
@doc false
def unquote(key)(record) do
:erlang.element(unquote(i + 1), record)
end
@doc false
def unquote(key)(value, record) do
:erlang.setelement(unquote(i + 1), record, value)
end
@doc false
def unquote(update)(function, record) do
:erlang.setelement(unquote(i + 1), record,
function.(:erlang.element(unquote(i + 1), record)))
end
end
[contents|accessors(t, i + 1)]
end
defp accessors([], _i) do
[]
end
# Define an updater method that receives a
# keyword list and updates the record.
defp updater(values) do
atom_fields =
for {key, _default} <- values, do: updater_lookup(key, key, values)
string_fields =
for {key, _default} <- values, do: updater_lookup(atom_to_binary(key), key, values)
atom_contents = quote do: {__MODULE__, unquote_splicing(atom_fields)}
string_contents = quote do: {__MODULE__, unquote_splicing(string_fields)}
quote do
@doc false
def update([], record) do
record
end
def update([{key, _}|_] = keywords, record) when is_atom(key) do
unquote(atom_contents)
end
def update([{key, _}|_] = keywords, record) when is_binary(key) do
unquote(string_contents)
end
end
end
defp updater_lookup(k, key, values) do
index = find_index(values, key, 0)
quote do
case :lists.keyfind(unquote(k), 1, keywords) do
false -> :erlang.element(unquote(index + 2), record)
{_, value} -> value
end
end
end
defp record_optimizable do
quote do
@record_optimized true
@record_optimizable []
@before_compile {unquote(__MODULE__), :__before_compile__}
@on_definition {unquote(__MODULE__), :__on_definition__}
end
end
defp switch_recorder do
quote do: @record_optimized false
end
## Types/specs generation
defp core_specs(values) do
types = for {_, _, spec} <- values, do: spec
options = for {k, _, v} <- values, do: {k, v}
quote do
unless Kernel.Typespec.defines_type?(__MODULE__, :t, 0) do
@type t :: {__MODULE__, unquote_splicing(types)}
end
unless Kernel.Typespec.defines_type?(__MODULE__, :options, 0) do
@type options :: unquote(options) | [{String.t, term}]
end
@spec new :: t
@spec new(options) :: t
@spec to_keywords(t) :: options
@spec update(options, t) :: t
@spec __record__(:name) :: atom
@spec __record__(:fields) :: [{atom, any}]
@spec __record__(:index, atom) :: non_neg_integer | nil
end
end
defp accessor_specs([{:__exception__, _, _}|t], 1, acc) do
accessor_specs(t, 2, acc)
end
defp accessor_specs([{key, _default, spec}|t], i, acc) do
update = binary_to_atom "update_" <> atom_to_binary(key)
contents = quote do
@spec unquote(key)(t) :: unquote(spec)
@spec unquote(key)(unquote(spec), t) :: t
@spec unquote(update)((unquote(spec) -> unquote(spec)), t) :: t
end
accessor_specs(t, i + 1, [contents | acc])
end
defp accessor_specs([], _i, acc), do: acc
## Helpers
defp is_keyword(list) when is_list(list), do: :lists.all(&is_keyword_tuple/1, list)
defp is_keyword(_), do: false
defp is_keyword_tuple({x, _}) when is_atom(x), do: true
defp is_keyword_tuple(_), do: false
defp convert_value(atom) when is_atom(atom), do: {atom, nil}
defp convert_value({atom, other}) when is_atom(atom), do:
{atom, check_value(atom, other)}
defp convert_value({field, _}), do:
raise(ArgumentError, message: "record field name has to be an atom, got #{inspect field}")
defp check_value(atom, other) when is_list(other) do
for(i <- other, do: check_value(atom, i))
other
end
defp check_value(atom, other) when is_tuple(other) do
for(i <- tuple_to_list(other), do: check_value(atom, i))
other
end
defp check_value(atom, other) when is_function(other) do
unless :erlang.fun_info(other, :env) == {:env, []} and
:erlang.fun_info(other, :type) == {:type, :external} do
raise ArgumentError, message: "record field default value #{inspect atom} can only contain " <>
"functions that point to an existing &Mod.fun/arity"
end
other
end
defp check_value(atom, other) when is_reference(other) or is_pid(other) or is_port(other) do
raise(ArgumentError, message: "record field default value #{inspect atom} cannot contain a reference, pid or port")
end
defp check_value(_atom, other), do: other
defp find_index([{k, _}|_], k, i), do: i
defp find_index([{_, _}|t], k, i), do: find_index(t, k, i + 1)
defp find_index([], _k, _i), do: nil
defp find_spec(types, name) do
matches = for {k, v} <- types, name == k, do: v
case matches do
[h|_] -> h
_ -> quote do: term
end
end
end
defmodule Record.DSL do
@moduledoc false
defmacro record_type(opts) when is_list(opts) do
escaped = for {k, v} <- opts, do: {k, Macro.escape(v)}
quote do
@record_types Keyword.merge(@record_types || [], unquote(escaped))
end
end
end
+5 -5
View File
@@ -4,7 +4,7 @@ defmodule Record.Extractor do
# Retrieve a record definition from an Erlang file using
# the same lookup as the *include* attribute from Erlang modules.
def extract(name, from: file) when is_binary(file) do
file = List.from_char_data!(file)
file = String.to_char_list(file)
realfile =
case :code.where_is_file(file) do
@@ -18,11 +18,11 @@ defmodule Record.Extractor do
# Retrieve a record definition from an Erlang file using
# the same lookup as the *include_lib* attribute from Erlang modules.
def extract(name, from_lib: file) when is_binary(file) do
[app|path] = :filename.split(List.from_char_data!(file))
[app|path] = :filename.split(String.to_char_list(file))
case :code.lib_dir(list_to_atom(app)) do
case :code.lib_dir(List.to_atom(app)) do
{:error, _} ->
raise ArgumentError, message: "lib file #{file} could not be found"
raise ArgumentError, "lib file #{file} could not be found"
libpath ->
extract_record name, :filename.join([libpath|path])
end
@@ -35,7 +35,7 @@ defmodule Record.Extractor do
if record = List.keyfind(records, name, 0) do
parse_record(record, form)
else
raise ArgumentError, message: "no record #{name} found at #{file}"
raise ArgumentError, "no record #{name} found at #{file}"
end
end
+61 -59
View File
@@ -22,55 +22,64 @@ defmodule Regex do
The modifiers available when creating a Regex are:
* `unicode` (u) - enables unicode specific patterns like \p. it expects valid unicode
strings to be given on match
* `caseless` (i) - add case insensitivity
* `dotall` (s) - causes dot to match newlines and also set newline to anycrlf.
The new line setting can be overridden by setting `(*CR)` or `(*LF)` or
`(*CRLF)` or `(*ANY)` according to re documentation
* `multiline` (m) - causes `^` and `$` to mark the beginning and end of each line.
Use `\A` and `\z` to match the end or beginning of the string
* `extended` (x) - whitespace characters are ignored except when escaped and
allow `#` to delimit comments
* `firstline` (f) - forces the unanchored pattern to match before or at the first
newline, though the matched text may continue over the newline
* `ungreedy` (r) - inverts the "greediness" of the regexp
* `unicode` (u) - enables unicode specific patterns like `\p`; it expects
valid unicode strings to be given on match
* `caseless` (i) - add case insensitivity
* `dotall` (s) - causes dot to match newlines and also set newline to
anycrlf; the new line setting can be overridden by setting `(*CR)` or
`(*LF)` or `(*CRLF)` or `(*ANY)` according to re documentation
* `multiline` (m) - causes `^` and `$` to mark the beginning and end of
each line; use `\A` and `\z` to match the end or beginning of the string
* `extended` (x) - whitespace characters are ignored except when escaped
and allow `#` to delimit comments
* `firstline` (f) - forces the unanchored pattern to match before or at the
first newline, though the matched text may continue over the newline
* `ungreedy` (r) - inverts the "greediness" of the regexp
The options not available are:
* `anchored` - not available, use `^` or `\A` instead
* `dollar_endonly` - not available, use `\z` instead
* `no_auto_capture` - not available, use `?:` instead
* `newline` - not available, use `(*CR)` or `(*LF)` or `(*CRLF)` or `(*ANYCRLF)`
or `(*ANY)` at the beginning of the regexp according to the re documentation
* `anchored` - not available, use `^` or `\A` instead
* `dollar_endonly` - not available, use `\z` instead
* `no_auto_capture` - not available, use `?:` instead
* `newline` - not available, use `(*CR)` or `(*LF)` or `(*CRLF)` or
`(*ANYCRLF)` or `(*ANY)` at the beginning of the regexp according to the
re documentation
## Captures
Many functions in this module allows what to capture in a regex
match via the `:capture` option. The supported values are:
* `:all` - all captured subpatterns including the complete matching string.
This is the default;
* `:all` - all captured subpatterns including the complete matching string
(this is the default)
* `:first` - only the first captured subpattern, which is always the complete
matching part of the string. All explicitly captured subpatterns are
discarded;
* `:first` - only the first captured subpattern, which is always the
complete matching part of the string; all explicitly captured subpatterns
are discarded
* `:all_but_first`- all but the first matching subpattern, i.e. all explicitly
captured subpatterns, but not the complete matching part of
the string;
* `:all_but_first`- all but the first matching subpattern, i.e. all
explicitly captured subpatterns, but not the complete matching part of
the string
* `:none` - do not return matching subpatterns at all;
* `:none` - do not return matching subpatterns at all
* `:all_names` - captures all names in the Regex;
* `:all_names` - captures all names in the Regex
* `list(binary)` - a list of named captures to capture;
* `list(binary)` - a list of named captures to capture
"""
defstruct re_pattern: nil :: term, source: "" :: binary, opts: "" :: binary
defexception CompileError, message: "regex could not be compiled"
defmodule CompileError do
defexception message: "regex could not be compiled"
end
@doc """
Compiles the regular expression.
@@ -165,9 +174,9 @@ defmodule Regex do
## Options
* `:return` - Set to `:index` to return indexes. Defaults to `:binary`;
* `:capture` - What to capture in the result. Check the moduledoc for Regex
to see the possible capture values;
* `:return` - set to `:index` to return indexes. Defaults to `:binary`.
* `:capture` - what to capture in the result. Check the moduledoc for `Regex`
to see the possible capture values.
## Examples
@@ -273,9 +282,9 @@ defmodule Regex do
## Options
* `:return` - Set to `:index` to return indexes. Defaults to `:binary`;
* `:capture` - What to capture in the result. Check the moduledoc for Regex
to see the possible capture values;
* `:return` - set to `:index` to return indexes. Defaults to `:binary`.
* `:capture` - what to capture in the result. Check the moduledoc for `Regex`
to see the possible capture values.
## Examples
@@ -308,13 +317,12 @@ defmodule Regex do
## Options
* `:parts` - when specified, splits the string into the
given number of parts. If not specified, `:parts`
is defaulted to `:infinity`, which will split the
string into the maximum number of parts possible
based on the given pattern.
* `:parts` - when specified, splits the string into the given number of
parts. If not specified, `:parts` is defaulted to `:infinity`, which will
split the string into the maximum number of parts possible based on the
given pattern.
* `:trim` - when true, remove blank strings from the result;
* `:trim` - when true, remove blank strings from the result.
## Examples
@@ -338,17 +346,8 @@ defmodule Regex do
def split(regex, string, options \\ [])
def split(%Regex{re_pattern: compiled}, string, options) when is_binary(string) do
if options[:global] != nil do
IO.write :stderr, "Support for :global in Regex.split/3 is deprecated, please use :parts instead\n#{Exception.format_stacktrace}"
end
parts =
cond do
Keyword.get(options, :global) == false -> 2
p = Keyword.get(options, :parts) -> if p == 0, do: :infinity, else: p
true -> :infinity
end
opts = [return: :binary, parts: parts]
parts = Keyword.get(options, :parts, :infinity)
opts = [return: :binary, parts: zero_to_infinity(parts)]
splits = :re.split(string, compiled, opts)
if Keyword.get(options, :trim, false) do
@@ -358,6 +357,9 @@ defmodule Regex do
end
end
defp zero_to_infinity(0), do: :infinity
defp zero_to_infinity(n), do: n
@doc ~S"""
Receives a regex, a binary and a replacement, returns a new
binary where the all matches are replaced by replacement.
@@ -374,8 +376,8 @@ defmodule Regex do
## Options
* `:global` - when `false`, replaces only the first occurrence
(defaults to true)
* `:global` - when `false`, replaces only the first occurrence
(defaults to true)
## Examples
@@ -414,9 +416,9 @@ defmodule Regex do
:nomatch ->
string
{:match, [mlist|t]} when is_list(mlist) ->
apply_list(string, replacement, [mlist|t]) |> iodata_to_binary
apply_list(string, replacement, [mlist|t]) |> IO.iodata_to_binary
{:match, slist} ->
apply_list(string, replacement, [slist]) |> iodata_to_binary
apply_list(string, replacement, [slist]) |> IO.iodata_to_binary
end
end
@@ -434,7 +436,7 @@ defmodule Regex do
defp precompile_replacement(<<?\\, rest :: binary>>) when byte_size(rest) > 0 do
{ns, rest} = pick_int(rest)
[list_to_integer(ns) | precompile_replacement(rest)]
[List.to_integer(ns) | precompile_replacement(rest)]
end
defp precompile_replacement(<<x, rest :: binary>>) do
@@ -488,7 +490,7 @@ defmodule Regex do
end
defp apply_replace(string, repl, indexes) do
indexes = list_to_tuple(indexes)
indexes = List.to_tuple(indexes)
for part <- repl do
cond do
+1 -4
View File
@@ -50,9 +50,6 @@ defmodule Set do
case unquote(set) do
%{__struct__: x} when is_atom(x) ->
x
x when is_tuple(x) ->
IO.write :stderr, "Using records with the Set module is deprecated, please use structs instead\n#{Exception.format_stacktrace}"
elem(x, 0)
x ->
unsupported_set(x)
end
@@ -334,6 +331,6 @@ defmodule Set do
end
defp unsupported_set(set) do
raise ArgumentError, message: "unsupported set: #{inspect set}"
raise ArgumentError, "unsupported set: #{inspect set}"
end
end
+85 -72
View File
@@ -76,8 +76,8 @@ defmodule Stream do
There are many functions in Elixir's standard library that return
streams, some examples are:
* `IO.stream/2` - Streams input lines, one by one;
* `URI.query_decoder/1` - Decodes a query string, pair by pair;
* `IO.stream/2` - streams input lines, one by one
* `URI.query_decoder/1` - decodes a query string, pair by pair
This module also provides many convenience functions for creating streams,
like `Stream.cycle/1`, `Stream.unfold/2`, `Stream.resource/3` and more.
@@ -89,55 +89,7 @@ defmodule Stream do
returns an anonymous function may return a struct in future releases.
"""
defmodule Lazy do
@moduledoc false
defstruct enum: nil, funs: [], accs: [], done: nil
end
defimpl Enumerable, for: Lazy do
@compile :inline_list_funs
def reduce(lazy, acc, fun) do
do_reduce(lazy, acc, fn x, [acc] ->
{reason, acc} = fun.(x, acc)
{reason, [acc]}
end)
end
def count(_lazy) do
{:error, __MODULE__}
end
def member?(_lazy, _value) do
{:error, __MODULE__}
end
defp do_reduce(%Lazy{enum: enum, funs: funs, accs: accs, done: done}, acc, fun) do
composed = :lists.foldl(fn fun, acc -> fun.(acc) end, fun, funs)
do_each(&Enumerable.reduce(enum, &1, composed),
done && {done, fun}, :lists.reverse(accs), acc)
end
defp do_each(reduce, done, accs, {command, acc}) 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
end
end
end
defstruct enum: nil, funs: [], accs: [], done: nil
@type acc :: any
@type element :: any
@@ -468,6 +420,7 @@ defmodule Stream do
stream = File.stream!("code")
|> Stream.map(&String.replace(&1, "#", "%"))
|> Stream.into(File.stream!("new"))
|> Stream.run
No computation will be done until we call one of the Enum functions
or `Stream.run/1`.
@@ -538,7 +491,7 @@ defmodule Stream do
"""
@spec take(Enumerable.t, non_neg_integer) :: Enumerable.t
def take(_enum, 0), do: %Lazy{enum: []}
def take(_enum, 0), do: %Stream{enum: []}
def take(enum, n) when n > 0 do
lazy enum, n, fn(f1) -> R.take(f1) end
@@ -590,7 +543,7 @@ defmodule Stream do
lazy enum, n, fn(f1) -> R.take_every(n, f1) end
end
def take_every(_enum, 0), do: %Lazy{enum: []}
def take_every(_enum, 0), do: %Stream{enum: []}
@doc """
Lazily takes elements of the enumerable while the given
@@ -656,8 +609,9 @@ defmodule Stream do
user.(val, user_acc)
catch
kind, reason ->
stacktrace = System.stacktrace
next.({:halt, next_acc})
:erlang.raise(kind, reason, :erlang.get_stacktrace)
:erlang.raise(kind, reason, stacktrace)
else
{[], user_acc} ->
do_transform(user_acc, user, fun, next_acc, next, inner_acc, inner)
@@ -700,8 +654,9 @@ defmodule Stream do
next.({:halt, next_acc})
{:halted, h}
kind, reason ->
stacktrace = System.stacktrace
next.({:halt, next_acc})
:erlang.raise(kind, reason, :erlang.get_stacktrace)
:erlang.raise(kind, reason, stacktrace)
else
{_, acc} ->
do_transform(user_acc, user, fun, next_acc, next, {:cont, acc}, inner)
@@ -835,8 +790,9 @@ defmodule Stream do
do_zip(zips, acc, callback, [], [])
catch
kind, reason ->
stacktrace = System.stacktrace
do_zip_close(zips)
:erlang.raise(kind, reason, :erlang.get_stacktrace)
:erlang.raise(kind, reason, stacktrace)
else
{:next, buffer, acc} ->
do_zip(buffer, acc, callback)
@@ -856,7 +812,7 @@ defmodule Stream do
end
defp do_zip([], acc, callback, list, buffer) do
zipped = list_to_tuple(:lists.reverse(list))
zipped = List.to_tuple(:lists.reverse(list))
{:next, :lists.reverse(buffer), callback.(zipped, acc)}
end
@@ -886,8 +842,9 @@ defmodule Stream do
@spec cycle(Enumerable.t) :: Enumerable.t
def cycle(enumerable) do
fn acc, fun ->
reduce = &Enumerable.reduce(enumerable, &1, fun)
do_cycle(reduce, reduce, acc)
inner = &do_cycle_each(&1, &2, fun)
outer = &Enumerable.reduce(enumerable, &1, inner)
do_cycle(outer, outer, acc)
end
end
@@ -900,16 +857,26 @@ defmodule Stream do
end
defp do_cycle(reduce, cycle, acc) do
case reduce.(acc) do
{:done, acc} ->
do_cycle(cycle, cycle, {:cont, acc})
{:halted, acc} ->
try do
reduce.(acc)
catch
{:stream_cycle, acc} ->
{:halted, acc}
else
{state, acc} when state in [:done, :halted] ->
do_cycle(cycle, cycle, {:cont, acc})
{:suspended, acc, continuation} ->
{:suspended, acc, &do_cycle(continuation, cycle, &1)}
end
end
defp do_cycle_each(x, acc, f) do
case f.(x, acc) do
{:halt, h} -> throw({:stream_cycle, h})
{_, _} = o -> o
end
end
@doc """
Emit a sequence of values, starting with `start_value`. Successive
values are generated by calling `next_fun` on the previous value.
@@ -1006,8 +973,9 @@ defmodule Stream do
end
catch
kind, reason ->
stacktrace = System.stacktrace
after_fun.(next_acc)
:erlang.raise(kind, reason, :erlang.get_stacktrace)
:erlang.raise(kind, reason, stacktrace)
else
nil ->
after_fun.(next_acc)
@@ -1021,7 +989,7 @@ defmodule Stream do
Emits a sequence of values for the given accumulator.
Successive values are generated by calling `next_fun` with the previous
accumulator and it must return a tuple with the current and next
accumulator and it must return a tuple with the current value and next
accumulator. The enumeration finishes if it returns `nil`.
## Examples
@@ -1045,7 +1013,7 @@ defmodule Stream do
defp do_unfold(next_acc, next_fun, {:cont, acc}, fun) do
case next_fun.(next_acc) do
nil -> {:done, acc}
nil -> {:done, acc}
{v, next_acc} -> do_unfold(next_acc, next_fun, fun.(v, acc), fun)
end
end
@@ -1054,18 +1022,63 @@ defmodule Stream do
@compile {:inline, lazy: 2, lazy: 3, lazy: 4}
defp lazy(%Lazy{funs: funs} = lazy, fun),
defp lazy(%Stream{funs: funs} = lazy, fun),
do: %{lazy | funs: [fun|funs] }
defp lazy(enum, fun),
do: %Lazy{enum: enum, funs: [fun]}
do: %Stream{enum: enum, funs: [fun]}
defp lazy(%Lazy{funs: funs, accs: accs} = lazy, acc, fun),
defp lazy(%Stream{funs: funs, accs: accs} = lazy, acc, fun),
do: %{lazy | funs: [fun|funs], accs: [acc|accs] }
defp lazy(enum, acc, fun),
do: %Lazy{enum: enum, funs: [fun], accs: [acc]}
do: %Stream{enum: enum, funs: [fun], accs: [acc]}
defp lazy(%Lazy{done: nil, funs: funs, accs: accs} = lazy, acc, fun, done),
defp lazy(%Stream{done: nil, funs: funs, accs: accs} = lazy, acc, fun, done),
do: %{lazy | funs: [fun|funs], accs: [acc|accs], done: done}
defp lazy(enum, acc, fun, done),
do: %Lazy{enum: enum, funs: [fun], accs: [acc], done: done}
do: %Stream{enum: enum, funs: [fun], accs: [acc], done: done}
end
defimpl Enumerable, for: Stream do
@compile :inline_list_funs
def reduce(lazy, acc, fun) do
do_reduce(lazy, acc, fn x, [acc] ->
{reason, acc} = fun.(x, acc)
{reason, [acc]}
end)
end
def count(_lazy) do
{:error, __MODULE__}
end
def member?(_lazy, _value) do
{:error, __MODULE__}
end
defp do_reduce(%Stream{enum: enum, funs: funs, accs: accs, done: done}, acc, fun) do
composed = :lists.foldl(fn fun, acc -> fun.(acc) end, fun, funs)
do_each(&Enumerable.reduce(enum, &1, composed),
done && {done, fun}, :lists.reverse(accs), acc)
end
defp do_each(reduce, done, accs, {command, acc}) 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
end
end
end
+200 -143
View File
@@ -15,15 +15,11 @@ defmodule String do
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 conversion functions, like `Kernel.binary_to_atom/1`,
`Kernel.binary_to_integer/2`, `Kernel.binary_to_term/1` and their inverses,
like `Kernel.integer_to_binary/2`
Finally, the [`:binary` module](http://erlang.org/doc/man/binary.html)
provides a few other functions that work on the byte level.
* `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
@@ -147,6 +143,7 @@ defmodule String do
def printable?(<<?\b, t :: binary>>), do: printable?(t)
def printable?(<<?\f, t :: binary>>), do: printable?(t)
def printable?(<<?\e, t :: binary>>), do: printable?(t)
def printable?(<<?\d, t :: binary>>), do: printable?(t)
def printable?(<<?\a, t :: binary>>), do: printable?(t)
def printable?(<<>>), do: true
@@ -172,8 +169,9 @@ defmodule String do
defdelegate split(binary), to: String.Unicode
@doc ~S"""
Divides a string into substrings based on a pattern,
returning a list of these substrings. The pattern can
Divides a string into substrings based on a pattern.
Returns a list of these substrings. The pattern can
be a string, a list of strings or a regular expression.
The string is split into as many parts as possible by
@@ -236,22 +234,16 @@ defmodule String do
def split(binary, "", options), do: split(binary, ~r""u, options)
def split(binary, pattern, options) do
if options[:global] != nil do
IO.write :stderr, "Support for :global in String.split/3 is deprecated, please use :parts instead\n#{Exception.format_stacktrace}"
end
if Regex.regex?(pattern) do
Regex.split(pattern, binary, options)
else
splits = case options[:parts] do
num when is_number(num) and num > 0 ->
split_parts(binary, pattern, num - 1)
num ->
if options[:global] != false or num == 0 do
splits =
case Keyword.get(options, :parts, :infinity) do
num when is_number(num) and num > 0 ->
split_parts(binary, pattern, num - 1)
_ ->
:binary.split(binary, pattern, [:global])
else
:binary.split(binary, pattern, [])
end
end
end
if Keyword.get(options, :trim, false) do
for split <- splits, split != "", do: split
@@ -266,10 +258,9 @@ defmodule String do
defp split_parts("", _pattern, _num, parts), do: Enum.reverse([""|parts])
defp split_parts(binary, _pattern, 0, parts), do: Enum.reverse([binary|parts])
defp split_parts(binary, pattern, num, parts) do
[head|tail] = :binary.split(binary, pattern)
case tail do
[] -> Enum.reverse([head|parts])
[str] -> split_parts(str, pattern, num - 1, [head|parts])
case :binary.split(binary, pattern) do
[head] -> Enum.reverse([head|parts])
[head, rest] -> split_parts(rest, pattern, num - 1, [head|parts])
end
end
@@ -654,7 +645,7 @@ defmodule String do
do_reverse(next_grapheme(rest), [grapheme|acc])
end
defp do_reverse(nil, acc), do: iodata_to_binary(acc)
defp do_reverse(nil, acc), do: IO.iodata_to_binary(acc)
@doc """
Returns a binary `subject` duplicated `n` times.
@@ -776,6 +767,65 @@ defmodule String do
def valid_character?(<<_ :: utf8>> = codepoint), do: valid?(codepoint)
def valid_character?(_), do: false
@doc ~S"""
Splits the string into chunks of characters that share a common trait.
The trait can be one of two options:
* `:valid` – the string is split into chunks of valid and invalid character
sequences
* `:printable` – the string is split into chunks of printable and
non-printable character sequences
Returns a list of binaries each of which contains only one kind of
characters.
If the given string is empty, an empty list is returned.
## Examples
iex> String.chunk(<<?a, ?b, ?c, 0>>, :valid)
["abc\000"]
iex> String.chunk(<<?a, ?b, ?c, 0, 0x0ffff::utf8>>, :valid)
["abc\000", <<0x0ffff::utf8>>]
iex> String.chunk(<<?a, ?b, ?c, 0, 0x0ffff::utf8>>, :printable)
["abc", <<0, 0x0ffff::utf8>>]
"""
@spec chunk(t, :valid | :printable) :: [t]
def chunk(string, trait)
def chunk("", _), do: []
def chunk(str, trait) when trait in [:valid, :printable] do
{cp, _} = next_codepoint(str)
pred_fn = make_chunk_pred(trait)
do_chunk(str, pred_fn.(cp), pred_fn)
end
defp do_chunk(str, flag, pred_fn), do: do_chunk(str, [], <<>>, flag, pred_fn)
defp do_chunk(<<>>, acc, <<>>, _, _), do: Enum.reverse(acc)
defp do_chunk(<<>>, acc, chunk, _, _), do: Enum.reverse(acc, [chunk])
defp do_chunk(str, acc, chunk, flag, pred_fn) do
{cp, rest} = next_codepoint(str)
if pred_fn.(cp) != flag do
do_chunk(rest, [chunk|acc], cp, not flag, pred_fn)
else
do_chunk(rest, acc, chunk <> cp, flag, pred_fn)
end
end
defp make_chunk_pred(:valid), do: &valid?/1
defp make_chunk_pred(:printable), do: &printable?/1
@doc """
Returns unicode graphemes in the string as per Extended Grapheme
Cluster algorithm outlined in the [Unicode Standard Annex #29,
@@ -877,7 +927,7 @@ defmodule String do
@doc """
Returns the grapheme in the `position` of the given utf8 `string`.
If `position` is greater than `string` length, than it returns `nil`.
If `position` is greater than `string` length, then it returns `nil`.
## Examples
@@ -924,7 +974,7 @@ defmodule String do
@doc """
Returns a substring starting at the offset given by the first, and
a length given by the second.
If the offset is greater than string length, than it returns `nil`.
If the offset is greater than string length, then it returns `""`.
## Examples
@@ -935,13 +985,13 @@ defmodule String do
"lixir"
iex> String.slice("elixir", 10, 3)
nil
""
iex> String.slice("elixir", -4, 4)
"ixir"
iex> String.slice("elixir", -10, 3)
nil
""
iex> String.slice("a", 0, 1500)
"a"
@@ -950,16 +1000,13 @@ defmodule String do
""
iex> String.slice("a", 2, 1500)
nil
""
"""
@spec slice(t, integer, integer) :: grapheme | nil
@spec slice(t, integer, integer) :: grapheme
def slice(string, start, 0) do
case abs(start) <= length(string) do
true -> ""
false -> nil
end
def slice(_, _, 0) do
""
end
def slice(string, start, len) when start >= 0 and len >= 0 do
@@ -970,7 +1017,7 @@ defmodule String do
real_start_pos = length(string) - abs(start)
case real_start_pos >= 0 do
true -> do_slice(next_grapheme(string), real_start_pos, real_start_pos + len - 1, 0, "")
false -> nil
false -> ""
end
end
@@ -979,7 +1026,7 @@ defmodule String do
range to the offset given by the end of the range.
If the start of the range is not a valid offset for the given
string or if the range is in reverse order, returns `nil`.
string or if the range is in reverse order, returns `""`.
## Examples
@@ -990,7 +1037,7 @@ defmodule String do
"lixir"
iex> String.slice("elixir", 10..3)
nil
""
iex> String.slice("elixir", -4..-1)
"ixir"
@@ -1002,10 +1049,10 @@ defmodule String do
"ixir"
iex> String.slice("elixir", -1..-4)
nil
""
iex> String.slice("elixir", -10..-7)
nil
""
iex> String.slice("a", 0..1500)
"a"
@@ -1014,10 +1061,10 @@ defmodule String do
""
iex> String.slice("a", 2..1500)
nil
""
"""
@spec slice(t, Range.t) :: t | nil
@spec slice(t, Range.t) :: t
def slice(string, range)
@@ -1038,11 +1085,13 @@ defmodule String do
if first >= 0 do
do_slice(next_grapheme(string), first, last, 0, "")
else
""
end
end
defp do_slice(_, start_pos, last_pos, _, _) when start_pos > last_pos do
nil
""
end
defp do_slice({_, rest}, start_pos, last_pos, current_pos, acc) when current_pos < start_pos do
@@ -1056,16 +1105,9 @@ defmodule String do
defp do_slice({char, _}, start_pos, last_pos, current_pos, acc) when current_pos >= start_pos and current_pos == last_pos do
acc <> char
end
defp do_slice(nil, start_pos, _, current_pos, acc) when start_pos == current_pos do
acc
end
defp do_slice(nil, _, _, _, acc) do
case acc do
"" -> nil
_ -> acc
end
acc
end
@doc """
@@ -1102,10 +1144,6 @@ defmodule String do
Kernel.match?({0, _}, :binary.match(string, prefix))
end
defp do_starts_with(_, _) do
raise ArgumentError
end
@doc """
Returns `true` if `string` ends with any of the suffixes given, otherwise
`false`. `suffixes` can be either a single suffix or a list of suffixes.
@@ -1137,16 +1175,12 @@ defmodule String do
end
defp do_ends_with(string, suffix) when is_binary(suffix) do
string_size = size(string)
suffix_size = size(suffix)
string_size = byte_size(string)
suffix_size = byte_size(suffix)
scope = {string_size - suffix_size, suffix_size}
(suffix_size <= string_size) and (:nomatch != :binary.match(string, suffix, [scope: scope]))
end
defp do_ends_with(_, _) do
raise ArgumentError
end
@doc """
Check if `string` matches the given regular expression.
@@ -1199,89 +1233,22 @@ defmodule String do
:nomatch != :binary.match(string, match)
end
defp do_contains(_, _) do
raise ArgumentError
end
@doc false
def to_char_list(list) do
IO.write :stderr, "String.to_char_list/1 is deprecated, please use List.from_char_data/1 instead\n#{Exception.format_stacktrace}"
List.from_char_data(list)
end
@doc false
def to_char_list!(list) do
IO.write :stderr, "String.to_char_list!/1 is deprecated, please use List.from_char_data!/1 instead\n#{Exception.format_stacktrace}"
List.from_char_data!(list)
end
@doc false
def from_char_list(list) do
IO.write :stderr, "String.from_char_list/1 is deprecated, please use String.from_char_data/1 instead\n#{Exception.format_stacktrace}"
from_char_data(list)
end
@doc false
def from_char_list!(list) do
IO.write :stderr, "String.from_char_list!/1 is deprecated, please use String.from_char_data!/1 instead\n#{Exception.format_stacktrace}"
from_char_data!(list)
end
@doc """
Converts char data (a list of integers and strings) into a string.
If a string is given, returns the string itself.
Converts a string into a char list.
## Examples
iex> String.from_char_data([0x00E6, 0x00DF])
{:ok, "æß"}
iex> String.from_char_data([0x0061, "bc"])
{:ok, "abc"}
iex> String.to_char_list("æß")
'æß'
Notice that this function expect a list of integer representing
UTF-8 codepoints. If you have a raw binary, you must instead use
[the `:binary` module](http://erlang.org/doc/man/binary.html).
"""
@spec from_char_data(char_data) :: {:ok, String.t} | {:error, binary, binary} | {:incomplete, binary, binary}
def from_char_data(binary) when is_binary(binary) do
binary
end
def from_char_data(list) when is_list(list) do
case :unicode.characters_to_binary(list) do
result when is_binary(result) ->
{:ok, result}
{:error, _, _} = error ->
error
{:incomplete, _, _} = incomplete ->
incomplete
end
end
@doc """
Converts char data (a list of integers and strings) into a string.
In case the conversion fails, it raises a `UnicodeConversionError`.
If a string is given, returns the string itself.
## Examples
iex> String.from_char_data!([0x00E6, 0x00DF])
"æß"
iex> String.from_char_data!([0x0061, "bc"])
"abc"
"""
@spec from_char_data!(char_data) :: String.t | no_return
def from_char_data!(binary) when is_binary(binary) do
binary
end
def from_char_data!(list) when is_list(list) do
case :unicode.characters_to_binary(list) do
result when is_binary(result) ->
@spec to_char_list(t) :: char_list
def to_char_list(string) when is_binary(string) do
case :unicode.characters_to_list(string) do
result when is_list(result) ->
result
{:error, encoded, rest} ->
@@ -1291,4 +1258,94 @@ defmodule String do
raise UnicodeConversionError, encoded: encoded, rest: rest, kind: :incomplete
end
end
@doc """
Converts a string to an atom.
Currently Elixir does not support conversions from strings
which contains Unicode codepoints greater than 0xFF.
Inlined by the compiler.
## Examples
iex> String.to_atom("my_atom")
:my_atom
"""
@spec to_atom(String.t) :: atom
def to_atom(string) do
:erlang.binary_to_atom(string, :utf8)
end
@doc """
Converts a string to an existing atom.
Currently Elixir does not support conversions from strings
which contains Unicode codepoints greater than 0xFF.
Inlined by the compiler.
## Examples
iex> :my_atom
iex> String.to_existing_atom("my_atom")
:my_atom
iex> String.to_existing_atom("this_atom_will_never_exist")
** (ArgumentError) argument error
"""
@spec to_existing_atom(String.t) :: atom
def to_existing_atom(string) do
:erlang.binary_to_existing_atom(string, :utf8)
end
@doc """
Returns a integer whose text representation is `string`.
Inlined by the compiler.
## Examples
iex> String.to_integer("123")
123
"""
@spec to_integer(String.t) :: integer
def to_integer(string) do
:erlang.binary_to_integer(string)
end
@doc """
Returns an integer whose text representation is `string` in base `base`.
Inlined by the compiler.
## Examples
iex> String.to_integer("3FF", 16)
1023
"""
@spec to_integer(String.t, pos_integer) :: integer
def to_integer(string, base) do
:erlang.binary_to_integer(string, base)
end
@doc """
Returns a float whose text representation is `string`.
Inlined by the compiler.
## Examples
iex> String.to_float("2.2017764e+0")
2.2017764
"""
@spec to_float(String.t) :: float
def to_float(string) do
:erlang.binary_to_float(string)
end
end
+4 -32
View File
@@ -18,23 +18,16 @@ defprotocol String.Chars do
end
defimpl String.Chars, for: Atom do
@doc """
Convert the atom literally to a binary, except
`nil` which is converted to an empty string.
"""
def to_string(nil) do
""
end
def to_string(atom) do
atom_to_binary(atom)
Atom.to_string(atom)
end
end
defimpl String.Chars, for: BitString do
@doc """
Returns the given binary or raises an error for bitstrings.
"""
def to_string(thing) when is_binary(thing) do
thing
end
@@ -48,38 +41,17 @@ defimpl String.Chars, for: BitString do
end
defimpl String.Chars, for: List do
@doc """
Consider the list is an iolist and converts it
to a binary. This allows a list of binaries, or
a charlist, or a mix of both, to be converted
successfully.
## Examples
iex> to_string('foo')
"foo"
iex> to_string(["foo", 'bar'])
"foobar"
"""
def to_string(char_list), do: String.from_char_data!(char_list)
def to_string(char_list), do: List.to_string(char_list)
end
defimpl String.Chars, for: Integer do
@doc """
Simply converts the integer to a string.
"""
def to_string(thing) do
integer_to_binary(thing)
Integer.to_string(thing)
end
end
defimpl String.Chars, for: Float do
@doc """
Simply converts the float to a string.
"""
def to_string(thing) do
iodata_to_binary(:io_lib_format.fwrite_g(thing))
IO.iodata_to_binary(:io_lib_format.fwrite_g(thing))
end
end
+9 -9
View File
@@ -10,7 +10,7 @@ defmodule StringIO do
"""
use GenServer.Behaviour
use GenServer
@doc """
Creates an IO device.
@@ -106,12 +106,12 @@ defmodule StringIO do
end
defp io_request({:put_chars, chars}, %{output: output} = s) do
{:ok, %{s | output: << output :: binary, String.from_char_data!(chars) :: binary >>}}
{:ok, %{s | output: << output :: binary, IO.chardata_to_string(chars) :: binary >>}}
end
defp io_request({:put_chars, m, f, as}, %{output: output} = s) do
chars = apply(m, f, as)
{:ok, %{s | output: << output :: binary, String.from_char_data!(chars) :: binary >>}}
{:ok, %{s | output: << output :: binary, IO.chardata_to_string(chars) :: binary >>}}
end
defp io_request({:put_chars, _encoding, chars}, s) do
@@ -183,7 +183,7 @@ defmodule StringIO do
{error, s}
{result, input} ->
if capture_prompt do
output = << output :: binary, String.from_char_data!(prompt) :: binary >>
output = << output :: binary, IO.chardata_to_string(prompt) :: binary >>
end
{result, %{s | input: input, output: output}}
@@ -199,7 +199,7 @@ defmodule StringIO do
end
defp do_get_chars(input, :latin1, n) do
<< chars :: [ binary, size(n) ], rest :: binary >> = input
<<chars :: [binary, size(n)], rest :: binary>> = input
{chars, rest}
end
@@ -209,7 +209,7 @@ defmodule StringIO do
{buf_count, split_pos} when buf_count < n or split_pos == :none ->
{input, ""}
{_buf_count, split_pos} ->
<< chars :: [ binary, size(split_pos) ], rest :: binary >> = input
<<chars :: [binary, size(split_pos)], rest :: binary>> = input
{chars, rest}
end
catch
@@ -231,7 +231,7 @@ defmodule StringIO do
{result, input} = do_get_line(chars, encoding)
if capture_prompt do
output = << output :: binary, String.from_char_data!(prompt) :: binary >>
output = << output :: binary, IO.chardata_to_string(prompt) :: binary >>
end
{result, %{s | input: input, output: output}}
@@ -261,7 +261,7 @@ defmodule StringIO do
{result, input, count} = do_get_until(chars, encoding, mod, fun, args)
if capture_prompt do
output = << output :: binary, :binary.copy(String.from_char_data!(prompt), count) :: binary >>
output = << output :: binary, :binary.copy(IO.chardata_to_string(prompt), count) :: binary >>
end
input =
@@ -335,6 +335,6 @@ defmodule StringIO do
send from, {:io_reply, reply_as, reply}
end
defp to_reply(list) when is_list(list), do: String.from_char_data!(list)
defp to_reply(list) when is_list(list), do: IO.chardata_to_string(list)
defp to_reply(other), do: other
end
+389
View File
@@ -0,0 +1,389 @@
defmodule Supervisor do
@moduledoc """
A behaviour module for implementing supervision functionality.
A supervisor is a process which supervises other processes called
child processes. Supervisors are used to build an hierarchical process
structure called a supervision tree, a nice way to structure fault-tolerant
applications.
A supervisor implemented using this module will have a standard set
of interface functions and include functionality for tracing and error
reporting. It will also fit into an supervision tree.
## Example
In order to define a supervisor, we need to first define a child process
that is going to be supervised. In order to do so, we will define a GenServer
that represents a stack:
defmodule Stack do
use GenServer
def start_link(state) do
GenServer.start_link(__MODULE__, state, [name: :sup_stack])
end
def handle_call(:pop, _from, [h|t]) do
{:reply, h, t}
end
def handle_cast({:push, h}, _from, t) do
{:noreply, [h|t]}
end
end
We can now define our supervisor and start it as follows:
# 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]
children = [
worker(Stack, [[:hello]])
]
# 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
with name `:sup_stack`, which allows us to call it directly and
get what is on the stack:
GenServer.call(:sup_stack, :pop)
#=> :hello
GenServer.cast(:sup_stack, {:push, :world})
#=> :ok
GenServer.call(:sup_stack, :pop)
#=> :world
However, there is a bug in our stack server. If we call `:pop` and
the stack is empty, it is going to crash because no clause matches.
Let's try it:
GenServer.call(:sup_stack, :pop)
=ERROR REPORT====
Luckily, since the server is being supervised by a supervisor, the
supervisor will automatically start a new one, with the default stack
of `[:hello]` like before:
GenServer.call(:sup_stack, :pop) == :hello
Supervisors support different strategies; in the example above, we
have chosen `:one_for_one`. Furthermore, each supervisor can have many
workers and supervisors as children, each of them with their specific
configuration, shutdown values, and restart strategies.
Continue reading this moduledoc to learn more about supervision strategies
and then follow to the `Supervisor.Spec` module documentation to learn
about the specification for workers and supervisors.
## Module-based supervisors
In the example above, a supervisor was dynamically created by passing
the supervision structure to `start_link/2`. However, supervisors
can also be created by explicitly defining a supervision module:
defmodule MyApp.Supervisor do
use Supervisor
def start_link do
Supervisor.start_link(__MODULE__, [])
end
def init([]) do
children = [
worker(Stack, [[:hello]])
]
supervise(children, strategy: :one_for_one)
end
end
You may want to use a module-based supervisor if:
* You need to do some particular action on supervisor
initialization, like setting up a ETS table.
* You want to perform partial hot-code swapping of the
tree. For example, if you add or remove a children,
the module-based supervision will add and remove the
new children directly, while the dynamic supervision
requires the whole tree to be restarted in order to
perform such swaps.
## Strategies
* `:one_for_one` - if a child process terminates, only that
process is restarted.
* `:one_for_all` - if a child process terminates, all other child
processes are terminated and then all child processes (including
the terminated one) are restarted.
* `:rest_for_one` - if a child process terminates, the "rest" of
the child processes, i.e. the child processes after the terminated
one in start order, are terminated. Then the terminated child
process and the rest of the child processes are restarted.
* `: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
in this module behave slightly differently when this strategy is
used.
## Name Registration
A supervisor is bound to the same name registration rules as a `GenServer`.
Read more about it in the `GenServer` docs.
"""
@doc false
defmacro __using__(_) do
quote location: :keep do
@behaviour :supervisor
import Supervisor.Spec
end
end
@typedoc "Return values of `start_link` functions"
@type on_start :: {:ok, pid} | :ignore |
{:error, {:already_started, pid} | {:shutdown, term} | term}
@typedoc "Return values of `start_child` functions"
@type on_start_child :: {:ok, child} | {:ok, child, info :: term} |
{:error, {:already_started, child} | :already_present | term}
@type child :: pid | :undefined
@typedoc "The Supervisor name"
@type name :: atom | {:global, term} | {:via, module, term}
@typedoc "Options used by the `start*` functions"
@type options :: [name: name,
strategy: Supervisor.Spec.strategy,
max_restarts: non_neg_integer,
max_seconds: non_neg_integer]
@typedoc "The supervisor reference"
@type supervisor :: pid | name | {atom, node}
@doc """
Starts a supervisor with the given children.
A strategy is required to be given as an option. Furthermore,
the `:max_restarts` and `:max_seconds` value can be configured
as described in `Supervisor.Spec.supervise/2` docs.
The options can also be used to register a supervisor name.
the supported values are described under the `Name Registration`
section in the `GenServer` module docs.
If the supervisor and its child processes are successfully created
(i.e. if the start function of all child processes returns `{:ok, child}`,
`{:ok, child, info}`, or `:ignore`) the function returns
`{:ok, pid}`, where `pid` is the pid of the supervisor. If there
already exists a process with the specified name, the function returns
`{:error, {:already_started, pid}}`, where pid is the pid of that
process.
If any of the child process start functions fail or return an error tuple or
an erroneous value, the supervisor will first terminate all already
started child processes with reason `:shutdown` and then terminate
itself and return `{:error, {:shutdown, reason}}`.
Note that the `Supervisor` is linked to the parent process
and will exit not only on crashes but also if the parent process
exits with `:normal` reason.
"""
@spec start_link([tuple], options) :: on_start
def start_link(children, options) when is_list(children) do
spec = Supervisor.Spec.supervise(children, options)
start_link(Supervisor.Default, spec, options)
end
@doc """
Starts a supervisor module with the given `arg`.
To start the supervisor, the `init/1` callback will be invoked
in the given module. The `init/1` callback must return a
supervision specification which can be created with the help
of `Supervisor.Spec` module.
If the `init/1` callback returns `:ignore`, this function returns
`:ignore` as well and the supervisor terminates with reason `:normal`.
If it fails or returns an incorrect value, this function returns
`{:error, term}` where `term` is a term with information about the
error, and the supervisor terminates with reason `term`.
The `:name` option can also be given in order to register a supervisor
name, the supported values are described under the `Name Registration`
section in the `GenServer` module docs.
Other failure conditions are specified in `start_link/2` docs.
"""
@spec start_link(module, term, options) :: on_start
def start_link(module, arg, options \\ []) when is_list(options) do
case Keyword.get(options, :name) do
nil ->
:supervisor.start_link(module, arg)
atom when is_atom(atom) ->
:supervisor.start_link({:local, atom}, module, arg)
other when is_tuple(other) ->
:supervisor.start_link(other, module, arg)
end
end
@doc """
Dynamically adds and starts a child specification to the supervisor.
`child_spec` should be a valid child specification (unless the supervisor
is a `:simple_one_for_one` supervisor, see below). The child process will
be started as defined in the child specification.
In the case of `:simple_one_for_one`, the child specification defined in
the supervisor will be used and instead of a `child_spec`, an arbitrary list
of terms is expected. The child process will then be started by appending
the given list to the existing function arguments in the child specification.
If there already exists a child specification with the specified id,
`child_spec` is discarded and the function returns an error with `:already_started`
or `:already_present` if the corresponding child process is running or not.
If the child process start function returns `{:ok, child}` or `{:ok, child, info}`,
the child specification and pid is added to the supervisor and the function returns
the same value.
If the child process start function returns `:ignore, the child specification is
added to the supervisor, the pid is set to undefined and the function returns
`{:ok, :undefined}`.
If the child process start function returns an error tuple or an erroneous value,
or if it fails, the child specification is discarded and the function returns
`{:error, error}` where `error` is a term containing information about the error
and child specification.
"""
@spec start_child(supervisor, Supervisor.Spec.spec | [term]) :: on_start_child
defdelegate start_child(supervisor, child_spec_or_args), to: :supervisor
@doc """
Terminates the given pid or child id.
If the supervisor is not a `simple_one_for_one`, the child id is expected
and the process, if there is one, is terminated; the child specification is
kept unless the child is temporary.
In case of a `simple_one_for_one` supervisor, a pid is expected. If the child
specification identifier is given instead of a `pid`, the function will
return `{:error, :simple_one_for_one}`.
A non-temporary child process may later be restarted by the supervisor. The child
process can also be restarted explicitly by calling `restart_child/2`. Use
`delete_child/2` to remove the child specification.
If successful, the function returns `:ok`. If there is no child specification or
pid, the function returns `{:error, :not_found}`.
"""
@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
@doc """
Deletes the child specification identified by `child_id`.
The corresponding child process must not be running, use `terminate_child/2`
to terminate it.
If successful, the function returns `:ok`. This function may error with an
appropriate error tuple if the `child_id` is not found, or if the current
process is running or being restarted.
This operation is not supported by `simple_one_for_one` supervisors.
"""
@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
@doc """
Restarts a child process identified by `child_id`.
The child specification must exist and the corresponding child process must not
be running.
Note that for temporary children, the child specification is automatically deleted
when the child terminates, and thus it is not possible to restart such children.
If the child process start function returns `{:ok, child}` or
`{:ok, child, info}`, the pid is added to the supervisor and the function returns
the same value.
If the child process start function returns `:ignore`, the pid remains set to
`:undefined` and the function returns `{:ok, :undefined}`.
This function may error with an appropriate error tuple if the `child_id` is not
found, or if the current process is running or being restarted.
If the child process start function returns an error tuple or an erroneous value,
or if it fails, the function returns `{:error, error}`.
This operation is not supported by `simple_one_for_one` supervisors.
"""
@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
@doc """
Returns a list with information about all children.
Note that calling this function when supervising a large number of children
under low memory conditions can cause an out of memory exception.
This function returns a list of tuples containing:
* `id` - as defined in the child specification or `:undefined` in the case
of a `simple_one_for_one` supervisor
* `child` - the pid of the corresponding child process, the atom
`:restarting` if the process is about to be restarted, or `:undefined` if
there is no such process
* `type` - `:worker` or `:supervisor` as defined in the child specification
* `modules` – as defined in the child specification
"""
@spec which_children(supervisor) ::
[{Supervisor.Spec.child_id | :undefined,
child | :restarting,
Supervisor.Spec.worker,
Supervisor.Spec.modules}]
defdelegate which_children(supervisor), to: :supervisor
@doc """
Returns a map containing count values for the supervisor.
The map contains the following keys:
* `:specs` - the total count of children, dead or alive
* `:active` - the count of all actively running child processes managed by
this supervisor
* `:supervisors` - the count of all supervisors whether or not the child
process is still alive
* `:workers` - the count of all workers, whether or not the child process
is still alive
"""
@spec count_children(supervisor) ::
[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
end
end
-184
View File
@@ -1,184 +0,0 @@
defmodule Supervisor.Behaviour do
@moduledoc """
This module is a convenience to define Supervisor
callbacks in Elixir. By using this module, you get
the module behaviour automatically tagged as
`:supervisor` and some helper functions are imported
to make defining supervisors easier.
For more information on supervisors, please check the
remaining functions defined in this module or refer to
the following:
http://www.erlang.org/doc/man/supervisor.html
http://www.erlang.org/doc/design_principles/sup_princ.html
http://learnyousomeerlang.com/supervisors
## Example
defmodule ExUnit.Sup do
use Supervisor.Behaviour
def init(user_options) do
tree = [ worker(ExUnit.Runner, [user_options]) ]
supervise(tree, strategy: :one_for_one)
end
end
{:ok, pid} = :supervisor.start_link(MyServer, [])
"""
@doc false
defmacro __using__(_) do
quote location: :keep do
@behaviour :supervisor
import unquote(__MODULE__)
end
end
@doc """
Receives a list of children (worker or supervisors) to
supervise and a set of options. Returns a tuple containing
the supervisor specification.
## Examples
supervise children, strategy: :one_for_one
## Options
* `:strategy` - the restart strategy option It can be either
`:one_for_one`, `:rest_for_one`, `:one_for_all` and
`:simple_one_for_one`;
* `:max_restarts` - the maximum amount of restarts allowed in
a time frame. Defaults to 5;
* `:max_seconds` - the time frame in which max_restarts applies.
Defaults to 5;
The `:strategy` option is required and by default maximum 5 restarts
are allowed in 5 seconds.
## Strategies
* `:one_for_one` - If a child process terminates, only that
process is restarted;
* `:one_for_all` - If a child process terminates, all other child
processes are terminated and then all child processes, including
the terminated one, are restarted;
* `:rest_for_one` - If a child process terminates, the "rest" of
the child processes, i.e. the child processes after the terminated
process in start order, are terminated. Then the terminated child
process and the rest of the child processes are restarted;
* `:simple_one_for_one` - Similar to `:one_for_one` but suits better
when dynamically attaching children;
"""
def supervise(children, options) do
unless strategy = options[:strategy] do
raise ArgumentError, message: "expected :strategy option to be given to supervise"
end
maxR = Keyword.get(options, :max_restarts, 5)
maxS = Keyword.get(options, :max_seconds, 5)
{:ok, {{strategy, maxR, maxS}, children}}
end
@child_doc """
## Options
* `:id` - a name used to identify the child specification
internally by the supervisor. Defaults to the module name;
* `:function` - the function to invoke on the child to start it.
Defaults to `:start_link`;
* `:restart` - defines when the child process should restart.
Defaults to `:permanent`;
* `:shutdown` - defines how a child process should be terminated.
Defaults to `5000` for a worker and `:infinity` for a supervisor;
* `:modules` - it should be a list with one element `[module]`,
where module is the name of the callback module only if the
child process is a supervisor, `gen_server` or `gen_fsm`. If the
child process is a gen_event, modules should be `:dynamic`.
Defaults to a list with the given module;
## Restart values
The following restart values are supported:
* `:permanent` - the child process is always restarted;
* `:temporary` - the child process is never restarted (not even
when the supervisor's strategy is `:rest_for_one` or `:one_for_all`);
* `:transient` - the child process is restarted only if it
terminates abnormally, i.e. with another exit reason than
`:normal`, `:shutdown` or `{:shutdown, term}`;
## Shutdown values
The following shutdown values are supported:
* `:brutal_kill` - the child process is unconditionally terminated
using `exit(child, :kill)`;
* `:infinity` - if the child process is a supervisor, it is a mechanism
to give the subtree enough time to shutdown. It can also be used with
workers with care;
* Finally, it can also be any integer meaning that the supervisor tells
the child process to terminate by calling `exit(child, :shutdown)` and
then waits for an exit signal back. If no exit signal is received within
the specified time (in miliseconds), the child process is unconditionally
terminated using `exit(child, :kill)`;
"""
@doc """
Defines the given `module` as a worker which will be started
with the given arguments.
worker ExUnit.Runner, [], restart: :permanent
By default, the function `:start_link` is invoked on the given module.
#{@child_doc}
"""
def worker(module, args, options \\ []) do
child(:worker, module, args, options)
end
@doc """
Defines the given `module` as a supervisor which will be started
with the given arguments.
supervisor ExUnit.Runner, [], restart: :permanent
By default, the function `:start_link` is invoked on the given module.
#{@child_doc}
"""
def supervisor(module, args, options \\ []) do
options = Keyword.update(options, :shutdown, :infinity, fn(x) -> x end)
child(:supervisor, module, args, options)
end
defp child(type, module, args, options) do
id = Keyword.get(options, :id, module)
modules = Keyword.get(options, :modules, [module])
function = Keyword.get(options, :function, :start_link)
restart = Keyword.get(options, :restart, :permanent)
shutdown = Keyword.get(options, :shutdown, 5000)
{id, {module, function, args},
restart, shutdown, type, modules}
end
end
+13
View File
@@ -0,0 +1,13 @@
defmodule Supervisor.Default do
@moduledoc false
@behaviour :supervisor
@doc """
Supevisor callback that simply returns the given args.
This is the supervisor used by `Supervisor.start_link/2`.
"""
def init(args) do
args
end
end
+248
View File
@@ -0,0 +1,248 @@
defmodule Supervisor.Spec do
@moduledoc """
Convenience functions for defining a supervision specification.
## Example
By using the functions in this module one can define a supervisor
and start it with `Supervisor.start_link/2`:
import Supervisor.Spec
children = [
worker(MyWorker, [arg1, arg2, arg3]),
supervisor(MySupervisor, [arg1])
]
Supervisor.start_link(children, strategy: :one_for_one)
In many situations, it may be handy to define supervisors backed
by a module:
defmodule MySupervisor do
use Supervisor
def start_link(arg) do
Supervisor.start_link(__MODULE__, arg)
end
def init(arg) do
children = [
worker(MyWorker, [arg], restart: :temporary)
]
supervise(children, strategy: :simple_one_for_one)
end
end
Notice in this case we don't have to explicitly import
`Supervisor.Spec` as `use Supervisor` automatically does so.
Explicit supervisors as above are required when there is a need to:
1. Partialy change the supervision tree during hot-code swaps.
2. Define supervisors inside other supervisors.
3. Perform actions inside the supervision `init/1` callback.
For example, you may want to start an ETS table that is linked to
the supervisor (i.e. if the supervision tree needs to be restarted,
the ETS table must be restarted too).
## Supervisor and worker options
In the example above, we have defined workers and supervisors
and each accepts the following options:
* `:id` - a name used to identify the child specification
internally by the supervisor; defaults to the given module
name
* `:function` - the function to invoke on the child to start it
* `:restart` - defines when the child process should restart
* `:shutdown` - defines how a child process should be terminated
* `:modules` - it should be a list with one element `[module]`,
where module is the name of the callback module only if the
child process is a `Supervisor` or `GenServer`; if the child
process is a `GenEvent`, modules should be `:dynamic`
### Restart values
The following restart values are supported:
* `:permanent` - the child process is always restarted
* `:temporary` - the child process is never restarted (not even
when the supervisor's strategy is `:rest_for_one` or `:one_for_all`)
* `:transient` - the child process is restarted only if it
terminates abnormally, i.e. with another exit reason than
`:normal`, `:shutdown` or `{:shutdown, term}`
### Shutdown values
The following shutdown values are supported:
* `:brutal_kill` - the child process is unconditionally terminated
using `exit(child, :kill)`.
* `:infinity` - if the child process is a supervisor, it is a mechanism
to give the subtree enough time to shutdown. It can also be used with
workers with care.
* Finally, it can also be any integer meaning that the supervisor tells
the child process to terminate by calling `Process.exit(child, :shutdown)`
and then waits for an exit signal back. If no exit signal is received
within the specified time (in miliseconds), the child process is
unconditionally terminated using `Process.exit(child, :kill)`.
"""
@typedoc "Supported strategies"
@type strategy :: :simple_one_for_one | :one_for_one | :one_for_all | :rest_for_one
@typedoc "Supported restart values"
@type restart :: :permanent | :transient | :temporary
@typedoc "Supported shutdown values"
@type shutdown :: :brutal_kill | :infinity | non_neg_integer
@typedoc "Supported worker values"
@type worker :: :worker | :supervisor
@typedoc "Supported module values"
@type modules :: :dynamic | [module]
@typedoc "Supported id values"
@type child_id :: term
@typedoc "The supervisor specification"
@type spec :: {child_id,
start_fun :: {module, atom, [term]},
restart,
shutdown,
worker,
modules}
@doc """
Receives a list of children (workers or supervisors) to
supervise and a set of options.
Returns a tuple containing the supervisor specification.
## Examples
supervise children, strategy: :one_for_one
## Options
* `:strategy` - the restart strategy option. It can be either
`:one_for_one`, `:rest_for_one`, `:one_for_all`, or
`:simple_one_for_one`. You can learn more about strategies
in the `Supervisor` module docs.
* `:max_restarts` - the maximum amount of restarts allowed in
a time frame. Defaults to 5.
* `:max_seconds` - the time frame in which `:max_restarts` applies.
Defaults to 5.
The `:strategy` option is required and by default maximum 5 restarts
are allowed within 5 seconds. Please check the `Supervisor` module for
a complete description of the available strategies.
"""
@spec supervise([spec], strategy: strategy,
max_restarts: non_neg_integer,
max_seconds: non_neg_integer) :: {:ok, tuple}
def supervise(children, options) do
unless strategy = options[:strategy] do
raise ArgumentError, "expected :strategy option to be given"
end
maxR = Keyword.get(options, :max_restarts, 5)
maxS = Keyword.get(options, :max_seconds, 5)
assert_unique_ids(Enum.map(children, &elem(&1, 0)))
{:ok, {{strategy, maxR, maxS}, children}}
end
defp assert_unique_ids([id|rest]) do
if id in rest do
raise ArgumentError,
"duplicated id #{inspect id} found in the supervisor specification, " <>
"please explicitly pass the :id option when defining this worker/supervisor"
else
assert_unique_ids(rest)
end
end
defp assert_unique_ids([]) do
:ok
end
@doc """
Defines the given `module` as a worker which will be started
with the given arguments.
worker ExUnit.Runner, [], restart: :permanent
By default, the function `start_link` is invoked on the given
module. Overall, the default values for the options are:
[id: module,
function: :start_link,
restart: :permanent,
shutdown: 5000,
modules: [module]]
Check `Supervisor.Spec` module docs for more information on
the options.
"""
@spec worker(module, [term], [restart: restart, shutdown: shutdown,
id: term, function: atom, modules: modules]) :: spec
def worker(module, args, options \\ []) do
child(:worker, module, args, options)
end
@doc """
Defines the given `module` as a supervisor which will be started
with the given arguments.
supervisor ExUnit.Runner, [], restart: :permanent
By default, the function `start_link` is invoked on the given
module. Overall, the default values for the options are:
[id: module,
function: :start_link,
restart: :permanent,
shutdown: :infinity,
modules: [module]]
Check `Supervisor.Spec` module docs for more information on
the options.
"""
@spec supervisor(module, [term], [restart: restart, shutdown: shutdown,
id: term, function: atom, modules: modules]) :: spec
def supervisor(module, args, options \\ []) do
options = Keyword.put_new(options, :shutdown, :infinity)
child(:supervisor, module, args, options)
end
defp child(type, module, args, options) do
id = Keyword.get(options, :id, module)
modules = Keyword.get(options, :modules, modules(module))
function = Keyword.get(options, :function, :start_link)
restart = Keyword.get(options, :restart, :permanent)
shutdown = Keyword.get(options, :shutdown, 5000)
{id, {module, function, args},
restart, shutdown, type, modules}
end
defp modules(GenEvent), do: :dynamic
defp modules(module), do: [module]
end
+26 -26
View File
@@ -44,7 +44,7 @@ defmodule System do
# Get the date at compilation time.
defmacrop get_date do
iodata_to_binary :httpd_util.rfc1123_date
IO.iodata_to_binary :httpd_util.rfc1123_date
end
@doc """
@@ -60,9 +60,9 @@ defmodule System do
Returns a keyword list with Elixir version, git tag info and compilation date.
"""
@spec build_info() :: Keyword.t
@spec build_info() :: map
def build_info do
[version: version, tag: get_describe, date: get_date]
%{version: version, tag: get_describe, date: get_date}
end
@doc """
@@ -94,7 +94,7 @@ defmodule System do
"""
def cwd do
case :file.get_cwd do
{:ok, base} -> String.from_char_data!(base)
{:ok, base} -> IO.chardata_to_string(base)
_ -> nil
end
end
@@ -153,11 +153,11 @@ defmodule System do
Returns a writable temporary directory.
Searches for directories in the following order:
1. The directory named by the TMPDIR environment variable
2. The directory named by the TEMP environment variable
3. The directory named by the TMP environment variable
4. `C:\TMP` on Windows or `/tmp` on Unix
5. As a last resort, the current working directory
1. the directory named by the TMPDIR environment variable
2. the directory named by the TEMP environment variable
3. the directory named by the TMP environment variable
4. `C:\TMP` on Windows or `/tmp` on Unix
5. as a last resort, the current working directory
Returns `nil` if none of the above are writable.
"""
@@ -193,7 +193,7 @@ defmodule System do
{:ok, stat} ->
case {stat.type, stat.access} do
{:directory, access} when access in [:read_write, :write] ->
String.from_char_data!(dir)
IO.chardata_to_string(dir)
_ ->
nil
end
@@ -233,7 +233,7 @@ defmodule System do
end
def cmd(command) when is_binary(command) do
String.from_char_data! :os.cmd(List.from_char_data!(command))
List.to_string :os.cmd(String.to_char_list(command))
end
@doc """
@@ -256,9 +256,9 @@ defmodule System do
end
def find_executable(program) when is_binary(program) do
case :os.find_executable(List.from_char_data!(program)) do
case :os.find_executable(String.to_char_list(program)) do
false -> nil
other -> String.from_char_data!(other)
other -> List.to_string(other)
end
end
@@ -271,7 +271,7 @@ defmodule System do
@spec get_env() :: %{String.t => String.t}
def get_env do
Enum.into(:os.getenv, %{}, fn var ->
var = String.from_char_data! var
var = IO.chardata_to_string var
[k, v] = String.split var, "=", parts: 2
{k, v}
end)
@@ -286,9 +286,9 @@ defmodule System do
"""
@spec get_env(binary) :: binary | nil
def get_env(varname) when is_binary(varname) do
case :os.getenv(List.from_char_data!(varname)) do
case :os.getenv(String.to_char_list(varname)) do
false -> nil
other -> String.from_char_data!(other)
other -> List.to_string(other)
end
end
@@ -301,7 +301,7 @@ defmodule System do
See http://www.erlang.org/doc/man/os.html#getpid-0 for more info.
"""
@spec get_pid() :: binary
def get_pid, do: iodata_to_binary(:os.getpid)
def get_pid, do: IO.iodata_to_binary(:os.getpid)
@doc """
Set an environment variable value.
@@ -310,7 +310,7 @@ defmodule System do
"""
@spec put_env(binary, binary) :: :ok
def put_env(varname, value) when is_binary(varname) and is_binary(value) do
:os.putenv List.from_char_data!(varname), List.from_char_data!(value)
:os.putenv String.to_char_list(varname), String.to_char_list(value)
:ok
end
@@ -332,7 +332,7 @@ defmodule System do
"""
@spec delete_env(String.t) :: :ok
def delete_env(varname) do
:os.unsetenv(List.from_char_data!(varname))
:os.unsetenv(String.to_char_list(varname))
:ok
end
@@ -355,14 +355,14 @@ defmodule System do
Halts the Erlang runtime system where the argument `status` must be a
non-negative integer, the atom `:abort` or a binary.
* If an integer, the runtime system exits with the integer value which
is returned to the operating system;
* If an integer, the runtime system exits with the integer value which
is returned to the operating system.
* If `:abort`, the runtime system aborts producing a core dump, if that is
enabled in the operating system;
* If `:abort`, the runtime system aborts producing a core dump, if that is
enabled in the operating system.
* If a string, an erlang crash dump is produced with status as slogan,
and then the runtime system exits with status code 1;
* If a string, an erlang crash dump is produced with status as slogan,
and then the runtime system exits with status code 1.
Note that on many platforms, only the status codes 0-255 are supported
by the operating system.
@@ -385,6 +385,6 @@ defmodule System do
end
def halt(status) when is_binary(status) do
:erlang.halt(List.from_char_data!(status))
:erlang.halt(String.to_char_list(status))
end
end
+220
View File
@@ -0,0 +1,220 @@
defmodule Task do
@moduledoc """
Conveniences for spawning and awaiting for tasks.
Tasks are processes meant to execute one particular
action throughout their life-cycle, often with little or no
communication with other processes. The most common use case
for tasks is to compute a value asynchronously:
task = Task.async(fn -> do_some_work() end)
res = do_some_other_work()
res + Task.await(task)
Tasks spawned with `async` can be awaited on by its caller
process (and only its caller) as shown in the example above.
They are implemented by spawning a process that sends a message
to the caller once the given computation is performed.
Besides `async/1` and `await/2`, tasks can also be
started as part of supervision trees and dynamically spawned
in remote nodes. We will explore all three scenarios next.
## async and await
The most common way to spawn a task is with `Task.async/1`. A new
process will be created, linked and monitored by the caller. Once
the task action finishes, a message will be sent to the caller
with the result.
`Task.await/2` is used to read the message sent by the task. On
`await`, Elixir will also setup a monitor to verify if the process
exited for any abnormal reason (or in case exits are being
trapped by the caller).
## Supervised tasks
It is also possible to spawn a task inside a supervision tree
with `start_link/1` and `start_link/3`:
Task.start_link(fn -> IO.puts "ok" end)
Such tasks can be mounted in your supervision tree as:
import Supervisor.Spec
children = [
worker(Task, [fn -> IO.puts "ok" end])
]
Since these tasks are supervised and not directly linked to
the caller, they cannot be awaited on. Note `start_link/1`,
unlike `async/1`, returns `{:ok, pid}` (which is
the result expected by supervision trees).
## Supervision trees
The `Task.Supervisor` module allows developers to start supervisors
that dynamically supervise tasks:
{:ok, pid} = Task.Supervisor.start_link()
Task.Supervisor.async(pid, fn -> do_work() end)
`Task.Supervisor` also makes it possible to spawn tasks in remote nodes as
long as the supervisor is registered locally or globally:
# In the remote node
Task.Supervisor.start_link(name: :tasks_sup)
# In the client
Task.Supervisor.async({:tasks_sup, :remote@local}, fn -> do_work() end)
`Task.Supervisor` is more often started in your supervision tree as:
import Supervisor.Spec
children = [
supervisor(Task.Supervisor, [[name: :tasks_sup]])
]
Check `Task.Supervisor` for other operations supported by the Task supervisor.
"""
@doc """
The Task struct.
It contains two fields:
* `:pid` - the process reference of the task process; it may be a pid
or a tuple containing the process and node names
* `:ref` - the task monitor reference
"""
defstruct pid: nil, ref: nil
@doc """
Starts a task as part of a supervision tree.
"""
@spec start_link(fun) :: {:ok, pid}
def start_link(fun) do
start_link(:erlang, :apply, [fun, []])
end
@doc """
Starts a task as part of a supervision tree.
"""
@spec start_link(module, atom, [term]) :: {:ok, pid}
def start_link(mod, fun, args) do
Task.Supervised.start_link(get_info(self), {mod, fun, args})
end
@doc """
Starts a task that can be awaited on.
This function spawns a process that is linked to and monitored
by the caller process. A `Task` struct is returned containing
the relevant information.
## Task's message format
The reply sent by the task will be in the format `{ref, msg}`,
where `ref` is the monitoring reference held by the task.
"""
@spec async(fun) :: t
def async(fun) do
async(:erlang, :apply, [fun, []])
end
@doc """
Starts a task that can be awaited on.
Similar to `async/1`, but the task is specified by the given
module, function and arguments.
"""
@spec async(module, atom, [term]) :: t
def async(mod, fun, args) do
mfa = {mod, fun, args}
pid = :proc_lib.spawn_link(Task.Supervised, :async, [self, get_info(self), mfa])
ref = Process.monitor(pid)
send(pid, {self(), ref})
%Task{pid: pid, ref: ref}
end
defp get_info(self) do
{node(),
case Process.info(self, :registered_name) do
{:registered_name, []} -> self()
{:registered_name, name} -> name
end}
end
@doc """
Awaits for a task reply.
A timeout, in milliseconds, can be given with default value
of `5000`. In case the task process dies, this function will
exit with the same reason as the task.
"""
@spec await(t, timeout) :: term | no_return
def await(%Task{ref: ref}=task, timeout \\ 5000) do
receive do
{^ref, reply} ->
Process.demonitor(ref, [:flush])
reply
{:DOWN, ^ref, _, _, :noconnection} ->
mfa = {__MODULE__, :await, [task, timeout]}
exit({{:nodedown, node(task.pid)}, mfa})
{:DOWN, ^ref, _, _, reason} ->
exit({reason, {__MODULE__, :await, [task, timeout]}})
after
timeout ->
Process.demonitor(ref, [:flush])
exit({:timeout, {__MODULE__, :await, [task, timeout]}})
end
end
@doc """
Receives a group of tasks and a message and finds
a task that matches the given message.
This function returns a tuple with the task and the
returned value in case the message matches a task that
exited with success, it raises in case the found task
failed or `nil` if no task was found.
This function is useful in situations where multiple
tasks are spawned and their results are collected
later on. For example, a `GenServer` can spawn tasks,
store the tasks in a list and later use `Task.find/2`
to see if incoming messages are from any of the tasks.
"""
@spec find([t], any) :: {term, t} | nil | no_return
def find(tasks, msg)
def find(tasks, {ref, reply}) when is_reference(ref) do
Enum.find_value tasks, fn
%Task{ref: task_ref} = t when ref == task_ref ->
Process.demonitor(ref, [:flush])
{reply, t}
%Task{} ->
nil
end
end
def find(tasks, {:DOWN, ref, _, _, reason} = msg) when is_reference(ref) do
find = fn(%Task{ref: task_ref}) -> task_ref == ref end
case Enum.find(tasks, find) do
%Task{pid: pid} when reason == :noconnection ->
exit({{:nodedown, node(pid)}, {__MODULE__, :find, [tasks, msg]}})
%Task{} ->
exit({reason, {__MODULE__, :find, [tasks, msg]}})
nil ->
nil
end
end
def find(_tasks, _msg) do
nil
end
end
+107
View File
@@ -0,0 +1,107 @@
defmodule Task.Supervised do
@moduledoc false
def start_link(info, fun) do
{:ok, :proc_lib.spawn_link(__MODULE__, :noreply, [info, fun])}
end
def start_link(caller, info, fun) do
:proc_lib.start_link(__MODULE__, :reply, [caller, info, fun])
end
def async(caller, info, mfa) do
initial_call(mfa)
ref = receive do: ({^caller, ref} -> ref)
send caller, {ref, do_apply(info, mfa)}
end
def reply(caller, info, mfa) do
initial_call(mfa)
:erlang.link(caller)
:proc_lib.init_ack({:ok, self()})
ref =
# There is a race condition on this operation when working accross
# node that manifests if a `Task.Supervisor.async/1` call is made
# while the supervisor is busy spawning previous tasks.
#
# Imagine the following workflow:
#
# 1. The nodes disconnect
# 2. The async call fails and is caught, the calling process does not exit
# 3. The task is spawned and links to the calling process, causing the nodes to reconnect
# 4. The calling process has not exited and so does not send its monitor reference
# 5. The spawned task waits forever for the monitor reference so it can begin
#
# We have solved this by specifying a timeout of 5000 seconds.
# Given no work is done in the client in between the task start and
# sending the reference, 5000 should be enough to not raise false
# negatives unless the nodes are indeed not available.
receive do
{^caller, ref} -> ref
after
5000 -> exit(:timeout)
end
send caller, {ref, do_apply(info, mfa)}
end
def noreply(info, mfa) do
initial_call(mfa)
do_apply(info, mfa)
end
defp initial_call(mfa) do
Process.put(:"$initial_call", get_initial_call(mfa))
end
defp get_initial_call({:erlang, :apply, [fun, []]}) when is_function(fun, 0) do
{:module, module} = :erlang.fun_info(fun, :module)
{:name, name} = :erlang.fun_info(fun, :name)
{module, name, 0}
end
defp get_initial_call({mod, fun, args}) do
{mod, fun, length(args)}
end
defp do_apply(info, {module, fun, args} = mfa) do
try do
apply(module, fun, args)
catch
:error, value ->
exit(info, mfa, {value, System.stacktrace()})
:throw, value ->
exit(info, mfa, {{:nocatch, value}, System.stacktrace()})
:exit, value ->
exit(info, mfa, value)
end
end
defp exit(_info, _mfa, reason)
when reason == :normal
when reason == :shutdown
when tuple_size(reason) == 2 and elem(reason, 0) == :shutdown do
exit(reason)
end
defp exit(info, mfa, reason) do
{fun, args} = get_running(mfa)
:error_logger.format(
"** Task ~p terminating~n" <>
"** Started from ~p~n" <>
"** When function == ~p~n" <>
"** arguments == ~p~n" <>
"** Reason for termination == ~n" <>
"** ~p~n", [self, get_from(info), fun, args, reason])
exit(reason)
end
defp get_from({node, pid_or_name}) when node == node(), do: pid_or_name
defp get_from(other), do: other
defp get_running({:erlang, :apply, [fun, []]}) when is_function(fun, 0), do: {fun, []}
defp get_running({mod, fun, args}), do: {:erlang.make_fun(mod, fun, length(args)), args}
end
+112
View File
@@ -0,0 +1,112 @@
defmodule Task.Supervisor do
@moduledoc """
A tasks supervisor.
This module defines a supervisor which can be used to dynamically
supervise tasks. Behind the scenes, this module is implemented as a
`:simple_one_for_one` supervisor where the workers are temporary
(i.e. they are not restarted after they die).
The functions in this module allow tasks to be spawned and awaited
from a supervisor, similar to the functions defined in the `Task` module.
## Name Registration
A `Task.Supervisor` is bound to the same name registration rules as a
`GenServer`. Read more about it in the `GenServer` docs.
"""
@doc """
Starts a new supervisor.
The supported options are:
* `:name` - used to register a supervisor name, the supported values are
described under the `Name Registration` section in the `GenServer` module
docs;
* `:shutdown` - `:brutal_kill` if the tasks must be killed directly on shutdown
or an integer indicating the timeout value, defaults to 5000 milliseconds;
"""
@spec start_link(Supervisor.options) :: Supervisor.on_start
def start_link(opts \\ []) do
import Supervisor.Spec
{shutdown, opts} = Keyword.pop(opts, :shutdown, 5000)
children = [worker(Task.Supervised, [], restart: :temporary, shutdown: shutdown)]
Supervisor.start_link(children, [strategy: :simple_one_for_one] ++ opts)
end
@doc """
Starts a task that can be awaited on.
The `supervisor` must be a reference as defined in `Task.Supervisor`.
For more information on tasks, check the `Task` module.
"""
@spec async(Supervisor.supervisor, fun) :: Task.t
def async(supervisor, fun) do
async(supervisor, :erlang, :apply, [fun, []])
end
@doc """
Starts a task that can be awaited on.
The `supervisor` must be a reference as defined in `Task.Supervisor`.
For more information on tasks, check the `Task` module.
"""
@spec async(Supervisor.supervisor, module, atom, [term]) :: Task.t
def async(supervisor, module, fun, args) do
args = [self, get_info(self), {module, fun, args}]
{:ok, pid} = Supervisor.start_child(supervisor, args)
ref = Process.monitor(pid)
send pid, {self(), ref}
%Task{pid: pid, ref: ref}
end
@doc """
Terminates the child with the given `pid`.
"""
@spec terminate_child(Supervisor.supervisor, pid) :: :ok
def terminate_child(supervisor, pid) when is_pid(pid) do
:supervisor.terminate_child(supervisor, pid)
end
@doc """
Returns all children pids.
"""
@spec children(Supervisor.supervisor) :: [pid]
def children(supervisor) do
:supervisor.which_children(supervisor) |> Enum.map(&elem(&1, 1))
end
@doc """
Starts a task as child of the given `supervisor`.
Note that the spawned process is not linked to the caller, but
only to the supervisor. This command is useful in case the
task needs to perform side-effects (like I/O) and does not need
to report back to the caller.
"""
@spec start_child(Supervisor.supervisor, fun) :: {:ok, pid}
def start_child(supervisor, fun) do
start_child(supervisor, :erlang, :apply, [fun, []])
end
@doc """
Starts a task as child of the given `supervisor`.
Similar to `start_child/2` except the task is specified
by the given `module`, `fun` and `args`.
"""
@spec start_child(Supervisor.supervisor, module, atom, [term]) :: {:ok, pid}
def start_child(supervisor, module, fun, args) do
Supervisor.start_child(supervisor, [get_info(self), {module, fun, args}])
end
defp get_info(self) do
{node(),
case Process.info(self, :registered_name) do
{:registered_name, []} -> self()
{:registered_name, name} -> name
end}
end
end
+13 -3
View File
@@ -4,7 +4,7 @@ defmodule Tuple do
"""
@doc """
Create a new tuple.
Creates a new tuple.
Creates a tuple of size `size` containing the
given `data` at every position.
@@ -23,7 +23,7 @@ defmodule Tuple do
end
@doc """
Insert an element into a tuple.
Inserts an element into a tuple.
Inserts `value` into `tuple` at the given zero-based `index`.
Raises an `ArgumentError` if `index` is greater than the
@@ -44,7 +44,7 @@ defmodule Tuple do
end
@doc """
Remove an element from a tuple.
Removes an element from a tuple.
Deletes the element at the zero-based `index` from `tuple`.
Raises an `ArgumentError` if `index` is greater than
@@ -63,4 +63,14 @@ defmodule Tuple do
def delete_at(tuple, index) do
:erlang.delete_element(index + 1, tuple)
end
@doc """
Converts a tuple to a list.
Inlined by the compiler.
"""
@spec to_list(tuple) :: list
def to_list(tuple) do
:erlang.tuple_to_list(tuple)
end
end
+107 -40
View File
@@ -58,7 +58,7 @@ defmodule URI do
"""
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), persist: true)
Application.put_env(:elixir, :uri, Map.put(dict, scheme, port), persistent: true)
end
@doc """
@@ -126,49 +126,98 @@ defmodule URI do
current =
case :binary.split(first, "=") do
[ key, value ] -> {decode(key), decode(value)}
[ key ] -> {decode(key), nil}
[key, value] ->
{decode_www_form(key), decode_www_form(value)}
[key] ->
{decode_www_form(key), nil}
end
{current, next}
end
defp pair({k, _}) when is_list(k) do
raise ArgumentError, message: "encode_query/1 keys cannot be lists, got: #{inspect k}"
raise ArgumentError, "encode_query/1 keys cannot be lists, got: #{inspect k}"
end
defp pair({_, v}) when is_list(v) do
raise ArgumentError, message: "encode_query/1 values cannot be lists, got: #{inspect v}"
raise ArgumentError, "encode_query/1 values cannot be lists, got: #{inspect v}"
end
defp pair({k, v}) do
encode(to_string(k)) <> "=" <> encode(to_string(v))
encode_www_form(to_string(k)) <>
"=" <> encode_www_form(to_string(v))
end
@doc """
Checks if the character is a "reserved" character in a URI.
Reserved characters are specified in RFC3986, section 2.2.
"""
def char_reserved?(c) do
c in ':/?#[]@!$&\'()*+,;='
end
@doc """
Checks if the character is a "unreserved" character in a URI.
Unreserved characters are specified in RFC3986, section 2.3.
"""
def char_unreserved?(c) do
c in ?0..?9 or
c in ?a..?z or
c in ?A..?Z or
c in '~_-.'
end
@doc """
Checks if the character is allowed unescaped in a URI.
This is the default used by `URI.encode/2` where both
reserved and unreserved characters are kept unescaped.
"""
def char_unescaped?(c) do
char_reserved?(c) or char_unreserved?(c)
end
@doc """
Percent-escape a URI.
Accepts `predicate` function as an argument to specify if char can be left as is.
## Example
iex> URI.encode("http://elixir-lang.org/getting_started/2.html")
"http%3A%2F%2Felixir-lang.org%2Fgetting_started%2F2.html"
iex> URI.encode("ftp://s-ite.tld/?value=put it+й")
"ftp://s-ite.tld/?value=put%20it+%D0%B9"
"""
def encode(s), do: for(<<c <- s>>, into: "", do: percent(c))
defp percent(32), do: <<?+>>
defp percent(?-), do: <<?->>
defp percent(?_), do: <<?_>>
defp percent(?.), do: <<?.>>
defp percent(c)
when c >= ?0 and c <= ?9
when c >= ?a and c <= ?z
when c >= ?A and c <= ?Z do
<<c>>
def encode(str, predicate \\ &char_unescaped?/1) when is_binary(str) do
for <<c <- str>>, into: "", do: percent(c, predicate)
end
defp percent(c), do: "%" <> hex(bsr(c, 4)) <> hex(band(c, 15))
@doc """
Encode a string as "x-www-urlencoded".
## Example
iex> URI.encode_www_form("put: it+й")
"put%3A+it%2B%D0%B9"
"""
def encode_www_form(str) when is_binary(str) do
for <<c <- str>>, into: "" do
case percent(c, &char_unreserved?/1) do
"%20" -> "+"
pct -> pct
end
end
end
defp percent(c, predicate) do
if predicate.(c) do
<<c>>
else
"%" <> hex(bsr(c, 4)) <> hex(band(c, 15))
end
end
defp hex(n) when n <= 9, do: <<n + ?0>>
defp hex(n), do: <<n + ?A - 10>>
@@ -183,28 +232,44 @@ defmodule URI do
"""
def decode(uri) do
decode(uri, uri)
unpercent(uri)
catch
:malformed_uri ->
raise ArgumentError, "malformed URI #{inspect uri}"
end
def decode(<<?%, hex1, hex2, tail :: binary >>, uri) do
<< bsl(hex2dec(hex1, uri), 4) + hex2dec(hex2, uri) >> <> decode(tail, uri)
@doc """
Decode a string as "x-www-urlencoded".
## Examples
iex> URI.decode_www_form("%3Call+in%2F")
"<all in/"
"""
def decode_www_form(str) do
String.split(str, "+") |> Enum.map_join(" ", &unpercent/1)
catch
:malformed_uri ->
raise ArgumentError, "malformed URI #{inspect str}"
end
def decode(<<head, tail :: binary >>, uri) do
<<check_plus(head)>> <> decode(tail, uri)
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)
end
def decode(<<>>, _uri), do: <<>>
defp unpercent(<<>>), do: <<>>
defp hex2dec(n, _uri) when n in ?A..?F, do: n - ?A + 10
defp hex2dec(n, _uri) when n in ?a..?f, do: n - ?a + 10
defp hex2dec(n, _uri) when n in ?0..?9, do: n - ?0
defp hex2dec(_n, uri) do
raise ArgumentError, message: "malformed URI #{inspect uri}"
end
defp check_plus(?+), do: 32
defp check_plus(c), do: c
defp hex_to_dec(n) when n in ?A..?F, do: n - ?A + 10
defp hex_to_dec(n) when n in ?a..?f, do: n - ?a + 10
defp hex_to_dec(n) when n in ?0..?9, do: n - ?0
defp hex_to_dec(_n), do: throw(:malformed_uri)
@doc """
Parses a URI into components.
@@ -222,6 +287,8 @@ defmodule URI do
host: "elixir-lang.org", port: 80}
"""
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/^(([^:\/?#]+):)?(\/\/([^\/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?/
@@ -235,7 +302,7 @@ defmodule URI do
if userinfo, do: authority = authority <> userinfo <> "@"
if host, do: authority = authority <> host
if port, do: authority = authority <> ":" <> integer_to_binary(port)
if port, do: authority = authority <> ":" <> Integer.to_string(port)
end
scheme = normalize_scheme(scheme)
@@ -257,7 +324,7 @@ defmodule URI do
components = Regex.run ~r/(^(.*)@)?(\[[a-zA-Z0-9:.]*\]|[^:]*)(:(\d*))?/, s
destructure [_, _, userinfo, host, _, port], nillify(components)
port = if port, do: binary_to_integer(port)
port = if port, do: String.to_integer(port)
host = if host, do: host |> String.lstrip(?[) |> String.rstrip(?])
{userinfo, host, port}
@@ -267,7 +334,7 @@ defmodule URI do
# to replace those with nil for consistency.
defp nillify(l) do
for s <- l do
if size(s) > 0, do: s, else: nil
if byte_size(s) > 0, do: s, else: nil
end
end
end
@@ -285,7 +352,7 @@ defimpl String.Chars, for: URI do
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_binary(uri.port)
if uri.port, do: result = result <> ":" <> Integer.to_string(uri.port)
if uri.path, do: result = result <> uri.path
if uri.query, do: result = result <> "?" <> uri.query
if uri.fragment, do: result = result <> "#" <> uri.fragment
+19 -14
View File
@@ -68,15 +68,20 @@ defmodule Version do
defstruct [:source, :matchspec]
end
defexception InvalidRequirement, [:message]
defexception InvalidVersion, [:message]
defmodule InvalidRequirementError do
defexception [:message]
end
defmodule InvalidVersionError do
defexception [:message]
end
@doc """
Check if the given version matches the specification.
Returns `true` if `version` satisfies `requirement`, `false` otherwise.
Raises a `Version.InvalidRequirement` exception if `requirement` is not
parseable, or `Version.InvalidVersion` if `version` is not parseable.
Raises a `Version.InvalidRequirementError` exception if `requirement` is not
parseable, or `Version.InvalidVersionError` if `version` is not parseable.
If given an already parsed version and requirement this function won't
raise.
@@ -89,10 +94,10 @@ defmodule Version do
false
iex> Version.match?("foo", "==1.0.0")
** (Version.InvalidVersion) foo
** (Version.InvalidVersionError) foo
iex> Version.match?("2.0.0", "== ==1.0.0")
** (Version.InvalidRequirement) == ==1.0.0
** (Version.InvalidRequirementError) == ==1.0.0
"""
@spec match?(version, requirement) :: boolean
@@ -101,7 +106,7 @@ defmodule Version do
{:ok, req} ->
match?(vsn, req)
:error ->
raise InvalidRequirement, message: req
raise InvalidRequirementError, message: req
end
end
@@ -115,7 +120,7 @@ defmodule Version do
the second and `:lt` for vice versa. If the two versions are equal `:eq`
is returned
Raises a `Version.InvalidVersion` exception if `version` is not parseable.
Raises a `Version.InvalidVersionError` exception if `version` is not parseable.
If given an already parsed version this function won't raise.
## Examples
@@ -127,7 +132,7 @@ defmodule Version do
:eq
iex> Version.compare("invalid", "2.0.1")
** (Version.InvalidVersion) invalid
** (Version.InvalidVersionError) invalid
"""
@spec compare(version, version) :: :gt | :eq | :lt
@@ -200,7 +205,7 @@ defmodule Version do
defp to_matchable(string) do
case Version.Parser.parse_version(string) do
{:ok, vsn} -> vsn
:error -> raise InvalidVersion, message: string
:error -> raise InvalidVersionError, message: string
end
end
@@ -308,9 +313,9 @@ defmodule Version do
if nil?(minor) or (nil?(patch) and not approximate?) do
:error
else
major = binary_to_integer(major)
minor = binary_to_integer(minor)
patch = patch && binary_to_integer(patch)
major = String.to_integer(major)
minor = String.to_integer(minor)
patch = patch && String.to_integer(patch)
case parse_pre(pre) do
{:ok, pre} ->
@@ -330,7 +335,7 @@ defmodule Version do
defp parse_pre([piece|t], acc) do
cond do
piece =~ ~r/^(0|[1-9][0-9]*)$/ ->
parse_pre(t, [binary_to_integer(piece)|acc])
parse_pre(t, [String.to_integer(piece)|acc])
piece =~ ~r/^[0-9]*$/ ->
:error
true ->
+31 -25
View File
@@ -9,9 +9,8 @@
-include("elixir.hrl").
%% Top level types
-export_type([char_list/0, char_data/0, as_boolean/1]).
-export_type([char_list/0, as_boolean/1]).
-type char_list() :: string().
-type char_data() :: unicode:chardata().
-type as_boolean(T) :: T.
%% OTP Application API
@@ -19,21 +18,28 @@
-export([start/2, stop/1, config_change/3]).
start(_Type, _Args) ->
%% Set the shell to unicode so printing inside scripts work
%% Those can take a while, so let's do it in a new process
spawn(fun() ->
io:setopts(standard_io, [binary,{encoding,utf8}]),
io:setopts(standard_error, [{unicode,true}]),
case file:native_name_encoding() of
latin1 ->
io:format(standard_error,
"warning: the VM is running with native name encoding of latin1 which may cause "
"Elixir to malfunction as it expects utf8. Please ensure your locale is set to UTF-8 "
"(which can be verified by running \"locale\" in your shell)~n", []);
_ ->
ok
end
end),
%% In case there is a shell, we can't really change its
%% encoding, so we just set binary to true. Otherwise
%% we must set the encoding as the user with no shell
%% has encoding set to latin1.
Opts =
case init:get_argument(noshell) of
{ok, _} -> [binary,{encoding,utf8}];
error -> [binary]
end,
io:setopts(standard_io, Opts),
io:setopts(standard_error, [{unicode,true}]),
case file:native_name_encoding() of
latin1 ->
io:format(standard_error,
"warning: the VM is running with native name encoding of latin1 which may cause "
"Elixir to malfunction as it expects utf8. Please ensure your locale is set to UTF-8 "
"(which can be verified by running \"locale\" in your shell)~n", []);
_ ->
ok
end,
elixir_sup:start_link().
stop(_S) ->
@@ -66,37 +72,37 @@ env_for_eval(Opts) ->
env_for_eval(Env, Opts) ->
Line = case lists:keyfind(line, 1, Opts) of
{line, RawLine} when is_integer(RawLine) -> RawLine;
{line, LineOpt} when is_integer(LineOpt) -> LineOpt;
false -> ?m(Env, line)
end,
File = case lists:keyfind(file, 1, Opts) of
{file, RawFile} when is_binary(RawFile) -> RawFile;
{file, FileOpt} when is_binary(FileOpt) -> FileOpt;
false -> ?m(Env, file)
end,
Local = case lists:keyfind(delegate_locals_to, 1, Opts) of
{delegate_locals_to, LocalOpt} -> LocalOpt;
{delegate_locals_to, LocalOpt} when is_atom(LocalOpt) -> LocalOpt;
false -> ?m(Env, local)
end,
Aliases = case lists:keyfind(aliases, 1, Opts) of
{aliases, AliasesOpt} -> AliasesOpt;
{aliases, AliasesOpt} when is_list(AliasesOpt) -> AliasesOpt;
false -> ?m(Env, aliases)
end,
Requires = case lists:keyfind(requires, 1, Opts) of
{requires, List} -> ordsets:from_list(List);
{requires, RequiresOpt} when is_list(RequiresOpt) -> ordsets:from_list(RequiresOpt);
false -> ?m(Env, requires)
end,
Functions = case lists:keyfind(functions, 1, Opts) of
{functions, FunctionsOpt} -> FunctionsOpt;
{functions, FunctionsOpt} when is_list(FunctionsOpt) -> FunctionsOpt;
false -> ?m(Env, functions)
end,
Macros = case lists:keyfind(macros, 1, Opts) of
{macros, MacrosOpt} -> MacrosOpt;
{macros, MacrosOpt} when is_list(MacrosOpt) -> MacrosOpt;
false -> ?m(Env, macros)
end,
@@ -109,7 +115,7 @@ env_for_eval(Env, Opts) ->
file := File, local := Local, module := Module,
macros := Macros, functions := Functions,
requires := Requires, aliases := Aliases, line := Line
}.
}.
%% String evaluation
+1 -1
View File
@@ -5,7 +5,7 @@
inspect(Atom) when is_atom(Atom) ->
case elixir_compiler:get_opt(internal) of
true -> atom_to_binary(Atom, utf8);
true -> atom_to_binary(Atom, utf8);
false -> 'Elixir.Inspect.Atom':inspect(Atom)
end.
+33 -24
View File
@@ -1,18 +1,18 @@
%% Handle code related to args, guard and -> matching for case,
%% fn, receive and friends. try is handled in elixir_try.
-module(elixir_clauses).
-export([match/3, clause/7, clauses/4, get_pairs/2, get_pairs/3,
-export([match/3, clause/7, clauses/4, guards/4, get_pairs/3, get_pairs/4,
extract_splat_guards/1, extract_guards/1]).
-include("elixir.hrl").
%% Get pairs from a clause.
get_pairs(Key, Clauses) ->
get_pairs(Key, Clauses, false).
get_pairs(Key, Clauses, AllowNil) ->
get_pairs(Key, Clauses, As) ->
get_pairs(Key, Clauses, As, false).
get_pairs(Key, Clauses, As, AllowNil) ->
case lists:keyfind(Key, 1, Clauses) of
{Key, Pairs} when is_list(Pairs) ->
[{Key, Meta, Left, Right} || {'->', Meta, [Left, Right]} <- Pairs];
[{As, Meta, Left, Right} || {'->', Meta, [Left, Right]} <- Pairs];
{Key, nil} when AllowNil ->
[];
false ->
@@ -35,18 +35,20 @@ clause(Line, Fun, Args, Expr, Guards, Return, S) when is_integer(Line) ->
{TArgs, SA} = match(Fun, Args, S#elixir_scope{extra_guards=[]}),
{TExpr, SE} = elixir_translator:translate_block(Expr, Return, SA#elixir_scope{extra_guards=nil}),
SG = SA#elixir_scope{context=guard, extra_guards=nil},
Extra = SA#elixir_scope.extra_guards,
TGuards = case Guards of
[] -> case Extra of [] -> []; _ -> [Extra] end;
_ -> [translate_guard(Line, Guard, Extra, SG) || Guard <- Guards]
end,
TGuards = guards(Line, Guards, Extra, SA),
{{clause, Line, TArgs, TGuards, unblock(TExpr)}, SE}.
% Translate/Extract guards from the given expression.
guards(Line, Guards, Extra, S) ->
SG = S#elixir_scope{context=guard, extra_guards=nil},
case Guards of
[] -> case Extra of [] -> []; _ -> [Extra] end;
_ -> [translate_guard(Line, Guard, Extra, SG) || Guard <- Guards]
end.
translate_guard(Line, Guard, Extra, S) ->
[element(1, elixir_translator:translate(elixir_quote:linify(Line, Guard), S))|Extra].
@@ -77,9 +79,8 @@ do_clauses(Meta, DecoupledClauses, Return, S) ->
% Transform tree just passing the variables counter forward
% and storing variables defined inside each clause.
Transformer = fun(X, {SAcc, VAcc}) ->
{TX, TS} = each_clause(X, Return, SAcc),
{TX,
{elixir_scope:mergec(S, TS), [TS#elixir_scope.export_vars|VAcc]}}
{TX, TS} = each_clause(Meta, X, Return, SAcc),
{TX, {elixir_scope:mergec(S, TS), [TS#elixir_scope.export_vars|VAcc]}}
end,
{TClauses, {TS, ReverseCV}} =
@@ -138,19 +139,27 @@ expand_clauses(_Line, [], [], _FinalVars, Acc, S) ->
% Handle each key/value clause pair and translate them accordingly.
each_clause({do, Meta, [Condition], Expr}, Return, S) ->
each_clause(Export, {match, Meta, [Condition], Expr}, Return, S) ->
Fun = wrap_export_fun(Export, fun elixir_translator:translate_args/2),
{Arg, Guards} = extract_guards(Condition),
clause(?line(Meta), fun elixir_translator:translate_args/2, [Arg], Expr, Guards, Return, S);
clause(?line(Meta), Fun, [Arg], Expr, Guards, Return, S);
each_clause({else, Meta, [Condition], Expr}, Return, S) ->
{Arg, Guards} = extract_guards(Condition),
clause(?line(Meta), fun elixir_translator:translate_args/2, [Arg], Expr, Guards, Return, S);
each_clause({'after', Meta, [Condition], Expr}, Return, S) ->
{TCondition, SC} = elixir_translator:translate(Condition, S),
each_clause(Export, {expr, Meta, [Condition], Expr}, Return, S) ->
{TCondition, SC} = (wrap_export_fun(Export, fun elixir_translator:translate/2))(Condition, S),
{TExpr, SB} = elixir_translator:translate_block(Expr, Return, SC),
{{clause, ?line(Meta), [TCondition], [], unblock(TExpr)}, SB}.
wrap_export_fun(Meta, Fun) ->
case lists:keyfind(export_head, 1, Meta) of
{export_head, true} ->
Fun;
_ ->
fun(Args, S) ->
{TArgs, TS} = Fun(Args, S),
{TArgs, TS#elixir_scope{export_vars = S#elixir_scope.export_vars}}
end
end.
% Check if the given expression is a match tuple.
% This is a small optimization to allow us to change
% existing assignments instead of creating new ones every time.
@@ -179,7 +188,7 @@ normalize_vars(Key, Value, #elixir_scope{vars=Vars,export_vars=ClauseVars} = S)
VS = S#elixir_scope{
vars=orddict:store(Key, Value, Vars),
export_vars=orddict:store(Key, Value, ClauseVars)
},
},
Expr = case orddict:find(Key, Vars) of
{ok, {PreValue, _}} -> {var, 0, PreValue};
+34 -38
View File
@@ -68,7 +68,9 @@ code_loading_compilation(Forms, Vars, #{line := Line} = E) ->
%% Pass {native, false} to speed up bootstrap
%% process when native is set to true
module(Form, ?m(E, file), [{native,false}], true, fun(_, Binary) ->
AllOpts = elixir_code_server:call(erl_compiler_options),
FinalOpts = AllOpts -- [native, warn_missing_spec],
module(Form, ?m(E, file), FinalOpts, true, fun(_, Binary) ->
%% If we have labeled locals, anonymous functions
%% were created and therefore we cannot ditch the
%% module
@@ -130,14 +132,13 @@ module(Forms, File, Opts, Callback) ->
Final =
case (get_opt(debug_info) == true) orelse
lists:member(debug_info, Opts) of
true -> [debug_info];
false -> []
true -> [debug_info] ++ elixir_code_server:call(erl_compiler_options);
false -> elixir_code_server:call(erl_compiler_options)
end,
module(Forms, File, Final, false, Callback).
module(Forms, File, RawOptions, Bootstrap, Callback) when
is_binary(File), is_list(Forms), is_list(RawOptions), is_boolean(Bootstrap), is_function(Callback) ->
Options = RawOptions ++ elixir_code_server:call(erl_compiler_options),
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),
case compile:noenv_forms([no_auto_import()|Forms], [return,{source,Listname}|Options]) of
@@ -172,38 +173,33 @@ core_file(File) ->
end.
core_main() ->
[
<<"lib/elixir/lib/kernel.ex">>,
<<"lib/elixir/lib/macro/env.ex">>,
<<"lib/elixir/lib/keyword.ex">>,
<<"lib/elixir/lib/module.ex">>,
<<"lib/elixir/lib/list.ex">>,
<<"lib/elixir/lib/record.ex">>,
<<"lib/elixir/lib/record/backend.ex">>,
<<"lib/elixir/lib/record/deprecated.ex">>,
<<"lib/elixir/lib/macro.ex">>,
<<"lib/elixir/lib/exception.ex">>,
<<"lib/elixir/lib/code.ex">>,
<<"lib/elixir/lib/module/locals_tracker.ex">>,
<<"lib/elixir/lib/protocol.ex">>,
<<"lib/elixir/lib/stream/reducers.ex">>,
<<"lib/elixir/lib/enum.ex">>,
<<"lib/elixir/lib/inspect/algebra.ex">>,
<<"lib/elixir/lib/inspect.ex">>,
<<"lib/elixir/lib/range.ex">>,
<<"lib/elixir/lib/regex.ex">>,
<<"lib/elixir/lib/string.ex">>,
<<"lib/elixir/lib/string/chars.ex">>,
<<"lib/elixir/lib/collectable.ex">>,
<<"lib/elixir/lib/io.ex">>,
<<"lib/elixir/lib/path.ex">>,
<<"lib/elixir/lib/system.ex">>,
<<"lib/elixir/lib/kernel/typespec.ex">>,
<<"lib/elixir/lib/kernel/cli.ex">>,
<<"lib/elixir/lib/kernel/error_handler.ex">>,
<<"lib/elixir/lib/kernel/parallel_compiler.ex">>,
<<"lib/elixir/lib/kernel/lexical_tracker.ex">>
].
[<<"lib/elixir/lib/kernel.ex">>,
<<"lib/elixir/lib/macro/env.ex">>,
<<"lib/elixir/lib/keyword.ex">>,
<<"lib/elixir/lib/module.ex">>,
<<"lib/elixir/lib/list.ex">>,
<<"lib/elixir/lib/macro.ex">>,
<<"lib/elixir/lib/code.ex">>,
<<"lib/elixir/lib/module/locals_tracker.ex">>,
<<"lib/elixir/lib/kernel/typespec.ex">>,
<<"lib/elixir/lib/exception.ex">>,
<<"lib/elixir/lib/protocol.ex">>,
<<"lib/elixir/lib/stream/reducers.ex">>,
<<"lib/elixir/lib/enum.ex">>,
<<"lib/elixir/lib/inspect/algebra.ex">>,
<<"lib/elixir/lib/inspect.ex">>,
<<"lib/elixir/lib/range.ex">>,
<<"lib/elixir/lib/regex.ex">>,
<<"lib/elixir/lib/string.ex">>,
<<"lib/elixir/lib/string/chars.ex">>,
<<"lib/elixir/lib/io.ex">>,
<<"lib/elixir/lib/path.ex">>,
<<"lib/elixir/lib/file.ex">>,
<<"lib/elixir/lib/system.ex">>,
<<"lib/elixir/lib/kernel/cli.ex">>,
<<"lib/elixir/lib/kernel/error_handler.ex">>,
<<"lib/elixir/lib/kernel/parallel_compiler.ex">>,
<<"lib/elixir/lib/kernel/lexical_tracker.ex">>].
binary_to_path({ModuleName, Binary}, CompilePath) ->
Path = filename:join(CompilePath, atom_to_list(ModuleName) ++ ".beam"),
+55 -30
View File
@@ -1,4 +1,4 @@
% Holds the logic responsible for functions definition (def(p) and defmacro(p)).
% 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,
@@ -129,20 +129,28 @@ make_struct_available(_, _, _, _) ->
%% Retrieve location from meta file or @file, otherwise nil
retrieve_location(Line, File, Module) ->
case not(elixir_compiler:get_opt(internal)) andalso
'Elixir.Module':get_attribute(Module, file) of
X when X == nil; X == false ->
if
is_binary(File) ->
{elixir_utils:characters_to_list(elixir_utils:relative_to_cwd(File)), Line};
true ->
nil
end;
case get_location_attribute(Module) of
nil when not is_binary(File) ->
nil;
nil ->
{normalize_location(File), Line};
X when is_binary(X) ->
'Elixir.Module':delete_attribute(Module, file),
{elixir_utils:characters_to_list(X), 0}
{normalize_location(X), 0};
{X, L} when is_binary(X) andalso is_integer(L) ->
'Elixir.Module':delete_attribute(Module, file),
{normalize_location(X), L}
end.
get_location_attribute(Module) ->
case elixir_compiler:get_opt(internal) of
true -> nil;
false -> 'Elixir.Module':get_attribute(Module, file)
end.
normalize_location(X) ->
elixir_utils:characters_to_list(elixir_utils:relative_to_cwd(X)).
%% Compile super
compile_super(Module, true, #{function := Function}) ->
@@ -158,16 +166,10 @@ translate_definition(Kind, Line, Module, Name, Args, Guards, Body, E) when is_in
{EArgs, EGuards, EBody, _} = elixir_exp_clauses:def(fun elixir_def_defaults:expand/2,
Args, Guards, expr_from_body(Line, Body), E),
%% Macros receive a special argument on invocation. Notice it does
%% not affect the arity of the stored function, but the clause
%% already contains it.
EAllArgs = case is_macro(Kind) of
true -> [{'_@CALLER', [{line,Line}], nil}|EArgs];
false -> EArgs
end,
Body == nil andalso check_args_for_bodyless_clause(Line, EArgs, E),
S = elixir_env:env_to_scope(E),
{Unpacked, Defaults} = elixir_def_defaults:unpack(Kind, Name, EAllArgs, S),
{Unpacked, Defaults} = elixir_def_defaults:unpack(Kind, Name, EArgs, S),
{Clauses, Super} = translate_clause(Body, Line, Kind, Unpacked, EGuards, EBody, S),
run_on_definition_callbacks(Kind, Line, Module, Name, EArgs, EGuards, EBody, E),
@@ -182,22 +184,30 @@ translate_clause(_, Line, Kind, Args, Guards, Body, S) ->
{TClause, TS} = elixir_clauses:clause(Line,
fun elixir_translator:translate_args/2, Args, Body, Guards, true, S),
%% Set __CALLER__ if used
FClause = case is_macro(Kind) andalso TS#elixir_scope.caller of
true ->
FBody = {'match', Line,
{'var', Line, '__CALLER__'},
elixir_utils:erl_call(Line, elixir_env, linify, [{var, Line, '_@CALLER'}])
},
setelement(5, TClause, [FBody|element(5, TClause)]);
false -> TClause
FClause = case is_macro(Kind) of
true ->
FArgs = {var, Line, '_@CALLER'},
MClause = setelement(3, TClause, [FArgs|element(3, TClause)]),
case TS#elixir_scope.caller of
true ->
FBody = {'match', Line,
{'var', Line, '__CALLER__'},
elixir_utils:erl_call(Line, elixir_env, linify, [{var, Line, '_@CALLER'}])
},
setelement(5, MClause, [FBody|element(5, TClause)]);
false ->
MClause
end;
false ->
TClause
end,
{[FClause], TS#elixir_scope.super}.
expr_from_body(_Line, nil) -> nil;
expr_from_body(_Line, nil) -> nil;
expr_from_body(_Line, [{do, Expr}]) -> Expr;
expr_from_body(Line, Else) -> {'try', [{line,Line}], [Else]}.
expr_from_body(Line, Else) -> {'try', [{line,Line}], [Else]}.
is_macro(defmacro) -> true;
is_macro(defmacrop) -> true;
@@ -327,6 +337,18 @@ defaults_conflict(A, D, Arity, Defaults) ->
((Arity >= (A - D)) andalso (Arity < A)) orelse
((A >= (Arity - Defaults)) andalso (A < Arity)).
check_args_for_bodyless_clause(Line, Args, E) ->
[ begin
elixir_errors:form_error(Line, ?m(E, file), ?MODULE, invalid_args_for_bodyless_clause)
end || Arg <- Args, invalid_arg(Arg) ].
invalid_arg({Name, _, Kind}) when is_atom(Name), is_atom(Kind) ->
false;
invalid_arg({'\\\\', _, [{Name, _, Kind}, _]}) when is_atom(Name), is_atom(Kind) ->
false;
invalid_arg(_) ->
true.
assert_no_aliases_name(Line, '__aliases__', [Atom], #{file := File}) when is_atom(Atom) ->
elixir_errors:form_error(Line, File, ?MODULE, {no_alias, Atom});
@@ -366,5 +388,8 @@ format_error({no_alias, Atom}) ->
format_error({invalid_def, Kind, NameAndArgs}) ->
io_lib:format("invalid syntax in ~ts ~ts", [Kind, 'Elixir.Macro':to_string(NameAndArgs)]);
format_error(invalid_args_for_bodyless_clause) ->
"can use only variables and \\\\ as arguments of bodyless clause";
format_error({missing_do, Kind}) ->
io_lib:format("missing do keyword in ~ts", [Kind]).
+19 -14
View File
@@ -21,17 +21,19 @@ unpack(Kind, Name, Args, S) ->
%% Unpack default from given args.
%% Returns the given arguments without their default
%% clauses and a list of clauses for the default calls.
unpack_each(Kind, Name, [{'\\\\', Line, [Expr, _]}|T] = List, Acc, Clauses, S) ->
Base = build_match(Acc, Line, []),
{Args, Invoke} = extract_defaults(List, Line, length(Base), [], []),
unpack_each(Kind, Name, [{'\\\\', DefMeta, [Expr, _]}|T] = List, Acc, Clauses, S) ->
Base = wrap_kind(Kind, build_match(Acc, [])),
{Args, Invoke} = extract_defaults(List, length(Base), [], []),
{DefArgs, SA} = elixir_clauses:match(fun elixir_translator:translate_args/2, Base ++ Args, S),
{DefInvoke, _} = elixir_translator:translate_args(Base ++ Invoke, SA),
Line = ?line(DefMeta),
Call = {call, Line,
{atom, Line, name_for_kind(Kind, Name)},
DefInvoke
},
},
Clause = {clause, Line, DefArgs, [], [Call]},
unpack_each(Kind, Name, T, [Expr|Acc], [Clause|Clauses], S);
@@ -44,25 +46,28 @@ unpack_each(_Kind, _Name, [], Acc, Clauses, _S) ->
% Extract default values from args following the current default clause.
extract_defaults([{'\\\\', _, [_Expr, Default]}|T], Line, Counter, NewArgs, NewInvoke) ->
extract_defaults(T, Line, Counter, NewArgs, [Default|NewInvoke]);
extract_defaults([{'\\\\', _, [_Expr, Default]}|T], Counter, NewArgs, NewInvoke) ->
extract_defaults(T, Counter, NewArgs, [Default|NewInvoke]);
extract_defaults([_|T], Line, Counter, NewArgs, NewInvoke) ->
H = {elixir_utils:atom_concat(["_@D", Counter]), Line, nil},
extract_defaults(T, Line, Counter + 1, [H|NewArgs], [H|NewInvoke]);
extract_defaults([_|T], Counter, NewArgs, NewInvoke) ->
H = {elixir_utils:atom_concat(["x", Counter]), [], nil},
extract_defaults(T, Counter + 1, [H|NewArgs], [H|NewInvoke]);
extract_defaults([], _Line, _Counter, NewArgs, NewInvoke) ->
extract_defaults([], _Counter, NewArgs, NewInvoke) ->
{lists:reverse(NewArgs), lists:reverse(NewInvoke)}.
% Build matches for all the previous argument until the current default clause.
build_match([], _Line, Acc) -> Acc;
build_match([], Acc) -> Acc;
build_match([_|T], Line, Acc) ->
Var = {elixir_utils:atom_concat(["_@D", length(T)]), Line, nil},
build_match(T, Line, [Var|Acc]).
build_match([_|T], Acc) ->
Var = {elixir_utils:atom_concat(["x", length(T)]), [], nil},
build_match(T, [Var|Acc]).
% Given the invoked function name based on the kind
wrap_kind(Kind, Args) when Kind == defmacro; Kind == defmacrop -> [{c, [], nil}|Args];
wrap_kind(_Kind, Args) -> Args.
name_for_kind(Kind, Name) when Kind == defmacro; Kind == defmacrop -> elixir_utils:macro_name(Name);
name_for_kind(_Kind, Name) -> Name.
+119 -75
View File
@@ -10,19 +10,25 @@
-include("elixir.hrl").
-import(ordsets, [is_element/2]).
-define(atom, 'Elixir.Atom').
-define(float, 'Elixir.Float').
-define(io, 'Elixir.IO').
-define(integer, 'Elixir.Integer').
-define(kernel, 'Elixir.Kernel').
-define(list, 'Elixir.List').
-define(map, 'Elixir.Map').
-define(node, 'Elixir.Node').
-define(process, 'Elixir.Process').
-define(string, 'Elixir.String').
-define(system, 'Elixir.System').
-define(tuple, 'Elixir.Tuple').
default_functions() ->
[ {?kernel, elixir_imported_functions()} ].
[{?kernel, elixir_imported_functions()}].
default_macros() ->
[ {?kernel, elixir_imported_macros()} ].
[{?kernel, elixir_imported_macros()}].
default_requires() ->
[ 'Elixir.Kernel', 'Elixir.Kernel.Typespec' ].
['Elixir.Kernel', 'Elixir.Kernel.Typespec'].
find_import(Meta, Name, Arity, E) ->
Tuple = {Name, Arity},
@@ -48,7 +54,7 @@ import_function(Meta, Name, Arity, E) ->
{function, Receiver} ->
elixir_lexical:record_import(Receiver, ?m(E, lexical_tracker)),
elixir_locals:record_import(Tuple, Receiver, ?m(E, module), ?m(E, function)),
remote_function(Receiver, Name, Arity, E);
remote_function(Meta, Receiver, Name, Arity, E);
{macro, _Receiver} ->
false;
{import, Receiver} ->
@@ -62,17 +68,19 @@ import_function(Meta, Name, Arity, E) ->
end
end.
require_function(_Meta, Receiver, Name, Arity, E) ->
require_function(Meta, Receiver, Name, Arity, E) ->
case is_element({Name, Arity}, get_optional_macros(Receiver)) of
true -> false;
false -> remote_function(Receiver, Name, Arity, E)
false -> remote_function(Meta, Receiver, Name, Arity, E)
end.
remote_function(Receiver, Name, Arity, E) ->
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
{AR, AN} -> {remote, AR, AN, Arity};
false -> {remote, Receiver, Name, Arity}
false -> {remote, Receiver, Name, Arity}
end.
%% Dispatches
@@ -82,8 +90,8 @@ dispatch_import(Meta, Name, Args, E, Callback) ->
case expand_import(Meta, {Name, Arity}, Args, E, []) of
{ok, Receiver, Quoted} ->
expand_quoted(Meta, Receiver, Name, Arity, Quoted, E);
{ok, Receiver} ->
elixir_exp:expand({{'.', [], [Receiver, Name]}, Meta, Args}, E);
{ok, Receiver, NewName, NewArgs} ->
elixir_exp:expand({{'.', [], [Receiver, NewName]}, Meta, NewArgs}, E);
error ->
Callback()
end.
@@ -139,33 +147,39 @@ 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, AR, AN, AA} ->
{ok, AR, {{'.', [], [AR, AN]}, [], AA}};
false ->
{ok, Receiver}
%% DEPRECATED
case {Receiver, Name, Arity} of
{?kernel, size, 1} ->
check_deprecation(Meta, Receiver, Name, Arity, E),
{ok, erlang, size, Args};
_ ->
case rewrite(Receiver, Name, Args, Arity) of
{ok, _, _, _} = Res -> Res;
false -> {ok, Receiver, Name, Args}
end
end;
{macro, Receiver} ->
check_deprecation(Meta, Receiver, Name, Arity, E),
elixir_lexical:record_import(Receiver, ?m(E, lexical_tracker)),
elixir_locals:record_import(Tuple, Receiver, Module, ?m(E, function)),
{ok, Receiver, expand_macro_named(Meta, Receiver, Name, Arity, Args, E)};
{import, Receiver} ->
case expand_require([{require,false}|Meta], Receiver, Tuple, Args, E) of
{ok, _, _} = Response -> Response;
error -> {ok, Receiver}
error -> {ok, Receiver, Name, Args}
end;
false when Module == ?kernel ->
case rewrite(Module, Name, Args, Arity) of
{ok, AR, AN, AA} ->
{ok, AR, {{'.', [], [AR, AN]}, [], AA}};
false ->
error
{ok, _, _, _} = Res -> Res;
false -> error
end;
false ->
error
end.
expand_require(Meta, Receiver, {Name, Arity} = Tuple, Args, E) ->
check_deprecation(Meta, Receiver, Name, Arity, E),
Module = ?m(E, module),
case is_element(Tuple, get_optional_macros(Receiver)) of
@@ -331,27 +345,12 @@ elixir_imported_macros() ->
error:undef -> []
end.
rewrite(?kernel, atom_to_binary, [Arg], _) ->
rewrite(?atom, to_string, [Arg], _) ->
{ok, erlang, atom_to_binary, [Arg, utf8]};
rewrite(?kernel, binary_to_atom, [Arg], _) ->
{ok, erlang, binary_to_atom, [Arg, utf8]};
rewrite(?kernel, binary_to_existing_atom, [Arg], _) ->
{ok, erlang, binary_to_existing_atom, [Arg, utf8]};
rewrite(?kernel, elem, [Tuple, Index], _) ->
{ok, erlang, element, [increment(Index), Tuple]};
rewrite(?kernel, set_elem, [Tuple, Index, Value], _) ->
rewrite(?kernel, put_elem, [Tuple, Index, Value], _) ->
{ok, erlang, setelement, [increment(Index), Tuple, Value]};
rewrite(?process, monitor, [Arg], _) ->
{ok, erlang, monitor, [process, Arg]};
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(?map, 'has_key?', [Map, Key], _) ->
{ok, maps, is_key, [Key, Map]};
rewrite(?map, fetch, [Map, Key], _) ->
@@ -360,6 +359,19 @@ 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};
@@ -371,6 +383,22 @@ increment(Number) when is_number(Number) ->
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, '+'};
@@ -392,26 +420,12 @@ inline(?kernel, '!==', 2) -> {erlang, '=/='};
inline(?kernel, abs, 1) -> {erlang, abs};
inline(?kernel, apply, 2) -> {erlang, apply};
inline(?kernel, apply, 3) -> {erlang, apply};
inline(?kernel, atom_to_list, 1) -> {erlang, atom_to_list};
inline(?kernel, binary_part, 3) -> {erlang, binary_part};
inline(?kernel, binary_to_float, 1) -> {erlang, binary_to_float};
inline(?kernel, binary_to_float, 2) -> {erlang, binary_to_float};
inline(?kernel, binary_to_integer, 1) -> {erlang, binary_to_integer};
inline(?kernel, binary_to_integer, 2) -> {erlang, binary_to_integer};
inline(?kernel, bit_size, 1) -> {erlang, bit_size};
inline(?kernel, bitstring_to_list, 1) -> {erlang, bitstring_to_list};
inline(?kernel, byte_size, 1) -> {erlang, byte_size};
inline(?kernel, 'div', 2) -> {erlang, 'div'};
inline(?kernel, exit, 1) -> {erlang, exit};
inline(?kernel, float_to_binary, 1) -> {erlang, float_to_binary};
inline(?kernel, float_to_list, 1) -> {erlang, float_to_list};
inline(?kernel, hd, 1) -> {erlang, hd};
inline(?kernel, integer_to_binary, 1) -> {erlang, integer_to_binary};
inline(?kernel, integer_to_binary, 2) -> {erlang, integer_to_binary};
inline(?kernel, integer_to_list, 1) -> {erlang, integer_to_list};
inline(?kernel, integer_to_list, 2) -> {erlang, integer_to_list};
inline(?kernel, iodata_length, 1) -> {erlang, iolist_size};
inline(?kernel, iodata_to_binary, 1) -> {erlang, iolist_to_binary};
inline(?kernel, is_atom, 1) -> {erlang, is_atom};
inline(?kernel, is_binary, 1) -> {erlang, is_binary};
inline(?kernel, is_bitstring, 1) -> {erlang, is_bitstring};
@@ -428,13 +442,6 @@ 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, list_to_atom, 1) -> {erlang, list_to_atom};
inline(?kernel, list_to_bitstring, 1) -> {erlang, list_to_bitstring};
inline(?kernel, list_to_existing_atom, 1) -> {erlang, list_to_existing_atom};
inline(?kernel, list_to_float, 1) -> {erlang, list_to_float};
inline(?kernel, list_to_integer, 1) -> {erlang, list_to_integer};
inline(?kernel, list_to_integer, 2) -> {erlang, list_to_integer};
inline(?kernel, list_to_tuple, 1) -> {erlang, list_to_tuple};
inline(?kernel, make_ref, 0) -> {erlang, make_ref};
inline(?kernel, map_size, 1) -> {erlang, map_size};
inline(?kernel, max, 2) -> {erlang, max};
@@ -445,7 +452,6 @@ 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, size, 1) -> {erlang, size};
inline(?kernel, spawn, 1) -> {erlang, spawn};
inline(?kernel, spawn, 3) -> {erlang, spawn};
inline(?kernel, spawn_link, 1) -> {erlang, spawn_link};
@@ -456,7 +462,19 @@ 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(?kernel, tuple_to_list, 1) -> {erlang, tuple_to_list};
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};
@@ -466,21 +484,47 @@ inline(?process, demonitor, 2) -> {erlang, demonitor};
inline(?process, link, 1) -> {erlang, link};
inline(?process, unlink, 1) -> {erlang, unlink};
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(?node, spawn_monitor, 2) -> {erlang, spawn_monitor};
inline(?node, spawn_monitor, 4) -> {erlang, spawn_monitor};
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(?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(?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;
Message ->
Warning = deprecation_message(Receiver, Name, Arity, Message),
elixir_errors:warn(?line(Meta), File, Warning)
end.
deprecation_message(Receiver, '__using__', _Arity, Message) ->
Warning = io_lib:format("use ~s is deprecated", [elixir_aliases:inspect(Receiver)]),
deprecation_message(Warning, Message);
deprecation_message(Receiver, Name, Arity, Message) ->
Warning = io_lib:format("~s.~s/~B is deprecated",
[elixir_aliases:inspect(Receiver), Name, Arity]),
deprecation_message(Warning, Message).
deprecation_message(Warning, Message) ->
case Message of
true -> Warning;
Message -> Warning ++ ", " ++ Message
end.
deprecation('Elixir.Kernel', 'size', 1) ->
"use byte_size/1 or tuple_size/1 instead";
% deprecation('Elixir.Mix.Generator', 'from_file', _) ->
% "instead pass [from_file: file] to embed_text/2 and embed_template/2 macros. "
% "Note that [from_file: file] expects paths relative to the current working "
% "directory and not to the current file";
% deprecation('Elixir.EEx.TransformerEngine', '__using__', _) ->
% "check EEx.SmartEngine for how to build custom engines";
% deprecation('Elixir.EEx.AssignsEngine', '__using__', _) ->
% "check EEx.SmartEngine for how to build custom engines";
deprecation(_, _, _) ->
false.
+15 -15
View File
@@ -3,8 +3,7 @@
-module(elixir_errors).
-export([compile_error/3, compile_error/4,
form_error/4, parse_error/4, warn/2, warn/3,
handle_file_warning/2, handle_file_warning/3, handle_file_error/2,
deprecation/3, deprecation/4]).
handle_file_warning/2, handle_file_warning/3, handle_file_error/2]).
-include("elixir.hrl").
-type line_or_meta() :: integer() | list().
@@ -21,7 +20,7 @@ warn(Warning) ->
warn(Caller, Warning) ->
warn([Caller, "warning: ", Warning]).
warn(Line, File, Warning) ->
warn(Line, File, Warning) when is_integer(Line) ->
warn(file_format(Line, File, "warning: " ++ Warning)).
%% Raised during expansion/translation/compilation.
@@ -56,23 +55,24 @@ parse_error(Meta, File, <<"syntax error before: ">>, <<"'end'">>) ->
raise(Meta, File, 'Elixir.SyntaxError', <<"unexpected token: end">>);
%% Binaries are wrapped in [<<...>>], so we need to unwrap them
parse_error(Meta, File, Error, <<"[<<", Token/binary>>) when is_binary(Error) ->
Rest = binary_part(Token, 0, byte_size(Token) - 3),
Message = <<Error / binary, Rest / binary >>,
raise(Meta, File, 'Elixir.SyntaxError', Message);
parse_error(Meta, File, Error, <<"[", _/binary>> = Full) when is_binary(Error) ->
Rest =
case binary:split(Full, <<"<<">>) of
[Lead, Token] ->
case binary:split(Token, <<">>">>) of
[Part, _] when Lead == <<$[>> -> Part;
_ -> <<$">>
end;
[_] ->
<<$">>
end,
raise(Meta, File, 'Elixir.SyntaxError', <<Error/binary, Rest/binary >>);
%% Everything else is fine as is
parse_error(Meta, File, Error, Token) when is_binary(Error), is_binary(Token) ->
Message = <<Error / binary, Token / binary >>,
raise(Meta, File, 'Elixir.SyntaxError', Message).
%% Shows a deprecation message
deprecation(Meta, File, Message) -> deprecation(Meta, File, Message, []).
deprecation(Meta, File, Message, Args) ->
io:format(file_format(?line(Meta), File, io_lib:format(Message, Args))).
%% Handle warnings and errors (called during module compilation)
%% Ignore on bootstrap
@@ -177,7 +177,7 @@ raise(Line, File, Kind, Message) when is_integer(Line), is_binary(File) ->
ok -> ok
end,
Stacktrace = erlang:get_stacktrace(),
Exception = Kind:new([{description, Message}, {file, File}, {line, Line}]),
Exception = Kind:exception([{description, Message}, {file, File}, {line, Line}]),
erlang:raise(error, Exception, tl(Stacktrace)).
file_format(0, File, Message) when is_binary(File) ->
+43 -43
View File
@@ -169,22 +169,15 @@ expand({quote, Meta, [KV, Do]}, E) when is_list(Do) ->
false -> compile_error(Meta, E#elixir_scope.file, "missing do keyword in quote")
end,
ValidOpts = [hygiene, context, var_context, location, line, unquote, bind_quoted],
ValidOpts = [context, location, line, unquote, bind_quoted],
{EKV, ET} = expand_opts(Meta, quote, ValidOpts, KV, E),
Hygiene = case lists:keyfind(hygiene, 1, EKV) of
{hygiene, List} when is_list(List) ->
List;
false ->
[]
end,
Context = case lists:keyfind(context, 1, EKV) of
{context, Atom} when is_atom(Atom) ->
Atom;
{context, Ctx} when is_atom(Ctx) and (Ctx /= nil) ->
Ctx;
{context, Ctx} ->
compile_error(Meta, ?m(E, file), "invalid :context for quote, "
"expected a compile time atom or alias, got: ~ts", ['Elixir.Kernel':inspect(Ctx)]);
"expected non nil compile time atom or alias, got: ~ts", ['Elixir.Kernel':inspect(Ctx)]);
false ->
case ?m(E, module) of
nil -> 'Elixir';
@@ -192,10 +185,6 @@ expand({quote, Meta, [KV, Do]}, E) when is_list(Do) ->
end
end,
Vars = lists:keyfind(vars, 1, Hygiene) /= {vars, false},
Aliases = lists:keyfind(aliases, 1, Hygiene) /= {aliases, false},
Imports = lists:keyfind(imports, 1, Hygiene) /= {imports, false},
Keep = lists:keyfind(location, 1, EKV) == {location, keep},
Line = proplists:get_value(line, EKV, false),
@@ -209,8 +198,7 @@ expand({quote, Meta, [KV, Do]}, E) when is_list(Do) ->
false -> DefaultUnquote
end,
Q = #elixir_quote{vars_hygiene=Vars, line=Line, keep=Keep, unquote=Unquote,
aliases_hygiene=Aliases, imports_hygiene=Imports, context=Context},
Q = #elixir_quote{line=Line, keep=Keep, unquote=Unquote, context=Context},
{Quoted, _Q} = elixir_quote:quote(Exprs, Binding, Q, ET),
expand(Quoted, ET);
@@ -237,11 +225,22 @@ expand({fn, Meta, Pairs}, E) ->
%% Case/Receive/Try
expand({'cond', Meta, [KV]}, E) ->
assert_no_match_or_guard_scope(Meta, 'cond', E),
{EClauses, EC} = elixir_exp_clauses:'cond'(Meta, KV, E),
{{'cond', Meta, [EClauses]}, EC};
expand({'case', Meta, [Expr, KV]}, E) ->
assert_no_match_or_guard_scope(Meta, 'case', E),
{EExpr, EE} = expand(Expr, E),
{EClauses, EC} = elixir_exp_clauses:'case'(Meta, KV, EE),
{{'case', Meta, [EExpr, EClauses]}, EC};
FClauses =
case (lists:keyfind(optimize_boolean, 1, Meta) == {optimize_boolean, true}) and
elixir_utils:returns_boolean(EExpr) of
true -> rewrite_case_clauses(EClauses);
false -> EClauses
end,
{{'case', Meta, [EExpr, FClauses]}, EC};
expand({'receive', Meta, [KV]}, E) ->
assert_no_match_or_guard_scope(Meta, 'receive', E),
@@ -255,11 +254,7 @@ expand({'try', Meta, [KV]}, E) ->
%% Comprehensions
expand({Kind, Meta, Args}, E) when is_list(Args), (Kind == lc) orelse (Kind == bc) ->
elixir_errors:deprecation(Meta, ?m(E, file), "~ts is deprecated, please use for comprehensions instead", [Kind]),
expand_comprehension(Meta, Kind, Args, E);
expand({for, Meta, Args}, E) when is_list(Args) ->
expand({for, Meta, [_|_] = Args}, E) ->
elixir_for:expand(Meta, Args, E);
%% Super
@@ -288,7 +283,7 @@ expand({'_', _, Kind} = Var, E) when is_atom(Kind) ->
expand({Name, Meta, Kind} = Var, #{context := match, export_vars := Export} = E) when is_atom(Name), is_atom(Kind) ->
Pair = {Name, var_kind(Meta, Kind)},
NewVars = ordsets:add_element(Pair, ?m(E, vars)),
NewExport = case (Export /= nil) andalso (lists:keyfind(export, 1, Meta) /= {export, false}) of
NewExport = case (Export /= nil) of
true -> ordsets:add_element(Pair, Export);
false -> Export
end,
@@ -417,6 +412,9 @@ expand_arg(Arg, {Acc1, Acc2}) ->
{EArg, EAcc} = expand(Arg, Acc1),
{EArg, {elixir_env:mergea(Acc1, EAcc), elixir_env:mergev(Acc2, EAcc)}}.
expand_args([Arg], E) ->
{EArg, EE} = expand(Arg, E),
{[EArg], EE};
expand_args(Args, #{context := match} = E) ->
expand_many(Args, E);
expand_args(Args, E) ->
@@ -539,27 +537,29 @@ expand_as({as, Other}, Meta, _IncludeByDefault, _Ref, E) ->
compile_error(Meta, ?m(E, file),
"invalid value for keyword :as, expected an alias, got: ~ts", ['Elixir.Macro':to_string(Other)]).
%% Comprehensions
expand_comprehension(Meta, Kind, Args, E) ->
case elixir_utils:split_last(Args) of
{Cases, [{do,Expr}]} ->
{ECases, EC} = lists:mapfoldl(fun expand_comprehension_clause/2, E, Cases),
{EExpr, _} = expand(Expr, EC),
{{Kind, Meta, ECases ++ [[{do,EExpr}]]}, E};
_ ->
compile_error(Meta, ?m(E, file), "missing do keyword in comprehension ~ts", [Kind])
end.
expand_comprehension_clause({Gen, Meta, [Left, Right]}, E) when Gen == inbits; Gen == inlist ->
{ERight, ER} = expand(Right, E),
{ELeft, EL} = elixir_exp_clauses:match(fun expand/2, Left, E),
{{Gen, Meta, [ELeft, ERight]}, elixir_env:mergev(EL, ER)};
expand_comprehension_clause(X, E) ->
expand(X, E).
%% Assertions
rewrite_case_clauses([{do,[
{'->', FalseMeta, [
[{'when', _, [Var, {'__op__', _,[
'orelse',
{{'.', _, [erlang, '=:=']}, _, [Var, nil]},
{{'.', _, [erlang, '=:=']}, _, [Var, false]}
]}]}],
FalseExpr
]},
{'->', TrueMeta, [
[{'_', _, _}],
TrueExpr
]}
]}]) ->
[{do, [
{'->', FalseMeta, [[false], FalseExpr]},
{'->', TrueMeta, [[true], TrueExpr]}
]}];
rewrite_case_clauses(Clauses) ->
Clauses.
assert_no_match_or_guard_scope(Meta, Kind, E) ->
assert_no_match_scope(Meta, Kind, E),
assert_no_guard_scope(Meta, Kind, E).
+57 -43
View File
@@ -1,7 +1,8 @@
%% Handle code related to args, guard and -> matching for case,
%% fn, receive and friends. try is handled in elixir_try.
-module(elixir_exp_clauses).
-export([match/3, clause/5, 'case'/3, 'receive'/3, 'try'/3, def/5]).
-export([match/3, clause/5, def/5, head/2,
'case'/3, 'receive'/3, 'try'/3, 'cond'/3]).
-import(elixir_errors, [compile_error/3, compile_error/4]).
-include("elixir.hrl").
@@ -18,19 +19,19 @@ def(Fun, Args, Guards, Body, E) ->
clause(Meta, Kind, Fun, {'->', ClauseMeta, [_, _]} = Clause, E) when is_function(Fun, 3) ->
clause(Meta, Kind, fun(X, Acc) -> Fun(ClauseMeta, X, Acc) end, Clause, E);
clause(_Meta, _Kind, Fun, {'->', Meta, [Left, Right]}, E) ->
{ELeft, EL} = head(Fun, Left, E),
{ELeft, EL} = Fun(Left, E),
{ERight, ER} = elixir_exp:expand(Right, EL),
{{'->', Meta, [ELeft, ERight]}, ER};
clause(Meta, Kind, _Fun, _, E) ->
compile_error(Meta, ?m(E, file), "expected -> clauses in ~ts", [Kind]).
head(Fun, [{'when', Meta, [_,_|_] = All}], E) ->
head([{'when', Meta, [_,_|_] = All}], E) ->
{Args, Guard} = elixir_utils:split_last(All),
{EArgs, EA} = match(Fun, Args, E),
{EArgs, EA} = match(fun elixir_exp:expand_args/2, Args, E),
{EGuard, EG} = guard(Guard, EA#{context := guard}),
{[{'when', Meta, EArgs ++ [EGuard]}], EG#{context := ?m(E, context)}};
head(Fun, Args, E) ->
match(Fun, Args, E).
head(Args, E) ->
match(fun elixir_exp:expand_args/2, Args, E).
guard({'when', Meta, [Left, Right]}, E) ->
{ELeft, EL} = guard(Left, E),
@@ -51,10 +52,28 @@ guard(Other, E) ->
{EClauses, elixir_env:mergev(EVars, E)}.
do_case(Meta, {'do', _} = Do, Acc, E) ->
expand_with_export(Meta, 'case', expand_arg(Meta, 'case', 'do'), Do, Acc, E);
Fun = expand_one(Meta, 'case', 'do', fun head/2),
expand_with_export(Meta, 'case', Fun, Do, Acc, E);
do_case(Meta, {Key, _}, _Acc, E) ->
compile_error(Meta, ?m(E, file), "unexpected keyword ~ts in case", [Key]).
%% Cond
'cond'(Meta, [], E) ->
compile_error(Meta, ?m(E, file), "missing do keyword in cond");
'cond'(Meta, KV, E) when not is_list(KV) ->
compile_error(Meta, ?m(E, file), "invalid arguments for cond");
'cond'(Meta, KV, E) ->
EE = E#{export_vars := []},
{EClauses, EVars} = lists:mapfoldl(fun(X, Acc) -> do_cond(Meta, X, Acc, EE) end, [], KV),
{EClauses, elixir_env:mergev(EVars, E)}.
do_cond(Meta, {'do', _} = Do, Acc, E) ->
Fun = expand_one(Meta, 'cond', 'do', fun elixir_exp:expand_args/2),
expand_with_export(Meta, 'cond', Fun, Do, Acc, E);
do_cond(Meta, {Key, _}, _Acc, E) ->
compile_error(Meta, ?m(E, file), "unexpected keyword ~ts in cond", [Key]).
%% Receive
'receive'(Meta, [], E) ->
@@ -69,12 +88,11 @@ do_case(Meta, {Key, _}, _Acc, E) ->
do_receive(_Meta, {'do', nil} = Do, Acc, _E) ->
{Do, Acc};
do_receive(Meta, {'do', _} = Do, Acc, E) ->
expand_with_export(Meta, 'receive', expand_arg(Meta, 'receive', 'do'), Do, Acc, E);
do_receive(_Meta, {'after', [{'->', Meta, [[Left], Right]}]}, Acc, E) ->
{ELeft, EL} = elixir_exp:expand(Left, E),
{ERight, ER} = elixir_exp:expand(Right, EL),
EClause = {'after', [{'->', Meta, [[ELeft], ERight]}]},
{EClause, elixir_env:merge_vars(Acc, ?m(ER, export_vars))};
Fun = expand_one(Meta, 'receive', 'do', fun head/2),
expand_with_export(Meta, 'receive', Fun, Do, Acc, E);
do_receive(Meta, {'after', [_]} = After, Acc, E) ->
Fun = expand_one(Meta, 'receive', 'after', fun elixir_exp:expand_args/2),
expand_with_export(Meta, 'receive', Fun, After, Acc, E);
do_receive(Meta, {'after', _}, _Acc, E) ->
compile_error(Meta, ?m(E, file), "expected a single -> clause for after in receive");
do_receive(Meta, {Key, _}, _Acc, E) ->
@@ -96,14 +114,22 @@ do_try(_Meta, {'after', Expr}, E) ->
{EExpr, _} = elixir_exp:expand(Expr, E),
{'after', EExpr};
do_try(Meta, {'else', _} = Else, E) ->
expand_without_export(Meta, 'try', expand_arg(Meta, 'try', 'else'), Else, E);
Fun = expand_one(Meta, 'try', 'else', fun head/2),
expand_without_export(Meta, 'try', Fun, Else, E);
do_try(Meta, {'catch', _} = Catch, E) ->
expand_without_export(Meta, 'try', fun elixir_exp:expand_args/2, Catch, E);
expand_without_export(Meta, 'try', fun expand_catch/3, Catch, E);
do_try(Meta, {'rescue', _} = Rescue, E) ->
expand_without_export(Meta, 'try', fun expand_rescue/3, Rescue, E);
do_try(Meta, {Key, _}, E) ->
compile_error(Meta, ?m(E, file), "unexpected keyword ~ts in try", [Key]).
expand_catch(_Meta, [_] = Args, E) ->
head(Args, E);
expand_catch(_Meta, [_, _] = Args, E) ->
head(Args, E);
expand_catch(Meta, _, E) ->
compile_error(Meta, ?m(E, file), "expected one or two args for catch clauses (->) in try").
expand_rescue(Meta, [Arg], E) ->
case expand_rescue(Arg, E) of
{EArg, EA} ->
@@ -121,14 +147,14 @@ expand_rescue({Name, _, Atom} = Var, E) when is_atom(Name), is_atom(Atom) ->
%% rescue var in [Exprs]
expand_rescue({in, Meta, [Left, Right]}, E) ->
{ERight, ER} = elixir_exp:expand(Right, E#{context := nil}),
{ELeft, EL} = elixir_exp:expand(Left, ER#{context := match}),
{ELeft, EL} = match(fun elixir_exp:expand/2, Left, E),
{ERight, ER} = elixir_exp:expand(Right, EL),
case ELeft of
{Name, _, Atom} when is_atom(Name), is_atom(Atom) ->
case normalize_rescue(ERight) of
false -> false;
Other -> {{in, Meta, [ELeft, Other]}, EL}
Other -> {{in, Meta, [ELeft, Other]}, ER}
end;
_ ->
false
@@ -141,44 +167,32 @@ expand_rescue(Arg, E) ->
normalize_rescue({'_', _, Atom} = N) when is_atom(Atom) -> N;
normalize_rescue(Atom) when is_atom(Atom) -> [Atom];
normalize_rescue(Other) ->
is_list(Other)
andalso lists:all(fun is_var_or_atom/1, Other)
andalso Other.
is_var_or_atom({Name, _, Atom}) when is_atom(Name), is_atom(Atom) -> true;
is_var_or_atom(Atom) when is_atom(Atom) -> true;
is_var_or_atom(_) -> false.
is_list(Other) andalso lists:all(fun is_atom/1, Other) andalso Other.
%% Expansion helpers
export_vars({Left, Meta, Right}) when is_atom(Left), is_list(Meta), is_atom(Right) ->
{Left, [{export,false}|Meta], Right};
export_vars({Left, Meta, Right}) ->
{export_vars(Left), Meta, export_vars(Right)};
export_vars({Left, Right}) ->
{export_vars(Left), export_vars(Right)};
export_vars(List) when is_list(List) ->
[export_vars(X) || X <- List];
export_vars(Other) ->
Other.
%% Returns a function that expands arguments
%% considering we have at maximum one entry.
expand_arg(Meta, Kind, Key) ->
expand_one(Meta, Kind, Key, Fun) ->
fun
([Arg], E) ->
{EArg, EA} = elixir_exp:expand(Arg, E),
{[EArg], EA};
([_] = Args, E) ->
Fun(Args, E);
(_, E) ->
compile_error(Meta, ?m(E, file), "expected one arg for ~ts clauses (->) in ~ts", [Key, Kind])
compile_error(Meta, ?m(E, file),
"expected one arg for ~ts clauses (->) in ~ts", [Key, Kind])
end.
%% Expands all -> pairs in a given key keeping the overall vars.
expand_with_export(Meta, Kind, Fun, {Key, Clauses}, Acc, E) when is_list(Clauses) ->
EFun =
case lists:keyfind(export_head, 1, Meta) of
{export_head, true} -> Fun;
_ -> fun(ExportArgs, ExportE) -> Fun(export_vars(ExportArgs), ExportE) end
{export_head, true} ->
Fun;
_ ->
fun(Args, #{export_vars := ExportVars} = EE) ->
{FArgs, FE} = Fun(Args, EE),
{FArgs, FE#{export_vars := ExportVars}}
end
end,
Transformer = fun(Clause, Vars) ->
{EClause, EC} = clause(Meta, Kind, EFun, Clause, E),

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