Compare commits

...
314 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
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
chyndman 377c64d1b0 Mix.Utils.symlink_or_copy always copies on Windows 2014-05-23 10:18:11 -07:00
264 changed files with 11293 additions and 6851 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
+139 -31
View File
@@ -1,4 +1,112 @@
# v0.13.3 (2014-05-24)
# 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
@@ -46,7 +154,7 @@
* [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)
## v0.13.2 (2014-05-11)
* Enhancements
* [Application] Add an Application module with common functions to work with OTP applications
@@ -55,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`
@@ -98,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
@@ -124,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
@@ -175,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
@@ -193,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
@@ -207,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
@@ -240,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
@@ -269,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`
@@ -293,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
@@ -330,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
@@ -339,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
@@ -357,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
@@ -404,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`
@@ -439,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
@@ -502,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`
@@ -552,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
@@ -596,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`
@@ -634,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
@@ -659,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
@@ -675,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
@@ -688,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
@@ -740,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
@@ -780,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
@@ -807,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
@@ -849,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
@@ -875,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
@@ -906,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
@@ -955,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
@@ -1005,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:
+10 -7
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
@@ -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."
@@ -170,6 +172,7 @@ test_doc_test: compile
test_stdlib: compile
@ echo "==> elixir (exunit)"
$(Q) exec epmd & exit
$(Q) cd lib/elixir && ../../bin/elixir -r "test/elixir/test_helper.exs" -pr "test/elixir/**/*_test.exs";
.dialyzer.base_plt:
+1 -1
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]
+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.3
0.14.2
+2 -6
View File
@@ -17,7 +17,6 @@ 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
@@ -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_OPTIONS $ERL -extra "$@"
exec "$ERL_PATH" -pa "$SCRIPT_PATH"/../lib/*/ebin $ELIXIR_ERL_OPTIONS $ERL -extra "$@"
+2 -4
View File
@@ -23,7 +23,6 @@ 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
@@ -68,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)
@@ -91,7 +89,7 @@ for /d %%d in ("%originPath%..\lib\*.") do (
SETLOCAL disabledelayedexpansion
:run
IF %useWerl% EQU 1 (
werl %ext_libs% -noshell %ELIXIR_ERL_OPTIONS% %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_OPTIONS% %parsErlang% -s elixir start_cli %beforeExtra% -extra %*
erl.exe %ext_libs% -noshell %ELIXIR_ERL_OPTIONS% %parsErlang% -s elixir start_cli %beforeExtra% -extra %*
)
+1 -1
View File
@@ -11,7 +11,7 @@ if [ $# -eq 0 ] || [ "$1" = "--help" ] || [ "$1" = "-h" ]; then
** Options given after -- are passed down to the executed code
** Options can be passed to the erlang runtime using ELIXIR_ERL_OPTIONS
** Options can be passed to the erlang compiler using ERL_COMPILER_OPTS" >&2
** Options can be passed to the erlang compiler using ERL_COMPILER_OPTIONS" >&2
exit 1
fi
+1 -1
View File
@@ -21,6 +21,6 @@ echo --verbose Print informational messages.
echo.
echo ** Options given after -- are passed down to the executed code
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_OPTS >&2
echo ** Options can be passed to the erlang compiler using ERL_COMPILER_OPTIONS >&2
:run
call "%~dp0\elixir.bat" +elixirc %*
-1
View File
@@ -16,7 +16,6 @@ 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
+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
+21 -19
View File
@@ -14,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
@@ -83,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 %>
@@ -93,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.
@@ -121,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.
@@ -148,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))
+1 -1
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 """
+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
+5 -5
View File
@@ -5,11 +5,11 @@ 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
+23 -13
View File
@@ -2,7 +2,7 @@ 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))}
@@ -12,6 +12,10 @@ defmodule EExText.Compiled do
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, "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
+15 -13
View File
@@ -3,8 +3,8 @@ defmodule Agent do
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 a same process
in different points in time.
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.
@@ -35,12 +35,12 @@ defmodule Agent do
end
end
Note that agents still provide a segregation in between the
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 fullfilled.
effectively block the agent until the request is fulfilled.
Consider these two examples:
@@ -60,7 +60,7 @@ defmodule Agent do
sent to the client cheaply or large enough to require processing on
the server (or at least some initial processing).
## Name registration
## Name Registration
An Agent is bound to the same name registration rules as GenServers.
Read more about it in the `GenServer` docs.
@@ -68,13 +68,13 @@ defmodule Agent do
## A word on distributed agents
It is important to consider the limitations of distributed agents. Agents
work by sending anonymous functions in between the caller and the agent.
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 by
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
@@ -122,20 +122,22 @@ defmodule Agent do
## 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 amount of milliseconds on initialization or it will
be terminated and the start function will return `{:error, :timeout}`.
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/3`.
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
`{: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.
@@ -176,7 +178,7 @@ defmodule Agent do
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)
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).
+1 -1
View File
@@ -44,7 +44,7 @@ defmodule Agent.Server do
# There is a race condition if the agent is
# restarted too fast and it is registered.
try do
self |> Process.info(:registered_name) |> elem(1) |> Process.unregister
self |> :erlang.process_info(:registered_name) |> elem(1) |> Process.unregister
rescue
_ -> :ok
end
+16 -14
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
-16
View File
@@ -1,16 +0,0 @@
defmodule Application.Behaviour do
@moduledoc false
defmacro __using__(_) do
quote location: :keep do
@behaviour :application
@doc false
def stop(_state) do
:ok
end
defoverridable [stop: 1]
end
end
end
+226 -98
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'
@@ -29,41 +112,82 @@ defmodule Base do
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: <<>>
+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
+98 -26
View File
@@ -59,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
@@ -198,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
@@ -245,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 """
@@ -308,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.
@@ -315,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
@@ -448,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.
+24 -24
View File
@@ -40,7 +40,7 @@ defmodule Dict do
For example:
defmodule MyDict do
use Dict.Behaviour
use Dict
# implement required functions (see below)
# override default implementations if optimization
@@ -49,11 +49,11 @@ defmodule Dict do
The client module must contain the following functions:
* `delete/2`
* `fetch/2`
* `put/3`
* `reduce/3`
* `size/1`
* `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.
@@ -61,24 +61,24 @@ defmodule Dict do
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`
* `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.
-9
View File
@@ -1,9 +0,0 @@
defmodule Dict.Behaviour do
@moduledoc false
defmacro __using__(_) do
quote do
use Dict
end
end
end
+51 -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`
@@ -937,13 +937,13 @@ defmodule Enum do
def join(collection, joiner) when is_binary(joiner) do
reduced = reduce(collection, :first, fn
entry, :first -> to_string(entry)
entry, acc -> acc <> joiner <> to_string(entry)
entry, :first -> enum_to_string(entry)
entry, acc -> [acc, joiner|enum_to_string(entry)]
end)
if reduced == :first do
""
else
reduced
IO.iodata_to_binary reduced
end
end
@@ -996,14 +996,14 @@ defmodule Enum do
def map_join(collection, joiner, mapper) when is_binary(joiner) do
reduced = reduce(collection, :first, fn
entry, :first -> to_string(mapper, entry)
entry, acc -> acc <> joiner <> to_string(mapper, entry)
entry, :first -> enum_to_string(mapper.(entry))
entry, acc -> [acc, joiner|enum_to_string(mapper.(entry))]
end)
if reduced == :first do
""
else
reduced
IO.iodata_to_binary reduced
end
end
@@ -1417,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
@@ -1436,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 ->
@@ -1445,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 """
@@ -1468,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
@@ -1489,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
@@ -1816,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)}
@@ -1826,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
@@ -2142,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
@@ -2195,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__}
+15 -3
View File
@@ -578,6 +578,14 @@ defmodule CaseClauseError do
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]
@@ -632,7 +640,7 @@ defmodule Code.LoadError do
defexception [:file, :message]
def exception(opts) do
file = opts[:file]
file = Keyword.fetch!(opts, :file)
%Code.LoadError{message: "could not load #{file}", file: file}
end
end
@@ -663,8 +671,8 @@ defmodule UnicodeConversionError do
def exception(opts) do
%UnicodeConversionError{
encoded: opts[:encoded],
message: "#{opts[:kind]} #{detail(opts[:rest])}"
encoded: Keyword.fetch!(opts, :encoded),
message: "#{Keyword.fetch!(opts, :kind)} #{detail Keyword.fetch!(opts, :rest)}"
}
end
@@ -733,6 +741,10 @@ defmodule ErlangError do
%SystemLimitError{}
end
def normalize(:cond_clause, _stacktrace) do
%CondClauseError{}
end
def normalize({:badarity, {fun, args}}, _stacktrace) do
%BadArityError{function: fun, args: args}
end
+133 -227
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(IO.chardata_to_string(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(IO.chardata_to_string(path))
:elixir_utils.read_file_type(IO.chardata_to_string(path)) == {:ok, :directory}
end
@doc """
@@ -267,12 +125,13 @@ 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
@@ -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 """
@@ -326,13 +210,13 @@ 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.
"""
@@ -359,15 +243,14 @@ 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}
@@ -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(IO.chardata_to_string(path), content, [:raw|modes])
F.write_file(IO.chardata_to_string(path), content, modes)
end
@doc """
@@ -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(IO.chardata_to_string(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 """
@@ -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
@@ -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
@@ -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,6 +938,7 @@ 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
@@ -1034,6 +953,7 @@ 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
@@ -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: IO.chardata_to_string(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 """
+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
+8 -6
View File
@@ -192,9 +192,10 @@ defmodule Float do
## 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)
* `: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
@@ -230,9 +231,10 @@ defmodule Float do
## 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)
* `: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
+181 -98
View File
@@ -67,60 +67,62 @@ defmodule GenEvent do
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
* `init(args)` - invoked when the event handler is added.
It must return:
It must return:
{:ok, state}
{:ok, state, :hibernate}
{:error, reason}
- `{:ok, state}`
- `{:ok, state, :hibernate}`
- `{:error, reason}`
* `handle_event(msg, state)` - invoked whenever an event is sent via
`notify/2` or `sync_notify/2`.
* `handle_event(msg, state)` - invoked whenever an event is sent via
`notify/2` or `sync_notify/2`.
It must return:
It must return:
{:ok, new_state}
{:ok, new_state, :hibernate}
{:swap_handler, args1, new_state, handler2, args2}
:remove_handler
- `{: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.
* `handle_call(msg, state)` - invoked when a `call/3` is done to a specific
handler.
It must return:
It must return:
{:ok, reply, new_state}
{:ok, reply, new_state, :hibernate}
{:swap_handler, reply, args1, new_state, handler2, args2}
{:remove_handler, reply}
- `{: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`;
* `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:
It must return:
{:noreply, state}
{:noreply, state, timeout}
{:stop, reason, state}
- `{: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.
* `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 swap).
* `code_change(old_vsn, state, extra)` - called when the application
code is being upgraded live (hot code swapping).
It must return:
It must return:
{:ok, new_state}
- `{:ok, new_state}`
## Name registering
## Name Registration
A GenEvent is bound to the same name registering rules as a `GenServer`.
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`.
`GenEvent`s can be streamed from and streamed with the help of `stream/2`.
Here are some examples:
stream = GenEvent.stream(pid)
@@ -128,7 +130,7 @@ defmodule GenEvent do
# Take the next 10 events
Enum.take(stream, 10)
# Print all other events
# Print all remaining events
for event <- stream do
IO.inspect event
end
@@ -138,13 +140,13 @@ defmodule GenEvent do
## Learn more
In case you desire to learn more about gen events, Elixir getting started
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
* 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"
@@ -168,12 +170,13 @@ defmodule GenEvent do
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`
* `: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
defstruct manager: nil, id: nil, timeout: :infinity, duration: :infinity, mode: :sync
@doc false
defmacro __using__(_) do
@@ -218,15 +221,19 @@ defmodule GenEvent do
@doc """
Starts an event manager linked to the current process.
This is often used to start the `GenServer` as part of a supervision tree.
This is often used to start the `GenEvent` as part of a supervision tree.
It accepts a the `:name` which is described under the `Name Registering`
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
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
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
@@ -236,7 +243,7 @@ defmodule GenEvent do
@doc """
Starts an event manager process without links (outside of a supervision tree).
See `spawn_link/1` for more information.
See `start_link/1` for more information.
"""
@spec start(options) :: on_start
def start(options \\ []) when is_list(options) do
@@ -260,17 +267,25 @@ defmodule GenEvent do
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;
* `: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)}
duration: Keyword.get(options, :duration, :infinity),
mode: Keyword.get(options, :mode, :sync)}
end
@doc """
@@ -287,7 +302,7 @@ defmodule GenEvent do
## Linked handlers
When adding a handler, a `:link` option may be given as true.
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
@@ -296,18 +311,18 @@ defmodule GenEvent do
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;
* `: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;
* `: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;
* `{: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;
* 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}
@@ -406,7 +421,7 @@ defmodule GenEvent do
terminate with a given reason.
If there was a linked connection between handler1 and a process pid, there
will be a link connection between handle2 and pid instead. A new link in
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.
@@ -441,16 +456,31 @@ defimpl Enumerable, for: GenEvent do
use GenEvent
@doc false
def init({mon_pid, pid, ref}) do
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, {pid, ref}}
{:ok, state}
end
@doc false
def handle_event(event, {pid, ref} = state) do
send pid, {ref, event}
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
@@ -458,7 +488,7 @@ defimpl Enumerable, for: GenEvent 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, fun)
Stream.resource(start_fun, next_fun, stop_fun).(acc, wrap_reducer(fun))
end
def count(_stream) do
@@ -469,38 +499,63 @@ defimpl Enumerable, for: GenEvent do
{:error, __MODULE__}
end
defp start(%{manager: manager, id: id, duration: duration} = stream) do
{mon_pid, mon_ref} = add_handler(manager, id, duration)
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, manager_pid}
# The subscription process died due to an abnormal reason.
{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, _manager_pid} = acc) do
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
{^mon_ref, event} -> {event, acc}
{:DOWN, ^mon_ref, _, _, :normal} -> nil
{: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(%{id: id}, {mon_ref, manager_pid}) do
remove_handler(mon_ref, manager_pid, id)
flush_events(mon_ref)
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(manager, id, duration) do
defp add_handler(mode, manager, id, duration) do
parent = self()
# The subscription is managed by another process, that dies if
@@ -522,13 +577,17 @@ defimpl Enumerable, for: GenEvent do
cancel = cancel_ref(id, mon_ref)
:ok = :gen_event.add_sup_handler(manager, {__MODULE__, cancel},
{self(), parent, mon_ref})
{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)
@@ -554,26 +613,50 @@ defimpl Enumerable, for: GenEvent do
defp cancel_ref(nil, mon_ref), do: mon_ref
defp cancel_ref(id, mon_ref), do: {id, mon_ref}
defp remove_handler(mon_ref, manager_pid, id) do
Process.demonitor(mon_ref, [:flush])
handler = {__MODULE__, cancel_ref(id, mon_ref)}
# handler may nolonger be there, if it is the removal will cause the monitor
# process to exit. If this returns successfuly then no more events will be
# forwarded.
_ = :gen_event.delete_handler(manager_pid, handler, :remove_handler)
catch
# Do not want to overide the exit reason of the mon_pid so catch errors.
# However if the exit is due to a disconnection, exit because messages could
# leak if the nodes are reconnected before the manager on the other node
# removes the handler. In this case it is very likely that the mon_pid
# exited with the same reason.
:exit, reason when reason !== {:nodedown, node(manager_pid)} ->
:ok
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)
{^mon_ref, _, _} ->
flush_events(mon_ref)
after
0 -> :ok
end
-45
View File
@@ -1,45 +0,0 @@
defmodule GenEvent.Behaviour do
@moduledoc false
@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
+77 -71
View File
@@ -45,14 +45,14 @@ defmodule GenServer do
GenServer.call(pid, :pop)
#=> :world
We start our `Stack` by calling `start_link/2`, passing the module
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 is therefore synchronous)
messages expect a reply from the server (and are therefore synchronous)
while **cast** messages do not.
Every time you do a `GenServer.call/2`, the client will send a message
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`.
@@ -63,64 +63,64 @@ defmodule GenServer do
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
* `init(args)` - invoked when the server is started.
It must return:
It must return:
{:ok, state}
{:ok, state, timeout}
:ignore
{:stop, reason}
- `{: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.
* `handle_call(msg, {from, ref}, state)` and `handle_cast(msg, state)` -
invoked to handle call (sync) and cast (async) messages.
It must return:
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}
- `{: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.
* `handle_info(msg, state)` - invoked to handle all other messages which
are received by the process.
It must return:
It must return:
{:noreply, state}
{:noreply, state, timeout}
{:stop, reason, state}
- `{: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`
* `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 swap).
* `code_change(old_vsn, state, extra)` - called when the application
code is being upgraded live (hot code swapping).
It must return:
It must return:
{:ok, new_state}
{:error, reason}
- `{:ok, new_state}`
- `{:error, reason}`
## Names registering
## 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 clean up on termination. The supported values are:
automatically cleaned up on termination. The supported values are:
* an atom - the GenServer is registered locally with the given name
using `Process.register/2`;
* 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;
* `{: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 along side a name which can be any term;
* `{: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:
@@ -130,22 +130,23 @@ defmodule GenServer do
# 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/2`,
`cast/2` and friends) will also accept an atom, or any `:global` or `:via`
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
* 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/2` and
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
them in functions too.
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:
@@ -192,14 +193,14 @@ defmodule GenServer do
## Learn more
In case you desire to learn more about gen servers, Elixir getting started
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
* 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"
@@ -269,27 +270,32 @@ defmodule GenServer do
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 registered as described in the module
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 option `:debug` is present, the corresponding function in the
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 option `:spawn_opt` is present, the given options will be passed
to the underlying process as in `Process.spawn/3`.
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
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
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
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.
@@ -321,13 +327,13 @@ defmodule GenServer do
end
@doc """
Makes a synchronous call to the `server` and wait for its reply.
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 Registering`
The server can be any of the values described in the `Name Registration`
section of the module documentation.
## Timeouts
@@ -349,9 +355,9 @@ defmodule GenServer do
@doc """
Sends an asynchronous request to the `server`.
This function returns `:ok` immediately, ignoring if the destination node
or server does not exist. `handle_cast/2` will be called on the server
to handle the request.
This function returns `:ok` immediately, regardless of whether the
destination node or server does exists. `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
@@ -392,7 +398,7 @@ defmodule GenServer do
end
@doc """
Replies a client.
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
@@ -402,7 +408,7 @@ defmodule GenServer do
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 return `:ok`.
This function always returns `:ok`.
"""
@spec reply({pid, reference}, term) :: :ok
def reply(client, reply)
-43
View File
@@ -1,43 +0,0 @@
defmodule GenServer.Behaviour do
@moduledoc false
@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
+21 -4
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
@@ -85,10 +86,11 @@ defmodule HashDict do
## 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
@@ -232,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
@@ -248,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
+9
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
@@ -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
+5 -70
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`.
@@ -210,9 +210,8 @@ defimpl Inspect, for: BitString do
end
defp escape(<<>>, _char, binary), do: binary
@doc false
# also used by Regex
# Also used by Regex
def escape_char(char) when char in ?\000..?\377,
do: octify(char)
@@ -237,7 +236,6 @@ defimpl Inspect, for: BitString do
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
@@ -349,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_string(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_string(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
@@ -443,13 +391,12 @@ end
defimpl Inspect, for: Regex do
def inspect(regex, _opts) do
delim =?/
delim = ?/
concat ["~r",
<<delim, escape(regex.source, delim)::binary, delim>>,
regex.opts]
end
defp escape(bin, term),
do: escape(bin, <<>>, term)
@@ -575,15 +522,3 @@ defimpl Inspect, for: Any do
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
+36 -44
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
@@ -160,19 +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_tuple(arg) and is_atom(elem(arg, 0)) do
# Remove this code when protocols+records are removed"
if opts.records do
try do
Inspect.inspect(arg, opts)
rescue
_ -> Inspect.Tuple.inspect(arg, opts)
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
@@ -194,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
@@ -207,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
@@ -230,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
@@ -251,9 +241,11 @@ defmodule Inspect.Algebra do
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
+27 -29
View File
@@ -21,12 +21,12 @@ 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`
"""
@@ -46,13 +46,13 @@ 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) :: chardata | nodata
def read(device \\ group_leader, chars_or_line)
@@ -69,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)
@@ -174,13 +174,13 @@ 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(chardata | String.Chars.t, pos_integer) :: chardata | nodata
@spec getn(device, chardata | String.Chars.t) :: chardata | nodata
@@ -208,14 +208,14 @@ defmodule IO do
@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, chardata | String.Chars.t) :: chardata | nodata
def gets(device \\ group_leader(), prompt) do
@@ -248,8 +248,7 @@ defmodule IO do
"""
@spec stream(device, :line | pos_integer) :: Enumerable.t
def stream(device, line_or_codepoints) do
struct 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 """
@@ -267,8 +266,7 @@ defmodule IO do
"""
@spec binstream(device, :line | pos_integer) :: Enumerable.t
def binstream(device, line_or_bytes) do
struct 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 """
+2 -2
View File
@@ -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
+9 -9
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
+8 -3
View File
@@ -14,14 +14,19 @@ defmodule IO.Stream do
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
* `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
+414 -420
View File
File diff suppressed because it is too large Load Diff
+25 -18
View File
@@ -129,17 +129,17 @@ defmodule Kernel.CLI do
end
defp parse_shared(["-pa", h|t], config) do
Enum.each Path.wildcard(Path.expand(h)), &Code.prepend_path(&1)
add_code_path(h, &Code.prepend_path/1)
parse_shared t, config
end
defp parse_shared(["-pz", h|t], config) do
Enum.each Path.wildcard(Path.expand(h)), &Code.append_path(&1)
add_code_path(h, &Code.append_path/1)
parse_shared t, config
end
defp parse_shared(["--app", h|t], config) do
parse_shared t, %{config | commands: &[{:app, h}|&1]}
parse_shared t, %{config | commands: [{:app, h} | config.commands]}
end
defp parse_shared(["--no-halt"|t], config) do
@@ -162,7 +162,7 @@ defmodule Kernel.CLI do
parse_shared t, config
end
defp parse_shared([erl|t], config) when erl in ["--detached", "--hidden", "--gen-debug"] do
defp parse_shared([erl|t], config) when erl in ["--detached", "--hidden"] do
parse_shared t, config
end
@@ -170,6 +170,15 @@ defmodule Kernel.CLI 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 parse_argv(["--"|t], config) do
@@ -240,7 +249,7 @@ defmodule Kernel.CLI do
"-" <> _ ->
shared_option? list, config, &parse_compiler(&1, &2)
_ ->
pattern = if :filelib.is_dir(h), do: "#{h}/**/*.ex", else: h
pattern = if File.dir?(h), do: "#{h}/**/*.ex", else: h
parse_compiler t, %{config | compile: [pattern | config.compile]}
end
end
@@ -315,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}"}
@@ -323,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
@@ -335,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
@@ -349,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} ->
@@ -363,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
@@ -380,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
+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.
+81 -39
View File
@@ -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,23 +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;
* `: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
@@ -1047,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
@@ -1305,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)
@@ -1375,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
@@ -1634,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
+115 -116
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`).
@@ -262,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 """
@@ -324,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 """
@@ -334,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 """
@@ -343,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 """
@@ -423,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
@@ -443,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
@@ -514,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
[]
@@ -525,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
@@ -565,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
@@ -578,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
@@ -748,7 +754,7 @@ defmodule Kernel.Typespec do
end
end
## From AST conversion
## To typespec conversion
defp line(meta) do
case :lists.keyfind(:line, 1, meta) do
@@ -779,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
@@ -822,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
@@ -859,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)
@@ -909,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
+21 -14
View File
@@ -24,6 +24,7 @@ defmodule Keyword do
in `Enum` and `List` can also be applied.
"""
@compile :inline_list_funcs
@behaviour Dict
@type key :: atom
@@ -174,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 """
@@ -192,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 """
@@ -206,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
@@ -227,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.
@@ -250,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 """
@@ -339,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 """
@@ -467,16 +474,16 @@ 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 """
@@ -497,7 +504,7 @@ 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 """
@@ -517,7 +524,7 @@ defmodule Keyword do
"""
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 """
-28
View File
@@ -580,34 +580,6 @@ defmodule List do
end
end
@doc false
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
end
@doc false
def from_char_data!(char_data) do
case :unicode.characters_to_list(char_data) 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
## Helpers
# replace_at
+35 -10
View File
@@ -109,6 +109,31 @@ defmodule Macro 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.
"""
@@ -488,8 +513,8 @@ defmodule Macro do
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
@@ -521,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]
@@ -531,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_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({:., _, [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
@@ -661,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
+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}
+1 -1
View File
@@ -11,7 +11,7 @@ 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
+48 -39
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
[] ->
@@ -544,7 +553,7 @@ defmodule Module do
end
defp simplify_signature({var, _, atom}, _i) when is_atom(atom) do
case atom_to_binary(var) do
case Atom.to_string(var) do
"_" <> rest -> {String.to_atom(rest), [], Elixir}
_ -> {var, [], nil}
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)
+25
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.
+61 -40
View File
@@ -32,7 +32,9 @@ 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
@@ -40,22 +42,22 @@ defmodule OptionParser do
switches and their formats. The switches can be specified via two
different options:
* `:strict` - the switches are strict. Any switch that does not
exist in the switch list is treated as an error;
* `:strict` - the switches are strict. Any switch that does not
exist in the switch list is treated as an error.
* `:switches` - configure some switches. Switches that does not
exist in the switch list are still attempted to be parsed;
* `: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;
* `: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
@@ -63,7 +65,7 @@ defmodule OptionParser do
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:
@@ -176,15 +178,17 @@ defmodule OptionParser do
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
* `{: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)
* `{: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)
* `{: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
* `{:error, rest}` - there are no switches at the top of the given argv
"""
@spec next(argv, options) ::
@@ -224,7 +228,7 @@ defmodule OptionParser do
else
{opt_name, kinds, value} = normalize_option(tagged, value, switches)
{value, kinds, rest} = normalize_value(value, kinds, rest, strict)
case validate_option(opt_name, value, kinds) do
case validate_option(value, kinds) do
{:ok, new_value} -> {:ok, opt_name, new_value, rest}
:invalid -> {:invalid, opt_name_bin, value, rest}
end
@@ -252,31 +256,31 @@ defmodule OptionParser do
{aliases, switches, strict, all}
end
defp validate_option(option, value, kinds) do
{invalid_opt, value} = cond do
defp validate_option(value, kinds) do
{is_invalid, value} = cond do
:invalid in kinds ->
{option, value}
{true, value}
:boolean in kinds ->
case value do
t when t in [true, "true"] -> {nil, true}
f when f in [false, "false"] -> {nil, false}
_ -> {option, value}
_ -> {true, value}
end
:integer in kinds ->
case Integer.parse(value) do
{value, ""} -> {nil, value}
_ -> {option, value}
_ -> {true, value}
end
:float in kinds ->
case Float.parse(value) do
{value, ""} -> {nil, value}
_ -> {option, value}
_ -> {true, value}
end
true ->
{nil, value}
end
if invalid_opt do
if is_invalid do
:invalid
else
{:ok, value}
@@ -301,11 +305,11 @@ defmodule OptionParser do
if alias = aliases[opt] do
{:default, alias}
else
{:unknown, opt}
:unknown
end
end
defp option_defined?({:unknown, _option}, _switches) do
defp option_defined?(:unknown, _switches) do
false
end
@@ -317,8 +321,8 @@ defmodule OptionParser do
Keyword.has_key?(switches, option)
end
defp normalize_option({:unknown, option}, value, _switches) do
{option, [:invalid], value}
defp normalize_option(:unknown, value, _switches) do
{nil, [:invalid], value}
end
defp normalize_option({:negated, option}, nil, switches) do
@@ -374,12 +378,22 @@ 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 |> String.to_atom
if str = to_underscore(option) do
String.to_atom(str)
end
end
defp reverse_negated(negated) do
@@ -387,16 +401,23 @@ defmodule OptionParser do
end
defp get_negated("no-" <> rest = option, value, switches) do
negated = get_option(rest)
option = if Keyword.has_key?(switches, negated) and value == nil do
negated
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
get_option(option)
:unknown
end
{:negated, option}
end
defp get_negated(rest, _value, _switches) do
{:default, get_option(rest)}
if option = get_option(rest) do
{:default, option}
else
:unknown
end
end
end
+61 -15
View File
@@ -62,10 +62,13 @@ defmodule Path do
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 = IO.chardata_to_string(relative_to)
@@ -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
@@ -479,6 +483,32 @@ defmodule Path do
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 """
Traverses paths according to the given `glob` expression.
@@ -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,10 +550,11 @@ 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
|> chardata_to_list
|> :filelib.wildcard
|> chardata_to_list()
|> :filelib.wildcard(mod)
|> Enum.map(&IO.chardata_to_string/1)
end
@@ -544,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
+27 -57
View File
@@ -6,11 +6,11 @@ defmodule Process do
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`
* `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`
"""
@@ -76,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.
@@ -194,46 +194,6 @@ defmodule Process do
:erlang.spawn_opt(mod, fun, args, opts)
end
@doc false
def spawn(fun) do
:erlang.spawn(fun)
end
@doc false
def spawn(mod, fun, args) do
:erlang.spawn(mod, fun, args)
end
@doc false
def spawn_link(fun) do
:erlang.spawn_link(fun)
end
@doc false
def spawn_link(mod, fun, args) do
:erlang.spawn_link(mod, fun, args)
end
@doc false
def spawn_monitor(fun) do
:erlang.spawn_monitor(fun)
end
@doc false
def spawn_monitor(mod, fun, args) do
:erlang.spawn_monitor(mod, fun, args)
end
@doc false
def self() do
:erlang.self()
end
@doc false
def send(dest, msg) do
:erlang.send(dest, msg)
end
@doc """
The calling process starts monitoring the item given.
It returns the monitor reference.
@@ -314,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
@@ -408,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
+567 -229
View File
@@ -1,225 +1,17 @@
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
is_tuple(unquote(arg)) and is_atom(elem(unquote(arg), 0)), 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, "#{inspect module} is not loaded"
end
try do
module.__protocol__(:name)
rescue
UndefinedFunctionError ->
raise ArgumentError, "#{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_char_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, "protocol functions expect at least one argument"
end
@@ -251,7 +43,7 @@ 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
@@ -260,21 +52,567 @@ defmodule Protocol.DSL do
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_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 """
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, "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
+2 -17
View File
@@ -14,8 +14,8 @@ defmodule Record do
In Elixir, records are used mostly in two situations:
1. To work with short, internal data;
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
@@ -101,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.
-539
View File
@@ -1,539 +0,0 @@
defmodule Record.Deprecated do
@moduledoc false
def defexception(name, fields, opts) do
quote do
fields = unquote(fields)
defmodule unquote(name) do
defexception fields
unquote(Keyword.get opts, :do)
end
end
end
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, "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 = String.to_atom(Atom.to_string(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
@spec __record__(atom) :: term
@spec __record__(atom, term) :: term
@spec __record__(atom, term, term) :: term
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, "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, "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, "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, "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, "expected arguments to be compile time keywords"
end
if Macro.Env.in_match?(caller) do
raise ArgumentError, "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, "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, "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_string(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 = String.to_atom "update_" <> Atom.to_string(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_string(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 = String.to_atom "update_" <> Atom.to_string(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, "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
+49 -43
View File
@@ -22,49 +22,56 @@ 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
"""
@@ -167,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
@@ -275,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
@@ -310,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
@@ -370,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
+4 -3
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.
@@ -420,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`.
@@ -988,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
+86 -71
View File
@@ -15,11 +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 functions for working with binaries (bytes)
[in the `:binary` module](http://erlang.org/doc/man/binary.html)
* `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
@@ -767,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,
@@ -868,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
@@ -915,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
@@ -926,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"
@@ -941,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
@@ -961,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
@@ -970,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
@@ -981,7 +1037,7 @@ defmodule String do
"lixir"
iex> String.slice("elixir", 10..3)
nil
""
iex> String.slice("elixir", -4..-1)
"ixir"
@@ -993,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"
@@ -1005,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)
@@ -1029,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
@@ -1047,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 """
@@ -1124,8 +1175,8 @@ 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
@@ -1297,40 +1348,4 @@ defmodule String do
def to_float(string) do
:erlang.binary_to_float(string)
end
@doc false
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 false
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) ->
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
end
+2 -2
View File
@@ -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
+65 -59
View File
@@ -1,11 +1,11 @@
defmodule Supervisor do
@moduledoc """
A behaviour module for implementing supevision functionality.
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 a fault
tolerant applications.
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
@@ -44,7 +44,7 @@ defmodule Supervisor do
worker(Stack, [[:hello]])
]
# Start the supervisor with our one children
# 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
@@ -73,12 +73,12 @@ defmodule Supervisor do
GenServer.call(:sup_stack, :pop) == :hello
Supervisors support different strategies, in the example above, we
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.
configuration, shutdown values, and restart strategies.
Continue reading this module to learn more about supervision 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.
@@ -86,7 +86,7 @@ defmodule Supervisor do
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 supevision module:
can also be created by explicitly defining a supervision module:
defmodule MyApp.Supervisor do
use Supervisor
@@ -96,51 +96,49 @@ defmodule Supervisor do
end
def init([]) do
import Supervisor.Spec
children = [
worker(Stack, [[:hello]])
]
supervisor(children, strategy: :one_for_one)
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 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;
* 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_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;
* `: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;
* `: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;
* `: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 registering
## Name Registration
A supervisor is bound to the same name registering rules as a `GenServer`.
A supervisor is bound to the same name registration rules as a `GenServer`.
Read more about it in the `GenServer` docs.
"""
@@ -181,22 +179,26 @@ defmodule Supervisor do
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 Registering`
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 all child process start functions return `{:ok, child}`,
(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
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 child process start function fails or returns an error tuple or
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
@@ -215,11 +217,11 @@ defmodule Supervisor do
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, 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 Registering`
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.
@@ -243,7 +245,7 @@ defmodule Supervisor do
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 a `:simple_one_for_one`, the child specification defined in
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.
@@ -262,7 +264,7 @@ defmodule Supervisor do
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
`{: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
@@ -272,18 +274,18 @@ defmodule Supervisor do
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 and, the child specification is
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 instead of a `pid`, the function will
specification identifier is given instead of a `pid`, the function will
return `{:error, :simple_one_for_one}`.
Non-temporary child process may later be restarted by the supervisor. The child
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
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}
@@ -343,15 +345,16 @@ defmodule Supervisor do
This function returns a list of tuples containing:
* the `id` - as defined in the child specification or `:undefined` in the case
of a `simple_one_for_one` supervisor;
* `id` - as defined in the child specification or `:undefined` in the case
of a `simple_one_for_one` supervisor
* the `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;
* `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
* the `type` - `:worker` or `:supervisor` as defined in the child specification;
* `type` - `:worker` or `:supervisor` as defined in the child specification
* the `modules` as defined in the child specification;
* `modules` – as defined in the child specification
"""
@spec which_children(supervisor) ::
[{Supervisor.Spec.child_id | :undefined,
@@ -361,17 +364,20 @@ defmodule Supervisor do
defdelegate which_children(supervisor), to: :supervisor
@doc """
Returns a map containing count value for the supervisor.
Returns a map containing count values for the supervisor.
The map contains the following keys:
* `:specs` - the total count of children, dead or alive;
* `:specs` - the total count of children, dead or alive
* `:active` - the count of all actively running child processes managed by this supervisor;
* `: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;
* `: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;
* `:workers` - the count of all workers, whether or not the child process
is still alive
"""
@spec count_children(supervisor) ::
-156
View File
@@ -1,156 +0,0 @@
defmodule Supervisor.Behaviour do
@moduledoc false
@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, "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
+44 -43
View File
@@ -40,64 +40,65 @@ defmodule Supervisor.Spec do
Explicit supervisors as above are required when there is a need to:
1. partialy change the supervision tree during hot-code swaps;
1. Partialy change the supervision tree during hot-code swaps.
2. define supervisors inside other supervisors;
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);
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;
* `: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;
* `:function` - the function to invoke on the child to start it
* `:restart` - defines when the child process should restart;
* `:restart` - defines when the child process should restart
* `:shutdown` - defines how a child process should be terminated;
* `: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`;
* `: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;
* `: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`);
* `: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}`;
* `: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)`;
* `: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;
* `: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)`;
* 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"
@@ -138,19 +139,19 @@ defmodule Supervisor.Spec do
## Options
* `:strategy` - the restart strategy option It can be either
`:one_for_one`, `:rest_for_one`, `:one_for_all` and
`:simple_one_for_one`. You can learn more about strategies
in the `Supervisor` module;
* `: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_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;
* `: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. Please check the `Supervisor` module for
are allowed within 5 seconds. Please check the `Supervisor` module for
a complete description of the available strategies.
"""
@spec supervise([spec], strategy: strategy,
@@ -188,7 +189,7 @@ defmodule Supervisor.Spec do
worker ExUnit.Runner, [], restart: :permanent
By default, the function `:start_link` is invoked on the given
By default, the function `start_link` is invoked on the given
module. Overall, the default values for the options are:
[id: module,
@@ -212,7 +213,7 @@ defmodule Supervisor.Spec do
supervisor ExUnit.Runner, [], restart: :permanent
By default, the function `:start_link` is invoked on the given
By default, the function `start_link` is invoked on the given
module. Overall, the default values for the options are:
[id: module,
+13 -13
View File
@@ -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 """
@@ -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.
"""
@@ -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.
+44 -51
View File
@@ -2,42 +2,35 @@ defmodule Task do
@moduledoc """
Conveniences for spawning and awaiting for tasks.
Tasks are processes that meant to execute one particular
action throughout their life-cycle, often with little
explicit communication with other processes. The most common
use case for tasks is to compute a value asynchronously:
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
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/1`, tasks can also be used as part
of supervision trees and dynamically spawned in remote nodes.
We will explore all three scenarios next.
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 and this process is linked and monitored
by the caller. However, the processes are unlinked right before
the task finishes, allowing the proper error to be triggered only
on `await/1`.
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.
This implies three things:
1) In case the caller crashes, the task will be killed and its
computation will abort;
2) In case the task crashes due to an error, the parent will
crash only on `await/1`;
3) In case the task crashes because a linked process caused
it to crash, the parent will crash immediately;
`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
@@ -46,7 +39,7 @@ defmodule Task do
Task.start_link(fn -> IO.puts "ok" end)
Such can be mounted in your supervision tree as:
Such tasks can be mounted in your supervision tree as:
import Supervisor.Spec
@@ -54,15 +47,10 @@ defmodule Task do
worker(Task, [fn -> IO.puts "ok" end])
]
Since such tasks are supervised and not directly linked to
the caller, they cannot be awaited on. For such reason,
differently from `async/1`, `start_link/1` returns `{:ok, pid}`
(which is the result expected by supervision trees).
Such tasks are useful as workers that run during your application
life-cycle and rarely communicate with other workers. For example,
a worker that pushes data to another server or a worker that consumes
events from an event manager and writes it to a log file.
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
@@ -78,7 +66,7 @@ defmodule Task do
# In the remote node
Task.Supervisor.start_link(name: :tasks_sup)
# On the client
# 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:
@@ -97,10 +85,10 @@ defmodule Task do
It contains two fields:
* `:pid` - the proces reference of the task process. It may be a pid
or a tuple containing the process and node names;
* `: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;
* `:ref` - the task monitor reference
"""
defstruct pid: nil, ref: nil
@@ -118,20 +106,20 @@ defmodule Task do
"""
@spec start_link(module, atom, [term]) :: {:ok, pid}
def start_link(mod, fun, args) do
Task.Supervised.start_link(:undefined, {mod, fun, args})
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 and monitored
to the caller process. A `Task` struct is returned containing
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 hold by the task.
where `ref` is the monitoring reference held by the task.
"""
@spec async(fun) :: t
def async(fun) do
@@ -147,16 +135,24 @@ defmodule Task do
@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(), mfa])
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 miliseconds, can be given with default value
A timeout, in milliseconds, can be given with default value
of `5000`. In case the task process dies, this function will
exit with the same reason as the task.
"""
@@ -168,7 +164,7 @@ defmodule Task do
reply
{:DOWN, ^ref, _, _, :noconnection} ->
mfa = {__MODULE__, :await, [task, timeout]}
exit({{:nodedown, get_node(task.pid)}, mfa})
exit({{:nodedown, node(task.pid)}, mfa})
{:DOWN, ^ref, _, _, reason} ->
exit({reason, {__MODULE__, :await, [task, timeout]}})
after
@@ -185,13 +181,13 @@ defmodule Task do
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.
failed or `nil` if no task was found.
This function is useful in situations where multiple
tasks are spawned and their results are collected just
later on. For example, a GenServer can spawn tasks,
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 upcoming messages are from any of the tasks.
to see if incoming messages are from any of the tasks.
"""
@spec find([t], any) :: {term, t} | nil | no_return
def find(tasks, msg)
@@ -210,7 +206,7 @@ defmodule Task 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, get_node(pid)}, {__MODULE__, :find, [tasks, msg]}})
exit({{:nodedown, node(pid)}, {__MODULE__, :find, [tasks, msg]}})
%Task{} ->
exit({reason, {__MODULE__, :find, [tasks, msg]}})
nil ->
@@ -221,7 +217,4 @@ defmodule Task do
def find(_tasks, _msg) do
nil
end
defp get_node({_, n}) when is_atom(n), do: n
defp get_node(pid) when is_pid(pid), do: pid
end
+66 -27
View File
@@ -1,15 +1,25 @@
defmodule Task.Supervised do
@moduledoc false
def start_link(:undefined, fun) do
:proc_lib.start_link(__MODULE__, :noreply, [fun])
def start_link(info, fun) do
{:ok, :proc_lib.spawn_link(__MODULE__, :noreply, [info, fun])}
end
def start_link(caller, fun) do
:proc_lib.start_link(__MODULE__, :reply, [caller, fun])
def start_link(caller, info, fun) do
:proc_lib.start_link(__MODULE__, :reply, [caller, info, fun])
end
def async(caller, {module, fun, args}) do
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
@@ -33,36 +43,65 @@ defmodule Task.Supervised do
5000 -> exit(:timeout)
end
try do
apply(module, fun, args)
else
result ->
send caller, {ref, result}
catch
:error, reason ->
exit({reason, System.stacktrace()})
:throw, value ->
exit({{:nocatch, value}, System.stacktrace()})
after
:erlang.unlink(caller)
end
send caller, {ref, do_apply(info, mfa)}
end
def reply(caller, mfa) do
:erlang.link(caller)
:proc_lib.init_ack({:ok, self()})
async(caller, mfa)
def noreply(info, mfa) do
initial_call(mfa)
do_apply(info, mfa)
end
def noreply({module, fun, args}) do
:proc_lib.init_ack({:ok, self()})
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, reason ->
exit({reason, System.stacktrace()})
:error, value ->
exit(info, mfa, {value, System.stacktrace()})
:throw, value ->
exit({{:nocatch, value}, System.stacktrace()})
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
+19 -10
View File
@@ -7,12 +7,12 @@ defmodule Task.Supervisor do
`: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 can be spawned and awaited
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 registering
## Name Registration
A `Task.Supervisor` is bound to the same name registering rules as a
A `Task.Supervisor` is bound to the same name registration rules as a
`GenServer`. Read more about it in the `GenServer` docs.
"""
@@ -22,11 +22,11 @@ defmodule Task.Supervisor do
The supported options are:
* `:name` - used to register a supervisor name, the supported values are
described under the `Name Registering` section in the `GenServer` module
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 miliseconds;
or an integer indicating the timeout value, defaults to 5000 milliseconds;
"""
@spec start_link(Supervisor.options) :: Supervisor.on_start
def start_link(opts \\ []) do
@@ -55,14 +55,15 @@ defmodule Task.Supervisor do
"""
@spec async(Supervisor.supervisor, module, atom, [term]) :: Task.t
def async(supervisor, module, fun, args) do
{:ok, pid} = Supervisor.start_child(supervisor, [self(), {module, fun, args}])
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 given child at pid.
Terminates the child with the given `pid`.
"""
@spec terminate_child(Supervisor.supervisor, pid) :: :ok
def terminate_child(supervisor, pid) when is_pid(pid) do
@@ -80,9 +81,9 @@ defmodule Task.Supervisor do
@doc """
Starts a task as child of the given `supervisor`.
Note the spawned process is not linked to the caller but
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 emit side-effects (like I/O) and does not need
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}
@@ -98,6 +99,14 @@ defmodule Task.Supervisor do
"""
@spec start_child(Supervisor.supervisor, module, atom, [term]) :: {:ok, pid}
def start_child(supervisor, module, fun, args) do
Supervisor.start_child(supervisor, [:undefined, {module, fun, args}])
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
+101 -34
View File
@@ -126,8 +126,10 @@ 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}
@@ -142,33 +144,80 @@ defmodule URI do
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, "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/^(([^:\/?#]+):)?(\/\/([^\/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?/
@@ -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
+7 -2
View File
@@ -68,8 +68,13 @@ defmodule Version do
defstruct [:source, :matchspec]
end
defexception InvalidRequirementError, [:message]
defexception InvalidVersionError, [:message]
defmodule InvalidRequirementError do
defexception [:message]
end
defmodule InvalidVersionError do
defexception [:message]
end
@doc """
Check if the given version matches the specification.
+30 -23
View File
@@ -18,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) ->
@@ -65,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,
@@ -108,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.
+24 -17
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, guards/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 ->
@@ -79,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}} =
@@ -140,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.
@@ -181,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};
+3 -2
View File
@@ -180,8 +180,9 @@ core_main() ->
<<"lib/elixir/lib/list.ex">>,
<<"lib/elixir/lib/macro.ex">>,
<<"lib/elixir/lib/code.ex">>,
<<"lib/elixir/lib/exception.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">>,
@@ -193,8 +194,8 @@ core_main() ->
<<"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/typespec.ex">>,
<<"lib/elixir/lib/kernel/cli.ex">>,
<<"lib/elixir/lib/kernel/error_handler.ex">>,
<<"lib/elixir/lib/kernel/parallel_compiler.ex">>,
+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.
+21 -52
View File
@@ -147,9 +147,17 @@ do_expand_import(Meta, {Name, Arity} = Tuple, Args, Module, E, Result) ->
{function, Receiver} ->
elixir_lexical:record_import(Receiver, ?m(E, lexical_tracker)),
elixir_locals:record_import(Tuple, Receiver, Module, ?m(E, function)),
case rewrite(Receiver, Name, Args, Arity) of
{ok, _, _, _} = Res -> Res;
false -> {ok, Receiver, Name, Args}
%% 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),
@@ -444,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};
@@ -508,54 +515,16 @@ deprecation_message(Warning, Message) ->
Message -> Warning ++ ", " ++ Message
end.
% deprecation('Elixir.Application.Behaviour', '__using__', _) ->
% "use Application instead";
% deprecation('Elixir.Dict.Behaviour', '__using__', _) ->
% "use Dict instead";
% deprecation('Elixir.GenEvent.Behaviour', '__using__', _) ->
% "use GenEvent instead";
% deprecation('Elixir.GenServer.Behaviour', '__using__', _) ->
% "use GenServer instead";
% deprecation('Elixir.Supervisor.Behaviour', '__using__', _) ->
% "use Supervisor instead";
% deprecation('Elixir.Kernel', raise, 3) ->
% "use reraise/2 instead";
% deprecation('Elixir.Kernel', defexception, 3) ->
% "use defexception/1 instead";
% deprecation('Elixir.List', 'from_char_data', 1) ->
% "use String.to_char_list instead";
% deprecation('Elixir.List', 'from_char_data!', 1) ->
% "use String.to_char_list instead";
% deprecation('Elixir.String', 'from_char_data', 1) ->
% "use List.to_string instead";
% deprecation('Elixir.String', 'from_char_data!', 1) ->
% "use List.to_string instead";
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('Elixir.Kernel', is_record, _) ->
"use Record.record? instead";
deprecation('Elixir.Kernel', defrecord, _) ->
"use structs instead";
deprecation('Elixir.Kernel', defrecordp, _) ->
"use Record.defrecordp/3 instead";
deprecation('Elixir.Process', spawn, 1) ->
"use the one in Kernel instead";
deprecation('Elixir.Process', spawn, 3) ->
"use the one in Kernel instead";
deprecation('Elixir.Process', spawn_link, _) ->
"use the one in Kernel instead";
deprecation('Elixir.Process', spawn_monitor, _) ->
"use the one in Kernel instead";
deprecation('Elixir.Process', self, 0) ->
"use the one in Kernel instead";
deprecation('Elixir.Process', send, 2) ->
"use the one in Kernel instead";
deprecation('Elixir.Record', defmacros, 4) ->
true;
deprecation('Elixir.Record', deftypes, 3) ->
true;
deprecation('Elixir.Record', deffunctions, 2) ->
true;
deprecation('Elixir.Mix', project, 0) ->
"use Mix.Project.config/0 instead";
deprecation(_, _, _) ->
false.
+40 -18
View File
@@ -169,17 +169,9 @@ 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, 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) ->
elixir_errors:warn(?line(Meta), ?m(E, file), "the :hygiene option in quote is deprecated"),
List;
false ->
[]
end,
Context = case lists:keyfind(context, 1, EKV) of
{context, Ctx} when is_atom(Ctx) and (Ctx /= nil) ->
Ctx;
@@ -193,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),
@@ -210,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);
@@ -238,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),
@@ -256,7 +254,7 @@ expand({'try', Meta, [KV]}, E) ->
%% Comprehensions
expand({for, Meta, Args}, E) when is_list(Args) ->
expand({for, Meta, [_|_] = Args}, E) ->
elixir_for:expand(Meta, Args, E);
%% Super
@@ -285,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,
@@ -414,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) ->
@@ -538,6 +539,27 @@ expand_as({as, Other}, Meta, _IncludeByDefault, _Ref, 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).
+56 -36
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
@@ -145,34 +171,28 @@ normalize_rescue(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),
+1 -1
View File
@@ -30,7 +30,7 @@ translate_fn_match(Arg, S) ->
expand(Meta, Clauses, E) when is_list(Clauses) ->
Transformer = fun(Clause) ->
{EClause, _} = elixir_exp_clauses:clause(Meta, fn, fun elixir_exp:expand_args/2, Clause, E),
{EClause, _} = elixir_exp_clauses:clause(Meta, fn, fun elixir_exp_clauses:head/2, Clause, E),
EClause
end,
{{fn, Meta, lists:map(Transformer, Clauses)}, E}.
+1 -1
View File
@@ -109,7 +109,7 @@ translate_filters(T, S) ->
translate_filter(Filter, S) ->
{TFilter, TS} = elixir_translator:translate(Filter, S),
case elixir_utils:returns_boolean(TFilter) of
case elixir_utils:returns_boolean(Filter) of
true ->
{{nil, TFilter}, TS};
false ->
+1
View File
@@ -205,6 +205,7 @@ special_form('unquote_splicing', 1) -> true;
special_form('fn', _) -> true;
special_form('super', _) -> true;
special_form('for', _) -> true;
special_form('cond', 1) -> true;
special_form('case', 2) -> true;
special_form('try', 2) -> true;
special_form('receive', 1) -> true;
+18 -38
View File
@@ -9,73 +9,53 @@ unescape_tokens/1, unescape_tokens/2, unescape_map/1]).
extract(Line, Raw, Interpol, String, Last) ->
%% Ignore whatever is in the scope and enable terminator checking.
Scope = Raw#elixir_tokenizer{terminators=[], check_terminators=true},
extract(Line, Scope, Interpol, String, [], 0, [], Last).
extract(Line, Scope, Interpol, String, [], [], Last).
%% Terminators
extract(Line, _Scope, _Interpol, [], Buffer, 0, Output, []) ->
extract(Line, _Scope, _Interpol, [], Buffer, Output, []) ->
finish_extraction(Line, Buffer, Output, []);
extract(Line, _Scope, _Interpol, [], _Buffer, 0, _Output, Last) ->
extract(Line, _Scope, _Interpol, [], _Buffer, _Output, Last) ->
{error, {string, Line, io_lib:format("missing terminator: ~ts", [[Last]]), []}};
extract(Line, _Scope, _Interpol, [Last|Remaining], Buffer, 0, Output, Last) ->
extract(Line, _Scope, _Interpol, [Last|Remaining], Buffer, Output, Last) ->
finish_extraction(Line, Buffer, Output, Remaining);
extract(Line, _Scope, _Interpol, [], _Buffer, _Search, _Output, Last) ->
{error, {string, Line, io_lib:format("missing terminator: ~ts", [[Last]]), []}};
%% Going through the string
extract(Line, Scope, Interpol, [$\\, $\n|Rest], Buffer, Search, Output, Last) ->
extract(Line+1, Scope, Interpol, Rest, Buffer, Search, Output, Last);
extract(Line, Scope, Interpol, [$\\, $\n|Rest], Buffer, Output, Last) ->
extract(Line+1, Scope, Interpol, Rest, Buffer, Output, Last);
extract(Line, Scope, Interpol, [$\\, $\r, $\n|Rest], Buffer, Search, Output, Last) ->
extract(Line+1, Scope, Interpol, Rest, Buffer, Search, Output, Last);
extract(Line, Scope, Interpol, [$\\, $\r, $\n|Rest], Buffer, Output, Last) ->
extract(Line+1, Scope, Interpol, Rest, Buffer, Output, Last);
extract(Line, Scope, Interpol, [$\n|Rest], Buffer, Search, Output, Last) ->
extract(Line+1, Scope, Interpol, Rest, [$\n|Buffer], Search, Output, Last);
extract(Line, Scope, Interpol, [$\n|Rest], Buffer, Output, Last) ->
extract(Line+1, Scope, Interpol, Rest, [$\n|Buffer], Output, Last);
extract(Line, Scope, Interpol, [$\\, $#, ${|Rest], Buffer, Search, Output, Last) ->
extract(Line, Scope, Interpol, Rest, [${,$#|Buffer], Search, Output, Last);
extract(Line, Scope, Interpol, [$\\, $#, ${|Rest], Buffer, Output, Last) ->
extract(Line, Scope, Interpol, Rest, [${,$#|Buffer], Output, Last);
extract(Line, Scope, Interpol, [$\\,Char|Rest], Buffer, Search, Output, Last) ->
extract(Line, Scope, Interpol, Rest, [Char,$\\|Buffer], Search, Output, Last);
extract(Line, Scope, Interpol, [$\\,Char|Rest], Buffer, Output, Last) ->
extract(Line, Scope, Interpol, Rest, [Char,$\\|Buffer], Output, Last);
extract(Line, Scope, true, [$#, ${|Rest], Buffer, Search, Output, Last) ->
extract(Line, Scope, true, [$#, ${|Rest], Buffer, Output, Last) ->
Output1 = build_string(Line, Buffer, Output),
case elixir_tokenizer:tokenize(Rest, Line, Scope) of
{error, {EndLine, _, "}"}, [$}|NewRest], Tokens} ->
Output2 = build_interpol(Line, Tokens, Output1),
extract(EndLine, Scope, true, NewRest, [], Search, Output2, Last);
extract(EndLine, Scope, true, NewRest, [], Output2, Last);
{error, Reason, _, _} ->
{error, Reason};
{ok, _EndLine, _} ->
{error, {string, Line, "missing interpolation terminator:}", []}}
end;
%% Matching () [] {} <> inside sigils
extract(Line, Scope, Interpol, [$(|Rest], Buffer, Search, Output, $)) ->
extract(Line, Scope, Interpol, Rest, [$(|Buffer], Search + 1, Output, $));
extract(Line, Scope, Interpol, [$[|Rest], Buffer, Search, Output, $]) ->
extract(Line, Scope, Interpol, Rest, [$[|Buffer], Search + 1, Output, $]);
extract(Line, Scope, Interpol, [${|Rest], Buffer, Search, Output, $}) ->
extract(Line, Scope, Interpol, Rest, [${|Buffer], Search + 1, Output, $});
extract(Line, Scope, Interpol, [$<|Rest], Buffer, Search, Output, $>) ->
extract(Line, Scope, Interpol, Rest, [$<|Buffer], Search + 1, Output, $>);
extract(Line, Scope, Interpol, [Last|Rest], Buffer, Search, Output, Last) ->
extract(Line, Scope, Interpol, Rest, [Last|Buffer], Search - 1, Output, Last);
%% Catch all clause
extract(Line, Scope, Interpol, [Char|Rest], Buffer, Search, Output, Last) ->
extract(Line, Scope, Interpol, Rest, [Char|Buffer], Search, Output, Last).
extract(Line, Scope, Interpol, [Char|Rest], Buffer, Output, Last) ->
extract(Line, Scope, Interpol, Rest, [Char|Buffer], Output, Last).
%% Unescape a series of tokens as returned by extract.
+172 -92
View File
@@ -1,6 +1,6 @@
-module(elixir_module).
-export([compile/4, data_table/1, docs_table/1, is_open/1,
format_error/1, eval_callbacks/5]).
expand_callback/6, add_beam_chunk/3, format_error/1]).
-include("elixir.hrl").
-define(acc_attr, '__acc_attributes').
@@ -55,8 +55,8 @@ do_compile(Line, Module, Block, Vars, E) ->
{All, Forms0} = functions_form(Line, File, Module, Base, Export, Def, Defmacro, Functions),
Forms1 = specs_form(Module, Private, Defmacro, Forms0),
Forms2 = attributes_form(Line, File, Module, Forms1),
Forms3 = typedocs_form(Module, Forms2),
Forms2 = types_form(Module, Forms1),
Forms3 = attributes_form(Line, File, Module, Forms2),
case ets:lookup(data_table(Module), 'on_load') of
[] -> ok;
@@ -111,8 +111,8 @@ build(Line, File, Module, Lexical) ->
_ -> ets:insert(DataTable, {on_definition, []})
end,
Attributes = [behaviour, on_load, spec, type, export_type, opaque, callback, compile],
ets:insert(DataTable, {?acc_attr, [before_compile, after_compile, on_definition|Attributes]}),
Attributes = [behaviour, on_load, spec, type, typep, opaque, callback, compile, external_resource],
ets:insert(DataTable, {?acc_attr, [before_compile, after_compile, on_definition, derive|Attributes]}),
ets:insert(DataTable, {?persisted_attr, [vsn|Attributes]}),
ets:insert(DataTable, {?docs_attr, ets:new(DataTable, [ordered_set, public])}),
ets:insert(DataTable, {?lexical_attr, Lexical}),
@@ -128,10 +128,19 @@ build(Line, File, Module, Lexical) ->
eval_form(Line, Module, Block, Vars, E) ->
{Value, EE} = elixir_compiler:eval_forms(Block, Vars, E),
elixir_def_overridable:store_pending(Module),
EC = eval_callbacks(Line, Module, before_compile, [elixir_env:linify({Line, EE})], EE),
EV = elixir_env:linify({Line, EE#{vars := [], export_vars := nil}}),
EC = eval_callbacks(Line, Module, before_compile, [EV], EV),
elixir_def_overridable:store_pending(Module),
{Value, EC}.
eval_callbacks(Line, Module, Name, Args, E) ->
Callbacks = lists:reverse(ets:lookup_element(data_table(Module), Name, 2)),
lists:foldl(fun({M,F}, Acc) ->
expand_callback(Line, M, F, Args, Acc#{vars := [], export_vars := nil},
fun(AM, AF, AA) -> apply(AM, AF, AA) end)
end, E, Callbacks).
%% Return the form with exports and function declarations.
functions_form(Line, File, Module, BaseAll, BaseExport, Def, Defmacro, BaseFunctions) ->
@@ -147,7 +156,7 @@ functions_form(Line, File, Module, BaseAll, BaseExport, Def, Defmacro, BaseFunct
%% Add attributes handling to the form
attributes_form(Line, _File, Module, Current) ->
attributes_form(Line, File, Module, Current) ->
Table = data_table(Module),
AccAttrs = ets:lookup_element(Table, '__acc_attributes', 2),
@@ -157,72 +166,131 @@ attributes_form(Line, _File, Module, Current) ->
case lists:member(Key, PersistedAttrs) of
false -> Acc;
true ->
Attrs = case lists:member(Key, AccAttrs) of
true -> Value;
false -> [Value]
end,
lists:foldl(fun(X, Final) -> [{attribute, Line, Key, X}|Final] end, Acc, Attrs)
Values =
case lists:member(Key, AccAttrs) of
true -> Value;
false -> [Value]
end,
lists:foldl(fun(X, Final) ->
[{attribute, Line, Key, X}|Final]
end, Acc, process_attribute(Line, File, Key, Values))
end
end,
ets:foldl(Transform, Current, Table).
%% Add typedocs to the form
typedocs_form(Module, Current) ->
Table = docs_table(Module),
Transform = fun({Tuple, Line, Kind, _Sig, Doc}, Acc) ->
case Kind of
type -> [{attribute, Line, typedoc, {Tuple, Doc}} | Acc];
opaque -> [{attribute, Line, typedoc, {Tuple, Doc}} | Acc];
_ -> Acc
end
process_attribute(Line, File, external_resource, Values) ->
lists:usort([process_external_resource(Line, File, Value) || Value <- Values]);
process_attribute(_Line, _File, _Key, Values) ->
Values.
process_external_resource(_Line, _File, Value) when is_binary(Value) ->
Value;
process_external_resource(Line, File, Value) ->
elixir_errors:handle_file_error(File,
{Line, ?MODULE, {invalid_external_resource, Value}}).
%% Types
types_form(Module, Forms0) ->
case code:ensure_loaded('Elixir.Kernel.Typespec') of
{module, 'Elixir.Kernel.Typespec'} ->
Types0 = 'Elixir.Module':get_attribute(Module, type) ++
'Elixir.Module':get_attribute(Module, typep) ++
'Elixir.Module':get_attribute(Module, opaque),
Types1 = ['Elixir.Kernel.Typespec':translate_type(Kind, Expr, Doc, Caller) ||
{Kind, Expr, Doc, Caller} <- Types0],
'Elixir.Module':delete_attribute(Module, type),
'Elixir.Module':delete_attribute(Module, typep),
'Elixir.Module':delete_attribute(Module, opaque),
Forms1 = types_attributes(Types1, Forms0),
Forms2 = export_types_attributes(Types1, Forms1),
typedocs_attributes(Types1, Forms2);
{error, _} ->
Forms0
end.
types_attributes(Types, Forms) ->
Fun = fun({{Kind, _NameArity, Expr}, Line, _Export, _Doc}, Acc) ->
[{attribute, Line, Kind, Expr}|Acc]
end,
ets:foldl(Transform, Current, Table).
lists:foldl(Fun, Forms, Types).
export_types_attributes(Types, Forms) ->
Fun = fun
({{_Kind, NameArity, _Expr}, Line, true, _Doc}, Acc) ->
[{attribute, Line, export_type, [NameArity]}|Acc];
({_Type, _Line, false, _Doc}, Acc) ->
Acc
end,
lists:foldl(Fun, Forms, Types).
typedocs_attributes(Types, Forms) ->
Fun = fun
({{_Kind, NameArity, _Expr}, Line, true, Doc}, Acc) when Doc =/= nil ->
[{attribute, Line, typedoc, {NameArity, Doc}}|Acc];
({_Type, _Line, _Export, _Doc}, Acc) ->
Acc
end,
lists:foldl(Fun, Forms, Types).
%% Specs
specs_form(Module, Private, Defmacro, Forms) ->
Defmacrop = [Tuple || {Tuple, defmacrop, _, _, _} <- Private],
case code:ensure_loaded('Elixir.Kernel.Typespec') of
{module, 'Elixir.Kernel.Typespec'} ->
Callbacks = 'Elixir.Module':get_attribute(Module, callback),
Specs = [translate_spec(Spec, Defmacro, Defmacrop) ||
Spec <- 'Elixir.Module':get_attribute(Module, spec)],
Defmacrop = [Tuple || {Tuple, defmacrop, _, _, _} <- Private],
Specs0 = 'Elixir.Module':get_attribute(Module, spec) ++
'Elixir.Module':get_attribute(Module, callback),
Specs1 = ['Elixir.Kernel.Typespec':translate_spec(Kind, Expr, Caller) ||
{Kind, Expr, Caller} <- Specs0],
Specs2 = lists:flatmap(fun(Spec) ->
translate_macro_spec(Spec, Defmacro, Defmacrop)
end, Specs1),
'Elixir.Module':delete_attribute(Module, spec),
'Elixir.Module':delete_attribute(Module, callback),
specs_attributes(Forms, Specs2);
Temp = specs_attributes(spec, Forms, Specs),
specs_attributes(callback, Temp, Callbacks);
{error, _} ->
Forms
end.
specs_attributes(Type, Forms, Specs) ->
Keys = lists:foldl(fun({Tuple, Value}, Acc) ->
lists:keystore(Tuple, 1, Acc, {Tuple, Value})
end, [], Specs),
lists:foldl(fun({Tuple, _}, Acc) ->
Values = [V || {K, V} <- Specs, K == Tuple],
{type, Line, _, _} = hd(Values),
[{attribute, Line, Type, {Tuple, Values}}|Acc]
end, Forms, Keys).
specs_attributes(Forms, Specs) ->
Dict = lists:foldl(fun({{Kind, NameArity, Spec}, Line}, Acc) ->
dict:append({Kind, NameArity}, {Spec, Line}, Acc)
end, dict:new(), Specs),
dict:fold(fun({Kind, NameArity}, ExprsLines, Acc) ->
{Exprs, Lines} = lists:unzip(ExprsLines),
Line = lists:min(Lines),
[{attribute, Line, Kind, {NameArity, Exprs}}|Acc]
end, Forms, Dict).
translate_spec({Spec, Rest}, Defmacro, Defmacrop) ->
case ordsets:is_element(Spec, Defmacrop) of
true -> {Spec, Rest};
translate_macro_spec({{spec, NameArity, Spec}, Line}, Defmacro, Defmacrop) ->
case ordsets:is_element(NameArity, Defmacrop) of
true -> [];
false ->
case ordsets:is_element(Spec, Defmacro) of
case ordsets:is_element(NameArity, Defmacro) of
true ->
{Name, Arity} = Spec,
{{elixir_utils:macro_name(Name), Arity + 1}, spec_for_macro(Rest)};
{Name, Arity} = NameArity,
[{{spec, {elixir_utils:macro_name(Name), Arity + 1}, spec_for_macro(Spec)}, Line}];
false ->
{Spec, Rest}
[{{spec, NameArity, Spec}, Line}]
end
end.
end;
translate_macro_spec({{callback, NameArity, Spec}, Line}, _Defmacro, _Defmacrop) ->
[{{callback, NameArity, Spec}, Line}].
spec_for_macro({type, Line, 'fun', [{type, _, product, Args}|T]}) ->
NewArgs = [{type,Line,term,[]}|Args],
NewArgs = [{type, Line, term, []}|Args],
{type, Line, 'fun', [{type, Line, product, NewArgs}|T]};
spec_for_macro(Else) -> Else.
@@ -236,9 +304,10 @@ compile_opts(Module) ->
end.
load_form(Line, Forms, Opts, #{file := File} = E) ->
elixir_compiler:module(Forms, File, Opts, fun(Module, Binary) ->
Env = elixir_env:linify({Line, E}),
eval_callbacks(Line, Module, after_compile, [Env, Binary], E),
elixir_compiler:module(Forms, File, Opts, fun(Module, Binary0) ->
Docs = elixir_compiler:get_opt(docs),
Binary = add_docs_chunk(Binary0, Module, Line, Docs),
eval_callbacks(Line, Module, after_compile, [E, Binary], E),
case get(elixir_compiled) of
Current when is_list(Current) ->
@@ -258,6 +327,25 @@ load_form(Line, Forms, Opts, #{file := File} = E) ->
Binary
end).
add_docs_chunk(Bin, Module, Line, true) ->
ChunkData = term_to_binary({elixir_docs_v1, [
{docs, get_docs(Module)},
{moduledoc, get_moduledoc(Line, Module)}
]}),
add_beam_chunk(Bin, "ExDc", ChunkData);
add_docs_chunk(Bin, _, _, _) -> Bin.
get_docs(Module) ->
ordsets:from_list(
[{Tuple, Line, Kind, Sig, Doc} ||
{Tuple, Line, Kind, Sig, Doc} <- ets:tab2list(docs_table(Module)),
Kind =/= type, Kind =/= opaque]).
get_moduledoc(Line, Module) ->
{Line, 'Elixir.Module':get_attribute(Module, moduledoc)}.
check_module_availability(Line, File, Module) ->
Reserved = ['Elixir.Any', 'Elixir.BitString', 'Elixir.Function', 'Elixir.PID',
'Elixir.Reference', 'Elixir.Elixir', 'Elixir'],
@@ -311,7 +399,6 @@ add_info_function(Line, File, Module, Export, Def, Defmacro) ->
true ->
elixir_errors:form_error(Line, File, ?MODULE, {internal_function_overridden, Pair});
false ->
Docs = elixir_compiler:get_opt(docs),
{
{attribute, Line, spec, {{'__info__', 1},
[{type, Line, 'fun', [{type, Line, product, [ {type, Line, atom, []}]}, {type, Line, term, []} ]}]
@@ -319,8 +406,6 @@ add_info_function(Line, File, Module, Export, Def, Defmacro) ->
{function, 0, '__info__', 1, [
functions_clause(Def),
macros_clause(Defmacro),
docs_clause(Module, Docs),
moduledoc_clause(Line, Module, Docs),
module_clause(Module),
else_clause()
]}
@@ -336,53 +421,45 @@ macros_clause(Defmacro) ->
module_clause(Module) ->
{clause, 0, [{atom, 0, module}], [], [{atom, 0, Module}]}.
docs_clause(Module, true) ->
Docs = ordsets:from_list(
[{Tuple, Line, Kind, Sig, Doc} ||
{Tuple, Line, Kind, Sig, Doc} <- ets:tab2list(docs_table(Module)),
Kind =/= type, Kind =/= opaque]),
{clause, 0, [{atom, 0, docs}], [], [elixir_utils:elixir_to_erl(Docs)]};
docs_clause(_Module, _) ->
{clause, 0, [{atom, 0, docs}], [], [{atom, 0, nil}]}.
moduledoc_clause(Line, Module, true) ->
Docs = 'Elixir.Module':get_attribute(Module, moduledoc),
{clause, 0, [{atom, 0, moduledoc}], [], [elixir_utils:elixir_to_erl({Line, Docs})]};
moduledoc_clause(_Line, _Module, _) ->
{clause, 0, [{atom, 0, moduledoc}], [], [{atom, 0, nil}]}.
else_clause() ->
Info = {call, 0, {atom, 0, module_info}, [{var, 0, atom}]},
{clause, 0, [{var, 0, atom}], [], [Info]}.
% HELPERS
eval_callbacks(Line, Module, Name, Args, E) ->
Callbacks = lists:reverse(ets:lookup_element(data_table(Module), Name, 2)),
Meta = [{line,Line},{require,false}],
%% Adds custom chunk to a .beam binary
add_beam_chunk(Bin, Id, ChunkData)
when is_binary(Bin), is_list(Id), is_binary(ChunkData) ->
{ok, _, Chunks0} = beam_lib:all_chunks(Bin),
NewChunk = {Id, ChunkData},
Chunks = [NewChunk|Chunks0],
{ok, NewBin} = beam_lib:build_module(Chunks),
NewBin.
lists:foldl(fun({M,F}, Acc) ->
{Expr, ET} = elixir_dispatch:dispatch_require(Meta, M, F, Args, Acc, fun(AM, AF, AA) ->
apply(AM, AF, AA),
{nil, Acc}
end),
%% Expands a callback given by M:F(Args). In case
%% the callback can't be expanded, invokes the given
%% fun passing a possibly expanded AM:AF(Args).
expand_callback(Line, M, F, Args, E, Fun) ->
Meta = [{line,Line},{require,false}],
if
is_atom(Expr) ->
ET;
true ->
try
{_Value, _Binding, EE, _S} = elixir:eval_forms(Expr, [], ET),
EE
catch
Kind:Reason ->
Info = {M, F, length(Args), location(Line, E)},
erlang:raise(Kind, Reason, prune_stacktrace(Info, erlang:get_stacktrace()))
end
end
end, E, Callbacks).
{EE, ET} = elixir_dispatch:dispatch_require(Meta, M, F, Args, E, fun(AM, AF, AA) ->
Fun(AM, AF, AA),
{ok, E}
end),
if
is_atom(EE) ->
ET;
true ->
try
{_Value, _Binding, EF, _S} = elixir:eval_forms(EE, [], ET),
EF
catch
Kind:Reason ->
Info = {M, F, length(Args), location(Line, E)},
erlang:raise(Kind, Reason, prune_stacktrace(Info, erlang:get_stacktrace()))
end
end.
location(Line, E) ->
[{file, elixir_utils:characters_to_list(?m(E, file))}, {line, Line}].
@@ -401,6 +478,9 @@ prune_stacktrace(Info, []) ->
format_error({invalid_clause, {Name, Arity}}) ->
io_lib:format("empty clause provided for nonexistent function or macro ~ts/~B", [Name, Arity]);
format_error({invalid_external_resource, Value}) ->
io_lib:format("expected a string value for @external_resource, got: ~p",
['Elixir.Kernel':inspect(Value)]);
format_error({unused_doc, typedoc}) ->
"@typedoc provided but no type follows it";
format_error({unused_doc, doc}) ->
@@ -408,7 +488,7 @@ format_error({unused_doc, doc}) ->
format_error({internal_function_overridden, {Name, Arity}}) ->
io_lib:format("function ~ts/~B is internal and should not be overridden", [Name, Arity]);
format_error({invalid_module, Module}) ->
io_lib:format("invalid module name: ~p", [Module]);
io_lib:format("invalid module name: ~ts", ['Elixir.Kernel':inspect(Module)]);
format_error({module_defined, Module}) ->
io_lib:format("redefining module ~ts", [elixir_aliases:inspect(Module)]);
format_error({module_reserved, Module}) ->
+23 -15
View File
@@ -1,9 +1,9 @@
Nonterminals
grammar expr_list
expr container_expr block_expr no_parens_expr no_parens_one_expr access_expr
bracket_expr bracket_at_expr matched_expr unmatched_expr max_expr
bracket_expr bracket_at_expr bracket_arg matched_expr unmatched_expr max_expr
op_expr matched_op_expr no_parens_op_expr
comp_op_eol at_op_eol unary_op_eol and_op_eol or_op_eol
comp_op_eol at_op_eol unary_op_eol and_op_eol or_op_eol capture_op_eol
add_op_eol mult_op_eol exp_op_eol two_op_eol pipe_op_eol stab_op_eol
arrow_op_eol match_op_eol when_op_eol in_op_eol in_match_op_eol type_op_eol
open_paren close_paren empty_paren
@@ -29,7 +29,7 @@ Terminals
number signed_number atom atom_safe atom_unsafe bin_string list_string sigil
dot_call_op op_identifier
comp_op at_op unary_op and_op or_op arrow_op match_op in_op in_match_op type_op
dual_op add_op mult_op exp_op two_op pipe_op stab_op when_op assoc_op
dual_op add_op mult_op exp_op two_op pipe_op stab_op when_op assoc_op capture_op
'true' 'false' 'nil' 'do' eol ',' '.'
'(' ')' '[' ']' '{' '}' '<<' '>>' '%{}' '%'
.
@@ -45,6 +45,7 @@ Expect 2.
Left 5 do.
Right 10 stab_op_eol. %% ->
Left 20 ','.
Nonassoc 30 capture_op_eol. %% &
Left 40 in_match_op_eol. %% <-, \\ (allowed in matches along =)
Right 50 when_op_eol. %% when
Right 60 type_op_eol. %% ::
@@ -60,7 +61,7 @@ Right 200 two_op_eol. %% ++, --, .., <>
Left 210 add_op_eol. %% + (op), - (op)
Left 220 mult_op_eol. %% * (op), / (op)
Left 250 exp_op_eol. %% ^ (op) (e.g ^^^)
Nonassoc 300 unary_op_eol. %% +, -, !, ^, not, &, ~~~
Nonassoc 300 unary_op_eol. %% +, -, !, ^, not, ~~~
Left 310 dot_call_op.
Left 310 dot_op. %% .
Nonassoc 320 at_op_eol. %% @
@@ -112,6 +113,8 @@ matched_expr -> unary_op_eol matched_expr : build_unary_op('$1', '$2').
matched_expr -> unary_op_eol no_parens_expr : build_unary_op('$1', '$2').
matched_expr -> at_op_eol matched_expr : build_unary_op('$1', '$2').
matched_expr -> at_op_eol no_parens_expr : build_unary_op('$1', '$2').
matched_expr -> capture_op_eol matched_expr : build_unary_op('$1', '$2').
matched_expr -> capture_op_eol no_parens_expr : build_unary_op('$1', '$2').
matched_expr -> no_parens_one_expr : '$1'.
matched_expr -> access_expr : '$1'.
@@ -123,6 +126,7 @@ unmatched_expr -> matched_expr op_expr : build_op(element(1, '$2'), '$1', elemen
unmatched_expr -> unmatched_expr op_expr : build_op(element(1, '$2'), '$1', element(2, '$2')).
unmatched_expr -> unary_op_eol expr : build_unary_op('$1', '$2').
unmatched_expr -> at_op_eol expr : build_unary_op('$1', '$2').
unmatched_expr -> capture_op_eol expr : build_unary_op('$1', '$2').
unmatched_expr -> block_expr : '$1'.
block_expr -> parens_call call_args_parens do_block : build_identifier('$1', '$2' ++ '$3').
@@ -189,7 +193,9 @@ no_parens_one_expr -> dot_identifier : build_identifier('$1', nil).
%% marks identifiers followed by brackets as bracket_identifier.
access_expr -> bracket_at_expr : '$1'.
access_expr -> bracket_expr : '$1'.
access_expr -> at_op_eol number : build_unary_op('$1', ?exprs('$2')).
access_expr -> unary_op_eol number : build_unary_op('$1', ?exprs('$2')).
access_expr -> capture_op_eol number : build_unary_op('$1', ?exprs('$2')).
access_expr -> fn_eol stab end_eol : build_fn('$1', build_stab(reverse('$2'))).
access_expr -> open_paren stab close_paren : build_stab(reverse('$2')).
access_expr -> number : ?exprs('$1').
@@ -214,12 +220,17 @@ max_expr -> parens_call call_args_parens : build_identifier('$1', '$2').
max_expr -> parens_call call_args_parens call_args_parens : build_nested_parens('$1', '$2', '$3').
max_expr -> dot_alias : '$1'.
bracket_expr -> dot_bracket_identifier list : build_access(build_identifier('$1', nil), '$2').
bracket_expr -> access_expr list : build_access('$1', '$2').
bracket_arg -> open_bracket ']' : build_list('$1', []).
bracket_arg -> open_bracket kw close_bracket : build_list('$1', '$2').
bracket_arg -> open_bracket container_expr close_bracket : build_list('$1', '$2').
bracket_arg -> open_bracket container_expr ',' close_bracket : build_list('$1', '$2').
bracket_at_expr -> at_op_eol dot_bracket_identifier list :
bracket_expr -> dot_bracket_identifier bracket_arg : build_access(build_identifier('$1', nil), '$2').
bracket_expr -> access_expr bracket_arg : build_access('$1', '$2').
bracket_at_expr -> at_op_eol dot_bracket_identifier bracket_arg :
build_access(build_unary_op('$1', build_identifier('$2', nil)), '$3').
bracket_at_expr -> at_op_eol access_expr list :
bracket_at_expr -> at_op_eol access_expr bracket_arg :
build_access(build_unary_op('$1', '$2'), '$3').
%% Blocks
@@ -308,6 +319,9 @@ two_op_eol -> two_op eol : '$1'.
pipe_op_eol -> pipe_op : '$1'.
pipe_op_eol -> pipe_op eol : '$1'.
capture_op_eol -> capture_op : '$1'.
capture_op_eol -> capture_op eol : '$1'.
unary_op_eol -> unary_op : '$1'.
unary_op_eol -> unary_op eol : '$1'.
unary_op_eol -> dual_op : '$1'.
@@ -527,12 +541,6 @@ meta(Node) -> meta(?line(Node)).
%% Operators
build_op({_Kind, Line, '/'}, {'&', _, [{Kind, _, Atom} = Left]}, Right) when is_number(Right), is_atom(Atom), is_atom(Kind) ->
{'&', meta(Line), [{'/', meta(Line), [Left, Right]}]};
build_op({_Kind, Line, '/'}, {'&', _, [{{'.', _, [_, _]}, _, []} = Left]}, Right) when is_number(Right) ->
{'&', meta(Line), [{'/', meta(Line), [Left, Right]}]};
build_op({_Kind, Line, 'in'}, {UOp, _, [Left]}, Right) when ?rearrange_uop(UOp) ->
{UOp, meta(Line), [{'in', meta(Line), [Left, Right]}]};
@@ -615,7 +623,7 @@ build_fn(Op, Stab) ->
build_access(Expr, {List, Line}) ->
Meta = meta(Line),
{{'.', Meta, ['Elixir.Kernel', access]}, Meta, [Expr, List]}.
{{'.', Meta, ['Elixir.Access', get]}, Meta, [Expr, List]}.
%% Interpolation aware
+9 -11
View File
@@ -27,29 +27,27 @@ translate_var(Meta, Name, Kind, S) when is_atom(Kind); is_integer(Kind) ->
true ->
{{var, Line, Current}, S};
false ->
%% If the variable is not exported, we use a counter name.
%% The same if the variable already exists or we are in a
%% noname context.
Private = (lists:keyfind(export, 1, Meta) == {export, false}),
%% We attempt to give vars a nice name because we
%% still use the unused vars warnings from erl_lint.
%%
%% Once we move the warning to Elixir compiler, we
%% can name vars as _@COUNTER.
{NewVar, Counter, NS} =
if
Kind /= nil ->
build_var('_', S);
Private orelse Exists orelse S#elixir_scope.noname ->
build_var(Name, S);
true ->
{Name, 0, S}
build_var(Name, S)
end,
FS = NS#elixir_scope{
vars=orddict:store(Tuple, {NewVar, Counter}, Vars),
match_vars=ordsets:add_element(Tuple, MatchVars),
export_vars=case S#elixir_scope.export_vars of
EV when Private; EV == nil -> EV;
EV -> orddict:store(Tuple, {NewVar, Counter}, EV)
nil -> nil;
EV -> orddict:store(Tuple, {NewVar, Counter}, EV)
end
},
},
{{var, Line, NewVar}, FS}
end;
+12 -5
View File
@@ -6,8 +6,10 @@
-define(at_op(T),
T == $@).
-define(capture_op(T),
T == $&).
-define(unary_op(T),
T == $&;
T == $!;
T == $^).
@@ -294,7 +296,7 @@ tokenize([$:,T1,T2|Rest], Line, Scope, Tokens) when
% ## Single Token Operators
tokenize([$:,T|Rest], Line, Scope, Tokens) when
?at_op(T); ?unary_op(T); ?dual_op(T); ?mult_op(T); ?comp_op(T);
?at_op(T); ?unary_op(T); ?capture_op(T); ?dual_op(T); ?mult_op(T); ?comp_op(T);
?match_op(T); ?pipe_op(T); T == $. ->
tokenize(Rest, Line, Scope, [{atom, Line, list_to_atom([T])}|Tokens]);
@@ -386,6 +388,9 @@ tokenize([T1,T2|Rest], Line, Scope, Tokens) when ?stab_op(T1, T2) ->
tokenize([T|Rest], Line, Scope, Tokens) when ?at_op(T) ->
handle_unary_op(Rest, Line, at_op, list_to_atom([T]), Scope, Tokens);
tokenize([T|Rest], Line, Scope, Tokens) when ?capture_op(T) ->
handle_unary_op(Rest, Line, capture_op, list_to_atom([T]), Scope, Tokens);
tokenize([T|Rest], Line, Scope, Tokens) when ?unary_op(T) ->
handle_unary_op(Rest, Line, unary_op, list_to_atom([T]), Scope, Tokens);
@@ -549,7 +554,7 @@ handle_dot([$.,T1,T2|Rest], Line, Scope, Tokens) when
% ## Single Token Operators
handle_dot([$.,T|Rest], Line, Scope, Tokens) when
?at_op(T); ?unary_op(T); ?dual_op(T); ?mult_op(T); ?comp_op(T);
?at_op(T); ?unary_op(T); ?capture_op(T); ?dual_op(T); ?mult_op(T); ?comp_op(T);
?match_op(T); ?pipe_op(T); T == $% ->
handle_call_identifier(Rest, Line, list_to_atom([T]), Scope, Tokens);
@@ -709,11 +714,13 @@ tokenize_number([$_,H|T], Acc, Bool) when ?is_digit(H) ->
tokenize_number(T, [H|Acc], Bool);
%% Check if we have e- followed by numbers (valid only for floats);
tokenize_number([$e,S,H|T], Acc, true) when ?is_digit(H), S == $+ orelse S == $- ->
tokenize_number([E,S,H|T], Acc, true)
when (E == $E) or (E == $e), ?is_digit(H), S == $+ orelse S == $- ->
tokenize_number(T, [H,S,$e|Acc], true);
%% Check if we have e followed by numbers (valid only for floats);
tokenize_number([$e,H|T], Acc, true) when ?is_digit(H) ->
tokenize_number([E,H|T], Acc, true)
when (E == $E) or (E == $e), ?is_digit(H) ->
tokenize_number(T, [H,$e|Acc], true);
%% Finally just numbers.
+62 -35
View File
@@ -70,24 +70,38 @@ translate({'&', Meta, [Arg]}, S) when is_integer(Arg) ->
translate({fn, Meta, Clauses}, S) ->
elixir_fn:translate(Meta, Clauses, S);
%% Cond
translate({'cond', _Meta, [[{do, Pairs}]]}, S) ->
[{'->', Meta, [[Condition], Body]}|T] = lists:reverse(Pairs),
Case =
case Condition of
{'_', _, Atom} when is_atom(Atom) ->
compile_error(Meta, S#elixir_scope.file, "unbound variable _ inside cond. "
"If you want the last clause to always match, you probably meant to use: true ->");
X when is_atom(X) and (X /= false) and (X /= nil) ->
build_cond_clauses(T, Body, Meta);
_ ->
{Truthy, Other} = build_truthy_clause(Meta, Condition, Body),
Error = {{'.', Meta, [erlang, error]}, [], [cond_clause]},
Falsy = {'->', Meta, [[Other], Error]},
Acc = {'case', Meta, [Condition, [{do, [Truthy, Falsy]}]]},
build_cond_clauses(T, Acc, Meta)
end,
translate(Case, S);
%% Case
translate({'case', Meta, [Expr, KV]}, #elixir_scope{return=Return} = RS) when is_list(KV) ->
S = RS#elixir_scope{noname=true, return=true},
Clauses = elixir_clauses:get_pairs(do, KV),
translate({'case', Meta, [Expr, KV]}, #elixir_scope{return=Return} = RS) ->
S = RS#elixir_scope{return=true},
Clauses = elixir_clauses:get_pairs(do, KV, match),
{TExpr, NS} = translate(Expr, S),
RClauses = case elixir_utils:returns_boolean(TExpr) of
true -> rewrite_case_clauses(Clauses);
false -> Clauses
end,
{TClauses, TS} = elixir_clauses:clauses(Meta, RClauses, Return, NS),
{TClauses, TS} = elixir_clauses:clauses(Meta, Clauses, Return, NS),
{{'case', ?line(Meta), TExpr, TClauses}, TS};
%% Try
translate({'try', Meta, [Clauses]}, #elixir_scope{return=Return} = RS) when is_list(Clauses) ->
translate({'try', Meta, [Clauses]}, #elixir_scope{return=Return} = RS) ->
S = RS#elixir_scope{noname=true, return=true},
Do = proplists:get_value('do', Clauses, nil),
{TDo, SB} = elixir_translator:translate(Do, S),
@@ -103,7 +117,7 @@ translate({'try', Meta, [Clauses]}, #elixir_scope{return=Return} = RS) when is_l
{TAfter, SA} = {[], mergec(S, SC)}
end,
Else = elixir_clauses:get_pairs(else, Clauses),
Else = elixir_clauses:get_pairs(else, Clauses, match),
{TElse, SE} = elixir_clauses:clauses(Meta, Else, Return, mergec(S, SA)),
SF = (mergec(S, SE))#elixir_scope{noname=RS#elixir_scope.noname},
@@ -111,16 +125,16 @@ translate({'try', Meta, [Clauses]}, #elixir_scope{return=Return} = RS) when is_l
%% Receive
translate({'receive', Meta, [KV]}, #elixir_scope{return=Return} = RS) when is_list(KV) ->
translate({'receive', Meta, [KV]}, #elixir_scope{return=Return} = RS) ->
S = RS#elixir_scope{return=true},
Do = elixir_clauses:get_pairs(do, KV, true),
Do = elixir_clauses:get_pairs(do, KV, match, true),
case lists:keyfind('after', 1, KV) of
false ->
{TClauses, SC} = elixir_clauses:clauses(Meta, Do, Return, S),
{{'receive', ?line(Meta), TClauses}, SC};
_ ->
After = elixir_clauses:get_pairs('after', KV),
After = elixir_clauses:get_pairs('after', KV, expr),
{TClauses, SC} = elixir_clauses:clauses(Meta, Do ++ After, Return, S),
{FClauses, TAfter} = elixir_utils:split_last(TClauses),
{_, _, [FExpr], _, FAfter} = TAfter,
@@ -129,7 +143,7 @@ translate({'receive', Meta, [KV]}, #elixir_scope{return=Return} = RS) when is_li
%% Comprehensions
translate({for, Meta, Args}, S) when is_list(Args) ->
translate({for, Meta, [_|_] = Args}, S) ->
elixir_for:translate(Meta, Args, S);
%% Super
@@ -154,7 +168,7 @@ translate({super, Meta, Args}, S) when is_list(Args) ->
%% Variables
translate({'^', Meta, [ {Name, VarMeta, Kind} ]}, #elixir_scope{context=match} = S) when is_atom(Name), is_atom(Kind) ->
translate({'^', Meta, [{Name, VarMeta, Kind}]}, #elixir_scope{context=match} = S) when is_atom(Name), is_atom(Kind) ->
Tuple = {Name, var_kind(VarMeta, Kind)},
case orddict:find(Tuple, S#elixir_scope.backup_vars) of
{ok, {Value, _Counter}} ->
@@ -316,24 +330,9 @@ var_kind(Meta, Kind) ->
false -> Kind
end.
%% Case
rewrite_case_clauses([
{do,Meta1,[{'when',_,[{V,M,C},{'__op__',_,['orelse',_,_]}]}],False},
{do,Meta2,[{'_',_,UC}],True}] = Clauses)
when is_atom(V), is_list(M), is_atom(C), is_atom(UC) ->
case lists:keyfind('cond', 1, M) of
{'cond',true} ->
[{do,Meta1,[false],False},{do,Meta2,[true],True}];
_ ->
Clauses
end;
rewrite_case_clauses(Clauses) ->
Clauses.
%% Pack a list of expressions from a block.
unblock({'block', _, Exprs}) -> Exprs;
unblock(Expr) -> [Expr].
unblock(Expr) -> [Expr].
%% Translate args
@@ -367,15 +366,43 @@ translate_block(Expr, Return, S) ->
false -> translate(Expr, S)
end.
%% Expressions that can handle no return may receive
%% return=false but must always return return=true.
%% return is typically true, except when we find one
%% of the expressions below, which may handle return=false
%% but must always return return=true.
handles_no_return({'try', _, [_]}) -> true;
handles_no_return({'cond', _, [_]}) -> true;
handles_no_return({'for', _, [_|_]}) -> true;
handles_no_return({'case', _, [_, _]}) -> true;
handles_no_return({'receive', _, [_]}) -> true;
handles_no_return({'__block__', _, [_|_]}) -> true;
handles_no_return(_) -> false.
%% Cond
build_cond_clauses([{'->', NewMeta, [[Condition], Body]}|T], Acc, OldMeta) ->
{Truthy, Other} = build_truthy_clause(NewMeta, Condition, Body),
Falsy = {'->', OldMeta, [[Other], Acc]},
Case = {'case', NewMeta, [Condition, [{do, [Truthy, Falsy]}]]},
build_cond_clauses(T, Case, NewMeta);
build_cond_clauses([], Acc, _) ->
Acc.
build_truthy_clause(Meta, Condition, Body) ->
case elixir_utils:returns_boolean(Condition) of
true ->
{{'->', Meta, [[true], Body]}, false};
false ->
Var = {'cond', [], 'Elixir'},
Head = {'when', [], [Var,
{'__op__', [], [
'andalso',
{{'.', [], [erlang, '/=']}, [], [Var, nil]},
{{'.', [], [erlang, '/=']}, [], [Var, false]}
]}
]},
{{'->', Meta, [[Head], Body]}, {'_', [], nil}}
end.
%% Assertions
assert_module_scope(Meta, Kind, #elixir_scope{module=nil,file=File}) ->
+14 -24
View File
@@ -3,8 +3,8 @@
-include("elixir.hrl").
clauses(_Meta, Clauses, Return, S) ->
Catch = elixir_clauses:get_pairs('catch', Clauses),
Rescue = elixir_clauses:get_pairs(rescue, Clauses),
Catch = elixir_clauses:get_pairs('catch', Clauses, 'catch'),
Rescue = elixir_clauses:get_pairs(rescue, Clauses, rescue),
reduce_clauses(Rescue ++ Catch, [], S, Return, S).
reduce_clauses([H|T], Acc, SAcc, Return, S) ->
@@ -18,10 +18,7 @@ each_clause({'catch', Meta, Raw, Expr}, Return, S) ->
Final = case Args of
[X] -> [throw, X, {'_', Meta, nil}];
[X,Y] -> [X, Y, {'_', Meta, nil}];
_ ->
elixir_errors:compile_error(Meta, S#elixir_scope.file,
"too many arguments given for catch")
[X,Y] -> [X, Y, {'_', Meta, nil}]
end,
Condition = [{'{}', Meta, Final}],
@@ -103,27 +100,17 @@ rescue_guards(Meta, Var, Aliases, S) ->
%% Matching of variables is done with Erlang exceptions is done in
%% function for optimization.
rescue_each_ref(Meta, Var, [H|T], Elixir, Erlang, S) when
H == 'Elixir.UndefinedFunctionError'; H == 'Elixir.ErlangError';
H == 'Elixir.ArgumentError'; H == 'Elixir.ArithmeticError';
H == 'Elixir.BadArityError'; H == 'Elixir.BadFunctionError';
H == 'Elixir.MatchError'; H == 'Elixir.CaseClauseError';
H == 'Elixir.TryClauseError'; H == 'Elixir.FunctionClauseError';
H == 'Elixir.SystemLimitError'; H == 'Elixir.BadStructError' ->
Expr = erl_rescue_guard_for(Meta, Var, H),
rescue_each_ref(Meta, Var, T, [H|Elixir], [Expr|Erlang], S);
rescue_each_ref(Meta, Var, [H|T], Elixir, Erlang, S) when is_atom(H) ->
rescue_each_ref(Meta, Var, T, [H|Elixir], Erlang, S);
case erl_rescue_guard_for(Meta, Var, H) of
false -> rescue_each_ref(Meta, Var, T, [H|Elixir], Erlang, S);
Expr -> rescue_each_ref(Meta, Var, T, [H|Elixir], [Expr|Erlang], S)
end;
rescue_each_ref(_, _, [], Elixir, Erlang, _) ->
{Elixir, Erlang}.
%% Handle erlang rescue matches.
erl_rescue_guard_for(Meta, Var, List) when is_list(List) ->
join(Meta, fun erl_or/3, [erl_rescue_guard_for(Meta, Var, X) || X <- List]);
erl_rescue_guard_for(Meta, Var, 'Elixir.UndefinedFunctionError') ->
{erl(Meta, '=='), Meta, [Var, undef]};
@@ -136,6 +123,9 @@ erl_rescue_guard_for(Meta, Var, 'Elixir.SystemLimitError') ->
erl_rescue_guard_for(Meta, Var, 'Elixir.ArithmeticError') ->
{erl(Meta, '=='), Meta, [Var, badarith]};
erl_rescue_guard_for(Meta, Var, 'Elixir.CondClauseError') ->
{erl(Meta, '=='), Meta, [Var, cond_clause]};
erl_rescue_guard_for(Meta, Var, 'Elixir.BadArityError') ->
erl_and(Meta,
erl_tuple_size(Meta, Var, 2),
@@ -178,7 +168,10 @@ erl_rescue_guard_for(Meta, Var, 'Elixir.ErlangError') ->
IsException = {erl(Meta, '/='), Meta, [
{erl(Meta, element), Meta, [2, Var]}, '__exception__'
]},
erl_or(Meta, IsNotTuple, IsException).
erl_or(Meta, IsNotTuple, IsException);
erl_rescue_guard_for(_, _, _) ->
false.
%% Helpers
@@ -191,9 +184,6 @@ erl_record_compare(Meta, Var, Expr) ->
Expr
]}.
join(Meta, Kind, [H|T]) ->
lists:foldl(fun(X, Acc) -> Kind(Meta, Acc, X) end, H, T).
prepend_to_block(_Meta, Expr, {'__block__', Meta, Args}) ->
{'__block__', Meta, [Expr|Args]};
+26 -16
View File
@@ -4,7 +4,7 @@
-export([elixir_to_erl/1, get_line/1, split_last/1,
characters_to_list/1, characters_to_binary/1, macro_name/1,
convert_to_boolean/4, returns_boolean/1, atom_concat/1,
file_type/1, file_type/2, relative_to_cwd/1, erl_call/4]).
read_file_type/1, read_link_type/1, relative_to_cwd/1, erl_call/4]).
-include("elixir.hrl").
-include_lib("kernel/include/file.hrl").
@@ -31,11 +31,14 @@ split_last(List) -> split_last(List, []).
split_last([H], Acc) -> {lists:reverse(Acc), H};
split_last([H|T], Acc) -> split_last(T, [H|Acc]).
file_type(File) ->
file_type(File, read_link_info).
read_file_type(File) ->
case file:read_file_info(File) of
{ok, #file_info{type=Type}} -> {ok, Type};
{error, _} = Error -> Error
end.
file_type(File, Op) ->
case file:Op(File) of
read_link_type(File) ->
case file:read_link_info(File) of
{ok, #file_info{type=Type}} -> {ok, Type};
{error, _} = Error -> Error
end.
@@ -126,36 +129,43 @@ elixir_to_erl_cons_2([], Acc) ->
%% Boolean checks
returns_boolean({op, _, Op, _}) when Op == 'not' -> true;
returns_boolean(Bool) when is_boolean(Bool) -> true;
returns_boolean({op, _, Op, _, _}) when
returns_boolean({{'.', _, [erlang, Op]}, _, [_]}) when Op == 'not' -> true;
returns_boolean({{'.', _, [erlang, Op]}, _, [_, _]}) when
Op == 'and'; Op == 'or'; Op == 'xor';
Op == '=='; Op == '/='; Op == '=<'; Op == '>=';
Op == '<'; Op == '>'; Op == '=:='; Op == '=/=' -> true;
returns_boolean({op, _, Op, _, Right}) when Op == 'andalso'; Op == 'orelse' ->
returns_boolean({'__op__', _, [Op, _, Right]}) when Op == 'andalso'; Op == 'orelse' ->
returns_boolean(Right);
returns_boolean({call, _, {remote, _, {atom, _, erlang}, {atom, _, Fun}}, [_]}) when
returns_boolean({{'.', _, [erlang, Fun]}, _, [_]}) when
Fun == is_atom; Fun == is_binary; Fun == is_bitstring; Fun == is_boolean;
Fun == is_float; Fun == is_function; Fun == is_integer; Fun == is_list;
Fun == is_number; Fun == is_pid; Fun == is_port; Fun == is_reference;
Fun == is_tuple -> true;
returns_boolean({call, _, {remote, _, {atom, _, erlang}, {atom, _, Fun}}, [_,_]}) when
returns_boolean({{'.', _, [erlang, Fun]}, _, [_, _]}) when
Fun == is_function -> true;
returns_boolean({call, _, {remote, _, {atom, _, erlang}, {atom, _, Fun}}, [_,_,_]}) when
returns_boolean({{'.', _, [erlang, Fun]}, _, [_, _, _]}) when
Fun == function_exported -> true;
returns_boolean({atom, _, Bool}) when is_boolean(Bool) -> true;
returns_boolean({'case', _, _, Clauses}) ->
returns_boolean({'case', _, [_, [{do, Clauses}]]}) ->
lists:all(fun
({clause,_,_,_,[Expr]}) -> returns_boolean(Expr);
(_) -> false
({'->',_,[_, Expr]}) -> returns_boolean(Expr)
end, Clauses);
returns_boolean({'cond', _, [[{do, Clauses}]]}) ->
lists:all(fun
({'->',_,[_, Expr]}) -> returns_boolean(Expr)
end, Clauses);
returns_boolean({'__block__', [], Exprs}) ->
returns_boolean(lists:last(Exprs));
returns_boolean(_) -> false.
convert_to_boolean(Line, Expr, Bool, S) when is_integer(Line) ->
+20 -10
View File
@@ -17,30 +17,40 @@ defmodule AccessTest do
@mod -13
-13 = @mod
test :nil do
test "for nil" do
assert nil[:foo] == nil
assert Access.get(nil, :foo) == nil
assert Access.get_and_update(nil, :foo, fn nil -> {:ok, :bar} end) == {:ok, :bar}
end
test :list do
test "for keywords" do
assert [foo: :bar][:foo] == :bar
assert [foo: [bar: :baz]][:foo][:bar] == :baz
assert [foo: [bar: :baz]][:fuu][:bar] == nil
assert Access.get([foo: :bar], :foo) == :bar
assert Access.get_and_update([], :foo, fn nil -> {:ok, :baz} end) == {:ok, [foo: :baz]}
assert Access.get_and_update([foo: :bar], :foo, fn :bar -> {:ok, :baz} end) == {:ok, [foo: :baz]}
end
test :map do
test "for maps" do
assert %{foo: :bar}[:foo] == :bar
assert %{1 => 1}[1] == 1
assert %{1.0 => 1.0}[1.0] == 1.0
assert %{1 => 1}[1.0] == nil
assert Access.get(%{foo: :bar}, :foo) == :bar
assert Access.get_and_update(%{}, :foo, fn nil -> {:ok, :baz} end) == {:ok, %{foo: :baz}}
assert Access.get_and_update(%{foo: :bar}, :foo, fn :bar -> {:ok, :baz} end) == {:ok, %{foo: :baz}}
end
test :atom do
exception = assert_raise RuntimeError, fn ->
foo = :foo
foo[:atom]
test "for atoms" do
assert_raise Protocol.UndefinedError, ~r"protocol Access not implemented for :foo", fn ->
Access.get(:foo, :bar)
end
assert_raise Protocol.UndefinedError, ~r"protocol Access not implemented for :foo", fn ->
Access.get_and_update(:foo, :bar, fn _ -> {:ok, :baz} end)
end
assert Exception.message(exception) ==
"The access protocol can only be invoked for atoms " <>
"at compilation time, tried to invoke it for :foo"
end
end

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