Compare commits

...
279 Commits
Author SHA1 Message Date
José Valim 52ff7e9686 Release v1.0.0 2014-09-10 14:11:13 +02:00
José Valim e2eb2cfdb1 Remove broken List.unzip 2014-09-10 01:24:34 +02:00
José Valim a5b4c1c04d Document and test Logger functions return :ok 2014-09-09 16:58:48 +02:00
José Valim e38f960d7e Update CHANGELOG 2014-09-09 16:55:47 +02:00
José Valim 9e74ee686e Add Logger.enable/1 and Logger.disable/1 2014-09-09 16:54:02 +02:00
José Valim e068625d7d Add integer guards around Enum.fetch/2 2014-09-09 00:09:18 +02:00
José Valim 00e8446a58 Remove support for inner swap_handler 2014-09-08 19:07:13 +02:00
José Valim 2aecfede5b Merge pull request #2759 from mikepack/transform-doc-fix
Fix typos in Stream.transform/3 docs
2014-09-07 22:29:51 +02:00
Mike Pack 9eebefb976 Fix typos in Stream.transform/3 docs 2014-09-07 14:24:28 -06:00
José Valim b709cba371 Properly document Task.Supervisor options 2014-09-07 16:40:01 +02:00
José Valim 1d919f59e3 Remove deprecated code 2014-09-07 15:36:07 +02:00
José Valim 0e8d3f9208 Start v1.0.0 2014-09-07 13:20:02 +02:00
José Valim 8931f34582 Release v1.0.0-rc2 2014-09-07 12:49:56 +02:00
José Valim 843706e36e Add free operators
<<~, ~>>, <~, ~>, <~>, <|>
2014-09-07 12:42:10 +02:00
José Valim 172bfdb128 Revert "Merge pull request #2702 from alexrp/master"
<~ is already a valid expression in Elixir.

This reverts commit 2c741ec6ae, reversing
changes made to 12a457829f.
2014-09-07 12:12:35 +02:00
José Valim 2c741ec6ae Merge pull request #2702 from alexrp/master
Introduce new operators intended for use by parser combinator libraries.
2014-09-07 12:06:29 +02:00
Eric Meadows-Jönsson 12a457829f Return the proper exit code from powershell 2014-09-06 21:10:48 +02:00
José Valim 0ff6850237 Raise if status code != 0 and use non-verbose modes 2014-09-06 20:33:02 +02:00
Eric Meadows-Jönsson 63f6088476 Bump Hex requirement 2014-09-06 20:13:37 +02:00
Eric Meadows-Jönsson 206f6413f8 Merge pull request #2756 from elixir-lang/emj-archive-shell
Fetch Hex with wget or curl
2014-09-06 20:12:49 +02:00
Eric Meadows-Jönsson a2c0424b37 Add file exists check on windows when installing archive 2014-09-06 19:14:36 +02:00
Eric Meadows-Jönsson 6ea7d3cef6 Fetch Hex with wget or curl
This way we can use https and do peer verification without bundling
certificates with elixir.
2014-09-06 16:37:57 +02:00
José Valim 2cafd8c060 Ensure we don't look up inexisting variables for default args 2014-09-05 17:59:45 +02:00
José Valim 7271dfebfb Update CHANGELOG 2014-09-05 16:52:26 +02:00
José Valim 2eff3590c1 Lowercase drive letters from cwd, closes #2747 2014-09-05 16:43:55 +02:00
José Valim b578f45060 Fix IEx test 2014-09-05 16:35:30 +02:00
José Valim a34ccfe129 Make the regex type non-opaque 2014-09-05 16:27:20 +02:00
José Valim 8f4442f2df Test :sys functions with GenEvent 2014-09-05 16:15:28 +02:00
José Valim 598e42e00b Tidy up GenEvent docs and test suite 2014-09-05 13:34:26 +02:00
José Valim b93912b9dc Deprecate monitor option in GenEvent 2014-09-05 13:20:40 +02:00
José Valim 71eca8a5d6 Move from already_added to already_present 2014-09-05 12:50:42 +02:00
José Valim f0bb0e1f7f Do not wrap process handlers in a tuple 2014-09-05 12:26:59 +02:00
José Valim 2cb0639208 Move from module_not_found to the more generic handler_not_found 2014-09-05 11:59:53 +02:00
José Valim 081e3b36ec Allow only one process handler per process 2014-09-05 11:51:33 +02:00
José Valim d521d7927f Leave clause in case the handler can fail 2014-09-04 19:21:37 +02:00
José Valim 7313d26720 Remove deprecated branches 2014-09-04 13:58:58 +02:00
José Valim 577a07d47f Do not consider handler swap to be failures 2014-09-04 13:24:17 +02:00
José Valim f6093d40d7 Tag all doctest tests 2014-09-04 12:10:35 +02:00
José Valim 1dd034d4aa Merge pull request #2752 from fishcakez/dialyzer_warns
Fix some dialyzer warnings
2014-09-03 21:05:29 +02:00
James Fish 2417b196a4 Fix elixir dialyzer warnings 2014-09-03 19:29:01 +01:00
James Fish 50bb712e22 Fix iex dialyzer warnings 2014-09-03 19:24:32 +01:00
James Fish 7b0747c907 Fix :sys.replace_state/2,3 for GenEvent 2014-09-03 19:24:01 +01:00
James Fish b878e414de Fix mix dialyzer warnings and improve task specs 2014-09-03 19:23:29 +01:00
James Fish c447868e0f Fix Mix lock files to use maps 2014-09-03 19:22:42 +01:00
James Fish 681d5aa9c4 Fix logger dialyzer warnings 2014-09-03 19:20:09 +01:00
James Fish 0263dcc621 Don't check for race conditions with dialyzer
For more information:
http://erlang.org/pipermail/erlang-bugs/2014-July/004518.html
2014-09-03 19:13:39 +01:00
José Valim 1ae24c5c5d Update CHANGELOG 2014-09-03 14:38:28 +02:00
José Valim 6c02ebe4af Merge pull request #2720 from edgurgel/print-mailbox-on-failing-assert-receive
Print process mailbox on failing assert_receive
2014-09-03 14:24:16 +02:00
José Valim a08e44c5a9 Default max_restarts to 3 (closer to Erlang) 2014-09-03 14:23:03 +02:00
José Valim 08f30098db Improve iex error message 2014-09-03 14:22:16 +02:00
José Valim 5dbd91e4fa Merge pull request #2746 from styx/master
Fixes #2744
2014-09-03 13:46:23 +02:00
Mikhail S. Pobolovets 5b2eb54794 #2744: Ensure Path.relative_to is case insensitive on the drive letter
Signed-off-by: Mikhail S. Pobolovets <styx.mp@gmail.com>
2014-09-03 14:40:03 +03:00
José Valim 2c17007693 Merge pull request #2749 from alco/macro-env-doc
Document how to obtain a custom instance of Macro.Env
2014-09-03 13:13:15 +02:00
Alexei Sholik 2770c32f3d Document how to obtain a custom instance of Macro.Env 2014-09-03 11:10:07 +03:00
José Valim 8c8f6d8346 Merge pull request #2743 from fishcakez/loggertest_exit_signal_rc
Fix exit signal race condition in logger tests
2014-09-02 12:22:22 +02:00
James Fish 3950130e73 Fix exit signal race condition in logger tests
Test process must wait for exit signal before untrapping exits,
otherwise exit signal may cause it to exit.
2014-09-02 11:07:18 +01:00
Alex Rønne Petersen c8bcd89c7c Introduce new operators intended for use by parser combinator libraries.
The expected definitions (which are by no means required) in
parser combinator speak are:

* `p  ~>> f`   =  `bind(p, f)`
* `p1 ~>  p2`  =  `pair_right(p1, p2)`
* `p1 <~  p2`  =  `pair_left(p1, p2)`
* `p1 <~> p2`  =  `pair_both(p1, p2)`
* `p  |~> f`   =  `map(p, f)`
* `1p <|> p2`  =  `either(p1, p2)`
2014-09-02 01:57:50 +02:00
José Valim 7f7aaa115f Merge pull request #2736 from fishcakez/logger_stopped_exit
Fix Logger.log/2 not to raise on Logger exit
2014-09-01 19:33:11 -04:00
José Valim 53aeb26407 Merge pull request #2741 from styx/master
Fixes #2737
2014-09-01 19:31:55 -04:00
Mikhail S. Pobolovets bba0d12510 #2737: deps.unlock should warn when given missing dependency
Signed-off-by: Mikhail S. Pobolovets <styx.mp@gmail.com>
2014-09-01 17:53:03 +03:00
Eric Meadows-Jönsson 035d8227a5 Merge pull request #2740 from styx/patch-1
Typo fix
2014-09-01 13:36:43 +02:00
Eric Meadows-Jönsson 003a4da87e Merge pull request #2738 from mgwidmann/master
Correct documentation for proper grammer.
2014-09-01 13:36:17 +02:00
Mikhail S. Pobolovets 1977631b7f Typo fix 2014-09-01 11:58:09 +03:00
matt c9a0ec000a Correct documentation for proper grammer. 2014-08-31 23:55:41 -04:00
Eric Meadows-Jönsson ba842ba113 No commas in deps.unlock 2014-08-31 19:49:50 +02:00
James Fish 6304919d0d Update Logger mode after configuration
Compute mode after configure as possible mode should change.
2014-08-31 17:04:23 +01:00
James Fish d2d27e96e5 Fix Logger.log/2 not to raise on Logger exit 2014-08-31 16:40:57 +01:00
Eric Meadows-Jönsson fc6f7c532c Fix unused warnings 2014-08-31 09:01:00 +02:00
José Valim 1bee85e518 Merge pull request #2734 from gjaldon/clearer-unquote-splicing-error
Friendlier unquote_splicing error with hints
2014-08-30 14:58:30 -04:00
Gabriel Jaldon 66b211987d Friendlier unquote_splicing error with hints 2014-08-31 02:01:17 +08:00
José Valim 15fdae4de6 Start v1.0.0-rc2 2014-08-30 13:38:53 -04:00
José Valim 5b4497fa6b Release v1.0.0-rc1 2014-08-30 13:33:02 -04:00
José Valim 612e124ec9 Add tests for sync_threshold 2014-08-29 08:13:53 -04:00
José Valim abd2b35738 Get rid of Logger timeout config, closes #2724 2014-08-29 08:01:03 -04:00
José Valim 2afb875099 Merge pull request #2725 from MSch/document-compile-attr-accumulation
Document that @compile accumulates.
2014-08-28 14:10:41 -04:00
Martin Schürrer 88507f65f8 Document that @compile accumulates. 2014-08-28 20:05:56 +02:00
José Valim 275d1f5494 Straight up and unify impls of clean, deps.clean and deps.unlock 2014-08-28 13:00:36 -04:00
José Valim e6420c8d13 Merge pull request #2715 from nurugger07/remove_unused_deps
Add an unused flag to deps.clean & deps.unlock
2014-08-28 11:38:46 -04:00
Eduardo Gurgel 60bcf1cadb Print process mailbox on failing assert_receive 2014-08-29 00:33:48 +12:00
Eduardo Gurgel d86ca5826c Format multiline AssertionError message with correct padding 2014-08-29 00:33:25 +12:00
José Valim 2af00f4c33 Merge pull request #2723 from edgurgel/fix-private-function-on-algebra
Make do_surround_many private on Inspect.Algebra
2014-08-27 16:08:55 -04:00
Eduardo Gurgel 0fe86e77c1 Make do_surround_many private on Inspect.Algebra 2014-08-28 08:01:32 +12:00
José Valim 1ca451f779 Normalize is_guard checks 2014-08-27 15:28:31 -04:00
José Valim ebe0c8cf8e Update CHANGELOG 2014-08-27 12:21:12 -04:00
José Valim de9feb26d5 Deprecate x.Alias syntax 2014-08-27 12:17:15 -04:00
José Valim b8528f6c8e Remove warnings from the test suite 2014-08-25 14:42:24 -04:00
Alexei Sholik c9fbbac391 Add version checks and proper error formatting to escripts. Closes #2709
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2014-08-25 14:26:54 -04:00
José Valim 98e1b4ccf8 Ensure we tag the destination when compiling through mix
Closes #2533
2014-08-25 14:26:10 -04:00
Alexei Sholik ae19491620 Merge pull request #2701 from alco/escript-protocol-consolidation
Escript protocol consolidation
2014-08-23 23:24:48 +03:00
Alexei Sholik 64f3760b19 Add missing fixtures for escript test 2014-08-23 23:16:29 +03:00
José Valim 0b38bc050f Demonitor and flush when appropriate 2014-08-23 15:56:38 -04:00
José Valim 9431338654 Add timeouts to ExUnit, closes #2660 2014-08-23 15:55:33 -04:00
José Valim 88be750a32 import_config/1 now accepts wildcards 2014-08-23 15:14:12 -04:00
José Valim 032c8b8291 Update CHANGELOG 2014-08-23 14:42:52 -04:00
José Valim 2d90d8ac60 Remove code duplication on watcher 2014-08-23 14:38:28 -04:00
José Valim a8b2cb99ee Use to_string when possible on Logger.log 2014-08-23 14:34:51 -04:00
José Valim 68274311fb Merge pull request #2712 from fishcakez/logger_rc
Fix logger watcher race condition
2014-08-23 14:34:05 -04:00
José Valim 88f84f80b5 Merge pull request #2713 from fishcakez/logger_eh_racecon
Fix race condition in Logger.ErrorHandlerTest
2014-08-23 14:32:48 -04:00
James Fish 2bf898e8fe Fix race condition in Logger.ErrorHandlerTest
When crashing the error logger handler, the handler's watcher could log
its message before Logger was flushed, causing the watcher's log
message to appear in the first, rather than second, capture_log.
2014-08-23 19:20:06 +01:00
José Valim d45f451943 Allow configurable timeout and catch exits on logger 2014-08-23 14:09:11 -04:00
James Fish c0a075724f Fix logger watcher race condition
Previously a backend could be removed from logger and the watcher would
not have exited before the remove_backend/1 call returned. This meant
that a new watcher could not always be started because the child
(watcher) might still exist but be about to die.
2014-08-23 18:59:13 +01:00
José Valim f1a39375c9 Do not crash the caller when Logger is not available
Instead, return :ok | {:error, :not_available} and leave it up
to the caller to decide.
2014-08-23 13:43:58 -04:00
Alexei Sholik b9c1b22163 Add protocol consolidation support to escript.build. Closes #2699 2014-08-23 20:40:56 +03:00
José Valim be75e3be17 Purge in memory in modules before consolidating, closes #2700 2014-08-23 13:32:19 -04:00
José Valim 9793caa1ef Ensure we automatically load deps on compile 2014-08-23 12:22:38 -04:00
José Valim 0792fe2c52 Merge pull request #2708 from alco/typo
Fix a typo in Mix.Project.compile_path doc
2014-08-22 12:02:31 -04:00
Alexei Sholik 4408aa3450 Fix a typo in Mix.Project.compile_path doc 2014-08-22 18:33:24 +03:00
Eric Meadows-Jönsson e4ba8d5d21 Mix should not depend directly on Logger 2014-08-22 15:56:25 +02:00
Eric Meadows-Jönsson 689f14a867 Always compile project if task cannot be found
Closes #2704.
2014-08-21 15:26:58 +02:00
Eric Meadows-Jönsson aa5b414e8e Merge pull request #2703 from Kosmas/master
Update CONTRIBUTING.md
2014-08-21 12:19:11 +02:00
Kosmas Chatzimichalis 07467a733b Update CONTRIBUTING.md
Corrected 'should be send' to 'should be sent'
2014-08-21 11:34:34 +02:00
Alexei Sholik 74391b793c Merge pull request #2687 from alco/mix-language-setting
Mix language setting
2014-08-20 20:56:20 +03:00
Alexei Sholik f0b8bc1756 Use common code for Elixir and Erlang escripts 2014-08-20 20:38:24 +03:00
José Valim 3e54b95bf2 Fix the build 2014-08-20 11:30:30 -04:00
José Valim 313d26cb3b Use task start to get better reports 2014-08-20 10:28:11 -04:00
José Valim 44b85be014 Add Task.start/1 and Task.start/3 2014-08-20 10:27:23 -04:00
José Valim e344b1f9d8 Add a small note to UndefinedFunctionError when the module is not available 2014-08-20 10:22:36 -04:00
Eric Meadows-Jönsson f5f4205969 Merge pull request #2698 from Kosmas/master
Update CONTRIBUTING.md
2014-08-20 15:08:20 +02:00
Kosmas Chatzimichalis 538c717ded Update CONTRIBUTING.md
renamed 'issues tracker' to 'issue tracker' for consistency
2014-08-20 14:54:50 +02:00
Eric Meadows-Jönsson 8dd81e661a Merge pull request #2697 from hamiltop/patch-1
Fix docs (missing indentation in examples)
2014-08-20 11:24:32 +02:00
hamiltop 26b61b98f9 Fix docs (missing indentation in examples) 2014-08-19 20:28:24 -07:00
Eric Meadows-Jönsson ab9450d92f Don't print the underlying type for @opaque
Closes #2612
2014-08-19 17:14:30 +02:00
Eric Meadows-Jönsson a4a23b9a93 Fix Enum.traverse deprecated warning doctest 2014-08-19 17:00:58 +02:00
Eric Meadows-Jönsson 2f83ea5a7f Wrap erlang macros in () 2014-08-19 17:00:58 +02:00
Eric Meadows-Jönsson 7940d223ec Move elixir.hrl to src/ 2014-08-19 17:00:58 +02:00
Eric Meadows-Jönsson c6fa951520 Better error message on nbsp 2014-08-19 17:00:58 +02:00
José Valim e5c8194a70 Rename :add_stream_handler to :add_process_handler 2014-08-19 09:55:26 -04:00
Eric Meadows-Jönsson 4a7e3dd11f Document alias arguments 2014-08-19 14:16:19 +02:00
José Valim 638fccb79d Remove duration from GenEvent.Stream 2014-08-19 07:34:49 -04:00
José Valim e9d84ec43b Allow gen_event_EXIT notifications to be disabled for streams 2014-08-19 07:07:23 -04:00
Eric Meadows-Jönsson bb35b9afb1 Include makefile in precompiled zip 2014-08-18 23:11:27 +02:00
Eric Meadows-Jönsson 1496c04592 Improve error message for invalid typespec 2014-08-18 23:08:26 +02:00
José Valim 3e9725a5a8 Make tests that remove the logging backend sync 2014-08-18 16:51:38 -04:00
José Valim 1565ec7658 Update CHANGELOG 2014-08-18 16:43:17 -04:00
José Valim f1aa2f01c7 Merge pull request #2695 from elixir-lang/jv-ge
GenEvent++
2014-08-18 16:33:11 -04:00
José Valim 7284542116 Split GenEvent.Stream into its own file 2014-08-18 16:28:25 -04:00
José Valim 4f17e8fd1c Also tag notify messages 2014-08-18 16:20:25 -04:00
José Valim f536507b9d Make the mode a property of the sender 2014-08-18 15:38:55 -04:00
José Valim 592300a0f0 Ensure Logger watcher monitors event manager 2014-08-18 09:18:28 -04:00
Alexei Sholik 28b1bd16de Document the :language setting 2014-08-18 16:12:15 +03:00
José Valim 19fdd84f80 Update docs 2014-08-17 19:48:50 -04:00
José Valim 631e2a051a Document lack of guaranteed on monitored handlers 2014-08-17 18:44:06 -04:00
José Valim df0f7b5f95 Fix failures in logger 2014-08-17 18:36:59 -04:00
José Valim a0dfc4160e Ensure proper whereis expansion 2014-08-17 18:28:14 -04:00
José Valim ee8331023e Do not add duplicated handlers 2014-08-17 16:55:11 -04:00
José Valim a2ee71f5ab Implement some of fishcakez feedback 2014-08-17 13:18:55 -04:00
José Valim 60163194e2 Improve coverage for GenEvent 2014-08-17 09:20:02 -04:00
José Valim ba37682300 Merge pull request #2691 from herenowcoder/patch-1
fix doc typo in Macro.expand/2
2014-08-17 08:57:53 -04:00
Wojciech Kaczmarek 15265b9352 fix doc typo in Macro.expand/2 2014-08-17 14:55:25 +02:00
Alexei Sholik 0a929310de Support building Erlang-based escripts in mix escript.build 2014-08-17 15:19:42 +03:00
José Valim a8670c1cfa Do not pre-compile a project if an alias was found
Closes #2689
2014-08-17 06:10:20 -04:00
Alexei Sholik ebd18d45fa Only add :elixir application for Elixir projects in compile.app task 2014-08-17 01:22:33 +03:00
José Valim ccbfec611a Merge pull request #2684 from alco/mix-compile-erlang
Properly handle compilation errors in Erlang compiler
2014-08-16 17:05:00 -04:00
Alexei Sholik 98a0999c2e Properly handle compilation errors in Erlang compiler 2014-08-16 23:45:44 +03:00
José Valim e504142d3a Merge pull request #2686 from billgathen/patch-1
Docs: Using autocomplete for function discovery in IEx
2014-08-16 15:58:01 -04:00
José Valim c04e13169f Deprecate Collectable.empty and Enum.traverse
The current implementation has three issues:

1. It is slow as we can't reuse the data structure but rather
   build a new one from scratch

2. Due to 1., trees can't be traversed

3. empty/1 is unable to deal with resources
2014-08-16 15:46:58 +02:00
José Valim 96a4bff67b Use the new resource acc on GenEvent.stream/2 2014-08-16 15:41:30 +02:00
José Valim 46de5ea462 Make Stream.resource/3 more like Stream.transform/2
With this change, we allow the accumulator to be passed
to after_fun once iteration is done.

This, however, introduces a deprecation on the old
format used by Stream.resource/3.
2014-08-16 15:35:56 +02:00
Bill Gathen 22257b8342 Using autocomplete for function discovery
I had to go to the mailing list to discover this trick. Knowing it's autocomplete supplying the information makes it obvious in hindsight, but I had it in my head that it would be part of the h() command. It might also be worth adding a mention of autocomplete to the h(IEx) page, as well, but since this is likely first help page a new user would come across, this may be enough.
2014-08-16 08:59:18 -04:00
José Valim a0be9fe272 Improve Logger formatter defaults
* Make colors in the docs consistent with the ones in the code
* Add new lines before and after each message
2014-08-15 18:30:30 +02:00
José Valim 1fd5a2a38e Use monitored handlers instead of linked ones 2014-08-15 18:02:47 +02:00
José Valim 13aec18bd5 Update docs 2014-08-15 13:57:22 +02:00
José Valim a85938d092 Fix the build 2014-08-15 13:54:08 +02:00
José Valim 0d4af57abe Port GenEvent to Elixir for upcoming extensions 2014-08-15 13:16:14 +02:00
José Valim e00db53112 Talk about correctness first, performance later 2014-08-14 22:09:00 +02:00
José Valim b941618a10 Port the :gen_event client API to Elixir
The only difference is that notify is made similar to
GenServer's cast where an unknown atom name does not
trigger an error.
2014-08-13 18:20:10 +02:00
Eric Meadows-Jönsson d895c762a1 Add --force option to 'mix local.rebar' 2014-08-13 17:11:28 +02:00
Eric Meadows-Jönsson 427c4b01c9 Ensure load_tasks return no duplicates 2014-08-13 17:10:40 +02:00
Eric Meadows-Jönsson 9d5a728cb8 Output mix version on 'mix -v' 2014-08-13 17:10:40 +02:00
José Valim 1ed99d4ac7 Improve error message for undefined struct 2014-08-13 09:19:26 +02:00
José Valim ceb17fac7d Use GenServer when possible 2014-08-12 23:22:51 +02:00
José Valim c638f45542 Merge pull request #2674 from koudelka/unquote-doc-fix
Minor unquote/1 doc fix.
2014-08-12 21:40:02 +02:00
Michael Shapiro c09905d537 Minor unquote/1 doc fix. 2014-08-12 12:23:08 -07:00
José Valim 117a428030 Merge pull request #2668 from reset/fix-nocolor
fix --no-color / --color option for `mix test`
2014-08-12 19:32:33 +02:00
Jamie Winsor 2ada7c40d7 fix --no-color / --color option for mix test 2014-08-12 10:32:10 -07:00
José Valim cd162bf3d6 Do not inherit bugs on GenServer calls and casts
Prior to this change, we were inheriting OTP bug where
bad arguments in call were treated as exits.

We also fixed the bug in cast where messages could be
delivered out of order. It is preferrable to wait on
connection.
2014-08-12 17:30:25 +02:00
José Valim e6e71675ac Make a distinction between eol and ; 2014-08-12 16:27:45 +02:00
José Valim c54e0caf6f Merge pull request #2670 from chyndman/cygpath-quotes
Use quotes to avoid space-in-path issues with cygpath
2014-08-12 09:34:41 +02:00
José Valim b64552780e Merge pull request #2671 from lexmag/no-purge
No need in some purges in `Kernel.WarningTest`
2014-08-12 09:34:25 +02:00
Eric Meadows-Jönsson add8c66e82 Merge pull request #2673 from alexrp/master
Clarify match?/2 docs.
2014-08-12 02:02:35 +02:00
Alex Rønne Petersen aecbd2c16a Clarify match?/2 docs. 2014-08-12 01:54:08 +02:00
Aleksei Magusev 32a649c1cd No need for Stream. prefix 2014-08-12 03:31:03 +04:00
Aleksei Magusev a1e657cb75 No need in some purges in Kernel.WarningTest 2014-08-12 03:06:48 +04:00
Chris Hyndman 6bb4c4de03 Use quotes to avoid space-in-path issues with cygpath 2014-08-11 15:13:50 -07:00
José Valim ea5b56d434 Merge pull request #2667 from MSch/fix-new-config-exs
Fix example in config/config.exs that's created for new projects.
2014-08-11 20:51:03 +02:00
Martin Schürrer a4082748cf Fix example in config/config.exs that's created for new projects. 2014-08-11 20:43:36 +02:00
José Valim 4d763846c3 Ensure code escape \ works in IEx 2014-08-11 10:05:24 +02:00
José Valim d56cc88f5f Start v0.15.2-dev 2014-08-11 10:05:24 +02:00
José Valim 5e27b7b72b Merge pull request #2666 from hamiltop/stream_cycle_performance
Add special case for lists in Stream.cycle
2014-08-11 09:53:38 +02:00
Peter Hamilton 260d6d16ab Add special case for lists in Stream.cycle 2014-08-11 00:38:53 -07:00
José Valim 1a82f126a4 Merge pull request #2664 from hamiltop/add_timer_and_interval_streams
Add Stream.interval/1 and Stream.timer/1
2014-08-11 08:16:51 +02:00
Peter Hamilton 3cf3eee2ea Clean up interval/1 and timer/1 docs 2014-08-10 16:03:43 -07:00
Peter Hamilton 9e8a41b2d3 Add Stream.interval/1 and Stream.timer/1 2014-08-10 11:25:53 -07:00
José Valim a54d9212b8 Release v0.15.1 2014-08-10 19:06:49 +02:00
José Valim ee33608f75 Update CHANGELOG 2014-08-10 19:05:45 +02:00
José Valim 6e91275782 Merge pull request #2663 from lexmag/fix-pipe_op
Bind value to proper name in `elixir_tokenizer`
2014-08-10 17:56:59 +02:00
Aleksei Magusev c6f27541c1 Bind value to proper name in elixir_tokenizer 2014-08-10 19:41:13 +04:00
José Valim a2662a4b51 Ensure elixir and iex works on MinGW 2014-08-10 15:11:18 +02:00
José Valim 1d29262949 Check for terminal on both stdout and stderr 2014-08-10 14:40:46 +02:00
José Valim 1d655fa8f2 Check for terminal support from Elixir command line 2014-08-10 13:57:41 +02:00
José Valim 4bec785f4c Also support :sync mode in GenEvent, make :ack one the default 2014-08-10 12:00:13 +02:00
José Valim e3273f731f Fix build 2014-08-09 21:50:24 +02:00
José Valim a8564a7045 Deprecate functions as collectable 2014-08-09 21:32:32 +02:00
José Valim e2bac56556 Reply to GenEvent manager from stream right away
This way the GenEvent can start processing the other
handlers, reducing the processing time for each event
from sum(handlers) to max(handlers).
2014-08-09 21:12:51 +02:00
José Valim a38e6b7a0d Ensure aliases are invoked for recursive tasks
Closes #2650
2014-08-09 13:59:32 +02:00
José Valim 72199ca71d Fix build 2014-08-09 13:47:48 +02:00
José Valim 9420945f59 Leave it to the application to start Logger after compilation
Closes #2649
2014-08-09 13:38:27 +02:00
Eric Meadows-Jönsson 4ca1736f86 Accept more forms of git versions
Fixes the parsing of “git version 1.9.3 (Apple Git-50)”.
Closes #2653.
2014-08-09 11:16:25 +02:00
José Valim fd85ca443e Merge pull request #2652 from chyndman/elixir-bat-iex-startcli
Suppress "-s elixir start_cli" for IEx
2014-08-09 10:36:32 +02:00
Chris Hyndman 187e86749e Suppress "-s elixir start_cli" for IEx 2014-08-08 18:13:42 -07:00
José Valim e04a7aef6a Merge pull request #2651 from fishcakez/translator_format
Improve translator formatting
2014-08-08 22:19:23 +02:00
José Valim 0b1c8c92c0 Set -noshell on bin/iex 2014-08-08 22:11:06 +02:00
Chris Hyndman bf270d144f Prepend -noshell and friends always within Cygwin
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2014-08-08 22:11:06 +02:00
James Fish 2c009d1ec3 Improve translator formatting
* Improve application stop reasons
* Include stacktrace in crash reports
2014-08-08 21:08:36 +01:00
José Valim 08555f545c Merge pull request #2648 from sunaku/makefile-message-clarity
Makefile: shorten tip about Unicode embedding time
2014-08-08 20:41:26 +02:00
Suraj N. Kurapati 65d3af694b Makefile: shorten tip about Unicode embedding time
The estimate about this step taking "up to a minute" really depends on
the machine we're compiling on.  For example, on an old or underpowered
machine, it may take _longer_ than a minute.  So this patch changes the
wording accordingly and puts the reason for the delay at the beginning.
2014-08-08 09:07:26 -07:00
José Valim 8aa43eaedd Merge pull request #2647 from jwarwick/task_docs
Fixed doc typos in Task and URI
2014-08-08 16:30:12 +02:00
John Warwick 334c732691 Fixed doc typos in Task and URI 2014-08-08 10:23:36 -04:00
José Valim 24587110ad Merge pull request #2646 from jwarwick/logger_type
Fixed Logger.configure_backend spec
2014-08-08 16:18:46 +02:00
John Warwick b98421fe51 Fixed Logger.configure_backend spec 2014-08-08 10:11:39 -04:00
José Valim 752e3f8bc3 Update CHANGELOG 2014-08-08 13:31:40 +02:00
José Valim 6c8e70b1f8 Load handlers inside supervisor as they are now dynamic 2014-08-08 13:21:55 +02:00
José Valim c6fbfd2a67 Copy and check elixir version in archives
Closes #2396
2014-08-08 12:53:50 +02:00
José Valim 56ea6e52e4 Update configure_backend docs 2014-08-08 11:39:10 +02:00
José Valim 4726e0af91 Persist calls to add_backend and remove_backend 2014-08-08 11:38:18 +02:00
José Valim ee82679908 Merge pull request #2645 from tomkadwill/doc_false_handle_info
Added @doc false to handle_info function
2014-08-08 11:25:56 +02:00
Tom Kadwill aedd5a938f Added @doc false to handle_info function 2014-08-08 10:21:26 +01:00
José Valim 2d9c3fb6f0 Print warnings when used ? with characters with escape codes
Closes #2640
2014-08-08 11:13:13 +02:00
José Valim 6f07f4af22 Create the config directory before writing to config file 2014-08-08 10:23:54 +02:00
José Valim 656bd640e6 Load config/config.exs into escript
Closes #2643
2014-08-08 10:13:17 +02:00
José Valim bc2cb15a96 Add Logger.flush/0 2014-08-07 20:14:04 +02:00
José Valim dbf205b311 Deprecate Module.function/3 2014-08-07 20:00:57 +02:00
José Valim 0e0bb1c2a7 Do not raise conflicts on imports used from quotes, closes #2589 2014-08-07 18:09:19 +02:00
José Valim 736406a9b3 Do not allow invalid quoted expression to leak
Closes #2499
2014-08-07 17:42:05 +02:00
José Valim 886917753e Ensure we can escape bitstrings, add Macro.validate/1 2014-08-07 17:42:05 +02:00
José Valim f29889da99 Make environment explicit on escript build 2014-08-07 17:42:05 +02:00
José Valim aee33eac6d Merge pull request #2638 from jwarwick/logger_docs
Fixing typos in Logger module docs
2014-08-07 16:16:30 +02:00
John Warwick d8428ba1e6 Fixing typos in Logger module docs 2014-08-07 10:11:36 -04:00
José Valim 1f725b0d2c Merge pull request #2637 from fishcakez/improve_sasl_reports
Improve Logger's SASL report translation
2014-08-06 22:37:22 +02:00
James Fish 38f895cc13 Improve Logger's SASL report translation
* Pattern match keyword lists for faster parsing
* Support :supervisor_bridge reports
* Support missing :initial_call in :crash_report
* Improve formatting of registered name in :crash_report output
* More tests
2014-08-06 21:21:36 +01:00
José Valim 3fc07eff8e Merge pull request #2636 from voltone/uri-recomposition
Improved RFC compliance for URI to_string
2014-08-06 21:20:48 +02:00
Bram Verburg aa244c30fc Improved RFC compliance for URI to_string 2014-08-06 21:55:48 +03:00
José Valim f8647dae3e Ensure backends receive the id when added, closes #2635 2014-08-06 19:56:22 +02:00
José Valim 39fa2878ce Fix list rendering on ANSI docs 2014-08-06 19:42:26 +02:00
José Valim 5b5b95eb11 Update CHANGELOG 2014-08-06 13:27:54 +02:00
José Valim 67caa3ebb3 Fix padding in Logger 2014-08-06 13:20:01 +02:00
José Valim d09cd173bf Merge pull request #2634 from fishcakez/sasl_reports
Add SASL log forwarding option to Logger
2014-08-06 12:05:59 +02:00
José Valim 79fb40ab6a Correctly follow code specs when loading binaries
If we are compiling the module, we will load it with the
output of its beam file. If the module exists only in
memory, :code.which/1 will return `:in_memory`.

