Compare commits

...
Author SHA1 Message Date
José Valim 2f2a52a648 Release v0.15.0 2014-08-02 21:49:21 +02:00
José Valim 89fd8f305f Print a nice prelude if we fail to load a project, closes #2430 2014-08-02 15:19:53 +02:00
José Valim 353c043d8c Use Mix.Shell.printable_app_name 2014-08-02 14:50:55 +02:00
José Valim 54a40efeec Push the given app name as default on Mix.Project 2014-08-02 14:29:12 +02:00
José Valim f781795032 Bump hex requirement 2014-08-02 14:04:07 +02:00
José Valim 54ffb5768d Add a test case for printing samples on IO shell 2014-08-02 12:13:36 +02:00
José Valim 8fc068821f Print app name on Mix.shell.cmd/1/2 2014-08-02 12:08:42 +02:00
Eric Meadows-Jönsson 1f91fd351e Merge pull request #2619 from knewter/feature/2616_print_app_name_before_compiling_makefile_dependencies
Print app name before compiling Makefile deps
2014-08-02 03:00:03 +02:00
Josh Adams ab4399323f Print app name before compiling Makefile deps
This should close out #2616.
2014-08-01 19:40:11 -05:00
José Valim 3b34a57bd2 Avoid race conditions in the struct detection 2014-08-01 23:58:02 +02:00
José Valim 62a469dc23 Improve mix new error messages 2014-08-01 21:36:28 +02:00
José Valim 8b0c4127ef Merge pull request #2618 from patrickgombert/mix-new
Validate app and module on mix new
2014-08-01 21:22:56 +02:00
Patrick Gombert dc4b7c89ea Validate app and module on mix new 2014-08-01 14:15:15 -05:00
José Valim b5febd991c Allow elixirc_paths to also be given through the command line
Once given, the command line overrides the project
configuration in `mix compile.elixir`.

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

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

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

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2014-07-08 11:39:18 +02:00
José Valim 597d90b326 Add comments to code:ensure_loaded/1 snippets 2014-07-08 11:28:24 +02:00
José Valim 5dcad26b79 Ensure we don't loop when printing inspect failures, closes #2503 2014-07-08 11:25:49 +02:00
José Valim f8c2d5cb4f Merge pull request #2502 from alco/escript-embed-elixir
Fix the usage of `embed_elixir` in escript.build
2014-07-08 10:46:31 +02:00
Alexei Sholik b0f432b2f8 Fix the usage of embed_elixir in escript.build 2014-07-08 01:27:43 +03:00
José Valim 651a4bad67 Clarify behaviour of split on Windows 2014-07-07 20:31:27 +02:00
José Valim f52e350adc Ensure deps are not recompiled every second time, closes #2471 2014-07-07 17:38:51 +02:00
José Valim 35e8019447 Improve mix docs 2014-07-07 17:29:02 +02:00
José Valim a6fab4a111 Use a native splitting on String.split/1 instead of relying on regexes
The new implementation is 20x faster for small strings and about
100x times faster for large ones.
2014-07-06 21:20:52 +02:00
José Valim 64d62d2472 Emit warnings for underscored variables appearing twice in a pattern, closes #2480 2014-07-06 20:18:00 +02:00
Eric Meadows-Jönsson 10b322aa48 Rename Mix.Shell.info option :escape => :ansi 2014-07-06 16:28:38 +02:00
Eric Meadows-Jönsson 981955301d Shell info messages can still be escaped 2014-07-06 02:39:16 +02:00
Eric Meadows-Jönsson ac425f04ef Ensure the message is not ANSI escaped
Closes #2496.
2014-07-06 02:20:40 +02:00
José Valim 1dda1563da Ensure docs with no function raise warnings 2014-07-05 22:25:15 +02:00
José Valim 0e71b1e711 Merge pull request #2495 from thmzlt/patch-1
Fix typo in GenEvent moduledoc
2014-07-05 22:11:31 +02:00
thmzlt 841f6f2387 Fix typo in GenEvent moduledoc 2014-07-05 17:08:49 -03:00
José Valim 2eb7fb97de Check if ANSI escape codes are enabled on IEx's clear, closes #2441 2014-07-05 15:16:00 +02:00
José Valim e781983e6b Fix regex compilation and set ucp flags 2014-07-05 15:01:25 +02:00
José Valim 7d17a12783 Add test cases for ExUnit.DocTest failures 2014-07-05 14:53:24 +02:00
jw2013 d888057689 throws error for missing docs in DocTest
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2014-07-05 14:49:10 +02:00
José Valim 76d6e05399 Ensure case wrapper in cond gets the proper line 2014-07-05 14:45:07 +02:00
José Valim 1bd0ca6dea Return explicit :ok from more functions 2014-07-05 13:43:52 +02:00
José Valim 8e1f7fddca Always return ok from tracking functions 2014-07-05 13:21:59 +02:00
José Valim abda47903b Merge pull request #2494 from jw2013/typo
fix typo in range moduledoc
2014-07-05 10:39:49 +02:00
jw2013 030e72c9c6 fix range typo 2014-07-04 22:07:08 -07:00
Eric Meadows-Jönsson 88dbad9650 Fix some sentence tenses in changelog 2014-07-05 03:19:47 +02:00
Eric Meadows-Jönsson 07263b8533 Merge pull request #2493 from jwarwick/changelog_typos
Fixed Changelog typos
2014-07-05 03:13:44 +02:00
John Warwick f67741b6dc Fixed Changelog typos 2014-07-04 20:51:13 -04:00
José Valim 4724ff12e2 Fix bug when flat_map is used inside another flat_map with enumerable, closes #2483 2014-07-04 21:51:57 +02:00
José Valim f7c7ff46b4 Store stacktrace before invoking callback function 2014-07-04 19:27:05 +02:00
José Valim c4868f8549 s/file/function in IO docs 2014-07-04 19:26:31 +02:00
José Valim 85951052ca Add a note for binary_part/3 on String.slice/3 2014-07-04 18:37:02 +02:00
José Valim 28bd15b76c Consider multiple @external_resource entries in mix 2014-07-04 18:30:55 +02:00
José Valim 1fdb715967 Add a note about IO.bin* 2014-07-04 18:30:54 +02:00
Eric Meadows-Jönsson 0279547232 Re-add deprecated local tasks 2014-07-04 15:46:23 +02:00
José Valim 7fabf32d9e Do not crash if applications are already started 2014-07-04 12:13:53 +02:00
José Valim 4cbf0eb2ac Merge pull request #2484 from fishcakez/dialyzer_fixes
Fix unmatched returns dialyzer warnings
2014-07-04 11:54:55 +02:00
José Valim 071b0830f8 Ensure line numbers are properly preserved in EEx, closes #2485 2014-07-04 11:54:07 +02:00
James Fish 39cd4a6594 Fix unmatched returns dialyzer warnings 2014-07-04 02:45:16 +01:00
José Valim 0d2332fe5e Update CHANGELOG 2014-07-03 21:03:17 +02:00
José Valim c807fa6ec5 Add Mix.compilers 2014-07-03 21:01:13 +02:00
José Valim 4a10806d9a Pass a list to :error_logger 2014-07-03 20:51:41 +02:00
José Valim 57031f8cb0 Improve typespecs for protocols once more 2014-07-03 20:04:06 +02:00
José Valim b292486ac9 Improve more typespecs 2014-07-03 19:14:06 +02:00
José Valim 595f9bb0b1 Improve poorly specified callbacks 2014-07-03 16:50:37 +02:00
José Valim ebe3397ad9 Do not include unreachable locals 2014-07-03 16:26:44 +02:00
José Valim bc12b76bcc Merge pull request #2481 from fishcakez/remote_call_ast
Fix remote call AST
2014-07-03 14:45:17 +02:00
José Valim d8df85e99c Update plt in .gitignore 2014-07-03 14:15:58 +02:00
José Valim 80b9879d0a Refactor how definitions are analyzed 2014-07-03 14:15:24 +02:00
James Fish d54f9ec931 Fix remote call AST 2014-07-03 11:59:25 +01:00
José Valim d2258acbb3 Ensure yanked locals are added back with all nodes, closes #2188 2014-07-03 11:43:42 +02:00
José Valim 6c3a4bd019 Fix the guard for this clause evaluates to false warning
Since the guard can be hidden inside if/unless, it is better
to have a more generic error, stating we will always get the
same result from an check/guard.
2014-07-03 00:58:02 +02:00
José Valim 7903211546 Merge pull request #2479 from jwarwick/process_typo
Fixed Process doc typos
2014-07-02 22:35:49 +02:00
John Warwick 6b87fcf903 Fixed Process doc typos 2014-07-02 16:33:57 -04:00
José Valim 07d0d1b275 Improve some typespecs 2014-07-02 22:32:16 +02:00
José Valim 95361172a0 Update CHANGELOG 2014-07-02 22:05:41 +02:00
José Valim a0f3086167 Convert existing bitstring examples to new syntax 2014-07-02 21:32:00 +02:00
José Valim 65a59b41e5 New bitstring syntax, closes #1950 2014-07-02 21:19:01 +02:00
José Valim 78dc02fb81 Merge pull request #2477 from fishcakez/dialyze
Fix dialyzer makefile commands
2014-07-02 17:37:17 +02:00
José Valim dc577b8bfa Ensure mix preserves -pa and -pz given to the command line 2014-07-02 17:30:20 +02:00
José Valim ed9862ea91 First deprecate types in struct fields 2014-07-02 17:30:20 +02:00
James Fish f710c61179 Fix dialyzer makefile commands 2014-07-02 15:55:02 +01:00
Eric Meadows-Jönsson d86cf14f0d Add shorthand syntax for nonempty_list() 2014-07-02 15:48:03 +02:00
Eric Meadows-Jönsson bdfd5817dc Fix typespec_to_ast for lists 2014-07-02 02:44:47 +02:00
José Valim 3b2ece4ddc Merge pull request #2474 from chyndman/elixir-bat-short-circuit
Fix for batch file misbehaving with parenthetical arguments
2014-07-02 00:12:56 +02:00
José Valim 3452f364aa Improve typespecs and docs 2014-07-02 00:12:06 +02:00
José Valim dad77a4d4b Mention MIX_ENV on compile.protocols 2014-07-02 00:12:06 +02:00
Eric Meadows-Jönsson c8b925c581 Merge pull request #2475 from alco/version-doc-fix
Fix weird examples in Version docs
2014-07-02 00:02:43 +02:00
Alexei Sholik 24297c87e6 Fix weird examples in Version docs 2014-07-02 00:26:54 +03:00
Chris Hyndman 0c589eff04 Umanbiguous check for parameters 2014-07-01 13:51:25 -07:00
Chris Hyndman 3a3684ca78 Process Erlang commands last 2014-07-01 13:44:23 -07:00
José Valim 6e39366c40 Merge pull request #2473 from jwarwick/changelog_typo
Fixed CHANGELOG typo
2014-07-01 20:45:46 +02:00
John Warwick fd0ccb7741 Fixed CHANGELOG typo 2014-07-01 14:09:09 -04:00
José Valim 81f151245e Merge pull request #2472 from michaelkschmidt/master
Use absolute symbolic links on windows
2014-07-01 19:06:30 +02:00
Michael Schmidt acfdd5b59e Use absolute symbolic links on windows 2014-07-01 11:17:30 -05:00
Eric Meadows-Jönsson 588800c495 Merge pull request #2467 from ericmj/multiclause-defaule
Raise on multiple clauses with default arg
2014-07-01 16:51:12 +02:00
Eric Meadows-Jönsson cfd157ab7d Merge pull request #2466 from ericmj/typespec-struct
Remove default generation of `@type t` for structs
2014-07-01 16:51:01 +02:00
Eric Meadows-Jönsson 377eb6126d Raise on multiple clauses with default arg 2014-07-01 16:41:19 +02:00
Eric Meadows-Jönsson 2cad4c96bd Remove default generation of @type t for structs
`Struct{}` in typespecs will generate default types for all missing
fields.
2014-07-01 16:23:30 +02:00
José Valim 2453501c83 Rename escriptize to escript.build 2014-07-01 15:23:24 +02:00
José Valim dcc3c6290e Deprecate embed_extra_apps in escriptize 2014-07-01 15:17:59 +02:00
José Valim 54240e1b4f Inline functions in Port module 2014-07-01 14:54:56 +02:00
José Valim 6639f91ba2 No longer fill in missing intermediate values with empty maps in Access 2014-07-01 13:46:49 +02:00
José Valim 15bf309760 Improve error message for struct expansion in invalid contexts 2014-07-01 12:41:27 +02:00
José Valim 1de0302ecb Fix failing test and improve zip/unzip docs 2014-07-01 12:39:06 +02:00
José Valim 871cbda3f8 Allow functions in get_in/2 and get_and_update_in/3 (#2470) 2014-07-01 12:25:07 +02:00
José Valim c4ea85d7e5 Bug fixes on windows for buggy 17.1 2014-07-01 10:16:39 +02:00
José Valim a8f3125774 Deprecate lists in find_executable/1 2014-06-30 21:43:26 +02:00
José Valim c564af180b Update CHANGELOG 2014-06-30 21:35:54 +02:00
José Valim 3ebe1ed728 Use binaries so we can use Elixir functions 2014-06-30 21:30:01 +02:00
José Valim 6b030b2978 Use the new System.cmd/3 in stdlib and apps 2014-06-30 21:15:00 +02:00
José Valim 9f2fa0f7af Add System.cmd/3, closes #2251 2014-06-30 20:50:44 +02:00
José Valim da06e35443 Remove wrong usage of exit(0|1) 2014-06-30 20:35:06 +02:00
José Valim da4d047a37 Use iolists in collectable for binaries 2014-06-30 20:33:38 +02:00
José Valim 782314abff Also use exit({:shutdown, 1}) on mix test 2014-06-30 16:59:11 +02:00
José Valim 94d53f052b Use exit({:shutdown, int}) to signal shell status 2014-06-30 16:38:26 +02:00
José Valim 9ceebf96db Move local.install and local.uninstall to archive.* 2014-06-30 12:28:51 +02:00
José Valim 8611bb7322 Run command line and escripts in a new process without trap exit
Closes #1985
2014-06-30 11:48:31 +02:00
José Valim ba2e1c95ef Remove duplicated specs and old PR example 2014-06-30 10:17:31 +02:00
José Valim 5979b5ca75 Merge pull request #2464 from KronicDeth/feature/2460/enum-sort-by
Enum.sort_by/3
2014-06-30 10:09:45 +02:00
Luke Imhoff f144cae300 Fix phrasing of Enum.sort_by/3 @doc 2014-06-29 16:09:52 -05:00
Luke Imhoff 8bcf473b68 Pull reused test data into variable 2014-06-29 16:03:42 -05:00
Luke Imhoff b10fdfc7b8 Fix spacing 2014-06-29 16:03:09 -05:00
Luke Imhoff 95eb1c0978 Fix @spec sort_by
Use format provided by José Valim that uses `when` to eliminate the need
for `@type mapped_element`.
2014-06-29 15:30:34 -05:00
Luke Imhoff 7a5cb8d374 Add @spec for Enum.sort_by/3 2014-06-28 20:38:01 -05:00
Luke Imhoff f23b133d29 Add @doc for Enum.sort_by/3 2014-06-28 20:23:32 -05:00
Luke Imhoff 0bdee49f7f Enum.sort_by/3
Enum.sort_by/3 takes a collection, a mapper function, an an optional
sorter function.  The function sorts by the mapped value using the sorter.
The optional sorter default to `<=/2`.
2014-06-28 17:52:04 -05:00
237 changed files with 7726 additions and 3215 deletions
+1 -2
View File
@@ -10,5 +10,4 @@
/lib/elixir/test/ebin /lib/elixir/test/ebin
/rel/elixir /rel/elixir
erl_crash.dump erl_crash.dump
.dialyzer_plt .elixir.plt
.dialyzer.base_plt
+1
View File
@@ -7,3 +7,4 @@ notifications:
- eric.meadows.jonsson@gmail.com - eric.meadows.jonsson@gmail.com
otp_release: otp_release:
- 17.0 - 17.0
- 17.1
+87 -7
View File
@@ -1,18 +1,98 @@
# Changelog # Changelog
## v0.14.3-dev ## v0.15.0 (2014-08-02)
* Enhancements * Enhancements
* [Agent] Improve the Agent API to also accept functions that receive explicit module, function and arguments
* [IEx] Support `--werl` call on Windows
* [Logger] Add `Logger`
* [Map] Add `Map.from_struct/1`
* [Mix] Allow `--app` flag to be passed to `mix new`
* [Mix] Support lowercase `http(s)_proxy` environment variables
* [Mix] Allow `elixirc_paths` to also be given through the command line to `mix compile.elixir`
* [String] `String.slice/2` and `String.slice/3` have been optimized
* Bug fixes * Bug fixes
* [Kernel] `|>`, `<<<`, `>>>` and `^^^` were made left associative in operator table * [IEx] Ensure functions in `Kernel.SpecialForms` and `IEx.Helpers` are also auto-completed
* [Kernel] `<`, `>`, `<=`, `>=` were given higher precedence than comparison ones (`==`, `!=`, etc) in operator table * [IEx] Ensure remote shells can be started with `--remsh`
* [Kernel] Correctly parse unary/binary operators regardless of number of spaces
* Soft deprecations (no warnings emitted) * [Kernel] Ensure private functions are not exported
* [Protocol] Do not expose protocol convention on `assert_impl!/2`
* [Regex] Do not consider include captures on `Regex.split/3` results
* [Stream] Implement the `Inspect` protocol for Streams so we do not leak the Stream representation
* Deprecations * Deprecations
* [IEx] IEx color configuration expects a list of atoms instead of a string with colors separated by comma
* [Inspect] `Inspect.Algebra.surround_many/6` now expects Inspect.Opts instead of an integer limit
* [Inspect] `Inspect.Algebra.pretty/2` is deprecated in favor of `Inspect.Algebra.format/2` that instead returns iodata. This function was used only by documentation examples and it is unlikely to affect actual code
* [IO] `IO.ANSI.terminal?` is deprecated in favor of `IO.ANSI.enabled?`
* [IO] `IO.ANSI.escape/2` and `IO.ANSI.escape_fragment/2` is deprecated in favor of `IO.ANSI.format/2` and `IO.ANSI.format_fragment/2`
* [Kernel] Leading `0` for octals is deprecated in favor of `0o`
* [Kernel] `0X` for hexadecimals is deprecated in favor of `0x`
* [Kernel] `0B` for binaries is deprecated in favor of `0b`
* [Mix] Mix color configuration expects a list of atoms instead of a string with colors separated by comma
* [String] `\NNN`, `\NN` and `\N` for octals are deprecated inside string, sigils and chars in favor of hexadecimal entries with `\x`
* Backwards incompatible changes * Backwards incompatible changes
* [Kernel] `binding/1` and `binding/2` expecting a list were removed
* [Regex] Do not consider include captures on `Regex.split/3` results
## v0.14.3 (2014-07-12)
* Enhancements
* [Access] Allow function access on `get_in/2` and `get_and_update_in/3`
* [Enum] Add `Enum.sort_by/3`
* [ExUnit] Match the line filter by proximity instead of exact match
* [Float] Support precision in `Float.ceil/1` and `Float.floor/1`
* [IO] Add `IO.(bin)read(device, :all)`
* [Kernel] Print a warning if a dangling `@doc` clause is found
* [Mix] Use absolute symbolic links on Windows for `_build` instead of copying
* [Mix] Add `Mix.compilers` that returns all default compilers used by mix tasks
* [Mix] Issue warning and reset mtime for source files from the future
* [Mix] Support task aliases in Mix
* [OptionParser] Add `OptionParser.split/1` that splits a string into argv
* [Record] Allow a record to be converted to a keyword list with `record(some_record)`
* [String] Improve performance of `String.split/1`
* [Typespec] Allow `%Struct{}` syntax to be used in typespecs
* [Typespec] Allow `record(:record, fields)` syntax to be used in typespecs
* Bug fixes
* [IEx] Do not print ANSI sequences on `IEx.Helpers.clear/0` if ANSI sequences are not supported
* [Inspect] Ensure `Inspect.Algebra.to_doc/2` doesn't go into a loop when there is a failure printing a struct
* [Kernel] `|>`, `<<<`, `>>>` and `^^^` made left associative in operator table
* [Kernel] `<`, `>`, `<=`, `>=` given higher precedence than comparison operators (`==`, `!=`, etc) in the operator table
* [Kernel] Run command line and escripts in a process that does not trap exits
* [Kernel] Fix a bug where Mix paths had higher priority than CLI ones, causing protocol consolidations to not be properly loaded
* [Kernel] Fix wording on error messages when a check/guard always passes or always fails
* [Kernel] Fix a bug where an unused function warning was printed even when the function was used via `defoverridable`
* [Kernel] Improve typespecs so they don't generate supertype dialyzer warnings
* [Macro] `to_string` correctly displays sigils
* [Mix] Ensure Mix dependencies are not compiled every second time when `mix deps.compile` is invoked
* [Mix] Fix a bug where `Mix.shell.error/1` and friends choked when printing a map
* [Mix] Ensure multiple `@external_resource` entries are read by Mix compilers
* [Mix] Fix a bug where tasks for umbrella projects were not properly reenabled
* [Stream] Fix bug when `flat_map` is used inside another `flat_map` with an Enumerable
* [Typespec] Fix a bug where the `list` typespec was incorrectly rendered as `[]`
* Soft deprecations (no warnings emitted)
* [Kernel] Using a list for bitstring modifiers is deprecated (as in `<<x :: [little, utf16]>>`), please use `-` as the separator instead (as in `<<x :: little-utf16>>`)
* [System] `System.cmd/1` is deprecated in favor of `System.cmd/3`
* Deprecations
* [Mix] `mix escriptize` is deprecated in favor of `escript.build`
* [Mix] `mix local.install` and `mix local.uninstall` have been deprecated in favor of `mix archive.install` and `mix archive.uninstall` respectively
* [Mix] `:embed_extra_apps` for escripts is deprecated, instead list the dependencies inside `def application`
* [System] Giving a char list to `System.find_executable/1` is deprecated
* Backwards incompatible changes
* [Access] No longer fill in missing intermediate values with empty maps
* [Float] `Float.ceil/2` and `Float.floor/2` now always returns floats and no longer accept integers
* [Kernel] `defstruct` no longer automatically defines a type
* [Kernel] `exit(integer)` is no longer supported from scripts to configure the exit signal. Use `exit({:shutdown, integer})` instead
* [Kernel] Default argument values have to be defined in a function head if the function has multiple clauses
* [Mix] `mix archive.build` replaces the functionality of `mix archive`, `mix archive` now lists locally installed archives
* [Mix] `Mix.shell.info/1` no longer automatically escape ANSI sequences. Instead if has to be explicitly enabled with the `ansi: true` option
* [OptionParser] `--no-SWITCH` are only allowed for declared booleans switches
## v0.14.2 (2014-06-29) ## v0.14.2 (2014-06-29)
@@ -28,7 +108,7 @@
* Bug fixes * Bug fixes
* [CLI] Support paths inside archives in `-pa` and `-pz` options * [CLI] Support paths inside archives in `-pa` and `-pz` options
* [IEx] Remove delay when printing data from the an application start callback * [IEx] Remove delay when printing data from the application start callback
* [IEx] Ensure we show a consistent error when we cannot evaluate `.iex.exs` * [IEx] Ensure we show a consistent error when we cannot evaluate `.iex.exs`
* [Kernel] Ensure derived protocols are defined with a file * [Kernel] Ensure derived protocols are defined with a file
* [Kernel] Change precedence of `&` to not special case `/` * [Kernel] Change precedence of `&` to not special case `/`
@@ -70,7 +150,7 @@
* [URI] `encode/1` does not escape reserved/unreserved characters by default nor encodes whitespace as `+` (check `URI.encode_www_form/1` and `URI.decode_www_form/1` for previous behaviour) * [URI] `encode/1` does not escape reserved/unreserved characters by default nor encodes whitespace as `+` (check `URI.encode_www_form/1` and `URI.decode_www_form/1` for previous behaviour)
* Deprecations * Deprecations
* [Mix] `:escript_*` options were moved into a single `:escript` group * [Mix] `:escript_*` options moved into a single `:escript` group
* Backwards incompatible changes * Backwards incompatible changes
* [GenEvent] `GenEvent.stream/2` defaults to `:sync` mode * [GenEvent] `GenEvent.stream/2` defaults to `:sync` mode
-1
View File
@@ -279,7 +279,6 @@ We have saved some excellent pull requests we have received in the past in case
you are looking for some examples: you are looking for some examples:
* https://github.com/elixir-lang/elixir/pull/992 * https://github.com/elixir-lang/elixir/pull/992
* https://github.com/elixir-lang/elixir/pull/1041
* https://github.com/elixir-lang/elixir/pull/1058 * https://github.com/elixir-lang/elixir/pull/1058
* https://github.com/elixir-lang/elixir/pull/1059 * https://github.com/elixir-lang/elixir/pull/1059
+27 -15
View File
@@ -11,7 +11,7 @@ INSTALL_DIR = $(INSTALL) -m755 -d
INSTALL_DATA = $(INSTALL) -m644 INSTALL_DATA = $(INSTALL) -m644
INSTALL_PROGRAM = $(INSTALL) -m755 INSTALL_PROGRAM = $(INSTALL) -m755
.PHONY: install compile erlang elixir dialyze test clean docs release_docs release_zip check_erlang_release .PHONY: install compile erlang elixir build_plt clean_plt dialyze test clean docs release_docs release_zip check_erlang_release
.NOTPARALLEL: compile .NOTPARALLEL: compile
#==> Functions #==> Functions
@@ -32,7 +32,7 @@ $(1): lib/$(1)/ebin/Elixir.$(2).beam lib/$(1)/ebin/$(1).app
lib/$(1)/ebin/$(1).app: lib/$(1)/mix.exs lib/$(1)/ebin/$(1).app: lib/$(1)/mix.exs
$(Q) mkdir -p lib/$(1)/_build/shared/lib/$(1) $(Q) mkdir -p lib/$(1)/_build/shared/lib/$(1)
$(Q) cp -R lib/$(1)/ebin lib/$(1)/_build/shared/lib/$(1)/ $(Q) cp -R lib/$(1)/ebin lib/$(1)/_build/shared/lib/$(1)/
$(Q) cd lib/$(1) && ../../bin/elixir -e "Mix.start(:permanent, [])" -r mix.exs -e "Mix.Task.run('compile.app')" $(Q) cd lib/$(1) && ../../bin/elixir -e 'Mix.start(:permanent, [])' -r mix.exs -e 'Mix.Task.run("compile.app")'
$(Q) cp lib/$(1)/_build/shared/lib/$(1)/ebin/$(1).app lib/$(1)/ebin/$(1).app $(Q) cp lib/$(1)/_build/shared/lib/$(1)/ebin/$(1).app lib/$(1)/ebin/$(1).app
$(Q) rm -rf lib/$(1)/_build $(Q) rm -rf lib/$(1)/_build
@@ -68,10 +68,10 @@ erlang:
# Since Mix depends on EEx and EEx depends on # Since Mix depends on EEx and EEx depends on
# Mix, we first compile EEx without the .app # Mix, we first compile EEx without the .app
# file, then mix and then compile EEx fully # file, then mix and then compile EEx fully
elixir: stdlib lib/eex/ebin/Elixir.EEx.beam mix ex_unit eex iex elixir: stdlib lib/eex/ebin/Elixir.EEx.beam mix ex_unit logger eex iex
stdlib: $(KERNEL) VERSION stdlib: $(KERNEL) VERSION
$(KERNEL): lib/elixir/lib/*.ex lib/elixir/lib/*/*.ex $(KERNEL): lib/elixir/lib/*.ex lib/elixir/lib/*/*.ex lib/elixir/lib/*/*/*.ex
$(Q) if [ ! -f $(KERNEL) ]; then \ $(Q) if [ ! -f $(KERNEL) ]; then \
echo "==> bootstrap (compile)"; \ echo "==> bootstrap (compile)"; \
$(ERL) -s elixir_compiler core -s erlang halt; \ $(ERL) -s elixir_compiler core -s erlang halt; \
@@ -90,6 +90,7 @@ $(UNICODE): lib/elixir/unicode/*
$(Q) cd lib/elixir && ../../$(ELIXIRC) unicode/unicode.ex -o ebin; $(Q) cd lib/elixir && ../../$(ELIXIRC) unicode/unicode.ex -o ebin;
$(eval $(call APP_TEMPLATE,ex_unit,ExUnit)) $(eval $(call APP_TEMPLATE,ex_unit,ExUnit))
$(eval $(call APP_TEMPLATE,logger,Logger))
$(eval $(call APP_TEMPLATE,eex,EEx)) $(eval $(call APP_TEMPLATE,eex,EEx))
$(eval $(call APP_TEMPLATE,mix,Mix)) $(eval $(call APP_TEMPLATE,mix,Mix))
$(eval $(call APP_TEMPLATE,iex,IEx)) $(eval $(call APP_TEMPLATE,iex,IEx))
@@ -133,6 +134,7 @@ docs: compile ../ex_doc/bin/ex_doc
$(call DOCS,Mix,mix,Mix) $(call DOCS,Mix,mix,Mix)
$(call DOCS,IEx,iex,IEx) $(call DOCS,IEx,iex,IEx)
$(call DOCS,ExUnit,ex_unit,ExUnit) $(call DOCS,ExUnit,ex_unit,ExUnit)
$(call DOCS,Logger,logger,Logger)
../ex_doc/bin/ex_doc: ../ex_doc/bin/ex_doc:
@ echo "ex_doc is not found in ../ex_doc as expected. See README for more information." @ echo "ex_doc is not found in ../ex_doc as expected. See README for more information."
@@ -164,7 +166,7 @@ $(TEST_EBIN)/%.beam: $(TEST_ERL)/%.erl
$(Q) mkdir -p $(TEST_EBIN) $(Q) mkdir -p $(TEST_EBIN)
$(Q) $(ERLC) -o $(TEST_EBIN) $< $(Q) $(ERLC) -o $(TEST_EBIN) $<
test_elixir: test_stdlib test_ex_unit test_doc_test test_mix test_eex test_iex test_elixir: test_stdlib test_ex_unit test_logger test_doc_test test_mix test_eex test_iex
test_doc_test: compile test_doc_test: compile
@ echo "==> doctest (exunit)" @ echo "==> doctest (exunit)"
@@ -173,16 +175,26 @@ test_doc_test: compile
test_stdlib: compile test_stdlib: compile
@ echo "==> elixir (exunit)" @ echo "==> elixir (exunit)"
$(Q) exec epmd & exit $(Q) exec epmd & exit
$(Q) cd lib/elixir && ../../bin/elixir -r "test/elixir/test_helper.exs" -pr "test/elixir/**/*_test.exs"; $(Q) if [ "$(OS)" = "Windows_NT" ]; then \
cd lib/elixir && cmd //C call ../../bin/elixir.bat -r "test/elixir/test_helper.exs" -pr "test/elixir/**/*_test.exs"; \
else \
cd lib/elixir && ../../bin/elixir -r "test/elixir/test_helper.exs" -pr "test/elixir/**/*_test.exs"; \
fi
.dialyzer.base_plt: #==> Dialyzer tasks
@ echo "==> Adding Erlang/OTP basic applications to a new base PLT"
$(Q) dialyzer --output_plt .dialyzer.base_plt --build_plt --apps erts kernel stdlib compiler tools syntax_tools parsetools
dialyze: .dialyzer.base_plt DIALYZER_OPTS = --no_check_plt --fullpath -Werror_handling -Wunmatched_returns -Wrace_conditions -Wunderspecs
$(Q) rm -f .dialyzer_plt PLT = .elixir.plt
$(Q) cp .dialyzer.base_plt .dialyzer_plt
@ echo "==> Adding Elixir to PLT..." $(PLT):
$(Q) dialyzer --plt .dialyzer_plt --add_to_plt -r lib/elixir/ebin lib/ex_unit/ebin lib/eex/ebin lib/iex/ebin lib/mix/ebin @ echo "==> Building PLT with Elixir's dependencies..."
$(Q) dialyzer --output_plt $(PLT) --build_plt --apps erts kernel stdlib compiler syntax_tools parsetools tools ssl inets
clean_plt:
$(Q) rm -f $(PLT)
build_plt: clean_plt $(PLT)
dialyze: compile $(PLT)
@ echo "==> Dialyzing Elixir..." @ echo "==> Dialyzing Elixir..."
$(Q) dialyzer --plt .dialyzer_plt -r lib/elixir/ebin lib/ex_unit/ebin lib/eex/ebin lib/iex/ebin lib/mix/ebin $(Q) dialyzer --plt $(PLT) $(DIALYZER_OPTS) lib/*/ebin
+10 -10
View File
@@ -2,30 +2,30 @@
This document simply outlines the release process: This document simply outlines the release process:
1. Remove `-dev` extension from VERSION 1. Remove all `-dev` extension from versions (see below for all files)
2. Ensure CHANGELOG is updated and timestamp 2. Ensure CHANGELOG is updated and add current date
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 the new tag
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, name it Precompiled.zip
8. Merge master into stable branch and push it 8. Fast-forward 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.org` 10. `make release_docs` once again and push it to `elixir-lang/docs`
11. Also update `release` file in `elixir-lang.org` 11. Add the release to `elixir.csv` file in `elixir-lang/elixir-lang.github.com`
## Places where version is mentioned ## Places where version is mentioned
* VERSION * VERSION (make sure there is no newline in this file)
* CHANGELOG * CHANGELOG.md
* src/elixir.app.src * src/elixir.app.src (not lib/elixir/src/elixir.app.src)
+1 -1
View File
@@ -1 +1 @@
0.14.3-dev 0.15.0
+21 -17
View File
@@ -1,4 +1,5 @@
@echo off @echo off
setlocal
if "%1"=="" goto documentation if "%1"=="" goto documentation
if "%1"=="--help" goto documentation if "%1"=="--help" goto documentation
if "%1"=="-h" goto documentation if "%1"=="-h" goto documentation
@@ -22,12 +23,13 @@ echo --sname name Makes and assigns a short name to the distributed node
echo --cookie cookie Sets a cookie for this distributed node echo --cookie cookie Sets a cookie for this distributed node
echo --hidden Makes a hidden node echo --hidden Makes a hidden node
echo --detached Starts the Erlang VM detached from console echo --detached Starts the Erlang VM detached from console
echo --werl Uses Erlang's Windows shell GUI
echo --no-halt Does not halt the Erlang VM after execution echo --no-halt Does not halt the Erlang VM after execution
echo. echo.
echo ** Options marked with (*) can be given more than once echo ** Options marked with (*) can be given more than once
echo ** Options given after the .exs file or -- are passed down to the executed code echo ** Options given after the .exs file or -- are passed down to the executed code
echo ** Options can be passed to the erlang runtime using ELIXIR_ERL_OPTIONS or --erl echo ** Options can be passed to the erlang runtime using ELIXIR_ERL_OPTIONS or --erl
goto :EOF goto end
:parseopts :parseopts
@@ -59,23 +61,23 @@ if "%par%"=="""" (
goto :expand_erl_libs goto :expand_erl_libs
) )
rem ******* EXECUTION OPTIONS ********************** rem ******* EXECUTION OPTIONS **********************
IF "%par%"==""+iex"" (Set useWerl=1) IF "%par%"==""--werl"" (Set useWerl=1)
rem ******* ERLANG PARAMETERS **********************
IF NOT "%par%"=="%par:--detached=%" (Set parsErlang=%parsErlang% -detached)
IF NOT "%par%"=="%par:--hidden=%" (Set parsErlang=%parsErlang% -hidden)
IF NOT "%par%"=="%par:--cookie=%" (Set parsErlang=%parsErlang% -setcookie %1 && shift)
IF NOT "%par%"=="%par:--sname=%" (Set parsErlang=%parsErlang% -sname %1 && shift)
IF NOT "%par%"=="%par:--name=%" (Set parsErlang=%parsErlang% -name %1 && shift)
IF NOT "%par%"=="%par:--erl=%" (Set beforeExtra=%beforeExtra% %~1 && shift)
rem ******* elixir parameters ********************** rem ******* elixir parameters **********************
rem Note: we don't have to do anything with options that don't take an argument rem Note: we don't have to do anything with options that don't take an argument
IF NOT "%par%"=="%par:-e=%" (shift) IF """"=="%par:-e=%" (shift)
IF NOT "%par%"=="%par:-r=%" (shift) IF """"=="%par:-r=%" (shift)
IF NOT "%par%"=="%par:-pr=%" (shift) IF """"=="%par:-pr=%" (shift)
IF NOT "%par%"=="%par:-pa=%" (shift) IF """"=="%par:-pa=%" (shift)
IF NOT "%par%"=="%par:-pz=%" (shift) IF """"=="%par:-pz=%" (shift)
IF NOT "%par%"=="%par:--app=%" (shift) IF """"=="%par:--app=%" (shift)
IF NOT "%par%"=="%par:--remsh=%" (shift) IF """"=="%par:--remsh=%" (shift)
rem ******* ERLANG PARAMETERS **********************
IF """"=="%par:--detached=%" (Set parsErlang=%parsErlang% -detached)
IF """"=="%par:--hidden=%" (Set parsErlang=%parsErlang% -hidden)
IF """"=="%par:--cookie=%" (Set parsErlang=%parsErlang% -setcookie %1 && shift)
IF """"=="%par:--sname=%" (Set parsErlang=%parsErlang% -sname %1 && shift)
IF """"=="%par:--name=%" (Set parsErlang=%parsErlang% -name %1 && shift)
IF """"=="%par:--erl=%" (Set beforeExtra=%beforeExtra% %~1 && shift)
goto:startloop goto:startloop
rem ******* assume all pre-params are parsed ******************** rem ******* assume all pre-params are parsed ********************
@@ -89,7 +91,9 @@ for /d %%d in ("%originPath%..\lib\*.") do (
SETLOCAL disabledelayedexpansion SETLOCAL disabledelayedexpansion
:run :run
IF %useWerl% EQU 1 ( IF %useWerl% EQU 1 (
werl.exe %ext_libs% %ELIXIR_ERL_OPTIONS% %parsErlang% -s elixir start_cli %beforeExtra% -extra %* start werl.exe %ext_libs% %ELIXIR_ERL_OPTIONS% %parsErlang% -s elixir start_cli %beforeExtra% -extra %*
) ELSE ( ) ELSE (
erl.exe %ext_libs% -noshell %ELIXIR_ERL_OPTIONS% %parsErlang% -s elixir start_cli %beforeExtra% -extra %* erl.exe %ext_libs% -noshell %ELIXIR_ERL_OPTIONS% %parsErlang% -s elixir start_cli %beforeExtra% -extra %*
) )
:end
endlocal
+1
View File
@@ -1,3 +1,4 @@
#!/usr/bin/env elixir #!/usr/bin/env elixir
# Reminder: apply any changes in this file to bin\mix.bat
Mix.start Mix.start
Mix.CLI.main Mix.CLI.main
+1 -1
View File
@@ -1,2 +1,2 @@
@echo off @echo off
call "%~dp0\elixir.bat" "%~dp0\mix" %* call "%~dp0\elixir.bat" -e Mix.start -e Mix.CLI.main %*
+10 -14
View File
@@ -1,27 +1,23 @@
# Initialize with path to mix.bat relative to caller's working directory # Store path to mix.bat as a FileInfo object
$toCmd = '' + (Resolve-Path -relative (Split-Path $MyInvocation.MyCommand.Path)) + '\mix.bat' $mixBatPath = (Get-ChildItem (((Get-ChildItem $MyInvocation.MyCommand.Path).Directory.FullName) + '\mix.bat'))
$newArgs = @()
foreach ($arg in $args) for ($i = 0; $i -lt $args.length; $i++)
{ {
$toCmd += ' ' if ($args[$i] -is [array])
if ($arg -is [array])
{ {
# Commas created the array so we need to reintroduce those commas # Commas created the array so we need to reintroduce those commas
for ($i = 0; $i -lt $arg.length; $i++) for ($j = 0; $j -lt $args[$i].length - 1; $j++)
{ {
$toCmd += $arg[$i] $newArgs += ($args[$i][$j] + ',')
if ($i -ne ($arg.length - 1))
{
$toCmd += ', '
}
} }
$newArgs += $args[$i][-1]
} }
else else
{ {
$toCmd += $arg $newArgs += $args[$i]
} }
} }
# Corrected arguments are ready to pass to batch file # Corrected arguments are ready to pass to batch file
cmd /c $toCmd & $mixBatPath $newArgs
+6 -2
View File
@@ -64,7 +64,8 @@ defmodule EEx.Engine do
end end
""" """
def handle_assign({:@, line, [{name, _, atom}]}) when is_atom(name) and is_atom(atom) do def handle_assign({:@, meta, [{name, _, atom}]}) when is_atom(name) and is_atom(atom) do
line = meta[:line] || 0
quote line: line, do: Dict.get(var!(assigns), unquote(name)) quote line: line, do: Dict.get(var!(assigns), unquote(name))
end end
@@ -98,7 +99,10 @@ defmodule EEx.Engine do
def handle_expr(buffer, "=", expr) do def handle_expr(buffer, "=", expr) do
quote do quote do
tmp = unquote(buffer) tmp = unquote(buffer)
tmp <> to_string(unquote(expr)) tmp <> (case unquote(expr) do
bin when is_binary(bin) -> bin
oth -> String.Chars.to_string(oth)
end)
end end
end end
-60
View File
@@ -1,63 +1,3 @@
defmodule EEx.TransformerEngine do
@moduledoc false
@doc false
defmacro __using__(_) do
quote do
@behaviour EEx.Engine
def handle_body(body) do
EEx.Engine.handle_body(body)
end
def handle_text(buffer, text) do
EEx.Engine.handle_text(buffer, text)
end
def handle_expr(buffer, mark, expr) do
EEx.Engine.handle_expr(buffer, mark, transform(expr))
end
defp transform({a, b, c}) do
{transform(a), b, transform(c)}
end
defp transform({a, b}) do
{transform(a), transform(b)}
end
defp transform(list) when is_list(list) do
for i <- list, do: transform(i)
end
defp transform(other) do
other
end
defoverridable [transform: 1, handle_body: 1, handle_expr: 3, handle_text: 2]
end
end
end
defmodule EEx.AssignsEngine do
@moduledoc false
@doc false
defmacro __using__(_) do
quote unquote: false do
defp transform({:@, line, [{name, _, atom}]}) when is_atom(name) and is_atom(atom) do
quote do: Dict.get(var!(assigns), unquote(name))
end
defp transform(arg) do
super(arg)
end
defoverridable [transform: 1]
end
end
end
defmodule EEx.SmartEngine do defmodule EEx.SmartEngine do
@moduledoc """ @moduledoc """
The default engine used by EEx. The default engine used by EEx.
+10
View File
@@ -19,6 +19,16 @@ defmodule EEx.SmartEngineTest do
assert_eval "1\n2\n3\n", "<%= for x <- [1, 2, 3] do %><%= x %>\n<% end %>" assert_eval "1\n2\n3\n", "<%= for x <- [1, 2, 3] do %><%= x %>\n<% end %>"
end end
test "compiled preserved line numbers" do
result = EEx.compile_string("<%= @hello %>", engine: EEx.SmartEngine)
Macro.prewalk(result, fn
{_left, meta, _right} ->
assert Keyword.get(meta, :line, 0) in [0, 1]
_ ->
:ok
end)
end
defp assert_eval(expected, actual, binding \\ []) do defp assert_eval(expected, actual, binding \\ []) do
result = EEx.eval_string(actual, binding, file: __ENV__.file) result = EEx.eval_string(actual, binding, file: __ENV__.file)
assert result == expected assert result == expected
+2 -1
View File
@@ -50,7 +50,8 @@ defmodule EExTest do
use ExUnit.Case, async: true use ExUnit.Case, async: true
doctest EEx doctest EEx
doctest EEx.AssignsEngine doctest EEx.Engine
doctest EEx.SmartEngine
test "evaluates simple string" do test "evaluates simple string" do
assert_eval "foo bar", "foo bar" assert_eval "foo bar", "foo bar"
+1 -1
View File
@@ -21,7 +21,7 @@
-record(elixir_quote, { -record(elixir_quote, {
line=false, line=false,
keep=false, file=nil,
context=nil, context=nil,
vars_hygiene=true, vars_hygiene=true,
aliases_hygiene=true, aliases_hygiene=true,
+4 -3
View File
@@ -119,7 +119,7 @@ defimpl Access, for: Map do
def get_and_update!(other, key, _fun) do def get_and_update!(other, key, _fun) do
raise ArgumentError, raise ArgumentError,
"could not update key #{inspect key}. Expected map/struct, got: #{inspect other}" "could not put/update key #{inspect key}. Expected map/struct, got: #{inspect other}"
end end
end end
@@ -132,8 +132,9 @@ defimpl Access, for: Atom do
undefined(atom) undefined(atom)
end end
def get_and_update(nil, _, fun) do def get_and_update(nil, key, _fun) do
fun.(nil) raise ArgumentError,
"could not put/update key #{inspect key} on a nil value"
end end
def get_and_update(atom, _key, _fun) do def get_and_update(atom, _key, _fun) do
+89 -18
View File
@@ -68,22 +68,22 @@ defmodule Agent do
## A word on distributed agents ## A word on distributed agents
It is important to consider the limitations of distributed agents. Agents It is important to consider the limitations of distributed agents. Agents
work by sending anonymous functions between the caller and the agent. provides two APIs, one that works with anonymous functions and another
In a distributed setup with multiple nodes, agents only work if the caller that expects explicit module, function and arguments.
(client) and the agent have the same version of a given module.
This setup may exhibit issues when doing "rolling upgrades". By rolling In a distributed setup with multiple nodes, the API that accepts anonymous
upgrades we mean the following situation: you wish to deploy a new version of functions only works if the caller (client) and the agent have the same
your software by *shutting down* some of your nodes and replacing them with version of the caller module.
nodes running a new version of the software. In this setup, part of your
environment will have one version of a given module and the other part
another version (the newer one) of the same module; this may cause agents to
crash. That said, if you plan to run in distributed environments, agents
should likely be avoided.
Note, however, that agents work fine if you want to perform hot code Keep in mind this issue also shows up when performing "rolling upgrades"
swapping, as it keeps both the old and new versions of a given module. with agents. By rolling upgrades we mean the following situation: you wish
We detail how to do hot code swapping with agents in the next section. to deploy a new version of your software by *shutting down* some of your
nodes and replacing them with nodes running a new version of the software.
In this setup, part of your environment will have one version of a given
module and the other part another version (the newer one) of the same module.
The best solution is to simply use the explicit module, function and arguments
APIs when working with distributed agents.
## Hot code swapping ## Hot code swapping
@@ -111,7 +111,7 @@ defmodule Agent do
@type state :: term @type state :: term
@doc """ @doc """
Starts an agent linked to the current process. Starts an agent linked to the current process with the given function.
This is often used to start the agent as part of a supervision tree. This is often used to start the agent as part of a supervision tree.
@@ -149,6 +149,18 @@ defmodule Agent do
GenServer.start_link(Agent.Server, fun, options) GenServer.start_link(Agent.Server, fun, options)
end end
@doc """
Starts an agent linked to the current process with the given module
function and arguments.
Same as `start_link/2` but a module, function and args are expected
instead of an anonymous function.
"""
@spec start_link(module, atom, [any], GenServer.options) :: on_start
def start_link(module, fun, args, options \\ []) do
GenServer.start_link(Agent.Server, {module, fun, args}, options)
end
@doc """ @doc """
Starts an agent process without links (outside of a supervision tree). Starts an agent process without links (outside of a supervision tree).
@@ -160,7 +172,18 @@ defmodule Agent do
end end
@doc """ @doc """
Gets the agent value and executes the given function. Starts an agent with the given module function and arguments.
Similar to `start/2` but a module, function and args are expected
instead of an anonymous function.
"""
@spec start(module, atom, [any], GenServer.options) :: on_start
def start(module, fun, args, options \\ []) do
GenServer.start(Agent.Server, {module, fun, args}, options)
end
@doc """
Gets an agent value via the given function.
The function `fun` is sent to the `agent` which invokes the function The function `fun` is sent to the `agent` which invokes the function
passing the agent state. The result of the function invocation is passing the agent state. The result of the function invocation is
@@ -173,6 +196,18 @@ defmodule Agent do
GenServer.call(agent, {:get, fun}, timeout) GenServer.call(agent, {:get, fun}, timeout)
end end
@doc """
Gets an agent value via the given function.
Same as `get/3` but a module, function and args are expected
instead of an anonymous function. The state is added as first
argument to the given list of args.
"""
@spec get(agent, module, atom, [term], timeout) :: any
def get(agent, module, fun, args, timeout \\ 5000) do
GenServer.call(agent, {:get, {module, fun, args}}, timeout)
end
@doc """ @doc """
Gets and updates the agent state in one operation. Gets and updates the agent state in one operation.
@@ -188,6 +223,18 @@ defmodule Agent do
GenServer.call(agent, {:get_and_update, fun}, timeout) GenServer.call(agent, {:get_and_update, fun}, timeout)
end end
@doc """
Gets and updates the agent state in one operation.
Same as `get_and_update/3` but a module, function and args are expected
instead of an anonymous function. The state is added as first
argument to the given list of args.
"""
@spec get_and_update(agent, module, atom, [term], timeout) :: any
def get_and_update(agent, module, fun, args, timeout \\ 5000) do
GenServer.call(agent, {:get_and_update, {module, fun, args}}, timeout)
end
@doc """ @doc """
Updates the agent state. Updates the agent state.
@@ -197,11 +244,23 @@ defmodule Agent do
A timeout can also be specified (it has a default value of 5000). A timeout can also be specified (it has a default value of 5000).
This function always returns `:ok`. This function always returns `:ok`.
""" """
@spec update(agent, (state -> state)) :: :ok @spec update(agent, (state -> state), timeout) :: :ok
def update(agent, fun, timeout \\ 5000) when is_function(fun, 1) do def update(agent, fun, timeout \\ 5000) when is_function(fun, 1) do
GenServer.call(agent, {:update, fun}, timeout) GenServer.call(agent, {:update, fun}, timeout)
end end
@doc """
Updates the agent state.
Same as `update/3` but a module, function and args are expected
instead of an anonymous function. The state is added as first
argument to the given list of args.
"""
@spec update(agent, module, atom, [term], timeout) :: :ok
def update(agent, module, fun, args, timeout \\ 5000) do
GenServer.call(agent, {:update, {module, fun, args}}, timeout)
end
@doc """ @doc """
Performs a cast (fire and forget) operation on the agent state. Performs a cast (fire and forget) operation on the agent state.
@@ -213,7 +272,19 @@ defmodule Agent do
""" """
@spec cast(agent, (state -> state)) :: :ok @spec cast(agent, (state -> state)) :: :ok
def cast(agent, fun) when is_function(fun, 1) do def cast(agent, fun) when is_function(fun, 1) do
GenServer.cast(agent, fun) GenServer.cast(agent, {:cast, fun})
end
@doc """
Performs a cast (fire and forget) operation on the agent state.
Same as `cast/2` but a module, function and args are expected
instead of an anonymous function. The state is added as first
argument to the given list of args.
"""
@spec cast(agent, module, atom, [term]) :: :ok
def cast(agent, module, fun, args) do
GenServer.cast(agent, {:cast, {module, fun, args}})
end end
@doc """ @doc """
+11 -8
View File
@@ -4,20 +4,20 @@ defmodule Agent.Server do
use GenServer use GenServer
def init(fun) do def init(fun) do
{:ok, fun.()} {:ok, run(fun, [])}
end end
def handle_call({:get, fun}, _from, state) do def handle_call({:get, fun}, _from, state) do
{:reply, fun.(state), state} {:reply, run(fun, [state]), state}
end end
def handle_call({:get_and_update, fun}, _from, state) do def handle_call({:get_and_update, fun}, _from, state) do
{reply, state} = fun.(state) {reply, state} = run(fun, [state])
{:reply, reply, state} {:reply, reply, state}
end end
def handle_call({:update, fun}, _from, state) do def handle_call({:update, fun}, _from, state) do
{:reply, :ok, fun.(state)} {:reply, :ok, run(fun, [state])}
end end
def handle_call(:stop, _from, state) do def handle_call(:stop, _from, state) do
@@ -28,16 +28,16 @@ defmodule Agent.Server do
super(msg, from, state) super(msg, from, state)
end end
def handle_cast(fun, state) when is_function(fun, 1) do def handle_cast({:cast, fun}, state) do
{:noreply, fun.(state)} {:noreply, run(fun, [state])}
end end
def handle_cast(msg, state) do def handle_cast(msg, state) do
super(msg, state) super(msg, state)
end end
def code_change(_old, state, { m, f, a }) do def code_change(_old, state, fun) do
{:ok, apply(m, f, [state|a])} {:ok, run(fun, [state])}
end end
def terminate(_reason, _state) do def terminate(_reason, _state) do
@@ -50,4 +50,7 @@ defmodule Agent.Server do
end end
:ok :ok
end end
defp run({m, f, a}, extra), do: apply(m, f, extra ++ a)
defp run(fun, extra), do: apply(fun, extra)
end end
+1 -1
View File
@@ -322,7 +322,7 @@ defmodule Application do
@doc """ @doc """
Formats the error reason returned by `start/2`, Formats the error reason returned by `start/2`,
`ensure_started/2, `stop/1`, `load/1` and `unload/1`, `ensure_started/2`, `stop/1`, `load/1` and `unload/1`,
returns a string. returns a string.
""" """
@spec format_error(any) :: String.t @spec format_error(any) :: String.t
+4 -4
View File
@@ -499,7 +499,7 @@ defmodule Base do
defp do_encode64(<<>>, _), do: <<>> defp do_encode64(<<>>, _), do: <<>>
defp do_encode64(data, enc) do defp do_encode64(data, enc) do
split = 3 * div(byte_size(data), 3) split = 3 * div(byte_size(data), 3)
<<main::[size(split), binary], rest::binary>> = data <<main::size(split)-binary, rest::binary>> = data
main = for <<c::6 <- main>>, into: <<>>, do: <<enc.(c)::8>> main = for <<c::6 <- main>>, into: <<>>, do: <<enc.(c)::8>>
case rest do case rest do
<<c1::6, c2::6, c3::4>> -> <<c1::6, c2::6, c3::4>> ->
@@ -514,7 +514,7 @@ defmodule Base do
defp do_decode64(<<>>, _), do: <<>> defp do_decode64(<<>>, _), do: <<>>
defp do_decode64(string, dec) when rem(byte_size(string), 4) == 0 do defp do_decode64(string, dec) when rem(byte_size(string), 4) == 0 do
split = byte_size(string) - 4 split = byte_size(string) - 4
<<main::[size(split), binary], rest::binary>> = string <<main::size(split)-binary, rest::binary>> = string
main = for <<c::8 <- main>>, into: <<>>, do: <<dec.(c)::6>> main = for <<c::8 <- main>>, into: <<>>, do: <<dec.(c)::6>>
case rest do case rest do
<<c1::8, c2::8, ?=, ?=>> -> <<c1::8, c2::8, ?=, ?=>> ->
@@ -534,7 +534,7 @@ defmodule Base do
defp do_encode32(<<>>, _), do: <<>> defp do_encode32(<<>>, _), do: <<>>
defp do_encode32(data, enc) do defp do_encode32(data, enc) do
split = 5 * div(byte_size(data), 5) split = 5 * div(byte_size(data), 5)
<<main::[size(split), binary], rest::binary>> = data <<main::size(split)-binary, rest::binary>> = data
main = for <<c::5 <- main>>, into: <<>>, do: <<enc.(c)::8>> main = for <<c::5 <- main>>, into: <<>>, do: <<enc.(c)::8>>
case rest do case rest do
<<c1::5, c2::5, c3::5, c4::5, c5::5, c6::5, c7::2>> -> <<c1::5, c2::5, c3::5, c4::5, c5::5, c6::5, c7::2>> ->
@@ -561,7 +561,7 @@ defmodule Base do
defp do_decode32(<<>>, _), do: <<>> defp do_decode32(<<>>, _), do: <<>>
defp do_decode32(string, dec) when rem(byte_size(string), 8) == 0 do defp do_decode32(string, dec) when rem(byte_size(string), 8) == 0 do
split = byte_size(string) - 8 split = byte_size(string) - 8
<<main::[size(split), binary], rest::binary>> = string <<main::size(split)-binary, rest::binary>> = string
main = for <<c::8 <- main>>, into: <<>>, do: <<dec.(c)::5>> main = for <<c::8 <- main>>, into: <<>>, do: <<dec.(c)::5>>
case rest do case rest do
<<c1::8, c2::8, ?=, ?=, ?=, ?=, ?=, ?=>> -> <<c1::8, c2::8, ?=, ?=, ?=, ?=, ?=, ?=>> ->
+17
View File
@@ -51,6 +51,23 @@ defmodule Code do
:code.del_path(to_char_list(Path.expand path)) :code.del_path(to_char_list(Path.expand path))
end end
@doc """
Re-add paths given to the command line to keep their position
on the overall code path.
Some tools may change the code path by prepending new items but
still want the paths given by the user to have higher priority.
Calling this function guarantees the paths are re-added on
top of the user given ones.
"""
@spec readd_paths() :: :ok
def readd_paths() do
{pa, pz} = :elixir_code_server.call(:paths)
:code.add_pathsa(pa)
:code.add_pathsz(pz)
:ok
end
@doc """ @doc """
Evaluate the contents given by `string`. Evaluate the contents given by `string`.
+2 -2
View File
@@ -85,8 +85,8 @@ defimpl Collectable, for: BitString do
def into(original) do def into(original) do
{original, fn {original, fn
bitstring, {:cont, x} -> <<bitstring :: bits, x :: bits>> acc, {:cont, x} when is_bitstring(x) -> [acc|x]
bitstring, :done -> bitstring acc, :done -> IO.iodata_to_binary(acc)
_, :halt -> :ok _, :halt -> :ok
end} end}
end end
+78 -78
View File
@@ -288,8 +288,8 @@ defmodule Dict do
## Examples ## Examples
iex> d = Enum.into([a: 1, b: 2], dict_impl.new) iex> dict = Enum.into([a: 1, b: 2], dict_impl.new)
iex> Enum.sort(Dict.keys(d)) iex> Enum.sort(Dict.keys(dict))
[:a,:b] [:a,:b]
""" """
@@ -304,8 +304,8 @@ defmodule Dict do
## Examples ## Examples
iex> d = Enum.into([a: 1, b: 2], dict_impl.new) iex> dict = Enum.into([a: 1, b: 2], dict_impl.new)
iex> Enum.sort(Dict.values(d)) iex> Enum.sort(Dict.values(dict))
[1,2] [1,2]
""" """
@@ -319,8 +319,8 @@ defmodule Dict do
## Examples ## Examples
iex> d = Enum.into([a: 1, b: 2], dict_impl.new) iex> dict = Enum.into([a: 1, b: 2], dict_impl.new)
iex> Dict.size(d) iex> Dict.size(dict)
2 2
""" """
@@ -334,10 +334,10 @@ defmodule Dict do
## Examples ## Examples
iex> d = Enum.into([a: 1], dict_impl.new) iex> dict = Enum.into([a: 1], dict_impl.new)
iex> Dict.has_key?(d, :a) iex> Dict.has_key?(dict, :a)
true true
iex> Dict.has_key?(d, :b) iex> Dict.has_key?(dict, :b)
false false
""" """
@@ -352,12 +352,12 @@ defmodule Dict do
## Examples ## Examples
iex> d = Enum.into([a: 1], dict_impl.new) iex> dict = Enum.into([a: 1], dict_impl.new)
iex> Dict.get(d, :a) iex> Dict.get(dict, :a)
1 1
iex> Dict.get(d, :b) iex> Dict.get(dict, :b)
nil nil
iex> Dict.get(d, :b, 3) iex> Dict.get(dict, :b, 3)
3 3
""" """
@spec get(t, key, value) :: value @spec get(t, key, value) :: value
@@ -371,10 +371,10 @@ defmodule Dict do
## Examples ## Examples
iex> d = Enum.into([a: 1], dict_impl.new) iex> dict = Enum.into([a: 1], dict_impl.new)
iex> Dict.fetch(d, :a) iex> Dict.fetch(dict, :a)
{:ok, 1} {:ok, 1}
iex> Dict.fetch(d, :b) iex> Dict.fetch(dict, :b)
:error :error
""" """
@@ -389,8 +389,8 @@ defmodule Dict do
## Examples ## Examples
iex> d = Enum.into([a: 1], dict_impl.new) iex> dict = Enum.into([a: 1], dict_impl.new)
iex> Dict.fetch!(d, :a) iex> Dict.fetch!(dict, :a)
1 1
""" """
@@ -405,9 +405,9 @@ defmodule Dict do
## Examples ## Examples
iex> d = Enum.into([a: 1, b: 2], dict_impl.new) iex> dict = Enum.into([a: 1, b: 2], dict_impl.new)
iex> d = Dict.put(d, :a, 3) iex> dict = Dict.put(dict, :a, 3)
iex> Dict.get(d, :a) iex> Dict.get(dict, :a)
3 3
""" """
@@ -421,9 +421,9 @@ defmodule Dict do
## Examples ## Examples
iex> d = Enum.into([a: 1, b: 2], dict_impl.new) iex> dict = Enum.into([a: 1, b: 2], dict_impl.new)
iex> d = Dict.put_new(d, :a, 3) iex> dict = Dict.put_new(dict, :a, 3)
iex> Dict.get(d, :a) iex> Dict.get(dict, :a)
1 1
""" """
@@ -438,13 +438,13 @@ defmodule Dict do
## Examples ## Examples
iex> d = Enum.into([a: 1, b: 2], dict_impl.new) iex> dict = Enum.into([a: 1, b: 2], dict_impl.new)
iex> d = Dict.delete(d, :a) iex> dict = Dict.delete(dict, :a)
iex> Dict.get(d, :a) iex> Dict.get(dict, :a)
nil nil
iex> d = Enum.into([b: 2], dict_impl.new) iex> dict = Enum.into([b: 2], dict_impl.new)
iex> Dict.delete(d, :a) == d iex> Dict.delete(dict, :a) == dict
true true
""" """
@@ -454,10 +454,10 @@ defmodule Dict do
end end
@doc """ @doc """
Merges the dict `b` into dict `a`. Merges the dict `dict2` into dict `dict1`.
If one of the dict `b` entries already exists in the `dict`, If one of the `dict2` entries already exists in `dict1`, the
the functions in entries in `b` have higher precedence unless a functions in entries in `dict2` have higher precedence unless a
function is given to resolve conflicts. function is given to resolve conflicts.
Notice this function is polymorphic as it merges dicts of any Notice this function is polymorphic as it merges dicts of any
@@ -466,18 +466,18 @@ defmodule Dict do
## Examples ## Examples
iex> d1 = Enum.into([a: 1, b: 2], dict_impl.new) iex> dict1 = Enum.into([a: 1, b: 2], dict_impl.new)
iex> d2 = Enum.into([a: 3, d: 4], dict_impl.new) iex> dict2 = Enum.into([a: 3, d: 4], dict_impl.new)
iex> d = Dict.merge(d1, d2) iex> dict = Dict.merge(dict1, dict2)
iex> [a: Dict.get(d, :a), b: Dict.get(d, :b), d: Dict.get(d, :d)] iex> [a: Dict.get(dict, :a), b: Dict.get(dict, :b), d: Dict.get(dict, :d)]
[a: 3, b: 2, d: 4] [a: 3, b: 2, d: 4]
iex> d1 = Enum.into([a: 1, b: 2], dict_impl.new) iex> dict1 = Enum.into([a: 1, b: 2], dict_impl.new)
iex> d2 = Enum.into([a: 3, d: 4], dict_impl.new) iex> dict2 = Enum.into([a: 3, d: 4], dict_impl.new)
iex> d = Dict.merge(d1, d2, fn(_k, v1, v2) -> iex> dict = Dict.merge(dict1, dict2, fn(_k, v1, v2) ->
...> v1 + v2 ...> v1 + v2
...> end) ...> end)
iex> [a: Dict.get(d, :a), b: Dict.get(d, :b), d: Dict.get(d, :d)] iex> [a: Dict.get(dict, :a), b: Dict.get(dict, :b), d: Dict.get(dict, :d)]
[a: 4, b: 2, d: 4] [a: 4, b: 2, d: 4]
""" """
@@ -502,18 +502,18 @@ defmodule Dict do
## Examples ## Examples
iex> dict = Enum.into([a: 1], dict_impl.new) iex> dict = Enum.into([a: 1], dict_impl.new)
iex> {v, d} = Dict.pop dict, :a iex> {v, dict} = Dict.pop dict, :a
iex> {v, Enum.sort(d)} iex> {v, Enum.sort(dict)}
{1,[]} {1,[]}
iex> dict = Enum.into([a: 1], dict_impl.new) iex> dict = Enum.into([a: 1], dict_impl.new)
iex> {v, d} = Dict.pop dict, :b iex> {v, dict} = Dict.pop dict, :b
iex> {v, Enum.sort(d)} iex> {v, Enum.sort(dict)}
{nil,[a: 1]} {nil,[a: 1]}
iex> dict = Enum.into([a: 1], dict_impl.new) iex> dict = Enum.into([a: 1], dict_impl.new)
iex> {v, d} = Dict.pop dict, :b, 3 iex> {v, dict} = Dict.pop dict, :b, 3
iex> {v, Enum.sort(d)} iex> {v, Enum.sort(dict)}
{3,[a: 1]} {3,[a: 1]}
""" """
@@ -528,9 +528,9 @@ defmodule Dict do
## Examples ## Examples
iex> d = Enum.into([a: 1, b: 2], dict_impl.new) iex> dict = Enum.into([a: 1, b: 2], dict_impl.new)
iex> d = Dict.update!(d, :a, fn(val) -> -val end) iex> dict = Dict.update!(dict, :a, fn(val) -> -val end)
iex> Dict.get(d, :a) iex> Dict.get(dict, :a)
-1 -1
""" """
@@ -546,9 +546,9 @@ defmodule Dict do
## Examples ## Examples
iex> d = Enum.into([a: 1, b: 2], dict_impl.new) iex> dict = Enum.into([a: 1, b: 2], dict_impl.new)
iex> d = Dict.update(d, :c, 3, fn(val) -> -val end) iex> dict = Dict.update(dict, :c, 3, fn(val) -> -val end)
iex> Dict.get(d, :c) iex> Dict.get(dict, :c)
3 3
""" """
@@ -566,19 +566,19 @@ defmodule Dict do
## Examples ## Examples
iex> d = Enum.into([a: 1, b: 2, c: 3, d: 4], dict_impl.new) iex> dict = Enum.into([a: 1, b: 2, c: 3, d: 4], dict_impl.new)
iex> {d1, d2} = Dict.split(d, [:a, :c, :e]) iex> {dict1, dict2} = Dict.split(dict, [:a, :c, :e])
iex> {Dict.to_list(d1) |> Enum.sort, Dict.to_list(d2) |> Enum.sort} iex> {Dict.to_list(dict1) |> Enum.sort, Dict.to_list(dict2) |> Enum.sort}
{[a: 1, c: 3], [b: 2, d: 4]} {[a: 1, c: 3], [b: 2, d: 4]}
iex> d = Enum.into([], dict_impl.new) iex> dict = Enum.into([], dict_impl.new)
iex> {d1, d2} = Dict.split(d, [:a, :c]) iex> {dict1, dict2} = Dict.split(dict, [:a, :c])
iex> {Dict.to_list(d1), Dict.to_list(d2)} iex> {Dict.to_list(dict1), Dict.to_list(dict2)}
{[], []} {[], []}
iex> d = Enum.into([a: 1, b: 2], dict_impl.new) iex> dict = Enum.into([a: 1, b: 2], dict_impl.new)
iex> {d1, d2} = Dict.split(d, [:a, :b, :c]) iex> {dict1, dict2} = Dict.split(dict, [:a, :b, :c])
iex> {Dict.to_list(d1) |> Enum.sort, Dict.to_list(d2)} iex> {Dict.to_list(dict1) |> Enum.sort, Dict.to_list(dict2)}
{[a: 1, b: 2], []} {[a: 1, b: 2], []}
""" """
@@ -593,14 +593,14 @@ defmodule Dict do
## Examples ## Examples
iex> d = Enum.into([a: 1, b: 2], dict_impl.new) iex> dict = Enum.into([a: 1, b: 2], dict_impl.new)
iex> d = Dict.drop(d, [:a, :c, :d]) iex> dict = Dict.drop(dict, [:a, :c, :d])
iex> Dict.to_list(d) iex> Dict.to_list(dict)
[b: 2] [b: 2]
iex> d = Enum.into([a: 1, b: 2], dict_impl.new) iex> dict = Enum.into([a: 1, b: 2], dict_impl.new)
iex> d = Dict.drop(d, [:c, :d]) iex> dict = Dict.drop(dict, [:c, :d])
iex> Dict.to_list(d) |> Enum.sort iex> Dict.to_list(dict) |> Enum.sort
[a: 1, b: 2] [a: 1, b: 2]
""" """
@@ -616,12 +616,12 @@ defmodule Dict do
## Examples ## Examples
iex> d = Enum.into([a: 1, b: 2], dict_impl.new) iex> dict = Enum.into([a: 1, b: 2], dict_impl.new)
iex> d = Dict.take(d, [:a, :c, :d]) iex> dict = Dict.take(dict, [:a, :c, :d])
iex> Dict.to_list(d) iex> Dict.to_list(dict)
[a: 1] [a: 1]
iex> d = Dict.take(d, [:c, :d]) iex> dict = Dict.take(dict, [:c, :d])
iex> Dict.to_list(d) iex> Dict.to_list(dict)
[] []
""" """
@@ -645,14 +645,14 @@ defmodule Dict do
## Examples ## Examples
iex> a = Enum.into([a: 2, b: 3, f: 5, c: 123], dict_impl.new) iex> dict1 = Enum.into([a: 2, b: 3, f: 5, c: 123], dict_impl.new)
iex> b = [a: 2, b: 3, f: 5, c: 123] iex> dict2 = [a: 2, b: 3, f: 5, c: 123]
iex> Dict.equal?(a, b) iex> Dict.equal?(dict1, dict2)
true true
iex> a = Enum.into([a: 2, b: 3, f: 5, c: 123], dict_impl.new) iex> dict1 = Enum.into([a: 2, b: 3, f: 5, c: 123], dict_impl.new)
iex> b = [] iex> dict2 = []
iex> Dict.equal?(a, b) iex> Dict.equal?(dict1, dict2)
false false
""" """
+56 -7
View File
@@ -270,8 +270,13 @@ defmodule Enum do
@doc """ @doc """
Finds the element at the given index (zero-based). Finds the element at the given index (zero-based).
Returns `default` if index is out of bounds. Returns `default` if index is out of bounds.
Note this operation takes linear time. In order to access
the element at index `n`, it will need to traverse `n`
previous elements.
## Examples ## Examples
iex> Enum.at([2, 4, 6], 0) iex> Enum.at([2, 4, 6], 0)
@@ -287,7 +292,6 @@ defmodule Enum do
:none :none
""" """
@spec at(t, integer) :: element | nil
@spec at(t, integer, default) :: element | default @spec at(t, integer, default) :: element | default
def at(collection, n, default \\ nil) do def at(collection, n, default \\ nil) do
case fetch(collection, n) do case fetch(collection, n) do
@@ -330,7 +334,6 @@ defmodule Enum do
[[1, 2, 3], [4, 5, 6]] [[1, 2, 3], [4, 5, 6]]
""" """
@spec chunk(t, non_neg_integer, non_neg_integer) :: [list]
@spec chunk(t, non_neg_integer, non_neg_integer, t | nil) :: [list] @spec chunk(t, non_neg_integer, non_neg_integer, t | nil) :: [list]
def chunk(coll, n, step, pad \\ nil) when n > 0 and step > 0 do def chunk(coll, n, step, pad \\ nil) when n > 0 and step > 0 do
limit = :erlang.max(n, step) limit = :erlang.max(n, step)
@@ -566,12 +569,17 @@ defmodule Enum do
@doc """ @doc """
Finds the element at the given index (zero-based). Finds the element at the given index (zero-based).
Returns `{:ok, element}` if found, otherwise `:error`. Returns `{:ok, element}` if found, otherwise `:error`.
A negative index can be passed, which means the collection is A negative index can be passed, which means the collection is
enumerated once and the index is counted from the end (i.e. enumerated once and the index is counted from the end (i.e.
`-1` fetches the last element). `-1` fetches the last element).
Note this operation takes linear time. In order to access
the element at index `n`, it will need to traverse `n`
previous elements.
## Examples ## Examples
iex> Enum.fetch([2, 4, 6], 0) iex> Enum.fetch([2, 4, 6], 0)
@@ -611,9 +619,14 @@ defmodule Enum do
@doc """ @doc """
Finds the element at the given index (zero-based). Finds the element at the given index (zero-based).
Raises `OutOfBoundsError` if the given position Raises `OutOfBoundsError` if the given position
is outside the range of the collection. is outside the range of the collection.
Note this operation takes linear time. In order to access
the element at index `n`, it will need to traverse `n`
previous elements.
## Examples ## Examples
iex> Enum.fetch!([2, 4, 6], 0) iex> Enum.fetch!([2, 4, 6], 0)
@@ -689,7 +702,6 @@ defmodule Enum do
3 3
""" """
@spec find(t, (element -> any)) :: element | nil
@spec find(t, default, (element -> any)) :: element | default @spec find(t, default, (element -> any)) :: element | default
def find(collection, ifnone \\ nil, fun) def find(collection, ifnone \\ nil, fun)
@@ -716,7 +728,6 @@ defmodule Enum do
true true
""" """
@spec find_value(t, (element -> any)) :: any | :nil
@spec find_value(t, any, (element -> any)) :: any | :nil @spec find_value(t, any, (element -> any)) :: any | :nil
def find_value(collection, ifnone \\ nil, fun) def find_value(collection, ifnone \\ nil, fun)
@@ -931,7 +942,6 @@ defmodule Enum do
"1 = 2 = 3" "1 = 2 = 3"
""" """
@spec join(t) :: String.t
@spec join(t, String.t) :: String.t @spec join(t, String.t) :: String.t
def join(collection, joiner \\ "") def join(collection, joiner \\ "")
@@ -990,7 +1000,6 @@ defmodule Enum do
"2 = 4 = 6" "2 = 4 = 6"
""" """
@spec map_join(t, (element -> any)) :: String.t
@spec map_join(t, String.t, (element -> any)) :: String.t @spec map_join(t, String.t, (element -> any)) :: String.t
def map_join(collection, joiner \\ "", mapper) def map_join(collection, joiner \\ "", mapper)
@@ -1396,7 +1405,7 @@ defmodule Enum do
result. For example, one could do the following to set a seed result. For example, one could do the following to set a seed
dynamically: dynamically:
:random.seed(:erlang.now) :random.seed(:os.timestamp)
## Examples ## Examples
@@ -1568,6 +1577,46 @@ defmodule Enum do
reduce(collection, [], &sort_reducer(&1, &2, fun)) |> sort_terminator(fun) reduce(collection, [], &sort_reducer(&1, &2, fun)) |> sort_terminator(fun)
end end
@doc """
Sorts the mapped results of the `collection` according to the `sorter` function.
This function maps each element of the collection using the `mapper`
function. The collection is then sorted by the mapped elements using the
`sorter` function, which defaults to `<=/2`
`sort_by/3` differs from `sort/2` in that it only calculates the comparison
value for each element in the collection once instead of once for each
element in each comparison. If the same function is being called on both
element, it's also more compact to use `sort_by/3`.
This technique is also known as a
[Schwartzian Transform](https://en.wikipedia.org/wiki/Schwartzian_transform),
or the Lisp decorate-sort-undecorate idiom as the `mapper` is decorating the
original `collection`, then `sorter` is sorting the decorations, and finally
the `collection` is being undecorated so only the original elements remain,
but now in sorted order.
## Examples
Using the default `sorter` of `<=/2`:
iex> Enum.sort_by ["some", "kind", "of", "monster"], &byte_size/1
["of", "some", "kind", "monster"]
Using a custom `sorter` to override the order:
iex> Enum.sort_by ["some", "kind", "of", "monster"], &byte_size/1, &>=/2
["monster", "some", "kind", "of"]
"""
@spec sort_by(t, (element -> mapped_element), (mapped_element, mapped_element -> boolean)) :: list when mapped_element: element
def sort_by(collection, mapper, sorter \\ &<=/2) do
collection
|> map(&{&1, mapper.(&1)})
|> sort(&sorter.(elem(&1, 1), elem(&2, 1)))
|> map(&elem(&1, 0))
end
@doc """ @doc """
Splits the enumerable into two collections, leaving `count` Splits the enumerable into two collections, leaving `count`
elements in the first one. If `count` is a negative number, elements in the first one. If `count` is a negative number,
+5 -3
View File
@@ -5,16 +5,16 @@ defmodule Exception do
Note that stacktraces in Elixir are updated on throw, Note that stacktraces in Elixir are updated on throw,
errors and exits. For example, at any given moment, errors and exits. For example, at any given moment,
`System.stacktrace` will return the stacktrace for the `System.stacktrace` will return the stacktrace for the
last throw/error/exit that ocurred in the current process. last throw/error/exit that occurred in the current process.
Do not rely on the particular format returned by the `format` Do not rely on the particular format returned by the `format`
functions in this module. They may be changed in future releases functions in this module. They may be changed in future releases
in order to better suit Elixir's tool chain. In other words, in order to better suit Elixir's tool chain. In other words,
by using the functions in this module it is guarantee you will by using the functions in this module it is guaranteed you will
format exceptions as in the current Elixir version being used. format exceptions as in the current Elixir version being used.
""" """
@typedoc "The exception type (as generated by defexception)" @typedoc "The exception type"
@type t :: %{__struct__: module, __exception__: true} @type t :: %{__struct__: module, __exception__: true}
@typedoc "The kind handled by formatting functions" @typedoc "The kind handled by formatting functions"
@@ -482,6 +482,7 @@ end
defmodule RuntimeError do defmodule RuntimeError do
defexception message: "runtime error" defexception message: "runtime error"
@spec exception(String.t) :: Exception.t
def exception(msg) when is_binary(msg) do def exception(msg) when is_binary(msg) do
%RuntimeError{message: msg} %RuntimeError{message: msg}
end end
@@ -494,6 +495,7 @@ end
defmodule ArgumentError do defmodule ArgumentError do
defexception message: "argument error" defexception message: "argument error"
@spec exception(String.t) :: Exception.t
def exception(msg) when is_binary(msg) do def exception(msg) when is_binary(msg) do
%ArgumentError{message: msg} %ArgumentError{message: msg}
end end
+21 -9
View File
@@ -308,11 +308,15 @@ defmodule File do
def touch(path, time \\ :calendar.local_time) do def touch(path, time \\ :calendar.local_time) do
path = IO.chardata_to_string(path) path = IO.chardata_to_string(path)
case F.change_time(path, time) do case F.change_time(path, time) do
{:error, :enoent} -> {:error, :enoent} -> touch_new(path, time)
write(path, "") other -> other
F.change_time(path, time) end
other -> end
other
defp touch_new(path, time) do
case write(path, "", [:append]) do
:ok -> F.change_time(path, time)
{:error, _reason} = error -> error
end end
end end
@@ -548,7 +552,8 @@ defmodule File do
[dest|acc] [dest|acc]
{:error, :eexist} -> {:error, :eexist} ->
if callback.(src, dest) do if callback.(src, dest) do
rm(dest) # If rm/1 fails, copy/2 will fail
_ = rm(dest)
case copy(src, dest) do case copy(src, dest) do
{:ok, _} -> {:ok, _} ->
copy_file_mode!(src, dest) copy_file_mode!(src, dest)
@@ -569,7 +574,8 @@ defmodule File do
[dest|acc] [dest|acc]
{:error, :eexist} -> {:error, :eexist} ->
if callback.(src, dest) do if callback.(src, dest) do
rm(dest) # If rm/1 fails, iF.make_symlink/2 will fail
_ = rm(dest)
case F.make_symlink(link, dest) do case F.make_symlink(link, dest) do
:ok -> [dest|acc] :ok -> [dest|acc]
{:error, reason} -> {:error, reason, src} {:error, reason} -> {:error, reason, src}
@@ -665,14 +671,20 @@ defmodule File do
if match? {:win32, _}, :os.type do if match? {:win32, _}, :os.type do
case F.read_file_info(IO.chardata_to_string(path)) do case F.read_file_info(IO.chardata_to_string(path)) do
{:ok, file_info} when elem(file_info, 3) in [:read, :none] -> {:ok, file_info} when elem(file_info, 3) in [:read, :none] ->
File.chmod(path, (elem(file_info, 7) + 0200)) change_mode_windows(path, file_info)
F.delete(path)
_ -> _ ->
nil nil
end end
end end
end end
defp change_mode_windows(path, file_info) do
case File.chmod(path, (elem(file_info, 7) + 0o200)) do
:ok -> F.delete(path)
{:error, _reason} = error -> error
end
end
@doc """ @doc """
Same as `rm/1`, but raises an exception in case of failure. Otherwise `:ok`. Same as `rm/1`, but raises an exception in case of failure. Otherwise `:ok`.
""" """
+1
View File
@@ -57,6 +57,7 @@ defmodule File.Stat do
pairs = :lists.zip(keys, vals) pairs = :lists.zip(keys, vals)
defstruct keys defstruct keys
@type t :: %__MODULE__{}
@doc """ @doc """
Converts a `File.Stat` struct to a `:file_info` record. Converts a `File.Stat` struct to a `:file_info` record.
+8 -2
View File
@@ -13,6 +13,8 @@ defmodule File.Stream do
defstruct path: nil, modes: [], line_or_bytes: :line, raw: true defstruct path: nil, modes: [], line_or_bytes: :line, raw: true
@type t :: %__MODULE__{}
@doc false @doc false
def __build__(path, modes, line_or_bytes) do def __build__(path, modes, line_or_bytes) do
raw = :lists.keyfind(:encoding, 1, modes) == false raw = :lists.keyfind(:encoding, 1, modes) == false
@@ -55,10 +57,14 @@ defmodule File.Stream do
false -> IO.write(device, x) false -> IO.write(device, x)
end end
:ok, :done -> :ok, :done ->
:file.close(device) # If delayed_write option is used and the last write failed will
# MatchError here as {:error, _} is returned.
:ok = :file.close(device)
stream stream
:ok, :halt -> :ok, :halt ->
:file.close(device) # If delayed_write option is used and the last write failed will
# MatchError here as {:error, _} is returned.
:ok = :file.close(device)
end end
end end
end end
+51 -29
View File
@@ -1,3 +1,5 @@
import Kernel, except: [round: 1]
defmodule Float do defmodule Float do
@moduledoc """ @moduledoc """
Functions for working with floating point numbers. Functions for working with floating point numbers.
@@ -99,57 +101,71 @@ defmodule Float do
@doc """ @doc """
Rounds a float to the largest integer less than or equal to `num`. Rounds a float to the largest integer less than or equal to `num`.
Floor also accepts a precision to round a floating point value down
to an arbitrary number of fractional digits (between 0 and 15).
This function always returns floats. One may use `Kernel.trunc/1` to
truncate the result to an integer afterwards.
## Examples ## Examples
iex> Float.floor(34)
34
iex> Float.floor(34.25) iex> Float.floor(34.25)
34 34.0
iex> Float.floor(-56.5) iex> Float.floor(-56.5)
-57 -57.0
iex> Float.floor(34.253, 2)
34.25
""" """
@spec floor(float | integer) :: integer @spec floor(float, 0..15) :: float
def floor(num) when is_integer(num), do: num def floor(number, precision \\ 0) when is_float(number) and precision in 0..15 do
def floor(num) when is_float(num) do power = power_of_10(precision)
truncated = :erlang.trunc(num) number = number * power
case :erlang.abs(num - truncated) do truncated = trunc(number)
x when x > 0 and num < 0 -> truncated - 1 variance = if number - truncated < 0, do: -1.0, else: 0.0
_ -> truncated (truncated + variance) / power
end
end end
@doc """ @doc """
Rounds a float to the largest integer greater than or equal to `num`. Rounds a float to the largest integer greater than or equal to `num`.
Ceil also accepts a precision to round a floating point value down to
an arbitrary number of fractional digits (between 0 and 15).
This function always returns floats. One may use `Kernel.trunc/1` to
truncate the result to an integer afterwards.
## Examples ## Examples
iex> Float.ceil(34)
34
iex> Float.ceil(34.25) iex> Float.ceil(34.25)
35 35.0
iex> Float.ceil(-56.5) iex> Float.ceil(-56.5)
-56 -56.0
iex> Float.ceil(34.253, 2)
34.26
""" """
@spec ceil(float | integer) :: integer @spec ceil(float, 0..15) :: float
def ceil(num) when is_integer(num), do: num def ceil(number, precision \\ 0) when is_float(number) and precision in 0..15 do
def ceil(num) when is_float(num) do power = power_of_10(precision)
truncated = :erlang.trunc(num) number = number * power
case :erlang.abs(num - truncated) do truncated = trunc(number)
x when x > 0 and num > 0 -> truncated + 1 variance = if number - truncated > 0, do: 1.0, else: 0.0
_ -> truncated (truncated + variance) / power
end
end end
@doc """ @doc """
Rounds a floating point value to an arbitrary number of fractional digits Rounds a floating point value to an arbitrary number of fractional digits
(between 0 and 15). (between 0 and 15).
This function only accepts floats and returns floats. Use `Kernel.round/1`
if you want a function that accepts both floats and integers and always
returns an integer.
## Examples ## Examples
iex> Float.round(5.5674, 3) iex> Float.round(5.5674, 3)
@@ -165,9 +181,15 @@ defmodule Float do
-5.568 -5.568
""" """
@spec round(float, integer) :: float @spec round(float, 0..15) :: float
def round(number, precision) when is_float(number) and is_integer(precision) and precision in 0..15 do def round(number, precision \\ 0) when is_float(number) and precision in 0..15 do
Kernel.round(number * :math.pow(10, precision)) / :math.pow(10, precision) power = power_of_10(precision)
Kernel.round(number * power) / power
end
Enum.reduce 0..15, 1, fn x, acc ->
defp power_of_10(unquote(x)), do: unquote(acc)
acc * 10
end end
@doc """ @doc """
+49 -35
View File
@@ -1,3 +1,29 @@
defmodule GenEvent.Stream do
@moduledoc """
Defines a `GenEvent` stream.
This is a struct returned by `stream/2`. The struct is public and
contains the following fields:
* `:manager` - the manager reference given to `GenEvent.stream/2`
* `:id` - the event stream id for cancellation
* `:timeout` - the timeout in between events, defaults to `:infinity`
* `:duration` - the duration of the subscription, defaults to `:infinity`
* `:mode` - if the subscription mode is sync or async, defaults to `:sync`
"""
defstruct manager: nil, id: nil, timeout: :infinity, duration: :infinity, mode: :sync
@typedoc "The stream mode"
@type mode :: :sync | :async
@type t :: %__MODULE__{
manager: GenEvent.manager,
id: term,
timeout: timeout,
duration: timeout,
mode: mode}
end
defmodule GenEvent do defmodule GenEvent do
@moduledoc """ @moduledoc """
A behaviour module for implementing event handling functionality. A behaviour module for implementing event handling functionality.
@@ -13,7 +39,7 @@ defmodule GenEvent do
## Example ## Example
There are many use cases for event handlers. For example, a logging There are many use cases for event handlers. For example, a logging
system can be built using event handlers where which log message is system can be built using event handlers where each log message is
an event and different event handlers can be plugged to handle the an event and different event handlers can be plugged to handle the
log messages. One handler may print error messages on the terminal, log messages. One handler may print error messages on the terminal,
another can write it to a file, while a third one can keep the another can write it to a file, while a third one can keep the
@@ -99,12 +125,6 @@ defmodule GenEvent do
are received by the process. Must return the same values as are received by the process. Must return the same values as
`handle_event/2`. `handle_event/2`.
It must return:
- `{:noreply, state}`
- `{:noreply, state, timeout}`
- `{:stop, reason, state}`
* `terminate(reason, state)` - called when the event handler is removed or * `terminate(reason, state)` - called when the event handler is removed or
the event manager is terminating. It can return any term. the event manager is terminating. It can return any term.
@@ -164,20 +184,6 @@ defmodule GenEvent do
@typedoc "Supported values for new handlers" @typedoc "Supported values for new handlers"
@type handler :: module | {module, term} @type handler :: module | {module, term}
@doc """
Defines a `GenEvent` stream.
This is a struct returned by `stream/2`. The struct is public and
contains the following fields:
* `:manager` - the manager reference given to `GenEvent.stream/2`
* `:id` - the event stream id for cancellation
* `:timeout` - the timeout in between events, defaults to `:infinity`
* `:duration` - the duration of the subscription, defaults to `:infinity`
* `:mode` - if the subscription mode is sync or async, defaults to `:sync`
"""
defstruct manager: nil, id: nil, timeout: :infinity, duration: :infinity, mode: :sync
@doc false @doc false
defmacro __using__(_) do defmacro __using__(_) do
quote location: :keep do quote location: :keep do
@@ -194,8 +200,12 @@ defmodule GenEvent do
end end
@doc false @doc false
def handle_call(_request, state) do def handle_call(msg, state) do
{:ok, {:error, :bad_call}, state} # We do this to trick dialyzer to not complain about non-local returns.
case :random.uniform(1) do
1 -> exit({:bad_call, msg})
2 -> {:remove_handler, :ok}
end
end end
@doc false @doc false
@@ -262,10 +272,12 @@ defmodule GenEvent do
end end
@doc """ @doc """
Returns a stream that consumes and notifies events to the `manager`. Returns a stream that consumes events from the `manager`.
The stream is a `GenEvent` struct that implements the `Enumerable` The stream is a `GenEvent` struct that implements the `Enumerable`
protocol. The supported options are: protocol. Consumption of events only begins when enumeration starts.
`The supported options are:
* `:id` - an id to identify all live stream instances; when an `:id` is * `:id` - an id to identify all live stream instances; when an `:id` is
given, existing streams can be called with via `cancel_streams`. given, existing streams can be called with via `cancel_streams`.
@@ -280,12 +292,14 @@ defmodule GenEvent do
before moving on to the next event handler. before moving on to the next event handler.
""" """
@spec stream(manager, Keyword.t) :: GenEvent.Stream.t
def stream(manager, options \\ []) do def stream(manager, options \\ []) do
%GenEvent{manager: manager, %GenEvent.Stream{
id: Keyword.get(options, :id), manager: manager,
timeout: Keyword.get(options, :timeout, :infinity), id: Keyword.get(options, :id),
duration: Keyword.get(options, :duration, :infinity), timeout: Keyword.get(options, :timeout, :infinity),
mode: Keyword.get(options, :mode, :sync)} duration: Keyword.get(options, :duration, :infinity),
mode: Keyword.get(options, :mode, :sync)}
end end
@doc """ @doc """
@@ -379,15 +393,15 @@ defmodule GenEvent do
when the stream is created via `stream/2`. Passing a stream without when the stream is created via `stream/2`. Passing a stream without
an id leads to an argument error. an id leads to an argument error.
""" """
@spec cancel_streams(t) :: :ok @spec cancel_streams(GenEvent.Stream.t) :: :ok
def cancel_streams(%GenEvent{id: nil}) do def cancel_streams(%GenEvent.Stream{id: nil}) do
raise ArgumentError, "cannot cancel streams without an id" raise ArgumentError, "cannot cancel streams without an id"
end end
def cancel_streams(%GenEvent{manager: manager, id: id}) do def cancel_streams(%GenEvent.Stream{manager: manager, id: id}) do
handlers = :gen_event.which_handlers(manager) handlers = :gen_event.which_handlers(manager)
for {Enumerable.GenEvent, {handler_id, _}} = ref <- handlers, _ = for {Enumerable.GenEvent.Stream, {handler_id, _}} = ref <- handlers,
handler_id === id do handler_id === id do
:gen_event.delete_handler(manager, ref, :remove_handler) :gen_event.delete_handler(manager, ref, :remove_handler)
end end
@@ -452,7 +466,7 @@ defmodule GenEvent do
defdelegate stop(manager), to: :gen_event defdelegate stop(manager), to: :gen_event
end end
defimpl Enumerable, for: GenEvent do defimpl Enumerable, for: GenEvent.Stream do
use GenEvent use GenEvent
@doc false @doc false
+1 -1
View File
@@ -11,7 +11,7 @@ defmodule GenServer do
## Example ## Example
The GenServer behaviour abstracts the common client-server interaction. The GenServer behaviour abstracts the common client-server interaction.
Developer are only required to implement the callbacks and functionality they are Developers are only required to implement the callbacks and functionality they are
interested in. interested in.
Let's start with a code example and then explore the available callbacks. Let's start with a code example and then explore the available callbacks.
+1 -1
View File
@@ -21,7 +21,7 @@ defmodule HashDict do
@node_size 8 @node_size 8
@node_template :erlang.make_tuple(@node_size, []) @node_template :erlang.make_tuple(@node_size, [])
@opaque t :: map @opaque t :: %__MODULE__{size: non_neg_integer, root: term}
@doc false @doc false
defstruct size: 0, root: @node_template defstruct size: 0, root: @node_template
+1 -1
View File
@@ -21,7 +21,7 @@ defmodule HashSet do
@node_size 8 @node_size 8
@node_template :erlang.make_tuple(@node_size, []) @node_template :erlang.make_tuple(@node_size, [])
@opaque t :: map @opaque t :: %__MODULE__{size: non_neg_integer, root: term}
@doc false @doc false
defstruct size: 0, root: @node_template defstruct size: 0, root: @node_template
+17 -18
View File
@@ -212,29 +212,28 @@ defimpl Inspect, for: BitString do
@doc false @doc false
# Also used by Regex # Also used by Regex
def escape_char(char) when char in ?\000..?\377, def escape_char(0) do
do: octify(char) <<?\\, ?0>>
def escape_char(char), do: hexify(char)
defp octify(byte) do
<< hi :: size(2), mi :: size(3), lo :: size(3) >> = << byte >>
<< ?\\, ?0 + hi, ?0 + mi, ?0 + lo >>
end end
defp hexify(char) when char < 0x10000 do def escape_char(char) when char < 0x100 do
<<a::4, b::4>> = <<char::size(8)>>
<<?\\, ?x, to_hex(a), to_hex(b)>>
end
def escape_char(char) when char < 0x10000 do
<<a::4, b::4, c::4, d::4>> = <<char::size(16)>> <<a::4, b::4, c::4, d::4>> = <<char::size(16)>>
<<?\\, ?x, ?{, to_hex(a), to_hex(b), to_hex(c), to_hex(d), ?}>> <<?\\, ?x, ?{, to_hex(a), to_hex(b), to_hex(c), to_hex(d), ?}>>
end end
defp hexify(char) when char < 0x1000000 do def escape_char(char) when char < 0x1000000 do
<<a::4, b::4, c::4, d::4, e::4, f::4>> = <<char::size(24)>> <<a::4, b::4, c::4, d::4, e::4, f::4>> = <<char::size(24)>>
<<?\\, ?x, ?{, to_hex(a), to_hex(b), to_hex(c), <<?\\, ?x, ?{, to_hex(a), to_hex(b), to_hex(c),
to_hex(d), to_hex(e), to_hex(f), ?}>> to_hex(d), to_hex(e), to_hex(f), ?}>>
end end
defp to_hex(c) when c in 0..9, do: ?0+c defp to_hex(c) when c in 0..9, do: ?0+c
defp to_hex(c) when c in 10..15, do: ?a+c-10 defp to_hex(c) when c in 10..15, do: ?A+c-10
defp append(<<h, t :: binary>>, binary), do: append(t, << binary :: binary, h >>) defp append(<<h, t :: binary>>, binary), do: append(t, << binary :: binary, h >>)
defp append(<<>>, binary), do: binary defp append(<<>>, binary), do: binary
@@ -298,9 +297,9 @@ defimpl Inspect, for: List do
lists == :as_char_lists or (lists == :infer and printable?(thing)) -> lists == :as_char_lists or (lists == :infer and printable?(thing)) ->
<< ?', Inspect.BitString.escape(IO.chardata_to_string(thing), ?') :: binary, ?' >> << ?', Inspect.BitString.escape(IO.chardata_to_string(thing), ?') :: binary, ?' >>
keyword?(thing) -> keyword?(thing) ->
surround_many("[", thing, "]", opts.limit, &keyword(&1, opts)) surround_many("[", thing, "]", opts, &keyword/2)
true -> true ->
surround_many("[", thing, "]", opts.limit, &to_doc(&1, opts)) surround_many("[", thing, "]", opts, &to_doc/2)
end end
end end
@@ -347,7 +346,7 @@ defimpl Inspect, for: Tuple do
def inspect({}, _opts), do: "{}" def inspect({}, _opts), do: "{}"
def inspect(tuple, opts) do def inspect(tuple, opts) do
surround_many("{", Tuple.to_list(tuple), "}", opts.limit, &to_doc(&1, opts)) surround_many("{", Tuple.to_list(tuple), "}", opts, &to_doc/2)
end end
end end
@@ -358,14 +357,14 @@ defimpl Inspect, for: Map do
def inspect(map, name, opts) do def inspect(map, name, opts) do
map = :maps.to_list(map) map = :maps.to_list(map)
surround_many("%" <> name <> "{", map, "}", opts.limit, traverse_fun(map, opts)) surround_many("%" <> name <> "{", map, "}", opts, traverse_fun(map))
end end
defp traverse_fun(list, opts) do defp traverse_fun(list) do
if Inspect.List.keyword?(list) do if Inspect.List.keyword?(list) do
&Inspect.List.keyword(&1, opts) &Inspect.List.keyword/2
else else
&to_map(&1, opts) &to_map/2
end end
end end
+156 -92
View File
@@ -29,15 +29,24 @@ defmodule Inspect.Opts do
* `:pretty` - if set to true enables pretty printing, defaults to false. * `:pretty` - if set to true enables pretty printing, defaults to false.
* `:width` - defaults to the 80 characters. * `:width` - defaults to the 80 characters, used when pretty is true or
when printing to IO devices.
""" """
defstruct structs: true :: boolean, defstruct structs: true,
binaries: :infer :: :infer | :as_binaries | :as_strings, binaries: :infer,
char_lists: :infer :: :infer | :as_lists | :as_char_lists, char_lists: :infer,
limit: 50 :: pos_integer, limit: 50,
width: 80 :: pos_integer | :infinity, width: 80,
pretty: false :: boolean pretty: false
@type t :: %__MODULE__{
structs: boolean,
binaries: :infer | :as_binaries | :as_strings,
char_lists: :infer | :as_lists | :as_char_lists,
limit: pos_integer,
width: pos_integer | :infinity,
pretty: boolean}
end end
defmodule Inspect.Algebra do defmodule Inspect.Algebra do
@@ -58,8 +67,8 @@ defmodule Inspect.Algebra do
elements together and render them: elements together and render them:
iex> doc = Inspect.Algebra.concat(Inspect.Algebra.empty, "foo") iex> doc = Inspect.Algebra.concat(Inspect.Algebra.empty, "foo")
iex> Inspect.Algebra.pretty(doc, 80) iex> Inspect.Algebra.format(doc, 80)
"foo" ["foo"]
The functions `nest/2`, `space/2` and `line/2` help you put the The functions `nest/2`, `space/2` and `line/2` help you put the
document together into a rigid structure. However, the document document together into a rigid structure. However, the document
@@ -69,15 +78,15 @@ defmodule Inspect.Algebra do
render it: render it:
iex> doc = Inspect.Algebra.glue("a", " ", "b") iex> doc = Inspect.Algebra.glue("a", " ", "b")
iex> Inspect.Algebra.pretty(doc, 80) iex> Inspect.Algebra.format(doc, 80)
"a b" ["a", " ", "b"]
Notice the break was represented as is, because we haven't reached Notice the break was represented as is, because we haven't reached
a line limit. Once we do, it is replaced by a newline: a line limit. Once we do, it is replaced by a newline:
iex> doc = Inspect.Algebra.glue(String.duplicate("a", 20), " ", "b") iex> doc = Inspect.Algebra.glue(String.duplicate("a", 20), " ", "b")
iex> Inspect.Algebra.pretty(doc, 10) iex> Inspect.Algebra.format(doc, 10)
"aaaaaaaaaaaaaaaaaaaa\nb" ["aaaaaaaaaaaaaaaaaaaa", "\n", "b"]
Finally, this module also contains Elixir related functions, a bit Finally, this module also contains Elixir related functions, a bit
tied to Elixir formatting, namely `surround/3` and `surround_many/5`. tied to Elixir formatting, namely `surround/3` and `surround_many/5`.
@@ -170,10 +179,26 @@ defmodule Inspect.Algebra do
Inspect.inspect(map, opts) Inspect.inspect(map, opts)
rescue rescue
e -> e ->
res = Inspect.Map.inspect(map, opts) # Because we try to raise a nice error message in case
raise ArgumentError, # we can't inspect a struct, there is a chance the error
"Got #{inspect e.__struct__} with message " <> # message itself relies on the struct being printed, so
"\"#{Exception.message(e)}\" while inspecting #{pretty(res, opts.width)}" # we need to trap the inspected messages to guarantee
# we won't try to render any failed instruct when building
# the error message.
if Process.get(:inspect_trap) do
Inspect.Map.inspect(map, opts)
else
try do
Process.put(:inspect_trap, true)
res = Inspect.Map.inspect(map, opts)
formatted = IO.iodata_to_binary(format(res, :infinity))
raise ArgumentError,
"Got #{inspect e.__struct__} with message " <>
"\"#{Exception.message(e)}\" while inspecting #{formatted}"
after
Process.delete(:inspect_trap)
end
end
end end
else else
Inspect.Map.inspect(map, opts) Inspect.Map.inspect(map, opts)
@@ -201,9 +226,9 @@ defmodule Inspect.Algebra do
## Examples ## Examples
iex> doc = Inspect.Algebra.concat "Tasteless", "Artosis" iex> doc = Inspect.Algebra.concat "hello", "world"
iex> Inspect.Algebra.pretty(doc, 80) iex> Inspect.Algebra.format(doc, 80)
"TastelessArtosis" ["hello", "world"]
""" """
@spec concat(t, t) :: doc_cons @spec concat(t, t) :: doc_cons
@@ -227,8 +252,8 @@ defmodule Inspect.Algebra do
## Examples ## Examples
iex> doc = Inspect.Algebra.nest(Inspect.Algebra.glue("hello", "world"), 5) iex> doc = Inspect.Algebra.nest(Inspect.Algebra.glue("hello", "world"), 5)
iex> Inspect.Algebra.pretty(doc, 5) iex> Inspect.Algebra.format(doc, 5)
"hello\n world" ["hello", "\n ", "world"]
""" """
@spec nest(t, non_neg_integer) :: doc_nest @spec nest(t, non_neg_integer) :: doc_nest
@@ -252,15 +277,15 @@ defmodule Inspect.Algebra do
Let's glue two docs together with a break and then render it: Let's glue two docs together with a break and then render it:
iex> doc = Inspect.Algebra.glue("a", " ", "b") iex> doc = Inspect.Algebra.glue("a", " ", "b")
iex> Inspect.Algebra.pretty(doc, 80) iex> Inspect.Algebra.format(doc, 80)
"a b" ["a", " ", "b"]
Notice the break was represented as is, because we haven't reached Notice the break was represented as is, because we haven't reached
a line limit. Once we do, it is replaced by a newline: a line limit. Once we do, it is replaced by a newline:
iex> doc = Inspect.Algebra.glue(String.duplicate("a", 20), " ", "b") iex> doc = Inspect.Algebra.glue(String.duplicate("a", 20), " ", "b")
iex> Inspect.Algebra.pretty(doc, 10) iex> Inspect.Algebra.format(doc, 10)
"aaaaaaaaaaaaaaaaaaaa\nb" ["aaaaaaaaaaaaaaaaaaaa", "\n", "b"]
""" """
@spec break(binary) :: doc_break @spec break(binary) :: doc_break
@@ -303,10 +328,10 @@ defmodule Inspect.Algebra do
...> "B" ...> "B"
...> ) ...> )
...> )) ...> ))
iex> Inspect.Algebra.pretty(doc, 80) iex> Inspect.Algebra.format(doc, 80)
"Hello, A B" ["Hello,", " ", "A", " ", "B"]
iex> Inspect.Algebra.pretty(doc, 6) iex> Inspect.Algebra.format(doc, 6)
"Hello,\nA B" ["Hello,", "\n", "A", " ", "B"]
""" """
@spec group(t) :: doc_group @spec group(t) :: doc_group
@@ -320,8 +345,8 @@ defmodule Inspect.Algebra do
## Examples ## Examples
iex> doc = Inspect.Algebra.space "Hughes", "Wadler" iex> doc = Inspect.Algebra.space "Hughes", "Wadler"
iex> Inspect.Algebra.pretty(doc, 80) iex> Inspect.Algebra.format(doc, 80)
"Hughes Wadler" ["Hughes", " ", "Wadler"]
""" """
@spec space(t, t) :: doc_cons @spec space(t, t) :: doc_cons
@@ -333,8 +358,8 @@ defmodule Inspect.Algebra do
## Examples ## Examples
iex> doc = Inspect.Algebra.line "Hughes", "Wadler" iex> doc = Inspect.Algebra.line "Hughes", "Wadler"
iex> Inspect.Algebra.pretty(doc, 80) iex> Inspect.Algebra.format(doc, 80)
"Hughes\nWadler" ["Hughes", "\n", "Wadler"]
""" """
@spec line(t, t) :: doc_cons @spec line(t, t) :: doc_cons
@@ -350,8 +375,8 @@ defmodule Inspect.Algebra do
iex> doc = Inspect.Algebra.folddoc(doc, fn(x,y) -> iex> doc = Inspect.Algebra.folddoc(doc, fn(x,y) ->
...> Inspect.Algebra.concat [x, "!", y] ...> Inspect.Algebra.concat [x, "!", y]
...> end) ...> end)
iex> Inspect.Algebra.pretty(doc, 80) iex> Inspect.Algebra.format(doc, 80)
"A!B" ["A", "!", "B"]
""" """
@spec folddoc([t], ((t, t) -> t)) :: t @spec folddoc([t], ((t, t) -> t)) :: t
@@ -371,8 +396,8 @@ defmodule Inspect.Algebra do
## Examples ## Examples
iex> doc = Inspect.Algebra.surround "[", Inspect.Algebra.glue("a", "b"), "]" iex> doc = Inspect.Algebra.surround "[", Inspect.Algebra.glue("a", "b"), "]"
iex> Inspect.Algebra.pretty(doc, 3) iex> Inspect.Algebra.format(doc, 3)
"[a\n b]" ["[", "a", "\n ", "b", "]"]
""" """
@spec surround(binary, t, binary) :: t @spec surround(binary, t, binary) :: t
@@ -381,52 +406,97 @@ defmodule Inspect.Algebra do
end end
@doc ~S""" @doc ~S"""
Maps and glues a collection of items together using the given separator Maps and glues a collection of items.
and surrounds them. A limit can be passed which, once reached, stops
gluing and outputs "..." instead. It uses the given left and right as surrounding and a separator for
each item. A limit can be passed which, once reached, stops gluing
and outputs "..." instead.
## Examples ## Examples
iex> doc = Inspect.Algebra.surround_many("[", Enum.to_list(1..5), "]", :infinity, &Integer.to_string(&1)) iex> doc = Inspect.Algebra.surround_many("[", Enum.to_list(1..5), "]",
iex> Inspect.Algebra.pretty(doc, 5) ...> %Inspect.Opts{limit: :infinity}, fn i, _opts -> to_string(i) end)
iex> Inspect.Algebra.format(doc, 5) |> IO.iodata_to_binary
"[1,\n 2,\n 3,\n 4,\n 5]" "[1,\n 2,\n 3,\n 4,\n 5]"
iex> doc = Inspect.Algebra.surround_many("[", Enum.to_list(1..5), "]", 3, &Integer.to_string(&1)) iex> doc = Inspect.Algebra.surround_many("[", Enum.to_list(1..5), "]",
iex> Inspect.Algebra.pretty(doc, 20) ...> %Inspect.Opts{limit: 3}, fn i, _opts -> to_string(i) end)
iex> Inspect.Algebra.format(doc, 20) |> IO.iodata_to_binary
"[1, 2, 3, ...]" "[1, 2, 3, ...]"
iex> doc = Inspect.Algebra.surround_many("[", Enum.to_list(1..5), "]", 3, &Integer.to_string(&1), "!") iex> doc = Inspect.Algebra.surround_many("[", Enum.to_list(1..5), "]",
iex> Inspect.Algebra.pretty(doc, 20) ...> %Inspect.Opts{limit: 3}, fn i, _opts -> to_string(i) end, "!")
iex> Inspect.Algebra.format(doc, 20) |> IO.iodata_to_binary
"[1! 2! 3! ...]" "[1! 2! 3! ...]"
""" """
@spec surround_many(binary, [any], binary, integer | :infinity, (term -> t), binary) :: t @spec surround_many(binary, [any], binary, integer | :infinity, (term -> t), binary) :: t
def surround_many(left, docs, right, limit, fun, separator \\ @surround_separator) def surround_many(left, docs, right, opts, fun, separator \\ @surround_separator) do
if is_integer(opts) do
IO.write :stderr, "Inspect.Algebra.surround_many/6 with an integer limit is deprecated, " <>
"please pass Inspect.Opts instead\n#{Exception.format_stacktrace}"
old_surround_many(left, docs, right, opts, fun, separator)
else
do_surround_many(left, docs, right, opts.limit, opts, fun, separator)
end
end
def surround_many(left, [], right, _, _fun, _) do def do_surround_many(left, [], right, _, _opts, _fun, _) do
concat(left, right) concat(left, right)
end end
def surround_many(left, docs, right, limit, fun, sep) do def do_surround_many(left, docs, right, limit, _opts, fun, sep) do
surround(left, surround_many(docs, limit, fun, sep), right) surround(left, do_surround_many(docs, limit, _opts, fun, sep), right)
end end
defp surround_many(_, 0, _fun, _sep) do defp do_surround_many(_, 0, _opts, _fun, _sep) do
"..." "..."
end end
defp surround_many([h], _limit, fun, _sep) do defp do_surround_many([h], limit, opts, fun, _sep) do
fun.(h) fun.(h, %{opts | limit: limit})
end end
defp surround_many([h|t], limit, fun, sep) when is_list(t) do defp do_surround_many([h|t], limit, opts, fun, sep) when is_list(t) do
limit = decrement(limit)
glue( glue(
concat(fun.(h), sep), concat(fun.(h, %{opts | limit: limit}), sep),
surround_many(t, decrement(limit), fun, sep) do_surround_many(t, limit, opts, fun, sep)
) )
end end
defp surround_many([h|t], _limit, fun, _sep) do defp do_surround_many([h|t], limit, opts, fun, _sep) do
limit = decrement(limit)
glue(
concat(fun.(h, %{opts | limit: limit}), @tail_separator),
fun.(t, %{opts | limit: limit})
)
end
def old_surround_many(left, [], right, _, _fun, _) do
concat(left, right)
end
def old_surround_many(left, docs, right, limit, fun, sep) do
surround(left, old_surround_many(docs, limit, fun, sep), right)
end
defp old_surround_many(_, 0, _fun, _sep) do
"..."
end
defp old_surround_many([h], _limit, fun, _sep) do
fun.(h)
end
defp old_surround_many([h|t], limit, fun, sep) when is_list(t) do
glue(
concat(fun.(h), sep),
old_surround_many(t, decrement(limit), fun, sep)
)
end
defp old_surround_many([h|t], _limit, fun, _sep) do
glue( glue(
concat(fun.(h), @tail_separator), concat(fun.(h), @tail_separator),
fun.(t) fun.(t)
@@ -437,50 +507,50 @@ defmodule Inspect.Algebra do
defp decrement(counter), do: counter - 1 defp decrement(counter), do: counter - 1
@doc """ @doc """
The pretty printing function. The formatting function.
Takes the maximum width and a document to print as its arguments Takes the maximum width and a document to print as its arguments
and returns the string representation of the best layout for the and returns an IO data representation of the best layout for the
document to fit in the given width. document to fit in the given width.
""" """
@spec pretty(t, non_neg_integer | :infinity) :: binary @spec format(t, non_neg_integer | :infinity) :: binary
def format(d, w) do
format(w, 0, [{0, default_mode(w), doc_group(d)}])
end
@doc false
def pretty(d, w) do def pretty(d, w) do
sdoc = format w, 0, [{0, default_mode(w), doc_group(d)}] format(d, w) |> IO.iodata_to_binary
render(sdoc)
end end
defp default_mode(:infinity), do: :flat defp default_mode(:infinity), do: :flat
defp default_mode(_), do: :break defp default_mode(_), do: :break
# Rendering and internal helpers
# Record representing the document mode to be rendered: flat or broken # Record representing the document mode to be rendered: flat or broken
@typep mode :: :flat | :break @typep mode :: :flat | :break
@doc false
@spec fits?(integer, [{integer, mode, t}]) :: boolean @spec fits?(integer, [{integer, mode, t}]) :: boolean
def fits?(w, _) when w < 0, do: false defp fits?(w, _) when w < 0, do: false
def fits?(_, []), do: true defp fits?(_, []), do: true
def fits?(_, [{_, _, :doc_line} | _]), do: true defp fits?(_, [{_, _, :doc_line} | _]), do: true
def fits?(w, [{_, _, :doc_nil} | t]), do: fits?(w, t) defp fits?(w, [{_, _, :doc_nil} | t]), do: fits?(w, t)
def fits?(w, [{i, m, doc_cons(x, y)} | t]), do: fits?(w, [{i, m, x} | [{i, m, y} | t]]) defp fits?(w, [{i, m, doc_cons(x, y)} | t]), do: fits?(w, [{i, m, x} | [{i, m, y} | t]])
def fits?(w, [{i, m, doc_nest(x, j)} | t]), do: fits?(w, [{i + j, m, x} | t]) defp fits?(w, [{i, m, doc_nest(x, j)} | t]), do: fits?(w, [{i + j, m, x} | t])
def fits?(w, [{i, _, doc_group(x)} | t]), do: fits?(w, [{i, :flat, x} | t]) defp fits?(w, [{i, _, doc_group(x)} | t]), do: fits?(w, [{i, :flat, x} | t])
def fits?(w, [{_, _, s} | t]) when is_binary(s), do: fits?((w - byte_size s), t) defp fits?(w, [{_, _, s} | t]) when is_binary(s), do: fits?((w - byte_size s), t)
def fits?(w, [{_, :flat, doc_break(s)} | t]), do: fits?((w - byte_size s), t) defp fits?(w, [{_, :flat, doc_break(s)} | t]), do: fits?((w - byte_size s), t)
def fits?(_, [{_, :break, doc_break(_)} | _]), do: true defp fits?(_, [{_, :break, doc_break(_)} | _]), do: true
@doc false
@spec format(integer | :infinity, integer, [{integer, mode, t}]) :: [binary] @spec format(integer | :infinity, integer, [{integer, mode, t}]) :: [binary]
def format(_, _, []), do: [] defp format(_, _, []), do: []
def format(w, _, [{i, _, :doc_line} | t]), do: [indent(i) | format(w, i, t)] defp format(w, _, [{i, _, :doc_line} | t]), do: [indent(i) | format(w, i, t)]
def format(w, k, [{_, _, :doc_nil} | t]), do: format(w, k, t) defp format(w, k, [{_, _, :doc_nil} | t]), do: format(w, k, t)
def format(w, k, [{i, m, doc_cons(x, y)} | t]), do: format(w, k, [{i, m, x} | [{i, m, y} | t]]) defp format(w, k, [{i, m, doc_cons(x, y)} | t]), do: format(w, k, [{i, m, x} | [{i, m, y} | t]])
def format(w, k, [{i, m, doc_nest(x, j)} | t]), do: format(w, k, [{i + j, m, x} | t]) defp format(w, k, [{i, m, doc_nest(x, j)} | t]), do: format(w, k, [{i + j, m, x} | t])
def format(w, k, [{i, m, doc_group(x)} | t]), do: format(w, k, [{i, m, x} | t]) defp format(w, k, [{i, m, doc_group(x)} | t]), do: format(w, k, [{i, m, x} | t])
def format(w, k, [{_, _, s} | t]) when is_binary(s), do: [s | format(w, (k + byte_size s), t)] defp format(w, k, [{_, _, s} | t]) when is_binary(s), do: [s | format(w, (k + byte_size s), t)]
def format(w, k, [{_, :flat, doc_break(s)} | t]), do: [s | format(w, (k + byte_size s), t)] defp format(w, k, [{_, :flat, doc_break(s)} | t]), do: [s | format(w, (k + byte_size s), t)]
def format(w, k, [{i, :break, doc_break(s)} | t]) do defp format(w, k, [{i, :break, doc_break(s)} | t]) do
k = k + byte_size(s) k = k + byte_size(s)
if w == :infinity or fits?(w - k, t) do if w == :infinity or fits?(w - k, t) do
@@ -492,10 +562,4 @@ defmodule Inspect.Algebra do
defp indent(0), do: @newline defp indent(0), do: @newline
defp indent(i), do: @newline <> :binary.copy(" ", i) defp indent(i), do: @newline <> :binary.copy(" ", i)
@doc false
@spec render([binary]) :: binary
def render(sdoc) do
IO.iodata_to_binary sdoc
end
end end
+2 -2
View File
@@ -115,7 +115,7 @@ defmodule Integer do
'7' '7'
""" """
@spec to_char_list(integer) :: list @spec to_char_list(integer) :: char_list
def to_char_list(number) do def to_char_list(number) do
:erlang.integer_to_list(number) :erlang.integer_to_list(number)
end end
@@ -132,7 +132,7 @@ defmodule Integer do
'3FF' '3FF'
""" """
@spec to_char_list(integer, pos_integer) :: list @spec to_char_list(integer, pos_integer) :: char_list
def to_char_list(number, base) do def to_char_list(number, base) do
:erlang.integer_to_list(number, base) :erlang.integer_to_list(number, base)
end end
+56 -16
View File
@@ -43,8 +43,10 @@ defmodule IO do
end end
@doc """ @doc """
Reads `count` characters from the IO device or until Reads `count` characters from the IO device, a whole
the end of the line if `:line` is given. It returns: `:line` or the whole device with `:all`.
It returns:
* `data` - the input characters * `data` - the input characters
@@ -53,10 +55,17 @@ defmodule IO do
* `{:error, reason}` - other (rare) error condition; * `{:error, reason}` - other (rare) error condition;
for instance, `{:error, :estale}` if reading from an for instance, `{:error, :estale}` if reading from an
NFS volume NFS volume
If `:all` is given, `:eof` is never returned, but an
empty string in case the device has reached EOF.
""" """
@spec read(device, :line | non_neg_integer) :: chardata | nodata @spec read(device, :all | :line | non_neg_integer) :: chardata | nodata
def read(device \\ group_leader, chars_or_line) def read(device \\ group_leader, chars_or_line)
def read(device, :all) do
do_read_all(map_dev(device), "")
end
def read(device, :line) do def read(device, :line) do
:io.get_line(map_dev(device), '') :io.get_line(map_dev(device), '')
end end
@@ -65,9 +74,19 @@ defmodule IO do
:io.get_chars(map_dev(device), '', count) :io.get_chars(map_dev(device), '', count)
end end
defp do_read_all(mapped_dev, acc) do
case :io.get_line(mapped_dev, "") do
line when is_binary(line) -> do_read_all(mapped_dev, acc <> line)
:eof -> acc
other -> other
end
end
@doc """ @doc """
Reads `count` bytes from the IO device or until Reads `count` characters from the IO device, a whole
the end of the line if `:line` is given. It returns: `:line` or the whole device with `:all`.
It returns:
* `data` - the input characters * `data` - the input characters
@@ -76,10 +95,20 @@ defmodule IO do
* `{:error, reason}` - other (rare) error condition; * `{:error, reason}` - other (rare) error condition;
for instance, `{:error, :estale}` if reading from an for instance, `{:error, :estale}` if reading from an
NFS volume NFS volume
If `:all` is given, `:eof` is never returned, but an
empty string in case the device has reached EOF.
Note: do not use this function on IO devices in unicode mode
as it will return the wrong result.
""" """
@spec binread(device, :line | non_neg_integer) :: iodata | nodata @spec binread(device, :all | :line | non_neg_integer) :: iodata | nodata
def binread(device \\ group_leader, chars_or_line) def binread(device \\ group_leader, chars_or_line)
def binread(device, :all) do
do_binread_all(map_dev(device), "")
end
def binread(device, :line) do def binread(device, :line) do
case :file.read_line(map_dev(device)) do case :file.read_line(map_dev(device)) do
{:ok, data} -> data {:ok, data} -> data
@@ -94,6 +123,14 @@ defmodule IO do
end end
end end
defp do_binread_all(mapped_dev, acc) do
case :file.read_line(mapped_dev) do
{:ok, data} -> do_binread_all(mapped_dev, acc <> data)
:eof -> acc
other -> other
end
end
@doc """ @doc """
Writes the given argument to the given device. Writes the given argument to the given device.
@@ -119,6 +156,9 @@ defmodule IO do
as a binary, no unicode conversion happens. as a binary, no unicode conversion happens.
Check `write/2` for more information. Check `write/2` for more information.
Note: do not use this function on IO devices in unicode mode
as it will return the wrong result.
""" """
@spec binwrite(device, iodata) :: :ok | {:error, term} @spec binwrite(device, iodata) :: :ok | {:error, term}
def binwrite(device \\ group_leader(), item) when is_iodata(item) do def binwrite(device \\ group_leader(), item) when is_iodata(item) do
@@ -139,17 +179,13 @@ defmodule IO do
@doc """ @doc """
Inspects and writes the given argument to the device. Inspects and writes the given argument to the device.
It sets by default pretty printing to true and returns It enables pretty printing by default with width of
the item itself. 80 characters. Th width can be changed by explicitly
passing the `:width` option.
Note this function does not use the IO device width
because some IO devices does not implement the
appropriate functions. Setting the width must be done
explicitly by passing the `:width` option.
## Examples ## Examples
IO.inspect Process.list IO.inspect Process.list, width: 40
""" """
@spec inspect(term, Keyword.t) :: term @spec inspect(term, Keyword.t) :: term
@@ -162,8 +198,9 @@ defmodule IO do
""" """
@spec inspect(device, term, Keyword.t) :: term @spec inspect(device, term, Keyword.t) :: term
def inspect(device, item, opts) when is_list(opts) do def inspect(device, item, opts) when is_list(opts) do
opts = Keyword.put_new(opts, :pretty, true) opts = struct(Inspect.Opts, opts)
puts device, Kernel.inspect(item, opts) iodata = Inspect.Algebra.format(Inspect.Algebra.to_doc(item, opts), opts.width)
puts device, iodata
item item
end end
@@ -263,6 +300,9 @@ defmodule IO do
Note that an IO stream has side effects and every time Note that an IO stream has side effects and every time
you go over the stream you may get different results. you go over the stream you may get different results.
Finally, do not use this function on IO devices in unicode
mode as it will return the wrong result.
""" """
@spec binstream(device, :line | pos_integer) :: Enumerable.t @spec binstream(device, :line | pos_integer) :: Enumerable.t
def binstream(device, line_or_bytes) do def binstream(device, line_or_bytes) do
+89 -40
View File
@@ -10,6 +10,10 @@ defmodule IO.ANSI.Sequence do
defp escape_sequence(unquote(Atom.to_char_list(name))) do defp escape_sequence(unquote(Atom.to_char_list(name))) do
unquote(name)() unquote(name)()
end end
defp format_sequence(unquote(name)) do
unquote(name)()
end
end end
end end
end end
@@ -24,21 +28,31 @@ defmodule IO.ANSI do
import IO.ANSI.Sequence import IO.ANSI.Sequence
@doc """ @typep ansicode :: atom()
Checks whether the default I/O device is a terminal or a file. @typep ansilist :: maybe_improper_list(char() | ansicode() | binary() | ansilist(), binary() | ansicode() | [])
@type ansidata :: ansilist() | ansicode() | binary()
Used to identify whether printing ANSI escape sequences will likely @doc false
be displayed as intended. This is checked by sending a message to
the group leader. In case the group leader does not support the message,
it will likely lead to a timeout (and a slow down on execution time).
"""
@spec terminal? :: boolean
@spec terminal?(:io.device) :: boolean
def terminal?(device \\ :erlang.group_leader) do def terminal?(device \\ :erlang.group_leader) do
!match?({:win32, _}, :os.type()) and !match?({:win32, _}, :os.type()) and
match?({:ok, _}, :io.columns(device)) match?({:ok, _}, :io.columns(device))
end end
@doc """
Checks if ANSI coloring is supported and enabled on this machine.
By default, ANSI is only enabled on UNIX machines. Enabling or
disabling ANSI escapes can be done by configuring the
`:ansi_enabled` value in the `:elixir` application.
"""
@spec enabled? :: boolean
def enabled? do
case Application.fetch_env(:elixir, :ansi_enabled) do
{:ok, boolean} when is_boolean(boolean) -> boolean
:error -> !match?({:win32, _}, :os.type())
end
end
@doc "Resets all attributes" @doc "Resets all attributes"
defsequence :reset, 0 defsequence :reset, 0
@@ -134,27 +148,82 @@ defmodule IO.ANSI do
raise ArgumentError, "invalid ANSI sequence specification: #{other}" raise ArgumentError, "invalid ANSI sequence specification: #{other}"
end end
defp format_sequence(other) do
raise ArgumentError, "invalid ANSI sequence specification: #{other}"
end
@doc ~S""" @doc ~S"""
Escapes a string by converting named ANSI sequences into actual ANSI codes. Formats a chardata-like argument by converting named ANSI sequences into actual
ANSI codes.
The format for referring to sequences is `%{red}` and `%{red,bright}` (for The named sequences are represented by atoms.
multiple sequences).
It will also append a `%{reset}` to the string. If you don't want this It will also append an `IO.ANSI.reset` to the chardata when a conversion is
behaviour, use `escape_fragment/2`. performed. If you don't want this behaviour, use `format_fragment/2`.
An optional boolean parameter can be passed to enable or disable An optional boolean parameter can be passed to enable or disable
emitting actual ANSI codes. When `false`, no ANSI codes will emitted. emitting actual ANSI codes. When `false`, no ANSI codes will emitted.
By default, standard output will be checked if it is a terminal capable By default checks if ANSI is enabled using the `enabled?/0` function.
of handling these sequences (using `terminal?/1` function)
## Examples ## Examples
iex> IO.ANSI.escape("Hello %{red,bright,green}yes", true) iex> IO.ANSI.format(["Hello, ", :red, :bright, "world!"], true)
"Hello \e[31m\e[1m\e[32myes\e[0m" [[[[[[], "Hello, "] | "\e[31m"] | "\e[1m"], "world!"] | "\e[0m"]
""" """
@spec escape(String.t, emit :: boolean) :: String.t def format(chardata, emit \\ enabled?) when is_boolean(emit) do
do_format(chardata, [], [], emit, :maybe)
end
@doc ~S"""
Formats a chardata-like argument by converting named ANSI sequences into actual
ANSI codes.
The named sequences are represented by atoms.
An optional boolean parameter can be passed to enable or disable
emitting actual ANSI codes. When `false`, no ANSI codes will emitted.
By default checks if ANSI is enabled using the `enabled?/0` function.
## Examples
iex> IO.ANSI.format_fragment([:bright, 'Word'], true)
[[[[[[] | "\e[1m"], 87], 111], 114], 100]
"""
def format_fragment(chardata, emit \\ enabled?) when is_boolean(emit) do
do_format(chardata, [], [], emit, false)
end
defp do_format([term | rest], rem, acc, emit, append_reset) do
do_format(term, [rest | rem], acc, emit, append_reset)
end
defp do_format(term, rem, acc, true, append_reset) when is_atom(term) do
do_format([], rem, [acc | format_sequence(term)], true, !!append_reset)
end
defp do_format(term, rem, acc, false, append_reset) when is_atom(term) do
do_format([], rem, acc, false, append_reset)
end
defp do_format(term, rem, acc, emit, append_reset) when not is_list(term) do
do_format([], rem, [acc | [term]], emit, append_reset)
end
defp do_format([], [next | rest], acc, emit, append_reset) do
do_format(next, rest, acc, emit, append_reset)
end
defp do_format([], [], acc, true, true) do
[acc | IO.ANSI.reset]
end
defp do_format([], [], acc, _emit, _append_reset) do
acc
end
@doc false
def escape(string, emit \\ terminal?) when is_binary(string) and is_boolean(emit) do def escape(string, emit \\ terminal?) when is_binary(string) and is_boolean(emit) do
{rendered, emitted} = do_escape(string, emit, false, nil, []) {rendered, emitted} = do_escape(string, emit, false, nil, [])
if emitted do if emitted do
@@ -164,27 +233,7 @@ defmodule IO.ANSI do
end end
end end
@doc ~S""" @doc false
Escapes a string by converting named ANSI sequences into actual ANSI codes.
The format for referring to sequences is `%{red}` and `%{red,bright}` (for
multiple sequences).
An optional boolean parameter can be passed to enable or disable
emitting actual ANSI codes. When `false`, no ANSI codes will emitted.
By default, standard output will be checked if it is a terminal capable
of handling these sequences (using `terminal?/1` function)
## Examples
iex> IO.ANSI.escape_fragment("Hello %{red,bright,green}yes", true)
"Hello \e[31m\e[1m\e[32myes"
iex> IO.ANSI.escape_fragment("%{reset}bye", true)
"\e[0mbye"
"""
@spec escape_fragment(String.t, emit :: boolean) :: String.t
def escape_fragment(string, emit \\ terminal?) when is_binary(string) and is_boolean(emit) 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, _emitted} = do_escape(string, emit, false, nil, [])
escaped escaped
+147 -32
View File
@@ -8,27 +8,29 @@ defmodule IO.ANSI.Docs do
The supported values are: The supported values are:
* `:enabled` - toggles coloring on and off (true) * `:enabled` - toggles coloring on and off (true)
* `:doc_code` - code blocks (cyan, bright) * `:doc_bold` - bold text (bright)
* `:doc_inline_code` - inline code (cyan) * `:doc_code` - code blocks (cyan, bright)
* `:doc_headings` - h1 and h2 headings (yellow, bright) * `:doc_headings` - h1 and h2 headings (yellow, bright)
* `:doc_title` - top level heading (reverse, yellow, bright) * `:doc_inline_code` - inline code (cyan)
* `:doc_bold` - bold text (bright) * `:doc_table_heading` - style for table headings
* `:doc_underline` - underlined text (underline) * `:doc_title` - top level heading (reverse, yellow, bright)
* `:width` - the width to format the text (80) * `:doc_underline` - underlined text (underline)
* `:width` - the width to format the text (80)
Values for the color settings are strings with Values for the color settings are strings with
comma-separated ANSI values. comma-separated ANSI values.
""" """
def default_options do def default_options do
[enabled: true, [enabled: true,
doc_code: "cyan,bright", doc_bold: [:bright],
doc_inline_code: "cyan", doc_code: [:cyan, :bright],
doc_headings: "yellow,bright", doc_headings: [:yellow, :bright],
doc_title: "reverse,yellow,bright", doc_inline_code: [:cyan],
doc_bold: "bright", doc_table_heading: [:reverse],
doc_underline: "underline", doc_title: [:reverse, :yellow, :bright],
width: 80] doc_underline: [:underline],
width: 80]
end end
@doc """ @doc """
@@ -43,12 +45,13 @@ defmodule IO.ANSI.Docs do
padding = div(width + String.length(heading), 2) padding = div(width + String.length(heading), 2)
heading = heading |> String.rjust(padding) |> String.ljust(width) heading = heading |> String.rjust(padding) |> String.ljust(width)
write(:doc_title, heading, options) write(:doc_title, heading, options)
newline_after_block
end end
@doc """ @doc """
Prints the documentation body. Prints the documentation body.
In addition to the priting string, takes a set of options In addition to the printing string, takes a set of options
defined in `default_options/1`. defined in `default_options/1`.
""" """
def print(doc, options \\ []) do def print(doc, options \\ []) do
@@ -84,13 +87,17 @@ defmodule IO.ANSI.Docs do
process_code(rest, [line], indent, options) process_code(rest, [line], indent, options)
end end
defp process([line | rest], indent, options) do defp process(all=[line | rest], indent, options) do
{stripped, count} = strip_spaces(line, 0) {stripped, count} = strip_spaces(line, 0)
case stripped do if is_table_line?(stripped) && length(rest) > 0 && is_table_line?(hd(rest)) do
<<bullet, ?\s, item :: binary >> when bullet in @bullets -> process_table(all, indent, options)
process_list(item, rest, count, indent, options) else
_ -> case stripped do
process_text(rest, [line], indent, false, options) <<bullet, ?\s, item :: binary >> when bullet in @bullets ->
process_list(item, rest, count, indent, options)
_ ->
process_text(rest, [line], indent, false, options)
end
end end
end end
@@ -110,11 +117,13 @@ defmodule IO.ANSI.Docs do
defp write_h2(heading, options) do defp write_h2(heading, options) do
write(:doc_headings, heading, options) write(:doc_headings, heading, options)
newline_after_block
end end
defp write_h3(heading, indent, options) do defp write_h3(heading, indent, options) do
IO.write(indent) IO.write(indent)
write(:doc_headings, heading, options) write(:doc_headings, heading, options)
newline_after_block
end end
## Lists ## Lists
@@ -194,7 +203,7 @@ defmodule IO.ANSI.Docs do
|> String.split(~r{\s}) |> String.split(~r{\s})
|> write_with_wrap(options[:width] - byte_size(indent), indent, from_list) |> write_with_wrap(options[:width] - byte_size(indent), indent, from_list)
unless from_list, do: IO.puts(IO.ANSI.reset) unless from_list, do: newline_after_block
end end
## Code blocks ## Code blocks
@@ -219,13 +228,109 @@ defmodule IO.ANSI.Docs do
defp write_code(code, indent, options) do defp write_code(code, indent, options) do
write(:doc_code, "#{indent}┃ #{Enum.join(Enum.reverse(code), "\n#{indent}┃ ")}", options) write(:doc_code, "#{indent}┃ #{Enum.join(Enum.reverse(code), "\n#{indent}┃ ")}", options)
newline_after_block
end
## Tables
defp process_table(lines, indent, options) do
{table, rest} = Enum.split_while(lines, &is_table_line?/1)
table_lines(table, options)
newline_after_block
process(rest, indent, options)
end
defp table_lines(lines, options) do
lines = Enum.map(lines, &split_into_columns(&1, options))
count = Enum.map(lines, &length/1) |> Enum.max
lines = Enum.map(lines, &pad_to_number_of_columns(&1, count))
widths = for line <- lines, do:
(for {_col, length} <- line, do: length)
col_widths = Enum.reduce(widths,
List.duplicate(0, count),
&max_column_widths/2)
render_table(lines, col_widths, options)
end
defp split_into_columns(line, options) do
line
|> String.strip(?|)
|> String.strip()
|> String.split(~r/\s\|\s/)
|> Enum.map(&render_column(&1, options))
end
defp render_column(col, options) do
col = col
|> String.replace(~r/\\ \|/x, "|")
|> handle_links
|> handle_inline(nil, [], [], options)
len = col
|> String.replace(~r/\e\[\d+m/, "")
|> String.length
{col, len}
end
defp pad_to_number_of_columns(cols, col_count),
do: cols ++ List.duplicate({"", 0}, col_count - length(cols))
defp max_column_widths(cols, widths),
do: Enum.zip(cols, widths) |> Enum.map(fn {a,b} -> max(a,b) end)
# If second line is heading separator, use the heading style on the first
defp render_table([first, second | rest], widths, options) do
combined = Enum.zip(first, widths)
if table_header?(second) do
draw_table_row(combined, options, :heading)
render_table(rest, widths, options)
else
draw_table_row(combined, options)
render_table([second | rest], widths, options)
end
end
defp render_table([first | rest], widths, options) do
combined = Enum.zip(first, widths)
draw_table_row(combined, options)
render_table(rest, widths, options)
end
defp render_table([], _, _),
do: nil
defp table_header?(row), do:
Enum.all?(row, fn {col, _} -> col =~ ~r/^:?-+:?$/ end)
defp draw_table_row(cols_and_widths, options, heading \\ false) do
columns =
Enum.map_join(cols_and_widths, " | ", fn {{col, length}, width} ->
col <> String.duplicate(" ", width - length)
end)
if heading do
write(:doc_table_heading, columns, options)
else
IO.puts columns
end
end end
## Helpers ## Helpers
@table_line_re ~r'''
( ^ \s{0,3} \| (?: [^|]+ \|)+ \s* $ )
|
(\s \| \s)
'''x
defp is_table_line?(line), do: Regex.match?(@table_line_re, line)
defp write(style, string, options) do defp write(style, string, options) do
IO.puts color(style, options) <> string <> IO.ANSI.reset IO.puts [color(style, options), string, IO.ANSI.reset]
IO.puts IO.ANSI.reset
end end
defp write_with_wrap([], _available, _indent, _first) do defp write_with_wrap([], _available, _indent, _first) do
@@ -363,14 +468,24 @@ defmodule IO.ANSI.Docs do
[color_for(h, options)|t] [color_for(h, options)|t]
end end
defp color_for("`", colors), do: color(:doc_inline_code, colors) defp color_for(mark, colors) do
defp color_for("_", colors), do: color(:doc_underline, colors) case mark do
defp color_for("*", colors), do: color(:doc_bold, colors) "`" -> color(:doc_inline_code, colors)
defp color_for("**", colors), do: color(:doc_bold, colors) "_" -> color(:doc_underline, colors)
"*" -> color(:doc_bold, colors)
"**" -> color(:doc_bold, colors)
end
end
defp color(style, colors) do defp color(style, colors) do
color = colors[style] color = colors[style]
enabled = colors[:enabled] if is_binary(color) do
IO.ANSI.escape_fragment("%{#{color}}", enabled) IO.puts :stderr, "warning: ANSI colors as strings is deprecated, " <>
"they now must be a list of atoms, got #{inspect color} for #{inspect style}"
color = String.split(color, ",") |> Enum.map(&String.to_atom/1)
end
IO.ANSI.format_fragment(color, colors[:enabled])
end end
defp newline_after_block, do: IO.puts(IO.ANSI.reset)
end end
+2
View File
@@ -22,6 +22,8 @@ defmodule IO.Stream do
defstruct device: nil, raw: true, line_or_bytes: :line defstruct device: nil, raw: true, line_or_bytes: :line
@type t :: %__MODULE__{}
@doc false @doc false
def __build__(device, raw, line_or_bytes) do def __build__(device, raw, line_or_bytes) do
%IO.Stream{device: device, raw: raw, line_or_bytes: line_or_bytes} %IO.Stream{device: device, raw: raw, line_or_bytes: line_or_bytes}
+236 -222
View File
@@ -150,9 +150,9 @@ defmodule Kernel do
3 3
""" """
@spec byte_size(bitstring) :: non_neg_integer @spec byte_size(binary) :: non_neg_integer
def byte_size(bitstring) do def byte_size(binary) do
:erlang.byte_size(bitstring) :erlang.byte_size(binary)
end end
@doc """ @doc """
@@ -183,9 +183,56 @@ defmodule Kernel do
## Examples ## Examples
When a process reaches its end, by default it exits with
reason `:normal`. You can also call it explicitly if you
want to terminate a process but not signal any failure:
exit(:normal) exit(:normal)
In case something goes wrong, you can also use `exit/1` with
a different reason:
exit(:seems_bad) exit(:seems_bad)
If the reason is not `:normal`, all linked process to the
exited process will crash (unless they are trapping exits).
## OTP exits
Exits are used by OTP to determine if a process exited abnormally
or not. The following exits are considered "normal":
* `exit(:normal)`
* `exit(:shutdown)`
* `exit({:shutdown, term})`
Exiting with any other reason is considered abnormal and treated
as a crash. This means the default supervisor behaviour kicks in,
error reports are emitted, etc.
This behaviour is relied on in many different places. For example,
`ExUnit` uses `exit(:shutdown)` when exiting the test process to
signal linked processes, supervision trees and so on to politely
shutdown too.
## CLI exits
Building on top of the exit signals mentioned above, if the
process started by the command line exits with any of the three
reasons above, its exit is considered normal and the Operating
System process will exit with status 0.
It is, however, possible to customize the Operating System exit
signal by invoking:
exit({:shutdown, integer})
This will cause the OS process to exit with the status given by
`integer` while signaling all linked OTP processes to politely
shutdown.
Any other exit reason will cause the OS process to exit with
status `1` and linked OTP processes to crash.
""" """
@spec exit(term) :: no_return @spec exit(term) :: no_return
def exit(reason) do def exit(reason) do
@@ -838,11 +885,6 @@ defmodule Kernel do
:erlang.--(left, right) :erlang.--(left, right)
end end
@doc false
def left xor right do
:erlang.xor(left, right)
end
@doc """ @doc """
Boolean not. Argument must be a boolean. Boolean not. Argument must be a boolean.
@@ -1372,14 +1414,14 @@ defmodule Kernel do
iex> inspect [1, 2, 3, 4, 5], limit: 3 iex> inspect [1, 2, 3, 4, 5], limit: 3
"[1, 2, 3, ...]" "[1, 2, 3, ...]"
iex> inspect("josé" <> <<0>>) iex> inspect("olá" <> <<0>>)
"<<106, 111, 115, 195, 169, 0>>" "<<111, 108, 195, 161, 0>>"
iex> inspect("josé" <> <<0>>, binaries: :as_strings) iex> inspect("olá" <> <<0>>, binaries: :as_strings)
"\"josé\\000\"" "\"olá\\0\""
iex> inspect("josé", binaries: :as_binaries) iex> inspect("olá", binaries: :as_binaries)
"<<106, 111, 115, 195, 169>>" "<<111, 108, 195, 161>>"
Note that the inspect protocol does not necessarily return a valid Note that the inspect protocol does not necessarily return a valid
representation of an Elixir term. In such cases, the inspected result representation of an Elixir term. In such cases, the inspected result
@@ -1396,7 +1438,9 @@ defmodule Kernel do
true -> opts.width true -> opts.width
false -> :infinity false -> :infinity
end end
Inspect.Algebra.pretty(Inspect.Algebra.to_doc(arg, opts), limit) IO.iodata_to_binary(
Inspect.Algebra.format(Inspect.Algebra.to_doc(arg, opts), limit)
)
end end
@doc """ @doc """
@@ -1415,18 +1459,18 @@ defmodule Kernel do
## Example ## Example
defmodule User do defmodule User do
defstruct name: "jose" defstruct name: "john"
end end
struct(User) struct(User)
#=> %User{name: "jose"} #=> %User{name: "john"}
opts = [name: "eric"] opts = [name: "meg"]
user = struct(User, opts) user = struct(User, opts)
#=> %User{name: "eric"} #=> %User{name: "meg"}
struct(user, unknown: "value") struct(user, unknown: "value")
#=> %User{name: "eric"} #=> %User{name: "meg"}
""" """
@spec struct(module | map, Enum.t) :: map @spec struct(module | map, Enum.t) :: map
@@ -1453,47 +1497,77 @@ defmodule Kernel do
Gets a value from a nested structure. Gets a value from a nested structure.
Uses the `Access` protocol to traverse the structures Uses the `Access` protocol to traverse the structures
according to the given `keys`. according to the given `keys`, unless the `key` is a
function.
If a key is a function, the function will be invoked
passing three arguments, the operation (`:get`), the
data to be accessed, and a function to be invoked next.
This means `get_in/2` can be extended to provide
custom lookups. The downside is that functions cannot be
stored as keys in the accessed data structures.
## Examples ## Examples
iex> users = %{"josé" => %{age: 27}, "eric" => %{age: 23}} iex> users = %{"john" => %{age: 27}, "meg" => %{age: 23}}
iex> get_in(users, ["josé", :age]) iex> get_in(users, ["john", :age])
27 27
In case any of entries in the middle returns `nil`, `nil` will be returned In case any of entries in the middle returns `nil`, `nil` will be returned
as per the Access protocol: as per the Access protocol:
iex> users = %{"josé" => %{age: 27}, "eric" => %{age: 23}} iex> users = %{"john" => %{age: 27}, "meg" => %{age: 23}}
iex> get_in(users, ["unknown", :age]) iex> get_in(users, ["unknown", :age])
nil nil
When one of the keys is a function, the function is invoked.
In the example below, we use a function to get all the maps
inside a list:
iex> users = [%{name: "john", age: 27}, %{name: "meg", age: 23}]
iex> all = fn :get, data, next -> Enum.map(data, next) end
iex> get_in(users, [all, :age])
[27, 23]
If the previous value before invoking the function is nil,
the function *will* receive nil as a value and must handle it
accordingly.
""" """
@spec get_in(Access.t, nonempty_list(term)) :: term @spec get_in(Access.t, nonempty_list(term)) :: term
def get_in(data, keys) def get_in(data, keys)
def get_in(nil, list) when is_list(list), do: nil
def get_in(data, [h]), do: Access.get(data, h) def get_in(data, [h]) when is_function(h),
def get_in(data, [h|t]), do: get_in(Access.get(data, h), t) do: h.(:get, data, &(&1))
def get_in(data, [h|t]) when is_function(h),
do: h.(:get, data, &get_in(&1, t))
def get_in(nil, [_]),
do: nil
def get_in(nil, [_|t]),
do: get_in(nil, t)
def get_in(data, [h]),
do: Access.get(data, h)
def get_in(data, [h|t]),
do: get_in(Access.get(data, h), t)
@doc """ @doc """
Puts a value in a nested structure. Puts a value in a nested structure.
Uses the `Access` protocol to traverse the structures Uses the `Access` protocol to traverse the structures
according to the given `keys`. according to the given `keys`, unless the `key` is a
function. If the key is a function, it will be invoked
as specified in `get_and_update_in/3`.
## Examples ## Examples
iex> users = %{"josé" => %{age: 27}, "eric" => %{age: 23}} iex> users = %{"john" => %{age: 27}, "meg" => %{age: 23}}
iex> put_in(users, ["josé", :age], 28) iex> put_in(users, ["john", :age], 28)
%{"josé" => %{age: 28}, "eric" => %{age: 23}} %{"john" => %{age: 28}, "meg" => %{age: 23}}
In case any of entries in the middle returns `nil`, a map is dynamically
created:
iex> users = %{"josé" => %{age: 27}, "eric" => %{age: 23}}
iex> put_in(users, ["dave", :age], 13)
%{"josé" => %{age: 27}, "eric" => %{age: 23}, "dave" => %{age: 13}}
In case any of entries in the middle returns `nil`,
an error will be raised when trying to access it next.
""" """
@spec put_in(Access.t, nonempty_list(term), term) :: Access.t @spec put_in(Access.t, nonempty_list(term), term) :: Access.t
def put_in(data, keys, value) do def put_in(data, keys, value) do
@@ -1504,21 +1578,18 @@ defmodule Kernel do
Updates a key in a nested structure. Updates a key in a nested structure.
Uses the `Access` protocol to traverse the structures Uses the `Access` protocol to traverse the structures
according to the given `keys`. according to the given `keys`, unless the `key` is a
function. If the key is a function, it will be invoked
as specified in `get_and_update_in/3`.
## Examples ## Examples
iex> users = %{"josé" => %{age: 27}, "eric" => %{age: 23}} iex> users = %{"john" => %{age: 27}, "meg" => %{age: 23}}
iex> update_in(users, ["josé", :age], &(&1 + 1)) iex> update_in(users, ["john", :age], &(&1 + 1))
%{"josé" => %{age: 28}, "eric" => %{age: 23}} %{"john" => %{age: 28}, "meg" => %{age: 23}}
In case any of entries in the middle returns `nil`, a map is dynamically
created:
iex> users = %{"josé" => %{age: 27}, "eric" => %{age: 23}}
iex> update_in(users, ["dave", :age], &((&1 || 0) + 1))
%{"josé" => %{age: 27}, "eric" => %{age: 23}, "dave" => %{age: 1}}
In case any of entries in the middle returns `nil`,
an error will be raised when trying to access it next.
""" """
@spec update_in(Access.t, nonempty_list(term), (term -> term)) :: Access.t @spec update_in(Access.t, nonempty_list(term), (term -> term)) :: Access.t
def update_in(data, keys, fun) do def update_in(data, keys, fun) do
@@ -1528,9 +1599,20 @@ defmodule Kernel do
@doc """ @doc """
Gets a value and updates a nested structure. Gets a value and updates a nested structure.
It expects a tuple to be returned, containing the value retrieved It expects a tuple to be returned, containing the value
and the update one. Uses the `Access` protocol to traverse the retrieved and the update one.
structures according to the given `keys`.
Uses the `Access` protocol to traverse the structures
according to the given `keys`, unless the `key` is a
function.
If a key is a function, the function will be invoked
passing three arguments, the operation (`:get_and_update`),
the data to be accessed, and a function to be invoked next.
This means `get_and_update_in/3` can be extended to provide
custom lookups. The downside is that functions cannot be stored
as keys in the accessed data structures.
## Examples ## Examples
@@ -1539,27 +1621,38 @@ defmodule Kernel do
update it at the same time. For example, it could be used to increase update it at the same time. For example, it could be used to increase
the age of a user by one and return the previous age in one pass: the age of a user by one and return the previous age in one pass:
iex> users = %{"josé" => %{age: 27}, "eric" => %{age: 23}} iex> users = %{"john" => %{age: 27}, "meg" => %{age: 23}}
iex> get_and_update_in(users, ["josé", :age], &{&1, &1 + 1}) iex> get_and_update_in(users, ["john", :age], &{&1, &1 + 1})
{27, %{"josé" => %{age: 28}, "eric" => %{age: 23}}} {27, %{"john" => %{age: 28}, "meg" => %{age: 23}}}
In case any of entries in the middle returns `nil`, a map is dynamically When one of the keys is a function, the function is invoked.
created: In the example below, we use a function to get and increment all
ages inside a list:
iex> users = %{"josé" => %{age: 27}, "eric" => %{age: 23}} iex> users = [%{name: "john", age: 27}, %{name: "meg", age: 23}]
iex> get_and_update_in(users, ["dave", :age], &{&1, 13}) iex> all = fn :get_and_update, data, next ->
{nil, %{"josé" => %{age: 27}, "eric" => %{age: 23}, "dave" => %{age: 13}}} ...> Enum.map(data, next) |> List.unzip() |> List.to_tuple()
...> end
iex> get_and_update_in(users, [all, :age], &{&1, &1 + 1})
{[27, 23], [%{name: "john", age: 28}, %{name: "meg", age: 24}]}
If the previous value before invoking the function is nil,
the function *will* receive `nil` as a value and must handle it
accordingly (be it by failing or providing a sane default).
""" """
@spec get_and_update_in(Access.t, nonempty_list(term), @spec get_and_update_in(Access.t, nonempty_list(term),
(term -> {get, term})) :: {get, Access.t} when get: var (term -> {get, term})) :: {get, Access.t} when get: var
def get_and_update_in(data, keys, fun) def get_and_update_in(data, keys, fun)
def get_and_update_in(nil, list, fun), do: get_and_update_in(%{}, list, fun) def get_and_update_in(data, [h], fun) when is_function(h),
def get_and_update_in(data, [h], fun), do: Access.get_and_update(data, h, fun) do: h.(:get_and_update, data, fun)
def get_and_update_in(data, [h|t], fun) do def get_and_update_in(data, [h|t], fun) when is_function(h),
Access.get_and_update(data, h, &get_and_update_in(&1, t, fun)) do: h.(:get_and_update, data, &get_and_update_in(&1, t, fun))
end
def get_and_update_in(data, [h], fun),
do: Access.get_and_update(data, h, fun)
def get_and_update_in(data, [h|t], fun),
do: Access.get_and_update(data, h, &get_and_update_in(&1, t, fun))
@doc """ @doc """
Puts a value in a nested structure via the given `path`. Puts a value in a nested structure via the given `path`.
@@ -1579,13 +1672,13 @@ defmodule Kernel do
## Examples ## Examples
iex> users = %{"josé" => %{age: 27}, "eric" => %{age: 23}} iex> users = %{"john" => %{age: 27}, "meg" => %{age: 23}}
iex> put_in(users["josé"][:age], 28) iex> put_in(users["john"][:age], 28)
%{"josé" => %{age: 28}, "eric" => %{age: 23}} %{"john" => %{age: 28}, "meg" => %{age: 23}}
iex> users = %{"josé" => %{age: 27}, "eric" => %{age: 23}} iex> users = %{"john" => %{age: 27}, "meg" => %{age: 23}}
iex> put_in(users["josé"].age, 28) iex> put_in(users["john"].age, 28)
%{"josé" => %{age: 28}, "eric" => %{age: 23}} %{"john" => %{age: 28}, "meg" => %{age: 23}}
""" """
defmacro put_in(path, value) do defmacro put_in(path, value) do
@@ -1612,13 +1705,13 @@ defmodule Kernel do
## Examples ## Examples
iex> users = %{"josé" => %{age: 27}, "eric" => %{age: 23}} iex> users = %{"john" => %{age: 27}, "meg" => %{age: 23}}
iex> update_in(users["josé"][:age], &(&1 + 1)) iex> update_in(users["john"][:age], &(&1 + 1))
%{"josé" => %{age: 28}, "eric" => %{age: 23}} %{"john" => %{age: 28}, "meg" => %{age: 23}}
iex> users = %{"josé" => %{age: 27}, "eric" => %{age: 23}} iex> users = %{"john" => %{age: 27}, "meg" => %{age: 23}}
iex> update_in(users["josé"].age, &(&1 + 1)) iex> update_in(users["john"].age, &(&1 + 1))
%{"josé" => %{age: 28}, "eric" => %{age: 23}} %{"john" => %{age: 28}, "meg" => %{age: 23}}
""" """
defmacro update_in(path, fun) do defmacro update_in(path, fun) do
@@ -1644,9 +1737,9 @@ defmodule Kernel do
## Examples ## Examples
iex> users = %{"josé" => %{age: 27}, "eric" => %{age: 23}} iex> users = %{"john" => %{age: 27}, "meg" => %{age: 23}}
iex> get_and_update_in(users["josé"][:age], &{&1, &1 + 1}) iex> get_and_update_in(users["john"].age, &{&1, &1 + 1})
{27, %{"josé" => %{age: 28}, "eric" => %{age: 23}}} {27, %{"john" => %{age: 28}, "meg" => %{age: 23}}}
## Paths ## Paths
@@ -1661,15 +1754,15 @@ defmodule Kernel do
Here are some valid paths: Here are some valid paths:
users["josé"][:age] users["john"][:age]
users["josé"].age users["john"].age
User.all["josé"].age User.all["john"].age
all_users()["josé"].age all_users()["john"].age
Here are some invalid ones: Here are some invalid ones:
# Does a remote call after the initial value # Does a remote call after the initial value
users["josé"].do_something(arg1, arg2) users["john"].do_something(arg1, arg2)
# Does not access any field # Does not access any field
users users
@@ -1690,7 +1783,7 @@ defmodule Kernel do
defp nest_get_and_update_in(h, [{:access, key}|t], fun) do defp nest_get_and_update_in(h, [{:access, key}|t], fun) do
quote do quote do
Access.get_and_update( Access.get_and_update(
case(unquote(h), do: (nil -> %{}; o -> o)), unquote(h),
unquote(key), unquote(key),
unquote(nest_get_and_update_in(t, fun)) unquote(nest_get_and_update_in(t, fun))
) )
@@ -1714,7 +1807,7 @@ defmodule Kernel do
defp unnest(other, [], kind) do defp unnest(other, [], kind) do
raise ArgumentError, raise ArgumentError,
"expected expression given to #{kind} to access at least one field, got: #{Macro.to_string other}" "expected expression given to #{kind} to access at least one element, got: #{Macro.to_string other}"
end end
defp unnest(other, acc, kind) do defp unnest(other, acc, kind) do
@@ -1723,7 +1816,7 @@ defmodule Kernel do
false -> false ->
raise ArgumentError, raise ArgumentError,
"expression given to #{kind} must start with a variable, local or remote call " <> "expression given to #{kind} must start with a variable, local or remote call " <>
"and be followed by field access, got: #{Macro.to_string other}" "and be followed by an element access, got: #{Macro.to_string other}"
end end
end end
@@ -1894,10 +1987,7 @@ defmodule Kernel do
case is_list(args) and length(args) == 1 and typespec(name) do case is_list(args) and length(args) == 1 and typespec(name) do
false -> false ->
case name == :typedoc and not bootstraped?(Kernel.Typespec) do do_at(args, name, function?, __CALLER__)
true -> nil
false -> do_at(args, name, function?, __CALLER__)
end
macro -> macro ->
case bootstraped?(Kernel.Typespec) do case bootstraped?(Kernel.Typespec) do
false -> nil false -> nil
@@ -1964,8 +2054,9 @@ defmodule Kernel do
defp typespec(_), do: false defp typespec(_), do: false
@doc """ @doc """
Returns the binding as a keyword list where the variable name Returns the binding for the given context as a keyword list.
is the key and the variable value is the value.
The variable name is the key and the variable value is the value.
## Examples ## Examples
@@ -1976,59 +2067,18 @@ defmodule Kernel do
iex> binding() iex> binding()
[x: 2] [x: 2]
""" iex> binding(:foo)
defmacro binding() do
do_binding(nil, nil, __CALLER__.vars, Macro.Env.in_match?(__CALLER__))
end
@doc """
Receives a list of atoms at compilation time and returns the
binding of the given variables as a keyword list where the
variable name is the key and the variable value is the value.
In case a variable in the list does not exist in the binding,
it is not included in the returned result.
## Examples
iex> x = 1
iex> binding([:x, :y])
[x: 1]
"""
defmacro binding(list) when is_list(list) do
do_binding(list, nil, __CALLER__.vars, Macro.Env.in_match?(__CALLER__))
end
defmacro binding(context) when is_atom(context) do
do_binding(nil, context, __CALLER__.vars, Macro.Env.in_match?(__CALLER__))
end
@doc """
Receives a list of atoms at compilation time and returns the
binding of the given variables in the given context as a keyword
list where the variable name is the key and the variable value
is the value.
In case a variable in the list does not exist in the binding,
it is not included in the returned result.
## Examples
iex> var!(x, :foo) = 1
iex> binding([:x, :y])
[] []
iex> binding([:x, :y], :foo) iex> var!(x, :foo) = 1
1
iex> binding(:foo)
[x: 1] [x: 1]
""" """
defmacro binding(list, context) when is_list(list) and is_atom(context) do defmacro binding(context \\ nil) do
do_binding(list, context, __CALLER__.vars, Macro.Env.in_match?(__CALLER__)) in_match? = Macro.Env.in_match?(__CALLER__)
end for {v, c} <- __CALLER__.vars, c == context do
{v, wrap_binding(in_match?, {v, [], c})}
defp do_binding(list, context, vars, in_match) do
for {v, c} <- vars, c == context, list == nil or :lists.member(v, list) do
{v, wrap_binding(in_match, {v, [], c})}
end end
end end
@@ -2561,7 +2611,7 @@ defmodule Kernel do
# Generate the alias for this module definition # Generate the alias for this module definition
{new, old} = module_nesting(env.module, full) {new, old} = module_nesting(env.module, full)
meta = [defined: full, context: true] ++ alias_meta(alias) meta = [defined: full, context: env.module] ++ alias_meta(alias)
{full, {:alias, meta, [old, [as: new, warn: false]]}} {full, {:alias, meta, [old, [as: new, warn: false]]}}
false -> false ->
@@ -2819,28 +2869,31 @@ defmodule Kernel do
## Types ## Types
`defstruct` automatically generates a type `t` unless one exists. It is recommended to define types for structs, by convention this type
The following definition: is called `t`. To define a struct in a type the struct literal syntax
is used:
defmodule User do defmodule User do
defstruct name: "José" :: String.t, defstruct name: "john", age: 25
age: 25 :: integer @type t :: %User{name: String.t, age: integer}
end end
Generates a type as follows: It is recommended to only use the struct syntax when defining the struct's
type. When referring to another struct use `User.t`, not `%User{}`. Fields
in the struct not included in the type defaults to `term`.
@type t :: %User{name: String.t, age: integer} Private structs that are not used outside its module should use the private
type attribute `@typep`. Public structs whose internal structure is private
In case a struct does not declare a field type, it defaults to `term`. to the local module (you are not allowed to pattern match it or directly
access fields) should use the `@opaque` attribute. Structs whose internal
structure is public should use `@type`.
""" """
defmacro defstruct(kv) do defmacro defstruct(fields) do
{fields, types} = split_fields_and_types(kv)
fields = fields =
quote bind_quoted: [fields: fields] do quote bind_quoted: [fields: fields] do
fields = :lists.map(fn fields = :lists.map(fn
{ key, _ } = pair when is_atom(key) -> pair {key, _} = pair when is_atom(key) -> pair
key when is_atom(key) -> { key, nil } key when is_atom(key) -> {key, nil}
other -> raise ArgumentError, "struct field names must be atoms, got: #{inspect other}" other -> raise ArgumentError, "struct field names must be atoms, got: #{inspect other}"
end, fields) end, fields)
@@ -2853,63 +2906,18 @@ defmodule Kernel do
Protocol.__derive__(derive, __MODULE__, __ENV__) Protocol.__derive__(derive, __MODULE__, __ENV__)
end end
@spec __struct__() :: t @spec __struct__() :: %__MODULE__{}
def __struct__() do def __struct__() do
@struct @struct
end end
end end
types =
case bootstraped?(Kernel.Typespec) do
true when types == [] ->
quote unquote: false do
unless Kernel.Typespec.defines_type?(__MODULE__, :t, 0) do
types = :lists.map(fn {key, _} ->
{key, quote(do: term)}
end, fields)
@type t :: %{unquote_splicing(types), __struct__: __MODULE__}
end
end
true ->
quote do
unless Kernel.Typespec.defines_type?(__MODULE__, :t, 0) do
@type t :: %{unquote_splicing(types), __struct__: __MODULE__}
end
end
false ->
nil
end
quote do quote do
unquote(fields) unquote(fields)
unquote(types)
fields fields
end end
end end
defp split_fields_and_types(kv) do
case Keyword.keyword?(kv) do
true -> split_fields_and_types(kv, [], [])
false -> {kv, []}
end
end
defp split_fields_and_types([{field, {:::, _, [default, type]}}|t], fields, types) do
split_fields_and_types(t, [{field, default}|fields], [{field, type}|types])
end
defp split_fields_and_types([{field, default}|t], fields, types) do
split_fields_and_types(t, [{field, default}|fields], [{field, quote(do: term)}|types])
end
defp split_fields_and_types([field|t], fields, types) do
split_fields_and_types(t, [field|fields], [{field, quote(do: term)}|types])
end
defp split_fields_and_types([], fields, types) do
{:lists.reverse(fields), :lists.reverse(types)}
end
@doc ~S""" @doc ~S"""
Defines an exception. Defines an exception.
@@ -2970,7 +2978,7 @@ defmodule Kernel do
@behaviour Exception @behaviour Exception
fields = defstruct unquote(fields) fields = defstruct unquote(fields)
@spec exception(term) :: t @spec exception(Keyword.t) :: Exception.t
def exception(args) when is_list(args) do def exception(args) when is_list(args) do
Kernel.struct(__struct__, args) Kernel.struct(__struct__, args)
end end
@@ -2978,7 +2986,7 @@ defmodule Kernel do
defoverridable exception: 1 defoverridable exception: 1
if Keyword.has_key?(fields, :message) do if Keyword.has_key?(fields, :message) do
@spec message(t) :: String.t @spec message(Exception.t) :: String.t
def message(exception) do def message(exception) do
exception.message exception.message
end end
@@ -3309,7 +3317,7 @@ defmodule Kernel do
## Sigils ## Sigils
@doc """ @doc ~S"""
Handles the sigil ~S. It simply returns a string Handles the sigil ~S. It simply returns a string
without escaping characters and without interpolations. without escaping characters and without interpolations.
@@ -3318,15 +3326,15 @@ defmodule Kernel do
iex> ~S(foo) iex> ~S(foo)
"foo" "foo"
iex> ~S(f\#{o}o) iex> ~S(f#{o}o)
"f\\\#{o}o" "f\#{o}o"
""" """
defmacro sigil_S(string, []) do defmacro sigil_S(string, []) do
string string
end end
@doc """ @doc ~S"""
Handles the sigil ~s. It returns a string as if it was double quoted Handles the sigil ~s. It returns a string as if it was double quoted
string, unescaping characters and replacing interpolations. string, unescaping characters and replacing interpolations.
@@ -3335,15 +3343,18 @@ defmodule Kernel do
iex> ~s(foo) iex> ~s(foo)
"foo" "foo"
iex> ~s(f\#{:o}o) iex> ~s(f#{:o}o)
"foo" "foo"
iex> ~s(f\#{:o}o)
"f\#{:o}o"
""" """
defmacro sigil_s({:<<>>, line, pieces}, []) do defmacro sigil_s({:<<>>, line, pieces}, []) do
{:<<>>, line, Macro.unescape_tokens(pieces)} {:<<>>, line, Macro.unescape_tokens(pieces)}
end end
@doc """ @doc ~S"""
Handles the sigil ~C. It simply returns a char list Handles the sigil ~C. It simply returns a char list
without escaping characters and without interpolations. without escaping characters and without interpolations.
@@ -3352,15 +3363,15 @@ defmodule Kernel do
iex> ~C(foo) iex> ~C(foo)
'foo' 'foo'
iex> ~C(f\#{o}o) iex> ~C(f#{o}o)
'f\\\#{o}o' 'f\#{o}o'
""" """
defmacro sigil_C({:<<>>, _line, [string]}, []) when is_binary(string) do defmacro sigil_C({:<<>>, _line, [string]}, []) when is_binary(string) do
String.to_char_list(string) String.to_char_list(string)
end end
@doc """ @doc ~S"""
Handles the sigil ~c. It returns a char list as if it were a single Handles the sigil ~c. It returns a char list as if it were a single
quoted string, unescaping characters and replacing interpolations. quoted string, unescaping characters and replacing interpolations.
@@ -3369,9 +3380,12 @@ defmodule Kernel do
iex> ~c(foo) iex> ~c(foo)
'foo' 'foo'
iex> ~c(f\#{:o}o) iex> ~c(f#{:o}o)
'foo' 'foo'
iex> ~c(f\#{:o}o)
'f\#{:o}o'
""" """
# We can skip the runtime conversion if we are # We can skip the runtime conversion if we are
@@ -3405,13 +3419,13 @@ defmodule Kernel do
quote do: Regex.compile!(unquote(binary), unquote(:binary.list_to_bin(options))) quote do: Regex.compile!(unquote(binary), unquote(:binary.list_to_bin(options)))
end end
@doc """ @doc ~S"""
Handles the sigil ~R. It returns a Regex pattern without escaping Handles the sigil ~R. It returns a Regex pattern without escaping
nor interpreting interpolations. nor interpreting interpolations.
## Examples ## Examples
iex> Regex.match?(~R(f\#{1,3}o), "f\#o") iex> Regex.match?(~R(f#{1,3}o), "f#o")
true true
""" """
@@ -3420,7 +3434,7 @@ defmodule Kernel do
Macro.escape(regex) Macro.escape(regex)
end end
@doc """ @doc ~S"""
Handles the sigil ~w. It returns a list of "words" split by whitespace. Handles the sigil ~w. It returns a list of "words" split by whitespace.
## Modifiers ## Modifiers
@@ -3431,7 +3445,7 @@ defmodule Kernel do
## Examples ## Examples
iex> ~w(foo \#{:bar} baz) iex> ~w(foo #{:bar} baz)
["foo", "bar", "baz"] ["foo", "bar", "baz"]
iex> ~w(--source test/enum_test.exs) iex> ~w(--source test/enum_test.exs)
@@ -3451,7 +3465,7 @@ defmodule Kernel do
split_words(binary, modifiers) split_words(binary, modifiers)
end end
@doc """ @doc ~S"""
Handles the sigil ~W. It returns a list of "words" split by whitespace Handles the sigil ~W. It returns a list of "words" split by whitespace
without escaping nor interpreting interpolations. without escaping nor interpreting interpolations.
@@ -3463,8 +3477,8 @@ defmodule Kernel do
## Examples ## Examples
iex> ~W(foo \#{bar} baz) iex> ~W(foo #{bar} baz)
["foo", "\\\#{bar}", "baz"] ["foo", "\#{bar}", "baz"]
""" """
defmacro sigil_W({:<<>>, _line, [string]}, modifiers) when is_binary(string) do defmacro sigil_W({:<<>>, _line, [string]}, modifiers) when is_binary(string) do
+61 -49
View File
@@ -3,7 +3,7 @@ defmodule Kernel.CLI do
@blank_config %{commands: [], output: ".", compile: [], @blank_config %{commands: [], output: ".", compile: [],
halt: true, compiler_options: [], errors: [], halt: true, compiler_options: [], errors: [],
verbose_compile: false} pa: [], pz: [], verbose_compile: false}
@doc """ @doc """
This is the API invoked by Elixir boot process. This is the API invoked by Elixir boot process.
@@ -12,9 +12,10 @@ defmodule Kernel.CLI do
argv = for arg <- argv, do: IO.chardata_to_string(arg) argv = for arg <- argv, do: IO.chardata_to_string(arg)
{config, argv} = parse_argv(argv) {config, argv} = parse_argv(argv)
:elixir_code_server.cast({:paths, config.pa, config.pz})
System.argv(argv) System.argv(argv)
run fn -> run fn _ ->
errors = process_commands(config) errors = process_commands(config)
if errors != [] do if errors != [] do
@@ -33,37 +34,19 @@ defmodule Kernel.CLI do
by escripts generated by Elixir. by escripts generated by Elixir.
""" """
def run(fun, halt \\ true) do def run(fun, halt \\ true) do
try do res = exec_fun(fun, {:ok, 0})
fun.() if elem(res, 0) == :shutdown or halt do
if halt do {_, int} = at_exit(res)
at_exit(0) System.halt(int)
System.halt(0)
end
catch
:exit, reason when is_integer(reason) ->
at_exit(reason)
System.halt(reason)
:exit, :normal ->
at_exit(0)
System.halt(0)
kind, reason ->
at_exit(1)
print_error(kind, reason, System.stacktrace)
System.halt(1)
end end
end end
@doc """ @doc false
Parses ARGV returning the CLI config and trailing args.
"""
def parse_argv(argv) do def parse_argv(argv) do
parse_argv(argv, @blank_config) parse_argv(argv, @blank_config)
end end
@doc """ @doc false
Process commands according to the parsed config from `parse_argv/1`.
Returns all errors.
"""
def process_commands(config) do def process_commands(config) do
results = Enum.map(Enum.reverse(config.commands), &process_command(&1, config)) results = Enum.map(Enum.reverse(config.commands), &process_command(&1, config))
errors = for {:error, msg} <- results, do: msg errors = for {:error, msg} <- results, do: msg
@@ -72,21 +55,48 @@ defmodule Kernel.CLI do
## Helpers ## Helpers
defp at_exit(status) do defp at_exit(res) do
hooks = :elixir_code_server.call(:flush_at_exit) hooks = :elixir_code_server.call(:flush_at_exit)
res = Enum.reduce(hooks, res, &exec_fun/2)
if hooks == [], do: res, else: at_exit(res)
end
for hook <- hooks do defp exec_fun(fun, res) when is_function(fun, 1) and is_tuple(res) do
try do parent = self()
hook.(status)
catch {pid, ref} =
kind, reason -> spawn_monitor fn ->
print_error(kind, reason, System.stacktrace) try do
fun.(elem(res, 1))
catch
:exit, {:shutdown, int} when is_integer(int) ->
send parent, {self, {:shutdown, int}}
exit({:shutdown, int})
:exit, reason
when reason == :normal
when reason == :shutdown
when tuple_size(reason) == 2 and elem(reason, 0) == :shutdown ->
send parent, {self, {:shutdown, 0}}
exit(reason)
kind, reason ->
stack = System.stacktrace
print_error(kind, reason, stack)
send parent, {self, {:shutdown, 1}}
:erlang.raise(kind, reason, stack)
else
_ ->
send parent, {self, res}
end
end end
end
# If an at_exit callback adds a receive do
# new hook we need to invoke it. {^pid, res} ->
unless hooks == [], do: at_exit(status) :erlang.demonitor(ref, [:flush])
res
{:DOWN, ^ref, _, _, other} ->
print_error({:EXIT, pid}, other, [])
{:shutdown, 1}
end
end end
defp shared_option?(list, config, callback) do defp shared_option?(list, config, callback) do
@@ -129,13 +139,15 @@ defmodule Kernel.CLI do
end end
defp parse_shared(["-pa", h|t], config) do defp parse_shared(["-pa", h|t], config) do
add_code_path(h, &Code.prepend_path/1) paths = expand_code_path(h)
parse_shared t, config Enum.each(paths, &:code.add_patha/1)
parse_shared t, %{config | pa: config.pa ++ paths}
end end
defp parse_shared(["-pz", h|t], config) do defp parse_shared(["-pz", h|t], config) do
add_code_path(h, &Code.append_path/1) paths = expand_code_path(h)
parse_shared t, config Enum.each(paths, &:code.add_pathz/1)
parse_shared t, %{config | pz: config.pz ++ paths}
end end
defp parse_shared(["--app", h|t], config) do defp parse_shared(["--app", h|t], config) do
@@ -162,7 +174,7 @@ defmodule Kernel.CLI do
parse_shared t, config parse_shared t, config
end end
defp parse_shared([erl|t], config) when erl in ["--detached", "--hidden"] do defp parse_shared([erl|t], config) when erl in ["--detached", "--hidden", "--werl"] do
parse_shared t, config parse_shared t, config
end end
@@ -170,12 +182,11 @@ defmodule Kernel.CLI do
{list, config} {list, config}
end end
defp expand_code_path(path) do
defp add_code_path(path, fun) do
path = Path.expand(path) path = Path.expand(path)
case Path.wildcard(path) do case Path.wildcard(path) do
[] -> fun.(path) [] -> [to_char_list(path)]
list -> Enum.each(list, fun) list -> Enum.map(list, &to_char_list/1)
end end
end end
@@ -352,7 +363,8 @@ defmodule Kernel.CLI do
end end
defp process_command({:compile, patterns}, config) do defp process_command({:compile, patterns}, config) do
:filelib.ensure_dir(:filename.join(config.output, ".")) # If ensuring the dir returns an error no files will be found.
_ = :filelib.ensure_dir(:filename.join(config.output, "."))
case filter_multiple_patterns(patterns) do case filter_multiple_patterns(patterns) do
{:ok, []} -> {:ok, []} ->
@@ -407,8 +419,8 @@ defmodule Kernel.CLI do
# the actual Elixir executable. # the actual Elixir executable.
case :os.type() do case :os.type() do
{:win32, _} -> {:win32, _} ->
exec = Path.rootname(exec) base = Path.rootname(exec)
if File.regular?(exec), do: exec if File.regular?(base), do: base, else: exec
_ -> _ ->
exec exec
end end
+2 -2
View File
@@ -25,11 +25,11 @@ defmodule Kernel.ErrorHandler do
defp ensure_loaded(module) do defp ensure_loaded(module) do
case Code.ensure_loaded(module) do case Code.ensure_loaded(module) do
{:module, _} -> [] {:module, _} -> :ok
{:error, _} -> {:error, _} ->
parent = :erlang.get(:elixir_compiler_pid) parent = :erlang.get(:elixir_compiler_pid)
ref = :erlang.make_ref ref = :erlang.make_ref
send parent, {:waiting, module, self(), ref, module} send parent, {:waiting, :module, self(), ref, module}
:erlang.garbage_collect(self) :erlang.garbage_collect(self)
receive do receive do
{^ref, :ready} -> :ok {^ref, :ready} -> :ok
+3
View File
@@ -101,6 +101,7 @@ defmodule Kernel.LexicalTracker do
{:ok, :ets.new(:lexical, [:protected])} {:ok, :ets.new(:lexical, [:protected])}
end end
@doc false
def handle_call(:ets, _from, d) do def handle_call(:ets, _from, d) do
{:reply, d, d} {:reply, d, d}
end end
@@ -146,10 +147,12 @@ defmodule Kernel.LexicalTracker do
{:noreply, d} {:noreply, d}
end end
@doc false
def terminate(_reason, _d) do def terminate(_reason, _d) do
:ok :ok
end end
@doc false
def code_change(_old, d, _extra) do def code_change(_old, d, _extra) do
{:ok, d} {:ok, d}
end end
+4 -4
View File
@@ -43,7 +43,7 @@ defmodule Kernel.ParallelCompiler do
end end
defp spawn_compilers(files, path, callbacks) do defp spawn_compilers(files, path, callbacks) do
Code.ensure_loaded(Kernel.ErrorHandler) true = Code.ensure_loaded?(Kernel.ErrorHandler)
compiler_pid = self() compiler_pid = self()
:elixir_code_server.cast({:reset_warnings, compiler_pid}) :elixir_code_server.cast({:reset_warnings, compiler_pid})
schedulers = max(:erlang.system_info(:schedulers_online), 2) schedulers = max(:erlang.system_info(:schedulers_online), 2)
@@ -54,7 +54,7 @@ defmodule Kernel.ParallelCompiler do
# compilation status will be set to error and we fail with CompileError # compilation status will be set to error and we fail with CompileError
case :elixir_code_server.call({:compilation_status, compiler_pid}) do case :elixir_code_server.call({:compilation_status, compiler_pid}) do
:ok -> result :ok -> result
:error -> exit(1) :error -> exit({:shutdown, 1})
end end
end end
@@ -87,7 +87,7 @@ defmodule Kernel.ParallelCompiler do
:erlang.process_flag(:error_handler, Kernel.ErrorHandler) :erlang.process_flag(:error_handler, Kernel.ErrorHandler)
exit(try do exit(try do
if output do _ = if output do
:elixir_compiler.file_to_path(h, output) :elixir_compiler.file_to_path(h, output)
else else
:elixir_compiler.file(h) :elixir_compiler.file(h)
@@ -186,7 +186,7 @@ defmodule Kernel.ParallelCompiler do
if all_missing?(entries, waiting, queued) do if all_missing?(entries, waiting, queued) do
collect_failures(queued, length(queued) - 1) collect_failures(queued, length(queued) - 1)
end end
exit(1) exit({:shutdown, 1})
end end
end end
+21 -18
View File
@@ -114,7 +114,7 @@ defmodule Kernel.SpecialForms do
defmodule User do defmodule User do
def __struct__ do def __struct__ do
%{name: "josé", age: 27} %{name: "john", age: 27}
end end
end end
@@ -122,7 +122,7 @@ defmodule Kernel.SpecialForms do
`Kernel.defstruct/1` macro: `Kernel.defstruct/1` macro:
defmodule User do defmodule User do
defstruct name: "josé", age: 27 defstruct name: "john", age: 27
end end
Now a struct can be created as follows: Now a struct can be created as follows:
@@ -132,13 +132,13 @@ defmodule Kernel.SpecialForms do
Underneath a struct is just a map with a `__struct__` field Underneath a struct is just a map with a `__struct__` field
pointing to the `User` module: pointing to the `User` module:
%User{} == %{__struct__: User, name: "josé", age: 27} %User{} == %{__struct__: User, name: "john", age: 27}
A struct also validates that the given keys are part of the defined A struct also validates that the given keys are part of the defined
struct. The example below will fail because there is no key struct. The example below will fail because there is no key
`:full_name` in the `User` struct: `:full_name` in the `User` struct:
%User{full_name: "José Valim"} %User{full_name: "john doe"}
Note that a struct specifies a minimum set of keys required Note that a struct specifies a minimum set of keys required
for operations. Other keys can be added to structs via the for operations. Other keys can be added to structs via the
@@ -242,14 +242,14 @@ defmodule Kernel.SpecialForms do
Here, `val` is interpreted as unsigned. Here, `val` is interpreted as unsigned.
The endianness of a segment can be big, little or native (the The endianness of a segment can be big, little or native (the
latter meaning it will be resolved at VM load time). Passing latter meaning it will be resolved at VM load time). Many options
many options can be done by giving a list: can be given by using `-` as separator:
<<102 :: [integer, native], rest :: binary>> <<102 :: integer-native, rest :: binary>>
Or: Or:
<<102 :: [unsigned, big, integer], rest :: binary>> <<102 :: unsigned-big-integer, rest :: binary>>
And so on. And so on.
@@ -271,7 +271,7 @@ defmodule Kernel.SpecialForms do
We can also match by specifying size and unit explicitly: We can also match by specifying size and unit explicitly:
iex> <<102, _rest :: [size(2), unit(8)]>> = "foo" iex> <<102, _rest :: size(2)-unit(8)>> = "foo"
"foo" "foo"
However, if we expect a size of 32, it won't match: However, if we expect a size of 32, it won't match:
@@ -286,12 +286,15 @@ defmodule Kernel.SpecialForms do
in a binary match can use the default size (all others must in a binary match can use the default size (all others must
have their size specified explicitly). have their size specified explicitly).
Size can also be specified using a syntax shortcut. Instead of Size and unit can also be specified using a syntax shortcut
writing `size(8)`, one can write just `8` and it will be interpreted when passing integer values:
as `size(8)`
iex> << 1 :: 3 >> == << 1 :: size(3) >> << x :: 8 >> == << x :: size(8) >>
true << x :: 8 * 4 >> == << x :: size(8)-unit(4) >>
<< x :: _ * 4 >> == << x :: unit(4) >>
This syntax reflects the fact the effective size is given by
multiplying the size by the unit.
For floats, `size * unit` must result in 32 or 64, corresponding For floats, `size * unit` must result in 32 or 64, corresponding
to binary32 and binary64, respectively. to binary32 and binary64, respectively.
@@ -1171,10 +1174,10 @@ defmodule Kernel.SpecialForms do
Note generators can also be used to filter as it removes any value Note generators can also be used to filter as it removes any value
that doesn't match the left side of `<-`: that doesn't match the left side of `<-`:
iex> for {:user, name} <- [user: "jose", admin: "john", user: "eric"] do iex> for {:user, name} <- [user: "john", admin: "john", user: "meg"] do
...> String.upcase(name) ...> String.upcase(name)
...> end ...> end
["JOSE", "ERIC"] ["JOHN", "MEG"]
Bitstring generators are also supported and are very useful when you Bitstring generators are also supported and are very useful when you
need to organize bitstring streams: need to organize bitstring streams:
@@ -1513,9 +1516,9 @@ defmodule Kernel.SpecialForms do
The catch clause can be used to catch throws values and exits. The catch clause can be used to catch throws values and exits.
try do try do
exit(1) exit(:shutdown)
catch catch
:exit, 1 -> IO.puts "Exited with 1" :exit, :shutdown -> IO.puts "Exited with shutdown reason"
end end
try do try do
+137 -46
View File
@@ -1,5 +1,5 @@
defmodule Kernel.Typespec do defmodule Kernel.Typespec do
@moduledoc """ @moduledoc ~S"""
Provides macros and functions for working with typespecs. Provides macros and functions for working with typespecs.
Elixir comes with a notation for declaring types and specifications. Elixir is Elixir comes with a notation for declaring types and specifications. Elixir is
@@ -38,6 +38,7 @@ defmodule Kernel.Typespec do
| Fun | Fun
| Integer | Integer
| List | List
| Map
| Tuple | Tuple
| Union | Union
| UserDefined # Described in section "Defining a type" | UserDefined # Described in section "Defining a type"
@@ -65,16 +66,33 @@ defmodule Kernel.Typespec do
| nonempty_list(Type) # proper non-empty list | nonempty_list(Type) # proper non-empty list
| [] # empty list | [] # empty list
| [Type] # shorthand for list(Type) | [Type] # shorthand for list(Type)
| [...] # shorthand for nonempty_list()
| [Type, ...] # shorthand for nonempty_list(Type) | [Type, ...] # shorthand for nonempty_list(Type)
| [Keyword]
Tuple :: tuple # a tuple of any size Map :: map() # map of any size
| {} # empty tuple | %{} # map of any size
| %Struct{} # struct (see defstruct/1)
| %Struct{Keyword}
| %{Keyword}
| %{Pairs}
Tuple :: tuple # a tuple of any size
| {} # empty tuple
| {TList} | {TList}
| record(Atom) # record (see Record)
| record(Atom, Keyword)
Keyword :: ElixirAtom: Type
| ElixirAtom: Type, Keyword
Pairs :: Type => Type
| Type => Type, Pairs
TList :: Type TList :: Type
| Type, TList | Type, TList
Union :: Type1 | Type2 Union :: Type | Type
### Bit strings ### Bit strings
@@ -104,36 +122,38 @@ defmodule Kernel.Typespec do
## Built-in types ## Built-in types
Built-in type | Defined as Built-in type | Defined as
:-------------------- | :--------- :-------------------- | :---------
`term` | `any` `term` | `any`
`binary` | `<< _ :: _ * 8 >>` `binary` | `<< _ :: _ * 8 >>`
`bitstring` | `<< _ :: _ * 1 >>` `bitstring` | `<< _ :: _ * 1 >>`
`boolean` | `false` &#124; `true` `boolean` | `false` \| `true`
`byte` | `0..255` `byte` | `0..255`
`char` | `0..0xffff` `char` | `0..0x10ffff`
`number` | `integer` &#124; `float` `number` | `integer` \| `float`
`list` | `[any]` `char_list` | `[char]`
`maybe_improper_list` | `maybe_improper_list(any, any)` `list` | `[any]`
`nonempty_list` | `nonempty_list(any)` `maybe_improper_list` | `maybe_improper_list(any, any)`
`iodata` | `iolist` &#124; `binary` `nonempty_list` | `nonempty_list(any)`
`iolist` | `maybe_improper_list(byte` &#124; `binary` &#124; `iolist, binary` &#124; `[])` `iodata` | `iolist` \| `binary`
`module` | `atom` `iolist` | `maybe_improper_list(byte` \| `binary` \| `iolist, binary` \| `[])`
`mfa` | `{atom, atom, arity}` `module` | `atom` \| `tuple`
`arity` | `0..255` `mfa` | `{atom, atom, arity}`
`node` | `atom` `arity` | `0..255`
`timeout` | `:infinity` &#124; `non_neg_integer` `node` | `atom`
`no_return` | `none` `timeout` | `:infinity` \| `non_neg_integer`
`fun` | `(... -> any)` `no_return` | `none`
`fun` | `(... -> any)`
Some built-in types cannot be expressed with valid syntax according to the Some built-in types cannot be expressed with valid syntax according to the
language defined above. language defined above.
Built-in type | Can be interpreted as Built-in type | Can be interpreted as
:---------------- | :-------------------- :---------------- | :--------------------
`non_neg_integer` | `0..` `non_neg_integer` | `0..`
`pos_integer` | `1..` `pos_integer` | `1..`
`neg_integer` | `..-1` `neg_integer` | `..-1`
Types defined in other modules are referred to as "remote types", they are Types defined in other modules are referred to as "remote types", they are
referenced as `Module.type_name` (ex. `Enum.t` or `String.t`). referenced as `Module.type_name` (ex. `Enum.t` or `String.t`).
@@ -488,9 +508,9 @@ defmodule Kernel.Typespec do
do: type_to_signature(other) do: type_to_signature(other)
@doc false @doc false
def type_to_signature({:::, _, [{name, _, nil}, _]}), def type_to_signature({:::, _, [{name, _, context}, _]}) when is_atom(name) and is_atom(context),
do: {name, 0} do: {name, 0}
def type_to_signature({:::, _, [{name, _, args}, _]}), def type_to_signature({:::, _, [{name, _, args}, _]}) when is_atom(name),
do: {name, length(args)} do: {name, length(args)}
## Macro callbacks ## Macro callbacks
@@ -536,7 +556,7 @@ defmodule Kernel.Typespec do
if not export and doc do if not export and doc do
:elixir_errors.warn(caller.line, caller.file, "type #{name}/#{arity} is private, " <> :elixir_errors.warn(caller.line, caller.file, "type #{name}/#{arity} is private, " <>
"@typedoc's are always discarded for private types\n") "@typedoc's are always discarded for private types")
end end
{{kind, {name, arity}, type}, caller.line, export, doc} {{kind, {name, arity}, type}, caller.line, export, doc}
@@ -640,8 +660,20 @@ defmodule Kernel.Typespec do
end end
end end
defp typespec_to_ast({:type, _line, :list, args}) do defp typespec_to_ast({:type, line, :list, []}) do
for arg <- args, do: typespec_to_ast(arg) {:list, [line: line], []}
end
defp typespec_to_ast({:type, _line, :list, [arg]}) do
[typespec_to_ast(arg)]
end
defp typespec_to_ast({:type, line, :nonempty_list, []}) do
[{:..., [line: line], nil}]
end
defp typespec_to_ast({:type, line, :nonempty_list, [arg]}) do
[typespec_to_ast(arg), {:..., [line: line], nil}]
end end
defp typespec_to_ast({:type, line, :map, fields}) do defp typespec_to_ast({:type, line, :map, fields}) do
@@ -690,7 +722,11 @@ defmodule Kernel.Typespec do
end end
defp typespec_to_ast({:type, line, :range, [left, right]}) do defp typespec_to_ast({:type, line, :range, [left, right]}) do
{:"..", [line: line], [typespec_to_ast(left), typespec_to_ast(right)]} {:.., [line: line], [typespec_to_ast(left), typespec_to_ast(right)]}
end
defp typespec_to_ast({:type, _line, nil, []}) do
[]
end end
defp typespec_to_ast({:type, line, name, args}) do defp typespec_to_ast({:type, line, name, args}) do
@@ -747,9 +783,9 @@ defmodule Kernel.Typespec do
defp erl_to_ex_var(var) do defp erl_to_ex_var(var) do
case Atom.to_string(var) do case Atom.to_string(var) do
<<"_", c :: [binary, size(1)], rest :: binary>> -> <<"_", c :: binary-size(1), rest :: binary>> ->
String.to_atom("_#{String.downcase(c)}#{rest}") String.to_atom("_#{String.downcase(c)}#{rest}")
<<c :: [binary, size(1)], rest :: binary>> -> <<c :: binary-size(1), rest :: binary>> ->
String.to_atom("#{String.downcase(c)}#{rest}") String.to_atom("#{String.downcase(c)}#{rest}")
end end
end end
@@ -792,7 +828,58 @@ defmodule Kernel.Typespec do
end end
defp typespec({:%, _, [name, {:%{}, meta, fields}]}, vars, caller) do defp typespec({:%, _, [name, {:%{}, meta, fields}]}, vars, caller) do
typespec({:%{}, meta, [{:__struct__, name}|fields]}, vars, caller) module = Macro.expand(name, caller)
struct =
if module == caller.module do
Module.get_attribute(module, :struct) ||
compile_error(caller, "struct is not defined for #{Macro.to_string(name)}")
else
module.__struct__
end
struct =
:lists.map(fn {field, _} ->
{field, quote do: term()}
end, Map.to_list(struct))
:lists.foreach(fn {field, _} ->
unless Keyword.has_key?(struct, field) do
compile_error(caller, "undefined field #{field} on struct #{Macro.to_string(name)}")
end
end, fields)
fields = Keyword.merge(struct, [__struct__: module] ++ fields)
typespec({:%{}, meta, fields}, vars, caller)
end
# Handle records
defp typespec({:record, meta, [atom]}, vars, caller) do
typespec({:record, meta, [atom, []]}, vars, caller)
end
defp typespec({:record, meta, [atom, fields]}, vars, caller) do
case Macro.expand({atom, [], [{atom, [], []}]}, caller) do
keyword when is_list(keyword) ->
keyword =
:lists.map(fn {field, _} ->
{field, quote do: term()}
end, keyword)
:lists.foreach(fn {field, _} ->
unless Keyword.has_key?(keyword, field) do
compile_error(caller, "undefined field #{field} on record #{inspect atom}")
end
end, fields)
fields = Keyword.merge(keyword, fields)
types = Keyword.values(fields)
typespec({:{}, meta, [atom|types]}, vars, caller)
_ ->
compile_error(caller, "unknown record #{inspect atom}")
end
end end
# Handle ranges # Handle ranges
@@ -866,11 +953,11 @@ defmodule Kernel.Typespec do
end end
# Handle local calls # Handle local calls
defp typespec({:string, meta, arguments}, vars, caller) do defp typespec({type, meta, arguments}, vars, caller) when type in [:string, :nonempty_string] do
:elixir_errors.warn caller.line, caller.file, "string() type use is discouraged. For character lists, use " <> :elixir_errors.warn caller.line, caller.file, "#{type}() type use is discouraged. For character lists, use " <>
"char_list() type, for strings, String.t()\n#{Exception.format_stacktrace(Macro.Env.stacktrace(caller))}" "char_list() type, for strings, String.t()\n#{Exception.format_stacktrace(Macro.Env.stacktrace(caller))}"
arguments = for arg <- arguments, do: typespec(arg, vars, caller) arguments = for arg <- arguments, do: typespec(arg, vars, caller)
{:type, line(meta), :string, arguments} {:type, line(meta), type, arguments}
end end
defp typespec({:char_list, _meta, []}, vars, caller) do defp typespec({:char_list, _meta, []}, vars, caller) do
@@ -899,14 +986,18 @@ defmodule Kernel.Typespec do
typespec({nil, [], []}, vars, caller) typespec({nil, [], []}, vars, caller)
end end
defp typespec([spec], vars, caller) do defp typespec([{:..., _, atom}], vars, caller) when is_atom(atom) do
typespec({:list, [], [spec]}, vars, caller) typespec({:nonempty_list, [], []}, vars, caller)
end end
defp typespec([spec, {:"...", _, quoted}], vars, caller) when is_atom(quoted) do defp typespec([spec, {:..., _, atom}], vars, caller) when is_atom(atom) do
typespec({:nonempty_list, [], [spec]}, vars, caller) typespec({:nonempty_list, [], [spec]}, vars, caller)
end end
defp typespec([spec], vars, caller) do
typespec({:list, [], [spec]}, vars, caller)
end
defp typespec(list, vars, caller) do defp typespec(list, vars, caller) do
[h|t] = :lists.reverse(list) [h|t] = :lists.reverse(list)
union = :lists.foldl(fn(x, acc) -> union = :lists.foldl(fn(x, acc) ->
@@ -941,7 +1032,7 @@ defmodule Kernel.Typespec do
end end
end end
defp fn_args(meta, [{:"...", _, _}], _vars, _caller) do defp fn_args(meta, [{:..., _, _}], _vars, _caller) do
{:type, line(meta), :any} {:type, line(meta), :any}
end end
+45 -32
View File
@@ -9,19 +9,19 @@ defmodule Keyword do
behave exactly as a dictionary and mimic the API defined behave exactly as a dictionary and mimic the API defined
by the `Dict` behaviour. by the `Dict` behaviour.
For example, `Keyword.get` will get the first entry matching For example, `Keyword.get/3` will get the first entry matching
the given key, regardless if duplicated entries exist. the given key, regardless if duplicated entries exist.
Similarly, `Keyword.put` and `Keyword.delete` ensure all Similarly, `Keyword.put/3` and `Keyword.delete/3` ensure all
duplicated entries for a given key are removed when invoked. duplicated entries for a given key are removed when invoked.
A handful of functions exist to handle duplicated keys, in A handful of functions exist to handle duplicated keys, in
particular, `from_enum` allows creating a new keywords without particular, `Enum.into/2` allows creating new keywords without
removing duplicated keys, `get_values` returns all values for removing duplicated keys, `get_values/2` returns all values for
a given key and `delete_first` deletes just one of the existing a given key and `delete_first/2` deletes just one of the existing
entries. entries.
Since a keyword list is simply a list, all the operations defined Since a keyword list is simply a list, all the operations defined
in `Enum` and `List` can also be applied. in `Enum` and `List` can be applied.
""" """
@compile :inline_list_funcs @compile :inline_list_funcs
@@ -34,7 +34,7 @@ defmodule Keyword do
@type t(value) :: [{key, value}] @type t(value) :: [{key, value}]
@doc """ @doc """
Checks if the given argument is a keywords list or not. Checks if the given argument is a keyword list or not.
""" """
@spec keyword?(term) :: boolean @spec keyword?(term) :: boolean
def keyword?([{key, _value} | rest]) when is_atom(key) do def keyword?([{key, _value} | rest]) when is_atom(key) do
@@ -56,7 +56,7 @@ defmodule Keyword do
Creates a keyword from an enumerable. Creates a keyword from an enumerable.
Duplicated entries are removed, the latest one prevails. Duplicated entries are removed, the latest one prevails.
I.e. differently from `Enum.into(enumerable, [])`, Unlike `Enum.into(enumerable, [])`,
`Keyword.new(enumerable)` guarantees the keys are unique. `Keyword.new(enumerable)` guarantees the keys are unique.
## Examples ## Examples
@@ -76,7 +76,7 @@ defmodule Keyword do
Creates a keyword from an enumerable via the transformation function. Creates a keyword from an enumerable via the transformation function.
Duplicated entries are removed, the latest one prevails. Duplicated entries are removed, the latest one prevails.
I.e. differently from `Enum.into(enumerable, [], fun)`, Unlike `Enum.into(enumerable, [], fun)`,
`Keyword.new(enumerable, fun)` guarantees the keys are unique. `Keyword.new(enumerable, fun)` guarantees the keys are unique.
## Examples ## Examples
@@ -96,7 +96,7 @@ defmodule Keyword do
@doc """ @doc """
Gets the value for a specific `key`. Gets the value for a specific `key`.
If `key` does not exist, return default value (`nil` if no default value). If `key` does not exist, return the default value (`nil` if no default value).
If duplicated entries exist, the first one is returned. If duplicated entries exist, the first one is returned.
Use `get_values/2` to retrieve all entries. Use `get_values/2` to retrieve all entries.
@@ -124,6 +124,7 @@ defmodule Keyword do
@doc """ @doc """
Fetches the value for a specific `key` and returns it in a tuple. Fetches the value for a specific `key` and returns it in a tuple.
If the `key` does not exist, returns `:error`. If the `key` does not exist, returns `:error`.
## Examples ## Examples
@@ -144,8 +145,9 @@ defmodule Keyword do
end end
@doc """ @doc """
Fetches the value for specific `key`. If `key` does not exist, Fetches the value for specific `key`.
a `KeyError` is raised.
If `key` does not exist, a `KeyError` is raised.
## Examples ## Examples
@@ -184,8 +186,9 @@ defmodule Keyword do
end end
@doc """ @doc """
Returns all keys from the keyword list. Duplicated Returns all keys from the keyword list.
keys appear duplicated in the final list of keys.
Duplicated keys appear duplicated in the final list of keys.
## Examples ## Examples
@@ -217,6 +220,7 @@ defmodule Keyword do
@doc """ @doc """
Deletes the entries in the keyword list for a `key` with `value`. Deletes the entries in the keyword list for a `key` with `value`.
If no `key` with `value` exists, returns the keyword list unchanged. If no `key` with `value` exists, returns the keyword list unchanged.
## Examples ## Examples
@@ -238,8 +242,9 @@ defmodule Keyword do
@doc """ @doc """
Deletes the entries in the keyword list for a specific `key`. Deletes the entries in the keyword list for a specific `key`.
If the `key` does not exist, returns the keyword list unchanged. If the `key` does not exist, returns the keyword list unchanged.
Use `delete_first` to delete just the first entry in case of Use `delete_first/2` to delete just the first entry in case of
duplicated keys. duplicated keys.
## Examples ## Examples
@@ -261,6 +266,7 @@ defmodule Keyword do
@doc """ @doc """
Deletes the first entry in the keyword list for a specific `key`. Deletes the first entry in the keyword list for a specific `key`.
If the `key` does not exist, returns the keyword list unchanged. If the `key` does not exist, returns the keyword list unchanged.
## Examples ## Examples
@@ -319,7 +325,9 @@ defmodule Keyword do
end end
@doc """ @doc """
Checks if two keywords are equal. I.e. they contain Checks if two keywords are equal.
Two keywords are considered to be equal if they contain
the same keys and those keys contain the same values. the same keys and those keys contain the same values.
## Examples ## Examples
@@ -334,8 +342,9 @@ defmodule Keyword do
end end
@doc """ @doc """
Merges two keyword lists into one. If they have duplicated Merges two keyword lists into one.
entries, the one given as second argument wins.
If they have duplicated keys, the one given in the second argument wins.
## Examples ## Examples
@@ -350,8 +359,9 @@ defmodule Keyword do
end end
@doc """ @doc """
Merges two keyword lists into one. If they have duplicated Merges two keyword lists into one.
entries, the given function is invoked to solve conflicts.
If they have duplicated keys, the given function is invoked to solve conflicts.
## Examples ## Examples
@@ -392,10 +402,11 @@ defmodule Keyword do
end end
@doc """ @doc """
Updates the `key` with the given function. If the `key` does Updates the `key` with the given function.
not exist, raises `KeyError`.
If there are duplicated entries, they are all removed and only the first one If the `key` does not exist, raises `KeyError`.
If there are duplicated keys, they are all removed and only the first one
is updated. is updated.
## Examples ## Examples
@@ -425,10 +436,11 @@ defmodule Keyword do
end end
@doc """ @doc """
Updates the `key` with the given function. If the `key` does Updates the `key` with the given function.
not exist, inserts the given `initial` value.
If there are duplicated entries, they are all removed and only the first one If the `key` does not exist, inserts the given `initial` value.
If there are duplicated keys, they are all removed and only the first one
is updated. is updated.
## Examples ## Examples
@@ -455,8 +467,9 @@ defmodule Keyword do
@doc """ @doc """
Takes all entries corresponding to the given keys and extracts them into a Takes all entries corresponding to the given keys and extracts them into a
separate keyword list. Returns a tuple with the new list and the old list separate keyword list.
with removed keys.
Returns a tuple with the new list and the old list with removed keys.
Keys for which there are no entires in the keyword list are ignored. Keys for which there are no entires in the keyword list are ignored.
@@ -508,7 +521,7 @@ defmodule Keyword do
end end
@doc """ @doc """
Drops the given keys from the dict. Drops the given keys from the keyword list.
Duplicated keys are preserved in the new keyword list. Duplicated keys are preserved in the new keyword list.
@@ -531,7 +544,7 @@ defmodule Keyword do
Returns the first value associated with `key` in the keyword Returns the first value associated with `key` in the keyword
list as well as the keyword list without `key`. list as well as the keyword list without `key`.
All duplicated entries are removed. See `pop_first/3` for All duplicated keys are removed. See `pop_first/3` for
removing only the first entry. removing only the first entry.
## Examples ## Examples
@@ -558,10 +571,10 @@ defmodule Keyword do
@doc """ @doc """
Returns the first value associated with `key` in the keyword Returns the first value associated with `key` in the keyword
list as well as the keyword list without that particular ocurrence list as well as the keyword list without that particular occurrence
of `key`. of `key`.
Duplicated entries are not removed. Duplicated keys are not removed.
## Examples ## Examples
+7 -3
View File
@@ -307,6 +307,8 @@ defmodule List do
@doc """ @doc """
Zips corresponding elements from each list in `list_of_lists`. Zips corresponding elements from each list in `list_of_lists`.
The zipping finishes as soon as any list terminates.
## Examples ## Examples
iex> List.zip([[1, 2], [3, 4], [5, 6]]) iex> List.zip([[1, 2], [3, 4], [5, 6]])
@@ -323,8 +325,10 @@ defmodule List do
end end
@doc """ @doc """
Unzips the given list of lists or tuples into separate lists and returns a Unzips the given list of tuples into a list of lists.
list of lists.
The number of unzipped elements is equal to the size
of the smallest tuple in the list.
## Examples ## Examples
@@ -566,7 +570,7 @@ defmodule List do
"abc" "abc"
""" """
@spec to_string(:unicode.char_list) :: String.t @spec to_string(:unicode.charlist) :: String.t
def to_string(list) when is_list(list) do def to_string(list) when is_list(list) do
case :unicode.characters_to_binary(list) do case :unicode.characters_to_binary(list) do
result when is_binary(result) -> result when is_binary(result) ->
+46 -22
View File
@@ -16,7 +16,7 @@ defmodule Macro do
:&&, :||, :<>, :++, :--, :\\, :::, :<-, :.., :|>, :=~, :&&, :||, :<>, :++, :--, :\\, :::, :<-, :.., :|>, :=~,
:<, :>, :->, :<, :>, :->,
:+, :-, :*, :/, :=, :|, :., :+, :-, :*, :/, :=, :|, :.,
:and, :or, :xor, :when, :in, :and, :or, :when, :in,
:<<<, :>>>, :|||, :&&&, :^^^, :~~~] :<<<, :>>>, :|||, :&&&, :^^^, :~~~]
@doc false @doc false
@@ -35,7 +35,7 @@ defmodule Macro do
::: -> {:right, 60} ::: -> {:right, 60}
:| -> {:right, 70} :| -> {:right, 70}
:= -> {:right, 90} := -> {:right, 90}
o when o in [:||, :|||, :or, :xor] -> {:left, 130} o when o in [:||, :|||, :or] -> {:left, 130}
o when o in [:&&, :&&&, :and] -> {:left, 140} o when o in [:&&, :&&&, :and] -> {:left, 140}
o when o in [:==, :!=, :=~, :===, :!==] -> {:left, 150} o when o in [:==, :!=, :=~, :===, :!==] -> {:left, 150}
o when o in [:<, :<=, :>=, :>] -> {:left, 160} o when o in [:<, :<=, :>=, :>] -> {:left, 160}
@@ -74,7 +74,7 @@ defmodule Macro do
def pipe(expr, call_args, position) def pipe(expr, call_args, position)
def pipe(expr, {:&, _, _} = call_args, _integer) do def pipe(expr, {:&, _, _} = call_args, _integer) do
raise ArgumentError, "cannot pipe #{to_string expr} into #{to_string call_args}" bad_pipe(expr, call_args)
end end
def pipe(expr, {call, line, atom}, integer) when is_atom(atom) do def pipe(expr, {call, line, atom}, integer) when is_atom(atom) do
@@ -86,7 +86,12 @@ defmodule Macro do
end end
def pipe(expr, call_args, _integer) do def pipe(expr, call_args, _integer) do
raise ArgumentError, "cannot pipe #{to_string expr} into #{to_string call_args}" bad_pipe(expr, call_args)
end
defp bad_pipe(expr, call_args) do
raise ArgumentError, "cannot pipe #{to_string expr} into #{to_string call_args}, " <>
"can only pipe into local calls foo(), remote calls Foo.bar() or anonymous functions calls foo.()"
end end
@doc """ @doc """
@@ -307,9 +312,9 @@ defmodule Macro do
single- and double-quoted strings. Check `unescape_string/2` single- and double-quoted strings. Check `unescape_string/2`
for information on how to customize the escaping map. for information on how to customize the escaping map.
In this setup, Elixir will escape the following: `\a`, `\b`, In this setup, Elixir will escape the following: `\0`, `\a`, `\b`,
`\d`, `\e`, `\f`, `\n`, `\r`, `\s`, `\t` and `\v`. Octals are `\d`, `\e`, `\f`, `\n`, `\r`, `\s`, `\t` and `\v`. Hexadecimals
also escaped according to the latin1 set they represent. are also supported via `\xNN` and `\x{NN...}` syntax.
This function is commonly used on sigil implementations This function is commonly used on sigil implementations
(like `~r`, `~s` and others) which receive a raw, unescaped (like `~r`, `~s` and others) which receive a raw, unescaped
@@ -340,6 +345,7 @@ defmodule Macro do
representing the codepoint of the character it wants to unescape. representing the codepoint of the character it wants to unescape.
Here is the default mapping function implemented by Elixir: Here is the default mapping function implemented by Elixir:
def unescape_map(?0), do: ?0
def unescape_map(?a), do: ?\a def unescape_map(?a), do: ?\a
def unescape_map(?b), do: ?\b def unescape_map(?b), do: ?\b
def unescape_map(?d), do: ?\d def unescape_map(?d), do: ?\d
@@ -350,20 +356,14 @@ defmodule Macro do
def unescape_map(?s), do: ?\s def unescape_map(?s), do: ?\s
def unescape_map(?t), do: ?\t def unescape_map(?t), do: ?\t
def unescape_map(?v), do: ?\v def unescape_map(?v), do: ?\v
def unescape_map(?x), do: true
def unescape_map(e), do: e def unescape_map(e), do: e
If the `unescape_map` function returns `false`. The char is If the `unescape_map` function returns `false`. The char is
not escaped and `\` is kept in the char list. not escaped and `\` is kept in the char list.
## Octals Hexadecimals will be escaped if the map function returns `true`
for `?x`.
Octals will by default be escaped unless the map function
returns `false` for `?0`.
## Hex
Hexadecimals will by default be escaped unless the map function
returns `false` for `?x`.
## Examples ## Examples
@@ -525,11 +525,15 @@ defmodule Macro do
# All other calls # All other calls
def to_string({target, _, args} = ast, fun) when is_list(args) do def to_string({target, _, args} = ast, fun) when is_list(args) do
{list, last} = :elixir_utils.split_last(args) if sigil = sigil_call(ast, fun) do
fun.(ast, case kw_blocks?(last) do sigil
true -> call_to_string_with_args(target, list, fun) <> kw_blocks_to_string(last, fun) else
false -> call_to_string_with_args(target, args, fun) {list, last} = :elixir_utils.split_last(args)
end) fun.(ast, case kw_blocks?(last) do
true -> call_to_string_with_args(target, list, fun) <> kw_blocks_to_string(last, fun)
false -> call_to_string_with_args(target, args, fun)
end)
end
end end
# Two-item tuples # Two-item tuples
@@ -565,6 +569,26 @@ defmodule Macro do
defp module_to_string(atom, _fun) when is_atom(atom), do: inspect(atom, []) defp module_to_string(atom, _fun) when is_atom(atom), do: inspect(atom, [])
defp module_to_string(other, fun), do: call_to_string(other, fun) defp module_to_string(other, fun), do: call_to_string(other, fun)
defp sigil_call({func, _, [{:<<>>, _, [string]}, args]} = ast, fun) when is_list(args) do
sigil =
case Atom.to_string(func) do
<<"sigil_", name>> ->
"~" <> <<name>> <>
fun.(string, inspect(string, [])) <>
sigil_args(args, fun)
_ ->
nil
end
fun.(ast, sigil)
end
defp sigil_call(_other, _fun) do
nil
end
defp sigil_args([], _fun), do: ""
defp sigil_args(args, fun), do: fun.(args, List.to_string(args))
defp call_to_string(atom, _fun) when is_atom(atom), do: Atom.to_string(atom) defp call_to_string(atom, _fun) when is_atom(atom), do: Atom.to_string(atom)
defp call_to_string({:., _, [arg]}, fun), do: module_to_string(arg, fun) <> "." defp call_to_string({:., _, [arg]}, fun), do: module_to_string(arg, fun) <> "."
defp call_to_string({:., _, [left, right]}, fun), do: module_to_string(left, fun) <> "." <> call_to_string(right, fun) defp call_to_string({:., _, [left, right]}, fun), do: module_to_string(left, fun) <> "." <> call_to_string(right, fun)
@@ -838,7 +862,7 @@ defmodule Macro do
end end
expand = :elixir_dispatch.expand_import(meta, {atom, length(args)}, args, expand = :elixir_dispatch.expand_import(meta, {atom, length(args)}, args,
env, extra) env, extra, true)
case expand do case expand do
{:ok, receiver, quoted} -> {:ok, receiver, quoted} ->
+3 -3
View File
@@ -29,7 +29,7 @@ defmodule Macro.Env do
* `local` - the module to expand local functions to * `local` - the module to expand local functions to
""" """
@type name_arity :: {atom, non_neg_integer} @type name_arity :: {atom, arity}
@type file :: binary @type file :: binary
@type line :: non_neg_integer @type line :: non_neg_integer
@type aliases :: [{module, module}] @type aliases :: [{module, module}]
@@ -42,10 +42,10 @@ defmodule Macro.Env do
@type vars :: [{atom, atom | non_neg_integer}] @type vars :: [{atom, atom | non_neg_integer}]
@type export_vars :: vars | nil @type export_vars :: vars | nil
@type lexical_tracker :: pid @type lexical_tracker :: pid
@type local :: module | nil @type local :: atom | nil
@type t :: %{__struct__: __MODULE__, @type t :: %{__struct__: __MODULE__,
module: module, module: atom,
file: file, file: file,
line: line, line: line,
function: name_arity | nil, function: name_arity | nil,
+27
View File
@@ -38,5 +38,32 @@ defmodule Map do
end, map1, map2 end, map1, map2
end end
@doc """
Converts a struct to map.
It accepts the struct module or a struct itself and
simply removes the `__struct__` field from the struct.
## Example
defmodule User do
defstruct [:name]
end
Map.from_struct(User)
#=> %{name: nil}
Map.from_struct(%User{name: "john"})
#=> %{name: "john"}
"""
def from_struct(struct) when is_atom(struct) do
:maps.remove(:__struct__, struct.__struct__)
end
def from_struct(%{__struct__: _} = struct) do
:maps.remove(:__struct__, struct)
end
def equal?(%{} = map1, %{} = map2), do: map1 === map2 def equal?(%{} = map1, %{} = map2), do: map1 === map2
end end
+44 -41
View File
@@ -766,15 +766,9 @@ defmodule Module do
@foo @foo
Expands to: Expands close to:
Module.get_attribute(__MODULE__, :foo, true) Module.get_attribute(__MODULE__, :foo)
Notice the third argument may be given to indicate a stacktrace
to be emitted when the attribute was not previously defined.
The default value for `warn` is nil for direct calls but the `@foo`
macro sets it to the proper stacktrace automatically, warning
every time `@foo` is used but not set previously.
## Examples ## Examples
@@ -788,37 +782,9 @@ defmodule Module do
end end
""" """
@spec get_attribute(module, atom, warn :: nil | [tuple]) :: term @spec get_attribute(atom, atom) :: term
def get_attribute(module, key, warn \\ nil) when def get_attribute(module, key) do
is_atom(key) and (is_list(warn) or nil?(warn)) do get_attribute(module, key, nil)
assert_not_compiled!(:get_attribute, module)
table = data_table_for(module)
case :ets.lookup(table, key) do
[{^key, val}] -> val
[] ->
acc = :ets.lookup_element(table, :__acc_attributes, 2)
cond do
:lists.member(key, acc) ->
[]
is_list(warn) ->
:elixir_errors.warn warn_info(warn), "undefined module attribute @#{key}, " <>
"please remove access to @#{key} or explicitly set it to nil before access\n"
nil
true ->
nil
end
end
end
defp warn_info([entry|_]) do
opts = elem(entry, tuple_size(entry) - 1)
Exception.format_file_line(Keyword.get(opts, :file), Keyword.get(opts, :line)) <> " "
end
defp warn_info([]) do
""
end end
@doc """ @doc """
@@ -832,10 +798,12 @@ defmodule Module do
end end
""" """
@spec delete_attribute(atom, atom) :: :ok
def delete_attribute(module, key) when is_atom(key) do def delete_attribute(module, key) when is_atom(key) do
assert_not_compiled!(:delete_attribute, module) assert_not_compiled!(:delete_attribute, module)
table = data_table_for(module) table = data_table_for(module)
:ets.delete(table, key) :ets.delete(table, key)
:ok
end end
@doc """ @doc """
@@ -911,7 +879,9 @@ defmodule Module do
:ok -> :ok ->
:ok :ok
{:error, :private_doc} -> {:error, :private_doc} ->
:elixir_errors.warn line, env.file, "function #{name}/#{arity} is private, @doc's are always discarded for private functions\n" :elixir_errors.warn line, env.file,
"function #{name}/#{arity} is private, " <>
"@doc's are always discarded for private functions"
end end
delete_attribute(module, :doc) delete_attribute(module, :doc)
@@ -933,6 +903,38 @@ defmodule Module do
:ets.insert(table, {key, new}) :ets.insert(table, {key, new})
end end
@doc false
def get_attribute(module, key, warn) when is_atom(key) and (is_list(warn) or nil?(warn)) do
assert_not_compiled!(:get_attribute, module)
table = data_table_for(module)
case :ets.lookup(table, key) do
[{^key, val}] -> val
[] ->
acc = :ets.lookup_element(table, :__acc_attributes, 2)
cond do
:lists.member(key, acc) ->
[]
is_list(warn) ->
:elixir_errors.warn warn_info(warn), "undefined module attribute @#{key}, " <>
"please remove access to @#{key} or explicitly set it to nil before access"
nil
true ->
nil
end
end
end
defp warn_info([entry|_]) do
opts = elem(entry, tuple_size(entry) - 1)
Exception.format_file_line(Keyword.get(opts, :file), Keyword.get(opts, :line)) <> " "
end
defp warn_info([]) do
""
end
## Helpers ## Helpers
defp normalize_attribute(:on_load, atom) when is_atom(atom) do defp normalize_attribute(:on_load, atom) when is_atom(atom) do
@@ -940,7 +942,8 @@ defmodule Module do
end end
defp normalize_attribute(:behaviour, atom) when is_atom(atom) do defp normalize_attribute(:behaviour, atom) when is_atom(atom) do
Code.ensure_compiled(atom) # Attempt to compile behaviour but ignore failure (will warn later)
_ = Code.ensure_compiled(atom)
atom atom
end end
+64 -26
View File
@@ -65,16 +65,19 @@ defmodule Module.LocalsTracker do
""" """
@spec reachable(ref) :: [local] @spec reachable(ref) :: [local]
def reachable(ref) do def reachable(ref) do
d = :gen_server.call(to_pid(ref), :digraph, @timeout) reachable_from(:gen_server.call(to_pid(ref), :digraph, @timeout), :local)
reduce_reachable(d, :local, []) end
defp reachable_from(d, starting) do
:sets.to_list(reduce_reachable(d, starting, :sets.new))
end end
defp reduce_reachable(d, vertex, vertices) do defp reduce_reachable(d, vertex, vertices) do
neighbours = :digraph.out_neighbours(d, vertex) neighbours = :digraph.out_neighbours(d, vertex)
neighbours = (for {_, _} = t <- neighbours, do: t) |> :ordsets.from_list neighbours = (for {_, _} = t <- neighbours, do: t) |> :sets.from_list
remaining = :ordsets.subtract(neighbours, vertices) remaining = :sets.subtract(neighbours, vertices)
vertices = :ordsets.union(neighbours, vertices) vertices = :sets.union(neighbours, vertices)
:lists.foldl(&reduce_reachable(d, &1, &2), vertices, remaining) :sets.fold(&reduce_reachable(d, &1, &2), vertices, remaining)
end end
defp to_pid(pid) when is_pid(pid), do: pid defp to_pid(pid) when is_pid(pid), do: pid
@@ -134,9 +137,7 @@ defmodule Module.LocalsTracker do
# Reattach a previously yanked node # Reattach a previously yanked node
@doc false @doc false
def reattach(pid, kind, tuple, neighbours) do def reattach(pid, kind, tuple, neighbours) do
pid = to_pid(pid) :gen_server.cast(to_pid(pid), {:reattach, kind, tuple, neighbours})
add_definition(pid, kind, tuple)
:gen_server.cast(pid, {:reattach, tuple, neighbours})
end end
# Collecting all conflicting imports with the given functions # Collecting all conflicting imports with the given functions
@@ -156,12 +157,42 @@ defmodule Module.LocalsTracker do
# given also accounting the expected amount of default # given also accounting the expected amount of default
# clauses a private function have. # clauses a private function have.
@doc false @doc false
def collect_unused_locals(pid, private) do def collect_unused_locals(ref, private) do
reachable = reachable(pid) d = :gen_server.call(to_pid(ref), :digraph, @timeout)
:lists.foldl(&collect_unused_locals(&1, &2, reachable), [], private) {unreachable(d, private), collect_warnings(d, private)}
end end
defp collect_unused_locals({tuple, kind, 0}, acc, reachable) do defp unreachable(d, private) do
unreachable = for {tuple, _, _} <- private, do: tuple
private =
for {tuple, :defp, _} <- private do
neighbours = :digraph.in_neighbours(d, tuple)
neighbours = for {_, _} = t <- neighbours, do: t
{tuple, :sets.from_list(neighbours)}
end
reduce_unreachable(private, [], :sets.from_list(unreachable))
end
defp reduce_unreachable([{vertex, callers}|t], acc, unreachable) do
if :sets.is_subset(callers, unreachable) do
reduce_unreachable(t, [{vertex, callers}|acc], unreachable)
else
reduce_unreachable(acc ++ t, [], :sets.del_element(vertex, unreachable))
end
end
defp reduce_unreachable([], _acc, unreachable) do
:sets.to_list(unreachable)
end
defp collect_warnings(d, private) do
reachable = reachable_from(d, :local)
:lists.foldl(&collect_warnings(&1, &2, reachable), [], private)
end
defp collect_warnings({tuple, kind, 0}, acc, reachable) do
if :lists.member(tuple, reachable) do if :lists.member(tuple, reachable) do
acc acc
else else
@@ -169,7 +200,7 @@ defmodule Module.LocalsTracker do
end end
end end
defp collect_unused_locals({tuple, kind, default}, acc, reachable) when default > 0 do defp collect_warnings({tuple, kind, default}, acc, reachable) when default > 0 do
{name, arity} = tuple {name, arity} = tuple
min = arity - default min = arity - default
max = arity max = arity
@@ -211,6 +242,7 @@ defmodule Module.LocalsTracker do
{:ok, {d, []}} {:ok, {d, []}}
end end
@doc false
def handle_call({:cache_env, env}, _from, {d, cache}) do def handle_call({:cache_env, env}, _from, {d, cache}) do
case cache do case cache do
[{i,^env}|_] -> [{i,^env}|_] ->
@@ -227,10 +259,9 @@ defmodule Module.LocalsTracker do
end end
def handle_call({:yank, local}, _from, {d, _} = state) do def handle_call({:yank, local}, _from, {d, _} = state) do
in_vertices = :digraph.in_neighbours(d, local)
out_vertices = :digraph.out_neighbours(d, local) out_vertices = :digraph.out_neighbours(d, local)
:digraph.del_vertex(d, local) :digraph.del_edges(d, :digraph.out_edges(d, local))
{:reply, {in_vertices, out_vertices}, state} {:reply, {[], out_vertices}, state}
end end
def handle_call(:digraph, _from, {d, _} = state) do def handle_call(:digraph, _from, {d, _} = state) do
@@ -268,9 +299,17 @@ defmodule Module.LocalsTracker do
{:noreply, state} {:noreply, state}
end end
def handle_cast({:reattach, tuple, {in_neigh, out_neigh}}, {d, _} = state) do def handle_cast({:reattach, _kind, tuple, {in_neigh, out_neigh}}, {d, _} = state) do
for from <- in_neigh, do: replace_edge(d, from, tuple) for from <- in_neigh do
for to <- out_neigh, do: replace_edge(d, tuple, to) :digraph.add_vertex(d, from)
replace_edge!(d, from, tuple)
end
for to <- out_neigh do
:digraph.add_vertex(d, to)
replace_edge!(d, tuple, to)
end
{:noreply, state} {:noreply, state}
end end
@@ -282,10 +321,12 @@ defmodule Module.LocalsTracker do
{:stop, {:bad_cast, msg}, state} {:stop, {:bad_cast, msg}, state}
end end
@doc false
def terminate(_reason, _state) do def terminate(_reason, _state) do
:ok :ok
end end
@doc false
def code_change(_old, state, _extra) do def code_change(_old, state, _extra) do
{:ok, state} {:ok, state}
end end
@@ -300,6 +341,8 @@ defmodule Module.LocalsTracker do
if function != nil do if function != nil do
replace_edge!(d, function, tuple) replace_edge!(d, function, tuple)
end end
:ok
end end
defp handle_add_local(d, from, to) do defp handle_add_local(d, from, to) do
@@ -320,11 +363,6 @@ defmodule Module.LocalsTracker do
unless :lists.member(to, :digraph.out_neighbours(d, from)) do unless :lists.member(to, :digraph.out_neighbours(d, from)) do
[:"$e"|_] = :digraph.add_edge(d, from, to) [:"$e"|_] = :digraph.add_edge(d, from, to)
end end
end :ok
defp replace_edge(d, from, to) do
unless :lists.member(to, :digraph.out_neighbours(d, from)) do
:digraph.add_edge(d, from, to)
end
end end
end end
+122 -59
View File
@@ -45,7 +45,7 @@ defmodule OptionParser do
* `:strict` - the switches are strict. Any switch that does not * `:strict` - the switches are strict. Any switch that does not
exist in the switch list is treated as an error. exist in the switch list is treated as an error.
* `:switches` - configure some switches. Switches that does not * `:switches` - defines some switches. Switches that does not
exist in the switch list are still attempted to be parsed. exist in the switch list are still attempted to be parsed.
Note only `:strict` or `:switches` may be given at once. Note only `:strict` or `:switches` may be given at once.
@@ -59,7 +59,7 @@ defmodule OptionParser do
* `:float` - parses the switch as a float. * `:float` - parses the switch as a float.
* `:string` - returns the switch as a string. * `:string` - returns the switch as a string.
If a switch can't be parsed or is not specfied in the strict case, If a switch can't be parsed or is not specified in the strict case,
the option is returned in the invalid options list (third element the option is returned in the invalid options list (third element
of the returned tuple). of the returned tuple).
@@ -91,14 +91,8 @@ defmodule OptionParser do
## Negation switches ## Negation switches
All switches starting with `--no-` are considered to be booleans and never In case a switch is declared as boolean, it may be passed as `--no-SWITCH`
parse the next value: which will set the option to false:
iex> OptionParser.parse(["--no-op", "path/to/file"])
{[no_op: true], ["path/to/file"], []}
However, in case the base switch exists, it sets that particular switch to
false:
iex> OptionParser.parse(["--no-op", "path/to/file"], switches: [op: :boolean]) iex> OptionParser.parse(["--no-op", "path/to/file"], switches: [op: :boolean])
{[op: false], ["path/to/file"], []} {[op: false], ["path/to/file"], []}
@@ -113,8 +107,7 @@ defmodule OptionParser do
""" """
@spec parse(argv, options) :: {parsed, argv, errors} @spec parse(argv, options) :: {parsed, argv, errors}
def parse(argv, opts \\ []) when is_list(argv) and is_list(opts) do def parse(argv, opts \\ []) when is_list(argv) and is_list(opts) do
config = compile_config(opts, true) do_parse(argv, compile_config(opts), [], [], [], true)
do_parse(argv, config, [], [], [])
end end
@doc """ @doc """
@@ -134,37 +127,36 @@ defmodule OptionParser do
""" """
@spec parse_head(argv, options) :: {parsed, argv, errors} @spec parse_head(argv, options) :: {parsed, argv, errors}
def parse_head(argv, opts \\ []) when is_list(argv) and is_list(opts) do def parse_head(argv, opts \\ []) when is_list(argv) and is_list(opts) do
config = compile_config(opts, false) do_parse(argv, compile_config(opts), [], [], [], false)
do_parse(argv, config, [], [], [])
end end
defp do_parse([], _config, opts, args, invalid) do defp do_parse([], _config, opts, args, invalid, _all?) do
{Enum.reverse(opts), Enum.reverse(args), Enum.reverse(invalid)} {Enum.reverse(opts), Enum.reverse(args), Enum.reverse(invalid)}
end end
defp do_parse(argv, {aliases, switches, strict, all}=config, opts, args, invalid) do defp do_parse(argv, {aliases, switches, strict}=config, opts, args, invalid, all?) do
case next(argv, aliases, switches, strict) do case next(argv, aliases, switches, strict) do
{:ok, option, value, rest} -> {:ok, option, value, rest} ->
# the option exist and it was successfully parsed # the option exist and it was successfully parsed
kinds = List.wrap Keyword.get(switches, option) kinds = List.wrap Keyword.get(switches, option)
new_opts = do_store_option(opts, option, value, kinds) new_opts = do_store_option(opts, option, value, kinds)
do_parse(rest, config, new_opts, args, invalid) do_parse(rest, config, new_opts, args, invalid, all?)
{:invalid, option, value, rest} -> {:invalid, option, value, rest} ->
# the option exist but it has wrong value # the option exist but it has wrong value
do_parse(rest, config, opts, args, [{option, value}|invalid]) do_parse(rest, config, opts, args, [{option, value}|invalid], all?)
{:undefined, option, _value, rest} -> {:undefined, option, _value, rest} ->
# the option does not exist (for strict cases) # the option does not exist (for strict cases)
do_parse(rest, config, opts, args, [{option, nil}|invalid]) do_parse(rest, config, opts, args, [{option, nil}|invalid], all?)
{:error, ["--"|rest]} -> {:error, ["--"|rest]} ->
{Enum.reverse(opts), Enum.reverse(args, rest), Enum.reverse(invalid)} {Enum.reverse(opts), Enum.reverse(args, rest), Enum.reverse(invalid)}
{:error, [arg|rest]=remaining_args} -> {:error, [arg|rest]=remaining_args} ->
# there is no option # there is no option
if all do if all? do
do_parse(rest, config, opts, [arg|args], invalid) do_parse(rest, config, opts, [arg|args], invalid, all?)
else else
{Enum.reverse(opts), Enum.reverse(args, remaining_args), Enum.reverse(invalid)} {Enum.reverse(opts), Enum.reverse(args, remaining_args), Enum.reverse(invalid)}
end end
@@ -193,12 +185,12 @@ defmodule OptionParser do
@spec next(argv, options) :: @spec next(argv, options) ::
{:ok, key :: atom, value :: term, argv} | {:ok, key :: atom, value :: term, argv} |
{:invalid, key :: atom, value :: term, argv} | {:invalid, String.t, String.t | nil, argv} |
{:undefined, key :: atom, value :: term, argv} | {:undefined, String.t, String.t | nil, argv} |
{:error, argv} {:error, argv}
def next(argv, opts \\ []) when is_list(argv) and is_list(opts) do def next(argv, opts \\ []) when is_list(argv) and is_list(opts) do
{aliases, switches, strict, _} = compile_config(opts, true) {aliases, switches, strict} = compile_config(opts)
next(argv, aliases, switches, strict) next(argv, aliases, switches, strict)
end end
@@ -221,7 +213,7 @@ defmodule OptionParser do
defp next(["-" <> option|rest], aliases, switches, strict) do defp next(["-" <> option|rest], aliases, switches, strict) do
{option, value} = split_option(option) {option, value} = split_option(option)
opt_name_bin = "-" <> option opt_name_bin = "-" <> option
tagged = tag_option(option, value, switches, aliases) tagged = tag_option(option, switches, aliases)
if strict and not option_defined?(tagged, switches) do if strict and not option_defined?(tagged, switches) do
{:undefined, opt_name_bin, value, rest} {:undefined, opt_name_bin, value, rest}
@@ -239,9 +231,95 @@ defmodule OptionParser do
{:error, argv} {:error, argv}
end end
@doc """
Receives a key-value enumerable and convert it to argv.
Keys must be atoms. Keys with nil value are discarded,
boolean values are converted to `--key` or `--no-key`
and all other values are converted using `to_string/1`.
## Examples
iex> OptionParser.to_argv([foo_bar: "baz"])
["--foo-bar", "baz"]
iex> OptionParser.to_argv([bool: true, bool: false, discarded: nil])
["--bool", "--no-bool"]
"""
@spec to_argv(Enumerable.t) :: argv
def to_argv(enum) do
Enum.flat_map(enum, fn
{_key, nil} -> []
{key, true} -> [to_switch(key)]
{key, false} -> [to_switch(key, "--no-")]
{key, value} -> [to_switch(key), to_string(value)]
end)
end
defp to_switch(key, prefix \\ "--") when is_atom(key) do
prefix <> String.replace(Atom.to_string(key), "_", "-")
end
@doc ~S"""
Splits a string into argv chunks.
## Examples
iex> OptionParser.split("foo bar")
["foo", "bar"]
iex> OptionParser.split("foo \"bar baz\"")
["foo", "bar baz"]
"""
@spec split(String.t) :: argv
def split(string) do
do_split(strip_leading_spaces(string), "", [], nil)
end
# If we have a escaped quote, simply remove the escape
defp do_split(<<?\\, quote, t :: binary>>, buffer, acc, quote),
do: do_split(t, <<buffer::binary, quote>>, acc, quote)
# If we have a quote and we were not in a quote, start one
defp do_split(<<quote, t :: binary>>, buffer, acc, nil) when quote in [?", ?'],
do: do_split(t, buffer, acc, quote)
# If we have a quote and we were inside it, close it
defp do_split(<<quote, t :: binary>>, buffer, acc, quote),
do: do_split(t, buffer, acc, nil)
# If we have a escaped quote/space, simply remove the escape as long as we are not inside a quote
defp do_split(<<?\\, h, t :: binary>>, buffer, acc, nil) when h in [?\s, ?', ?"],
do: do_split(t, <<buffer::binary, h>>, acc, nil)
# If we have space and we are outside of a quote, start new segment
defp do_split(<<?\s, t :: binary>>, buffer, acc, nil),
do: do_split(strip_leading_spaces(t), "", [buffer|acc], nil)
# All other characters are moved to buffer
defp do_split(<<h, t::binary>>, buffer, acc, quote) do
do_split(t, <<buffer::binary, h>>, acc, quote)
end
# Finish the string expecting a nil marker
defp do_split(<<>>, "", acc, nil),
do: Enum.reverse(acc)
defp do_split(<<>>, buffer, acc, nil),
do: Enum.reverse([buffer|acc])
# Otherwise raise
defp do_split(<<>>, _, _acc, marker) do
raise "argv string did not terminate properly, a #{<<marker>>} was opened but never closed"
end
defp strip_leading_spaces(" " <> t), do: strip_leading_spaces(t)
defp strip_leading_spaces(t), do: t
## Helpers ## Helpers
defp compile_config(opts, all) do defp compile_config(opts) do
aliases = opts[:aliases] || [] aliases = opts[:aliases] || []
{switches, strict} = cond do {switches, strict} = cond do
@@ -253,7 +331,7 @@ defmodule OptionParser do
{[], false} {[], false}
end end
{aliases, switches, strict, all} {aliases, switches, strict}
end end
defp validate_option(value, kinds) do defp validate_option(value, kinds) do
@@ -296,11 +374,11 @@ defmodule OptionParser do
end end
end end
defp tag_option(<<?-, option :: binary>>, value, switches, _aliases) do defp tag_option(<<?-, option :: binary>>, switches, _aliases) do
get_negated(option, value, switches) get_negated(option, switches)
end end
defp tag_option(option, _value, _switches, aliases) when is_binary(option) do defp tag_option(option, _switches, aliases) when is_binary(option) do
opt = get_option(option) opt = get_option(option)
if alias = aliases[opt] do if alias = aliases[opt] do
{:default, alias} {:default, alias}
@@ -325,23 +403,14 @@ defmodule OptionParser do
{nil, [:invalid], value} {nil, [:invalid], value}
end end
defp normalize_option({:negated, option}, nil, switches) do defp normalize_option({:negated, option}, value, switches) do
kinds = List.wrap(switches[option]) if value do
{option, [:invalid], value}
cond do else
:boolean in kinds -> {option, List.wrap(switches[option]), false}
{option, kinds, false}
kinds == [] ->
{option, kinds, true}
true ->
{reverse_negated(option), [:invalid], nil}
end end
end end
defp normalize_option({:negated, option}, value, _switches) do
{option, [:invalid], value}
end
defp normalize_option({:default, option}, value, switches) do defp normalize_option({:default, option}, value, switches) do
{option, List.wrap(switches[option]), value} {option, List.wrap(switches[option]), value}
end end
@@ -396,24 +465,18 @@ defmodule OptionParser do
end end
end end
defp reverse_negated(negated) do defp get_negated("no-" <> rest = original, switches) do
String.to_atom("no_" <> Atom.to_string(negated)) cond do
end (negated = get_option(rest)) && :boolean in List.wrap(switches[negated]) ->
{:negated, negated}
defp get_negated("no-" <> rest = option, value, switches) do option = get_option(original) ->
if negated = get_option(rest) do {:default, option}
option = if Keyword.has_key?(switches, negated) and value == nil do true ->
negated :unknown
else
get_option(option)
end
{:negated, option}
else
:unknown
end end
end end
defp get_negated(rest, _value, _switches) do defp get_negated(rest, _switches) do
if option = get_option(rest) do if option = get_option(rest) do
{:default, option} {:default, option}
else else
+27 -5
View File
@@ -159,7 +159,7 @@ defmodule Path do
def type(name) when is_list(name) or is_binary(name) do def type(name) when is_list(name) or is_binary(name) do
case :os.type() do case :os.type() do
{:win32, _} -> win32_pathtype(name) {:win32, _} -> win32_pathtype(name)
_ -> unix_pathtype(name) _ -> unix_pathtype(name)
end |> elem(0) end |> elem(0)
end end
@@ -458,9 +458,14 @@ defmodule Path do
defp maybe_remove_dirsep(name, _), do: defp maybe_remove_dirsep(name, _), do:
:lists.reverse(name) :lists.reverse(name)
@doc """ @doc ~S"""
Returns a list with the path split by the path separator. Splits the path into a list at the path separator.
If an empty string is given, returns the root path.
If an empty string is given, returns an empty list.
On Windows, path is split on both "\" and "/" separators
and the driver letter, if there is one, is always returned
in lowercase.
## Examples ## Examples
@@ -486,6 +491,11 @@ defmodule Path do
defmodule Wildcard do defmodule Wildcard do
@moduledoc false @moduledoc false
def read_link_info(file) do
call({:read_link_info, file})
end
# For compatibility with buggy Erlang 17.1.
def read_file_info(file) do def read_file_info(file) do
call({:read_link_info, file}) call({:read_link_info, file})
end end
@@ -575,11 +585,23 @@ defmodule Path do
defp expand_home(type) do defp expand_home(type) do
case IO.chardata_to_string(type) do case IO.chardata_to_string(type) do
"~" <> rest -> System.user_home! <> rest "~" <> rest -> resolve_home(rest)
rest -> rest rest -> rest
end end
end end
defp resolve_home(""), do: System.user_home!
defp resolve_home(rest) do
case {rest, major_os_type} do
{"\\" <> _, :win32} ->
System.user_home! <> rest
{"/" <> _, _} ->
System.user_home! <> rest
_ -> rest
end
end
defp normalize(path), do: normalize(split(path), []) defp normalize(path), do: normalize(split(path), [])
defp normalize([".."|t], ["/"|_] = acc) do defp normalize([".."|t], ["/"|_] = acc) do
+18
View File
@@ -5,6 +5,8 @@ defmodule Port do
@doc """ @doc """
See http://www.erlang.org/doc/man/erlang.html#open_port-2. See http://www.erlang.org/doc/man/erlang.html#open_port-2.
Inlined by the compiler.
""" """
def open(name, settings) do def open(name, settings) do
:erlang.open_port(name, settings) :erlang.open_port(name, settings)
@@ -12,6 +14,8 @@ defmodule Port do
@doc """ @doc """
See http://www.erlang.org/doc/man/erlang.html#port_close-1. See http://www.erlang.org/doc/man/erlang.html#port_close-1.
Inlined by the compiler.
""" """
def close(port) do def close(port) do
:erlang.port_close(port) :erlang.port_close(port)
@@ -19,6 +23,8 @@ defmodule Port do
@doc """ @doc """
See http://www.erlang.org/doc/man/erlang.html#port_command-2. See http://www.erlang.org/doc/man/erlang.html#port_command-2.
Inlined by the compiler.
""" """
def command(port, data, options \\ []) do def command(port, data, options \\ []) do
:erlang.port_command(port, data, options) :erlang.port_command(port, data, options)
@@ -26,6 +32,8 @@ defmodule Port do
@doc """ @doc """
See http://www.erlang.org/doc/man/erlang.html#port_connect-2. See http://www.erlang.org/doc/man/erlang.html#port_connect-2.
Inlined by the compiler.
""" """
def connect(port, pid) do def connect(port, pid) do
:erlang.port_connect(port, pid) :erlang.port_connect(port, pid)
@@ -33,6 +41,8 @@ defmodule Port do
@doc """ @doc """
See http://www.erlang.org/doc/man/erlang.html#port_control-3. See http://www.erlang.org/doc/man/erlang.html#port_control-3.
Inlined by the compiler.
""" """
def control(port, operation, data) do def control(port, operation, data) do
:erlang.port_control(port, operation, data) :erlang.port_control(port, operation, data)
@@ -40,6 +50,8 @@ defmodule Port do
@doc """ @doc """
See http://www.erlang.org/doc/man/erlang.html#port_call-3. See http://www.erlang.org/doc/man/erlang.html#port_call-3.
Inlined by the compiler.
""" """
def call(port, operation, data) do def call(port, operation, data) do
:erlang.port_call(port, operation, data) :erlang.port_call(port, operation, data)
@@ -47,6 +59,8 @@ defmodule Port do
@doc """ @doc """
See http://www.erlang.org/doc/man/erlang.html#port_info-1. See http://www.erlang.org/doc/man/erlang.html#port_info-1.
Inlined by the compiler.
""" """
def info(port) do def info(port) do
:erlang.port_info(port) :erlang.port_info(port)
@@ -54,6 +68,8 @@ defmodule Port do
@doc """ @doc """
See http://www.erlang.org/doc/man/erlang.html#port_info-2. See http://www.erlang.org/doc/man/erlang.html#port_info-2.
Inlined by the compiler.
""" """
def info(port, item) do def info(port, item) do
:erlang.port_info(port, item) :erlang.port_info(port, item)
@@ -61,6 +77,8 @@ defmodule Port do
@doc """ @doc """
See http://www.erlang.org/doc/man/erlang.html#ports-0. See http://www.erlang.org/doc/man/erlang.html#ports-0.
Inlined by the compiler.
""" """
def list do def list do
:erlang.ports :erlang.ports
+23 -23
View File
@@ -34,7 +34,7 @@ defmodule Process do
end end
@doc """ @doc """
Returns the value for the given key. Returns the value for the given `key`.
""" """
@spec get(term) :: term @spec get(term) :: term
@spec get(term, default :: term) :: term @spec get(term, default :: term) :: term
@@ -64,7 +64,7 @@ defmodule Process do
end end
@doc """ @doc """
Deletes the given key from the dictionary. Deletes the given `key` from the dictionary.
""" """
@spec delete(term) :: term | nil @spec delete(term) :: term | nil
def delete(key) do def delete(key) do
@@ -79,11 +79,11 @@ defmodule Process do
1. If pid is not trapping exits, pid will exit with the given reason. 1. If pid is not trapping exits, pid will exit with the given reason.
2. If pid is trapping exits, the exit signal is transformed into a message 2. If pid is trapping exits, the exit signal is transformed into a message
{:EXIT, from, reason} and delivered to the message queue of pid. `{:EXIT, from, reason}` and delivered to the message queue of pid.
3. If reason is the atom `:normal`, pid will not exit. If it is trapping 3. If reason is the atom `:normal`, pid will not exit. If it is trapping
exits, the exit signal is transformed into a message {:EXIT, from, exits, the exit signal is transformed into a message `{:EXIT, from,
:normal} and delivered to its message queue. :normal}` and delivered to its message queue.
4. If reason is the atom `:kill`, that is if `exit(pid, :kill)` is called, 4. If reason is the atom `:kill`, that is if `exit(pid, :kill)` is called,
an untrappable exit signal is sent to pid which will unconditionally an untrappable exit signal is sent to pid which will unconditionally
@@ -129,10 +129,10 @@ defmodule Process do
end end
@doc """ @doc """
Sends `msg` to `dest` after `time` millisecons. Sends `msg` to `dest` after `time` milliseconds.
If `dest` is a pid, it has to be a pid of a local process, dead or alive. If `dest` is a pid, it must be the pid of a local process, dead or alive.
If `dest` is an atom, it is supposed to be the name of a registered process If `dest` is an atom, it must be the name of a registered process
which is looked up at the time of delivery. No error is given if the name does which is looked up at the time of delivery. No error is given if the name does
not refer to a process. not refer to a process.
@@ -208,8 +208,8 @@ defmodule Process do
end end
@doc """ @doc """
If monitor_ref is a reference which the calling process If `monitor_ref` is a reference which the calling process
obtained by calling monitor/1, this monitoring is turned off. obtained by calling `monitor/1`, this monitoring is turned off.
If the monitoring is already turned off, nothing happens. If the monitoring is already turned off, nothing happens.
See http://www.erlang.org/doc/man/erlang.html#demonitor-2 for more info. See http://www.erlang.org/doc/man/erlang.html#demonitor-2 for more info.
@@ -227,7 +227,7 @@ defmodule Process do
processes currently existing on the local node. processes currently existing on the local node.
Note that a process that is exiting, exists but is not alive, i.e., Note that a process that is exiting, exists but is not alive, i.e.,
alive?/1 will return false for a process that is exiting, `alive?/1` will return `false` for a process that is exiting,
but its process identifier will be part of the result returned. but its process identifier will be part of the result returned.
See http://www.erlang.org/doc/man/erlang.html#processes-0 for more info. See http://www.erlang.org/doc/man/erlang.html#processes-0 for more info.
@@ -252,7 +252,7 @@ defmodule Process do
@doc """ @doc """
Removes the link, if there is one, between the calling process and Removes the link, if there is one, between the calling process and
the process or port referred to by `pid`. Returns true and does not the process or port referred to by `pid`. Returns `true` and does not
fail, even if there is no link or `id` does not exist fail, even if there is no link or `id` does not exist
See http://www.erlang.org/doc/man/erlang.html#unlink-1 for more info. See http://www.erlang.org/doc/man/erlang.html#unlink-1 for more info.
@@ -265,13 +265,13 @@ defmodule Process do
end end
@doc """ @doc """
Associates the name with a pid or a port identifier. name, which must Associates the name with a pid or a port identifier. `name`, which must
be an atom, can be used instead of the pid / port identifier with the be an atom, can be used instead of the pid / port identifier with the
`Kernel.send/2` function. `Kernel.send/2` function.
`Process.register/2` will fail with `ArgumentError` if the pid supplied `Process.register/2` will fail with `ArgumentError` if the pid supplied
is no longer alive, (check with `alive?/1`) or if the name is is no longer alive, (check with `alive?/1`) or if the name is
already registered (check with `registered?/1`). already registered (check with `whereis/1`).
""" """
@spec register(pid | port, atom) :: true @spec register(pid | port, atom) :: true
def register(pid, name) when not name in [nil, false, true] do def register(pid, name) when not name in [nil, false, true] do
@@ -290,7 +290,7 @@ defmodule Process do
@doc """ @doc """
Returns the pid or port identifier with the registered name. Returns the pid or port identifier with the registered name.
Returns nil if the name is not registered. Returns `nil` if the name is not registered.
See http://www.erlang.org/doc/man/erlang.html#whereis-1 for more info. See http://www.erlang.org/doc/man/erlang.html#whereis-1 for more info.
""" """
@@ -309,7 +309,7 @@ defmodule Process do
@doc """ @doc """
Sets the group leader of `pid` to `leader`. Typically, this is used when a processes Sets the group leader of `pid` to `leader`. Typically, this is used when a processes
started from a certain shell should have another group leader than `:init`. started from a certain shell should have a group leader other than `:init`.
""" """
@spec group_leader(pid, leader :: pid) :: true @spec group_leader(pid, leader :: pid) :: true
def group_leader(pid, leader) do def group_leader(pid, leader) do
@@ -317,7 +317,7 @@ defmodule Process do
end end
@doc """ @doc """
Returns a list of names which have been registered using register/2. Returns a list of names which have been registered using `register/2`.
""" """
@spec registered :: [atom] @spec registered :: [atom]
def registered do def registered do
@@ -339,9 +339,9 @@ defmodule Process do
end end
@doc """ @doc """
Sets certain flags for the process Pid, in the same manner as flag/2. Sets certain flags for the process `pid`, in the same manner as `flag/2`.
Returns the old value of the flag. The allowed values for Flag are Returns the old value of the flag. The allowed values for `flag` are
only a subset of those allowed in flag/2, namely: save_calls. only a subset of those allowed in `flag/2`, namely: `save_calls`.
See http://www.erlang.org/doc/man/erlang.html#process_flag-3 for more info. See http://www.erlang.org/doc/man/erlang.html#process_flag-3 for more info.
""" """
@@ -351,7 +351,7 @@ defmodule Process do
end end
@doc """ @doc """
Returns information about the process identified by pid or nil if the process Returns information about the process identified by `pid` or `nil` if the process
is not alive. is not alive.
Use this only for debugging information. Use this only for debugging information.
@@ -363,8 +363,8 @@ defmodule Process do
end end
@doc """ @doc """
Returns information about the process identified by pid Returns information about the process identified by `pid`
or nil if the process is not alive. or `nil` if the process is not alive.
See http://www.erlang.org/doc/man/erlang.html#process_info-2 for more info. See http://www.erlang.org/doc/man/erlang.html#process_info-2 for more info.
""" """
+40 -19
View File
@@ -85,11 +85,13 @@ defmodule Protocol do
Returns `:ok` if so, otherwise raises ArgumentError. Returns `:ok` if so, otherwise raises ArgumentError.
""" """
@spec assert_impl!(module, module) :: :ok | no_return @spec assert_impl!(module, module) :: :ok | no_return
def assert_impl!(protocol, impl) do def assert_impl!(protocol, base) do
assert_impl!(protocol, impl, "") assert_impl!(protocol, base, "")
end end
defp assert_impl!(protocol, impl, extra) do defp assert_impl!(protocol, base, extra) do
impl = Module.concat(protocol, base)
case Code.ensure_compiled(impl) do case Code.ensure_compiled(impl) do
{:module, ^impl} -> :ok {:module, ^impl} -> :ok
_ -> raise ArgumentError, _ -> raise ArgumentError,
@@ -426,7 +428,7 @@ defmodule Protocol do
defp after_defprotocol do defp after_defprotocol do
quote bind_quoted: [builtin: builtin] do quote bind_quoted: [builtin: builtin] do
@spec impl_for(term) :: module | nil @spec impl_for(term) :: atom() | nil
Kernel.def impl_for(data) Kernel.def impl_for(data)
# Define the implementation for structs. # Define the implementation for structs.
@@ -449,7 +451,7 @@ defmodule Protocol do
end end
end end
@spec impl_for!(term) :: module | no_return @spec impl_for!(term) :: atom() | no_return()
Kernel.def impl_for!(data) do Kernel.def impl_for!(data) do
impl_for(data) || raise(Protocol.UndefinedError, protocol: __MODULE__, value: data) impl_for(data) || raise(Protocol.UndefinedError, protocol: __MODULE__, value: data)
end end
@@ -494,12 +496,19 @@ defmodule Protocol do
@protocol [fallback_to_any: !!@fallback_to_any, consolidated: false] @protocol [fallback_to_any: !!@fallback_to_any, consolidated: false]
@doc false @doc false
@spec __protocol__(atom) :: term @spec __protocol__(:name) :: __MODULE__
Kernel.def __protocol__(:name), do: __MODULE__ @spec __protocol__(:functions) :: unquote(Protocol.__functions_spec__(@functions))
Kernel.def __protocol__(:name), do: __MODULE__
Kernel.def __protocol__(:functions), do: unquote(:lists.sort(@functions)) Kernel.def __protocol__(:functions), do: unquote(:lists.sort(@functions))
end end
end end
@doc false
def __functions_spec__([]),
do: []
def __functions_spec__([h|t]),
do: [:lists.foldl(&{:|, [], [&1, &2]}, h, t), quote(do: ...)]
@doc false @doc false
def __impl__(protocol, opts) do def __impl__(protocol, opts) do
do_defimpl(protocol, :lists.keysort(1, opts)) do_defimpl(protocol, :lists.keysort(1, opts))
@@ -510,6 +519,20 @@ defmodule Protocol do
end end
defp do_defimpl(protocol, [do: block, for: for]) do defp do_defimpl(protocol, [do: block, for: for]) do
# Unquote the implementation just later
# when all variables will already be injected
# into the module body.
__impl__ =
quote unquote: false do
@doc false
@spec __impl__(:for) :: unquote(for)
@spec __impl__(:target) :: __MODULE__
@spec __impl__(:protocol) :: unquote(protocol)
def __impl__(:for), do: unquote(for)
def __impl__(:target), do: __MODULE__
def __impl__(:protocol), do: unquote(protocol)
end
quote do quote do
protocol = unquote(protocol) protocol = unquote(protocol)
for = unquote(for) for = unquote(for)
@@ -527,11 +550,7 @@ defmodule Protocol do
Module.register_attribute(__MODULE__, :impl, persist: true) Module.register_attribute(__MODULE__, :impl, persist: true)
@impl [protocol: @protocol, for: @for] @impl [protocol: @protocol, for: @for]
@doc false unquote(__impl__)
@spec __impl__(atom) :: term
def __impl__(:target), do: __MODULE__
def __impl__(:protocol), do: @protocol
def __impl__(:for), do: @for
end end
end end
end end
@@ -557,14 +576,14 @@ defmodule Protocol do
end end
defp derive(protocol, for, struct, opts, env) do defp derive(protocol, for, struct, opts, env) do
impl = Module.concat(protocol, Map)
extra = ", cannot derive #{inspect protocol} for #{inspect for}" extra = ", cannot derive #{inspect protocol} for #{inspect for}"
assert_protocol!(protocol, extra) assert_protocol!(protocol, extra)
assert_impl!(protocol, impl, extra) assert_impl!(protocol, Map, extra)
# Clean up variables from eval context # Clean up variables from eval context
env = %{env | vars: [], export_vars: nil} env = %{env | vars: [], export_vars: nil}
args = [for, struct, opts] args = [for, struct, opts]
impl = Module.concat(protocol, Map)
:elixir_module.expand_callback(env.line, impl, :__deriving__, args, env, fn :elixir_module.expand_callback(env.line, impl, :__deriving__, args, env, fn
mod, fun, args -> mod, fun, args ->
@@ -576,7 +595,9 @@ defmodule Protocol do
@impl [protocol: unquote(protocol), for: unquote(for)] @impl [protocol: unquote(protocol), for: unquote(for)]
@doc false @doc false
@spec __impl__(atom) :: term @spec __impl__(:target) :: unquote(impl)
@spec __impl__(:protocol) :: unquote(protocol)
@spec __impl__(:for) :: unquote(for)
def __impl__(:target), do: unquote(impl) def __impl__(:target), do: unquote(impl)
def __impl__(:protocol), do: unquote(protocol) def __impl__(:protocol), do: unquote(protocol)
def __impl__(:for), do: unquote(for) def __impl__(:for), do: unquote(for)
@@ -592,10 +613,10 @@ defmodule Protocol do
found = found =
for {:spec, expr, caller} <- specs, for {:spec, expr, caller} <- specs,
Kernel.Typespec.spec_to_signature(expr) == signature do Kernel.Typespec.spec_to_signature(expr) == signature do
Kernel.Typespec.define_spec(:callback, expr, caller) Kernel.Typespec.define_spec(:callback, expr, caller)
true true
end end
found != [] found != []
end end
+3 -2
View File
@@ -2,7 +2,7 @@ defmodule Range do
@moduledoc """ @moduledoc """
Defines a Range. Defines a Range.
A Range are represented internally as a struct. However, A Range is represented internally as a struct. However,
the most common form of creating and matching on ranges the most common form of creating and matching on ranges
is via the `../2` macro, auto-imported from Kernel: is via the `../2` macro, auto-imported from Kernel:
@@ -18,7 +18,8 @@ defmodule Range do
defstruct first: nil, last: nil defstruct first: nil, last: nil
@type t(first, last) :: %{__struct__: Range, first: first, last: last} @type t :: %Range{}
@type t(first, last) :: %Range{first: first, last: last}
@doc """ @doc """
Creates a new range. Creates a new range.
+53 -10
View File
@@ -4,7 +4,7 @@ defmodule Record do
Records are simply tuples where the first element is an atom: Records are simply tuples where the first element is an atom:
iex> Record.record? {User, "jose", 27} iex> Record.record? {User, "john", 27}
true true
This module provides conveniences for working with records at This module provides conveniences for working with records at
@@ -20,6 +20,20 @@ defmodule Record do
The macros `defrecord/3` and `defrecordp/3` can be used to create The macros `defrecord/3` and `defrecordp/3` can be used to create
records while `extract/2` can be used to extract records from Erlang records while `extract/2` can be used to extract records from Erlang
files. files.
## Types
Types can be defined for tuples with the `record/2` macro (only available
in typespecs). Like with the generated record macros it will expand to
a tuple.
defmodule MyModule do
require Record
Record.defrecord :user name: "john", age: 25
@type user :: record(:user, name: String.t, age: integer)
# expands to: `@type user :: {:user, String.t, integer}`
end
""" """
@doc """ @doc """
@@ -49,7 +63,7 @@ defmodule Record do
## Examples ## Examples
iex> record = {User, "jose", 27} iex> record = {User, "john", 27}
iex> Record.record?(record, User) iex> Record.record?(record, User)
true true
@@ -77,7 +91,7 @@ defmodule Record do
## Examples ## Examples
iex> record = {User, "jose", 27} iex> record = {User, "john", 27}
iex> Record.record?(record) iex> Record.record?(record)
true true
iex> tuple = {} iex> tuple = {}
@@ -111,26 +125,31 @@ defmodule Record do
## Examples ## Examples
defmodule User do defmodule User do
Record.defrecord :user, [name: "José", age: "25"] require Record
Record.defrecord :user, [name: "meg", age: "25"]
end end
In the example above, a set of macros named `user` but with different In the example above, a set of macros named `user` but with different
arities will be defined to manipulate the underlying record: arities will be defined to manipulate the underlying record:
# To create records # To create records
user() #=> {:user, "José", 25} record = user() #=> {:user, "meg", 25}
user(age: 26) #=> {:user, "José", 26} record = user(age: 26) #=> {:user, "meg", 26}
# To get a field from the record # To get a field from the record
user(record, :name) #=> "José" user(record, :name) #=> "meg"
# To update the record # To update the record
user(record, age: 26) #=> {:user, "José", 26} user(record, age: 26) #=> {:user, "meg", 26}
# Convert a record to a keyword list
user(record) #=> [name: "meg", age: 26]
By default, Elixir uses the record name as the first element of By default, Elixir uses the record name as the first element of
the tuple (the tag). But it can be changed to something else: the tuple (the tag). But it can be changed to something else:
defmodule User do defmodule User do
require Record
Record.defrecord :user, User, name: nil Record.defrecord :user, User, name: nil
end end
@@ -190,8 +209,15 @@ defmodule Record do
Keyword.keyword?(args) -> Keyword.keyword?(args) ->
create(atom, fields, args, caller) create(atom, fields, args, caller)
true -> true ->
msg = "expected arguments to be a compile time atom or keywords, got: #{Macro.to_string args}" case Macro.expand(args, caller) do
raise ArgumentError, msg {:{}, _, [^atom|list]} when length(list) == length(fields) ->
record = List.to_tuple([atom|list])
Macro.escape(Record.__keyword__(atom, fields, record))
{^atom, arg} when length(fields) == 1 ->
Macro.escape(Record.__keyword__(atom, fields, {atom, arg}))
_ ->
quote do: Record.__keyword__(unquote(atom), unquote(fields), unquote(args))
end
end end
end end
@@ -279,4 +305,21 @@ defmodule Record do
defp find_index([{k, _}|_], k, i), do: i + 2 defp find_index([{k, _}|_], k, i), do: i + 2
defp find_index([{_, _}|t], k, i), do: find_index(t, k, i + 1) defp find_index([{_, _}|t], k, i), do: find_index(t, k, i + 1)
defp find_index([], _k, _i), do: nil defp find_index([], _k, _i), do: nil
# Returns a keyword list of the record
@doc false
def __keyword__(atom, fields, record) do
if record?(record, atom) do
[_tag|values] = Tuple.to_list(record)
join_keyword(fields, values, [])
else
msg = "expected argument to be a literal atom, literal keyword or a #{inspect atom} record, got runtime: #{inspect record}"
raise ArgumentError, msg
end
end
defp join_keyword([{field, _default}|fields], [value|values], acc),
do: join_keyword(fields, values, [{field, value}| acc])
defp join_keyword([], [], acc),
do: :lists.reverse(acc)
end end
+71 -34
View File
@@ -22,8 +22,9 @@ defmodule Regex do
The modifiers available when creating a Regex are: The modifiers available when creating a Regex are:
* `unicode` (u) - enables unicode specific patterns like `\p`; it expects * `unicode` (u) - enables unicode specific patterns like `\p` and changes
valid unicode strings to be given on match modifiers like `\w`, `\W`, `\s` and friends to also match on unicode.
It expects valid unicode strings to be given on match
* `caseless` (i) - add case insensitivity * `caseless` (i) - add case insensitivity
@@ -75,7 +76,9 @@ defmodule Regex do
""" """
defstruct re_pattern: nil :: term, source: "" :: binary, opts: "" :: binary defstruct re_pattern: nil, source: "", opts: ""
@opaque t :: %__MODULE__{re_pattern: term, source: binary, opts: binary}
defmodule CompileError do defmodule CompileError do
defexception message: "regex could not be compiled" defexception message: "regex could not be compiled"
@@ -104,7 +107,7 @@ defmodule Regex do
def compile(source, options \\ "") def compile(source, options \\ "")
def compile(source, options) when is_binary(options) do def compile(source, options) when is_binary(options) do
case translate_options(options) do case translate_options(options, []) do
{:error, rest} -> {:error, rest} ->
{:error, {:invalid_option, rest}} {:error, {:invalid_option, rest}}
@@ -318,12 +321,17 @@ defmodule Regex do
## Options ## Options
* `:parts` - when specified, splits the string into the given number of * `:parts` - when specified, splits the string into the given number of
parts. If not specified, `:parts` is defaulted to `:infinity`, which will parts. If not specified, `:parts` defaults to `:infinity`, which will
split the string into the maximum number of parts possible based on the split the string into the maximum number of parts possible based on the
given pattern. given pattern.
* `:trim` - when true, remove blank strings from the result. * `:trim` - when true, remove blank strings from the result.
* `:on` - specifies which captures and order to split the string
on. Check the moduledoc for `Regex` to see the possible capture
values. Defaults to `:first` which means captures inside the
Regex does not affect the split result.
## Examples ## Examples
iex> Regex.split(~r/-/, "a-b-c") iex> Regex.split(~r/-/, "a-b-c")
@@ -338,27 +346,61 @@ defmodule Regex do
iex> Regex.split(~r//, "abc") iex> Regex.split(~r//, "abc")
["a", "b", "c", ""] ["a", "b", "c", ""]
iex> Regex.split(~r//, "abc", trim: true) iex> Regex.split(~r/a(?<second>b)c/, "abc")
["a", "b", "c"] ["", ""]
iex> Regex.split(~r/a(?<second>b)c/, "abc", on: [:second])
["a", "c"]
""" """
def split(regex, string, options \\ []) def split(regex, string, options \\ [])
def split(%Regex{re_pattern: compiled}, string, options) when is_binary(string) do def split(%Regex{}, "", _opts), do: [""]
parts = Keyword.get(options, :parts, :infinity)
opts = [return: :binary, parts: zero_to_infinity(parts)]
splits = :re.split(string, compiled, opts)
if Keyword.get(options, :trim, false) do def split(%Regex{re_pattern: compiled}, string, opts) when is_binary(string) do
for split <- splits, split != "", do: split on = Keyword.get(opts, :on, :first)
else case :re.run(string, compiled, [:global, capture: on]) do
splits {:match, matches} ->
do_split(matches, string, 0,
parts_to_index(Keyword.get(opts, :parts, :infinity)),
Keyword.get(opts, :trim, false))
:match ->
[string]
:nomatch ->
[string]
end end
end end
defp zero_to_infinity(0), do: :infinity defp parts_to_index(:infinity), do: 0
defp zero_to_infinity(n), do: n defp parts_to_index(n) when is_integer(n) and n > 0, do: n
defp do_split(_, string, offset, _counter, true) when byte_size(string) <= offset,
do: []
defp do_split(_, string, offset, 1, _trim),
do: [binary_part(string, offset, byte_size(string) - offset)]
defp do_split([], string, offset, _counter, _trim),
do: [binary_part(string, offset, byte_size(string) - offset)]
defp do_split([[{pos, _}|h]|t], string, offset, counter, trim) when pos - offset < 0,
do: do_split([h|t], string, offset, counter, trim)
defp do_split([[]|t], string, offset, counter, trim),
do: do_split(t, string, offset, counter, trim)
defp do_split([[{pos, length}|h]|t], string, offset, counter, trim) do
new_offset = pos + length
keep = pos - offset
if keep == 0 and (length == 0 or trim) do
do_split([h|t], string, new_offset, counter, trim)
else
<<_::binary-size(offset), part::binary-size(keep), _::binary>> = string
[part|do_split([h|t], string, new_offset, counter - 1, trim)]
end
end
@doc ~S""" @doc ~S"""
Receives a regex, a binary and a replacement, returns a new Receives a regex, a binary and a replacement, returns a new
@@ -471,12 +513,12 @@ defmodule Regex do
defp apply_list(whole, string, pos, replacement, [[{mpos, _} | _] | _] = list) when mpos > pos do defp apply_list(whole, string, pos, replacement, [[{mpos, _} | _] | _] = list) when mpos > pos do
length = mpos - pos length = mpos - pos
<<untouched :: [size(length), binary], rest :: binary>> = string <<untouched :: binary-size(length), rest :: binary>> = string
[untouched | apply_list(whole, rest, mpos, replacement, list)] [untouched | apply_list(whole, rest, mpos, replacement, list)]
end end
defp apply_list(whole, string, pos, replacement, [[{mpos, length} | _] = head | tail]) when mpos == pos do defp apply_list(whole, string, pos, replacement, [[{mpos, length} | _] = head | tail]) when mpos == pos do
<<_ :: [size(length), binary], rest :: binary>> = string <<_ :: size(length)-binary, rest :: binary>> = string
new_data = apply_replace(whole, replacement, head) new_data = apply_replace(whole, replacement, head)
[new_data | apply_list(whole, rest, pos + length, replacement, tail)] [new_data | apply_list(whole, rest, pos + length, replacement, tail)]
end end
@@ -509,7 +551,7 @@ defmodule Regex do
end end
defp get_index(string, {pos, len}) do defp get_index(string, {pos, len}) do
<<_ :: [size(pos), binary], res :: [size(len), binary], _ :: binary>> = string <<_ :: size(pos)-binary, res :: size(len)-binary, _ :: binary>> = string
res res
end end
@@ -559,18 +601,13 @@ defmodule Regex do
# Private Helpers # Private Helpers
defp translate_options(<<?g, t :: binary>>) do defp translate_options(<<?u, t :: binary>>, acc), do: translate_options(t, [:unicode, :ucp|acc])
IO.write :stderr, "The /g flag for regular expressions is no longer needed\n#{Exception.format_stacktrace}" defp translate_options(<<?i, t :: binary>>, acc), do: translate_options(t, [:caseless|acc])
translate_options(t) defp translate_options(<<?x, t :: binary>>, acc), do: translate_options(t, [:extended|acc])
end defp translate_options(<<?f, t :: binary>>, acc), do: translate_options(t, [:firstline|acc])
defp translate_options(<<?r, t :: binary>>, acc), do: translate_options(t, [:ungreedy|acc])
defp translate_options(<<?u, t :: binary>>), do: [:unicode|translate_options(t)] defp translate_options(<<?s, t :: binary>>, acc), do: translate_options(t, [:dotall, {:newline, :anycrlf}|acc])
defp translate_options(<<?i, t :: binary>>), do: [:caseless|translate_options(t)] defp translate_options(<<?m, t :: binary>>, acc), do: translate_options(t, [:multiline|acc])
defp translate_options(<<?x, t :: binary>>), do: [:extended|translate_options(t)] defp translate_options(<<>>, acc), do: acc
defp translate_options(<<?f, t :: binary>>), do: [:firstline|translate_options(t)] defp translate_options(rest, _acc), do: {:error, rest}
defp translate_options(<<?r, t :: binary>>), do: [:ungreedy|translate_options(t)]
defp translate_options(<<?s, t :: binary>>), do: [:dotall, {:newline, :anycrlf}|translate_options(t)]
defp translate_options(<<?m, t :: binary>>), do: [:multiline|translate_options(t)]
defp translate_options(<<>>), do: []
defp translate_options(rest), do: {:error, rest}
end end
+33 -17
View File
@@ -89,12 +89,14 @@ defmodule Stream do
returns an anonymous function may return a struct in future releases. returns an anonymous function may return a struct in future releases.
""" """
@doc false
defstruct enum: nil, funs: [], accs: [], done: nil defstruct enum: nil, funs: [], accs: [], done: nil
@type acc :: any @type acc :: any
@type element :: any @type element :: any
@type index :: non_neg_integer @type index :: non_neg_integer
@type default :: any @type default :: any
@type t :: %__MODULE__{}
# Require Stream.Reducers and its callbacks # Require Stream.Reducers and its callbacks
require Stream.Reducers, as: R require Stream.Reducers, as: R
@@ -427,7 +429,7 @@ defmodule Stream do
""" """
@spec run(Enumerable.t) :: :ok @spec run(Enumerable.t) :: :ok
def run(stream) do def run(stream) do
Enumerable.reduce(stream, {:cont, nil}, fn(_, _) -> {:cont, nil} end) _ = Enumerable.reduce(stream, {:cont, nil}, fn(_, _) -> {:cont, nil} end)
:ok :ok
end end
@@ -616,12 +618,14 @@ defmodule Stream do
{[], user_acc} -> {[], user_acc} ->
do_transform(user_acc, user, fun, next_acc, next, inner_acc, inner) do_transform(user_acc, user, fun, next_acc, next, inner_acc, inner)
{list, user_acc} when is_list(list) -> {list, user_acc} when is_list(list) ->
do_list_transform(user_acc, user, fun, next_acc, next, inner_acc, inner, &Enumerable.List.reduce(list, &1, fun)) do_list_transform(user_acc, user, fun, next_acc, next, inner_acc, inner,
&Enumerable.List.reduce(list, &1, fun))
{:halt, _user_acc} -> {:halt, _user_acc} ->
next.({:halt, next_acc}) next.({:halt, next_acc})
{:halted, elem(inner_acc, 1)} {:halted, elem(inner_acc, 1)}
{other, user_acc} -> {other, user_acc} ->
do_other_transform(user_acc, user, fun, next_acc, next, inner_acc, inner, &Enumerable.reduce(other, &1, inner)) do_enum_transform(user_acc, user, fun, next_acc, next, inner_acc, inner,
&Enumerable.reduce(other, &1, inner))
end end
{reason, _} -> {reason, _} ->
{reason, elem(inner_acc, 1)} {reason, elem(inner_acc, 1)}
@@ -633,8 +637,9 @@ defmodule Stream do
reduce.(inner_acc) reduce.(inner_acc)
catch catch
kind, reason -> kind, reason ->
stacktrace = System.stacktrace
next.({:halt, next_acc}) next.({:halt, next_acc})
:erlang.raise(kind, reason, :erlang.get_stacktrace) :erlang.raise(kind, reason, stacktrace)
else else
{:done, acc} -> {:done, acc} ->
do_transform(user_acc, user, fun, next_acc, next, {:cont, acc}, inner) do_transform(user_acc, user, fun, next_acc, next, {:cont, acc}, inner)
@@ -646,29 +651,31 @@ defmodule Stream do
end end
end end
defp do_other_transform(user_acc, user, fun, next_acc, next, inner_acc, inner, reduce) do defp do_enum_transform(user_acc, user, fun, next_acc, next, {op, inner_acc}, inner, reduce) do
try do try do
reduce.(inner_acc) reduce.({op, [:outer|inner_acc]})
catch catch
{:stream_transform, h} ->
next.({:halt, next_acc})
{:halted, h}
kind, reason -> kind, reason ->
stacktrace = System.stacktrace stacktrace = System.stacktrace
next.({:halt, next_acc}) next.({:halt, next_acc})
:erlang.raise(kind, reason, stacktrace) :erlang.raise(kind, reason, stacktrace)
else else
{_, acc} -> {:halted, [:outer|acc]} ->
do_transform(user_acc, user, fun, next_acc, next, {:cont, acc}, inner) do_transform(user_acc, user, fun, next_acc, next, {:cont, acc}, inner)
{:suspended, acc, c} -> {:halted, [:inner|acc]} ->
{:suspended, acc, &do_other_transform(user_acc, user, fun, next_acc, next, &1, inner, c)} next.({:halt, next_acc})
{:halted, acc}
{:done, [_|acc]} ->
do_transform(user_acc, user, fun, next_acc, next, {:cont, acc}, inner)
{:suspended, [_|acc], c} ->
{:suspended, acc, &do_enum_transform(user_acc, user, fun, next_acc, next, &1, inner, c)}
end end
end end
defp do_transform_each(x, acc, f) do defp do_transform_each(x, [:outer|acc], f) do
case f.(x, acc) do case f.(x, acc) do
{:halt, h} -> throw({:stream_transform, h}) {:halt, res} -> {:halt, [:inner|res]}
{_, _} = o -> o {op, res} -> {op, [:outer|res]}
end end
end end
@@ -1082,3 +1089,12 @@ defimpl Enumerable, for: Stream do
end end
end end
end end
defimpl Inspect, for: Stream do
import Inspect.Algebra
def inspect(%{enum: enum, funs: funs}, opts) do
inner = [enum: enum, funs: Enum.reverse(funs)]
concat ["#Stream<", to_doc(inner, opts), ">"]
end
end
+123 -88
View File
@@ -71,29 +71,29 @@ defmodule String do
Although codepoints could be represented as integers, this Although codepoints could be represented as integers, this
module represents all codepoints as strings. For example: module represents all codepoints as strings. For example:
iex> String.codepoints("josé") iex> String.codepoints("olá")
["j", "o", "s", "é"] ["o", "l", "á"]
There are a couple of ways to retrieve a character integer There are a couple of ways to retrieve a character integer
codepoint. One may use the `?` special macro: codepoint. One may use the `?` construct:
iex> ?j iex> ?o
106 111
iex> ?é iex> ?á
233 225
Or also via pattern matching: Or also via pattern matching:
iex> << eacute :: utf8 >> = "é" iex> << eacute :: utf8 >> = "á"
iex> eacute iex> eacute
233 225
As we have seen above, codepoints can be inserted into As we have seen above, codepoints can be inserted into
a string by their hexadecimal code: a string by their hexadecimal code:
"jos\x{0065}\x{0301}" #=> "ol\x{0061}\x{0301}" #=>
"josé" "olá"
## Self-synchronization ## Self-synchronization
@@ -129,7 +129,7 @@ defmodule String do
@spec printable?(t) :: boolean @spec printable?(t) :: boolean
def printable?(<< h :: utf8, t :: binary >>) def printable?(<< h :: utf8, t :: binary >>)
when h in ?\040..?\176 when h in 0x20..0x7E
when h in 0xA0..0xD7FF when h in 0xA0..0xD7FF
when h in 0xE000..0xFFFD when h in 0xE000..0xFFFD
when h in 0x10000..0x10FFFF do when h in 0x10000..0x10FFFF do
@@ -227,40 +227,53 @@ defmodule String do
""" """
@spec split(t, t | [t] | Regex.t) :: [t] @spec split(t, t | [t] | Regex.t) :: [t]
@spec split(t, t | [t] | Regex.t, Keyword.t) :: [t] @spec split(t, t | [t] | Regex.t, Keyword.t) :: [t]
def split(binary, pattern, options \\ []) def split(string, pattern, options \\ [])
def split("", _pattern, _options), do: [""] def split(string, "", options) do
parts = Keyword.get(options, :parts, :infinity)
split_codepoints(string, parts_to_index(parts), Keyword.get(options, :trim, false))
end
def split(binary, "", options), do: split(binary, ~r""u, options) def split(string, pattern, options) do
def split(binary, pattern, options) do
if Regex.regex?(pattern) do if Regex.regex?(pattern) do
Regex.split(pattern, binary, options) Regex.split(pattern, string, options)
else else
splits = parts = Keyword.get(options, :parts, :infinity)
case Keyword.get(options, :parts, :infinity) do trim = Keyword.get(options, :trim, false)
num when is_number(num) and num > 0 -> if parts == :infinity and trim == false do
split_parts(binary, pattern, num - 1) :binary.split(string, pattern, [:global])
_ ->
:binary.split(binary, pattern, [:global])
end
if Keyword.get(options, :trim, false) do
for split <- splits, split != "", do: split
else else
splits split_parts(string, pattern, parts_to_index(parts), trim)
end end
end end
end end
defp split_parts("", _pattern, _num), do: [""] defp parts_to_index(:infinity), do: 0
defp split_parts(binary, pattern, num), do: split_parts(binary, pattern, num, []) defp parts_to_index(n) when is_integer(n) and n > 0, do: n
defp split_parts("", _pattern, _num, parts), do: Enum.reverse([""|parts])
defp split_parts(binary, _pattern, 0, parts), do: Enum.reverse([binary|parts]) defp split_codepoints(binary, 1, _trim), do: [binary]
defp split_parts(binary, pattern, num, parts) do defp split_codepoints(<<h :: utf8, t :: binary>>, count, trim),
case :binary.split(binary, pattern) do do: [<<h :: utf8>>|split_codepoints(t, count - 1, trim)]
[head] -> Enum.reverse([head|parts]) defp split_codepoints(<<h, t :: binary>>, count, trim),
[head, rest] -> split_parts(rest, pattern, num - 1, [head|parts]) do: [<<h>>|split_codepoints(t, count - 1, trim)]
defp split_codepoints(<<>>, _, true), do: []
defp split_codepoints(<<>>, _, false), do: [""]
defp split_parts("", _pattern, _num, true), do: []
defp split_parts("", _pattern, _num, _trim), do: [""]
defp split_parts(string, _pattern, 1, _trim), do: [string]
defp split_parts(string, pattern, num, trim) do
case :binary.split(string, pattern) do
[""] when trim ->
[]
[head] ->
[head]
[head, tail] ->
if trim and head == "" do
split_parts(tail, pattern, num, trim)
else
[head|split_parts(tail, pattern, num-1, trim)]
end
end end
end end
@@ -322,8 +335,8 @@ defmodule String do
iex> String.upcase("ab 123 xpto") iex> String.upcase("ab 123 xpto")
"AB 123 XPTO" "AB 123 XPTO"
iex> String.upcase("josé") iex> String.upcase("olá")
"JOSÉ" "OLÁ"
""" """
@spec upcase(t) :: t @spec upcase(t) :: t
@@ -340,8 +353,8 @@ defmodule String do
iex> String.downcase("AB 123 XPTO") iex> String.downcase("AB 123 XPTO")
"ab 123 xpto" "ab 123 xpto"
iex> String.downcase("JOSÉ") iex> String.downcase("OLÁ")
"josé" "olá"
""" """
@spec downcase(t) :: t @spec downcase(t) :: t
@@ -364,8 +377,8 @@ defmodule String do
iex> String.capitalize("fin") iex> String.capitalize("fin")
"Fin" "Fin"
iex> String.capitalize("josé") iex> String.capitalize("olá")
"José" "Olá"
""" """
@spec capitalize(t) :: t @spec capitalize(t) :: t
@@ -405,7 +418,7 @@ defmodule String do
# does not traverse the whole binary). # does not traverse the whole binary).
def rstrip(string, char) when char in 0..127 do def rstrip(string, char) when char in 0..127 do
if :binary.last(string) == char do if :binary.last(string) == char do
do_rstrip(string, "", char) rstrip(binary_part(string, 0, byte_size(string) - 1), char)
else else
string string
end end
@@ -672,8 +685,8 @@ defmodule String do
## Examples ## Examples
iex> String.codepoints("josé") iex> String.codepoints("olá")
["j", "o", "s", "é"] ["o", "l", "á"]
iex> String.codepoints("оптими зации") iex> String.codepoints("оптими зации")
["о","п","т","и","м","и"," ","з","а","ц","и","и"] ["о","п","т","и","м","и"," ","з","а","ц","и","и"]
@@ -699,8 +712,8 @@ defmodule String do
## Examples ## Examples
iex> String.next_codepoint("josé") iex> String.next_codepoint("olá")
{"j", "osé"} {"o", "lá"}
""" """
@compile {:inline, next_codepoint: 1} @compile {:inline, next_codepoint: 1}
@@ -786,10 +799,10 @@ defmodule String do
## Examples ## Examples
iex> String.chunk(<<?a, ?b, ?c, 0>>, :valid) iex> String.chunk(<<?a, ?b, ?c, 0>>, :valid)
["abc\000"] ["abc\0"]
iex> String.chunk(<<?a, ?b, ?c, 0, 0x0ffff::utf8>>, :valid) iex> String.chunk(<<?a, ?b, ?c, 0, 0x0ffff::utf8>>, :valid)
["abc\000", <<0x0ffff::utf8>>] ["abc\0", <<0x0ffff::utf8>>]
iex> String.chunk(<<?a, ?b, ?c, 0, 0x0ffff::utf8>>, :printable) iex> String.chunk(<<?a, ?b, ?c, 0, 0x0ffff::utf8>>, :printable)
["abc", <<0, 0x0ffff::utf8>>] ["abc", <<0, 0x0ffff::utf8>>]
@@ -849,8 +862,8 @@ defmodule String do
## Examples ## Examples
iex> String.next_grapheme("josé") iex> String.next_grapheme("olá")
{"j", "osé"} {"o", "lá"}
""" """
@compile {:inline, next_grapheme: 1} @compile {:inline, next_grapheme: 1}
@@ -974,8 +987,13 @@ defmodule String do
@doc """ @doc """
Returns a substring starting at the offset given by the first, and Returns a substring starting at the offset given by the first, and
a length given by the second. a length given by the second.
If the offset is greater than string length, then it returns `""`. If the offset is greater than string length, then it returns `""`.
Remember this function works with unicode codepoints and consider
the slices to represent codepoints offsets. If you want to split
on raw bytes, check `Kernel.binary_part/3` instead.
## Examples ## Examples
iex> String.slice("elixir", 1, 3) iex> String.slice("elixir", 1, 3)
@@ -1010,17 +1028,29 @@ defmodule String do
end end
def slice(string, start, len) when start >= 0 and len >= 0 do def slice(string, start, len) when start >= 0 and len >= 0 do
do_slice(next_grapheme(string), start, start + len - 1, 0, "") case do_count_bytes(next_grapheme(string), start, 0) do
{nil, _} -> ""
{next, start_bytes} ->
{_, len_bytes} = do_count_bytes(next, len, 0)
binary_part(string, start_bytes, len_bytes)
end
end end
def slice(string, start, len) when start < 0 and len >= 0 do def slice(string, start, len) when start < 0 and len >= 0 do
real_start_pos = length(string) - abs(start) start = length(string) + start
case real_start_pos >= 0 do case start >= 0 do
true -> do_slice(next_grapheme(string), real_start_pos, real_start_pos + len - 1, 0, "") true -> slice(string, start, len)
false -> "" false -> ""
end end
end end
defp do_count_bytes(next, 0, acc), do: {next, acc}
defp do_count_bytes(nil, _, acc), do: {nil, acc}
defp do_count_bytes({char, rest}, counter, acc) do
do_count_bytes(next_grapheme(rest), counter - 1, acc + byte_size(char))
end
@doc """ @doc """
Returns a substring from the offset given by the start of the Returns a substring from the offset given by the start of the
range to the offset given by the end of the range. range to the offset given by the end of the range.
@@ -1028,6 +1058,14 @@ defmodule String do
If the start of the range is not a valid offset for the given If the start of the range is not a valid offset for the given
string or if the range is in reverse order, returns `""`. string or if the range is in reverse order, returns `""`.
If the start or end of the range are negative, the whole string
is traversed first in order to convert the negative indexes into
positive ones.
Remember this function works with unicode codepoints and consider
the slices to represent codepoints offsets. If you want to split
on raw bytes, check `Kernel.binary_part/3` instead.
## Examples ## Examples
iex> String.slice("elixir", 1..3) iex> String.slice("elixir", 1..3)
@@ -1060,54 +1098,51 @@ defmodule String do
iex> String.slice("a", 1..1500) iex> String.slice("a", 1..1500)
"" ""
iex> String.slice("a", 2..1500)
""
""" """
@spec slice(t, Range.t) :: t @spec slice(t, Range.t) :: t
def slice(string, range) def slice(string, range)
def slice(string, first..last) when first >= 0 and last >= 0 do def slice("", _.._), do: ""
do_slice(next_grapheme(string), first, last, 0, "")
def slice(string, first..-1) when first >= 0 do
case do_count_bytes(next_grapheme(string), first, 0) do
{nil, _} -> ""
{_, start_bytes} -> binary_part(string, start_bytes, byte_size(string) - start_bytes)
end
end end
def slice(string, first..last) do def slice(string, first..last) when first >= 0 and last >= 0 do
total = length(string) if last >= first do
slice(string, first, last - first + 1)
if first < 0 do
first = total + first
end
if last < 0 do
last = total + last
end
if first >= 0 do
do_slice(next_grapheme(string), first, last, 0, "")
else else
"" ""
end end
end end
defp do_slice(_, start_pos, last_pos, _, _) when start_pos > last_pos do def slice(string, first..last) do
"" {bytes, length} = do_acc_bytes(next_grapheme(string), [], 0)
if first < 0, do: first = length + first
if last < 0, do: last = length + last
if first < 0 or first > last or first > length do
""
else
last = min(last + 1, length)
bytes = Enum.drop(bytes, length - last)
first = last - first
{length_bytes, start_bytes} = Enum.split(bytes, first)
binary_part(string, Enum.sum(start_bytes), Enum.sum(length_bytes))
end
end end
defp do_slice({_, rest}, start_pos, last_pos, current_pos, acc) when current_pos < start_pos do defp do_acc_bytes({char, rest}, bytes, length) do
do_slice(next_grapheme(rest), start_pos, last_pos, current_pos + 1, acc) do_acc_bytes(next_grapheme(rest), [byte_size(char)|bytes], length + 1)
end end
defp do_slice({char, rest}, start_pos, last_pos, current_pos, acc) when current_pos >= start_pos and current_pos < last_pos do defp do_acc_bytes(nil, bytes, length) do
do_slice(next_grapheme(rest), start_pos, last_pos, current_pos + 1, acc <> char) {bytes, length}
end
defp do_slice({char, _}, start_pos, last_pos, current_pos, acc) when current_pos >= start_pos and current_pos == last_pos do
acc <> char
end
defp do_slice(nil, _, _, _, acc) do
acc
end end
@doc """ @doc """
+2 -2
View File
@@ -199,7 +199,7 @@ defmodule StringIO do
end end
defp do_get_chars(input, :latin1, n) do defp do_get_chars(input, :latin1, n) do
<<chars :: [binary, size(n)], rest :: binary>> = input <<chars :: binary-size(n), rest :: binary>> = input
{chars, rest} {chars, rest}
end end
@@ -209,7 +209,7 @@ defmodule StringIO do
{buf_count, split_pos} when buf_count < n or split_pos == :none -> {buf_count, split_pos} when buf_count < n or split_pos == :none ->
{input, ""} {input, ""}
{_buf_count, split_pos} -> {_buf_count, split_pos} ->
<<chars :: [binary, size(split_pos)], rest :: binary>> = input <<chars :: binary-size(split_pos), rest :: binary>> = input
{chars, rest} {chars, rest}
end end
catch catch
+1 -1
View File
@@ -28,7 +28,7 @@ defmodule Supervisor do
{:reply, h, t} {:reply, h, t}
end end
def handle_cast({:push, h}, _from, t) do def handle_cast({:push, h}, t) do
{:noreply, [h|t]} {:noreply, [h|t]}
end end
end end
+128 -29
View File
@@ -202,32 +202,23 @@ defmodule System do
end end
@doc """ @doc """
Register a program exit handler function. Registers a program exit handler function.
Registers a function that will be invoked Registers a function that will be invoked at the end of program execution.
at the end of program execution. Useful for Useful for invoking a hook in "script" mode.
invoking a hook in "script" mode.
The function must receive the exit status code The handler always executes in a different process from the one it was
as an argument. registered in. As a consequence, any resources managed by the calling process
(ETS tables, open files, etc.) won't be available by the time the handler
function is invoked.
The function must receive the exit status code as an argument.
""" """
def at_exit(fun) when is_function(fun, 1) do def at_exit(fun) when is_function(fun, 1) do
:elixir_code_server.cast {:at_exit, fun} :elixir_code_server.cast {:at_exit, fun}
end end
@doc """ @doc false
Execute a system command.
Executes `command` in a command shell of the target OS,
captures the standard output of the command and returns
the result as a binary.
If `command` is a char list, a char list is returned.
Otherwise a string, correctly encoded in UTF-8, is expected.
"""
@spec cmd(String.t) :: String.t
@spec cmd(char_list) :: char_list
def cmd(command) when is_list(command) do def cmd(command) when is_list(command) do
:os.cmd(command) :os.cmd(command)
end end
@@ -237,24 +228,15 @@ defmodule System do
end end
@doc """ @doc """
Locate an executable on the system. Locates an executable on the system.
This function looks up an executable program given This function looks up an executable program given
its name using the environment variable PATH on Unix its name using the environment variable PATH on Unix
and Windows. It also considers the proper executable and Windows. It also considers the proper executable
extension for each OS, so for Windows it will try to extension for each OS, so for Windows it will try to
lookup files with `.com`, `.cmd` or similar extensions. lookup files with `.com`, `.cmd` or similar extensions.
If `program` is a char list, a char list is returned.
Returns a binary otherwise.
""" """
@spec find_executable(binary) :: binary | nil @spec find_executable(binary) :: binary | nil
@spec find_executable(char_list) :: char_list | nil
def find_executable(program) when is_list(program) do
:os.find_executable(program) || nil
end
def find_executable(program) when is_binary(program) do def find_executable(program) when is_binary(program) do
case :os.find_executable(String.to_char_list(program)) do case :os.find_executable(String.to_char_list(program)) do
false -> nil false -> nil
@@ -387,4 +369,121 @@ defmodule System do
def halt(status) when is_binary(status) do def halt(status) when is_binary(status) do
:erlang.halt(String.to_char_list(status)) :erlang.halt(String.to_char_list(status))
end end
@doc """
Executes the given `command` with `args`.
`command` is expected to be an executable available in PATH
unless an absolute path is given.
`args` must be a list of strings which are not expanded
in any way. For example, this means wildcard expansion will
not happen unless `Path.wildcard/2` is used. On Windows though,
wildcard expansion is up to the program.
A set of options are also supported and described below.
## Options
* `:into` - injects the result into the given collectable, defaults to `""`
* `:cd` - the directory to run the command in
* `:env` - an enumerable of tuples containing environment key-value as binary
* `:arg0` - set the command arg0
* `:stderr_to_stdout` - redirects stderr to stdout when true
* `:parallelism` - when true, the VM will schedule port tasks to improve
parallelism in the system. If set to false, the VM will try to perform
commands immediately, improving latency at the expense of parallelism.
The default can be set on system startup by passing the "+spp" argument
to `--erl`.
## Error reasons
If invalid arguments are given, `ArgumentError` is raised by
`System.cmd/3`. `System.cmd/3` also expects a strict set of
options and will raise if unknown or invalid options are given.
Furthermore, `System.cmd/3` may fail with one of the POSIX reasons
detailed below:
* `:system_limit` - all available ports in the Erlang emulator are in use
* `:enomem` - there was not enough memory to create the port
* `:eagain` - there are no more available operating system processes
* `:enametoolong` - the external command given was too long
* `:emfile` - there are no more available file descriptors
(for the operating system process that the Erlang emulator runs in)
* `:enfile` - the file table is full (for the entire operating system)
* `:eacces` - the command does not point to an executable file
* `:enoent` - the command does not point to an existing file
## Shell commands
If you desire to execute a trusted command inside a shell, with pipes,
redirecting and so on, please check
[Erlang's :os.cmd/1 function](http://www.erlang.org/doc/man/os.html#cmd-1).
"""
@spec cmd(binary, [binary], Keyword.t) ::
{Collectable.t, exit_status :: non_neg_integer}
def cmd(command, args, opts \\ []) when is_binary(command) and is_list(args) do
command = String.to_char_list(command)
command =
if Path.type(command) == :absolute do
command
else
:os.find_executable(command) || command
end
{into, opts} = cmd_opts(opts, [:use_stdio, :exit_status, :binary, :hide, args: args], "")
{initial, fun} = Collectable.into(into)
do_cmd Port.open({:spawn_executable, command}, opts), initial, fun
end
defp do_cmd(port, acc, fun) do
receive do
{^port, {:data, data}} ->
do_cmd(port, fun.(acc, {:cont, data}), fun)
{^port, {:exit_status, status}} ->
{fun.(acc, :done), status}
end
end
defp cmd_opts([{:into, any}|t], opts, _into),
do: cmd_opts(t, opts, any)
defp cmd_opts([{:cd, bin}|t], opts, into) when is_binary(bin),
do: cmd_opts(t, [{:cd, bin}|opts], into)
defp cmd_opts([{:arg0, bin}|t], opts, into) when is_binary(bin),
do: cmd_opts(t, [{:arg0, bin}|opts], into)
defp cmd_opts([{:stderr_to_stdout, bool}|t], opts, into) when is_boolean(bool),
do: cmd_opts(t, [:stderr_to_stdout|opts], into)
defp cmd_opts([{:parallelism, bool}|t], opts, into) when is_boolean(bool),
do: cmd_opts(t, [{:parallelism, bool}|opts], into)
defp cmd_opts([{:env, enum}|t], opts, into),
do: cmd_opts(t, [{:env, validate_env(enum)}|opts], into)
defp cmd_opts([{key, val}|_], _opts, _into),
do: raise(ArgumentError, "invalid option #{inspect key} with value #{inspect val}")
defp cmd_opts([], opts, into),
do: {into, opts}
defp validate_env(enum) do
Enum.map enum, fn
{k, v} ->
{String.to_char_list(k), String.to_char_list(v)}
other ->
raise ArgumentError, "invalid environment key-value #{inspect other}"
end
end
end end
+13 -3
View File
@@ -58,7 +58,7 @@ defmodule Task do
that dynamically supervise tasks: that dynamically supervise tasks:
{:ok, pid} = Task.Supervisor.start_link() {:ok, pid} = Task.Supervisor.start_link()
Task.Supervisor.async(pid, fn -> do_work() end) Task.Supervisor.async(pid, MyMod, :my_fun, [arg1, arg2, arg3])
`Task.Supervisor` also makes it possible to spawn tasks in remote nodes as `Task.Supervisor` also makes it possible to spawn tasks in remote nodes as
long as the supervisor is registered locally or globally: long as the supervisor is registered locally or globally:
@@ -67,7 +67,7 @@ defmodule Task do
Task.Supervisor.start_link(name: :tasks_sup) Task.Supervisor.start_link(name: :tasks_sup)
# In the client # In the client
Task.Supervisor.async({:tasks_sup, :remote@local}, fn -> do_work() end) Task.Supervisor.async({:tasks_sup, :remote@local}, MyMod, :my_fun, [arg1, arg2, arg3])
`Task.Supervisor` is more often started in your supervision tree as: `Task.Supervisor` is more often started in your supervision tree as:
@@ -77,7 +77,15 @@ defmodule Task do
supervisor(Task.Supervisor, [[name: :tasks_sup]]) supervisor(Task.Supervisor, [[name: :tasks_sup]])
] ]
Check `Task.Supervisor` for other operations supported by the Task supervisor. Note that, when working with distributed tasks, one should use the `async/4` API,
that expects explicit module, function and arguments, instead of `async/2` that
works with anonymous functions. That's because the anonymous function API expects
the same module version to exist on all involved nodes. Check the `Agent` module
documentation for more information on distributed processes, as the limitations
described in the agents documentation apply to the whole ecosystem.
Finally, check `Task.Supervisor` for other operations supported by the Task
supervisor.
""" """
@doc """ @doc """
@@ -93,6 +101,8 @@ defmodule Task do
""" """
defstruct pid: nil, ref: nil defstruct pid: nil, ref: nil
@type t :: %__MODULE__{}
@doc """ @doc """
Starts a task as part of a supervision tree. Starts a task as part of a supervision tree.
""" """
+6 -6
View File
@@ -89,12 +89,12 @@ defmodule Task.Supervised do
{fun, args} = get_running(mfa) {fun, args} = get_running(mfa)
:error_logger.format( :error_logger.format(
"** Task ~p terminating~n" <> '** Task ~p terminating~n' ++
"** Started from ~p~n" <> '** Started from ~p~n' ++
"** When function == ~p~n" <> '** When function == ~p~n' ++
"** arguments == ~p~n" <> '** arguments == ~p~n' ++
"** Reason for termination == ~n" <> '** Reason for termination == ~n' ++
"** ~p~n", [self, get_from(info), fun, args, reason]) '** ~p~n', [self, get_from(info), fun, args, reason])
exit(reason) exit(reason)
end end
+2
View File
@@ -7,6 +7,8 @@ defmodule URI do
fragment: nil, authority: nil, fragment: nil, authority: nil,
userinfo: nil, host: nil, port: nil userinfo: nil, host: nil, port: nil
@type t :: %__MODULE__{}
import Bitwise import Bitwise
@ports %{ @ports %{
+20 -6
View File
@@ -58,14 +58,26 @@ defmodule Version do
@type version :: String.t | t @type version :: String.t | t
@type requirement :: String.t | Version.Requirement.t @type requirement :: String.t | Version.Requirement.t
@type matchable :: {major :: String.t | non_neg_integer, @type major :: String.t | non_neg_integer
minor :: non_neg_integer | nil, @type minor :: non_neg_integer | nil
patch :: non_neg_integer | nil, @type patch :: non_neg_integer | nil
pre :: [String.t]} @type pre :: [String.t]
@type build :: String.t | nil
@type matchable :: {major :: major,
minor :: minor,
patch :: patch,
pre :: pre}
@type t :: %__MODULE__{
major: major,
minor: minor,
patch: patch,
pre: pre,
build: build}
defmodule Requirement do defmodule Requirement do
@moduledoc false @moduledoc false
defstruct [:source, :matchspec] defstruct [:source, :matchspec]
@type t :: %__MODULE__{}
end end
defmodule InvalidRequirementError do defmodule InvalidRequirementError do
@@ -157,7 +169,8 @@ defmodule Version do
## Examples ## Examples
iex> Version.parse("2.0.1-alpha1") |> elem(1) iex> {:ok, version} = Version.parse("2.0.1-alpha1")
iex> version
#Version<2.0.1-alpha1> #Version<2.0.1-alpha1>
iex> Version.parse("2.0-alpha1") iex> Version.parse("2.0-alpha1")
@@ -181,7 +194,8 @@ defmodule Version do
## Examples ## Examples
iex> Version.parse_requirement("== 2.0.1") |> elem(1) iex> {:ok, req} = Version.parse_requirement("== 2.0.1")
iex> req
#Version.Requirement<== 2.0.1> #Version.Requirement<== 2.0.1>
iex> Version.parse_requirement("== == 2.0.1") iex> Version.parse_requirement("== == 2.0.1")
+1 -4
View File
@@ -4,9 +4,6 @@ defmodule Elixir.Mixfile do
def project do def project do
[app: :elixir, [app: :elixir,
version: System.version, version: System.version,
build_per_environment: false, build_per_environment: false]
escript_embed_elixir: false,
escript_main_module: :elixir,
escript_emu_args: "%%! -noshell\n"]
end end
end end
+20 -13
View File
@@ -2,7 +2,7 @@
%% private to the Elixir compiler and reserved to be used by Elixir only. %% private to the Elixir compiler and reserved to be used by Elixir only.
-module(elixir). -module(elixir).
-behaviour(application). -behaviour(application).
-export([main/1, start_cli/0, -export([start_cli/0,
string_to_quoted/4, 'string_to_quoted!'/4, string_to_quoted/4, 'string_to_quoted!'/4,
env_for_eval/1, env_for_eval/2, quoted_to_erl/2, quoted_to_erl/3, env_for_eval/1, env_for_eval/2, quoted_to_erl/2, quoted_to_erl/3,
eval/2, eval/3, eval_forms/3, eval_forms/4, eval_quoted/3]). eval/2, eval/3, eval_forms/3, eval_forms/4, eval_quoted/3]).
@@ -28,8 +28,10 @@ start(_Type, _Args) ->
error -> [binary] error -> [binary]
end, end,
io:setopts(standard_io, Opts), ok = io:setopts(standard_io, Opts),
io:setopts(standard_error, [{unicode,true}]), %% Must use undocument {unicode, true} to set unicode on standard_error, more
%% info: http://erlang.org/pipermail/erlang-bugs/2014-April/004310.html
ok = io:setopts(standard_error, [{unicode,true}]),
case file:native_name_encoding() of case file:native_name_encoding() of
latin1 -> latin1 ->
io:format(standard_error, io:format(standard_error,
@@ -48,16 +50,16 @@ stop(_S) ->
config_change(_Changed, _New, _Remove) -> config_change(_Changed, _New, _Remove) ->
ok. ok.
%% escript entry point
main(Args) ->
application:start(?MODULE),
'Elixir.Kernel.CLI':main(Args).
%% Boot and process given options. Invoked by Elixir's script. %% Boot and process given options. Invoked by Elixir's script.
start_cli() -> start_cli() ->
application:start(?MODULE), {ok, _} = application:ensure_all_started(?MODULE),
%% We start the Logger so tools that depend on Elixir
%% always have the Logger directly accessible. However
%% Logger is not a dependency of the Elixir application,
%% which means releases that want to use Logger must
%% always list it as part of its applications.
_ = application:start(logger),
'Elixir.Kernel.CLI':main(init:get_plain_arguments()). 'Elixir.Kernel.CLI':main(init:get_plain_arguments()).
%% EVAL HOOKS %% EVAL HOOKS
@@ -153,14 +155,19 @@ eval_forms(Tree, Binding, Env, Scope) ->
{atom, _, Atom} -> {atom, _, Atom} ->
{Atom, Binding, NewEnv, NewScope}; {Atom, Binding, NewEnv, NewScope};
_ -> _ ->
{value, Value, NewBinding} = erl_eval(Erl, ParsedBinding), {value, Value, NewBinding} = erl_eval(Erl, ParsedBinding, Env),
{Value, elixir_scope:dump_binding(NewBinding, NewScope), NewEnv, NewScope} {Value, elixir_scope:dump_binding(NewBinding, NewScope), NewEnv, NewScope}
end. end.
erl_eval(Erl, ParsedBinding) -> erl_eval(Erl, ParsedBinding, E) ->
case erl_eval:check_command([Erl], ParsedBinding) of
ok -> ok;
{error, Desc} -> elixir_errors:handle_file_error(?m(E, file), Desc)
end,
% Below must be all one line for locations to be the same when the stacktrace % Below must be all one line for locations to be the same when the stacktrace
% needs to be extended to the full stacktrace. % needs to be extended to the full stacktrace.
try erl_eval:expr(Erl, ParsedBinding) catch Class:Exception -> erlang:raise(Class, Exception, get_stacktrace()) end. try erl_eval:expr(Erl, ParsedBinding, none, none, none) catch Class:Exception -> erlang:raise(Class, Exception, get_stacktrace()) end.
get_stacktrace() -> get_stacktrace() ->
Stacktrace = erlang:get_stacktrace(), Stacktrace = erlang:get_stacktrace(),
+102 -67
View File
@@ -35,70 +35,108 @@ expand_bitstr(Fun, [H|T], Acc, E) ->
%% Expand bit info %% Expand bit info
expand_bit_info(Meta, Info, E) when is_list(Info) ->
expand_bit_info(Meta, Info, default, [], E);
expand_bit_info(Meta, Info, E) -> expand_bit_info(Meta, Info, E) ->
expand_bit_info(Meta, [Info], E). expand_bit_info(Meta, unpack_bit_info(Meta, Info, E), default, [], E).
expand_bit_info(Meta, [{Expr, ExprMeta, Args}|T], Size, Types, E) when is_atom(Expr) -> expand_bit_info(Meta, [{size, _, [_]=Args}|T], Size, Types, E) ->
ListArgs = if is_atom(Args) -> []; is_list(Args) -> Args end, case Size of
case expand_bit_type_or_size(Expr, ListArgs) of default ->
type -> {[EArg], EE} = elixir_exp:expand_args(Args, E),
{EArgs, EE} = elixir_exp:expand_args(ListArgs, E),
expand_bit_info(Meta, T, Size, [{Expr, [], EArgs}|Types], EE); case EArg of
size -> {Var, _, Context} when is_atom(Var) and is_atom(Context) ->
case Size of ok;
default -> ok; _ when is_integer(EArg) ->
_ -> elixir_errors:compile_error(Meta, ?m(E, file), "duplicated size definition in bitstring") ok;
_ ->
elixir_errors:compile_error(Meta, ?m(E, file),
"size in bitstring expects an integer or a variable as argument, got: ~ts",
['Elixir.Macro':to_string(EArg)])
end, end,
{EArgs, EE} = elixir_exp:expand_args(ListArgs, E),
expand_bit_info(Meta, T, {Expr, [], EArgs}, Types, EE); expand_bit_info(Meta, T, {size, [], [EArg]}, Types, EE);
none -> _ ->
handle_unknown_bit_info(Meta, {Expr, ExprMeta, ListArgs}, T, Size, Types, E) elixir_errors:compile_error(Meta, ?m(E, file),
"duplicated size definition in bitstring")
end; end;
expand_bit_info(Meta, [Int|T], Size, Types, E) when is_integer(Int) -> expand_bit_info(Meta, [{Expr, ExprMeta, Args}|T], Size, Types, E) when is_atom(Expr) ->
expand_bit_info(Meta, [{size, [], [Int]}|T], Size, Types, E); case expand_bit_type(Expr, Args) of
type ->
{EArgs, EE} = elixir_exp:expand_args(Args, E),
validate_bit_type_args(Meta, Expr, EArgs, EE),
expand_bit_info(Meta, T, Size, [{Expr, [], EArgs}|Types], EE);
none ->
handle_unknown_bit_info(Meta, {Expr, ExprMeta, Args}, T, Size, Types, E)
end;
expand_bit_info(Meta, [Expr|_], _Size, _Types, E) -> expand_bit_info(Meta, [Expr|_], _Size, _Types, E) ->
elixir_errors:compile_error(Meta, ?m(E, file), elixir_errors:compile_error(Meta, ?m(E, file),
"unknown bitstring specifier ~ts", ['Elixir.Kernel':inspect(Expr)]); "unknown bitstring specifier ~ts", ['Elixir.Kernel':inspect(Expr)]);
expand_bit_info(_Meta, [], Size, Types, _) -> expand_bit_info(Meta, [], Size, Types, _) ->
case Size of [H|T] = case Size of
default -> lists:reverse(Types); default -> lists:reverse(Types);
_ -> [Size|lists:reverse(Types)] _ -> lists:reverse(Types, [Size])
end. end,
lists:foldl(fun(I, Acc) -> {'-', Meta, [Acc, I]} end, H, T).
expand_bit_type_or_size(binary, []) -> type; expand_bit_type(binary, []) -> type;
expand_bit_type_or_size(integer, []) -> type; expand_bit_type(integer, []) -> type;
expand_bit_type_or_size(float, []) -> type; expand_bit_type(float, []) -> type;
expand_bit_type_or_size(bitstring, []) -> type; expand_bit_type(bitstring, []) -> type;
expand_bit_type_or_size(bytes, []) -> type; expand_bit_type(bytes, []) -> type;
expand_bit_type_or_size(bits, []) -> type; expand_bit_type(bits, []) -> type;
expand_bit_type_or_size(utf8, []) -> type; expand_bit_type(utf8, []) -> type;
expand_bit_type_or_size(utf16, []) -> type; expand_bit_type(utf16, []) -> type;
expand_bit_type_or_size(utf32, []) -> type; expand_bit_type(utf32, []) -> type;
expand_bit_type_or_size(signed, []) -> type; expand_bit_type(signed, []) -> type;
expand_bit_type_or_size(unsigned, []) -> type; expand_bit_type(unsigned, []) -> type;
expand_bit_type_or_size(big, []) -> type; expand_bit_type(big, []) -> type;
expand_bit_type_or_size(little, []) -> type; expand_bit_type(little, []) -> type;
expand_bit_type_or_size(native, []) -> type; expand_bit_type(native, []) -> type;
expand_bit_type_or_size(unit, [_]) -> type; expand_bit_type(unit, [_]) -> type;
expand_bit_type_or_size(size, [_]) -> size; expand_bit_type(_, _) -> none.
expand_bit_type_or_size(_, _) -> none.
handle_unknown_bit_info(Meta, {_, ExprMeta, _} = Expr, T, Size, Types, E) -> validate_bit_type_args(Meta, unit, [Unit], E) when not is_integer(Unit) ->
case 'Elixir.Macro':expand(Expr, elixir_env:linify({?line(ExprMeta), E})) of elixir_errors:compile_error(Meta, ?m(E, file),
"unit in bitstring expects an integer as argument, got: ~ts",
['Elixir.Macro':to_string(Unit)]);
validate_bit_type_args(_Meta, _Expr, _Args, _E) ->
ok.
handle_unknown_bit_info(Meta, Expr, T, Size, Types, E) ->
case 'Elixir.Macro':expand(Expr, elixir_env:linify({?line(Meta), E})) of
Expr -> Expr ->
elixir_errors:compile_error(ExprMeta, ?m(E, file), elixir_errors:compile_error(Meta, ?m(E, file),
"unknown bitstring specifier ~ts", ['Elixir.Macro':to_string(Expr)]); "unknown bitstring specifier ~ts", ['Elixir.Macro':to_string(Expr)]);
Other -> Info ->
List = case is_list(Other) of true -> Other; false -> [Other] end, expand_bit_info(Meta, unpack_bit_info(Meta, Info, E) ++ T, Size, Types, E)
expand_bit_info(Meta, List ++ T, Size, Types, E)
end. end.
%% We can remove unpack_bit_info/3 once deprecated.
unpack_bit_info(Meta, [H|T], E) ->
elixir_errors:warn(?line(Meta), ?m(E, file), "passing a list of bitstring modifiers is deprecated, "
"please separate them with - instead"),
Dashed = lists:foldl(fun(I, Acc) -> {'-', Meta, [Acc, I]} end, H, T),
unpack_bit_info(Dashed, []);
unpack_bit_info(_Meta, Expr, _E) ->
unpack_bit_info(Expr, []).
unpack_bit_info({'-', _, [H, T]}, Acc) ->
unpack_bit_info(H, unpack_bit_info(T, Acc));
unpack_bit_info({'*', _, [{'_', _, Atom}, Unit]}, Acc) when is_atom(Atom) and is_integer(Unit) ->
[{unit, [], [Unit]}|Acc];
unpack_bit_info({'*', _, [Size, Unit]}, Acc) when is_integer(Size) and is_integer(Unit) ->
[{size, [], [Size]}, {unit, [], [Unit]}|Acc];
unpack_bit_info(Size, Acc) when is_integer(Size) ->
[{size, [], [Size]}|Acc];
unpack_bit_info({Expr, Meta, Args}, Acc) when is_atom(Expr) ->
ListArgs = if is_atom(Args) -> []; is_list(Args) -> Args end,
[{Expr, Meta, ListArgs}|Acc];
unpack_bit_info(Other, Acc) ->
[Other|Acc].
%% Translation %% Translation
has_size({bin, _, Elements}) -> has_size({bin, _, Elements}) ->
@@ -124,7 +162,7 @@ build_bitstr_each(_Fun, [], _Meta, S, Acc) ->
{Acc, S}; {Acc, S};
build_bitstr_each(Fun, [{'::',_,[H,V]}|T], Meta, S, Acc) -> build_bitstr_each(Fun, [{'::',_,[H,V]}|T], Meta, S, Acc) ->
{Size, Types} = extract_bit_info(Meta, V, S#elixir_scope{context=nil}), {Size, Types} = extract_bit_info(V, S#elixir_scope{context=nil}),
build_bitstr_each(Fun, T, Meta, S, Acc, H, Size, Types); build_bitstr_each(Fun, T, Meta, S, Acc, H, Size, Types);
build_bitstr_each(Fun, [H|T], Meta, S, Acc) -> build_bitstr_each(Fun, [H|T], Meta, S, Acc) ->
@@ -201,23 +239,20 @@ unit_size([], Guess) -> Guess.
%% Extra bitstring specifiers %% Extra bitstring specifiers
extract_bit_info(Meta, [{size, _, [Arg]}|T], S) -> extract_bit_info({'-', _, [L, {size, _, [Size]}]}, S) ->
case elixir_translator:translate(Arg, S) of {extract_bit_size(Size, S), extract_bit_type(L, [])};
{{Kind, _, _} = Size, _} when Kind == integer; Kind == var -> extract_bit_info({size, _, [Size]}, S) ->
{Size, extract_bit_type(Meta, T, S)}; {extract_bit_size(Size, S), []};
_ -> extract_bit_info(L, _S) ->
elixir_errors:compile_error(Meta, S#elixir_scope.file, {default, extract_bit_type(L, [])}.
"size in bitstring expects an integer or a variable as argument, got: ~ts", ['Elixir.Macro':to_string(Arg)])
end;
extract_bit_info(Meta, T, S) ->
{default, extract_bit_type(Meta, T, S)}.
extract_bit_type(Meta, [{unit, _, [Arg]}|T], S) when is_integer(Arg) -> extract_bit_size(Size, S) ->
[{unit, Arg}|extract_bit_type(Meta, T, S)]; {TSize, _} = elixir_translator:translate(Size, S),
extract_bit_type(Meta, [{unit, _, [Arg]}|_], S) -> TSize.
elixir_errors:compile_error(Meta, S#elixir_scope.file,
"unit in bitstring expects an integer as argument, got: ~ts", ['Elixir.Macro':to_string(Arg)]); extract_bit_type({'-', _, [L, R]}, Acc) ->
extract_bit_type(Meta, [{Other, _, []}|T], S) -> extract_bit_type(L, extract_bit_type(R, Acc));
[Other|extract_bit_type(Meta, T, S)]; extract_bit_type({unit, _, [Arg]}, Acc) ->
extract_bit_type(_Meta, [], _S) -> [{unit, Arg}|Acc];
[]. extract_bit_type({Other, _, []}, Acc) ->
[Other|Acc].
+17 -5
View File
@@ -11,6 +11,7 @@
loaded=[], loaded=[],
at_exit=[], at_exit=[],
pool={[],0}, pool={[],0},
paths={[],[]},
compiler_options=[{docs,true},{debug_info,true},{warnings_as_errors,false}], compiler_options=[{docs,true},{debug_info,true},{warnings_as_errors,false}],
erl_compiler_options=nil erl_compiler_options=nil
}). }).
@@ -27,9 +28,13 @@ start_link() ->
gen_server:start_link({local, ?MODULE}, ?MODULE, ok, []). gen_server:start_link({local, ?MODULE}, ?MODULE, ok, []).
init(ok) -> init(ok) ->
code:ensure_loaded('Elixir.Macro.Env'), %% We attempt to load those modules here so throughout
code:ensure_loaded('Elixir.Module.LocalsTracker'), %% the codebase we can use a code:is_loaded/1 check on
code:ensure_loaded('Elixir.Kernel.LexicalTracker'), %% bootstrap instead of using the more expensive
%% code:ensure_loaded/1 multiple times.
_ = code:ensure_loaded('Elixir.Macro.Env'),
_ = code:ensure_loaded('Elixir.Module.LocalsTracker'),
_ = code:ensure_loaded('Elixir.Kernel.LexicalTracker'),
{ok, #elixir_code_server{}}. {ok, #elixir_code_server{}}.
handle_call({acquire, Path}, From, Config) -> handle_call({acquire, Path}, From, Config) ->
@@ -64,7 +69,8 @@ handle_call({compilation_status, CompilerPid}, _From, Config) ->
CompilationStatusList = Config#elixir_code_server.compilation_status, CompilationStatusList = Config#elixir_code_server.compilation_status,
CompilationStatusListNew = orddict:erase(CompilerPid, CompilationStatusList), CompilationStatusListNew = orddict:erase(CompilerPid, CompilationStatusList),
CompilationStatus = orddict:fetch(CompilerPid, CompilationStatusList), CompilationStatus = orddict:fetch(CompilerPid, CompilationStatusList),
{reply, CompilationStatus, Config#elixir_code_server{compilation_status=CompilationStatusListNew}}; {reply, CompilationStatus,
Config#elixir_code_server{compilation_status=CompilationStatusListNew}};
handle_call(retrieve_module_name, _From, Config) -> handle_call(retrieve_module_name, _From, Config) ->
case Config#elixir_code_server.pool of case Config#elixir_code_server.pool of
@@ -83,6 +89,9 @@ handle_call(erl_compiler_options, _From, Config) ->
{reply, Opts, Config} {reply, Opts, Config}
end; end;
handle_call(paths, _From, Config) ->
{reply, Config#elixir_code_server.paths, Config};
handle_call(Request, _From, Config) -> handle_call(Request, _From, Config) ->
{stop, {badcall, Request}, Config}. {stop, {badcall, Request}, Config}.
@@ -115,7 +124,7 @@ handle_cast({loaded, Path}, Config) ->
{ok, true} -> {ok, true} ->
{noreply, Config}; {noreply, Config};
{ok, {Ref, List}} when is_list(List), is_reference(Ref) -> {ok, {Ref, List}} when is_list(List), is_reference(Ref) ->
[Pid ! {elixir_code_server, Ref, loaded} || {Pid, _Tag} <- lists:reverse(List)], _ = [Pid ! {elixir_code_server, Ref, loaded} || {Pid, _Tag} <- lists:reverse(List)],
Done = orddict:store(Path, true, Current), Done = orddict:store(Path, true, Current),
{noreply, Config#elixir_code_server{loaded=Done}}; {noreply, Config#elixir_code_server{loaded=Done}};
error -> error ->
@@ -131,6 +140,9 @@ handle_cast({unload_files, Files}, Config) ->
handle_cast({return_module_name, H}, #elixir_code_server{pool={T,Counter}} = Config) -> handle_cast({return_module_name, H}, #elixir_code_server{pool={T,Counter}} = Config) ->
{noreply, Config#elixir_code_server{pool={[H|T],Counter}}}; {noreply, Config#elixir_code_server{pool={[H|T],Counter}}};
handle_cast({paths, PA, PZ}, #elixir_code_server{} = Config) ->
{noreply, Config#elixir_code_server{paths={PA,PZ}}};
handle_cast(Request, Config) -> handle_cast(Request, Config) ->
{stop, {badcast, Request}, Config}. {stop, {badcast, Request}, Config}.
+6 -6
View File
@@ -40,7 +40,7 @@ file(Relative) when is_binary(Relative) ->
file_to_path(File, Path) when is_binary(File), is_binary(Path) -> file_to_path(File, Path) when is_binary(File), is_binary(Path) ->
Lists = file(File), Lists = file(File),
[binary_to_path(X, Path) || X <- Lists], _ = [binary_to_path(X, Path) || X <- Lists],
Lists. Lists.
%% Evaluation %% Evaluation
@@ -144,7 +144,7 @@ module(Forms, File, Options, Bootstrap, Callback) when
case compile:noenv_forms([no_auto_import()|Forms], [return,{source,Listname}|Options]) of case compile:noenv_forms([no_auto_import()|Forms], [return,{source,Listname}|Options]) of
{ok, ModuleName, Binary, Warnings} -> {ok, ModuleName, Binary, Warnings} ->
format_warnings(Bootstrap, Warnings), format_warnings(Bootstrap, Warnings),
code:load_binary(ModuleName, Listname, Binary), {module, ModuleName} = code:load_binary(ModuleName, Listname, Binary),
Callback(ModuleName, Binary); Callback(ModuleName, Binary);
{error, Errors, Warnings} -> {error, Errors, Warnings} ->
format_warnings(Bootstrap, Warnings), format_warnings(Bootstrap, Warnings),
@@ -157,14 +157,14 @@ no_auto_import() ->
%% CORE HANDLING %% CORE HANDLING
core() -> core() ->
application:start(elixir), {ok, _} = application:ensure_all_started(elixir),
elixir_code_server:cast({compiler_options, [{docs,false},{internal,true}]}), elixir_code_server:cast({compiler_options, [{docs,false},{internal,true}]}),
[core_file(File) || File <- core_main()]. [core_file(File) || File <- core_main()].
core_file(File) -> core_file(File) ->
try try
Lists = file(File), Lists = file(File),
[binary_to_path(X, "lib/elixir/ebin") || X <- Lists], _ = [binary_to_path(X, "lib/elixir/ebin") || X <- Lists],
io:format("Compiled ~ts~n", [File]) io:format("Compiled ~ts~n", [File])
catch catch
Kind:Reason -> Kind:Reason ->
@@ -214,11 +214,11 @@ format_errors([]) ->
format_errors(Errors) -> format_errors(Errors) ->
lists:foreach(fun ({File, Each}) -> lists:foreach(fun ({File, Each}) ->
BinFile = elixir_utils:characters_to_binary(File), BinFile = elixir_utils:characters_to_binary(File),
lists:foreach(fun (Error) -> elixir_errors:handle_file_error(BinFile, Error) end, Each) lists:foreach(fun(Error) -> elixir_errors:handle_file_error(BinFile, Error) end, Each)
end, Errors). end, Errors).
format_warnings(Bootstrap, Warnings) -> format_warnings(Bootstrap, Warnings) ->
lists:foreach(fun ({File, Each}) -> lists:foreach(fun ({File, Each}) ->
BinFile = elixir_utils:characters_to_binary(File), BinFile = elixir_utils:characters_to_binary(File),
lists:foreach(fun (Warning) -> elixir_errors:handle_file_warning(Bootstrap, BinFile, Warning) end, Each) lists:foreach(fun(Warning) -> elixir_errors:handle_file_warning(Bootstrap, BinFile, Warning) end, Each)
end, Warnings). end, Warnings).
+141 -85
View File
@@ -2,7 +2,7 @@
-module(elixir_def). -module(elixir_def).
-export([table/1, clauses_table/1, setup/1, -export([table/1, clauses_table/1, setup/1,
cleanup/1, reset_last/1, lookup_definition/2, cleanup/1, reset_last/1, lookup_definition/2,
delete_definition/2, store_definition/6, unwrap_definitions/1, delete_definition/2, store_definition/6, unwrap_definitions/2,
store_each/8, format_error/1]). store_each/8, format_error/1]).
-include("elixir.hrl"). -include("elixir.hrl").
@@ -55,7 +55,7 @@ store_definition(Line, Kind, CheckClauses, Call, Body, Pos) ->
{Name, Args} = case NameAndArgs of {Name, Args} = case NameAndArgs of
{N, _, A} when is_atom(N), is_atom(A) -> {N, []}; {N, _, A} when is_atom(N), is_atom(A) -> {N, []};
{N, _, A} when is_atom(N), is_list(A) -> {N, A}; {N, _, A} when is_atom(N), is_list(A) -> {N, A};
_ -> elixir_errors:form_error(Line, ?m(E, file), ?MODULE, {invalid_def, Kind, NameAndArgs}) _ -> elixir_errors:form_error([{line, Line}], ?m(E, file), ?MODULE, {invalid_def, Kind, NameAndArgs})
end, end,
%% Now that we have verified the call format, %% Now that we have verified the call format,
@@ -64,11 +64,17 @@ store_definition(Line, Kind, CheckClauses, Call, Body, Pos) ->
DoCheckClauses = (not lists:keymember(context, 1, Meta)) andalso (CheckClauses), DoCheckClauses = (not lists:keymember(context, 1, Meta)) andalso (CheckClauses),
%% Check if there is a file information in the definition. %% Check if there is a file information in the definition.
%% If so, we assume this come from another source and we need %% If so, we assume this come from another source and
%% to linify taking into account keep line numbers. %% we need to linify taking into account keep line numbers.
{File, Key} = case lists:keyfind(file, 1, Meta) of {Location, Key} = case lists:keyfind(file, 1, Meta) of
{file, Bin} when is_binary(Bin) -> {Bin, keep}; {file, KeepFile} when is_binary(KeepFile) ->
_ -> {nil, line} case lists:keyfind(keep, 1, Meta) of
{keep, KeepLine} when is_integer(KeepLine) -> KeepLine;
_ -> KeepLine = 0
end,
{{KeepFile, KeepLine}, keep};
_ ->
{nil, line}
end, end,
LinifyArgs = elixir_quote:linify(Line, Key, Args), LinifyArgs = elixir_quote:linify(Line, Key, Args),
@@ -77,15 +83,15 @@ store_definition(Line, Kind, CheckClauses, Call, Body, Pos) ->
assert_no_aliases_name(Line, Name, Args, E), assert_no_aliases_name(Line, Name, Args, E),
store_definition(Line, Kind, DoCheckClauses, Name, store_definition(Line, Kind, DoCheckClauses, Name,
LinifyArgs, LinifyGuards, LinifyBody, File, E). LinifyArgs, LinifyGuards, LinifyBody, Location, E).
store_definition(Line, Kind, CheckClauses, Name, Args, Guards, Body, MetaFile, #{module := Module} = ER) -> store_definition(Line, Kind, CheckClauses, Name, Args, Guards, Body, KeepLocation, #{module := Module} = ER) ->
Arity = length(Args), Arity = length(Args),
Tuple = {Name, Arity}, Tuple = {Name, Arity},
E = ER#{function := Tuple}, E = ER#{function := Tuple},
elixir_locals:record_definition(Tuple, Kind, Module), elixir_locals:record_definition(Tuple, Kind, Module),
Location = retrieve_location(Line, MetaFile, Module), Location = retrieve_location(KeepLocation, Module),
{Function, Defaults, Super} = translate_definition(Kind, Line, Module, Name, Args, Guards, Body, E), {Function, Defaults, Super} = translate_definition(Kind, Line, Module, Name, Args, Guards, Body, E),
DefaultsLength = length(Defaults), DefaultsLength = length(Defaults),
@@ -115,31 +121,36 @@ run_on_definition_callbacks(Kind, Line, Module, Name, Args, Guards, Expr, E) ->
_ -> _ ->
Env = elixir_env:linify({Line, E}), Env = elixir_env:linify({Line, E}),
Callbacks = 'Elixir.Module':get_attribute(Module, on_definition), Callbacks = 'Elixir.Module':get_attribute(Module, on_definition),
[Mod:Fun(Env, Kind, Name, Args, Guards, Expr) || {Mod, Fun} <- Callbacks] _ = [Mod:Fun(Env, Kind, Name, Args, Guards, Expr) || {Mod, Fun} <- Callbacks],
ok
end. end.
make_struct_available(def, Module, '__struct__', []) -> make_struct_available(def, Module, '__struct__', []) ->
case erlang:get(elixir_compiler_pid) of case erlang:get(elixir_compiler_pid) of
undefined -> ok; undefined -> ok;
Pid -> Pid ! {struct_available, Module} Pid ->
Pid ! {struct_available, Module},
ok
end; end;
make_struct_available(_, _, _, _) -> make_struct_available(_, _, _, _) ->
ok. ok.
%% Retrieve location from meta file or @file, otherwise nil %% Retrieve location from meta file (if Key == keep)
%% or @file, otherwise nil
retrieve_location(Line, File, Module) -> retrieve_location(Location, Module) ->
case get_location_attribute(Module) of case get_location_attribute(Module) of
nil when not is_binary(File) -> nil when is_tuple(Location) ->
nil; {File, Line} = Location,
nil ->
{normalize_location(File), Line}; {normalize_location(File), Line};
X when is_binary(X) -> nil ->
nil;
File when is_binary(File) ->
'Elixir.Module':delete_attribute(Module, file), 'Elixir.Module':delete_attribute(Module, file),
{normalize_location(X), 0}; {normalize_location(File), 0};
{X, L} when is_binary(X) andalso is_integer(L) -> {File, Line} when is_binary(File) andalso is_integer(Line) ->
'Elixir.Module':delete_attribute(Module, file), 'Elixir.Module':delete_attribute(Module, file),
{normalize_location(X), L} {normalize_location(File), Line}
end. end.
get_location_attribute(Module) -> get_location_attribute(Module) ->
@@ -148,8 +159,8 @@ get_location_attribute(Module) ->
false -> 'Elixir.Module':get_attribute(Module, file) false -> 'Elixir.Module':get_attribute(Module, file)
end. end.
normalize_location(X) -> normalize_location(File) ->
elixir_utils:characters_to_list(elixir_utils:relative_to_cwd(X)). elixir_utils:characters_to_list(elixir_utils:relative_to_cwd(File)).
%% Compile super %% Compile super
@@ -179,7 +190,7 @@ translate_definition(Kind, Line, Module, Name, Args, Guards, Body, E) when is_in
translate_clause(nil, _Line, _Kind, _Args, [], _Body, _S) -> translate_clause(nil, _Line, _Kind, _Args, [], _Body, _S) ->
{[], false}; {[], false};
translate_clause(nil, Line, Kind, _Args, _Guards, _Body, #elixir_scope{file=File}) -> translate_clause(nil, Line, Kind, _Args, _Guards, _Body, #elixir_scope{file=File}) ->
elixir_errors:form_error(Line, File, ?MODULE, {missing_do, Kind}); elixir_errors:form_error([{line, Line}], File, ?MODULE, {missing_do, Kind});
translate_clause(_, Line, Kind, Args, Guards, Body, S) -> translate_clause(_, Line, Kind, Args, Guards, Body, S) ->
{TClause, TS} = elixir_clauses:clause(Line, {TClause, TS} = elixir_clauses:clause(Line,
fun elixir_translator:translate_args/2, Args, Body, Guards, true, S), fun elixir_translator:translate_args/2, Args, Body, Guards, true, S),
@@ -216,88 +227,121 @@ is_macro(_) -> false.
% Unwrap the functions stored in the functions table. % Unwrap the functions stored in the functions table.
% It returns a list of all functions to be exported, plus the macros, % It returns a list of all functions to be exported, plus the macros,
% and the body of all functions. % and the body of all functions.
unwrap_definitions(Module) -> unwrap_definitions(File, Module) ->
Table = table(Module), Table = table(Module),
CTable = clauses_table(Module), CTable = clauses_table(Module),
ets:delete(Table, last), ets:delete(Table, last),
unwrap_definition(ets:tab2list(Table), CTable, [], [], [], [], [], [], []).
unwrap_definition([Fun|T], CTable, All, Exports, Private, Def, Defmacro, Functions, Tail) -> {All, Private} = unwrap_definition(ets:tab2list(Table), File, Module, CTable, [], []),
Tuple = element(1, Fun), Unreachable = elixir_locals:warn_unused_local(File, Module, Private),
Clauses = [Clause || {_, Clause} <- ets:lookup(CTable, Tuple)], split_definition(All, Unreachable, [], [], [], [], [], {[], []}).
{NewFun, NewExports, NewPrivate, NewDef, NewDefmacro} = unwrap_definition([Fun|T], File, Module, CTable, All, Private) ->
case Clauses of {Tuple, Kind, Line, _, Check, Location, {Defaults, _, _}} = Fun,
[] -> {false, Exports, Private, Def, Defmacro}; Export = export(Kind, Tuple),
_ -> unwrap_definition(element(2, Fun), Tuple, Fun, Exports, Private, Def, Defmacro)
end,
{NewFunctions, NewTail} = case NewFun of case [Clause || {_, Clause} <- ets:lookup(CTable, Tuple)] of
[] ->
warn_bodyless_function(Line, File, Module, Kind, Tuple),
unwrap_definition(T, File, Module, CTable, All, Private);
Clauses ->
Unwrapped = {Tuple, Kind, Line, Location,
function_for_stored_definition(Line, Export, Clauses)},
NewPrivate =
if
Kind == defp; Kind == defmacrop ->
[{Tuple, Kind, Line, Check, Defaults}|Private];
true ->
Private
end,
unwrap_definition(T, File, Module, CTable, [Unwrapped|All], NewPrivate)
end;
unwrap_definition([], _File, _Module, _CTable, All, Private) ->
{All, Private}.
split_definition([{Tuple, def, Line, Location, Body}|T], Unreachable,
Def, Defp, Defmacro, Defmacrop, Exports, Functions) ->
split_definition(T, Unreachable, [Tuple|Def], Defp, Defmacro, Defmacrop,
[export(def, Tuple)|Exports],
add_definition(Line, Location, Body, Functions));
split_definition([{Tuple, defp, Line, Location, Body}|T], Unreachable,
Def, Defp, Defmacro, Defmacrop, Exports, Functions) ->
case lists:member(Tuple, Unreachable) of
false -> false ->
NewAll = All, split_definition(T, Unreachable, Def, [Tuple|Defp], Defmacro, Defmacrop,
{Functions, Tail}; Exports, add_definition(Line, Location, Body, Functions));
_ -> true ->
NewAll = [Tuple|All], split_definition(T, Unreachable, Def, [Tuple|Defp], Defmacro, Defmacrop,
function_for_stored_definition(NewFun, Clauses, Functions, Tail) Exports, Functions)
end, end;
unwrap_definition(T, CTable, NewAll, NewExports, NewPrivate, split_definition([{Tuple, defmacro, Line, Location, Body}|T], Unreachable,
NewDef, NewDefmacro, NewFunctions, NewTail); Def, Defp, Defmacro, Defmacrop, Exports, Functions) ->
unwrap_definition([], _CTable, All, Exports, Private, Def, Defmacro, Functions, Tail) -> split_definition(T, Unreachable, Def, Defp, [Tuple|Defmacro], Defmacrop,
{All, Exports, Private, ordsets:from_list(Def), [export(defmacro, Tuple)|Exports],
ordsets:from_list(Defmacro), lists:reverse(Tail ++ Functions)}. add_definition(Line, Location, Body, Functions));
unwrap_definition(def, Tuple, Fun, Exports, Private, Def, Defmacro) -> split_definition([{Tuple, defmacrop, _Line, _Location, _Body}|T], Unreachable,
{Fun, [Tuple|Exports], Private, [Tuple|Def], Defmacro}; Def, Defp, Defmacro, Defmacrop, Exports, Functions) ->
unwrap_definition(defmacro, {Name, Arity} = Tuple, Fun, Exports, Private, Def, Defmacro) -> split_definition(T, Unreachable, Def, Defp, Defmacro, [Tuple|Defmacrop],
Macro = {elixir_utils:macro_name(Name), Arity + 1}, Exports, Functions);
{setelement(1, Fun, Macro), [Macro|Exports], Private, Def, [Tuple|Defmacro]};
unwrap_definition(defp, Tuple, Fun, Exports, Private, Def, Defmacro) -> split_definition([], _Unreachable, Def, Defp, Defmacro, Defmacrop, Exports, {Head, Tail}) ->
%% {Name, Arity}, Kind, Line, Check, Defaults {Def, Defp, Defmacro, Defmacrop, Exports, Head ++ Tail}.
Info = {Tuple, defp, element(3, Fun), element(5, Fun), element(7, Fun)},
{Fun, Exports, [Info|Private], Def, Defmacro};
unwrap_definition(defmacrop, Tuple, Fun, Exports, Private, Def, Defmacro) ->
%% {Name, Arity}, Kind, Line, Check, Defaults
Info = {Tuple, defmacrop, element(3, Fun), element(5, Fun), element(7, Fun)},
{false, Exports, [Info|Private], Def, Defmacro}.
%% Helpers %% Helpers
function_for_stored_definition({{Name,Arity}, _, Line, _, _, nil, _}, Clauses, Functions, Tail) -> export(Kind, {Name, Arity}) when Kind == defmacro; Kind == defmacrop ->
{[{function, Line, Name, Arity, Clauses}|Functions], Tail}; {elixir_utils:macro_name(Name), Arity + 1};
export(Kind, {Name, Arity}) when Kind == def; Kind == defp ->
{Name, Arity}.
function_for_stored_definition({{Name,Arity}, _, Line, _, _, Location, _}, Clauses, Functions, Tail) -> function_for_stored_definition(Line, {Name,Arity}, Clauses) ->
{Functions, [ {function, Line, Name, Arity, Clauses}.
{function, Line, Name, Arity, Clauses},
{attribute, Line, file, Location} | Tail add_definition(_Line, nil, Body, {Head, Tail}) ->
]}. {[Body|Head], Tail};
add_definition(Line, Location, Body, {Head, Tail}) ->
{Head,
[{attribute, Line, file, Location}, Body|Tail]}.
default_function_for(Kind, Name, {clause, Line, Args, _Guards, _Exprs} = Clause) default_function_for(Kind, Name, {clause, Line, Args, _Guards, _Exprs} = Clause)
when Kind == defmacro; Kind == defmacrop -> when Kind == defmacro; Kind == defmacrop ->
{function, Line, Name, length(Args) - 1, [Clause]}; {function, Line, Name, length(Args) - 1, [Clause]};
default_function_for(_, Name, {clause, Line, Args, _Guards, _Exprs} = Clause) -> default_function_for(_, Name, {clause, Line, Args, _Guards, _Exprs} = Clause) ->
{function, Line, Name, length(Args), [Clause]}. {function, Line, Name, length(Args), [Clause]}.
warn_bodyless_function(_Line, _File, Special, _Kind, _Tuple)
when Special == 'Elixir.Kernel.SpecialForms'; Special == 'Elixir.Module' ->
ok;
warn_bodyless_function(Line, File, _Module, Kind, Tuple) ->
elixir_errors:form_warn([{line, Line}], File, ?MODULE, {bodyless_fun, Kind, Tuple}),
ok.
%% Store each definition in the table. %% Store each definition in the table.
%% This function also checks and emit warnings in case %% This function also checks and emit warnings in case
%% the kind, of the visibility of the function changes. %% the kind, of the visibility of the function changes.
store_each(Check, Kind, File, Location, Table, CTable, Defaults, {function, Line, Name, Arity, Clauses}) -> store_each(Check, Kind, File, Location, Table, CTable, Defaults, {function, Line, Name, Arity, Clauses}) ->
Tuple = {Name, Arity}, Tuple = {Name, Arity},
HasBody = Clauses =/= [],
case ets:lookup(Table, Tuple) of case ets:lookup(Table, Tuple) of
[{Tuple, StoredKind, StoredLine, StoredFile, StoredCheck, StoredLocation, StoredDefaults}] -> [{Tuple, StoredKind, StoredLine, StoredFile, StoredCheck, StoredLocation, {StoredDefaults, LastHasBody, LastDefaults}}] ->
FinalLine = StoredLine, FinalLine = StoredLine,
FinalLocation = StoredLocation, FinalLocation = StoredLocation,
FinalDefaults = max(Defaults, StoredDefaults), FinalDefaults = {max(Defaults, StoredDefaults), HasBody, Defaults},
check_valid_kind(Line, File, Name, Arity, Kind, StoredKind), check_valid_kind(Line, File, Name, Arity, Kind, StoredKind),
(Check and StoredCheck) andalso (Check and StoredCheck) andalso
check_valid_clause(Line, File, Name, Arity, Kind, Table, StoredLine, StoredFile), check_valid_clause(Line, File, Name, Arity, Kind, Table, StoredLine, StoredFile),
check_valid_defaults(Line, File, Name, Arity, Kind, Defaults, StoredDefaults); check_valid_defaults(Line, File, Name, Arity, Kind, Defaults, LastDefaults, LastHasBody);
[] -> [] ->
FinalLine = Line, FinalLine = Line,
FinalLocation = Location, FinalLocation = Location,
FinalDefaults = Defaults FinalDefaults = {Defaults, HasBody, Defaults}
end, end,
Check andalso ets:insert(Table, {last, {Name, Arity}}), Check andalso ets:insert(Table, {last, {Name, Arity}}),
ets:insert(CTable, [{Tuple, Clause} || Clause <- Clauses ]), ets:insert(CTable, [{Tuple, Clause} || Clause <- Clauses ]),
@@ -307,7 +351,7 @@ store_each(Check, Kind, File, Location, Table, CTable, Defaults, {function, Line
check_valid_kind(_Line, _File, _Name, _Arity, Kind, Kind) -> []; check_valid_kind(_Line, _File, _Name, _Arity, Kind, Kind) -> [];
check_valid_kind(Line, File, Name, Arity, Kind, StoredKind) -> check_valid_kind(Line, File, Name, Arity, Kind, StoredKind) ->
elixir_errors:form_error(Line, File, ?MODULE, elixir_errors:form_error([{line, Line}], File, ?MODULE,
{changed_kind, {Name, Arity, StoredKind, Kind}}). {changed_kind, {Name, Arity, StoredKind, Kind}}).
check_valid_clause(Line, File, Name, Arity, Kind, Table, StoredLine, StoredFile) -> check_valid_clause(Line, File, Name, Arity, Kind, Table, StoredLine, StoredFile) ->
@@ -315,21 +359,29 @@ check_valid_clause(Line, File, Name, Arity, Kind, Table, StoredLine, StoredFile)
{Name,Arity} -> []; {Name,Arity} -> [];
[] -> []; [] -> [];
_ -> _ ->
Relative = elixir_utils:relative_to_cwd(StoredFile), Relative = elixir_utils:relative_to_cwd(elixir_utils:relative_to_cwd(StoredFile)),
elixir_errors:handle_file_warning(File, {Line, ?MODULE, elixir_errors:form_warn([{line, Line}], File, ?MODULE,
{ungrouped_clause, {Kind, Name, Arity, StoredLine, Relative}}}) {ungrouped_clause, {Kind, Name, Arity, StoredLine, Relative}})
end. end.
check_valid_defaults(_Line, _File, _Name, _Arity, _Kind, 0, _) -> []; % Any clause after clause with defaults (body less does not count)
check_valid_defaults(Line, File, Name, Arity, Kind, _, 0) -> check_valid_defaults(Line, File, Name, Arity, Kind, _, StoredDefaults, true) when StoredDefaults > 0 ->
elixir_errors:handle_file_warning(File, {Line, ?MODULE, {out_of_order_defaults, {Kind, Name, Arity}}}); elixir_errors:form_error([{line, Line}], File, ?MODULE,
check_valid_defaults(Line, File, Name, Arity, Kind, _, _) -> {clauses_with_defaults, {Kind, Name, Arity}});
elixir_errors:form_error(Line, File, ?MODULE, {clauses_with_defaults, {Kind, Name, Arity}}). % Clause without defaults
check_valid_defaults(_Line, _File, _Name, _Arity, _Kind, 0, _, _) -> [];
% Clause with defaults after clause without defaults
check_valid_defaults(Line, File, Name, Arity, Kind, _, 0, _) ->
elixir_errors:form_warn([{line, Line}], File, ?MODULE, {out_of_order_defaults, {Kind, Name, Arity}});
% Clause with defaults after clause with defaults
check_valid_defaults(Line, File, Name, Arity, Kind, _, _, _) ->
elixir_errors:form_error([{line, Line}], File, ?MODULE,
{clauses_with_defaults, {Kind, Name, Arity}}).
check_previous_defaults(Table, Line, Name, Arity, Kind, Defaults, E) -> check_previous_defaults(Table, Line, Name, Arity, Kind, Defaults, E) ->
Matches = ets:match(Table, {{Name, '$2'}, '$1', '_', '_', '_', '_', '$3'}), Matches = ets:match(Table, {{Name, '$2'}, '$1', '_', '_', '_', '_', {'$3', '_', '_'}}),
[ begin [ begin
elixir_errors:form_error(Line, ?m(E, file), ?MODULE, elixir_errors:form_error([{line, Line}], ?m(E, file), ?MODULE,
{defs_with_defaults, Name, {Kind, Arity}, {K, A}}) {defs_with_defaults, Name, {Kind, Arity}, {K, A}})
end || [K, A, D] <- Matches, A /= Arity, D /= 0, defaults_conflict(A, D, Arity, Defaults)]. end || [K, A, D] <- Matches, A /= Arity, D /= 0, defaults_conflict(A, D, Arity, Defaults)].
@@ -339,7 +391,8 @@ defaults_conflict(A, D, Arity, Defaults) ->
check_args_for_bodyless_clause(Line, Args, E) -> check_args_for_bodyless_clause(Line, Args, E) ->
[ begin [ begin
elixir_errors:form_error(Line, ?m(E, file), ?MODULE, invalid_args_for_bodyless_clause) elixir_errors:form_error([{line, Line}], ?m(E, file), ?MODULE,
invalid_args_for_bodyless_clause)
end || Arg <- Args, invalid_arg(Arg) ]. end || Arg <- Args, invalid_arg(Arg) ].
invalid_arg({Name, _, Kind}) when is_atom(Name), is_atom(Kind) -> invalid_arg({Name, _, Kind}) when is_atom(Name), is_atom(Kind) ->
@@ -350,13 +403,16 @@ invalid_arg(_) ->
true. true.
assert_no_aliases_name(Line, '__aliases__', [Atom], #{file := File}) when is_atom(Atom) -> assert_no_aliases_name(Line, '__aliases__', [Atom], #{file := File}) when is_atom(Atom) ->
elixir_errors:form_error(Line, File, ?MODULE, {no_alias, Atom}); elixir_errors:form_error([{line, Line}], File, ?MODULE, {no_alias, Atom});
assert_no_aliases_name(_Meta, _Aliases, _Args, _S) -> assert_no_aliases_name(_Meta, _Aliases, _Args, _S) ->
ok. ok.
%% Format errors %% Format errors
format_error({bodyless_fun, Kind, {Name, Arity}}) ->
io_lib:format("bodyless clause provided for nonexistent ~ts ~ts/~B", [Kind, Name, Arity]);
format_error({no_module,{Kind,Name,Arity}}) -> format_error({no_module,{Kind,Name,Arity}}) ->
io_lib:format("cannot define function outside module, invalid scope for ~ts ~ts/~B", [Kind, Name, Arity]); io_lib:format("cannot define function outside module, invalid scope for ~ts ~ts/~B", [Kind, Name, Arity]);
+4 -3
View File
@@ -36,7 +36,7 @@ store(Module, Function, GenerateName) ->
{_Count, _Clause, _Neighbours, true} -> ok; {_Count, _Clause, _Neighbours, true} -> ok;
{Count, Clause, Neighbours, false} -> {Count, Clause, Neighbours, false} ->
overridable(Module, orddict:store(Function, {Count, Clause, Neighbours, true}, Overridable)), overridable(Module, orddict:store(Function, {Count, Clause, Neighbours, true}, Overridable)),
{{{Name, Arity}, Kind, Line, File, _Check, Location, Defaults}, Clauses} = Clause, {{{Name, Arity}, Kind, Line, File, _Check, Location, {Defaults, _HasBody, _LastDefaults}}, Clauses} = Clause,
{FinalKind, FinalName} = case GenerateName of {FinalKind, FinalName} = case GenerateName of
true -> {defp, name(Module, Function, Overridable)}; true -> {defp, name(Module, Function, Overridable)};
@@ -58,8 +58,9 @@ store(Module, Function, GenerateName) ->
%% Store pending declarations that were not manually made concrete. %% Store pending declarations that were not manually made concrete.
store_pending(Module) -> store_pending(Module) ->
[store(Module, X, false) || {X, {_, _, _, false}} <- overridable(Module), _ = [store(Module, X, false) || {X, {_, _, _, false}} <- overridable(Module),
not 'Elixir.Module':'defines?'(Module, X)]. not 'Elixir.Module':'defines?'(Module, X)],
ok.
%% Error handling %% Error handling
+30 -18
View File
@@ -4,7 +4,7 @@
-module(elixir_dispatch). -module(elixir_dispatch).
-export([dispatch_import/5, dispatch_require/6, -export([dispatch_import/5, dispatch_require/6,
require_function/5, import_function/4, require_function/5, import_function/4,
expand_import/5, expand_require/5, expand_import/6, expand_require/5,
default_functions/0, default_macros/0, default_requires/0, default_functions/0, default_macros/0, default_requires/0,
find_import/4, format_error/1]). find_import/4, format_error/1]).
-include("elixir.hrl"). -include("elixir.hrl").
@@ -18,6 +18,7 @@
-define(list, 'Elixir.List'). -define(list, 'Elixir.List').
-define(map, 'Elixir.Map'). -define(map, 'Elixir.Map').
-define(node, 'Elixir.Node'). -define(node, 'Elixir.Node').
-define(port, 'Elixir.Port').
-define(process, 'Elixir.Process'). -define(process, 'Elixir.Process').
-define(string, 'Elixir.String'). -define(string, 'Elixir.String').
-define(system, 'Elixir.System'). -define(system, 'Elixir.System').
@@ -87,7 +88,7 @@ remote_function(Meta, Receiver, Name, Arity, E) ->
dispatch_import(Meta, Name, Args, E, Callback) -> dispatch_import(Meta, Name, Args, E, Callback) ->
Arity = length(Args), Arity = length(Args),
case expand_import(Meta, {Name, Arity}, Args, E, []) of case expand_import(Meta, {Name, Arity}, Args, E, [], false) of
{ok, Receiver, Quoted} -> {ok, Receiver, Quoted} ->
expand_quoted(Meta, Receiver, Name, Arity, Quoted, E); expand_quoted(Meta, Receiver, Name, Arity, Quoted, E);
{ok, Receiver, NewName, NewArgs} -> {ok, Receiver, NewName, NewArgs} ->
@@ -114,12 +115,12 @@ dispatch_require(_Meta, Receiver, Name, Args, _E, Callback) ->
%% Macros expansion %% Macros expansion
expand_import(Meta, {Name, Arity} = Tuple, Args, E, Extra) -> expand_import(Meta, {Name, Arity} = Tuple, Args, E, Extra, External) ->
Module = ?m(E, module), Module = ?m(E, module),
Dispatch = find_dispatch(Meta, Tuple, Extra, E), Dispatch = find_dispatch(Meta, Tuple, Extra, E),
Function = ?m(E, function), Function = ?m(E, function),
Local = (Function /= nil) andalso (Function /= Tuple) andalso AllowLocals = External orelse ((Function /= nil) andalso (Function /= Tuple)),
elixir_locals:macro_for(Module, Name, Arity), Local = AllowLocals andalso elixir_locals:macro_for(Module, Name, Arity),
case Dispatch of case Dispatch of
%% In case it is an import, we dispatch the import. %% In case it is an import, we dispatch the import.
@@ -476,6 +477,17 @@ inline(?process, demonitor, 2) -> {erlang, demonitor};
inline(?process, link, 1) -> {erlang, link}; inline(?process, link, 1) -> {erlang, link};
inline(?process, unlink, 1) -> {erlang, unlink}; inline(?process, unlink, 1) -> {erlang, unlink};
inline(?port, open, 2) -> {erlang, open_port};
inline(?port, call, 3) -> {erlang, port_call};
inline(?port, close, 1) -> {erlang, port_close};
inline(?port, command, 2) -> {erlang, port_command};
inline(?port, command, 3) -> {erlang, port_command};
inline(?port, connect, 2) -> {erlang, port_connect};
inline(?port, control, 3) -> {erlang, port_control};
inline(?port, info, 1) -> {erlang, port_info};
inline(?port, info, 2) -> {erlang, port_info};
inline(?port, list, 0) -> {erlang, ports};
inline(?string, to_float, 1) -> {erlang, binary_to_float}; inline(?string, to_float, 1) -> {erlang, binary_to_float};
inline(?string, to_integer, 1) -> {erlang, binary_to_integer}; inline(?string, to_integer, 1) -> {erlang, binary_to_integer};
inline(?string, to_integer, 2) -> {erlang, binary_to_integer}; inline(?string, to_integer, 2) -> {erlang, binary_to_integer};
@@ -507,15 +519,15 @@ deprecation_message(Warning, Message) ->
Message -> Warning ++ ", " ++ Message Message -> Warning ++ ", " ++ Message
end. end.
deprecation('Elixir.Mix.Generator', 'from_file', _) -> deprecation('Elixir.System', 'cmd', 1) ->
"instead pass [from_file: file] to embed_text/2 and embed_template/2 macros. " "use System.cmd/3 instead";
"Note that [from_file: file] expects paths relative to the current working " deprecation('Elixir.Inspect.Algebra', 'pretty', 2) ->
"directory and not to the current file"; "use Inspect.Algebra.format/2 instead";
deprecation('Elixir.EEx.TransformerEngine', '__using__', _) -> deprecation('Elixir.IO.ANSI', 'escape', _) ->
"check EEx.SmartEngine for how to build custom engines"; "use the new API in IO.ANSI.format/2 instead";
deprecation('Elixir.EEx.AssignsEngine', '__using__', _) -> deprecation('Elixir.IO.ANSI', 'escape_fragment', _) ->
"check EEx.SmartEngine for how to build custom engines"; "use the new API in IO.ANSI.format_fragment/2 instead";
deprecation('Elixir.Kernel', 'xor', _) -> deprecation('Elixir.IO.ANSI', 'terminal?', _) ->
true; %% Remember to remove xor operator from tokenizer "use IO.ANSI.enabled?/0 instead";
deprecation(_, _, _) -> deprecation(_, _, _) ->
false. false.
+88 -61
View File
@@ -2,36 +2,37 @@
% This is not exposed in the Elixir language. % This is not exposed in the Elixir language.
-module(elixir_errors). -module(elixir_errors).
-export([compile_error/3, compile_error/4, -export([compile_error/3, compile_error/4,
form_error/4, parse_error/4, warn/2, warn/3, form_error/4, form_warn/4, parse_error/4, warn/2, warn/3,
handle_file_warning/2, handle_file_warning/3, handle_file_error/2]). handle_file_warning/2, handle_file_warning/3, handle_file_error/2]).
-include("elixir.hrl"). -include("elixir.hrl").
-type line_or_meta() :: integer() | list(). -spec warn(non_neg_integer(), binary(), iolist()) -> ok.
warn(Warning) -> warn(Line, File, Warning) when is_integer(Line), is_binary(File) ->
CompilerPid = get(elixir_compiler_pid), warn(file_format(Line, File), Warning).
if
CompilerPid =/= undefined -> -spec warn(iolist(), iolist()) -> ok.
elixir_code_server:cast({register_warning, CompilerPid});
true -> false
end,
io:put_chars(standard_error, Warning).
warn(Caller, Warning) -> warn(Caller, Warning) ->
warn([Caller, "warning: ", Warning]). do_warn([Caller, "warning: ", Warning, $\n]).
warn(Line, File, Warning) when is_integer(Line) -> %% General forms handling.
warn(file_format(Line, File, "warning: " ++ Warning)).
%% Raised during expansion/translation/compilation. -spec form_error(list(), binary(), module(), any()) -> no_return().
-spec form_error(line_or_meta(), binary(), module(), any()) -> no_return().
form_error(Meta, File, Module, Desc) -> form_error(Meta, File, Module, Desc) ->
compile_error(Meta, File, format_error(Module, Desc)). compile_error(Meta, File, format_error(Module, Desc)).
-spec compile_error(line_or_meta(), binary(), iolist()) -> no_return(). -spec form_warn(list(), binary(), module(), any()) -> ok.
-spec compile_error(line_or_meta(), binary(), iolist(), list()) -> no_return().
form_warn(Meta, File, Module, Desc) when is_list(Meta) ->
{MetaLine, MetaFile} = meta_location(Meta, File),
warn(MetaLine, MetaFile, format_error(Module, Desc)).
%% Compilation error.
-spec compile_error(list(), binary(), iolist()) -> no_return().
-spec compile_error(list(), binary(), iolist(), list()) -> no_return().
compile_error(Meta, File, Message) when is_list(Message) -> compile_error(Meta, File, Message) when is_list(Message) ->
raise(Meta, File, 'Elixir.CompileError', elixir_utils:characters_to_binary(Message)). raise(Meta, File, 'Elixir.CompileError', elixir_utils:characters_to_binary(Message)).
@@ -39,23 +40,23 @@ compile_error(Meta, File, Message) when is_list(Message) ->
compile_error(Meta, File, Format, Args) when is_list(Format) -> compile_error(Meta, File, Format, Args) when is_list(Format) ->
compile_error(Meta, File, io_lib:format(Format, Args)). compile_error(Meta, File, io_lib:format(Format, Args)).
%% Raised on tokenizing/parsing %% Tokenization parsing/errors.
-spec parse_error(line_or_meta(), binary(), binary(), binary()) -> no_return(). -spec parse_error(non_neg_integer(), binary(), binary(), binary()) -> no_return().
parse_error(Meta, File, Error, <<>>) -> parse_error(Line, File, Error, <<>>) ->
Message = case Error of Message = case Error of
<<"syntax error before: ">> -> <<"syntax error: expression is incomplete">>; <<"syntax error before: ">> -> <<"syntax error: expression is incomplete">>;
_ -> Error _ -> Error
end, end,
raise(Meta, File, 'Elixir.TokenMissingError', Message); do_raise(Line, File, 'Elixir.TokenMissingError', Message);
%% Show a nicer message for missing end tokens %% Show a nicer message for missing end tokens
parse_error(Meta, File, <<"syntax error before: ">>, <<"'end'">>) -> parse_error(Line, File, <<"syntax error before: ">>, <<"'end'">>) ->
raise(Meta, File, 'Elixir.SyntaxError', <<"unexpected token: end">>); do_raise(Line, File, 'Elixir.SyntaxError', <<"unexpected token: end">>);
%% Binaries are wrapped in [<<...>>], so we need to unwrap them %% Binaries are wrapped in [<<...>>], so we need to unwrap them
parse_error(Meta, File, Error, <<"[", _/binary>> = Full) when is_binary(Error) -> parse_error(Line, File, Error, <<"[", _/binary>> = Full) when is_binary(Error) ->
Rest = Rest =
case binary:split(Full, <<"<<">>) of case binary:split(Full, <<"<<">>) of
[Lead, Token] -> [Lead, Token] ->
@@ -66,27 +67,27 @@ parse_error(Meta, File, Error, <<"[", _/binary>> = Full) when is_binary(Error) -
[_] -> [_] ->
<<$">> <<$">>
end, end,
raise(Meta, File, 'Elixir.SyntaxError', <<Error/binary, Rest/binary >>); do_raise(Line, File, 'Elixir.SyntaxError', <<Error/binary, Rest/binary >>);
%% Everything else is fine as is %% Everything else is fine as is
parse_error(Meta, File, Error, Token) when is_binary(Error), is_binary(Token) -> parse_error(Line, File, Error, Token) when is_binary(Error), is_binary(Token) ->
Message = <<Error / binary, Token / binary >>, Message = <<Error/binary, Token/binary >>,
raise(Meta, File, 'Elixir.SyntaxError', Message). do_raise(Line, File, 'Elixir.SyntaxError', Message).
%% Handle warnings and errors (called during module compilation) %% Handle warnings and errors from Erlang land (called during module compilation)
%% Ignore on bootstrap %% Ignore on bootstrap
handle_file_warning(true, _File, {_Line, sys_core_fold, nomatch_guard}) -> []; handle_file_warning(true, _File, {_Line, sys_core_fold, nomatch_guard}) -> ok;
handle_file_warning(true, _File, {_Line, sys_core_fold, {nomatch_shadow, _}}) -> []; handle_file_warning(true, _File, {_Line, sys_core_fold, {nomatch_shadow, _}}) -> ok;
%% Ignore always %% Ignore always
handle_file_warning(_, _File, {_Line, sys_core_fold, useless_building}) -> []; handle_file_warning(_, _File, {_Line, sys_core_fold, useless_building}) -> ok;
%% This is an Erlang bug, it considers {tuple, _}.call to always fail %% This is an Erlang bug, it considers {tuple, _}.call to always fail
handle_file_warning(_, _File, {_Line, v3_kernel, bad_call}) -> []; handle_file_warning(_, _File, {_Line, v3_kernel, bad_call}) -> ok;
%% We handle unused local warnings ourselves %% We handle unused local warnings ourselves
handle_file_warning(_, _File, {_Line, erl_lint, {unused_function, _}}) -> []; handle_file_warning(_, _File, {_Line, erl_lint, {unused_function, _}}) -> ok;
%% Make no_effect clauses pretty %% Make no_effect clauses pretty
handle_file_warning(_, File, {Line, sys_core_fold, {no_effect, {erlang, F, A}}}) -> handle_file_warning(_, File, {Line, sys_core_fold, {no_effect, {erlang, F, A}}}) ->
@@ -95,7 +96,7 @@ handle_file_warning(_, File, {Line, sys_core_fold, {no_effect, {erlang, F, A}}})
false -> false ->
case erl_internal:bif(F, A) of case erl_internal:bif(F, A) of
false -> {"the call to :erlang.~ts/~B has no effect", [F,A]}; false -> {"the call to :erlang.~ts/~B has no effect", [F,A]};
true -> {"the call to ~ts/~B has no effect", [F,A]} true -> {"the call to ~ts/~B has no effect", [F,A]}
end end
end, end,
Message = io_lib:format(Fmt, Args), Message = io_lib:format(Fmt, Args),
@@ -116,7 +117,7 @@ handle_file_warning(_, File, {Line,erl_lint,{undefined_behaviour_func,{Fun,Arity
handle_file_warning(_, File, {Line,erl_lint,{undefined_behaviour,Module}}) -> handle_file_warning(_, File, {Line,erl_lint,{undefined_behaviour,Module}}) ->
case elixir_compiler:get_opt(internal) of case elixir_compiler:get_opt(internal) of
true -> []; true -> ok;
false -> false ->
Message = io_lib:format("behaviour ~ts undefined", [elixir_aliases:inspect(Module)]), Message = io_lib:format("behaviour ~ts undefined", [elixir_aliases:inspect(Module)]),
warn(Line, File, Message) warn(Line, File, Message)
@@ -124,11 +125,15 @@ handle_file_warning(_, File, {Line,erl_lint,{undefined_behaviour,Module}}) ->
%% Ignore unused vars at "weird" lines (<= 0) %% Ignore unused vars at "weird" lines (<= 0)
handle_file_warning(_, _File, {Line,erl_lint,{unused_var,_Var}}) when Line =< 0 -> handle_file_warning(_, _File, {Line,erl_lint,{unused_var,_Var}}) when Line =< 0 ->
[]; ok;
%% Ignore shadowed vars as we guarantee no conflicts ourselves %% Ignore shadowed vars as we guarantee no conflicts ourselves
handle_file_warning(_, _File, {_Line,erl_lint,{shadowed_var,_Var,_Where}}) -> handle_file_warning(_, _File, {_Line,erl_lint,{shadowed_var,_Var,_Where}}) ->
[]; ok;
%% Rewrite nomatch_guard to be more generic it can happen inside if, unless, etc
handle_file_warning(_, File, {Line, sys_core_fold, nomatch_guard}) ->
warn(Line, File, "this check/guard will always yield the same result");
%% Properly format other unused vars %% Properly format other unused vars
handle_file_warning(_, File, {Line,erl_lint,{unused_var,Var}}) -> handle_file_warning(_, File, {Line,erl_lint,{unused_var,Var}}) ->
@@ -152,39 +157,27 @@ handle_file_error(File, {Line,erl_lint,{unsafe_var,Var,{In,_Where}}}) ->
_ -> In _ -> In
end, end,
Message = io_lib:format("cannot define variable ~ts inside ~ts", [format_var(Var), Translated]), Message = io_lib:format("cannot define variable ~ts inside ~ts", [format_var(Var), Translated]),
raise(Line, File, 'Elixir.CompileError', iolist_to_binary(Message)); do_raise(Line, File, 'Elixir.CompileError', elixir_utils:characters_to_binary(Message));
handle_file_error(File, {Line,erl_lint,{spec_fun_undefined,{M,F,A}}}) -> handle_file_error(File, {Line,erl_lint,{spec_fun_undefined,{M,F,A}}}) ->
Message = io_lib:format("spec for undefined function ~ts.~ts/~B", [elixir_aliases:inspect(M), F, A]), Message = io_lib:format("spec for undefined function ~ts.~ts/~B", [elixir_aliases:inspect(M), F, A]),
raise(Line, File, 'Elixir.CompileError', iolist_to_binary(Message)); do_raise(Line, File, 'Elixir.CompileError', elixir_utils:characters_to_binary(Message));
handle_file_error(File, {Line,Module,Desc}) -> handle_file_error(File, {Line,Module,Desc}) ->
form_error(Line, File, Module, Desc). Message = format_error(Module, Desc),
do_raise(Line, File, 'Elixir.CompileError', elixir_utils:characters_to_binary(Message)).
%% Helpers %% Helpers
raise(Meta, File, Kind, Message) when is_list(Meta) -> raise(Meta, File, Kind, Message) when is_list(Meta) ->
raise(?line(Meta), File, Kind, Message); {MetaLine, MetaFile} = meta_location(Meta, File),
do_raise(MetaLine, MetaFile, Kind, Message).
raise(none, File, Kind, Message) -> file_format(0, File) ->
raise(0, File, Kind, Message); io_lib:format("~ts: ", [elixir_utils:relative_to_cwd(File)]);
raise(Line, File, Kind, Message) when is_integer(Line), is_binary(File) -> file_format(Line, File) ->
%% Populate the stacktrace so we can raise it io_lib:format("~ts:~w: ", [elixir_utils:relative_to_cwd(File), Line]).
try
throw(ok)
catch
ok -> ok
end,
Stacktrace = erlang:get_stacktrace(),
Exception = Kind:exception([{description, Message}, {file, File}, {line, Line}]),
erlang:raise(error, Exception, tl(Stacktrace)).
file_format(0, File, Message) when is_binary(File) ->
io_lib:format("~ts: ~ts~n", [elixir_utils:relative_to_cwd(File), Message]);
file_format(Line, File, Message) when is_binary(File) ->
io_lib:format("~ts:~w: ~ts~n", [elixir_utils:relative_to_cwd(File), Line, Message]).
format_var(Var) -> format_var(Var) ->
list_to_atom(lists:takewhile(fun(X) -> X /= $@ end, atom_to_list(Var))). list_to_atom(lists:takewhile(fun(X) -> X /= $@ end, atom_to_list(Var))).
@@ -215,3 +208,37 @@ translate_comp_op('=<') -> '<=';
translate_comp_op('=:=') -> '==='; translate_comp_op('=:=') -> '===';
translate_comp_op('=/=') -> '!=='; translate_comp_op('=/=') -> '!==';
translate_comp_op(Other) -> Other. translate_comp_op(Other) -> Other.
meta_location(Meta, File) ->
case lists:keyfind(file, 1, Meta) of
{file, MetaFile} when is_binary(MetaFile) ->
case lists:keyfind(keep, 1, Meta) of
{keep, MetaLine} when is_integer(MetaLine) -> ok;
_ -> MetaLine = 0
end,
{MetaLine, MetaFile};
_ ->
{?line(Meta), File}
end.
do_warn(Warning) ->
CompilerPid = get(elixir_compiler_pid),
if
CompilerPid =/= undefined ->
elixir_code_server:cast({register_warning, CompilerPid});
true -> false
end,
io:put_chars(standard_error, Warning),
ok.
do_raise(none, File, Kind, Message) ->
do_raise(0, File, Kind, Message);
do_raise(Line, File, Kind, Message) when is_integer(Line), is_binary(File), is_binary(Message) ->
try
throw(ok)
catch
ok -> ok
end,
Stacktrace = erlang:get_stacktrace(),
Exception = Kind:exception([{description, Message}, {file, File}, {line, Line}]),
erlang:raise(error, Exception, tl(Stacktrace)).
+19 -12
View File
@@ -169,7 +169,7 @@ expand({quote, Meta, [KV, Do]}, E) when is_list(Do) ->
false -> compile_error(Meta, E#elixir_scope.file, "missing do keyword in quote") false -> compile_error(Meta, E#elixir_scope.file, "missing do keyword in quote")
end, end,
ValidOpts = [context, location, line, unquote, bind_quoted], ValidOpts = [context, location, line, file, unquote, bind_quoted],
{EKV, ET} = expand_opts(Meta, quote, ValidOpts, KV, E), {EKV, ET} = expand_opts(Meta, quote, ValidOpts, KV, E),
Context = case lists:keyfind(context, 1, EKV) of Context = case lists:keyfind(context, 1, EKV) of
@@ -185,8 +185,20 @@ expand({quote, Meta, [KV, Do]}, E) when is_list(Do) ->
end end
end, end,
Keep = lists:keyfind(location, 1, EKV) == {location, keep}, {File, Line} = case lists:keyfind(location, 1, EKV) of
Line = proplists:get_value(line, EKV, false), {location, keep} ->
{elixir_utils:relative_to_cwd(?m(E, file)), false};
false ->
{ case lists:keyfind(file, 1, EKV) of
{file, F} -> F;
false -> nil
end,
case lists:keyfind(line, 1, EKV) of
{line, L} -> L;
false -> false
end }
end,
{Binding, DefaultUnquote} = case lists:keyfind(bind_quoted, 1, EKV) of {Binding, DefaultUnquote} = case lists:keyfind(bind_quoted, 1, EKV) of
{bind_quoted, BQ} -> {BQ, false}; {bind_quoted, BQ} -> {BQ, false};
@@ -194,11 +206,11 @@ expand({quote, Meta, [KV, Do]}, E) when is_list(Do) ->
end, end,
Unquote = case lists:keyfind(unquote, 1, EKV) of Unquote = case lists:keyfind(unquote, 1, EKV) of
{unquote, Bool} when is_boolean(Bool) -> Bool; {unquote, U} when is_boolean(U) -> U;
false -> DefaultUnquote false -> DefaultUnquote
end, end,
Q = #elixir_quote{line=Line, keep=Keep, unquote=Unquote, context=Context}, Q = #elixir_quote{line=Line, file=File, unquote=Unquote, context=Context},
{Quoted, _Q} = elixir_quote:quote(Exprs, Binding, Q, ET), {Quoted, _Q} = elixir_quote:quote(Exprs, Binding, Q, ET),
expand(Quoted, ET); expand(Quoted, ET);
@@ -296,13 +308,8 @@ expand({Name, Meta, Kind} = Var, #{vars := Vars} = E) when is_atom(Name), is_ato
VarMeta = lists:keyfind(var, 1, Meta), VarMeta = lists:keyfind(var, 1, Meta),
if if
VarMeta == {var, true} -> VarMeta == {var, true} ->
Extra = case Kind of compile_error(Meta, ?m(E, file), "expected var \"~ts\"~ts to expand to an existing variable "
nil -> ""; "or be part of a match", [Name, elixir_scope:context_info(Kind)]);
_ -> io_lib:format(" (context ~ts)", [elixir_aliases:inspect(Kind)])
end,
compile_error(Meta, ?m(E, file), "expected var ~ts~ts to expand to an existing "
"variable or be a part of a match", [Name, Extra]);
true -> true ->
expand({Name, Meta, []}, E) expand({Name, Meta, []}, E)
end end
+63 -15
View File
@@ -33,11 +33,8 @@ extract(Line, Scope, Interpol, [$\\, $\r, $\n|Rest], Buffer, Output, Last) ->
extract(Line, Scope, Interpol, [$\n|Rest], Buffer, Output, Last) -> extract(Line, Scope, Interpol, [$\n|Rest], Buffer, Output, Last) ->
extract(Line+1, Scope, Interpol, Rest, [$\n|Buffer], Output, Last); extract(Line+1, Scope, Interpol, Rest, [$\n|Buffer], Output, Last);
extract(Line, Scope, Interpol, [$\\, $#, ${|Rest], Buffer, Output, Last) -> extract(Line, Scope, true, [$\\, $#, ${|Rest], Buffer, Output, Last) ->
extract(Line, Scope, Interpol, Rest, [${,$#|Buffer], Output, Last); extract(Line, Scope, true, Rest, [${,$#|Buffer], 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, Output, Last) -> extract(Line, Scope, true, [$#, ${|Rest], Buffer, Output, Last) ->
Output1 = build_string(Line, Buffer, Output), Output1 = build_string(Line, Buffer, Output),
@@ -52,6 +49,9 @@ extract(Line, Scope, true, [$#, ${|Rest], Buffer, Output, Last) ->
{error, {string, Line, "missing interpolation terminator:}", []}} {error, {string, Line, "missing interpolation terminator:}", []}}
end; end;
extract(Line, Scope, Interpol, [$\\,Char|Rest], Buffer, Output, Last) ->
extract(Line, Scope, Interpol, Rest, [Char,$\\|Buffer], Output, Last);
%% Catch all clause %% Catch all clause
extract(Line, Scope, Interpol, [Char|Rest], Buffer, Output, Last) -> extract(Line, Scope, Interpol, [Char|Rest], Buffer, Output, Last) ->
@@ -75,42 +75,88 @@ unescape_chars(String) ->
unescape_chars(String, Map) -> unescape_chars(String, Map) ->
Octals = Map($0) /= false, Octals = Map($0) /= false,
Hex = Map($x) /= false, Hex = Map($x) == true,
unescape_chars(String, Map, Octals, Hex, <<>>). unescape_chars(String, Map, Octals, Hex, <<>>).
%% Deprecated
unescape_chars(<<$\\,A,B,C,Rest/binary>>, Map, true, Hex, Acc) when ?is_octal(A), A =< $3, ?is_octal(B), ?is_octal(C) -> unescape_chars(<<$\\,A,B,C,Rest/binary>>, Map, true, Hex, Acc) when ?is_octal(A), A =< $3, ?is_octal(B), ?is_octal(C) ->
io:format(standard_error, "warning: octals inside strings/sigils/chars are deprecated, got: \\~ts~n", [<<A, B, C>>]),
append_escaped(Rest, Map, [A,B,C], true, Hex, Acc, 8); append_escaped(Rest, Map, [A,B,C], true, Hex, Acc, 8);
unescape_chars(<<$\\,A,B,Rest/binary>>, Map, true, Hex, Acc) when ?is_octal(A), ?is_octal(B) -> unescape_chars(<<$\\,A,B,Rest/binary>>, Map, true, Hex, Acc) when ?is_octal(A), ?is_octal(B) ->
io:format(standard_error, "warning: octals inside strings/sigils/chars are deprecated, got: \\~ts~n", [<<A, B>>]),
append_escaped(Rest, Map, [A,B], true, Hex, Acc, 8); append_escaped(Rest, Map, [A,B], true, Hex, Acc, 8);
unescape_chars(<<$\\,$0,Rest/binary>>, Map, true, Hex, Acc) ->
append_escaped(Rest, Map, [$0], true, Hex, Acc, 8);
unescape_chars(<<$\\,A,Rest/binary>>, Map, true, Hex, Acc) when ?is_octal(A) -> unescape_chars(<<$\\,A,Rest/binary>>, Map, true, Hex, Acc) when ?is_octal(A) ->
io:format(standard_error, "warning: octals inside strings/sigils/chars are deprecated, got: \\~ts~n", [<<A>>]),
append_escaped(Rest, Map, [A], true, Hex, Acc, 8); append_escaped(Rest, Map, [A], true, Hex, Acc, 8);
unescape_chars(<<$\\,P,A,B,Rest/binary>>, Map, Octal, true, Acc) when (P == $x orelse P == $X), ?is_hex(A), ?is_hex(B) -> unescape_chars(<<$\\,$X,A,B,Rest/binary>>, Map, Octal, true, Acc) when ?is_hex(A), ?is_hex(B) ->
io:format(standard_error, "warning: \\X inside strings/sigils/chars is deprecated, please use \\x instead~n", []),
append_escaped(Rest, Map, [A,B], Octal, true, Acc, 16); append_escaped(Rest, Map, [A,B], Octal, true, Acc, 16);
unescape_chars(<<$\\,P,A,Rest/binary>>, Map, Octal, true, Acc) when (P == $x orelse P == $X), ?is_hex(A) -> unescape_chars(<<$\\,$X,A,Rest/binary>>, Map, Octal, true, Acc) when ?is_hex(A) ->
io:format(standard_error, "warning: \\X inside strings/sigils/chars is deprecated, please use \\x instead~n", []),
append_escaped(Rest, Map, [A], Octal, true, Acc, 16); append_escaped(Rest, Map, [A], Octal, true, Acc, 16);
unescape_chars(<<$\\,P,${,A,$},Rest/binary>>, Map, Octal, true, Acc) when (P == $x orelse P == $X), ?is_hex(A) -> unescape_chars(<<$\\,$X,${,A,$},Rest/binary>>, Map, Octal, true, Acc) when ?is_hex(A) ->
io:format(standard_error, "warning: \\X inside strings/sigils/chars is deprecated, please use \\x instead~n", []),
append_escaped(Rest, Map, [A], Octal, true, Acc, 16); append_escaped(Rest, Map, [A], Octal, true, Acc, 16);
unescape_chars(<<$\\,P,${,A,B,$},Rest/binary>>, Map, Octal, true, Acc) when (P == $x orelse P == $X), ?is_hex(A), ?is_hex(B) -> unescape_chars(<<$\\,$X,${,A,B,$},Rest/binary>>, Map, Octal, true, Acc) when ?is_hex(A), ?is_hex(B) ->
io:format(standard_error, "warning: \\X inside strings/sigils/chars is deprecated, please use \\x instead~n", []),
append_escaped(Rest, Map, [A,B], Octal, true, Acc, 16); append_escaped(Rest, Map, [A,B], Octal, true, Acc, 16);
unescape_chars(<<$\\,P,${,A,B,C,$},Rest/binary>>, Map, Octal, true, Acc) when (P == $x orelse P == $X), ?is_hex(A), ?is_hex(B), ?is_hex(C) -> unescape_chars(<<$\\,$X,${,A,B,C,$},Rest/binary>>, Map, Octal, true, Acc) when ?is_hex(A), ?is_hex(B), ?is_hex(C) ->
io:format(standard_error, "warning: \\X inside strings/sigils/chars is deprecated, please use \\x instead~n", []),
append_escaped(Rest, Map, [A,B,C], Octal, true, Acc, 16); append_escaped(Rest, Map, [A,B,C], Octal, true, Acc, 16);
unescape_chars(<<$\\,P,${,A,B,C,D,$},Rest/binary>>, Map, Octal, true, Acc) when (P == $x orelse P == $X), ?is_hex(A), ?is_hex(B), ?is_hex(C), ?is_hex(D) -> unescape_chars(<<$\\,$X,${,A,B,C,D,$},Rest/binary>>, Map, Octal, true, Acc) when ?is_hex(A), ?is_hex(B), ?is_hex(C), ?is_hex(D) ->
io:format(standard_error, "warning: \\X inside strings/sigils/chars is deprecated, please use \\x instead~n", []),
append_escaped(Rest, Map, [A,B,C,D], Octal, true, Acc, 16); append_escaped(Rest, Map, [A,B,C,D], Octal, true, Acc, 16);
unescape_chars(<<$\\,P,${,A,B,C,D,E,$},Rest/binary>>, Map, Octal, true, Acc) when (P == $x orelse P == $X), ?is_hex(A), ?is_hex(B), ?is_hex(C), ?is_hex(D), ?is_hex(E) -> unescape_chars(<<$\\,$X,${,A,B,C,D,E,$},Rest/binary>>, Map, Octal, true, Acc) when ?is_hex(A), ?is_hex(B), ?is_hex(C), ?is_hex(D), ?is_hex(E) ->
append_escaped(Rest, Map, [A,B,C,D,E], Octal, true, Acc, 16); io:format(standard_error, "warning: \\X inside strings/sigils/chars is deprecated, please use \\x instead~n", []),
append_escaped(Rest, Map, [A,B,C,D,E], Octal, true, Acc, 16);
unescape_chars(<<$\\,P,${,A,B,C,D,E,F,$},Rest/binary>>, Map, Octal, true, Acc) when (P == $x orelse P == $X), ?is_hex(A), ?is_hex(B), ?is_hex(C), ?is_hex(D), ?is_hex(E), ?is_hex(F) -> unescape_chars(<<$\\,$X,${,A,B,C,D,E,F,$},Rest/binary>>, Map, Octal, true, Acc) when ?is_hex(A), ?is_hex(B), ?is_hex(C), ?is_hex(D), ?is_hex(E), ?is_hex(F) ->
io:format(standard_error, "warning: \\X inside strings/sigils/chars is deprecated, please use \\x instead~n", []),
append_escaped(Rest, Map, [A,B,C,D,E,F], Octal, true, Acc, 16); append_escaped(Rest, Map, [A,B,C,D,E,F], Octal, true, Acc, 16);
%% End of deprecated
unescape_chars(<<$\\,$x,A,B,Rest/binary>>, Map, Octal, true, Acc) when ?is_hex(A), ?is_hex(B) ->
append_escaped(Rest, Map, [A,B], Octal, true, Acc, 16);
unescape_chars(<<$\\,$x,A,Rest/binary>>, Map, Octal, true, Acc) when ?is_hex(A) ->
append_escaped(Rest, Map, [A], Octal, true, Acc, 16);
unescape_chars(<<$\\,$x,${,A,$},Rest/binary>>, Map, Octal, true, Acc) when ?is_hex(A) ->
append_escaped(Rest, Map, [A], Octal, true, Acc, 16);
unescape_chars(<<$\\,$x,${,A,B,$},Rest/binary>>, Map, Octal, true, Acc) when ?is_hex(A), ?is_hex(B) ->
append_escaped(Rest, Map, [A,B], Octal, true, Acc, 16);
unescape_chars(<<$\\,$x,${,A,B,C,$},Rest/binary>>, Map, Octal, true, Acc) when ?is_hex(A), ?is_hex(B), ?is_hex(C) ->
append_escaped(Rest, Map, [A,B,C], Octal, true, Acc, 16);
unescape_chars(<<$\\,$x,${,A,B,C,D,$},Rest/binary>>, Map, Octal, true, Acc) when ?is_hex(A), ?is_hex(B), ?is_hex(C), ?is_hex(D) ->
append_escaped(Rest, Map, [A,B,C,D], Octal, true, Acc, 16);
unescape_chars(<<$\\,$x,${,A,B,C,D,E,$},Rest/binary>>, Map, Octal, true, Acc) when ?is_hex(A), ?is_hex(B), ?is_hex(C), ?is_hex(D), ?is_hex(E) ->
append_escaped(Rest, Map, [A,B,C,D,E], Octal, true, Acc, 16);
unescape_chars(<<$\\,$x,${,A,B,C,D,E,F,$},Rest/binary>>, Map, Octal, true, Acc) when ?is_hex(A), ?is_hex(B), ?is_hex(C), ?is_hex(D), ?is_hex(E), ?is_hex(F) ->
append_escaped(Rest, Map, [A,B,C,D,E,F], Octal, true, Acc, 16);
unescape_chars(<<$\\,$x,_/binary>>, _Map, _Octal, true, _Acc) ->
Msg = <<"missing hex sequence after \\x">>,
error('Elixir.ArgumentError':exception([{message,Msg}]));
unescape_chars(<<$\\,Escaped,Rest/binary>>, Map, Octals, Hex, Acc) -> unescape_chars(<<$\\,Escaped,Rest/binary>>, Map, Octals, Hex, Acc) ->
case Map(Escaped) of case Map(Escaped) of
false -> unescape_chars(Rest, Map, Octals, Hex, <<Acc/binary, $\\, Escaped>>); false -> unescape_chars(Rest, Map, Octals, Hex, <<Acc/binary, $\\, Escaped>>);
@@ -134,6 +180,7 @@ append_escaped(Rest, Map, List, Octal, Hex, Acc, Base) ->
% Unescape Helpers % Unescape Helpers
unescape_map($0) -> 0;
unescape_map($a) -> 7; unescape_map($a) -> 7;
unescape_map($b) -> $\b; unescape_map($b) -> $\b;
unescape_map($d) -> $\d; unescape_map($d) -> $\d;
@@ -144,6 +191,7 @@ unescape_map($r) -> $\r;
unescape_map($s) -> $\s; unescape_map($s) -> $\s;
unescape_map($t) -> $\t; unescape_map($t) -> $\t;
unescape_map($v) -> $\v; unescape_map($v) -> $\v;
unescape_map($x) -> true;
unescape_map(E) -> E. unescape_map(E) -> E.
% Extract Helpers % Extract Helpers
+14 -27
View File
@@ -27,51 +27,38 @@ run(File, Callback) ->
%% RECORD %% RECORD
record_alias(Module, Line, Warn, Ref) -> record_alias(Module, Line, Warn, Ref) ->
if_tracker(Ref, fun(Pid) -> if_tracker(Ref, fun(Pid) -> ?tracker:add_alias(Pid, Module, Line, Warn), ok end).
?tracker:add_alias(Pid, Module, Line, Warn),
true
end).
record_import(Module, Line, Warn, Ref) -> record_import(Module, Line, Warn, Ref) ->
if_tracker(Ref, fun(Pid) -> if_tracker(Ref, fun(Pid) -> ?tracker:add_import(Pid, Module, Line, Warn), ok end).
?tracker:add_import(Pid, Module, Line, Warn),
true
end).
record_alias(Module, Ref) -> record_alias(Module, Ref) ->
if_tracker(Ref, fun(Pid) -> if_tracker(Ref, fun(Pid) -> ?tracker:alias_dispatch(Pid, Module), ok end).
?tracker:alias_dispatch(Pid, Module),
true
end).
record_import(Module, Ref) -> record_import(Module, Ref) ->
if_tracker(Ref, fun(Pid) -> if_tracker(Ref, fun(Pid) -> ?tracker:import_dispatch(Pid, Module), ok end).
?tracker:import_dispatch(Pid, Module),
true
end).
record_remote(Module, Ref) -> record_remote(Module, Ref) ->
if_tracker(Ref, fun(Pid) -> if_tracker(Ref, fun(Pid) -> ?tracker:remote_dispatch(Pid, Module), ok end).
?tracker:remote_dispatch(Pid, Module),
true
end).
%% HELPERS %% HELPERS
if_tracker(nil, _Callback) -> false; if_tracker(nil, _Callback) -> ok;
if_tracker(Pid, Callback) when is_pid(Pid) -> Callback(Pid). if_tracker(Pid, Callback) when is_pid(Pid) -> Callback(Pid).
%% ERROR HANDLING %% ERROR HANDLING
warn_unused_imports(File, Pid) -> warn_unused_imports(File, Pid) ->
[ begin [begin
elixir_errors:handle_file_warning(File, {L, ?MODULE, {unused_import, M}}) elixir_errors:form_warn([{line, L}], File, ?MODULE, {unused_import, M})
end || {M, L} <- ?tracker:collect_unused_imports(Pid)]. end || {M, L} <- ?tracker:collect_unused_imports(Pid)],
ok.
warn_unused_aliases(File, Pid) -> warn_unused_aliases(File, Pid) ->
[ begin [begin
elixir_errors:handle_file_warning(File, {L, ?MODULE, {unused_alias, M}}) elixir_errors:form_warn([{line, L}], File, ?MODULE, {unused_alias, M})
end || {M, L} <- ?tracker:collect_unused_aliases(Pid)]. end || {M, L} <- ?tracker:collect_unused_aliases(Pid)],
ok.
format_error({unused_alias, Module}) -> format_error({unused_alias, Module}) ->
io_lib:format("unused alias ~ts", [elixir_aliases:inspect(Module)]); io_lib:format("unused alias ~ts", [elixir_aliases:inspect(Module)]);
+33 -41
View File
@@ -4,7 +4,7 @@
setup/1, cleanup/1, cache_env/1, get_cached_env/1, setup/1, cleanup/1, cache_env/1, get_cached_env/1,
record_local/2, record_local/3, record_import/4, record_local/2, record_local/3, record_import/4,
record_definition/3, record_defaults/4, record_definition/3, record_defaults/4,
ensure_no_function_conflict/4, warn_unused_local/3, format_error/1 ensure_no_import_conflict/4, warn_unused_local/3, format_error/1
]). ]).
-export([macro_for/3, local_for/3, local_for/4]). -export([macro_for/3, local_for/3, local_for/4]).
@@ -73,53 +73,41 @@ rewrite_clause(Else, _) -> Else.
setup(Module) -> setup(Module) ->
case code:is_loaded(?tracker) of case code:is_loaded(?tracker) of
{file, _} -> ets:insert(Module, {?attr, ?tracker:start_link()}); {file, _} -> ets:insert(Module, {?attr, ?tracker:start_link()}), ok;
false -> ok false -> ok
end. end.
cleanup(Module) -> cleanup(Module) ->
if_tracker(Module, fun(Pid) -> unlink(Pid), ?tracker:stop(Pid) end). if_tracker(Module, fun(Pid) -> unlink(Pid), ?tracker:stop(Pid), ok end).
record_local(Tuple, Module) when is_atom(Module) -> record_local(Tuple, Module) when is_atom(Module) ->
if_tracker(Module, fun(Pid) -> if_tracker(Module, fun(Pid) -> ?tracker:add_local(Pid, Tuple), ok end).
?tracker:add_local(Pid, Tuple),
true
end).
record_local(Tuple, _Module, Function) record_local(Tuple, _Module, Function)
when Function == nil; Function == Tuple -> false; when Function == nil; Function == Tuple -> ok;
record_local(Tuple, Module, Function) -> record_local(Tuple, Module, Function) ->
if_tracker(Module, fun(Pid) -> if_tracker(Module, fun(Pid) -> ?tracker:add_local(Pid, Function, Tuple), ok end).
?tracker:add_local(Pid, Function, Tuple),
true
end).
record_import(_Tuple, Receiver, Module, _Function) record_import(_Tuple, Receiver, Module, _Function)
when Module == nil; Module == Receiver -> false; when Module == nil; Module == Receiver -> false;
record_import(Tuple, Receiver, Module, Function) -> record_import(Tuple, Receiver, Module, Function) ->
if_tracker(Module, fun(Pid) -> if_tracker(Module, fun(Pid) -> ?tracker:add_import(Pid, Function, Receiver, Tuple), ok end).
?tracker:add_import(Pid, Function, Receiver, Tuple),
true
end).
record_definition(Tuple, Kind, Module) -> record_definition(Tuple, Kind, Module) ->
if_tracker(Module, fun(Pid) -> if_tracker(Module, fun(Pid) -> ?tracker:add_definition(Pid, Kind, Tuple), ok end).
?tracker:add_definition(Pid, Kind, Tuple),
true
end).
record_defaults(_Tuple, _Kind, _Module, 0) -> record_defaults(_Tuple, _Kind, _Module, 0) ->
true; ok;
record_defaults(Tuple, Kind, Module, Defaults) -> record_defaults(Tuple, Kind, Module, Defaults) ->
if_tracker(Module, fun(Pid) -> if_tracker(Module, fun(Pid) -> ?tracker:add_defaults(Pid, Kind, Tuple, Defaults), ok end).
?tracker:add_defaults(Pid, Kind, Tuple, Defaults),
true
end).
if_tracker(Module, Callback) -> if_tracker(Module, Callback) ->
if_tracker(Module, ok, Callback).
if_tracker(Module, Default, Callback) ->
try ets:lookup_element(Module, ?attr, 2) of try ets:lookup_element(Module, ?attr, 2) of
Pid -> Callback(Pid) Pid -> Callback(Pid)
catch catch
error:badarg -> false error:badarg -> Default
end. end.
%% CACHING %% CACHING
@@ -140,25 +128,29 @@ get_cached_env(Env) -> Env.
%% ERROR HANDLING %% ERROR HANDLING
ensure_no_function_conflict(Meta, File, Module, AllDefined) -> ensure_no_import_conflict(_Line, _File, 'Elixir.Kernel', _All) ->
if_tracker(Module, fun(Pid) -> ok;
[ begin ensure_no_import_conflict(Line, File, Module, All) ->
elixir_errors:form_error(Meta, File, ?MODULE, {function_conflict, Error}) if_tracker(Module, ok, fun(Pid) ->
end || Error <- ?tracker:collect_imports_conflicts(Pid, AllDefined) ] _ = [ begin
end), elixir_errors:form_error([{line, Line}], File, ?MODULE, {function_conflict, Error})
ok. end || Error <- ?tracker:collect_imports_conflicts(Pid, All) ],
ok
end).
warn_unused_local(File, Module, Private) -> warn_unused_local(File, Module, Private) ->
if_tracker(Module, fun(Pid) -> if_tracker(Module, [], fun(Pid) ->
Args = [ {Fun, Kind, Defaults} || Args = [{Fun, Kind, Defaults} ||
{Fun, Kind, _Line, true, Defaults} <- Private], {Fun, Kind, _Line, true, Defaults} <- Private],
Unused = ?tracker:collect_unused_locals(Pid, Args), {Unreachable, Warnings} = ?tracker:collect_unused_locals(Pid, Args),
[ begin [begin
{_, _, Line, _, _} = lists:keyfind(element(2, Error), 1, Private), {_, _, Line, _, _} = lists:keyfind(element(2, Error), 1, Private),
elixir_errors:handle_file_warning(File, {Line, ?MODULE, Error}) elixir_errors:form_warn([{line, Line}], File, ?MODULE, Error)
end || Error <- Unused ] end || Error <- Warnings ],
Unreachable
end). end).
format_error({function_conflict,{Receivers, Name, Arity}}) -> format_error({function_conflict,{Receivers, Name, Arity}}) ->
+22 -20
View File
@@ -22,19 +22,8 @@ expand_struct(Meta, Left, Right, E) ->
EMeta = EMeta =
case lists:member(ELeft, ?m(E, context_modules)) of case lists:member(ELeft, ?m(E, context_modules)) of
true -> true -> [{struct, context}|Meta];
case (ELeft == ?m(E, module)) and false -> Meta
(?m(E, function) == nil) of
true ->
compile_error(Meta, ?m(E, file),
"cannot access struct ~ts in body of the module that defines it as "
"the struct fields are not yet accessible",
[elixir_aliases:inspect(ELeft)]);
false ->
[{struct, context}|Meta]
end;
false ->
Meta
end, end,
case ERight of case ERight of
@@ -90,12 +79,11 @@ translate_struct(Meta, Name, {'%{}', MapMeta, Args}, S) ->
%% Helpers %% Helpers
load_struct(Meta, Name, S) -> load_struct(Meta, Name, S) ->
Context = lists:keyfind(struct, 1, Meta) == {struct, context},
Local = Local =
elixir_module:is_open(Name) andalso not(ensure_loaded(Name)) andalso
(case lists:keyfind(struct, 1, Meta) of (Context orelse wait_for_struct(Name)),
{struct, context} -> true;
_ -> wait_for_struct(Name)
end),
try try
case Local of case Local of
@@ -112,8 +100,22 @@ load_struct(Meta, Name, S) ->
catch catch
error:undef -> error:undef ->
Inspected = elixir_aliases:inspect(Name), Inspected = elixir_aliases:inspect(Name),
compile_error(Meta, S#elixir_scope.file, "~ts.__struct__/0 is undefined, "
"cannot expand struct ~ts", [Inspected, Inspected]) case Context of
true ->
compile_error(Meta, S#elixir_scope.file,
"cannot access struct ~ts in the same context that defines it as "
"the struct fields are not yet accessible", [Inspected]);
false ->
compile_error(Meta, S#elixir_scope.file, "~ts.__struct__/0 is undefined, "
"cannot expand struct ~ts", [Inspected, Inspected])
end
end.
ensure_loaded(Module) ->
case code:ensure_loaded(Module) of
{module, Module} -> true;
{error, _} -> false
end. end.
wait_for_struct(Module) -> wait_for_struct(Module) ->
+87 -78
View File
@@ -42,35 +42,41 @@ compile(Module, Block, Vars, #{line := Line} = Env) when is_atom(Module) ->
end; end;
compile(Module, _Block, _Vars, #{line := Line, file := File}) -> compile(Module, _Block, _Vars, #{line := Line, file := File}) ->
elixir_errors:form_error(Line, File, ?MODULE, {invalid_module, Module}). elixir_errors:form_error([{line, Line}], File, ?MODULE, {invalid_module, Module}).
do_compile(Line, Module, Block, Vars, E) -> do_compile(Line, Module, Block, Vars, E) ->
File = ?m(E, file), File = ?m(E, file),
check_module_availability(Line, File, Module), check_module_availability(Line, File, Module),
build(Line, File, Module, ?m(E, lexical_tracker)),
Docs = elixir_compiler:get_opt(docs),
build(Line, File, Module, Docs, ?m(E, lexical_tracker)),
try try
{Result, NE} = eval_form(Line, Module, Block, Vars, E), {Result, NE} = eval_form(Line, Module, Block, Vars, E),
{Base, Export, Private, Def, Defmacro, Functions} = elixir_def:unwrap_definitions(Module),
{All, Forms0} = functions_form(Line, File, Module, Base, Export, Def, Defmacro, Functions), _ = case ets:lookup(data_table(Module), 'on_load') of
Forms1 = specs_form(Module, Private, Defmacro, Forms0),
Forms2 = types_form(Module, Forms1),
Forms3 = attributes_form(Line, File, Module, Forms2),
case ets:lookup(data_table(Module), 'on_load') of
[] -> ok; [] -> ok;
[{on_load,OnLoad}] -> [{on_load,OnLoad}] ->
[elixir_locals:record_local(Tuple, Module) || Tuple <- OnLoad] [elixir_locals:record_local(Tuple, Module) || Tuple <- OnLoad]
end, end,
AllFunctions = Def ++ [T || {T, defp, _, _, _} <- Private], {Def, Defp, Defmacro, Defmacrop, Exports, Functions} =
elixir_locals:ensure_no_function_conflict(Line, File, Module, AllFunctions), elixir_def:unwrap_definitions(File, Module),
elixir_locals:warn_unused_local(File, Module, Private),
warn_invalid_clauses(Line, File, Module, All),
warn_unused_docs(Line, File, Module),
Location = {elixir_utils:relative_to_cwd(elixir_utils:characters_to_list(File)), Line}, {All, Forms0} = functions_form(Line, File, Module, Def, Defp,
Defmacro, Defmacrop, Exports, Functions),
Forms1 = specs_form(Module, Defmacro, Defmacrop, Forms0),
Forms2 = types_form(Line, File, Module, Forms1),
Forms3 = attributes_form(Line, File, Module, Forms2),
elixir_locals:ensure_no_import_conflict(Line, File, Module, All),
case Docs of
true -> warn_unused_docs(Line, File, Module, doc);
false -> false
end,
Location = {elixir_utils:characters_to_list(elixir_utils:relative_to_cwd(File)), Line},
Final = [ Final = [
{attribute, Line, file, Location}, {attribute, Line, file, Location},
@@ -88,7 +94,7 @@ do_compile(Line, Module, Block, Vars, E) ->
%% Hook that builds both attribute and functions and set up common hooks. %% Hook that builds both attribute and functions and set up common hooks.
build(Line, File, Module, Lexical) -> build(Line, File, Module, Docs, Lexical) ->
%% Table with meta information about the module. %% Table with meta information about the module.
DataTable = data_table(Module), DataTable = data_table(Module),
@@ -97,16 +103,16 @@ build(Line, File, Module, Lexical) ->
true -> true ->
[{OldFile, OldLine}] = ets:lookup_element(OldTable, ?location_attr, 2), [{OldFile, OldLine}] = ets:lookup_element(OldTable, ?location_attr, 2),
Error = {module_in_definition, Module, OldFile, OldLine}, Error = {module_in_definition, Module, OldFile, OldLine},
elixir_errors:form_error(Line, File, ?MODULE, Error); elixir_errors:form_error([{line, Line}], File, ?MODULE, Error);
false -> false ->
[] []
end, end,
ets:new(DataTable, [set, named_table, public]), DataTable = ets:new(DataTable, [set, named_table, public]),
ets:insert(DataTable, {before_compile, []}), ets:insert(DataTable, {before_compile, []}),
ets:insert(DataTable, {after_compile, []}), ets:insert(DataTable, {after_compile, []}),
case elixir_compiler:get_opt(docs) of case Docs of
true -> ets:insert(DataTable, {on_definition, [{'Elixir.Module', compile_doc}]}); true -> ets:insert(DataTable, {on_definition, [{'Elixir.Module', compile_doc}]});
_ -> ets:insert(DataTable, {on_definition, []}) _ -> ets:insert(DataTable, {on_definition, []})
end, end,
@@ -121,7 +127,8 @@ build(Line, File, Module, Lexical) ->
%% Setup other modules %% Setup other modules
elixir_def:setup(Module), elixir_def:setup(Module),
elixir_locals:setup(Module). elixir_locals:setup(Module),
ok.
%% Receives the module representation and evaluates it. %% Receives the module representation and evaluates it.
@@ -143,16 +150,13 @@ eval_callbacks(Line, Module, Name, Args, E) ->
%% Return the form with exports and function declarations. %% Return the form with exports and function declarations.
functions_form(Line, File, Module, BaseAll, BaseExport, Def, Defmacro, BaseFunctions) -> functions_form(Line, File, Module, Def, Defp, Defmacro, Defmacrop, Exports, Body) ->
{InfoSpec, Info} = add_info_function(Line, File, Module, BaseExport, Def, Defmacro), All = Def ++ Defmacro ++ Defp ++ Defmacrop,
{Spec, Info} = add_info_function(Line, File, Module, All, Def, Defmacro),
All = [{'__info__', 1}|BaseAll], {[{'__info__',1}|All],
Export = [{'__info__', 1}|BaseExport], [{attribute, Line, export, lists:sort([{'__info__',1}|Exports])},
Functions = [InfoSpec,Info|BaseFunctions], Spec, Info | Body]}.
{All, [
{attribute, Line, export, lists:sort(Export)} | Functions
]}.
%% Add attributes handling to the form %% Add attributes handling to the form
@@ -188,12 +192,12 @@ process_attribute(_Line, _File, _Key, Values) ->
process_external_resource(_Line, _File, Value) when is_binary(Value) -> process_external_resource(_Line, _File, Value) when is_binary(Value) ->
Value; Value;
process_external_resource(Line, File, Value) -> process_external_resource(Line, File, Value) ->
elixir_errors:handle_file_error(File, elixir_errors:form_error([{line, Line}], File,
{Line, ?MODULE, {invalid_external_resource, Value}}). ?MODULE, {invalid_external_resource, Value}).
%% Types %% Types
types_form(Module, Forms0) -> types_form(Line, File, Module, Forms0) ->
case code:ensure_loaded('Elixir.Kernel.Typespec') of case code:ensure_loaded('Elixir.Kernel.Typespec') of
{module, 'Elixir.Kernel.Typespec'} -> {module, 'Elixir.Kernel.Typespec'} ->
Types0 = 'Elixir.Module':get_attribute(Module, type) ++ Types0 = 'Elixir.Module':get_attribute(Module, type) ++
@@ -207,6 +211,8 @@ types_form(Module, Forms0) ->
'Elixir.Module':delete_attribute(Module, typep), 'Elixir.Module':delete_attribute(Module, typep),
'Elixir.Module':delete_attribute(Module, opaque), 'Elixir.Module':delete_attribute(Module, opaque),
warn_unused_docs(Line, File, Module, typedoc),
Forms1 = types_attributes(Types1, Forms0), Forms1 = types_attributes(Types1, Forms0),
Forms2 = export_types_attributes(Types1, Forms1), Forms2 = export_types_attributes(Types1, Forms1),
typedocs_attributes(Types1, Forms2); typedocs_attributes(Types1, Forms2);
@@ -241,11 +247,9 @@ typedocs_attributes(Types, Forms) ->
%% Specs %% Specs
specs_form(Module, Private, Defmacro, Forms) -> specs_form(Module, Defmacro, Defmacrop, Forms) ->
case code:ensure_loaded('Elixir.Kernel.Typespec') of case code:ensure_loaded('Elixir.Kernel.Typespec') of
{module, 'Elixir.Kernel.Typespec'} -> {module, 'Elixir.Kernel.Typespec'} ->
Defmacrop = [Tuple || {Tuple, defmacrop, _, _, _} <- Private],
Specs0 = 'Elixir.Module':get_attribute(Module, spec) ++ Specs0 = 'Elixir.Module':get_attribute(Module, spec) ++
'Elixir.Module':get_attribute(Module, callback), 'Elixir.Module':get_attribute(Module, callback),
@@ -274,10 +278,10 @@ specs_attributes(Forms, Specs) ->
end, Forms, Dict). end, Forms, Dict).
translate_macro_spec({{spec, NameArity, Spec}, Line}, Defmacro, Defmacrop) -> translate_macro_spec({{spec, NameArity, Spec}, Line}, Defmacro, Defmacrop) ->
case ordsets:is_element(NameArity, Defmacrop) of case lists:member(NameArity, Defmacrop) of
true -> []; true -> [];
false -> false ->
case ordsets:is_element(NameArity, Defmacro) of case lists:member(NameArity, Defmacro) of
true -> true ->
{Name, Arity} = NameArity, {Name, Arity} = NameArity,
[{{spec, {elixir_utils:macro_name(Name), Arity + 1}, spec_for_macro(Spec)}, Line}]; [{{spec, {elixir_utils:macro_name(Name), Arity + 1}, spec_for_macro(Spec)}, Line}];
@@ -314,14 +318,14 @@ load_form(Line, Forms, Opts, #{file := File} = E) ->
put(elixir_compiled, [{Module,Binary}|Current]), put(elixir_compiled, [{Module,Binary}|Current]),
case get(elixir_compiler_pid) of case get(elixir_compiler_pid) of
undefined -> []; undefined -> ok;
PID -> PID ->
Ref = make_ref(), Ref = make_ref(),
PID ! {module_available, self(), Ref, File, Module, Binary}, PID ! {module_available, self(), Ref, File, Module, Binary},
receive {Ref, ack} -> ok end receive {Ref, ack} -> ok end
end; end;
_ -> _ ->
[] ok
end, end,
Binary Binary
@@ -345,13 +349,12 @@ get_docs(Module) ->
get_moduledoc(Line, Module) -> get_moduledoc(Line, Module) ->
{Line, 'Elixir.Module':get_attribute(Module, moduledoc)}. {Line, 'Elixir.Module':get_attribute(Module, moduledoc)}.
check_module_availability(Line, File, Module) -> check_module_availability(Line, File, Module) ->
Reserved = ['Elixir.Any', 'Elixir.BitString', 'Elixir.Function', 'Elixir.PID', Reserved = ['Elixir.Any', 'Elixir.BitString', 'Elixir.Function', 'Elixir.PID',
'Elixir.Reference', 'Elixir.Elixir', 'Elixir'], 'Elixir.Reference', 'Elixir.Elixir', 'Elixir'],
case lists:member(Module, Reserved) of case lists:member(Module, Reserved) of
true -> elixir_errors:handle_file_error(File, {Line, ?MODULE, {module_reserved, Module}}); true -> elixir_errors:form_error([{line, Line}], File, ?MODULE, {module_reserved, Module});
false -> ok false -> ok
end, end,
@@ -359,7 +362,7 @@ check_module_availability(Line, File, Module) ->
false -> false ->
case code:ensure_loaded(Module) of case code:ensure_loaded(Module) of
{module, _} -> {module, _} ->
elixir_errors:handle_file_warning(File, {Line, ?MODULE, {module_defined, Module}}); elixir_errors:form_warn([{line, Line}], File, ?MODULE, {module_defined, Module});
{error, _} -> {error, _} ->
ok ok
end; end;
@@ -367,56 +370,64 @@ check_module_availability(Line, File, Module) ->
ok ok
end. end.
warn_invalid_clauses(_Line, _File, 'Elixir.Kernel.SpecialForms', _All) -> ok; warn_unused_docs(Line, File, Module, Attribute) ->
warn_invalid_clauses(_Line, File, Module, All) -> case ets:member(data_table(Module), Attribute) of
ets:foldl(fun true ->
({_, _, Kind, _, _}, _) when Kind == type; Kind == opaque -> elixir_errors:form_warn([{line, Line}], File, ?MODULE, {unused_doc, Attribute});
ok; _ ->
({Tuple, Line, _, _, _}, _) -> ok
case lists:member(Tuple, All) of end.
false ->
elixir_errors:handle_file_warning(File, {Line, ?MODULE, {invalid_clause, Tuple}});
true ->
ok
end
end, ok, docs_table(Module)).
warn_unused_docs(Line, File, Module) -> % __INFO__
lists:foreach(fun(Attribute) ->
case ets:member(data_table(Module), Attribute) of
true ->
elixir_errors:handle_file_warning(File, {Line, ?MODULE, {unused_doc, Attribute}});
_ ->
ok
end
end, [typedoc]).
% EXTRA FUNCTIONS add_info_function(Line, File, Module, All, Def, Defmacro) ->
add_info_function(Line, File, Module, Export, Def, Defmacro) ->
Pair = {'__info__', 1}, Pair = {'__info__', 1},
case lists:member(Pair, Export) of case lists:member(Pair, All) of
true -> true ->
elixir_errors:form_error(Line, File, ?MODULE, {internal_function_overridden, Pair}); elixir_errors:form_error([{line, Line}], File, ?MODULE, {internal_function_overridden, Pair});
false -> false ->
{ Spec =
{attribute, Line, spec, {{'__info__', 1}, {attribute, Line, spec, {Pair,
[{type, Line, 'fun', [{type, Line, product, [ {type, Line, atom, []}]}, {type, Line, term, []} ]}] [{type, Line, 'fun', [
{type, Line, product, [
{type, Line, union, [
{atom, Line, attributes},
{atom, Line, compile},
{atom, Line, exports},
{atom, Line, functions},
{atom, Line, imports},
{atom, Line, macros},
{atom, Line, module}
]}
]},
{type, Line, union, [
{type, Line, atom, []},
{type, Line, list, [
{type, Line, tuple, [
{type, Line, atom, []},
{type, Line, any, []}
]}
]}
]}
]}]
}}, }},
Info =
{function, 0, '__info__', 1, [ {function, 0, '__info__', 1, [
functions_clause(Def), functions_clause(Def),
macros_clause(Defmacro), macros_clause(Defmacro),
module_clause(Module), module_clause(Module),
else_clause() else_clause()
]} ]},
}
{Spec, Info}
end. end.
functions_clause(Def) -> functions_clause(Def) ->
{clause, 0, [{atom, 0, functions}], [], [elixir_utils:elixir_to_erl(Def)]}. {clause, 0, [{atom, 0, functions}], [], [elixir_utils:elixir_to_erl(lists:sort(Def))]}.
macros_clause(Defmacro) -> macros_clause(Defmacro) ->
{clause, 0, [{atom, 0, macros}], [], [elixir_utils:elixir_to_erl(Defmacro)]}. {clause, 0, [{atom, 0, macros}], [], [elixir_utils:elixir_to_erl(lists:sort(Defmacro))]}.
module_clause(Module) -> module_clause(Module) ->
{clause, 0, [{atom, 0, module}], [], [{atom, 0, Module}]}. {clause, 0, [{atom, 0, module}], [], [{atom, 0, Module}]}.
@@ -476,8 +487,6 @@ prune_stacktrace(Info, []) ->
% ERROR HANDLING % ERROR HANDLING
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}) -> format_error({invalid_external_resource, Value}) ->
io_lib:format("expected a string value for @external_resource, got: ~p", io_lib:format("expected a string value for @external_resource, got: ~p",
['Elixir.Kernel':inspect(Value)]); ['Elixir.Kernel':inspect(Value)]);
+1 -2
View File
@@ -54,7 +54,7 @@ Right 60 type_op_eol. %% ::
Right 70 pipe_op_eol. %% | Right 70 pipe_op_eol. %% |
Right 80 assoc_op_eol. %% => Right 80 assoc_op_eol. %% =>
Right 90 match_op_eol. %% = Right 90 match_op_eol. %% =
Left 130 or_op_eol. %% ||, |||, or, xor Left 130 or_op_eol. %% ||, |||, or
Left 140 and_op_eol. %% &&, &&&, and Left 140 and_op_eol. %% &&, &&&, and
Left 150 comp_op_eol. %% ==, !=, =~, ===, !== Left 150 comp_op_eol. %% ==, !=, =~, ===, !==
Left 160 rel_op_eol. %% <, >, <=, >= Left 160 rel_op_eol. %% <, >, <=, >=
@@ -226,7 +226,6 @@ 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 -> parens_call call_args_parens call_args_parens : build_nested_parens('$1', '$2', '$3').
max_expr -> dot_alias : '$1'. max_expr -> dot_alias : '$1'.
bracket_arg -> open_bracket ']' : build_list('$1', []).
bracket_arg -> open_bracket kw close_bracket : build_list('$1', '$2'). 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_arg -> open_bracket container_expr ',' close_bracket : build_list('$1', '$2'). bracket_arg -> open_bracket container_expr ',' close_bracket : build_list('$1', '$2').
+31 -26
View File
@@ -1,10 +1,10 @@
-module(elixir_quote). -module(elixir_quote).
-export([escape/2, linify/2, linify/3, linify_with_context_counter/3, quote/4]). -export([escape/2, linify/2, linify/3, linify_with_context_counter/3, quote/4]).
-export([dot/6, tail_list/3, list/2]). %% Quote callbacks -export([dot/5, tail_list/3, list/2]). %% Quote callbacks
-include("elixir.hrl"). -include("elixir.hrl").
-define(defs(Kind), Kind == def; Kind == defp; Kind == defmacro; Kind == defmacrop). -define(defs(Kind), Kind == def; Kind == defp; Kind == defmacro; Kind == defmacrop).
-define(lexical(Kind), Kind == import; Kind == alias; Kind == '__aliases__'). -define(lexical(Kind), Kind == import; Kind == alias; Kind == require).
-compile({inline, [keyfind/2, keystore/3, keydelete/2, keyreplace/3, keynew/3]}). -compile({inline, [keyfind/2, keystore/3, keydelete/2, keyreplace/3, keynew/3]}).
%% Apply the line from site call on quoted contents. %% Apply the line from site call on quoted contents.
@@ -23,7 +23,8 @@ do_linify(Line, Key, {Receiver, Counter} = Var, {Left, Meta, Receiver})
when is_atom(Left), is_list(Meta), Left /= '_' -> when is_atom(Left), is_list(Meta), Left /= '_' ->
do_tuple_linify(Line, Key, Var, keynew(counter, Meta, Counter), Left, Receiver); do_tuple_linify(Line, Key, Var, keynew(counter, Meta, Counter), Left, Receiver);
do_linify(Line, Key, {_, Counter} = Var, {Lexical, [_|_] = Meta, [_|_] = Args}) when ?lexical(Lexical) -> do_linify(Line, Key, {_, Counter} = Var, {Lexical, [_|_] = Meta, [_|_] = Args})
when ?lexical(Lexical); Lexical == '__aliases__' ->
do_tuple_linify(Line, Key, Var, keynew(counter, Meta, Counter), Lexical, Args); do_tuple_linify(Line, Key, Var, keynew(counter, Meta, Counter), Lexical, Args);
do_linify(Line, Key, Var, {Left, Meta, Right}) when is_list(Meta) -> do_linify(Line, Key, Var, {Left, Meta, Right}) when is_list(Meta) ->
@@ -61,8 +62,8 @@ do_linify_meta(Line, Key, Meta) ->
%% Some expressions cannot be unquoted at compilation time. %% Some expressions cannot be unquoted at compilation time.
%% This function is responsible for doing runtime unquoting. %% This function is responsible for doing runtime unquoting.
dot(Meta, Left, Right, Args, Context, File) -> dot(Meta, Left, Right, Args, Context) ->
annotate(dot(Meta, Left, Right, Args), Context, File). annotate(dot(Meta, Left, Right, Args), Context).
dot(Meta, Left, {'__aliases__', _, Args}, nil) -> dot(Meta, Left, {'__aliases__', _, Args}, nil) ->
{'__aliases__', Meta, [Left|Args]}; {'__aliases__', Meta, [Left|Args]};
@@ -117,28 +118,29 @@ validate_list(List) when not is_list(List) ->
argument_error(Message) -> argument_error(Message) ->
error('Elixir.ArgumentError':exception([{message,Message}])). error('Elixir.ArgumentError':exception([{message,Message}])).
%% Annotates the AST with context and other info %% Annotates the AST with context and other info.
%%
%% Note we need to delete the counter because linify
%% adds the counter recursively, even inside quoted
%% expressions, so we need to clean up the forms to
%% allow them to get a new counter on the next expansion.
annotate({Def, Meta, [{H, M, A}|T]}, Context, File) when ?defs(Def) -> annotate({Def, Meta, [{H, M, A}|T]}, Context) when ?defs(Def) ->
%% Store the context information in the first element of the {Def, Meta, [{H, keystore(context, M, Context), A}|T]};
%% definition tuple so we can access it later on. annotate({{'.', _, [_, Def]} = Target, Meta, [{H, M, A}|T]}, Context) when ?defs(Def) ->
MM = keystore(context, keystore(file, M, File), Context), {Target, Meta, [{H, keystore(context, M, Context), A}|T]};
{Def, Meta, [{H, MM, A}|T]};
annotate({{'.', _, [_, Def]} = Target, Meta, [{H, M, A}|T]}, Context, File) when ?defs(Def) ->
MM = keystore(context, keystore(file, M, File), Context),
{Target, Meta, [{H, MM, A}|T]};
annotate({Lexical, Meta, [_|_] = Args}, Context, _File) when Lexical == import; Lexical == alias -> annotate({Lexical, Meta, [_|_] = Args}, Context) when ?lexical(Lexical) ->
NewMeta = keystore(context, keydelete(counter, Meta), Context), NewMeta = keystore(context, keydelete(counter, Meta), Context),
{Lexical, NewMeta, Args}; {Lexical, NewMeta, Args};
annotate(Tree, _Context, _File) -> Tree. annotate(Tree, _Context) -> Tree.
%% Escapes the given expression. It is similar to quote, but %% Escapes the given expression. It is similar to quote, but
%% lines are kept and hygiene mechanisms are disabled. %% lines are kept and hygiene mechanisms are disabled.
escape(Expr, Unquote) -> escape(Expr, Unquote) ->
{Res, Q} = quote(Expr, nil, #elixir_quote{ {Res, Q} = quote(Expr, nil, #elixir_quote{
line=true, line=true,
keep=false, file=nil,
vars_hygiene=false, vars_hygiene=false,
aliases_hygiene=false, aliases_hygiene=false,
imports_hygiene=false, imports_hygiene=false,
@@ -221,11 +223,11 @@ do_quote({Name, Meta, ArgsOrAtom}, #elixir_quote{imports_hygiene=true} = Q, E) w
keystore(import, keystore(context, Meta, Q#elixir_quote.context), Receiver) keystore(import, keystore(context, Meta, Q#elixir_quote.context), Receiver)
end, end,
Annotated = annotate({Name, NewMeta, ArgsOrAtom}, Q#elixir_quote.context, file(E, Q)), Annotated = annotate({Name, NewMeta, ArgsOrAtom}, Q#elixir_quote.context),
do_quote_tuple(Annotated, Q, E); do_quote_tuple(Annotated, Q, E);
do_quote({_, _, _} = Tuple, #elixir_quote{escape=false} = Q, E) -> do_quote({_, _, _} = Tuple, #elixir_quote{escape=false} = Q, E) ->
Annotated = annotate(Tuple, Q#elixir_quote.context, file(E, Q)), Annotated = annotate(Tuple, Q#elixir_quote.context),
do_quote_tuple(Annotated, Q, E); do_quote_tuple(Annotated, Q, E);
%% Literals %% Literals
@@ -267,7 +269,7 @@ do_quote(Other, Q, _) ->
do_quote_call(Left, Meta, Expr, Args, Q, E) -> do_quote_call(Left, Meta, Expr, Args, Q, E) ->
All = [meta(Meta, Q), Left, {unquote, Meta, [Expr]}, Args, All = [meta(Meta, Q), Left, {unquote, Meta, [Expr]}, Args,
Q#elixir_quote.context, file(E, Q)], Q#elixir_quote.context],
{TAll, TQ} = lists:mapfoldl(fun(X, Acc) -> do_quote(X, Acc, E) end, Q, All), {TAll, TQ} = lists:mapfoldl(fun(X, Acc) -> do_quote(X, Acc, E) end, Q, All),
{{{'.', Meta, [elixir_quote, dot]}, Meta, TAll}, TQ}. {{{'.', Meta, [elixir_quote, dot]}, Meta, TAll}, TQ}.
@@ -285,16 +287,19 @@ do_quote_tuple({Left, Meta, Right}, Q, E) ->
{TRight, RQ} = do_quote(Right, LQ, E), {TRight, RQ} = do_quote(Right, LQ, E),
{{'{}', [], [TLeft, meta(Meta, Q), TRight]}, RQ}. {{'{}', [], [TLeft, meta(Meta, Q), TRight]}, RQ}.
file(#{file := File}, #elixir_quote{keep=true}) -> File; meta(Meta, Q) ->
file(_, _) -> nil. file(line(Meta, Q), Q).
meta(Meta, #elixir_quote{keep=true}) -> file(Meta, #elixir_quote{file=nil}) -> Meta;
file(Meta, #elixir_quote{file=File}) -> [{file, File}|Meta].
line(Meta, #elixir_quote{file=File}) when File /= nil ->
[case KV of {line, V} -> {keep, V}; _ -> KV end || KV <- Meta]; [case KV of {line, V} -> {keep, V}; _ -> KV end || KV <- Meta];
meta(Meta, #elixir_quote{line=true}) -> line(Meta, #elixir_quote{line=true}) ->
Meta; Meta;
meta(Meta, #elixir_quote{line=false}) -> line(Meta, #elixir_quote{line=false}) ->
keydelete(line, Meta); keydelete(line, Meta);
meta(Meta, #elixir_quote{line=Line}) -> line(Meta, #elixir_quote{line=Line}) ->
keystore(line, Meta, Line). keystore(line, Meta, Line).
reverse_improper(L) -> reverse_improper(L, []). reverse_improper(L) -> reverse_improper(L, []).
+26 -2
View File
@@ -1,9 +1,10 @@
%% Convenience functions used to manipulate scope and its variables. %% Convenience functions used to manipulate scope and its variables.
-module(elixir_scope). -module(elixir_scope).
-export([translate_var/4, build_var/2, -export([translate_var/4, build_var/2, context_info/1,
load_binding/2, dump_binding/2, load_binding/2, dump_binding/2,
mergev/2, mergec/2, mergef/2, mergev/2, mergec/2, mergef/2,
merge_vars/2, merge_opt_vars/2 merge_vars/2, merge_opt_vars/2,
format_error/1
]). ]).
-include("elixir.hrl"). -include("elixir.hrl").
@@ -25,6 +26,7 @@ translate_var(Meta, Name, Kind, S) when is_atom(Kind); is_integer(Kind) ->
case Exists andalso ordsets:is_element(Tuple, MatchVars) of case Exists andalso ordsets:is_element(Tuple, MatchVars) of
true -> true ->
warn_underscored_var(Line, S#elixir_scope.file, Name, Kind),
{{var, Line, Current}, S}; {{var, Line, Current}, S};
false -> false ->
%% We attempt to give vars a nice name because we %% We attempt to give vars a nice name because we
@@ -60,6 +62,19 @@ build_var(Key, S) ->
Cnt = orddict:fetch(Key, New), Cnt = orddict:fetch(Key, New),
{elixir_utils:atom_concat([Key, "@", Cnt]), Cnt, S#elixir_scope{counter=New}}. {elixir_utils:atom_concat([Key, "@", Cnt]), Cnt, S#elixir_scope{counter=New}}.
context_info(Kind) when Kind == nil; is_integer(Kind) -> "";
context_info(Kind) -> io_lib:format(" (context ~ts)", [elixir_aliases:inspect(Kind)]).
warn_underscored_var(Line, File, Name, Kind) ->
case atom_to_list(Name) of
"_@" ++ _ ->
ok; %% Automatically generated variables
"_" ++ _ ->
elixir_errors:form_warn([{line, Line}], File, ?MODULE, {unused_match, Name, Kind});
_ ->
ok
end.
%% SCOPE MERGING %% SCOPE MERGING
%% Receives two scopes and return a new scope based on %% Receives two scopes and return a new scope based on
@@ -138,3 +153,12 @@ dump_binding([{{Var, Kind} = Key, {InternalName,_}}|T], Binding, Acc) when is_at
dump_binding([_|T], Binding, Acc) -> dump_binding([_|T], Binding, Acc) ->
dump_binding(T, Binding, Acc); dump_binding(T, Binding, Acc);
dump_binding([], _Binding, Acc) -> Acc. dump_binding([], _Binding, Acc) -> Acc.
%% Errors
format_error({unused_match, Name, Kind}) ->
io_lib:format("the underscored variable \"~ts\"~ts appears more than once in a "
"match. This means the pattern will only match if all \"~ts\" bind "
"to the same value. If this is the intended behaviour, please "
"remove the leading underscore from the variable name, otherwise "
"give the variables different names", [Name, context_info(Kind), Name]).
+110 -41
View File
@@ -118,18 +118,40 @@ tokenize([], EndLine, #elixir_tokenizer{terminators=[{Start, StartLine}|_]}, Tok
% Base integers % Base integers
tokenize([$0,X,H|T], Line, Scope, Tokens) when (X == $x orelse X == $X), ?is_hex(H) -> tokenize([$0,$x,H|T], Line, Scope, Tokens) when ?is_hex(H) ->
{Rest, Number} = tokenize_hex([H|T], []), {Rest, Number} = tokenize_hex([H|T], []),
tokenize(Rest, Line, Scope, [{number, Line, Number}|Tokens]); tokenize(Rest, Line, Scope, [{number, Line, Number}|Tokens]);
tokenize([$0,B,H|T], Line, Scope, Tokens) when (B == $b orelse B == $B), ?is_bin(H) -> tokenize([$0,$b,H|T], Line, Scope, Tokens) when ?is_bin(H) ->
{Rest, Number} = tokenize_bin([H|T], []),
tokenize(Rest, Line, Scope, [{number, Line, Number}|Tokens]);
tokenize([$0,$o,H|T], Line, Scope, Tokens) when ?is_octal(H) ->
{Rest, Number} = tokenize_octal([H|T], []),
tokenize(Rest, Line, Scope, [{number, Line, Number}|Tokens]);
%% Deprecated
tokenize([$0,$X,H|T], Line, Scope, Tokens) when ?is_hex(H) ->
io:format("~ts:~w: warning: 0X for hexadecimals is deprecated, please use 0x instead~n",
[Scope#elixir_tokenizer.file, Line]),
{Rest, Number} = tokenize_hex([H|T], []),
tokenize(Rest, Line, Scope, [{number, Line, Number}|Tokens]);
tokenize([$0,$B,H|T], Line, Scope, Tokens) when ?is_bin(H) ->
io:format("~ts:~w: warning: 0B for binaries is deprecated, please use 0b instead~n",
[Scope#elixir_tokenizer.file, Line]),
{Rest, Number} = tokenize_bin([H|T], []), {Rest, Number} = tokenize_bin([H|T], []),
tokenize(Rest, Line, Scope, [{number, Line, Number}|Tokens]); tokenize(Rest, Line, Scope, [{number, Line, Number}|Tokens]);
tokenize([$0,H|T], Line, Scope, Tokens) when ?is_octal(H) -> tokenize([$0,H|T], Line, Scope, Tokens) when ?is_octal(H) ->
io:format("~ts:~w: warning: octals starting with leading 0 is deprecated, please use 0o instead, got octal start with: ~ts~n",
[Scope#elixir_tokenizer.file, Line, <<$0, H>>]),
{Rest, Number} = tokenize_octal([H|T], []), {Rest, Number} = tokenize_octal([H|T], []),
tokenize(Rest, Line, Scope, [{number, Line, Number}|Tokens]); tokenize(Rest, Line, Scope, [{number, Line, Number}|Tokens]);
%% End of deprecated
% Comments % Comments
tokenize([$#|String], Line, Scope, Tokens) -> tokenize([$#|String], Line, Scope, Tokens) ->
@@ -159,44 +181,86 @@ tokenize([$~,S,H|T] = Original, Line, Scope, Tokens) when ?is_sigil(H), ?is_upca
% Char tokens % Char tokens
tokenize([$?,$\\,P,${,A,B,C,D,E,F,$}|T], Line, Scope, Tokens) tokenize([$?,$\\,$x,${,A,B,C,D,E,F,$}|T], Line, Scope, Tokens)
when (P == $x orelse P == $X), ?is_hex(A), ?is_hex(B), ?is_hex(C), ?is_hex(D), ?is_hex(E), ?is_hex(F) -> when ?is_hex(A), ?is_hex(B), ?is_hex(C), ?is_hex(D), ?is_hex(E), ?is_hex(F) ->
Char = escape_char([$\\,P,${,A,B,C,D,E,F,$}]), Char = escape_char([$\\,$x,${,A,B,C,D,E,F,$}]),
tokenize(T, Line, Scope, [{number, Line, Char}|Tokens]); tokenize(T, Line, Scope, [{number, Line, Char}|Tokens]);
tokenize([$?,$\\,P,${,A,B,C,D,E,$}|T], Line, Scope, Tokens) tokenize([$?,$\\,$x,${,A,B,C,D,E,$}|T], Line, Scope, Tokens)
when (P == $x orelse P == $X), ?is_hex(A), ?is_hex(B), ?is_hex(C), ?is_hex(D), ?is_hex(E) -> when ?is_hex(A), ?is_hex(B), ?is_hex(C), ?is_hex(D), ?is_hex(E) ->
Char = escape_char([$\\,P,${,A,B,C,D,E,$}]), Char = escape_char([$\\,$x,${,A,B,C,D,E,$}]),
tokenize(T, Line, Scope, [{number, Line, Char}|Tokens]); tokenize(T, Line, Scope, [{number, Line, Char}|Tokens]);
tokenize([$?,$\\,P,${,A,B,C,D,$}|T], Line, Scope, Tokens) tokenize([$?,$\\,$x,${,A,B,C,D,$}|T], Line, Scope, Tokens)
when (P == $x orelse P == $X), ?is_hex(A), ?is_hex(B), ?is_hex(C), ?is_hex(D) -> when ?is_hex(A), ?is_hex(B), ?is_hex(C), ?is_hex(D) ->
Char = escape_char([$\\,P,${,A,B,C,D,$}]), Char = escape_char([$\\,$x,${,A,B,C,D,$}]),
tokenize(T, Line, Scope, [{number, Line, Char}|Tokens]); tokenize(T, Line, Scope, [{number, Line, Char}|Tokens]);
tokenize([$?,$\\,P,${,A,B,C,$}|T], Line, Scope, Tokens) tokenize([$?,$\\,$x,${,A,B,C,$}|T], Line, Scope, Tokens)
when (P == $x orelse P == $X), ?is_hex(A), ?is_hex(B), ?is_hex(C) -> when ?is_hex(A), ?is_hex(B), ?is_hex(C) ->
Char = escape_char([$\\,P,${,A,B,C,$}]), Char = escape_char([$\\,$x,${,A,B,C,$}]),
tokenize(T, Line, Scope, [{number, Line, Char}|Tokens]); tokenize(T, Line, Scope, [{number, Line, Char}|Tokens]);
tokenize([$?,$\\,P,${,A,B,$}|T], Line, Scope, Tokens) tokenize([$?,$\\,$x,${,A,B,$}|T], Line, Scope, Tokens)
when (P == $x orelse P == $X), ?is_hex(A), ?is_hex(B) -> when ?is_hex(A), ?is_hex(B) ->
Char = escape_char([$\\,P,${,A,B,$}]), Char = escape_char([$\\,$x,${,A,B,$}]),
tokenize(T, Line, Scope, [{number, Line, Char}|Tokens]); tokenize(T, Line, Scope, [{number, Line, Char}|Tokens]);
tokenize([$?,$\\,P,${,A,$}|T], Line, Scope, Tokens) tokenize([$?,$\\,$x,${,A,$}|T], Line, Scope, Tokens)
when (P == $x orelse P == $X), ?is_hex(A) -> when ?is_hex(A) ->
Char = escape_char([$\\,P,${,A,$}]), Char = escape_char([$\\,$x,${,A,$}]),
tokenize(T, Line, Scope, [{number, Line, Char}|Tokens]); tokenize(T, Line, Scope, [{number, Line, Char}|Tokens]);
tokenize([$?,$\\,P,A,B|T], Line, Scope, Tokens) tokenize([$?,$\\,$x,A,B|T], Line, Scope, Tokens)
when (P == $x orelse P == $X), ?is_hex(A), ?is_hex(B) -> when ?is_hex(A), ?is_hex(B) ->
Char = escape_char([$\\,P,A,B]), Char = escape_char([$\\,$x,A,B]),
tokenize(T, Line, Scope, [{number, Line, Char}|Tokens]); tokenize(T, Line, Scope, [{number, Line, Char}|Tokens]);
tokenize([$?,$\\,P,A|T], Line, Scope, Tokens) tokenize([$?,$\\,$x,A|T], Line, Scope, Tokens)
when (P == $x orelse P == $X), ?is_hex(A) -> when ?is_hex(A) ->
Char = escape_char([$\\,P,A]), Char = escape_char([$\\,$x,A]),
tokenize(T, Line, Scope, [{number, Line, Char}|Tokens]);
%% Deprecated
tokenize([$?,$\\,$X,${,A,B,C,D,E,F,$}|T], Line, Scope, Tokens)
when ?is_hex(A), ?is_hex(B), ?is_hex(C), ?is_hex(D), ?is_hex(E), ?is_hex(F) ->
Char = escape_char([$\\,$X,${,A,B,C,D,E,F,$}]),
tokenize(T, Line, Scope, [{number, Line, Char}|Tokens]);
tokenize([$?,$\\,$X,${,A,B,C,D,E,$}|T], Line, Scope, Tokens)
when ?is_hex(A), ?is_hex(B), ?is_hex(C), ?is_hex(D), ?is_hex(E) ->
Char = escape_char([$\\,$X,${,A,B,C,D,E,$}]),
tokenize(T, Line, Scope, [{number, Line, Char}|Tokens]);
tokenize([$?,$\\,$X,${,A,B,C,D,$}|T], Line, Scope, Tokens)
when ?is_hex(A), ?is_hex(B), ?is_hex(C), ?is_hex(D) ->
Char = escape_char([$\\,$X,${,A,B,C,D,$}]),
tokenize(T, Line, Scope, [{number, Line, Char}|Tokens]);
tokenize([$?,$\\,$X,${,A,B,C,$}|T], Line, Scope, Tokens)
when ?is_hex(A), ?is_hex(B), ?is_hex(C) ->
Char = escape_char([$\\,$X,${,A,B,C,$}]),
tokenize(T, Line, Scope, [{number, Line, Char}|Tokens]);
tokenize([$?,$\\,$X,${,A,B,$}|T], Line, Scope, Tokens)
when ?is_hex(A), ?is_hex(B) ->
Char = escape_char([$\\,$X,${,A,B,$}]),
tokenize(T, Line, Scope, [{number, Line, Char}|Tokens]);
tokenize([$?,$\\,$X,${,A,$}|T], Line, Scope, Tokens)
when ?is_hex(A) ->
Char = escape_char([$\\,$X,${,A,$}]),
tokenize(T, Line, Scope, [{number, Line, Char}|Tokens]);
tokenize([$?,$\\,$X,A,B|T], Line, Scope, Tokens)
when ?is_hex(A), ?is_hex(B) ->
Char = escape_char([$\\,$X,A,B]),
tokenize(T, Line, Scope, [{number, Line, Char}|Tokens]);
tokenize([$?,$\\,$X,A|T], Line, Scope, Tokens)
when ?is_hex(A) ->
Char = escape_char([$\\,$X,A]),
tokenize(T, Line, Scope, [{number, Line, Char}|Tokens]); tokenize(T, Line, Scope, [{number, Line, Char}|Tokens]);
tokenize([$?,$\\,A,B,C|T], Line, Scope, Tokens) tokenize([$?,$\\,A,B,C|T], Line, Scope, Tokens)
@@ -209,11 +273,16 @@ tokenize([$?,$\\,A,B|T], Line, Scope, Tokens)
Char = escape_char([$\\,A,B]), Char = escape_char([$\\,A,B]),
tokenize(T, Line, Scope, [{number, Line, Char}|Tokens]); tokenize(T, Line, Scope, [{number, Line, Char}|Tokens]);
tokenize([$?,$\\,$0|T], Line, Scope, Tokens) ->
tokenize(T, Line, Scope, [{number, Line, 0}|Tokens]);
tokenize([$?,$\\,A|T], Line, Scope, Tokens) tokenize([$?,$\\,A|T], Line, Scope, Tokens)
when ?is_octal(A) -> when ?is_octal(A) ->
Char = escape_char([$\\,A]), Char = escape_char([$\\,A]),
tokenize(T, Line, Scope, [{number, Line, Char}|Tokens]); tokenize(T, Line, Scope, [{number, Line, Char}|Tokens]);
%% End of deprecation
tokenize([$?,$\\,H|T], Line, Scope, Tokens) -> tokenize([$?,$\\,H|T], Line, Scope, Tokens) ->
Char = elixir_interpolation:unescape_map(H), Char = elixir_interpolation:unescape_map(H),
tokenize(T, Line, Scope, [{number, Line, Char}|Tokens]); tokenize(T, Line, Scope, [{number, Line, Char}|Tokens]);
@@ -461,22 +530,10 @@ tokenize([H|_] = String, Line, Scope, Tokens) when ?is_downcase(H); H == $_ ->
Error Error
end; end;
% Ambiguous unary/binary operators tokens
tokenize([Space, Sign, NotMarker|T], Line, Scope, [{Identifier, _, _} = H|Tokens]) when
?dual_op(Sign),
?is_horizontal_space(Space),
not(?is_space(NotMarker)),
NotMarker /= $(, NotMarker /= $[, NotMarker /= $<, NotMarker /= ${, %% containers
NotMarker /= $%, NotMarker /= $+, NotMarker /= $-, NotMarker /= $/, NotMarker /= $>, %% operators
Identifier == identifier ->
Rest = [NotMarker|T],
tokenize(Rest, Line, Scope, [{dual_op, Line, list_to_atom([Sign])}, setelement(1, H, op_identifier)|Tokens]);
% Spaces % Spaces
tokenize([T|Rest], Line, Scope, Tokens) when ?is_horizontal_space(T) -> tokenize([T|Rest], Line, Scope, Tokens) when ?is_horizontal_space(T) ->
tokenize(strip_horizontal_space(Rest), Line, Scope, Tokens); handle_space_sensitive_tokens(strip_horizontal_space(Rest), Line, Scope, Tokens);
tokenize(T, Line, _Scope, Tokens) -> tokenize(T, Line, _Scope, Tokens) ->
{error, {Line, "invalid token: ", until_eol(T)}, T, Tokens}. {error, {Line, "invalid token: ", until_eol(T)}, T, Tokens}.
@@ -498,7 +555,7 @@ until_eol([]) -> [];
until_eol([H|T]) -> [H|until_eol(T)]. until_eol([H|T]) -> [H|until_eol(T)].
escape_char(List) -> escape_char(List) ->
<< Char/utf8 >> = elixir_interpolation:unescape_chars(list_to_binary(List)), <<Char/utf8>> = elixir_interpolation:unescape_chars(list_to_binary(List)),
Char. Char.
%% Handlers %% Handlers
@@ -590,6 +647,19 @@ handle_call_identifier(Rest, Line, Op, Scope, Tokens) ->
Token = check_call_identifier(identifier, Line, Op, Rest), Token = check_call_identifier(identifier, Line, Op, Rest),
tokenize(Rest, Line, Scope, [Token|add_token_with_nl({'.', Line}, Tokens)]). tokenize(Rest, Line, Scope, [Token|add_token_with_nl({'.', Line}, Tokens)]).
% ## Ambiguous unary/binary operators tokens
handle_space_sensitive_tokens([Sign, NotMarker|T], Line, Scope, [{Identifier, _, _} = H|Tokens]) when
?dual_op(Sign),
not(?is_space(NotMarker)),
NotMarker /= $(, NotMarker /= $[, NotMarker /= $<, NotMarker /= ${, %% containers
NotMarker /= $%, NotMarker /= $+, NotMarker /= $-, NotMarker /= $/, NotMarker /= $>, %% operators
Identifier == identifier ->
Rest = [NotMarker|T],
tokenize(Rest, Line, Scope, [{dual_op, Line, list_to_atom([Sign])}, setelement(1, H, op_identifier)|Tokens]);
handle_space_sensitive_tokens(String, Line, Scope, Tokens) ->
tokenize(String, Line, Scope, Tokens).
%% Helpers %% Helpers
eol(_Line, _Mod, [{',',_}|_] = Tokens) -> Tokens; eol(_Line, _Mod, [{',',_}|_] = Tokens) -> Tokens;
@@ -945,7 +1015,6 @@ keyword('nil') -> token;
keyword('not') -> unary_op; keyword('not') -> unary_op;
keyword('and') -> and_op; keyword('and') -> and_op;
keyword('or') -> or_op; keyword('or') -> or_op;
keyword('xor') -> or_op;
keyword('when') -> when_op; keyword('when') -> when_op;
keyword('in') -> in_op; keyword('in') -> in_op;
+8 -3
View File
@@ -72,7 +72,7 @@ translate({fn, Meta, Clauses}, S) ->
%% Cond %% Cond
translate({'cond', _Meta, [[{do, Pairs}]]}, S) -> translate({'cond', CondMeta, [[{do, Pairs}]]}, S) ->
[{'->', Meta, [[Condition], Body]}|T] = lists:reverse(Pairs), [{'->', Meta, [[Condition], Body]}|T] = lists:reverse(Pairs),
Case = Case =
case Condition of case Condition of
@@ -88,7 +88,7 @@ translate({'cond', _Meta, [[{do, Pairs}]]}, S) ->
Acc = {'case', Meta, [Condition, [{do, [Truthy, Falsy]}]]}, Acc = {'case', Meta, [Condition, [{do, [Truthy, Falsy]}]]},
build_cond_clauses(T, Acc, Meta) build_cond_clauses(T, Acc, Meta)
end, end,
translate(Case, S); translate(replace_case_meta(CondMeta, Case), S);
%% Case %% Case
@@ -249,7 +249,7 @@ translate({{'.', _, [Left, Right]}, Meta, Args}, S)
{atom, Line, 'true'}}, {atom, Line, 'true'}},
{map_field_assoc, Line, {map_field_assoc, Line,
{atom, Line, key}, {atom, Line, key},
{atom, Line, TRight}}, TRight},
{map_field_assoc, Line, {map_field_assoc, Line,
{atom, Line, term}, {atom, Line, term},
TVar}]}, TVar}]},
@@ -387,6 +387,11 @@ build_cond_clauses([{'->', NewMeta, [[Condition], Body]}|T], Acc, OldMeta) ->
build_cond_clauses([], Acc, _) -> build_cond_clauses([], Acc, _) ->
Acc. Acc.
replace_case_meta(Meta, {'case', _, Args}) ->
{'case', Meta, Args};
replace_case_meta(_Meta, Other) ->
Other.
build_truthy_clause(Meta, Condition, Body) -> build_truthy_clause(Meta, Condition, Body) ->
case elixir_utils:returns_boolean(Condition) of case elixir_utils:returns_boolean(Condition) of
true -> true ->
+1 -1
View File
@@ -46,7 +46,7 @@ read_link_type(File) ->
relative_to_cwd(Path) -> relative_to_cwd(Path) ->
case elixir_compiler:get_opt(internal) of case elixir_compiler:get_opt(internal) of
true -> Path; true -> Path;
false -> 'Elixir.String':to_char_list('Elixir.Path':relative_to_cwd(Path)) false -> 'Elixir.Path':relative_to_cwd(Path)
end. end.
characters_to_list(Data) when is_list(Data) -> characters_to_list(Data) when is_list(Data) ->
+3 -1
View File
@@ -20,7 +20,9 @@ defmodule AccessTest do
test "for nil" do test "for nil" do
assert nil[:foo] == nil assert nil[:foo] == nil
assert Access.get(nil, :foo) == nil assert Access.get(nil, :foo) == nil
assert Access.get_and_update(nil, :foo, fn nil -> {:ok, :bar} end) == {:ok, :bar} assert_raise ArgumentError, "could not put/update key :foo on a nil value", fn ->
Access.get_and_update(nil, :foo, fn nil -> {:ok, :bar} end)
end
end end
test "for keywords" do test "for keywords" do
+12 -8
View File
@@ -3,7 +3,11 @@ Code.require_file "test_helper.exs", __DIR__
defmodule AgentTest do defmodule AgentTest do
use ExUnit.Case, async: true use ExUnit.Case, async: true
test "start_link/2 workflow with unregistered name" do def identity(state) do
state
end
test "start_link/2 workflow with unregistered name and anonymous functions" do
{:ok, pid} = Agent.start_link(fn -> %{} end) {:ok, pid} = Agent.start_link(fn -> %{} end)
{:links, links} = Process.info(self, :links) {:links, links} = Process.info(self, :links)
@@ -17,13 +21,13 @@ defmodule AgentTest do
wait_until_dead(pid) wait_until_dead(pid)
end end
test "start/2 workflow with registered name" do test "start/2 workflow with registered name and module functions" do
{:ok, pid} = Agent.start(fn -> %{} end, name: :agent) {:ok, pid} = Agent.start(Map, :new, [], name: :agent)
assert Process.info(pid, :registered_name) == {:registered_name, :agent} assert Process.info(pid, :registered_name) == {:registered_name, :agent}
assert Agent.cast(:agent, &Map.put(&1, :hello, :world)) == :ok assert Agent.cast(:agent, Map, :put, [:hello, :world]) == :ok
assert Agent.get(:agent, &Map.get(&1, :hello)) == :world assert Agent.get(:agent, Map, :get, [:hello]) == :world
assert Agent.get_and_update(:agent, &Map.pop(&1, :hello)) == :world assert Agent.get_and_update(:agent, Map, :pop, [:hello]) == :world
assert Agent.get(:agent, &(&1)) == %{} assert Agent.get(:agent, AgentTest, :identity, []) == %{}
assert Agent.stop(:agent) == :ok assert Agent.stop(:agent) == :ok
assert Process.info(pid, :registered_name) == nil assert Process.info(pid, :registered_name) == nil
end end
@@ -31,7 +35,7 @@ defmodule AgentTest do
test ":sys.change_code/4 with mfa" do test ":sys.change_code/4 with mfa" do
{ :ok, pid } = Agent.start_link(fn -> %{} end) { :ok, pid } = Agent.start_link(fn -> %{} end)
:ok = :sys.suspend(pid) :ok = :sys.suspend(pid)
mfa = { Map, :put, [:hello, :world] } mfa = {Map, :put, [:hello, :world]}
assert :sys.change_code(pid, __MODULE__, "vsn", mfa) == :ok assert :sys.change_code(pid, __MODULE__, "vsn", mfa) == :ok
:ok = :sys.resume(pid) :ok = :sys.resume(pid)
assert Agent.get(pid, &Map.get(&1, :hello)) == :world assert Agent.get(pid, &Map.get(&1, :hello)) == :world
+10
View File
@@ -28,6 +28,16 @@ defmodule CodeTest do
assert Code.eval_string("var!(a, Sample) = 1") == {1, [{{:a,Sample},1}]} assert Code.eval_string("var!(a, Sample) = 1") == {1, [{{:a,Sample},1}]}
end end
test :eval_binary_errors do
msg = "nofile:2: a binary field without size is only allowed at the end of a binary pattern"
assert_raise CompileError, msg, fn ->
Code.eval_string("""
foo = "foo"
"\\"" <> bar <> "\\"" = foo
""")
end
end
test :eval_with_unnamed_scopes do test :eval_with_unnamed_scopes do
assert {%RuntimeError{}, [a: %RuntimeError{}]} = assert {%RuntimeError{}, [a: %RuntimeError{}]} =
Code.eval_string("a = (try do (raise \"hello\") rescue e -> e end)") Code.eval_string("a = (try do (raise \"hello\") rescue e -> e end)")
+22
View File
@@ -280,6 +280,28 @@ defmodule EnumTest.List do
assert Enum.sort([5, 3, 2, 4, 1], &(&1 > &2)) == [5, 4, 3, 2, 1] assert Enum.sort([5, 3, 2, 4, 1], &(&1 > &2)) == [5, 4, 3, 2, 1]
end end
test :sort_by do
collection = [
[other_data: 1, sorted_data: 5],
[other_data: 3, sorted_data: 4],
[other_data: 4, sorted_data: 3],
[other_data: 2, sorted_data: 2],
[other_data: 5, sorted_data: 1]
]
assert Enum.sort_by(
collection,
&(&1[:sorted_data])
) == [
[other_data: 5, sorted_data: 1],
[other_data: 2, sorted_data: 2],
[other_data: 4, sorted_data: 3],
[other_data: 3, sorted_data: 4],
[other_data: 1, sorted_data: 5]
]
assert Enum.sort_by(collection, &(&1[:sorted_data]), &>=/2) == collection
end
test :split do test :split do
assert Enum.split([1, 2, 3], 0) == {[], [1, 2, 3]} assert Enum.split([1, 2, 3], 0) == {[], [1, 2, 3]}
assert Enum.split([1, 2, 3], 1) == {[1], [2, 3]} assert Enum.split([1, 2, 3], 1) == {[1], [2, 3]}
+11 -11
View File
@@ -689,7 +689,7 @@ defmodule FileTest do
fixture = tmp_path("tmp_test.txt") fixture = tmp_path("tmp_test.txt")
File.write(fixture, "test") File.write(fixture, "test")
assert File.exists?(fixture) assert File.exists?(fixture)
File.chmod(fixture, 0100444) File.chmod(fixture, 0o100444)
assert File.rm(fixture) == :ok assert File.rm(fixture) == :ok
refute File.exists?(fixture) refute File.exists?(fixture)
end end
@@ -1195,14 +1195,14 @@ defmodule FileTest do
File.touch(fixture) File.touch(fixture)
try do try do
assert File.chmod(fixture, 0100666) == :ok assert File.chmod(fixture, 0o100666) == :ok
stat = File.stat!(fixture) stat = File.stat!(fixture)
assert stat.mode == 0100666 assert stat.mode == 0o100666
unless is_win? do unless is_win? do
assert File.chmod(fixture, 0100777) == :ok assert File.chmod(fixture, 0o100777) == :ok
stat = File.stat!(fixture) stat = File.stat!(fixture)
assert stat.mode == 0100777 assert stat.mode == 0o100777
end end
after after
File.rm(fixture) File.rm(fixture)
@@ -1214,14 +1214,14 @@ defmodule FileTest do
File.touch(fixture) File.touch(fixture)
try do try do
assert File.chmod!(fixture, 0100666) == :ok assert File.chmod!(fixture, 0o100666) == :ok
stat = File.stat!(fixture) stat = File.stat!(fixture)
assert stat.mode == 0100666 assert stat.mode == 0o100666
unless is_win? do unless is_win? do
assert File.chmod!(fixture, 0100777) == :ok assert File.chmod!(fixture, 0o100777) == :ok
stat = File.stat!(fixture) stat = File.stat!(fixture)
assert stat.mode == 0100777 assert stat.mode == 0o100777
end end
after after
File.rm(fixture) File.rm(fixture)
@@ -1232,7 +1232,7 @@ defmodule FileTest do
fixture = tmp_path("tmp_test.txt") fixture = tmp_path("tmp_test.txt")
File.rm(fixture) File.rm(fixture)
assert File.chmod(fixture, 0100777) == {:error,:enoent} assert File.chmod(fixture, 0o100777) == {:error,:enoent}
end end
test :chmod_with_failure! do test :chmod_with_failure! do
@@ -1241,7 +1241,7 @@ defmodule FileTest do
message = ~r"could not change mode for #{escape fixture}: no such file or directory" message = ~r"could not change mode for #{escape fixture}: no such file or directory"
assert_raise File.Error, message, fn -> assert_raise File.Error, message, fn ->
File.chmod!(fixture, 0100777) File.chmod!(fixture, 0o100777)
end end
end end
+1 -1
View File
@@ -5,4 +5,4 @@ defmodule AtExit do
end end
System.at_exit fn(status) -> IO.puts "cruel world with status #{status}" end System.at_exit fn(status) -> IO.puts "cruel world with status #{status}" end
AtExit.at_exit("goodbye ") AtExit.at_exit("goodbye ")
exit(0) exit({:shutdown, 1})
+44 -26
View File
@@ -28,35 +28,53 @@ defmodule FloatTest do
end end
test :floor do test :floor do
assert Float.floor(12) === 12 assert Float.floor(12.524235) === 12.0
assert Float.floor(-12) === -12 assert Float.floor(-12.5) === -13.0
assert Float.floor(12.524235) === 12 assert Float.floor(-12.524235) === -13.0
assert Float.floor(-12.5) === -13 assert Float.floor(7.5e3) === 7500.0
assert Float.floor(-12.524235) === -13 assert Float.floor(7.5432e3) === 7543.0
assert Float.floor(7.5e3) === 7500 assert Float.floor(7.5e-3) === 0.0
assert Float.floor(7.5432e3) === 7543 assert Float.floor(-12.32453e4) === -123246.0
assert Float.floor(7.5e-3) === 0 assert Float.floor(-12.32453e-10) === -1.0
assert Float.floor(-12.32453e4) === -123246 assert Float.floor(0.32453e-10) === 0.0
assert Float.floor(-12.32453e-10) === -1 assert Float.floor(-0.32453e-10) === -1.0
assert Float.floor(0.32453e-10) === 0 assert Float.floor(1.32453e-10) === 0.0
assert Float.floor(-0.32453e-10) === -1 end
assert Float.floor(1.32453e-10) === 0
test :floor_with_precision do
assert Float.floor(12.524235, 0) === 12.0
assert Float.floor(-12.524235, 0) === -13.0
assert Float.floor(12.52, 2) === 12.52
assert Float.floor(-12.52, 2) === -12.52
assert Float.floor(12.524235, 2) === 12.52
assert Float.floor(-12.524235, 3) === -12.525
end end
test :ceil do test :ceil do
assert Float.ceil(12) === 12 assert Float.ceil(12.524235) === 13.0
assert Float.ceil(-12) === -12 assert Float.ceil(-12.5) === -12.0
assert Float.ceil(12.524235) === 13 assert Float.ceil(-12.524235) === -12.0
assert Float.ceil(-12.5) === -12 assert Float.ceil(7.5e3) === 7500.0
assert Float.ceil(-12.524235) === -12 assert Float.ceil(7.5432e3) === 7544.0
assert Float.ceil(7.5e3) === 7500 assert Float.ceil(7.5e-3) === 1.0
assert Float.ceil(7.5432e3) === 7544 assert Float.ceil(-12.32453e4) === -123245.0
assert Float.ceil(7.5e-3) === 1 assert Float.ceil(-12.32453e-10) === 0.0
assert Float.ceil(-12.32453e4) === -123245 assert Float.ceil(0.32453e-10) === 1.0
assert Float.ceil(-12.32453e-10) === 0 assert Float.ceil(-0.32453e-10) === 0.0
assert Float.ceil(0.32453e-10) === 1 assert Float.ceil(1.32453e-10) === 1.0
assert Float.ceil(-0.32453e-10) === 0 end
assert Float.ceil(1.32453e-10) === 1
test :ceil_with_precision do
assert Float.ceil(12.524235, 0) === 13.0
assert Float.ceil(-12.524235, 0) === -12.0
assert Float.ceil(12.52, 2) === 12.52
assert Float.ceil(-12.52, 2) === -12.52
assert Float.ceil(12.524235, 2) === 12.53
assert Float.ceil(-12.524235, 3) === -12.524
end end
test :round do test :round do

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