Compare commits

...
151 Commits
Author SHA1 Message Date
José Valim 3c48099718 Release v0.13.3 2014-05-24 09:59:49 +02:00
José Valim 419e2a1d2a Merge pull request #2332 from devinus/stream-struct
Change Stream's struct to just %Stream{}
2014-05-24 07:20:44 +02:00
Devin Torres 39c84f4324 Change Stream's struct to just %Stream{} 2014-05-24 00:09:32 -05:00
José Valim 8652ee5838 Do not allow keyword on containers without pos args 2014-05-23 20:59:38 +02:00
José Valim beb1bbeb3d Ensure config/config.exs is really persisted 2014-05-23 19:22:00 +02:00
José Valim cc9783a437 Update CHANGELOG 2014-05-23 17:07:39 +02:00
José Valim e566d7cda0 Merge pull request #2320 from alco/regex-literal
Draft implementation of custom inspect algorithm for regexes
2014-05-23 23:41:49 +09:00
José Valim 5c43548b0f Remove unused reduce/3 from map 2014-05-23 16:35:05 +02:00
José Valim 82518c3376 Ensure doctests failures are properly tested 2014-05-23 16:35:05 +02:00
José Valim 8ab8663370 Change help heading on mix 2014-05-23 16:35:05 +02:00
Eric Meadows-Jönsson d44d71cce1 Do not install rebar bundled with elixir 2014-05-23 13:39:46 +02:00
Alexei Sholik 4d66ac72e1 Dedicated inspect implementation for regexes. Fix #2250
We no longer share the implementation with BitString because regex
literals have slightly different escaping rules
2014-05-23 14:36:56 +03:00
José Valim fb49aa1e99 Deprecate Dict.Behaviour in favor of Dict 2014-05-23 00:09:36 +02:00
José Valim a6b9d2ed72 Merge pull request #2324 from chyndman/elixir-bat-fix
elixir.bat fixed to prevent eating quotes
2014-05-22 23:07:31 +02:00
chyndman df1366c1d6 elixir.bat fixed to prevent eating quotes 2014-05-22 14:02:28 -07:00
José Valim 0bcc02d816 Merge pull request #2321 from chyndman/mix-bat-help
elixir.bat defers to mix for handling help options
2014-05-23 04:02:52 +09:00
chyndman 1c9dff1fee elixir.bat only triggers help at first argument 2014-05-22 11:57:25 -07:00
José Valim ed4ee3fe5c Merge pull request #2322 from alco/agent-doc
Proof-read Agent docs
2014-05-23 03:48:06 +09:00
Alexei Sholik cc8f6a4ead Proof-read Agent docs 2014-05-22 20:23:37 +03:00
chyndman 82f800e1c5 elixir.bat defers to mix for handling help options 2014-05-22 09:51:28 -07:00
José Valim 18aeb78547 Exit with proper test code on test_erlang, closes #2313 2014-05-22 12:21:08 +02:00
José Valim 83ccde4db6 Update CHANGELOG, fix tests and typespecs 2014-05-22 11:29:42 +02:00
José Valim 59900260a1 Merge pull request #2312 from alco/option-parser-fixes
Return invalid options as strings
2014-05-22 18:19:52 +09:00
José Valim 6bfcf2f33c \d is a printable character 2014-05-22 10:37:09 +02:00
José Valim 45bd25057f Merge pull request #2317 from vicmargar/fix-exception-doc
Fix doc example for exception/1
2014-05-22 10:30:42 +02:00
José Valim 3f6e6101d8 Merge pull request #2315 from chyndman/gsoc-windows
Mix.CLITest: explicitly declare environment variables (Windows-friendly)
2014-05-22 10:27:54 +02:00
Victor Martinez eb17182ed4 Fix example for exception/1 2014-05-22 00:40:48 +01:00
chyndman 5b08851a8d Mix.CLITest: explicitly declare environment variables (Windows-friendly) 2014-05-21 16:19:37 -07:00
Eric Meadows-Jönsson bc0adc2f4f Merge pull request #2316 from alco/string-to_char_list
String to char list
2014-05-22 00:48:31 +02:00
Alexei Sholik 67f2be2eb9 Fix example for String.to_char_list 2014-05-22 01:18:17 +03:00
Alexei Sholik fe5d8e6483 Mention removal of String.to_char_list! in the CHANGELOG 2014-05-22 01:17:37 +03:00
Alexei Sholik 4cc28c2e8c Return invalid options as strings 2014-05-22 00:05:41 +03:00
Eric Meadows-Jönsson 78453597c2 Bump Hex requirement 2014-05-21 22:06:46 +02:00
José Valim 927594bb14 Always return nil for undefined options (#2308) 2014-05-21 21:09:01 +02:00
José Valim fab45f58c6 Merge pull request #2311 from alco/option-parser-fixes
Do not assume invalid options are of boolean type
2014-05-21 20:41:14 +02:00
Alexei Sholik a9375903ae Do not assume invalid options are of boolean type 2014-05-21 17:36:19 +03:00
José Valim 44d4b5a7bf Merge pull request #2298 from alco/mix-run-errors
Report errors about missing files in the mix run task
2014-05-21 15:20:13 +02:00
José Valim ed582c3977 Merge pull request #2309 from alco/option-parser-tests
Fix parsing of "-" in OptionParser
2014-05-21 15:19:04 +02:00
Alexei Sholik 9d4231c92c Add tests for non-existent file errors in mix run 2014-05-21 14:07:50 +03:00
Alexei Sholik ecdb3e9aa9 Report errors about missing files in the mix run task 2014-05-21 14:03:00 +03:00
Alexei Sholik fb2686fe37 Parse "-" as a plain argument 2014-05-21 13:55:28 +03:00
José Valim 55e885f5c5 Ensure we don't leak erlang terms on failure, closes #2303 2014-05-21 12:44:12 +02:00
José Valim f92d8393da (Soft) deprecate set_elem/3 in favor of put_elem/3, closes #2259 2014-05-21 12:04:22 +02:00
José Valim cb0c3d8a2a Support :strict option on parse and improve return types 2014-05-21 11:50:11 +02:00
José Valim d9ece7edb0 Merge pull request #2290 from alco/option-parser-next
Tentative implementation of the `next` function
2014-05-21 11:09:50 +02:00
Andrey Pavlov 24a256686a Description for mix test --seed option
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2014-05-21 11:05:53 +02:00
José Valim ce9b381f67 Unify mix run tests 2014-05-21 11:04:37 +02:00
José Valim a0d42b47b6 Merge pull request #2304 from knewter/feature/agent_doc_typo_fix
Fix typo in Agent docs
2014-05-21 10:33:24 +02:00
José Valim e3e5b9b85b Merge pull request #2305 from benjamintanweihao/patch-1
Fix typo
2014-05-21 10:33:10 +02:00
José Valim 5f4180a898 Merge pull request #2306 from benjamintanweihao/patch-2
Trailing whitespace.
2014-05-21 10:32:57 +02:00
Benjamin Tan Wei Hao 184cb36e00 Trailing whitespace. 2014-05-21 11:33:47 +08:00
Benjamin Tan Wei Hao 48a0211156 Fix typo 2014-05-21 11:24:46 +08:00
Josh Adams 9dc7172ec5 Fix typo in Agent docs
Replace reference to GenEvent with reference to Agent
2014-05-20 20:46:13 -05:00
José Valim 00db100bd4 Merge pull request #2301 from alco/mix-readme-template
Make the title in the Readme generated by mix new more prominent
2014-05-21 00:59:29 +02:00
Alexei Sholik 1804db48c8 Make the title in the Readme generated by mix new more prominent 2014-05-21 01:36:33 +03:00
José Valim b92368104e Merge pull request #2300 from lexmag/mix-run-warn-fix
Fix match in `Mix.Tasks.Run` test
2014-05-20 23:28:13 +02:00
Aleksei Magusev 6f0c0269d2 Fix match in Mix.Tasks.Run test 2014-05-21 01:14:37 +04:00
José Valim 9b431a8831 Speed up CLI tests by avoiding booting Elixir 2014-05-20 22:42:01 +02:00
José Valim d53509efd2 Merge pull request #2299 from lucasweiblen/fix-typos
Fix typos
2014-05-20 22:25:21 +02:00
Lucas Weiblen ea0226b3da Fix typo on ExUnit 2014-05-20 16:42:48 -03:00
Lucas Weiblen 4346a41811 Fix typos on Mix 2014-05-20 16:42:33 -03:00
José Valim b342739dba Tag wrong heredoc start as SyntaxError instead of TokenMissingError 2014-05-20 20:25:31 +02:00
José Valim b570850faf Fix Stream.cycle/1 with inner halting stream, closes #2285 2014-05-20 16:51:00 +02:00
José Valim 5882cf5fb6 Merge pull request #2297 from alco/mix-run-test
Small fix in the run task tests
2014-05-20 16:44:09 +02:00
Alexei Sholik 4dde1b076b Small fix in the run task tests 2014-05-20 17:36:52 +03:00
Alexei Sholik 142ebc3ecd Fix handling of boolean options with bad values 2014-05-20 17:32:12 +03:00
Alexei Sholik 339c57f1c6 Fix value normalization in strict mode 2014-05-20 17:32:12 +03:00
Alexei Sholik 76260b057f Refactor option parsing internals to support strict mode 2014-05-20 17:32:12 +03:00
Alexei Sholik 40a384aef6 Tentative implementation of the next function 2014-05-20 17:32:12 +03:00
José Valim e7fcaff839 Merge pull request #2289 from lexmag/master
Change `OptionParser` to remove `--`. Fix command-line arguments handling.
2014-05-20 16:14:04 +02:00
José Valim a4b01b2f97 Fix race condition on supervisor test 2014-05-20 15:57:18 +02:00
Aleksei Magusev ee561bd473 Fix raising Code.LoadError 2014-05-20 17:10:46 +04:00
Aleksei Magusev 6696133abb Fix command-line arguments handling in Kernel.CLI 2014-05-20 17:10:46 +04:00
Aleksei Magusev 42b0c24f6a Fix command-line arguments handling in Mix.Tasks.Run 2014-05-20 17:10:33 +04:00
José Valim b8d02e94b2 Merge pull request #2296 from alco/record-doc
Fix example of using `defrecord`
2014-05-20 14:16:37 +02:00
Alexei Sholik d9a8bbb65e Fix example of using defrecord 2014-05-20 14:20:23 +03:00
Aleksei Magusev c0bc64fb19 Change OptionParser to remove the end of command options (--) 2014-05-20 12:49:10 +04:00
Aleksei Magusev 0445647d63 Fix indentation 2014-05-20 12:37:20 +04:00
José Valim da03542309 Merge pull request #2294 from ericmj/reraise
reraise should work like raise
2014-05-20 10:31:55 +02:00
Eric Meadows-Jönsson 2e24e228d1 reraise should work like raise 2014-05-20 10:29:07 +02:00
José Valim f83eb30975 Merge pull request #2295 from kiela/master
Add links to LEGAL and LICENSE files in README.md
2014-05-20 10:00:14 +02:00
Kamil Kieliszczyk 80d81ed47a Add links to LEGAL and LICENSE files in README.md 2014-05-20 02:31:51 +02:00
José Valim dc9803de6d Remove inlist and inbits from keywords list 2014-05-19 23:12:45 +02:00
José Valim 4e5c851c44 Update CHANGELOG 2014-05-19 18:56:34 +02:00
José Valim b76d39e9d4 Ensure map_join also works for empty binaries 2014-05-19 18:54:13 +02:00
José Valim be34a75124 Disabling :hygiene in quote is deprecated 2014-05-19 18:25:56 +02:00
José Valim 78c16c0e89 Do not allow nil context to be set on quote 2014-05-19 15:41:09 +02:00
José Valim 860ca5b5cb Add Macro.prewalk/2/3 and Macro.postwalk/2/3 2014-05-19 15:31:58 +02:00
José Valim ce8733fc6f Merge pull request #2293 from liveforeverx/fix_enum
fix Enum.join for empty elements in the begining of the collection
2014-05-19 12:44:12 +02:00
Dima Aleksandrov 24109337fe fix Enum.join for empty elements in the begining of the collection 2014-05-19 12:35:52 +02:00
José Valim 90c2959ed7 Update record.ex docs, closes #2291 2014-05-19 11:42:20 +02:00
José Valim 70e3be3ccf Merge pull request #2292 from mururu/fix-doc_test-docs
Fix ExUnit.DocTest docs
2014-05-19 11:37:24 +02:00
Yuki Ito eeb796107e Fix ExUnit.DocTest docs 2014-05-19 17:04:47 +09:00
José Valim 166bbb17f9 Reduce examples of disabling macros hygiene 2014-05-18 00:51:45 +02:00
José Valim 4ed84f86cd Update CHANGELOG 2014-05-17 17:10:30 +02:00
José Valim 540c763c85 Move binary and list conversions to String and List 2014-05-17 17:02:46 +02:00
José Valim 3d4877ffda Get rid of BitString module and bitstring conversions 2014-05-17 16:34:26 +02:00
José Valim 84262257ef Move Integer/Float conversions to their respective modules 2014-05-17 16:29:49 +02:00
José Valim 68dd952b45 More deprecations around tuples and records 2014-05-17 16:13:02 +02:00
José Valim a068b5a4b0 Move List and String deprecations to new mechanism 2014-05-17 16:02:56 +02:00
José Valim c6da7b56ba Optimize cond expansion 2014-05-17 15:56:47 +02:00
Eric Meadows-Jönsson dbe918c911 Merge pull request #2288 from ericmj/compile-deprecations
Make deprecations compiler warnings
2014-05-17 15:53:53 +02:00
Eric Meadows-Jönsson 4977ec8c05 Make deprecations compiler warnings 2014-05-17 15:52:00 +02:00
José Valim bf8f5c4bb8 Add String.to_char_list and List.to_string 2014-05-17 13:59:17 +02:00
José Valim 3bb8e17e9e Add iodata_to_binary and chardata_to_string to IO 2014-05-17 13:06:40 +02:00
José Valim 6b755112ce Add a test for mix config files 2014-05-17 12:09:10 +02:00
José Valim 9650344c76 Do faster expansion on rewrites 2014-05-16 23:49:12 +02:00
José Valim ca6788113e Deprecations should go to standard_error 2014-05-16 22:39:56 +02:00
José Valim 095e4a7d2e Improve docs for string, process and supervisor 2014-05-16 22:28:20 +02:00
José Valim 23475f9590 Make exception specs match the ones defined in callback 2014-05-16 18:53:06 +02:00
José Valim e69a35a85b Merge pull request #2281 from bitwalker/please-the-dialyzer-gods
Add typespecs for builtins, __record__,  __struct__, exception
2014-05-16 18:51:58 +02:00
Paul Schoenfelder a6392a8220 Add typespecs for builtins, __record__, and __struct__
Added typespecs for:

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

[ci skip]
2014-05-15 20:36:48 +12:00
José Valim 5e34d0a7f1 Allow h +/1 as valid syntax 2014-05-13 14:21:51 +02:00
José Valim 5d775f2c24 Remove warning from new applications 2014-05-13 13:56:01 +02:00
José Valim 9acf4b2df9 Merge pull request #2279 from alco/keyword-doc-fixes
Keyword doc fixes
2014-05-13 16:52:48 +09:00
Alexei Sholik 45ee18578b Small doc fixes for Keyword 2014-05-13 00:26:21 +03:00
Alexei Sholik fafdf40687 Better docs and tests for Keyword.update and Keyword.update! 2014-05-13 00:26:07 +03:00
José Valim 22206f87bf Merge pull request #2276 from alco/option-parser-fix
Option parser fix
2014-05-13 03:44:06 +09:00
José Valim 2e7d938707 Merge pull request #2277 from alco/elixir-translator-warning
Remove unused import in elixir_translator.erl
2014-05-12 20:37:00 +02:00
Alexei Sholik 57ccb29bc4 Remove unused import in elixir_translator.erl 2014-05-12 21:35:21 +03:00
Alexei Sholik 79bb046ad6 Fix unused variable warning 2014-05-12 21:33:04 +03:00
Alexei Sholik 24c17c350c Format OptionParser tests more nicely 2014-05-12 21:27:44 +03:00
Alexei Sholik 2187db5415 Don’t ignore values for ‘--no-*’ options 2014-05-12 21:24:31 +03:00
Alexei Sholik 84cdbe3562 Add more sanity-check tests for OptionParser 2014-05-12 21:24:31 +03:00
Alexei Sholik 95ed007b1c Properly handle non-boolean values for boolean options 2014-05-12 21:24:31 +03:00
José Valim 55bd1d4e5a core -> stdlib in makefile 2014-05-12 19:53:09 +02:00
José Valim e6e1af7d84 Merge pull request #2275 from alco/makefile-rename-kernel
Rename some Makefile targets. Closes #2274
2014-05-12 19:50:50 +02:00
Alexei Sholik 1019917f1f Rename some Makefile targets. Closes #2274
kernel -> core
test_kernel -> test_stdlib
2014-05-12 20:41:06 +03:00
José Valim 9f0a30de43 Merge xgen and soft deprecate existing behaviours 2014-05-12 19:10:50 +02:00
José Valim ef67a9527a List all soft deprecations that have become deprecated 2014-05-12 12:37:03 +02:00
José Valim ff04acd3a6 Update exit signals in Exception to {:EXIT, pid} 2014-05-12 12:35:43 +02:00
José Valim a79f7c291f Merge pull request #2270 from fishcakez/format_banner_EXIT
Format EXIT signals consistently
2014-05-12 12:13:06 +02:00
José Valim 10ea9bdd40 Mention ERL_COMPILER_OPTIONS and rename ELIXIR_ERL_OPTS to ELIXIR_ERL_OPTIONS
Closes #2271
2014-05-12 09:47:37 +02:00
James Fish ba62dc9522 Format EXIT signals consistently 2014-05-11 18:01:13 +01:00
Eric Meadows-Jönsson d9bb48f691 Always filter children of remote dependencies 2014-05-11 15:13:09 +02:00
José Valim 580107d29c Deprecate code that was previously soft-deprecated 2014-05-11 14:19:05 +02:00
José Valim bb8f3aaa8d Remove deprecated code 2014-05-11 13:55:11 +02:00
José Valim f4b436cbc3 Start v0.13.3-dev 2014-05-11 12:30:45 +02:00
215 changed files with 7275 additions and 4356 deletions
+48 -1
View File
@@ -1,3 +1,51 @@
# v0.13.3 (2014-05-24)
* Enhancements
* [OptionParser] Add `:strict` option that only parses known switches
* [OptionParser] Add `next/2` useful for manual parsing of options
* [Macro] Add `Macro.prewalk/2/3` and `Macro.postwalk/2/3`
* [Kernel] `GenEvent`, `GenServer`, `Supervisor`, `Agent` and `Task` modules added
* [Kernel] Make deprecations compiler warnings to avoid the same deprecation being printed multiple times
* Bug fixes
* [Enum] Fix `Enum.join/2` and `Enum.map_join/3` for empty binaries at the beginning of the collection
* [ExUnit] Ensure the formatter doesn't error when printing :EXITs
* [Kernel] Rename `ELIXIR_ERL_OPTS` to `ELIXIR_ERL_OPTIONS` for consistency with `ERL_COMPILER_OPTIONS`
* [OptionParser] Parse `-` as a plain argument
* [OptionParser] `--` is always removed from argument list on `parse/2` and when it is the leading entry on `parse_head/2`
* [Regex] Properly escape regex (previously regex controls were double escaped)
* Soft deprecations (no warnings emitted)
* [Dict] `Dict.Behaviour` is deprecated in favor of `Dict`
* [Kernel] `Application.Behaviour`, `GenEvent.Behaviour`, `GenServer.Behaviour` and `Supervisor.Behaviour` are deprecated in favor of `Application`, `GenEvent`, `GenServer` and `Supervisor`
* [Kernel] `defexception/3` is deprecated in favor of `defexception/1`
* [Kernel] `raise/3` is deprecated in favor of `reraise/2`
* [Kernel] `set_elem/3` is deprecated in favor of `put_elem/3`
* Soft deprecations for conversions (no warnings emitted)
* [Kernel] `atom_to_binary/1` and `atom_to_list/1` are deprecated in favor of `Atom.to_string/1` and `Atom.to_char_list/1`
* [Kernel] `bitstring_to_list/1` and `list_to_bitstring/1` are deprecated in favor of the `:erlang` ones
* [Kernel] `binary_to_atom/1`, `binary_to_existing_atom/1`, `binary_to_float/1`, `binary_to_integer/1` and `binary_to_integer/2` are deprecated in favor of conversion functions in `String`
* [Kernel] `float_to_binary/*` and `float_to_list/*` are deprecated in favor of `Float.to_string/*` and `Float.to_char_list/*`
* [Kernel] `integer_to_binary/*` and `integer_to_list/*` are deprecated in favor of `Integer.to_string/*` and `Integer.to_char_list/*`
* [Kernel] `iodata_to_binary/1` and `iodata_length/1` are deprecated `IO.iodata_to_binary/1` and `IO.iodata_length/1`
* [Kernel] `list_to_atom/1`, `list_to_existing_atom/1`, `list_to_float/1`, `list_to_integer/1`, `list_to_integer/2` and `list_to_tuple/1` are deprecated in favor of conversion functions in `List`
* [Kernel] `tuple_to_list/1` is deprecated in favor of `Tuple.to_list/1`
* [List] `List.from_char_data/1` and `List.from_char_data!/1` deprecated in favor of `String.to_char_list/1`
* [String] `String.from_char_data/1` and `String.from_char_data!/1` deprecated in favor of `List.to_string/1`
* Deprecations
* [Kernel] `is_exception/1`, `is_record/1` and `is_record/2` are deprecated in favor of `Exception.exception?1`, `Record.record?/1` and `Record.record?/2`
* [Kernel] `defrecord/3` is deprecated in favor of structs
* [Kernel] `:hygiene` in `quote` is deprecated
* [Mix] `Mix.project/0` is deprecated in favor of `Mix.Project.config/0`
* [Process] `Process.spawn/1`, `Process.spawn/3`, `Process.spawn_link/1`, `Process.spawn_link/3`, `Process.spawn_monitor/1`, `Process.spawn_monitor/3`, `Process.send/2` and `Process.self/0` are deprecated in favor of the ones in `Kernel`
* Backwards incompatible changes
* [Exception] Exceptions now generate structs instead of records
* [OptionParser] Errors on parsing returns the switch and value as binaries (unparsed)
* [String] `String.to_char_list/1` (previously deprecated) no longer returns a tuple but the char list only and raises in case of failure
# v0.13.2 (2014-05-11)
* Enhancements
@@ -30,7 +78,6 @@
* [System] Convert remaining functions in System to rely on char data
* Soft deprecations (no warnings emitted)
* [Application] `use Application.Behaviour` is deprecated in favor of `use Application`
* [Exception] `exception.message` is deprecated in favor `Exception.message/1` for retrieving exception messages
* [Kernel] `is_exception/1`, `is_record/1` and `is_record/2` are deprecated in favor of `Exception.exception?1`, `Record.record?/1` and `Record.record?/2`
* [Mix] `Mix.project/0` is deprecated in favor of `Mix.Project.config/0`
+15 -9
View File
@@ -67,10 +67,10 @@ erlang:
# Since Mix depends on EEx and EEx depends on
# Mix, we first compile EEx without the .app
# file, then mix and then compile eex fully
elixir: kernel lib/eex/ebin/Elixir.EEx.beam mix ex_unit eex iex
# file, then mix and then compile EEx fully
elixir: stdlib lib/eex/ebin/Elixir.EEx.beam mix ex_unit eex iex
kernel: $(KERNEL) VERSION
stdlib: $(KERNEL) VERSION
$(KERNEL): lib/elixir/lib/*.ex lib/elixir/lib/*/*.ex
$(Q) if [ ! -f $(KERNEL) ]; then \
echo "==> bootstrap (compile)"; \
@@ -149,20 +149,26 @@ release_docs: docs
test: test_erlang test_elixir
test_erlang: compile
TEST_ERL = lib/elixir/test/erlang
TEST_EBIN = lib/elixir/test/ebin
TEST_ERLS = $(addprefix $(TEST_EBIN)/, $(addsuffix .beam, $(basename $(notdir $(wildcard $(TEST_ERL)/*.erl)))))
test_erlang: compile $(TEST_ERLS)
@ echo "==> elixir (eunit)"
$(Q) mkdir -p lib/elixir/test/ebin
$(Q) $(ERLC) -pa lib/elixir/ebin -o lib/elixir/test/ebin lib/elixir/test/erlang/*.erl
$(Q) $(ERL) -pa lib/elixir/test/ebin -s test_helper test -s erlang halt;
$(Q) $(ERL) -pa $(TEST_EBIN) -s test_helper test;
@ echo ""
test_elixir: test_kernel test_ex_unit test_doc_test test_mix test_eex test_iex
$(TEST_EBIN)/%.beam: $(TEST_ERL)/%.erl
$(Q) mkdir -p $(TEST_EBIN)
$(Q) $(ERLC) -o $(TEST_EBIN) $<
test_elixir: test_stdlib test_ex_unit test_doc_test test_mix test_eex test_iex
test_doc_test: compile
@ echo "==> doctest (exunit)"
$(Q) cd lib/elixir && ../../bin/elixir -r "test/doc_test.exs";
test_kernel: compile
test_stdlib: compile
@ echo "==> elixir (exunit)"
$(Q) cd lib/elixir && ../../bin/elixir -r "test/elixir/test_helper.exs" -pr "test/elixir/**/*_test.exs";
+1 -1
View File
@@ -54,4 +54,4 @@ We appreciate any contribution to Elixir, so check out our [CONTRIBUTING.md](CON
Elixir source code is released under Apache 2 License with some parts under Erlang's license (EPL).
Check LEGAL and LICENSE files for more information.
Check [LEGAL](LEGAL) and [LICENSE](LICENSE) files for more information.
+1 -1
View File
@@ -1 +1 @@
0.13.2
0.13.3
+2 -2
View File
@@ -21,7 +21,7 @@ if [ $# -eq 0 ] || [ "$1" = "--help" ] || [ "$1" = "-h" ]; then
** Options marked with (*) can be given more than once
** Options given after the .exs file or -- are passed down to the executed code
** Options can be passed to the erlang runtime using ELIXIR_ERL_OPTS or --erl" >&2
** Options can be passed to the erlang runtime using ELIXIR_ERL_OPTIONS or --erl" >&2
exit 1
fi
@@ -94,4 +94,4 @@ if [ -z "$ERL_PATH" ]; then
fi
fi
exec "$ERL_PATH" -pa "$SCRIPT_PATH"/../lib/*/ebin -noshell $ELIXIR_ERL_OPTS $ERL -extra "$@"
exec "$ERL_PATH" -pa "$SCRIPT_PATH"/../lib/*/ebin -noshell $ELIXIR_ERL_OPTIONS $ERL -extra "$@"
+7 -11
View File
@@ -1,12 +1,8 @@
@echo off
set argc=0
for %%A in (%*) do (
if "%%A"=="--help" goto documentation
if "%%A"=="-h" goto documentation
if "%%A"=="/h" goto documentation
set /A argc+=1
)
if %argc%==0 goto documentation
if "%1"=="" goto documentation
if "%1"=="--help" goto documentation
if "%1"=="-h" goto documentation
if "%1"=="/h" goto documentation
goto parseopts
:documentation
@@ -31,7 +27,7 @@ echo --gen-debug Turns on default debugging for all GenServers
echo.
echo ** Options marked with (*) can be given more than once
echo ** Options given after the .exs file or -- are passed down to the executed code
echo ** Options can be passed to the erlang runtime using ELIXIR_ERL_OPTS or --erl
echo ** Options can be passed to the erlang runtime using ELIXIR_ERL_OPTIONS or --erl
goto :EOF
:parseopts
@@ -95,7 +91,7 @@ for /d %%d in ("%originPath%..\lib\*.") do (
SETLOCAL disabledelayedexpansion
:run
IF %useWerl% EQU 1 (
werl %ext_libs% -noshell %ELIXIR_ERL_OPTS% %parsErlang% -s elixir start_cli %beforeExtra% -extra %*
werl %ext_libs% -noshell %ELIXIR_ERL_OPTIONS% %parsErlang% -s elixir start_cli %beforeExtra% -extra %*
) ELSE (
erl %ext_libs% -noshell %ELIXIR_ERL_OPTS% %parsErlang% -s elixir start_cli %beforeExtra% -extra %*
erl %ext_libs% -noshell %ELIXIR_ERL_OPTIONS% %parsErlang% -s elixir start_cli %beforeExtra% -extra %*
)
+2 -1
View File
@@ -10,7 +10,8 @@ if [ $# -eq 0 ] || [ "$1" = "--help" ] || [ "$1" = "-h" ]; then
--verbose Print informational messages.
** Options given after -- are passed down to the executed code
** Options can be passed to the erlang runtime using ELIXIR_ERL_OPTS" >&2
** Options can be passed to the erlang runtime using ELIXIR_ERL_OPTIONS
** Options can be passed to the erlang compiler using ERL_COMPILER_OPTS" >&2
exit 1
fi
+2 -2
View File
@@ -19,8 +19,8 @@ echo --ignore-module-conflict
echo --warnings-as-errors Treat warnings as errors and return non-zero exit code
echo --verbose Print informational messages.
echo.
echo ** Options marked with (*) can be given more than once
echo ** Options given after -- are passed down to the executed code
echo ** Options can be passed to the erlang runtime using ELIXIR_ERL_OPTS" >&2
echo ** Options can be passed to the erlang runtime using ELIXIR_ERL_OPTIONS
echo ** Options can be passed to the erlang compiler using ERL_COMPILER_OPTS >&2
:run
call "%~dp0\elixir.bat" +elixirc %*
+1 -1
View File
@@ -23,7 +23,7 @@ if [ $# -gt 0 ] && ([ "$1" = "--help" ] || [ "$1" = "-h" ]); then
** Options marked with (*) can be given more than once
** Options given after the .exs file or -- are passed down to the executed code
** Options can be passed to the VM using ELIXIR_ERL_OPTS or --erl" >&2
** Options can be passed to the VM using ELIXIR_ERL_OPTIONS or --erl" >&2
exit 1
fi
+3 -1
View File
@@ -1,4 +1,6 @@
defexception EEx.SyntaxError, message: nil
defmodule EEx.SyntaxError do
defexception [:message]
end
defmodule EEx do
@moduledoc ~S"""
+2 -2
View File
@@ -21,7 +21,7 @@ defmodule EEx.Compiler do
# Generates the buffers by handling each expression from the tokenizer
defp generate_buffer([{:text, chars}|t], buffer, scope, state) do
buffer = state.engine.handle_text(buffer, String.from_char_data!(chars))
buffer = state.engine.handle_text(buffer, IO.chardata_to_string(chars))
generate_buffer(t, buffer, scope, state)
end
@@ -68,7 +68,7 @@ defmodule EEx.Compiler do
defp wrap_expr(current, line, buffer, chars, state) do
new_lines = List.duplicate(?\n, line - state.line)
key = length(state.quoted)
placeholder = '__EEX__(' ++ integer_to_list(key) ++ ');'
placeholder = '__EEX__(' ++ Integer.to_char_list(key) ++ ');'
{current ++ placeholder ++ new_lines ++ chars,
%{state | quoted: [{key, buffer}|state.quoted]}}
end
+1 -1
View File
@@ -13,7 +13,7 @@ defmodule EEx.Tokenizer do
"""
def tokenize(bin, line) when is_binary(bin) do
tokenize(List.from_char_data!(bin), line)
tokenize(String.to_char_list(bin), line)
end
def tokenize(list, line) do
+1 -1
View File
@@ -7,7 +7,7 @@ defmodule EExText.Compiled do
fill_in_stacktrace
{__ENV__.line, hd(tl(System.stacktrace))}
end
{:erlang, 1, 2}.tuple_to_list
EEx.function_from_string :def, :string_sample, "<%= a + b %>", [:a, :b]
filename = Path.join(__DIR__, "fixtures/eex_template_with_bindings.eex")
+1 -1
View File
@@ -42,7 +42,7 @@ defimpl Access, for: List do
end
def access(_dict, key) do
raise ArgumentError, message: "the access protocol for lists expect the key to be an atom, got: #{inspect key}"
raise ArgumentError, "the access protocol for lists expect the key to be an atom, got: #{inspect key}"
end
end
+226
View File
@@ -0,0 +1,226 @@
defmodule Agent do
@moduledoc """
Agents are a simple abstraction around state.
Often in Elixir there is a need to share or store state that
must be accessed from different processes or by a same process
in different points in time.
The Agent module provides a basic server implementation that
allows state to be retrieved and updated via a simple API.
## Examples
For example, in the Mix tool that ships with Elixir, we need
to keep a set of all tasks executed by a given project. Since
this set is shared, we can implement it with an Agent:
defmodule Mix.TasksServer do
def start_link do
Agent.start_link(fn -> HashSet.new end, name: __MODULE__)
end
@doc "Checks if the task has already executed"
def executed?(task, project) do
item = {task, project}
Agent.get(__MODULE__, fn set ->
item in set
end)
end
@doc "Marks a task as executed"
def put_task(task, project) do
item = {task, project}
Agent.update(__MODULE__, &Set.put(&1, item))
end
end
Note that agents still provide a segregation in between the
client and server APIs, as seen in GenServers. In particular,
all code inside the function passed to the agent is executed
by the agent. This distinction is important because you may
want to avoid expensive operations inside the agent, as it will
effectively block the agent until the request is fullfilled.
Consider these two examples:
# Compute in the agent/server
def get_something(agent) do
Agent.get(agent, fn state -> do_something_expensive(state) end)
end
# Compute in the agent/client
def get_something(agent) do
Agent.get(agent, &(&1)) |> do_something_expensive()
end
The first one blocks the agent while the second one copies
all the state to the client and executes the operation in the client.
The trade-off here is exactly if the data is small enough to be
sent to the client cheaply or large enough to require processing on
the server (or at least some initial processing).
## Name registration
An Agent is bound to the same name registration rules as GenServers.
Read more about it in the `GenServer` docs.
## A word on distributed agents
It is important to consider the limitations of distributed agents. Agents
work by sending anonymous functions in between the caller and the agent.
In a distributed setup with multiple nodes, agents only work if the caller
(client) and the agent have the same version of a given module.
This setup may exhibit issues when doing "rolling upgrades". By rolling
upgrades we mean the following situation: you wish to deploy a new version of
your software by *shutting down* some of your nodes and replacing them by
nodes running a new version of the software. In this setup, part of your
environment will have one version of a given module and the other part
another version (the newer one) of the same module; this may cause agents to
crash. That said, if you plan to run in distributed environments, agents
should likely be avoided.
Note, however, that agents work fine if you want to perform hot code
swapping, as it keeps both the old and new versions of a given module.
We detail how to do hot code swapping with agents in the next section.
## Hot code swapping
An agent can have its code hot swapped live by simply passing a module,
function and args tuple to the update instruction. For example, imagine
you have an agent named `:sample` and you want to convert its inner state
from some dict structure to a map. It can be done with the following
instruction:
{:update, :sample, {:advanced, {Enum, :into, [%{}]}}}
The agent's state will be added to the given list as the first argument.
"""
@typedoc "Return values of `start*` functions"
@type on_start :: {:ok, pid} | {:error, {:already_started, pid} | term}
@typedoc "The agent name"
@type name :: atom | {:global, term} | {:via, module, term}
@typedoc "The agent reference"
@type agent :: pid | {atom, node} | name
@typedoc "The agent state"
@type state :: term
@doc """
Starts an agent linked to the current process.
This is often used to start the agent as part of a supervision tree.
Once the agent is spawned, the given function is invoked and its return
value is used as the agent state. Note that `start_link` does not return
until the given function has returned.
## Options
The `:name` option is used for registration as described in the module
documentation. If the `:timeout` option is present, the agent is allowed to
spend at most the given amount of milliseconds on initialization or it will
be terminated and the start function will return `{:error, :timeout}`.
If the `:debug` option is present, the corresponding function in the
[`:sys` module](http://www.erlang.org/doc/man/sys.html) will be invoked.
If the `:spawn_opt` option is present, its value will be passed as options
to the underlying process as in `Process.spawn/3`.
## Return values
If the server is successfully created and initialized, the function returns
`{:ok, pid}`, where pid is the pid of the server. If there already exists
an agent with the specified name, the function returns
`{:error, {:already_started, pid}}` with the pid of that process.
If the given function callback fails with `reason`, the function returns
`{:error, reason}`.
"""
@spec start_link((() -> term), GenServer.options) :: on_start
def start_link(fun, options \\ []) when is_function(fun, 0) do
GenServer.start_link(Agent.Server, fun, options)
end
@doc """
Starts an agent process without links (outside of a supervision tree).
See `start_link/2` for more information.
"""
@spec start((() -> term), GenServer.options) :: on_start
def start(fun, options \\ []) when is_function(fun, 0) do
GenServer.start(Agent.Server, fun, options)
end
@doc """
Gets the agent value and executes the given function.
The function `fun` is sent to the `agent` which invokes the function
passing the agent state. The result of the function invocation is
returned.
A timeout can also be specified (it has a default value of 5000).
"""
@spec get(agent, (state -> a), timeout) :: a when a: var
def get(agent, fun, timeout \\ 5000) when is_function(fun, 1) do
GenServer.call(agent, {:get, fun}, timeout)
end
@doc """
Gets and updates the agent state in one operation.
The function `fun` is sent to the `agent` which invokes the function
passing the agent state. The function must return a tuple with two
elements, the first being the value to return (i.e. the get value)
and the second one is the new state.
A timeout can also be specified (it has a default value of 5000).
"""
@spec get_and_update(agent, (state -> {a, state}), timeout) :: a when a: var
def get_and_update(agent, fun, timeout \\ 5000) when is_function(fun, 1) do
GenServer.call(agent, {:get_and_update, fun}, timeout)
end
@doc """
Updates the agent state.
The function `fun` is sent to the `agent` which invokes the function
passing the agent state. The function must return the new state.
A timeout can also be specified (it has a default value of 5000).
This function always returns `:ok`.
"""
@spec update(agent, (state -> state)) :: :ok
def update(agent, fun, timeout \\ 5000) when is_function(fun, 1) do
GenServer.call(agent, {:update, fun}, timeout)
end
@doc """
Performs a cast (fire and forget) operation on the agent state.
The function `fun` is sent to the `agent` which invokes the function
passing the agent state. The function must return the new state.
Note that `cast` returns `:ok` immediately, regardless of whether the
destination node or agent exists.
"""
@spec cast(agent, (state -> state)) :: :ok
def cast(agent, fun) when is_function(fun, 1) do
GenServer.cast(agent, fun)
end
@doc """
Stops the agent.
Returns `:ok` if the agent is stopped within the given `timeout`.
"""
@spec stop(agent, timeout) :: :ok
def stop(agent, timeout \\ 5000) do
GenServer.call(agent, :stop, timeout)
end
end
+53
View File
@@ -0,0 +1,53 @@
defmodule Agent.Server do
@moduledoc false
use GenServer
def init(fun) do
{:ok, fun.()}
end
def handle_call({:get, fun}, _from, state) do
{:reply, fun.(state), state}
end
def handle_call({:get_and_update, fun}, _from, state) do
{reply, state} = fun.(state)
{:reply, reply, state}
end
def handle_call({:update, fun}, _from, state) do
{:reply, :ok, fun.(state)}
end
def handle_call(:stop, _from, state) do
{:stop, :normal, :ok, state}
end
def handle_call(msg, from, state) do
super(msg, from, state)
end
def handle_cast(fun, state) when is_function(fun, 1) do
{:noreply, fun.(state)}
end
def handle_cast(msg, state) do
super(msg, state)
end
def code_change(_old, state, { m, f, a }) do
{:ok, apply(m, f, [state|a])}
end
def terminate(_reason, _state) do
# There is a race condition if the agent is
# restarted too fast and it is registered.
try do
self |> Process.info(:registered_name) |> elem(1) |> Process.unregister
rescue
_ -> :ok
end
:ok
end
end
+3 -3
View File
@@ -246,7 +246,7 @@ defmodule Application do
@doc """
Stops the given `app`.
When stopped, the application is still loaded.
When stopped, the application is still loaded.
"""
@spec stop(app) :: :ok | {:error, term}
def stop(app) do
@@ -305,8 +305,8 @@ defmodule Application do
@spec app_dir(app) :: String.t
def app_dir(app) when is_atom(app) do
case :code.lib_dir(app) do
lib when is_list(lib) -> String.from_char_data!(lib)
{:error, :bad_name} -> raise ArgumentError, message: "unknown application: #{inspect app}"
lib when is_list(lib) -> IO.chardata_to_string(lib)
{:error, :bad_name} -> raise ArgumentError, "unknown application: #{inspect app}"
end
end
-1
View File
@@ -2,7 +2,6 @@ defmodule Application.Behaviour do
@moduledoc false
defmacro __using__(_) do
# IO.write :stderr, "use Application.Behaviour is deprecated, please use Application instead\n#{Exception.format_stacktrace}"
quote location: :keep do
@behaviour :application
+25
View File
@@ -0,0 +1,25 @@
defmodule Atom do
@doc """
Convenience functions for working with atoms.
"""
@doc """
Converts an atom to string.
Inlined by the compiler.
"""
@spec to_string(atom) :: String.t
def to_string(atom) do
:erlang.atom_to_binary(atom, :utf8)
end
@doc """
Converts an atom to a char list.
Inlined by the compiler.
"""
@spec to_char_list(atom) :: char_list
def to_char_list(atom) do
:erlang.atom_to_list(atom)
end
end
+4 -4
View File
@@ -25,7 +25,7 @@ defmodule Base do
defp unquote(dec)(unquote(encoding)), do: unquote(value)
end
defp unquote(dec)(c) do
raise ArgumentError, message: "non-alphabet digit found: #{<<c>>}"
raise ArgumentError, "non-alphabet digit found: #{<<c>>}"
end
end
@@ -365,7 +365,7 @@ defmodule Base do
end
end
defp do_decode16(_, _) do
raise ArgumentError, message: "odd-length string"
raise ArgumentError, "odd-length string"
end
defp do_encode64(<<>>, _), do: <<>>
@@ -400,7 +400,7 @@ defmodule Base do
end
end
defp do_decode64(_, _) do
raise ArgumentError, message: "incorrect padding"
raise ArgumentError, "incorrect padding"
end
defp do_encode32(<<>>, _), do: <<>>
@@ -458,7 +458,7 @@ defmodule Base do
end
end
defp do_decode32(_, _) do
raise ArgumentError, message: "incorrect padding"
raise ArgumentError, "incorrect padding"
end
end
+3 -3
View File
@@ -78,7 +78,7 @@ defmodule Behaviour do
{name, args} ->
do_callback(:def, name, args, name, length(args), args, return, guards, caller)
_ ->
raise ArgumentError, message: "invalid syntax in defcallback #{Macro.to_string(spec)}"
raise ArgumentError, "invalid syntax in defcallback #{Macro.to_string(spec)}"
end
end
@@ -88,7 +88,7 @@ defmodule Behaviour do
do_callback(:defmacro, :"MACRO-#{name}", [quote(do: env :: Macro.Env.t)|args],
name, length(args), args, return, guards, caller)
_ ->
raise ArgumentError, message: "invalid syntax in defmacrocallback #{Macro.to_string(spec)}"
raise ArgumentError, "invalid syntax in defmacrocallback #{Macro.to_string(spec)}"
end
end
@@ -111,7 +111,7 @@ defmodule Behaviour do
end
defp ensure_not_default({:\\, _, [_, _]}) do
raise ArgumentError, message: "default arguments \\\\ not supported in defcallback/defmacrocallback"
raise ArgumentError, "default arguments \\\\ not supported in defcallback/defmacrocallback"
end
defp ensure_not_default(_), do: :ok
+6 -13
View File
@@ -1,11 +1,4 @@
defmodule Code do
defexception LoadError, [:file, :message] do
def exception(opts) do
file = opts[:file]
LoadError[message: "could not load #{file}", file: file]
end
end
@moduledoc """
Utilities for managing code compilation, code evaluation and code loading.
@@ -135,14 +128,14 @@ defmodule Code do
## Examples
iex> contents = quote(hygiene: [vars: false], do: a + b)
iex> contents = quote(do: var!(a) + var!(b))
iex> Code.eval_quoted(contents, [a: 1, b: 2], file: __ENV__.file, line: __ENV__.line)
{3, [a: 1, b: 2]}
For convenience, you can pass `__ENV__` as the `opts` argument and
all options will be automatically extracted from the current environment:
iex> contents = quote(hygiene: [vars: false], do: a + b)
iex> contents = quote(do: var!(a) + var!(b))
iex> Code.eval_quoted(contents, [a: 1, b: 2], __ENV__)
{3, [a: 1, b: 2]}
@@ -171,7 +164,7 @@ defmodule Code do
valid = is_list(requires) and Enum.all?(requires, &is_atom(&1))
unless valid do
raise ArgumentError, message: "expected :#{kind} option given to eval in the format: [module]"
raise ArgumentError, "expected :#{kind} option given to eval in the format: [module]"
end
end
@@ -181,7 +174,7 @@ defmodule Code do
end)
unless valid do
raise ArgumentError, message: "expected :#{kind} option given to eval in the format: [{module, module}]"
raise ArgumentError, "expected :#{kind} option given to eval in the format: [{module, module}]"
end
end
@@ -193,7 +186,7 @@ defmodule Code do
end)
unless valid do
raise ArgumentError, message: "expected :#{kind} option given to eval in the format: [{module, [{name, arity}]}]"
raise ArgumentError, "expected :#{kind} option given to eval in the format: [{module, [{name, arity}]}]"
end
end
@@ -470,7 +463,7 @@ defmodule Code do
if File.regular?(file) do
file
else
raise LoadError, file: file
raise Code.LoadError, file: file
end
end
end
+205 -4
View File
@@ -31,6 +31,70 @@ defmodule Dict do
Dictionaries are required to implement all operations
using the match (`===`) operator.
## Default implementation
Default implementations for some functions in the `Dict` module
are provided via `use Dict`.
For example:
defmodule MyDict do
use Dict.Behaviour
# implement required functions (see below)
# override default implementations if optimization
# is needed
end
The client module must contain the following functions:
* `delete/2`
* `fetch/2`
* `put/3`
* `reduce/3`
* `size/1`
All functions, except `reduce/3`, are required by the Dict behaviour.
`reduce/3` must be implemtented as per the Enumerable protocol.
Based on these functions, `Dict` generates default implementations
for the following functions:
* `drop/2`
* `equal?/2`
* `fetch!/2`
* `get/2`
* `get/3`
* `has_key?/2`
* `keys/1`
* `merge/2`
* `merge/3`
* `pop/2`
* `pop/3`
* `put_new/3`
* `split/2`
* `take/2`
* `to_list/1`
* `update/4`
* `update!/3`
* `values/1`
All of these functions are defined as overridable, so you can provide
your own implementation if needed.
Note you can also test your custom module via `Dict`'s doctests:
defmodule MyDict do
# ...
end
defmodule MyTests do
use ExUnit.Case
doctest Dict
defp dict_impl, do: MyDict
end
"""
use Behaviour
@@ -63,6 +127,146 @@ defmodule Dict do
defcallback update!(t, key, (value -> value)) :: t | no_return
defcallback values(t) :: list(value)
defmacro __using__(_) do
# Use this import to guarantee proper code expansion
import Kernel, except: [size: 1]
quote do
@behaviour Dict
def get(dict, key, default \\ nil) do
case fetch(dict, key) do
{:ok, value} -> value
:error -> default
end
end
def fetch!(dict, key) do
case fetch(dict, key) do
{:ok, value} -> value
:error -> raise KeyError, key: key, term: dict
end
end
def has_key?(dict, key) do
match? {:ok, _}, fetch(dict, key)
end
def put_new(dict, key, value) do
case has_key?(dict, key) do
true -> dict
false -> put(dict, key, value)
end
end
def drop(dict, keys) do
Enum.reduce(keys, dict, &delete(&2, &1))
end
def take(dict, keys) do
Enum.reduce(keys, new, fn key, acc ->
case fetch(dict, key) do
{:ok, value} -> put(acc, key, value)
:error -> acc
end
end)
end
def to_list(dict) do
reduce(dict, {:cont, []}, fn
kv, acc -> {:cont, [kv|acc]}
end) |> elem(1) |> :lists.reverse
end
def keys(dict) do
reduce(dict, {:cont, []}, fn
{k, _}, acc -> {:cont, [k|acc]}
end) |> elem(1) |> :lists.reverse
end
def values(dict) do
reduce(dict, {:cont, []}, fn
{_, v}, acc -> {:cont, [v|acc]}
end) |> elem(1) |> :lists.reverse
end
def equal?(dict1, dict2) do
# Use this import to avoid conflicts in the user code
import Kernel, except: [size: 1]
case size(dict1) == size(dict2) do
false -> false
true ->
reduce(dict1, {:cont, true}, fn({k, v}, _acc) ->
case fetch(dict2, k) do
{:ok, ^v} -> {:cont, true}
_ -> {:halt, false}
end
end) |> elem(1)
end
end
def merge(dict1, dict2, fun \\ fn(_k, _v1, v2) -> v2 end) do
# Use this import to avoid conflicts in the user code
import Kernel, except: [size: 1]
if size(dict1) < size(dict2) do
reduce(dict1, {:cont, dict2}, fn {k, v1}, acc ->
{:cont, update(acc, k, v1, &fun.(k, v1, &1))}
end)
else
reduce(dict2, {:cont, dict1}, fn {k, v2}, acc ->
{:cont, update(acc, k, v2, &fun.(k, &1, v2))}
end)
end |> elem(1)
end
def update(dict, key, initial, fun) do
case fetch(dict, key) do
{:ok, value} ->
put(dict, key, fun.(value))
:error ->
put(dict, key, initial)
end
end
def update!(dict, key, fun) do
case fetch(dict, key) do
{:ok, value} ->
put(dict, key, fun.(value))
:error ->
raise KeyError, key: key, term: dict
end
end
def pop(dict, key, default \\ nil) do
case fetch(dict, key) do
{:ok, value} ->
{value, delete(dict, key)}
:error ->
{default, dict}
end
end
def split(dict, keys) do
Enum.reduce(keys, {new, dict}, fn key, {inc, exc} = acc ->
case fetch(exc, key) do
{:ok, value} ->
{put(inc, key, value), delete(exc, key)}
:error ->
acc
end
end)
end
defoverridable merge: 2, merge: 3, equal?: 2, to_list: 1, keys: 1,
values: 1, take: 2, drop: 2, get: 2, get: 3, fetch!: 2,
has_key?: 2, put_new: 3, pop: 2, pop: 3, split: 2,
update: 4, update!: 3
end
end
defmacrop target(dict) do
quote do
case unquote(dict) do
@@ -70,9 +274,6 @@ defmodule Dict do
x
%{} ->
Map
x when is_tuple(x) ->
IO.write :stderr, "Using records with the Dict module is deprecated, please use structs instead\n#{Exception.format_stacktrace}"
elem(x, 0)
x when is_list(x) ->
Keyword
x ->
@@ -487,6 +688,6 @@ defmodule Dict do
end
defp unsupported_dict(dict) do
raise ArgumentError, message: "unsupported dict: #{inspect dict}"
raise ArgumentError, "unsupported dict: #{inspect dict}"
end
end
+2 -192
View File
@@ -1,199 +1,9 @@
defmodule Dict.Behaviour do
@moduledoc ~S"""
Default implementations for some required functions in the `Dict` module.
@moduledoc false
This module makes it easier to create your own `Dict` compliant
module, by providing default implementations for some required functions.
## Example
defmodule MyDict do
use Dict.Behaviour
# implement required functions (see below)
# override default implementations if optimization is needed
end
The client module must contain the following functions:
* `delete/2`
* `fetch/2`
* `put/3`
* `reduce/3`
* `size/1`
All functions, except `reduce/3`, are required by the Dict behaviour.
`reduce/3` must be implemtented as per the Enumerable protocol.
Based on these functions, `Dict.Behaviour` generates default implementations
for the following functions:
* `drop/2`
* `equal?/2`
* `fetch!/2`
* `get/2`
* `get/3`
* `has_key?/2`
* `keys/1`
* `merge/2`
* `merge/3`
* `pop/2`
* `pop/3`
* `put_new/3`
* `split/2`
* `take/2`
* `to_list/1`
* `values/1`
* `update/4`
* `update!/3`
All of these functions are defined as overridable, so you can provide your own
implementation if needed.
Note you can also test your custom module via `Dict`'s doctests:
defmodule MyDict do
def new(keywords \\ []) do
...
end
end
defmodule MyTests do
use ExUnit.Case
doctest Dict
defp dict_impl, do: MyDict
end
"""
defmacro __using__(_) do
# Use this import to guarantee proper code expansion
import Kernel, except: [size: 1]
quote do
@behaviour Dict
def get(dict, key, default \\ nil) do
case fetch(dict, key) do
{:ok, value} -> value
:error -> default
end
end
def fetch!(dict, key) do
case fetch(dict, key) do
{:ok, value} -> value
:error -> raise KeyError, key: key, term: dict
end
end
def has_key?(dict, key) do
match? {:ok, _}, fetch(dict, key)
end
def put_new(dict, key, value) do
update(dict, key, value, fn(v) -> v end)
end
def drop(dict, keys) do
Enum.reduce keys, dict, &delete(&2, &1)
end
def take(dict, keys) do
Enum.reduce keys, new, fn key, acc ->
case fetch(dict, key) do
{:ok, value} -> put(acc, key, value)
:error -> acc
end
end
end
def to_list(dict) do
reduce(dict, {:cont, []}, fn
kv, acc -> {:cont, [kv|acc]}
end) |> elem(1) |> :lists.reverse
end
def keys(dict) do
reduce(dict, {:cont, []}, fn
{k, _}, acc -> {:cont, [k|acc]}
end) |> elem(1) |> :lists.reverse
end
def values(dict) do
reduce(dict, {:cont, []}, fn
{_, v}, acc -> {:cont, [v|acc]}
end) |> elem(1) |> :lists.reverse
end
def equal?(dict1, dict2) do
# Use this import to avoid conflicts in the user code
import Kernel, except: [size: 1]
case size(dict1) == size(dict2) do
false -> false
true ->
reduce(dict1, {:cont, true}, fn({k, v}, _acc) ->
case fetch(dict2, k) do
{:ok, ^v} -> {:cont, true}
_ -> {:halt, false}
end
end) |> elem(1)
end
end
def merge(dict1, dict2, fun \\ fn(_k, _v1, v2) -> v2 end) do
reduce(dict1, {:cont, dict2}, fn {k, v1}, acc ->
{:cont, update(acc, k, v1, &fun.(k, v1, &1))}
end) |> elem(1)
end
def update(dict, key, initial, fun) do
case fetch(dict, key) do
{ :ok, value } ->
put(dict, key, fun.(value))
:error ->
put(dict, key, initial)
end
end
def update!(dict, key, fun) do
case fetch(dict, key) do
{ :ok, value } ->
put(dict, key, fun.(value))
:error ->
raise KeyError, key: key, term: dict
end
end
def pop(dict, key, default \\ nil) do
case fetch(dict, key) do
{ :ok, value } ->
{ value, delete(dict, key) }
:error ->
{ default, dict }
end
end
def split(dict, keys) do
Enum.reduce keys, { new, dict }, fn key, { inc, exc } = acc ->
case fetch(exc, key) do
{ :ok, value } ->
{ put(inc, key, value), delete(exc, key) }
:error ->
acc
end
end
end
defoverridable merge: 2, merge: 3, equal?: 2, to_list: 1, keys: 1,
values: 1, take: 2, drop: 2, get: 2, get: 3, fetch!: 2,
has_key?: 2, put_new: 3, pop: 2, pop: 3, update: 4, update!: 3,
split: 2
use Dict
end
end
end
+16 -5
View File
@@ -936,10 +936,15 @@ defmodule Enum do
def join(collection, joiner \\ "")
def join(collection, joiner) when is_binary(joiner) do
reduce(collection, "", fn
entry, "" -> to_string(entry)
reduced = reduce(collection, :first, fn
entry, :first -> to_string(entry)
entry, acc -> acc <> joiner <> to_string(entry)
end)
if reduced == :first do
""
else
reduced
end
end
@doc """
@@ -990,10 +995,16 @@ defmodule Enum do
def map_join(collection, joiner \\ "", mapper)
def map_join(collection, joiner, mapper) when is_binary(joiner) do
reduce(collection, "", fn
entry, "" -> to_string(mapper, entry)
entry, acc -> acc <> joiner <> to_string(mapper, entry)
reduced = reduce(collection, :first, fn
entry, :first -> to_string(mapper, entry)
entry, acc -> acc <> joiner <> to_string(mapper, entry)
end)
if reduced == :first do
""
else
reduced
end
end
@doc """
+356 -244
View File
@@ -1,180 +1,26 @@
# Some exceptions implement `message/1` instead of `exception/1` mostly
# for bootstrap reasons. It is recommended for applications to implement
# `exception/1` instead of `message/1` as described in `defexception/3` docs.
defexception RuntimeError, message: "runtime error"
defexception ArgumentError, message: "argument error"
defexception ArithmeticError, message: "bad argument in arithmetic expression"
defexception SystemLimitError, message: "a system limit has been reached"
defexception SyntaxError, [file: nil, line: nil, description: "syntax error"] do
def message(exception) do
Exception.format_file_line(Path.relative_to_cwd(exception.file), exception.line) <>
" " <> exception.description
end
end
defexception TokenMissingError, [file: nil, line: nil, description: "expression is incomplete"] do
def message(exception) do
Exception.format_file_line(Path.relative_to_cwd(exception.file), exception.line) <>
" " <> exception.description
end
end
defexception CompileError, [file: nil, line: nil, description: "compile error"] do
def message(exception) do
Exception.format_file_line(Path.relative_to_cwd(exception.file), exception.line) <>
" " <> exception.description
end
end
defexception BadFunctionError, [term: nil] do
def message(exception) do
"expected a function, got: #{inspect(exception.term)}"
end
end
defexception BadStructError, [struct: nil, term: nil] do
def message(exception) do
"expected a struct named #{inspect(exception.struct)}, got: #{inspect(exception.term)}"
end
end
defexception MatchError, [term: nil] do
def message(exception) do
"no match of right hand side value: #{inspect(exception.term)}"
end
end
defexception CaseClauseError, [term: nil] do
def message(exception) do
"no case clause matching: #{inspect(exception.term)}"
end
end
defexception TryClauseError, [term: nil] do
def message(exception) do
"no try clause matching: #{inspect(exception.term)}"
end
end
defexception BadArityError, [function: nil, args: nil] do
def message(exception) do
fun = exception.function
args = exception.args
insp = Enum.map_join(args, ", ", &inspect/1)
{:arity, arity} = :erlang.fun_info(fun, :arity)
"#{inspect(fun)} with arity #{arity} called with #{count(length(args), insp)}"
end
defp count(0, _insp), do: "no arguments"
defp count(1, insp), do: "1 argument (#{insp})"
defp count(x, insp), do: "#{x} arguments (#{insp})"
end
defexception UndefinedFunctionError, [module: nil, function: nil, arity: nil] do
def message(exception) do
if exception.function do
formatted = Exception.format_mfa exception.module, exception.function, exception.arity
"undefined function: #{formatted}"
else
"undefined function"
end
end
end
defexception FunctionClauseError, [module: nil, function: nil, arity: nil] do
def message(exception) do
if exception.function do
formatted = Exception.format_mfa exception.module, exception.function, exception.arity
"no function clause matching in #{formatted}"
else
"no function clause matches"
end
end
end
defexception Protocol.UndefinedError, [protocol: nil, value: nil, description: nil] do
def message(exception) do
msg = "protocol #{inspect exception.protocol} not implemented for #{inspect exception.value}"
if exception.description do
msg <> ", " <> exception.description
else
msg
end
end
end
defexception ErlangError, [original: nil] do
def message(exception) do
"erlang error: #{inspect(exception.original)}"
end
end
defexception KeyError, key: nil, term: nil do
def message(exception) do
"key #{inspect exception.key} not found in: #{inspect exception.term}"
end
end
defexception UnicodeConversionError, [:encoded, :message] do
def exception(opts) do
UnicodeConversionError[
encoded: opts[:encoded],
message: "#{opts[:kind]} #{detail(opts[:rest])}"
]
end
defp detail(rest) when is_binary(rest) do
"encoding starting at #{inspect rest}"
end
defp detail([h|_]) do
"code point #{h}"
end
end
defexception Enum.OutOfBoundsError, message: "out of bounds error"
defexception Enum.EmptyError, message: "empty error"
defexception File.Error, [reason: nil, action: "", path: nil] do
def message(exception) do
formatted = iodata_to_binary(:file.format_error(reason exception))
"could not #{action exception} #{path exception}: #{formatted}"
end
end
defexception File.CopyError, [reason: nil, action: "", source: nil, destination: nil, on: nil] do
def message(exception) do
formatted = iodata_to_binary(:file.format_error(reason exception))
location = if on = on(exception), do: ". #{on}", else: ""
"could not #{action exception} from #{source exception} to " <>
"#{destination exception}#{location}: #{formatted}"
end
end
defmodule Exception do
@moduledoc """
Functions to work with and pretty print exceptions.
Functions to format throw/catch/exit and exceptions.
Note that stacktraces in Elixir are updated on throw,
errors and exits. For example, at any given moment,
`System.stacktrace` will return the stacktrace for the
last throw/error/exit that ocurred in the current process.
Finally note developers should not rely on the particular
format of the `format` functions provided by this module.
They may be changed in future releases in order to better
suit Elixir's tool chain. In other words, by using the
functions in this module it is guarantee you will format
exceptions as in the current Elixir version being used.
Do not rely on the particular format returned by the `format`
functions in this module. They may be changed in future releases
in order to better suit Elixir's tool chain. In other words,
by using the functions in this module it is guarantee you will
format exceptions as in the current Elixir version being used.
"""
@type t :: tuple
@typedoc "The exception type (as generated by defexception)"
@type t :: %{__struct__: module, __exception__: true}
@typedoc "The kind handled by formatting functions"
@type kind :: :error | :exit | :throw | {:EXIT, pid}
@type stacktrace :: [stacktrace_entry]
@type process ::
pid | atom | {atom, node} | {:global, term} | {:via, module, term}
@type stacktrace_entry ::
{module, function, arity_or_args, location} |
{function, arity_or_args, location}
@@ -182,24 +28,25 @@ defmodule Exception do
@typep arity_or_args :: non_neg_integer | list
@typep location :: Keyword.t
@callback exception(term) :: t
@callback message(t) :: String.t
@doc """
Returns true if the given argument is an exception.
"""
def exception?(exception) do
is_tuple(exception) and tuple_size(exception) > 1 and
:erlang.element(2, exception) == :__exception__
end
def exception?(%{__struct__: struct, __exception__: true}) when is_atom(struct), do: true
def exception?(_), do: false
@doc """
Gets the message for an exception.
"""
def message(exception) do
def message(%{__struct__: module, __exception__: true} = exception) when is_atom(module) do
try do
exception.message
module.message(exception)
rescue
e ->
raise ArgumentError, message:
"Got #{inspect e.__record__(:name)} with message " <>
raise ArgumentError,
"Got #{inspect e.__struct__} with message " <>
"\"#{message(e)}\" while retrieving message for #{inspect(exception)}"
end
end
@@ -220,16 +67,17 @@ defmodule Exception do
an empty stacktrace, `[]`, must be used.
"""
@spec normalize(:error, any, stacktrace) :: t
@spec normalize(:exit | :throw, any, stacktrace) :: any
# Generating a stacktrace is expensive, default to nil to only fetch it when
# needed.
@spec normalize(kind, payload, stacktrace) :: payload when payload: var
# Generating a stacktrace is expensive, default to nil
# to only fetch it when needed.
def normalize(kind, payload, stacktrace \\ nil)
def normalize(:error, exception, stacktrace) do
if exception?(exception) do
exception
else
normalize_error(exception, stacktrace)
ErlangError.normalize(exception, stacktrace)
end
end
@@ -237,60 +85,6 @@ defmodule Exception do
payload
end
defp normalize_error(:badarg, _stacktrace) do
ArgumentError[]
end
defp normalize_error(:badarith, _stacktrace) do
ArithmeticError[]
end
defp normalize_error(:system_limit, _stacktrace) do
SystemLimitError[]
end
defp normalize_error({:badarity, {fun, args}}, _stacktrace) do
BadArityError[function: fun, args: args]
end
defp normalize_error({:badfun, term}, _stacktrace) do
BadFunctionError[term: term]
end
defp normalize_error({:badstruct, struct, term}, _stacktrace) do
BadStructError[struct: struct, term: term]
end
defp normalize_error({:badmatch, term}, _stacktrace) do
MatchError[term: term]
end
defp normalize_error({:case_clause, term}, _stacktrace) do
CaseClauseError[term: term]
end
defp normalize_error({:try_clause, term}, _stacktrace) do
TryClauseError[term: term]
end
defp normalize_error(:undef, stacktrace) do
{mod, fun, arity} = from_stacktrace(stacktrace || :erlang.get_stacktrace)
UndefinedFunctionError[module: mod, function: fun, arity: arity]
end
defp normalize_error(:function_clause, stacktrace) do
{mod, fun, arity} = from_stacktrace(stacktrace || :erlang.get_stacktrace)
FunctionClauseError[module: mod, function: fun, arity: arity]
end
defp normalize_error({:badarg, payload}, _stacktrace) do
ArgumentError[message: "argument error: #{inspect(payload)}"]
end
defp normalize_error(other, _stacktrace) do
ErlangError[original: other]
end
@doc """
Normalizes and formats any throw, error and exit.
@@ -304,12 +98,12 @@ defmodule Exception do
not return the stacktrace corresponding to the exception
an empty stacktrace, `[]`, must be used.
"""
@spec format_banner(:error | :exit | :throw, any, stacktrace) :: String.t
@spec format_banner(kind, any, stacktrace | nil) :: String.t
def format_banner(kind, exception, stacktrace \\ nil)
def format_banner(:error, exception, stacktrace) do
exception = normalize(:error, exception, stacktrace)
"** (" <> inspect(exception.__record__(:name)) <> ") " <> message(exception)
"** (" <> inspect(exception.__struct__) <> ") " <> message(exception)
end
def format_banner(:throw, reason, _stacktrace) do
@@ -320,18 +114,34 @@ defmodule Exception do
"** (exit) " <> format_exit(reason, <<"\n ">>)
end
@doc """
Normalizes and formats an exception and stacktrace.
def format_banner({:EXIT, pid}, reason, _stacktrace) do
"** (EXIT from #{inspect pid}) " <> format_exit(reason, <<"\n ">>)
end
Behaves the same as `format_banner/3` but also includes
the formatted stacktrace.
@doc """
Normalizes and formats throw/errors/exits and stacktrace.
It relies on `format_banner/3` and `format_stacktrace/1`
to generate the final format.
Note that `{:EXIT, pid}` do not generate a stacktrace though
(as they are retrieved as messages without stacktraces).
"""
@spec format(:error | :exit | :throw, any, stacktrace) :: String.t
def format(kind, exception, stacktrace \\ :erlang.get_stacktrace()) do
message = format_banner(kind, exception, stacktrace)
@spec format(kind, any, stacktrace | nil) :: String.t
def format(kind, payload, stacktrace \\ nil)
def format({:EXIT, _} = kind, any, _) do
format_banner(kind, any)
end
def format(kind, payload, stacktrace) do
stacktrace = stacktrace || System.stacktrace
message = format_banner(kind, payload, stacktrace)
case stacktrace do
[] -> message
_ -> message <> "\n" <> format_stacktrace(stacktrace)
_ -> message <> "\n" <> format_stacktrace(stacktrace)
end
end
@@ -547,7 +357,7 @@ defmodule Exception do
defp format_application(module) do
case :application.get_application(module) do
{:ok, app} -> "(" <> atom_to_binary(app) <> ") "
{:ok, app} -> "(" <> Atom.to_string(app) <> ") "
:undefined -> ""
end
end
@@ -624,7 +434,7 @@ defmodule Exception do
end
defp format_arity(arity) when is_integer(arity) do
"/" <> integer_to_binary(arity)
"/" <> Integer.to_string(arity)
end
@doc """
@@ -662,6 +472,308 @@ defmodule Exception do
defp format_location(opts) when is_list(opts) do
format_file_line Keyword.get(opts, :file), Keyword.get(opts, :line), " "
end
end
# Some exceptions implement `message/1` instead of `exception/1` mostly
# for bootstrap reasons. It is recommended for applications to implement
# `exception/1` instead of `message/1` as described in `defexception/1`
# docs.
defmodule RuntimeError do
defexception message: "runtime error"
def exception(msg) when is_binary(msg) do
%RuntimeError{message: msg}
end
def exception(arg) do
super(arg)
end
end
defmodule ArgumentError do
defexception message: "argument error"
def exception(msg) when is_binary(msg) do
%ArgumentError{message: msg}
end
def exception(arg) do
super(arg)
end
end
defmodule ArithmeticError do
defexception []
def message(_) do
"bad argument in arithmetic expression"
end
end
defmodule SystemLimitError do
defexception []
def message(_) do
"a system limit has been reached"
end
end
defmodule SyntaxError do
defexception [file: nil, line: nil, description: "syntax error"]
def message(exception) do
Exception.format_file_line(Path.relative_to_cwd(exception.file), exception.line) <>
" " <> exception.description
end
end
defmodule TokenMissingError do
defexception [file: nil, line: nil, description: "expression is incomplete"]
def message(exception) do
Exception.format_file_line(Path.relative_to_cwd(exception.file), exception.line) <>
" " <> exception.description
end
end
defmodule CompileError do
defexception [file: nil, line: nil, description: "compile error"]
def message(exception) do
Exception.format_file_line(Path.relative_to_cwd(exception.file), exception.line) <>
" " <> exception.description
end
end
defmodule BadFunctionError do
defexception [term: nil]
def message(exception) do
"expected a function, got: #{inspect(exception.term)}"
end
end
defmodule BadStructError do
defexception [struct: nil, term: nil]
def message(exception) do
"expected a struct named #{inspect(exception.struct)}, got: #{inspect(exception.term)}"
end
end
defmodule MatchError do
defexception [term: nil]
def message(exception) do
"no match of right hand side value: #{inspect(exception.term)}"
end
end
defmodule CaseClauseError do
defexception [term: nil]
def message(exception) do
"no case clause matching: #{inspect(exception.term)}"
end
end
defmodule TryClauseError do
defexception [term: nil]
def message(exception) do
"no try clause matching: #{inspect(exception.term)}"
end
end
defmodule BadArityError do
defexception [function: nil, args: nil]
def message(exception) do
fun = exception.function
args = exception.args
insp = Enum.map_join(args, ", ", &inspect/1)
{:arity, arity} = :erlang.fun_info(fun, :arity)
"#{inspect(fun)} with arity #{arity} called with #{count(length(args), insp)}"
end
defp count(0, _insp), do: "no arguments"
defp count(1, insp), do: "1 argument (#{insp})"
defp count(x, insp), do: "#{x} arguments (#{insp})"
end
defmodule UndefinedFunctionError do
defexception [module: nil, function: nil, arity: nil]
def message(exception) do
if exception.function do
formatted = Exception.format_mfa exception.module, exception.function, exception.arity
"undefined function: #{formatted}"
else
"undefined function"
end
end
end
defmodule FunctionClauseError do
defexception [module: nil, function: nil, arity: nil]
def message(exception) do
if exception.function do
formatted = Exception.format_mfa exception.module, exception.function, exception.arity
"no function clause matching in #{formatted}"
else
"no function clause matches"
end
end
end
defmodule Code.LoadError do
defexception [:file, :message]
def exception(opts) do
file = opts[:file]
%Code.LoadError{message: "could not load #{file}", file: file}
end
end
defmodule Protocol.UndefinedError do
defexception [protocol: nil, value: nil, description: nil]
def message(exception) do
msg = "protocol #{inspect exception.protocol} not implemented for #{inspect exception.value}"
if exception.description do
msg <> ", " <> exception.description
else
msg
end
end
end
defmodule KeyError do
defexception key: nil, term: nil
def message(exception) do
"key #{inspect exception.key} not found in: #{inspect exception.term}"
end
end
defmodule UnicodeConversionError do
defexception [:encoded, :message]
def exception(opts) do
%UnicodeConversionError{
encoded: opts[:encoded],
message: "#{opts[:kind]} #{detail(opts[:rest])}"
}
end
defp detail(rest) when is_binary(rest) do
"encoding starting at #{inspect rest}"
end
defp detail([h|_]) do
"code point #{h}"
end
end
defmodule Enum.OutOfBoundsError do
defexception []
def message(_) do
"out of bounds error"
end
end
defmodule Enum.EmptyError do
defexception []
def message(_) do
"empty error"
end
end
defmodule File.Error do
defexception [reason: nil, action: "", path: nil]
def message(exception) do
formatted = IO.iodata_to_binary(:file.format_error(exception.reason))
"could not #{exception.action} #{exception.path}: #{formatted}"
end
end
defmodule File.CopyError do
defexception [reason: nil, action: "", source: nil, destination: nil, on: nil]
def message(exception) do
formatted = IO.iodata_to_binary(:file.format_error(exception.reason))
location = if on = exception.on, do: ". #{on}", else: ""
"could not #{exception.action} from #{exception.source} to " <>
"#{exception.destination}#{location}: #{formatted}"
end
end
defmodule ErlangError do
defexception [original: nil]
def message(exception) do
"erlang error: #{inspect(exception.original)}"
end
@doc false
def normalize(:badarg, _stacktrace) do
%ArgumentError{}
end
def normalize(:badarith, _stacktrace) do
%ArithmeticError{}
end
def normalize(:system_limit, _stacktrace) do
%SystemLimitError{}
end
def normalize({:badarity, {fun, args}}, _stacktrace) do
%BadArityError{function: fun, args: args}
end
def normalize({:badfun, term}, _stacktrace) do
%BadFunctionError{term: term}
end
def normalize({:badstruct, struct, term}, _stacktrace) do
%BadStructError{struct: struct, term: term}
end
def normalize({:badmatch, term}, _stacktrace) do
%MatchError{term: term}
end
def normalize({:case_clause, term}, _stacktrace) do
%CaseClauseError{term: term}
end
def normalize({:try_clause, term}, _stacktrace) do
%TryClauseError{term: term}
end
def normalize(:undef, stacktrace) do
{mod, fun, arity} = from_stacktrace(stacktrace || :erlang.get_stacktrace)
%UndefinedFunctionError{module: mod, function: fun, arity: arity}
end
def normalize(:function_clause, stacktrace) do
{mod, fun, arity} = from_stacktrace(stacktrace || :erlang.get_stacktrace)
%FunctionClauseError{module: mod, function: fun, arity: arity}
end
def normalize({:badarg, payload}, _stacktrace) do
%ArgumentError{message: "argument error: #{inspect(payload)}"}
end
def normalize(other, _stacktrace) do
%ErlangError{original: other}
end
defp from_stacktrace([{module, function, args, _}|_]) when is_list(args) do
{module, function, length(args)}
+50 -50
View File
@@ -228,7 +228,7 @@ defmodule File do
"""
@spec regular?(Path.t) :: boolean
def regular?(path) do
FL.is_regular(String.from_char_data!(path))
FL.is_regular(IO.chardata_to_string(path))
end
@doc """
@@ -236,7 +236,7 @@ defmodule File do
"""
@spec dir?(Path.t) :: boolean
def dir?(path) do
FL.is_dir(String.from_char_data!(path))
FL.is_dir(IO.chardata_to_string(path))
end
@doc """
@@ -258,7 +258,7 @@ defmodule File do
"""
@spec exists?(Path.t) :: boolean
def exists?(path) do
match?({:ok, _}, F.read_file_info(String.from_char_data!(path)))
match?({:ok, _}, F.read_file_info(IO.chardata_to_string(path)))
end
@doc """
@@ -276,7 +276,7 @@ defmodule File do
"""
@spec mkdir(Path.t) :: :ok | {:error, posix}
def mkdir(path) do
F.make_dir(String.from_char_data!(path))
F.make_dir(IO.chardata_to_string(path))
end
@doc """
@@ -284,7 +284,7 @@ defmodule File do
"""
@spec mkdir!(Path.t) :: :ok | no_return
def mkdir!(path) do
path = String.from_char_data!(path)
path = IO.chardata_to_string(path)
case mkdir(path) do
:ok -> :ok
{:error, reason} ->
@@ -312,7 +312,7 @@ defmodule File do
"""
@spec mkdir_p!(Path.t) :: :ok | no_return
def mkdir_p!(path) do
path = String.from_char_data!(path)
path = IO.chardata_to_string(path)
case mkdir_p(path) do
:ok -> :ok
{:error, reason} ->
@@ -338,7 +338,7 @@ defmodule File do
"""
@spec read(Path.t) :: {:ok, binary} | {:error, posix}
def read(path) do
F.read_file(String.from_char_data!(path))
F.read_file(IO.chardata_to_string(path))
end
@doc """
@@ -347,7 +347,7 @@ defmodule File do
"""
@spec read!(Path.t) :: binary | no_return
def read!(path) do
path = String.from_char_data!(path)
path = IO.chardata_to_string(path)
case read(path) do
{:ok, binary} ->
binary
@@ -372,7 +372,7 @@ defmodule File do
"""
@spec stat(Path.t, stat_options) :: {:ok, File.Stat.t} | {:error, posix}
def stat(path, opts \\ []) do
case F.read_file_info(String.from_char_data!(path), opts) do
case F.read_file_info(IO.chardata_to_string(path), opts) do
{:ok, fileinfo} ->
{:ok, File.Stat.from_record(fileinfo)}
error ->
@@ -386,7 +386,7 @@ defmodule File do
"""
@spec stat!(Path.t, stat_options) :: File.Stat.t | no_return
def stat!(path, opts \\ []) do
path = String.from_char_data!(path)
path = IO.chardata_to_string(path)
case stat(path, opts) do
{:ok, info} -> info
{:error, reason} ->
@@ -400,7 +400,7 @@ defmodule File do
"""
@spec write_stat(Path.t, File.Stat.t, stat_options) :: :ok | {:error, posix}
def write_stat(path, stat, opts \\ []) do
F.write_file_info(String.from_char_data!(path), File.Stat.to_record(stat), opts)
F.write_file_info(IO.chardata_to_string(path), File.Stat.to_record(stat), opts)
end
@doc """
@@ -409,7 +409,7 @@ defmodule File do
"""
@spec write_stat!(Path.t, File.Stat.t, stat_options) :: :ok | no_return
def write_stat!(path, stat, opts \\ []) do
path = String.from_char_data!(path)
path = IO.chardata_to_string(path)
case write_stat(path, stat, opts) do
:ok -> :ok
{:error, reason} ->
@@ -423,7 +423,7 @@ defmodule File do
"""
@spec touch(Path.t, :calendar.datetime) :: :ok | {:error, posix}
def touch(path, time \\ :calendar.local_time) do
path = String.from_char_data!(path)
path = IO.chardata_to_string(path)
case F.change_time(path, time) do
{:error, :enoent} ->
write(path, "")
@@ -439,7 +439,7 @@ defmodule File do
"""
@spec touch!(Path.t, :calendar.datetime) :: :ok | no_return
def touch!(path, time \\ :calendar.local_time) do
path = String.from_char_data!(path)
path = IO.chardata_to_string(path)
case touch(path, time) do
:ok -> :ok
{:error, reason} ->
@@ -482,7 +482,7 @@ defmodule File do
"""
@spec copy(Path.t, Path.t, pos_integer | :infinity) :: {:ok, non_neg_integer} | {:error, posix}
def copy(source, destination, bytes_count \\ :infinity) do
F.copy(String.from_char_data!(source), String.from_char_data!(destination), bytes_count)
F.copy(IO.chardata_to_string(source), IO.chardata_to_string(destination), bytes_count)
end
@doc """
@@ -491,8 +491,8 @@ defmodule File do
"""
@spec copy!(Path.t, Path.t, pos_integer | :infinity) :: non_neg_integer | no_return
def copy!(source, destination, bytes_count \\ :infinity) do
source = String.from_char_data!(source)
destination = String.from_char_data!(destination)
source = IO.chardata_to_string(source)
destination = IO.chardata_to_string(destination)
case copy(source, destination, bytes_count) do
{:ok, bytes_count} -> bytes_count
{:error, reason} ->
@@ -522,8 +522,8 @@ defmodule File do
"""
@spec cp(Path.t, Path.t, (Path.t, Path.t -> boolean)) :: :ok | {:error, posix}
def cp(source, destination, callback \\ fn(_, _) -> true end) do
source = String.from_char_data!(source)
destination = String.from_char_data!(destination)
source = IO.chardata_to_string(source)
destination = IO.chardata_to_string(destination)
case do_cp_file(source, destination, callback, []) do
{:error, reason, _} -> {:error, reason}
@@ -537,8 +537,8 @@ defmodule File do
"""
@spec cp!(Path.t, Path.t, (Path.t, Path.t -> boolean)) :: :ok | no_return
def cp!(source, destination, callback \\ fn(_, _) -> true end) do
source = String.from_char_data!(source)
destination = String.from_char_data!(destination)
source = IO.chardata_to_string(source)
destination = IO.chardata_to_string(destination)
case cp(source, destination, callback) do
:ok -> :ok
@@ -593,8 +593,8 @@ defmodule File do
"""
@spec cp_r(Path.t, Path.t, (Path.t, Path.t -> boolean)) :: {:ok, [binary]} | {:error, posix, binary}
def cp_r(source, destination, callback \\ fn(_, _) -> true end) when is_function(callback) do
source = String.from_char_data!(source)
destination = String.from_char_data!(destination)
source = IO.chardata_to_string(source)
destination = IO.chardata_to_string(destination)
case do_cp_r(source, destination, callback, []) do
{:error, _, _} = error -> error
@@ -608,8 +608,8 @@ defmodule File do
"""
@spec cp_r!(Path.t, Path.t, (Path.t, Path.t -> boolean)) :: [binary] | no_return
def cp_r!(source, destination, callback \\ fn(_, _) -> true end) do
source = String.from_char_data!(source)
destination = String.from_char_data!(destination)
source = IO.chardata_to_string(source)
destination = IO.chardata_to_string(destination)
case cp_r(source, destination, callback) do
{:ok, files} -> files
@@ -725,7 +725,7 @@ defmodule File do
"""
@spec write(Path.t, iodata, list) :: :ok | {:error, posix}
def write(path, content, modes \\ []) do
F.write_file(String.from_char_data!(path), content, [:raw|modes])
F.write_file(IO.chardata_to_string(path), content, [:raw|modes])
end
@doc """
@@ -733,7 +733,7 @@ defmodule File do
"""
@spec write!(Path.t, iodata, list) :: :ok | no_return
def write!(path, content, modes \\ []) do
path = String.from_char_data!(path)
path = IO.chardata_to_string(path)
case F.write_file(path, content, modes) do
:ok -> :ok
{:error, reason} ->
@@ -765,7 +765,7 @@ defmodule File do
"""
@spec rm(Path.t) :: :ok | {:error, posix}
def rm(path) do
F.delete(String.from_char_data!(path))
F.delete(IO.chardata_to_string(path))
end
@doc """
@@ -773,7 +773,7 @@ defmodule File do
"""
@spec rm!(Path.t) :: :ok | no_return
def rm!(path) do
path = String.from_char_data!(path)
path = IO.chardata_to_string(path)
case rm(path) do
:ok -> :ok
{:error, reason} ->
@@ -796,7 +796,7 @@ defmodule File do
"""
@spec rmdir(Path.t) :: :ok | {:error, posix}
def rmdir(path) do
F.del_dir(String.from_char_data!(path))
F.del_dir(IO.chardata_to_string(path))
end
@doc """
@@ -804,7 +804,7 @@ defmodule File do
"""
@spec rmdir!(Path.t) :: :ok | {:error, posix}
def rmdir!(path) do
path = String.from_char_data!(path)
path = IO.chardata_to_string(path)
case rmdir(path) do
:ok -> :ok
{:error, reason} ->
@@ -832,7 +832,7 @@ defmodule File do
"""
@spec rm_rf(Path.t) :: {:ok, [binary]} | {:error, posix, binary}
def rm_rf(path) do
do_rm_rf(String.from_char_data!(path), {:ok, []})
do_rm_rf(IO.chardata_to_string(path), {:ok, []})
end
defp do_rm_rf(path, {:ok, _} = entry) do
@@ -907,7 +907,7 @@ defmodule File do
"""
@spec rm_rf!(Path.t) :: [binary] | no_return
def rm_rf!(path) do
path = String.from_char_data!(path)
path = IO.chardata_to_string(path)
case rm_rf(path) do
{:ok, files} -> files
{:error, reason, _} ->
@@ -977,7 +977,7 @@ defmodule File do
def open(path, modes \\ [])
def open(path, modes) when is_list(modes) do
F.open(String.from_char_data!(path), open_defaults(modes, true))
F.open(IO.chardata_to_string(path), open_defaults(modes, true))
end
def open(path, function) when is_function(function) do
@@ -1024,7 +1024,7 @@ defmodule File do
"""
@spec open!(Path.t, list) :: io_device | no_return
def open!(path, modes \\ []) do
path = String.from_char_data!(path)
path = IO.chardata_to_string(path)
case open(path, modes) do
{:ok, device} -> device
{:error, reason} ->
@@ -1038,7 +1038,7 @@ defmodule File do
"""
@spec open!(Path.t, list, (io_device -> res)) :: res | no_return when res: var
def open!(path, modes, function) do
path = String.from_char_data!(path)
path = IO.chardata_to_string(path)
case open(path, modes, function) do
{:ok, device} -> device
{:error, reason} ->
@@ -1057,7 +1057,7 @@ defmodule File do
@spec cwd() :: {:ok, binary} | {:error, posix}
def cwd() do
case F.get_cwd do
{:ok, base} -> {:ok, String.from_char_data!(base)}
{:ok, base} -> {:ok, IO.chardata_to_string(base)}
{:error, _} = error -> error
end
end
@@ -1068,7 +1068,7 @@ defmodule File do
@spec cwd!() :: binary | no_return
def cwd!() do
case F.get_cwd do
{:ok, cwd} -> String.from_char_data!(cwd)
{:ok, cwd} -> IO.chardata_to_string(cwd)
{:error, reason} ->
raise File.Error, reason: reason, action: "get current working directory"
end
@@ -1081,7 +1081,7 @@ defmodule File do
"""
@spec cd(Path.t) :: :ok | {:error, posix}
def cd(path) do
F.set_cwd(String.from_char_data!(path))
F.set_cwd(IO.chardata_to_string(path))
end
@doc """
@@ -1089,7 +1089,7 @@ defmodule File do
"""
@spec cd!(Path.t) :: :ok | no_return
def cd!(path) do
path = String.from_char_data!(path)
path = IO.chardata_to_string(path)
case F.set_cwd(path) do
:ok -> :ok
{:error, reason} ->
@@ -1124,8 +1124,8 @@ defmodule File do
"""
@spec ls(Path.t) :: {:ok, [binary]} | {:error, posix}
def ls(path \\ ".") do
case F.list_dir(String.from_char_data!(path)) do
{:ok, file_list} -> {:ok, Enum.map(file_list, &String.from_char_data!/1)}
case F.list_dir(IO.chardata_to_string(path)) do
{:ok, file_list} -> {:ok, Enum.map(file_list, &IO.chardata_to_string/1)}
{:error, _} = error -> error
end
end
@@ -1136,7 +1136,7 @@ defmodule File do
"""
@spec ls!(Path.t) :: [binary] | no_return
def ls!(path \\ ".") do
path = String.from_char_data!(path)
path = IO.chardata_to_string(path)
case ls(path) do
{:ok, value} -> value
{:error, reason} ->
@@ -1197,7 +1197,7 @@ defmodule File do
modes
end
%File.Stream{path: String.from_char_data!(path), modes: modes,
%File.Stream{path: IO.chardata_to_string(path), modes: modes,
raw: raw, line_or_bytes: line_or_bytes}
end
@@ -1208,7 +1208,7 @@ defmodule File do
"""
@spec chmod(Path.t, integer) :: :ok | {:error, posix}
def chmod(path, mode) do
F.change_mode(String.from_char_data!(path), mode)
F.change_mode(IO.chardata_to_string(path), mode)
end
@doc """
@@ -1216,7 +1216,7 @@ defmodule File do
"""
@spec chmod!(Path.t, integer) :: :ok | no_return
def chmod!(path, mode) do
path = String.from_char_data!(path)
path = IO.chardata_to_string(path)
case chmod(path, mode) do
:ok -> :ok
{:error, reason} ->
@@ -1231,7 +1231,7 @@ defmodule File do
"""
@spec chgrp(Path.t, integer) :: :ok | {:error, posix}
def chgrp(path, gid) do
F.change_group(String.from_char_data!(path), gid)
F.change_group(IO.chardata_to_string(path), gid)
end
@doc """
@@ -1239,7 +1239,7 @@ defmodule File do
"""
@spec chgrp!(Path.t, integer) :: :ok | no_return
def chgrp!(path, gid) do
path = String.from_char_data!(path)
path = IO.chardata_to_string(path)
case chgrp(path, gid) do
:ok -> :ok
{:error, reason} ->
@@ -1254,7 +1254,7 @@ defmodule File do
"""
@spec chown(Path.t, integer) :: :ok | {:error, posix}
def chown(path, uid) do
F.change_owner(String.from_char_data!(path), uid)
F.change_owner(IO.chardata_to_string(path), uid)
end
@doc """
@@ -1262,7 +1262,7 @@ defmodule File do
"""
@spec chown!(Path.t, integer) :: :ok | no_return
def chown!(path, uid) do
path = String.from_char_data!(path)
path = IO.chardata_to_string(path)
case chown(path, uid) do
:ok -> :ok
{:error, reason} ->
+80
View File
@@ -169,4 +169,84 @@ defmodule Float do
def round(number, precision) when is_float(number) and is_integer(precision) and precision in 0..15 do
Kernel.round(number * :math.pow(10, precision)) / :math.pow(10, precision)
end
@doc """
Returns a char list which corresponds to the text representation of the given float.
Inlined by the compiler.
## Examples
iex> Float.to_char_list(7.0)
'7.00000000000000000000e+00'
"""
@spec to_char_list(float) :: char_list
def to_char_list(number) do
:erlang.float_to_list(number)
end
@doc """
Returns a list which corresponds to the text representation
of `float`.
## Options
* `:decimals` — number of decimal points to show
* `:scientific` — number of decimal points to show, in scientific format
* `:compact` — when true, use the most compact representation (ignored with the `scientific` option)
## Examples
iex> Float.to_char_list 7.1, [decimals: 2, compact: true]
'7.1'
"""
@spec to_char_list(float, list) :: char_list
def to_char_list(float, options) do
:erlang.float_to_list(float, expand_compact(options))
end
@doc """
Returns a binary which corresponds to the text representation
of `some_float`.
Inlined by the compiler.
## Examples
iex> Float.to_string(7.0)
"7.00000000000000000000e+00"
"""
@spec to_string(float) :: String.t
def to_string(some_float) do
:erlang.float_to_binary(some_float)
end
@doc """
Returns a binary which corresponds to the text representation
of `float`.
## Options
* `:decimals` — number of decimal points to show
* `:scientific` — number of decimal points to show, in scientific format
* `:compact` — when true, use the most compact representation (ignored with the `scientific` option)
## Examples
iex> Float.to_string 7.1, [decimals: 2, compact: true]
"7.1"
"""
@spec to_string(float, list) :: String.t
def to_string(float, options) do
:erlang.float_to_binary(float, expand_compact(options))
end
defp expand_compact([{:compact, false}|t]), do: expand_compact(t)
defp expand_compact([{:compact, true}|t]), do: [:compact|expand_compact(t)]
defp expand_compact([h|t]), do: [h|expand_compact(t)]
defp expand_compact([]), do: []
end
+581
View File
@@ -0,0 +1,581 @@
defmodule GenEvent do
@moduledoc """
A behaviour module for implementing event handling functionality.
The event handling model consists of a generic event manager
process with an arbitrary number of event handlers which are
added and deleted dynamically.
An event manager implemented using this module will have a standard
set of interface functions and include functionality for tracing and
error reporting. It will also fit into an supervision tree.
## Example
There are many use cases for event handlers. For example, a logging
system can be built using event handlers where which log message is
an event and different event handlers can be plugged to handle the
log messages. One handler may print error messages on the terminal,
another can write it to a file, while a third one can keep the
messages in memory (like a buffer) until they are read.
As an example, let's have a GenEvent that accumulates messages until
they are collected by an explicit call.
defmodule LoggerHandler do
use GenEvent
# Callbacks
def handle_event({:log, x}, messages) do
{:ok, [x|messages]}
end
def handle_call(:messages, messages) do
{:ok, Enum.reverse(messages), []}
end
end
{:ok, pid} = GenEvent.start_link()
GenEvent.add_handler(pid, LoggerHandler, [])
#=> :ok
GenEvent.notify(pid, {:log, 1})
#=> :ok
GenEvent.notify(pid, {:log, 2})
#=> :ok
GenEvent.call(pid, LoggerHandler, :messages)
#=> [1, 2]
GenEvent.call(pid, LoggerHandler, :messages)
#=> []
We start a new event manager by calling `GenEvent.start_link/0`.
Notifications can be sent to the event manager which will then
invoke `handle_event/0` for each registered handler.
We can add new handlers with `add_handler/4`. Calls can also
be made to specific handlers by using `call/3`.
## Callbacks
There are 6 callbacks required to be implemented in a `GenEvent`. By
adding `use GenEvent` to your module, Elixir will automatically define
all 6 callbacks for you, leaving it up to you to implement the ones
you want to customize. The callbacks are:
* `init(args)` - invoked when the event handler is added
It must return:
{:ok, state}
{:ok, state, :hibernate}
{:error, reason}
* `handle_event(msg, state)` - invoked whenever an event is sent via
`notify/2` or `sync_notify/2`.
It must return:
{:ok, new_state}
{:ok, new_state, :hibernate}
{:swap_handler, args1, new_state, handler2, args2}
:remove_handler
* `handle_call(msg, state)` - invoked when a `call/3` is done to a specific handler.
It must return:
{:ok, reply, new_state}
{:ok, reply, new_state, :hibernate}
{:swap_handler, reply, args1, new_state, handler2, args2}
{:remove_handler, reply}
* `handle_info(msg, state)` - invoked to handle all other messages which
are received by the process. Must return the same values as `handle_event/2`;
It must return:
{:noreply, state}
{:noreply, state, timeout}
{:stop, reason, state}
* `terminate(reason, state)` - called when the event handler is removed or the
event manager is terminating. It can return any term.
* `code_change(old_vsn, state, extra)` - called when the application
code is being upgraded live (hot code swap).
It must return:
{:ok, new_state}
## Name registering
A GenEvent is bound to the same name registering rules as a `GenServer`.
Read more about it in the `GenServer` docs.
## Streaming
`GenEvent`'s can be streamed from and streamed with the help of `stream/2`.
Here are some examples:
stream = GenEvent.stream(pid)
# Take the next 10 events
Enum.take(stream, 10)
# Print all other events
for event <- stream do
IO.inspect event
end
A stream may also be given an id, which allows all streams with the given
id to be cancelled at any moment via `cancel_streams/1`.
## Learn more
In case you desire to learn more about gen events, Elixir getting started
guides provide a tutorial-like introduction. The documentation and links
in Erlang can also provide extra insight.
* http://elixir-lang.org/getting_started/mix/1.html
* http://www.erlang.org/doc/man/gen_event.html
* http://learnyousomeerlang.com/event-handlers
"""
@typedoc "Return values of `start*` functions"
@type on_start :: {:ok, pid} | {:error, {:already_started, pid}}
@typedoc "The GenEvent manager name"
@type name :: atom | {:global, term} | {:via, module, term}
@typedoc "Options used by the `start*` functions"
@type options :: [name: name]
@typedoc "The event manager reference"
@type manager :: pid | name | {atom, node}
@typedoc "Supported values for new handlers"
@type handler :: module | {module, term}
@doc """
Defines a `GenEvent` stream.
This is a struct returned by `stream/2`. The struct is public and
contains the following fields:
* `:manager` - the manager reference given to `GenEvent.stream/2`
* `:id` - the event stream id for cancellation
* `:timeout` - the timeout in between events, defaults to `:infinity`
* `:duration` - the duration of the subscription, defaults to `:infinity`
"""
defstruct manager: nil, id: nil, timeout: :infinity, duration: :infinity
@doc false
defmacro __using__(_) do
quote location: :keep do
@behaviour :gen_event
@doc false
def init(args) do
{:ok, args}
end
@doc false
def handle_event(_event, state) do
{:ok, state}
end
@doc false
def handle_call(_request, state) do
{:ok, {:error, :bad_call}, state}
end
@doc false
def handle_info(_msg, state) do
{:ok, state}
end
@doc false
def terminate(reason, state) do
:ok
end
@doc false
def code_change(_old, state, _extra) do
{:ok, state}
end
defoverridable [init: 1, handle_event: 2, handle_call: 2,
handle_info: 2, terminate: 2, code_change: 3]
end
end
@doc """
Starts an event manager linked to the current process.
This is often used to start the `GenServer` as part of a supervision tree.
It accepts a the `:name` which is described under the `Name Registering`
section in the `GenServer` module docs.
If the event manager is successfully created and initialized the function
returns `{:ok, pid}`, where pid is the pid of the server. If there already
exists a process with the specified server name the function returns
`{:error, {:already_started, pid}}` with the pid of that process.
"""
@spec start_link(options) :: on_start
def start_link(options \\ []) when is_list(options) do
do_start(:link, options)
end
@doc """
Starts an event manager process without links (outside of a supervision tree).
See `spawn_link/1` for more information.
"""
@spec start(options) :: on_start
def start(options \\ []) when is_list(options) do
do_start(:nolink, options)
end
defp do_start(mode, options) do
case Keyword.get(options, :name) do
nil ->
:gen.start(:gen_event, mode, :"no callback module", [], [])
atom when is_atom(atom) ->
:gen.start(:gen_event, mode, {:local, atom}, :"no callback module", [], [])
other when is_tuple(other) ->
:gen.start(:gen_event, mode, other, :"no callback module", [], [])
end
end
@doc """
Returns a stream that consumes and notifies events to the `manager`.
The stream is a `GenEvent` struct that implements the `Enumerable`
protocol. The supported options are:
* `:id` - an id to identify all live stream instances. When an `:id` is given,
existing streams can be called with via `cancel_streams`;
* `:timeout` (Enumerable) - raises if no event arrives in X milliseconds;
* `:duration` (Enumerable) - only consume events during the X milliseconds
from the streaming start;
"""
def stream(manager, options \\ []) do
%GenEvent{manager: manager,
id: Keyword.get(options, :id),
timeout: Keyword.get(options, :timeout, :infinity),
duration: Keyword.get(options, :duration, :infinity)}
end
@doc """
Adds a new event handler to the event `manager`.
The event manager will call the `init/1` callback with `args` to
initiate the event handler and its internal state.
If `init/1` returns a correct value indicating successful completion,
the event manager adds the event handler and this function returns
`:ok`. If the callback fails with `reason` or returns `{:error, reason}`,
the event handler is ignored and this function returns `{:EXIT, reason}`
or `{:error, reason}`, respectively.
## Linked handlers
When adding a handler, a `:link` option may be given as true.
This means the event handler and the calling process are now linked.
If the calling process later terminates with `reason`, the event manager
will delete the event handler by calling the `terminate/2` callback with
`{:stop, reason}` as argument. If the event handler later is deleted,
the event manager sends a message `{:gen_event_EXIT, handler, reason}`
to the calling process. Reason is one of the following:
* `:normal` - if the event handler has been removed due to a call to
`remove_handler/3`, or `:remove_handler` has been returned by a callback
function;
* `:shutdown` - if the event handler has been removed because the event
manager is terminating;
* `{:swapped, new_handler, pid}` - if the process pid has replaced the
event handler by another;
* a term - if the event handler is removed due to an error. Which term
depends on the error;
"""
@spec add_handler(manager, handler, term, [link: boolean]) :: :ok | {:EXIT, term} | {:error, term}
def add_handler(manager, handler, args, options \\ []) do
case Keyword.get(options, :link, false) do
true -> :gen_event.add_sup_handler(manager, handler, args)
false -> :gen_event.add_handler(manager, handler, args)
end
end
@doc """
Sends an event notification to the event `manager`.
The event manager will call `handle_event/2` for each installed event handler.
`notify` is asynchronous and will return immediately after the notification is
sent. `notify` will not fail even if the specified event manager does not exist,
unless it is specified as `name` (atom).
"""
@spec notify(manager, term) :: :ok
defdelegate notify(manager, event), to: :gen_event
@doc """
Sends a sync event notification to the event `manager`.
In other words, this function only returns `:ok` after the event manager
invokes the `handle_event/2` on each installed event handler.
See `notify/2` for more info.
"""
@spec sync_notify(manager, term) :: :ok
defdelegate sync_notify(manager, event), to: :gen_event
@doc """
Makes a synchronous call to the event `handler` installed in `manager`.
The given `request` is sent and the caller waits until a reply arrives or
a timeout occurs. The event manager will call `handle_call/2` to handle
the request.
The return value `reply` is defined in the return value of `handle_call/2`.
If the specified event handler is not installed, the function returns
`{:error, :bad_module}`.
"""
@spec call(manager, handler, term, timeout) :: term | {:error, term}
def call(manager, handler, request, timeout \\ 5000) do
:gen_event.call(manager, handler, request, timeout)
end
@doc """
Cancels all streams currently running with the given `:id`.
In order for a stream to be cancelled, an `:id` must be passed
when the stream is created via `stream/2`. Passing a stream without
an id leads to an argument error.
"""
@spec cancel_streams(t) :: :ok
def cancel_streams(%GenEvent{id: nil}) do
raise ArgumentError, "cannot cancel streams without an id"
end
def cancel_streams(%GenEvent{manager: manager, id: id}) do
handlers = :gen_event.which_handlers(manager)
for {Enumerable.GenEvent, {handler_id, _}} = ref <- handlers,
handler_id === id do
:gen_event.delete_handler(manager, ref, :remove_handler)
end
:ok
end
@doc """
Removes an event handler from the event `manager`.
The event manager will call `terminate/2` to terminate the event handler
and return the callback value. If the specified event handler is not
installed, the function returns `{:error, :module_not_found}`.
"""
@spec remove_handler(manager, handler, term) :: term | {:error, term}
def remove_handler(manager, handler, args) do
:gen_event.delete_handler(manager, handler, args)
end
@doc """
Replaces an old event handler with a new one in the event `manager`.
First, the old event handler is deleted by calling `terminate/2` with
the given `args1` and collects the return value. Then the new event handler
is added and initiated by calling `init({args2, term}), where term is the
return value of calling `terminate/2` in the old handler. This makes it
possible to transfer information from one handler to another.
The new handler will be added even if the specified old event handler
is not installed in which case `term = :error` or if the handler fails to
terminate with a given reason.
If there was a linked connection between handler1 and a process pid, there
will be a link connection between handle2 and pid instead. A new link in
between the caller process and the new handler can also be set with by
giving `link: true` as option. See `add_handler/4` for more information.
If `init/1` in the second handler returns a correct value, this function
returns `:ok`.
"""
@spec swap_handler(manager, handler, term, handler, term, [link: boolean]) :: :ok | {:error, term}
def swap_handler(manager, handler1, args1, handler2, args2, options \\ []) do
case Keyword.get(options, :link, false) do
true -> :gen_event.swap_sup_handler(manager, {handler1, args1}, {handler2, args2})
false -> :gen_event.swap_handler(manager, {handler1, args1}, {handler2, args2})
end
end
@doc """
Returns a list of all event handlers installed in the `manager`.
"""
@spec which_handlers(manager) :: [handler]
defdelegate which_handlers(manager), to: :gen_event
@doc """
Terminates the event `manager`.
Before terminating, the event manager will call `terminate(:stop, ...)`
for each installed event handler.
"""
@spec stop(manager) :: :ok
defdelegate stop(manager), to: :gen_event
end
defimpl Enumerable, for: GenEvent do
use GenEvent
@doc false
def init({mon_pid, pid, ref}) do
# Tell the mon_pid we are good to go, and send self() so that this handler
# can be removed later without using the managers name.
send(mon_pid, {:UP, ref, self()})
{:ok, {pid, ref}}
end
@doc false
def handle_event(event, {pid, ref} = state) do
send pid, {ref, event}
{:ok, state}
end
def reduce(stream, acc, fun) do
start_fun = fn() -> start(stream) end
next_fun = &next(stream, &1)
stop_fun = &stop(stream, &1)
Stream.resource(start_fun, next_fun, stop_fun).(acc, fun)
end
def count(_stream) do
{:error, __MODULE__}
end
def member?(_stream, _item) do
{:error, __MODULE__}
end
defp start(%{manager: manager, id: id, duration: duration} = stream) do
{mon_pid, mon_ref} = add_handler(manager, id, duration)
send mon_pid, {:UP, mon_ref, self()}
receive do
# The subscription process gave us a go.
{:UP, ^mon_ref, manager_pid} ->
{mon_ref, manager_pid}
# The subscription process died due to an abnormal reason.
{:DOWN, ^mon_ref, _, _, reason} ->
exit({reason, {__MODULE__, :start, [stream]}})
end
end
defp next(%{timeout: timeout} = stream, {mon_ref, _manager_pid} = acc) do
receive do
{^mon_ref, event} -> {event, acc}
{:DOWN, ^mon_ref, _, _, :normal} -> nil
{:DOWN, ^mon_ref, _, _, reason} ->
exit({reason, {__MODULE__, :next, [stream, acc]}})
after
timeout ->
exit({:timeout, {__MODULE__, :next, [stream, acc]}})
end
end
defp stop(%{id: id}, {mon_ref, manager_pid}) do
remove_handler(mon_ref, manager_pid, id)
flush_events(mon_ref)
end
defp add_handler(manager, id, duration) do
parent = self()
# The subscription is managed by another process, that dies if
# the handler dies, and is killed when there is a need to remove
# the subscription.
spawn_monitor(fn ->
# It is possible that the handler could be removed, and then the GenEvent
# could exit before this process has exited normally. Because the removal
# does not cause an unlinking this process would exit with the same
# reason. Trapping exits ensures that no errors is raised in this case.
Process.flag(:trap_exit, true)
parent_ref = Process.monitor(parent)
# Receive the notification from the parent, unless it died.
mon_ref = receive do
{:UP, ref, ^parent} -> ref
{:DOWN, ^parent_ref, _, _, _} -> exit(:normal)
end
cancel = cancel_ref(id, mon_ref)
:ok = :gen_event.add_sup_handler(manager, {__MODULE__, cancel},
{self(), parent, mon_ref})
receive do
# This message is already in the mailbox if we got this far.
{:UP, ^mon_ref, manager_pid} ->
send(parent, {:UP, mon_ref, manager_pid})
receive do
# If the parent died, we can exit normally.
{:DOWN, ^parent_ref, _, _, _} ->
exit(:normal)
# reason should be normal unless the handler is swapped.
{:gen_event_EXIT, {__MODULE__, ^cancel}, reason} ->
exit(reason)
# Exit if the manager dies, so the streamer is notified.
{:EXIT, ^manager_pid, :noconnection} ->
exit({:nodedown, node(manager_pid)})
{:EXIT, ^manager_pid, reason} ->
exit(reason)
after
# Our time is over, notify the parent.
duration -> exit(:normal)
end
end
end)
end
defp cancel_ref(nil, mon_ref), do: mon_ref
defp cancel_ref(id, mon_ref), do: {id, mon_ref}
defp remove_handler(mon_ref, manager_pid, id) do
Process.demonitor(mon_ref, [:flush])
handler = {__MODULE__, cancel_ref(id, mon_ref)}
# handler may nolonger be there, if it is the removal will cause the monitor
# process to exit. If this returns successfuly then no more events will be
# forwarded.
_ = :gen_event.delete_handler(manager_pid, handler, :remove_handler)
catch
# Do not want to overide the exit reason of the mon_pid so catch errors.
# However if the exit is due to a disconnection, exit because messages could
# leak if the nodes are reconnected before the manager on the other node
# removes the handler. In this case it is very likely that the mon_pid
# exited with the same reason.
:exit, reason when reason !== {:nodedown, node(manager_pid)} ->
:ok
end
defp flush_events(mon_ref) do
receive do
{^mon_ref, _} -> flush_events(mon_ref)
after
0 -> :ok
end
end
end
+1 -56
View File
@@ -1,60 +1,5 @@
defmodule GenEvent.Behaviour do
@moduledoc """
This module is a convenience for defining GenEvent callbacks in Elixir.
GenEvent is an OTP behaviour that encapsulates event handling functionality.
## Example
Below is an example of a GenEvent that stores notifications
until they are fetched:
defmodule MyEventHandler do
use GenEvent.Behaviour
# Callbacks
def init(_) do
{:ok, []}
end
def handle_event({:notification, x}, notifications) do
{:ok, [x|notifications]}
end
def handle_call(:notifications, notifications) do
{:ok, Enum.reverse(notifications), []}
end
end
{:ok, pid} = :gen_event.start_link
#=> {:ok,#PID<0.42.0>}
:gen_event.add_handler(pid, MyEventHandler, [])
#=> :ok
:gen_event.notify(pid, {:notification, 1})
#=> :ok
:gen_event.notify(pid, {:notification, 2})
#=> :ok
:gen_event.call(pid, MyEventHandler, :notifications)
#=> [1, 2]
:gen_event.call(pid, MyEventHandler, :notifications)
#=> []
Notice we never call the server callbacks directly, they are called
by OTP whenever we interact with the server.
Starting and sending messages to the GenEvent is done
via Erlang's `:gen_event` module. For more information,
please refer to the following:
* http://www.erlang.org/doc/man/gen_event.html
* http://learnyousomeerlang.com/event-handlers
"""
@moduledoc false
@doc false
defmacro __using__(_) do
+424
View File
@@ -0,0 +1,424 @@
defmodule GenServer do
@moduledoc """
A behaviour module for implementing the server of a client-server relation.
A GenServer is a process as any other Elixir process and it can be used
to keep state, execute code asynchronously and so on. The advantage of using
a generic server process (GenServer) implemented using this module is that it
will have a standard set of interface functions and include functionality for
tracing and error reporting. It will also fit into a supervision tree.
## Example
The GenServer behaviour abstracts the common client-server interaction.
Developer are only required to implement the callbacks and functionality they are
interested in.
Let's start with a code example and then explore the available callbacks.
Imagine we want a GenServer that works like a stack, allowing us to push
and pop items:
defmodule Stack do
use GenServer
# Callbacks
def handle_call(:pop, _from, [h|t]) do
{:reply, h, t}
end
def handle_cast({:push, item}, state) do
{:noreply, [item|state]}
end
end
# Start the server
{:ok, pid} = GenServer.start_link(Stack, [:hello])
# This is the client
GenServer.call(pid, :pop)
#=> :hello
GenServer.cast(pid, {:push, :world})
#=> :ok
GenServer.call(pid, :pop)
#=> :world
We start our `Stack` by calling `start_link/2`, passing the module
with the server implementation and its initial argument (a list
representing the stack containing the item `:hello`). We can primarily
interact with the server by sending two types of messages. **call**
messages expect a reply from the server (and is therefore synchronous)
while **cast** messages do not.
Every time you do a `GenServer.call/2`, the client will send a message
that must be handled by the `handle_call/3` callback in the GenServer.
A `cast/2` message must be handled by `handle_cast/2`.
## Callbacks
There are 6 callbacks required to be implemented in a `GenServer`. By
adding `use GenServer` to your module, Elixir will automatically define
all 6 callbacks for you, leaving it up to you to implement the ones
you want to customize. The callbacks are:
* `init(args)` - invoked when the server is started
It must return:
{:ok, state}
{:ok, state, timeout}
:ignore
{:stop, reason}
* `handle_call(msg, {from, ref}, state)` and `handle_cast(msg, state)` -
invoked to handle call (sync) and cast (async) messages.
It must return:
{:reply, reply, new_state}
{:reply, reply, new_state, timeout}
{:reply, reply, new_state, :hibernate}
{:noreply, new_state}
{:noreply, new_state, timeout}
{:noreply, new_state, :hibernate}
{:stop, reason, new_state}
{:stop, reason, reply, new_state}
* `handle_info(msg, state)` - invoked to handle all other messages which
are received by the process.
It must return:
{:noreply, state}
{:noreply, state, timeout}
{:stop, reason, state}
* `terminate(reason, state)` - called when the server is about to
terminate, useful for cleaning up. It must return `:ok`
* `code_change(old_vsn, state, extra)` - called when the application
code is being upgraded live (hot code swap).
It must return:
{:ok, new_state}
{:error, reason}
## Names registering
Both `start_link/3` and `start/3` support the `GenServer` to register
a name on start via the `:name` option. Registered names are also
automatically clean up on termination. The supported values are:
* an atom - the GenServer is registered locally with the given name
using `Process.register/2`;
* `{:global, term}`- the GenServer is registered globally with the given
term using the functions in the `:global` module;
* `{:via, module, term}` - the GenServer is registered with the given
mechanism and name. The `:via` option expects a module name to control
the registration mechanism along side a name which can be any term;
For example, we could start and register our Stack server locally as follows:
# Start the server and register it locally with name MyStack
{:ok, _} = GenServer.start_link(Stack, [:hello], name: MyStack)
# Now messages can be sent directly to MyStack
GenServer.call(MyStack, :pop) #=> :hello
Once the server is started, the remaining functions in this module (`call/2`,
`cast/2` and friends) will also accept an atom, or any `:global` or `:via`
tuples. In general, the following formats are supported:
* a `pid`
* an `atom` if the server is locally registered
* `{atom, node}` if the server is locally registered at another node
* `{:global, term}` if the server is globally registered
* `{:via, module, name}` if the server is registered through an alternative registry
## Client / Server APIs
Although in the example above we have used `GenServer.start_link/2` and
friends to directly start and communicate with the server, most of the
time we don't call the `GenServer` functions directly, instead, we wrap
them in functions too.
Here is a better implementation of our Stack module:
defmodule Stack do
use GenServer
# Client
def start_link(default) do
GenServer.start_link(__MODULE__, default)
end
def push(pid, item) do
GenServer.cast(pid, {:push, item})
end
def pop(pid) do
GenServer.call(pid, :pop)
end
# Server (callbacks)
def handle_call(:pop, _from, [h|t]) do
{:reply, h, t}
end
def handle_call(request, from, state) do
# Call the default implementation from GenServer
super(request, from, state)
end
def handle_cast({:push, item}, state) do
{:noreply, [item|state]}
end
def handle_cast(request, state) do
super(request, state)
end
end
In practice, it is common to have both server and client functions in
the same module. If the server and/or client implementations are growing
complex, you may want to have them in different modules.
## Learn more
In case you desire to learn more about gen servers, Elixir getting started
guides provide a tutorial-like introduction. The documentation and links
in Erlang can also provide extra insight.
* http://elixir-lang.org/getting_started/mix/1.html
* http://www.erlang.org/doc/man/gen_server.html
* http://www.erlang.org/doc/design_principles/gen_server_concepts.html
* http://learnyousomeerlang.com/clients-and-servers
"""
@typedoc "Return values of `start*` functions"
@type on_start :: {:ok, pid} | :ignore | {:error, {:already_started, pid} | term}
@typedoc "The GenServer name"
@type name :: atom | {:global, term} | {:via, module, term}
@typedoc "Options used by the `start*` functions"
@type options :: [debug: debug,
name: name,
timeout: timeout,
spawn_opt: Process.spawn_opt]
@typedoc "debug options supported by the `start*` functions"
@type debug :: [:trace | :log | :statistics | {:log_to_file, Path.t}]
@typedoc "The server reference"
@type server :: pid | name | {atom, node}
@doc false
defmacro __using__(_) do
quote location: :keep do
@behaviour :gen_server
@doc false
def init(args) do
{:ok, args}
end
@doc false
def handle_call(msg, _from, state) do
{:stop, {:bad_call, msg}, state}
end
@doc false
def handle_info(_msg, state) do
{:noreply, state}
end
@doc false
def handle_cast(msg, state) do
{:stop, {:bad_cast, msg}, state}
end
@doc false
def terminate(_reason, _state) do
:ok
end
@doc false
def code_change(_old, state, _extra) do
{:ok, state}
end
defoverridable [init: 1, handle_call: 3, handle_info: 2,
handle_cast: 2, terminate: 2, code_change: 3]
end
end
@doc """
Starts a `GenServer` process linked to the current process.
This is often used to start the `GenServer` as part of a supervision tree.
Once the server is started, it calls the `init/1` function in the given `module`
passing the given `args` to initialize it. To ensure a synchronized start-up
procedure, this function does not return until `init/1` has returned.
## Options
The `:name` option is used for name registered as described in the module
documentation. If the option `:timeout` option is present, the server is
allowed to spend the given milliseconds initializing or it will be
terminated and the start function will return `{:error, :timeout}`.
If the option `:debug` is present, the corresponding function in the
[`:sys` module](http://www.erlang.org/doc/man/sys.html) will be invoked.
If the option `:spawn_opt` is present, the given options will be passed
to the underlying process as in `Process.spawn/3`.
## Return values
If the server is successfully created and initialized the function returns
`{:ok, pid}`, where pid is the pid of the server. If there already exists a
process with the specified server name the function returns
`{:error, {:already_started, pid}}` with the pid of that process.
If the `init/1` callback fails with reason, the function returns
`{:error, reason}`. Otherwise, if it returns `{:stop, reason}`
or `:ignore`, the process is terminated and the function returns
`{:error, reason}` or `:ignore`, respectively.
"""
@spec start_link(module, any, options) :: on_start
def start_link(module, args, options \\ []) when is_atom(module) and is_list(options) do
do_start(:link, module, args, options)
end
@doc """
Starts a `GenServer` process without links (outside of a supervision tree).
See `start_link/3` for more information.
"""
@spec start(module, any, options) :: on_start
def start(module, args, options \\ []) when is_atom(module) and is_list(options) do
do_start(:nolink, module, args, options)
end
defp do_start(link, module, args, options) do
case Keyword.pop(options, :name) do
{nil, opts} ->
:gen.start(:gen_server, link, module, args, opts)
{atom, opts} when is_atom(atom) ->
:gen.start(:gen_server, link, {:local, atom}, module, args, opts)
{other, opts} when is_tuple(other) ->
:gen.start(:gen_server, link, other, module, args, opts)
end
end
@doc """
Makes a synchronous call to the `server` and wait for its reply.
The client sends the given `request` to the server and waits until a reply
arrives or a timeout occurs. `handle_call/3` will be called on the server
to handle the request.
The server can be any of the values described in the `Name Registering`
section of the module documentation.
## Timeouts
The `timeout` is an integer greater than zero which specifies how many
milliseconds to wait for a reply, or the atom `:infinity` to wait
indefinitely. The default value is 5000. If no reply is received within
the specified time, the function call fails. If the caller catches the
failure and continues running, and the server is just late with the reply,
it may arrive at any time later into the caller's message queue. The caller
must in this case be prepared for this and discard any such garbage messages
that are two element tuples with a reference as the first element.
"""
@spec call(server, term, timeout) :: term
def call(server, request, timeout \\ 5000) do
:gen_server.call(server, request, timeout)
end
@doc """
Sends an asynchronous request to the `server`.
This function returns `:ok` immediately, ignoring if the destination node
or server does not exist. `handle_cast/2` will be called on the server
to handle the request.
"""
@spec cast(server, term) :: :ok
defdelegate cast(server, request), to: :gen_server
@doc """
Casts all servers locally registered as `name` at the specified nodes.
The function returns immediately and ignores nodes that do not exist, or where the
server name does not exist.
See `multi_call/4` for more information.
"""
@spec abcast([node], name :: atom, term) :: :abcast
def abcast(nodes \\ nodes(), name, request) do
:gen_server.abcast(nodes, name, request)
end
@doc """
Calls all servers locally registered as `name` at the specified `nodes`.
The `request` is first sent to every node and then we wait for the
replies. This function returns a tuple containing the node and its reply
as first element and all bad nodes as second element. The bad nodes is a
list of nodes that either did not exist, or where a server with the given
`name` did not exist or did not reply.
Nodes is a list of node names to which the request is sent. The default
value is the list of all known nodes.
To avoid that late answers (after the timeout) pollute the caller's message
queue, a middleman process is used to do the actual calls. Late answers will
then be discarded when they arrive to a terminated process.
"""
@spec multi_call([node], name :: atom, term, timeout) ::
{replies :: [{node, term}], bad_nodes :: [node]}
def multi_call(nodes \\ nodes(), name, request, timeout \\ :infinity) do
:gen_server.multi_call(nodes, name, request, timeout)
end
@doc """
Replies a client.
This function can be used by a server to explicitly send a reply to a
client that called `call/3` or `multi_call/4`. When the reply cannot be
defined in the return value of `handle_call/3`.
The `client` must be the `from` argument (the second argument) received
in `handle_call/3` callbacks. Reply is an arbitrary term which will be
given back to the client as the return value of the call.
This function always return `:ok`.
"""
@spec reply({pid, reference}, term) :: :ok
def reply(client, reply)
def reply({to, tag}, reply) do
try do
send(to, {tag, reply})
:ok
catch
_, _ -> :ok
end
end
@compile {:inline, [nodes: 0]}
defp nodes do
[node()|:erlang.nodes()]
end
end
+1 -83
View File
@@ -1,87 +1,5 @@
defmodule GenServer.Behaviour do
@moduledoc """
This module is a convenience for defining GenServer callbacks in Elixir.
A server is responsible for reacting to messages received from a client.
A GenServer is an OTP behaviour that encapsulates common server
functionalities.
## Example
Below is an example of a GenServer that pushes and pops items
onto a stack:
defmodule MyServer do
use GenServer.Behaviour
# Callbacks
def handle_call(:pop, _from, [h|t]) do
{:reply, h, t}
end
def handle_call(request, from, config) do
# Call the default implementation from GenServer.Behaviour
super(request, from, config)
end
def handle_cast({:push, item}, config) do
{:noreply, [item|config]}
end
def handle_cast(request, config) do
super(request, config)
end
end
{:ok, pid} = :gen_server.start_link(MyServer, [:hello], [])
:gen_server.call(pid, :pop)
#=> :hello
:gen_server.cast(pid, {:push, :world})
#=> :ok
:gen_server.call(pid, :pop)
#=> :world
Notice we never call the server callbacks directly, they are called
by OTP whenever we interact with the server. **cast** messages are
asynchronous while **call** ones are synchronous. For a
GenServer, there are 8 different values a callback such as
`handle_call` or `handle_cast` can return:
{:reply, reply, new_state}
{:reply, reply, new_state, timeout}
{:reply, reply, new_state, :hibernate}
{:noreply, new_state}
{:noreply, new_state, timeout}
{:noreply, new_state, :hibernate}
{:stop, reason, new_state}
{:stop, reason, reply, new_state}
There are 6 callbacks required to be implemented in a GenServer. The
`GenServer.Behaviour` module defines all of them automatically, but
allows us to customize the ones we need. The required callbacks are:
* `init(args)` - invoked when the server is started;
* `handle_call(msg, from, state)` - invoked to handle call messages;
* `handle_cast(msg, state)` - invoked to handle cast messages;
* `handle_info(msg, state)` - handle all other messages which are
normally received by processes;
* `terminate(reason, state)` - called when the server is about to
terminate, useful for cleaning up;
* `code_change(old_vsn, state, extra)` - called when the application
code is being upgraded live (hot code swap);
Starting and sending messages to the GenServer is done
via Erlang's `:gen_server` module. For more information,
please refer to the following:
* http://www.erlang.org/doc/man/gen_server.html
* http://www.erlang.org/doc/design_principles/gen_server_concepts.html
* http://learnyousomeerlang.com/clients-and-servers
"""
@moduledoc false
@doc false
defmacro __using__(_) do
+21 -42
View File
@@ -43,7 +43,7 @@ defmodule HashDict do
def update!(%HashDict{root: root, size: size} = dict, key, fun) when is_function(fun, 1) do
{root, counter} = do_update(root, key, fn -> raise KeyError, key: key, term: dict end,
fun, key_hash(key))
fun, key_hash(key))
%HashDict{root: root, size: size + counter}
end
@@ -59,14 +59,14 @@ defmodule HashDict do
def delete(dict, key) do
case dict_delete(dict, key) do
{dict, _value} -> dict
:error -> dict
:error -> dict
end
end
def pop(dict, key, default \\ nil) do
case dict_delete(dict, key) do
{dict, value} -> {value, dict}
:error -> {default, dict}
:error -> {default, dict}
end
end
@@ -83,27 +83,6 @@ defmodule HashDict do
end)
end
def split(dict, keys) do
Enum.reduce keys, {new, dict}, fn key, {inc, exc} = acc ->
case dict_delete(exc, key) do
{exc, value} -> {put(inc, key, value), exc}
:error -> acc
end
end
end
def merge(%HashDict{size: size1} = dict1, %HashDict{size: size2} = dict2, callback) when size1 < size2 do
reduce(dict1, {:cont, dict2}, fn {k, v1}, acc ->
{:cont, update(acc, k, v1, &callback.(k, v1, &1))}
end) |> elem(1)
end
def merge(%HashDict{} = dict1, %HashDict{} = dict2, callback) do
reduce(dict2, {:cont, dict1}, fn {k, v2}, acc ->
{:cont, update(acc, k, v2, &callback.(k, &1, v2))}
end) |> elem(1)
end
## General helpers
defp dict_delete(%HashDict{root: root, size: size}, key) do
@@ -129,17 +108,17 @@ defmodule HashDict do
index = key_mask(hash)
case elem(node, index) do
[] ->
{set_elem(node, index, [key|value]), 1}
{put_elem(node, index, [key|value]), 1}
[^key|_] ->
{set_elem(node, index, [key|value]), 0}
{put_elem(node, index, [key|value]), 0}
[k|v] ->
n = set_elem(@node_template, key_mask(key_shift(hash)), [key|value])
{set_elem(node, index, {k, v, n}), 1}
n = put_elem(@node_template, key_mask(key_shift(hash)), [key|value])
{put_elem(node, index, {k, v, n}), 1}
{^key, _, n} ->
{set_elem(node, index, {key, value, n}), 0}
{put_elem(node, index, {key, value, n}), 0}
{k, v, n} ->
{n, counter} = do_put(n, key, value, key_shift(hash))
{set_elem(node, index, {k, v, n}), counter}
{put_elem(node, index, {k, v, n}), counter}
end
end
@@ -147,17 +126,17 @@ defmodule HashDict do
index = key_mask(hash)
case elem(node, index) do
[] ->
{set_elem(node, index, [key|initial.()]), 1}
{put_elem(node, index, [key|initial.()]), 1}
[^key|value] ->
{set_elem(node, index, [key|fun.(value)]), 0}
{put_elem(node, index, [key|fun.(value)]), 0}
[k|v] ->
n = set_elem(@node_template, key_mask(key_shift(hash)), [key|initial.()])
{set_elem(node, index, {k, v, n}), 1}
n = put_elem(@node_template, key_mask(key_shift(hash)), [key|initial.()])
{put_elem(node, index, {k, v, n}), 1}
{^key, value, n} ->
{set_elem(node, index, {key, fun.(value), n}), 0}
{put_elem(node, index, {key, fun.(value), n}), 0}
{k, v, n} ->
{n, counter} = do_update(n, key, initial, fun, key_shift(hash))
{set_elem(node, index, {k, v, n}), counter}
{put_elem(node, index, {k, v, n}), counter}
end
end
@@ -167,17 +146,17 @@ defmodule HashDict do
[] ->
:error
[^key|value] ->
{set_elem(node, index, []), value}
{put_elem(node, index, []), value}
[_|_] ->
:error
{^key, value, n} ->
{set_elem(node, index, do_compact_node(n)), value}
{put_elem(node, index, do_compact_node(n)), value}
{k, v, n} ->
case do_delete(n, key, key_shift(hash)) do
{@node_template, value} ->
{set_elem(node, index, [k|v]), value}
{put_elem(node, index, [k|v]), value}
{n, value} ->
{set_elem(node, index, {k, v, n}), value}
{put_elem(node, index, {k, v, n}), value}
:error ->
:error
end
@@ -188,12 +167,12 @@ defmodule HashDict do
defp do_compact_node(node) when elem(node, unquote(index)) != [] do
case elem(node, unquote(index)) do
[k|v] ->
case set_elem(node, unquote(index), []) do
case put_elem(node, unquote(index), []) do
@node_template -> [k|v]
n -> {k, v, n}
end
{k, v, n} ->
{k, v, set_elem(node, unquote(index), do_compact_node(n))}
{k, v, put_elem(node, unquote(index), do_compact_node(n))}
end
end
end
+10 -10
View File
@@ -134,15 +134,15 @@ defmodule HashSet do
index = key_mask(hash)
case elem(node, index) do
[] ->
{set_elem(node, index, [term]), 1}
{put_elem(node, index, [term]), 1}
[^term|_] ->
{node, 0}
[t] ->
n = set_elem(@node_template, key_mask(key_shift(hash)), [term])
{set_elem(node, index, [t|n]), 1}
n = put_elem(@node_template, key_mask(key_shift(hash)), [term])
{put_elem(node, index, [t|n]), 1}
[t|n] ->
{n, counter} = do_put(n, term, key_shift(hash))
{set_elem(node, index, [t|n]), counter}
{put_elem(node, index, [t|n]), counter}
end
end
@@ -152,17 +152,17 @@ defmodule HashSet do
[] ->
:error
[^term] ->
{:ok, set_elem(node, index, [])}
{:ok, put_elem(node, index, [])}
[_] ->
:error
[^term|n] ->
{:ok, set_elem(node, index, do_compact_node(n))}
{:ok, put_elem(node, index, do_compact_node(n))}
[t|n] ->
case do_delete(n, term, key_shift(hash)) do
{:ok, @node_template} ->
{:ok, set_elem(node, index, [t])}
{:ok, put_elem(node, index, [t])}
{:ok, n} ->
{:ok, set_elem(node, index, [t|n])}
{:ok, put_elem(node, index, [t|n])}
:error ->
:error
end
@@ -173,12 +173,12 @@ defmodule HashSet do
defp do_compact_node(node) when elem(node, unquote(index)) != [] do
case elem(node, unquote(index)) do
[t] ->
case set_elem(node, unquote(index), []) do
case put_elem(node, unquote(index), []) do
@node_template -> [t]
n -> [t|n]
end
[t|n] ->
[t|set_elem(node, unquote(index), do_compact_node(n))]
[t|put_elem(node, unquote(index), do_compact_node(n))]
end
end
end
+97 -26
View File
@@ -70,7 +70,7 @@ defimpl Inspect, for: Atom do
def inspect(:""), do: ":\"\""
def inspect(atom) do
binary = atom_to_binary(atom)
binary = Atom.to_string(atom)
cond do
valid_ref_identifier?(binary) ->
@@ -142,7 +142,7 @@ end
defimpl Inspect, for: BitString do
def inspect(thing, %Inspect.Opts{binaries: bins} = opts) when is_binary(thing) do
if bins == :as_strings or (bins == :infer and String.printable?(thing)) do
<< ?", escape(thing, ?") :: binary, ?" >>
<<?", escape(thing, ?") :: binary, ?">>
else
inspect_bitstring(thing, opts)
end
@@ -202,19 +202,42 @@ defimpl Inspect, for: BitString do
else
<< byte :: size(8), h :: binary >> = head
t = << h :: binary, t :: binary >>
escape(t, char, << binary :: binary, octify(byte) :: binary >>)
escape(t, char, << binary :: binary, escape_char(byte) :: binary >>)
end
end
defp escape(<<h, t :: binary>>, char, binary) do
escape(t, char, << binary :: binary, octify(h) :: binary >>)
escape(t, char, << binary :: binary, escape_char(h) :: binary >>)
end
defp escape(<<>>, _char, binary), do: binary
@doc false
# also used by Regex
def escape_char(char) when char in ?\000..?\377,
do: octify(char)
def escape_char(char), do: hexify(char)
defp octify(byte) do
<< hi :: size(2), mi :: size(3), lo :: size(3) >> = << byte >>
<< ?\\, ?0 + hi, ?0 + mi, ?0 + lo >>
end
defp hexify(char) when char < 0x10000 do
<<a::4, b::4, c::4, d::4>> = <<char::size(16)>>
<<?\\, ?x, ?{, to_hex(a), to_hex(b), to_hex(c), to_hex(d), ?}>>
end
defp hexify(char) when char < 0x1000000 do
<<a::4, b::4, c::4, d::4, e::4, f::4>> = <<char::size(24)>>
<<?\\, ?x, ?{, to_hex(a), to_hex(b), to_hex(c),
to_hex(d), to_hex(e), to_hex(f), ?}>>
end
defp to_hex(c) when c in 0..9, do: ?0+c
defp to_hex(c) when c in 10..15, do: ?a+c-10
defp append(<<h, t :: binary>>, binary), do: append(t, << binary :: binary, h >>)
defp append(<<>>, binary), do: binary
@@ -229,11 +252,11 @@ defimpl Inspect, for: BitString do
end
defp each_bit(<<h, t :: bitstring>>, counter, acc) when t != <<>> do
each_bit(t, decrement(counter), acc <> integer_to_binary(h) <> ", ")
each_bit(t, decrement(counter), acc <> Integer.to_string(h) <> ", ")
end
defp each_bit(<<h :: size(8)>>, _counter, acc) do
acc <> integer_to_binary(h)
acc <> Integer.to_string(h)
end
defp each_bit(<<>>, _counter, acc) do
@@ -243,7 +266,7 @@ defimpl Inspect, for: BitString do
defp each_bit(bitstring, _counter, acc) do
size = bit_size(bitstring)
<<h :: size(size)>> = bitstring
acc <> integer_to_binary(h) <> "::size(" <> integer_to_binary(size) <> ")"
acc <> Integer.to_string(h) <> "::size(" <> Integer.to_string(size) <> ")"
end
defp decrement(:infinity), do: :infinity
@@ -275,7 +298,7 @@ defimpl Inspect, for: List do
def inspect(thing, %Inspect.Opts{char_lists: lists} = opts) do
cond do
lists == :as_char_lists or (lists == :infer and printable?(thing)) ->
<< ?', Inspect.BitString.escape(String.from_char_data!(thing), ?') :: binary, ?' >>
<< ?', Inspect.BitString.escape(IO.chardata_to_string(thing), ?') :: binary, ?' >>
keyword?(thing) ->
surround_many("[", thing, "]", opts.limit, &keyword(&1, opts))
true ->
@@ -291,7 +314,7 @@ defimpl Inspect, for: List do
end
def keyword?([{key, _value} | rest]) when is_atom(key) do
case atom_to_list(key) do
case Atom.to_char_list(key) do
'Elixir.' ++ _ -> false
_ -> keyword?(rest)
end
@@ -329,14 +352,14 @@ defimpl Inspect, for: Tuple do
if opts.records do
record_inspect(tuple, opts)
else
surround_many("{", tuple_to_list(tuple), "}", opts.limit, &to_doc(&1, opts))
surround_many("{", Tuple.to_list(tuple), "}", opts.limit, &to_doc(&1, opts))
end
end
## Helpers
defp record_inspect(record, opts) do
[name|tail] = tuple_to_list(record)
[name|tail] = Tuple.to_list(record)
if is_atom(name) && (fields = record_fields(name)) && (length(fields) == size(record) - 1) do
surround_record(name, fields, tail, opts)
@@ -346,7 +369,7 @@ defimpl Inspect, for: Tuple do
end
defp record_fields(name) do
case atom_to_binary(name) do
case Atom.to_string(name) do
"Elixir." <> _ ->
try do
name.__record__(:fields)
@@ -365,7 +388,7 @@ defimpl Inspect, for: Tuple do
end
defp zip_fields([{key, _}|tk], [value|tv]) do
case atom_to_binary(key) do
case Atom.to_string(key) do
"_" <> _ -> zip_fields(tk, tv)
key -> [{key, value}|zip_fields(tk, tv)]
end
@@ -408,20 +431,68 @@ end
defimpl Inspect, for: Integer do
def inspect(thing, _opts) do
integer_to_binary(thing)
Integer.to_string(thing)
end
end
defimpl Inspect, for: Float do
def inspect(thing, _opts) do
iodata_to_binary(:io_lib_format.fwrite_g(thing))
IO.iodata_to_binary(:io_lib_format.fwrite_g(thing))
end
end
defimpl Inspect, for: Regex do
def inspect(regex, opts) do
concat ["~r", to_doc(regex.source, opts), regex.opts]
def inspect(regex, _opts) do
delim =?/
concat ["~r",
<<delim, escape(regex.source, delim)::binary, delim>>,
regex.opts]
end
defp escape(bin, term),
do: escape(bin, <<>>, term)
defp escape(<<?\\, term>> <> rest, buf, term),
do: escape(rest, buf <> <<?\\, term>>, term)
defp escape(<<term>> <> rest, buf, term),
do: escape(rest, buf <> <<?\\, term>>, term)
# the list of characters is from `String.printable?` impl
# minus characters treated specially by regex: \s, \d, \b, \e
defp escape(<<?\n>> <> rest, buf, term),
do: escape(rest, <<buf::binary, ?\\, ?n>>, term)
defp escape(<<?\r>> <> rest, buf, term),
do: escape(rest, <<buf::binary, ?\\, ?r>>, term)
defp escape(<<?\t>> <> rest, buf, term),
do: escape(rest, <<buf::binary, ?\\, ?t>>, term)
defp escape(<<?\v>> <> rest, buf, term),
do: escape(rest, <<buf::binary, ?\\, ?v>>, term)
defp escape(<<?\f>> <> rest, buf, term),
do: escape(rest, <<buf::binary, ?\\, ?f>>, term)
defp escape(<<?\a>> <> rest, buf, term),
do: escape(rest, <<buf::binary, ?\\, ?a>>, term)
defp escape(<<c::utf8>> <> rest, buf, term) do
charstr = <<c::utf8>>
if String.printable?(charstr) and not c in [?\d, ?\b, ?\e] do
escape(rest, buf <> charstr, term)
else
escape(rest, buf <> Inspect.BitString.escape_char(c), term)
end
end
defp escape(<<c>> <> rest, buf, term),
do: escape(rest, <<buf::binary, Inspect.BitString.escape_char(c)>>, term)
defp escape(<<>>, buf, _), do: buf
end
defimpl Inspect, for: Function do
@@ -432,7 +503,7 @@ defimpl Inspect, for: Function do
if fun_info[:type] == :external and fun_info[:env] == [] do
"&#{Inspect.Atom.inspect(mod)}.#{fun_info[:name]}/#{fun_info[:arity]}"
else
case atom_to_list(mod) do
case Atom.to_char_list(mod) do
'elixir_compiler_' ++ _ ->
if function_exported?(mod, :__RELATIVE__, 0) do
"#Function<#{uniq(fun_info)} in file:#{mod.__RELATIVE__}>"
@@ -455,7 +526,7 @@ defimpl Inspect, for: Function do
end
defp extract_name(name) do
name = atom_to_binary(name)
name = Atom.to_string(name)
case :binary.split(name, "-", [:global]) do
["", name | _] -> "." <> name
_ -> "." <> name
@@ -463,27 +534,27 @@ defimpl Inspect, for: Function do
end
defp uniq(fun_info) do
integer_to_binary(fun_info[:new_index]) <> "." <>
integer_to_binary(fun_info[:uniq])
Integer.to_string(fun_info[:new_index]) <> "." <>
Integer.to_string(fun_info[:uniq])
end
end
defimpl Inspect, for: PID do
def inspect(pid, _opts) do
"#PID" <> iodata_to_binary(:erlang.pid_to_list(pid))
"#PID" <> IO.iodata_to_binary(:erlang.pid_to_list(pid))
end
end
defimpl Inspect, for: Port do
def inspect(port, _opts) do
iodata_to_binary :erlang.port_to_list(port)
IO.iodata_to_binary :erlang.port_to_list(port)
end
end
defimpl Inspect, for: Reference do
def inspect(ref, _opts) do
'#Ref' ++ rest = :erlang.ref_to_list(ref)
"#Reference" <> iodata_to_binary(rest)
"#Reference" <> IO.iodata_to_binary(rest)
end
end
@@ -496,8 +567,8 @@ defimpl Inspect, for: Any do
else
dunder ->
if :maps.keys(dunder) == :maps.keys(map) do
Inspect.Map.inspect(:maps.remove(:__struct__, map),
Inspect.Atom.inspect(struct, opts), opts)
pruned = :maps.remove(:__exception__, :maps.remove(:__struct__, map))
Inspect.Map.inspect(pruned, Inspect.Atom.inspect(struct, opts), opts)
else
Inspect.Map.inspect(map, opts)
end
+47 -45
View File
@@ -114,19 +114,25 @@ defmodule Inspect.Algebra do
@type t :: :doc_nil | :doc_line | doc_cons | doc_nest | doc_break | doc_group | binary
require Record
@typep doc_cons :: {:doc_cons, t, t}
Record.defrecordp :doc_cons, left: :doc_nil, right: :doc_nil
defmacrop doc_cons(left, right) do
quote do: {:doc_cons, unquote(left), unquote(right)}
end
@typep doc_nest :: {:doc_nest, non_neg_integer, t}
Record.defrecordp :doc_nest, indent: 1, doc: :doc_nil
@typep doc_nest :: {:doc_nest, t, non_neg_integer}
defmacrop doc_nest(doc, indent) do
quote do: {:doc_nest, unquote(doc), unquote(indent) }
end
@typep doc_break :: {:doc_break, binary}
Record.defrecordp :doc_break, str: " "
defmacrop doc_break(break) do
quote do: {:doc_break, unquote(break)}
end
@typep doc_group :: {:doc_group, t}
Record.defrecordp :doc_group, doc: :doc_nil
defmacrop doc_group(group) do
quote do: {:doc_group, unquote(group)}
end
defmacrop is_doc(doc) do
if Macro.Env.in_guard?(__CALLER__) do
@@ -154,17 +160,13 @@ defmodule Inspect.Algebra do
according to the inspect protocol.
"""
@spec to_doc(any, Inspect.Opts.t) :: t
def to_doc(arg, %Inspect.Opts{} = opts) when is_record(arg) do
# IO.write :stderr, "inspect records is deprecated (just remove this code when protocols+records are removed)\n#{Exception.format_stacktrace}"
def to_doc(arg, %Inspect.Opts{} = opts) when is_tuple(arg) and is_atom(elem(arg, 0)) do
# Remove this code when protocols+records are removed"
if opts.records do
try do
Inspect.inspect(arg, opts)
rescue
e ->
res = Inspect.Tuple.inspect(arg, opts)
raise ArgumentError, message:
"Got #{inspect e.__record__(:name)} with message " <>
"#{Exception.message(e)} while inspecting #{pretty(res, opts.width)}"
_ -> Inspect.Tuple.inspect(arg, opts)
end
else
Inspect.Tuple.inspect(arg, opts)
@@ -178,8 +180,8 @@ defmodule Inspect.Algebra do
rescue
e ->
res = Inspect.Map.inspect(map, opts)
raise ArgumentError, message:
"Got #{inspect e.__record__(:name)} with message " <>
raise ArgumentError,
"Got #{inspect e.__struct__} with message " <>
"\"#{Exception.message(e)}\" while inspecting #{pretty(res, opts.width)}"
end
else
@@ -217,7 +219,7 @@ defmodule Inspect.Algebra do
"""
@spec concat(t, t) :: doc_cons
def concat(x, y) when is_doc(x) and is_doc(y) do
doc_cons(left: x, right: y)
doc_cons(x, y)
end
@doc """
@@ -245,7 +247,7 @@ defmodule Inspect.Algebra do
end
def nest(x, i) when is_doc(x) and is_integer(i) do
doc_nest(indent: i, doc: x)
doc_nest(x, i)
end
@doc ~S"""
@@ -270,10 +272,10 @@ defmodule Inspect.Algebra do
"""
@spec break(binary) :: doc_break
def break(s) when is_binary(s), do: doc_break(str: s)
def break(s) when is_binary(s), do: doc_break(s)
@spec break() :: doc_break
def break(), do: doc_break(str: @break)
def break(), do: doc_break(@break)
@doc """
Inserts a break between two docs. See `break/1` for more info.
@@ -317,7 +319,7 @@ defmodule Inspect.Algebra do
"""
@spec group(t) :: doc_group
def group(d) when is_doc(d) do
doc_group(doc: d)
doc_group(d)
end
@doc """
@@ -393,15 +395,15 @@ defmodule Inspect.Algebra do
## Examples
iex> doc = Inspect.Algebra.surround_many("[", Enum.to_list(1..5), "]", :infinity, &integer_to_binary(&1))
iex> doc = Inspect.Algebra.surround_many("[", Enum.to_list(1..5), "]", :infinity, &Integer.to_string(&1))
iex> Inspect.Algebra.pretty(doc, 5)
"[1,\n 2,\n 3,\n 4,\n 5]"
iex> doc = Inspect.Algebra.surround_many("[", Enum.to_list(1..5), "]", 3, &integer_to_binary(&1))
iex> doc = Inspect.Algebra.surround_many("[", Enum.to_list(1..5), "]", 3, &Integer.to_string(&1))
iex> Inspect.Algebra.pretty(doc, 20)
"[1, 2, 3, ...]"
iex> doc = Inspect.Algebra.surround_many("[", Enum.to_list(1..5), "]", 3, &integer_to_binary(&1), "!")
iex> doc = Inspect.Algebra.surround_many("[", Enum.to_list(1..5), "]", 3, &Integer.to_string(&1), "!")
iex> Inspect.Algebra.pretty(doc, 20)
"[1! 2! 3! ...]"
@@ -451,7 +453,7 @@ defmodule Inspect.Algebra do
"""
@spec pretty(t, non_neg_integer | :infinity) :: binary
def pretty(d, w) do
sdoc = format w, 0, [{0, default_mode(w), doc_group(doc: d)}]
sdoc = format w, 0, [{0, default_mode(w), doc_group(d)}]
render(sdoc)
end
@@ -465,28 +467,28 @@ defmodule Inspect.Algebra do
@doc false
@spec fits?(integer, [{integer, mode, t}]) :: boolean
def fits?(w, _) when w < 0, do: false
def fits?(_, []), do: true
def fits?(_, [{_, _, :doc_line} | _]), do: true
def fits?(w, [{_, _, :doc_nil} | t]), do: fits?(w, t)
def fits?(w, [{i, m, doc_cons(left: x, right: y)} | t]), do: fits?(w, [{i, m, x} | [{i, m, y} | t]])
def fits?(w, [{i, m, doc_nest(indent: j, doc: x)} | t]), do: fits?(w, [{i + j, m, x} | t])
def fits?(w, [{i, _, doc_group(doc: x)} | t]), do: fits?(w, [{i, :flat, x} | t])
def fits?(w, [{_, _, s} | t]) when is_binary(s), do: fits?((w - byte_size s), t)
def fits?(w, [{_, :flat, doc_break(str: s)} | t]), do: fits?((w - byte_size s), t)
def fits?(_, [{_, :break, doc_break(str: _)} | _]), do: true
def fits?(w, _) when w < 0, do: false
def fits?(_, []), do: true
def fits?(_, [{_, _, :doc_line} | _]), do: true
def fits?(w, [{_, _, :doc_nil} | t]), do: fits?(w, t)
def fits?(w, [{i, m, doc_cons(x, y)} | t]), do: fits?(w, [{i, m, x} | [{i, m, y} | t]])
def fits?(w, [{i, m, doc_nest(x, j)} | t]), do: fits?(w, [{i + j, m, x} | t])
def fits?(w, [{i, _, doc_group(x)} | t]), do: fits?(w, [{i, :flat, x} | t])
def fits?(w, [{_, _, s} | t]) when is_binary(s), do: fits?((w - byte_size s), t)
def fits?(w, [{_, :flat, doc_break(s)} | t]), do: fits?((w - byte_size s), t)
def fits?(_, [{_, :break, doc_break(_)} | _]), do: true
@doc false
@spec format(integer | :infinity, integer, [{integer, mode, t}]) :: [binary]
def format(_, _, []), do: []
def format(w, _, [{i, _, :doc_line} | t]), do: [indent(i) | format(w, i, t)]
def format(w, k, [{_, _, :doc_nil} | t]), do: format(w, k, t)
def format(w, k, [{i, m, doc_cons(left: x, right: y)} | t]), do: format(w, k, [{i, m, x} | [{i, m, y} | t]])
def format(w, k, [{i, m, doc_nest(indent: j, doc: x)} | t]), do: format(w, k, [{i + j, m, x} | t])
def format(w, k, [{i, m, doc_group(doc: x)} | t]), do: format(w, k, [{i, m, x} | t])
def format(w, k, [{_, _, s} | t]) when is_binary(s), do: [s | format(w, (k + byte_size s), t)]
def format(w, k, [{_, :flat, doc_break(str: s)} | t]), do: [s | format(w, (k + byte_size s), t)]
def format(w, k, [{i, :break, doc_break(str: s)} | t]) do
def format(_, _, []), do: []
def format(w, _, [{i, _, :doc_line} | t]), do: [indent(i) | format(w, i, t)]
def format(w, k, [{_, _, :doc_nil} | t]), do: format(w, k, t)
def format(w, k, [{i, m, doc_cons(x, y)} | t]), do: format(w, k, [{i, m, x} | [{i, m, y} | t]])
def format(w, k, [{i, m, doc_nest(x, j)} | t]), do: format(w, k, [{i + j, m, x} | t])
def format(w, k, [{i, m, doc_group(x)} | t]), do: format(w, k, [{i, m, x} | t])
def format(w, k, [{_, _, s} | t]) when is_binary(s), do: [s | format(w, (k + byte_size s), t)]
def format(w, k, [{_, :flat, doc_break(s)} | t]), do: [s | format(w, (k + byte_size s), t)]
def format(w, k, [{i, :break, doc_break(s)} | t]) do
k = k + byte_size(s)
if w == :infinity or fits?(w - k, t) do
@@ -502,6 +504,6 @@ defmodule Inspect.Algebra do
@doc false
@spec render([binary]) :: binary
def render(sdoc) do
iodata_to_binary sdoc
IO.iodata_to_binary sdoc
end
end
+67
View File
@@ -69,4 +69,71 @@ defmodule Integer do
defp do_parse(bitstring, acc) do
{acc, bitstring}
end
@doc """
Returns a binary which corresponds to the text representation
of `some_integer`.
Inlined by the compiler.
## Examples
iex> Integer.to_string(123)
"123"
"""
@spec to_string(integer) :: String.t
def to_string(some_integer) do
:erlang.integer_to_binary(some_integer)
end
@doc """
Returns a binary which corresponds to the text representation
of `some_integer` in base `base`.
Inlined by the compiler.
## Examples
iex> Integer.to_string(100, 16)
"64"
"""
@spec to_string(integer, pos_integer) :: String.t
def to_string(some_integer, base) do
:erlang.integer_to_binary(some_integer, base)
end
@doc """
Returns a char list which corresponds to the text representation of the given integer.
Inlined by the compiler.
## Examples
iex> Integer.to_char_list(7)
'7'
"""
@spec to_char_list(integer) :: list
def to_char_list(number) do
:erlang.integer_to_list(number)
end
@doc """
Returns a char list which corresponds to the text representation of the
given integer in the given case.
Inlined by the compiler.
## Examples
iex> Integer.to_char_list(1023, 16)
'3FF'
"""
@spec to_char_list(integer, pos_integer) :: list
def to_char_list(number, base) do
:erlang.integer_to_list(number, base)
end
end
+101 -80
View File
@@ -1,66 +1,3 @@
defexception IO.StreamError, [:reason, :message] do
def exception(opts) do
reason = opts[:reason]
formatted = iodata_to_binary(:file.format_error(reason))
IO.StreamError[message: "error during streaming: #{formatted}", reason: reason]
end
end
defmodule IO.Stream do
@moduledoc """
Defines a `IO.Stream` struct returned by `IO.stream/2` and `IO.binstream/2`.
The following fields are public:
* `device` - the IO device
* `raw` - a boolean indicating if bin functions should be used
* `line_or_bytes` - if reading should read lines or a given amount of bytes
"""
defstruct device: nil, raw: true, line_or_bytes: :line
defimpl Collectable do
def empty(stream) do
stream
end
def into(%{device: device, raw: raw} = stream) do
{:ok, into(stream, device, raw)}
end
defp into(stream, device, raw) do
fn
:ok, {:cont, x} ->
case raw do
true -> IO.binwrite(device, x)
false -> IO.write(device, x)
end
:ok, _ -> stream
end
end
end
defimpl Enumerable do
def reduce(%{device: device, raw: raw, line_or_bytes: line_or_bytes}, acc, fun) do
next_fun =
case raw do
true -> &IO.each_binstream(&1, line_or_bytes)
false -> &IO.each_stream(&1, line_or_bytes)
end
Stream.unfold(device, next_fun).(acc, fun)
end
def count(_stream) do
{:error, __MODULE__}
end
def member?(_stream, _term) do
{:error, __MODULE__}
end
end
end
defmodule IO do
@moduledoc """
Functions handling IO.
@@ -95,6 +32,7 @@ defmodule IO do
@type device :: atom | pid
@type nodata :: {:error, term} | :eof
@type chardata() :: :unicode.chardata()
import :erlang, only: [group_leader: 0]
@@ -116,7 +54,7 @@ defmodule IO do
for instance `{:error, :estale}` if reading from an
NFS file system.
"""
@spec read(device, :line | non_neg_integer) :: char_data | nodata
@spec read(device, :line | non_neg_integer) :: chardata | nodata
def read(device \\ group_leader, chars_or_line)
def read(device, :line) do
@@ -171,9 +109,9 @@ defmodule IO do
#=> "error"
"""
@spec write(device, char_data | String.Chars.t) :: :ok
@spec write(device, chardata | String.Chars.t) :: :ok
def write(device \\ group_leader(), item) do
:io.put_chars map_dev(device), to_char_data(item)
:io.put_chars map_dev(device), to_chardata(item)
end
@doc """
@@ -190,12 +128,12 @@ defmodule IO do
@doc """
Writes the argument to the device, similar to `write/2`,
but adds a newline at the end. The argument is expected
to be a char_data.
to be a chardata.
"""
@spec puts(device, char_data | String.Chars.t) :: :ok
@spec puts(device, chardata | String.Chars.t) :: :ok
def puts(device \\ group_leader(), item) do
erl_dev = map_dev(device)
:io.put_chars erl_dev, [to_char_data(item), ?\n]
:io.put_chars erl_dev, [to_chardata(item), ?\n]
end
@doc """
@@ -244,8 +182,8 @@ defmodule IO do
for instance `{:error, :estale}` if reading from an
NFS file system.
"""
@spec getn(char_data | String.Chars.t, pos_integer) :: char_data | nodata
@spec getn(device, char_data | String.Chars.t) :: char_data | nodata
@spec getn(chardata | String.Chars.t, pos_integer) :: chardata | nodata
@spec getn(device, chardata | String.Chars.t) :: chardata | nodata
def getn(prompt, count \\ 1)
def getn(prompt, count) when is_integer(count) do
@@ -262,9 +200,9 @@ defmodule IO do
the number of unicode codepoints to be retrieved.
Otherwise, `count` is the number of raw bytes to be retrieved.
"""
@spec getn(device, char_data | String.Chars.t, pos_integer) :: char_data | nodata
@spec getn(device, chardata | String.Chars.t, pos_integer) :: chardata | nodata
def getn(device, prompt, count) do
:io.get_chars(map_dev(device), to_char_data(prompt), count)
:io.get_chars(map_dev(device), to_chardata(prompt), count)
end
@doc """
@@ -279,9 +217,9 @@ defmodule IO do
for instance `{:error, :estale}` if reading from an
NFS file system.
"""
@spec gets(device, char_data | String.Chars.t) :: char_data | nodata
@spec gets(device, chardata | String.Chars.t) :: chardata | nodata
def gets(device \\ group_leader(), prompt) do
:io.get_line(map_dev(device), to_char_data(prompt))
:io.get_line(map_dev(device), to_chardata(prompt))
end
@doc """
@@ -310,7 +248,8 @@ defmodule IO do
"""
@spec stream(device, :line | pos_integer) :: Enumerable.t
def stream(device, line_or_codepoints) do
%IO.Stream{device: map_dev(device), raw: false, line_or_bytes: line_or_codepoints}
struct IO.Stream,
device: map_dev(device), raw: false, line_or_bytes: line_or_codepoints
end
@doc """
@@ -328,7 +267,89 @@ defmodule IO do
"""
@spec binstream(device, :line | pos_integer) :: Enumerable.t
def binstream(device, line_or_bytes) do
%IO.Stream{device: map_dev(device), raw: true, line_or_bytes: line_or_bytes}
struct IO.Stream,
device: map_dev(device), raw: true, line_or_bytes: line_or_bytes
end
@doc """
Converts chardata (a list of integers representing codepoints,
lists and strings) into a string.
In case the conversion fails, it raises a `UnicodeConversionError`.
If a string is given, returns the string itself.
## Examples
iex> IO.chardata_to_string([0x00E6, 0x00DF])
"æß"
iex> IO.chardata_to_string([0x0061, "bc"])
"abc"
"""
@spec chardata_to_string(chardata) :: String.t | no_return
def chardata_to_string(string) when is_binary(string) do
string
end
def chardata_to_string(list) when is_list(list) do
case :unicode.characters_to_binary(list) do
result when is_binary(result) ->
result
{:error, encoded, rest} ->
raise UnicodeConversionError, encoded: encoded, rest: rest, kind: :invalid
{:incomplete, encoded, rest} ->
raise UnicodeConversionError, encoded: encoded, rest: rest, kind: :incomplete
end
end
@doc """
Converts iodata (a list of integers representing bytes, lists
and binaries) into a binary.
Notice that this function treats lists of integers as raw bytes
and does not perform any kind of encoding conversion. If you want
to convert from a char list to a string (UTF-8 encoded), please
use `chardata_to_string/1` instead.
If this function receives a binary, the same binary is returned.
Inlined by the compiler.
## Examples
iex> bin1 = <<1, 2, 3>>
iex> bin2 = <<4, 5>>
iex> bin3 = <<6>>
iex> IO.iodata_to_binary([bin1, 1, [2, 3, bin2], 4|bin3])
<<1,2,3,1,2,3,4,5,4,6>>
iex> bin = <<1, 2, 3>>
iex> IO.iodata_to_binary(bin)
<<1,2,3>>
"""
@spec iodata_to_binary(iodata) :: binary
def iodata_to_binary(item) do
:erlang.iolist_to_binary(item)
end
@doc """
Returns the size of an iodata.
Inlined by the compiler.
## Examples
iex> IO.iodata_length([1, 2|<<3, 4>>])
4
"""
@spec iodata_length(iodata) :: non_neg_integer
def iodata_length(item) do
:erlang.iolist_size(item)
end
@doc false
@@ -355,13 +376,13 @@ defmodule IO do
end
end
@compile {:inline, map_dev: 1, to_char_data: 1}
@compile {:inline, map_dev: 1, to_chardata: 1}
# Map the Elixir names for standard io and error to Erlang names
defp map_dev(:stdio), do: :standard_io
defp map_dev(:stderr), do: :standard_error
defp map_dev(other) when is_atom(other) or is_pid(other) or is_tuple(other), do: other
defp to_char_data(list) when is_list(list), do: list
defp to_char_data(other), do: to_string(other)
defp to_chardata(list) when is_list(list), do: list
defp to_chardata(other), do: to_string(other)
end
+5 -5
View File
@@ -7,7 +7,7 @@ defmodule IO.ANSI.Sequence do
"\e[#{unquote(code)}#{unquote(terminator)}"
end
defp escape_sequence(unquote(atom_to_list(name))) do
defp escape_sequence(unquote(Atom.to_char_list(name))) do
unquote(name)()
end
end
@@ -131,7 +131,7 @@ defmodule IO.ANSI do
defsequence :clear, "2", "J"
defp escape_sequence(other) do
raise ArgumentError, message: "invalid ANSI sequence specification: #{other}"
raise ArgumentError, "invalid ANSI sequence specification: #{other}"
end
@doc ~S"""
@@ -210,8 +210,8 @@ defmodule IO.ANSI do
end
defp do_escape(<<>>, _emit, _emitted, buffer, _acc) when is_list(buffer) do
buffer = iodata_to_binary Enum.reverse(buffer)
raise ArgumentError, message: "missing } for escape fragment #{buffer}"
buffer = IO.iodata_to_binary Enum.reverse(buffer)
raise ArgumentError, "missing } for escape fragment #{buffer}"
end
defp do_escape(<<?%, ?{, t :: binary>>, emit, emitted, nil, acc) do
@@ -223,6 +223,6 @@ defmodule IO.ANSI do
end
defp do_escape(<<>>, _emit, emitted, nil, acc) do
{iodata_to_binary(Enum.reverse(acc)), emitted}
{IO.iodata_to_binary(Enum.reverse(acc)), emitted}
end
end
+1 -1
View File
@@ -355,7 +355,7 @@ defmodule IO.ANSI.Docs do
end
defp handle_inline(<<>>, _mark, buffer, acc, _options) do
iodata_to_binary Enum.reverse([Enum.reverse(buffer)|acc])
IO.iodata_to_binary Enum.reverse([Enum.reverse(buffer)|acc])
end
defp inline_buffer(buffer, options) do
+64
View File
@@ -0,0 +1,64 @@
defmodule IO.StreamError do
defexception [:reason, :message]
def exception(opts) do
reason = opts[:reason]
formatted = IO.iodata_to_binary(:file.format_error(reason))
%IO.StreamError{message: "error during streaming: #{formatted}", reason: reason}
end
end
defmodule IO.Stream do
@moduledoc """
Defines a `IO.Stream` struct returned by `IO.stream/2` and `IO.binstream/2`.
The following fields are public:
* `device` - the IO device
* `raw` - a boolean indicating if bin functions should be used
* `line_or_bytes` - if reading should read lines or a given amount of bytes
"""
defstruct device: nil, raw: true, line_or_bytes: :line
defimpl Collectable do
def empty(stream) do
stream
end
def into(%{device: device, raw: raw} = stream) do
{:ok, into(stream, device, raw)}
end
defp into(stream, device, raw) do
fn
:ok, {:cont, x} ->
case raw do
true -> IO.binwrite(device, x)
false -> IO.write(device, x)
end
:ok, _ -> stream
end
end
end
defimpl Enumerable do
def reduce(%{device: device, raw: raw, line_or_bytes: line_or_bytes}, acc, fun) do
next_fun =
case raw do
true -> &IO.each_binstream(&1, line_or_bytes)
false -> &IO.each_stream(&1, line_or_bytes)
end
Stream.unfold(device, next_fun).(acc, fun)
end
def count(_stream) do
{:error, __MODULE__}
end
def member?(_stream, _term) do
{:error, __MODULE__}
end
end
end
+346 -738
View File
File diff suppressed because it is too large Load Diff
+88 -69
View File
@@ -9,15 +9,13 @@ defmodule Kernel.CLI do
This is the API invoked by Elixir boot process.
"""
def main(argv) do
argv = for arg <- argv, do: String.from_char_data!(arg)
argv = for arg <- argv, do: IO.chardata_to_string(arg)
{config, argv} = process_argv(argv, @blank_config)
{config, argv} = parse_argv(argv)
System.argv(argv)
run fn ->
command_results = Enum.map(Enum.reverse(config.commands), &process_command(&1, config))
command_errors = for {:error, msg} <- command_results, do: msg
errors = Enum.reverse(config.errors) ++ command_errors
errors = process_commands(config)
if errors != [] do
Enum.each(errors, &IO.puts(:stderr, &1))
@@ -55,6 +53,23 @@ defmodule Kernel.CLI do
end
end
@doc """
Parses ARGV returning the CLI config and trailing args.
"""
def parse_argv(argv) do
parse_argv(argv, @blank_config)
end
@doc """
Process commands according to the parsed config from `parse_argv/1`.
Returns all errors.
"""
def process_commands(config) do
results = Enum.map(Enum.reverse(config.commands), &process_command(&1, config))
errors = for {:error, msg} <- results, do: msg
Enum.reverse(config.errors, errors)
end
## Helpers
defp at_exit(status) do
@@ -75,7 +90,7 @@ defmodule Kernel.CLI do
end
defp shared_option?(list, config, callback) do
case process_shared(list, config) do
case parse_shared(list, config) do
{[h|hs], _} when h == hd(list) ->
new_config = %{config | errors: ["#{h} : Unknown option" | config.errors]}
callback.(hs, new_config)
@@ -106,160 +121,164 @@ defmodule Kernel.CLI do
[]
end
# Process shared options
# Parse shared options
defp process_shared([opt|_t], _config) when opt in ["-v", "--version"] do
defp parse_shared([opt|_t], _config) when opt in ["-v", "--version"] do
IO.puts "Elixir #{System.version}"
System.halt 0
end
defp process_shared(["-pa", h|t], config) do
defp parse_shared(["-pa", h|t], config) do
Enum.each Path.wildcard(Path.expand(h)), &Code.prepend_path(&1)
process_shared t, config
parse_shared t, config
end
defp process_shared(["-pz", h|t], config) do
defp parse_shared(["-pz", h|t], config) do
Enum.each Path.wildcard(Path.expand(h)), &Code.append_path(&1)
process_shared t, config
parse_shared t, config
end
defp process_shared(["--app", h|t], config) do
process_shared t, %{config | commands: &[{:app, h}|&1]}
defp parse_shared(["--app", h|t], config) do
parse_shared t, %{config | commands: &[{:app, h}|&1]}
end
defp process_shared(["--no-halt"|t], config) do
process_shared t, %{config | halt: false}
defp parse_shared(["--no-halt"|t], config) do
parse_shared t, %{config | halt: false}
end
defp process_shared(["-e", h|t], config) do
process_shared t, %{config | commands: [{:eval, h} | config.commands]}
defp parse_shared(["-e", h|t], config) do
parse_shared t, %{config | commands: [{:eval, h} | config.commands]}
end
defp process_shared(["-r", h|t], config) do
process_shared t, %{config | commands: [{:require, h} | config.commands]}
defp parse_shared(["-r", h|t], config) do
parse_shared t, %{config | commands: [{:require, h} | config.commands]}
end
defp process_shared(["-pr", h|t], config) do
process_shared t, %{config | commands: [{:parallel_require, h} | config.commands]}
defp parse_shared(["-pr", h|t], config) do
parse_shared t, %{config | commands: [{:parallel_require, h} | config.commands]}
end
defp process_shared([erl, _|t], config) when erl in ["--erl", "--sname", "--name", "--cookie"] do
process_shared t, config
defp parse_shared([erl, _|t], config) when erl in ["--erl", "--sname", "--name", "--cookie"] do
parse_shared t, config
end
defp process_shared([erl|t], config) when erl in ["--detached", "--hidden", "--gen-debug"] do
process_shared t, config
defp parse_shared([erl|t], config) when erl in ["--detached", "--hidden", "--gen-debug"] do
parse_shared t, config
end
defp process_shared(list, config) do
defp parse_shared(list, config) do
{list, config}
end
# Process init options
defp process_argv(["--"|t], config) do
defp parse_argv(["--"|t], config) do
{config, t}
end
defp process_argv(["+elixirc"|t], config) do
process_compiler t, config
defp parse_argv(["+elixirc"|t], config) do
parse_compiler t, config
end
defp process_argv(["+iex"|t], config) do
process_iex t, config
defp parse_argv(["+iex"|t], config) do
parse_iex t, config
end
defp process_argv(["-S", h|t], config) do
defp parse_argv(["-S", h|t], config) do
{%{config | commands: [{:script, h} | config.commands]}, t}
end
defp process_argv([h|t] = list, config) do
defp parse_argv([h|t] = list, config) do
case h do
"-" <> _ ->
shared_option? list, config, &process_argv(&1, &2)
shared_option? list, config, &parse_argv(&1, &2)
_ ->
{%{config | commands: [{:file, h} | config.commands]}, t}
if Keyword.has_key?(config.commands, :eval) do
{config, list}
else
{%{config | commands: [{:file, h} | config.commands]}, t}
end
end
end
defp process_argv([], config) do
defp parse_argv([], config) do
{config, []}
end
# Process compiler options
# Parse compiler options
defp process_compiler(["--"|t], config) do
defp parse_compiler(["--"|t], config) do
{config, t}
end
defp process_compiler(["-o", h|t], config) do
process_compiler t, %{config | output: h}
defp parse_compiler(["-o", h|t], config) do
parse_compiler t, %{config | output: h}
end
defp process_compiler(["--no-docs"|t], config) do
process_compiler t, %{config | compiler_options: [{:docs, false} | config.compiler_options]}
defp parse_compiler(["--no-docs"|t], config) do
parse_compiler t, %{config | compiler_options: [{:docs, false} | config.compiler_options]}
end
defp process_compiler(["--no-debug-info"|t], config) do
process_compiler t, %{config | compiler_options: [{:debug_info, false} | config.compiler_options]}
defp parse_compiler(["--no-debug-info"|t], config) do
parse_compiler t, %{config | compiler_options: [{:debug_info, false} | config.compiler_options]}
end
defp process_compiler(["--ignore-module-conflict"|t], config) do
process_compiler t, %{config | compiler_options: [{:ignore_module_conflict, true} | config.compiler_options]}
defp parse_compiler(["--ignore-module-conflict"|t], config) do
parse_compiler t, %{config | compiler_options: [{:ignore_module_conflict, true} | config.compiler_options]}
end
defp process_compiler(["--warnings-as-errors"|t], config) do
process_compiler t, %{config | compiler_options: [{:warnings_as_errors, true} | config.compiler_options]}
defp parse_compiler(["--warnings-as-errors"|t], config) do
parse_compiler t, %{config | compiler_options: [{:warnings_as_errors, true} | config.compiler_options]}
end
defp process_compiler(["--verbose"|t], config) do
process_compiler t, %{config | verbose_compile: true}
defp parse_compiler(["--verbose"|t], config) do
parse_compiler t, %{config | verbose_compile: true}
end
defp process_compiler([h|t] = list, config) do
defp parse_compiler([h|t] = list, config) do
case h do
"-" <> _ ->
shared_option? list, config, &process_compiler(&1, &2)
shared_option? list, config, &parse_compiler(&1, &2)
_ ->
pattern = if :filelib.is_dir(h), do: "#{h}/**/*.ex", else: h
process_compiler t, %{config | compile: [pattern | config.compile]}
parse_compiler t, %{config | compile: [pattern | config.compile]}
end
end
defp process_compiler([], config) do
defp parse_compiler([], config) do
{%{config | commands: [{:compile, config.compile}|config.commands]}, []}
end
# Process iex options
# Parse iex options
defp process_iex(["--"|t], config) do
defp parse_iex(["--"|t], config) do
{config, t}
end
# This clause is here so that Kernel.CLI does not error out with "unknown
# option"
defp process_iex(["--dot-iex", _|t], config) do
process_iex t, config
# This clause is here so that Kernel.CLI does not
# error out with "unknown option"
defp parse_iex(["--dot-iex", _|t], config) do
parse_iex t, config
end
defp process_iex([opt, _|t], config) when opt in ["--remsh"] do
process_iex t, config
defp parse_iex([opt, _|t], config) when opt in ["--remsh"] do
parse_iex t, config
end
defp process_iex(["-S", h|t], config) do
defp parse_iex(["-S", h|t], config) do
{%{config | commands: [{:script, h} | config.commands]}, t}
end
defp process_iex([h|t] = list, config) do
defp parse_iex([h|t] = list, config) do
case h do
"-" <> _ ->
shared_option? list, config, &process_iex(&1, &2)
shared_option? list, config, &parse_iex(&1, &2)
_ ->
{%{config | commands: [{:file, h} | config.commands]}, t}
end
end
defp process_iex([], config) do
defp parse_iex([], config) do
{config, []}
end
@@ -267,7 +286,7 @@ defmodule Kernel.CLI do
defp process_command({:cookie, h}, _config) do
if Node.alive? do
wrapper fn -> Node.set_cookie(binary_to_atom(h)) end
wrapper fn -> Node.set_cookie(String.to_atom(h)) end
else
{:error, "--cookie : Cannot set cookie if the node is not alive (set --name or --sname)"}
end
@@ -278,7 +297,7 @@ defmodule Kernel.CLI do
end
defp process_command({:app, app}, _config) when is_binary(app) do
case Application.ensure_all_started(binary_to_atom(app)) do
case Application.ensure_all_started(String.to_atom(app)) do
{:error, {app, reason}} ->
{:error, "--app : Could not start application #{app}: " <>
Application.format_error(reason)}
+2 -1
View File
@@ -7,7 +7,8 @@
# Note that since this is required for bootstrap, we can't use
# any of the `GenServer.Behaviour` conveniences.
defmodule Kernel.LexicalTracker do
@timeout 30_000
@moduledoc false
@timeout 30_000
@behaviour :gen_server
@import 2
+1 -9
View File
@@ -29,7 +29,7 @@ defmodule Kernel.SpecialForms do
Conveniences for manipulating tuples can be found in the
`Tuple` module. Some functions for working with tuples are
also available in `Kernel`, namely `Kernel.elem/2`,
`Kernel.set_elem/3` and `Kernel.tuple_size/1`.
`Kernel.put_elem/3` and `Kernel.tuple_size/1`.
## Examples
@@ -667,7 +667,6 @@ defmodule Kernel.SpecialForms do
able to unquote;
* `:location` - When set to `:keep`, keeps the current line and file from quote.
Read the Stacktrace information section below for more information;
* `:hygiene` - Allows a developer to disable hygiene selectively;
* `:context` - Sets the resolution context;
* `:bind_quoted` - Passes a binding to the macro. Whenever a binding is given,
`unquote` is automatically disabled;
@@ -864,10 +863,6 @@ defmodule Kernel.SpecialForms do
ContextHygiene.read
#=> 1
Hygiene for variables can be disabled overall as:
quote hygiene: [vars: false], do: x
## Hygiene in aliases
Aliases inside quote are hygienic by default.
@@ -987,9 +982,6 @@ defmodule Kernel.SpecialForms do
Lazy.return_size #=> 2
As in aliases, import expansion can be explicitly disabled
via the `hygiene: [imports: false]` option.
## Stacktrace information
When defining functions via macros, developers have the option of
+3 -13
View File
@@ -153,8 +153,6 @@ defmodule Kernel.Typespec do
@type dict(key, value) :: [{key, value}]
Types can also be defined for records, see `defrecord/3`.
## Defining a specification
@spec function_name(type1, type2) :: return_type
@@ -707,10 +705,6 @@ defmodule Kernel.Typespec do
typespec_to_ast({:type, line, :char_list, []})
end
defp typespec_to_ast({:remote_type, line, [{:atom, _, :elixir}, {:atom, _, :char_data}, []]}) do
typespec_to_ast({:type, line, :char_data, []})
end
defp typespec_to_ast({:remote_type, line, [{:atom, _, :elixir}, {:atom, _, :as_boolean}, [arg]]}) do
typespec_to_ast({:type, line, :as_boolean, [arg]})
end
@@ -746,11 +740,11 @@ defmodule Kernel.Typespec do
defp typespec_to_ast(other), do: other
defp erl_to_ex_var(var) do
case atom_to_binary(var) do
case Atom.to_string(var) do
<<"_", c :: [binary, size(1)], rest :: binary>> ->
binary_to_atom("_#{String.downcase(c)}#{rest}")
String.to_atom("_#{String.downcase(c)}#{rest}")
<<c :: [binary, size(1)], rest :: binary>> ->
binary_to_atom("#{String.downcase(c)}#{rest}")
String.to_atom("#{String.downcase(c)}#{rest}")
end
end
@@ -884,10 +878,6 @@ defmodule Kernel.Typespec do
typespec((quote do: :elixir.char_list()), vars, caller)
end
defp typespec({:char_data, _meta, []}, vars, caller) do
typespec((quote do: :elixir.char_data()), vars, caller)
end
defp typespec({:as_boolean, _meta, [arg]}, vars, caller) do
typespec((quote do: :elixir.as_boolean(unquote(arg))), vars, caller)
end
+20 -5
View File
@@ -6,13 +6,14 @@ defmodule Keyword do
A keyword may have duplicated keys so it is not strictly
a dictionary. However most of the functions in this module
behaves exactly as a dictionary and mimic the API defined
behave exactly as a dictionary and mimic the API defined
by the `Dict` behaviour.
For example, `Keyword.get` will get the first entry matching
the given key, regardless if duplicated entries exist.
Similarly, `Keyword.put` and `Keyword.delete` ensure all
duplicated entries for a given key are removed when invoked.
A handful of functions exist to handle duplicated keys, in
particular, `from_enum` allows creating a new keywords without
removing duplicated keys, `get_values` returns all values for
@@ -387,6 +388,9 @@ defmodule Keyword do
Updates the `key` with the given function. If the `key` does
not exist, raises `KeyError`.
If there are duplicated entries, they are all removed and only the first one
is updated.
## Examples
iex> Keyword.update!([a: 1], :a, &(&1 * 2))
@@ -417,6 +421,9 @@ defmodule Keyword do
Updates the `key` with the given function. If the `key` does
not exist, inserts the given `initial` value.
If there are duplicated entries, they are all removed and only the first one
is updated.
## Examples
iex> Keyword.update([a: 1], :a, 13, &(&1 * 2))
@@ -440,8 +447,13 @@ defmodule Keyword do
end
@doc """
Splits the given keywords in two given the given keys.
Duplicated keys are preserved in the split keyword list.
Takes all entries corresponding to the given keys and extracts them into a
separate keyword list. Returns a tuple with the new list and the old list
with removed keys.
Keys for which there are no entires in the keyword list are ignored.
Entries with duplicated keys end up in the same keyword list.
## Examples
@@ -468,7 +480,9 @@ defmodule Keyword do
end
@doc """
Takes the given keys from the dict.
Takes all entries corresponding to the given keys and returns them in a new
keyword list.
Duplicated keys are preserved in the new keyword list.
## Examples
@@ -488,6 +502,7 @@ defmodule Keyword do
@doc """
Drops the given keys from the dict.
Duplicated keys are preserved in the new keyword list.
## Examples
@@ -496,7 +511,7 @@ defmodule Keyword do
iex> Keyword.drop(d, [:b, :d])
[a: 1, c: 3]
iex> d = [a: 1, b: 2, c: 3, d: 4, a: 5]
iex> d = [a: 1, b: 2, b: 3, c: 3, d: 4, a: 5]
iex> Keyword.drop(d, [:b, :d])
[a: 1, c: 3, a: 5]
+125 -31
View File
@@ -337,7 +337,7 @@ defmodule List do
"""
@spec unzip([tuple]) :: [list]
def unzip(list) when is_list(list) do
:lists.map &tuple_to_list/1, zip(list)
:lists.map &Tuple.to_list/1, zip(list)
end
@doc """
@@ -454,21 +454,133 @@ defmodule List do
end
@doc """
Converts char data into a char list.
Converts a char list to an atom.
A char data is a string or a list of integers and strings and it
is converted into a list with each codepoint represented as its
respective integer value.
Currently Elixir does not support conversions from char lists
which contains Unicode codepoints greater than 0xFF.
Inlined by the compiler.
## Examples
iex> List.from_char_data("æß")
{:ok, 'æß'}
iex> List.from_char_data([?a, "bc"])
{:ok, 'abc'}
iex> List.to_atom('elixir')
:elixir
"""
@spec from_char_data(char_data) :: {:ok, char_list} | {:error, list, binary} | {:incomplete, list, binary}
@spec to_atom(char_list) :: atom
def to_atom(char_list) do
:erlang.list_to_atom(char_list)
end
@doc """
Converts a char list to an existing atom.
Currently Elixir does not support conversions from char lists
which contains Unicode codepoints greater than 0xFF.
Inlined by the compiler.
"""
@spec to_existing_atom(char_list) :: atom
def to_existing_atom(char_list) do
:erlang.list_to_existing_atom(char_list)
end
@doc """
Returns the float whose text representation is `char_list`.
Inlined by the compiler.
## Examples
iex> List.to_float('2.2017764e+0')
2.2017764
"""
@spec to_float(char_list) :: float
def to_float(char_list) do
:erlang.list_to_float(char_list)
end
@doc """
Returns an integer whose text representation is `char_list`.
Inlined by the compiler.
## Examples
iex> List.to_integer('123')
123
"""
@spec to_integer(char_list) :: integer
def to_integer(char_list) do
:erlang.list_to_integer(char_list)
end
@doc """
Returns an integer whose text representation is `char_list` in base `base`.
Inlined by the compiler.
## Examples
iex> List.to_integer('3FF', 16)
1023
"""
@spec to_integer(char_list, non_neg_integer) :: integer
def to_integer(char_list, base) do
:erlang.list_to_integer(char_list, base)
end
@doc """
Converts a list to a tuple.
Inlined by the compiler.
## Examples
iex> List.to_tuple([:share, [:elixir, 163]])
{:share, [:elixir, 163]}
"""
@spec to_tuple(list) :: tuple
def to_tuple(list) do
:erlang.list_to_tuple(list)
end
@doc """
Converts a list of integers representing codepoints, lists or
strings into a string.
Notice that this function expect a list of integer representing
UTF-8 codepoints. If you have a list of bytes, you must instead use
[the `:binary` module](http://erlang.org/doc/man/binary.html).
## Examples
iex> List.to_string([0x00E6, 0x00DF])
"æß"
iex> List.to_string([0x0061, "bc"])
"abc"
"""
@spec to_string(:unicode.char_list) :: String.t
def to_string(list) when is_list(list) do
case :unicode.characters_to_binary(list) do
result when is_binary(result) ->
result
{:error, encoded, rest} ->
raise UnicodeConversionError, encoded: encoded, rest: rest, kind: :invalid
{:incomplete, encoded, rest} ->
raise UnicodeConversionError, encoded: encoded, rest: rest, kind: :incomplete
end
end
@doc false
def from_char_data(char_data) do
case :unicode.characters_to_list(char_data) do
result when is_list(result) ->
@@ -482,25 +594,7 @@ defmodule List do
end
end
@doc """
Converts char data into a char list.
A char data is a string or a list of integers and strings and it
is converted into a list with each codepoint represented as its
respective integer value.
In case the conversion fails or is incomplete, it raises a
`UnicodeConversionError`.
## Examples
iex> List.from_char_data!("æß")
'æß'
iex> List.from_char_data!("abc")
'abc'
"""
@spec from_char_data!(char_data) :: char_list | no_return
@doc false
def from_char_data!(char_data) do
case :unicode.characters_to_list(char_data) do
result when is_list(result) ->
@@ -592,7 +686,7 @@ defmodule List do
case heads do
nil -> :lists.reverse acc
_ -> do_zip mlist, [list_to_tuple(:lists.reverse(heads))|acc]
_ -> do_zip mlist, [:erlang.list_to_tuple(:lists.reverse(heads))|acc]
end
end
@@ -608,6 +702,6 @@ defmodule List do
{nil, nil}
end
defp to_list(tuple) when is_tuple(tuple), do: tuple_to_list(tuple)
defp to_list(tuple) when is_tuple(tuple), do: Tuple.to_list(tuple)
defp to_list(list) when is_list(list), do: list
end
+5 -5
View File
@@ -13,7 +13,7 @@ defprotocol List.Chars do
end
defimpl List.Chars, for: Atom do
def to_char_list(atom), do: atom_to_list(atom)
def to_char_list(atom), do: Atom.to_char_list(atom)
end
defimpl List.Chars, for: BitString do
@@ -21,7 +21,7 @@ defimpl List.Chars, for: BitString do
Returns the given binary converted to a char list.
"""
def to_char_list(thing) when is_binary(thing) do
List.from_char_data!(thing)
String.to_char_list(thing)
end
def to_char_list(thing) do
@@ -38,7 +38,7 @@ end
defimpl List.Chars, for: Integer do
def to_char_list(thing) do
integer_to_list(thing)
Integer.to_char_list(thing)
end
end
@@ -47,10 +47,10 @@ defimpl List.Chars, for: Float do
@limit :math.pow(10, @digits)
def to_char_list(thing) when thing > @limit do
float_to_list(thing, scientific: @digits)
Float.to_char_list(thing, scientific: @digits)
end
def to_char_list(thing) do
float_to_list(thing, compact: true, decimals: @digits)
Float.to_char_list(thing, compact: true, decimals: @digits)
end
end
+120 -55
View File
@@ -16,7 +16,7 @@ defmodule Macro do
:&&, :||, :<>, :++, :--, :\\, :::, :<-, :.., :|>, :=~,
:<, :>, :->,
:+, :-, :*, :/, :=, :|, :.,
:and, :or, :xor, :when, :in, :inlist, :inbits,
:and, :or, :xor, :when, :in,
:<<<, :>>>, :|||, :&&&, :^^^, :~~~]
@doc false
@@ -30,7 +30,7 @@ defmodule Macro do
@spec binary_op_props(atom) :: {:left | :right, precedence :: integer}
defp binary_op_props(o) do
case o do
o when o in [:<-, :inlist, :inbits, :\\, :::] -> {:left, 40}
o when o in [:<-, :\\, :::] -> {:left, 40}
:| -> {:right, 50}
:when -> {:right, 70}
:= -> {:right, 80}
@@ -68,7 +68,7 @@ defmodule Macro do
def pipe(expr, call_args, position)
def pipe(expr, {:&, _, _} = call_args, _integer) do
raise ArgumentError, message: "cannot pipe #{to_string expr} into #{to_string call_args}"
raise ArgumentError, "cannot pipe #{to_string expr} into #{to_string call_args}"
end
def pipe(expr, {call, line, atom}, integer) when is_atom(atom) do
@@ -80,17 +80,15 @@ defmodule Macro do
end
def pipe(expr, call_args, _integer) do
raise ArgumentError,
message: "cannot pipe #{to_string expr} into #{to_string call_args}"
raise ArgumentError, "cannot pipe #{to_string expr} into #{to_string call_args}"
end
@doc """
Recurs the quoted expression applying the given function to
each metadata node.
Applies the given function to the node metadata if it contains one.
This is often useful to remove information like lines and
hygienic counters from the expression for either storage or
comparison.
This is often useful when used with `Macro.prewalk/1` to remove
information like lines and hygienic counters from the expression
for either storage or comparison.
## Examples
@@ -104,21 +102,110 @@ defmodule Macro do
def update_meta(quoted, fun)
def update_meta({left, meta, right}, fun) when is_list(meta) do
{update_meta(left, fun), fun.(meta), update_meta(right, fun)}
end
def update_meta({left, right}, fun) do
{update_meta(left, fun), update_meta(right, fun)}
end
def update_meta(list, fun) when is_list(list) do
for x <- list, do: update_meta(x, fun)
{left, fun.(meta), right}
end
def update_meta(other, _fun) do
other
end
@doc """
Performs a depth-first, pre-order traversal of quoted expressions.
"""
@spec prewalk(t, (t -> t)) :: t
def prewalk(ast, fun) when is_function(fun, 1) do
elem(prewalk(ast, nil, fn x, nil -> {fun.(x), nil} end), 0)
end
@doc """
Performs a depth-first, pre-order traversal of quoted expressions
using an accumulator.
"""
@spec prewalk(t, any, (t, any -> {t, any})) :: {t, any}
def prewalk(ast, acc, fun) when is_function(fun, 2) do
{ast, acc} = fun.(ast, acc)
do_prewalk(ast, acc, fun)
end
defp do_prewalk({form, meta, args}, acc, fun) do
unless is_atom(form) do
{form, acc} = fun.(form, acc)
{form, acc} = do_prewalk(form, acc, fun)
end
unless is_atom(args) do
{args, acc} = Enum.map_reduce(args, acc, fn x, acc ->
{x, acc} = fun.(x, acc)
do_prewalk(x, acc, fun)
end)
end
{{form, meta, args}, acc}
end
defp do_prewalk({left, right}, acc, fun) do
{left, acc} = fun.(left, acc)
{left, acc} = do_prewalk(left, acc, fun)
{right, acc} = fun.(right, acc)
{right, acc} = do_prewalk(right, acc, fun)
{{left, right}, acc}
end
defp do_prewalk(list, acc, fun) when is_list(list) do
Enum.map_reduce(list, acc, fn x, acc ->
{x, acc} = fun.(x, acc)
do_prewalk(x, acc, fun)
end)
end
defp do_prewalk(x, acc, _fun) do
{x, acc}
end
@doc """
Performs a depth-first, post-order traversal of quoted expressions.
"""
@spec postwalk(t, (t -> t)) :: t
def postwalk(ast, fun) when is_function(fun, 1) do
elem(postwalk(ast, nil, fn x, nil -> {fun.(x), nil} end), 0)
end
@doc """
Performs a depth-first, post-order traversal of quoted expressions
using an accumulator.
"""
@spec postwalk(t, any, (t, any -> {t, any})) :: {t, any}
def postwalk(ast, acc, fun) when is_function(fun, 2) do
do_postwalk(ast, acc, fun)
end
defp do_postwalk({form, meta, args}, acc, fun) do
unless is_atom(form) do
{form, acc} = do_postwalk(form, acc, fun)
end
unless is_atom(args) do
{args, acc} = Enum.map_reduce(args, acc, &do_postwalk(&1, &2, fun))
end
fun.({form, meta, args}, acc)
end
defp do_postwalk({left, right}, acc, fun) do
{left, acc} = do_postwalk(left, acc, fun)
{right, acc} = do_postwalk(right, acc, fun)
fun.({left, right}, acc)
end
defp do_postwalk(list, acc, fun) when is_list(list) do
{list, acc} = Enum.map_reduce(list, acc, &do_postwalk(&1, &2, fun))
fun.(list, acc)
end
defp do_postwalk(x, acc, fun) do
fun.(x, acc)
end
@doc """
Decomposes a local or remote call into its remote part (when provided),
function name and argument list.
@@ -300,7 +387,7 @@ defmodule Macro do
# Variables
def to_string({var, _, atom} = ast, fun) when is_atom(atom) do
fun.(ast, atom_to_binary(var))
fun.(ast, Atom.to_string(var))
end
# Aliases
@@ -389,7 +476,7 @@ defmodule Macro do
# Unary ops
def to_string({unary, _, [{binary, _, [_, _]} = arg]} = ast, fun)
when unary in unquote(@unary_ops) and binary in unquote(@binary_ops) do
fun.(ast, atom_to_binary(unary) <> "(" <> to_string(arg, fun) <> ")")
fun.(ast, Atom.to_string(unary) <> "(" <> to_string(arg, fun) <> ")")
end
def to_string({:not, _, [arg]} = ast, fun) do
@@ -397,7 +484,7 @@ defmodule Macro do
end
def to_string({op, _, [arg]} = ast, fun) when op in unquote(@unary_ops) do
fun.(ast, atom_to_binary(op) <> to_string(arg, fun))
fun.(ast, Atom.to_string(op) <> to_string(arg, fun))
end
# Access
@@ -425,7 +512,7 @@ defmodule Macro do
list == [] ->
"[]"
:io_lib.printable_list(list) ->
"'" <> Inspect.BitString.escape(String.from_char_data!(list), ?') <> "'"
"'" <> Inspect.BitString.escape(IO.chardata_to_string(list), ?') <> "'"
Keyword.keyword?(list) ->
"[" <> kw_list_to_string(list, fun) <> "]"
true ->
@@ -447,7 +534,7 @@ defmodule Macro do
defp module_to_string(atom, _fun) when is_atom(atom), do: inspect(atom, records: false)
defp module_to_string(other, fun), do: call_to_string(other, fun)
defp call_to_string(atom, _fun) when is_atom(atom), do: atom_to_binary(atom)
defp call_to_string(atom, _fun) when is_atom(atom), do: Atom.to_string(atom)
defp call_to_string({:., _, [arg]}, fun), do: module_to_string(arg, fun) <> "."
defp call_to_string({:., _, [left, right]}, fun), do: module_to_string(left, fun) <> "." <> call_to_string(right, fun)
defp call_to_string(other, fun), do: to_string(other, fun)
@@ -481,7 +568,7 @@ defmodule Macro do
defp kw_block_to_string(key, value, fun) do
block = adjust_new_lines block_to_string(value, fun), "\n "
atom_to_binary(key) <> "\n " <> block <> "\n"
Atom.to_string(key) <> "\n " <> block <> "\n"
end
defp block_to_string([{:->, _, _}|_] = block, fun) do
@@ -510,7 +597,11 @@ defmodule Macro do
defp kw_list_to_string(list, fun) do
Enum.map_join(list, ", ", fn {key, value} ->
atom_to_binary(key) <> ": " <> to_string(value, fun)
atom_name = case Inspect.Atom.inspect(key) do
":" <> rest -> rest
other -> other
end
atom_name <> ": " <> to_string(value, fun)
end)
end
@@ -590,7 +681,7 @@ defmodule Macro do
Consider the implementation below:
defmacro defmodule_with_length(name, do: block) do
length = length(atom_to_list(name))
length = length(Atom.to_char_list(name))
quote do
defmodule unquote(name) do
@@ -630,7 +721,7 @@ defmodule Macro do
defmacro defmodule_with_length(name, do: block) do
expanded = Macro.expand(name, __CALLER__)
length = length(atom_to_list(expanded))
length = length(Atom.to_char_list(expanded))
quote do
defmodule unquote(name) do
@@ -722,7 +813,7 @@ defmodule Macro do
{:ok, receiver, quoted} ->
next = :elixir_counter.next
{:elixir_quote.linify_with_context_counter(0, {receiver, next}, quoted), true}
{:ok, _receiver} ->
{:ok, _receiver, _name, _args} ->
{original, false}
:error ->
{original, false}
@@ -770,30 +861,4 @@ defmodule Macro do
defp expand_until({tree, false}, _env) do
tree
end
@doc """
Recursively traverses the quoted expression checking if all sub-terms are
safe.
Terms are considered safe if they represent data structures and don't actually
evaluate code. Returns `:ok` unless a given term is unsafe,
which is returned as `{:unsafe, term}`.
"""
def safe_term(terms) do
IO.write :stderr, "Macro.safe_term/1 is deprecated\n#{Exception.format_stacktrace}"
do_safe_term(terms) || :ok
end
defp do_safe_term({local, _, terms}) when local in [:{}, :%{}, :__aliases__] do
do_safe_term(terms)
end
defp do_safe_term({unary, _, [term]}) when unary in [:+, :-] do
do_safe_term(term)
end
defp do_safe_term({left, right}), do: do_safe_term(left) || do_safe_term(right)
defp do_safe_term(terms) when is_list(terms), do: Enum.find_value(terms, &do_safe_term(&1))
defp do_safe_term(terms) when is_tuple(terms), do: {:unsafe, terms}
defp do_safe_term(_), do: nil
end
+3 -43
View File
@@ -15,6 +15,8 @@ defmodule Map do
defdelegate [keys(map), values(map), size(map), merge(map1, map2), to_list(map)], to: :maps
@compile {:inline, fetch: 2, put: 3, delete: 2, has_key?: 2}
@doc """
Returns a new empty map.
"""
@@ -24,21 +26,10 @@ defmodule Map do
def fetch(map, key), do: :maps.find(key, map)
def pop(map, key, default \\ nil) do
{get(map, key, default), delete(map, key)}
end
def put(map, key, val) do
:maps.put(key, val, map)
end
def put_new(map, key, val) do
case has_key?(map, key) do
true -> map
false -> :maps.put(key, val, map)
end
end
def delete(map, key), do: :maps.remove(key, map)
def merge(map1, map2, callback) do
@@ -47,36 +38,5 @@ defmodule Map do
end, map1, map2
end
def split(map, keys) do
acc = {%{}, %{}}
:maps.fold fn k, v, {take, drop} ->
if k in keys do
{put(take, k, v), drop}
else
{take, put(drop, k, v)}
end
end, acc, map
end
def update!(map, key, fun) do
case :maps.find(key, map) do
:error ->
raise(KeyError, key: key, term: map)
{:ok, val} ->
:maps.put(key, fun.(val), map)
end
end
def update(map, key, initial, fun) do
case :maps.find(key, map) do
:error ->
:maps.put(key, initial, map)
{:ok, val} ->
:maps.put(key, fun.(val), map)
end
end
def equal?(map, map), do: true
def equal?(_, _), do: false
def equal?(%{} = map1, %{} = map2), do: map1 === map2
end
+5 -5
View File
@@ -534,8 +534,8 @@ defmodule Module do
end
defp simplify_signature({:%, _, [left, _]}, _i) when is_atom(left) do
last = List.last(String.split(atom_to_binary(left), "."))
atom = binary_to_atom(downcase(last))
last = List.last(String.split(Atom.to_string(left), "."))
atom = String.to_atom(downcase(last))
{atom, [], nil}
end
@@ -545,7 +545,7 @@ defmodule Module do
defp simplify_signature({var, _, atom}, _i) when is_atom(atom) do
case atom_to_binary(var) do
"_" <> rest -> {binary_to_atom(rest), [], Elixir}
"_" <> rest -> {String.to_atom(rest), [], Elixir}
_ -> {var, [], nil}
end
end
@@ -945,7 +945,7 @@ defmodule Module do
end
defp normalize_attribute(key, _value) when key in [:type, :typep, :export_type, :opaque, :callback] do
raise ArgumentError, message: "attributes type, typep, export_type, opaque and callback " <>
raise ArgumentError, "attributes type, typep, export_type, opaque and callback " <>
"must be set via Kernel.Typespec"
end
@@ -968,7 +968,7 @@ defmodule Module do
defp assert_not_compiled!(fun, module) do
open?(module) ||
raise ArgumentError,
message: "could not call #{fun} on module #{inspect module} because it was already compiled"
"could not call #{fun} on module #{inspect module} because it was already compiled"
end
defp loaded?(module), do: is_tuple :code.is_loaded(module)
+2 -2
View File
@@ -49,8 +49,8 @@ defmodule Node do
See http://www.erlang.org/doc/man/erlang.html#nodes-1 for more info.
"""
@typep list_arg :: :visible | :hidden | :connected | :this | :known
@spec list(list_arg | [list_arg]) :: [t]
@typep state :: :visible | :hidden | :connected | :this | :known
@spec list(state | [state]) :: [t]
def list(args) do
:erlang.nodes(args)
end
+227 -85
View File
@@ -3,6 +3,11 @@ defmodule OptionParser do
This module contains functions to parse command line arguments.
"""
@type argv :: [String.t]
@type parsed :: Keyword.t
@type errors :: [{String.t, String.t | nil}]
@type options :: [switches: Keyword.t, strict: Keyword.t, aliases: Keyword.t]
@doc """
Parses `argv` into a keywords list.
@@ -32,7 +37,18 @@ defmodule OptionParser do
## Switches
Many times though, it is better to explicitly list the available
switches and their formats. The following types are supported:
switches and their formats. The switches can be specified via two
different options:
* `:strict` - the switches are strict. Any switch that does not
exist in the switch list is treated as an error;
* `:switches` - configure some switches. Switches that does not
exist in the switch list are still attempted to be parsed;
Note only `:strict` or `:switches` may be given at once.
For each switch, the following types are supported:
* `:boolean` - Marks the given switch as a boolean. Boolean switches
never consume the following value unless it is
@@ -41,8 +57,9 @@ defmodule OptionParser do
* `:float` - Parses the switch as a float;
* `:string` - Returns the switch as a string;
If a switch can't be parsed, the option is returned in the invalid
options list (third element of the returned tuple).
If a switch can't be parsed or is not specfied in the strict case,
the option is returned in the invalid options list (third element
of the returned tuple).
The following extra "types" are supported:
@@ -50,18 +67,25 @@ defmodule OptionParser do
Examples:
iex> OptionParser.parse(["--unlock", "path/to/file"], switches: [unlock: :boolean])
iex> OptionParser.parse(["--unlock", "path/to/file"], strict: [unlock: :boolean])
{[unlock: true], ["path/to/file"], []}
iex> OptionParser.parse(["--unlock", "--limit", "0", "path/to/file"],
...> switches: [unlock: :boolean, limit: :integer])
...> strict: [unlock: :boolean, limit: :integer])
{[unlock: true, limit: 0], ["path/to/file"], []}
iex> OptionParser.parse(["--limit", "3"], switches: [limit: :integer])
iex> OptionParser.parse(["--limit", "3"], strict: [limit: :integer])
{[limit: 3], [], []}
iex> OptionParser.parse(["--limit", "yyz"], switches: [limit: :integer])
{[], [], [limit: "yyz"]}
iex> OptionParser.parse(["--limit", "xyz"], strict: [limit: :integer])
{[], [], [{"--limit", "xyz"}]}
iex> OptionParser.parse(["--unknown", "xyz"], strict: [])
{[], ["xyz"], [{"--unknown", nil}]}
iex> OptionParser.parse(["--limit", "3", "--unknown", "xyz"],
...> switches: [limit: :integer])
{[limit: 3, unknown: "xyz"], [], []}
## Negation switches
@@ -85,8 +109,10 @@ defmodule OptionParser do
{[debug: true], [], []}
"""
@spec parse(argv, options) :: {parsed, argv, errors}
def parse(argv, opts \\ []) when is_list(argv) and is_list(opts) do
parse(argv, opts, true)
config = compile_config(opts, true)
do_parse(argv, config, [], [], [])
end
@doc """
@@ -104,71 +130,156 @@ defmodule OptionParser do
{[verbose: true, source: "lib"], ["test/enum_test.exs", "--unlock"], []}
"""
@spec parse_head(argv, options) :: {parsed, argv, errors}
def parse_head(argv, opts \\ []) when is_list(argv) and is_list(opts) do
parse(argv, opts, false)
config = compile_config(opts, false)
do_parse(argv, config, [], [], [])
end
defp do_parse([], _config, opts, args, invalid) do
{Enum.reverse(opts), Enum.reverse(args), Enum.reverse(invalid)}
end
defp do_parse(argv, {aliases, switches, strict, all}=config, opts, args, invalid) do
case next(argv, aliases, switches, strict) do
{:ok, option, value, rest} ->
# the option exist and it was successfully parsed
kinds = List.wrap Keyword.get(switches, option)
new_opts = do_store_option(opts, option, value, kinds)
do_parse(rest, config, new_opts, args, invalid)
{:invalid, option, value, rest} ->
# the option exist but it has wrong value
do_parse(rest, config, opts, args, [{option, value}|invalid])
{:undefined, option, _value, rest} ->
# the option does not exist (for strict cases)
do_parse(rest, config, opts, args, [{option, nil}|invalid])
{:error, ["--"|rest]} ->
{Enum.reverse(opts), Enum.reverse(args, rest), Enum.reverse(invalid)}
{:error, [arg|rest]=remaining_args} ->
# there is no option
if all do
do_parse(rest, config, opts, [arg|args], invalid)
else
{Enum.reverse(opts), Enum.reverse(args, remaining_args), Enum.reverse(invalid)}
end
end
end
@doc """
Low-level function that parses one option.
It accepts the same options as `parse/2` and `parse_head/2`
as both functions are built on top of next. This function
may return:
* `{:ok, key, value, rest}` - the option `key` with `value` was successfully parsed
* `{:invalid, key, value, rest}` - the option `key` is invalid with `value`
(returned when the switch type does not match the one given via the command line)
* `{:undefined, key, value, rest}` - the option `key` is undefined
(returned on strict cases and the switch is unknown)
* `{:error, rest}` - there are no switches at the top of the given argv
"""
@spec next(argv, options) ::
{:ok, key :: atom, value :: term, argv} |
{:invalid, key :: atom, value :: term, argv} |
{:undefined, key :: atom, value :: term, argv} |
{:error, argv}
def next(argv, opts \\ []) when is_list(argv) and is_list(opts) do
{aliases, switches, strict, _} = compile_config(opts, true)
next(argv, aliases, switches, strict)
end
defp next([], _aliases, _switches, _strict) do
{:error, []}
end
defp next(["--"|_]=argv, _aliases, _switches, _strict) do
{:error, argv}
end
defp next(["-"|_]=argv, _aliases, _switches, _strict) do
{:error, argv}
end
defp next(["- " <> _|_]=argv, _aliases, _switches, _strict) do
{:error, argv}
end
defp next(["-" <> option|rest], aliases, switches, strict) do
{option, value} = split_option(option)
opt_name_bin = "-" <> option
tagged = tag_option(option, value, switches, aliases)
if strict and not option_defined?(tagged, switches) do
{:undefined, opt_name_bin, value, rest}
else
{opt_name, kinds, value} = normalize_option(tagged, value, switches)
{value, kinds, rest} = normalize_value(value, kinds, rest, strict)
case validate_option(opt_name, value, kinds) do
{:ok, new_value} -> {:ok, opt_name, new_value, rest}
:invalid -> {:invalid, opt_name_bin, value, rest}
end
end
end
defp next(argv, _aliases, _switches, _strict) do
{:error, argv}
end
## Helpers
defp parse(argv, opts, bool) do
aliases = opts[:aliases] || []
switches = opts[:switches] || []
parse(argv, aliases, switches, bool)
defp compile_config(opts, all) do
aliases = opts[:aliases] || []
{switches, strict} = cond do
s = opts[:switches] ->
{s, false}
s = opts[:strict] ->
{s, true}
true ->
{[], false}
end
{aliases, switches, strict, all}
end
defp parse(argv, aliases, switches, all) do
parse(argv, aliases, switches, [], [], [], all)
end
defp validate_option(option, value, kinds) do
{invalid_opt, value} = cond do
:invalid in kinds ->
{option, value}
:boolean in kinds ->
case value do
t when t in [true, "true"] -> {nil, true}
f when f in [false, "false"] -> {nil, false}
_ -> {option, value}
end
:integer in kinds ->
case Integer.parse(value) do
{value, ""} -> {nil, value}
_ -> {option, value}
end
:float in kinds ->
case Float.parse(value) do
{value, ""} -> {nil, value}
_ -> {option, value}
end
true ->
{nil, value}
end
defp parse(["--"|_] = value, _aliases, _switches, dict, args, invalid, _all) do
{Enum.reverse(dict), Enum.reverse(args, value), Enum.reverse(invalid)}
end
defp parse(["-" <> option|t], aliases, switches, dict, args, invalid, all) do
{option, value} = split_option(option)
{option, kinds, value} = normalize_option(option, value, switches, aliases)
{value, kinds, t} = normalize_value(value, kinds, t)
{dict, invalid} = store_option(dict, invalid, option, value, kinds)
parse(t, aliases, switches, dict, args, invalid, all)
end
defp parse([h|t], aliases, switches, dict, args, invalid, true) do
parse(t, aliases, switches, dict, [h|args], invalid, true)
end
defp parse([], _, _switches, dict, args, invalid, true) do
{Enum.reverse(dict), Enum.reverse(args), Enum.reverse(invalid)}
end
defp parse(value, _, _switches, dict, _args, invalid, false) do
{Enum.reverse(dict), value, Enum.reverse(invalid)}
end
defp store_option(dict, invalid, option, value, kinds) do
{invalid_option, value} =
cond do
:invalid in kinds ->
{option, value}
:boolean in kinds ->
{nil, value in [true, "true"]}
:integer in kinds ->
case Integer.parse(value) do
{value, ""} -> {nil, value}
_ -> {option, value}
end
:float in kinds ->
case Float.parse(value) do
{value, ""} -> {nil, value}
_ -> {option, value}
end
true ->
{nil, value}
end
if invalid_option do
{dict, [{option, value}|invalid]}
if invalid_opt do
:invalid
else
{do_store_option(dict, option, value, kinds), invalid}
{:ok, value}
end
end
@@ -181,20 +292,36 @@ defmodule OptionParser do
end
end
defp normalize_option(<<?-, option :: binary>>, value, switches, _aliases) do
normalize_option(get_negated(option, switches), value, switches)
defp tag_option(<<?-, option :: binary>>, value, switches, _aliases) do
get_negated(option, value, switches)
end
defp normalize_option(option, value, switches, aliases) do
option = get_option(option)
if alias = aliases[option] do
normalize_option({:default, alias}, value, switches)
defp tag_option(option, _value, _switches, aliases) when is_binary(option) do
opt = get_option(option)
if alias = aliases[opt] do
{:default, alias}
else
{option, [:invalid], value}
{:unknown, opt}
end
end
defp normalize_option({:negated, option}, _value, switches) do
defp option_defined?({:unknown, _option}, _switches) do
false
end
defp option_defined?({:negated, option}, switches) do
Keyword.has_key?(switches, option)
end
defp option_defined?({:default, option}, switches) do
Keyword.has_key?(switches, option)
end
defp normalize_option({:unknown, option}, value, _switches) do
{option, [:invalid], value}
end
defp normalize_option({:negated, option}, nil, switches) do
kinds = List.wrap(switches[option])
cond do
@@ -203,15 +330,20 @@ defmodule OptionParser do
kinds == [] ->
{option, kinds, true}
true ->
{option, [:invalid], false}
{reverse_negated(option), [:invalid], nil}
end
end
defp normalize_option({:negated, option}, value, _switches) do
{option, [:invalid], value}
end
defp normalize_option({:default, option}, value, switches) do
{option, List.wrap(switches[option]), value}
end
defp normalize_value(nil, kinds, t) do
defp normalize_value(nil, kinds, t, strict) do
nil_or_true = if strict, do: nil, else: true
cond do
:boolean in kinds ->
{true, kinds, t}
@@ -219,19 +351,21 @@ defmodule OptionParser do
[h|t] = t
{h, kinds, t}
kinds == [] ->
{true, kinds, t}
{nil_or_true, kinds, t}
true ->
{true, [:invalid], t}
{nil, [:invalid], t}
end
end
defp normalize_value(value, kinds, t) do
defp normalize_value(value, kinds, t, _) do
{value, kinds, t}
end
defp value_in_tail?(["-" <> _|_]), do: false
defp value_in_tail?([]), do: false
defp value_in_tail?(_), do: true
defp value_in_tail?(["-"|_]), do: true
defp value_in_tail?(["- " <> _|_]), do: true
defp value_in_tail?(["-" <> _|_]), do: false
defp value_in_tail?([]), do: false
defp value_in_tail?(_), do: true
defp split_option(option) do
case :binary.split(option, "=") do
@@ -245,16 +379,24 @@ defmodule OptionParser do
end
defp get_option(option) do
option |> to_underscore |> binary_to_atom
option |> to_underscore |> String.to_atom
end
defp get_negated("no-" <> rest = option, switches) do
defp reverse_negated(negated) do
String.to_atom("no_" <> Atom.to_string(negated))
end
defp get_negated("no-" <> rest = option, value, switches) do
negated = get_option(rest)
option = if Keyword.has_key?(switches, negated), do: negated, else: get_option(option)
option = if Keyword.has_key?(switches, negated) and value == nil do
negated
else
get_option(option)
end
{:negated, option}
end
defp get_negated(rest, _switches) do
defp get_negated(rest, _value, _switches) do
{:default, get_option(rest)}
end
end
+33 -20
View File
@@ -13,7 +13,7 @@ defmodule Path do
"""
alias :filename, as: FN
@type t :: char_data
@type t :: :unicode.chardata()
@doc """
Converts the given path to an absolute one. Unlike
@@ -58,7 +58,7 @@ defmodule Path do
"""
@spec absname(t, t) :: binary
def absname(path, relative_to) do
path = String.from_char_data!(path)
path = IO.chardata_to_string(path)
case type(path) do
:relative -> join(relative_to, path)
:absolute ->
@@ -68,7 +68,7 @@ defmodule Path do
path
end
:volumerelative ->
relative_to = String.from_char_data!(relative_to)
relative_to = IO.chardata_to_string(relative_to)
absname_vr(split(path), split(relative_to), relative_to)
end
end
@@ -85,7 +85,7 @@ defmodule Path do
defp absname_vr([<<x, ?:>>|name], _, _relative) do
cwd =
case :file.get_cwd([x, ?:]) do
{:ok, dir} -> String.from_char_data!(dir)
{:ok, dir} -> IO.chardata_to_string(dir)
{:error, _} -> <<x, ?:, ?/>>
end
absname(join(name), cwd)
@@ -181,7 +181,7 @@ defmodule Path do
case :os.type() do
{:win32, _} -> win32_pathtype(name)
_ -> unix_pathtype(name)
end |> elem(1) |> String.from_char_data!
end |> elem(1) |> IO.chardata_to_string
end
defp unix_pathtype(<<?/, relative :: binary>>), do:
@@ -245,7 +245,7 @@ defmodule Path do
"""
@spec relative_to(t, t) :: binary
def relative_to(path, from) do
path = String.from_char_data!(path)
path = IO.chardata_to_string(path)
relative_to(split(path), split(from), path)
end
@@ -269,7 +269,7 @@ defmodule Path do
@spec relative_to_cwd(t) :: binary
def relative_to_cwd(path) do
case :file.get_cwd do
{:ok, base} -> relative_to(path, String.from_char_data!(base))
{:ok, base} -> relative_to(path, IO.chardata_to_string(base))
_ -> path
end
end
@@ -292,7 +292,7 @@ defmodule Path do
"""
@spec basename(t) :: binary
def basename(path) do
FN.basename(String.from_char_data!(path))
FN.basename(IO.chardata_to_string(path))
end
@doc """
@@ -314,7 +314,7 @@ defmodule Path do
"""
@spec basename(t, t) :: binary
def basename(path, extension) do
FN.basename(String.from_char_data!(path), String.from_char_data!(extension))
FN.basename(IO.chardata_to_string(path), IO.chardata_to_string(extension))
end
@doc """
@@ -330,7 +330,7 @@ defmodule Path do
"""
@spec dirname(t) :: binary
def dirname(path) do
FN.dirname(String.from_char_data!(path))
FN.dirname(IO.chardata_to_string(path))
end
@doc """
@@ -347,7 +347,7 @@ defmodule Path do
"""
@spec extname(t) :: binary
def extname(path) do
FN.extension(String.from_char_data!(path))
FN.extension(IO.chardata_to_string(path))
end
@doc """
@@ -364,7 +364,7 @@ defmodule Path do
"""
@spec rootname(t) :: binary
def rootname(path) do
FN.rootname(String.from_char_data!(path))
FN.rootname(IO.chardata_to_string(path))
end
@doc """
@@ -382,7 +382,7 @@ defmodule Path do
"""
@spec rootname(t, t) :: binary
def rootname(path, extension) do
FN.rootname(String.from_char_data!(path), String.from_char_data!(extension))
FN.rootname(IO.chardata_to_string(path), IO.chardata_to_string(extension))
end
@doc """
@@ -407,7 +407,7 @@ defmodule Path do
def join([name1, name2|rest]), do:
join([join(name1, name2)|rest])
def join([name]), do:
do_join(String.from_char_data!(name), <<>>, [], major_os_type())
do_join(IO.chardata_to_string(name), <<>>, [], major_os_type())
@doc """
Joins two paths.
@@ -420,7 +420,7 @@ defmodule Path do
"""
@spec join(t, t) :: binary
def join(left, right),
do: do_join(String.from_char_data!(left), relative(right), [], major_os_type())
do: do_join(IO.chardata_to_string(left), relative(right), [], major_os_type())
defp major_os_type do
:os.type |> elem(0)
@@ -435,7 +435,7 @@ defmodule Path do
defp do_join(<<?/, rest :: binary>>, relativename, [?/|result], os_type), do:
do_join(rest, relativename, [?/|result], os_type)
defp do_join(<<>>, <<>>, result, os_type), do:
iodata_to_binary(maybe_remove_dirsep(result, os_type))
IO.iodata_to_binary(maybe_remove_dirsep(result, os_type))
defp do_join(<<>>, relativename, [?:|rest], :win32), do:
do_join(relativename, <<>>, [?:|rest], :win32)
defp do_join(<<>>, relativename, [?/|result], os_type), do:
@@ -476,7 +476,7 @@ defmodule Path do
def split(""), do: []
def split(path) do
FN.split(String.from_char_data!(path))
FN.split(IO.chardata_to_string(path))
end
@doc """
@@ -515,15 +515,28 @@ defmodule Path do
@spec wildcard(t) :: [binary]
def wildcard(glob) do
glob
|> List.from_char_data!
|> chardata_to_list
|> :filelib.wildcard
|> Enum.map(&String.from_char_data!/1)
|> Enum.map(&IO.chardata_to_string/1)
end
# Normalize the given path by expanding "..", "." and "~".
defp chardata_to_list(chardata) do
case :unicode.characters_to_list(chardata) do
result when is_list(result) ->
result
{:error, encoded, rest} ->
raise UnicodeConversionError, encoded: encoded, rest: rest, kind: :invalid
{:incomplete, encoded, rest} ->
raise UnicodeConversionError, encoded: encoded, rest: rest, kind: :incomplete
end
end
defp expand_home(type) do
case String.from_char_data!(type) do
case IO.chardata_to_string(type) do
"~" <> rest -> System.user_home! <> rest
rest -> rest
end
+10 -21
View File
@@ -2,10 +2,16 @@ defmodule Process do
@moduledoc """
Conveniences for working with processes and the process dictionary.
Some of the functions in this module are inlined by the compiler,
similar to functions in the `Kernel` module and they are explicitly
marked in their docs as "inlined by the compiler". For more information
about inlined functions, check out the `Kernel` module.
Besides the functions available in this module, the `Kernel` module
exposes and auto-imports some basic functionality related to processes
available through the functions:
* `Kernel.spawn/1` and `Kernel.spawn/3`
* `Kernel.spawn_link/1` and `Kernel.spawn_link/3`
* `Kernel.spawn_monitor/1` and `Kernel.spawn_monitor/3`
* `Kernel.self/0`
* `Kernel.send/2`
"""
@doc """
@@ -57,15 +63,6 @@ defmodule Process do
nillify :erlang.put(key, value)
end
@doc """
Deletes all items in the dictionary.
"""
@spec delete :: [{term, term}]
def delete() do
IO.write :stderr, "Process.delete/0 is deprecated\n#{Exception.format_stacktrace}"
:erlang.erase()
end
@doc """
Deletes the given key from the dictionary.
"""
@@ -199,49 +196,41 @@ defmodule Process do
@doc false
def spawn(fun) do
# IO.write :stderr, "Process.spawn/1 is deprecated, use the one in Kernel instead\n#{Exception.format_stacktrace}"
:erlang.spawn(fun)
end
@doc false
def spawn(mod, fun, args) do
# IO.write :stderr, "Process.spawn/3 is deprecated, use the one in Kernel instead\n#{Exception.format_stacktrace}"
:erlang.spawn(mod, fun, args)
end
@doc false
def spawn_link(fun) do
# IO.write :stderr, "Process.spawn_link/1 is deprecated, use the one in Kernel instead\n#{Exception.format_stacktrace}"
:erlang.spawn_link(fun)
end
@doc false
def spawn_link(mod, fun, args) do
# IO.write :stderr, "Process.spawn_link/3 is deprecated, use the one in Kernel instead\n#{Exception.format_stacktrace}"
:erlang.spawn_link(mod, fun, args)
end
@doc false
def spawn_monitor(fun) do
# IO.write :stderr, "Process.spawn_monitor/1 is deprecated, use the one in Kernel instead\n#{Exception.format_stacktrace}"
:erlang.spawn_monitor(fun)
end
@doc false
def spawn_monitor(mod, fun, args) do
# IO.write :stderr, "Process.spawn_monitor/3 is deprecated, use the one in Kernel instead\n#{Exception.format_stacktrace}"
:erlang.spawn_monitor(mod, fun, args)
end
@doc false
def self() do
# IO.write :stderr, "Process.self/0 is deprecated, use the one in Kernel instead\n#{Exception.format_stacktrace}"
:erlang.self()
end
@doc false
def send(dest, msg) do
# IO.write :stderr, "Process.send/2 is deprecated, use the one in Kernel instead\n#{Exception.format_stacktrace}"
:erlang.send(dest, msg)
end
+8 -7
View File
@@ -35,7 +35,8 @@ defmodule Protocol do
quote unquote: false do
# == Deprecated records handling ==
{arg, impl} = Protocol.rec_impl_for(__MODULE__)
Kernel.def impl_for(unquote(arg)) when Kernel.is_record(unquote(arg)), do: unquote(impl)
Kernel.def impl_for(unquote(arg)) when
is_tuple(unquote(arg)) and is_atom(elem(unquote(arg), 0)), do: unquote(impl)
# == Deprecated records handling ==
@spec impl_for(term) :: module | nil
@@ -154,14 +155,14 @@ defmodule Protocol do
def assert_protocol(module) do
case Code.ensure_compiled(module) do
{:module, ^module} -> nil
_ -> raise ArgumentError, message: "#{inspect module} is not loaded"
_ -> raise ArgumentError, "#{inspect module} is not loaded"
end
try do
module.__protocol__(:name)
rescue
UndefinedFunctionError ->
raise ArgumentError, message: "#{inspect module} is not a protocol"
raise ArgumentError, "#{inspect module} is not a protocol"
end
end
@@ -199,7 +200,7 @@ defmodule Protocol do
impl_for = quote do
atom = :erlang.element(1, unquote(arg))
case not(atom in unquote(all)) and match?('Elixir.' ++ _, atom_to_list(atom)) do
case not(atom in unquote(all)) and match?('Elixir.' ++ _, Atom.to_char_list(atom)) do
true ->
target = Module.concat(unquote(current), atom)
case impl_for?(target) do
@@ -220,7 +221,7 @@ defmodule Protocol.DSL do
@doc false
defmacro def({_, _, args}) when args == [] or is_atom(args) do
raise ArgumentError, message: "protocol functions expect at least one argument"
raise ArgumentError, "protocol functions expect at least one argument"
end
defmacro def({name, _, args}) when is_atom(name) and is_list(args) do
@@ -230,7 +231,7 @@ defmodule Protocol.DSL do
type_args = [quote(do: t) | type_args]
call_args = for i <- :lists.seq(2, arity),
do: {binary_to_atom(<<?x, i + 64>>), [], __MODULE__}
do: {String.to_atom(<<?x, i + 64>>), [], __MODULE__}
call_args = [quote(do: t) | call_args]
quote do
@@ -256,7 +257,7 @@ defmodule Protocol.DSL do
end
defmacro def(_) do
raise ArgumentError, message: "invalid args for def inside defprotocol"
raise ArgumentError, "invalid args for def inside defprotocol"
end
@doc false
+2 -2
View File
@@ -51,7 +51,7 @@ defmodule Protocol.Consolidation do
"""
@spec extract_impls(module, [char_list | String.t]) :: [atom]
def extract_impls(protocol, paths) when is_atom(protocol) do
prefix = atom_to_list(protocol) ++ '.'
prefix = Atom.to_char_list(protocol) ++ '.'
extract_matching_by_attribute paths, prefix, fn
_mod, attributes ->
case attributes[:impl] do
@@ -125,7 +125,7 @@ defmodule Protocol.Consolidation do
{:error, :not_a_protocol} |
{:error, :no_beam_info}
def apply_to(protocol, types) when is_atom(protocol) do
raise ArgumentError, message: "consolidation is disabled as we can't consolidate records " <>
raise ArgumentError, "consolidation is disabled as we can't consolidate records " <>
"and structs at once. Consolidation will be added back once " <>
"polymorphic records are removed"
ensure_protocol(protocol)
+119 -11
View File
@@ -12,11 +12,9 @@ defmodule Record do
manipulate the tuples, providing fast operations on top of
the tuples compact structure.
In Elixir, records are used in two situations:
1. To work with short, internal data. See `Inspect.Algebra`
implementation for a good example;
In Elixir, records are used mostly in two situations:
1. To work with short, internal data;
2. To interface with Erlang records;
The macros `defrecord/3` and `defrecordp/3` can be used to create
@@ -151,20 +149,21 @@ defmodule Record do
Record.defrecord :user, User, name: nil
end
user() #=> {User, nil}
require User
User.user() #=> {User, nil}
"""
defmacro defrecord(name, tag \\ nil, kv) do
quote bind_quoted: [name: name, tag: tag, kv: kv] do
tag = tag || name
fields = Macro.escape Record.Backend.default_fields(:record, kv)
fields = Macro.escape Record.__fields__(:defrecord, kv)
defmacro(unquote(name)(args \\ [])) do
Record.Backend.access(unquote(tag), unquote(fields), args, __CALLER__)
Record.__access__(unquote(tag), unquote(fields), args, __CALLER__)
end
defmacro(unquote(name)(record, args)) do
Record.Backend.access(unquote(tag), unquote(fields), record, args, __CALLER__)
Record.__access__(unquote(tag), unquote(fields), record, args, __CALLER__)
end
end
end
@@ -175,15 +174,124 @@ defmodule Record do
defmacro defrecordp(name, tag \\ nil, kv) do
quote bind_quoted: [name: name, tag: tag, kv: kv] do
tag = tag || name
fields = Macro.escape Record.Backend.default_fields(:record, kv)
fields = Macro.escape Record.__fields__(:defrecordp, kv)
defmacrop(unquote(name)(args \\ [])) do
Record.Backend.access(unquote(tag), unquote(fields), args, __CALLER__)
Record.__access__(unquote(tag), unquote(fields), args, __CALLER__)
end
defmacrop(unquote(name)(record, args)) do
Record.Backend.access(unquote(tag), unquote(fields), record, args, __CALLER__)
Record.__access__(unquote(tag), unquote(fields), record, args, __CALLER__)
end
end
end
# Normalizes of record fields to have default values.
@doc false
def __fields__(type, fields) do
:lists.map(fn
{ key, _ } = pair when is_atom(key) -> pair
key when is_atom(key) -> { key, nil }
other -> raise ArgumentError, "#{type} fields must be atoms, got: #{inspect other}"
end, fields)
end
# Callback invoked from record/0 and record/1 macros.
@doc false
def __access__(atom, fields, args, caller) do
cond do
is_atom(args) ->
index(atom, fields, args)
Keyword.keyword?(args) ->
create(atom, fields, args, caller)
true ->
msg = "expected arguments to be a compile time atom or keywords, got: #{Macro.to_string args}"
raise ArgumentError, msg
end
end
# Callback invoked from the record/2 macro.
@doc false
def __access__(atom, fields, record, args, caller) do
cond do
is_atom(args) ->
get(atom, fields, record, args)
Keyword.keyword?(args) ->
update(atom, fields, record, args, caller)
true ->
msg = "expected arguments to be a compile time atom or keywords, got: #{Macro.to_string args}"
raise ArgumentError, msg
end
end
# Gets the index of field.
defp index(atom, fields, field) do
if index = find_index(fields, field, 0) do
index - 1 # Convert to Elixir index
else
raise ArgumentError, "record #{inspect atom} does not have the key: #{inspect field}"
end
end
# Creates a new record with the given default fields and keyword values.
defp create(atom, fields, keyword, caller) do
in_match = Macro.Env.in_match?(caller)
{match, remaining} =
Enum.map_reduce(fields, keyword, fn({field, default}, each_keyword) ->
new_fields =
case Keyword.has_key?(each_keyword, field) do
true -> Keyword.get(each_keyword, field)
false ->
case in_match do
true -> {:_, [], nil}
false -> Macro.escape(default)
end
end
{new_fields, Keyword.delete(each_keyword, field)}
end)
case remaining do
[] ->
{:{}, [], [atom|match]}
_ ->
keys = for {key, _} <- remaining, do: key
raise ArgumentError, "record #{inspect atom} does not have the key: #{inspect hd(keys)}"
end
end
# Updates a record given by var with the given keyword.
defp update(atom, fields, var, keyword, caller) do
if Macro.Env.in_match?(caller) do
raise ArgumentError, "cannot invoke update style macro inside match"
end
Enum.reduce keyword, var, fn({key, value}, acc) ->
index = find_index(fields, key, 0)
if index do
quote do
:erlang.setelement(unquote(index), unquote(acc), unquote(value))
end
else
raise ArgumentError, "record #{inspect atom} does not have the key: #{inspect key}"
end
end
end
# Gets a record key from the given var.
defp get(atom, fields, var, key) do
index = find_index(fields, key, 0)
if index do
quote do
:erlang.element(unquote(index), unquote(var))
end
else
raise ArgumentError, "record #{inspect atom} does not have the key: #{inspect key}"
end
end
defp find_index([{k, _}|_], k, i), do: i + 2
defp find_index([{_, _}|t], k, i), do: find_index(t, k, i + 1)
defp find_index([], _k, _i), do: nil
end
-153
View File
@@ -1,153 +0,0 @@
defmodule Record.Backend do
# Callback functions invoked by defrecord, defrecordp and friends.
@moduledoc false
@doc """
Normalizes a list of record or struct fields to have default values.
"""
def default_fields(type, fields) do
Enum.map(fields, fn
{ key, _ } = pair when is_atom(key) -> pair
key when is_atom(key) -> { key, nil }
other -> raise ArgumentError, message: "#{type} fields must be atoms, got: #{inspect other}"
end)
end
@doc """
Splits a keywords list into fields and types.
This logic is shared by records and structs.
"""
def split_fields_and_types(kv) do
if Keyword.keyword?(kv) do
split_fields_and_types(kv, [], [])
else
{kv, []}
end
end
defp split_fields_and_types([{field, {:::, _, [default, type]}}|t], fields, types) do
split_fields_and_types(t, [{field, default}|fields], [{field, type}|types])
end
defp split_fields_and_types([{field, default}|t], fields, types) do
split_fields_and_types(t, [{field, default}|fields], [{field, quote(do: term)}|types])
end
defp split_fields_and_types([field|t], fields, types) do
split_fields_and_types(t, [field|fields], [{field, quote(do: term)}|types])
end
defp split_fields_and_types([], fields, types) do
{:lists.reverse(fields), :lists.reverse(types)}
end
@doc """
Callback invoked from record/0 and record/1 macros.
"""
def access(atom, fields, args, caller) do
cond do
is_atom(args) ->
index(atom, fields, args)
Keyword.keyword?(args) ->
create(atom, fields, args, caller)
true ->
raise ArgumentError,
message: "expected arguments to be a compile time atom or keywords, got: #{Macro.to_string args}"
end
end
@doc """
Callback invoked from the record/2 macro.
"""
def access(atom, fields, record, args, caller) do
cond do
is_atom(args) ->
get(atom, fields, record, args)
Keyword.keyword?(args) ->
update(atom, fields, record, args, caller)
true ->
raise ArgumentError,
message: "expected arguments to be a compile time atom or keywords, got: #{Macro.to_string args}"
end
end
@doc """
Gets the index of field.
"""
def index(atom, fields, field) do
if index = find_index(fields, field, 0) do
index - 1 # Convert to Elixir index
else
raise ArgumentError, message: "record #{inspect atom} does not have the key: #{inspect field}"
end
end
@doc """
Creates a new record with the given default fields and keyword values.
"""
def create(atom, fields, keyword, caller) do
in_match = Macro.Env.in_match?(caller)
{match, remaining} =
Enum.map_reduce(fields, keyword, fn({field, default}, each_keyword) ->
new_fields =
case Keyword.has_key?(each_keyword, field) do
true -> Keyword.get(each_keyword, field)
false ->
case in_match do
true -> {:_, [], nil}
false -> Macro.escape(default)
end
end
{new_fields, Keyword.delete(each_keyword, field)}
end)
case remaining do
[] ->
{:{}, [], [atom|match]}
_ ->
keys = for {key, _} <- remaining, do: key
raise ArgumentError, message: "record #{inspect atom} does not have the key: #{inspect hd(keys)}"
end
end
@doc """
Updates a record given by var with the given keyword.
"""
def update(atom, fields, var, keyword, caller) do
if Macro.Env.in_match?(caller) do
raise ArgumentError, message: "cannot invoke update style macro inside match"
end
Enum.reduce keyword, var, fn({key, value}, acc) ->
index = find_index(fields, key, 0)
if index do
quote do
:erlang.setelement(unquote(index), unquote(acc), unquote(value))
end
else
raise ArgumentError, message: "record #{inspect atom} does not have the key: #{inspect key}"
end
end
end
@doc """
Gets a record key from the given var.
"""
def get(atom, fields, var, key) do
index = find_index(fields, key, 0)
if index do
quote do
:erlang.element(unquote(index), unquote(var))
end
else
raise ArgumentError, message: "record #{inspect atom} does not have the key: #{inspect key}"
end
end
defp find_index([{k, _}|_], k, i), do: i + 2
defp find_index([{_, _}|t], k, i), do: find_index(t, k, i + 1)
defp find_index([], _k, _i), do: nil
end
+29 -16
View File
@@ -1,6 +1,16 @@
defmodule Record.Deprecated do
@moduledoc false
def defexception(name, fields, opts) do
quote do
fields = unquote(fields)
defmodule unquote(name) do
defexception fields
unquote(Keyword.get opts, :do)
end
end
end
def defrecord(name, fields, opts) do
block = Keyword.get(opts, :do, nil)
record_check!(fields)
@@ -23,7 +33,7 @@ defmodule Record.Deprecated do
end
defp record_check!([{field, {:::, _, [_, _]}}|_]) when is_atom(field) do
raise ArgumentError, message: "typespecs are not supported inlined with defrecord, " <>
raise ArgumentError, "typespecs are not supported inlined with defrecord, " <>
"please use record_type instead"
end
@@ -40,7 +50,7 @@ defmodule Record.Deprecated do
Record.Deprecated.defmacros(name, fields, __ENV__, tag)
if def_type do
type = binary_to_atom(atom_to_binary(name) <> "_t")
type = String.to_atom(Atom.to_string(name) <> "_t")
@typep unquote(type)() :: {unquote(tag || name), unquote_splicing(types)}
end
end
@@ -156,6 +166,9 @@ defmodule Record.Deprecated do
defmacro __before_compile__(_) do
quote do
@spec __record__(atom) :: term
@spec __record__(atom, term) :: term
@spec __record__(atom, term, term) :: term
def __record__(:optimizable), do: @record_optimizable
end
end
@@ -179,13 +192,13 @@ defmodule Record.Deprecated do
if index = find_index(fields, arg, 0) do
index + 1
else
raise ArgumentError, message: "record #{inspect atom} does not have the key: #{inspect arg}"
raise ArgumentError, "record #{inspect atom} does not have the key: #{inspect arg}"
end
end
def access(atom, fields, keyword, caller) do
unless is_keyword(keyword) do
raise ArgumentError, message: "expected contents inside brackets to be a keyword list or an atom, got: #{inspect keyword}"
raise ArgumentError, "expected contents inside brackets to be a keyword list or an atom, got: #{inspect keyword}"
end
in_match = Macro.Env.in_match?(caller)
@@ -215,7 +228,7 @@ defmodule Record.Deprecated do
{:{}, [], [atom|match]}
_ ->
keys = for {key, _} <- remaining, do: key
raise ArgumentError, message: "record #{inspect atom} does not have the key: #{inspect hd(keys)}"
raise ArgumentError, "record #{inspect atom} does not have the key: #{inspect hd(keys)}"
end
end
@@ -226,17 +239,17 @@ defmodule Record.Deprecated do
is_keyword(args) ->
update(atom, fields, record, args, caller)
true ->
raise ArgumentError, message: "expected arguments to be a compile time atom or keywords"
raise ArgumentError, "expected arguments to be a compile time atom or keywords"
end
end
defp update(atom, fields, var, keyword, caller) do
unless is_keyword(keyword) do
raise ArgumentError, message: "expected arguments to be compile time keywords"
raise ArgumentError, "expected arguments to be compile time keywords"
end
if Macro.Env.in_match?(caller) do
raise ArgumentError, message: "cannot invoke update style macro inside match context"
raise ArgumentError, "cannot invoke update style macro inside match context"
end
Enum.reduce keyword, var, fn({key, value}, acc) ->
@@ -246,7 +259,7 @@ defmodule Record.Deprecated do
:erlang.setelement(unquote(index + 2), unquote(acc), unquote(value))
end
else
raise ArgumentError, message: "record #{inspect atom} does not have the key: #{inspect key}"
raise ArgumentError, "record #{inspect atom} does not have the key: #{inspect key}"
end
end
end
@@ -258,7 +271,7 @@ defmodule Record.Deprecated do
:erlang.element(unquote(index + 2), unquote(var))
end
else
raise ArgumentError, message: "record #{inspect atom} does not have the key: #{inspect key}"
raise ArgumentError, "record #{inspect atom} does not have the key: #{inspect key}"
end
end
@@ -297,7 +310,7 @@ defmodule Record.Deprecated do
# the given key from the ordered dict, falling back to the
# default value if one does not exist.
atom_selective = for {k, v} <- values, do: initialize_lookup(k, v)
string_selective = for {k, v} <- values, do: initialize_lookup(atom_to_binary(k), v)
string_selective = for {k, v} <- values, do: initialize_lookup(Atom.to_string(k), v)
quote do
@doc false
@@ -338,7 +351,7 @@ defmodule Record.Deprecated do
end
defp accessors([{key, _default}|t], i) do
update = binary_to_atom "update_" <> atom_to_binary(key)
update = String.to_atom "update_" <> Atom.to_string(key)
contents = quote do
@doc false
@@ -372,7 +385,7 @@ defmodule Record.Deprecated do
for {key, _default} <- values, do: updater_lookup(key, key, values)
string_fields =
for {key, _default} <- values, do: updater_lookup(atom_to_binary(key), key, values)
for {key, _default} <- values, do: updater_lookup(Atom.to_string(key), key, values)
atom_contents = quote do: {__MODULE__, unquote_splicing(atom_fields)}
string_contents = quote do: {__MODULE__, unquote_splicing(string_fields)}
@@ -446,7 +459,7 @@ defmodule Record.Deprecated do
end
defp accessor_specs([{key, _default, spec}|t], i, acc) do
update = binary_to_atom "update_" <> atom_to_binary(key)
update = String.to_atom "update_" <> Atom.to_string(key)
contents = quote do
@spec unquote(key)(t) :: unquote(spec)
@@ -481,14 +494,14 @@ defmodule Record.Deprecated do
end
defp check_value(atom, other) when is_tuple(other) do
for(i <- tuple_to_list(other), do: check_value(atom, i))
for(i <- Tuple.to_list(other), do: check_value(atom, i))
other
end
defp check_value(atom, other) when is_function(other) do
unless :erlang.fun_info(other, :env) == {:env, []} and
:erlang.fun_info(other, :type) == {:type, :external} do
raise ArgumentError, message: "record field default value #{inspect atom} can only contain " <>
raise ArgumentError, "record field default value #{inspect atom} can only contain " <>
"functions that point to an existing &Mod.fun/arity"
end
other
+5 -5
View File
@@ -4,7 +4,7 @@ defmodule Record.Extractor do
# Retrieve a record definition from an Erlang file using
# the same lookup as the *include* attribute from Erlang modules.
def extract(name, from: file) when is_binary(file) do
file = List.from_char_data!(file)
file = String.to_char_list(file)
realfile =
case :code.where_is_file(file) do
@@ -18,11 +18,11 @@ defmodule Record.Extractor do
# Retrieve a record definition from an Erlang file using
# the same lookup as the *include_lib* attribute from Erlang modules.
def extract(name, from_lib: file) when is_binary(file) do
[app|path] = :filename.split(List.from_char_data!(file))
[app|path] = :filename.split(String.to_char_list(file))
case :code.lib_dir(list_to_atom(app)) do
case :code.lib_dir(List.to_atom(app)) do
{:error, _} ->
raise ArgumentError, message: "lib file #{file} could not be found"
raise ArgumentError, "lib file #{file} could not be found"
libpath ->
extract_record name, :filename.join([libpath|path])
end
@@ -35,7 +35,7 @@ defmodule Record.Extractor do
if record = List.keyfind(records, name, 0) do
parse_record(record, form)
else
raise ArgumentError, message: "no record #{name} found at #{file}"
raise ArgumentError, "no record #{name} found at #{file}"
end
end
+12 -16
View File
@@ -70,7 +70,9 @@ defmodule Regex do
defstruct re_pattern: nil :: term, source: "" :: binary, opts: "" :: binary
defexception CompileError, message: "regex could not be compiled"
defmodule CompileError do
defexception message: "regex could not be compiled"
end
@doc """
Compiles the regular expression.
@@ -338,17 +340,8 @@ defmodule Regex do
def split(regex, string, options \\ [])
def split(%Regex{re_pattern: compiled}, string, options) when is_binary(string) do
if options[:global] != nil do
IO.write :stderr, "Support for :global in Regex.split/3 is deprecated, please use :parts instead\n#{Exception.format_stacktrace}"
end
parts =
cond do
Keyword.get(options, :global) == false -> 2
p = Keyword.get(options, :parts) -> if p == 0, do: :infinity, else: p
true -> :infinity
end
opts = [return: :binary, parts: parts]
parts = Keyword.get(options, :parts, :infinity)
opts = [return: :binary, parts: zero_to_infinity(parts)]
splits = :re.split(string, compiled, opts)
if Keyword.get(options, :trim, false) do
@@ -358,6 +351,9 @@ defmodule Regex do
end
end
defp zero_to_infinity(0), do: :infinity
defp zero_to_infinity(n), do: n
@doc ~S"""
Receives a regex, a binary and a replacement, returns a new
binary where the all matches are replaced by replacement.
@@ -414,9 +410,9 @@ defmodule Regex do
:nomatch ->
string
{:match, [mlist|t]} when is_list(mlist) ->
apply_list(string, replacement, [mlist|t]) |> iodata_to_binary
apply_list(string, replacement, [mlist|t]) |> IO.iodata_to_binary
{:match, slist} ->
apply_list(string, replacement, [slist]) |> iodata_to_binary
apply_list(string, replacement, [slist]) |> IO.iodata_to_binary
end
end
@@ -434,7 +430,7 @@ defmodule Regex do
defp precompile_replacement(<<?\\, rest :: binary>>) when byte_size(rest) > 0 do
{ns, rest} = pick_int(rest)
[list_to_integer(ns) | precompile_replacement(rest)]
[List.to_integer(ns) | precompile_replacement(rest)]
end
defp precompile_replacement(<<x, rest :: binary>>) do
@@ -488,7 +484,7 @@ defmodule Regex do
end
defp apply_replace(string, repl, indexes) do
indexes = list_to_tuple(indexes)
indexes = List.to_tuple(indexes)
for part <- repl do
cond do
+1 -4
View File
@@ -50,9 +50,6 @@ defmodule Set do
case unquote(set) do
%{__struct__: x} when is_atom(x) ->
x
x when is_tuple(x) ->
IO.write :stderr, "Using records with the Set module is deprecated, please use structs instead\n#{Exception.format_stacktrace}"
elem(x, 0)
x ->
unsupported_set(x)
end
@@ -334,6 +331,6 @@ defmodule Set do
end
defp unsupported_set(set) do
raise ArgumentError, message: "unsupported set: #{inspect set}"
raise ArgumentError, "unsupported set: #{inspect set}"
end
end
+81 -69
View File
@@ -89,55 +89,7 @@ defmodule Stream do
returns an anonymous function may return a struct in future releases.
"""
defmodule Lazy do
@moduledoc false
defstruct enum: nil, funs: [], accs: [], done: nil
end
defimpl Enumerable, for: Lazy do
@compile :inline_list_funs
def reduce(lazy, acc, fun) do
do_reduce(lazy, acc, fn x, [acc] ->
{reason, acc} = fun.(x, acc)
{reason, [acc]}
end)
end
def count(_lazy) do
{:error, __MODULE__}
end
def member?(_lazy, _value) do
{:error, __MODULE__}
end
defp do_reduce(%Lazy{enum: enum, funs: funs, accs: accs, done: done}, acc, fun) do
composed = :lists.foldl(fn fun, acc -> fun.(acc) end, fun, funs)
do_each(&Enumerable.reduce(enum, &1, composed),
done && {done, fun}, :lists.reverse(accs), acc)
end
defp do_each(reduce, done, accs, {command, acc}) do
case reduce.({command, [acc|accs]}) do
{:suspended, [acc|accs], continuation} ->
{:suspended, acc, &do_each(continuation, done, accs, &1)}
{:halted, [acc|_]} ->
{:halted, acc}
{:done, [acc|_] = accs} ->
case done do
nil ->
{:done, acc}
{done, fun} ->
case done.(fun).(accs) do
{:cont, [acc|_]} -> {:done, acc}
{:halt, [acc|_]} -> {:halted, acc}
{:suspend, [acc|_]} -> {:suspended, acc, &({:done, elem(&1, 1)})}
end
end
end
end
end
defstruct enum: nil, funs: [], accs: [], done: nil
@type acc :: any
@type element :: any
@@ -538,7 +490,7 @@ defmodule Stream do
"""
@spec take(Enumerable.t, non_neg_integer) :: Enumerable.t
def take(_enum, 0), do: %Lazy{enum: []}
def take(_enum, 0), do: %Stream{enum: []}
def take(enum, n) when n > 0 do
lazy enum, n, fn(f1) -> R.take(f1) end
@@ -590,7 +542,7 @@ defmodule Stream do
lazy enum, n, fn(f1) -> R.take_every(n, f1) end
end
def take_every(_enum, 0), do: %Lazy{enum: []}
def take_every(_enum, 0), do: %Stream{enum: []}
@doc """
Lazily takes elements of the enumerable while the given
@@ -656,8 +608,9 @@ defmodule Stream do
user.(val, user_acc)
catch
kind, reason ->
stacktrace = System.stacktrace
next.({:halt, next_acc})
:erlang.raise(kind, reason, :erlang.get_stacktrace)
:erlang.raise(kind, reason, stacktrace)
else
{[], user_acc} ->
do_transform(user_acc, user, fun, next_acc, next, inner_acc, inner)
@@ -700,8 +653,9 @@ defmodule Stream do
next.({:halt, next_acc})
{:halted, h}
kind, reason ->
stacktrace = System.stacktrace
next.({:halt, next_acc})
:erlang.raise(kind, reason, :erlang.get_stacktrace)
:erlang.raise(kind, reason, stacktrace)
else
{_, acc} ->
do_transform(user_acc, user, fun, next_acc, next, {:cont, acc}, inner)
@@ -835,8 +789,9 @@ defmodule Stream do
do_zip(zips, acc, callback, [], [])
catch
kind, reason ->
stacktrace = System.stacktrace
do_zip_close(zips)
:erlang.raise(kind, reason, :erlang.get_stacktrace)
:erlang.raise(kind, reason, stacktrace)
else
{:next, buffer, acc} ->
do_zip(buffer, acc, callback)
@@ -856,7 +811,7 @@ defmodule Stream do
end
defp do_zip([], acc, callback, list, buffer) do
zipped = list_to_tuple(:lists.reverse(list))
zipped = List.to_tuple(:lists.reverse(list))
{:next, :lists.reverse(buffer), callback.(zipped, acc)}
end
@@ -886,8 +841,9 @@ defmodule Stream do
@spec cycle(Enumerable.t) :: Enumerable.t
def cycle(enumerable) do
fn acc, fun ->
reduce = &Enumerable.reduce(enumerable, &1, fun)
do_cycle(reduce, reduce, acc)
inner = &do_cycle_each(&1, &2, fun)
outer = &Enumerable.reduce(enumerable, &1, inner)
do_cycle(outer, outer, acc)
end
end
@@ -900,16 +856,26 @@ defmodule Stream do
end
defp do_cycle(reduce, cycle, acc) do
case reduce.(acc) do
{:done, acc} ->
do_cycle(cycle, cycle, {:cont, acc})
{:halted, acc} ->
try do
reduce.(acc)
catch
{:stream_cycle, acc} ->
{:halted, acc}
else
{state, acc} when state in [:done, :halted] ->
do_cycle(cycle, cycle, {:cont, acc})
{:suspended, acc, continuation} ->
{:suspended, acc, &do_cycle(continuation, cycle, &1)}
end
end
defp do_cycle_each(x, acc, f) do
case f.(x, acc) do
{:halt, h} -> throw({:stream_cycle, h})
{_, _} = o -> o
end
end
@doc """
Emit a sequence of values, starting with `start_value`. Successive
values are generated by calling `next_fun` on the previous value.
@@ -1006,8 +972,9 @@ defmodule Stream do
end
catch
kind, reason ->
stacktrace = System.stacktrace
after_fun.(next_acc)
:erlang.raise(kind, reason, :erlang.get_stacktrace)
:erlang.raise(kind, reason, stacktrace)
else
nil ->
after_fun.(next_acc)
@@ -1045,7 +1012,7 @@ defmodule Stream do
defp do_unfold(next_acc, next_fun, {:cont, acc}, fun) do
case next_fun.(next_acc) do
nil -> {:done, acc}
nil -> {:done, acc}
{v, next_acc} -> do_unfold(next_acc, next_fun, fun.(v, acc), fun)
end
end
@@ -1054,18 +1021,63 @@ defmodule Stream do
@compile {:inline, lazy: 2, lazy: 3, lazy: 4}
defp lazy(%Lazy{funs: funs} = lazy, fun),
defp lazy(%Stream{funs: funs} = lazy, fun),
do: %{lazy | funs: [fun|funs] }
defp lazy(enum, fun),
do: %Lazy{enum: enum, funs: [fun]}
do: %Stream{enum: enum, funs: [fun]}
defp lazy(%Lazy{funs: funs, accs: accs} = lazy, acc, fun),
defp lazy(%Stream{funs: funs, accs: accs} = lazy, acc, fun),
do: %{lazy | funs: [fun|funs], accs: [acc|accs] }
defp lazy(enum, acc, fun),
do: %Lazy{enum: enum, funs: [fun], accs: [acc]}
do: %Stream{enum: enum, funs: [fun], accs: [acc]}
defp lazy(%Lazy{done: nil, funs: funs, accs: accs} = lazy, acc, fun, done),
defp lazy(%Stream{done: nil, funs: funs, accs: accs} = lazy, acc, fun, done),
do: %{lazy | funs: [fun|funs], accs: [acc|accs], done: done}
defp lazy(enum, acc, fun, done),
do: %Lazy{enum: enum, funs: [fun], accs: [acc], done: done}
do: %Stream{enum: enum, funs: [fun], accs: [acc], done: done}
end
defimpl Enumerable, for: Stream do
@compile :inline_list_funs
def reduce(lazy, acc, fun) do
do_reduce(lazy, acc, fn x, [acc] ->
{reason, acc} = fun.(x, acc)
{reason, [acc]}
end)
end
def count(_lazy) do
{:error, __MODULE__}
end
def member?(_lazy, _value) do
{:error, __MODULE__}
end
defp do_reduce(%Stream{enum: enum, funs: funs, accs: accs, done: done}, acc, fun) do
composed = :lists.foldl(fn fun, acc -> fun.(acc) end, fun, funs)
do_each(&Enumerable.reduce(enum, &1, composed),
done && {done, fun}, :lists.reverse(accs), acc)
end
defp do_each(reduce, done, accs, {command, acc}) do
case reduce.({command, [acc|accs]}) do
{:suspended, [acc|accs], continuation} ->
{:suspended, acc, &do_each(continuation, done, accs, &1)}
{:halted, [acc|_]} ->
{:halted, acc}
{:done, [acc|_] = accs} ->
case done do
nil ->
{:done, acc}
{done, fun} ->
case done.(fun).(accs) do
{:cont, [acc|_]} -> {:done, acc}
{:halt, [acc|_]} -> {:halted, acc}
{:suspend, [acc|_]} -> {:suspended, acc, &({:done, elem(&1, 1)})}
end
end
end
end
end
+127 -85
View File
@@ -18,12 +18,8 @@ defmodule String do
* `Kernel.binary_part/3` - retrieves part of the binary
* `Kernel.bit_size/1` and `Kernel.byte_size/1` - size related functions
* `Kernel.is_bitstring/1` and `Kernel.is_binary/1` - type checking function
* Plus a number of conversion functions, like `Kernel.binary_to_atom/1`,
`Kernel.binary_to_integer/2`, `Kernel.binary_to_term/1` and their inverses,
like `Kernel.integer_to_binary/2`
Finally, the [`:binary` module](http://erlang.org/doc/man/binary.html)
provides a few other functions that work on the byte level.
* Plus a number of functions for working with binaries (bytes)
[in the `:binary` module](http://erlang.org/doc/man/binary.html)
## Codepoints and graphemes
@@ -147,6 +143,7 @@ defmodule String do
def printable?(<<?\b, t :: binary>>), do: printable?(t)
def printable?(<<?\f, t :: binary>>), do: printable?(t)
def printable?(<<?\e, t :: binary>>), do: printable?(t)
def printable?(<<?\d, t :: binary>>), do: printable?(t)
def printable?(<<?\a, t :: binary>>), do: printable?(t)
def printable?(<<>>), do: true
@@ -172,8 +169,9 @@ defmodule String do
defdelegate split(binary), to: String.Unicode
@doc ~S"""
Divides a string into substrings based on a pattern,
returning a list of these substrings. The pattern can
Divides a string into substrings based on a pattern.
Returns a list of these substrings. The pattern can
be a string, a list of strings or a regular expression.
The string is split into as many parts as possible by
@@ -236,22 +234,16 @@ defmodule String do
def split(binary, "", options), do: split(binary, ~r""u, options)
def split(binary, pattern, options) do
if options[:global] != nil do
IO.write :stderr, "Support for :global in String.split/3 is deprecated, please use :parts instead\n#{Exception.format_stacktrace}"
end
if Regex.regex?(pattern) do
Regex.split(pattern, binary, options)
else
splits = case options[:parts] do
num when is_number(num) and num > 0 ->
split_parts(binary, pattern, num - 1)
num ->
if options[:global] != false or num == 0 do
splits =
case Keyword.get(options, :parts, :infinity) do
num when is_number(num) and num > 0 ->
split_parts(binary, pattern, num - 1)
_ ->
:binary.split(binary, pattern, [:global])
else
:binary.split(binary, pattern, [])
end
end
end
if Keyword.get(options, :trim, false) do
for split <- splits, split != "", do: split
@@ -266,10 +258,9 @@ defmodule String do
defp split_parts("", _pattern, _num, parts), do: Enum.reverse([""|parts])
defp split_parts(binary, _pattern, 0, parts), do: Enum.reverse([binary|parts])
defp split_parts(binary, pattern, num, parts) do
[head|tail] = :binary.split(binary, pattern)
case tail do
[] -> Enum.reverse([head|parts])
[str] -> split_parts(str, pattern, num - 1, [head|parts])
case :binary.split(binary, pattern) do
[head] -> Enum.reverse([head|parts])
[head, rest] -> split_parts(rest, pattern, num - 1, [head|parts])
end
end
@@ -654,7 +645,7 @@ defmodule String do
do_reverse(next_grapheme(rest), [grapheme|acc])
end
defp do_reverse(nil, acc), do: iodata_to_binary(acc)
defp do_reverse(nil, acc), do: IO.iodata_to_binary(acc)
@doc """
Returns a binary `subject` duplicated `n` times.
@@ -1102,10 +1093,6 @@ defmodule String do
Kernel.match?({0, _}, :binary.match(string, prefix))
end
defp do_starts_with(_, _) do
raise ArgumentError
end
@doc """
Returns `true` if `string` ends with any of the suffixes given, otherwise
`false`. `suffixes` can be either a single suffix or a list of suffixes.
@@ -1143,10 +1130,6 @@ defmodule String do
(suffix_size <= string_size) and (:nomatch != :binary.match(string, suffix, [scope: scope]))
end
defp do_ends_with(_, _) do
raise ArgumentError
end
@doc """
Check if `string` matches the given regular expression.
@@ -1199,49 +1182,123 @@ defmodule String do
:nomatch != :binary.match(string, match)
end
defp do_contains(_, _) do
raise ArgumentError
end
@doc false
def to_char_list(list) do
IO.write :stderr, "String.to_char_list/1 is deprecated, please use List.from_char_data/1 instead\n#{Exception.format_stacktrace}"
List.from_char_data(list)
end
@doc false
def to_char_list!(list) do
IO.write :stderr, "String.to_char_list!/1 is deprecated, please use List.from_char_data!/1 instead\n#{Exception.format_stacktrace}"
List.from_char_data!(list)
end
@doc false
def from_char_list(list) do
IO.write :stderr, "String.from_char_list/1 is deprecated, please use String.from_char_data/1 instead\n#{Exception.format_stacktrace}"
from_char_data(list)
end
@doc false
def from_char_list!(list) do
IO.write :stderr, "String.from_char_list!/1 is deprecated, please use String.from_char_data!/1 instead\n#{Exception.format_stacktrace}"
from_char_data!(list)
end
@doc """
Converts char data (a list of integers and strings) into a string.
If a string is given, returns the string itself.
Converts a string into a char list.
## Examples
iex> String.from_char_data([0x00E6, 0x00DF])
{:ok, "æß"}
iex> String.to_char_list("æß")
'æß'
iex> String.from_char_data([0x0061, "bc"])
{:ok, "abc"}
Notice that this function expect a list of integer representing
UTF-8 codepoints. If you have a raw binary, you must instead use
[the `:binary` module](http://erlang.org/doc/man/binary.html).
"""
@spec to_char_list(t) :: char_list
def to_char_list(string) when is_binary(string) do
case :unicode.characters_to_list(string) do
result when is_list(result) ->
result
{:error, encoded, rest} ->
raise UnicodeConversionError, encoded: encoded, rest: rest, kind: :invalid
{:incomplete, encoded, rest} ->
raise UnicodeConversionError, encoded: encoded, rest: rest, kind: :incomplete
end
end
@doc """
Converts a string to an atom.
Currently Elixir does not support conversions from strings
which contains Unicode codepoints greater than 0xFF.
Inlined by the compiler.
## Examples
iex> String.to_atom("my_atom")
:my_atom
"""
@spec from_char_data(char_data) :: {:ok, String.t} | {:error, binary, binary} | {:incomplete, binary, binary}
@spec to_atom(String.t) :: atom
def to_atom(string) do
:erlang.binary_to_atom(string, :utf8)
end
@doc """
Converts a string to an existing atom.
Currently Elixir does not support conversions from strings
which contains Unicode codepoints greater than 0xFF.
Inlined by the compiler.
## Examples
iex> :my_atom
iex> String.to_existing_atom("my_atom")
:my_atom
iex> String.to_existing_atom("this_atom_will_never_exist")
** (ArgumentError) argument error
"""
@spec to_existing_atom(String.t) :: atom
def to_existing_atom(string) do
:erlang.binary_to_existing_atom(string, :utf8)
end
@doc """
Returns a integer whose text representation is `string`.
Inlined by the compiler.
## Examples
iex> String.to_integer("123")
123
"""
@spec to_integer(String.t) :: integer
def to_integer(string) do
:erlang.binary_to_integer(string)
end
@doc """
Returns an integer whose text representation is `string` in base `base`.
Inlined by the compiler.
## Examples
iex> String.to_integer("3FF", 16)
1023
"""
@spec to_integer(String.t, pos_integer) :: integer
def to_integer(string, base) do
:erlang.binary_to_integer(string, base)
end
@doc """
Returns a float whose text representation is `string`.
Inlined by the compiler.
## Examples
iex> String.to_float("2.2017764e+0")
2.2017764
"""
@spec to_float(String.t) :: float
def to_float(string) do
:erlang.binary_to_float(string)
end
@doc false
def from_char_data(binary) when is_binary(binary) do
binary
end
@@ -1259,22 +1316,7 @@ defmodule String do
end
end
@doc """
Converts char data (a list of integers and strings) into a string.
In case the conversion fails, it raises a `UnicodeConversionError`.
If a string is given, returns the string itself.
## Examples
iex> String.from_char_data!([0x00E6, 0x00DF])
"æß"
iex> String.from_char_data!([0x0061, "bc"])
"abc"
"""
@spec from_char_data!(char_data) :: String.t | no_return
@doc false
def from_char_data!(binary) when is_binary(binary) do
binary
end
+4 -32
View File
@@ -18,23 +18,16 @@ defprotocol String.Chars do
end
defimpl String.Chars, for: Atom do
@doc """
Convert the atom literally to a binary, except
`nil` which is converted to an empty string.
"""
def to_string(nil) do
""
end
def to_string(atom) do
atom_to_binary(atom)
Atom.to_string(atom)
end
end
defimpl String.Chars, for: BitString do
@doc """
Returns the given binary or raises an error for bitstrings.
"""
def to_string(thing) when is_binary(thing) do
thing
end
@@ -48,38 +41,17 @@ defimpl String.Chars, for: BitString do
end
defimpl String.Chars, for: List do
@doc """
Consider the list is an iolist and converts it
to a binary. This allows a list of binaries, or
a charlist, or a mix of both, to be converted
successfully.
## Examples
iex> to_string('foo')
"foo"
iex> to_string(["foo", 'bar'])
"foobar"
"""
def to_string(char_list), do: String.from_char_data!(char_list)
def to_string(char_list), do: List.to_string(char_list)
end
defimpl String.Chars, for: Integer do
@doc """
Simply converts the integer to a string.
"""
def to_string(thing) do
integer_to_binary(thing)
Integer.to_string(thing)
end
end
defimpl String.Chars, for: Float do
@doc """
Simply converts the float to a string.
"""
def to_string(thing) do
iodata_to_binary(:io_lib_format.fwrite_g(thing))
IO.iodata_to_binary(:io_lib_format.fwrite_g(thing))
end
end
+7 -7
View File
@@ -10,7 +10,7 @@ defmodule StringIO do
"""
use GenServer.Behaviour
use GenServer
@doc """
Creates an IO device.
@@ -106,12 +106,12 @@ defmodule StringIO do
end
defp io_request({:put_chars, chars}, %{output: output} = s) do
{:ok, %{s | output: << output :: binary, String.from_char_data!(chars) :: binary >>}}
{:ok, %{s | output: << output :: binary, IO.chardata_to_string(chars) :: binary >>}}
end
defp io_request({:put_chars, m, f, as}, %{output: output} = s) do
chars = apply(m, f, as)
{:ok, %{s | output: << output :: binary, String.from_char_data!(chars) :: binary >>}}
{:ok, %{s | output: << output :: binary, IO.chardata_to_string(chars) :: binary >>}}
end
defp io_request({:put_chars, _encoding, chars}, s) do
@@ -183,7 +183,7 @@ defmodule StringIO do
{error, s}
{result, input} ->
if capture_prompt do
output = << output :: binary, String.from_char_data!(prompt) :: binary >>
output = << output :: binary, IO.chardata_to_string(prompt) :: binary >>
end
{result, %{s | input: input, output: output}}
@@ -231,7 +231,7 @@ defmodule StringIO do
{result, input} = do_get_line(chars, encoding)
if capture_prompt do
output = << output :: binary, String.from_char_data!(prompt) :: binary >>
output = << output :: binary, IO.chardata_to_string(prompt) :: binary >>
end
{result, %{s | input: input, output: output}}
@@ -261,7 +261,7 @@ defmodule StringIO do
{result, input, count} = do_get_until(chars, encoding, mod, fun, args)
if capture_prompt do
output = << output :: binary, :binary.copy(String.from_char_data!(prompt), count) :: binary >>
output = << output :: binary, :binary.copy(IO.chardata_to_string(prompt), count) :: binary >>
end
input =
@@ -335,6 +335,6 @@ defmodule StringIO do
send from, {:io_reply, reply_as, reply}
end
defp to_reply(list) when is_list(list), do: String.from_char_data!(list)
defp to_reply(list) when is_list(list), do: IO.chardata_to_string(list)
defp to_reply(other), do: other
end
+383
View File
@@ -0,0 +1,383 @@
defmodule Supervisor do
@moduledoc """
A behaviour module for implementing supevision functionality.
A supervisor is a process which supervises other processes called
child processes. Supervisors are used to build an hierarchical process
structure called a supervision tree, a nice way to structure a fault
tolerant applications.
A supervisor implemented using this module will have a standard set
of interface functions and include functionality for tracing and error
reporting. It will also fit into an supervision tree.
## Example
In order to define a supervisor, we need to first define a child process
that is going to be supervised. In order to do so, we will define a GenServer
that represents a stack:
defmodule Stack do
use GenServer
def start_link(state) do
GenServer.start_link(__MODULE__, state, [name: :sup_stack])
end
def handle_call(:pop, _from, [h|t]) do
{:reply, h, t}
end
def handle_cast({:push, h}, _from, t) do
{:noreply, [h|t]}
end
end
We can now define our supervisor and start it as follows:
# Import helpers for defining supervisors
import Supervisor.Spec
# We are going to supervise the Stack server which will
# be started with a single argument [:hello]
children = [
worker(Stack, [[:hello]])
]
# Start the supervisor with our one children
{:ok, pid} = Supervisor.start_link(children, strategy: :one_for_one)
Notice that when starting the GenServer, we have registered it
with name `:sup_stack`, which allows us to call it directly and
get what is on the stack:
GenServer.call(:sup_stack, :pop)
#=> :hello
GenServer.cast(:sup_stack, {:push, :world})
#=> :ok
GenServer.call(:sup_stack, :pop)
#=> :world
However, there is a bug in our stack server. If we call `:pop` and
the stack is empty, it is going to crash because no clause matches.
Let's try it:
GenServer.call(:sup_stack, :pop)
=ERROR REPORT====
Luckily, since the server is being supervised by a supervisor, the
supervisor will automatically start a new one, with the default stack
of `[:hello]` like before:
GenServer.call(:sup_stack, :pop) == :hello
Supervisors support different strategies, in the example above, we
have chosen `:one_for_one`. Furthermore, each supervisor can have many
workers and supervisors as children, each of them with their specific
configuration, shutdown values and restart strategies.
Continue reading this module to learn more about supervision strategies
and then follow to the `Supervisor.Spec` module documentation to learn
about the specification for workers and supervisors.
## Module-based supervisors
In the example above, a supervisor was dynamically created by passing
the supervision structure to `start_link/2`. However, supervisors
can also be created by explicitly defining a supevision module:
defmodule MyApp.Supervisor do
use Supervisor
def start_link do
Supervisor.start_link(__MODULE__, [])
end
def init([]) do
import Supervisor.Spec
children = [
worker(Stack, [[:hello]])
]
supervisor(children, strategy: :one_for_one)
end
end
You may want to use a module-based supervisor if:
* You need to do some particular action on supervisor
initialization, like setting up a ETS table;
* You want to perform partial hot-code swapping of the
tree. For example, if you add or remove a children,
the module-based supervision will add and remove the
new children directly, while the dynamic supervision
requires the whole tree to be restarted in order to
perform such swaps;
## Strategies
* `:one_for_one` - If a child process terminates, only that
process is restarted;
* `:one_for_all` - If a child process terminates, all other child
processes are terminated and then all child processes, including
the terminated one, are restarted;
* `:rest_for_one` - If a child process terminates, the "rest" of
the child processes, i.e. the child processes after the terminated
process in start order, are terminated. Then the terminated child
process and the rest of the child processes are restarted;
* `:simple_one_for_one` - Similar to `:one_for_one` but suits better
when dynamically attaching children. This strategy requires the
supervisor specification to contain only one children. Many functions
in this module behave slightly differently when this strategy is
used;
## Name registering
A supervisor is bound to the same name registering rules as a `GenServer`.
Read more about it in the `GenServer` docs.
"""
@doc false
defmacro __using__(_) do
quote location: :keep do
@behaviour :supervisor
import Supervisor.Spec
end
end
@typedoc "Return values of `start_link` functions"
@type on_start :: {:ok, pid} | :ignore |
{:error, {:already_started, pid} | {:shutdown, term} | term}
@typedoc "Return values of `start_child` functions"
@type on_start_child :: {:ok, child} | {:ok, child, info :: term} |
{:error, {:already_started, child} | :already_present | term}
@type child :: pid | :undefined
@typedoc "The Supervisor name"
@type name :: atom | {:global, term} | {:via, module, term}
@typedoc "Options used by the `start*` functions"
@type options :: [name: name,
strategy: Supervisor.Spec.strategy,
max_restarts: non_neg_integer,
max_seconds: non_neg_integer]
@typedoc "The supervisor reference"
@type supervisor :: pid | name | {atom, node}
@doc """
Starts a supervisor with the given children.
A strategy is required to be given as an option. Furthermore,
the `:max_restarts` and `:max_seconds` value can be configured
as described in `Supervisor.Spec.supervise/2` docs.
The options can also be used to register a supervisor name,
the supported values are described under the `Name Registering`
section in the `GenServer` module docs.
If the supervisor and its child processes are successfully created
(i.e. if all child process start functions return `{:ok, child}`,
`{:ok, child, info}`, or `:ignore`) the function returns
`{:ok, pid}`, where `pid` is the pid of the supervisor. If there
already exists a process with the specified name the function returns
`{:error, {:already_started, pid}}`, where pid is the pid of that
process.
If any child process start function fails or returns an error tuple or
an erroneous value, the supervisor will first terminate all already
started child processes with reason `:shutdown` and then terminate
itself and return `{:error, {:shutdown, reason}}`.
"""
@spec start_link([tuple], options) :: on_start
def start_link(children, options) when is_list(children) do
spec = Supervisor.Spec.supervise(children, options)
start_link(Supervisor.Default, spec, options)
end
@doc """
Starts a supervisor module with the given `arg`.
To start the supervisor, the `init/1` callback will be invoked
in the given module. The `init/1` callback must return a
supervision specification which can be created with the help
of `Supervisor.Spec` module.
If the `init/1` callback returns `:ignore`, this function returns
`:ignore` as well and the supervisor terminates with reason `:normal`.
If it fails or returns an incorrect value, this function returns
`{:error, term} where term is a term with information about the
error, and the supervisor terminates with reason `term`.
The `:name` option can also be given in order to register a supervisor
name, the supported values are described under the `Name Registering`
section in the `GenServer` module docs.
Other failure conditions are specified in `start_link/2` docs.
"""
@spec start_link(module, term, options) :: on_start
def start_link(module, arg, options \\ []) when is_list(options) do
case Keyword.get(options, :name) do
nil ->
:supervisor.start_link(module, arg)
atom when is_atom(atom) ->
:supervisor.start_link({:local, atom}, module, arg)
other when is_tuple(other) ->
:supervisor.start_link(other, module, arg)
end
end
@doc """
Dynamically adds and starts a child specification to the supervisor.
`child_spec` should be a valid child specification (unless the supervisor
is a `:simple_one_for_one` supervisor, see below). The child process will
be started as defined in the child specification.
In the case of a `:simple_one_for_one`, the child specification defined in
the supervisor will be used and instead of a `child_spec`, an arbitrary list
of terms is expected. The child process will then be started by appending
the given list to the existing function arguments in the child specification.
If there already exists a child specification with the specified id,
`child_spec` is discarded and the function returns an error with `:already_started`
or `:already_present` if the corresponding child process is running or not.
If the child process start function returns `{:ok, child}` or `{:ok, child, info}`,
the child specification and pid is added to the supervisor and the function returns
the same value.
If the child process start function returns `:ignore, the child specification is
added to the supervisor, the pid is set to undefined and the function returns
`{:ok, :undefined}`.
If the child process start function returns an error tuple or an erroneous value,
or if it fails, the child specification is discarded and the function returns
`{:error, error}` where error is a term containing information about the error
and child specification.
"""
@spec start_child(supervisor, Supervisor.Spec.spec | [term]) :: on_start_child
defdelegate start_child(supervisor, child_spec_or_args), to: :supervisor
@doc """
Terminates the given pid or child id.
If the supervisor is not a `simple_one_for_one`, the child id is expected
and the process, if there is one, is terminated and, the child specification is
kept unless the child is temporary.
In case of a `simple_one_for_one` supervisor, a pid is expected. If the child
specification identifier is given instead instead of a `pid`, the function will
return `{:error, :simple_one_for_one}`.
Non-temporary child process may later be restarted by the supervisor. The child
process can also be restarted explicitly by calling `restart_child/2`. Use
`delete_child/2` to remove the child specification.
If successful, the function returns ok. If there is no child specification or
pid, the function returns `{:error, :not_found}`.
"""
@spec terminate_child(supervisor, pid | Supervisor.Spec.child_id) :: :ok | {:error, error}
when error: :not_found | :simple_one_for_one
defdelegate terminate_child(supervisor, pid_or_child_id), to: :supervisor
@doc """
Deletes the child specification identified by `child_id`.
The corresponding child process must not be running, use `terminate_child/2`
to terminate it.
If successful, the function returns `:ok`. This function may error with an
appropriate error tuple if the `child_id` is not found, or if the current
process is running or being restarted.
This operation is not supported by `simple_one_for_one` supervisors.
"""
@spec delete_child(supervisor, Supervisor.Spec.child_id) :: :ok | {:error, error}
when error: :not_found | :simple_one_for_one | :running | :restarting
defdelegate delete_child(supervisor, child_id), to: :supervisor
@doc """
Restarts a child process identified by `child_id`.
The child specification must exist and the corresponding child process must not
be running.
Note that for temporary children, the child specification is automatically deleted
when the child terminates, and thus it is not possible to restart such children.
If the child process start function returns `{:ok, child}` or
`{:ok, child, info}`, the pid is added to the supervisor and the function returns
the same value.
If the child process start function returns `:ignore`, the pid remains set to
`:undefined` and the function returns `{:ok, :undefined}`.
This function may error with an appropriate error tuple if the `child_id` is not
found, or if the current process is running or being restarted.
If the child process start function returns an error tuple or an erroneous value,
or if it fails, the function returns `{:error, error}`.
This operation is not supported by `simple_one_for_one` supervisors.
"""
@spec restart_child(supervisor, Supervisor.Spec.child_id) ::
{:ok, child} | {:ok, child, term} | {:error, error}
when error: :not_found | :simple_one_for_one | :running | :restarting | term
defdelegate restart_child(supervisor, child_id), to: :supervisor
@doc """
Returns a list with information about all children.
Note that calling this function when supervising a large number of children
under low memory conditions can cause an out of memory exception.
This function returns a list of tuples containing:
* the `id` - as defined in the child specification or `:undefined` in the case
of a `simple_one_for_one` supervisor;
* the `child` - the pid of the corresponding child process, the atom `:restarting`
if the process is about to be restarted or `:undefined` if there is no such process;
* the `type` - `:worker` or `:supervisor` as defined in the child specification;
* the `modules` as defined in the child specification;
"""
@spec which_children(supervisor) ::
[{Supervisor.Spec.child_id | :undefined,
child | :restarting,
Supervisor.Spec.worker,
Supervisor.Spec.modules}]
defdelegate which_children(supervisor), to: :supervisor
@doc """
Returns a map containing count value for the supervisor.
The map contains the following keys:
* `:specs` - the total count of children, dead or alive;
* `:active` - the count of all actively running child processes managed by this supervisor;
* `:supervisors` - the count of all supervisors whether or not the child process is still alive;
* `:workers` - the count of all workers, whether or not the child process is still alive;
"""
@spec count_children(supervisor) ::
[specs: non_neg_integer, active: non_neg_integer,
supervisors: non_neg_integer, workers: non_neg_integer]
def count_children(supervisor) do
:supervisor.count_children(supervisor) |> :maps.from_list
end
end
+2 -30
View File
@@ -1,33 +1,5 @@
defmodule Supervisor.Behaviour do
@moduledoc """
This module is a convenience to define Supervisor
callbacks in Elixir. By using this module, you get
the module behaviour automatically tagged as
`:supervisor` and some helper functions are imported
to make defining supervisors easier.
For more information on supervisors, please check the
remaining functions defined in this module or refer to
the following:
http://www.erlang.org/doc/man/supervisor.html
http://www.erlang.org/doc/design_principles/sup_princ.html
http://learnyousomeerlang.com/supervisors
## Example
defmodule ExUnit.Sup do
use Supervisor.Behaviour
def init(user_options) do
tree = [ worker(ExUnit.Runner, [user_options]) ]
supervise(tree, strategy: :one_for_one)
end
end
{:ok, pid} = :supervisor.start_link(MyServer, [])
"""
@moduledoc false
@doc false
defmacro __using__(_) do
@@ -81,7 +53,7 @@ defmodule Supervisor.Behaviour do
"""
def supervise(children, options) do
unless strategy = options[:strategy] do
raise ArgumentError, message: "expected :strategy option to be given to supervise"
raise ArgumentError, "expected :strategy option to be given to supervise"
end
maxR = Keyword.get(options, :max_restarts, 5)
+13
View File
@@ -0,0 +1,13 @@
defmodule Supervisor.Default do
@moduledoc false
@behaviour :supervisor
@doc """
Supevisor callback that simply returns the given args.
This is the supervisor used by `Supervisor.start_link/2`.
"""
def init(args) do
args
end
end
+247
View File
@@ -0,0 +1,247 @@
defmodule Supervisor.Spec do
@moduledoc """
Convenience functions for defining a supervision specification.
## Example
By using the functions in this module one can define a supervisor
and start it with `Supervisor.start_link/2`:
import Supervisor.Spec
children = [
worker(MyWorker, [arg1, arg2, arg3]),
supervisor(MySupervisor, [arg1])
]
Supervisor.start_link(children, strategy: :one_for_one)
In many situations, it may be handy to define supervisors backed
by a module:
defmodule MySupervisor do
use Supervisor
def start_link(arg) do
Supervisor.start_link(__MODULE__, arg)
end
def init(arg) do
children = [
worker(MyWorker, [arg], restart: :temporary)
]
supervise(children, strategy: :simple_one_for_one)
end
end
Notice in this case we don't have to explicitly import
`Supervisor.Spec` as `use Supervisor` automatically does so.
Explicit supervisors as above are required when there is a need to:
1. partialy change the supervision tree during hot-code swaps;
2. define supervisors inside other supervisors;
3. perform actions inside the supervision `init/1` callback.
For example, you may want to start an ETS table that is linked to
the supervisor (i.e. if the supervision tree needs to be restarted,
the ETS table must be restarted too);
## Supervisor and worker options
In the example above, we have defined workers and supervisors
and each accepts the following options:
* `:id` - a name used to identify the child specification
internally by the supervisor. Defaults to the given module
name;
* `:function` - the function to invoke on the child to start it;
* `:restart` - defines when the child process should restart;
* `:shutdown` - defines how a child process should be terminated;
* `:modules` - it should be a list with one element `[module]`,
where module is the name of the callback module only if the
child process is a `Supervisor` or `GenServer` If the child
process is a `GenEvent`, modules should be `:dynamic`;
### Restart values
The following restart values are supported:
* `:permanent` - the child process is always restarted;
* `:temporary` - the child process is never restarted (not even
when the supervisor's strategy is `:rest_for_one` or `:one_for_all`);
* `:transient` - the child process is restarted only if it
terminates abnormally, i.e. with another exit reason than
`:normal`, `:shutdown` or `{:shutdown, term}`;
### Shutdown values
The following shutdown values are supported:
* `:brutal_kill` - the child process is unconditionally terminated
using `exit(child, :kill)`;
* `:infinity` - if the child process is a supervisor, it is a mechanism
to give the subtree enough time to shutdown. It can also be used with
workers with care;
* Finally, it can also be any integer meaning that the supervisor tells
the child process to terminate by calling `Process.exit(child, :shutdown)`
and then waits for an exit signal back. If no exit signal is received
within the specified time (in miliseconds), the child process is
unconditionally terminated using `Process.exit(child, :kill)`;
"""
@typedoc "Supported strategies"
@type strategy :: :simple_one_for_one | :one_for_one | :one_for_all | :rest_for_one
@typedoc "Supported restart values"
@type restart :: :permanent | :transient | :temporary
@typedoc "Supported shutdown values"
@type shutdown :: :brutal_kill | :infinity | non_neg_integer
@typedoc "Supported worker values"
@type worker :: :worker | :supervisor
@typedoc "Supported module values"
@type modules :: :dynamic | [module]
@typedoc "Supported id values"
@type child_id :: term
@typedoc "The supervisor specification"
@type spec :: {child_id,
start_fun :: {module, atom, [term]},
restart,
shutdown,
worker,
modules}
@doc """
Receives a list of children (workers or supervisors) to
supervise and a set of options.
Returns a tuple containing the supervisor specification.
## Examples
supervise children, strategy: :one_for_one
## Options
* `:strategy` - the restart strategy option It can be either
`:one_for_one`, `:rest_for_one`, `:one_for_all` and
`:simple_one_for_one`. You can learn more about strategies
in the `Supervisor` module;
* `:max_restarts` - the maximum amount of restarts allowed in
a time frame. Defaults to 5;
* `:max_seconds` - the time frame in which max_restarts applies.
Defaults to 5;
The `:strategy` option is required and by default maximum 5 restarts
are allowed in 5 seconds. Please check the `Supervisor` module for
a complete description of the available strategies.
"""
@spec supervise([spec], strategy: strategy,
max_restarts: non_neg_integer,
max_seconds: non_neg_integer) :: {:ok, tuple}
def supervise(children, options) do
unless strategy = options[:strategy] do
raise ArgumentError, "expected :strategy option to be given"
end
maxR = Keyword.get(options, :max_restarts, 5)
maxS = Keyword.get(options, :max_seconds, 5)
assert_unique_ids(Enum.map(children, &elem(&1, 0)))
{:ok, {{strategy, maxR, maxS}, children}}
end
defp assert_unique_ids([id|rest]) do
if id in rest do
raise ArgumentError,
"duplicated id #{inspect id} found in the supervisor specification, " <>
"please explicitly pass the :id option when defining this worker/supervisor"
else
assert_unique_ids(rest)
end
end
defp assert_unique_ids([]) do
:ok
end
@doc """
Defines the given `module` as a worker which will be started
with the given arguments.
worker ExUnit.Runner, [], restart: :permanent
By default, the function `:start_link` is invoked on the given
module. Overall, the default values for the options are:
[id: module,
function: :start_link,
restart: :permanent,
shutdown: 5000,
modules: [module]]
Check `Supervisor.Spec` module docs for more information on
the options.
"""
@spec worker(module, [term], [restart: restart, shutdown: shutdown,
id: term, function: atom, modules: modules]) :: spec
def worker(module, args, options \\ []) do
child(:worker, module, args, options)
end
@doc """
Defines the given `module` as a supervisor which will be started
with the given arguments.
supervisor ExUnit.Runner, [], restart: :permanent
By default, the function `:start_link` is invoked on the given
module. Overall, the default values for the options are:
[id: module,
function: :start_link,
restart: :permanent,
shutdown: :infinity,
modules: [module]]
Check `Supervisor.Spec` module docs for more information on
the options.
"""
@spec supervisor(module, [term], [restart: restart, shutdown: shutdown,
id: term, function: atom, modules: modules]) :: spec
def supervisor(module, args, options \\ []) do
options = Keyword.put_new(options, :shutdown, :infinity)
child(:supervisor, module, args, options)
end
defp child(type, module, args, options) do
id = Keyword.get(options, :id, module)
modules = Keyword.get(options, :modules, modules(module))
function = Keyword.get(options, :function, :start_link)
restart = Keyword.get(options, :restart, :permanent)
shutdown = Keyword.get(options, :shutdown, 5000)
{id, {module, function, args},
restart, shutdown, type, modules}
end
defp modules(GenEvent), do: :dynamic
defp modules(module), do: [module]
end
+13 -13
View File
@@ -44,7 +44,7 @@ defmodule System do
# Get the date at compilation time.
defmacrop get_date do
iodata_to_binary :httpd_util.rfc1123_date
IO.iodata_to_binary :httpd_util.rfc1123_date
end
@doc """
@@ -94,7 +94,7 @@ defmodule System do
"""
def cwd do
case :file.get_cwd do
{:ok, base} -> String.from_char_data!(base)
{:ok, base} -> IO.chardata_to_string(base)
_ -> nil
end
end
@@ -193,7 +193,7 @@ defmodule System do
{:ok, stat} ->
case {stat.type, stat.access} do
{:directory, access} when access in [:read_write, :write] ->
String.from_char_data!(dir)
IO.chardata_to_string(dir)
_ ->
nil
end
@@ -233,7 +233,7 @@ defmodule System do
end
def cmd(command) when is_binary(command) do
String.from_char_data! :os.cmd(List.from_char_data!(command))
List.to_string :os.cmd(String.to_char_list(command))
end
@doc """
@@ -256,9 +256,9 @@ defmodule System do
end
def find_executable(program) when is_binary(program) do
case :os.find_executable(List.from_char_data!(program)) do
case :os.find_executable(String.to_char_list(program)) do
false -> nil
other -> String.from_char_data!(other)
other -> List.to_string(other)
end
end
@@ -271,7 +271,7 @@ defmodule System do
@spec get_env() :: %{String.t => String.t}
def get_env do
Enum.into(:os.getenv, %{}, fn var ->
var = String.from_char_data! var
var = IO.chardata_to_string var
[k, v] = String.split var, "=", parts: 2
{k, v}
end)
@@ -286,9 +286,9 @@ defmodule System do
"""
@spec get_env(binary) :: binary | nil
def get_env(varname) when is_binary(varname) do
case :os.getenv(List.from_char_data!(varname)) do
case :os.getenv(String.to_char_list(varname)) do
false -> nil
other -> String.from_char_data!(other)
other -> List.to_string(other)
end
end
@@ -301,7 +301,7 @@ defmodule System do
See http://www.erlang.org/doc/man/os.html#getpid-0 for more info.
"""
@spec get_pid() :: binary
def get_pid, do: iodata_to_binary(:os.getpid)
def get_pid, do: IO.iodata_to_binary(:os.getpid)
@doc """
Set an environment variable value.
@@ -310,7 +310,7 @@ defmodule System do
"""
@spec put_env(binary, binary) :: :ok
def put_env(varname, value) when is_binary(varname) and is_binary(value) do
:os.putenv List.from_char_data!(varname), List.from_char_data!(value)
:os.putenv String.to_char_list(varname), String.to_char_list(value)
:ok
end
@@ -332,7 +332,7 @@ defmodule System do
"""
@spec delete_env(String.t) :: :ok
def delete_env(varname) do
:os.unsetenv(List.from_char_data!(varname))
:os.unsetenv(String.to_char_list(varname))
:ok
end
@@ -385,6 +385,6 @@ defmodule System do
end
def halt(status) when is_binary(status) do
:erlang.halt(List.from_char_data!(status))
:erlang.halt(String.to_char_list(status))
end
end
+227
View File
@@ -0,0 +1,227 @@
defmodule Task do
@moduledoc """
Conveniences for spawning and awaiting for tasks.
Tasks are processes that meant to execute one particular
action throughout their life-cycle, often with little
explicit communication with other processes. The most common
use case for tasks is to compute a value asynchronously:
task = Task.async(fn -> do_some_work() end)
res = do_some_other_work()
res + Task.await(task)
Tasks spawned with async can be awaited on by its caller
process (and only its caller) as shown in the example above.
They are implemented by spawning a process that sends a message
to the caller once the given computation is performed.
Besides `async/1` and `await/1`, tasks can also be used as part
of supervision trees and dynamically spawned in remote nodes.
We will explore all three scenarios next.
## async and await
The most common way to spawn a task is with `Task.async/1`. A new
process will be created and this process is linked and monitored
by the caller. However, the processes are unlinked right before
the task finishes, allowing the proper error to be triggered only
on `await/1`.
This implies three things:
1) In case the caller crashes, the task will be killed and its
computation will abort;
2) In case the task crashes due to an error, the parent will
crash only on `await/1`;
3) In case the task crashes because a linked process caused
it to crash, the parent will crash immediately;
## Supervised tasks
It is also possible to spawn a task inside a supervision tree
with `start_link/1` and `start_link/3`:
Task.start_link(fn -> IO.puts "ok" end)
Such can be mounted in your supervision tree as:
import Supervisor.Spec
children = [
worker(Task, [fn -> IO.puts "ok" end])
]
Since such tasks are supervised and not directly linked to
the caller, they cannot be awaited on. For such reason,
differently from `async/1`, `start_link/1` returns `{:ok, pid}`
(which is the result expected by supervision trees).
Such tasks are useful as workers that run during your application
life-cycle and rarely communicate with other workers. For example,
a worker that pushes data to another server or a worker that consumes
events from an event manager and writes it to a log file.
## Supervision trees
The `Task.Supervisor` module allows developers to start supervisors
that dynamically supervise tasks:
{:ok, pid} = Task.Supervisor.start_link()
Task.Supervisor.async(pid, fn -> do_work() end)
`Task.Supervisor` also makes it possible to spawn tasks in remote nodes as
long as the supervisor is registered locally or globally:
# In the remote node
Task.Supervisor.start_link(name: :tasks_sup)
# On the client
Task.Supervisor.async({:tasks_sup, :remote@local}, fn -> do_work() end)
`Task.Supervisor` is more often started in your supervision tree as:
import Supervisor.Spec
children = [
supervisor(Task.Supervisor, [[name: :tasks_sup]])
]
Check `Task.Supervisor` for other operations supported by the Task supervisor.
"""
@doc """
The Task struct.
It contains two fields:
* `:pid` - the proces reference of the task process. It may be a pid
or a tuple containing the process and node names;
* `:ref` - the task monitor reference;
"""
defstruct pid: nil, ref: nil
@doc """
Starts a task as part of a supervision tree.
"""
@spec start_link(fun) :: {:ok, pid}
def start_link(fun) do
start_link(:erlang, :apply, [fun, []])
end
@doc """
Starts a task as part of a supervision tree.
"""
@spec start_link(module, atom, [term]) :: {:ok, pid}
def start_link(mod, fun, args) do
Task.Supervised.start_link(:undefined, {mod, fun, args})
end
@doc """
Starts a task that can be awaited on.
This function spawns a process that is linked and monitored
to the caller process. A `Task` struct is returned containing
the relevant information.
## Task's message format
The reply sent by the task will be in the format `{ref, msg}`,
where `ref` is the monitoring reference hold by the task.
"""
@spec async(fun) :: t
def async(fun) do
async(:erlang, :apply, [fun, []])
end
@doc """
Starts a task that can be awaited on.
Similar to `async/1`, but the task is specified by the given
module, function and arguments.
"""
@spec async(module, atom, [term]) :: t
def async(mod, fun, args) do
mfa = {mod, fun, args}
pid = :proc_lib.spawn_link(Task.Supervised, :async, [self(), mfa])
ref = Process.monitor(pid)
send(pid, {self(), ref})
%Task{pid: pid, ref: ref}
end
@doc """
Awaits for a task reply.
A timeout, in miliseconds, can be given with default value
of `5000`. In case the task process dies, this function will
exit with the same reason as the task.
"""
@spec await(t, timeout) :: term | no_return
def await(%Task{ref: ref}=task, timeout \\ 5000) do
receive do
{^ref, reply} ->
Process.demonitor(ref, [:flush])
reply
{:DOWN, ^ref, _, _, :noconnection} ->
mfa = {__MODULE__, :await, [task, timeout]}
exit({{:nodedown, get_node(task.pid)}, mfa})
{:DOWN, ^ref, _, _, reason} ->
exit({reason, {__MODULE__, :await, [task, timeout]}})
after
timeout ->
Process.demonitor(ref, [:flush])
exit({:timeout, {__MODULE__, :await, [task, timeout]}})
end
end
@doc """
Receives a group of tasks and a message and finds
a task that matches the given message.
This function returns a tuple with the task and the
returned value in case the message matches a task that
exited with success, it raises in case the found task
failed or nil if no task was found.
This function is useful in situations where multiple
tasks are spawned and their results are collected just
later on. For example, a GenServer can spawn tasks,
store the tasks in a list and later use `Task.find/2`
to see if upcoming messages are from any of the tasks.
"""
@spec find([t], any) :: {term, t} | nil | no_return
def find(tasks, msg)
def find(tasks, {ref, reply}) when is_reference(ref) do
Enum.find_value tasks, fn
%Task{ref: task_ref} = t when ref == task_ref ->
Process.demonitor(ref, [:flush])
{reply, t}
%Task{} ->
nil
end
end
def find(tasks, {:DOWN, ref, _, _, reason} = msg) when is_reference(ref) do
find = fn(%Task{ref: task_ref}) -> task_ref == ref end
case Enum.find(tasks, find) do
%Task{pid: pid} when reason == :noconnection ->
exit({{:nodedown, get_node(pid)}, {__MODULE__, :find, [tasks, msg]}})
%Task{} ->
exit({reason, {__MODULE__, :find, [tasks, msg]}})
nil ->
nil
end
end
def find(_tasks, _msg) do
nil
end
defp get_node({_, n}) when is_atom(n), do: n
defp get_node(pid) when is_pid(pid), do: pid
end
+68
View File
@@ -0,0 +1,68 @@
defmodule Task.Supervised do
@moduledoc false
def start_link(:undefined, fun) do
:proc_lib.start_link(__MODULE__, :noreply, [fun])
end
def start_link(caller, fun) do
:proc_lib.start_link(__MODULE__, :reply, [caller, fun])
end
def async(caller, {module, fun, args}) do
ref =
# There is a race condition on this operation when working accross
# node that manifests if a `Task.Supervisor.async/1` call is made
# while the supervisor is busy spawning previous tasks.
#
# Imagine the following workflow:
#
# 1. The nodes disconnect
# 2. The async call fails and is caught, the calling process does not exit
# 3. The task is spawned and links to the calling process, causing the nodes to reconnect
# 4. The calling process has not exited and so does not send its monitor reference
# 5. The spawned task waits forever for the monitor reference so it can begin
#
# We have solved this by specifying a timeout of 5000 seconds.
# Given no work is done in the client in between the task start and
# sending the reference, 5000 should be enough to not raise false
# negatives unless the nodes are indeed not available.
receive do
{^caller, ref} -> ref
after
5000 -> exit(:timeout)
end
try do
apply(module, fun, args)
else
result ->
send caller, {ref, result}
catch
:error, reason ->
exit({reason, System.stacktrace()})
:throw, value ->
exit({{:nocatch, value}, System.stacktrace()})
after
:erlang.unlink(caller)
end
end
def reply(caller, mfa) do
:erlang.link(caller)
:proc_lib.init_ack({:ok, self()})
async(caller, mfa)
end
def noreply({module, fun, args}) do
:proc_lib.init_ack({:ok, self()})
try do
apply(module, fun, args)
catch
:error, reason ->
exit({reason, System.stacktrace()})
:throw, value ->
exit({{:nocatch, value}, System.stacktrace()})
end
end
end
+103
View File
@@ -0,0 +1,103 @@
defmodule Task.Supervisor do
@moduledoc """
A tasks supervisor.
This module defines a supervisor which can be used to dynamically
supervise tasks. Behind the scenes, this module is implemented as a
`:simple_one_for_one` supervisor where the workers are temporary
(i.e. they are not restarted after they die).
The functions in this module allow tasks can be spawned and awaited
from a supervisor, similar to the functions defined in the `Task` module.
## Name registering
A `Task.Supervisor` is bound to the same name registering rules as a
`GenServer`. Read more about it in the `GenServer` docs.
"""
@doc """
Starts a new supervisor.
The supported options are:
* `:name` - used to register a supervisor name, the supported values are
described under the `Name Registering` section in the `GenServer` module
docs;
* `:shutdown` - `:brutal_kill` if the tasks must be killed directly on shutdown
or an integer indicating the timeout value, defaults to 5000 miliseconds;
"""
@spec start_link(Supervisor.options) :: Supervisor.on_start
def start_link(opts \\ []) do
import Supervisor.Spec
{shutdown, opts} = Keyword.pop(opts, :shutdown, 5000)
children = [worker(Task.Supervised, [], restart: :temporary, shutdown: shutdown)]
Supervisor.start_link(children, [strategy: :simple_one_for_one] ++ opts)
end
@doc """
Starts a task that can be awaited on.
The `supervisor` must be a reference as defined in `Task.Supervisor`.
For more information on tasks, check the `Task` module.
"""
@spec async(Supervisor.supervisor, fun) :: Task.t
def async(supervisor, fun) do
async(supervisor, :erlang, :apply, [fun, []])
end
@doc """
Starts a task that can be awaited on.
The `supervisor` must be a reference as defined in `Task.Supervisor`.
For more information on tasks, check the `Task` module.
"""
@spec async(Supervisor.supervisor, module, atom, [term]) :: Task.t
def async(supervisor, module, fun, args) do
{:ok, pid} = Supervisor.start_child(supervisor, [self(), {module, fun, args}])
ref = Process.monitor(pid)
send pid, {self(), ref}
%Task{pid: pid, ref: ref}
end
@doc """
Terminates the given child at pid.
"""
@spec terminate_child(Supervisor.supervisor, pid) :: :ok
def terminate_child(supervisor, pid) when is_pid(pid) do
:supervisor.terminate_child(supervisor, pid)
end
@doc """
Returns all children pids.
"""
@spec children(Supervisor.supervisor) :: [pid]
def children(supervisor) do
:supervisor.which_children(supervisor) |> Enum.map(&elem(&1, 1))
end
@doc """
Starts a task as child of the given `supervisor`.
Note the spawned process is not linked to the caller but
only to the supervisor. This command is useful in case the
task needs to emit side-effects (like I/O) and does not need
to report back to the caller.
"""
@spec start_child(Supervisor.supervisor, fun) :: {:ok, pid}
def start_child(supervisor, fun) do
start_child(supervisor, :erlang, :apply, [fun, []])
end
@doc """
Starts a task as child of the given `supervisor`.
Similar to `start_child/2` except the task is specified
by the given `module`, `fun` and `args`.
"""
@spec start_child(Supervisor.supervisor, module, atom, [term]) :: {:ok, pid}
def start_child(supervisor, module, fun, args) do
Supervisor.start_child(supervisor, [:undefined, {module, fun, args}])
end
end
+13 -3
View File
@@ -4,7 +4,7 @@ defmodule Tuple do
"""
@doc """
Create a new tuple.
Creates a new tuple.
Creates a tuple of size `size` containing the
given `data` at every position.
@@ -23,7 +23,7 @@ defmodule Tuple do
end
@doc """
Insert an element into a tuple.
Inserts an element into a tuple.
Inserts `value` into `tuple` at the given zero-based `index`.
Raises an `ArgumentError` if `index` is greater than the
@@ -44,7 +44,7 @@ defmodule Tuple do
end
@doc """
Remove an element from a tuple.
Removes an element from a tuple.
Deletes the element at the zero-based `index` from `tuple`.
Raises an `ArgumentError` if `index` is greater than
@@ -63,4 +63,14 @@ defmodule Tuple do
def delete_at(tuple, index) do
:erlang.delete_element(index + 1, tuple)
end
@doc """
Converts a tuple to a list.
Inlined by the compiler.
"""
@spec to_list(tuple) :: list
def to_list(tuple) do
:erlang.tuple_to_list(tuple)
end
end
+7 -7
View File
@@ -58,7 +58,7 @@ defmodule URI do
"""
def default_port(scheme, port) when is_binary(scheme) and port > 0 do
{:ok, dict} = Application.fetch_env(:elixir, :uri)
Application.put_env(:elixir, :uri, Map.put(dict, scheme, port), persist: true)
Application.put_env(:elixir, :uri, Map.put(dict, scheme, port), persistent: true)
end
@doc """
@@ -134,11 +134,11 @@ defmodule URI do
end
defp pair({k, _}) when is_list(k) do
raise ArgumentError, message: "encode_query/1 keys cannot be lists, got: #{inspect k}"
raise ArgumentError, "encode_query/1 keys cannot be lists, got: #{inspect k}"
end
defp pair({_, v}) when is_list(v) do
raise ArgumentError, message: "encode_query/1 values cannot be lists, got: #{inspect v}"
raise ArgumentError, "encode_query/1 values cannot be lists, got: #{inspect v}"
end
defp pair({k, v}) do
@@ -200,7 +200,7 @@ defmodule URI do
defp hex2dec(n, _uri) when n in ?a..?f, do: n - ?a + 10
defp hex2dec(n, _uri) when n in ?0..?9, do: n - ?0
defp hex2dec(_n, uri) do
raise ArgumentError, message: "malformed URI #{inspect uri}"
raise ArgumentError, "malformed URI #{inspect uri}"
end
defp check_plus(?+), do: 32
@@ -235,7 +235,7 @@ defmodule URI do
if userinfo, do: authority = authority <> userinfo <> "@"
if host, do: authority = authority <> host
if port, do: authority = authority <> ":" <> integer_to_binary(port)
if port, do: authority = authority <> ":" <> Integer.to_string(port)
end
scheme = normalize_scheme(scheme)
@@ -257,7 +257,7 @@ defmodule URI do
components = Regex.run ~r/(^(.*)@)?(\[[a-zA-Z0-9:.]*\]|[^:]*)(:(\d*))?/, s
destructure [_, _, userinfo, host, _, port], nillify(components)
port = if port, do: binary_to_integer(port)
port = if port, do: String.to_integer(port)
host = if host, do: host |> String.lstrip(?[) |> String.rstrip(?])
{userinfo, host, port}
@@ -285,7 +285,7 @@ defimpl String.Chars, for: URI do
if uri.scheme, do: result = result <> uri.scheme <> "://"
if uri.userinfo, do: result = result <> uri.userinfo <> "@"
if uri.host, do: result = result <> uri.host
if uri.port, do: result = result <> ":" <> integer_to_binary(uri.port)
if uri.port, do: result = result <> ":" <> Integer.to_string(uri.port)
if uri.path, do: result = result <> uri.path
if uri.query, do: result = result <> "?" <> uri.query
if uri.fragment, do: result = result <> "#" <> uri.fragment
+14 -14
View File
@@ -68,15 +68,15 @@ defmodule Version do
defstruct [:source, :matchspec]
end
defexception InvalidRequirement, [:message]
defexception InvalidVersion, [:message]
defexception InvalidRequirementError, [:message]
defexception InvalidVersionError, [:message]
@doc """
Check if the given version matches the specification.
Returns `true` if `version` satisfies `requirement`, `false` otherwise.
Raises a `Version.InvalidRequirement` exception if `requirement` is not
parseable, or `Version.InvalidVersion` if `version` is not parseable.
Raises a `Version.InvalidRequirementError` exception if `requirement` is not
parseable, or `Version.InvalidVersionError` if `version` is not parseable.
If given an already parsed version and requirement this function won't
raise.
@@ -89,10 +89,10 @@ defmodule Version do
false
iex> Version.match?("foo", "==1.0.0")
** (Version.InvalidVersion) foo
** (Version.InvalidVersionError) foo
iex> Version.match?("2.0.0", "== ==1.0.0")
** (Version.InvalidRequirement) == ==1.0.0
** (Version.InvalidRequirementError) == ==1.0.0
"""
@spec match?(version, requirement) :: boolean
@@ -101,7 +101,7 @@ defmodule Version do
{:ok, req} ->
match?(vsn, req)
:error ->
raise InvalidRequirement, message: req
raise InvalidRequirementError, message: req
end
end
@@ -115,7 +115,7 @@ defmodule Version do
the second and `:lt` for vice versa. If the two versions are equal `:eq`
is returned
Raises a `Version.InvalidVersion` exception if `version` is not parseable.
Raises a `Version.InvalidVersionError` exception if `version` is not parseable.
If given an already parsed version this function won't raise.
## Examples
@@ -127,7 +127,7 @@ defmodule Version do
:eq
iex> Version.compare("invalid", "2.0.1")
** (Version.InvalidVersion) invalid
** (Version.InvalidVersionError) invalid
"""
@spec compare(version, version) :: :gt | :eq | :lt
@@ -200,7 +200,7 @@ defmodule Version do
defp to_matchable(string) do
case Version.Parser.parse_version(string) do
{:ok, vsn} -> vsn
:error -> raise InvalidVersion, message: string
:error -> raise InvalidVersionError, message: string
end
end
@@ -308,9 +308,9 @@ defmodule Version do
if nil?(minor) or (nil?(patch) and not approximate?) do
:error
else
major = binary_to_integer(major)
minor = binary_to_integer(minor)
patch = patch && binary_to_integer(patch)
major = String.to_integer(major)
minor = String.to_integer(minor)
patch = patch && String.to_integer(patch)
case parse_pre(pre) do
{:ok, pre} ->
@@ -330,7 +330,7 @@ defmodule Version do
defp parse_pre([piece|t], acc) do
cond do
piece =~ ~r/^(0|[1-9][0-9]*)$/ ->
parse_pre(t, [binary_to_integer(piece)|acc])
parse_pre(t, [String.to_integer(piece)|acc])
piece =~ ~r/^[0-9]*$/ ->
:error
true ->
+1 -2
View File
@@ -9,9 +9,8 @@
-include("elixir.hrl").
%% Top level types
-export_type([char_list/0, char_data/0, as_boolean/1]).
-export_type([char_list/0, as_boolean/1]).
-type char_list() :: string().
-type char_data() :: unicode:chardata().
-type as_boolean(T) :: T.
%% OTP Application API
+10 -8
View File
@@ -1,7 +1,7 @@
%% Handle code related to args, guard and -> matching for case,
%% fn, receive and friends. try is handled in elixir_try.
-module(elixir_clauses).
-export([match/3, clause/7, clauses/4, get_pairs/2, get_pairs/3,
-export([match/3, clause/7, clauses/4, guards/4, get_pairs/2, get_pairs/3,
extract_splat_guards/1, extract_guards/1]).
-include("elixir.hrl").
@@ -35,18 +35,20 @@ clause(Line, Fun, Args, Expr, Guards, Return, S) when is_integer(Line) ->
{TArgs, SA} = match(Fun, Args, S#elixir_scope{extra_guards=[]}),
{TExpr, SE} = elixir_translator:translate_block(Expr, Return, SA#elixir_scope{extra_guards=nil}),
SG = SA#elixir_scope{context=guard, extra_guards=nil},
Extra = SA#elixir_scope.extra_guards,
TGuards = case Guards of
[] -> case Extra of [] -> []; _ -> [Extra] end;
_ -> [translate_guard(Line, Guard, Extra, SG) || Guard <- Guards]
end,
TGuards = guards(Line, Guards, Extra, SA),
{{clause, Line, TArgs, TGuards, unblock(TExpr)}, SE}.
% Translate/Extract guards from the given expression.
guards(Line, Guards, Extra, S) ->
SG = S#elixir_scope{context=guard, extra_guards=nil},
case Guards of
[] -> case Extra of [] -> []; _ -> [Extra] end;
_ -> [translate_guard(Line, Guard, Extra, SG) || Guard <- Guards]
end.
translate_guard(Line, Guard, Extra, S) ->
[element(1, elixir_translator:translate(elixir_quote:linify(Line, Guard), S))|Extra].
+33 -38
View File
@@ -68,7 +68,9 @@ code_loading_compilation(Forms, Vars, #{line := Line} = E) ->
%% Pass {native, false} to speed up bootstrap
%% process when native is set to true
module(Form, ?m(E, file), [{native,false}], true, fun(_, Binary) ->
AllOpts = elixir_code_server:call(erl_compiler_options),
FinalOpts = AllOpts -- [native, warn_missing_spec],
module(Form, ?m(E, file), FinalOpts, true, fun(_, Binary) ->
%% If we have labeled locals, anonymous functions
%% were created and therefore we cannot ditch the
%% module
@@ -130,14 +132,13 @@ module(Forms, File, Opts, Callback) ->
Final =
case (get_opt(debug_info) == true) orelse
lists:member(debug_info, Opts) of
true -> [debug_info];
false -> []
true -> [debug_info] ++ elixir_code_server:call(erl_compiler_options);
false -> elixir_code_server:call(erl_compiler_options)
end,
module(Forms, File, Final, false, Callback).
module(Forms, File, RawOptions, Bootstrap, Callback) when
is_binary(File), is_list(Forms), is_list(RawOptions), is_boolean(Bootstrap), is_function(Callback) ->
Options = RawOptions ++ elixir_code_server:call(erl_compiler_options),
module(Forms, File, Options, Bootstrap, Callback) when
is_binary(File), is_list(Forms), is_list(Options), is_boolean(Bootstrap), is_function(Callback) ->
Listname = elixir_utils:characters_to_list(File),
case compile:noenv_forms([no_auto_import()|Forms], [return,{source,Listname}|Options]) of
@@ -172,38 +173,32 @@ core_file(File) ->
end.
core_main() ->
[
<<"lib/elixir/lib/kernel.ex">>,
<<"lib/elixir/lib/macro/env.ex">>,
<<"lib/elixir/lib/keyword.ex">>,
<<"lib/elixir/lib/module.ex">>,
<<"lib/elixir/lib/list.ex">>,
<<"lib/elixir/lib/record.ex">>,
<<"lib/elixir/lib/record/backend.ex">>,
<<"lib/elixir/lib/record/deprecated.ex">>,
<<"lib/elixir/lib/macro.ex">>,
<<"lib/elixir/lib/exception.ex">>,
<<"lib/elixir/lib/code.ex">>,
<<"lib/elixir/lib/module/locals_tracker.ex">>,
<<"lib/elixir/lib/protocol.ex">>,
<<"lib/elixir/lib/stream/reducers.ex">>,
<<"lib/elixir/lib/enum.ex">>,
<<"lib/elixir/lib/inspect/algebra.ex">>,
<<"lib/elixir/lib/inspect.ex">>,
<<"lib/elixir/lib/range.ex">>,
<<"lib/elixir/lib/regex.ex">>,
<<"lib/elixir/lib/string.ex">>,
<<"lib/elixir/lib/string/chars.ex">>,
<<"lib/elixir/lib/collectable.ex">>,
<<"lib/elixir/lib/io.ex">>,
<<"lib/elixir/lib/path.ex">>,
<<"lib/elixir/lib/system.ex">>,
<<"lib/elixir/lib/kernel/typespec.ex">>,
<<"lib/elixir/lib/kernel/cli.ex">>,
<<"lib/elixir/lib/kernel/error_handler.ex">>,
<<"lib/elixir/lib/kernel/parallel_compiler.ex">>,
<<"lib/elixir/lib/kernel/lexical_tracker.ex">>
].
[<<"lib/elixir/lib/kernel.ex">>,
<<"lib/elixir/lib/macro/env.ex">>,
<<"lib/elixir/lib/keyword.ex">>,
<<"lib/elixir/lib/module.ex">>,
<<"lib/elixir/lib/list.ex">>,
<<"lib/elixir/lib/macro.ex">>,
<<"lib/elixir/lib/code.ex">>,
<<"lib/elixir/lib/exception.ex">>,
<<"lib/elixir/lib/module/locals_tracker.ex">>,
<<"lib/elixir/lib/protocol.ex">>,
<<"lib/elixir/lib/stream/reducers.ex">>,
<<"lib/elixir/lib/enum.ex">>,
<<"lib/elixir/lib/inspect/algebra.ex">>,
<<"lib/elixir/lib/inspect.ex">>,
<<"lib/elixir/lib/range.ex">>,
<<"lib/elixir/lib/regex.ex">>,
<<"lib/elixir/lib/string.ex">>,
<<"lib/elixir/lib/string/chars.ex">>,
<<"lib/elixir/lib/io.ex">>,
<<"lib/elixir/lib/path.ex">>,
<<"lib/elixir/lib/system.ex">>,
<<"lib/elixir/lib/kernel/typespec.ex">>,
<<"lib/elixir/lib/kernel/cli.ex">>,
<<"lib/elixir/lib/kernel/error_handler.ex">>,
<<"lib/elixir/lib/kernel/parallel_compiler.ex">>,
<<"lib/elixir/lib/kernel/lexical_tracker.ex">>].
binary_to_path({ModuleName, Binary}, CompilePath) ->
Path = filename:join(CompilePath, atom_to_list(ModuleName) ++ ".beam"),
+148 -73
View File
@@ -10,19 +10,25 @@
-include("elixir.hrl").
-import(ordsets, [is_element/2]).
-define(atom, 'Elixir.Atom').
-define(float, 'Elixir.Float').
-define(io, 'Elixir.IO').
-define(integer, 'Elixir.Integer').
-define(kernel, 'Elixir.Kernel').
-define(list, 'Elixir.List').
-define(map, 'Elixir.Map').
-define(node, 'Elixir.Node').
-define(process, 'Elixir.Process').
-define(string, 'Elixir.String').
-define(system, 'Elixir.System').
-define(tuple, 'Elixir.Tuple').
default_functions() ->
[ {?kernel, elixir_imported_functions()} ].
[{?kernel, elixir_imported_functions()}].
default_macros() ->
[ {?kernel, elixir_imported_macros()} ].
[{?kernel, elixir_imported_macros()}].
default_requires() ->
[ 'Elixir.Kernel', 'Elixir.Kernel.Typespec' ].
['Elixir.Kernel', 'Elixir.Kernel.Typespec'].
find_import(Meta, Name, Arity, E) ->
Tuple = {Name, Arity},
@@ -48,7 +54,7 @@ import_function(Meta, Name, Arity, E) ->
{function, Receiver} ->
elixir_lexical:record_import(Receiver, ?m(E, lexical_tracker)),
elixir_locals:record_import(Tuple, Receiver, ?m(E, module), ?m(E, function)),
remote_function(Receiver, Name, Arity, E);
remote_function(Meta, Receiver, Name, Arity, E);
{macro, _Receiver} ->
false;
{import, Receiver} ->
@@ -62,17 +68,19 @@ import_function(Meta, Name, Arity, E) ->
end
end.
require_function(_Meta, Receiver, Name, Arity, E) ->
require_function(Meta, Receiver, Name, Arity, E) ->
case is_element({Name, Arity}, get_optional_macros(Receiver)) of
true -> false;
false -> remote_function(Receiver, Name, Arity, E)
false -> remote_function(Meta, Receiver, Name, Arity, E)
end.
remote_function(Receiver, Name, Arity, E) ->
remote_function(Meta, Receiver, Name, Arity, E) ->
check_deprecation(Meta, Receiver, Name, Arity, E),
elixir_lexical:record_remote(Receiver, ?m(E, lexical_tracker)),
case inline(Receiver, Name, Arity) of
{AR, AN} -> {remote, AR, AN, Arity};
false -> {remote, Receiver, Name, Arity}
false -> {remote, Receiver, Name, Arity}
end.
%% Dispatches
@@ -82,8 +90,8 @@ dispatch_import(Meta, Name, Args, E, Callback) ->
case expand_import(Meta, {Name, Arity}, Args, E, []) of
{ok, Receiver, Quoted} ->
expand_quoted(Meta, Receiver, Name, Arity, Quoted, E);
{ok, Receiver} ->
elixir_exp:expand({{'.', [], [Receiver, Name]}, Meta, Args}, E);
{ok, Receiver, NewName, NewArgs} ->
elixir_exp:expand({{'.', [], [Receiver, NewName]}, Meta, NewArgs}, E);
error ->
Callback()
end.
@@ -140,32 +148,30 @@ do_expand_import(Meta, {Name, Arity} = Tuple, Args, Module, E, Result) ->
elixir_lexical:record_import(Receiver, ?m(E, lexical_tracker)),
elixir_locals:record_import(Tuple, Receiver, Module, ?m(E, function)),
case rewrite(Receiver, Name, Args, Arity) of
{ok, AR, AN, AA} ->
{ok, AR, {{'.', [], [AR, AN]}, [], AA}};
false ->
{ok, Receiver}
{ok, _, _, _} = Res -> Res;
false -> {ok, Receiver, Name, Args}
end;
{macro, Receiver} ->
check_deprecation(Meta, Receiver, Name, Arity, E),
elixir_lexical:record_import(Receiver, ?m(E, lexical_tracker)),
elixir_locals:record_import(Tuple, Receiver, Module, ?m(E, function)),
{ok, Receiver, expand_macro_named(Meta, Receiver, Name, Arity, Args, E)};
{import, Receiver} ->
case expand_require([{require,false}|Meta], Receiver, Tuple, Args, E) of
{ok, _, _} = Response -> Response;
error -> {ok, Receiver}
error -> {ok, Receiver, Name, Args}
end;
false when Module == ?kernel ->
case rewrite(Module, Name, Args, Arity) of
{ok, AR, AN, AA} ->
{ok, AR, {{'.', [], [AR, AN]}, [], AA}};
false ->
error
{ok, _, _, _} = Res -> Res;
false -> error
end;
false ->
error
end.
expand_require(Meta, Receiver, {Name, Arity} = Tuple, Args, E) ->
check_deprecation(Meta, Receiver, Name, Arity, E),
Module = ?m(E, module),
case is_element(Tuple, get_optional_macros(Receiver)) of
@@ -331,27 +337,12 @@ elixir_imported_macros() ->
error:undef -> []
end.
rewrite(?kernel, atom_to_binary, [Arg], _) ->
rewrite(?atom, to_string, [Arg], _) ->
{ok, erlang, atom_to_binary, [Arg, utf8]};
rewrite(?kernel, binary_to_atom, [Arg], _) ->
{ok, erlang, binary_to_atom, [Arg, utf8]};
rewrite(?kernel, binary_to_existing_atom, [Arg], _) ->
{ok, erlang, binary_to_existing_atom, [Arg, utf8]};
rewrite(?kernel, elem, [Tuple, Index], _) ->
{ok, erlang, element, [increment(Index), Tuple]};
rewrite(?kernel, set_elem, [Tuple, Index, Value], _) ->
rewrite(?kernel, put_elem, [Tuple, Index, Value], _) ->
{ok, erlang, setelement, [increment(Index), Tuple, Value]};
rewrite(?process, monitor, [Arg], _) ->
{ok, erlang, monitor, [process, Arg]};
rewrite(?tuple, insert_at, [Tuple, Index, Term], _) ->
{ok, erlang, insert_element, [increment(Index), Tuple, Term]};
rewrite(?tuple, delete_at, [Tuple, Index], _) ->
{ok, erlang, delete_element, [increment(Index), Tuple]};
rewrite(?tuple, duplicate, [Data, Size], _) ->
{ok, erlang, make_tuple, [Size, Data]};
rewrite(?map, 'has_key?', [Map, Key], _) ->
{ok, maps, is_key, [Key, Map]};
rewrite(?map, fetch, [Map, Key], _) ->
@@ -360,6 +351,19 @@ rewrite(?map, put, [Map, Key, Value], _) ->
{ok, maps, put, [Key, Value, Map]};
rewrite(?map, delete, [Map, Key], _) ->
{ok, maps, remove, [Key, Map]};
rewrite(?process, monitor, [Arg], _) ->
{ok, erlang, monitor, [process, Arg]};
rewrite(?string, to_atom, [Arg], _) ->
{ok, erlang, binary_to_atom, [Arg, utf8]};
rewrite(?string, to_existing_atom, [Arg], _) ->
{ok, erlang, binary_to_existing_atom, [Arg, utf8]};
rewrite(?tuple, insert_at, [Tuple, Index, Term], _) ->
{ok, erlang, insert_element, [increment(Index), Tuple, Term]};
rewrite(?tuple, delete_at, [Tuple, Index], _) ->
{ok, erlang, delete_element, [increment(Index), Tuple]};
rewrite(?tuple, duplicate, [Data, Size], _) ->
{ok, erlang, make_tuple, [Size, Data]};
rewrite(Receiver, Name, Args, Arity) ->
case inline(Receiver, Name, Arity) of
{AR, AN} -> {ok, AR, AN, Args};
@@ -371,6 +375,22 @@ increment(Number) when is_number(Number) ->
increment(Other) ->
{{'.', [], [erlang, '+']}, [], [Other, 1]}.
inline(?atom, to_char_list, 1) -> {erlang, atom_to_list};
inline(?io, iodata_length, 1) -> {erlang, iolist_size};
inline(?io, iodata_to_binary, 1) -> {erlang, iolist_to_binary};
inline(?integer, to_string, 1) -> {erlang, integer_to_binary};
inline(?integer, to_string, 2) -> {erlang, integer_to_binary};
inline(?integer, to_char_list, 1) -> {erlang, integer_to_list};
inline(?integer, to_char_list, 2) -> {erlang, integer_to_list};
inline(?float, to_string, 1) -> {erlang, float_to_binary};
inline(?float, to_char_list, 1) -> {erlang, float_to_list};
inline(?list, to_atom, 1) -> {erlang, list_to_atom};
inline(?list, to_existing_atom, 1) -> {erlang, list_to_existing_atom};
inline(?list, to_float, 1) -> {erlang, list_to_float};
inline(?list, to_integer, 1) -> {erlang, list_to_integer};
inline(?list, to_integer, 2) -> {erlang, list_to_integer};
inline(?list, to_tuple, 1) -> {erlang, list_to_tuple};
inline(?kernel, '+', 2) -> {erlang, '+'};
inline(?kernel, '-', 2) -> {erlang, '-'};
inline(?kernel, '+', 1) -> {erlang, '+'};
@@ -392,26 +412,12 @@ inline(?kernel, '!==', 2) -> {erlang, '=/='};
inline(?kernel, abs, 1) -> {erlang, abs};
inline(?kernel, apply, 2) -> {erlang, apply};
inline(?kernel, apply, 3) -> {erlang, apply};
inline(?kernel, atom_to_list, 1) -> {erlang, atom_to_list};
inline(?kernel, binary_part, 3) -> {erlang, binary_part};
inline(?kernel, binary_to_float, 1) -> {erlang, binary_to_float};
inline(?kernel, binary_to_float, 2) -> {erlang, binary_to_float};
inline(?kernel, binary_to_integer, 1) -> {erlang, binary_to_integer};
inline(?kernel, binary_to_integer, 2) -> {erlang, binary_to_integer};
inline(?kernel, bit_size, 1) -> {erlang, bit_size};
inline(?kernel, bitstring_to_list, 1) -> {erlang, bitstring_to_list};
inline(?kernel, byte_size, 1) -> {erlang, byte_size};
inline(?kernel, 'div', 2) -> {erlang, 'div'};
inline(?kernel, exit, 1) -> {erlang, exit};
inline(?kernel, float_to_binary, 1) -> {erlang, float_to_binary};
inline(?kernel, float_to_list, 1) -> {erlang, float_to_list};
inline(?kernel, hd, 1) -> {erlang, hd};
inline(?kernel, integer_to_binary, 1) -> {erlang, integer_to_binary};
inline(?kernel, integer_to_binary, 2) -> {erlang, integer_to_binary};
inline(?kernel, integer_to_list, 1) -> {erlang, integer_to_list};
inline(?kernel, integer_to_list, 2) -> {erlang, integer_to_list};
inline(?kernel, iodata_length, 1) -> {erlang, iolist_size};
inline(?kernel, iodata_to_binary, 1) -> {erlang, iolist_to_binary};
inline(?kernel, is_atom, 1) -> {erlang, is_atom};
inline(?kernel, is_binary, 1) -> {erlang, is_binary};
inline(?kernel, is_bitstring, 1) -> {erlang, is_bitstring};
@@ -428,13 +434,6 @@ inline(?kernel, is_port, 1) -> {erlang, is_port};
inline(?kernel, is_reference, 1) -> {erlang, is_reference};
inline(?kernel, is_tuple, 1) -> {erlang, is_tuple};
inline(?kernel, length, 1) -> {erlang, length};
inline(?kernel, list_to_atom, 1) -> {erlang, list_to_atom};
inline(?kernel, list_to_bitstring, 1) -> {erlang, list_to_bitstring};
inline(?kernel, list_to_existing_atom, 1) -> {erlang, list_to_existing_atom};
inline(?kernel, list_to_float, 1) -> {erlang, list_to_float};
inline(?kernel, list_to_integer, 1) -> {erlang, list_to_integer};
inline(?kernel, list_to_integer, 2) -> {erlang, list_to_integer};
inline(?kernel, list_to_tuple, 1) -> {erlang, list_to_tuple};
inline(?kernel, make_ref, 0) -> {erlang, make_ref};
inline(?kernel, map_size, 1) -> {erlang, map_size};
inline(?kernel, max, 2) -> {erlang, max};
@@ -456,7 +455,19 @@ inline(?kernel, throw, 1) -> {erlang, throw};
inline(?kernel, tl, 1) -> {erlang, tl};
inline(?kernel, trunc, 1) -> {erlang, trunc};
inline(?kernel, tuple_size, 1) -> {erlang, tuple_size};
inline(?kernel, tuple_to_list, 1) -> {erlang, tuple_to_list};
inline(?map, keys, 1) -> {maps, keys};
inline(?map, merge, 2) -> {maps, merge};
inline(?map, size, 1) -> {maps, size};
inline(?map, values, 1) -> {maps, values};
inline(?map, to_list, 1) -> {maps, to_list};
inline(?node, spawn, 2) -> {erlang, spawn};
inline(?node, spawn, 3) -> {erlang, spawn_opt};
inline(?node, spawn, 4) -> {erlang, spawn};
inline(?node, spawn, 5) -> {erlang, spawn_opt};
inline(?node, spawn_link, 2) -> {erlang, spawn_link};
inline(?node, spawn_link, 4) -> {erlang, spawn_link};
inline(?process, exit, 2) -> {erlang, exit};
inline(?process, spawn, 2) -> {erlang, spawn_opt};
@@ -466,21 +477,85 @@ inline(?process, demonitor, 2) -> {erlang, demonitor};
inline(?process, link, 1) -> {erlang, link};
inline(?process, unlink, 1) -> {erlang, unlink};
inline(?node, spawn, 2) -> {erlang, spawn};
inline(?node, spawn, 3) -> {erlang, spawn_opt};
inline(?node, spawn, 4) -> {erlang, spawn};
inline(?node, spawn, 5) -> {erlang, spawn_opt};
inline(?node, spawn_link, 2) -> {erlang, spawn_link};
inline(?node, spawn_link, 4) -> {erlang, spawn_link};
inline(?node, spawn_monitor, 2) -> {erlang, spawn_monitor};
inline(?node, spawn_monitor, 4) -> {erlang, spawn_monitor};
inline(?string, to_float, 1) -> {erlang, binary_to_float};
inline(?string, to_integer, 1) -> {erlang, binary_to_integer};
inline(?string, to_integer, 2) -> {erlang, binary_to_integer};
inline(?system, stacktrace, 0) -> {erlang, get_stacktrace};
inline(?map, keys, 1) -> {maps, keys};
inline(?map, merge, 2) -> {maps, merge};
inline(?map, size, 1) -> {maps, size};
inline(?map, values, 1) -> {maps, values};
inline(?map, to_list, 1) -> {maps, to_list};
inline(?tuple, to_list, 1) -> {erlang, tuple_to_list};
inline(_, _, _) -> false.
check_deprecation(Meta, Receiver, Name, Arity, #{file := File}) ->
case deprecation(Receiver, Name, Arity) of
false -> ok;
Message ->
Warning = deprecation_message(Receiver, Name, Arity, Message),
elixir_errors:warn(?line(Meta), File, Warning)
end.
deprecation_message(Receiver, '__using__', _Arity, Message) ->
Warning = io_lib:format("use ~s is deprecated", [elixir_aliases:inspect(Receiver)]),
deprecation_message(Warning, Message);
deprecation_message(Receiver, Name, Arity, Message) ->
Warning = io_lib:format("~s.~s/~B is deprecated",
[elixir_aliases:inspect(Receiver), Name, Arity]),
deprecation_message(Warning, Message).
deprecation_message(Warning, Message) ->
case Message of
true -> Warning;
Message -> Warning ++ ", " ++ Message
end.
% deprecation('Elixir.Application.Behaviour', '__using__', _) ->
% "use Application instead";
% deprecation('Elixir.Dict.Behaviour', '__using__', _) ->
% "use Dict instead";
% deprecation('Elixir.GenEvent.Behaviour', '__using__', _) ->
% "use GenEvent instead";
% deprecation('Elixir.GenServer.Behaviour', '__using__', _) ->
% "use GenServer instead";
% deprecation('Elixir.Supervisor.Behaviour', '__using__', _) ->
% "use Supervisor instead";
% deprecation('Elixir.Kernel', raise, 3) ->
% "use reraise/2 instead";
% deprecation('Elixir.Kernel', defexception, 3) ->
% "use defexception/1 instead";
% deprecation('Elixir.List', 'from_char_data', 1) ->
% "use String.to_char_list instead";
% deprecation('Elixir.List', 'from_char_data!', 1) ->
% "use String.to_char_list instead";
% deprecation('Elixir.String', 'from_char_data', 1) ->
% "use List.to_string instead";
% deprecation('Elixir.String', 'from_char_data!', 1) ->
% "use List.to_string instead";
deprecation('Elixir.Kernel', is_record, _) ->
"use Record.record? instead";
deprecation('Elixir.Kernel', defrecord, _) ->
"use structs instead";
deprecation('Elixir.Kernel', defrecordp, _) ->
"use Record.defrecordp/3 instead";
deprecation('Elixir.Process', spawn, 1) ->
"use the one in Kernel instead";
deprecation('Elixir.Process', spawn, 3) ->
"use the one in Kernel instead";
deprecation('Elixir.Process', spawn_link, _) ->
"use the one in Kernel instead";
deprecation('Elixir.Process', spawn_monitor, _) ->
"use the one in Kernel instead";
deprecation('Elixir.Process', self, 0) ->
"use the one in Kernel instead";
deprecation('Elixir.Process', send, 2) ->
"use the one in Kernel instead";
deprecation('Elixir.Record', defmacros, 4) ->
true;
deprecation('Elixir.Record', deftypes, 3) ->
true;
deprecation('Elixir.Record', deffunctions, 2) ->
true;
deprecation('Elixir.Mix', project, 0) ->
"use Mix.Project.config/0 instead";
deprecation(_, _, _) ->
false.
+15 -15
View File
@@ -3,8 +3,7 @@
-module(elixir_errors).
-export([compile_error/3, compile_error/4,
form_error/4, parse_error/4, warn/2, warn/3,
handle_file_warning/2, handle_file_warning/3, handle_file_error/2,
deprecation/3, deprecation/4]).
handle_file_warning/2, handle_file_warning/3, handle_file_error/2]).
-include("elixir.hrl").
-type line_or_meta() :: integer() | list().
@@ -21,7 +20,7 @@ warn(Warning) ->
warn(Caller, Warning) ->
warn([Caller, "warning: ", Warning]).
warn(Line, File, Warning) ->
warn(Line, File, Warning) when is_integer(Line) ->
warn(file_format(Line, File, "warning: " ++ Warning)).
%% Raised during expansion/translation/compilation.
@@ -56,23 +55,24 @@ parse_error(Meta, File, <<"syntax error before: ">>, <<"'end'">>) ->
raise(Meta, File, 'Elixir.SyntaxError', <<"unexpected token: end">>);
%% Binaries are wrapped in [<<...>>], so we need to unwrap them
parse_error(Meta, File, Error, <<"[<<", Token/binary>>) when is_binary(Error) ->
Rest = binary_part(Token, 0, byte_size(Token) - 3),
Message = <<Error / binary, Rest / binary >>,
raise(Meta, File, 'Elixir.SyntaxError', Message);
parse_error(Meta, File, Error, <<"[", _/binary>> = Full) when is_binary(Error) ->
Rest =
case binary:split(Full, <<"<<">>) of
[Lead, Token] ->
case binary:split(Token, <<">>">>) of
[Part, _] when Lead == <<$[>> -> Part;
_ -> <<$">>
end;
[_] ->
<<$">>
end,
raise(Meta, File, 'Elixir.SyntaxError', <<Error/binary, Rest/binary >>);
%% Everything else is fine as is
parse_error(Meta, File, Error, Token) when is_binary(Error), is_binary(Token) ->
Message = <<Error / binary, Token / binary >>,
raise(Meta, File, 'Elixir.SyntaxError', Message).
%% Shows a deprecation message
deprecation(Meta, File, Message) -> deprecation(Meta, File, Message, []).
deprecation(Meta, File, Message, Args) ->
io:format(file_format(?line(Meta), File, io_lib:format(Message, Args))).
%% Handle warnings and errors (called during module compilation)
%% Ignore on bootstrap
@@ -177,7 +177,7 @@ raise(Line, File, Kind, Message) when is_integer(Line), is_binary(File) ->
ok -> ok
end,
Stacktrace = erlang:get_stacktrace(),
Exception = Kind:new([{description, Message}, {file, File}, {line, Line}]),
Exception = Kind:exception([{description, Message}, {file, File}, {line, Line}]),
erlang:raise(error, Exception, tl(Stacktrace)).
file_format(0, File, Message) when is_binary(File) ->
+5 -27
View File
@@ -169,22 +169,23 @@ expand({quote, Meta, [KV, Do]}, E) when is_list(Do) ->
false -> compile_error(Meta, E#elixir_scope.file, "missing do keyword in quote")
end,
ValidOpts = [hygiene, context, var_context, location, line, unquote, bind_quoted],
ValidOpts = [hygiene, context, location, line, unquote, bind_quoted],
{EKV, ET} = expand_opts(Meta, quote, ValidOpts, KV, E),
Hygiene = case lists:keyfind(hygiene, 1, EKV) of
{hygiene, List} when is_list(List) ->
elixir_errors:warn(?line(Meta), ?m(E, file), "the :hygiene option in quote is deprecated"),
List;
false ->
[]
end,
Context = case lists:keyfind(context, 1, EKV) of
{context, Atom} when is_atom(Atom) ->
Atom;
{context, Ctx} when is_atom(Ctx) and (Ctx /= nil) ->
Ctx;
{context, Ctx} ->
compile_error(Meta, ?m(E, file), "invalid :context for quote, "
"expected a compile time atom or alias, got: ~ts", ['Elixir.Kernel':inspect(Ctx)]);
"expected non nil compile time atom or alias, got: ~ts", ['Elixir.Kernel':inspect(Ctx)]);
false ->
case ?m(E, module) of
nil -> 'Elixir';
@@ -255,10 +256,6 @@ expand({'try', Meta, [KV]}, E) ->
%% Comprehensions
expand({Kind, Meta, Args}, E) when is_list(Args), (Kind == lc) orelse (Kind == bc) ->
elixir_errors:deprecation(Meta, ?m(E, file), "~ts is deprecated, please use for comprehensions instead", [Kind]),
expand_comprehension(Meta, Kind, Args, E);
expand({for, Meta, Args}, E) when is_list(Args) ->
elixir_for:expand(Meta, Args, E);
@@ -539,25 +536,6 @@ expand_as({as, Other}, Meta, _IncludeByDefault, _Ref, E) ->
compile_error(Meta, ?m(E, file),
"invalid value for keyword :as, expected an alias, got: ~ts", ['Elixir.Macro':to_string(Other)]).
%% Comprehensions
expand_comprehension(Meta, Kind, Args, E) ->
case elixir_utils:split_last(Args) of
{Cases, [{do,Expr}]} ->
{ECases, EC} = lists:mapfoldl(fun expand_comprehension_clause/2, E, Cases),
{EExpr, _} = expand(Expr, EC),
{{Kind, Meta, ECases ++ [[{do,EExpr}]]}, E};
_ ->
compile_error(Meta, ?m(E, file), "missing do keyword in comprehension ~ts", [Kind])
end.
expand_comprehension_clause({Gen, Meta, [Left, Right]}, E) when Gen == inbits; Gen == inlist ->
{ERight, ER} = expand(Right, E),
{ELeft, EL} = elixir_exp_clauses:match(fun expand/2, Left, E),
{{Gen, Meta, [ELeft, ERight]}, elixir_env:mergev(EL, ER)};
expand_comprehension_clause(X, E) ->
expand(X, E).
%% Assertions
assert_no_match_or_guard_scope(Meta, Kind, E) ->
+1 -7
View File
@@ -141,13 +141,7 @@ expand_rescue(Arg, E) ->
normalize_rescue({'_', _, Atom} = N) when is_atom(Atom) -> N;
normalize_rescue(Atom) when is_atom(Atom) -> [Atom];
normalize_rescue(Other) ->
is_list(Other)
andalso lists:all(fun is_var_or_atom/1, Other)
andalso Other.
is_var_or_atom({Name, _, Atom}) when is_atom(Name), is_atom(Atom) -> true;
is_var_or_atom(Atom) when is_atom(Atom) -> true;
is_var_or_atom(_) -> false.
is_list(Other) andalso lists:all(fun is_atom/1, Other) andalso Other.
%% Expansion helpers
-2
View File
@@ -205,8 +205,6 @@ special_form('unquote_splicing', 1) -> true;
special_form('fn', _) -> true;
special_form('super', _) -> true;
special_form('for', _) -> true;
special_form('bc', _) -> true;
special_form('lc', _) -> true;
special_form('case', 2) -> true;
special_form('try', 2) -> true;
special_form('receive', 1) -> true;
+17 -13
View File
@@ -135,11 +135,11 @@ eval_form(Line, Module, Block, Vars, E) ->
%% Return the form with exports and function declarations.
functions_form(Line, File, Module, BaseAll, BaseExport, Def, Defmacro, BaseFunctions) ->
Info = add_info_function(Line, File, Module, BaseExport, Def, Defmacro),
{InfoSpec, Info} = add_info_function(Line, File, Module, BaseExport, Def, Defmacro),
All = [{'__info__', 1}|BaseAll],
Export = [{'__info__', 1}|BaseExport],
Functions = [Info|BaseFunctions],
Functions = [InfoSpec,Info|BaseFunctions],
{All, [
{attribute, Line, export, lists:sort(Export)} | Functions
@@ -259,9 +259,8 @@ load_form(Line, Forms, Opts, #{file := File} = E) ->
end).
check_module_availability(Line, File, Module) ->
Reserved = ['Elixir.Atom', 'Elixir.BitString', 'Elixir.Function',
'Elixir.PID', 'Elixir.Reference', 'Elixir.Any',
'Elixir.Elixir', 'Elixir'],
Reserved = ['Elixir.Any', 'Elixir.BitString', 'Elixir.Function', 'Elixir.PID',
'Elixir.Reference', 'Elixir.Elixir', 'Elixir'],
case lists:member(Module, Reserved) of
true -> elixir_errors:handle_file_error(File, {Line, ?MODULE, {module_reserved, Module}});
@@ -313,14 +312,19 @@ add_info_function(Line, File, Module, Export, Def, Defmacro) ->
elixir_errors:form_error(Line, File, ?MODULE, {internal_function_overridden, Pair});
false ->
Docs = elixir_compiler:get_opt(docs),
{function, 0, '__info__', 1, [
functions_clause(Def),
macros_clause(Defmacro),
docs_clause(Module, Docs),
moduledoc_clause(Line, Module, Docs),
module_clause(Module),
else_clause()
]}
{
{attribute, Line, spec, {{'__info__', 1},
[{type, Line, 'fun', [{type, Line, product, [ {type, Line, atom, []}]}, {type, Line, term, []} ]}]
}},
{function, 0, '__info__', 1, [
functions_clause(Def),
macros_clause(Defmacro),
docs_clause(Module, Docs),
moduledoc_clause(Line, Module, Docs),
module_clause(Module),
else_clause()
]}
}
end.
functions_clause(Def) ->
+12 -11
View File
@@ -23,10 +23,10 @@ Nonterminals
.
Terminals
identifier kw_identifier kw_identifier_string bracket_identifier
identifier kw_identifier kw_identifier_safe kw_identifier_unsafe bracket_identifier
paren_identifier do_identifier block_identifier
fn 'end' aliases
number signed_number atom atom_string bin_string list_string sigil
number signed_number atom atom_safe atom_unsafe bin_string list_string sigil
dot_call_op op_identifier
comp_op at_op unary_op and_op or_op arrow_op match_op in_op in_match_op type_op
dual_op add_op mult_op exp_op two_op pipe_op stab_op when_op assoc_op
@@ -45,7 +45,7 @@ Expect 2.
Left 5 do.
Right 10 stab_op_eol. %% ->
Left 20 ','.
Left 40 in_match_op_eol. %% <-, inlist, inbits, \\ (allowed in matches along =)
Left 40 in_match_op_eol. %% <-, \\ (allowed in matches along =)
Right 50 when_op_eol. %% when
Right 60 type_op_eol. %% ::
Right 70 pipe_op_eol. %% |
@@ -208,7 +208,8 @@ access_expr -> max_expr : '$1'.
%% Aliases and properly formed calls. Used by map_expr.
max_expr -> atom : ?exprs('$1').
max_expr -> atom_string : build_atom_string('$1').
max_expr -> atom_safe : build_quoted_atom('$1', true).
max_expr -> atom_unsafe : build_quoted_atom('$1', false).
max_expr -> parens_call call_args_parens : build_identifier('$1', '$2').
max_expr -> parens_call call_args_parens call_args_parens : build_nested_parens('$1', '$2', '$3').
max_expr -> dot_alias : '$1'.
@@ -410,7 +411,6 @@ container_expr -> no_parens_expr : throw_no_parens_many_strict('$1').
container_args_base -> container_expr : ['$1'].
container_args_base -> container_args_base ',' container_expr : ['$3'|'$1'].
container_args -> kw : ['$1'].
container_args -> container_args_base : lists:reverse('$1').
container_args -> container_args_base ',' : lists:reverse('$1').
container_args -> container_args_base ',' kw : lists:reverse(['$3'|'$1']).
@@ -428,8 +428,10 @@ call_args_parens -> open_paren call_args_parens_base ',' kw close_paren : revers
kw_eol -> kw_identifier : ?exprs('$1').
kw_eol -> kw_identifier eol : ?exprs('$1').
kw_eol -> kw_identifier_string : build_atom_string('$1').
kw_eol -> kw_identifier_string eol : build_atom_string('$1').
kw_eol -> kw_identifier_safe : build_quoted_atom('$1', true).
kw_eol -> kw_identifier_safe eol : build_quoted_atom('$1', true).
kw_eol -> kw_identifier_unsafe : build_quoted_atom('$1', false).
kw_eol -> kw_identifier_unsafe eol : build_quoted_atom('$1', false).
kw_base -> kw_eol container_expr : [{'$1', '$2'}].
kw_base -> kw_base ',' kw_eol container_expr : [{'$3', '$4'}|'$1'].
@@ -513,7 +515,6 @@ Erlang code.
-define(line(Node), element(2, Node)).
-define(exprs(Node), element(3, Node)).
-define(rearrange_uop(Op), (Op == 'not' orelse Op == '!')).
-define(is_atom_string(Atom), (Atom == atom_string orelse Atom == kw_identifier_string)).
%% The following directive is needed for (significantly) faster
%% compilation of the generated .erl file by the HiPE compiler
@@ -631,11 +632,11 @@ build_list_string({list_string, _Line, [H]}) when is_binary(H) ->
elixir_utils:characters_to_list(H);
build_list_string({list_string, Line, Args}) ->
Meta = meta(Line),
{{'.', Meta, ['Elixir.List', 'from_char_data!']}, Meta, [{'<<>>', Meta, string_parts(Args)}]}.
{{'.', Meta, ['Elixir.String', to_char_list]}, Meta, [{'<<>>', Meta, string_parts(Args)}]}.
build_atom_string({Atom, _Line, Safe, [H]}) when ?is_atom_string(Atom) andalso is_binary(H) ->
build_quoted_atom({_, _Line, [H]}, Safe) when is_binary(H) ->
Op = binary_to_atom_op(Safe), erlang:Op(H, utf8);
build_atom_string({Atom, Line, Safe, Args}) when ?is_atom_string(Atom) ->
build_quoted_atom({_, Line, Args}, Safe) ->
Meta = meta(Line),
{{'.', Meta, [erlang, binary_to_atom_op(Safe)]}, Meta, [{'<<>>', Meta, string_parts(Args)}, utf8]}.
+14 -10
View File
@@ -238,8 +238,11 @@ tokenize([$:,H|T] = Original, Line, Scope, Tokens) when ?is_quote(H) ->
case elixir_interpolation:extract(Line, Scope, true, T, H) of
{NewLine, Parts, Rest} ->
Unescaped = unescape_tokens(Parts),
ExistingAtomsOnly = Scope#elixir_tokenizer.existing_atoms_only,
tokenize(Rest, NewLine, Scope, [{atom_string, Line, ExistingAtomsOnly, Unescaped}|Tokens]);
Key = case Scope#elixir_tokenizer.existing_atoms_only of
true -> atom_safe;
false -> atom_unsafe
end,
tokenize(Rest, NewLine, Scope, [{Key, Line, Unescaped}|Tokens]);
{error, Reason} ->
interpolation_error(Reason, Original, Tokens, " (for atom starting at line ~B)", [Line])
end;
@@ -453,7 +456,8 @@ tokenize([Space, Sign, NotMarker|T], Line, Scope, [{Identifier, _, _} = H|Tokens
?dual_op(Sign),
?is_horizontal_space(Space),
not(?is_space(NotMarker)),
NotMarker /= $(, NotMarker /= $+, NotMarker /= $-, NotMarker /= $>,
NotMarker /= $(, NotMarker /= $[, NotMarker /= $<, NotMarker /= ${, %% containers
NotMarker /= $%, NotMarker /= $+, NotMarker /= $-, NotMarker /= $/, NotMarker /= $>, %% operators
Identifier == identifier ->
Rest = [NotMarker|T],
tokenize(Rest, Line, Scope, [{dual_op, Line, list_to_atom([Sign])}, setelement(1, H, op_identifier)|Tokens]);
@@ -503,8 +507,11 @@ handle_strings(T, Line, H, Scope, Tokens) ->
interpolation_error(Reason, [H|T], Tokens, " (for string starting at line ~B)", [Line]);
{NewLine, Parts, [$:|Rest]} when ?is_space(hd(Rest)) ->
Unescaped = unescape_tokens(Parts),
ExistingAtomsOnly = Scope#elixir_tokenizer.existing_atoms_only,
tokenize(Rest, NewLine, Scope, [{kw_identifier_string, Line, ExistingAtomsOnly, Unescaped}|Tokens]);
Key = case Scope#elixir_tokenizer.existing_atoms_only of
true -> kw_identifier_safe;
false -> kw_identifier_unsafe
end,
tokenize(Rest, NewLine, Scope, [{Key, Line, Unescaped}|Tokens]);
{NewLine, Parts, Rest} ->
Token = {string_type(H), Line, unescape_tokens(Parts)},
tokenize(Rest, NewLine, Scope, [Token|Tokens])
@@ -626,9 +633,8 @@ extract_heredoc(Line0, Rest0, Marker) ->
{error, {ErrorLine, io_lib:format(Message, [Terminator, Line0]), []}}
end;
error ->
Terminator = [Marker, Marker, Marker],
Message = "heredoc start ~ts must be followed by a new line",
{error, {Line0, io_lib:format(Message, [Terminator]), []}}
Message = "heredoc start must be followed by a new line after ",
{error, {Line0, io_lib:format(Message, []), [Marker, Marker, Marker]}}
end.
%% Remove spaces from heredoc based on the position of the final quotes.
@@ -928,8 +934,6 @@ keyword('or') -> or_op;
keyword('xor') -> or_op;
keyword('when') -> when_op;
keyword('in') -> in_op;
keyword('inlist') -> in_match_op;
keyword('inbits') -> in_match_op;
% Block keywords
keyword('after') -> block;
+14 -45
View File
@@ -2,7 +2,7 @@
%% Expects the tree to be expanded.
-module(elixir_translator).
-export([translate/2, translate_arg/3, translate_args/2, translate_block/3]).
-import(elixir_scope, [mergev/2, mergec/2, mergef/2]).
-import(elixir_scope, [mergev/2, mergec/2]).
-import(elixir_errors, [compile_error/3, compile_error/4]).
-include("elixir.hrl").
@@ -129,9 +129,6 @@ translate({'receive', Meta, [KV]}, #elixir_scope{return=Return} = RS) when is_li
%% Comprehensions
translate({Kind, Meta, Args}, S) when is_list(Args), (Kind == lc) orelse (Kind == bc) ->
translate_comprehension(Meta, Kind, Args, S);
translate({for, Meta, Args}, S) when is_list(Args) ->
elixir_for:translate(Meta, Args, S);
@@ -229,11 +226,19 @@ translate({{'.', _, [Left, Right]}, Meta, Args}, S)
true ->
{Var, _, SV} = elixir_scope:build_var('_', SC),
TVar = {var, Line, Var},
TMap = {tuple, Line, [
{atom, Line, 'Elixir.KeyError'},
{atom, Line, '__exception__'},
TRight,
TVar]},
TMap = {map, Line, [
{map_field_assoc, Line,
{atom, Line, '__struct__'},
{atom, Line, 'Elixir.KeyError'}},
{map_field_assoc, Line,
{atom, Line, '__exception__'},
{atom, Line, 'true'}},
{map_field_assoc, Line,
{atom, Line, key},
{atom, Line, TRight}},
{map_field_assoc, Line,
{atom, Line, term},
TVar}]},
%% TODO There is a bug in dialyzer that makes it fail on
%% empty maps. We work around the bug below by using
@@ -371,42 +376,6 @@ handles_no_return({'receive', _, [_]}) -> true;
handles_no_return({'__block__', _, [_|_]}) -> true;
handles_no_return(_) -> false.
%% Comprehensions
translate_comprehension(Meta, Kind, Args, S) ->
{Cases, [{do,Expr}]} = elixir_utils:split_last(Args),
{TCases, SC} = lists:mapfoldl(fun(C, Acc) -> translate_comprehension_clause(Meta, C, Acc) end, S, Cases),
{TExpr, SE} = translate_comprehension_do(Meta, Kind, Expr, SC),
{{Kind, ?line(Meta), TExpr, TCases}, mergef(S, SE)}.
translate_comprehension_do(_Meta, bc, {'<<>>', _, _} = Expr, S) ->
translate(Expr, S);
translate_comprehension_do(Meta, bc, _Expr, S) ->
compile_error(Meta, S#elixir_scope.file, "a bit comprehension expects a bit string << >> to be returned");
translate_comprehension_do(_Meta, _Kind, Expr, S) ->
translate(Expr, S).
translate_comprehension_clause(_Meta, {inbits, Meta, [{'<<>>', _, _} = Left, Right]}, S) ->
{TRight, SR} = translate(Right, S),
{TLeft, SL } = elixir_clauses:match(fun elixir_translator:translate/2, Left, SR),
{{b_generate, ?line(Meta), TLeft, TRight}, SL};
translate_comprehension_clause(_Meta, {inbits, Meta, [_Left, _Right]}, S) ->
compile_error(Meta, S#elixir_scope.file, "a bit comprehension expects a bit string << >> to be used in inbits generators");
translate_comprehension_clause(_Meta, {inlist, Meta, [Left, Right]}, S) ->
{TRight, SR} = translate(Right, S),
{TLeft, SL } = elixir_clauses:match(fun elixir_translator:translate/2, Left, SR),
{{generate, ?line(Meta), TLeft, TRight}, SL};
translate_comprehension_clause(Meta, X, S) ->
Line = ?line(Meta),
{TX, TS} = translate(X, S),
{BX, BS} = elixir_utils:convert_to_boolean(Line, TX, true, TS),
{{match, Line, {var, Line, '_'}, BX}, BS}.
%% Assertions
assert_module_scope(Meta, Kind, #elixir_scope{module=nil,file=File}) ->
+85 -117
View File
@@ -1,15 +1,17 @@
-module(elixir_try).
-export([clauses/4, format_error/1]).
-export([clauses/4]).
-include("elixir.hrl").
clauses(_Meta, Clauses, Return, S) ->
Catch = elixir_clauses:get_pairs('catch', Clauses),
Rescue = elixir_clauses:get_pairs(rescue, Clauses),
Transformer = fun(X, SAcc) ->
{TX, TS} = each_clause(X, Return, SAcc),
{TX, elixir_scope:mergec(S, TS)}
end,
lists:mapfoldl(Transformer, S, Rescue ++ Catch).
reduce_clauses(Rescue ++ Catch, [], S, Return, S).
reduce_clauses([H|T], Acc, SAcc, Return, S) ->
{TH, TS} = each_clause(H, Return, SAcc),
reduce_clauses(T, TH ++ Acc, elixir_scope:mergec(S, TS), Return, S);
reduce_clauses([], Acc, SAcc, _Return, _S) ->
{lists:reverse(Acc), SAcc}.
each_clause({'catch', Meta, Raw, Expr}, Return, S) ->
{Args, Guards} = elixir_clauses:extract_splat_guards(Raw),
@@ -17,37 +19,35 @@ each_clause({'catch', Meta, Raw, Expr}, Return, S) ->
Final = case Args of
[X] -> [throw, X, {'_', Meta, nil}];
[X,Y] -> [X, Y, {'_', Meta, nil}];
_ ->
elixir_errors:compile_error(Meta, S#elixir_scope.file, "too many arguments given for catch")
_ ->
elixir_errors:compile_error(Meta, S#elixir_scope.file,
"too many arguments given for catch")
end,
Condition = [{'{}', Meta, Final}],
elixir_clauses:clause(?line(Meta), fun elixir_translator:translate_args/2,
Condition, Expr, Guards, Return, S);
{TC, TS} = elixir_clauses:clause(?line(Meta), fun elixir_translator:translate_args/2,
Condition, Expr, Guards, Return, S),
{[TC], TS};
each_clause({rescue, Meta, [{in, _, [Left, Right]}], Expr}, Return, S) ->
case Left of
{'_', _, LAtom} when is_atom(LAtom) ->
{VarName, _, CS} = elixir_scope:build_var('_', S),
{Clause, _} = rescue_guards(Meta, {VarName, Meta, nil}, Right, S),
each_clause({'catch', Meta, Clause, Expr}, Return, CS);
_ ->
{Clause, Safe} = rescue_guards(Meta, Left, Right, S),
case Safe of
true ->
each_clause({'catch', Meta, Clause, Expr}, Return, S);
false ->
{VarName, _, CS} = elixir_scope:build_var('_', S),
ClauseVar = {VarName, Meta, nil},
{FinalClause, _} = rescue_guards(Meta, ClauseVar, Right, S),
Match = {'=', Meta, [
Left,
{{'.', Meta, ['Elixir.Exception', normalize]}, Meta, [error, ClauseVar]}
]},
FinalExpr = prepend_to_block(Meta, Match, Expr),
each_clause({'catch', Meta, FinalClause, FinalExpr}, Return, CS)
end
end;
{VarName, _, CS} = elixir_scope:build_var('_', S),
Var = {VarName, Meta, nil},
{Parts, Safe, FS} = rescue_guards(Meta, Var, Right, CS),
Body =
case Left of
{'_', _, Atom} when is_atom(Atom) ->
Expr;
_ ->
Normalized =
case Safe of
true -> Var;
false -> {{'.', Meta, ['Elixir.Exception', normalize]}, Meta, [error, Var]}
end,
prepend_to_block(Meta, {'=', Meta, [Left, Normalized]}, Expr)
end,
build_rescue(Meta, Parts, Body, Return, FS);
each_clause({rescue, Meta, _, _}, _Return, S) ->
elixir_errors:compile_error(Meta, S#elixir_scope.file, "invalid arguments for rescue in try");
@@ -57,90 +57,70 @@ each_clause({Key, Meta, _, _}, _Return, S) ->
%% Helpers
build_rescue(Meta, Parts, Body, Return, S) ->
Matches = [Match || {Match, _} <- Parts],
{{clause, Line, TMatches, _, TBody}, TS} =
elixir_clauses:clause(?line(Meta), fun elixir_translator:translate_args/2,
Matches, Body, [], Return, S),
TClauses =
[begin
TArgs = [{tuple, Line, [{atom, Line, error}, TMatch, {var, Line, '_'}]}],
TGuards = elixir_clauses:guards(Line, Guards, [], TS),
{clause, Line, TArgs, TGuards, TBody}
end || {TMatch, {_, Guards}} <- lists:zip(TMatches, Parts)],
{TClauses, TS}.
%% Convert rescue clauses into guards.
rescue_guards(_, Var, {'_', _, _}, _) -> {[error, Var], false};
rescue_guards(_, Var, {'_', _, _}, S) -> {[{Var, []}], false, S};
rescue_guards(Meta, Var, Guards, S) ->
{RawElixir, RawErlang} = rescue_each_var(Meta, Var, Guards),
{Elixir, Erlang, Safe} = rescue_each_ref(Meta, Var, Guards, RawElixir, RawErlang, RawErlang == [], S),
rescue_guards(Meta, Var, Aliases, S) ->
{Elixir, Erlang} = rescue_each_ref(Meta, Var, Aliases, [], [], S),
Final = case Elixir == [] of
true -> Erlang;
false ->
IsTuple = {erl(Meta, is_tuple), Meta, [Var]},
IsException = {erl(Meta, '=='), Meta, [
{erl(Meta, element), Meta, [2, Var]}, '__exception__'
]},
OrElse = join(Meta, fun erl_or/3, Elixir),
[join(Meta, fun erl_and/3, [IsTuple, IsException, OrElse])|Erlang]
end,
{
[{'when', Meta, [error, Var, join_when(Meta, Final)]}],
Safe
}.
{ElixirParts, ES} =
case Elixir of
[] -> {[], S};
_ ->
{VarName, _, CS} = elixir_scope:build_var('_', S),
StructVar = {VarName, Meta, nil},
Map = {'%{}', Meta, [{'__struct__', StructVar}, {'__exception__', true}]},
Match = {'=', Meta, [Map, Var]},
Guards = [{erl(Meta, '=='), Meta, [StructVar, Mod]} || Mod <- Elixir],
{[{Match, Guards}], CS}
end,
%% Handle variables in the right side of rescue.
ErlangParts =
case Erlang of
[] -> [];
_ -> [{Var, Erlang}]
end,
rescue_each_var(Meta, ClauseVar, Guards) ->
Vars = [Var || Var <- Guards, is_var(Var)],
case Vars == [] of
true -> {[], []};
false ->
Elixir = [erl_exception_compare(Meta, ClauseVar, Var) || Var <- Vars],
Erlang = lists:map(fun(Rescue) ->
Compares = [{erl(Meta, '=='), Meta, [Rescue, Var]} || Var <- Vars],
erl_and(Meta,
erl_rescue_guard_for(Meta, ClauseVar, Rescue),
join(Meta, fun erl_or/3, Compares))
end, erlang_rescues()),
{Elixir, Erlang}
end.
{ElixirParts ++ ErlangParts, ErlangParts == [], ES}.
%% Rescue each atom name considering their Erlang or Elixir matches.
%% Matching of variables is done with Erlang exceptions is done in another
%% method for optimization.
%% Matching of variables is done with Erlang exceptions is done in
%% function for optimization.
%% Ignore variables
rescue_each_ref(Meta, Var, [{Name, _, Atom}|T], Elixir, Erlang, Safe, S) when is_atom(Name), is_atom(Atom) ->
rescue_each_ref(Meta, Var, T, Elixir, Erlang, Safe, S);
rescue_each_ref(Meta, Var, [H|T], Elixir, Erlang, _Safe, S) when
rescue_each_ref(Meta, Var, [H|T], Elixir, Erlang, S) when
H == 'Elixir.UndefinedFunctionError'; H == 'Elixir.ErlangError';
H == 'Elixir.ArgumentError'; H == 'Elixir.ArithmeticError';
H == 'Elixir.BadArityError'; H == 'Elixir.BadFunctionError';
H == 'Elixir.MatchError'; H == 'Elixir.CaseClauseError';
H == 'Elixir.TryClauseError'; H == 'Elixir.FunctionClauseError';
H == 'Elixir.SystemLimitError'; H == 'Elixir.BadStructError' ->
Expr = erl_or(Meta,
erl_rescue_guard_for(Meta, Var, H),
erl_exception_compare(Meta, Var, H)),
rescue_each_ref(Meta, Var, T, Elixir, [Expr|Erlang], false, S);
Expr = erl_rescue_guard_for(Meta, Var, H),
rescue_each_ref(Meta, Var, T, [H|Elixir], [Expr|Erlang], S);
rescue_each_ref(Meta, Var, [H|T], Elixir, Erlang, Safe, S) when is_atom(H) ->
rescue_each_ref(Meta, Var, T, [erl_exception_compare(Meta, Var, H)|Elixir], Erlang, Safe, S);
rescue_each_ref(Meta, Var, [H|T], Elixir, Erlang, S) when is_atom(H) ->
rescue_each_ref(Meta, Var, T, [H|Elixir], Erlang, S);
rescue_each_ref(Meta, Var, [H|T], Elixir, Erlang, Safe, S) ->
case elixir_translator:translate(H, S) of
{{atom, _, Atom}, _} ->
rescue_each_ref(Meta, Var, [Atom|T], Elixir, Erlang, Safe, S);
_ ->
rescue_each_ref(Meta, Var, T, [erl_exception_compare(Meta, Var, H)|Elixir], Erlang, Safe, S)
end;
rescue_each_ref(_, _, [], Elixir, Erlang, Safe, _) ->
{Elixir, Erlang, Safe}.
rescue_each_ref(_, _, [], Elixir, Erlang, _) ->
{Elixir, Erlang}.
%% Handle erlang rescue matches.
erlang_rescues() ->
[
'Elixir.UndefinedFunctionError', 'Elixir.ArgumentError', 'Elixir.ArithmeticError',
'Elixir.BadArityError', 'Elixir.BadFunctionError', 'Elixir.MatchError',
'Elixir.CaseClauseError', 'Elixir.TryClauseError', 'Elixir.FunctionClauseError',
'Elixir.SystemLimitError', 'Elixir.ErlangError', 'Elixir.BadStructError'
].
erl_rescue_guard_for(Meta, Var, List) when is_list(List) ->
join(Meta, fun erl_or/3, [erl_rescue_guard_for(Meta, Var, X) || X <- List]);
@@ -159,39 +139,39 @@ erl_rescue_guard_for(Meta, Var, 'Elixir.ArithmeticError') ->
erl_rescue_guard_for(Meta, Var, 'Elixir.BadArityError') ->
erl_and(Meta,
erl_tuple_size(Meta, Var, 2),
erl_exception_compare(Meta, Var, badarity));
erl_record_compare(Meta, Var, badarity));
erl_rescue_guard_for(Meta, Var, 'Elixir.BadFunctionError') ->
erl_and(Meta,
erl_tuple_size(Meta, Var, 2),
erl_exception_compare(Meta, Var, badfun));
erl_record_compare(Meta, Var, badfun));
erl_rescue_guard_for(Meta, Var, 'Elixir.MatchError') ->
erl_and(Meta,
erl_tuple_size(Meta, Var, 2),
erl_exception_compare(Meta, Var, badmatch));
erl_record_compare(Meta, Var, badmatch));
erl_rescue_guard_for(Meta, Var, 'Elixir.CaseClauseError') ->
erl_and(Meta,
erl_tuple_size(Meta, Var, 2),
erl_exception_compare(Meta, Var, case_clause));
erl_record_compare(Meta, Var, case_clause));
erl_rescue_guard_for(Meta, Var, 'Elixir.TryClauseError') ->
erl_and(Meta,
erl_tuple_size(Meta, Var, 2),
erl_exception_compare(Meta, Var, try_clause));
erl_record_compare(Meta, Var, try_clause));
erl_rescue_guard_for(Meta, Var, 'Elixir.BadStructError') ->
erl_and(Meta,
erl_tuple_size(Meta, Var, 3),
erl_exception_compare(Meta, Var, badstruct));
erl_record_compare(Meta, Var, badstruct));
erl_rescue_guard_for(Meta, Var, 'Elixir.ArgumentError') ->
erl_or(Meta,
{erl(Meta, '=='), Meta, [Var, badarg]},
erl_and(Meta,
erl_tuple_size(Meta, Var, 2),
erl_exception_compare(Meta, Var, badarg)));
erl_record_compare(Meta, Var, badarg)));
erl_rescue_guard_for(Meta, Var, 'Elixir.ErlangError') ->
IsNotTuple = {erl(Meta, 'not'), Meta, [{erl(Meta, is_tuple), Meta, [Var]}]},
@@ -202,19 +182,10 @@ erl_rescue_guard_for(Meta, Var, 'Elixir.ErlangError') ->
%% Helpers
format_error({rescue_no_match, Var, Alias}) ->
VarBinary = 'Elixir.Macro':to_string(Var),
AliasBinary = 'Elixir.Macro':to_string(Alias),
Message = "rescue clause (~ts = ~ts) can never match, maybe you meant to write: ~ts in [~ts] ?",
io_lib:format(Message, [AliasBinary, VarBinary, VarBinary, AliasBinary]).
is_var({Name, _, Atom}) when is_atom(Name), is_atom(Atom) -> true;
is_var(_) -> false.
erl_tuple_size(Meta, Var, Size) ->
{erl(Meta, '=='), Meta, [{erl(Meta, tuple_size), Meta, [Var]}, Size]}.
erl_exception_compare(Meta, Var, Expr) ->
erl_record_compare(Meta, Var, Expr) ->
{erl(Meta, '=='), Meta, [
{erl(Meta, element), Meta, [1, Var]},
Expr
@@ -223,9 +194,6 @@ erl_exception_compare(Meta, Var, Expr) ->
join(Meta, Kind, [H|T]) ->
lists:foldl(fun(X, Acc) -> Kind(Meta, Acc, X) end, H, T).
join_when(Meta, [H|T]) ->
lists:foldl(fun(X, Acc) -> {'when', Meta, [X, Acc]} end, H, T).
prepend_to_block(_Meta, Expr, {'__block__', Meta, Args}) ->
{'__block__', Meta, [Expr|Args]};
+3 -3
View File
@@ -43,7 +43,7 @@ file_type(File, Op) ->
relative_to_cwd(Path) ->
case elixir_compiler:get_opt(internal) of
true -> Path;
false -> 'Elixir.List':'from_char_data!'('Elixir.Path':relative_to_cwd(Path))
false -> 'Elixir.String':to_char_list('Elixir.Path':relative_to_cwd(Path))
end.
characters_to_list(Data) when is_list(Data) ->
@@ -51,7 +51,7 @@ characters_to_list(Data) when is_list(Data) ->
characters_to_list(Data) ->
case elixir_compiler:get_opt(internal) of
true -> unicode:characters_to_list(Data);
false -> 'Elixir.List':'from_char_data!'(Data)
false -> 'Elixir.String':to_char_list(Data)
end.
characters_to_binary(Data) when is_binary(Data) ->
@@ -59,7 +59,7 @@ characters_to_binary(Data) when is_binary(Data) ->
characters_to_binary(Data) ->
case elixir_compiler:get_opt(internal) of
true -> unicode:characters_to_binary(Data);
false -> 'Elixir.String':'from_char_data!'(Data)
false -> 'Elixir.List':to_string(Data)
end.
%% elixir to erl. Handles only valid quoted expressions,
+4 -2
View File
@@ -4,6 +4,7 @@ defmodule KernelTest do
use ExUnit.Case, async: true
doctest Access
doctest Atom
doctest Base
doctest Bitwise
doctest Code
@@ -14,6 +15,7 @@ defmodule KernelTest do
doctest Inspect
doctest Inspect.Algebra
doctest Integer
doctest IO
doctest IO.ANSI
doctest Kernel
doctest Kernel.SpecialForms
@@ -26,13 +28,13 @@ defmodule KernelTest do
doctest OptionParser
doctest Path
doctest Process
doctest Protocol.Consolidation
doctest Protocol
doctest Range
doctest Record
doctest Regex
doctest Stream
doctest String
doctest String.Chars.List
doctest String.Chars
doctest StringIO
doctest Tuple
doctest URI
+56
View File
@@ -0,0 +1,56 @@
Code.require_file "test_helper.exs", __DIR__
defmodule AgentTest do
use ExUnit.Case, async: true
test "start_link/2 workflow with unregistered name" do
{:ok, pid} = Agent.start_link(fn -> %{} end)
{:links, links} = Process.info(self, :links)
assert pid in links
assert Agent.update(pid, &Map.put(&1, :hello, :world)) == :ok
assert Agent.get(pid, &Map.get(&1, :hello), 3000) == :world
assert Agent.get_and_update(pid, &Map.pop(&1, :hello), 3000) == :world
assert Agent.get(pid, &(&1)) == %{}
assert Agent.stop(pid) == :ok
wait_until_dead(pid)
end
test "start/2 workflow with registered name" do
{:ok, pid} = Agent.start(fn -> %{} end, name: :agent)
assert Process.info(pid, :registered_name) == {:registered_name, :agent}
assert Agent.cast(:agent, &Map.put(&1, :hello, :world)) == :ok
assert Agent.get(:agent, &Map.get(&1, :hello)) == :world
assert Agent.get_and_update(:agent, &Map.pop(&1, :hello)) == :world
assert Agent.get(:agent, &(&1)) == %{}
assert Agent.stop(:agent) == :ok
assert Process.info(pid, :registered_name) == nil
end
test ":sys.change_code/4 with mfa" do
{ :ok, pid } = Agent.start_link(fn -> %{} end)
:ok = :sys.suspend(pid)
mfa = { Map, :put, [:hello, :world] }
assert :sys.change_code(pid, __MODULE__, "vsn", mfa) == :ok
:ok = :sys.resume(pid)
assert Agent.get(pid, &Map.get(&1, :hello)) == :world
assert Agent.stop(pid) == :ok
end
test ":sys.change_code/4 with raising mfa" do
{ :ok, pid } = Agent.start_link(fn -> %{} end)
:ok = :sys.suspend(pid)
mfa = { :erlang, :error, [] }
assert match?({ :error, _ }, :sys.change_code(pid, __MODULE__, "vsn", mfa))
:ok = :sys.resume(pid)
assert Agent.get(pid, &(&1)) == %{}
assert Agent.stop(pid) == :ok
end
defp wait_until_dead(pid) do
if Process.alive?(pid) do
wait_until_dead(pid)
end
end
end
+13
View File
@@ -0,0 +1,13 @@
Code.require_file "test_helper.exs", __DIR__
defmodule AtomTest do
use ExUnit.Case, async: true
test "to_string/1" do
assert Atom.to_string(:"héllo") == "héllo"
end
test "to_char_list/1" do
assert Atom.to_char_list(:"héllo") == 'héllo'
end
end
+1 -25
View File
@@ -42,28 +42,4 @@ bar '''
assert '\x{10FFF}' == [69631]
assert '\x{10FFFF}' == [1114111]
end
test :from_char_data do
assert List.from_char_data("æß") == {:ok, [?æ, ?ß]}
assert List.from_char_data("abc") == {:ok, [?a, ?b, ?c]}
assert List.from_char_data(<< 0xDF, 0xFF >>) == {:error, [], << 223, 255 >>}
assert List.from_char_data(<< 106, 111, 115, 195 >>) == {:incomplete, 'jos', << 195 >>}
end
test :from_char_data! do
assert List.from_char_data!("æß") == [?æ, ?ß]
assert List.from_char_data!("abc") == [?a, ?b, ?c]
assert_raise UnicodeConversionError,
"invalid encoding starting at <<223, 255>>", fn ->
List.from_char_data!(<< 0xDF, 0xFF >>)
end
assert_raise UnicodeConversionError,
"incomplete encoding starting at <<195>>", fn ->
List.from_char_data!(<< 106, 111, 115, 195 >>)
end
end
end
end
+6 -2
View File
@@ -29,7 +29,7 @@ defmodule CodeTest do
end
test :eval_with_unnamed_scopes do
assert {RuntimeError[], [a: RuntimeError[]]} =
assert {%RuntimeError{}, [a: %RuntimeError{}]} =
Code.eval_string("a = (try do (raise \"hello\") rescue e -> e end)")
end
@@ -70,6 +70,10 @@ defmodule CodeTest do
test :eval_file do
assert Code.eval_file(fixture_path("code_sample.exs")) == {3, [var: 3]}
assert_raise Code.LoadError, fn ->
Code.eval_file("non_existent.exs")
end
end
test :require do
@@ -111,7 +115,7 @@ defmodule CodeTest do
end
test :compile_source do
assert __MODULE__.__info__(:compile)[:source] == List.from_char_data!(__ENV__.file)
assert __MODULE__.__info__(:compile)[:source] == String.to_char_list(__ENV__.file)
end
test :compile_info_returned_with_source_accessible_through_keyword_module do
@@ -1,57 +0,0 @@
Code.require_file "../test_helper.exs", __DIR__
defmodule Record.AccessTest do
use ExUnit.Case, async: true
defrecord User, age: 0, name: nil do
def get_age(__MODULE__[age: age]) do
age
end
end
test "access with keywords" do
assert User[] == {User, 0, nil}
assert User[age: 1] == {User, 1, nil}
end
test "access with variable inside match" do
assert get_var(User.new) == 0
assert get_var(User.new(age: 1)) == 1
end
test "access match on record name" do
assert is_user(User.new) == true
assert is_user({Access.AtomTest, 1}) == false
assert is_user({User, 1, 2, 3}) == false
end
test "access with field match" do
assert is_zero(User.new) == true
assert is_zero(User.new(age: 1)) == false
end
test "match (=)" do
assert User[age: 1] = User.new(age: 1)
refute User[age: 1] = User.new(age: 0)
end
test "access with underscore" do
assert User[age: 0, name: 0] == User[_: 0]
assert User[_: _] = User[_: "x"]
refute match?(User[_: 0], User[_: 1])
end
test "access during record definition" do
assert User.new(age: 13).get_age == 13
end
defp get_var(User[age: age]) do
age
end
defp is_zero(User[age: 0]), do: true
defp is_zero(User[age: _]), do: false
defp is_user(User[]), do: true
defp is_user(_), do: false
end
@@ -1,116 +0,0 @@
Code.require_file "../test_helper.exs", __DIR__
defmodule Record.PrivateTest do
use ExUnit.Case, async: true
require Record
defmodule Macros do
defrecordp :_user, __MODULE__, name: "José", age: 25
defrecordp :_my_user, :my_user, name: "José", age: 25
name = :_file_info
defrecordp name, Record.extract(:file_info, from_lib: "kernel/include/file.hrl")
Record.defmacros(:_macro, [c: 2, d: 3], __ENV__)
def new() do
_user()
end
def new(name, age) do
_user(name: name, age: age)
end
def name(_user(name: name)) do
name
end
def age(user) do
_user(user, :age)
end
def add_bar_to_name(_user(name: name) = user) do
_user(user, name: name <> " bar")
end
def name_ix() do
_user(:name)
end
def age_ix() do
_user(:age)
end
def my_new() do
_my_user()
end
def my_new(name, age) do
_my_user(name: name, age: age)
end
def my_name(_my_user(name: name)) do
name
end
def my_age(user) do
_my_user(user, :age)
end
def my_add_bar_to_name(_my_user(name: name) = user) do
_my_user(user, name: name <> " bar")
end
def my_name_ix() do
_my_user(:name)
end
def my_age_ix() do
_my_user(:age)
end
def file_info() do
_file_info()
end
def macro() do
_macro()
end
end
test "defrecordp generates macros that generates tuples" do
record = Macros.my_new
assert Macros.my_name(record) == "José"
assert Macros.my_age(record) == 25
record = Macros.my_new("Foo", 25)
assert Macros.my_name(record) == "Foo"
record = Macros.my_add_bar_to_name(record)
assert Macros.my_name(record) == "Foo bar"
assert elem(record, 0) == :my_user
end
test "defrecordp access index" do
assert Macros.name_ix == 1
assert Macros.age_ix == 2
assert Macros.my_name_ix == 1
assert Macros.my_age_ix == 2
end
test "defrecordp with dynamic arguments" do
assert [:_file_info|_] = Macros.file_info() |> tuple_to_list
end
test "defmacros" do
assert {:_macro, 2, 3} = Macros.macro()
end
defrecordp :match, [:name]
test "defrecordp generates macros that are matchable" do
assert match(name: 42) = match(name: 42)
end
end

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