Closes #2533
2014-08-06 12:05:55 +02:00
José Valim ae2cb6bc43 Add the Windows wiki link to the README 2014-08-06 12:05:55 +02:00
James Fish 413b815ecc Add tests for SASL report logging 2014-08-06 09:47:17 +01:00
James Fish df89b5022e Add SASL log forwarding option to Logger 2014-08-05 21:22:50 +01:00
José Valim 90729655ee Merge pull request #2633 from lexmag/not-match
Use `not` instead of `!` in `IO.ANSI.enabled?/0`
2014-08-05 18:12:12 +02:00
Aleksei Magusev 171e9b3659 Use not instead of ! in IO.ANSI.enabled?/0 2014-08-05 19:29:39 +04:00
José Valim 3e256fcc1e Merge pull request #2631 from chyndman/contributing-windows
Remove Windows-related CONTRIBUTING.md section (now in Wiki)
2014-08-05 09:18:27 +02:00
Chris Hyndman 29ebbeda93 Remove Windows-related CONTRIBUTING.md section (now in Wiki) 2014-08-04 15:14:02 -07:00
José Valim 214d056911 Support on log messages 2014-08-04 20:38:13 +02:00
pragdave 9f065a77ca align the messages between log messages with different length level text. Added an extra format $levelpad, which is a space if the level is "warn" or "info"
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2014-08-04 20:32:43 +02:00
José Valim e70ac9768e Fix iex test 2014-08-04 19:41:37 +02:00
José Valim ec6c98ef47 Merge Logger.metadata 2014-08-04 19:03:24 +02:00
José Valim 7d0f92e12a Fix docs and specs introspection in iex 2014-08-04 19:03:24 +02:00
José Valim 8bf6193ccc Merge pull request #2630 from scrogson/patch-2
Doc correction for GenEvent
2014-08-04 18:51:15 +02:00
Sonny Scroggin 50f5dc76fb Doc correction for GenEvent 2014-08-04 11:45:22 -05:00
José Valim b9000999c9 Use the Logger PID to avoid race conditions on shutdown, closes #2628 2014-08-04 15:21:06 +02:00
José Valim 52be06c314 Do not expand args for unknown functions, closes #2387 2014-08-04 13:46:49 +02:00
José Valim 3da560f10c Update Mix.Dep.Lock documentation 2014-08-04 13:18:58 +02:00
José Valim f0f25300a0 Load mix deps only when there is a need to use them
This helps speed up peformance to many tasks. Tasks like
clean, help and compile --list must now explicitly call
loadpaths with checks disabled so they can still show
data coming from dependencies but without causing crashes.
2014-08-04 13:04:56 +02:00
José Valim c8a3a619af Update CHANGELOG 2014-08-04 10:45:59 +02:00
José Valim 25f64051ba Remove documentation from defimpls 2014-08-04 10:45:00 +02:00
José Valim 5efa6bb1c4 Since IO.ANSI.enabled? is a fast operation, no need to prefetch it on IEx 2014-08-04 10:43:02 +02:00
José Valim cb48f90f28 Merge pull request #2627 from smpallen99/inspect_base
Add integer base support to inspect
2014-08-04 10:42:34 +02:00
Stephen M. Pallen 3ba93b1186 Add integer base support to inspect
Option :base for inspect/2 supports :binary, :octal, :decimal, :hex.
Includes tests and documentation.
2014-08-03 20:17:58 -04:00
José Valim 7babcd0ed2 Merge pull request #2625 from niku/doc-about-.iex.exs
Improve IEx docs about .iex.exs
2014-08-03 20:04:12 +02:00
José Valim b6c1f2b2e1 Merge pull request #2624 from lexmag/pathtype
Reuse pathtype detection in `Path` module
2014-08-03 20:03:25 +02:00
Johnny Winn 5f853f8e71 Implement flag to clean unused dependencies
This addresses issues #2569
https://github.com/elixir-lang/elixir/issues/2569
2014-08-03 13:42:51 -04:00
Aleksei Magusev 6cccf3ac22 Reuse pathtype detection in Path module 2014-08-03 21:20:11 +04:00
niku 5b9b186d4b Improve IEx docs about .iex.exs 2014-08-03 23:02:03 +09:00
José Valim 57898bee03 Do not normalize paths on join, closes #2500 2014-08-03 14:59:38 +02:00
José Valim 6a6ba9f50d Remove deprecated code 2014-08-02 22:35:57 +02:00
José Valim 1e3135514f Start v0.15.1-dev 2014-08-02 21:57:43 +02:00
229 changed files with 5704 additions and 2585 deletions
+107
View File
@@ -1,5 +1,112 @@
# Changelog
## v1.0.0 (2014-09-10)
* Enhancements
* [Logger] Add `Logger.enable/1` and `Logger.disable/1`
* Backwards incompatible changes
* [GenEvent] No longer support `{:swap_handler, ...}` as callback return value
* [List] `List.unzip/2` was removed
## v1.0.0-rc2 (2014-09-07)
* Enhancements
* [ExUnit] Print process mailbox on failing `assert_receive`
* [ExUnit] Tag all doctests with the `:doctest` tag
* [Kernel] Friendlier `unquote_splicing` error on inline quote
* [Kernel] Add `<~`, `~>`, `<<~`, `~>>`, `<~>`, `<|>` as operators
* [Mix] `mix deps.unlock` warns when given a missing dependency
* [Mix] Fetch hex using HTTPS
* Bug fixes
* [File] Ensure `File.cwd` generates lowercase drive letters on Windows
* [GenEvent] Ensure the event manager does not crash on `:sys.replace_state/3`
* [GenEvent] Do not consider swap handlers to be failures in a stream
* [Logger] Fix `Logger.log/2` not to raise on Logger exit
* [Logger] Recompute sync/async mode on Logger configuration
* [System] Ensure `System.cwd` generates lowercase drive letters on Windows
* Deprecations
* [GenEvent] The `:monitor` option in `add_handler/4` and `swap_handler/6` is deprecated in favor of `add_mon_handler/3` and `swap_mon_handler/5`
* Backwards incompatible changes
* [Supervisor] Make `max_restarts` default to 3 (closer to upcoming Erlang defaults)
## v1.0.0-rc1 (2014-08-30)
* Enhancements
* [ExUnit] Add timeouts to tests (which is configurable via tags)
* [GenEvent] Add support for `GenEvent.ack_notify/2`
* [GenEvent] Add support for monitored handlers to `GenEvent.add_handler/4` that removes the drawbacks in the previous linked handlers
* [Logger] Allow any value that implements `String.Chars` to be logger
* [Logger] Allow `Logger.log/3` timeout to be configured
* [Mix] Add `--force` option to `mix local.rebar`
* [Mix] Fix `--no-color`/`--color` option for `mix test`
* [Mix] Add a `:language` option to improve the usability of Erlang projects using Mix
* [Mix] Accept wildcards on `Mix.Config.import_config/1`
* [Mix] Support protocol consolidation on escripts
* Bug fixes
* [IEx] Do not print the underlying type for `@opaque` in the `t` helper
* [GenEvent] Do not allow a handler to be added more than once
* [GenServer] Do not treat bad arguments as exits in `GenServer.call/2`
* [GenServer] Do not deliver out of order messages on `GenServer.cast/2` on distributed mode
* [Mix] Do not pre-compile a Mix project if an alias was found
* [Mix] Properly handle compilation errors in the Erlang compiler
* [Mix] Always try to compile project if task cannot be found
* [Mix] Purge in memory modules before consolidating
* [Mix] Always compile a project if a task cannot be found
* Deprecations
* [Collectable] Deprecate `Collectable.empty/1` and `Enum.traverse/2`
* [Integer] `odd?/1` and `even?/1` are deprecated in favor of `is_odd/1` and `is_even/1`
* [Kernel] `nil?/1` is deprecated in favor of `is_nil/1`
* [Kernel] `x.Alias` is deprecated in favor of an explicit `Module.concat/2`
* [Record] `record?/1` and `record?/2` are deprecated in favor of `is_record/1` and `is_record/2`
* [Stream] Returning `{item, acc} | nil` from `Stream.resource/2` is deprecated, instead return `{[item], acc} | {:halt, acc}` (similar to `Stream.transform/3`)
* Backwards incompatible changes
* [GenEvent] `GenEvent.cancel_stream/1` no longer is
* [GenEvent] The `:mode` and `:duration` options are deprecated and no longer supported in `GenEvent.stream/2` (the mode is now a property of the sender)
* [GenEvent] The `:linked` option is deprecated and no longer supported in `GenEvent.add_handler/4` (check monitored handlers instead)
## v0.15.1 (2014-08-10)
* Enhancements
* [GenEvent] Support `:ack` mode for GenEvent streams
* [Inspect] Support `:base` option in `inspect/2` to choose the base (`:binary`, `:octal`, `:decimal` or `:hex`) numbers a printed
* [kernel] Print warnings when used ? with characters with escape codes
* [Logger] Add SASL log forwarding option to Logger
* [Logger] Add `$padlevel` to option Logger formatter
* [Logger] Backends receive the exact handler value when added, allowing a key to be passed for configuration lookup
* [Logger] Add `Logger.flush/0` to flush the Logger (useful for testing)
* [Logger] Persist backends dynamically added or removed via `add_backend/2` and `remove_backend/2`
* [Macro] Add `Macro.validate/1` to recursively check if a value is a valid quoted expression
* [Mix] Load mix deps only when there is a need to use them (this improves the timing for the majority of tasks in a Mix project)
* [Mix] Make the environment explicit on the success message generated after `escript.build` is invoked
* [Mix] Load `config/config.exs` inside escripts
* [Mix] Store and check Elixir version requirement for generated archives
* Bug fixes
* [CLI] Fix shell bugs when running on Windows with Cygwin or MinGW
* [Kernel] Modules compiled by Elixir now report the correct beam location when `:code.which/1` is invoked. If the bytecode is only available in memory, `:code.which/1` returns `:in_memory`
* [Kernel] Do not expand args for unknown functions/macros
* [Kernel] Ensure `defstruct`, `@attr` inside and friends raise a nice error messages when values cannot be properly escaped
* [Kernel] Do not raise conflicts on imports used from inside quotes
* [Logger] Metadata is now correctly merged on each `Logger.metadata/1` call
* [Logger] Use the Logger PID on `:error_logger` wrapper to avoid race conditions on shutdown
* [Macro] Ensure bitstrings work with `Macro.escape/1`
* [Mix] Ensure aliases are invoked on umbrella recursive tasks
* [Mix] Leave it up to the application to start the Logger after compilation
* [Mix] Accept more forms of git versions (like "git version 1.9.3 (Apple Git-50)")
* [Path] Do not normalize paths in `Path.join/2` as normalization is beyond the scope of such function
* [URI] `to_string/1` now properly converts URI to strings when the schema is missing
* Deprecations
* [Collectable] Having a function as collectable is deprecated
* [Module] `Module.function/3` is deprecated, please use `:erlang.make_fun/3` instead
## v0.15.0 (2014-08-02)
* Enhancements
+2 -22
View File
@@ -10,7 +10,7 @@ Use the issues tracker for:
* [bug reports](#bugs-reports)
* [submitting pull requests](#pull-requests)
Please **do not** use the issues tracker for personal support requests nor feature requests. Support requests should be send to:
Please **do not** use the issue tracker for personal support requests nor feature requests. Support requests should be sent to:
* [the elixir-talk mailing list](http://groups.google.com/group/elixir-lang-talk)
* [Stack Overflow](http://stackoverflow.com/questions/ask?tags=elixir)
@@ -18,7 +18,7 @@ Please **do not** use the issues tracker for personal support requests nor featu
Feature requests can be discussed on [the elixir-core mailing list](http://groups.google.com/group/elixir-lang-core).
We do our best to keep the issues tracker tidy and organized, making it useful
We do our best to keep the issue tracker tidy and organized, making it useful
for everyone. For example, we classify open issues per application and perceived
difficulty of the issue, making it easier for developers to
[contribute to Elixir](#contributing).
@@ -107,26 +107,6 @@ check [the official build status on Travis-CI](https://travis-ci.org/elixir-lang
With tests running and passing, you are ready to contribute to Elixir and
send your pull requests.
### Building on Windows
There are a few extra steps you'll need to take for contributing from Windows.
Basically, once you have Erlang 17, Git, and MSYS from MinGW on your system,
you're all set. Specifically, here's what you need to do to get up and running:
1. Install [Git](http://www.git-scm.com/download/win),
[Erlang](http://www.erlang.org/download.html), and the
[MinGW Installation Manager](http://sourceforge.net/projects/mingw/files/latest/download?source=files).
2. Use the MinGW Installation Manager to install the msys-bash, msys-make, and
msys-grep packages.
3. Add `;C:\Program Files (x86)\Git\bin;C:\Program Files\erl6.0\bin;C:\Program Files\erl6.0\erts-6.0\bin;C:\MinGW\msys\1.0\bin`
to your "Path" environment variable . (This is under Control Panel > System
and Security > System > Advanced system settings > Environment Variables >
System variables)
You can now work in the Command Prompt similar to how you would on other OS'es,
except for some things (like creating symlinks) you'll need to run the Command
Prompt as an Administrator.
## Contributing Documentation
Code documentation (`@doc`, `@moduledoc`, `@typedoc`) has a special convention:
+3 -3
View File
@@ -86,7 +86,7 @@ $(KERNEL): lib/elixir/lib/*.ex lib/elixir/lib/*/*.ex lib/elixir/lib/*/*/*.ex
unicode: $(UNICODE)
$(UNICODE): lib/elixir/unicode/*
@ echo "==> unicode (compile)";
@ echo "This step can take up to a minute to compile in order to embed the Unicode database"
@ echo "Embedding the Unicode database... (this may take a while)"
$(Q) cd lib/elixir && ../../$(ELIXIRC) unicode/unicode.ex -o ebin;
$(eval $(call APP_TEMPLATE,ex_unit,ExUnit))
@@ -142,7 +142,7 @@ docs: compile ../ex_doc/bin/ex_doc
release_zip: compile
rm -rf v$(VERSION).zip
zip -9 -r v$(VERSION).zip bin CHANGELOG.md LEGAL lib/*/ebin LICENSE README.md VERSION
zip -9 -r v$(VERSION).zip bin CHANGELOG.md LEGAL lib/*/ebin LICENSE Makefile README.md VERSION
release_docs: docs
cd ../docs
@@ -183,7 +183,7 @@ test_stdlib: compile
#==> Dialyzer tasks
DIALYZER_OPTS = --no_check_plt --fullpath -Werror_handling -Wunmatched_returns -Wrace_conditions -Wunderspecs
DIALYZER_OPTS = --no_check_plt --fullpath -Werror_handling -Wunmatched_returns -Wunderspecs
PLT = .elixir.plt
$(PLT):
+2
View File
@@ -12,6 +12,8 @@ If you want to contribute to Elixir or run it from source, clone this repository
$ cd elixir
$ make clean test
> Note: if you are running on Windows, [this article includes important notes for compiling Elixir from source on Windows](https://github.com/elixir-lang/elixir/wiki/Windows).
If Elixir fails to build (specifically when pulling in a new version via git), be sure to remove any previous build artifacts by running `make clean`, then `make test`.
If tests pass, you are ready to move on to the [Getting Started guide][1] or to try Interactive Elixir by running: `bin/iex` in your terminal.
+2 -2
View File
@@ -14,9 +14,9 @@ This document simply outlines the release process:
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, name it Precompiled.zip
7. Release new zip with `make release_zip`, push new zip to GitHub Releases, name it `Precompiled.zip`
8. Fast-forward merge master into stable branch and push it
8. Fast-forward merge master into stable branch with `git merge master --ff` and push it
9. After release, bump versions, add `-dev` back and commit
+1 -1
View File
@@ -1 +1 @@
0.15.0
1.0.0
+8 -1
View File
@@ -80,7 +80,14 @@ done
SELF=$(readlink_f "$0")
SCRIPT_PATH=$(dirname "$SELF")
if [ "$MODE" != "iex" ]; then ERL="$ERL -noshell -s elixir start_cli"; fi
if [ "$OSTYPE" = "cygwin" ]; then SCRIPT_PATH=$(cygpath -m "SCRIPT_PATH"); fi
if [ "$MODE" != "iex" ]; then ERL="-noshell -s elixir start_cli $ERL"; fi
# Check for terminal support
if [ "$OS" != "Windows_NT" ]; then
if test -t 1 -a -t 2; then ERL="-elixir ansi_enabled true $ERL"; fi
fi
if [ -z "$ERL_PATH" ]; then
if [ -f "$SCRIPT_PATH/../releases/RELEASES" ] && [ -f "$SCRIPT_PATH/erl" ]; then
+9 -2
View File
@@ -48,6 +48,9 @@ set beforeExtra=
rem Flag which determines whether or not to use werl vs erl
set useWerl=0
rem Designates which mode / Elixir component to run as
set runMode="elixir"
rem Recursive loop called for each parameter that parses the cmd line parameters
:startloop
set par="%1"
@@ -62,6 +65,7 @@ if "%par%"=="""" (
)
rem ******* EXECUTION OPTIONS **********************
IF "%par%"==""--werl"" (Set useWerl=1)
IF "%par%"==""+iex"" (Set runMode="iex")
rem ******* elixir parameters **********************
rem Note: we don't have to do anything with options that don't take an argument
IF """"=="%par:-e=%" (shift)
@@ -90,10 +94,13 @@ for /d %%d in ("%originPath%..\lib\*.") do (
)
SETLOCAL disabledelayedexpansion
:run
IF NOT %runMode% == "iex" (
set beforeExtra=-s elixir start_cli %beforeExtra%
)
IF %useWerl% EQU 1 (
start werl.exe %ext_libs% %ELIXIR_ERL_OPTIONS% %parsErlang% -s elixir start_cli %beforeExtra% -extra %*
start werl.exe %ext_libs% %ELIXIR_ERL_OPTIONS% %parsErlang% %beforeExtra% -extra %*
) ELSE (
erl.exe %ext_libs% -noshell %ELIXIR_ERL_OPTIONS% %parsErlang% -s elixir start_cli %beforeExtra% -extra %*
erl.exe %ext_libs% -noshell %ELIXIR_ERL_OPTIONS% %parsErlang% %beforeExtra% -extra %*
)
:end
endlocal
+3 -1
View File
@@ -38,4 +38,6 @@ readlink_f () {
SELF=$(readlink_f "$0")
SCRIPT_PATH=$(dirname "$SELF")
exec "$SCRIPT_PATH"/elixir --no-halt --erl "-user Elixir.IEx.CLI" +iex "$@"
if [ "$OS" = "Windows_NT" ]; then NOSHELL="-noshell "; fi
exec "$SCRIPT_PATH"/elixir --no-halt --erl "$NOSHELL -user Elixir.IEx.CLI" +iex "$@"
+3 -4
View File
@@ -34,10 +34,9 @@ defmodule EEx do
All functions in this module accepts EEx-related options.
They are:
* `:line` - the line to be used as the template start. Defaults to 1.
* `:file` - the file to be used in the template. Defaults to the given
file the template is read from or to "nofile" when compiling
from a string.
* `:line` - the line to be used as the template start. Defaults to 1.
* `:file` - the file to be used in the template. Defaults to the given
file the template is read from or to "nofile" when compiling from a string.
* `:engine` - the EEx engine to be used for compilation.
## Engine
+1 -1
View File
@@ -201,7 +201,7 @@ defmodule Application do
`:applications` in the `.app` file in case they were not previously
started.
"""
@spec ensure_all_started(app, start_type) :: {:ok, [app]} | {:error, term}
@spec ensure_all_started(app, start_type) :: {:ok, [app]} | {:error, {app, term}}
def ensure_all_started(app, type \\ :temporary) when is_atom(app) do
:application.ensure_all_started(app, type)
end
+3 -43
View File
@@ -8,12 +8,6 @@ defprotocol Collectable do
iex> Enum.into([a: 1, b: 2], %{})
%{a: 1, b: 2}
If a collection implements both `Enumerable` and `Collectable`, both
operations can be combined with `Enum.traverse/2`:
iex> Enum.traverse(%{a: 1, b: 2}, fn {k, v} -> {k, v * 2} end)
%{a: 2, b: 4}
## Why Collectable?
The `Enumerable` protocol is useful to take values out of a collection.
@@ -27,25 +21,13 @@ defprotocol Collectable do
shape where just the range limits are stored.
The `Collectable` module was designed to fill the gap left by the
`Enumerable` protocol. It provides two functions: `into/1` and `empty/1`.
`into/1` can be seen as the opposite of `Enumerable.reduce/3`. If
`Enumerable` is about taking values out, `Collectable.into/1` is about
collecting those values into a structure.
`empty/1` receives a collectable and returns an empty version of the
same collectable. By combining the enumerable functionality with `into/1`
and `empty/1`, one can, for example, implement a traversal mechanism.
`Enumerable` protocol. `into/1` can be seen as the opposite of
`Enumerable.reduce/3`. If `Enumerable` is about taking values out,
`Collectable.into/1` is about collecting those values into a structure.
"""
@type command :: {:cont, term} | :done | :halt
@doc """
Receives a collectable structure and returns an empty one.
"""
@spec empty(t) :: t
def empty(collectable)
@doc """
Returns a function that collects values alongside
the initial accumulation value.
@@ -65,10 +47,6 @@ defprotocol Collectable do
end
defimpl Collectable, for: List do
def empty(_list) do
[]
end
def into(original) do
{[], fn
list, {:cont, x} -> [x|list]
@@ -79,10 +57,6 @@ defimpl Collectable, for: List do
end
defimpl Collectable, for: BitString do
def empty(_bitstring) do
""
end
def into(original) do
{original, fn
acc, {:cont, x} when is_bitstring(x) -> [acc|x]
@@ -92,21 +66,7 @@ defimpl Collectable, for: BitString do
end
end
defimpl Collectable, for: Function do
def empty(function) do
function
end
def into(function) do
{function, function}
end
end
defimpl Collectable, for: Map do
def empty(_map) do
%{}
end
def into(original) do
{original, fn
map, {:cont, {k, v}} -> :maps.put(k, v, map)
+4 -25
View File
@@ -341,7 +341,7 @@ defmodule Enum do
{_, {acc, {buffer, i}}} =
Enumerable.reduce(coll, {:cont, {[], {[], 0}}}, R.chunk(n, step, limit))
if nil?(pad) || i == 0 do
if is_nil(pad) || i == 0 do
:lists.reverse(acc)
else
buffer = :lists.reverse(buffer) ++ take(pad, n - i)
@@ -593,11 +593,11 @@ defmodule Enum do
"""
@spec fetch(t, integer) :: {:ok, element} | :error
def fetch(collection, n) when is_list(collection) and n >= 0 do
def fetch(collection, n) when is_list(collection) and is_integer(n) and n >= 0 do
do_fetch(collection, n)
end
def fetch(collection, n) when n >= 0 do
def fetch(collection, n) when is_integer(n) and n >= 0 do
res =
Enumerable.reduce(collection, {:cont, 0}, fn(entry, acc) ->
if acc == n do
@@ -613,7 +613,7 @@ defmodule Enum do
end
end
def fetch(collection, n) when n < 0 do
def fetch(collection, n) when is_integer(n) and n < 0 do
do_fetch(reverse(collection), abs(n + 1))
end
@@ -1798,27 +1798,6 @@ defmodule Enum do
reverse(collection) |> :lists.reverse
end
@doc """
Traverses the given enumerable keeping its shape.
It also expects the enumerable to implement the `Collectable` protocol.
## Examples
iex> Enum.traverse(%{a: 1, b: 2}, fn {k, v} -> {k, v * 2} end)
%{a: 2, b: 4}
"""
@spec traverse(Enumerable.t, (term -> term)) :: Collectable.t
def traverse(collection, transform) when is_list(collection) do
:lists.map(transform, collection)
end
def traverse(collection, transform) do
into(collection, Collectable.empty(collection), transform)
end
@doc """
Enumerates the collection, removing all duplicated items.
+16 -7
View File
@@ -613,12 +613,17 @@ defmodule BadArityError do
end
defmodule UndefinedFunctionError do
defexception [module: nil, function: nil, arity: nil]
defexception [module: nil, function: nil, arity: nil, self: false]
def message(exception) do
if exception.function do
formatted = Exception.format_mfa exception.module, exception.function, exception.arity
"undefined function: #{formatted}"
def message(%{function: function, module: module, arity: arity, self: self}) do
if function do
formatted = Exception.format_mfa module, function, arity
suffix = if self or is_nil(module) or :code.is_loaded(module) do
""
else
" (module #{inspect module} is not available)"
end
"undefined function: #{formatted}" <> suffix
else
"undefined function"
end
@@ -772,8 +777,9 @@ defmodule ErlangError do
end
def normalize(:undef, stacktrace) do
{mod, fun, arity} = from_stacktrace(stacktrace || :erlang.get_stacktrace)
%UndefinedFunctionError{module: mod, function: fun, arity: arity}
stacktrace = stacktrace || :erlang.get_stacktrace
{mod, fun, arity} = from_stacktrace(stacktrace)
%UndefinedFunctionError{module: mod, function: fun, arity: arity, self: from_self(stacktrace)}
end
def normalize(:function_clause, stacktrace) do
@@ -800,4 +806,7 @@ defmodule ErlangError do
defp from_stacktrace(_) do
{nil, nil, nil}
end
defp from_self([{module, _, _, _}, {module, _, _, _}|_]), do: true
defp from_self(_), do: false
end
+33 -17
View File
@@ -75,6 +75,12 @@ defmodule File do
@type posix :: :file.posix()
@type io_device :: :file.io_device()
@type stat_options :: [time: :local | :universal | :posix]
@type mode :: :append | :binary | :compressed | :delayed_write | :exclusive |
:raw | :read | :read_ahead | :sync | :write |
{:encoding , :latin1 | :unicode | :utf16 | :utf32 | :utf8 |
{:utf16, :big | :little} | {:utf32, :big | :little}} |
{:read_ahead, pos_integer} |
{:delayed_write, non_neg_integer, non_neg_integer}
@doc """
Returns `true` if the path is a regular file.
@@ -126,12 +132,12 @@ defmodule File do
Typical error reasons are:
* `:eacces` - missing search or write permissions for the parent
directories of `path`
directories of `path`
* `:eexist` - there is already a file or directory named `path`
* `:enoent` - a component of `path` does not exist
* `:enospc` - there is a no space left on the device
* `:enotdir` - a component of `path` is not a directory;
on some platforms, `:enoent` is returned instead
on some platforms, `:enoent` is returned instead
"""
@spec mkdir(Path.t) :: :ok | {:error, posix}
def mkdir(path) do
@@ -612,7 +618,7 @@ defmodule File do
Check `File.open/2` for other available options.
"""
@spec write(Path.t, iodata, list) :: :ok | {:error, posix}
@spec write(Path.t, iodata, [mode]) :: :ok | {:error, posix}
def write(path, content, modes \\ []) do
F.write_file(IO.chardata_to_string(path), content, modes)
end
@@ -620,7 +626,7 @@ defmodule File do
@doc """
Same as `write/3` but raises an exception if it fails, returns `:ok` otherwise.
"""
@spec write!(Path.t, iodata, list) :: :ok | no_return
@spec write!(Path.t, iodata, [mode]) :: :ok | no_return
def write!(path, content, modes \\ []) do
path = IO.chardata_to_string(path)
case F.write_file(path, content, modes) do
@@ -902,7 +908,8 @@ defmodule File do
File.close(file)
"""
@spec open(Path.t, list) :: {:ok, io_device} | {:error, posix}
@spec open(Path.t, [mode | :ram]) :: {:ok, io_device} | {:error, posix}
@spec open(Path.t, (io_device -> res)) :: {:ok, res} | {:error, posix} when res: var
def open(path, modes \\ [])
def open(path, modes) when is_list(modes) do
@@ -935,7 +942,7 @@ defmodule File do
end)
"""
@spec open(Path.t, list, (io_device -> res)) :: {:ok, res} | {:error, posix} when res: var
@spec open(Path.t, [mode | :ram], (io_device -> res)) :: {:ok, res} | {:error, posix} when res: var
def open(path, modes, function) do
case open(path, modes) do
{:ok, device} ->
@@ -953,7 +960,7 @@ defmodule File do
Returns the `io_device` otherwise.
"""
@spec open!(Path.t, list) :: io_device | no_return
@spec open!(Path.t, [mode]) :: io_device | no_return
def open!(path, modes \\ []) do
path = IO.chardata_to_string(path)
case open(path, modes) do
@@ -968,7 +975,7 @@ defmodule File do
Returns the function result otherwise.
"""
@spec open!(Path.t, list, (io_device -> res)) :: res | no_return when res: var
@spec open!(Path.t, [mode | :ram], (io_device -> res)) :: res | no_return when res: var
def open!(path, modes, function) do
path = IO.chardata_to_string(path)
case open(path, modes, function) do
@@ -989,18 +996,27 @@ defmodule File do
@spec cwd() :: {:ok, binary} | {:error, posix}
def cwd() do
case F.get_cwd do
{:ok, base} -> {:ok, IO.chardata_to_string(base)}
{:ok, base} -> {:ok, IO.chardata_to_string(fix_drive_letter(base))}
{:error, _} = error -> error
end
end
defp fix_drive_letter([l, ?:, ?/ | rest] = original) when l in ?A..?Z do
case :os.type() do
{:win32, _} -> [l+?a-?A, ?:, ?/ | rest]
_ -> original
end
end
defp fix_drive_letter(original), do: original
@doc """
The same as `cwd/0`, but raises an exception if it fails.
"""
@spec cwd!() :: binary | no_return
def cwd!() do
case F.get_cwd do
{:ok, cwd} -> IO.chardata_to_string(cwd)
case cwd() do
{:ok, cwd} -> cwd
{:error, reason} ->
raise File.Error, reason: reason, action: "get current working directory"
end
@@ -1124,7 +1140,7 @@ defmodule File do
Returns `:ok` on success, or `{:error, reason}`
on failure.
"""
@spec chmod(Path.t, integer) :: :ok | {:error, posix}
@spec chmod(Path.t, non_neg_integer) :: :ok | {:error, posix}
def chmod(path, mode) do
F.change_mode(IO.chardata_to_string(path), mode)
end
@@ -1132,7 +1148,7 @@ defmodule File do
@doc """
Same as `chmod/2`, but raises an exception in case of failure. Otherwise `:ok`.
"""
@spec chmod!(Path.t, integer) :: :ok | no_return
@spec chmod!(Path.t, non_neg_integer) :: :ok | no_return
def chmod!(path, mode) do
path = IO.chardata_to_string(path)
case chmod(path, mode) do
@@ -1147,7 +1163,7 @@ defmodule File do
for a given `file`. Returns `:ok` on success, or
`{:error, reason}` on failure.
"""
@spec chgrp(Path.t, integer) :: :ok | {:error, posix}
@spec chgrp(Path.t, non_neg_integer) :: :ok | {:error, posix}
def chgrp(path, gid) do
F.change_group(IO.chardata_to_string(path), gid)
end
@@ -1155,7 +1171,7 @@ defmodule File do
@doc """
Same as `chgrp/2`, but raises an exception in case of failure. Otherwise `:ok`.
"""
@spec chgrp!(Path.t, integer) :: :ok | no_return
@spec chgrp!(Path.t, non_neg_integer) :: :ok | no_return
def chgrp!(path, gid) do
path = IO.chardata_to_string(path)
case chgrp(path, gid) do
@@ -1170,7 +1186,7 @@ defmodule File do
for a given `file`. Returns `:ok` on success,
or `{:error, reason}` on failure.
"""
@spec chown(Path.t, integer) :: :ok | {:error, posix}
@spec chown(Path.t, non_neg_integer) :: :ok | {:error, posix}
def chown(path, uid) do
F.change_owner(IO.chardata_to_string(path), uid)
end
@@ -1178,7 +1194,7 @@ defmodule File do
@doc """
Same as `chown/2`, but raises an exception in case of failure. Otherwise `:ok`.
"""
@spec chown!(Path.t, integer) :: :ok | no_return
@spec chown!(Path.t, non_neg_integer) :: :ok | no_return
def chown!(path, uid) do
path = IO.chardata_to_string(path)
case chown(path, uid) do
File diff suppressed because it is too large Load Diff
+173
View File
@@ -0,0 +1,173 @@
defmodule GenEvent.Stream do
@moduledoc """
Defines a `GenEvent` stream.
This is a struct returned by `stream/2`. The struct is public and
contains the following fields:
* `:manager` - the manager reference given to `GenEvent.stream/2`
* `:timeout` - the timeout in between events, defaults to `:infinity`
"""
defstruct manager: nil, timeout: :infinity
@type t :: %__MODULE__{
manager: GenEvent.manager,
timeout: timeout}
@doc false
def init({_pid, _ref} = state) do
{:ok, state}
end
@doc false
def handle_event(event, _state) do
exit({:bad_event, event})
end
@doc false
def handle_call(msg, _state) do
exit({:bad_call, msg})
end
@doc false
def handle_info(_msg, state) do
{:ok, state}
end
@doc false
def terminate(_reason, _state) do
:ok
end
@doc false
def code_change(_old, state, _extra) do
{:ok, state}
end
end
defimpl Enumerable, for: GenEvent.Stream do
def reduce(stream, acc, fun) do
start_fun = fn() -> start(stream) end
next_fun = &next(stream, &1)
stop_fun = &stop(stream, &1)
Stream.resource(start_fun, next_fun, stop_fun).(acc, wrap_reducer(fun))
end
def count(_stream) do
{:error, __MODULE__}
end
def member?(_stream, _item) do
{:error, __MODULE__}
end
defp wrap_reducer(fun) do
fn
{:ack, manager, ref, event}, acc ->
send manager, {ref, :ok}
fun.(event, acc)
{:async, _manager, _ref, event}, acc ->
fun.(event, acc)
{:sync, manager, ref, event}, acc ->
try do
fun.(event, acc)
after
send manager, {ref, :ok}
end
end
end
defp start(%{manager: manager} = stream) do
try do
{:ok, {pid, ref}} = :gen.call(manager, self(), {:add_process_handler, self(), self()}, :infinity)
mon_ref = Process.monitor(pid)
{pid, ref, mon_ref}
catch
:exit, reason -> exit({reason, {__MODULE__, :start, [stream]}})
end
end
defp next(%{timeout: timeout} = stream, {pid, ref, mon_ref} = acc) do
self = self()
receive do
# The handler was removed. Stop iteration, resolve the
# event later. We need to demonitor now, otherwise DOWN
# appears with higher priority in the shutdown process.
{:gen_event_EXIT, ^self, _reason} = event ->
Process.demonitor(mon_ref, [:flush])
send(self, event)
{:halt, {:removed, acc}}
# The manager died. Stop iteration, resolve the event later.
{:DOWN, ^mon_ref, _, _, _} = event ->
send(self, event)
{:halt, {:removed, acc}}
# Got an async event.
{_from, {^pid, ^ref}, {:notify, event}} ->
{[{:async, pid, ref, event}], acc}
# Got a sync event.
{_from, {^pid, ^ref}, {:sync_notify, event}} ->
{[{:sync, pid, ref, event}], acc}
# Got an ack event.
{_from, {^pid, ^ref}, {:ack_notify, event}} ->
{[{:ack, pid, ref, event}], acc}
after
timeout ->
exit({:timeout, {__MODULE__, :next, [stream, acc]}})
end
end
# If we reach this branch, we know the handler was already
# removed, so we don't trigger a request for doing so.
defp stop(stream, {:removed, {pid, ref, mon_ref} = acc}) do
case wait_for_handler_removal(pid, ref, mon_ref) do
:ok ->
flush_events(ref)
{:error, reason} ->
exit({reason, {__MODULE__, :stop, [stream, acc]}})
end
end
# If we reach this branch, the handler was not removed yet,
# so we trigger a request for doing so.
defp stop(stream, {pid, _, _} = acc) do
parent = self()
_ = Task.start(fn -> GenEvent.remove_handler(pid, parent, :shutdown) end)
stop(stream, {:removed, acc})
end
defp wait_for_handler_removal(pid, ref, mon_ref) do
self = self()
receive do
{_from, {^pid, ^ref}, {notify, _event}} when notify in [:ack_notify, :sync_notify] ->
send pid, {ref, :done}
wait_for_handler_removal(pid, ref, mon_ref)
{:gen_event_EXIT, ^self, reason}
when reason == :normal
when reason == :shutdown
when tuple_size(reason) == 3 and elem(reason, 0) == :swapped ->
Process.demonitor(mon_ref, [:flush])
:ok
{:gen_event_EXIT, ^self, reason} ->
Process.demonitor(mon_ref, [:flush])
{:error, reason}
{:DOWN, ^mon_ref, _, _, reason} ->
{:error, reason}
end
end
defp flush_events(ref) do
receive do
{_from, {_pid, ^ref}, {:notify, _event}} ->
flush_events(ref)
after
0 -> :ok
end
end
end
+57 -7
View File
@@ -349,18 +349,57 @@ defmodule GenServer do
"""
@spec call(server, term, timeout) :: term
def call(server, request, timeout \\ 5000) do
:gen_server.call(server, request, timeout)
try do
:gen.call(server, :"$gen_call", request, timeout)
catch
:exit, reason ->
exit({reason, {__MODULE__, :call, [server, request, timeout]}})
else
{:ok, res} -> res
end
end
@doc """
Sends an asynchronous request to the `server`.
This function returns `:ok` immediately, regardless of whether the
destination node or server does exists. `handle_cast/2` will be called on the
server to handle the request.
This function returns `:ok` immediately, regardless of
whether the destination node or server does exists, unless
the server is specified as an atom.
`handle_cast/2` will be called on the server to handle
the request. In case the server is a node which is not
yet connected to the caller one, the call is going to
block until a connection happens. This is different than
the behaviour in OTP's `:gen_server` where the message
would be sent by another process, which could cause
messages to arrive out of order.
"""
@spec cast(server, term) :: :ok
defdelegate cast(server, request), to: :gen_server
def cast(server, request)
def cast({:global, name}, request) do
try do
:global.send(name, cast_msg(request))
:ok
catch
_, _ -> :ok
end
end
def cast({:via, mod, name}, request) do
try do
mod.send(name, cast_msg(request))
:ok
catch
_, _ -> :ok
end
end
def cast({name, node}, request) when is_atom(name) and is_atom(node),
do: do_send({name, node}, cast_msg(request))
def cast(dest, request) when is_atom(dest) or is_pid(dest),
do: do_send(dest, cast_msg(request))
@doc """
Casts all servers locally registered as `name` at the specified nodes.
@@ -371,8 +410,19 @@ defmodule GenServer do
See `multi_call/4` for more information.
"""
@spec abcast([node], name :: atom, term) :: :abcast
def abcast(nodes \\ nodes(), name, request) do
:gen_server.abcast(nodes, name, request)
def abcast(nodes \\ nodes(), name, request) when is_list(nodes) and is_atom(name) do
msg = cast_msg(request)
_ = for node <- nodes, do: do_send({name, node}, msg)
:abcast
end
defp cast_msg(req) do
{:"$gen_cast", req}
end
defp do_send(dest, msg) do
send(dest, msg)
:ok
end
@doc """
+22 -21
View File
@@ -271,25 +271,6 @@ defimpl Inspect, for: BitString do
end
defimpl Inspect, for: List do
@doc ~S"""
Represents a list, checking if it can be printed or not.
If so, a single-quoted representation is returned,
otherwise the brackets syntax is used. Keywords are
printed in keywords syntax.
## Examples
iex> inspect('bar')
"'bar'"
iex> inspect([0|'bar'])
"[0, 98, 97, 114]"
iex> inspect([:foo,:bar])
"[:foo, :bar]"
"""
def inspect([], _opts), do: "[]"
def inspect(thing, %Inspect.Opts{char_lists: lists} = opts) do
@@ -377,8 +358,28 @@ defimpl Inspect, for: Map do
end
defimpl Inspect, for: Integer do
def inspect(thing, _opts) do
Integer.to_string(thing)
def inspect(thing, %Inspect.Opts{base: base}) do
Integer.to_string(thing, base_to_value(base))
|> prepend_prefix(base)
end
defp base_to_value(base) do
case base do
:binary -> 2
:decimal -> 10
:octal -> 8
:hex -> 16
end
end
defp prepend_prefix(value, :decimal), do: value
defp prepend_prefix(value, base) do
prefix = case base do
:binary -> "0b"
:octal -> "0o"
:hex -> "0x"
end
prefix <> value
end
end
+10 -46
View File
@@ -31,6 +31,10 @@ defmodule Inspect.Opts do
* `:width` - defaults to the 80 characters, used when pretty is true or
when printing to IO devices.
* `:base` - print integers as :binary, :octal, :decimal, or :hex, defaults
to :decimal
"""
defstruct structs: true,
@@ -38,6 +42,7 @@ defmodule Inspect.Opts do
char_lists: :infer,
limit: 50,
width: 80,
base: :decimal,
pretty: false
@type t :: %__MODULE__{
@@ -46,6 +51,7 @@ defmodule Inspect.Opts do
char_lists: :infer | :as_lists | :as_char_lists,
limit: pos_integer,
width: pos_integer | :infinity,
base: :decimal | :binary | :hex | :octal,
pretty: boolean}
end
@@ -428,24 +434,17 @@ defmodule Inspect.Algebra do
...> %Inspect.Opts{limit: 3}, fn i, _opts -> to_string(i) end, "!")
iex> Inspect.Algebra.format(doc, 20) |> IO.iodata_to_binary
"[1! 2! 3! ...]"
"""
@spec surround_many(binary, [any], binary, integer | :infinity, (term -> t), binary) :: t
def surround_many(left, docs, right, opts, fun, separator \\ @surround_separator) do
if is_integer(opts) do
IO.write :stderr, "Inspect.Algebra.surround_many/6 with an integer limit is deprecated, " <>
"please pass Inspect.Opts instead\n#{Exception.format_stacktrace}"
old_surround_many(left, docs, right, opts, fun, separator)
else
do_surround_many(left, docs, right, opts.limit, opts, fun, separator)
end
do_surround_many(left, docs, right, opts.limit, opts, fun, separator)
end
def do_surround_many(left, [], right, _, _opts, _fun, _) do
defp do_surround_many(left, [], right, _, _opts, _fun, _) do
concat(left, right)
end
def do_surround_many(left, docs, right, limit, _opts, fun, sep) do
defp do_surround_many(left, docs, right, limit, _opts, fun, sep) do
surround(left, do_surround_many(docs, limit, _opts, fun, sep), right)
end
@@ -473,36 +472,6 @@ defmodule Inspect.Algebra do
)
end
def old_surround_many(left, [], right, _, _fun, _) do
concat(left, right)
end
def old_surround_many(left, docs, right, limit, fun, sep) do
surround(left, old_surround_many(docs, limit, fun, sep), right)
end
defp old_surround_many(_, 0, _fun, _sep) do
"..."
end
defp old_surround_many([h], _limit, fun, _sep) do
fun.(h)
end
defp old_surround_many([h|t], limit, fun, sep) when is_list(t) do
glue(
concat(fun.(h), sep),
old_surround_many(t, decrement(limit), fun, sep)
)
end
defp old_surround_many([h|t], _limit, fun, _sep) do
glue(
concat(fun.(h), @tail_separator),
fun.(t)
)
end
defp decrement(:infinity), do: :infinity
defp decrement(counter), do: counter - 1
@@ -513,16 +482,11 @@ defmodule Inspect.Algebra do
and returns an IO data representation of the best layout for the
document to fit in the given width.
"""
@spec format(t, non_neg_integer | :infinity) :: binary
@spec format(t, non_neg_integer | :infinity) :: iodata
def format(d, w) do
format(w, 0, [{0, default_mode(w), doc_group(d)}])
end
@doc false
def pretty(d, w) do
format(d, w) |> IO.iodata_to_binary
end
defp default_mode(:infinity), do: :flat
defp default_mode(_), do: :break
+8 -6
View File
@@ -9,9 +9,10 @@ defmodule Integer do
Determines if an integer is odd.
Returns `true` if `n` is an odd number, otherwise `false`.
Implemented as a macro so it is allowed in guard clauses.
Allowed in guard clauses.
"""
defmacro odd?(n) do
defmacro is_odd(n) do
quote do: (unquote(n) &&& 1) == 1
end
@@ -19,9 +20,10 @@ defmodule Integer do
Determines if an integer is even.
Returns `true` if `n` is an even number, otherwise `false`.
Implemented as a macro so it is allowed in guard clauses.
Allowed in guard clauses.
"""
defmacro even?(n) do
defmacro is_even(n) do
quote do: (unquote(n) &&& 1) == 0
end
@@ -99,7 +101,7 @@ defmodule Integer do
"64"
"""
@spec to_string(integer, pos_integer) :: String.t
@spec to_string(integer, 2..36) :: String.t
def to_string(some_integer, base) do
:erlang.integer_to_binary(some_integer, base)
end
@@ -132,7 +134,7 @@ defmodule Integer do
'3FF'
"""
@spec to_char_list(integer, pos_integer) :: char_list
@spec to_char_list(integer, 2..36) :: char_list
def to_char_list(number, base) do
:erlang.integer_to_list(number, base)
end
+4 -4
View File
@@ -394,11 +394,11 @@ defmodule IO do
def each_stream(device, what) do
case read(device, what) do
:eof ->
nil
{:halt, device}
{:error, reason} ->
raise IO.StreamError, reason: reason
data ->
{data, device}
{[data], device}
end
end
@@ -406,11 +406,11 @@ defmodule IO do
def each_binstream(device, what) do
case binread(device, what) do
:eof ->
nil
{:halt, device}
{:error, reason} ->
raise IO.StreamError, reason: reason
data ->
{data, device}
{[data], device}
end
end
+5 -73
View File
@@ -7,10 +7,6 @@ defmodule IO.ANSI.Sequence do
"\e[#{unquote(code)}#{unquote(terminator)}"
end
defp escape_sequence(unquote(Atom.to_char_list(name))) do
unquote(name)()
end
defp format_sequence(unquote(name)) do
unquote(name)()
end
@@ -32,25 +28,17 @@ defmodule IO.ANSI do
@typep ansilist :: maybe_improper_list(char() | ansicode() | binary() | ansilist(), binary() | ansicode() | [])
@type ansidata :: ansilist() | ansicode() | binary()
@doc false
def terminal?(device \\ :erlang.group_leader) do
!match?({:win32, _}, :os.type()) and
match?({:ok, _}, :io.columns(device))
end
@doc """
Checks if ANSI coloring is supported and enabled on this machine.
By default, ANSI is only enabled on UNIX machines. Enabling or
disabling ANSI escapes can be done by configuring the
`:ansi_enabled` value in the `:elixir` application.
This function simply reads the configuration value for
`:ansi_enabled` in the `:elixir` application. The value is by
default false unless Elixir can detect during startup that
both `stdout` and `stderr` are terminals.
"""
@spec enabled? :: boolean
def enabled? do
case Application.fetch_env(:elixir, :ansi_enabled) do
{:ok, boolean} when is_boolean(boolean) -> boolean
:error -> !match?({:win32, _}, :os.type())
end
Application.get_env(:elixir, :ansi_enabled, false)
end
@doc "Resets all attributes"
@@ -144,10 +132,6 @@ defmodule IO.ANSI do
@doc "Clear screen"
defsequence :clear, "2", "J"
defp escape_sequence(other) do
raise ArgumentError, "invalid ANSI sequence specification: #{other}"
end
defp format_sequence(other) do
raise ArgumentError, "invalid ANSI sequence specification: #{other}"
end
@@ -222,56 +206,4 @@ defmodule IO.ANSI do
defp do_format([], [], acc, _emit, _append_reset) do
acc
end
@doc false
def escape(string, emit \\ terminal?) when is_binary(string) and is_boolean(emit) do
{rendered, emitted} = do_escape(string, emit, false, nil, [])
if emitted do
rendered <> reset
else
rendered
end
end
@doc false
def escape_fragment(string, emit \\ terminal?) when is_binary(string) and is_boolean(emit) do
{escaped, _emitted} = do_escape(string, emit, false, nil, [])
escaped
end
defp do_escape(<<?}, t :: binary>>, emit, emitted, buffer, acc) when is_list(buffer) do
sequences =
buffer
|> Enum.reverse()
|> :string.tokens(',')
|> Enum.map(&(&1 |> :string.strip |> escape_sequence))
|> Enum.reverse()
if emit and sequences != [] do
do_escape(t, emit, true, nil, sequences ++ acc)
else
do_escape(t, emit, emitted, nil, acc)
end
end
defp do_escape(<<h, t :: binary>>, emit, emitted, buffer, acc) when is_list(buffer) do
do_escape(t, emit, emitted, [h|buffer], acc)
end
defp do_escape(<<>>, _emit, _emitted, buffer, _acc) when is_list(buffer) do
buffer = IO.iodata_to_binary Enum.reverse(buffer)
raise ArgumentError, "missing } for escape fragment #{buffer}"
end
defp do_escape(<<?%, ?{, t :: binary>>, emit, emitted, nil, acc) do
do_escape(t, emit, emitted, [], acc)
end
defp do_escape(<<h, t :: binary>>, emit, emitted, nil, acc) do
do_escape(t, emit, emitted, nil, [h|acc])
end
defp do_escape(<<>>, _emit, emitted, nil, acc) do
{IO.iodata_to_binary(Enum.reverse(acc)), emitted}
end
end
+95 -107
View File
@@ -59,56 +59,63 @@ defmodule IO.ANSI.Docs do
doc
|> String.split(["\r\n","\n"], trim: false)
|> Enum.map(&String.rstrip/1)
|> process("", options)
|> process([], "", options)
end
defp process([], _indent, _options), do: nil
defp process([], text, indent, options) do
write_text(text, indent, options)
end
defp process(["# " <> heading | rest], _indent, options) do
defp process(["# " <> heading | rest], text, indent, options) do
write_text(text, indent, options)
write_h1(String.strip(heading), options)
process(rest, "", options)
process(rest, [], "", options)
end
defp process(["## " <> heading | rest], _indent, options) do
defp process(["## " <> heading | rest], text, indent, options) do
write_text(text, indent, options)
write_h2(String.strip(heading), options)
process(rest, "", options)
process(rest, [], "", options)
end
defp process(["### " <> heading | rest], indent, options) do
defp process(["### " <> heading | rest], text, indent, options) do
write_text(text, indent, options)
write_h3(String.strip(heading), indent, options)
process(rest, indent, options)
process(rest, [], "", options)
end
defp process(["" | rest], indent, options) do
process(rest, indent, options)
defp process(["" | rest], text, indent, options) do
write_text(text, indent, options)
process(rest, [], indent, options)
end
defp process([" " <> line | rest], indent, options) do
defp process([" " <> line | rest], text, indent, options) do
write_text(text, indent, options)
process_code(rest, [line], indent, options)
end
defp process(all=[line | rest], indent, options) do
{stripped, count} = strip_spaces(line, 0)
if is_table_line?(stripped) && length(rest) > 0 && is_table_line?(hd(rest)) do
defp process(all=[line | rest], text, indent, options) do
{stripped, count} = strip_spaces(line, 0, :infinity)
if is_table_line?(stripped) and rest != [] and is_table_line?(hd(rest)) do
write_text(text, indent, options)
process_table(all, indent, options)
else
case stripped do
<<bullet, ?\s, item :: binary >> when bullet in @bullets ->
process_list(item, rest, count, indent, options)
<<bullet, ?\s, item :: binary>> when bullet in @bullets ->
write_text(text, indent, options)
process_list("• ", item, rest, count, indent, options)
<<d1, ?., ?\s, item :: binary>> when d1 in ?0..?9 ->
write_text(text, indent, options)
process_list(<<d1, ?., ?\s>>, item, rest, count, indent, options)
<<d1, d2, ?., ?\s, item :: binary>> when d1 in ?0..?9 and d2 in ?0..?9 ->
write_text(text, indent, options)
process_list(<<d1, d2, ?., ?\s>>, item, rest, count, indent, options)
_ ->
process_text(rest, [line], indent, false, options)
process(rest, [stripped | text], indent, options)
end
end
end
defp strip_spaces(" " <> line, acc) do
strip_spaces(line, acc + 1)
end
defp strip_spaces(rest, acc) do
{rest, acc}
end
## Headings
defp write_h1(heading, options) do
@@ -128,82 +135,70 @@ defmodule IO.ANSI.Docs do
## Lists
defp process_list(line, rest, count, indent, options) do
IO.write indent <> "• "
{contents, rest, done} = process_list_next(rest, count, false, [])
process_text(contents, [line], indent <> " ", true, options)
if done, do: IO.puts(IO.ANSI.reset)
process(rest, indent, options)
defp process_list(entry, line, rest, count, indent, options) do
# The first list always win some extra padding
if indent == "", do: entry = " " <> entry
new_indent = indent <> String.duplicate(" ", String.length(entry))
{contents, rest, done} = process_list_next(rest, count, byte_size(new_indent), [])
process(contents, [indent <> entry <> line, :no_wrap], new_indent, options)
if done, do: newline_after_block()
process(rest, [], indent, options)
end
# Process the thing after a list item entry. It can be either:
#
# * Continuation of the list
# * A nested list
# * The end of the list
#
defp process_list_next([" " <> _ = line | rest], count, _done, acc) do
case list_next(line, count) do
:done -> {Enum.reverse(acc), [line|rest], false}
chopped -> process_list_next(rest, count, false, [chopped|acc])
defp process_list_next([line | rest], count, max, acc) do
{stripped, next_count} = strip_spaces(line, 0, max)
case process_list_next_kind(stripped, rest, count, next_count) do
:next -> process_list_next(rest, count, max, [stripped | acc])
:done -> {Enum.reverse(acc), [line | rest], true}
:list -> {Enum.reverse(acc), [line | rest], false}
end
end
defp process_list_next([<<bullet, ?\s, _ :: binary>> | _] = rest, _count, _done, acc) when bullet in @bullets do
{Enum.reverse(acc), rest, false}
defp process_list_next([], _count, _max, acc) do
{Enum.reverse(acc), [], true}
end
defp process_list_next(["" | rest], count, _done, acc) do
process_list_next(rest, count, true, [""|acc])
end
defp process_list_next(rest, _count, done, acc) do
{Enum.reverse(acc), rest, done}
end
defp list_next(<<bullet, ?\s, _ :: binary>>, 0) when bullet in @bullets, do: :done
defp list_next(line, 0), do: chop(line, 2)
defp list_next(" " <> line, acc), do: list_next(line, acc - 1)
defp list_next(line, _acc), do: line
defp chop(" " <> line, acc) when acc > 0, do: chop(line, acc - 1)
defp chop(line, _acc), do: line
## Text (paragraphs / lists)
defp process_text(doc=["" | _], para, indent, from_list, options) do
write_text(Enum.reverse(para), indent, from_list, options)
process(doc, indent, options)
end
defp process_text([], para, indent, from_list, options) do
write_text(Enum.reverse(para), indent, from_list, options)
end
defp process_text([line | rest], para, indent, true, options) do
{stripped, count} = strip_spaces(line, 0)
case stripped do
<<bullet, ?\s, item :: binary>> when bullet in @bullets ->
write_text(Enum.reverse(para), indent, true, options)
process_list(item, rest, count, indent, options)
defp process_list_next_kind(stripped, rest, count, next_count) do
case {stripped, rest} do
{<<bullet, ?\s, _ :: binary>>, _} when bullet in @bullets and next_count <= count ->
:list
{<<d1, ?., ?\s, _ :: binary>>, _} when d1 in ?0..?9 and next_count <= count ->
:list
{<<d1, d2, ?., ?\s, _ :: binary>>, _} when d1 in ?0..?9 and d2 in ?0..?9 and next_count <= count ->
:list
{"", [" " <> _ | _]} ->
:next
{"", _} ->
:done
_ ->
process_text(rest, [line | para], indent, true, options)
:next
end
end
defp process_text([line | rest], para, indent, from_list, options) do
process_text(rest, [line | para], indent, from_list, options)
## Text
defp write_text(text, indent, options) do
case Enum.reverse(text) do
[:no_wrap|rest] -> write_text(rest, indent, options, true)
rest -> write_text(rest, indent, options, false)
end
end
defp write_text(lines, indent, from_list, options) do
defp write_text([], _indent, _options, _no_wrap) do
:ok
end
defp write_text(lines, indent, options, no_wrap) do
lines
|> Enum.join(" ")
|> handle_links
|> handle_inline(nil, [], [], options)
|> String.split(~r{\s})
|> write_with_wrap(options[:width] - byte_size(indent), indent, from_list)
|> write_with_wrap(options[:width] - byte_size(indent), indent, no_wrap)
unless from_list, do: newline_after_block
unless no_wrap, do: newline_after_block()
end
## Code blocks
@@ -213,17 +208,17 @@ defmodule IO.ANSI.Docs do
end
# Blank line between code blocks
defp process_code([ "", " " <> line | rest ], code, indent, options) do
defp process_code(["", " " <> line | rest], code, indent, options) do
process_code(rest, [line, "" | code], indent, options)
end
defp process_code([ " " <> line | rest ], code, indent, options) do
defp process_code([" " <> line | rest], code, indent, options) do
process_code(rest, [line|code], indent, options)
end
defp process_code(rest, code, indent, options) do
write_code(code, indent, options)
process(rest, indent, options)
process(rest, [], indent, options)
end
defp write_code(code, indent, options) do
@@ -237,7 +232,7 @@ defmodule IO.ANSI.Docs do
{table, rest} = Enum.split_while(lines, &is_table_line?/1)
table_lines(table, options)
newline_after_block
process(rest, indent, options)
process(rest, [], indent, options)
end
defp table_lines(lines, options) do
@@ -268,12 +263,7 @@ defmodule IO.ANSI.Docs do
|> String.replace(~r/\\ \|/x, "|")
|> handle_links
|> handle_inline(nil, [], [], options)
len = col
|> String.replace(~r/\e\[\d+m/, "")
|> String.length
{col, len}
{col, length_without_escape(col, 0)}
end
defp pad_to_number_of_columns(cols, col_count),
@@ -319,15 +309,20 @@ defmodule IO.ANSI.Docs do
end
end
## Helpers
@table_line_re ~r'''
defp is_table_line?(line) do
Regex.match?(~r'''
( ^ \s{0,3} \| (?: [^|]+ \|)+ \s* $ )
|
(\s \| \s)
'''x
'''x, line)
end
defp is_table_line?(line), do: Regex.match?(@table_line_re, line)
## Helpers
defp strip_spaces(" " <> line, acc, max) when acc < max,
do: strip_spaces(line, acc + 1, max)
defp strip_spaces(rest, acc, _max),
do: {rest, acc}
defp write(style, string, options) do
IO.puts [color(style, options), string, IO.ANSI.reset]
@@ -375,7 +370,7 @@ defmodule IO.ANSI.Docs do
defp length_without_escape(rest, count) do
case String.next_grapheme(rest) do
{_, rest} -> length_without_escape(rest, count + 1)
{_, rest} -> length_without_escape(rest, count + 1)
nil -> count
end
end
@@ -388,10 +383,8 @@ defmodule IO.ANSI.Docs do
defp escape_underlines_in_link(text) do
case Regex.match?(~r{.*(https?\S*)}, text) do
true ->
Regex.replace(~r{_}, text, "\\\\_")
_ ->
text
true -> Regex.replace(~r{_}, text, "\\\\_")
_ -> text
end
end
@@ -479,11 +472,6 @@ defmodule IO.ANSI.Docs do
defp color(style, colors) do
color = colors[style]
if is_binary(color) do
IO.puts :stderr, "warning: ANSI colors as strings is deprecated, " <>
"they now must be a list of atoms, got #{inspect color} for #{inspect style}"
color = String.split(color, ",") |> Enum.map(&String.to_atom/1)
end
IO.ANSI.format_fragment(color, colors[:enabled])
end
+1 -1
View File
@@ -57,7 +57,7 @@ defmodule IO.Stream do
true -> &IO.each_binstream(&1, line_or_bytes)
false -> &IO.each_stream(&1, line_or_bytes)
end
Stream.unfold(device, next_fun).(acc, fun)
Stream.resource(fn -> device end, next_fun, &(&1)).(acc, fun)
end
def count(_stream) do
+52 -29
View File
@@ -1423,6 +1423,18 @@ defmodule Kernel do
iex> inspect("olá", binaries: :as_binaries)
"<<111, 108, 195, 161>>"
iex> inspect('bar')
"'bar'"
iex> inspect([0|'bar'])
"[0, 98, 97, 114]"
iex> inspect(100, base: :octal)
"0o144"
iex> inspect(100, base: :hex)
"0x64"
Note that the inspect protocol does not necessarily return a valid
representation of an Elixir term. In such cases, the inspected result
must start with `#`. For example, inspecting a function will return:
@@ -1631,7 +1643,7 @@ defmodule Kernel do
iex> users = [%{name: "john", age: 27}, %{name: "meg", age: 23}]
iex> all = fn :get_and_update, data, next ->
...> Enum.map(data, next) |> List.unzip() |> List.to_tuple()
...> Enum.map(data, next) |> :lists.unzip
...> end
iex> get_and_update_in(users, [all, :age], &{&1, &1 + 1})
{[27, 23], [%{name: "john", age: 28}, %{name: "meg", age: 24}]}
@@ -1869,20 +1881,20 @@ defmodule Kernel do
## Examples
iex> nil?(1)
iex> is_nil(1)
false
iex> nil?(nil)
iex> is_nil(nil)
true
"""
defmacro nil?(x) do
defmacro is_nil(x) do
quote do: unquote(x) == nil
end
@doc """
A convenient macro that checks if the right side matches
the left side. The left side is allowed to be a match pattern.
A convenience macro that checks if the right side (an expression)
matches the left side (a pattern).
## Examples
@@ -2001,7 +2013,7 @@ defmodule Kernel do
defp do_at([arg], name, function?, env) do
case function? do
true ->
raise ArgumentError, "cannot dynamically set attribute @#{name} inside function"
raise ArgumentError, "cannot set attribute @#{name} inside function/macro"
false ->
case name do
:behavior ->
@@ -2022,7 +2034,14 @@ defmodule Kernel do
case function? do
true ->
attr = Module.get_attribute(env.module, name, stack)
:erlang.element(1, :elixir_quote.escape(attr, false))
try do
:elixir_quote.escape(attr, false)
rescue
e in [ArgumentError] ->
raise ArgumentError, "cannot inject attribute @#{name} into function/macro because " <> Exception.message(e)
else
{val, _} -> val
end
false ->
escaped = case stack do
[] -> []
@@ -2360,7 +2379,7 @@ defmodule Kernel do
it is not loaded. Check `Code.ensure_loaded/1` for more
information.
"""
@spec function_exported?(atom | tuple, atom, integer) :: boolean
@spec function_exported?(atom | tuple, atom, arity) :: boolean
def function_exported?(module, function, arity) do
:erlang.function_exported(module, function, arity)
end
@@ -2889,31 +2908,35 @@ defmodule Kernel do
structure is public should use `@type`.
"""
defmacro defstruct(fields) do
fields =
quote bind_quoted: [fields: fields] do
fields = :lists.map(fn
{key, _} = pair when is_atom(key) -> pair
key when is_atom(key) -> {key, nil}
other -> raise ArgumentError, "struct field names must be atoms, got: #{inspect other}"
end, fields)
quote bind_quoted: [fields: fields] do
fields = :lists.map(fn
{key, val} when is_atom(key) ->
try do
Macro.escape(val)
rescue
e in [ArgumentError] ->
raise ArgumentError, "invalid value for struct field #{key}, " <> Exception.message(e)
else
_ -> {key, val}
end
key when is_atom(key) ->
{key, nil}
other ->
raise ArgumentError, "struct field names must be atoms, got: #{inspect other}"
end, fields)
@struct :maps.put(:__struct__, __MODULE__, :maps.from_list(fields))
@struct :maps.put(:__struct__, __MODULE__, :maps.from_list(fields))
case Module.get_attribute(__MODULE__, :derive) do
[] ->
:ok
derive ->
Protocol.__derive__(derive, __MODULE__, __ENV__)
end
case Module.get_attribute(__MODULE__, :derive) do
[] -> :ok
derive -> Protocol.__derive__(derive, __MODULE__, __ENV__)
end
@spec __struct__() :: %__MODULE__{}
def __struct__() do
@struct
end
@spec __struct__() :: %__MODULE__{}
def __struct__() do
@struct
end
quote do
unquote(fields)
fields
end
end
+1 -1
View File
@@ -408,7 +408,7 @@ defmodule Kernel.CLI do
end
defp wrapper(fun) do
fun.()
_ = fun.()
:ok
end
+44 -39
View File
@@ -18,14 +18,16 @@ defmodule Kernel.LexicalTracker do
Returns all remotes linked to in this lexical scope.
"""
def remotes(arg) do
# If the module is compiled from a function, its lexical
# scope may be long gone, so it has no associated PID.
if pid = to_pid(arg) do
ets = :gen_server.call(pid, :ets, @timeout)
:ets.match(ets, {:"$1", :_, :_}) |> List.flatten
else
[]
end
ets = :gen_server.call(to_pid(arg), :ets, @timeout)
:ets.match(ets, {:"$1", :_, :_}) |> List.flatten
end
@doc """
Gets the destination the lexical scope is meant to
compile to.
"""
def dest(arg) do
:gen_server.call(to_pid(arg), :dest, @timeout)
end
defp to_pid(pid) when is_pid(pid), do: pid
@@ -39,9 +41,8 @@ defmodule Kernel.LexicalTracker do
# Starts the tracker and returns its pid.
@doc false
def start_link do
{:ok, pid} = :gen_server.start_link(__MODULE__, [], [])
pid
def start_link(dest) do
:gen_server.start_link(__MODULE__, dest, [])
end
@doc false
@@ -96,65 +97,69 @@ defmodule Kernel.LexicalTracker do
# Callbacks
def init([]) do
{:ok, :ets.new(:lexical, [:protected])}
def init(dest) do
{:ok, {:ets.new(:lexical, [:protected]), dest}}
end
@doc false
def handle_call(:ets, _from, d) do
{:reply, d, d}
def handle_call(:ets, _from, {d, dest}) do
{:reply, d, {d, dest}}
end
def handle_call(request, _from, d) do
{:stop, {:bad_call, request}, d}
def handle_call(:dest, _from, {d, dest}) do
{:reply, dest, {d, dest}}
end
def handle_cast({:remote_dispatch, module}, d) do
def handle_call(request, _from, {d, dest}) do
{:stop, {:bad_call, request}, {d, dest}}
end
def handle_cast({:remote_dispatch, module}, {d, dest}) do
add_module(d, module)
{:noreply, d}
{:noreply, {d, dest}}
end
def handle_cast({:import_dispatch, module}, d) do
def handle_cast({:import_dispatch, module}, {d, dest}) do
add_dispatch(d, module, @import)
{:noreply, d}
{:noreply, {d, dest}}
end
def handle_cast({:alias_dispatch, module}, d) do
def handle_cast({:alias_dispatch, module}, {d, dest}) do
add_dispatch(d, module, @alias)
{:noreply, d}
{:noreply, {d, dest}}
end
def handle_cast({:add_import, module, line, warn}, d) do
def handle_cast({:add_import, module, line, warn}, {d, dest}) do
add_directive(d, module, line, warn, @import)
{:noreply, d}
{:noreply, {d, dest}}
end
def handle_cast({:add_alias, module, line, warn}, d) do
def handle_cast({:add_alias, module, line, warn}, {d, dest}) do
add_directive(d, module, line, warn, @alias)
{:noreply, d}
{:noreply, {d, dest}}
end
def handle_cast(:stop, d) do
{:stop, :normal, d}
def handle_cast(:stop, {d, dest}) do
{:stop, :normal, {d, dest}}
end
def handle_cast(msg, d) do
{:stop, {:bad_cast, msg}, d}
end
def handle_info(_msg, d) do
{:noreply, d}
def handle_cast(msg, {d, dest}) do
{:stop, {:bad_cast, msg}, {d, dest}}
end
@doc false
def terminate(_reason, _d) do
def handle_info(_msg, {d, dest}) do
{:noreply, {d, dest}}
end
@doc false
def terminate(_reason, _state) do
:ok
end
@doc false
def code_change(_old, d, _extra) do
{:ok, d}
def code_change(_old, state, _extra) do
{:ok, state}
end
# Callbacks helpers
+34 -30
View File
@@ -15,7 +15,7 @@ defmodule Kernel.ParallelCompiler do
is set to `true` and there is a warning, this function will fail
with an exception.
This function receives a set of callbacks as options:
This function accepts the following options:
* `:each_file` - for each file compiled, invokes the callback passing the
file
@@ -23,32 +23,36 @@ defmodule Kernel.ParallelCompiler do
* `:each_module` - for each module compiled, invokes the callback passing
the file, module and the module bytecode
The compiler doesn't care about the return values of the callbacks.
* `:dest` - the destination directory for the beam files. When using `files/2`,
this information is only used to properly annotate the beam files before
they are loaded into memory. If you want a file to actually be writen to
`dest`, use `files_to_path/3` instead.
Returns the modules generated by each compiled file.
"""
def files(files, callbacks \\ [])
def files(files, options \\ [])
def files(files, callbacks) when is_list(callbacks) do
spawn_compilers(files, nil, callbacks)
def files(files, options) when is_list(options) do
spawn_compilers(files, nil, options)
end
@doc """
Compiles the given files to the given path.
Read `files/2` for more information.
"""
def files_to_path(files, path, callbacks \\ [])
def files_to_path(files, path, options \\ [])
def files_to_path(files, path, callbacks) when is_binary(path) and is_list(callbacks) do
spawn_compilers(files, path, callbacks)
def files_to_path(files, path, options) when is_binary(path) and is_list(options) do
spawn_compilers(files, path, options)
end
defp spawn_compilers(files, path, callbacks) do
defp spawn_compilers(files, path, options) do
true = Code.ensure_loaded?(Kernel.ErrorHandler)
compiler_pid = self()
:elixir_code_server.cast({:reset_warnings, compiler_pid})
schedulers = max(:erlang.system_info(:schedulers_online), 2)
result = spawn_compilers(files, files, path, callbacks, [], [], schedulers, [])
result = spawn_compilers(files, files, path, options, [], [], schedulers, [])
# In case --warning-as-errors is enabled and there was a warning,
# compilation status will be set to error and we fail with CompileError
@@ -59,21 +63,21 @@ defmodule Kernel.ParallelCompiler do
end
# We already have 4 currently running, don't spawn new ones
defp spawn_compilers(entries, original, output, callbacks, waiting, queued, schedulers, result) when
defp spawn_compilers(entries, original, output, options, waiting, queued, schedulers, result) when
length(queued) - length(waiting) >= schedulers do
wait_for_messages(entries, original, output, callbacks, waiting, queued, schedulers, result)
wait_for_messages(entries, original, output, options, waiting, queued, schedulers, result)
end
# Release waiting processes
defp spawn_compilers([h|t], original, output, callbacks, waiting, queued, schedulers, result) when is_pid(h) do
defp spawn_compilers([h|t], original, output, options, waiting, queued, schedulers, result) when is_pid(h) do
{_kind, ^h, ref, _module} = List.keyfind(waiting, h, 1)
send h, {ref, :ready}
waiting = List.keydelete(waiting, h, 1)
spawn_compilers(t, original, output, callbacks, waiting, queued, schedulers, result)
spawn_compilers(t, original, output, options, waiting, queued, schedulers, result)
end
# Spawn a compiler for each file in the list until we reach the limit
defp spawn_compilers([h|t], original, output, callbacks, waiting, queued, schedulers, result) do
defp spawn_compilers([h|t], original, output, options, waiting, queued, schedulers, result) do
parent = self()
{pid, ref} =
@@ -90,7 +94,7 @@ defmodule Kernel.ParallelCompiler do
_ = if output do
:elixir_compiler.file_to_path(h, output)
else
:elixir_compiler.file(h)
:elixir_compiler.file(h, Keyword.get(options, :dest))
end
{:compiled, h}
catch
@@ -99,31 +103,31 @@ defmodule Kernel.ParallelCompiler do
end)
end
spawn_compilers(t, original, output, callbacks, waiting,
spawn_compilers(t, original, output, options, waiting,
[{pid, ref, h}|queued], schedulers, result)
end
# No more files, nothing waiting, queue is empty, we are done
defp spawn_compilers([], _original, _output, _callbacks, [], [], _schedulers, result) do
defp spawn_compilers([], _original, _output, _options, [], [], _schedulers, result) do
for {:module, mod} <- result, do: mod
end
# Queued x, waiting for x: POSSIBLE ERROR! Release processes so we get the failures
defp spawn_compilers([], original, output, callbacks, waiting, queued, schedulers, result) when length(waiting) == length(queued) do
defp spawn_compilers([], original, output, options, waiting, queued, schedulers, result) when length(waiting) == length(queued) do
Enum.each queued, fn {child, _, _} ->
{_kind, ^child, ref, _module} = List.keyfind(waiting, child, 1)
send child, {ref, :release}
end
wait_for_messages([], original, output, callbacks, waiting, queued, schedulers, result)
wait_for_messages([], original, output, options, waiting, queued, schedulers, result)
end
# No more files, but queue and waiting are not full or do not match
defp spawn_compilers([], original, output, callbacks, waiting, queued, schedulers, result) do
wait_for_messages([], original, output, callbacks, waiting, queued, schedulers, result)
defp spawn_compilers([], original, output, options, waiting, queued, schedulers, result) do
wait_for_messages([], original, output, options, waiting, queued, schedulers, result)
end
# Wait for messages from child processes
defp wait_for_messages(entries, original, output, callbacks, waiting, queued, schedulers, result) do
defp wait_for_messages(entries, original, output, options, waiting, queued, schedulers, result) do
receive do
{:struct_available, module} ->
available = for {:struct, pid, _, waiting_module} <- waiting,
@@ -131,11 +135,11 @@ defmodule Kernel.ParallelCompiler do
not pid in entries,
do: pid
spawn_compilers(available ++ entries, original, output, callbacks,
spawn_compilers(available ++ entries, original, output, options,
waiting, queued, schedulers, [{:struct, module}|result])
{:module_available, child, ref, file, module, binary} ->
if callback = Keyword.get(callbacks, :each_module) do
if callback = Keyword.get(options, :each_module) do
callback.(file, module, binary)
end
@@ -147,7 +151,7 @@ defmodule Kernel.ParallelCompiler do
not pid in entries,
do: pid
spawn_compilers(available ++ entries, original, output, callbacks,
spawn_compilers(available ++ entries, original, output, options,
waiting, queued, schedulers, [{:module, module}|result])
{:waiting, kind, child, ref, on} ->
@@ -160,10 +164,10 @@ defmodule Kernel.ParallelCompiler do
waiting = [{kind, child, ref, on}|waiting]
end
spawn_compilers(entries, original, output, callbacks, waiting, queued, schedulers, result)
spawn_compilers(entries, original, output, options, waiting, queued, schedulers, result)
{:DOWN, _down_ref, :process, down_pid, {:compiled, file}} ->
if callback = Keyword.get(callbacks, :each_file) do
if callback = Keyword.get(options, :each_file) do
callback.(file)
end
@@ -172,11 +176,11 @@ defmodule Kernel.ParallelCompiler do
new_entries = List.delete(entries, down_pid)
new_queued = List.keydelete(queued, down_pid, 0)
new_waiting = List.keydelete(waiting, down_pid, 1)
spawn_compilers(new_entries, original, output, callbacks, new_waiting, new_queued, schedulers, result)
spawn_compilers(new_entries, original, output, options, new_waiting, new_queued, schedulers, result)
{:DOWN, down_ref, :process, _down_pid, reason} ->
handle_failure(down_ref, reason, entries, waiting, queued)
wait_for_messages(entries, original, output, callbacks, waiting, queued, schedulers, result)
wait_for_messages(entries, original, output, options, waiting, queued, schedulers, result)
end
end
+4 -32
View File
@@ -343,28 +343,9 @@ defmodule Kernel.SpecialForms do
Note that `Kernel."HELLO"` will be treated as a remote call and not an alias.
This choice was done so every time single- or double-quotes are used, we have
a remote call irregardless of the quote contents. This decision is also reflected
a remote call regardless of the quote contents. This decision is also reflected
in the quoted expressions discussed below.
## Runtime (dynamic) behaviour
The result returned by `.` is always specified by the right-side:
iex> x = String
iex> x.downcase("FOO")
"foo"
iex> x.Sample
String.Sample
In case the right-side is also dynamic, `.`'s behaviour can be reproduced
at runtime via `apply/3` and `Module.concat/2`:
iex> apply(:erlang, :+, [1,2])
3
iex> Module.concat(Kernel, Sample)
Kernel.Sample
## Quoted expression
When `.` is used, the quoted expression may take two distinct
@@ -1112,7 +1093,7 @@ defmodule Kernel.SpecialForms do
## Examples
Imagine the situation you have a variable `name` and
Imagine the situation you have a variable `value` and
you want to inject it inside some quote. The first attempt
would be:
@@ -1327,22 +1308,13 @@ defmodule Kernel.SpecialForms do
On the other hand, aliases holds some properties:
1. The head element of aliases can be any term.
1. The head element of aliases can be any term that must expand to
an atom at compilation time.
2. The tail elements of aliases are guaranteed to always be atoms.
3. When the head element of aliases is the atom `:Elixir`, no expansion happen.
4. When the head element of aliases is not an atom, it is expanded at runtime:
quote do: some_var.Foo
{:__aliases__, [], [{:some_var, [], Elixir}, :Foo]}
Since `some_var` is not available at compilation time, the compiler
expands such expression to:
Module.concat [some_var, Foo]
"""
defmacro __aliases__(args)
+5 -1
View File
@@ -998,7 +998,7 @@ defmodule Kernel.Typespec do
typespec({:list, [], [spec]}, vars, caller)
end
defp typespec(list, vars, caller) do
defp typespec(list, vars, caller) when is_list(list) do
[h|t] = :lists.reverse(list)
union = :lists.foldl(fn(x, acc) ->
{:|, [], [validate_kw(x, list, caller), acc]}
@@ -1006,6 +1006,10 @@ defmodule Kernel.Typespec do
typespec({:list, [], [union]}, vars, caller)
end
defp typespec(other, _vars, caller) do
compile_error(caller, "unexpected expression in typespec: #{Macro.to_string other}")
end
## Helpers
defp compile_error(caller, desc) do
+1 -1
View File
@@ -136,7 +136,7 @@ defmodule Keyword do
:error
"""
@spec fetch(t, key) :: {:ok, value}
@spec fetch(t, key) :: {:ok, value} | :error
def fetch(keywords, key) when is_list(keywords) and is_atom(key) do
case :lists.keyfind(key, 1, keywords) do
{^key, value} -> {:ok, value}
+2 -22
View File
@@ -247,7 +247,7 @@ defmodule List do
[a: 1, b: 2, c: 3]
"""
@spec keystore([tuple], any, non_neg_integer, tuple) :: [tuple]
@spec keystore([tuple], any, non_neg_integer, tuple) :: [tuple, ...]
def keystore(list, key, position, new_tuple) do
:lists.keystore(key, position + 1, list, new_tuple)
end
@@ -324,26 +324,6 @@ defmodule List do
do_zip(list_of_lists, [])
end
@doc """
Unzips the given list of tuples into a list of lists.
The number of unzipped elements is equal to the size
of the smallest tuple in the list.
## Examples
iex> List.unzip([{1, 2}, {3, 4}])
[[1, 3], [2, 4]]
iex> List.unzip([{1, :a, "apple"}, {2, :b, "banana"}, {3, :c}])
[[1, 2, 3], [:a, :b, :c]]
"""
@spec unzip([tuple]) :: [list]
def unzip(list) when is_list(list) do
:lists.map &Tuple.to_list/1, zip(list)
end
@doc """
Returns a list with `value` inserted at the specified `index`.
Note that `index` is capped at the list length. Negative indices
@@ -532,7 +512,7 @@ defmodule List do
1023
"""
@spec to_integer(char_list, non_neg_integer) :: integer
@spec to_integer(char_list, 2..36) :: integer
def to_integer(char_list, base) do
:erlang.list_to_integer(char_list, base)
end
+38 -2
View File
@@ -17,6 +17,7 @@ defmodule Macro do
:<, :>, :->,
:+, :-, :*, :/, :=, :|, :.,
:and, :or, :when, :in,
:~>>, :<<~, :~>, :<~, :<~>, :<|>,
:<<<, :>>>, :|||, :&&&, :^^^, :~~~]
@doc false
@@ -39,7 +40,8 @@ defmodule Macro do
o when o in [:&&, :&&&, :and] -> {:left, 140}
o when o in [:==, :!=, :=~, :===, :!==] -> {:left, 150}
o when o in [:<, :<=, :>=, :>] -> {:left, 160}
o when o in [:|>, :<<<, :>>>] -> {:left, 170}
o when o in [:|>, :<<<, :>>>, :<~, :~>,
:<<~, :~>>, :<~>, :<|>] -> {:left, 170}
:in -> {:left, 180}
o when o in [:++, :--, :.., :<>] -> {:right, 200}
o when o in [:+, :-] -> {:left, 210}
@@ -305,6 +307,40 @@ defmodule Macro do
elem(:elixir_quote.escape(expr, Keyword.get(opts, :unquote, false)), 0)
end
@doc """
Validates the given expressions are valid quoted expressions.
Check the `type:Macro.t` for the specification of a valid
quoted expression.
"""
@spec validate(term) :: :ok | {:error, term}
def validate(expr) do
find_invalid(expr) || :ok
end
defp find_invalid({left, right}), do:
find_invalid(left) || find_invalid(right)
defp find_invalid({left, meta, right}) when is_list(meta) and (is_atom(right) or is_list(right)), do:
find_invalid(left) || find_invalid(right)
defp find_invalid(list) when is_list(list), do:
Enum.find_value(list, &find_invalid/1)
defp find_invalid(pid) when is_pid(pid), do: nil
defp find_invalid(atom) when is_atom(atom), do: nil
defp find_invalid(num) when is_number(num), do: nil
defp find_invalid(bin) when is_binary(bin), do: nil
defp find_invalid(fun) when is_function(fun) do
unless :erlang.fun_info(fun, :env) == {:env, []} and
:erlang.fun_info(fun, :type) == {:type, :external} do
{:error, fun}
end
end
defp find_invalid(other), do: {:error, other}
@doc ~S"""
Unescape the given chars.
@@ -903,7 +939,7 @@ defmodule Macro do
be expanded.
This function uses `expand_once/2` under the hood. Check
`expand_once/2` for more information and exmaples.
`expand_once/2` for more information and examples.
"""
def expand(tree, env) do
expand_until({tree, true}, env)
+18 -3
View File
@@ -4,7 +4,22 @@ defmodule Macro.Env do
The current environment can be accessed at any time as
`__ENV__`. Inside macros, the caller environment can be
accessed as `__CALLER__`. It contains the following fields:
accessed as `__CALLER__`.
An instance of `Macro.Env` must not be modified by hand. If you need to
create a custom environment to pass to `Code.eval_quoted/3`, use the
following trick:
def make_custom_env do
import SomeModule, only: [some_function: 2]
alias A.B.C
__ENV__
end
You may then call `make_custom_env()` to get a struct with the desired
imports and aliases included.
It contains the following fields:
* `module` - the current module name
* `file` - the current file name as a binary
@@ -105,9 +120,9 @@ defmodule Macro.Env do
"""
def stacktrace(%{__struct__: Macro.Env} = env) do
cond do
nil?(env.module) ->
is_nil(env.module) ->
[{:elixir_compiler, :__FILE__, 1, relative_location(env)}]
nil?(env.function) ->
is_nil(env.function) ->
[{env.module, :__MODULE__, 0, relative_location(env)}]
true ->
{name, arity} = env.function
+5 -15
View File
@@ -85,6 +85,9 @@ defmodule Module do
See http://www.erlang.org/doc/man/compile.html for the list of supported
options.
Several uses of `@compile` will accumulate instead of overriding
previous ones.
### Example
defmodule M do
@@ -476,19 +479,6 @@ defmodule Module do
:elixir_aliases.safe_concat([left, right])
end
@doc """
Gets an anonymous function from the given module, function
and arity. The module and function are not verified to exist.
iex> fun = Module.function(Kernel, :is_atom, 1)
iex> fun.(:hello)
true
"""
def function(mod, fun, arity) do
:erlang.make_fun(mod, fun, arity)
end
@doc """
Attaches documentation to a given function or type. It expects
the module the function/type belongs to, the line (a non negative
@@ -530,7 +520,7 @@ defmodule Module do
line,
kind,
merge_signatures(old_sign, signature, 1),
if(nil?(doc), do: old_doc, else: doc)
if(is_nil(doc), do: old_doc, else: doc)
})
:ok
end
@@ -904,7 +894,7 @@ defmodule Module do
end
@doc false
def get_attribute(module, key, warn) when is_atom(key) and (is_list(warn) or nil?(warn)) do
def get_attribute(module, key, warn) when is_atom(key) and (is_list(warn) or is_nil(warn)) do
assert_not_compiled!(:get_attribute, module)
table = data_table_for(module)
+3 -3
View File
@@ -92,8 +92,7 @@ defmodule Module.LocalsTracker do
# Starts the tracker and returns its pid.
@doc false
def start_link do
{:ok, pid} = :gen_server.start_link(__MODULE__, [], [])
pid
:gen_server.start_link(__MODULE__, [], [])
end
# Adds a definition into the tracker. A public
@@ -272,6 +271,7 @@ defmodule Module.LocalsTracker do
{:stop, {:bad_call, request}, state}
end
@doc false
def handle_info(_msg, state) do
{:noreply, state}
end
@@ -360,7 +360,7 @@ defmodule Module.LocalsTracker do
end
defp replace_edge!(d, from, to) do
unless :lists.member(to, :digraph.out_neighbours(d, from)) do
_ = unless :lists.member(to, :digraph.out_neighbours(d, from)) do
[:"$e"|_] = :digraph.add_edge(d, from, to)
end
:ok
+1 -1
View File
@@ -30,7 +30,7 @@ defmodule Node do
returns `{:error, :not_allowed}`. Returns `{:error, :not_found}` if the
local node is not alive.
"""
@spec stop() :: :ok | {:error, term}
@spec stop() :: :ok | {:error, :not_allowed | :not_found}
def stop() do
:net_kernel.stop()
end
+105 -75
View File
@@ -60,40 +60,70 @@ defmodule Path do
def absname(path, relative_to) do
path = IO.chardata_to_string(path)
case type(path) do
:relative -> join(relative_to, path)
:absolute ->
cond do
path == "/" ->
path
:binary.last(path) == ?/ ->
binary_part(path, 0, byte_size(path) - 1)
true ->
path
end
:relative -> absname_join(relative_to, path)
:absolute -> absname_join([path])
:volumerelative ->
relative_to = IO.chardata_to_string(relative_to)
absname_vr(split(path), split(relative_to), relative_to)
end
end
## Absolute path on current drive
# Absolute path on current drive
defp absname_vr(["/"|rest], [volume|_], _relative),
do: join([volume|rest])
do: absname_join([volume|rest])
## Relative to current directory on current drive.
# Relative to current directory on current drive.
defp absname_vr([<<x, ?:>>|rest], [<<x, _ :: binary>>|_], relative),
do: absname(join(rest), relative)
do: absname(absname_join(rest), relative)
## Relative to current directory on another drive.
# Relative to current directory on another drive.
defp absname_vr([<<x, ?:>>|name], _, _relative) do
cwd =
case :file.get_cwd([x, ?:]) do
{:ok, dir} -> IO.chardata_to_string(dir)
{:error, _} -> <<x, ?:, ?/>>
end
absname(join(name), cwd)
absname(absname_join(name), cwd)
end
# Joins a list
defp absname_join([name1, name2|rest]), do:
absname_join([absname_join(name1, name2)|rest])
defp absname_join([name]), do:
do_absname_join(IO.chardata_to_string(name), <<>>, [], major_os_type())
# Joins two paths
defp absname_join(left, right),
do: do_absname_join(IO.chardata_to_string(left), relative(right), [], major_os_type())
defp do_absname_join(<<uc_letter, ?:, rest :: binary>>, relativename, [], :win32) when uc_letter in ?A..?Z, do:
do_absname_join(rest, relativename, [?:, uc_letter+?a-?A], :win32)
defp do_absname_join(<<?\\, rest :: binary>>, relativename, result, :win32), do:
do_absname_join(<<?/, rest :: binary>>, relativename, result, :win32)
defp do_absname_join(<<?/, rest :: binary>>, relativename, [?., ?/|result], os_type), do:
do_absname_join(rest, relativename, [?/|result], os_type)
defp do_absname_join(<<?/, rest :: binary>>, relativename, [?/|result], os_type), do:
do_absname_join(rest, relativename, [?/|result], os_type)
defp do_absname_join(<<>>, <<>>, result, os_type), do:
IO.iodata_to_binary(reverse_maybe_remove_dirsep(result, os_type))
defp do_absname_join(<<>>, relativename, [?:|rest], :win32), do:
do_absname_join(relativename, <<>>, [?:|rest], :win32)
defp do_absname_join(<<>>, relativename, [?/|result], os_type), do:
do_absname_join(relativename, <<>>, [?/|result], os_type)
defp do_absname_join(<<>>, relativename, result, os_type), do:
do_absname_join(relativename, <<>>, [?/|result], os_type)
defp do_absname_join(<<char, rest :: binary>>, relativename, result, os_type), do:
do_absname_join(rest, relativename, [char|result], os_type)
defp reverse_maybe_remove_dirsep([?/, ?:, letter], :win32), do:
[letter, ?:, ?/]
defp reverse_maybe_remove_dirsep([?/], _), do:
[?/]
defp reverse_maybe_remove_dirsep([?/|name], _), do:
:lists.reverse(name)
defp reverse_maybe_remove_dirsep(name, _), do:
:lists.reverse(name)
@doc """
Converts the path to an absolute one and expands
any `.` and `..` characters and a leading `~`.
@@ -106,7 +136,7 @@ defmodule Path do
"""
@spec expand(t) :: binary
def expand(path) do
normalize absname(expand_home(path), System.cwd!)
expand_dot absname(expand_home(path), System.cwd!)
end
@doc """
@@ -133,7 +163,7 @@ defmodule Path do
"""
@spec expand(t, t) :: binary
def expand(path, relative_to) do
normalize absname(absname(expand_home(path), expand_home(relative_to)), System.cwd!)
expand_dot absname(absname(expand_home(path), expand_home(relative_to)), System.cwd!)
end
@doc """
@@ -157,10 +187,7 @@ defmodule Path do
"""
@spec type(t) :: :absolute | :relative | :volumerelative
def type(name) when is_list(name) or is_binary(name) do
case :os.type() do
{:win32, _} -> win32_pathtype(name)
_ -> unix_pathtype(name)
end |> elem(0)
pathtype(name, major_os_type) |> elem(0)
end
@doc """
@@ -182,10 +209,20 @@ defmodule Path do
"""
@spec relative(t) :: binary
def relative(name) do
case :os.type() do
{:win32, _} -> win32_pathtype(name)
_ -> unix_pathtype(name)
end |> elem(1) |> IO.chardata_to_string
relative(name, major_os_type())
end
defp relative(name, os_type) do
pathtype(name, os_type)
|> elem(1)
|> IO.chardata_to_string
end
defp pathtype(name, os_type) do
case os_type do
:win32 -> win32_pathtype(name)
_ -> unix_pathtype(name)
end
end
defp unix_pathtype(<<?/, relative :: binary>>), do:
@@ -390,7 +427,7 @@ defmodule Path do
end
@doc """
Returns a string with one or more path components joined by the path separator.
Joins a list of strings.
This function should be used to convert a list of strings to a path.
Note that any trailing slash is removed on join.
@@ -411,11 +448,14 @@ defmodule Path do
def join([name1, name2|rest]), do:
join([join(name1, name2)|rest])
def join([name]), do:
do_join(IO.chardata_to_string(name), <<>>, [], major_os_type())
name
@doc """
Joins two paths.
The right path will always be expanded to its relative format
and any trailing slash is removed on join.
## Examples
iex> Path.join("foo", "bar")
@@ -423,40 +463,25 @@ defmodule Path do
"""
@spec join(t, t) :: binary
def join(left, right),
do: do_join(IO.chardata_to_string(left), relative(right), [], major_os_type())
defp major_os_type do
:os.type |> elem(0)
def join(left, right) do
left = IO.chardata_to_string(left)
os_type = major_os_type()
do_join(left, right, os_type) |> remove_dirsep(os_type)
end
defp do_join(<<uc_letter, ?:, rest :: binary>>, relativename, [], :win32) when uc_letter in ?A..?Z, do:
do_join(rest, relativename, [?:, uc_letter+?a-?A], :win32)
defp do_join(<<?\\, rest :: binary>>, relativename, result, :win32), do:
do_join(<<?/, rest :: binary>>, relativename, result, :win32)
defp do_join(<<?/, rest :: binary>>, relativename, [?., ?/|result], os_type), do:
do_join(rest, relativename, [?/|result], os_type)
defp do_join(<<?/, rest :: binary>>, relativename, [?/|result], os_type), do:
do_join(rest, relativename, [?/|result], os_type)
defp do_join(<<>>, <<>>, result, os_type), do:
IO.iodata_to_binary(maybe_remove_dirsep(result, os_type))
defp do_join(<<>>, relativename, [?:|rest], :win32), do:
do_join(relativename, <<>>, [?:|rest], :win32)
defp do_join(<<>>, relativename, [?/|result], os_type), do:
do_join(relativename, <<>>, [?/|result], os_type)
defp do_join(<<>>, relativename, result, os_type), do:
do_join(relativename, <<>>, [?/|result], os_type)
defp do_join(<<char, rest :: binary>>, relativename, result, os_type), do:
do_join(rest, relativename, [char|result], os_type)
defp do_join("", right, os_type), do: relative(right, os_type)
defp do_join(left, "", _os_type), do: left
defp do_join(left, right, os_type), do: remove_dirsep(left, os_type) <> "/" <> relative(right, os_type)
defp maybe_remove_dirsep([?/, ?:, letter], :win32), do:
[letter, ?:, ?/]
defp maybe_remove_dirsep([?/], _), do:
[?/]
defp maybe_remove_dirsep([?/|name], _), do:
:lists.reverse(name)
defp maybe_remove_dirsep(name, _), do:
:lists.reverse(name)
defp remove_dirsep("", _os_type), do: ""
defp remove_dirsep(bin, os_type) do
last = :binary.last(bin)
if last == ?/ or (last == ?\\ and os_type == :win32) do
binary_part(bin, 0, byte_size(bin) - 1)
else
bin
end
end
@doc ~S"""
Splits the path into a list at the path separator.
@@ -568,7 +593,7 @@ defmodule Path do
|> Enum.map(&IO.chardata_to_string/1)
end
# Normalize the given path by expanding "..", "." and "~".
# expand_dot the given path by expanding "..", "." and "~".
defp chardata_to_list(chardata) do
case :unicode.characters_to_list(chardata) do
@@ -602,29 +627,34 @@ defmodule Path do
end
end
defp normalize(path), do: normalize(split(path), [])
defp expand_dot(<<"/../", rest::binary>>),
do: expand_dot("/" <> rest)
defp expand_dot(<<letter, ":/../", rest::binary>>) when letter in ?a..?z,
do: expand_dot(<<letter, ":/", rest::binary>>)
defp expand_dot("/.."),
do: "/"
defp expand_dot(<<letter, ":/..">>) when letter in ?a..?z,
do: expand_dot(<<letter, ":/">>)
defp expand_dot(path),
do: expand_dot(:binary.split(path, "/", [:global]), [])
defp normalize([".."|t], ["/"|_] = acc) do
normalize t, acc
defp expand_dot([".."|t], [_, _|acc]) do
expand_dot t, acc
end
defp normalize([".."|t], [<<letter, ?:, ?/>>|_] = acc) when letter in ?a..?z do
normalize t, acc
defp expand_dot(["."|t], acc) do
expand_dot t, acc
end
defp normalize([".."|t], [_|acc]) do
normalize t, acc
defp expand_dot([h|t], acc) do
expand_dot t, ["/", h|acc]
end
defp normalize(["."|t], acc) do
normalize t, acc
defp expand_dot([], ["/"|acc]) do
IO.iodata_to_binary(:lists.reverse(acc))
end
defp normalize([h|t], acc) do
normalize t, [h|acc]
end
defp normalize([], acc) do
join :lists.reverse(acc)
defp major_os_type do
:os.type |> elem(0)
end
end
+1 -1
View File
@@ -345,7 +345,7 @@ defmodule Process do
See http://www.erlang.org/doc/man/erlang.html#process_flag-3 for more info.
"""
@spec flag(pid, process_flag, term) :: term
@spec flag(pid, :save_calls, non_neg_integer) :: non_neg_integer
def flag(pid, flag, value) do
:erlang.process_flag(pid, flag, value)
end
+1 -1
View File
@@ -289,7 +289,7 @@ defmodule Protocol do
defp beam_file(module) when is_atom(module) do
case :code.which(module) do
:non_existing -> module
atom when is_atom(atom) -> module
file -> file
end
end
+22 -12
View File
@@ -4,7 +4,7 @@ defmodule Record do
Records are simply tuples where the first element is an atom:
iex> Record.record? {User, "john", 27}
iex> Record.is_record {User, "john", 27}
true
This module provides conveniences for working with records at
@@ -64,11 +64,11 @@ defmodule Record do
## Examples
iex> record = {User, "john", 27}
iex> Record.record?(record, User)
iex> Record.is_record(record, User)
true
"""
defmacro record?(data, kind) do
defmacro is_record(data, kind) do
case Macro.Env.in_guard?(__CALLER__) do
true ->
quote do
@@ -92,14 +92,14 @@ defmodule Record do
## Examples
iex> record = {User, "john", 27}
iex> Record.record?(record)
iex> Record.is_record(record)
true
iex> tuple = {}
iex> Record.record?(tuple)
iex> Record.is_record(tuple)
false
"""
defmacro record?(data) do
defmacro is_record(data) do
case Macro.Env.in_guard?(__CALLER__) do
true ->
quote do
@@ -160,7 +160,7 @@ defmodule Record do
defmacro defrecord(name, tag \\ nil, kv) do
quote bind_quoted: [name: name, tag: tag, kv: kv] do
tag = tag || name
fields = Macro.escape Record.__fields__(:defrecord, kv)
fields = Record.__fields__(:defrecord, kv)
defmacro(unquote(name)(args \\ [])) do
Record.__access__(unquote(tag), unquote(fields), args, __CALLER__)
@@ -178,7 +178,7 @@ defmodule Record do
defmacro defrecordp(name, tag \\ nil, kv) do
quote bind_quoted: [name: name, tag: tag, kv: kv] do
tag = tag || name
fields = Macro.escape Record.__fields__(:defrecordp, kv)
fields = Record.__fields__(:defrecordp, kv)
defmacrop(unquote(name)(args \\ [])) do
Record.__access__(unquote(tag), unquote(fields), args, __CALLER__)
@@ -194,9 +194,19 @@ defmodule Record do
@doc false
def __fields__(type, fields) do
:lists.map(fn
{ key, _ } = pair when is_atom(key) -> pair
key when is_atom(key) -> { key, nil }
other -> raise ArgumentError, "#{type} fields must be atoms, got: #{inspect other}"
{key, val} when is_atom(key) ->
try do
Macro.escape(val)
rescue
e in [ArgumentError] ->
raise ArgumentError, "invalid value for record field #{key}, " <> Exception.message(e)
else
val -> {key, val}
end
key when is_atom(key) ->
{key, nil}
other ->
raise ArgumentError, "#{type} fields must be atoms, got: #{inspect other}"
end, fields)
end
@@ -309,7 +319,7 @@ defmodule Record do
# Returns a keyword list of the record
@doc false
def __keyword__(atom, fields, record) do
if record?(record, atom) do
if is_record(record, atom) do
[_tag|values] = Tuple.to_list(record)
join_keyword(fields, values, [])
else
+14 -2
View File
@@ -78,7 +78,7 @@ defmodule Regex do
defstruct re_pattern: nil, source: "", opts: ""
@opaque t :: %__MODULE__{re_pattern: term, source: binary, opts: binary}
@type t :: %__MODULE__{re_pattern: term, source: binary, opts: binary}
defmodule CompileError do
defexception message: "regex could not be compiled"
@@ -133,6 +133,7 @@ defmodule Regex do
Compiles the regular expression according to the given options.
Fails with `Regex.CompileError` if the regex cannot be compiled.
"""
@spec compile(binary, binary | [term]) :: t
def compile!(source, options \\ "") do
case compile(source, options) do
{:ok, regex} -> regex
@@ -152,6 +153,7 @@ defmodule Regex do
false
"""
@spec match?(t, String.t) :: boolean
def match?(%Regex{re_pattern: compiled}, string) when is_binary(string) do
:re.run(string, compiled, [{:capture, :none}]) == :match
end
@@ -168,6 +170,8 @@ defmodule Regex do
false
"""
@spec regex?(t) :: true
@spec regex?(any) :: false
def regex?(%Regex{}), do: true
def regex?(_), do: false
@@ -193,6 +197,7 @@ defmodule Regex do
[{2,2},{3,1}]
"""
@spec run(t, binary, [term]) :: nil | [binary] | [{integer, integer}]
def run(regex, string, options \\ [])
def run(%Regex{re_pattern: compiled}, string, options) when is_binary(string) do
@@ -223,6 +228,7 @@ defmodule Regex do
nil
"""
@spec named_captures(t, String.t, [term]) :: map | nil
def named_captures(regex, string, options \\ []) when is_binary(string) do
names = names(regex)
options = Keyword.put(options, :capture, names)
@@ -233,6 +239,7 @@ defmodule Regex do
@doc """
Returns the underlying `re_pattern` in the regular expression.
"""
@spec re_pattern(t) :: term
def re_pattern(%Regex{re_pattern: compiled}) do
compiled
end
@@ -246,6 +253,7 @@ defmodule Regex do
"foo"
"""
@spec source(t) :: String.t
def source(%Regex{source: source}) do
source
end
@@ -259,6 +267,7 @@ defmodule Regex do
"m"
"""
@spec opts(t) :: String.t
def opts(%Regex{opts: opts}) do
opts
end
@@ -272,6 +281,7 @@ defmodule Regex do
["foo"]
"""
@spec names(t) :: [String.t]
def names(%Regex{re_pattern: re_pattern}) do
{:namelist, names} = :re.inspect(re_pattern, :namelist)
names
@@ -301,6 +311,7 @@ defmodule Regex do
[]
"""
@spec scan(t, String.t, [term]) :: [[String.t]]
def scan(regex, string, options \\ [])
def scan(%Regex{re_pattern: compiled}, string, options) when is_binary(string) do
@@ -353,7 +364,7 @@ defmodule Regex do
["a", "c"]
"""
@spec split(t, String.t, [term]) :: [String.t]
def split(regex, string, options \\ [])
def split(%Regex{}, "", _opts), do: [""]
@@ -439,6 +450,7 @@ defmodule Regex do
"[b][d]"
"""
@spec replace(t, String.t, String.t | (... -> String.t), [term]) :: String.t
def replace(regex, string, replacement, options \\ [])
def replace(regex, string, replacement, options) when is_binary(replacement) do
+1 -1
View File
@@ -198,7 +198,7 @@ defmodule Set do
if target1 == target2 do
target1.intersection(set1, set2)
else
target1.reduce(set1, {:cont, Collectable.empty(set1)}, fn v, acc ->
target1.reduce(set1, {:cont, target1.new}, fn v, acc ->
{:cont, if(target2.member?(set2, v), do: target1.put(acc, v), else: acc)}
end) |> elem(1)
end
+113 -15
View File
@@ -162,7 +162,7 @@ defmodule Stream do
end
defp do_chunk(acc(h, {buffer, count} = old, t) = acc, n, pad, f1) do
if nil?(pad) || count == 0 do
if is_nil(pad) || count == 0 do
{:cont, acc}
else
buffer = :lists.reverse(buffer) ++ Enum.take(pad, n - count)
@@ -338,6 +338,25 @@ defmodule Stream do
lazy enum, fn(f1) -> R.filter_map(filter, mapper, f1) end
end
@doc """
Creates a stream that emits a value every `n` milliseconds.
The values emitted are an increasing counter starting at `0`.
## Examples
iex> Stream.interval(10) |> Enum.take(10)
[0,1,2,3,4,5,6,7,8,9]
"""
@spec interval(non_neg_integer) :: Enumerable.t
def interval(n) do
unfold 0, fn (count) ->
:timer.sleep(n)
{count, count + 1}
end
end
@doc """
Injects the stream values into the given collectable as a side-effect.
@@ -563,6 +582,22 @@ defmodule Stream do
lazy enum, fn(f1) -> R.take_while(fun, f1) end
end
@doc """
Creates a stream that emits a single value after `n` milliseconds.
The value emitted is `0`.
## Examples
iex> Stream.timer(10) |> Enum.to_list
[0]
"""
@spec timer(non_neg_integer) :: Enumerable.t
def timer(n) do
take(interval(n), 1)
end
@doc """
Transforms an existing stream.
@@ -577,7 +612,7 @@ defmodule Stream do
## Examples
`Stream.transform/3` is a useful as it can be used as basis to implement
`Stream.transform/3` is useful as it can be used as the basis to implement
many of the functions defined in this module. For example, we can implement
`Stream.take(enum, n)` as follows:
@@ -847,6 +882,15 @@ defmodule Stream do
"""
@spec cycle(Enumerable.t) :: Enumerable.t
def cycle(enumerable)
def cycle(enumerable) when is_list(enumerable) do
unfold {enumerable, enumerable}, fn
{source, [h | t]} -> {h, {source, t}}
{source = [h | t], []} -> {h, {source, t}}
end
end
def cycle(enumerable) do
fn acc, fun ->
inner = &do_cycle_each(&1, &2, fun)
@@ -934,14 +978,15 @@ defmodule Stream do
@doc """
Emits a sequence of values for the given resource.
Similar to `unfold/2` but the initial value is computed lazily via
`start_fun` and executes an `after_fun` at the end of enumeration
(both in cases of success and failure).
Similar to `transform/2` but the initial accumulated value is
computed lazily via `start_fun` and executes an `after_fun` at
the end of enumeration (both in cases of success and failure).
Successive values are generated by calling `next_fun` with the
previous accumulator (the initial value being the result returned
by `start_fun`) and it must return a tuple with the current and
next accumulator. The enumeration finishes if it returns `nil`.
by `start_fun`) and it must return a tuple containing a list
of items to be emitted and the next accumulator. The enumeration
finishes if it returns `{:halt, acc}`.
As the name says, this function is useful to stream values from
resources.
@@ -951,8 +996,8 @@ defmodule Stream do
Stream.resource(fn -> File.open!("sample") end,
fn file ->
case IO.read(file, :line) do
data when is_binary(data) -> {data, file}
_ -> nil
data when is_binary(data) -> {[data], file}
_ -> {:halt, file}
end
end,
fn file -> File.close(file) end)
@@ -974,9 +1019,11 @@ defmodule Stream do
defp do_resource(next_acc, next_fun, {:cont, acc}, fun, after_fun) do
try do
# Optimize the most common cases
case next_fun.(next_acc) do
nil -> nil
{v, next_acc} -> {fun.(v, acc), next_acc}
{[], next_acc} -> {:opt, {:cont, acc}, next_acc}
{[v], next_acc} -> {:opt, fun.(v, acc), next_acc}
{_, _} = other -> other
end
catch
kind, reason ->
@@ -984,11 +1031,62 @@ defmodule Stream do
after_fun.(next_acc)
:erlang.raise(kind, reason, stacktrace)
else
nil ->
after_fun.(next_acc)
{:done, acc}
{acc, next_acc} ->
{:opt, acc, next_acc} ->
do_resource(next_acc, next_fun, acc, fun, after_fun)
{:halt, next_acc} ->
do_resource(next_acc, next_fun, {:halt, acc}, fun, after_fun)
{list, next_acc} when is_list(list) ->
do_list_resource(next_acc, next_fun, {:cont, acc}, fun, after_fun,
&Enumerable.List.reduce(list, &1, fun))
{enum, next_acc} ->
inner = &do_resource_each(&1, &2, fun)
do_enum_resource(next_acc, next_fun, {:cont, acc}, fun, after_fun,
&Enumerable.reduce(enum, &1, inner))
end
end
defp do_list_resource(next_acc, next_fun, acc, fun, after_fun, reduce) do
try do
reduce.(acc)
catch
kind, reason ->
stacktrace = System.stacktrace
after_fun.(next_acc)
:erlang.raise(kind, reason, stacktrace)
else
{:done, acc} ->
do_resource(next_acc, next_fun, {:cont, acc}, fun, after_fun)
{:halted, acc} ->
do_resource(next_acc, next_fun, {:halt, acc}, fun, after_fun)
{:suspended, acc, c} ->
{:suspended, acc, &do_list_resource(next_acc, next_fun, &1, fun, after_fun, c)}
end
end
defp do_enum_resource(next_acc, next_fun, {op, acc}, fun, after_fun, reduce) do
try do
reduce.({op, [:outer|acc]})
catch
kind, reason ->
stacktrace = System.stacktrace
after_fun.(next_acc)
:erlang.raise(kind, reason, stacktrace)
else
{:halted, [:outer|acc]} ->
do_resource(next_acc, next_fun, {:cont, acc}, fun, after_fun)
{:halted, [:inner|acc]} ->
do_resource(next_acc, next_fun, {:halt, acc}, fun, after_fun)
{:done, [_|acc]} ->
do_resource(next_acc, next_fun, {:cont, acc}, fun, after_fun)
{:suspended, [_|acc], c} ->
{:suspended, acc, &do_enum_resource(next_acc, next_fun, &1, fun, after_fun, c)}
end
end
defp do_resource_each(x, [:outer|acc], f) do
case f.(x, acc) do
{:halt, res} -> {:halt, [:inner|res]}
{op, res} -> {op, [:outer|res]}
end
end
+81 -21
View File
@@ -4,28 +4,12 @@ defmodule String do
@moduledoc ~S"""
A String in Elixir is a UTF-8 encoded binary.
## String and binary operations
The functions in this module act according to the
Unicode Standard, version 6.3.0. For example,
`capitalize/1`, `downcase/1`, `strip/1` are provided by this
module.
In addition to this module, Elixir provides more low-level
operations that work directly with binaries. Some
of those can be found in the `Kernel` module, as:
* `Kernel.binary_part/3` - retrieves part of the binary
* `Kernel.bit_size/1` and `Kernel.byte_size/1` - size related functions
* `Kernel.is_bitstring/1` and `Kernel.is_binary/1` - type checking function
* Plus a number of functions for working with binaries (bytes)
[in the `:binary` module](http://erlang.org/doc/man/binary.html)
## Codepoints and graphemes
As per the Unicode Standard, a codepoint is an Unicode
Character, which may be represented by one or more bytes.
For example, the character "é" is represented with two
The functions in this module act according to the Unicode
Standard, version 6.3.0.As per the standard, a codepoint is
an Unicode Character, which may be represented by one or more
bytes. For example, the character "é" is represented with two
bytes:
iex> byte_size("é")
@@ -66,6 +50,82 @@ defmodule String do
This current Elixir version implements Extended Grapheme Cluster
algorithm.
## String and binary operations
To act accordingly to the Unicode Standard, many functions
in this module runs in linear time, as it needs to traverse
the whole string considering the proper Unicode codepoints.
For example, `String.length/1` is going to take longer as
the input grows. On the other hand, `byte_size/1` always runs
in constant time (i.e. regardless of the input size).
This means often there are performance costs in using the
functions in this module, compared to the more low-level
operations that work directly with binaries:
* `Kernel.binary_part/3` - retrieves part of the binary
* `Kernel.bit_size/1` and `Kernel.byte_size/1` - size related functions
* `Kernel.is_bitstring/1` and `Kernel.is_binary/1` - type checking function
* Plus a number of functions for working with binaries (bytes)
[in the `:binary` module](http://erlang.org/doc/man/binary.html)
There are many situations where using the `String` module can
be avoided in favor of binary functions or pattern matching.
For example, imagine you have a string `prefix` and you want to
remove this prefix from another string named `full`.
One may be tempted to write:
iex> take_prefix = fn full, prefix ->
...> base = String.length(prefix)
...> String.slice(full, base, String.length(full) - base)
...> end
...> take_prefix.("Mr. John", "Mr. ")
"John"
Although the function above works, it performs poorly. To
calculate the length of the string, we need to traverse it
fully, so we traverse both `prefix` and `full` strings, then
slice the `full` one, traversing it again.
A first attempting at improving it could be with ranges:
iex> take_prefix = fn full, prefix ->
...> base = String.length(prefix)
...> String.slice(full, base..-1)
...> end
...> take_prefix.("Mr. John", "Mr. ")
"John"
While this is much better (we don't traverse `full` twice),
it could still be improved. In this case, since we want to
extract a substring from a string, we can use `byte_size/1`
and `binary_part/3` as there is no chance we will slice in
the middle of a codepoint made of more than one byte:
iex> take_prefix = fn full, prefix ->
...> base = byte_size(prefix)
...> binary_part(full, base, byte_size(full) - base)
...> end
...> take_prefix.("Mr. John", "Mr. ")
"John"
Or simply used pattern matching:
iex> take_prefix = fn full, prefix ->
...> base = byte_size(prefix)
...> <<_ :: binary-size(base), rest :: binary>> = full
...> rest
...> end
...> take_prefix.("Mr. John", "Mr. ")
"John"
On the other hand, if you want to dynamically slice a string
based on an integer value, then using `String.slice/3` is the
best option as it guarantees we won't incorrectly split a valid
codepoint in multiple bytes.
## Integer codepoints
Although codepoints could be represented as integers, this
@@ -1363,7 +1423,7 @@ defmodule String do
1023
"""
@spec to_integer(String.t, pos_integer) :: integer
@spec to_integer(String.t, 2..36) :: integer
def to_integer(string, base) do
:erlang.binary_to_integer(string, base)
end
+3 -3
View File
@@ -36,7 +36,7 @@ defmodule StringIO do
"""
@spec open(binary, Keyword.t) :: {:ok, pid}
def open(string, options \\ []) when is_binary(string) do
:gen_server.start_link(__MODULE__, {string, options}, [])
GenServer.start_link(__MODULE__, {string, options}, [])
end
@doc """
@@ -52,7 +52,7 @@ defmodule StringIO do
"""
@spec contents(pid) :: {binary, binary}
def contents(pid) when is_pid(pid) do
:gen_server.call(pid, :contents)
GenServer.call(pid, :contents)
end
@doc """
@@ -68,7 +68,7 @@ defmodule StringIO do
"""
@spec close(pid) :: {:ok, {binary, binary}}
def close(pid) when is_pid(pid) do
:gen_server.call(pid, :close)
GenServer.call(pid, :close)
end
## callbacks
+25 -9
View File
@@ -132,7 +132,7 @@ defmodule Supervisor do
* `:simple_one_for_one` - similar to `:one_for_one` but suits better
when dynamically attaching children. This strategy requires the
supervisor specification to contain only one children. Many functions
supervisor specification to contain only one child. Many functions
in this module behave slightly differently when this strategy is
used.
@@ -268,7 +268,9 @@ defmodule Supervisor do
and child specification.
"""
@spec start_child(supervisor, Supervisor.Spec.spec | [term]) :: on_start_child
defdelegate start_child(supervisor, child_spec_or_args), to: :supervisor
def start_child(supervisor, child_spec_or_args) do
call(supervisor, {:start_child, child_spec_or_args})
end
@doc """
Terminates the given pid or child id.
@@ -290,7 +292,9 @@ defmodule Supervisor do
"""
@spec terminate_child(supervisor, pid | Supervisor.Spec.child_id) :: :ok | {:error, error}
when error: :not_found | :simple_one_for_one
defdelegate terminate_child(supervisor, pid_or_child_id), to: :supervisor
def terminate_child(supervisor, pid_or_child_id) do
call(supervisor, {:terminate_child, pid_or_child_id})
end
@doc """
Deletes the child specification identified by `child_id`.
@@ -306,7 +310,9 @@ defmodule Supervisor do
"""
@spec delete_child(supervisor, Supervisor.Spec.child_id) :: :ok | {:error, error}
when error: :not_found | :simple_one_for_one | :running | :restarting
defdelegate delete_child(supervisor, child_id), to: :supervisor
def delete_child(supervisor, child_id) do
call(supervisor, {:delete_child, child_id})
end
@doc """
Restarts a child process identified by `child_id`.
@@ -335,7 +341,9 @@ defmodule Supervisor do
@spec restart_child(supervisor, Supervisor.Spec.child_id) ::
{:ok, child} | {:ok, child, term} | {:error, error}
when error: :not_found | :simple_one_for_one | :running | :restarting | term
defdelegate restart_child(supervisor, child_id), to: :supervisor
def restart_child(supervisor, child_id) do
call(supervisor, {:restart_child, child_id})
end
@doc """
Returns a list with information about all children.
@@ -361,7 +369,9 @@ defmodule Supervisor do
child | :restarting,
Supervisor.Spec.worker,
Supervisor.Spec.modules}]
defdelegate which_children(supervisor), to: :supervisor
def which_children(supervisor) do
call(supervisor, :which_children)
end
@doc """
Returns a map containing count values for the supervisor.
@@ -381,9 +391,15 @@ defmodule Supervisor do
"""
@spec count_children(supervisor) ::
[specs: non_neg_integer, active: non_neg_integer,
supervisors: non_neg_integer, workers: non_neg_integer]
%{specs: non_neg_integer, active: non_neg_integer,
supervisors: non_neg_integer, workers: non_neg_integer}
def count_children(supervisor) do
:supervisor.count_children(supervisor) |> :maps.from_list
call(supervisor, :count_children) |> :maps.from_list
end
@compile {:inline, call: 2}
defp call(supervisor, req) do
GenServer.call(supervisor, req, :infinity)
end
end
+3 -3
View File
@@ -145,12 +145,12 @@ defmodule Supervisor.Spec do
in the `Supervisor` module docs.
* `:max_restarts` - the maximum amount of restarts allowed in
a time frame. Defaults to 5.
a time frame. Defaults to 3.
* `:max_seconds` - the time frame in which `:max_restarts` applies.
Defaults to 5.
The `:strategy` option is required and by default maximum 5 restarts
The `:strategy` option is required and by default maximum 3 restarts
are allowed within 5 seconds. Please check the `Supervisor` module for
a complete description of the available strategies.
"""
@@ -162,7 +162,7 @@ defmodule Supervisor.Spec do
raise ArgumentError, "expected :strategy option to be given"
end
maxR = Keyword.get(options, :max_restarts, 5)
maxR = Keyword.get(options, :max_restarts, 3)
maxS = Keyword.get(options, :max_seconds, 5)
assert_unique_ids(Enum.map(children, &elem(&1, 0)))
+10 -10
View File
@@ -94,11 +94,20 @@ defmodule System do
"""
def cwd do
case :file.get_cwd do
{:ok, base} -> IO.chardata_to_string(base)
{:ok, base} -> IO.chardata_to_string(fix_drive_letter(base))
_ -> nil
end
end
defp fix_drive_letter([l, ?:, ?/ | rest] = original) when l in ?A..?Z do
case :os.type() do
{:win32, _} -> [l+?a-?A, ?:, ?/ | rest]
_ -> original
end
end
defp fix_drive_letter(original), do: original
@doc """
Current working directory, exception on error.
@@ -218,15 +227,6 @@ defmodule System do
:elixir_code_server.cast {:at_exit, fun}
end
@doc false
def cmd(command) when is_list(command) do
:os.cmd(command)
end
def cmd(command) when is_binary(command) do
List.to_string :os.cmd(String.to_char_list(command))
end
@doc """
Locates an executable on the system.
+27 -3
View File
@@ -77,8 +77,8 @@ defmodule Task do
supervisor(Task.Supervisor, [[name: :tasks_sup]])
]
Note that, when working with distributed tasks, one should use the `async/4` API,
that expects explicit module, function and arguments, instead of `async/2` that
Note that, when working with distributed tasks, one should use the `async/3` API,
that expects explicit module, function and arguments, instead of `async/1` that
works with anonymous functions. That's because the anonymous function API expects
the same module version to exist on all involved nodes. Check the `Agent` module
documentation for more information on distributed processes, as the limitations
@@ -119,6 +119,30 @@ defmodule Task do
Task.Supervised.start_link(get_info(self), {mod, fun, args})
end
@doc """
Starts a task.
This is only used when the task is used for side-effects
(i.e. no interest in its return result) and it should not
be linked to the current process.
"""
@spec start(fun) :: {:ok, pid}
def start(fun) do
start(:erlang, :apply, [fun, []])
end
@doc """
Starts a task.
This is only used when the task is used for side-effects
(i.e. no interest in its return result) and it should not
be linked to the current process.
"""
@spec start(module, atom, [term]) :: {:ok, pid}
def start(mod, fun, args) do
Task.Supervised.start(get_info(self), {mod, fun, args})
end
@doc """
Starts a task that can be awaited on.
@@ -160,7 +184,7 @@ defmodule Task do
end
@doc """
Awaits for a task reply.
Awaits a task reply.
A timeout, in milliseconds, can be given with default value
of `5000`. In case the task process dies, this function will
+4
View File
@@ -1,6 +1,10 @@
defmodule Task.Supervised do
@moduledoc false
def start(info, fun) do
{:ok, :proc_lib.spawn(__MODULE__, :noreply, [info, fun])}
end
def start_link(info, fun) do
{:ok, :proc_lib.spawn_link(__MODULE__, :noreply, [info, fun])}
end
+10 -1
View File
@@ -25,14 +25,23 @@ defmodule Task.Supervisor do
described under the `Name Registration` section in the `GenServer` module
docs;
* `:restart` - the restart strategy, may be `:temporary` (the default),
`:transient` or `:permanent`. Check `Supervisor.Spec` for more info.
Defaults to temporary as most tasks can't be effectively restarted after
a crash;
* `:shutdown` - `:brutal_kill` if the tasks must be killed directly on shutdown
or an integer indicating the timeout value, defaults to 5000 milliseconds;
* `:max_restarts` and `:max_seconds` - as specified in `Supervisor.Spec.supervise/2`;
"""
@spec start_link(Supervisor.options) :: Supervisor.on_start
def start_link(opts \\ []) do
import Supervisor.Spec
{restart, opts} = Keyword.pop(opts, :restart, :temporary)
{shutdown, opts} = Keyword.pop(opts, :shutdown, 5000)
children = [worker(Task.Supervised, [], restart: :temporary, shutdown: shutdown)]
children = [worker(Task.Supervised, [], restart: restart, shutdown: shutdown)]
Supervisor.start_link(children, [strategy: :simple_one_for_one] ++ opts)
end
+14 -6
View File
@@ -68,7 +68,7 @@ defmodule URI do
Takes an enumerable (containing a sequence of two-item tuples)
and returns a string of the form "key1=value1&key2=value2..." where
keys and values are URL encoded as per `encode/1`.
keys and values are URL encoded as per `encode/2`.
Keys and values can be any term that implements the `String.Chars`
protocol, except lists which are explicitly forbidden.
@@ -309,7 +309,7 @@ defmodule URI do
scheme = normalize_scheme(scheme)
if nil?(port) and not nil?(scheme) do
if is_nil(port) and not is_nil(scheme) do
port = default_port(scheme)
end
@@ -349,12 +349,20 @@ defimpl String.Chars, for: URI do
if uri.port == port, do: uri = %{uri | port: nil}
end
# Based on http://tools.ietf.org/html/rfc3986#section-5.3
if uri.host do
authority = uri.host
if uri.userinfo, do: authority = uri.userinfo <> "@" <> authority
if uri.port, do: authority = authority <> ":" <> Integer.to_string(uri.port)
else
authority = uri.authority
end
result = ""
if uri.scheme, do: result = result <> uri.scheme <> "://"
if uri.userinfo, do: result = result <> uri.userinfo <> "@"
if uri.host, do: result = result <> uri.host
if uri.port, do: result = result <> ":" <> Integer.to_string(uri.port)
if uri.scheme, do: result = result <> uri.scheme <> ":"
if authority, do: result = result <> "//" <> authority
if uri.path, do: result = result <> uri.path
if uri.query, do: result = result <> "?" <> uri.query
if uri.fragment, do: result = result <> "#" <> uri.fragment
+1 -1
View File
@@ -324,7 +324,7 @@ defmodule Version do
patch = nillify(patch)
pre = nillify(pre)
if nil?(minor) or (nil?(patch) and not approximate?) do
if is_nil(minor) or (is_nil(patch) and not approximate?) do
:error
else
major = String.to_integer(major)
+6 -1
View File
@@ -54,12 +54,17 @@ config_change(_Changed, _New, _Remove) ->
start_cli() ->
{ok, _} = application:ensure_all_started(?MODULE),
%% We start the Logger so tools that depend on Elixir
%% always have the Logger directly accessible. However
%% Logger is not a dependency of the Elixir application,
%% which means releases that want to use Logger must
%% always list it as part of its applications.
_ = application:start(logger),
_ = case code:ensure_loaded('Elixir.Logger') of
{module, _} -> application:start(logger);
{error, _} -> ok
end,
'Elixir.Kernel.CLI':main(init:get_plain_arguments()).
%% EVAL HOOKS
@@ -41,28 +41,29 @@
%% Used in tokenization and interpolation
%% Numbers
-define(is_hex(S), ?is_digit(S) orelse (S >= $A andalso S =< $F) orelse (S >= $a andalso S =< $f)).
-define(is_bin(S), S >= $0 andalso S =< $1).
-define(is_octal(S), S >= $0 andalso S =< $7).
-define(is_leading_octal(S), S >= $0 andalso S =< $3).
-define(is_hex(S), (?is_digit(S) orelse (S >= $A andalso S =< $F) orelse (S >= $a andalso S =< $f))).
-define(is_bin(S), (S >= $0 andalso S =< $1)).
-define(is_octal(S), (S >= $0 andalso S =< $7)).
-define(is_leading_octal(S), (S >= $0 andalso S =< $3)).
%% Digits and letters
-define(is_digit(S), S >= $0 andalso S =< $9).
-define(is_upcase(S), S >= $A andalso S =< $Z).
-define(is_downcase(S), S >= $a andalso S =< $z).
-define(is_digit(S), (S >= $0 andalso S =< $9)).
-define(is_upcase(S), (S >= $A andalso S =< $Z)).
-define(is_downcase(S), (S >= $a andalso S =< $z)).
%% Atoms
-define(is_atom_start(S), ?is_quote(S) orelse ?is_upcase(S) orelse ?is_downcase(S) orelse (S == $_)).
-define(is_atom(S), ?is_identifier(S) orelse (S == $@)).
-define(is_atom_start(S), (?is_quote(S) orelse ?is_upcase(S) orelse ?is_downcase(S) orelse (S == $_))).
-define(is_atom(S), (?is_identifier(S) orelse (S == $@))).
-define(is_identifier(S), ?is_digit(S) orelse ?is_upcase(S) orelse ?is_downcase(S) orelse (S == $_)).
-define(is_sigil(S), (S == $/) orelse (S == $<) orelse (S == $") orelse (S == $') orelse
(S == $[) orelse (S == $() orelse (S == ${) orelse (S == $|)).
-define(is_identifier(S), (?is_digit(S) orelse ?is_upcase(S) orelse ?is_downcase(S) orelse (S == $_))).
-define(is_sigil(S), ((S == $/) orelse (S == $<) orelse (S == $") orelse (S == $') orelse
(S == $[) orelse (S == $() orelse (S == ${) orelse (S == $|))).
%% Quotes
-define(is_quote(S), S == $" orelse S == $').
-define(is_quote(S), (S == $" orelse S == $')).
%% Spaces
-define(is_horizontal_space(S), (S == $\s) orelse (S == $\t)).
-define(is_vertical_space(S), (S == $\r) orelse (S == $\n)).
-define(is_space(S), ?is_horizontal_space(S) orelse ?is_vertical_space(S)).
-define(is_horizontal_space(S), ((S == $\s) orelse (S == $\t))).
-define(is_vertical_space(S), ((S == $\r) orelse (S == $\n))).
-define(is_space(S), (?is_horizontal_space(S) orelse ?is_vertical_space(S))).
-define(is_invalid_space(S), (S == 16#A0)).
+2 -11
View File
@@ -36,7 +36,7 @@ expand_bitstr(Fun, [H|T], Acc, E) ->
%% Expand bit info
expand_bit_info(Meta, Info, E) ->
expand_bit_info(Meta, unpack_bit_info(Meta, Info, E), default, [], E).
expand_bit_info(Meta, unpack_bit_info(Info, []), default, [], E).
expand_bit_info(Meta, [{size, _, [_]=Args}|T], Size, Types, E) ->
case Size of
@@ -111,18 +111,9 @@ handle_unknown_bit_info(Meta, Expr, T, Size, Types, E) ->
elixir_errors:compile_error(Meta, ?m(E, file),
"unknown bitstring specifier ~ts", ['Elixir.Macro':to_string(Expr)]);
Info ->
expand_bit_info(Meta, unpack_bit_info(Meta, Info, E) ++ T, Size, Types, E)
expand_bit_info(Meta, unpack_bit_info(Info, []) ++ T, Size, Types, E)
end.
%% We can remove unpack_bit_info/3 once deprecated.
unpack_bit_info(Meta, [H|T], E) ->
elixir_errors:warn(?line(Meta), ?m(E, file), "passing a list of bitstring modifiers is deprecated, "
"please separate them with - instead"),
Dashed = lists:foldl(fun(I, Acc) -> {'-', Meta, [Acc, I]} end, H, T),
unpack_bit_info(Dashed, []);
unpack_bit_info(_Meta, Expr, _E) ->
unpack_bit_info(Expr, []).
unpack_bit_info({'-', _, [H, T]}, Acc) ->
unpack_bit_info(H, unpack_bit_info(T, Acc));
unpack_bit_info({'*', _, [{'_', _, Atom}, Unit]}, Acc) when is_atom(Atom) and is_integer(Unit) ->
+37 -18
View File
@@ -1,5 +1,5 @@
-module(elixir_compiler).
-export([get_opt/1, string/2, quoted/2, file/1, file_to_path/2]).
-export([get_opt/1, string/2, quoted/2, file/1, file/2, file_to_path/2]).
-export([core/0, module/4, eval_forms/3]).
-include("elixir.hrl").
@@ -15,15 +15,19 @@ get_opt(Key) ->
%% Compilation entry points.
string(Contents, File) when is_list(Contents), is_binary(File) ->
string(Contents, File, nil).
string(Contents, File, Dest) ->
Forms = elixir:'string_to_quoted!'(Contents, 1, File, []),
quoted(Forms, File).
quoted(Forms, File, Dest).
quoted(Forms, File) when is_binary(File) ->
quoted(Forms, File, nil).
quoted(Forms, File, Dest) ->
Previous = get(elixir_compiled),
try
put(elixir_compiled, []),
elixir_lexical:run(File, fun
elixir_lexical:run(File, Dest, fun
(Pid) ->
Env = elixir:env_for_eval([{line,1},{file,File}]),
eval_forms(Forms, [], Env#{lexical_tracker := Pid})
@@ -34,14 +38,20 @@ quoted(Forms, File) when is_binary(File) ->
end.
file(Relative) when is_binary(Relative) ->
file(Relative, nil).
file(Relative, Dest) ->
File = filename:absname(Relative),
{ok, Bin} = file:read_file(File),
string(elixir_utils:characters_to_list(Bin), File).
string(elixir_utils:characters_to_list(Bin), File, case Dest of
nil -> Dest;
_ -> filename:absname(Dest)
end).
file_to_path(File, Path) when is_binary(File), is_binary(Path) ->
Lists = file(File),
_ = [binary_to_path(X, Path) || X <- Lists],
Lists.
file_to_path(File, Dest) when is_binary(File), is_binary(Dest) ->
Comp = file(File, Dest),
Abs = filename:absname(Dest),
_ = [binary_to_path(X, Abs) || X <- Comp],
Comp.
%% Evaluation
@@ -70,7 +80,7 @@ code_loading_compilation(Forms, Vars, #{line := Line} = E) ->
%% process when native is set to true
AllOpts = elixir_code_server:call(erl_compiler_options),
FinalOpts = AllOpts -- [native, warn_missing_spec],
module(Form, ?m(E, file), FinalOpts, true, fun(_, Binary) ->
inner_module(Form, FinalOpts, true, E, fun(_, Binary) ->
%% If we have labeled locals, anonymous functions
%% were created and therefore we cannot ditch the
%% module
@@ -128,29 +138,38 @@ allows_fast_compilation(_) -> false.
%% Compile the module by forms based on the scope information
%% executes the callback in case of success. This automatically
%% handles errors and warnings. Used by this module and elixir_module.
module(Forms, File, Opts, Callback) ->
module(Forms, Opts, E, Callback) ->
Final =
case (get_opt(debug_info) == true) orelse
lists:member(debug_info, Opts) of
true -> [debug_info] ++ elixir_code_server:call(erl_compiler_options);
false -> elixir_code_server:call(erl_compiler_options)
end,
module(Forms, File, Final, false, Callback).
inner_module(Forms, Final, false, E, Callback).
module(Forms, File, Options, Bootstrap, Callback) when
is_binary(File), is_list(Forms), is_list(Options), is_boolean(Bootstrap), is_function(Callback) ->
Listname = elixir_utils:characters_to_list(File),
inner_module(Forms, Options, Bootstrap, #{file := File} = E, Callback) when
is_list(Forms), is_list(Options), is_boolean(Bootstrap), is_function(Callback) ->
Source = elixir_utils:characters_to_list(File),
case compile:noenv_forms([no_auto_import()|Forms], [return,{source,Listname}|Options]) of
{ok, ModuleName, Binary, Warnings} ->
case compile:noenv_forms([no_auto_import()|Forms], [return,{source,Source}|Options]) of
{ok, Module, Binary, Warnings} ->
format_warnings(Bootstrap, Warnings),
{module, ModuleName} = code:load_binary(ModuleName, Listname, Binary),
Callback(ModuleName, Binary);
{module, Module} = code:load_binary(Module, beam_location(E), Binary),
Callback(Module, Binary);
{error, Errors, Warnings} ->
format_warnings(Bootstrap, Warnings),
format_errors(Errors)
end.
beam_location(#{module := nil}) -> in_memory;
beam_location(#{lexical_tracker := Pid, module := Module}) ->
case elixir_lexical:dest(Pid) of
nil -> in_memory;
Dest ->
filename:join(elixir_utils:characters_to_list(Dest),
atom_to_list(Module) ++ ".beam")
end.
no_auto_import() ->
{attribute, 0, compile, no_auto_import}.
+11 -2
View File
@@ -82,6 +82,7 @@ store_definition(Line, Kind, CheckClauses, Call, Body, Pos) ->
LinifyBody = elixir_quote:linify(Line, Key, Body),
assert_no_aliases_name(Line, Name, Args, E),
assert_valid_name(Line, Kind, Name, Args, E),
store_definition(Line, Kind, DoCheckClauses, Name,
LinifyArgs, LinifyGuards, LinifyBody, Location, E).
@@ -175,7 +176,7 @@ translate_definition(Kind, Line, Module, Name, Args, Guards, Body, E) when is_in
Arity = length(Args),
{EArgs, EGuards, EBody, _} = elixir_exp_clauses:def(fun elixir_def_defaults:expand/2,
Args, Guards, expr_from_body(Line, Body), E),
Args, Guards, expr_from_body(Line, Body), E),
Body == nil andalso check_args_for_bodyless_clause(Line, EArgs, E),
@@ -404,8 +405,12 @@ invalid_arg(_) ->
assert_no_aliases_name(Line, '__aliases__', [Atom], #{file := File}) when is_atom(Atom) ->
elixir_errors:form_error([{line, Line}], File, ?MODULE, {no_alias, Atom});
assert_no_aliases_name(_Line, _Aliases, _Args, _S) ->
ok.
assert_no_aliases_name(_Meta, _Aliases, _Args, _S) ->
assert_valid_name(Line, Kind, is_record, [_, _], #{file := File}) when Kind == defp; Kind == def ->
elixir_errors:form_error([{line, Line}], File, ?MODULE, {is_record, Kind});
assert_valid_name(_Line, _Kind, _Name, _Args, _S) ->
ok.
%% Format errors
@@ -447,5 +452,9 @@ format_error({invalid_def, Kind, NameAndArgs}) ->
format_error(invalid_args_for_bodyless_clause) ->
"can use only variables and \\\\ as arguments of bodyless clause";
format_error({is_record, Kind}) ->
io_lib:format("cannot define function named ~ts is_record/2 due to compability "
"issues with the Erlang compiler (it is a known bug)", [Kind]);
format_error({missing_do, Kind}) ->
io_lib:format("missing do keyword in ~ts", [Kind]).
+2 -1
View File
@@ -4,10 +4,11 @@
-include("elixir.hrl").
expand(Args, E) ->
NoContext = E#{context := nil},
lists:mapfoldl(fun
({'\\\\', Meta, [Left, Right]}, Acc) ->
{ELeft, EL} = elixir_exp:expand(Left, Acc),
{ERight, _} = elixir_exp:expand(Right, Acc#{context := nil}),
{ERight, _} = elixir_exp:expand(Right, NoContext),
{{'\\\\', Meta, [ELeft, ERight]}, EL};
(Left, Acc) ->
elixir_exp:expand(Left, Acc)
+5 -12
View File
@@ -31,17 +31,20 @@ default_macros() ->
default_requires() ->
['Elixir.Kernel', 'Elixir.Kernel.Typespec'].
%% This is used by elixir_quote. Note we don't record the
%% import locally because at that point there is no
%% ambiguity.
find_import(Meta, Name, Arity, E) ->
Tuple = {Name, Arity},
case find_dispatch(Meta, Tuple, [], E) of
{function, Receiver} ->
elixir_lexical:record_import(Receiver, ?m(E, lexical_tracker)),
elixir_locals:record_import(Tuple, Receiver, ?m(E, module), ?m(E, function)),
%% elixir_locals:record_import(Tuple, Receiver, ?m(E, module), ?m(E, function)),
Receiver;
{macro, Receiver} ->
elixir_lexical:record_import(Receiver, ?m(E, lexical_tracker)),
elixir_locals:record_import(Tuple, Receiver, ?m(E, module), ?m(E, function)),
%% elixir_locals:record_import(Tuple, Receiver, ?m(E, module), ?m(E, function)),
Receiver;
_ ->
false
@@ -519,15 +522,5 @@ deprecation_message(Warning, Message) ->
Message -> Warning ++ ", " ++ Message
end.
deprecation('Elixir.System', 'cmd', 1) ->
"use System.cmd/3 instead";
deprecation('Elixir.Inspect.Algebra', 'pretty', 2) ->
"use Inspect.Algebra.format/2 instead";
deprecation('Elixir.IO.ANSI', 'escape', _) ->
"use the new API in IO.ANSI.format/2 instead";
deprecation('Elixir.IO.ANSI', 'escape_fragment', _) ->
"use the new API in IO.ANSI.format_fragment/2 instead";
deprecation('Elixir.IO.ANSI', 'terminal?', _) ->
"use IO.ANSI.enabled?/0 instead";
deprecation(_, _, _) ->
false.
+5 -5
View File
@@ -6,12 +6,12 @@
handle_file_warning/2, handle_file_warning/3, handle_file_error/2]).
-include("elixir.hrl").
-spec warn(non_neg_integer(), binary(), iolist()) -> ok.
-spec warn(non_neg_integer(), binary(), unicode:chardata()) -> ok.
warn(Line, File, Warning) when is_integer(Line), is_binary(File) ->
warn(file_format(Line, File), Warning).
-spec warn(iolist(), iolist()) -> ok.
-spec warn(unicode:chardata(), unicode:chardata()) -> ok.
warn(Caller, Warning) ->
do_warn([Caller, "warning: ", Warning, $\n]).
@@ -31,8 +31,8 @@ form_warn(Meta, File, Module, Desc) when is_list(Meta) ->
%% Compilation error.
-spec compile_error(list(), binary(), iolist()) -> no_return().
-spec compile_error(list(), binary(), iolist(), list()) -> no_return().
-spec compile_error(list(), binary(), unicode:charlist()) -> no_return().
-spec compile_error(list(), binary(), string(), list()) -> no_return().
compile_error(Meta, File, Message) when is_list(Message) ->
raise(Meta, File, 'Elixir.CompileError', elixir_utils:characters_to_binary(Message)).
@@ -241,4 +241,4 @@ do_raise(Line, File, Kind, Message) when is_integer(Line), is_binary(File), is_b
end,
Stacktrace = erlang:get_stacktrace(),
Exception = Kind:exception([{description, Message}, {file, File}, {line, Line}]),
erlang:raise(error, Exception, tl(Stacktrace)).
erlang:raise(error, Exception, tl(Stacktrace)).
+4 -3
View File
@@ -66,6 +66,8 @@ expand({'__aliases__', _, _} = Alias, E) ->
elixir_lexical:record_remote(Receiver, ?m(E, lexical_tracker)),
{Receiver, EA};
false ->
elixir_errors:warn(?m(E, line), ?m(E, file), io_lib:format("an alias must expand to an atom "
"at compilation time but got ~ts", ['Elixir.Macro':to_string(hd(EAliases))])),
{{{'.', [], [elixir_aliases, concat]}, [], [EAliases]}, EA}
end
end;
@@ -354,7 +356,7 @@ expand({_, Meta, Args} = Invalid, E) when is_list(Meta) and is_list(Args) ->
expand({_, _, _} = Tuple, E) ->
compile_error([{line,0}], ?m(E, file), "invalid quoted expression: ~ts",
['Elixir.Kernel':inspect(Tuple, [{records,false}])]);
['Elixir.Kernel':inspect(Tuple, [])]);
%% Literals
@@ -456,8 +458,7 @@ assert_no_ambiguous_op(_Atom, _Meta, _Args, _E) ->
ok.
expand_local(Meta, Name, Args, #{local := nil, function := nil} = E) ->
{EArgs, EA} = expand_args(Args, E),
{{Name, Meta, EArgs}, EA};
compile_error(Meta, ?m(E, file), "undefined function ~ts/~B", [Name, length(Args)]);
expand_local(Meta, Name, Args, #{local := nil, module := Module, function := Function} = E) ->
elixir_locals:record_local({Name, length(Args)}, Module, Function),
{EArgs, EA} = expand_args(Args, E),
+26 -79
View File
@@ -74,104 +74,51 @@ unescape_chars(String) ->
unescape_chars(String, fun unescape_map/1).
unescape_chars(String, Map) ->
Octals = Map($0) /= false,
Hex = Map($x) == true,
unescape_chars(String, Map, Octals, Hex, <<>>).
unescape_chars(String, Map, Map($x) == true, <<>>).
%% Deprecated
unescape_chars(<<$\\,$x,A,B,Rest/binary>>, Map, true, Acc) when ?is_hex(A), ?is_hex(B) ->
append_escaped(Rest, Map, [A,B], true, Acc, 16);
unescape_chars(<<$\\,A,B,C,Rest/binary>>, Map, true, Hex, Acc) when ?is_octal(A), A =< $3, ?is_octal(B), ?is_octal(C) ->
io:format(standard_error, "warning: octals inside strings/sigils/chars are deprecated, got: \\~ts~n", [<<A, B, C>>]),
append_escaped(Rest, Map, [A,B,C], true, Hex, Acc, 8);
unescape_chars(<<$\\,$x,A,Rest/binary>>, Map, true, Acc) when ?is_hex(A) ->
append_escaped(Rest, Map, [A], true, Acc, 16);
unescape_chars(<<$\\,A,B,Rest/binary>>, Map, true, Hex, Acc) when ?is_octal(A), ?is_octal(B) ->
io:format(standard_error, "warning: octals inside strings/sigils/chars are deprecated, got: \\~ts~n", [<<A, B>>]),
append_escaped(Rest, Map, [A,B], true, Hex, Acc, 8);
unescape_chars(<<$\\,$x,${,A,$},Rest/binary>>, Map, true, Acc) when ?is_hex(A) ->
append_escaped(Rest, Map, [A], true, Acc, 16);
unescape_chars(<<$\\,$0,Rest/binary>>, Map, true, Hex, Acc) ->
append_escaped(Rest, Map, [$0], true, Hex, Acc, 8);
unescape_chars(<<$\\,$x,${,A,B,$},Rest/binary>>, Map, true, Acc) when ?is_hex(A), ?is_hex(B) ->
append_escaped(Rest, Map, [A,B], true, Acc, 16);
unescape_chars(<<$\\,A,Rest/binary>>, Map, true, Hex, Acc) when ?is_octal(A) ->
io:format(standard_error, "warning: octals inside strings/sigils/chars are deprecated, got: \\~ts~n", [<<A>>]),
append_escaped(Rest, Map, [A], true, Hex, Acc, 8);
unescape_chars(<<$\\,$x,${,A,B,C,$},Rest/binary>>, Map, true, Acc) when ?is_hex(A), ?is_hex(B), ?is_hex(C) ->
append_escaped(Rest, Map, [A,B,C], true, Acc, 16);
unescape_chars(<<$\\,$X,A,B,Rest/binary>>, Map, Octal, true, Acc) when ?is_hex(A), ?is_hex(B) ->
io:format(standard_error, "warning: \\X inside strings/sigils/chars is deprecated, please use \\x instead~n", []),
append_escaped(Rest, Map, [A,B], Octal, true, Acc, 16);
unescape_chars(<<$\\,$x,${,A,B,C,D,$},Rest/binary>>, Map, true, Acc) when ?is_hex(A), ?is_hex(B), ?is_hex(C), ?is_hex(D) ->
append_escaped(Rest, Map, [A,B,C,D], true, Acc, 16);
unescape_chars(<<$\\,$X,A,Rest/binary>>, Map, Octal, true, Acc) when ?is_hex(A) ->
io:format(standard_error, "warning: \\X inside strings/sigils/chars is deprecated, please use \\x instead~n", []),
append_escaped(Rest, Map, [A], Octal, true, Acc, 16);
unescape_chars(<<$\\,$x,${,A,B,C,D,E,$},Rest/binary>>, Map, true, Acc) when ?is_hex(A), ?is_hex(B), ?is_hex(C), ?is_hex(D), ?is_hex(E) ->
append_escaped(Rest, Map, [A,B,C,D,E], true, Acc, 16);
unescape_chars(<<$\\,$X,${,A,$},Rest/binary>>, Map, Octal, true, Acc) when ?is_hex(A) ->
io:format(standard_error, "warning: \\X inside strings/sigils/chars is deprecated, please use \\x instead~n", []),
append_escaped(Rest, Map, [A], Octal, true, Acc, 16);
unescape_chars(<<$\\,$x,${,A,B,C,D,E,F,$},Rest/binary>>, Map, true, Acc) when ?is_hex(A), ?is_hex(B), ?is_hex(C), ?is_hex(D), ?is_hex(E), ?is_hex(F) ->
append_escaped(Rest, Map, [A,B,C,D,E,F], true, Acc, 16);
unescape_chars(<<$\\,$X,${,A,B,$},Rest/binary>>, Map, Octal, true, Acc) when ?is_hex(A), ?is_hex(B) ->
io:format(standard_error, "warning: \\X inside strings/sigils/chars is deprecated, please use \\x instead~n", []),
append_escaped(Rest, Map, [A,B], Octal, true, Acc, 16);
unescape_chars(<<$\\,$X,${,A,B,C,$},Rest/binary>>, Map, Octal, true, Acc) when ?is_hex(A), ?is_hex(B), ?is_hex(C) ->
io:format(standard_error, "warning: \\X inside strings/sigils/chars is deprecated, please use \\x instead~n", []),
append_escaped(Rest, Map, [A,B,C], Octal, true, Acc, 16);
unescape_chars(<<$\\,$X,${,A,B,C,D,$},Rest/binary>>, Map, Octal, true, Acc) when ?is_hex(A), ?is_hex(B), ?is_hex(C), ?is_hex(D) ->
io:format(standard_error, "warning: \\X inside strings/sigils/chars is deprecated, please use \\x instead~n", []),
append_escaped(Rest, Map, [A,B,C,D], Octal, true, Acc, 16);
unescape_chars(<<$\\,$X,${,A,B,C,D,E,$},Rest/binary>>, Map, Octal, true, Acc) when ?is_hex(A), ?is_hex(B), ?is_hex(C), ?is_hex(D), ?is_hex(E) ->
io:format(standard_error, "warning: \\X inside strings/sigils/chars is deprecated, please use \\x instead~n", []),
append_escaped(Rest, Map, [A,B,C,D,E], Octal, true, Acc, 16);
unescape_chars(<<$\\,$X,${,A,B,C,D,E,F,$},Rest/binary>>, Map, Octal, true, Acc) when ?is_hex(A), ?is_hex(B), ?is_hex(C), ?is_hex(D), ?is_hex(E), ?is_hex(F) ->
io:format(standard_error, "warning: \\X inside strings/sigils/chars is deprecated, please use \\x instead~n", []),
append_escaped(Rest, Map, [A,B,C,D,E,F], Octal, true, Acc, 16);
%% End of deprecated
unescape_chars(<<$\\,$x,A,B,Rest/binary>>, Map, Octal, true, Acc) when ?is_hex(A), ?is_hex(B) ->
append_escaped(Rest, Map, [A,B], Octal, true, Acc, 16);
unescape_chars(<<$\\,$x,A,Rest/binary>>, Map, Octal, true, Acc) when ?is_hex(A) ->
append_escaped(Rest, Map, [A], Octal, true, Acc, 16);
unescape_chars(<<$\\,$x,${,A,$},Rest/binary>>, Map, Octal, true, Acc) when ?is_hex(A) ->
append_escaped(Rest, Map, [A], Octal, true, Acc, 16);
unescape_chars(<<$\\,$x,${,A,B,$},Rest/binary>>, Map, Octal, true, Acc) when ?is_hex(A), ?is_hex(B) ->
append_escaped(Rest, Map, [A,B], Octal, true, Acc, 16);
unescape_chars(<<$\\,$x,${,A,B,C,$},Rest/binary>>, Map, Octal, true, Acc) when ?is_hex(A), ?is_hex(B), ?is_hex(C) ->
append_escaped(Rest, Map, [A,B,C], Octal, true, Acc, 16);
unescape_chars(<<$\\,$x,${,A,B,C,D,$},Rest/binary>>, Map, Octal, true, Acc) when ?is_hex(A), ?is_hex(B), ?is_hex(C), ?is_hex(D) ->
append_escaped(Rest, Map, [A,B,C,D], Octal, true, Acc, 16);
unescape_chars(<<$\\,$x,${,A,B,C,D,E,$},Rest/binary>>, Map, Octal, true, Acc) when ?is_hex(A), ?is_hex(B), ?is_hex(C), ?is_hex(D), ?is_hex(E) ->
append_escaped(Rest, Map, [A,B,C,D,E], Octal, true, Acc, 16);
unescape_chars(<<$\\,$x,${,A,B,C,D,E,F,$},Rest/binary>>, Map, Octal, true, Acc) when ?is_hex(A), ?is_hex(B), ?is_hex(C), ?is_hex(D), ?is_hex(E), ?is_hex(F) ->
append_escaped(Rest, Map, [A,B,C,D,E,F], Octal, true, Acc, 16);
unescape_chars(<<$\\,$x,_/binary>>, _Map, _Octal, true, _Acc) ->
unescape_chars(<<$\\,$x,_/binary>>, _Map, true, _Acc) ->
Msg = <<"missing hex sequence after \\x">>,
error('Elixir.ArgumentError':exception([{message,Msg}]));
unescape_chars(<<$\\,Escaped,Rest/binary>>, Map, Octals, Hex, Acc) ->
unescape_chars(<<$\\,Escaped,Rest/binary>>, Map, Hex, Acc) ->
case Map(Escaped) of
false -> unescape_chars(Rest, Map, Octals, Hex, <<Acc/binary, $\\, Escaped>>);
Other -> unescape_chars(Rest, Map, Octals, Hex, <<Acc/binary, Other>>)
false -> unescape_chars(Rest, Map, Hex, <<Acc/binary, $\\, Escaped>>);
Other -> unescape_chars(Rest, Map, Hex, <<Acc/binary, Other>>)
end;
unescape_chars(<<Char, Rest/binary>>, Map, Octals, Hex, Acc) ->
unescape_chars(Rest, Map, Octals, Hex, <<Acc/binary, Char>>);
unescape_chars(<<Char, Rest/binary>>, Map, Hex, Acc) ->
unescape_chars(Rest, Map, Hex, <<Acc/binary, Char>>);
unescape_chars(<<>>, _Map, _Octals, _Hex, Acc) -> Acc.
unescape_chars(<<>>, _Map, _Hex, Acc) -> Acc.
append_escaped(Rest, Map, List, Octal, Hex, Acc, Base) ->
append_escaped(Rest, Map, List, Hex, Acc, Base) ->
Codepoint = list_to_integer(List, Base),
try <<Acc/binary, Codepoint/utf8>> of
Binary -> unescape_chars(Rest, Map, Octal, Hex, Binary)
Binary -> unescape_chars(Rest, Map, Hex, Binary)
catch
error:badarg ->
Msg = <<"invalid or reserved unicode codepoint ", (integer_to_binary(Codepoint))/binary>>,
+6 -3
View File
@@ -1,6 +1,6 @@
%% Module responsible for tracking lexical information.
-module(elixir_lexical).
-export([run/2,
-export([run/3, dest/1,
record_alias/4, record_alias/2,
record_import/4, record_import/2,
record_remote/2, format_error/1
@@ -9,10 +9,10 @@
-define(tracker, 'Elixir.Kernel.LexicalTracker').
run(File, Callback) ->
run(File, Dest, Callback) ->
case code:is_loaded(?tracker) of
{file, _} ->
Pid = ?tracker:start_link(),
{ok, Pid} = ?tracker:start_link(Dest),
try
Callback(Pid)
after
@@ -24,6 +24,9 @@ run(File, Callback) ->
Callback(nil)
end.
dest(nil) -> nil;
dest(Pid) -> ?tracker:dest(Pid).
%% RECORD
record_alias(Module, Line, Warn, Ref) ->
+6 -2
View File
@@ -73,8 +73,12 @@ rewrite_clause(Else, _) -> Else.
setup(Module) ->
case code:is_loaded(?tracker) of
{file, _} -> ets:insert(Module, {?attr, ?tracker:start_link()}), ok;
false -> ok
{file, _} ->
{ok, Pid} = ?tracker:start_link(),
ets:insert(Module, {?attr, Pid}),
ok;
false ->
ok
end.
cleanup(Module) ->
+3 -3
View File
@@ -101,11 +101,11 @@ load_struct(Meta, Name, S) ->
error:undef ->
Inspected = elixir_aliases:inspect(Name),
case Context of
case Context andalso (S#elixir_scope.function == nil) of
true ->
compile_error(Meta, S#elixir_scope.file,
"cannot access struct ~ts in the same context that defines it as "
"the struct fields are not yet accessible", [Inspected]);
"cannot access struct ~ts, the struct was not yet defined or the struct is being "
"accessed in the same context that defines it", [Inspected]);
false ->
compile_error(Meta, S#elixir_scope.file, "~ts.__struct__/0 is undefined, "
"cannot expand struct ~ts", [Inspected, Inspected])
+4 -4
View File
@@ -34,7 +34,7 @@ compile(Module, Block, Vars, #{line := Line} = Env) when is_atom(Module) ->
case ?m(LexEnv, lexical_tracker) of
nil ->
elixir_lexical:run(?m(LexEnv, file), fun(Pid) ->
elixir_lexical:run(?m(LexEnv, file), nil, fun(Pid) ->
do_compile(Line, Module, Block, Vars, LexEnv#{lexical_tracker := Pid})
end);
_ ->
@@ -307,8 +307,8 @@ compile_opts(Module) ->
[] -> []
end.
load_form(Line, Forms, Opts, #{file := File} = E) ->
elixir_compiler:module(Forms, File, Opts, fun(Module, Binary0) ->
load_form(Line, Forms, Opts, E) ->
elixir_compiler:module(Forms, Opts, E, fun(Module, Binary0) ->
Docs = elixir_compiler:get_opt(docs),
Binary = add_docs_chunk(Binary0, Module, Line, Docs),
eval_callbacks(Line, Module, after_compile, [E, Binary], E),
@@ -321,7 +321,7 @@ load_form(Line, Forms, Opts, #{file := File} = E) ->
undefined -> ok;
PID ->
Ref = make_ref(),
PID ! {module_available, self(), Ref, File, Module, Binary},
PID ! {module_available, self(), Ref, ?m(E, file), Module, Binary},
receive {Ref, ack} -> ok end
end;
_ ->
+38 -30
View File
@@ -7,7 +7,7 @@ Nonterminals
add_op_eol mult_op_eol hat_op_eol two_op_eol pipe_op_eol stab_op_eol
arrow_op_eol match_op_eol when_op_eol in_op_eol in_match_op_eol
type_op_eol rel_op_eol
open_paren close_paren empty_paren
open_paren close_paren empty_paren eoe
list list_args open_bracket close_bracket
tuple open_curly close_curly
bit_string open_bit close_bit
@@ -16,11 +16,11 @@ Nonterminals
container_args_base container_args call_args_parens_base call_args_parens parens_call
call_args_no_parens_one call_args_no_parens_expr call_args_no_parens_comma_expr
call_args_no_parens_all call_args_no_parens_many call_args_no_parens_many_strict
stab stab_eol stab_expr stab_maybe_expr stab_parens_many
stab stab_eoe stab_expr stab_maybe_expr stab_parens_many
kw_eol kw_base kw call_args_no_parens_kw_expr call_args_no_parens_kw
dot_op dot_alias dot_identifier dot_op_identifier dot_do_identifier
dot_paren_identifier dot_bracket_identifier
do_block fn_eol do_eol end_eol block_eol block_item block_list
do_block fn_eoe do_eoe end_eoe block_eoe block_item block_list
.
Terminals
@@ -32,13 +32,13 @@ Terminals
comp_op at_op unary_op and_op or_op arrow_op match_op in_op in_match_op
type_op dual_op add_op mult_op hat_op two_op pipe_op stab_op when_op assoc_op
capture_op rel_op
'true' 'false' 'nil' 'do' eol ',' '.'
'true' 'false' 'nil' 'do' eol ';' ',' '.'
'(' ')' '[' ']' '{' '}' '<<' '>>' '%{}' '%'
.
Rootsymbol grammar.
%% There are two shift/reduce conflicts coming from call_args_parens.
%% Two shift/reduce conflicts coming from call_args_parens.
Expect 2.
%% Changes in ops and precedence should be reflected on lib/elixir/lib/macro.ex
@@ -58,12 +58,12 @@ Left 130 or_op_eol. %% ||, |||, or
Left 140 and_op_eol. %% &&, &&&, and
Left 150 comp_op_eol. %% ==, !=, =~, ===, !==
Left 160 rel_op_eol. %% <, >, <=, >=
Left 170 arrow_op_eol. %% < (op), (op) > (e.g |>, <<<, >>>)
Left 170 arrow_op_eol. %% < (op), (op) > (|>, <<<, >>>, ~>>, <<~, ~>, <~, <~>, <|>)
Left 180 in_op_eol. %% in
Right 200 two_op_eol. %% ++, --, .., <>
Left 210 add_op_eol. %% + (op), - (op)
Left 220 mult_op_eol. %% * (op), / (op)
Left 250 hat_op_eol. %% ^ (op) (e.g ^^^)
Left 250 hat_op_eol. %% ^ (op) (^^^)
Nonassoc 300 unary_op_eol. %% +, -, !, ^, not, ~~~
Left 310 dot_call_op.
Left 310 dot_op. %% .
@@ -72,16 +72,16 @@ Nonassoc 330 dot_identifier.
%%% MAIN FLOW OF EXPRESSIONS
grammar -> eol : nil.
grammar -> eoe : nil.
grammar -> expr_list : to_block('$1').
grammar -> eol expr_list : to_block('$2').
grammar -> expr_list eol : to_block('$1').
grammar -> eol expr_list eol : to_block('$2').
grammar -> eoe expr_list : to_block('$2').
grammar -> expr_list eoe : to_block('$1').
grammar -> eoe expr_list eoe : to_block('$2').
grammar -> '$empty' : nil.
% Note expressions are on reverse order
expr_list -> expr : ['$1'].
expr_list -> expr_list eol expr : ['$3'|'$1'].
expr_list -> expr_list eoe expr : ['$3'|'$1'].
expr -> empty_paren : nil.
expr -> matched_expr : '$1'.
@@ -202,8 +202,12 @@ access_expr -> bracket_expr : '$1'.
access_expr -> at_op_eol number : build_unary_op('$1', ?exprs('$2')).
access_expr -> unary_op_eol number : build_unary_op('$1', ?exprs('$2')).
access_expr -> capture_op_eol number : build_unary_op('$1', ?exprs('$2')).
access_expr -> fn_eol stab end_eol : build_fn('$1', build_stab(reverse('$2'))).
access_expr -> fn_eoe stab end_eoe : build_fn('$1', build_stab(reverse('$2'))).
access_expr -> open_paren stab close_paren : build_stab(reverse('$2')).
access_expr -> open_paren stab ';' close_paren : build_stab(reverse('$2')).
access_expr -> open_paren ';' stab ';' close_paren : build_stab(reverse('$3')).
access_expr -> open_paren ';' stab close_paren : build_stab(reverse('$3')).
access_expr -> open_paren ';' close_paren : build_stab([]).
access_expr -> number : ?exprs('$1').
access_expr -> signed_number : {element(4, '$1'), meta('$1'), ?exprs('$1')}.
access_expr -> list : element(1, '$1').
@@ -240,28 +244,32 @@ bracket_at_expr -> at_op_eol access_expr bracket_arg :
%% Blocks
do_block -> do_eol 'end' : [[{do,nil}]].
do_block -> do_eol stab end_eol : [[{do, build_stab(reverse('$2'))}]].
do_block -> do_eol block_list 'end' : [[{do, nil}|'$2']].
do_block -> do_eol stab_eol block_list 'end' : [[{do, build_stab(reverse('$2'))}|'$3']].
do_block -> do_eoe 'end' : [[{do,nil}]].
do_block -> do_eoe stab end_eoe : [[{do, build_stab(reverse('$2'))}]].
do_block -> do_eoe block_list 'end' : [[{do, nil}|'$2']].
do_block -> do_eoe stab_eoe block_list 'end' : [[{do, build_stab(reverse('$2'))}|'$3']].
fn_eol -> 'fn' : '$1'.
fn_eol -> 'fn' eol : '$1'.
eoe -> eol : '$1'.
eoe -> ';' : '$1'.
eoe -> eol ';' : '$1'.
do_eol -> 'do' : '$1'.
do_eol -> 'do' eol : '$1'.
fn_eoe -> 'fn' : '$1'.
fn_eoe -> 'fn' eoe : '$1'.
end_eol -> 'end' : '$1'.
end_eol -> eol 'end' : '$2'.
do_eoe -> 'do' : '$1'.
do_eoe -> 'do' eoe : '$1'.
block_eol -> block_identifier : '$1'.
block_eol -> block_identifier eol : '$1'.
end_eoe -> 'end' : '$1'.
end_eoe -> eoe 'end' : '$2'.
block_eoe -> block_identifier : '$1'.
block_eoe -> block_identifier eoe : '$1'.
stab -> stab_expr : ['$1'].
stab -> stab eol stab_expr : ['$3'|'$1'].
stab -> stab eoe stab_expr : ['$3'|'$1'].
stab_eol -> stab : '$1'.
stab_eol -> stab eol : '$1'.
stab_eoe -> stab : '$1'.
stab_eoe -> stab eoe : '$1'.
stab_expr -> expr : '$1'.
stab_expr -> stab_op_eol stab_maybe_expr : build_op('$1', [], '$2').
@@ -275,8 +283,8 @@ stab_expr -> stab_parens_many when_op expr stab_op_eol stab_maybe_expr :
stab_maybe_expr -> 'expr' : '$1'.
stab_maybe_expr -> '$empty' : nil.
block_item -> block_eol stab_eol : {?exprs('$1'), build_stab(reverse('$2'))}.
block_item -> block_eol : {?exprs('$1'), nil}.
block_item -> block_eoe stab_eoe : {?exprs('$1'), build_stab(reverse('$2'))}.
block_item -> block_eoe : {?exprs('$1'), nil}.
block_list -> block_item : ['$1'].
block_list -> block_item block_list : ['$1'|'$2'].
+36 -2
View File
@@ -151,6 +151,10 @@ escape(Expr, Unquote) ->
%% Quotes an expression and return its quoted Elixir AST.
quote({unquote_splicing, _, [_]}, _Binding, #elixir_quote{unquote=true}, _) ->
argument_error(<<"unquote_splicing only works inside arguments and block contexts, "
"wrap it in parens if you want it to work with one-liners">>);
quote(Expr, nil, Q, E) ->
do_quote(Expr, Q, E);
@@ -242,6 +246,15 @@ do_quote({Left, Right}, Q, E) ->
{TRight, RQ} = do_quote(Right, LQ, E),
{{TLeft, TRight}, RQ};
do_quote(BitString, #elixir_quote{escape=true} = Q, _) when is_bitstring(BitString) ->
case bit_size(BitString) rem 8 of
0 ->
{BitString, Q};
Size ->
<<Bits:Size, Bytes/binary>> = BitString,
{{'<<>>', [], [{'::', [], [Bits, Size]}, Bytes]}, Q}
end;
do_quote(Map, #elixir_quote{escape=true} = Q, E) when is_map(Map) ->
{TT, TQ} = do_quote(maps:to_list(Map), Q, E),
{{'%{}', [], TT}, TQ};
@@ -251,7 +264,7 @@ do_quote(Tuple, #elixir_quote{escape=true} = Q, E) when is_tuple(Tuple) ->
{{'{}', [], TT}, TQ};
do_quote(List, #elixir_quote{escape=true} = Q, E) when is_list(List) ->
% The improper case is pretty inefficient, but improper lists are are.
%% The improper case is a bit inefficient, but improper lists are rare.
case reverse_improper(List) of
{L} -> do_splice(L, Q, E);
{L, R} ->
@@ -259,14 +272,35 @@ do_quote(List, #elixir_quote{escape=true} = Q, E) when is_list(List) ->
{TR, QR} = do_quote(R, QL, E),
{update_last(TL, fun(X) -> {'|', [], [X, TR]} end), QR}
end;
do_quote(Other, #elixir_quote{escape=true} = Q, _)
when is_number(Other); is_pid(Other); is_atom(Other) ->
{Other, Q};
do_quote(Fun, #elixir_quote{escape=true} = Q, _) when is_function(Fun) ->
case (erlang:fun_info(Fun, env) == {env, []}) andalso
(erlang:fun_info(Fun, type) == {type, external}) of
true -> {Fun, Q};
false -> bad_escape(Fun)
end;
do_quote(Other, #elixir_quote{escape=true}, _) ->
bad_escape(Other);
do_quote(List, Q, E) when is_list(List) ->
do_splice(lists:reverse(List), Q, E);
do_splice(lists:reverse(List), Q, E);
do_quote(Other, Q, _) ->
{Other, Q}.
%% Quote helpers
bad_escape(Arg) ->
Msg = <<"cannot escape ", ('Elixir.Kernel':inspect(Arg, []))/binary, ". ",
"The supported values are: lists, tuples, maps, atoms, numbers, bitstrings, ",
"pids and remote functions in the format &Mod.fun/arity">>,
error('Elixir.ArgumentError':exception([{message,Msg}])).
do_quote_call(Left, Meta, Expr, Args, Q, E) ->
All = [meta(Meta, Q), Left, {unquote, Meta, [Expr]}, Args,
Q#elixir_quote.context],
+52 -96
View File
@@ -33,10 +33,16 @@
-define(arrow_op3(T1, T2, T3),
T1 == $<, T2 == $<, T3 == $<;
T1 == $>, T2 == $>, T3 == $>).
T1 == $>, T2 == $>, T3 == $>;
T1 == $~, T2 == $>, T3 == $>;
T1 == $<, T2 == $<, T3 == $~;
T1 == $<, T2 == $~, T3 == $>;
T1 == $<, T2 == $|, T3 == $>).
-define(arrow_op(T1, T2),
T1 == $|, T2 == $>).
T1 == $|, T2 == $>;
T1 == $~, T2 == $>;
T1 == $<, T2 == $~).
-define(rel_op(T),
T == $<;
@@ -80,7 +86,7 @@
-define(type_op(T1, T2),
T1 == $:, T2 == $:).
-define(pipe_op(T1),
-define(pipe_op(T),
T == $|).
tokenize(String, Line, #elixir_tokenizer{} = Scope) ->
@@ -130,28 +136,6 @@ tokenize([$0,$o,H|T], Line, Scope, Tokens) when ?is_octal(H) ->
{Rest, Number} = tokenize_octal([H|T], []),
tokenize(Rest, Line, Scope, [{number, Line, Number}|Tokens]);
%% Deprecated
tokenize([$0,$X,H|T], Line, Scope, Tokens) when ?is_hex(H) ->
io:format("~ts:~w: warning: 0X for hexadecimals is deprecated, please use 0x instead~n",
[Scope#elixir_tokenizer.file, Line]),
{Rest, Number} = tokenize_hex([H|T], []),
tokenize(Rest, Line, Scope, [{number, Line, Number}|Tokens]);
tokenize([$0,$B,H|T], Line, Scope, Tokens) when ?is_bin(H) ->
io:format("~ts:~w: warning: 0B for binaries is deprecated, please use 0b instead~n",
[Scope#elixir_tokenizer.file, Line]),
{Rest, Number} = tokenize_bin([H|T], []),
tokenize(Rest, Line, Scope, [{number, Line, Number}|Tokens]);
tokenize([$0,H|T], Line, Scope, Tokens) when ?is_octal(H) ->
io:format("~ts:~w: warning: octals starting with leading 0 is deprecated, please use 0o instead, got octal start with: ~ts~n",
[Scope#elixir_tokenizer.file, Line, <<$0, H>>]),
{Rest, Number} = tokenize_octal([H|T], []),
tokenize(Rest, Line, Scope, [{number, Line, Number}|Tokens]);
%% End of deprecated
% Comments
tokenize([$#|String], Line, Scope, Tokens) ->
@@ -221,73 +205,19 @@ tokenize([$?,$\\,$x,A|T], Line, Scope, Tokens)
Char = escape_char([$\\,$x,A]),
tokenize(T, Line, Scope, [{number, Line, Char}|Tokens]);
%% Deprecated
tokenize([$?,$\\,$X,${,A,B,C,D,E,F,$}|T], Line, Scope, Tokens)
when ?is_hex(A), ?is_hex(B), ?is_hex(C), ?is_hex(D), ?is_hex(E), ?is_hex(F) ->
Char = escape_char([$\\,$X,${,A,B,C,D,E,F,$}]),
tokenize(T, Line, Scope, [{number, Line, Char}|Tokens]);
tokenize([$?,$\\,$X,${,A,B,C,D,E,$}|T], Line, Scope, Tokens)
when ?is_hex(A), ?is_hex(B), ?is_hex(C), ?is_hex(D), ?is_hex(E) ->
Char = escape_char([$\\,$X,${,A,B,C,D,E,$}]),
tokenize(T, Line, Scope, [{number, Line, Char}|Tokens]);
tokenize([$?,$\\,$X,${,A,B,C,D,$}|T], Line, Scope, Tokens)
when ?is_hex(A), ?is_hex(B), ?is_hex(C), ?is_hex(D) ->
Char = escape_char([$\\,$X,${,A,B,C,D,$}]),
tokenize(T, Line, Scope, [{number, Line, Char}|Tokens]);
tokenize([$?,$\\,$X,${,A,B,C,$}|T], Line, Scope, Tokens)
when ?is_hex(A), ?is_hex(B), ?is_hex(C) ->
Char = escape_char([$\\,$X,${,A,B,C,$}]),
tokenize(T, Line, Scope, [{number, Line, Char}|Tokens]);
tokenize([$?,$\\,$X,${,A,B,$}|T], Line, Scope, Tokens)
when ?is_hex(A), ?is_hex(B) ->
Char = escape_char([$\\,$X,${,A,B,$}]),
tokenize(T, Line, Scope, [{number, Line, Char}|Tokens]);
tokenize([$?,$\\,$X,${,A,$}|T], Line, Scope, Tokens)
when ?is_hex(A) ->
Char = escape_char([$\\,$X,${,A,$}]),
tokenize(T, Line, Scope, [{number, Line, Char}|Tokens]);
tokenize([$?,$\\,$X,A,B|T], Line, Scope, Tokens)
when ?is_hex(A), ?is_hex(B) ->
Char = escape_char([$\\,$X,A,B]),
tokenize(T, Line, Scope, [{number, Line, Char}|Tokens]);
tokenize([$?,$\\,$X,A|T], Line, Scope, Tokens)
when ?is_hex(A) ->
Char = escape_char([$\\,$X,A]),
tokenize(T, Line, Scope, [{number, Line, Char}|Tokens]);
tokenize([$?,$\\,A,B,C|T], Line, Scope, Tokens)
when ?is_octal(A), A =< $3,?is_octal(B), ?is_octal(C) ->
Char = escape_char([$\\,A,B,C]),
tokenize(T, Line, Scope, [{number, Line, Char}|Tokens]);
tokenize([$?,$\\,A,B|T], Line, Scope, Tokens)
when ?is_octal(A), ?is_octal(B) ->
Char = escape_char([$\\,A,B]),
tokenize(T, Line, Scope, [{number, Line, Char}|Tokens]);
tokenize([$?,$\\,$0|T], Line, Scope, Tokens) ->
tokenize(T, Line, Scope, [{number, Line, 0}|Tokens]);
tokenize([$?,$\\,A|T], Line, Scope, Tokens)
when ?is_octal(A) ->
Char = escape_char([$\\,A]),
tokenize(T, Line, Scope, [{number, Line, Char}|Tokens]);
%% End of deprecation
tokenize([$?,$\\,H|T], Line, Scope, Tokens) ->
Char = elixir_interpolation:unescape_map(H),
tokenize(T, Line, Scope, [{number, Line, Char}|Tokens]);
tokenize([$?,Char|T], Line, Scope, Tokens) ->
case handle_char(Char) of
{Escape, Name} ->
Msg = io_lib:format("found ? followed by codepoint 0x~.16B (~ts), please use ~ts instead",
[Char, Name, Escape]),
elixir_errors:warn(Line, Scope#elixir_tokenizer.file, Msg);
false ->
ok
end,
tokenize(T, Line, Scope, [{number, Line, Char}|Tokens]);
% Heredocs
@@ -375,10 +305,19 @@ tokenize([$:,T|Rest], Line, Scope, Tokens) when
% End of line
tokenize(";" ++ Rest, Line, Scope, []) ->
tokenize(Rest, Line, Scope, eol(Line, ';', []));
tokenize(Rest, Line, Scope, [{';', Line}]);
tokenize(";" ++ Rest, Line, Scope, [Top|Tokens]) when element(1, Top) /= eol ->
tokenize(Rest, Line, Scope, eol(Line, ';', [Top|Tokens]));
tokenize(";" ++ Rest, Line, Scope, [Top|_] = Tokens) when element(1, Top) /= ';' ->
tokenize(Rest, Line, Scope, [{';', Line}|Tokens]);
tokenize("\\" = Original, Line, _Scope, Tokens) ->
{error, {Line, "invalid escape \\ at end of file", []}, Original, Tokens};
tokenize("\\\n" = Original, Line, _Scope, Tokens) ->
{error, {Line, "invalid escape \\ at end of file", []}, Original, Tokens};
tokenize("\\\r\n" = Original, Line, _Scope, Tokens) ->
{error, {Line, "invalid escape \\ at end of file", []}, Original, Tokens};
tokenize("\\\n" ++ Rest, Line, Scope, Tokens) ->
tokenize(Rest, Line + 1, Scope, Tokens);
@@ -387,10 +326,10 @@ tokenize("\\\r\n" ++ Rest, Line, Scope, Tokens) ->
tokenize(Rest, Line + 1, Scope, Tokens);
tokenize("\n" ++ Rest, Line, Scope, Tokens) ->
tokenize(Rest, Line + 1, Scope, eol(Line, newline, Tokens));
tokenize(Rest, Line + 1, Scope, eol(Line, Tokens));
tokenize("\r\n" ++ Rest, Line, Scope, Tokens) ->
tokenize(Rest, Line + 1, Scope, eol(Line, newline, Tokens));
tokenize(Rest, Line + 1, Scope, eol(Line, Tokens));
% Stand-alone tokens
@@ -534,6 +473,10 @@ tokenize([H|_] = String, Line, Scope, Tokens) when ?is_downcase(H); H == $_ ->
tokenize([T|Rest], Line, Scope, Tokens) when ?is_horizontal_space(T) ->
handle_space_sensitive_tokens(strip_horizontal_space(Rest), Line, Scope, Tokens);
tokenize([T|Rest] = Original, Line, _Scope, Tokens) when ?is_invalid_space(T) ->
Message = io_lib:format("invalid space character U+~.16B before: ", [T]),
{error, {Line, lists:flatten(Message), until_eol(Rest)}, Original, Tokens};
tokenize(T, Line, _Scope, Tokens) ->
{error, {Line, "invalid token: ", until_eol(T)}, T, Tokens}.
@@ -554,6 +497,18 @@ until_eol("\n" ++ _) -> [];
until_eol([]) -> [];
until_eol([H|T]) -> [H|until_eol(T)].
handle_char(7) -> {"\\a", "alert"};
handle_char($\b) -> {"\\b", "backspace"};
handle_char($\d) -> {"\\d", "delete"};
handle_char($\e) -> {"\\e", "escape"};
handle_char($\f) -> {"\\f", "form feed"};
handle_char($\n) -> {"\\n", "newline"};
handle_char($\r) -> {"\\r", "carriage return"};
handle_char($\s) -> {"\\s", "space"};
handle_char($\t) -> {"\\t", "tab"};
handle_char($\v) -> {"\\v", "vertical tab"};
handle_char(_) -> false.
escape_char(List) ->
<<Char/utf8>> = elixir_interpolation:unescape_chars(list_to_binary(List)),
Char.
@@ -662,9 +617,10 @@ handle_space_sensitive_tokens(String, Line, Scope, Tokens) ->
%% Helpers
eol(_Line, _Mod, [{',',_}|_] = Tokens) -> Tokens;
eol(_Line, _Mod, [{eol,_,_}|_] = Tokens) -> Tokens;
eol(Line, Mod, Tokens) -> [{eol,Line,Mod}|Tokens].
eol(_Line, [{';',_}|_] = Tokens) -> Tokens;
eol(_Line, [{',',_}|_] = Tokens) -> Tokens;
eol(_Line, [{eol,_}|_] = Tokens) -> Tokens;
eol(Line, Tokens) -> [{eol,Line}|Tokens].
unsafe_to_atom(Part, Line, #elixir_tokenizer{}) when
is_binary(Part) andalso size(Part) > 255;
@@ -891,7 +847,7 @@ check_call_identifier(_Kind, Line, Atom, [$(|_]) -> {paren_identifier, Line, Ato
check_call_identifier(_Kind, Line, Atom, [$[|_]) -> {bracket_identifier, Line, Atom};
check_call_identifier(Kind, Line, Atom, _Rest) -> {Kind, Line, Atom}.
add_token_with_nl(Left, [{eol,_,newline}|T]) -> [Left|T];
add_token_with_nl(Left, [{eol,_}|T]) -> [Left|T];
add_token_with_nl(Left, T) -> [Left|T].
%% Error handling
+2 -4
View File
@@ -195,18 +195,16 @@ translate({Name, Meta, Args}, S) when is_atom(Name), is_list(Meta), is_list(Args
if
S#elixir_scope.context == match ->
compile_error(Meta, S#elixir_scope.file,
"cannot invoke function ~ts/~B inside match", [Name, length(Args)]);
"cannot invoke local ~ts/~B inside match", [Name, length(Args)]);
S#elixir_scope.context == guard ->
Arity = length(Args),
File = S#elixir_scope.file,
case Arity of
0 -> compile_error(Meta, File, "unknown variable ~ts or cannot invoke "
"function ~ts/~B inside guard", [Name, Name, Arity]);
"local ~ts/~B inside guard", [Name, Name, Arity]);
_ -> compile_error(Meta, File, "cannot invoke local ~ts/~B inside guard",
[Name, Arity])
end;
S#elixir_scope.function == nil ->
compile_error(Meta, S#elixir_scope.file, "undefined function ~ts/~B", [Name, length(Args)]);
true ->
Line = ?line(Meta),
{TArgs, NS} = translate_args(Args, S),
-2
View File
@@ -381,8 +381,6 @@ defmodule DictTest.Common do
dict = new_dict([{1}, {2}, {3}], fn {x} -> {<<x + 64>>, x} end)
assert Dict.size(dict) == 3
assert Enum.sort(dict) == [{"A", 1}, {"B", 2}, {"C", 3}]
assert Collectable.empty(new_dict) == new_dict([])
end
test "is zippable" do
-10
View File
@@ -189,11 +189,6 @@ defmodule EnumTest.List do
assert Enum.into([a: 1, b: 2], %{c: 3}) == %{a: 1, b: 2, c: 3}
assert Enum.into(%{a: 1, b: 2}, []) == [a: 1, b: 2]
assert Enum.into([1, 2, 3], "numbers: ", &to_string/1) == "numbers: 123"
assert Enum.into([1, 2, 3], fn
func, {:cont, x} when is_function(func) -> [x]
list, {:cont, x} -> [x|list]
list, _ -> list
end) == [3, 2, 1]
end
test :intersperse do
@@ -368,11 +363,6 @@ defmodule EnumTest.List do
assert Enum.to_list(1 .. 3) == [1, 2, 3]
end
test :traverse do
assert Enum.traverse([1, 2, 3], &(&1 * &1)) == [1, 4, 9]
assert Enum.traverse(%{a: 1, b: 2}, fn {k, v} -> {k, v*2} end) == %{a: 2, b: 4}
end
test :uniq do
assert Enum.uniq([1, 2, 3, 2, 1]) == [1, 2, 3]
assert Enum.uniq([1, 2, 3, 2, 1], fn x -> x end) == [1, 2, 3]
+3 -1
View File
@@ -308,8 +308,10 @@ defmodule Kernel.ExceptionTest do
test "undefined function message" do
assert %UndefinedFunctionError{} |> message == "undefined function"
assert %UndefinedFunctionError{module: Kernel, function: :bar, arity: 1} |> message ==
"undefined function: Kernel.bar/1"
assert %UndefinedFunctionError{module: Foo, function: :bar, arity: 1} |> message ==
"undefined function: Foo.bar/1"
"undefined function: Foo.bar/1 (module Foo is not available)"
assert %UndefinedFunctionError{module: nil, function: :bar, arity: 0} |> message ==
"undefined function: nil.bar/0"
end
@@ -1 +1,6 @@
defmodule CompileSample, do: nil
defmodule CompileSample, do: nil
# Assert that modules compiled are loaded into memory
# with the proper destination for the BEAM files.
dest = __DIR__ |> Path.join("../../../tmp/#{CompileSample}.beam") |> Path.expand
^dest = CompileSample |> :code.which() |> List.to_string
@@ -0,0 +1,297 @@
Code.require_file "../test_helper.exs", __DIR__
defmodule GenEvent.StreamTest do
use ExUnit.Case, async: true
defmodule SlowHandler do
use GenEvent
def handle_event(_event, _state) do
:timer.sleep(100)
:remove_handler
end
end
defp notify(:async, manager, event), do: GenEvent.notify(manager, event)
defp notify(:sync, manager, event), do: GenEvent.sync_notify(manager, event)
defp notify(:ack, manager, event), do: GenEvent.ack_notify(manager, event)
@receive_timeout 1000
test "ack stream/2" do
{:ok, pid} = GenEvent.start_link()
parent = self()
spawn_link fn ->
send parent, Enum.take(GenEvent.stream(pid), 3)
end
wait_for_handlers(pid, 1)
for i <- 1..3 do
GenEvent.ack_notify(pid, i)
end
assert_receive [1, 2, 3], @receive_timeout
wait_for_handlers(pid, 0)
spawn_link fn ->
Enum.each GenEvent.stream(pid), fn _ ->
:timer.sleep(:infinity)
end
end
wait_for_handlers(pid, 1)
for i <- 1..6 do
spawn_link fn ->
GenEvent.ack_notify(pid, i)
end
end
# Note the length is 4 compared to 5 n the sync case.
wait_for_queue_length(pid, 4)
end
test "sync stream/2" do
{:ok, pid} = GenEvent.start_link()
parent = self()
spawn_link fn ->
send parent, Enum.take(GenEvent.stream(pid), 3)
end
wait_for_handlers(pid, 1)
for i <- 1..3 do
GenEvent.sync_notify(pid, i)
end
assert_receive [1, 2, 3], @receive_timeout
wait_for_handlers(pid, 0)
spawn_link fn ->
Enum.each GenEvent.stream(pid), fn _ ->
:timer.sleep(:infinity)
end
end
wait_for_handlers(pid, 1)
for i <- 1..6 do
spawn_link fn ->
GenEvent.sync_notify(pid, i)
end
end
wait_for_queue_length(pid, 5)
end
test "async stream/2" do
{:ok, pid} = GenEvent.start_link()
parent = self()
spawn_link fn ->
Enum.each(GenEvent.stream(pid), fn _ ->
:timer.sleep(:infinity)
end)
end
spawn_link fn ->
send parent, Enum.take(GenEvent.stream(pid), 3)
end
wait_for_handlers(pid, 2)
for i <- 1..3 do
GenEvent.notify(pid, i)
end
assert_receive [1, 2, 3], @receive_timeout
wait_for_handlers(pid, 1)
end
test "stream/2 with timeout" do
{:ok, pid} = GenEvent.start_link()
Process.flag(:trap_exit, true)
child = spawn_link fn ->
Enum.take(GenEvent.stream(pid, timeout: 50), 5)
end
assert_receive {:EXIT, ^child,
{:timeout, {Enumerable.GenEvent.Stream, :next, [_, _]}}}, @receive_timeout
end
test "stream/2 with error/timeout on subscription" do
{:ok, pid} = GenEvent.start_link()
child = spawn fn -> Enum.to_list(GenEvent.stream(pid)) end
wait_for_handlers(pid, 1)
Process.exit(child, :kill)
wait_for_handlers(pid, 0)
GenEvent.stop(pid)
end
test "stream/2 with manager killed and trap_exit" do
{:ok, pid} = GenEvent.start_link()
stream = GenEvent.stream(pid)
parent = self()
child = spawn_link fn ->
send parent, Enum.to_list(stream)
end
wait_for_handlers(pid, 1)
Process.flag(:trap_exit, true)
Process.exit(pid, :kill)
assert_receive {:EXIT, ^pid, :killed}, @receive_timeout
assert_receive ({:EXIT, ^child, {stat, {Enumerable.GenEvent.Stream, :stop, [_,_]}}}
when stat in [:killed, :noproc]), @receive_timeout
end
test "stream/2 with manager not alive" do
stream = GenEvent.stream(:does_not_exit)
parent = self()
child = spawn_link fn ->
send parent, Enum.to_list(stream)
end
Process.flag(:trap_exit, true)
assert_receive {:EXIT, ^child,
{:noproc, {Enumerable.GenEvent.Stream, :start, [_]}}}, @receive_timeout
end
Enum.each [:sync, :async, :ack], fn mode ->
@mode mode
test "#{mode} stream/2 with parallel use (and first finishing first)" do
{:ok, pid} = GenEvent.start_link()
stream = GenEvent.stream(pid)
parent = self()
spawn_link fn -> send parent, {:take3, Enum.take(stream, 3)} end
wait_for_handlers(pid, 1)
spawn_link fn -> send parent, {:take5, Enum.take(stream, 5)} end
wait_for_handlers(pid, 2)
# Notify the events for both handlers
for i <- 1..3 do
notify(@mode, pid, i)
end
assert_receive {:take3, [1, 2, 3]}, @receive_timeout
# Notify the events for to_list stream handler
for i <- 4..5 do
notify(@mode, pid, i)
end
assert_receive {:take5, [1, 2, 3, 4, 5]}, @receive_timeout
end
test "#{mode} stream/2 with manager stop" do
{:ok, pid} = GenEvent.start_link()
parent = self()
child = spawn_link fn ->
send parent, Enum.take(GenEvent.stream(pid), 5)
end
wait_for_handlers(pid, 1)
for i <- 1..3 do
notify(@mode, pid, i)
end
Process.flag(:trap_exit, true)
GenEvent.stop(pid)
assert_receive {:EXIT, ^child, :normal}, @receive_timeout
end
test "#{mode} stream/2 with swap_handler" do
{:ok, pid} = GenEvent.start_link()
stream = GenEvent.stream(pid, id: make_ref())
parent = self()
spawn_link(fn -> send parent, Enum.take(stream, 5) end)
wait_for_handlers(pid, 1)
for i <- 1..3 do
notify(@mode, pid, i)
end
[handler] = GenEvent.which_handlers(pid)
GenEvent.swap_handler(pid, handler, :swap_handler, UnknownHandler, [])
assert_receive [1, 2, 3], @receive_timeout
end
test "#{mode} stream/2 with manager unregistered" do
{:ok, pid} = GenEvent.start_link(name: :unreg)
stream = GenEvent.stream(:unreg)
parent = self()
spawn_link fn ->
send parent, Enum.take(stream, 5)
end
wait_for_handlers(pid, 1)
# Notify the events
for i <- 1..3 do
notify(@mode, pid, i)
end
# Unregister the process
Process.unregister(:unreg)
# Notify the remaining events
for i <- 4..5 do
notify(@mode, pid, i)
end
# We should have gotten the message and all handlers were removed
assert_receive [1, 2, 3, 4, 5], @receive_timeout
wait_for_handlers(pid, 0)
end
test "#{mode} stream/2 flushes events on abort" do
{:ok, pid} = GenEvent.start_link()
spawn_link fn ->
wait_for_handlers(pid, 2)
notify(@mode, pid, 1)
notify(@mode, pid, 2)
notify(@mode, pid, 3)
end
GenEvent.add_handler(pid, SlowHandler, [])
stream = GenEvent.stream(pid)
try do
Enum.each stream, fn _ -> throw :done end
catch
:done -> :ok
end
# Wait for the slow handler to be removed
# so all events have been handled
wait_for_handlers(pid, 0)
# Check no messages leaked.
refute_received _any
end
end
defp wait_for_handlers(pid, count) do
unless length(GenEvent.which_handlers(pid)) == count do
wait_for_handlers(pid, count)
end
end
defp wait_for_queue_length(pid, count) do
{:message_queue_len, n} = Process.info(pid, :message_queue_len)
unless n == count do
wait_for_queue_length(pid, count)
end
end
end
+428 -333
View File
@@ -1,403 +1,498 @@
Code.require_file "test_helper.exs", __DIR__
defmodule GenEventTest do
use ExUnit.Case, async: true
use ExUnit.Case
defmodule LoggerHandler do
defmodule ReplyHandler do
use GenEvent
def handle_event({:log, x}, messages) do
{:ok, [x|messages]}
def init(:raise) do
raise "oops"
end
def handle_call(:messages, messages) do
{:ok, Enum.reverse(messages), []}
def init({:throw, process}) do
{:ok, process}
end
def handle_call(call, state) do
super(call, state)
def init({:raise, _}) do
raise "oops"
end
def init({:swap, {:error, :not_found}}) do
{:error, :not_found_on_swap}
end
def init({:swap, parent}) when is_pid(parent) do
send parent, :swapped
{:ok, parent}
end
def init({:custom, return}) do
return
end
def init({parent, :hibernate}) do
{:ok, parent, :hibernate}
end
def init({parent, trap}) when is_pid(parent) and is_boolean(trap) do
Process.flag(:trap_exit, trap)
{:ok, parent}
end
def handle_event(:raise, _parent) do
raise "oops"
end
def handle_event(:hibernate, parent) do
{:ok, parent, :hibernate}
end
def handle_event({:custom, reply}, _parent) do
reply
end
def handle_event(event, parent) do
send parent, {:event, event}
{:ok, parent}
end
def handle_call(:raise, _parent) do
raise "oops"
end
def handle_call(:hibernate, parent) do
{:ok, :ok, parent, :hibernate}
end
def handle_call({:custom, reply}, _parent) do
reply
end
def handle_call(event, parent) do
send parent, {:call, event}
{:ok, :ok, parent}
end
def handle_info(:hibernate, parent) do
{:ok, parent, :hibernate}
end
def handle_info(event, parent) do
send parent, {:info, event}
{:ok, parent}
end
def terminate(:raise, _parent) do
raise "oops"
end
def terminate(:swapped, parent) do
send parent, {:terminate, :swapped}
parent
end
def terminate(arg, parent) do
send parent, {:terminate, arg}
end
end
defmodule SlowHandler do
defmodule DefaultHandler do
use GenEvent
end
def handle_event(_event, _state) do
:timer.sleep(100)
:remove_handler
defmodule Via do
def register_name(name, pid) do
Process.register(pid, name)
:yes
end
def whereis_name(name) do
Process.whereis(name) || :undefined
end
end
@receive_timeout 1000
test "start_link/2 and handler workflow" do
test "start/1" do
assert {:ok, pid} = GenEvent.start()
assert GenEvent.which_handlers(pid) == []
assert GenEvent.stop(pid) == :ok
assert {:ok, pid} = GenEvent.start(name: :my_gen_event_name)
assert GenEvent.which_handlers(:my_gen_event_name) == []
assert GenEvent.which_handlers(pid) == []
assert GenEvent.stop(:my_gen_event_name) == :ok
end
test "start_link/1" do
assert {:ok, pid} = GenEvent.start_link()
assert GenEvent.which_handlers(pid) == []
assert GenEvent.stop(pid) == :ok
assert {:ok, pid} = GenEvent.start_link(name: :my_gen_event_name)
assert GenEvent.which_handlers(:my_gen_event_name) == []
assert GenEvent.which_handlers(pid) == []
assert GenEvent.stop(:my_gen_event_name) == :ok
assert {:ok, pid} = GenEvent.start_link(name: {:global, :my_gen_event_name})
assert GenEvent.which_handlers({:global, :my_gen_event_name}) == []
assert GenEvent.which_handlers(pid) == []
assert GenEvent.stop({:global, :my_gen_event_name}) == :ok
assert {:ok, pid} = GenEvent.start_link(name: {:via, Via, :my_gen_event_name})
assert GenEvent.which_handlers({:via, Via, :my_gen_event_name}) == []
assert GenEvent.which_handlers(pid) == []
assert GenEvent.stop({:via, Via, :my_gen_event_name}) == :ok
assert {:ok, pid} = GenEvent.start_link(name: :my_gen_event_name)
assert GenEvent.start_link(name: :my_gen_event_name) ==
{:error, {:already_started, pid}}
end
test "handles exit signals" do
Process.flag(:trap_exit, true)
# Terminates on signal from parent when not trapping exits
{:ok, pid} = GenEvent.start_link()
:ok = GenEvent.add_handler(pid, ReplyHandler, {self(), false})
Process.exit(pid, :shutdown)
assert_receive {:EXIT, ^pid, :shutdown}
refute_received {:terminate, _}
{:links, links} = Process.info(self, :links)
assert pid in links
# Terminates on signal from parent when trapping exits
{:ok, pid} = GenEvent.start_link()
:ok = GenEvent.add_handler(pid, ReplyHandler, {self(), true})
Process.exit(pid, :shutdown)
assert_receive {:EXIT, ^pid, :shutdown}
assert_receive {:terminate, :stop}
assert GenEvent.notify(pid, {:log, 0}) == :ok
assert GenEvent.add_handler(pid, LoggerHandler, []) == :ok
assert GenEvent.notify(pid, {:log, 1}) == :ok
assert GenEvent.notify(pid, {:log, 2}) == :ok
# Terminates on signal not from parent when not trapping exits
{:ok, pid} = GenEvent.start_link()
:ok = GenEvent.add_handler(pid, ReplyHandler, {self(), false})
spawn fn -> Process.exit(pid, :shutdown) end
assert_receive {:EXIT, ^pid, :shutdown}
refute_received {:terminate, _}
assert GenEvent.call(pid, LoggerHandler, :messages) == [1, 2]
assert GenEvent.call(pid, LoggerHandler, :messages) == []
assert GenEvent.remove_handler(pid, LoggerHandler, []) == :ok
assert GenEvent.stop(pid) == :ok
# Does not terminate on signal not from parent when trapping exits
{:ok, pid} = GenEvent.start_link()
:ok = GenEvent.add_handler(pid, ReplyHandler, {self(), true})
terminator = spawn fn -> Process.exit(pid, :shutdown) end
assert_receive {:info, {:EXIT, ^terminator, :shutdown}}
refute_received {:terminate, _}
end
test "start/2 with linked handler" do
test "hibernates" do
{:ok, pid} = GenEvent.start()
:ok = GenEvent.add_handler(pid, ReplyHandler, {self(), :hibernate})
wait_until fn -> hibernating?(pid) end
wake_up(pid)
refute hibernating?(pid)
:ok = GenEvent.call(pid, ReplyHandler, :hibernate)
wait_until fn -> hibernating?(pid) end
wake_up(pid)
:ok = GenEvent.sync_notify(pid, :hibernate)
wait_until fn -> hibernating?(pid) end
GenEvent.stop(pid)
end
test "add_handler/3" do
{:ok, pid} = GenEvent.start()
{:links, links} = Process.info(self, :links)
refute pid in links
assert GenEvent.add_handler(pid, ReplyHandler, {:custom, {:error, :my_error}}) ==
{:error, :my_error}
assert GenEvent.add_handler(pid, ReplyHandler, {:custom, :oops}) ==
{:error, {:bad_return_value, :oops}}
assert GenEvent.add_handler(pid, LoggerHandler, [], link: true) == :ok
assert {:error, {%RuntimeError{}, _}} =
GenEvent.add_handler(pid, ReplyHandler, :raise)
{:links, links} = Process.info(self, :links)
assert pid in links
assert GenEvent.add_handler(pid, ReplyHandler, {:throw, self()}) == :ok
assert GenEvent.which_handlers(pid) == [ReplyHandler]
assert GenEvent.add_handler(pid, ReplyHandler, {:throw, self()}) == {:error, :already_present}
assert GenEvent.notify(pid, {:log, 1}) == :ok
assert GenEvent.sync_notify(pid, {:log, 2}) == :ok
assert GenEvent.call(pid, LoggerHandler, :messages) == [1, 2]
assert GenEvent.stop(pid) == :ok
assert GenEvent.add_handler(pid, {ReplyHandler, self()}, {self(), false}) == :ok
assert GenEvent.which_handlers(pid) == [{ReplyHandler, self()}, ReplyHandler]
end
test "start/2 with linked swap" do
test "add_mon_handler/3" do
{:ok, pid} = GenEvent.start()
parent = self()
{mon_pid, mon_ref} = spawn_monitor(fn ->
assert GenEvent.add_mon_handler(pid, ReplyHandler, {self(), false}) == :ok
send parent, :ok
receive after: (:infinity -> :ok)
end)
assert_receive :ok
assert GenEvent.add_handler(pid, {ReplyHandler, self()}, {self(), false}) == :ok
assert GenEvent.which_handlers(pid) == [{ReplyHandler, self()}, ReplyHandler]
# A regular monitor message is passed forward
send pid, {:DOWN, make_ref(), :process, self(), :oops}
assert_receive {:info, {:DOWN, _, :process, _, :oops}}
# Killing the monitor though is not passed forward
Process.exit(mon_pid, :oops)
assert_receive {:DOWN, ^mon_ref, :process, ^mon_pid, :oops}
refute_received {:info, {:DOWN, _, :process, _, :oops}}
assert GenEvent.which_handlers(pid) == [{ReplyHandler, self()}]
end
test "add_mon_handler/3 with notifications" do
{:ok, pid} = GenEvent.start()
self = self()
GenEvent.add_mon_handler(pid, ReplyHandler, {self(), false})
GenEvent.remove_handler(pid, ReplyHandler, :ok)
assert_receive {:gen_event_EXIT, ReplyHandler, :normal}
:ok = GenEvent.add_mon_handler(pid, ReplyHandler, {self(), false})
:ok = GenEvent.swap_handler(pid, ReplyHandler, :swapped, ReplyHandler, :swap)
assert_receive {:gen_event_EXIT, ReplyHandler, {:swapped, ReplyHandler, nil}}
:ok = GenEvent.swap_mon_handler(pid, ReplyHandler, :swapped, ReplyHandler, :swap)
:ok = GenEvent.swap_mon_handler(pid, ReplyHandler, :swapped, ReplyHandler, :swap)
assert_receive {:gen_event_EXIT, ReplyHandler, {:swapped, ReplyHandler, ^self}}
GenEvent.stop(pid)
assert_receive {:gen_event_EXIT, ReplyHandler, :shutdown}
end
test "remove_handler/3" do
{:ok, pid} = GenEvent.start()
assert GenEvent.add_handler(pid, LoggerHandler, []) == :ok
GenEvent.add_mon_handler(pid, ReplyHandler, {self(), false})
{:links, links} = Process.info(self, :links)
refute pid in links
assert GenEvent.remove_handler(pid, {ReplyHandler, self()}, :ok) ==
{:error, :not_found}
assert GenEvent.remove_handler(pid, ReplyHandler, :ok) ==
{:terminate, :ok}
assert_receive {:terminate, :ok}
assert GenEvent.swap_handler(pid, LoggerHandler, [], LoggerHandler, [], link: true) == :ok
GenEvent.add_mon_handler(pid, {ReplyHandler, self()}, {self(), false})
{:links, links} = Process.info(self, :links)
assert pid in links
assert GenEvent.remove_handler(pid, ReplyHandler, :ok) ==
{:error, :not_found}
assert {:error, {%RuntimeError{}, _}} =
GenEvent.remove_handler(pid, {ReplyHandler, self()}, :raise)
assert GenEvent.stop(pid) == :ok
assert GenEvent.which_handlers(pid) == []
end
test "start/2 with registered name" do
{:ok, _} = GenEvent.start(name: :logger)
assert GenEvent.stop(:logger) == :ok
test "swap_handler/5" do
{:ok, pid} = GenEvent.start()
GenEvent.add_handler(pid, ReplyHandler, {self(), false})
assert GenEvent.swap_handler(pid, ReplyHandler, :swapped,
{ReplyHandler, self()}, :swap) == :ok
assert_receive {:terminate, :swapped}
assert_receive :swapped
assert GenEvent.add_handler(pid, ReplyHandler, {self(), false}) == :ok
assert GenEvent.swap_handler(pid, ReplyHandler, :swapped,
{ReplyHandler, self()}, :swap) == {:error, :already_present}
assert GenEvent.which_handlers(pid) == [{ReplyHandler, self()}]
assert GenEvent.remove_handler(pid, {ReplyHandler, self()}, :remove_handler) ==
{:terminate, :remove_handler}
# The handler is initialized even when the module does not exist
# on swap. However, in this case, we are returning an error on init.
assert GenEvent.swap_handler(pid, ReplyHandler, :swapped, ReplyHandler, :swap) ==
{:error, :not_found_on_swap}
end
test "bad calls" do
test "notify/2" do
{:ok, pid} = GenEvent.start()
GenEvent.add_handler(pid, ReplyHandler, {self(), false})
assert GenEvent.notify(pid, :hello) == :ok
assert_receive {:event, :hello}
assert GenEvent.notify(pid, {:custom, :remove_handler}) == :ok
assert_receive {:terminate, :remove_handler}
assert GenEvent.which_handlers(pid) == []
Logger.remove_backend(:console)
GenEvent.add_handler(pid, ReplyHandler, {self(), false})
assert GenEvent.notify(pid, {:custom, :oops}) == :ok
assert_receive {:terminate, {:error, {:bad_return_value, :oops}}}
GenEvent.add_handler(pid, ReplyHandler, {self(), false})
assert GenEvent.notify(pid, :raise) == :ok
assert_receive {:terminate, {:error, {%RuntimeError{}, _}}}
after
Logger.add_backend(:console, flush: true)
end
test "notify/2 with bad args" do
assert GenEvent.notify({:global, :foo}, :bar) == :ok
assert GenEvent.notify({:foo, :bar}, :bar) == :ok
assert GenEvent.notify(self, :bar) == :ok
assert_raise ArgumentError, fn ->
GenEvent.notify(:foo, :bar)
end
end
test "ack_notify/2" do
{:ok, pid} = GenEvent.start()
GenEvent.add_handler(pid, ReplyHandler, {self(), false})
assert GenEvent.ack_notify(pid, :hello) == :ok
assert_receive {:event, :hello}
assert GenEvent.ack_notify(pid, {:custom, :remove_handler}) == :ok
assert_receive {:terminate, :remove_handler}
assert GenEvent.which_handlers(pid) == []
Logger.remove_backend(:console)
GenEvent.add_handler(pid, ReplyHandler, {self(), false})
assert GenEvent.ack_notify(pid, {:custom, :oops}) == :ok
assert_receive {:terminate, {:error, {:bad_return_value, :oops}}}
GenEvent.add_handler(pid, ReplyHandler, {self(), false})
assert GenEvent.ack_notify(pid, :raise) == :ok
assert_receive {:terminate, {:error, {%RuntimeError{}, _}}}
after
Logger.add_backend(:console, flush: true)
end
test "sync_notify/2" do
{:ok, pid} = GenEvent.start()
GenEvent.add_handler(pid, ReplyHandler, {self(), false})
assert GenEvent.sync_notify(pid, :hello) == :ok
assert_received {:event, :hello}
assert GenEvent.sync_notify(pid, {:custom, :remove_handler}) == :ok
assert_received {:terminate, :remove_handler}
assert GenEvent.which_handlers(pid) == []
Logger.remove_backend(:console)
GenEvent.add_handler(pid, ReplyHandler, {self(), false})
assert GenEvent.sync_notify(pid, {:custom, :oops}) == :ok
assert_received {:terminate, {:error, {:bad_return_value, :oops}}}
GenEvent.add_handler(pid, ReplyHandler, {self(), false})
assert GenEvent.sync_notify(pid, :raise) == :ok
assert_received {:terminate, {:error, {%RuntimeError{}, _}}}
after
Logger.add_backend(:console, flush: true)
end
test "call/3" do
{:ok, pid} = GenEvent.start()
GenEvent.add_handler(pid, ReplyHandler, {self(), false})
assert GenEvent.call(pid, ReplyHandler, :hello) == :ok
assert_receive {:call, :hello}
assert GenEvent.call(pid, ReplyHandler, {:custom, {:remove_handler, :ok}}) == :ok
assert_receive {:terminate, :remove_handler}
assert GenEvent.which_handlers(pid) == []
Logger.remove_backend(:console)
GenEvent.add_handler(pid, ReplyHandler, {self(), false})
assert {:error, {:bad_return_value, :oops}} =
GenEvent.call(pid, ReplyHandler, {:custom, :oops})
assert_receive {:terminate, {:error, {:bad_return_value, :oops}}}
assert GenEvent.which_handlers(pid) == []
GenEvent.add_handler(pid, ReplyHandler, {self(), false})
assert {:error, {%RuntimeError{}, _}} = GenEvent.call(pid, ReplyHandler, :raise)
assert_receive {:terminate, {:error, {%RuntimeError{}, _}}}
assert GenEvent.which_handlers(pid) == []
after
Logger.add_backend(:console, flush: true)
end
test "call/2 with bad args" do
Logger.remove_backend(:console)
{:ok, pid} = GenEvent.start_link()
assert GenEvent.add_handler(pid, LoggerHandler, []) == :ok
assert GenEvent.add_handler(pid, DefaultHandler, []) == :ok
assert GenEvent.call(pid, UnknownHandler, :messages) ==
{:error, :bad_module}
assert GenEvent.call(pid, LoggerHandler, :whatever) ==
{:error, {:EXIT, {:bad_call, :whatever}}}
{:error, :not_found}
assert GenEvent.call(pid, DefaultHandler, :whatever) ==
{:error, {:bad_call, :whatever}}
assert GenEvent.which_handlers(pid) == []
assert GenEvent.stop(pid) == :ok
after
Logger.add_backend(:console, flush: true)
end
test "sync stream/2" do
{:ok, pid} = GenEvent.start_link()
parent = self()
test ":sys.get_status/2" do
{:ok, pid} = GenEvent.start(name: :my_gen_event_name)
:ok = GenEvent.add_handler(pid, {ReplyHandler, :ok}, {self(), true})
spawn_link fn ->
send parent, Enum.take(GenEvent.stream(pid, mode: :sync), 3)
end
self = self()
status = :sys.get_status(pid)
GenEvent.stop(pid)
wait_for_handlers(pid, 1)
for i <- 1..3 do
GenEvent.sync_notify(pid, i)
end
# Receive one of the results
assert_receive [1, 2, 3], @receive_timeout
wait_for_handlers(pid, 0)
spawn_link fn ->
Enum.each(GenEvent.stream(pid, mode: :sync), fn _ ->
:timer.sleep(:infinity)
end)
end
wait_for_handlers(pid, 1)
for i <- 1..6 do
GenEvent.notify(pid, i)
end
wait_for_queue_length(pid, 5)
assert {:status, ^pid, {:module, GenEvent},
[pdict, _, ^pid, [], data]} = status
assert pdict[:"$ancestors"] == [self()]
assert pdict[:"$initial_call"] == {GenEvent, :init_it, 6}
assert {'Installed handlers', [
{:handler, ReplyHandler, {ReplyHandler, :ok}, ^self, nil, nil}]} = data[:items]
end
test "async stream/2" do
{:ok, pid} = GenEvent.start_link()
parent = self()
test ":sys.get_state/1 and :sys.replace_state/2" do
{:ok, pid} = GenEvent.start_link(name: :my_gen_event_name)
self = self()
spawn_link fn ->
Enum.each(GenEvent.stream(pid, mode: :async), fn _ ->
:timer.sleep(:infinity)
end)
end
assert [] = :sys.get_state(pid)
spawn_link fn ->
send parent, Enum.take(GenEvent.stream(pid, mode: :async), 3)
end
:ok = GenEvent.add_handler(pid, ReplyHandler, {self, true})
assert [{ReplyHandler, ReplyHandler, ^self}] = :sys.get_state(pid)
wait_for_handlers(pid, 2)
replacer = fn {ReplyHandler, ReplyHandler, _} -> {ReplyHandler, ReplyHandler, :unknown} end
:sys.replace_state(pid, replacer)
assert [{ReplyHandler, ReplyHandler, :unknown}] = :sys.get_state(pid)
for i <- 1..3 do
GenEvent.sync_notify(pid, i)
end
# Fail while replacing does not cause a crash
:sys.replace_state(pid, fn _ -> exit(:fail) end)
assert [{ReplyHandler, ReplyHandler, :unknown}] = :sys.get_state(pid)
# Receive one of the results
assert_receive [1, 2, 3], @receive_timeout
:ok = GenEvent.add_handler(pid, {ReplyHandler, :ok}, {self, true})
assert [{ReplyHandler, {ReplyHandler, :ok}, ^self},
{ReplyHandler, ReplyHandler, :unknown}] = :sys.get_state(pid)
# One of the subscriptions are gone
wait_for_handlers(pid, 1)
:ok = :sys.suspend(pid)
assert [{ReplyHandler, {ReplyHandler, :ok}, ^self},
{ReplyHandler, ReplyHandler, :unknown}] = :sys.get_state(pid)
:ok = :sys.resume(pid)
end
Enum.each [:sync, :async], fn mode ->
test "#{mode} stream/2 with parallel use (and first finishing first)" do
{:ok, pid} = GenEvent.start_link()
stream = GenEvent.stream(pid, duration: 200, mode: unquote(mode))
defp hibernating?(pid) do
Process.info(pid, :current_function) ==
{:current_function,{:erlang,:hibernate,3}}
end
parent = self()
spawn_link fn -> send parent, {:take, Enum.take(stream, 3)} end
wait_for_handlers(pid, 1)
spawn_link fn -> send parent, {:to_list, Enum.to_list(stream)} end
wait_for_handlers(pid, 2)
# Notify the events for both handlers
for i <- 1..3 do
GenEvent.sync_notify(pid, i)
end
assert_receive {:take, [1, 2, 3]}, @receive_timeout
# Notify the events for to_list stream handler
for i <- 4..5 do
GenEvent.sync_notify(pid, i)
end
assert_receive {:to_list, [1, 2, 3, 4, 5]}, @receive_timeout
end
test "#{mode} stream/2 with timeout" do
# Start a manager
{:ok, pid} = GenEvent.start_link()
Process.flag(:trap_exit, true)
pid = spawn_link fn ->
Enum.take(GenEvent.stream(pid, timeout: 50, mode: unquote(mode)), 5)
end
assert_receive {:EXIT, ^pid,
{:timeout, {Enumerable.GenEvent.Stream, :next, [_, _]}}}, @receive_timeout
end
test "#{mode} stream/2 with error/timeout on subscription" do
# Start a manager
{:ok, pid} = GenEvent.start_link()
# Start a subscriber with timeout
child = spawn fn -> Enum.to_list(GenEvent.stream(pid, mode: unquote(mode))) end
wait_for_handlers(pid, 1)
# Kill and wait until we have 0 handlers
Process.exit(child, :kill)
wait_for_handlers(pid, 0)
GenEvent.stop(pid)
end
test "#{mode} stream/2 with manager stop" do
# Start a manager and subscribers
{:ok, pid} = GenEvent.start_link()
parent = self()
stream_pid = spawn_link fn ->
send parent, Enum.take(GenEvent.stream(pid, mode: unquote(mode)), 5)
end
wait_for_handlers(pid, 1)
# Notify the events
for i <- 1..3 do
GenEvent.sync_notify(pid, i)
end
Process.flag(:trap_exit, true)
GenEvent.stop(pid)
assert_receive {:EXIT, ^stream_pid,
{:shutdown, {Enumerable.GenEvent.Stream, :next, [_, _]}}}, @receive_timeout
end
test "#{mode} stream/2 with cancel streams" do
# Start a manager and subscribers
{:ok, pid} = GenEvent.start_link()
stream = GenEvent.stream(pid, id: make_ref(), mode: unquote(mode))
parent = self()
spawn_link fn -> send parent, Enum.take(stream, 5) end
wait_for_handlers(pid, 1)
# Notify the events
for i <- 1..3 do
GenEvent.sync_notify(pid, i)
end
GenEvent.cancel_streams(stream)
assert_receive [1, 2, 3], @receive_timeout
GenEvent.stop(pid)
end
test "#{mode} stream/2 with swap_handler" do
# Start a manager and subscribers
{:ok, pid} = GenEvent.start_link()
stream = GenEvent.stream(pid, id: make_ref(), mode: unquote(mode))
parent = self()
stream_pid = spawn_link fn -> send parent, Enum.take(stream, 5) end
wait_for_handlers(pid, 1)
# Notify the events
for i <- 1..3 do
GenEvent.sync_notify(pid, i)
end
[handler] = GenEvent.which_handlers(pid)
Process.flag(:trap_exit, true)
GenEvent.swap_handler(pid, handler, :swap_handler, LogHandler, [])
assert_receive {:EXIT, ^stream_pid,
{{:swapped, LogHandler, _},
{Enumerable.GenEvent.Stream, :next, [_, _]}}}, @receive_timeout
end
test "#{mode} stream/2 with duration" do
# Start a manager and subscribers
{:ok, pid} = GenEvent.start_link()
stream = GenEvent.stream(pid, duration: 200, mode: unquote(mode))
parent = self()
spawn_link fn -> send parent, {:duration, Enum.take(stream, 10)} end
wait_for_handlers(pid, 1)
# Notify the events
for i <- 1..5 do
GenEvent.sync_notify(pid, i)
end
# Wait until the handler is gone
wait_for_handlers(pid, 0)
# The stream is not complete but terminated anyway due to duration
assert_receive {:duration, [1, 2, 3, 4, 5]}, @receive_timeout
GenEvent.stop(pid)
end
test "#{mode} stream/2 with manager killed and trap_exit" do
# Start a manager and subscribers
{:ok, pid} = GenEvent.start_link()
stream = GenEvent.stream(pid, mode: unquote(mode))
parent = self()
stream_pid = spawn_link fn ->
send parent, Enum.to_list(stream)
end
wait_for_handlers(pid, 1)
Process.flag(:trap_exit, true)
Process.exit(pid, :kill)
assert_receive {:EXIT, ^pid, :killed}, @receive_timeout
assert_receive {:EXIT, ^stream_pid,
{:killed, {Enumerable.GenEvent.Stream, :next, [_,_]}}}, @receive_timeout
end
test "#{mode} stream/2 with manager not alive" do
# Start a manager and subscribers
stream = GenEvent.stream(:does_not_exit, mode: unquote(mode))
parent = self()
stream_pid = spawn_link fn ->
send parent, Enum.to_list(stream)
end
Process.flag(:trap_exit, true)
assert_receive {:EXIT, ^stream_pid,
{:noproc, {Enumerable.GenEvent.Stream, :start, [_]}}}, @receive_timeout
end
test "#{mode} stream/2 with manager unregistered" do
# Start a manager and subscribers
{:ok, pid} = GenEvent.start_link(name: :unreg)
stream = GenEvent.stream(:unreg, mode: unquote(mode))
parent = self()
spawn_link fn ->
send parent, Enum.take(stream, 5)
end
wait_for_handlers(pid, 1)
# Notify the events
for i <- 1..3 do
GenEvent.sync_notify(pid, i)
end
# Unregister the process
Process.unregister(:unreg)
# Notify the remaining events
for i <- 4..5 do
GenEvent.sync_notify(pid, i)
end
# We should have gotten the message and all handlers were removed
assert_receive [1, 2, 3, 4, 5], @receive_timeout
wait_for_handlers(pid, 0)
end
test "#{mode} stream/2 flushes events on abort" do
# Start a manager and subscribers
{:ok, pid} = GenEvent.start_link()
spawn_link fn ->
wait_for_handlers(pid, 2)
GenEvent.notify(pid, 1)
GenEvent.notify(pid, 2)
GenEvent.notify(pid, 3)
end
GenEvent.add_handler(pid, SlowHandler, [])
stream = GenEvent.stream(pid, mode: unquote(mode))
try do
Enum.each stream, fn _ -> throw :done end
catch
:done -> :ok
end
# Wait for the slow handler to be removed
# so all events have been handled
wait_for_handlers(pid, 0)
# Check no messages leaked.
refute_received _any
defp wait_until(fun, counter \\ 0) do
cond do
counter > 100 ->
flunk "Waited for 1s, but #{inspect fun} never returned true"
fun.() ->
true
true ->
receive after: (10 -> wait_until(fun, counter + 1))
end
end
defp wait_for_handlers(pid, count) do
unless length(GenEvent.which_handlers(pid)) == count do
wait_for_handlers(pid, count)
end
end
defp wait_for_queue_length(pid, count) do
{:message_queue_len, n} = Process.info(pid, :message_queue_len)
unless n == count do
wait_for_queue_length(pid, count)
end
defp wake_up(pid) do
send pid, :wake
assert_receive {:info, :wake}
end
end
@@ -49,6 +49,12 @@ defmodule GenServerTest do
assert GenServer.cast(pid, {:push, :world}) == :ok
assert GenServer.call(pid, :pop) == :world
assert GenServer.call(pid, :stop) == :ok
assert GenServer.cast({:global, :foo}, {:push, :world}) == :ok
assert GenServer.cast({:via, :foo, :bar}, {:push, :world}) == :ok
assert_raise ArgumentError, fn ->
GenServer.cast(:foo, {:push, :world})
end
end
test "nil name" do
+22
View File
@@ -41,8 +41,14 @@ defmodule Inspect.AtomTest do
test :op do
assert inspect(:+) == ":+"
assert inspect(:<~) == ":<~"
assert inspect(:~>) == ":~>"
assert inspect(:&&&) == ":&&&"
assert inspect(:~~~) == ":~~~"
assert inspect(:<<~) == ":<<~"
assert inspect(:~>>) == ":~>>"
assert inspect(:<~>) == ":<~>"
assert inspect(:<|>) == ":<|>"
end
test :... do
@@ -121,6 +127,22 @@ defmodule Inspect.NumberTest do
assert inspect(100) == "100"
end
test :decimal do
assert inspect(100, base: :decimal) == "100"
end
test :hex do
assert inspect(100, base: :hex) == "0x64"
end
test :octal do
assert inspect(100, base: :octal) == "0o144"
end
test :binary do
assert inspect(86, base: :binary) == "0b1010110"
end
test :float do
assert inspect(1.0) == "1.0"
assert inspect(1.0E10) == "1.0e10"
+14 -14
View File
@@ -5,23 +5,23 @@ defmodule IntegerTest do
require Integer
test :odd? do
assert Integer.odd?(0) == false
assert Integer.odd?(1) == true
assert Integer.odd?(2) == false
assert Integer.odd?(3) == true
assert Integer.odd?(-1) == true
assert Integer.odd?(-2) == false
assert Integer.odd?(-3) == true
assert Integer.is_odd(0) == false
assert Integer.is_odd(1) == true
assert Integer.is_odd(2) == false
assert Integer.is_odd(3) == true
assert Integer.is_odd(-1) == true
assert Integer.is_odd(-2) == false
assert Integer.is_odd(-3) == true
end
test :even? do
assert Integer.even?(0) == true
assert Integer.even?(1) == false
assert Integer.even?(2) == true
assert Integer.even?(3) == false
assert Integer.even?(-1) == false
assert Integer.even?(-2) == true
assert Integer.even?(-3) == false
assert Integer.is_even(0) == true
assert Integer.is_even(1) == false
assert Integer.is_even(2) == true
assert Integer.is_even(3) == false
assert Integer.is_even(-1) == false
assert Integer.is_even(-2) == true
assert Integer.is_even(-3) == false
end
test :parse do
+17 -22
View File
@@ -5,11 +5,11 @@ defmodule IO.ANSI.DocsTest do
import ExUnit.CaptureIO
def format_heading(str) do
capture_io(fn -> IO.ANSI.Docs.print_heading(str, []) end) |> String.strip
capture_io(fn -> IO.ANSI.Docs.print_heading(str, []) end) |> String.rstrip
end
def format(str) do
capture_io(fn -> IO.ANSI.Docs.print(str, []) end) |> String.strip
capture_io(fn -> IO.ANSI.Docs.print(str, []) end) |> String.rstrip
end
test "heading is formatted" do
@@ -41,52 +41,47 @@ defmodule IO.ANSI.DocsTest do
test "* list is converted" do
result = format("* one\n* two\n* three\n")
assert result == "• one\n• two\n• three\n\e[0m"
assert result == " • one\n • two\n • three\n\e[0m"
end
test "* list surrounded by text is converted" do
result = format("Count:\n\n* one\n* two\n* three\n\nDone")
assert result == "Count:\n\e[0m\n• one\n• two\n• three\n\e[0m\nDone\n\e[0m"
assert result == "Count:\n\e[0m\n • one\n • two\n • three\n\e[0m\nDone\n\e[0m"
end
test "* list with continuation is converted" do
result = format("* one\n two\n three\n* four")
assert result == "• one two three\n• four"
result = format("* one\ntwo\n\n three\nfour\n* five")
assert result == " • one two\n three four\n\e[0m\n • five\n\e[0m"
end
test "* nested lists are converted" do
result = format("* one\n * one.one\n * one.two\n* two")
assert result == "• one\n • one.one\n • one.two\n• two"
assert result == " • one\n • one.one\n • one.two\n\e[0m\n • two\n\e[0m"
end
test "* lists with spaces are converted" do
result = format(" * one\n * two\n * three")
assert result == "• one\n• two\n• three"
assert result == " • one\n • two\n • three\n\e[0m"
end
test "* lists with code" do
result = format(" * one\n two three")
assert result == " • one\n\e[36m\e[1m ┃ two three\e[0m\n\e[0m\n\e[0m"
end
test "- list is converted" do
result = format("- one\n- two\n- three\n")
assert result == "• one\n• two\n• three\n\e[0m"
end
test "- list surrounded by text is converted" do
result = format("Count:\n\n- one\n- two\n- three\n\nDone")
assert result == "Count:\n\e[0m\n• one\n• two\n• three\n\e[0m\nDone\n\e[0m"
end
test "- list with continuation is converted" do
result = format("- one\n two\n three\n- four")
assert result == "• one two three\n• four"
assert result == " • one\n • two\n • three\n\e[0m"
end
test "+ list is converted" do
result = format("+ one\n+ two\n+ three\n")
assert result == "• one\n• two\n• three\n\e[0m"
assert result == " • one\n • two\n • three\n\e[0m"
end
test "+ and - nested lists are converted" do
result = format("- one\n + one.one\n + one.two\n- two")
assert result == "• one\n • one.one\n • one.two\n• two"
assert result == " • one\n • one.one\n • one.two\n\e[0m\n • two\n\e[0m"
end
test "paragraphs are split" do
@@ -101,7 +96,7 @@ defmodule IO.ANSI.DocsTest do
test "extra whitespace doesn't mess up a following list" do
result = format("para1\n \n* one\n* two")
assert result == "para1\n\e[0m\n• one\n• two"
assert result == "para1\n\e[0m\n • one\n • two\n\e[0m"
end
test "star/underscore/backtick works" do
@@ -6,6 +6,25 @@ defmodule Kernel.ComprehensionTest do
import ExUnit.CaptureIO
require Integer
defmodule PDict do
defstruct []
defimpl Collectable do
def empty(struct) do
struct
end
def into(struct) do
{struct,
fn
_, {:cont, x} -> Process.put(:into_cont, [x|Process.get(:into_cont)])
_, :done -> Process.put(:into_done, true)
_, :halt -> Process.put(:into_halt, true)
end}
end
end
end
defp to_bin(x) do
<< x >>
end
@@ -86,7 +105,7 @@ defmodule Kernel.ComprehensionTest do
Process.put(:into_done, false)
Process.put(:into_halt, false)
for x <- 1..3, into: collectable_pdict do
for x <- 1..3, into: %PDict{} do
x * 2
end
@@ -101,7 +120,7 @@ defmodule Kernel.ComprehensionTest do
Process.put(:into_halt, false)
catch_error(
for x <- 1..3, into: collectable_pdict do
for x <- 1..3, into: %PDict{} do
if x > 2, do: raise("oops"), else: x
end
)
@@ -114,21 +133,13 @@ defmodule Kernel.ComprehensionTest do
test "for comprehension with into, generators and filters" do
Process.put(:into_cont, [])
for x <- 1..3, Integer.odd?(x), << y <- "hello" >>, into: collectable_pdict do
for x <- 1..3, Integer.is_odd(x), << y <- "hello" >>, into: %PDict{} do
x + y
end
assert IO.iodata_to_binary(Process.get(:into_cont)) == "roohkpmmfi"
end
defp collectable_pdict do
fn
_, {:cont, x} -> Process.put(:into_cont, [x|Process.get(:into_cont)])
_, :done -> Process.put(:into_done, true)
_, :halt -> Process.put(:into_halt, true)
end
end
## List generators (inlined by the compiler)
test "list for comprehensions" do
+103 -13
View File
@@ -150,6 +150,15 @@ defmodule Kernel.ErrorsTest do
def hello(arg), do: arg
end
'''
assert_compile_fail CompileError,
"nofile:2: function foo/0 undefined",
~C'''
defmodule ErrorsTest do
def hello(foo, bar \\ foo)
def hello(foo, bar), do: foo + bar
end
'''
end
test :invalid_match_pattern do
@@ -206,8 +215,8 @@ defmodule Kernel.ErrorsTest do
test :unbound_expr do
assert_compile_fail CompileError,
"nofile:1: invalid argument for unary operator ^, expected an existing variable, got: ^x(1)",
'^x(1) = 1'
"nofile:1: invalid argument for unary operator ^, expected an existing variable, got: ^is_atom(:foo)",
'^is_atom(:foo) = true'
end
test :literal_on_map_and_struct do
@@ -232,8 +241,8 @@ defmodule Kernel.ErrorsTest do
test :struct_access_on_body do
assert_compile_fail CompileError,
"nofile:3: cannot access struct TZ in the same context that defines it " <>
"as the struct fields are not yet accessible",
"nofile:3: cannot access struct TZ, the struct was not yet defined or the struct " <>
"is being accessed in the same context that defines it",
'''
defmodule TZ do
defstruct %{name: "Brasilia"}
@@ -290,6 +299,23 @@ defmodule Kernel.ErrorsTest do
'unquote 1'
end
test :invalid_unquote_splicing_in_oneliners do
assert_compile_fail ArgumentError,
"unquote_splicing only works inside arguments and block contexts, " <>
"wrap it in parens if you want it to work with one-liners",
'''
defmodule Foo do
defmacro oneliner2 do
quote do: unquote_splicing 1
end
def callme do
oneliner2
end
end
'''
end
test :invalid_quote_args do
assert_compile_fail CompileError,
"nofile:1: invalid arguments for quote",
@@ -321,7 +347,17 @@ defmodule Kernel.ErrorsTest do
test :undefined_non_local_function do
assert_compile_fail CompileError,
"nofile:1: undefined function casea/2",
'casea foo, do: 1'
'casea foo, do: @hello :world'
end
test :invalid_attribute do
msg = ~r"cannot inject attribute @foo into function/macro because cannot escape "
assert_raise ArgumentError, msg, fn ->
defmodule Foo do
@foo fn -> end
def bar, do: @foo
end
end
end
test :invalid_fn_args do
@@ -330,6 +366,12 @@ defmodule Kernel.ErrorsTest do
'fn 1'
end
test :invalid_escape do
assert_compile_fail TokenMissingError,
"nofile:1: invalid escape \\ at end of file",
'1 \\'
end
test :function_local_conflict do
assert_compile_fail CompileError,
"nofile:1: imported Kernel.&&/2 conflicts with local function",
@@ -558,7 +600,7 @@ defmodule Kernel.ErrorsTest do
test :invalid_for_without_generators do
assert_compile_fail CompileError,
"nofile:1: for comprehensions must start with a generator",
'for x, do: x'
'for is_atom(:foo), do: :foo'
end
test :invalid_for_bit_generator do
@@ -580,19 +622,61 @@ defmodule Kernel.ErrorsTest do
'fn x -> x; x, y -> x + y end'
end
test :end_of_expression do
# All valid examples
Code.eval_quoted '''
1;
2;
3
(;)
(;1)
(1;)
(1; 2)
fn -> 1; 2 end
fn -> ; end
if true do
;
end
try do
;
catch
_, _ -> ;
after
;
end
'''
# All invalid examples
assert_compile_fail SyntaxError,
"nofile:1: syntax error before: ';'",
'1+;\n2'
assert_compile_fail SyntaxError,
"nofile:1: syntax error before: ';'",
'max(1,;2)'
end
test :new_line_error do
assert_compile_fail SyntaxError,
"nofile:3: syntax error before: newline",
"nofile:3: syntax error before: eol",
'if true do\n foo = [],\n baz\nend'
end
test :invalid_var_or_function_on_guard do
assert_compile_fail CompileError,
"nofile:2: unknown variable something_that_does_not_exist or " <>
"cannot invoke function something_that_does_not_exist/0 inside guard",
"nofile:4: unknown variable something_that_does_not_exist or " <>
"cannot invoke local something_that_does_not_exist/0 inside guard",
'''
case [] do
[] when something_that_does_not_exist == [] -> :ok
defmodule ErrorsTest do
def bar do
case [] do
[] when something_that_does_not_exist == [] -> :ok
end
end
end
'''
end
@@ -620,8 +704,14 @@ defmodule Kernel.ErrorsTest do
test :invalid_function_on_match do
assert_compile_fail CompileError,
"nofile:1: cannot invoke function something_that_does_not_exist/0 inside match",
'case [] do; something_that_does_not_exist() -> :ok; end'
"nofile:3: cannot invoke local something_that_does_not_exist/0 inside match",
'''
defmodule ErrorsTest do
def fun do
case [] do; something_that_does_not_exist() -> :ok; end
end
end
'''
end
test :invalid_remote_on_match do
@@ -55,11 +55,6 @@ defmodule Kernel.ExpansionTest do
assert expand_env(quote(do: World), __ENV__) |> elem(0) == :"Elixir.Source"
end
test "__aliases__: expands to elixir_aliases on runtime" do
assert expand(quote do: hello.World) ==
quote do: :elixir_aliases.concat([hello(), :World])
end
## =
test "=: sets context to match" do
+1 -1
View File
@@ -71,7 +71,7 @@ defmodule Kernel.FnTest do
test "imported partial application" do
import Record
assert (&record?(&1, :sample)).({:sample, 1})
assert (&is_record(&1, :sample)).({:sample, 1})
end
test "remote partial application" do
@@ -6,7 +6,12 @@ defmodule Kernel.LexicalTrackerTest do
alias Kernel.LexicalTracker, as: D
setup do
{:ok, [pid: D.start_link]}
{:ok, pid} = D.start_link("dest")
{:ok, [pid: pid]}
end
test "gets the destination", config do
assert D.dest(config[:pid]) == "dest"
end
test "can add remote dispatches", config do
@@ -409,3 +409,11 @@ defmodule Kernel.QuoteTest.ImportsHygieneTest do
assert with_length == 5
end
end
defmodule Kernel.QuoteTest.NoQuoteConflictTest do
defmacro x |> f do
quote do
unquote(x) |> unquote(f)
end
end
end
+3 -3
View File
@@ -226,7 +226,7 @@ defmodule Kernel.RaiseTest do
x in [UndefinedFunctionError] -> Exception.message(x)
end
assert result == "undefined function: DoNotExist.for_sure/0"
assert result == "undefined function: DoNotExist.for_sure/0 (module DoNotExist is not available)"
end
test :function_clause_error do
@@ -352,7 +352,7 @@ defmodule Kernel.RaiseTest do
x in [ErlangError] -> Exception.message(x)
end
assert result == "undefined function: DoNotExist.for_sure/0"
assert result == "undefined function: DoNotExist.for_sure/0 (module DoNotExist is not available)"
end
defmacrop exceptions do
@@ -366,7 +366,7 @@ defmodule Kernel.RaiseTest do
x in exceptions -> Exception.message(x)
end
assert result == "undefined function: DoNotExist.for_sure/0"
assert result == "undefined function: DoNotExist.for_sure/0 (module DoNotExist is not available)"
end
defp zero(0), do: 0
@@ -47,6 +47,14 @@ defmodule Kernel.TypespecTest do
end
end
test "unexpected expression in typespec" do
assert_raise CompileError, ~r"unexpected expression in typespec: \"foobar\"", fn ->
test_module do
@type mytype :: "foobar"
end
end
end
test "invalid function specification" do
assert_raise CompileError, ~r"invalid function type specification: myfun = 1", fn ->
test_module do
+16 -12
View File
@@ -26,8 +26,6 @@ defmodule Kernel.WarningTest do
{_arg, _arg} = {1, 1}
"""
end) =~ "warning: the underscored variable \"_arg\" appears more than once in a match"
after
purge Sample
end
test :unused_function do
@@ -160,7 +158,7 @@ defmodule Kernel.WarningTest do
"""
end) =~ "warning: unused import :lists"
after
purge [Sample]
purge Sample
end
test :unused_alias do
@@ -173,7 +171,7 @@ defmodule Kernel.WarningTest do
"""
end) =~ "warning: unused alias List"
after
purge [Sample]
purge Sample
end
test :unused_inside_dynamic_module do
@@ -189,7 +187,7 @@ defmodule Kernel.WarningTest do
end
end) =~ "warning: unused import String"
after
purge [Sample]
purge Sample
end
test :unused_guard do
@@ -233,7 +231,7 @@ defmodule Kernel.WarningTest do
"""
end) =~ "warning: bodyless clause provided for nonexistent def hello/0"
after
purge [Sample1]
purge Sample1
end
test :used_import_via_alias do
@@ -420,7 +418,7 @@ defmodule Kernel.WarningTest do
"""
end) =~ "warning: @behavior attribute is not supported, please use @behaviour instead"
after
purge [Sample]
purge Sample
end
test :undefined_macro_for_protocol do
@@ -449,7 +447,7 @@ defmodule Kernel.WarningTest do
"""
end) =~ "nofile:4: warning: clauses for the same def should be grouped together, def foo/2 was previously defined (nofile:2)"
after
purge [Sample]
purge Sample
end
test :warning_with_overridden_file do
@@ -462,7 +460,13 @@ defmodule Kernel.WarningTest do
"""
end) =~ "sample:3: warning: variable x is unused"
after
purge [Sample]
purge Sample
end
test :warning_on_codepoint_escape do
assert capture_err(fn ->
Code.eval_string "? "
end) =~ "nofile:1: warning: found ? followed by codepoint 0x20 (space), please use \\s instead"
end
test :typedoc_on_typep do
@@ -477,7 +481,7 @@ defmodule Kernel.WarningTest do
"""
end) =~ "nofile:3: warning: type priv/0 is private, @typedoc's are always discarded for private types"
after
purge [Sample]
purge Sample
end
test :typedoc_with_no_type do
@@ -489,7 +493,7 @@ defmodule Kernel.WarningTest do
"""
end) =~ "nofile:1: warning: @typedoc provided but no type follows it"
after
purge [Sample]
purge Sample
end
test :doc_with_no_function do
@@ -501,7 +505,7 @@ defmodule Kernel.WarningTest do
"""
end) =~ "nofile:1: warning: @doc provided but no definition follows it"
after
purge [Sample]
purge Sample
end
defp purge(list) when is_list(list) do
+7 -7
View File
@@ -41,12 +41,6 @@ defmodule KernelTest do
assert binding() == [a: 0]
end
test "nil?/1" do
assert nil?(nil) == true
assert nil?(0) == false
assert nil?(false) == false
end
test "in/2" do
assert 2 in [1, 2, 3]
assert 2 in 1..3
@@ -89,8 +83,14 @@ defmodule KernelTest do
assert case_in(-3, -1..-3) == true
end
@bitstring <<"foo", 16::4>>
test "bitstring attribute" do
assert @bitstring == <<"foo", 16::4>>
end
test "paren as nil" do
assert nil?(()) == true
assert is_nil(()) == true
assert ((); ();) == nil
assert [ 1, (), 3 ] == [1, nil, 3 ]
assert [do: ()] == [do: nil]
-7
View File
@@ -75,13 +75,6 @@ defmodule ListTest do
assert List.zip([[1, 4], [2, 5], []]) == []
end
test :unzip do
assert List.unzip([{1, 2, 3}, {4, 5, 6}]) == [[1, 4], [2, 5], [3, 6]]
assert List.unzip([{1, 2, 3}, {4, 5}]) == [[1, 4], [2, 5]]
assert List.unzip([[1, 2, 3], [4, 5]]) == [[1, 4], [2, 5]]
assert List.unzip([]) == []
end
test :keyfind do
assert List.keyfind([a: 1, b: 2], :a, 0) == {:a, 1}
assert List.keyfind([a: 1, b: 2], 2, 1) == {:b, 2}
+28
View File
@@ -42,6 +42,10 @@ defmodule MacroTest do
assert {:%{}, [], [a: 1]} = Macro.escape(%{a: 1})
end
test :escape_handles_bitstring do
assert {:<<>>, [], [{:::, [], [1, 4]}, ","]} == Macro.escape(<<300::12>>)
end
test :escape_works_recursively do
assert [1,{:{}, [], [:a,:b,:c]}, 3] == Macro.escape([1, {:a, :b, :c}, 3])
end
@@ -445,6 +449,30 @@ defmodule MacroTest do
"::Bar:.foo(:1:, :2:, :3:):"
end
## validate
test :validate do
ref = make_ref()
assert Macro.validate(1) == :ok
assert Macro.validate(1.0) == :ok
assert Macro.validate(:foo) == :ok
assert Macro.validate("bar") == :ok
assert Macro.validate(self()) == :ok
assert Macro.validate({1, 2}) == :ok
assert Macro.validate({:foo, [], :baz}) == :ok
assert Macro.validate({:foo, [], []}) == :ok
assert Macro.validate([1, 2, 3]) == :ok
assert Macro.validate(<<0::4>>) == {:error, <<0::4>>}
assert Macro.validate(ref) == {:error, ref}
assert Macro.validate({1, ref}) == {:error, ref}
assert Macro.validate({ref, 2}) == {:error, ref}
assert Macro.validate([1, ref, 3]) == {:error, ref}
assert Macro.validate({:foo, [], 0}) == {:error, {:foo, [], 0}}
assert Macro.validate({:foo, 0, []}) == {:error, {:foo, 0, []}}
end
## decompose_call
test :decompose_call do
@@ -6,7 +6,8 @@ defmodule Module.LocalsTrackerTest do
alias Module.LocalsTracker, as: D
setup do
{:ok, [pid: D.start_link]}
{:ok, pid} = D.start_link
{:ok, [pid: pid]}
end
## Locals
+4 -5
View File
@@ -54,6 +54,10 @@ defmodule ModuleTest do
end
end
test :in_memory do
assert :code.which(__MODULE__) == :in_memory
end
## Eval
test :eval_quoted do
@@ -236,9 +240,4 @@ defmodule ModuleTest do
assert Module.definitions_in(__MODULE__, :defp) == []
end
end
test :function do
assert Module.function(:erlang, :atom_to_list, 1).(:hello) == 'hello'
assert is_function Module.function(This, :also_works, 0)
end
end
+19 -6
View File
@@ -38,6 +38,12 @@ defmodule PathTest do
assert Path.relative("/usr/local/bin") == "usr/local/bin"
assert Path.relative("usr/local/bin") == "usr/local/bin"
assert Path.relative("../usr/local/bin") == "../usr/local/bin"
assert Path.relative_to("D:/usr/local/foo", "D:/usr/") == "local/foo"
assert Path.relative_to("D:/usr/local/foo", "d:/usr/") == "local/foo"
assert Path.relative_to("d:/usr/local/foo", "D:/usr/") == "local/foo"
assert Path.relative_to("D:/usr/local/foo", "d:") == "usr/local/foo"
assert Path.relative_to("D:/usr/local/foo", "D:") == "usr/local/foo"
end
test :type_win do
@@ -88,6 +94,7 @@ defmodule PathTest do
test :absname do
assert (Path.absname("/") |> strip_drive_letter_if_windows) == "/"
assert (Path.absname("/foo") |> strip_drive_letter_if_windows) == "/foo"
assert (Path.absname("/./foo") |> strip_drive_letter_if_windows) == "/foo"
assert (Path.absname("/foo/bar") |> strip_drive_letter_if_windows) == "/foo/bar"
assert (Path.absname("/foo/bar/") |> strip_drive_letter_if_windows) == "/foo/bar"
assert (Path.absname("/foo/bar/../bar") |> strip_drive_letter_if_windows) == "/foo/bar/../bar"
@@ -118,6 +125,7 @@ defmodule PathTest do
test :expand_path do
assert (Path.expand("/") |> strip_drive_letter_if_windows) == "/"
assert (Path.expand("/foo") |> strip_drive_letter_if_windows) == "/foo"
assert (Path.expand("/./foo") |> strip_drive_letter_if_windows) == "/foo"
assert (Path.expand("/foo/bar") |> strip_drive_letter_if_windows) == "/foo/bar"
assert (Path.expand("/foo/bar/") |> strip_drive_letter_if_windows) == "/foo/bar"
assert (Path.expand("/foo/bar/.") |> strip_drive_letter_if_windows)== "/foo/bar"
@@ -131,10 +139,9 @@ defmodule PathTest do
assert (Path.expand(['..', ?/, "bar/../bar"], '/foo/../foo/../foo') |>
strip_drive_letter_if_windows) == "/bar"
assert (Path.expand("/..") |> strip_drive_letter_if_windows) == "/"
assert Path.expand("bar/../bar", "foo") == Path.expand("foo/bar")
assert (Path.expand("/..") |> strip_drive_letter_if_windows) == "/"
end
test :relative_to do
@@ -200,11 +207,17 @@ defmodule PathTest do
assert Path.join("/foo", "bar") == "/foo/bar"
assert Path.join("~", "foo") == "~/foo"
assert Path.join("", "bar") == "/bar"
assert Path.join("/foo", "/bar") == "/foo/bar"
assert Path.join("/foo", "./bar") == "/foo/bar"
assert Path.join("", "bar") == "bar"
assert Path.join("bar", "") == "bar"
assert Path.join("", "/bar") == "bar"
assert Path.join("/bar", "") == "/bar"
assert Path.join([?/, "foo"], "./bar") == "/foo/bar"
assert Path.join("foo", "/bar") == "foo/bar"
assert Path.join("/foo", "/bar") == "/foo/bar"
assert Path.join("/foo", "/bar") == "/foo/bar"
assert Path.join("/foo", "./bar") == "/foo/./bar"
assert Path.join([?/, "foo"], "./bar") == "/foo/./bar"
end
test :split do
+28 -28
View File
@@ -64,20 +64,20 @@ defmodule ProtocolTest do
end
test "protocol implementations without any" do
assert nil? Sample.impl_for(:foo)
assert nil? Sample.impl_for(fn(x) -> x end)
assert nil? Sample.impl_for(1)
assert nil? Sample.impl_for(1.1)
assert nil? Sample.impl_for([])
assert nil? Sample.impl_for([1, 2, 3])
assert nil? Sample.impl_for({})
assert nil? Sample.impl_for({1, 2, 3})
assert nil? Sample.impl_for("foo")
assert nil? Sample.impl_for(<<1>>)
assert nil? Sample.impl_for(%{})
assert nil? Sample.impl_for(self)
assert nil? Sample.impl_for(hd(:erlang.ports))
assert nil? Sample.impl_for(make_ref)
assert is_nil Sample.impl_for(:foo)
assert is_nil Sample.impl_for(fn(x) -> x end)
assert is_nil Sample.impl_for(1)
assert is_nil Sample.impl_for(1.1)
assert is_nil Sample.impl_for([])
assert is_nil Sample.impl_for([1, 2, 3])
assert is_nil Sample.impl_for({})
assert is_nil Sample.impl_for({1, 2, 3})
assert is_nil Sample.impl_for("foo")
assert is_nil Sample.impl_for(<<1>>)
assert is_nil Sample.impl_for(%{})
assert is_nil Sample.impl_for(self)
assert is_nil Sample.impl_for(hd(:erlang.ports))
assert is_nil Sample.impl_for(make_ref)
assert Sample.impl_for(%ImplStruct{}) ==
Sample.ProtocolTest.ImplStruct
@@ -302,20 +302,20 @@ defmodule Protocol.ConsolidationTest do
end
test "consolidated implementations without any" do
assert nil? Sample.impl_for(:foo)
assert nil? Sample.impl_for(fn(x) -> x end)
assert nil? Sample.impl_for(1)
assert nil? Sample.impl_for(1.1)
assert nil? Sample.impl_for([])
assert nil? Sample.impl_for([1, 2, 3])
assert nil? Sample.impl_for({})
assert nil? Sample.impl_for({1, 2, 3})
assert nil? Sample.impl_for("foo")
assert nil? Sample.impl_for(<<1>>)
assert nil? Sample.impl_for(self)
assert nil? Sample.impl_for(%{})
assert nil? Sample.impl_for(hd(:erlang.ports))
assert nil? Sample.impl_for(make_ref)
assert is_nil Sample.impl_for(:foo)
assert is_nil Sample.impl_for(fn(x) -> x end)
assert is_nil Sample.impl_for(1)
assert is_nil Sample.impl_for(1.1)
assert is_nil Sample.impl_for([])
assert is_nil Sample.impl_for([1, 2, 3])
assert is_nil Sample.impl_for({})
assert is_nil Sample.impl_for({1, 2, 3})
assert is_nil Sample.impl_for("foo")
assert is_nil Sample.impl_for(<<1>>)
assert is_nil Sample.impl_for(self)
assert is_nil Sample.impl_for(%{})
assert is_nil Sample.impl_for(hd(:erlang.ports))
assert is_nil Sample.impl_for(make_ref)
assert Sample.impl_for(%ImplStruct{}) ==
Sample.Protocol.ConsolidationTest.ImplStruct
+4 -4
View File
@@ -30,10 +30,10 @@ defmodule RecordTest do
# We need indirection to avoid warnings
defp record?(data, kind) do
Record.record?(data, kind)
Record.is_record(data, kind)
end
test "record?/2" do
test "is_record/2" do
assert record?({User, "meg", 27}, User)
refute record?({User, "meg", 27}, Author)
refute record?(13, Author)
@@ -41,10 +41,10 @@ defmodule RecordTest do
# We need indirection to avoid warnings
defp record?(data) do
Record.record?(data)
Record.is_record(data)
end
test "record?/1" do
test "is_record/1" do
assert record?({User, "john", 27})
refute record?({"john", 27})
refute record?(13)

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