Compare commits

...
648 Commits
Author SHA1 Message Date
José Valim 7a24767137 Release v0.13.2 2014-05-11 12:20:09 +02:00
José Valim ad0818da9b Bump hex requirement 2014-05-11 12:02:32 +02:00
José Valim c0debf66ae Merge pull request #2267 from fishcakez/application_error
Rename Application.format_reason/1 to format_error
2014-05-10 20:03:33 +02:00
James Fish e15c8545c3 Rename Application.format_reason/1 to format_error
Makes the function name consistent with OTP modules, which all use
format_error to format reason in {:error, reason}
2014-05-10 18:56:41 +01:00
José Valim 29313e8dd5 Merge pull request #2266 from meh/version
Remove some cruft from Version and fix to_string/1
2014-05-10 18:46:11 +02:00
José Valim b0032e8df5 Merge pull request #2265 from lexmag/master
Fix handling of end of command options (`--`) in `OptionParser`
2014-05-10 18:28:26 +02:00
Aleksei Magusev 698d1f6440 Fix handling of end of command options (--) in OptionParser 2014-05-10 20:10:23 +04:00
meh 609fca162c Remove some cruft from Version and fix to_string/1 2014-05-10 18:05:42 +02:00
José Valim 23b0c8c5b1 Improve contributing guide 2014-05-10 16:57:28 +02:00
José Valim eb7c5415db Inline functions in the Tuple module, closes #2261 2014-05-10 16:40:08 +02:00
José Valim 80b31833a4 Support functions in Regex.replace/4 2014-05-10 16:21:01 +02:00
José Valim d5f35d4905 Update config.exs sample file 2014-05-10 12:16:26 +02:00
José Valim 0908e55971 Reduce amount of noise on elixir manifest files 2014-05-10 12:03:07 +02:00
José Valim 164d1d0049 Tighten guards in Exception to avoid false positives 2014-05-09 21:15:01 +02:00
José Valim e8c53a8e72 Merge pull request #2254 from meh/dict
Implement default split/2 for Dict.Behaviour
2014-05-09 10:05:27 +02:00
José Valim e7cd2511cf Merge pull request #2257 from alco/uri-docfix
URI.decode_query returns a map, not a HashDict
2014-05-09 10:05:05 +02:00
Alexei Sholik 7a977307c2 URI.decode_query returns a map, not a HashDict 2014-05-09 08:28:32 +03:00
meh 0eccb76930 Implement default split/2 for Dict.Behaviour 2014-05-09 00:17:24 +02:00
José Valim dad0f3fb5a Add Mix.Config.persist/1 2014-05-08 23:26:42 +02:00
José Valim 7ca57afbba Merge pull request #2252 from tomkadwill/improved_error_message_for_escript_main_module
Changed error message shown when escript_embed_elixir is incorrect
2014-05-08 23:02:40 +02:00
Tom Kadwill 0c6c72e8f6 Added check to raise an error if escript_main_module cannot be loaded 2014-05-08 21:56:01 +01:00
José Valim 41c8f83c6b Update CHANGELOG 2014-05-08 21:14:35 +02:00
José Valim 1f61ba454c Merge pull request #2253 from bitwalker/win-iex-help
Print help when --help, -h, or /h are given on windows
2014-05-09 04:10:41 +09:00
José Valim 55d0f568da Improve docs and code regarding ansi formatting 2014-05-08 21:09:34 +02:00
José Valim 8bfe32448e Allow mix/apps configuration via ~/.mix/config.exs 2014-05-08 20:57:58 +02:00
Paul Schoenfelder 19ad387943 Print help when --help, -h, or /h are given on windows 2014-05-08 13:51:02 -05:00
José Valim 8010e44bd4 Bring the multiple .iex.exs functionality 2014-05-08 20:21:30 +02:00
José Valim 8b203a8379 Use IO.ANSI.Docs to format mix help 2014-05-08 20:13:29 +02:00
José Valim de26484bda Normalize IEx and ExUnit configurations 2014-05-08 19:07:25 +02:00
José Valim cbef7e9629 Initial work on config 2014-05-08 16:55:14 +02:00
José Valim a184b4061e Rename :application to Application and remove unused fixtures 2014-05-08 16:55:14 +02:00
José Valim f3e26c28b0 Silence instalation process 2014-05-08 16:55:14 +02:00
José Valim b5ad462f27 Simplify binding implementation 2014-05-08 16:55:14 +02:00
Eric Meadows-Jönsson a9b2a95011 Allow records without field default values 2014-05-08 13:30:46 +02:00
José Valim 86bafe865c Update CHANGELOG 2014-05-07 23:14:54 +02:00
José Valim fc5050d122 Convert ex_unit context to a map 2014-05-07 23:13:53 +02:00
José Valim 011ad07d7d Convert internal records to structs 2014-05-07 23:00:51 +02:00
José Valim e9d13d5858 Ensure proper @type is generated for dynamic struct 2014-05-07 20:03:55 +02:00
José Valim 25a5dbc08b Only check for erlang release when copying .app file
This way we avoid the cost of checking it on every command.
When copying the .app file must be enough as we'd change
the Erlang requirement only on new versions.
2014-05-07 19:49:05 +02:00
José Valim b76387a394 Merge pull request #2249 from alco/makefile-version-check
Add a target to check minimum required Erlang version
2014-05-07 19:43:47 +02:00
José Valim 64c5acb923 Fix syntax error on .%, closes #2245 2014-05-07 19:36:31 +02:00
Alexei Sholik 9ed4d91f94 Add a target to check minimum required Erlang version 2014-05-07 20:21:48 +03:00
José Valim 45df7c4ae4 Merge pull request #2248 from arthurnn/patch-1
Update travis badge [skip ci]
2014-05-07 14:49:14 +02:00
Arthur Nogueira Neves 73eb206c86 Update travis badge [skip ci]
I know this is a bit lame, but the svg badge does look better =)
2014-05-07 08:44:29 -04:00
José Valim d08e2bb2fd Ensure --cover works on umbrella apps 2014-05-07 14:39:19 +02:00
José Valim 9bc8925018 Ensure defrecord and defstruct executes its args 2014-05-07 14:39:19 +02:00
José Valim 30d7114922 Convert doctest Test to a struct 2014-05-07 14:39:19 +02:00
José Valim 1e5633c4a1 Merge pull request #2242 from fishcakez/format_otp_exit
[WIP] Improve OTP behaviour error formatting
2014-05-07 14:16:21 +02:00
José Valim 6d44180ec2 Merge pull request #2243 from meh/dict
Implement default pop/2, pop/3, update/4, update!/3 for Dict.Behaviour
2014-05-07 14:09:43 +02:00
James Fish c2ceeb9ea4 Improve application error formatting
* Format (nested) supervision tree init failures
* Format :gen_server exit reasons
* Fixes :invalid_options text
* Fixes text when when application start returns {:error, reason}
2014-05-07 00:22:51 +01:00
José Valim b0a5197f42 Convert ExUnit.Test and ExUnit.TestCase to structs 2014-05-06 22:24:03 +02:00
meh 235c4d61b9 Implement default pop/2, pop/3, update/4, update!/3 for Dict.Behaviour 2014-05-06 21:52:57 +02:00
José Valim c5ffa76663 Soft deprecate is_exception/1 in favor of Exception.exception?/1 2014-05-06 19:01:36 +02:00
José Valim 31e639f34a Update CHANGELOG 2014-05-06 18:36:49 +02:00
José Valim f1effcb485 Use Exception.message/1 instead of exception.message 2014-05-06 18:33:02 +02:00
José Valim 237c485411 Improve docs for generated apps 2014-05-06 15:04:31 +02:00
José Valim 78ba84612c Ensure aliases are respected on root, closes #2227 2014-05-06 14:39:12 +02:00
Eric Meadows-Jönsson f8da2fb9c5 Use rebar hosted on S3 2014-05-06 13:47:53 +02:00
José Valim 7665a50a2d Rename symlink to ln_s according to File's convention 2014-05-06 13:29:00 +02:00
José Valim dfb8f38ce3 Merge pull request #2123 from duksis/symlink_on_file
Adds symlink to File
2014-05-06 20:24:59 +09:00
José Valim cd99add62d Create remote funs even in &mod.fun/arity, closes #2133 2014-05-06 12:44:02 +02:00
José Valim 615a54e8bf Hide module's location attribute 2014-05-06 10:46:30 +02:00
José Valim ad00d0ad6d Merge pull request #2240 from gmcabrita/fix-duplicated-module
Improve compiler message when duplicated modules are detected
2014-05-06 10:40:21 +02:00
José Valim 5b53459f86 Merge pull request #2241 from stoft/master
Grammar, removed superfluous 'be'
2014-05-06 10:39:19 +02:00
Rasmus 82238a8d20 Removed superfluous 'be' 2014-05-06 02:20:31 +02:00
Gonçalo Cabrita 66e70ac72f Fix compile error during boostraping 2014-05-06 01:01:30 +01:00
Gonçalo Cabrita 2d48d2ed5b Fix two test cases 2014-05-05 21:24:51 +00:00
Gonçalo Cabrita 8b54e28086 Compiler now mentions which file has a duplicated module. Fixes #2131 2014-05-05 21:24:51 +00:00
José Valim b2004b8e86 Do not throw/catch, instead use Process.info/2 2014-05-05 23:05:02 +02:00
José Valim 52ad74cdfc Only print char lists if in the ascii range 2014-05-05 20:02:36 +02:00
José Valim 5f83cf9bb3 Fix mix test 2014-05-05 19:45:07 +02:00
José Valim b2608e4fe7 Print all errors in case of missing files, closes #2239 2014-05-05 19:15:00 +02:00
José Valim aed52e44d1 Use Application.format_reason when formatting application exits 2014-05-05 18:46:27 +02:00
José Valim 91497b9f15 Rename format_message to format_banner 2014-05-05 17:57:24 +02:00
José Valim a3d85c0bad Rename format_reason to format_exit, make some defs private 2014-05-05 17:35:44 +02:00
José Valim 839afc4f3e Merge pull request #2235 from fishcakez/exception_format
Format exceptions consistently
2014-05-06 00:11:47 +09:00
José Valim abc293bff8 Mention how generators in comprehensions can be used for filtering 2014-05-05 13:13:45 +02:00
José Valim cbdfe4a14a Also support tuples in struct/2 2014-05-05 13:02:03 +02:00
James Fish 46b0f7b29e Format exceptions consistently
* Add utility functions to Exception to format exceptions
* Format mfa exits, such as :gen_server.call/3
* Format EXIT reasons
* Fixes IEx to send formatted exception in one IO request
* Fixes IEx to format stacktraces consistently with Exception
* Fixes Mix application start error messages
2014-05-05 02:16:07 +01:00
José Valim 52ebeb1e7d Ensure IEx.pry works with iex -S mix test (--trace) 2014-05-04 13:24:19 +02:00
José Valim 8797280b9e Define an Application module 2014-05-04 12:43:49 +02:00
José Valim 8a8b641271 Update CHANGELOG 2014-05-04 11:36:33 +02:00
José Valim e67ada5cfb Ensure --umbrella generates gitignore, closes #2237 2014-05-04 11:27:36 +02:00
Matheus Lima 07d7d538dc Adds "Elixir" to check module availability naming list 2014-05-04 11:27:25 +02:00
José Valim 939af3b459 Merge pull request #2233 from bitwalker/string-split-changes
Make String.split behave more like Regex.split
2014-05-04 11:09:14 +02:00
Paul Schoenfelder da784ebc1a Update use of deprecated :global option to use :parts 2014-05-03 23:47:37 -05:00
Paul Schoenfelder b670450b4c Use parts: :infinity instead of parts: 0 to split globally. 2014-05-03 23:38:08 -05:00
Paul Schoenfelder 03b1c70433 Add deprecation warning for :global in String.split/3 2014-05-03 23:37:27 -05:00
Paul Schoenfelder e04bab1d5e Deprecate :global in Regex.split/3 2014-05-03 23:36:12 -05:00
José Valim a97e1fabce Add a soft deprecation entry to the CHANGELOG 2014-05-03 22:00:41 +02:00
José Valim c86ecfdf7e Add Kernel.spawn_monitor and deprecate duplicated functions from Process 2014-05-03 21:57:55 +02:00
José Valim a90580b0d1 Fix how Elixir.Elixir is inspected 2014-05-03 21:52:58 +02:00
José Valim 60e663f26c Fix typo on Kernel.CLI 2014-05-03 12:40:40 +02:00
Paul Schoenfelder bf81156dbc Make String.split behave more like Regex.split 2014-05-02 20:52:19 -05:00
José Valim a67fe46ca5 Do not recompile the whole project when an .eex file changes
Right now, we simply trust the user that any external
dependency will be listed as a @file and use those
to track any external dependency.
2014-05-02 14:01:06 +02:00
José Valim 043f6c74b1 Consider only valid .git repos as checked out 2014-05-02 10:40:09 +02:00
Andreas Amsenius 2b1f561d91 Test git in dep folder to consider checked out
If the .git folder of a dep becomes corrupted, any git command
issued by mix could find a parent .git folder and wipe the parent
tree.

This fix only consider a git dependency checked out if git is able
to read the .git folder.

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2014-05-02 10:17:32 +02:00
José Valim 87cd24651e Add spaces in between | 2014-05-02 08:46:35 +02:00
José Valim 61f28c796a Merge pull request #2229 from MSch/mix-maps
Migrate records in mix to maps.
2014-05-02 08:32:40 +02:00
José Valim 1555b79121 Merge pull request #2230 from mgwidmann/master
Fixed typo and improper use of their in documentation
2014-05-02 08:31:55 +02:00
matt 452a33b085 Fixed typo and improper use of their in documentation 2014-05-01 21:49:21 -04:00
Martin Schürrer 2d3a3b1ca5 Migrate records in mix to maps. 2014-05-02 03:41:30 +02:00
José Valim ba40cb8a3e Deprecate Process.delete/0 2014-05-01 21:10:14 +02:00
José Valim 917a8ecd99 Only load Hex if we are actually going to use it 2014-05-01 21:02:07 +02:00
Eric Meadows-Jönsson 5539ad4536 Fix iex startup 2014-05-01 20:10:48 +02:00
José Valim a5fb3c9e0b Merge pull request #2226 from MSch/patch-1
Update erl version header in README
2014-05-01 19:25:13 +02:00
Martin Schürrer 14ead54ac6 Update erl version header in README
so people don't get confused whether elixir works on 17 RC1
2014-05-01 19:19:33 +02:00
José Valim c74a7781a8 Fix formatting and move Version.Schema to Version 2014-05-01 19:14:04 +02:00
José Valim fe66565765 Merge pull request #2225 from MSch/convert-all-the-records
Convert Stream.Lazy, StringIO, Kernel.CLI.Config, Version.Schema, Version.Requirement into structs and maps
2014-05-01 18:52:33 +02:00
Martin Schürrer 0966aea8a5 Convert Version.Schema and Version.Requirement into structs. 2014-05-01 18:47:14 +02:00
Martin Schürrer ea6272f59e Convert Kernel.CLI.Config into a map. 2014-05-01 18:40:17 +02:00
Martin Schürrer 9d3b7bb4d2 Convert StringIO's state record into a map. 2014-05-01 18:40:17 +02:00
Martin Schürrer 39e5581105 Convert Stream.Lazy into a struct. 2014-05-01 18:40:16 +02:00
José Valim 3b8ac0194b Improve mix help deps 2014-05-01 15:12:20 +02:00
José Valim 21f2f0f112 Report the test line on exunit errors 2014-05-01 14:25:31 +02:00
José Valim 92ccf4bf1d Convert Inspect.Opts into a struct 2014-05-01 13:15:08 +02:00
José Valim fe042c5463 Add Exception.message 2014-05-01 12:41:03 +02:00
José Valim 910810360d Soft deprecate Mix.project 2014-05-01 12:33:50 +02:00
José Valim 90378ab53f Improve docs for Mix.Project 2014-05-01 12:21:42 +02:00
José Valim 3c6e63a962 Ensure filters can compare aliases and regexes 2014-05-01 11:43:13 +02:00
José Valim 27a787e531 Simplify exunit test name generation and update code samples 2014-04-30 23:36:44 +02:00
José Valim f75666515a Ensure teardown failures are shown, closes #2222 2014-04-30 21:48:30 +02:00
José Valim 02a1538a3c Update CHANGELOG 2014-04-30 20:31:19 +02:00
José Valim b6991100f5 mix deps.clean works accross environments, closes #2210 2014-04-30 20:22:40 +02:00
José Valim fdf53787cf Remove unecessary .hrl macros 2014-04-30 19:24:04 +02:00
José Valim c435dbe38d Convert #elixir_env to a struct too 2014-04-30 16:32:13 +02:00
José Valim e5c13e6dd2 Convert Macro.Env to a struct 2014-04-30 15:07:50 +02:00
José Valim b1abcdcc41 Ensure we start a new evaluator in case the old one dies 2014-04-30 13:26:23 +02:00
José Valim d53a6a3491 Ensure string switches are not treated as booleans, closes #2218 2014-04-30 12:48:39 +02:00
José Valim 37d05da0ac Add a test to ensure Code.eval_quoted keeps stacktrace 2014-04-30 11:52:57 +02:00
José Valim 9407c7eedb Merge pull request #2219 from fishcakez/iex_stacktrace
Fix pruning IEx stacktraces too aggressively
2014-04-30 18:29:44 +09:00
José Valim dc0fd7675b Merge pull request #2216 from fishcakez/eval_stack
Fix stacktraces for :elixir.eval_forms/4
2014-04-30 18:27:10 +09:00
José Valim 9c0258b605 Revert "Use esl package to get erlang 17.0 on travis"
This reverts commit 76d0adfe3c.
2014-04-30 09:17:21 +02:00
José Valim bb1dc00de4 Merge pull request #2221 from fishcakez/dialyzer_fixes
Fix some dialyzer warnings
2014-04-30 09:12:14 +02:00
James Fish be0564b536 Fix some dialyzer warnings 2014-04-30 00:44:40 +01:00
José Valim 97d4c02a8f iodata_size -> iodata_length 2014-04-29 20:55:59 +02:00
José Valim 4049a79b28 Revert "Convert StringIO to a struct."
This reverts commit a49c539a9d.
2014-04-29 20:50:16 +02:00
James Fish d7df6d20a7 Fix pruning IEx stacktraces too aggressively 2014-04-29 19:22:59 +01:00
José Valim 1679f6b4d7 Tidy up ExUnit.Filters docs 2014-04-29 19:42:10 +02:00
José Valim 9caab33613 Move URI and Inspect.Algebra out of Elixir records 2014-04-29 19:40:27 +02:00
José Valim cae8f0933b Merge pull request #2214 from MSch/mix-test-line-numbers
Support line numbers in mix test, e.g. test/some/file_test.exs:12
2014-04-29 19:39:18 +02:00
José Valim 92c3bddf7b Merge pull request #2217 from MSch/string-io-struct
Convert StringIO to a struct.
2014-04-29 19:31:10 +02:00
Martin Schürrer a49c539a9d Convert StringIO to a struct. 2014-04-29 19:12:46 +02:00
James Fish 68d1bd9308 Fix stacktraces for :elixir.eval_forms/4
:erl_eval and :eval_bits can call :erlang.raise/3 with an incomplete
stacktrace, so extend the stacktrace when this occurs.

Mailing list thread:
https://groups.google.com/forum/#!topic/elixir-lang-core/cpSlgBjrRBQ
2014-04-29 17:57:39 +01:00
Martin Schürrer 7f8a52c264 Support line numbers in mix test, e.g. test/some/file_test.exs:12 2014-04-29 18:51:12 +02:00
Onorio Catenacci 565a7656c4 Fixing issue with command line parameters getting passed to elixir.bat from iex.bat
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2014-04-29 18:08:16 +02:00
José Valim dc768eb501 Convert File.Stat to a struct 2014-04-29 17:59:36 +02:00
José Valim ee78e10089 Disable ANSI on Windows 2014-04-29 16:52:35 +02:00
José Valim 6a5348b780 Improves macro expansion stacktraces, closes #2211
We make sure the paths in stacktraces are relatives
and properly point which macros are being expanded.
2014-04-29 16:18:04 +02:00
José Valim 0814d0b442 Properly format binary tokens on errors, closes #2144 2014-04-29 15:05:24 +02:00
Eric Meadows-Jönsson c57978ba30 Improve default args with multiple clauses error message 2014-04-29 14:04:46 +02:00
José Valim 8e4a8e9ed6 Do not call :io.columns on every inspect, closes #2036 2014-04-29 13:50:29 +02:00
José Valim 7415382c72 Merge pull request #2208 from fishcakez/dialyzer_fixes
[WIP] Fix some dialyzer warnings
2014-04-29 11:31:36 +02:00
José Valim 41f8c4edfe Merge pull request #2209 from lucasweiblen/fix-doc
Fix some typos
2014-04-29 11:30:26 +02:00
Lucas Weiblen 09244d3d79 Fix more typos 2014-04-28 18:57:42 -03:00
Lucas Weiblen b6b58ffae2 Fix doc on Mix.Shell.Process 2014-04-28 17:58:54 -03:00
Eric Meadows-Jönsson e343f210f6 Store relative beam paths in manifest 2014-04-28 22:55:04 +02:00
José Valim caaef2fb8f Fix set related warnings 2014-04-28 22:46:00 +02:00
James Fish 5cadeeea67 Fix some dialyzer warnings 2014-04-28 21:33:03 +01:00
José Valim 199ff77acc Fix mix build failure 2014-04-28 22:07:32 +02:00
José Valim 0117ea15ed Ensure we cover compile modules before we start the app 2014-04-28 21:45:20 +02:00
José Valim 3a1857c5ba Add comments to dialyzer work around 2014-04-28 20:47:11 +02:00
José Valim eec0211521 Merge pull request #2207 from fishcakez/dialyzer_abstract
Fix remote calls to work with dialyzer in 17.0
2014-04-28 20:44:31 +02:00
James Fish 93182b53cf Fix remote calls to work with dialyzer in 17.0
In 17.0 dialyzer will crash on an empty map case pattern when its is
known that pattern will not match. The is_map guard is used instead of
the empty map pattern.
2014-04-28 17:46:15 +01:00
José Valim 0a9b58eb8f Fix IO docs 2014-04-28 18:41:41 +02:00
José Valim 5e7b57669e Ensure ranges are supported on right hand side of in 2014-04-28 18:12:47 +02:00
Eric Meadows-Jönsson 50fdf47d05 Merge pull request #2206 from MSch/exunit-line-numbers
Make ExUnit.Filters parse integers and match atoms to binaries.
2014-04-28 17:54:28 +02:00
José Valim 6309ffb366 Make HashSet and HashDict structs 2014-04-28 17:46:15 +02:00
Martin Schürrer bc6975cdae Make ExUnit.Filters convert to_string before matching. Fixes #2064.
Makes mix test --only line:9 or --only "test:Test Message" work.
2014-04-28 17:38:33 +02:00
Eric Meadows-Jönsson 76a1602c5f Fix git checkout when directory already exists 2014-04-28 13:57:47 +02:00
Eric Meadows-Jönsson 9c14d3bd10 Fix error when cleaning single dependency 2014-04-28 13:57:47 +02:00
José Valim 025a31900a Update CHANGELOG 2014-04-28 12:55:25 +02:00
José Valim 39d1ab741a Convert Range to struct 2014-04-28 11:41:01 +02:00
José Valim 5f9fa7014a Convert regexes to structs 2014-04-28 11:01:57 +02:00
José Valim 09ea0d8e6f Remove Mix.Dep unloaded in favor of a direct converger call 2014-04-28 08:09:53 +02:00
José Valim 33792e6972 Extract hex functionality to common module 2014-04-28 08:00:13 +02:00
José Valim 107055d4d6 Convert remaining functions in System to rely on char data 2014-04-28 07:38:04 +02:00
José Valim 532c31c009 Improve docs for IO devices 2014-04-28 07:29:19 +02:00
José Valim 7d426641c6 Merge pull request #2200 from alco/rename-kernel-args
Rename the `args` variable to `attrs` or `opts` where appropriate
2014-04-28 06:44:08 +02:00
José Valim 7fc5d979e0 Merge pull request #2201 from jsvisa/code_module_docfix
docfix: delete the extra 'the'
2014-04-28 06:43:41 +02:00
jsvisa 70386a8fb9 docfix: delete the extra 'the' 2014-04-28 03:24:58 +02:00
Alexei Sholik f3bbb1ea43 Rename the args variable to attrs or opts where appropriate
It is a good convention to have the name "args" refer to variables
that actually contain a list of arguments dispatched to a function
in some way.

When passing a keyword list to a record constructor,
it is referred to as "attributes" or "attrs".

When passing a list of arbitrary options,
it is referred to as "opts".
2014-04-28 01:54:44 +03:00
Eric Meadows-Jönsson d868b4d066 Do not wrap single lists in :__block__ 2014-04-27 22:26:14 +02:00
José Valim 32b8371759 Merge pull request #2199 from fishcakez/fix_abstract
Fix compiler abstract code
2014-04-27 20:57:52 +02:00
James Fish 29e0fadec1 Fix compiler abstract code
Previously the source filename of a module was sometimes a binary, this
change ensures it is always an erlang string.

Previously dialyzer did not know not to auto import bifs as it does not
read the compile options from a beam. This change uses an attribute in
the abstract code rather than a compile option so that dialyzer does not
auto import bifs.
2014-04-27 13:49:35 +01:00
José Valim c91723b444 Remove deprecated code, enable previous soft deprecations 2014-04-27 13:39:32 +02:00
José Valim a4b6770174 Start v0.13.2-dev 2014-04-27 10:57:18 +02:00
José Valim c03d42b0cc Release v0.13.1 2014-04-27 10:06:55 +02:00
José Valim 450250704a Merge pull request #2198 from lucasweiblen/fix-typo
Fix typo on MacroTest
2014-04-27 10:05:20 +02:00
Lucas Weiblen e8d7b267b5 Fix typo on MacroTest 2014-04-27 00:44:07 -03:00
Eric Meadows-Jönsson d525b0f189 Merge pull request #2197 from ericmj/remote-converger-filter
Don't load dependencies in remote converger
2014-04-26 22:26:47 +02:00
Eric Meadows-Jönsson a1ff5b6440 Don't load dependencies in remote converger
Remote converger returns list of names that filters children returned
from Mix.Dep.Loader.load/1.
2014-04-26 22:12:57 +02:00
José Valim c2c1ebde39 Check for the hex version on CLI start 2014-04-26 15:45:00 +02:00
Eric Meadows-Jönsson 6f509808e5 Merge pull request #2195 from ericmj/remote-converger-children
Allow remote converger to set children of dependency
2014-04-26 00:51:10 +02:00
José Valim e667e2ec45 Use proper message when replacing a package 2014-04-26 00:29:44 +02:00
Eric Meadows-Jönsson 1d12f3a1d0 Add top_level field to Mix.Dep struct 2014-04-26 00:25:34 +02:00
Eric Meadows-Jönsson 26d2df6c5f Allow remote converger to set children of dependency 2014-04-26 00:25:34 +02:00
José Valim 00c2db54a6 Merge pull request #2194 from elixir-lang/travis-esl-package
Use esl package to get erlang 17.0 on travis
2014-04-25 20:34:03 +02:00
Eric Meadows-Jönsson 76d0adfe3c Use esl package to get erlang 17.0 on travis 2014-04-25 19:06:40 +02:00
José Valim 91b7eb56ab Merge pull request #2193 from alco/eex-doc
Don't mention default EEx engine twice; add a note about it in the code
2014-04-25 17:24:36 +02:00
Alexei Sholik c4de5f1849 Don't mention default EEx engine twice; add a note about it in the code 2014-04-25 14:50:51 +03:00
José Valim 651417a424 Add mix iex 2014-04-24 21:06:51 +02:00
José Valim 31c34dc04e Ensure we only allow horizontal space in heredocs 2014-04-24 20:57:06 +02:00
José Valim ad8587a853 Revert "Fix cli tests to avoid unix warnings"
Reverting in favor of the warning being fixed locally.

This reverts commit 0ddcba788d.
2014-04-24 18:49:11 +02:00
José Valim 0ddcba788d Fix cli tests to avoid unix warnings
When running on unix, we may emit warnings if the filesystem
is not configured to latin1, so we relax the tests assertion
to account for the warnings.
2014-04-24 18:15:41 +02:00
José Valim 55451fdcdc Make more deprecations soft, update CHANGELOG 2014-04-24 18:12:26 +02:00
José Valim 659a4794ed Improve message for rebar errors, closes #2189 2014-04-24 14:56:45 +02:00
José Valim d8a24ba05a Improve hex workflow
We do this by automatically starting hex, if one is available,
and by providing a local.hex task, similar to how we handle Rebar.

Note that this does not make Mix depend on Hex, we do this on a
best effort basis (similar to Rebar).
2014-04-24 13:48:57 +02:00
José Valim 3f5423837e Revert "Allow overriding of undefined dependency SCM"
This reverts commit 5dd7a2ba3a.

Conflicts:
	lib/mix/lib/mix/dep.ex
	lib/mix/lib/mix/dep/loader.ex
	lib/mix/test/fixtures/deps_status/custom/noscm_repo/mix.exs
	lib/mix/test/mix/tasks/deps_test.exs
2014-04-24 12:46:47 +02:00
José Valim 4df91f14ee Fix bug in for comprehensions 2014-04-24 11:27:49 +02:00
José Valim 45427c3b33 Fix Elixir tests on Windows, closes #2190 2014-04-24 10:45:25 +02:00
José Valim 665c9b3117 Make iolist_to_binary/iolist_size a soft deprecation 2014-04-24 10:35:28 +02:00
José Valim a04df8c543 Pass the relaxed option when downloading rebar
Closes #2152
2014-04-24 10:08:57 +02:00
José Valim 89ab7a2bdf Deprecate :env in project config and support MIX_EXS 2014-04-23 19:17:51 +02:00
José Valim 6c934c6f7f Use project :description if one is available 2014-04-23 19:17:51 +02:00
José Valim 87ee9995d0 Merge pull request #2186 from jsvisa/exunit_case_docfix
Docfix: write error
2014-04-23 14:17:23 +02:00
jsvisa 4e0e174063 Docfix: write error 2014-04-23 20:08:19 +08:00
José Valim 611bdbd0bc Remove records from EEx 2014-04-23 12:58:21 +02:00
José Valim 0243bf1a40 Remove records from IEx 2014-04-23 12:49:30 +02:00
José Valim 05b223f322 mix clean works accross environments 2014-04-23 12:25:44 +02:00
Eric Meadows-Jönsson c6dc65c418 Fix compiler crash for some map literals 2014-04-22 18:29:54 +02:00
José Valim ca2ea6c0bd Inline some map calls 2014-04-22 15:43:13 +02:00
José Valim 38aaeebfbc Correct Enum.group_by/3 typespec 2014-04-22 15:43:13 +02:00
Eric Meadows-Jönsson 89a6ad3a46 Merge pull request #2185 from alco/string-split_at
Add String.split_at
2014-04-22 14:11:35 +02:00
Alexei Sholik f35dd567a1 Add String.split_at
While it is straightforward to split a string given
an offset in bytes from its beginning like this

    <<left::[binary, size(offset)], right::binary>> = string

splitting at grapheme boundary or using negative offset
is not so simple. Hence this function.
2014-04-22 15:02:40 +03:00
José Valim 74c4b951a4 Move preferred_cli_env to a map 2014-04-21 20:21:43 +02:00
José Valim f932937928 No longer support tuples in function_exported? 2014-04-21 20:18:01 +02:00
José Valim 56f15d9f4e Do not add spaces after { and before }
This makes the source code consistent with the result
returned by inspect/2.
2014-04-21 19:06:35 +02:00
José Valim 7ccdb2ab29 Deprecate ListDict and access for lists with non-atom keys 2014-04-21 18:53:35 +02:00
José Valim c7d96af2de Fix regression on deprecated functions 2014-04-21 16:36:38 +02:00
José Valim ec776c7d68 Regexes are no longer unicode by default
By making them unicode, it was impossible match on binary streams,
as unicode expects valid unicode strings.
2014-04-21 13:01:15 +02:00
José Valim ea9b686b28 Emit a warning if running on latin1 2014-04-21 12:15:27 +02:00
José Valim 915b8d60d1 Macro.unpipe/1 now returns the pipe position 2014-04-21 11:38:08 +02:00
José Valim f6a24985b2 Also add a test for teardown failures 2014-04-21 11:29:04 +02:00
José Valim 76bee630a2 Clean up doc tests 2014-04-21 11:26:51 +02:00
José Valim 66214e97b2 Do not set +fnu by default 2014-04-21 11:18:02 +02:00
José Valim 832405c2cf Ensure we properly raise on doctest failures 2014-04-21 11:17:02 +02:00
José Valim 440f696f9b Merge pull request #2182 from pragdave/make_013_compatible
use 0.13 exunit exception in doctest
2014-04-21 17:43:31 +09:00
José Valim 8a7435e571 Update CHANGELOG 2014-04-21 10:34:13 +02:00
José Valim 7178b80ad1 Tidy up some final changes regarding regexes and deprecations 2014-04-21 10:33:18 +02:00
Yuki Ito 7b719fdf61 Regex.named_captures/2,3 returns a map of binaries <-> values instead
of a keyword

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>

Conflicts:
	lib/elixir/lib/regex.ex
2014-04-21 10:33:18 +02:00
José Valim e8f976a007 :named -> :all_names 2014-04-21 10:33:18 +02:00
Yuki Ito 0a2f184e0e Deprecate "groups" in Regex
* `Regex.groups` -> `Regex.names`
* :groups -> :named on `Regex.scan`
* :groups -> :named on `Regex.run`

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>

Conflicts:
	lib/elixir/lib/regex.ex
2014-04-21 10:33:18 +02:00
José Valim c40a37f677 Remove some deprecated functions and add missing warnings 2014-04-21 10:33:17 +02:00
José Valim 20042a51ba Merge pull request #1984 from pminten/enumerable-count-doc-improvement
Improve Enumerable count/member? docs
2014-04-21 16:01:22 +09:00
José Valim fc249de987 Update CHANGELOG 2014-04-21 08:56:35 +02:00
José Valim 6624f31472 Merge branch 'jv-char-data'
Conflicts:
	lib/eex/lib/eex/tokenizer.ex
	lib/elixir/lib/file.ex
	lib/elixir/lib/kernel.ex
	lib/elixir/lib/record/extractor.ex
	lib/elixir/lib/string.ex
	lib/ex_unit/lib/ex_unit/doc_test.ex
	lib/mix/lib/mix/archive.ex
	lib/mix/lib/mix/dep/fetcher.ex
	lib/mix/lib/mix/dep/umbrella.ex
2014-04-21 08:38:48 +02:00
pragdave cd900764d2 use 0.13 exunit exception in doctest 2014-04-20 16:10:47 -05:00
José Valim 1eb1704219 Start v0.13.1-dev 2014-04-20 16:19:35 +02:00
José Valim ada53524ca Release v0.13.0 2014-04-20 16:13:23 +02:00
José Valim e6e3570961 Improve noscm error message 2014-04-20 13:36:54 +02:00
José Valim db5adc1b35 Merge pull request #2181 from ericmj/override-undef-scm
Allow overriding of undefined dependency SCM
2014-04-20 08:29:38 +02:00
Eric Meadows-Jönsson 5dd7a2ba3a Allow overriding of undefined dependency SCM
This allows a developer to override an unrecognized SCM without Mix
raising.
2014-04-20 03:14:48 +02:00
José Valim edfb650c94 Properly format and indent test output 2014-04-19 11:31:58 +02:00
José Valim c012456573 Reject __struct__ field on struct/2 2014-04-19 08:48:31 +02:00
José Valim 860c056cbe Clean up new formatter implementation 2014-04-18 12:01:50 +02:00
pragdave 897424b185 Spruce up the output format of ex_unit
Conflicts:
	lib/ex_unit/lib/ex_unit/assertions.ex
2014-04-18 10:33:00 +02:00
Eric Meadows-Jönsson f4c61dae09 Merge pull request #2180 from MSch/docfix-special_forms-map
Docfix: Use Map.put/3 in Kernel.SpecialForms struct docs.
2014-04-18 03:05:36 +02:00
Martin Schürrer 73a76c6f7c Update example to use Map.put/3, accordingly with the last proposal
[Proposal 1](https://gist.github.com/josevalim/9acd0fe43063cbc33aa6)
has the update operation also put new keys in the map, while the
[2nd proposal](https://gist.github.com/josevalim/b30c881df36801611d13)
doesn't support this. The docs reflected the first proposal.
2014-04-18 02:43:20 +02:00
Eric Meadows-Jönsson 002bf16e2e Proper error message for bad args to 'mix help'
Closes #2179.
2014-04-18 01:15:08 +02:00
Rafael Mendonça França 47c922e571 Merge pull request #2177 from augiedb/patch-1
Small grammar fix
2014-04-17 18:27:02 -03:00
Rafael Mendonça França 60edb8489c Merge pull request #2178 from augiedb/patch-2
Minor grammar tweak
2014-04-17 18:26:41 -03:00
Augie De Blieck Jr. d539d606de Minor grammar tweak
Subject/verb agreement at the top
2014-04-17 15:24:39 -04:00
Augie De Blieck Jr. bb02b25e49 Small grammar fix 2014-04-17 15:21:53 -04:00
José Valim 6ce51612e0 Merge pull request #2175 from bitwalker/windows-shell-fixes
Fix bad conditional logic when checking for +iex
2014-04-17 18:31:28 +02:00
Paul Schoenfelder 6243bc8dc8 Fix bad conditional logic when checking for +iex 2014-04-17 11:27:38 -05:00
José Valim e39b32f235 Merge pull request #2174 from bitwalker/windows-shell-fixes
Use werl instead of erl when executing iex.bat
2014-04-16 20:00:10 +02:00
Paul Schoenfelder 0843ea0fbb Use smart shell for iex, and werl instead of erl 2014-04-16 12:45:52 -05:00
Paul Schoenfelder bbd6abb168 Use werl instead of erl when executing iex.bat 2014-04-16 12:11:45 -05:00
José Valim a29d2913f7 Merge pull request #2173 from zambal/master
--app argument should be converted to atom
2014-04-16 09:54:32 +02:00
zambal d1edad39c9 --app argument should be converted to atom 2014-04-16 09:48:40 +02:00
José Valim 74584ad921 s/records/structs in protocols docs 2014-04-15 20:49:03 +02:00
Eric Meadows-Jönsson 47f57d65e0 Merge pull request #2170 from ericmj/mix-unloaded-name
Fetch if remote converger updated dependency lock
2014-04-14 19:39:51 +02:00
Eric Meadows-Jönsson ed71b57c00 Fetch if remote converger updated dependency lock 2014-04-14 18:28:45 +02:00
José Valim 0246226487 Merge pull request #2169 from mururu/iex-inspect-options
Accept :structs, :binaries and :char_lists as iex inspect options
2014-04-14 13:44:08 +08:00
Yuki Ito 7da2a3ccc6 Accept :structs, :binaries and :char_lists as iex inspect options 2014-04-14 06:58:37 +09:00
José Valim d41f39a53a Add a note about Enum.sort/2 stability, closes #2163 2014-04-12 16:29:10 +02:00
José Valim 44c81ae0ed Document how ErlangError works, closes #2166 2014-04-12 08:54:09 +02:00
José Valim c6c6a27baf Merge pull request #2167 from fishcakez/assert_warnings
Fix ExUnit.Assertions receive macros compiler warnings
2014-04-12 14:43:30 +08:00
Igor Kapkov 97e70e6de1 change some doc examples to iex style 2014-04-12 08:41:22 +02:00
James Fish 980dbc96d7 Fix ExUnit.Assertions receive macros compiler warnings
Previously receive macros would produce warnings when a message was
supplied.
2014-04-11 23:31:22 +01:00
José Valim 0883c75335 Merge pull request #2164 from liveforeverx/fix_enum
fix matching on every map in Enum.into/2
2014-04-11 18:53:08 +02:00
Dima Aleksandrov 5761460bd6 use map_size guard, add test 2014-04-11 18:50:45 +02:00
Dima Aleksandrov d881d899a4 fix matching on every map in Enum.into/2 2014-04-11 18:41:58 +02:00
José Valim 166685caad Automatically define types for structs 2014-04-11 10:00:29 +02:00
José Valim f60bdb8d5f Support maps in typespecs 2014-04-11 10:00:29 +02:00
Eric Meadows-Jönsson c3ef484d14 Fix accidental commented code 2014-04-11 09:40:23 +02:00
José Valim 3cb586a672 Update release steps to rebuild build_info 2014-04-10 23:43:08 +02:00
Eric Meadows-Jönsson 68828523a0 Fix test error and warnings 2014-04-10 23:26:21 +02:00
Eric Meadows-Jönsson 3139168007 Remove is_map/1 optimization that breaks reduce on structs 2014-04-10 23:11:14 +02:00
José Valim 03189d05b3 Properly set standard_error to unicode 2014-04-10 19:54:34 +02:00
José Valim aa09359def Add Kernel.struct/2 2014-04-10 19:01:53 +02:00
José Valim 9595fb18dd Raise KeyError for now in map.bad_field 2014-04-10 16:58:34 +02:00
José Valim d2530e44eb Tidy up new record API 2014-04-10 16:36:26 +02:00
José Valim 21b1e51909 Start new records API 2014-04-10 12:56:59 +02:00
José Valim 9e96966622 Add a deprecation note to records, split deprecated modules 2014-04-10 12:43:49 +02:00
José Valim 845a3d1fea Make defstruct work on quoted expressions, closes #2156 2014-04-10 11:41:11 +02:00
Eric Meadows-Jönsson 8b70e07b2e Merge pull request #2161 from ericmj/remote-converge-lock
Remote converger should receive and update lock
2014-04-10 10:38:10 +02:00
Eric Meadows-Jönsson f6ca23c1a5 Remote converger should receive and update lock 2014-04-09 23:38:28 +02:00
José Valim cc03382831 Merge pull request #2160 from mururu/otp-version-scheme
Fix version scheme for Erlang/OTP
2014-04-10 01:40:38 +09:00
Yuki Ito 09e7dbf3d5 Fix version scheme for Erlang/OTP 2014-04-10 01:35:36 +09:00
José Valim 238eda25e3 Update to Erlang 17.0 2014-04-09 18:32:09 +02:00
José Valim a7c6b8da5c Document --no-deps-check rather than --no-deps 2014-04-08 21:53:38 +02:00
Eric Meadows-Jönsson 1a26ea2c19 Document --no-deps on 'mix compile' 2014-04-08 21:26:43 +02:00
José Valim 6d6e7d7137 Merge pull request #2155 from ericmj/eex-handle-body
Add `handle_body/1` callback to EEx.Engine
2014-04-08 19:50:35 +02:00
Eric Meadows-Jönsson f7d0c8c58e Add handle_body/1 callback to EEx.Engine 2014-04-08 19:17:59 +02:00
José Valim 4e93dab9a8 Exception.normalize/1 is deprecated in favor of Exception.normalize/2 2014-04-08 15:13:46 +02:00
José Valim 4847214766 Do not fill in stacktraces on protocol dispatch 2014-04-07 19:05:02 +02:00
José Valim e56df8ad70 Look text ahead on EEx do/end blocks instead of relying on buffers 2014-04-06 14:50:26 +02:00
José Valim 325c98ccbd Remove line and convert text tokens to char lists 2014-04-06 14:14:47 +02:00
José Valim 2daf136a53 Serve rebar without https, closes #2152 2014-04-06 11:59:34 +02:00
Eric Meadows-Jönsson 70b4c060e6 Allow queries in mix local.install URL 2014-04-06 02:31:48 +02:00
José Valim 620e452f9f Do not reuse shell variables in IEx, closes #2149 2014-04-04 10:58:16 +02:00
José Valim c373edff9b Improve float docs 2014-04-04 10:58:16 +02:00
José Valim 1bd50f4d36 Merge pull request #2148 from jwarwick/behaviour_docs
Fixed doc typos. Fixed unused function arg.
2014-04-04 09:42:32 +02:00
John Warwick be79bbf55a Fixed doc typos. Fixed unused function arg. 2014-04-03 21:57:02 -04:00
José Valim 89f651f628 Improve docs for Macro.expand/2 2014-04-01 12:14:08 +02:00
José Valim f3f2481e71 Ensure tracing set default max cases to 1 2014-03-31 13:45:05 +02:00
José Valim d33463dcd5 Unify precedence of when, :: and | 2014-03-31 12:28:33 +02:00
Eric Meadows-Jönsson 2de2322402 Remote converger should receive full dep struct 2014-03-31 11:27:57 +02:00
Eric Meadows-Jönsson f13ae1839c Show correct error if mix task could not be found 2014-03-31 11:26:00 +02:00
José Valim 29ebae3739 Ensure --help and mix help go through the same path, closes #2142 2014-03-28 23:24:50 +01:00
Eric Meadows-Jönsson 59dc733dfc Merge pull request #2138 from elixir-lang/mix-old-git
Support older gits in mix
2014-03-28 01:23:50 +01:00
Eric Meadows-Jönsson 0c9d5f16e9 Support older gits in mix
Do not add `--progress` flag to `git fetch` command unless git version
is 1.7.1 or newer.
2014-03-28 00:17:44 +01:00
José Valim b18ddd440f Update CHANGELOG 2014-03-27 15:01:38 +01:00
José Valim f4d852d292 Merge branch 'master' into v0.13-master 2014-03-27 14:57:44 +01:00
José Valim c37a244d66 Add Code.eval_file/2 2014-03-27 12:31:00 +01:00
José Valim bdab8045b9 Rename StringIO API to be more IO like (start -> open, stop -> close) 2014-03-27 09:17:04 +01:00
José Valim e8ea1969e4 Merge pull request #2096 from devinus/stringio
StringIO improvements
2014-03-27 09:04:52 +01:00
José Valim f669e2ba96 Merge pull request #2137 from pma/v0.13
Added Base module with implementation for base 64 and base 16
2014-03-27 09:03:39 +01:00
Paulo Almeida 17ddb2539d Added Base module implementing base encoding functions according to RFC4648 2014-03-27 08:28:46 +01:00
José Valim 9cbe498a02 Update heredoc tests 2014-03-26 12:16:50 +01:00
José Valim edf55a8424 Ensure doctests run as expected 2014-03-26 12:09:56 +01:00
José Valim 41bce3e068 There is no need to rely @on_definition callbacks to store tags 2014-03-26 09:54:08 +01:00
José Valim 914a8cdcd9 Ensure the same pid is not queued twice in the parallel compiler 2014-03-23 11:23:46 +01:00
José Valim a333f646d4 Do not assert for all entries at project root, closes #2129 2014-03-21 16:54:19 +01:00
José Valim d73c108b89 Revert "Use 17.0-rc2" 2014-03-21 16:53:10 +01:00
José Valim 580b9292d6 Use 17.0-rc2 no_auto_import compiler option 2014-03-21 16:47:24 +01:00
José Valim 07648bd075 Use 17.0-rc2 2014-03-21 16:44:29 +01:00
José Valim 149091a1a1 Define a type for structs 2014-03-21 16:42:10 +01:00
José Valim 34a3b41543 Merge pull request #2126 from knewter/feature/v0.13_comprehension_docfix
IO.stream/1 isn't a thing, this fixes the example
2014-03-19 10:38:52 +01:00
Josh Adams ad87292fa0 IO.stream/1 isn't a thing, this fixes the example 2014-03-18 20:06:18 -05:00
José Valim 2d9dcf860a List arg1, arg2 and so on in supervision tree sample 2014-03-18 23:47:48 +01:00
José Valim e2357d4b95 Revert "Add :app to dep opts"
This reverts commit 4af5f2b12f.
2014-03-18 19:48:02 +01:00
José Valim 8fac8cc25f Rename Mix.Dep test cases 2014-03-18 17:20:25 +01:00
José Valim ada913c7ef Convert lock to a map 2014-03-18 17:20:04 +01:00
José Valim df04a3788e Improve docs for & and |>, closes #2125 2014-03-18 16:40:54 +01:00
José Valim 65543f0766 Make Mix.Deps private and improve docs for apps and deps 2014-03-18 13:37:22 +01:00
José Valim a73d70176a Update CHANGELOG 2014-03-18 12:52:07 +01:00
José Valim 14d2ce460e Also raise proper error on keywords followed by numbers, closes #2091 2014-03-18 12:07:39 +01:00
Hugo Duksis 363b2b9352 Adds symlink to File
implementation of feature request #2114
2014-03-17 22:59:23 +01:00
Paul Schoenfelder 2ce02084ee Ensure generated .app always contains core dependencies
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2014-03-17 19:29:48 +01:00
José Valim 6ab07f6f5b Mention ArgumentError in Process docs 2014-03-17 16:39:08 +01:00
JC Grubbs 7d2babe272 additional notes for Process.register/2 to highlight failure conditions
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2014-03-17 16:37:36 +01:00
James Fish 913327749a Add Process.send/3
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2014-03-17 16:36:16 +01:00
James Fish 9be2f7f3d3 Add --gen-debug
Use erlang's -generic_debug to turn on default [:log, :statistics] for
:gen_servers

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2014-03-17 16:34:02 +01:00
José Valim 09e9917fc5 Remove reduce as a requirement from Dict API 2014-03-17 12:49:54 +01:00
Eric Meadows-Jönsson 4af5f2b12f Add :app to dep opts 2014-03-15 23:01:55 +01:00
José Valim 14fb6d7337 Do not allow structs on Dict nor Set 2014-03-15 20:11:05 +01:00
José Valim ecd5b3d8ef Ensure do can be at the start of its own line, closes #2053 2014-03-13 09:44:01 -07:00
José Valim a52bbfce8e Update Node docs and CHANGELOG 2014-03-13 08:00:11 -07:00
paladiy 6696184fc9 add ping method to Node module
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2014-03-13 07:48:10 -07:00
Josh Bush eda13d1e41 Add Enum.sum/1
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>

Conflicts:
	lib/elixir/test/elixir/enum_test.exs
2014-03-13 07:35:56 -07:00
Eric Meadows-Jönsson f69c2182f7 Fix escript generation for projects with deps 2014-03-13 12:14:54 +01:00
José Valim b5d48ada53 Write tests for streams in append mode 2014-03-12 09:47:59 -07:00
José Valim 27c9cc5bbb Generate the applications list in new apps 2014-03-12 09:19:14 -07:00
Eric Meadows-Jönsson fb211b0c44 Merge pull request #2111 from ericmj/splice-bytes
Allow splicing of binaries with specified size
2014-03-11 01:04:20 +01:00
Eric Meadows-Jönsson c7c5d3af24 Allow splicing of binaries with specified size 2014-03-11 00:46:43 +01:00
José Valim 6e9a3fd24d Update Mix docs 2014-03-10 16:24:52 -07:00
José Valim 34c2fb773f Merge pull request #2108 from ericmj/mix-dep-struct
Convert Mix.Dep record to struct
2014-03-10 18:13:08 +01:00
José Valim 9bdfa41c4d Bring v0.12.5 CHANGELOG 2014-03-09 17:20:08 -07:00
José Valim 85576f8fba Remove duplication in IEx.History 2014-03-09 17:14:39 -07:00
José Valim b07fbcf8b7 Release v0.12.5 2014-03-09 17:02:41 -07:00
Eric Meadows-Jönsson 453c731f8a Convert Mix.Dep record to struct 2014-03-09 23:42:38 +01:00
Eric Meadows-Jönsson d4dd48d2fb Macro.to_string/1 should not treat % as binary op 2014-03-09 23:24:24 +01:00
Eric Meadows-Jönsson 0f9f6152cf Document dep option :override 2014-03-09 16:11:13 +01:00
Eric Meadows-Jönsson eb998862d7 Remote converger should run on all deps 2014-03-09 16:10:02 +01:00
Eric Meadows-Jönsson 4ab8225cfb Properly clean spurious entries in waiting list 2014-03-09 16:05:09 +01:00
Eric Meadows-Jönsson 187e1b4a49 Properly clean spurious entries in waiting list 2014-03-09 16:02:26 +01:00
José Valim 318e068587 SystemTest should be sync 2014-03-08 23:17:05 -08:00
José Valim 3d6dd50866 Remove native checks and improve typespecs 2014-03-08 12:46:16 -08:00
José Valim b3ebf924a9 Remove Enum deprecated code 2014-03-08 10:52:39 -08:00
José Valim cd3e6d9238 Update dict and set types 2014-03-08 10:50:53 -08:00
José Valim 56db9fb043 Remove GenFSM 2014-03-08 07:58:48 -08:00
José Valim 455eb4c4ac Deprecate GenFSM
We plan to provide better abstractions around GenServer and GenEvent
in upcoming releases but we won't do so for GenFSM. There are other
interesting approaches out there, like `plain_fsm`, which are easier
to understand and use than OTP's FSM.
2014-03-08 07:53:53 -08:00
José Valim 897396447f Merge pull request #2102 from jwarwick/throw_doc
Fixed Kernel.SpecialForms.try reference
2014-03-09 00:44:33 +09:00
Ricardo de Cillo d79f3ddffd Remove support to MIX_GIT_FORCE_HTTPS environment option
This used to be useful in case the git port was blocked.
2014-03-08 07:44:05 -08:00
José Valim a6f048b3de Merge pull request #2104 from rcillo/rd-remove-git-force-https-option
Remove support to MIX_GIT_FORCE_HTTPS environment option
2014-03-09 00:43:31 +09:00
Ricardo de Cillo aa87d44cba Remove support to MIX_GIT_FORCE_HTTPS environment option
This used to be useful in case the git port was blocked.
2014-03-08 11:52:35 -03:00
John Warwick 9ba58c50ab Fixed Kernel.SpecialForms.try reference 2014-03-07 22:03:29 -08:00
José Valim deedadb528 Remove wrong traversable reference 2014-03-07 16:22:34 -08:00
José Valim 96e5d8bb41 Merge pull request #2100 from jwarwick/form_typo
Fixing doc typos in Kernel.SpecialForms
2014-03-07 16:09:55 -08:00
John Warwick 8c5eaa973b Fixing typos in Kernel.SpecialForms 2014-03-07 13:45:37 -08:00
José Valim 42ab7a5370 Remove outdated test 2014-03-07 06:17:32 -08:00
José Valim e73fce96d1 Support :into with Collectable in comprehensions 2014-03-07 06:11:38 -08:00
José Valim d8e414032f Merge pull request #2099 from jwarwick/collectable_docs
Fixed typos in Collectable docs
2014-03-07 04:52:48 -08:00
John Warwick 7362ca8fab Fixed typos in Collectable docs 2014-03-06 21:36:32 -08:00
José Valim 705bf0cd90 Merge pull request #2098 from jwarwick/file_docs
Fixed typos in File docs
2014-03-06 14:02:18 -08:00
John Warwick 71ca5672cc Fixed typos in File docs 2014-03-06 13:38:53 -08:00
José Valim 7494106933 Update CHANGELOG 2014-03-06 10:55:10 -08:00
José Valim cbc03036cb Merge pull request #2093 from elixir-lang/jv-into
Add support for Collectables
2014-03-06 10:50:39 -08:00
José Valim eaa52f1db3 Rename Traversable to Collectable 2014-03-06 10:48:33 -08:00
José Valim a05d0d3b26 Rename Traversable to Collectable 2014-03-06 10:43:11 -08:00
Devin Torres 9fc7a1c43f WIP StringIO improvements 2014-03-06 10:34:41 -06:00
José Valim d161986e93 Update CHANGELOG.md 2014-03-06 23:35:27 +08:00
José Valim 28367d9073 Ensure an after clause is generated only if required 2014-03-06 07:05:18 -08:00
José Valim 04da85d19e Update char list samples in IO tests 2014-03-06 07:05:00 -08:00
José Valim 866acc4f82 Ensure an after clause is generated only if required 2014-03-06 07:00:37 -08:00
José Valim cf730f2dbf Use raw modes for File.write/2 and File.Stream 2014-03-06 06:29:18 -08:00
José Valim 012256791d Add traversable to bootstrap 2014-03-05 16:03:40 -08:00
José Valim 5c410d9c34 Add IO.Stream as well 2014-03-05 15:35:13 -08:00
José Valim efb67a8416 Add File.Stream and Stream.into/3 2014-03-05 15:14:55 -08:00
José Valim 126a5a61df Deprecate more functionality to be changed or removed in v0.13 2014-03-05 08:18:09 -08:00
José Valim 26f5f640c9 Initial work on traversable 2014-03-05 08:08:43 -08:00
Eric Meadows-Jönsson 7f3a9ed9c3 Merge pull request #2092 from ericmj/kw-interpol
Support interpolation in keyword list atoms
2014-03-05 16:53:56 +01:00
Eric Meadows-Jönsson 04e774a4ba Support interpolation in keyword list atoms
key = "foo"
    ["#{key}": :value]
2014-03-05 16:43:26 +01:00
José Valim 38d1badb64 Update CHANGELOG 2014-03-05 04:24:24 -08:00
José Valim bb3cc518cf Merge pull request #2070 from mururu/capture-once
Raise error if another capture_io process already captured the device
2014-03-05 04:20:49 -08:00
Yuki Ito eaa2db1809 Raise error if another capture_io process already captured the device 2014-03-05 21:10:50 +09:00
José Valim a38636ac08 Compile project if a task via the CLI cannot be found 2014-03-05 03:05:18 -08:00
José Valim 6268dc3d22 Inline System.stacktrace/0 2014-03-05 03:05:18 -08:00
José Valim d50405135a Do not impose reduce/3 on Dict implementations 2014-03-05 03:05:18 -08:00
Eric Meadows-Jönsson e5fed25efd Add Mix.Shell.prompt/1 2014-03-04 00:28:00 +01:00
Eric Meadows-Jönsson 293711ff46 Merge pull request #2090 from bitwalker/gen-fsm-docs
Update Gen_FSM example to be more explicit, clear.
2014-03-03 19:33:52 +01:00
Paul Schoenfelder 1a17634de9 Update Gen_FSM example to be more explicit, clear.
By using a locally registered process name, this example has caused some
initial confusion to new users of Elixir. Using explicit PIDs will more
closely align with other examples, and reduce the number of things one
must understand in order to understand this example.
2014-03-03 12:21:33 -06:00
José Valim ae32bf1fe0 Merge pull request #2087 from ericmj/string-io-get-password
Support :io.get_password in StringIO
2014-03-03 22:39:14 +09:00
José Valim 433a46a0ad Merge pull request #2089 from jwarwick/module_docs
Fixed doc typos in Module
2014-03-03 07:44:32 +01:00
John Warwick b2fb6c9bb1 Fixed doc typos in Module 2014-03-02 21:35:30 -05:00
José Valim 75de306c46 Make the ParallelCompiler aware of missing structs
This allows modules that have recursive dependencies on structs
to be defined by having a fine grained control on the struct
definition besides the module one.
2014-03-02 16:24:45 +01:00
José Valim 598648da1a Merge pull request #2088 from jwarwick/doc_typos
Fixing doc typos
2014-03-02 08:48:27 +01:00
John Warwick accf75b118 Fixed Kernel.SpecialForms doc typos 2014-03-01 22:25:28 -05:00
John Warwick 0cbbefe11b Fixed StringIO doc typos 2014-03-01 21:56:08 -05:00
José Valim 17b4dbbdbb Ensure struct fields are properly escaped when injected into AST 2014-03-01 23:41:14 +01:00
Eric Meadows-Jönsson 9e068fb0a2 Support :io.get_password in StringIO 2014-03-01 17:58:51 +01:00
José Valim 59ca4728d1 Improve error message for illegal use of variable in map key, closes #2083 2014-03-01 08:46:35 +01:00
José Valim 7af4364121 Merge pull request #2085 from jwarwick/special_typo
Fixing doc typos in Kernel
2014-03-01 08:19:56 +01:00
John Warwick e38173602a Fixing doc typos in Kernel 2014-02-28 22:20:35 -05:00
Eric Meadows-Jönsson 9d149a3070 Merge pull request #2079 from ericmj/mix-dep-only
Add `:only` dep option to filter on environments
2014-02-28 22:23:54 +01:00
Eric Meadows-Jönsson 9158cad331 Add :only dep option to filter on environments
mix deps.get will by default fetch all deps unless --only flag is given.
2014-02-28 22:23:38 +01:00
José Valim 00b1aadd76 Add a __struct__ field on defstruct and ensure proper message on struct body access 2014-02-28 17:30:24 +01:00
José Valim c1fedcc643 Update CHANGELOG 2014-02-28 10:57:42 +01:00
José Valim 2f7972c57a Ensure we properly convert !(foo > bar) to strings 2014-02-28 10:54:54 +01:00
José Valim ef1b2ea49c Ensure we only print a map as a struct if the keys match 2014-02-28 10:23:38 +01:00
José Valim 69f2d5083f Improve docs for EEx 2014-02-28 08:41:13 +01:00
José Valim 10b1f56ad9 Ensure structs can be used on pattern matching 2014-02-27 16:30:11 +01:00
José Valim a6f171d183 Merge pull request #2076 from knewter/minor_maps_doc_updates
Fix typo in Maps, add 2 examples to docs
2014-02-27 16:07:26 +01:00
Josh Adams 21a231a914 Fix typo in Maps, add 2 examples to docs 2014-02-27 07:53:13 -06:00
José Valim 9e9583c29c Remove unecessary return checks from comprehensions 2014-02-27 13:21:00 +01:00
José Valim f6a513c71c Properly indent StringIO examples 2014-02-25 22:05:34 +01:00
Eric Meadows-Jönsson e1f9b3f291 Send user-agent on archive downloads 2014-02-25 00:43:23 +01:00
Eric Meadows-Jönsson 3f2458f7e3 Remove all old archives on local.* tasks 2014-02-25 00:43:23 +01:00
José Valim fb916430f5 Update tests to reflect we no longer need to check for reserved types 2014-02-24 17:13:16 +01:00
José Valim c9402713c9 Ensure undefined modules are reserved for future usage 2014-02-24 17:00:29 +01:00
José Valim 85b8fcf2d9 Ensure argument to Keyword is a list 2014-02-24 16:45:50 +01:00
Eric Meadows-Jönsson 13d0a4d3cf Verify that Mix.Tasks.* modules are tasks in 'mix local' 2014-02-23 20:57:23 +01:00
José Valim 9c1c424ed2 Guarantee HashDict.equal?/1 returns proper value when comparing subsets 2014-02-23 13:48:35 +01:00
José Valim 2121ce1875 Guarantee HashDict.equal?/1 returns proper value when comparing subsets
Conflicts:
	lib/elixir/test/elixir/hash_dict_test.exs
2014-02-23 13:48:06 +01:00
José Valim deba7e39a3 Properly indent chunks in System module 2014-02-23 13:06:11 +01:00
José Valim b1a6049622 Typespec and allow char data in File functions 2014-02-23 12:54:40 +01:00
José Valim 4f0e1c7ef9 Properly @spec functions in Path 2014-02-23 11:49:35 +01:00
José Valim 024edf3d40 Rename iolist_* to iodata_* 2014-02-23 11:32:03 +01:00
José Valim 6a725ab91f Provide List.from_char_data! and String.from_char_data! 2014-02-23 11:04:24 +01:00
José Valim 323a7eef1f Convert File and IO to char data, use +fnu flag by default 2014-02-22 20:08:44 +01:00
José Valim 233a3ed4ec Change the Path module to work only with char_data and always return binaries 2014-02-22 19:33:37 +01:00
Eric Meadows-Jönsson 26480d5755 Merge pull request #2069 from ericmj/explex-support
Add RemoteConverger
2014-02-22 13:23:50 +01:00
Eric Meadows-Jönsson ab4f1013d3 Add RemoteConverger 2014-02-22 13:19:05 +01:00
José Valim 5913097c17 Stream line getting started workflow 2014-02-21 20:40:59 +01:00
jsvisa fd8d1f1e4c Add test case for bug 2014-02-21 19:38:47 +01:00
jsvisa 9ebe851b38 Fix String.slice error
iex(76)> String.slice("string", 0..-2)
    nil # you see here is the issue, it shouldn't return `nil`
    iex(77)> String.slice("string", 1..-2)
    "trin"
    iex(78)> String.slice("string", -5..-2)
    "trin"
2014-02-21 19:38:47 +01:00
jsvisa af26e9fd69 Add test case for bug 2014-02-21 19:29:46 +01:00
jsvisa c6e8a905f5 Fix String.slice error
iex(76)> String.slice("string", 0..-2)
    nil # you see here is the issue, it shouldn't return `nil`
    iex(77)> String.slice("string", 1..-2)
    "trin"
    iex(78)> String.slice("string", -5..-2)
    "trin"
2014-02-21 19:29:46 +01:00
Eric Meadows-Jönsson a67411324b Start app after changing System.argv on 'mix run' 2014-02-21 19:22:40 +01:00
Eric Meadows-Jönsson cb81c6b5c7 Start app after changing System.argv on 'mix run' 2014-02-21 19:16:45 +01:00
José Valim ccf5182e73 Remove unused .line field 2014-02-21 10:54:46 +01:00
José Valim 97e044cff5 Support --only for filtering when running tests with mix test
Closes #2065
2014-02-21 10:34:22 +01:00
José Valim e47417912d Add a note about capture_io stderr requiring async: false 2014-02-19 22:36:17 +01:00
José Valim 128d82fde6 Add defstruct and allow unquoting inside maps/structs 2014-02-19 22:35:09 +01:00
José Valim 61170bda23 Remove section from Access to be deprecated 2014-02-19 21:46:26 +01:00
José Valim 89cc6feea0 Properly document regex options 2014-02-19 19:32:17 +01:00
José Valim 409fec08d3 Properly normalize filters in ExUnit runner 2014-02-19 19:10:07 +01:00
José Valim 2cc4fd2eb5 Deprecate is_regex/1 and is_range/1
Conflicts:
	lib/elixir/lib/string.ex
2014-02-19 18:22:58 +01:00
José Valim d94e7b595e Remove deprecated code 2014-02-19 18:20:11 +01:00
José Valim 1339462c82 Update CHANGELOG with ListDict soft deprecation 2014-02-19 16:04:48 +01:00
José Valim 7f8265f85c Deprecate is_regex/1 and is_range/1 2014-02-19 16:04:29 +01:00
José Valim 2e8c53c85b Add _ = EXPR to the list of expressions that can handle no return 2014-02-19 12:48:08 +01:00
José Valim 933834aedd Add try/case/receive to list of constructs that can handle returns 2014-02-19 12:45:15 +01:00
Yuki Ito 013303a44e Support end-of-line backslash in quoted expressions
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2014-02-19 12:23:30 +01:00
José Valim bab252f1e8 Do not accumulate results in comprehensions if they won't be used 2014-02-19 12:17:36 +01:00
José Valim 655e54b551 Update Macro functions to support maps 2014-02-19 10:33:59 +01:00
Eric Meadows-Jönsson 24bf9d3f9a Merge pull request #2060 from ericmj/v0.13
Add `System.delete_env/1`
2014-02-18 22:17:10 +01:00
Eric Meadows-Jönsson dab33cbddc Add System.delete_env/1 2014-02-18 21:41:45 +01:00
Eric Meadows-Jönsson ce73493cc5 Ensure that dep compilation doesn't error on elixir req mismatch 2014-02-18 19:06:23 +01:00
Eric Meadows-Jönsson 3984ed9da3 Configure ExUnit before requiring test_helper
Allows access to command line options from inside the test_helper.
2014-02-18 18:38:37 +01:00
José Valim a42a0188a1 Start supporting no return in expressions 2014-02-18 17:37:48 +01:00
José Valim f5660dad92 Add a test to ensure setup_all doesn't choke on errors 2014-02-18 13:36:21 +01:00
Eric Meadows-Jönsson 440bfdc3b4 Correctly report test setup errors and dont run tests 2014-02-18 13:11:33 +01:00
José Valim d66a3aea39 Ensure variables can be defined in comprehensions 2014-02-18 09:23:44 +01:00
José Valim db76866309 Minor style changes 2014-02-17 20:24:37 +01:00
pragdave d0dca93fb2 Add support for Enum.group_by/2.
Takes a collection and a function. The function takes a value and
returns a group for that value. The result is a map where the keys
are group values and the values are a list of elements from the
collection for which the function returned that particular
group value.
2014-02-17 17:07:12 +01:00
José Valim 0d66824965 Soft deprecate bc and lc 2014-02-17 16:27:43 +01:00
José Valim fd8859abdd Do not recognize undefined aliases as switches 2014-02-17 15:52:44 +01:00
José Valim bf3852b7bd Migrate from bc to for comprehensions 2014-02-17 14:53:45 +01:00
José Valim 3512c1a860 Start migrating lc to for comprehensions 2014-02-17 14:45:04 +01:00
José Valim 8e38540470 Support :into in for comprehensions 2014-02-17 14:24:17 +01:00
José Valim 33985326b1 Forbid bit generators without size in comprehensions 2014-02-17 12:44:34 +01:00
José Valim cbe041bdda Support binary generators 2014-02-16 18:32:54 +01:00
José Valim 20b14b3221 Ensure structs can be access from context modules 2014-02-16 13:49:34 +01:00
José Valim dd4ede4c1d Only access public __struct__ function 2014-02-16 13:29:31 +01:00
José Valim 9f89ac920c Add map access 2014-02-16 13:15:12 +01:00
Eric Meadows-Jönsson d7f8f28ed9 Fix handling of raw rebar deps 2014-02-15 14:18:01 +01:00
Eric Meadows-Jönsson 4637b57977 Fix handling of raw rebar deps 2014-02-15 14:17:16 +01:00
John Warwick ac61ddb09a Made Mix.Utils.camelize handle empty string 2014-02-14 19:43:18 +01:00
John Warwick 141f0a3ebf Make Mix.Utils.Underscore accept .'s and empty args 2014-02-14 19:43:16 +01:00
José Valim 207c626a6c Merge pull request #2057 from jwarwick/mix_underscore
Mix.Utils handling empty strings
2014-02-14 19:38:54 +01:00
John Warwick 7308d81729 Made Mix.Utils.camelize handle empty string 2014-02-14 13:05:05 -05:00
John Warwick 177a900be3 Make Mix.Utils.Underscore accept .'s and empty args 2014-02-14 12:26:26 -05:00
José Valim cb48e7670e Improve comprehensions test suite 2014-02-14 17:45:40 +01:00
José Valim a5889cffc0 Support nilly values and exceptions leaking in for filters 2014-02-14 17:41:54 +01:00
José Valim 933b3af54c Refactor translation and comprehension building in two stages 2014-02-14 11:31:28 +01:00
José Valim 8fcb832edc Support filters in comprehensions 2014-02-13 21:38:19 +01:00
José Valim 16c90ef97d Start translating for comprehensions 2014-02-13 20:38:56 +01:00
José Valim 8084f0a6bf Add a test for capture with access 2014-02-13 18:50:18 +01:00
José Valim 335d82e607 Start expanding for comprehensions 2014-02-13 18:47:53 +01:00
Eric Meadows-Jönsson c8813445c2 Revert "mix deps.get does not use MIX_ENV"
This reverts commit 1e9cd551ce.
2014-02-12 14:49:39 +01:00
Eric Meadows-Jönsson c51a2a5462 Revert "mix deps.get does not use MIX_ENV"
This reverts commit 1e9cd551ce.
2014-02-12 14:49:16 +01:00
Eric Meadows-Jönsson d0b28f4828 mix deps.get does not use MIX_ENV 2014-02-12 14:39:02 +01:00
Eric Meadows-Jönsson 1e9cd551ce mix deps.get does not use MIX_ENV 2014-02-12 14:33:33 +01:00
José Valim 8e5fec95fa Automatically recompile on outdated elixir version 2014-02-12 14:19:36 +01:00
José Valim 969d48ccaf Automatically recompile on outdated elixir version 2014-02-12 14:14:52 +01:00
José Valim 7715a09374 Remove private structs for now (new proposal coming soon) 2014-02-12 12:44:29 +01:00
José Valim 24203f8d9d Add v0.12.4 CHANGELOG 2014-02-12 12:19:16 +01:00
José Valim 2b04f540e2 Add guards to lstrip and rstrip 2014-02-12 12:17:55 +01:00
José Valim f2c914926d Emit warnings for using % as sigils 2014-02-12 11:16:37 +01:00
José Valim 11c4c2791d Add guards to lstrip and rstrip 2014-02-12 11:15:08 +01:00
José Valim 4051cf53dc Start v0.12.5-dev 2014-02-12 09:25:59 +01:00
José Valim 543dfdeac8 Release v0.12.4 2014-02-12 09:22:25 +01:00
José Valim 45c99aee82 Return remove_handler from handle_event 2014-02-11 12:44:07 +01:00
José Valim 2d25a6fb25 Fix test suite failure that showed up after randomization 2014-02-11 11:44:53 +01:00
José Valim aaf63a73b1 Randomize cases and tests in ExUnit 2014-02-11 11:38:53 +01:00
José Valim bacb1a530a Spawn one event manager per ExUnit run instead of a global one 2014-02-11 11:09:25 +01:00
Peter Minten c151739415 Use a gen_server for exunit formatter dispatch
This allows multiple formatters per run.

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2014-02-11 10:26:01 +01:00
José Valim d44e8bd1f6 Remove release files 2014-02-11 10:24:27 +01:00
José Valim 2a742e69e6 Update CHANGELOG 2014-02-11 10:07:35 +01:00
José Valim 5bda5d503b Support ERL_PATH in bin/elixir 2014-02-11 10:03:53 +01:00
Yuki Ito e871f7a9ca Add prompt option for IEx
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2014-02-11 09:48:48 +01:00
John Warwick 0aa4be5086 Fixed spec for Enum.flat_map 2014-02-11 07:25:00 +01:00
John Warwick 6fed5068ce Fixing result handling of :file.consult 2014-02-11 07:24:58 +01:00
John Warwick b1b1cd3263 Fixed spec for Enum.flat_map 2014-02-11 07:24:52 +01:00
John Warwick 55563dac1c Fixing result handling of :file.consult 2014-02-11 07:23:52 +01:00
John Warwick 548f24bbff Fixing doc typos in Version 2014-02-10 11:57:54 +01:00
John Warwick b478462fe2 Updating Version specs, dialyzer warnings 2014-02-10 11:57:52 +01:00
Eric Meadows-Jönsson ec350645a4 Merge pull request #2050 from jwarwick/version_spec
Updated specs in Version
2014-02-10 10:24:44 +01:00
John Warwick e4aa6e1bdf Fixing doc typos in Version 2014-02-09 22:47:05 -05:00
John Warwick f7f06010ae Updating Version specs, dialyzer warnings 2014-02-09 22:44:23 -05:00
José Valim ea34b7a302 Only raise on conflicts coming from functions 2014-02-09 21:20:24 +01:00
José Valim 73dc4536b0 Only raise on conflicts coming from functions 2014-02-09 21:20:16 +01:00
José Valim 2b298ab9fc CaptureIO now returns an empty string instead of nil when there is no capture 2014-02-09 16:44:15 +01:00
Yuki Ito 66ff417183 Write docs for StringIO 2014-02-09 16:19:54 +01:00
Yuki Ito c28b9b0ab4 Add default callbacks to StringIO 2014-02-09 16:19:48 +01:00
Yuki Ito 8058afe9c6 No need macro for string_io_test 2014-02-09 16:19:43 +01:00
Yuki Ito b6f80fc05f Use StringIO in CaptureIO
capture_io retunrs `""` instead of `nil`
2014-02-09 16:19:36 +01:00
Yuki Ito 920e056465 Implement StringIO 2014-02-09 16:19:28 +01:00
José Valim 6346029cf9 Clean up s/t/h helpers in IEx 2014-02-09 10:51:47 +01:00
José Valim 822dcd0557 Use export_head instead of export_all 2014-02-09 09:50:31 +01:00
José Valim 5ab85c7621 Use export_all for assert_received
We are using export_all for consistency given we can't
stop using export_all in case (moving case to the same
pattern as receive leads to warnings in the test suite).
2014-02-09 09:41:24 +01:00
Eric Meadows-Jönsson dbb6f358cc assert_receive(d) should leak
Closes #2030.
2014-02-08 21:48:02 +01:00
Eric Meadows-Jönsson 3a36c2e6ae assert_receive(d) should leak
Closes #2030.
2014-02-08 21:36:06 +01:00
José Valim 7e021d9bbb Ensure deps can run after mix deps.get 2014-02-08 20:02:22 +01:00
José Valim 6448de834c Ensure deps can run after mix deps.get 2014-02-08 20:02:01 +01:00
Eric Meadows-Jönsson 4a4a407644 Fix dependency compilation order 2014-02-08 19:41:50 +01:00
Eric Meadows-Jönsson f4d145c3a0 Fix dependency compilation order 2014-02-08 13:56:39 +01:00
José Valim 0bacbaeff7 Emit warnings for Regex.match? 2014-02-08 11:32:36 +01:00
José Valim 1844aec63e Use :application.ensure_all_started/1 2014-02-08 11:03:59 +01:00
José Valim baabbdec28 Update OTP release 2014-02-08 11:03:48 +01:00
José Valim 2137c28e4e Make module signature writer aware of structs 2014-02-08 10:54:29 +01:00
José Valim 79d8dd0020 Merge pull request #2042 from kiela/master
Add R16B03, R16B03-1 and 17.0-rc1 to .travis.yml
2014-02-08 10:49:25 +01:00
Kamil Kieliszczyk 9bce9e4fde Add R16B03, R16B03-1 and 17.0-rc1 to .travis.yml 2014-02-08 10:18:14 +01:00
José Valim 232a545d19 Avoid warnings on iex suite 2014-02-08 10:08:29 +01:00
José Valim 7a96e8131c Avoid warnings on iex suite 2014-02-08 10:07:46 +01:00
José Valim 675a2a60c8 No longer require regexes to be compiled with group information 2014-02-08 10:06:08 +01:00
José Valim b8c25bc258 Remove support to deprecated char lists in regexes 2014-02-08 09:00:53 +01:00
José Valim f76a867c75 Deprecate char lists in Regex 2014-02-08 08:46:31 +01:00
José Valim c1deb99b70 Deprecate char lists in Regex 2014-02-08 08:46:17 +01:00
José Valim 1625bdb454 Update CHANGELOG 2014-02-07 20:50:19 +01:00
José Valim 0776cc5a64 Do not compile after deps.get and deps.update 2014-02-07 20:48:02 +01:00
José Valim 371e96e2a8 Mark dependencies as requiring compilation every time they are fetched 2014-02-07 20:47:58 +01:00
José Valim 9aa41361a6 Compile dependencies when we can in deps.check 2014-02-07 20:47:53 +01:00
José Valim f17dec2c74 Take deps.loadpaths out of loadpaths to simplify call graph 2014-02-07 20:47:49 +01:00
José Valim b982822fdc Do not compile after deps.get and deps.update 2014-02-07 20:47:06 +01:00
José Valim 50820dfc9c Mark dependencies as requiring compilation every time they are fetched 2014-02-07 19:26:38 +01:00
José Valim ee0fb39822 Compile dependencies when we can in deps.check 2014-02-07 17:18:52 +01:00
José Valim 9760baff9c Take deps.loadpaths out of loadpaths to simplify call graph 2014-02-07 15:08:11 +01:00
pragdave 7641a5eded print_doc_arg attempted to handle argments with defaults. However, it added " \\ " between them, which is actually the string " \ ". This meant that (for example) "h IO.puts" dislayed "def puts(device \ group_leader(), item)". Changed it to escape both backslashes. 2014-02-07 12:54:34 +01:00
pragdave f7cd14d0f6 print_doc_arg attempted to handle argments with defaults. However, it added " \\ " between them, which is actually the string " \ ". This meant that (for example) "h IO.puts" dislayed "def puts(device \ group_leader(), item)". Changed it to escape both backslashes. 2014-02-07 12:54:25 +01:00
José Valim f3aad51922 Add a test to ensure we can still inspect "outdated" functions 2014-02-06 21:11:38 +01:00
José Valim b29603f1b7 Remove unecessary exceptions 2014-02-06 20:36:43 +01:00
José Valim afb04a19f9 Do not defined a URI.Error, simply raise ArgumentError 2014-02-06 20:36:39 +01:00
José Valim 848218cdac Remove unecessary exceptions 2014-02-06 20:36:21 +01:00
José Valim 6a1cef052f Do not defined a URI.Error, simply raise ArgumentError 2014-02-06 20:32:27 +01:00
José Valim 455076446b Raise URI.Error on malformed URIs, closes #2034 2014-02-06 19:49:24 +01:00
José Valim 50e9ae6ca7 Raise URI.Error on malformed URIs, closes #2034 2014-02-06 19:40:44 +01:00
José Valim 6e83cb8d82 Implement structs fallback in Inspect and add structs option to Inspect.Opts 2014-02-06 17:18:28 +01:00
José Valim 1f0861771a Implement protocol support for structs 2014-02-06 15:56:21 +01:00
José Valim 4917bfca29 Update Erlang dependency in rebar 2014-02-06 14:17:19 +01:00
José Valim b46481e12f Improve doc test error messages 2014-02-06 14:14:03 +01:00
José Valim e4c2fc2ac1 Document %{} and % special forms 2014-02-06 13:22:30 +01:00
José Valim 99e69bd6c5 Support structs special forms 2014-02-06 12:30:14 +01:00
Dima Aleksandrov 4d366f4688 * fix Enum.reduce for Maps
* implement Map module
* add Map as Dict implementation

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2014-02-05 21:58:40 +01:00
José Valim daa0049b32 Move map and struct functionality to elixir_map 2014-02-05 17:37:16 +01:00
Dima Aleksandrov bc06c5e43f implement updates on maps
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2014-02-05 17:18:36 +01:00
José Valim da1541c791 Implement access and slightly reorganize tests 2014-02-05 16:11:53 +01:00
José Valim 1ce2433212 Implement Enum for maps and add map_size to Kernel 2014-02-05 15:41:31 +01:00
José Valim 8cecbf5e9d Temporarily disable consolidation since we cant handle both records and structs at once 2014-02-05 15:27:34 +01:00
Dima Aleksandrov a1b53d35ea * implement Inspect for keyword in maps
* add basic tests for inspecting maps
* add basic support for inspecting structs

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2014-02-05 15:00:46 +01:00
José Valim de71ecf195 Remove outdated default args syntax 2014-02-05 14:58:28 +01:00
José Valim 05a85a8e9c Effectively deprecate // and deprecate ^ in anonymous function clause heads 2014-02-05 14:50:45 +01:00
José Valim 5a618258a3 Remove deprecated sigils 2014-02-05 14:41:00 +01:00
José Valim ba54afbf45 Move to new sigils syntax 2014-02-05 14:39:04 +01:00
José Valim 2af50f7b78 Move to new sigils syntax 2014-02-05 14:38:54 +01:00
José Valim 1ea44670ce Properly raise on matching on map keys 2014-02-05 13:45:26 +01:00
José Valim ff0df96cfc Get rid of record rewriter 2014-02-05 13:06:27 +01:00
José Valim 3476f224b7 Ensure maps properly handle variables in values 2014-02-05 13:03:01 +01:00
José Valim e612592e21 Inline is_map, ensure proper traversal 2014-02-05 11:56:52 +01:00
Dima Aleksandrov cfbd5bbb7f Basic maps compilation 2014-02-05 11:46:01 +01:00
José Valim 46e000394b Merge pull request #2031 from liveforeverx/v0.13
Macro.to_string/1 for maps and struct AST
2014-02-04 23:46:47 -08:00
Dima Aleksandrov 875b95f609 Macro.to_string/1 for maps and struct AST 2014-02-05 08:22:31 +01:00
José Valim ffdf3d32b2 Ensure multiple kw args with pipe 2014-02-04 22:30:51 +01:00
José Valim 1fa74df6c7 Support map updates 2014-02-04 20:54:15 +01:00
José Valim cb9c17c025 Allow %{ foo => bar } 2014-02-04 18:00:04 +01:00
José Valim 923eeb9602 Unify max_expr to avoid duplication 2014-02-04 17:22:43 +01:00
José Valim c5ff2d1dcf Basic support for maps in the grammar 2014-02-03 20:18:01 +01:00
José Valim c65dcc9b19 Refactor grammar and unify base_expr into max_expr 2014-02-03 17:52:27 +01:00
José Valim 15dd4d3c07 Move to_matchable back to private 2014-02-03 15:18:34 +01:00
José Valim 058d55b68c Fix test suite warnings on 17.0-rc1
Those warnings happen because we need to test basic functionality,
like `nil?(nil) == true` and the Erlang compiler can see this
expression will always work.

We can work around this issue by adding a bit of indirection, like
`nil?(List.first([nil])) == true`.
2014-02-03 13:45:27 +01:00
José Valim 22ebd5f2d6 Fix build and update CHANGELOG 2014-02-03 10:09:24 +01:00
José Valim 8744abc39f Slightly reword version error messages 2014-02-03 09:45:36 +01:00
José Valim 1574b9380b Use throw/catch only inside matchspec. Keep to_matchspec/1 and from_matchspec/1 as public 2014-02-03 09:37:55 +01:00
José Valim 66441fb2cb Merge pull request #2028 from ericmj/version-semver
Enforce semantic versioning
2014-02-02 23:53:53 -08:00
Eric Meadows-Jönsson 2466307b74 :override option should override the version requirement 2014-02-02 22:14:56 +01:00
Eric Meadows-Jönsson 6a5b25045c Mix error messages should be capitalized 2014-02-02 20:30:53 +01:00
Eric Meadows-Jönsson 742a922b90 Only allow semver versions for mix projects 2014-02-02 20:29:38 +01:00
Eric Meadows-Jönsson 2e2d534257 Better Mix errors when version or requirements are invalid 2014-02-02 20:29:38 +01:00
Eric Meadows-Jönsson 271700692a Simplify Version interface
* Remove the validation functions in favor of parsing functions returning
error tuples.
* `match?/2` and `compare/2` only raises on parse errors, if given already
parsed version and requirement they will not raise.
2014-02-02 20:28:46 +01:00
Eric Meadows-Jönsson 6bc699672b Only allow semver versions in Version
Functions in Version that previously raised on invalid versions
or requirements now returns an error tuple.
2014-02-02 20:28:46 +01:00
José Valim c07b0d0912 Remove deprecate code 2014-02-02 20:22:28 +01:00
Eric Meadows-Jönsson 61cac4eebc Enable erlang 17.0 support and squelch some warnings 2014-02-02 20:20:19 +01:00
José Valim b723da6574 Start 0.12.4-dev 2014-02-02 19:55:12 +01:00
Peter Minten f87e5d3c12 Improve Enumerable count/member? docs 2014-01-11 14:49:17 +01:00
371 changed files with 21954 additions and 15774 deletions
+1 -3
View File
@@ -6,6 +6,4 @@ notifications:
- jose.valim@plataformatec.com.br
- eric.meadows.jonsson@gmail.com
otp_release:
- R16B02
- R16B01
- R16B
- 17.0
+164 -2
View File
@@ -1,3 +1,165 @@
# v0.13.2 (2014-05-11)
* Enhancements
* [Application] Add an Application module with common functions to work with OTP applications
* [Exception] Add `Exception.message/1`, `Exception.format_banner/1`, `Exception.format_exit/1` and `Exception.format/1`
* [File] Add `File.ln_s/1`
* [Mix] `mix deps.clean` now works accross environments
* [Mix] Support line numbers in `mix test`, e.g. test/some/file_test.exs:12
* [Mix] Use `@file` attributes to detect dependencies in between `.ex` and external files. This means changing an `.eex` file will no longer recompile the whole project only the files that depend directly on it
* [Mix] Support application configurations in `config/config.exs` which can be customized by specifying your own `:config_path`
* [Mix] Support user-wide configuration with `~/.mix/config.exs`
* [Mix] `mix help` now uses ANSI formatting to print guides
* [Regex] Support functions in `Regex.replace/4`
* [String] Support `:parts` in `String.split/3`
* Bug fixes
* [Code] Ensure we don't lose the caller stacktrace on code evaluation
* [IEx] Exit signals now exits the IEx evaluator and a new one is spawned on its place
* [IEx] Ensure we don't prune too much stacktrace when reporting failures
* [IEx] Fix an issue where `iex.bat` on Windows was not passing the proper parameters forward
* [Kernel] Ensure modules defined on root respect defined aliases
* [Kernel] Do not wrap single lists in `:__block__`
* [Kernel] Ensure emitted beam code works nicely with dialyzer
* [Kernel] Do not allow a module named `Elixir` to be defined
* [Kernel] Create remote funs even if mod is a variable in capture `&mod.fun/arity`
* [Kernel] Improve compiler message when duplicated modules are detected
* [Mix] Generate `.gitignore` for `--umbrella` projects
* [Mix] Verify if a git dependency in deps has a proper git checkout and clean it automatically when it doesn't
* [Mix] Ensure `mix test` works with `IEx.pry/0`
* [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`
* [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`
* Deprecations
* [IEx] IEx.Options is deprecated in favor of `IEx.configure/1` and `IEx.configuration/0`
* [Kernel] `lc` and `bc` comprehensions are deprecated in favor of `for`
* [Macro] `Macro.safe_terms/1` is deprecated
* [Process] `Process.delete/0` is deprecated
* [Regex] Deprecate `:global` option in `Regex.split/3` in favor of `parts: :infinity`
* [String] Deprecate `:global` option in `String.split/3` in favor of `parts: :infinity`
* Backwards incompatible changes
* [ExUnit] `ExUnit.Test` and `ExUnit.TestCase` has been converted to structs
* [ExUnit] The test and callback context has been converted to maps
* [Kernel] `File.Stat`, `HashDict`, `HashSet`, `Inspect.Opts`, `Macro.Env`, `Range`, `Regex` and `Version.Requirement` have been converted to structs. This means `is_record/2` checks will no longer work, instead, you can pattern match on them using `%Range{}` and similar
* [URI] The `URI.Info` record has now become the `URI` struct
* [Version] The `Version.Schema` record has now become the `Version` struct
# v0.13.1 (2014-04-27)
* Enhancements
* [Mix] Support `MIX_EXS` as configuration for running the current mix.exs file
* [Mix] Support Hex out of the box. This means users do not need to install Hex directly, instead, Mix will prompt whenever there is a need to have Hex installed
* Bug fixes
* [ExUnit] Ensure doctest failures are properly reported
* [Kernel] Fix a bug where comprehensions arguments were not properly take into account in the variable scope
* [Mix] Fix issue on rebar install when the endpoint was redirecting to a relative uri
* Soft deprecations (no warnings emitted)
* [Kernel] `iolist_size` and `iolist_to_binary` are deprecated in favor of `iodata_length` and `iodata_to_binary`
* [String] `String.to_char_list/1` is deprecated in favor of `List.from_char_data/1`
* [String] `String.from_char_list/1` is deprecated in favor of `String.from_char_data/1`
* Deprecations
* [Mix] `:env` key in project configuration is deprecated
* [Regex] `Regex.groups/1` is deprecated in favor of `Regex.names/1`
* Backwards incompatible changes
* [Macro] `Macro.unpipe/1` now returns tuples and `Macro.pipe/2` was removed in favor of `Macro.pipe/3` which explicitly expects the second element of the tuple returned by the new `Macro.unpipe/1`
* [Path] The functions in Path now only emit strings as result, regardless if the input was a char list or a string
* [Path] Atoms are no longer supported in Path functions
* [Regex] Regexes are no longer unicode by default. Instead, they must be explicitly marked with the `u` option
# v0.13.0 (2014-04-20)
* Enhancements
* [Base] Add `Base` module which does conversions to bases 16, 32, hex32, 64 and url64
* [Code] Add `Code.eval_file/2`
* [Collectable] Add the `Collectable` protocol that empowers `Enum.into/2` and `Stream.into/2` and the `:into` option in comprehensions
* [Collectable] Implement `Collectable` for lists, dicts, bitstrings, functions and provide both `File.Stream` and `IO.Stream`
* [EEx] Add `handle_body/1` callback to `EEx.Engine`
* [Enum] Add `Enum.group_by/2`, `Enum.into/2`, `Enum.into/3`, `Enum.traverse/2` and `Enum.sum/2`
* [ExUnit] Randomize cases and tests suite runs, allow seed configuration and the `--seed` flag via `mix test`
* [ExUnit] Support `--only` for filtering when running tests with `mix test`
* [ExUnit] Raise an error if another `capture_io` process already captured the device
* [ExUnit] Improve formatter to show source code and rely on lhs and rhs (instead of expected and actual)
* [IEx] Allow prompt configuration with the `:prompt` option
* [IEx] Use werl on Windows
* [Kernel] Support `ERL_PATH` in `bin/elixir`
* [Kernel] Support interpolation in keyword syntax
* [Map] Add a Map module and support 17.0 maps and structs
* [Mix] Add dependency option `:only` to specify the dependency environment. `mix deps.get` and `mix deps.update` works accross all environment unless `--only` is specified
* [Mix] Add `Mix.Shell.prompt/1`
* [Mix] Ensure the project is compiled in case Mix' CLI cannot find a task
* [Node] Add `Node.ping/1`
* [Process] Include `Process.send/3` and support the `--gen-debug` option
* [Regex] Regexes no longer need the "g" option when there is a need to use named captures
* [Stream] Add `Stream.into/2` and `Stream.into/3`
* [StringIO] Add a `StringIO` module that allows a String to be used as IO device
* [System] Add `System.delete_env/1` to remove a variable from the environment
* Bug fixes
* [CLI] Ensure `--app` is handled as an atom before processing
* [ExUnit] Ensure `ExUnit.Assertions` does not emit compiler warnings for `assert_receive`
* [Kernel] Ensure the same pid is not queued twice in the parallel compiler
* [Macro] `Macro.to_string/2` considers proper precedence when translating `!(foo > bar)` into a string
* [Mix] Automatically recompile on outdated Elixir version and show proper error messages
* [Mix] Ensure generated `.app` file includes core dependencies
* [Mix] Allow a dependency with no SCM to be overridden
* [Mix] Allow queries in `mix local.install` URL
* [OptionParser] Do not recognize undefined aliases as switches
* Soft deprecations (no warnings emitted)
* [Kernel] `lc` and `bc` comprehensions are deprecated in favor of `for`
* [ListDict] `ListDict` is deprecated in favor of `Map`
* [Record] `defrecord/2`, `defrecordp/3`, `is_record/1` and `is_record/2` macros in Kernel are deprecated. Instead, use the new macros and API defined in the `Record` module
* Deprecations
* [Dict] `Dict.empty/1`, `Dict.new/1` and `Dict.new/2` are deprecated
* [Exception] `Exception.normalize/1` is deprecated in favor of `Exception.normalize/2`
* Backwards incompatible changes
* [ExUnit] Formatters are now required to be a GenEvent and `ExUnit.run/2` returns a map with results
# v0.12.5 (2014-03-09)
* Bug fixes
* [Kernel] Ensure `try` does not generate an after clause. Generating an after clause forbade clauses in the `else` part from being tail recursive. This should improve performance and memory consumption of `Stream` functions
* [Mix] Automatically recompile on outdated Elixir version and show proper error messages
* Deprecations
* [File] `File.stream_to!/3` is deprecated
* [GenFSM] `GenFSM` is deprecated
* [Kernel] `%` for sigils is deprecated in favor of `~`
* [Kernel] `is_range/1` and `is_regex/1` are deprecated in favor of `Range.range?/1` and `Regex.regex?/1`
* [Stream] `Stream.after/1` is deprecated
* [URI] `URI.decode_query/1` is deprecated in favor of `URI.decode_query/2` with explicit dict argument
* [URI] Passing lists as key or values in `URI.encode_query/1` is deprecated
* Backwards incompatible changes
* [Mix] Remove `MIX_GIT_FORCE_HTTPS` as Git itself already provides mechanisms for doing so
# v0.12.4 (2014-02-12)
* Enhancements
* [Mix] `mix deps.get` and `mix deps.update` no longer compile dependencies afterwards. Instead, they mark the dependencies which are going to be automatically compiled next time `deps.check` is invoked (which is done automatically by most mix tasks). This means users should have a better workflow when migrating in between environments
* Deprecations
* [Kernel] `//` for default arguments is deprecated in favor of `\\`
* [Kernel] Using `%` for sigils is deprecated in favor of `~`. This is a soft deprecation, no warnings will be emitted for it in this release
* [Kernel] Using `^` inside function clause heads is deprecated, please use a guard instead
* Backwards incompatible changes
* [ExUnit] `CaptureIO` returns an empty string instead of nil when there is no capture
* [Version] The `Version` module now only works with SemVer. The functions `Version.parse/1` and `Version.parse_requirement/1` now return `{:ok,res} | :error` for the cases you want to handle non SemVer cases manually. All other functions will trigger errors on non semantics versions
# v0.12.3 (2014-02-02)
* Enhancements
@@ -710,7 +872,7 @@
* [ExUnit] Support context data in `setup_all`, `setup`, `teardown` and `teardown_all` callbacks
* [IEx] Support `after_spawn` callbacks which are invoked after each process is spawned
* [Kernel] Better error messages when invalid options are given to `import`, `alias` or `require`
* [Kernel] Allow partial application on literals, for example: `{ &1, &2 }` to build tuples or `[&1|&2]` to build cons cells
* [Kernel] Allow partial application on literals, for example: `{&1, &2}` to build tuples or `[&1|&2]` to build cons cells
* [Kernel] Added `integer_to_binary` and `binary_to_integer`
* [Kernel] Added `float_to_binary` and `binary_to_float`
* [Kernel] Many improvements to `unquote` and `unquote_splicing`. For example, `unquote(foo).unquote(bar)(args)` is supported and no longer need to be written via `apply`
@@ -763,7 +925,7 @@
* [Kernel] Deprecate `__LINE__` and `__FUNCTION__` in favor of `__ENV__.line` and `__ENV__.function`
* [Kernel] Deprecate `in_guard` in favor of `__CALLER__.in_guard?`
* [Kernel] `refer` is deprecated in favor of `alias`
* [Module] `Module.add_compile_callback(module, target, callback)` is deprecated in favor of `Module.put_attribute(module, :before_compile, { target, callback })`
* [Module] `Module.add_compile_callback(module, target, callback)` is deprecated in favor of `Module.put_attribute(module, :before_compile, {target, callback})`
* [Module] `Module.function_defined?` is deprecated in favor of `Module.defines?`
* [Module] `Module.defined_functions` is deprecated in favor of `Module.definitions_in`
+28 -17
View File
@@ -34,16 +34,15 @@ Guidelines for bug reports:
reported.
2. **Check if the issue has been fixed** &mdash; try to reproduce it using the
latest `master` or development branch in the repository.
`master` branch in the repository.
3. **Isolate the problem** &mdash; ideally create a reduced test
3. **Isolate and report the problem** &mdash; ideally create a reduced test
case.
A good bug report shouldn't leave others needing to chase you up for more
information. Please try to be as detailed as possible in your report. What is
your environment? What steps will reproduce the issue? What version of Erlang
and Elixir experience the problem? What would you expect to be the outcome?
All these details will help people to fix any potential bugs.
Please try to be as detailed as possible in your report. Include information about
your Operating System, your Erlang and Elixir versions. Please provide steps to
reproduce the issue as well as the outcome you were expecting! All these details
will help developers to fix any potential bugs.
Example:
@@ -67,8 +66,8 @@ Example:
Feature requests are welcome and should be discussed on [the elixir-core mailing list](http://groups.google.com/group/elixir-lang-core). But take a moment to find
out whether your idea fits with the scope and aims of the project. It's up to *you*
to make a strong case to convince the project's developers of the merits of this
feature. Please provide as much detail and context as possible.
to make a strong case to convince the community of the merits of this feature.
Please provide as much detail and context as possible.
## Contributing
@@ -91,6 +90,16 @@ You can run all tests in the root directory with `make test` and you can
also run tests for a specific framework `make test_#{NAME}`, for example,
`make test_ex_unit`.
In case you are changing a single file, you can compile and run tests only
for that particular file for fast development cycles. For example, if you
are changing the String module, you can compile it and run its tests as:
$ bin/elixirc lib/elixir/lib/string.ex -o lib/elixir/ebin
$ bin/elixir lib/elixir/test/elixir/string_test.exs
After your changes are done, please remember to run the full suite with
`make test`.
From time to time, your tests may fail in an existing Elixir checkout and
may require a clean start by running `make clean compile`. You can always
check [the official build status on Travis-CI](https://travis-ci.org/elixir-lang/elixir).
@@ -108,23 +117,25 @@ something like:
```elixir
@doc """
Return only those elements for which `fun` is true.
Returns only those elements for which `fun` is true.
...
"""
def filter(collection, fun) ...
```
For modules, records, protocols and types say what it is. For example write
For modules, protocols and types say what it is. For example write
something like:
```elixir
defrecord File.Stat, [...] do
defmodule File.Stat do
@moduledoc """
Information about a file.
...
"""
defstruct [...]
end
```
@@ -154,7 +165,7 @@ def filter(collection, fun) ...
```
This makes it easy to test the examples so that they don't go stale and examples
are often a great help in explaining what a function does.
are often a great help in explaining what a function does.
## Pull requests
@@ -165,10 +176,10 @@ commits.
**IMPORTANT**: By submitting a patch, you agree that your work will be
licensed under the license used by the project.
If you have any significant pull request in mind (e.g. implementing features,
refactoring code, porting to a different language), **please ask first**
otherwise you risk spending a lot of time working on something that the
project's developers might not want to merge into the project.
If you have any large pull request in mind (e.g. implementing features,
refactoring code, etc), **please ask first** otherwise you risk spending
a lot of time working on something that the project's developers might
not want to merge into the project.
Please adhere to the coding conventions in the project (indentation,
accurate comments, etc.) and don't forget to add your own tests and
+21 -14
View File
@@ -1,5 +1,5 @@
REBAR := rebar
ELIXIRC := bin/elixirc --verbose --ignore-module-conflict $(ELIXIRC_OPTS)
ELIXIRC := bin/elixirc --verbose --ignore-module-conflict
ERLC := erlc -I lib/elixir/include
ERL := erl -I lib/elixir/include -noshell -pa lib/elixir/ebin
VERSION := $(strip $(shell cat VERSION))
@@ -11,10 +11,20 @@ INSTALL_DIR = $(INSTALL) -m755 -d
INSTALL_DATA = $(INSTALL) -m644
INSTALL_PROGRAM = $(INSTALL) -m755
.PHONY: install compile erlang elixir dialyze test clean docs release_docs release_zip release_erl
.PHONY: install compile erlang elixir dialyze test clean docs release_docs release_zip check_erlang_release
.NOTPARALLEL: compile
#==> Templates
#==> Functions
# This check should work for older versions like R16B
# as well as new verions like 17.1 and 18
define CHECK_ERLANG_RELEASE
$(Q) erl -noshell -eval 'io:fwrite("~s", [erlang:system_info(otp_release)])' -s erlang halt | grep -q '^1[789]'; \
if [ $$? != 0 ]; then \
echo "At least Erlang 17.0 is required to build Elixir"; \
exit 1; \
fi;
endef
define APP_TEMPLATE
$(1): lib/$(1)/ebin/Elixir.$(2).beam lib/$(1)/ebin/$(1).app
@@ -46,6 +56,7 @@ default: compile
compile: lib/elixir/src/elixir.app.src erlang elixir
lib/elixir/src/elixir.app.src: src/elixir.app.src
$(Q) $(call CHECK_ERLANG_RELEASE)
$(Q) rm -rf lib/elixir/src/elixir.app.src
$(Q) echo "%% This file is automatically generated from <project_root>/src/elixir.app.src" \
>lib/elixir/src/elixir.app.src
@@ -61,7 +72,7 @@ elixir: kernel lib/eex/ebin/Elixir.EEx.beam mix ex_unit eex iex
kernel: $(KERNEL) VERSION
$(KERNEL): lib/elixir/lib/*.ex lib/elixir/lib/*/*.ex
$(Q) if [ ! -f $(KERNEL) ]; then \
$(Q) if [ ! -f $(KERNEL) ]; then \
echo "==> bootstrap (compile)"; \
$(ERL) -s elixir_compiler core -s erlang halt; \
fi
@@ -85,14 +96,14 @@ $(eval $(call APP_TEMPLATE,iex,IEx))
install: compile
@ echo "==> elixir (install)"
for dir in lib/*; do \
$(Q) for dir in lib/*; do \
$(INSTALL_DIR) "$(DESTDIR)$(PREFIX)/$(LIBDIR)/elixir/$$dir/ebin"; \
$(INSTALL_DATA) $$dir/ebin/* "$(DESTDIR)$(PREFIX)/$(LIBDIR)/elixir/$$dir/ebin"; \
done
$(INSTALL_DIR) "$(DESTDIR)$(PREFIX)/$(LIBDIR)/elixir/bin"
$(INSTALL_PROGRAM) $(filter-out %.bat, $(wildcard bin/*)) "$(DESTDIR)$(PREFIX)/$(LIBDIR)/elixir/bin"
$(INSTALL_DIR) "$(DESTDIR)$(PREFIX)/bin"
for file in "$(DESTDIR)$(PREFIX)"/$(LIBDIR)/elixir/bin/* ; do \
$(Q) $(INSTALL_DIR) "$(DESTDIR)$(PREFIX)/$(LIBDIR)/elixir/bin"
$(Q) $(INSTALL_PROGRAM) $(filter-out %.bat, $(wildcard bin/*)) "$(DESTDIR)$(PREFIX)/$(LIBDIR)/elixir/bin"
$(Q) $(INSTALL_DIR) "$(DESTDIR)$(PREFIX)/bin"
$(Q) for file in "$(DESTDIR)$(PREFIX)"/$(LIBDIR)/elixir/bin/* ; do \
ln -sf "../$(LIBDIR)/elixir/bin/$${file##*/}" "$(DESTDIR)$(PREFIX)/bin/" ; \
done
@@ -127,17 +138,13 @@ docs: compile ../ex_doc/bin/ex_doc
release_zip: compile
rm -rf v$(VERSION).zip
zip -9 -r v$(VERSION).zip bin CHANGELOG.md LEGAL lib/*/ebin LICENSE README.md rel VERSION
zip -9 -r v$(VERSION).zip bin CHANGELOG.md LEGAL lib/*/ebin LICENSE README.md VERSION
release_docs: docs
cd ../docs
rm -rf ../docs/master
mv docs ../docs/master
release_erl: compile
$(Q) rm -rf rel/elixir
$(Q) cd rel && ../rebar generate
#==> Tests tasks
test: test_erlang test_elixir
+3 -3
View File
@@ -1,6 +1,6 @@
![Elixir](https://github.com/elixir-lang/elixir-lang.github.com/raw/master/images/logo/logo.png)
=========
[![Build Status](https://secure.travis-ci.org/elixir-lang/elixir.png?branch=master "Build Status")](http://travis-ci.org/elixir-lang/elixir)
[![Build Status](https://secure.travis-ci.org/elixir-lang/elixir.svg?branch=master "Build Status")](http://travis-ci.org/elixir-lang/elixir)
For more about Elixir, installation and documentation, [check Elixir's website](http://elixir-lang.org/).
@@ -16,9 +16,9 @@ If Elixir fails to build (specifically when pulling in a new version via git), b
If tests pass, you are ready to move on to the [Getting Started guide][1] or to try Interactive Elixir by running: `bin/iex` in your terminal.
However, if tests fail, it is likely you have an outdated Erlang version (Elixir requires Erlang R16B or later). You can check your Erlang version by calling `erl` in the command line. You will see some information as follows:
However, if tests fail, it is likely you have an outdated Erlang version (Elixir requires Erlang 17.0 or later). You can check your Erlang version by calling `erl` in the command line. You will see some information as follows:
Erlang R16B (erts-5.10.1) [source] [64-bit] [smp:2:2] [rq:2] [async-threads:0] [hipe] [kernel-poll:false]
Erlang/OTP 17 [erts-6.0] [source-07b8f44] [64-bit] [smp:4:4] [async-threads:10] [hipe] [kernel-poll:false]
If you have the correct version and tests still fail, feel free to [open an issue][2].
+7 -9
View File
@@ -4,25 +4,23 @@ This document simply outlines the release process:
1) Remove `-dev` extension from VERSION
2) Run `make clean test` to ensure all tests pass from scratch and the CI is green
2) Ensure CHANGELOG is updated and timestamp
3) Ensure CHANGELOG is updated and tag release version with timestamp in it
3) Commit changes above with title "Release vVERSION" and generate new tag
4) Commit changes above with title "Release vVERSION"
4) Run `make clean test` to ensure all tests pass from scratch and the CI is green
5) Push master and create tag vVERSION from master branch
5) Push master and tags
6) Release new docs with `make release_docs`, move docs to `docs/stable`
7) Release new zip with `make release_zip`, push new zip to GitHub Releases
8) Push package to expm with `expm publish package.exs`
8) Merge master into stable branch and push it
9) Merge master into stable branch and push it
9) After release, bump versions, add `-dev` back and commit
10) After release, bump versions, add `-dev` back and commit
11) `make release_docs` once again and push `elixir-lang.github.com`
10) `make release_docs` once again and push it to `elixir-lang.github.com`
## Places where version is mentioned
+1 -1
View File
@@ -1 +1 @@
0.12.3
0.13.2
+21 -7
View File
@@ -17,6 +17,7 @@ if [ $# -eq 0 ] || [ "$1" = "--help" ] || [ "$1" = "-h" ]; then
--hidden Makes a hidden node
--detached Starts the Erlang VM detached from console
--no-halt Does not halt the Erlang VM after execution
--gen-debug Turns on default debugging for all GenServers
** Options marked with (*) can be given more than once
** Options given after the .exs file or -- are passed down to the executed code
@@ -34,6 +35,7 @@ readlink_f () {
fi
}
MODE="elixir"
ERL=""
I=1
@@ -41,7 +43,13 @@ while [ $I -le $# ]; do
S=1
eval "PEEK=\${$I}"
case "$PEEK" in
-v|--compile|--no-halt|+iex|+compile)
+iex)
MODE="iex"
;;
+elixirc)
MODE="elixirc"
;;
-v|--compile|--no-halt)
;;
-e|-r|-pr|-pa|-pz|--remsh|--app)
S=2
@@ -64,6 +72,9 @@ while [ $I -le $# ]; do
eval "VAL=\${$I}"
ERL="$ERL "$VAL""
;;
--gen-debug)
ERL="$ERL -generic_debug"
;;
*)
break
;;
@@ -73,11 +84,14 @@ done
SELF=$(readlink_f "$0")
SCRIPT_PATH=$(dirname "$SELF")
if [ "$ELIXIR_NO_CLI" != "1" ]; then ERL="$ERL -s elixir start_cli"; fi
if [ "$MODE" != "iex" ]; then ERL="$ERL -s elixir start_cli"; fi
if [ -f "$SCRIPT_PATH/../releases/RELEASES" ] && [ -f "$SCRIPT_PATH/erl" ]
then
exec "$SCRIPT_PATH"/erl -pa "$SCRIPT_PATH"/../lib/*/ebin -noshell $ELIXIR_ERL_OPTS $ERL -boot start -extra "$@"
else
exec erl -pa "$SCRIPT_PATH"/../lib/*/ebin -noshell $ELIXIR_ERL_OPTS $ERL -extra "$@"
if [ -z "$ERL_PATH" ]; then
if [ -f "$SCRIPT_PATH/../releases/RELEASES" ] && [ -f "$SCRIPT_PATH/erl" ]; then
ERL_PATH="$SCRIPT_PATH"/erl
else
ERL_PATH=erl
fi
fi
exec "$ERL_PATH" -pa "$SCRIPT_PATH"/../lib/*/ebin -noshell $ELIXIR_ERL_OPTS $ERL -extra "$@"
+20 -7
View File
@@ -1,11 +1,14 @@
@echo off
set argc=0
for %%x in (%*) do set /A argc+=1
if %argc%== 0 (
goto documentation
) else (
goto parseopts
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
goto parseopts
:documentation
echo Usage: %~nx0 [options] [.exs file] [data]
echo.
@@ -24,6 +27,7 @@ echo --cookie cookie Sets a cookie for this distributed node
echo --hidden Makes a hidden node
echo --detached Starts the Erlang VM detached from console
echo --no-halt Does not halt the Erlang VM after execution
echo --gen-debug Turns on default debugging for all GenServers
echo.
echo ** Options marked with (*) can be given more than once
echo ** Options given after the .exs file or -- are passed down to the executed code
@@ -44,6 +48,9 @@ set originPath=%~dp0
rem Optional parameters before the "-extra" parameter
set beforeExtra=
rem Flag which determines whether or not to use werl vs erl
set useWerl=0
rem Recursive loop called for each parameter that parses the cmd line parameters
:startloop
set par="%1"
@@ -56,6 +63,8 @@ if "%par%"=="""" (
rem if no parameters defined - special case for parameter that is already quoted
goto :expand_erl_libs
)
rem ******* EXECUTION OPTIONS **********************
IF "%par%"==""+iex"" (Set useWerl=1)
rem ******* ERLANG PARAMETERS **********************
IF NOT "%par%"=="%par:--detached=%" (Set parsErlang=%parsErlang% -detached)
IF NOT "%par%"=="%par:--hidden=%" (Set parsErlang=%parsErlang% -hidden)
@@ -63,6 +72,7 @@ IF NOT "%par%"=="%par:--cookie=%" (Set parsErlang=%parsErlang% -setcookie %1 &
IF NOT "%par%"=="%par:--sname=%" (Set parsErlang=%parsErlang% -sname %1 && shift)
IF NOT "%par%"=="%par:--name=%" (Set parsErlang=%parsErlang% -name %1 && shift)
IF NOT "%par%"=="%par:--erl=%" (Set beforeExtra=%beforeExtra% %~1 && shift)
IF NOT "%par%"=="%par:--gen-debug=%" (Set parsErlang=%parsErlang% -generic_debug)
rem ******* elixir parameters **********************
rem Note: we don't have to do anything with options that don't take an argument
IF NOT "%par%"=="%par:-e=%" (shift)
@@ -84,5 +94,8 @@ for /d %%d in ("%originPath%..\lib\*.") do (
)
SETLOCAL disabledelayedexpansion
:run
erl %ext_libs% -noshell %ELIXIR_ERL_OPTS% %parsErlang% -s elixir start_cli %beforeExtra% -extra %*
IF %useWerl% EQU 1 (
werl %ext_libs% -noshell %ELIXIR_ERL_OPTS% %parsErlang% -s elixir start_cli %beforeExtra% -extra %*
) ELSE (
erl %ext_libs% -noshell %ELIXIR_ERL_OPTS% %parsErlang% -s elixir start_cli %beforeExtra% -extra %*
)
+1 -1
View File
@@ -26,4 +26,4 @@ readlink_f () {
SELF=$(readlink_f "$0")
SCRIPT_PATH=$(dirname "$SELF")
exec "$SCRIPT_PATH"/elixir +compile "$@"
exec "$SCRIPT_PATH"/elixir +elixirc "$@"
+9 -6
View File
@@ -1,11 +1,14 @@
@echo off
set argc=0
for %%x in (%*) do set /A argc+=1
if %argc% == 0 (
goto documentation
) else (
goto run
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
goto run
:documentation
echo Usage: %~nx0 [elixir switches] [compiler switches] [.ex files]
echo.
@@ -20,4 +23,4 @@ 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
:run
call "%~dp0\elixir.bat" +compile %*
call "%~dp0\elixir.bat" +elixirc %*
+3 -2
View File
@@ -16,13 +16,14 @@ if [ $# -gt 0 ] && ([ "$1" = "--help" ] || [ "$1" = "-h" ]); then
--cookie \"cookie\" Sets a cookie for this distributed node
--hidden Makes a hidden node
--detached Starts the Erlang VM detached from console
--gen-debug Turns on default debugging for all GenServers
--remsh \"name\" Connects to a node using a remote shell
--dot-iex \"path\" Overrides default .iex.exs file and uses path instead;
path can be empty, then no file will be loaded
** Options marked with (*) can be given more than once
** Options given after the .exs file or -- are passed down to the executed code
** Options can be passed to the erlang runtime using ELIXIR_ERL_OPTS or --erl" >&2
** Options can be passed to the VM using ELIXIR_ERL_OPTS or --erl" >&2
exit 1
fi
@@ -38,4 +39,4 @@ readlink_f () {
SELF=$(readlink_f "$0")
SCRIPT_PATH=$(dirname "$SELF")
ELIXIR_NO_CLI=1 exec "$SCRIPT_PATH"/elixir --no-halt --erl "-user Elixir.IEx.CLI" +iex "$@"
exec "$SCRIPT_PATH"/elixir --no-halt --erl "-user Elixir.IEx.CLI" +iex "$@"
+1 -1
View File
@@ -1,2 +1,2 @@
@echo off
call "%~dp0\elixir.bat" +iex --no-halt -e "IEx.start" %*
call "%~dp0\elixir.bat" +iex --erl "-user Elixir.IEx.CLI" --no-halt %*
+19 -21
View File
@@ -1,12 +1,12 @@
defexception EEx.SyntaxError, message: nil
defmodule EEx do
@moduledoc %S"""
@moduledoc ~S"""
EEx stands for Embedded Elixir. It allows you to embed
Elixir code inside a string in a robust way:
EEx.eval_string "foo <%= bar %>", [bar: "baz"]
#=> "foo baz"
iex> EEx.eval_string "foo <%= bar %>", [bar: "baz"]
"foo baz"
## API
@@ -31,14 +31,13 @@ defmodule EEx do
All functions in this module accepts EEx-related options.
They are:
* `:line` - the line to be used as the template start.
Defaults to 1;
* `:file` - the file to be used in the template.
Defaults to the given file the template is read from
or to "nofile" when compiling from a string;
* `:engine` - the EEx engine to be used for compilation.
Defaults to `EEx.Engine`;
## Engine
@@ -79,12 +78,12 @@ defmodule EEx do
An example is the `@` macro which allows easy data access
in a template:
EEx.eval_string "<%= @foo %>", assigns: [foo: 1]
#=> 1
iex> EEx.eval_string "<%= @foo %>", assigns: [foo: 1]
"1"
In other words, <%= @foo %> is simply translated to:
<%= Keyword.get assigns, :foo %>
<%= Dict.get assigns, :foo %>
The assigns extension is useful when the number of variables
required by the template is not specified at compilation time.
@@ -97,18 +96,18 @@ defmodule EEx do
## Examples
defmodule Sample do
require EEx
EEx.function_from_string :def, :sample, "<%= a + b %>", [:a, :b]
end
Sample.sample(1, 2) #=> "3"
iex> defmodule Sample do
...> require EEx
...> EEx.function_from_string :def, :sample, "<%= a + b %>", [:a, :b]
...> end
iex> Sample.sample(1, 2)
"3"
"""
defmacro function_from_string(kind, name, source, args \\ [], options \\ []) do
quote bind_quoted: binding do
info = Keyword.merge [file: __ENV__.file, line: __ENV__.line], options
args = Enum.map args, fn arg -> { arg, [line: info[:line]], nil } end
args = Enum.map args, fn arg -> {arg, [line: info[:line]], nil} end
compiled = EEx.compile_string(source, info)
case kind do
@@ -144,7 +143,7 @@ defmodule EEx do
defmacro function_from_file(kind, name, file, args \\ [], options \\ []) do
quote bind_quoted: binding do
info = Keyword.merge options, [file: file, line: 1]
args = Enum.map args, fn arg -> { arg, [line: 1], nil } end
args = Enum.map args, fn arg -> {arg, [line: 1], nil} end
compiled = EEx.compile_file(file, info)
@file file
@@ -177,8 +176,8 @@ defmodule EEx do
## Examples
EEx.eval_string "foo <%= bar %>", [bar: "baz"]
#=> "foo baz"
iex> EEx.eval_string "foo <%= bar %>", [bar: "baz"]
"foo baz"
"""
def eval_string(source, bindings \\ [], options \\ []) do
@@ -195,8 +194,7 @@ defmodule EEx do
foo <%= bar %>
# iex
EEx.eval_file "sample.ex", [bar: "baz"]
#=> "foo baz"
EEx.eval_file "sample.ex", [bar: "baz"] #=> "foo baz"
"""
def eval_file(filename, bindings \\ [], options \\ []) do
@@ -208,7 +206,7 @@ defmodule EEx do
### Helpers
defp do_eval(compiled, bindings, options) do
{ result, _ } = Code.eval_quoted(compiled, bindings, options)
{result, _} = Code.eval_quoted(compiled, bindings, options)
result
end
end
+49 -44
View File
@@ -1,57 +1,62 @@
defrecord EEx.State, engine: EEx.SmartEngine, dict: [], file: "nofile", line: 1, start_line: 1
defmodule EEx.Compiler do
@moduledoc false
# when changing this setting, don't forget to update the docs for EEx
@default_engine EEx.SmartEngine
@doc """
This is the compilation entry point. It glues the tokenizer
and the engine together by handling the tokens and invoking
the engine every time a full expression or text is received.
"""
def compile(source, options) do
line = Keyword.get(options, :line, 1)
def compile(source, opts) do
file = opts[:file] || "nofile"
line = opts[:line] || 1
tokens = EEx.Tokenizer.tokenize(source, line)
state = EEx.State.new(options)
state = %{engine: opts[:engine] || @default_engine,
file: file, line: line, quoted: [], start_line: nil}
generate_buffer(tokens, "", [], state)
end
# Generates the buffers by handling each expression from the tokenizer
defp generate_buffer([{ :text, _line, chars }|t], buffer, scope, state) do
buffer = state.engine.handle_text(buffer, chars)
defp generate_buffer([{:text, chars}|t], buffer, scope, state) do
buffer = state.engine.handle_text(buffer, String.from_char_data!(chars))
generate_buffer(t, buffer, scope, state)
end
defp generate_buffer([{ :expr, line, mark, chars }|t], buffer, scope, state) do
defp generate_buffer([{:expr, line, mark, chars}|t], buffer, scope, state) do
expr = Code.string_to_quoted!(chars, [line: line, file: state.file])
buffer = state.engine.handle_expr(buffer, mark, expr)
generate_buffer(t, buffer, scope, state)
end
defp generate_buffer([{ :start_expr, line, mark, chars }|t], buffer, scope, state) do
{ contents, t } = generate_buffer(t, "", [chars|scope], state.dict([]).line(line).start_line(line))
defp generate_buffer([{:start_expr, start_line, mark, chars}|t], buffer, scope, state) do
{contents, line, t} = look_ahead_text(t, start_line, chars)
{contents, t} = generate_buffer(t, "", [contents|scope],
%{state | quoted: [], line: line, start_line: start_line})
buffer = state.engine.handle_expr(buffer, mark, contents)
generate_buffer(t, buffer, scope, state)
end
defp generate_buffer([{ :middle_expr, line, _, chars }|t], buffer, [current|scope], state) do
{ wrapped, state } = wrap_expr(current, line, buffer, chars, state)
generate_buffer(t, "", [wrapped|scope], state.line(line))
defp generate_buffer([{:middle_expr, line, _, chars}|t], buffer, [current|scope], state) do
{wrapped, state} = wrap_expr(current, line, buffer, chars, state)
generate_buffer(t, "", [wrapped|scope], %{state | line: line})
end
defp generate_buffer([{ :end_expr, line, _, chars }|t], buffer, [current|_], state) do
{ wrapped, state } = wrap_expr(current, line, buffer, chars, state)
defp generate_buffer([{:end_expr, line, _, chars}|t], buffer, [current|_], state) do
{wrapped, state} = wrap_expr(current, line, buffer, chars, state)
tuples = Code.string_to_quoted!(wrapped, [line: state.start_line, file: state.file])
buffer = insert_quotes(tuples, state.dict)
{ buffer, t }
buffer = insert_quoted(tuples, state.quoted)
{buffer, t}
end
defp generate_buffer([{ :end_expr, line, _, chars }|_], _buffer, [], _state) do
defp generate_buffer([{:end_expr, line, _, chars}|_], _buffer, [], _state) do
raise EEx.SyntaxError, message: "unexpected token: #{inspect chars} at line #{inspect line}"
end
defp generate_buffer([], buffer, [], _state) do
buffer
defp generate_buffer([], buffer, [], state) do
state.engine.handle_body(buffer)
end
defp generate_buffer([], _buffer, _scope, _state) do
@@ -62,50 +67,50 @@ 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) ++ ');'
{current ++ placeholder ++ new_lines ++ chars,
%{state | quoted: [{key, buffer}|state.quoted]}}
end
if state.dict == [] and empty?(buffer) do
{ current ++ new_lines ++ chars, state }
# Look text ahead on expressions
defp look_ahead_text([{:text, text}, {:middle_expr, line, _, chars}|t]=list, start, contents) do
if only_spaces?(text) do
{contents ++ text ++ chars, line, t}
else
key = length(state.dict)
placeholder = '__EEX__(' ++ integer_to_list(key) ++ ');'
{ current ++ placeholder ++ new_lines ++ chars, state.update_dict(&[{key, buffer}|&1]) }
{contents, start, list}
end
end
# Check if the syntax node represents an empty string
defp empty?(bin) when is_binary(bin) do
bc(<<c>> inbits bin, not c in [?\s, ?\t, ?\r, ?\n], do: <<c>>) == ""
defp look_ahead_text(t, start, contents) do
{contents, start, t}
end
defp empty?({ :<>, _, [left, right] }) do
empty?(left) and empty?(right)
end
defp empty?(_) do
false
defp only_spaces?(chars) do
Enum.all?(chars, &(&1 in [?\s, ?\t, ?\r, ?\n]))
end
# Changes placeholder to real expression
defp insert_quotes({ :__EEX__, _, [key] }, dict) do
{ ^key, value } = List.keyfind dict, key, 0
defp insert_quoted({:__EEX__, _, [key]}, quoted) do
{^key, value} = List.keyfind quoted, key, 0
value
end
defp insert_quotes({ left, line, right }, dict) do
{ insert_quotes(left, dict), line, insert_quotes(right, dict) }
defp insert_quoted({left, line, right}, quoted) do
{insert_quoted(left, quoted), line, insert_quoted(right, quoted)}
end
defp insert_quotes({ left, right }, dict) do
{ insert_quotes(left, dict), insert_quotes(right, dict) }
defp insert_quoted({left, right}, quoted) do
{insert_quoted(left, quoted), insert_quoted(right, quoted)}
end
defp insert_quotes(list, dict) when is_list(list) do
Enum.map list, &insert_quotes(&1, dict)
defp insert_quoted(list, quoted) when is_list(list) do
Enum.map list, &insert_quoted(&1, quoted)
end
defp insert_quotes(other, _dict) do
defp insert_quoted(other, _quoted) do
other
end
end
+19 -6
View File
@@ -1,7 +1,11 @@
defmodule EEx.Engine do
@moduledoc %S"""
@moduledoc ~S"""
This is the basic EEx engine that ships with Elixir.
An engine needs to implement two functions:
An engine needs to implement three functions:
* `handle_body(quoted)` - receives the final built quoted
expression, should do final post-processing and return a
quoted expression;
* `handle_text(buffer, text)` - it receives the buffer,
the text and must return a new quoted expression;
@@ -10,11 +14,11 @@ defmodule EEx.Engine do
the marker, the expr and must return a new quoted expression;
The marker is what follows exactly after `<%`. For example,
`<% foo %>` has an empty marker, but `<%= foo %>` has `'='`
`<% foo %>` has an empty marker, but `<%= foo %>` has `"="`
as marker. The allowed markers so far are:
* `''`
* `'='`
* `""`
* `"="`
Read `handle_expr/3` below for more information about the markers
implemented by default by this engine.
@@ -22,9 +26,18 @@ defmodule EEx.Engine do
use Behaviour
defcallback handle_body(Macro.t) :: Macro.t
defcallback handle_text(Macro.t, binary) :: Macro.t
defcallback handle_expr(Macro.t, binary, Macro.t) :: Macro.t
@doc """
The default implementation implementation simply returns the
given expression.
"""
def handle_body(quoted) do
quoted
end
@doc """
The default implementation simply concatenates text to the buffer.
"""
@@ -54,4 +67,4 @@ defmodule EEx.Engine do
tmp
end
end
end
end
+13 -9
View File
@@ -15,6 +15,10 @@ defmodule EEx.TransformerEngine do
quote do
@behaviour EEx.Engine
def handle_body(body) do
EEx.Engine.handle_body(body)
end
def handle_text(buffer, text) do
EEx.Engine.handle_text(buffer, text)
end
@@ -23,23 +27,23 @@ defmodule EEx.TransformerEngine do
EEx.Engine.handle_expr(buffer, mark, transform(expr))
end
defp transform({ a, b, c }) do
{ transform(a), b, transform(c) }
defp transform({a, b, c}) do
{transform(a), b, transform(c)}
end
defp transform({ a, b }) do
{ transform(a), transform(b) }
defp transform({a, b}) do
{transform(a), transform(b)}
end
defp transform(list) when is_list(list) do
lc i inlist list, do: transform(i)
for i <- list, do: transform(i)
end
defp transform(other) do
other
end
defoverridable [transform: 1, handle_expr: 3, handle_text: 2]
defoverridable [transform: 1, handle_body: 1, handle_expr: 3, handle_text: 2]
end
end
end
@@ -59,8 +63,8 @@ defmodule EEx.AssignsEngine do
use EEx.AssignsEngine
end
EEx.eval_string("<%= @foo %>", assigns: [foo: 1])
#=> 1
iex> EEx.eval_string("<%= @foo %>", assigns: [foo: 1])
"1"
In the example above, we can access the value `foo` under
the binding `assigns` using `@foo`. This is useful when
@@ -87,7 +91,7 @@ defmodule EEx.AssignsEngine do
@doc false
defmacro __using__(_) do
quote unquote: false do
defp transform({ :@, line, [{ name, _, atom }] }) when is_atom(name) and is_atom(atom) do
defp transform({:@, line, [{name, _, atom}]}) when is_atom(name) and is_atom(atom) do
quote do: Dict.get(var!(assigns), unquote(name))
end
+40 -38
View File
@@ -5,66 +5,67 @@ defmodule EEx.Tokenizer do
Tokenizes the given char list or binary.
It returns 4 different types of tokens as result:
* { :text, line, contents }
* { :expr, line, marker, contents }
* { :start_expr, line, marker, contents }
* { :end_expr, line, marker, contents }
* {:text, contents}
* {:expr, line, marker, contents}
* {:start_expr, line, marker, contents}
* {:middle_expr, line, marker, contents}
* {:end_expr, line, marker, contents}
"""
def tokenize(bin, line) when is_binary(bin) do
tokenize(String.to_char_list!(bin), line)
tokenize(List.from_char_data!(bin), line)
end
def tokenize(list, line) do
Enum.reverse(tokenize(list, line, line, [], []))
Enum.reverse(tokenize(list, line, [], []))
end
defp tokenize('<%%' ++ t, current_line, line, buffer, acc) do
{ buffer, new_line, rest } = tokenize_expr t, line, [?%, ?<|buffer]
tokenize rest, current_line, new_line, [?>, ?%|buffer], acc
defp tokenize('<%%' ++ t, line, buffer, acc) do
{buffer, new_line, rest} = tokenize_expr t, line, [?%, ?<|buffer]
tokenize rest, new_line, [?>, ?%|buffer], acc
end
defp tokenize('<%#' ++ t, current_line, line, buffer, acc) do
{ _, new_line, rest } = tokenize_expr t, line, []
tokenize rest, current_line, new_line, buffer, acc
defp tokenize('<%#' ++ t, line, buffer, acc) do
{_, new_line, rest} = tokenize_expr t, line, []
tokenize rest, new_line, buffer, acc
end
defp tokenize('<%' ++ t, current_line, line, buffer, acc) do
{ marker, t } = retrieve_marker(t)
{ expr, new_line, rest } = tokenize_expr t, line, []
defp tokenize('<%' ++ t, line, buffer, acc) do
{marker, t} = retrieve_marker(t)
{expr, new_line, rest} = tokenize_expr t, line, []
token = token_name(expr)
acc = tokenize_text(current_line, buffer, acc)
final = { token, line, marker, Enum.reverse(expr) }
tokenize rest, new_line, new_line, [], [final | acc]
acc = tokenize_text(buffer, acc)
final = {token, line, marker, Enum.reverse(expr)}
tokenize rest, new_line, [], [final | acc]
end
defp tokenize('\n' ++ t, current_line, line, buffer, acc) do
tokenize t, current_line, line + 1, [?\n|buffer], acc
defp tokenize('\n' ++ t, line, buffer, acc) do
tokenize t, line + 1, [?\n|buffer], acc
end
defp tokenize([h|t], current_line, line, buffer, acc) do
tokenize t, current_line, line, [h|buffer], acc
defp tokenize([h|t], line, buffer, acc) do
tokenize t, line, [h|buffer], acc
end
defp tokenize([], current_line, _line, buffer, acc) do
tokenize_text(current_line, buffer, acc)
defp tokenize([], _line, buffer, acc) do
tokenize_text(buffer, acc)
end
# Retrieve marker for <%
defp retrieve_marker('=' ++ t) do
{ "=", t }
{"=", t}
end
defp retrieve_marker(t) do
{ "", t }
{"", t}
end
# Tokenize an expression until we find %>
defp tokenize_expr([?%, ?>|t], line, buffer) do
{ buffer, line, t }
{buffer, line, t}
end
defp tokenize_expr('\n' ++ t, line, buffer) do
@@ -103,7 +104,7 @@ defmodule EEx.Tokenizer do
# token and, if so, it is not followed by an "end"
# token. If this is the case, we are on a start expr.
case :elixir_tokenizer.tokenize(rest, 1, file: "eex", check_terminators: false) do
{ :ok, _line, tokens } ->
{:ok, _line, tokens} ->
tokens = Enum.reverse(tokens)
fn_index = fn_index(tokens)
@@ -129,31 +130,32 @@ defmodule EEx.Tokenizer do
defp fn_index(tokens) do
Enum.find_index tokens, fn
{ :fn_paren, _ } -> true
{ :fn, _ } -> true
{:fn_paren, _} -> true
{:fn, _} -> true
_ -> false
end
end
defp end_index(tokens) do
Enum.find_index(tokens, &match?({ :end, _ }, &1)) || :infinity
Enum.find_index(tokens, &match?({:end, _}, &1)) || :infinity
end
defp check_spaces(string, token) do
if only_spaces?(string), do: token, else: :expr
if Enum.all?(string, &(&1 in [?\s, ?\t])) do
token
else
:expr
end
end
defp only_spaces?([h|t]) when h in [?\s, ?\t], do: only_spaces?(t)
defp only_spaces?(other), do: other == []
# Tokenize the buffered text by appending
# it to the given accumulator.
defp tokenize_text(_line, [], acc) do
defp tokenize_text([], acc) do
acc
end
defp tokenize_text(line, buffer, acc) do
[{ :text, line, String.from_char_list!(Enum.reverse(buffer)) } | acc]
defp tokenize_text(buffer, acc) do
[{:text, Enum.reverse(buffer)} | acc]
end
end
+3 -1
View File
@@ -2,6 +2,8 @@ defmodule EEx.Mixfile do
use Mix.Project
def project do
[app: :eex, version: System.version, build_per_environment: false]
[app: :eex,
version: System.version,
build_per_environment: false]
end
end
+4 -5
View File
@@ -7,17 +7,16 @@ defmodule EEx.SmartEngineTest do
assert_eval "foo bar", "foo bar"
end
test "evaluates with assigns as a ListDict" do
test "evaluates with assigns as keywords" do
assert_eval "1", "<%= @foo %>", assigns: [foo: 1]
end
test "evaluates with assigns as a HashDict" do
assigns = HashDict.new [foo: 1]
assert_eval "1", "<%= @foo %>", assigns: assigns
test "evaluates with assigns as a map" do
assert_eval "1", "<%= @foo %>", assigns: %{foo: 1}
end
test "evaluates with loops" do
assert_eval "1\n2\n3\n", "<%= lc x inlist [1, 2, 3] do %><%= x %>\n<% end %>"
assert_eval "1\n2\n3\n", "<%= for x <- [1, 2, 3] do %><%= x %>\n<% end %>"
end
defp assert_eval(expected, actual, binding \\ []) do
+29 -29
View File
@@ -5,23 +5,23 @@ defmodule EEx.TokenizerTest do
require EEx.Tokenizer, as: T
test "simple chars lists" do
assert T.tokenize('foo', 1) == [ { :text, 1, "foo" } ]
assert T.tokenize('foo', 1) == [ {:text, 'foo'} ]
end
test "simple strings" do
assert T.tokenize("foo", 1) == [ { :text, 1, "foo" } ]
assert T.tokenize("foo", 1) == [ {:text, 'foo'} ]
end
test "strings with embedded code" do
assert T.tokenize('foo <% bar %>', 1) == [ { :text, 1, "foo " }, { :expr, 1, "", ' bar ' } ]
assert T.tokenize('foo <% bar %>', 1) == [ {:text, 'foo '}, {:expr, 1, "", ' bar '} ]
end
test "strings with embedded equals code" do
assert T.tokenize('foo <%= bar %>', 1) == [ { :text, 1, "foo " }, { :expr, 1, "=", ' bar ' } ]
assert T.tokenize('foo <%= bar %>', 1) == [ {:text, 'foo '}, {:expr, 1, "=", ' bar '} ]
end
test "strings with more than one line" do
assert T.tokenize('foo\n<%= bar %>', 1) == [ { :text, 1, "foo\n" }, { :expr, 2, "=", ' bar ' } ]
assert T.tokenize('foo\n<%= bar %>', 1) == [ {:text, 'foo\n'}, {:expr, 2, "=", ' bar '} ]
end
test "strings with more than one line and expression with more than one line" do
@@ -33,67 +33,67 @@ baz %>
'''
assert T.tokenize(string, 1) == [
{:text, 1, "foo "},
{:text, 'foo '},
{:expr, 1, "=", ' bar\n\nbaz '},
{:text, 3, "\n"},
{:text, '\n'},
{:expr, 4, "", ' foo '},
{:text, 4, "\n"}
{:text, '\n'}
]
end
test "quotation" do
assert T.tokenize('foo <%% true %>', 1) == [
{ :text, 1, "foo <% true %>" }
{:text, 'foo <% true %>'}
]
end
test "quotation with do/end" do
assert T.tokenize('foo <%% true do %>bar<%% end %>', 1) == [
{ :text, 1, "foo <% true do %>bar<% end %>" }
{:text, 'foo <% true do %>bar<% end %>'}
]
end
test "comments" do
assert T.tokenize('foo <%# true %>', 1) == [
{ :text, 1, "foo " }
{:text, 'foo '}
]
end
test "comments with do/end" do
assert T.tokenize('foo <%# true do %>bar<%# end %>', 1) == [
{ :text, 1, "foo bar" }
{:text, 'foo bar'}
]
end
test "strings with embedded do end" do
assert T.tokenize('foo <% if true do %>bar<% end %>', 1) == [
{ :text, 1, "foo " },
{ :start_expr, 1, "", ' if true do ' },
{ :text, 1, "bar" },
{ :end_expr, 1, "", ' end ' }
{:text, 'foo '},
{:start_expr, 1, "", ' if true do '},
{:text, 'bar'},
{:end_expr, 1, "", ' end '}
]
end
test "strings with embedded -> end" do
assert T.tokenize('foo <% cond do %><% false -> %>bar<% true -> %>baz<% end %>', 1) == [
{ :text, 1, "foo " },
{ :start_expr, 1, "", ' cond do ' },
{ :middle_expr, 1, "", ' false -> ' },
{ :text, 1, "bar" },
{ :middle_expr, 1, "", ' true -> ' },
{ :text, 1, "baz" },
{ :end_expr, 1, "", ' end ' }
{:text, 'foo '},
{:start_expr, 1, "", ' cond do '},
{:middle_expr, 1, "", ' false -> '},
{:text, 'bar'},
{:middle_expr, 1, "", ' true -> '},
{:text, 'baz'},
{:end_expr, 1, "", ' end '}
]
end
test "strings with embedded keywords blocks" do
assert T.tokenize('foo <% if true do %>bar<% else %>baz<% end %>', 1) == [
{ :text, 1, "foo " },
{ :start_expr, 1, "", ' if true do ' },
{ :text, 1, "bar" },
{ :middle_expr, 1, "", ' else ' },
{ :text, 1, "baz" },
{ :end_expr, 1, "", ' end ' }
{:text, 'foo '},
{:start_expr, 1, "", ' if true do '},
{:text, 'bar'},
{:middle_expr, 1, "", ' else '},
{:text, 'baz'},
{:end_expr, 1, "", ' end '}
]
end
+51 -19
View File
@@ -5,9 +5,9 @@ require EEx
defmodule EExText.Compiled do
def before_compile do
fill_in_stacktrace
{ __ENV__.line, hd(tl(System.stacktrace)) }
{__ENV__.line, hd(tl(System.stacktrace))}
end
{ :erlang, 1, 2 }.tuple_to_list
{: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")
@@ -16,20 +16,20 @@ defmodule EExText.Compiled do
def after_compile do
fill_in_stacktrace
{ __ENV__.line, hd(tl(System.stacktrace)) }
{__ENV__.line, hd(tl(System.stacktrace))}
end
@file "unknown"
def unknown do
fill_in_stacktrace
{ __ENV__.line, hd(tl(System.stacktrace)) }
{__ENV__.line, hd(tl(System.stacktrace))}
end
defp fill_in_stacktrace do
try do
:erlang.error "failed"
catch
:error, _, stack -> stack
:error, _ -> System.stacktrace
end
end
end
@@ -45,6 +45,9 @@ end
defmodule EExTest do
use ExUnit.Case, async: true
doctest EEx
doctest EEx.AssignsEngine
test "evaluates simple string" do
assert_eval "foo bar", "foo bar"
end
@@ -279,6 +282,15 @@ foo
assert_eval "\n\n Good\n \n", string
end
test "for comprehensions" do
string = """
<%= for _name <- packages || [] do %>
<% end %>
<%= all || :done %>
"""
assert_eval "\ndone\n", string, packages: nil, all: nil
end
test "unicode" do
template = """
• <%= "•" %> •
@@ -317,35 +329,55 @@ foo
test "defined from file do not affect backtrace" do
assert EExText.Compiled.before_compile ==
{ 8,
{ EExText.Compiled,
{8,
{EExText.Compiled,
:before_compile,
0,
[file: to_char_list(Path.relative_to_cwd(__ENV__.file)), line: 7]
}
}
}
}
assert EExText.Compiled.after_compile ==
{ 19,
{ EExText.Compiled,
{19,
{EExText.Compiled,
:after_compile,
0,
[file: to_char_list(Path.relative_to_cwd(__ENV__.file)), line: 18]
}
}
}
}
assert EExText.Compiled.unknown ==
{ 25,
{ EExText.Compiled,
{25,
{EExText.Compiled,
:unknown,
0,
[file: 'unknown', line: 24]
}
}
}
}
end
defp assert_eval(expected, actual) do
result = EEx.eval_string(actual, [], file: __ENV__.file, engine: EEx.Engine)
defmodule TestEngine do
@behaviour EEx.Engine
def handle_body(body) do
{:wrapped, body}
end
def handle_text(buffer, text) do
EEx.Engine.handle_text(buffer, text)
end
def handle_expr(buffer, mark, expr) do
EEx.Engine.handle_expr(buffer, mark, expr)
end
end
test "calls handle_body" do
assert {:wrapped, "foo"} = EEx.eval_string("foo", [], engine: TestEngine)
end
defp assert_eval(expected, actual, binding \\ []) do
result = EEx.eval_string(actual, binding, file: __ENV__.file, engine: EEx.Engine)
assert result == expected
end
end
+3 -27
View File
@@ -1,19 +1,13 @@
-define(wrap_call(Line, Module, Function, Args),
{ call, Line,
{ remote, Line, { atom, Line, Module }, { atom, Line, Function } },
Args
}).
-define(atom_concat(Atoms), list_to_atom(lists:concat(Atoms))).
-define(elixir_macro(Macro), list_to_atom(lists:concat(['MACRO-',Macro]))).
-define(m(M, K), maps:get(K, M)).
-define(line(Opts), elixir_utils:get_line(Opts)).
-record(elixir_scope, {
context=nil, %% can be match, guards or nil
extra=nil, %% extra information about the context, like fn_match and do_match
extra=nil, %% extra information about the context, like fn_match and map_key
noname=false, %% when true, don't add new names (used by try)
super=false, %% when true, it means super was invoked
caller=false, %% when true, it means caller was invoked
return=true, %% when true, the return value is used
module=nil, %% the current module
function=nil, %% the current function
vars=[], %% a dict of defined variables and their alias
@@ -25,24 +19,6 @@
file=(<<"nofile">>) %% the current scope filename
}).
-record(elixir_env, {
module=nil, %% the current module
file=(<<"nofile">>), %% the current filename
line=1, %% the current line
function=nil, %% the current function
context=nil, %% can be match_vars, guards or nil
requires=[], %% a set with modules required
aliases=[], %% an orddict with aliases by new -> old names
functions=[], %% a list with functions imported from module
macros=[], %% a list with macros imported from module
macro_aliases=[], %% keep aliases defined inside a macro
context_modules=[], %% modules defined in the current context
vars=[], %% a set of defined variables
export_vars=nil, %% a set of variables to be exported in certain constructs, may be nil
lexical_tracker=nil, %% holds the lexical tracker pid
local=nil %% the module to delegate local functions to
}).
-record(elixir_quote, {
line=false,
keep=false,
+33 -22
View File
@@ -7,6 +7,22 @@ defprotocol Access do
is translated to `access foo, bar` which, by default,
invokes the `Access.access` protocol.
This protocol is implemented by default for Lists, Maps
and dictionary like types:
iex> keywords = [a: 1, b: 2]
iex> keywords[:a]
1
iex> map = %{a: 1, b: 2}
iex> map[:a]
1
iex> star_ratings = %{1.0 => "★", 1.5 => "★☆", 2.0 => "★★"}
iex> star_ratings[1.5]
"★☆"
The key access must be implemented using the `===` operator.
This protocol is limited and is implemented only for the
following built-in types: keywords, records and functions.
"""
@@ -18,33 +34,28 @@ defprotocol Access do
end
defimpl Access, for: List do
@doc """
Access the given key in a tuple list.
The key is found via the `===` operator.
def access(dict, key) when is_atom(key) do
case :lists.keyfind(key, 1, dict) do
{^key, value} -> value
false -> nil
end
end
## Examples
def access(_dict, key) do
raise ArgumentError, message: "the access protocol for lists expect the key to be an atom, got: #{inspect key}"
end
end
iex> keywords = [a: 1, b: 2]
...> keywords[:a]
1
iex> star_ratings = [{1.0, "★"}, {1.5, "★☆"}, {2.0, "★★"}]
...> star_ratings[1.5]
"★☆"
"""
def access(dict, key)
def access([{ key, value }|_], key), do: value
def access([{ _, _ }|t], key), do: access(t, key)
def access([], _key), do: nil
defimpl Access, for: Map do
def access(map, key) do
case :maps.find(key, map) do
{:ok, value} -> value
:error -> nil
end
end
end
defimpl Access, for: Atom do
@doc """
The access protocol can only be accessed by atoms
at compilation time. If we reach this, we should raise
an exception.
"""
def access(nil, _) do
nil
end
+403
View File
@@ -0,0 +1,403 @@
defmodule Application do
@moduledoc """
A module for working with applications and defining application callbacks.
In Elixir (actually, in Erlang/OTP), an application is a component
implementing some specific functionality, that can be started and stopped
as a unit, and which can be re-used in other systems as well.
Applications are defined with an application file named `APP.app` where
`APP` is the APP name, usually in `underscore_case` convention. The
application file must reside in the same `ebin` directory as the
application's modules bytecode.
In Elixir, Mix is responsible for compiling your source code and
generating your application `.app` file. Furthermore, Mix is also
responsible for configuring, starting and stoping your application
and its dependencies. For this reason, this documentation will focus
on the remaining aspects of your application: the application environment,
and the application callback module.
You can learn more about Mix compilation of `.app` files by typing
`mix help compile.app`.
## Application environment
Once an application is started, OTP provides an application environment
that can be used to configure applications.
Assuming you are inside a Mix project, you can edit your application
function in the `mix.exs` file to the following:
def application do
[env: [hello: :world]]
end
In the application function, we can define the default environment values
for our application. By starting your application with `iex -S mix`, you
can access the default value:
Application.get_env(:APP_NAME, :hello)
#=> {:ok, :hello}
It is also possible to put and delete values from the application value,
including new values that are not defined in the environment file (although
those should be avoided).
In the future, we plan to support configuration files which allows
developers to configure the environment of their dependencies.
Keep in mind that each application is responsible for its environment.
Do not use the fucntions in this module for directly access or modify
the environment of other application (as it may lead to inconsistent
data in the application environment).
## Application module callback
Often times, an application defines a supervision tree that must be started
and stopped when the application starts and stops. For such, we need to
define an application module callback. The first step is to define the
module callback in the application definition in the `mix.exs` file:
def application do
[mod: {MyApp, []}]
end
Our application now requires the `MyApp` module to provide an application
callback. This can be done by invoking `use Application` in that module
and defining a `start/2` callback, for example:
defmodule MyApp do
use Application
def start(_type, _args) do
MyApp.Supervisor.start_link()
end
end
`start/2` most commonly returns `{:ok, pid}` or `{:ok, pid, state}` where
`pid` identifies the supervision tree and the state is the application state.
`args` is second element of the tuple given to the `:mod` option.
The `type` passed into `start/2` is usually `:normal` unless in a distributed
setup where applications takeover and failovers are configured. This particular
aspect of applications can be read with more detail in the OTP documentation:
* http://www.erlang.org/doc/man/application.html
* http://www.erlang.org/doc/design_principles/applications.html
A developer may also implement the `stop/1` callback (automatically defined
by `use Application`) which does any application cleanup. It receives the
application state and can return any value. Notice that shutting down the
supervisor is automatically handled by the VM;
"""
@doc false
defmacro __using__(_) do
quote location: :keep do
@behaviour :application
@doc false
def stop(_state) do
:ok
end
defoverridable [stop: 1]
end
end
@type app :: atom
@type key :: atom
@type value :: term
@type start_type :: :permanent | :transient | :temporary
@doc """
Returns all key-value pairs for `app`.
"""
@spec get_all_env(app) :: [{key,value}]
def get_all_env(app) do
:application.get_all_env(app)
end
@doc """
Returns the value for `key` in `app`'s environment.
If the specified application is not loaded, or the configuration parameter
does not exist, the function returns the `default` value.
"""
@spec get_env(app, key, value) :: value
def get_env(app, key, default \\ nil) do
case :application.get_env(app, key) do
{:ok, value} -> value
:undefined -> default
end
end
@doc """
Returns the value for `key` in `app`'s environment in a tuple.
If the specified application is not loaded, or the configuration parameter
does not exist, the function returns `:error`.
"""
@spec fetch_env(app, key) :: {:ok, value} | :error
def fetch_env(app, key) do
case :application.get_env(app, key) do
{:ok, value} -> {:ok, value}
:undefined -> :error
end
end
@doc """
Puts the `value` in `key` for the given `app`.
## Options
* `:timeout` - the timeout for the change (defaults to 5000ms);
* `:persistent` - persists the given value on application load and reloads;
If `put_env/4` is called before the application is loaded, the application
environment values specified in the `.app` file will override the ones
previously set.
The persistent option can be set to true when there is a need to guarantee
parameters set with this function will not be overridden by the ones defined
in the application resource file on load. This means persistent values will
stick after the application is loaded and also on application reload.
"""
@spec put_env(app, key, value, [timeout: timeout, persistent: boolean]) :: :ok
def put_env(app, key, value, opts \\ []) do
:application.set_env(app, key, value, opts)
end
@doc """
Deletes the `key` from the given `app` environment.
See `put_env/4` for a description of the options.
"""
@spec delete_env(app, key, [timeout: timeout, persistent: boolean]) :: :ok
def delete_env(app, key, opts \\ []) do
:application.unset_env(app, key, opts)
end
@doc """
Ensures the given `app` is started.
Same as `start/2` but returns `:ok` if the application was already
started. This is useful in scripts and in test setup, where test
applications need to be explicitly started:
:ok = Application.ensure_started(:my_test_dep)
"""
@spec ensure_started(app, start_type) :: :ok | {:error, term}
def ensure_started(app, type \\ :temporary) when is_atom(app) do
:application.ensure_started(app, type)
end
@doc """
Ensures the given `app` and its applications are started.
Same as `start/2` but also starts the applications listed under
`:applications` in the `.app` file in case they were not previously
started.
"""
@spec ensure_all_started(app, start_type) :: {:ok, [app]} | {:error, term}
def ensure_all_started(app, type \\ :temporary) when is_atom(app) do
:application.ensure_all_started(app, type)
end
@doc """
Starts the given `app`.
If the `app` is not loaded, the application will first be loaded using `load/1`.
Any included application, defined in the `:included_applications` key of the
`.app` file will also be loaded, but they won't be started.
Furthermore, all applications listed in the `:applications` key must be explicitly
started before this application is. If not, `{:error, {:not_started, app}}` is
returned, where `app` is the name of the missing application.
In case you want to automatically load **and start** all of `app`'s dependencies,
see `ensure_all_started/2`.
The `type` argument specifies the type of the application:
* `:permanent` - if `app` terminates, all other applications and the entire
node are also terminated;
* `:transient` - if `app` terminates with `:normal` reason, it is reported
but no other applications are terminated. If a transient application terminates
abnormally, all other applications and the entire node are also terminated;
* `:temporary` - if `app` termiantes, it is reported but no other applications
are terminated (the default);
Note that it is always possible to stop an application explicitly by calling
`stop/1`. Regardless of the type of the application, no other applications will
be affected.
Note also that the `:transient` type is of little practical use, since when a
supervision tree terminates, the reason is set to `:shutdown`, not `:normal`.
"""
@spec start(app, start_type) :: :ok | {:error, term}
def start(app, type \\ :temporary) when is_atom(app) do
:application.start(app, type)
end
@doc """
Stops the given `app`.
When stopped, the application is still loaded.
"""
@spec stop(app) :: :ok | {:error, term}
def stop(app) do
:application.stop(app)
end
@doc """
Loads the given `app`.
In order to be loaded, an `.app` file must be in the load paths.
All `:included_applications` will also be loaded.
Loading the application does not start it nor load its modules, but
it does load its environment.
"""
@spec load(app) :: :ok | {:error, term}
def load(app) when is_atom(app) do
:application.load(app)
end
@doc """
Unloads the given `app`.
It will also unload all `:included_applications`.
Note that the function does not purge the application modules.
"""
@spec unload(app) :: :ok | {:error, term}
def unload(app) when is_atom(app) do
:application.unload(app)
end
@doc """
Gets the directory for app.
This information is returned based on the code path. Here is an
example:
File.mkdir_p!("foo/ebin")
Code.prepend_path("foo/ebin")
Application.app_dir(:foo)
#=> "foo"
Even though the directory is empty and there is no `.app` file
it is considered the application directory based on the name
"foo/ebin". The name may contain a dash `-` which is considered
to be the app version and it is removed for the lookup purposes:
File.mkdir_p!("bar-123/ebin")
Code.prepend_path("bar-123/ebin")
Application.app_dir(:bar)
#=> "bar-123"
For more information on code paths, check the `Code` module in
Elixir and also Erlang's `:code` module.
"""
@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}"
end
end
@doc """
Returns the given path inside `app_dir/1`.
"""
@spec app_dir(app, String.t) :: String.t
def app_dir(app, path) when is_binary(path) do
Path.join(app_dir(app), path)
end
@doc """
Formats the error reason returned by `start/2`,
`ensure_started/2, `stop/1`, `load/1` and `unload/1`,
returns a string.
"""
@spec format_error(any) :: String.t
def format_error(reason) do
try do
impl_format_error(reason)
catch
# A user could create an error that looks like a builtin one
# causing an error.
:error, _ ->
inspect(reason)
end
end
# exit(:normal) call is special cased, undo the special case.
defp impl_format_error({{:EXIT, :normal}, {mod, :start, args}}) do
Exception.format_exit({:normal, {mod, :start, args}})
end
# {:error, reason} return value
defp impl_format_error({reason, {mod, :start, args}}) do
Exception.format_mfa(mod, :start, args) <> " returned an error: " <>
Exception.format_exit(reason)
end
# error or exit(reason) call, use exit reason as reason.
defp impl_format_error({:bad_return, {{mod, :start, args}, {:EXIT, reason}}}) do
Exception.format_exit({reason, {mod, :start, args}})
end
# bad return value
defp impl_format_error({:bad_return, {{mod, :start, args}, return}}) do
Exception.format_mfa(mod, :start, args) <>
" returned a bad value: " <> inspect(return)
end
defp impl_format_error({:already_started, app}) when is_atom(app) do
"already started application #{app}"
end
defp impl_format_error({:not_started, app}) when is_atom(app) do
"not started application #{app}"
end
defp impl_format_error({:bad_application, app}) do
"bad application: #{inspect(app)}"
end
defp impl_format_error({:already_loaded, app}) when is_atom(app) do
"already loaded application #{app}"
end
defp impl_format_error({:not_loaded, app}) when is_atom(app) do
"not loaded application #{app}"
end
defp impl_format_error({:invalid_restart_type, restart}) do
"invalid application restart type: #{inspect(restart)}"
end
defp impl_format_error({:invalid_name, name}) do
"invalid application name: #{inspect(name)}"
end
defp impl_format_error({:invalid_options, opts}) do
"invalid application options: #{inspect(opts)}"
end
defp impl_format_error({:badstartspec, spec}) do
"bad application start specs: #{inspect(spec)}"
end
defp impl_format_error({'no such file or directory', file}) do
"could not find application file: #{file}"
end
defp impl_format_error(reason) do
Exception.format_exit(reason)
end
end
+2 -90
View File
@@ -1,96 +1,8 @@
defmodule Application.Behaviour do
@moduledoc """
Default callbacks for applications.
@moduledoc false
In Erlang/OTP, an application is a component that can be started
and stopped as a unit, and which can be reused in other systems.
The first step in creating an application is to define an application specification.
For example, if your application is named `:my_app`, an app specification
should exist at `ebin/my_app.app`. This file is usually defined by
build tools like Mix.
With the app specification in hand, we must define
application module callbacks that control how to start and stop
instances of the application. This module is about defining such callbacks.
There are two callbacks which must be implemented:
1. `start(type, args)` - must return `{ :ok, pid }` or
`{ :ok, pid, state }`, where `pid` is the process identifier
of the supervisor tree root and `state` is application defined
state information;
2. `stop(state)` - receives the `state` returned by `start` and should
do any necessary clean up. Notice that shutting down the supervisor
is automatically handled by the VM;
When using this module, it tags the module behaviour as
`:application` and provides a default `stop/1` callback. The `start/2` callback
still needs to be implemented by the user.
You can learn more about the `:application` module, the application
specification and the application module callbacks from these sources:
* http://www.erlang.org/doc/man/application.html
* http://www.erlang.org/doc/design_principles/applications.html
* http://learnyousomeerlang.com/building-otp-applications
## Example
defmodule MyApp do
use Application.Behaviour
def start(_type, args) do
MyApp.Sup.start_link(args)
end
end
"""
# Starts the given application and all of its dependencies that
# have not been started yet recursively.
#
# ## Supported types
#
# When starting an application, a type can be given:
#
# * `:permanent` - If a permanent application terminates, all other
# applications and the runtime system are also terminated;
# * `:transient` - If a transient application terminates with reason
# `:normal`, this is reported but no other applications are terminated.
# If a transient application terminates abnormally, all other
# applications and the runtime system are also terminated;
# * `:temporary` - If a temporary application terminates, this is reported
# but no other applications are terminated.
#
# The type only applies to the application being started. Its dependencies
# are all started with default type (which is :temporary).
#
# Note that transient mode is of little practical use, since when a
# supervision tree terminates, the reason is set to shutdown, not normal.
#
# ## Examples
#
# Application.Behaviour.start(:my_app)
#
@doc false
def start(app, type \\ :temporary) do
case :application.start(app, type) do
{ :error, { :not_started, dep } } ->
case start(dep) do
:ok -> start(app, type)
other -> other
end
{ :error, { :already_started, _ } } ->
:ok
other ->
other
end
end
@doc 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
+464
View File
@@ -0,0 +1,464 @@
defmodule Base do
import Bitwise
@moduledoc """
This module provides data encoding and decoding functions
according to [RFC 4648](http://tools.ietf.org/html/rfc4648).
This document defines the commonly used base 64, base 32, and base
16 encoding schemes.
"""
b16_alphabet = Enum.with_index '0123456789ABCDEF'
b64_alphabet = Enum.with_index 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
b64url_alphabet = Enum.with_index 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_'
b32_alphabet = Enum.with_index 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'
b32hex_alphabet = Enum.with_index '0123456789ABCDEFGHIJKLMNOPQRSTUV'
Enum.each [ {:enc16, :dec16, b16_alphabet},
{:enc64, :dec64, b64_alphabet},
{:enc32, :dec32, b32_alphabet},
{:enc64url, :dec64url, b64url_alphabet},
{:enc32hex, :dec32hex, b32hex_alphabet} ], fn({enc, dec, alphabet}) ->
for {encoding, value} <- alphabet do
defp unquote(enc)(unquote(value)), do: unquote(encoding)
defp unquote(dec)(unquote(encoding)), do: unquote(value)
end
defp unquote(dec)(c) do
raise ArgumentError, message: "non-alphabet digit found: #{<<c>>}"
end
end
@doc """
Encodes a binary string into a base 16 encoded string.
## Examples
iex> Base.encode16("foobar")
"666F6F626172"
"""
@spec encode16(binary) :: binary
def encode16(data) when is_binary(data) do
do_encode16(data, &enc16/1)
end
@doc """
Decodes a base 16 encoded string into a binary string.
The following alphabet is used both for encoding and decoding:
| Value | Encoding | Value | Encoding | Value | Encoding | Value | Encoding |
|------:|---------:|------:|---------:|------:|---------:|------:|---------:|
| 0| 0| 4| 4| 8| 8| 12| C|
| 1| 1| 5| 5| 9| 9| 13| D|
| 2| 2| 6| 6| 10| A| 14| E|
| 3| 3| 7| 7| 11| B| 15| F|
## Examples
iex> Base.decode16("666F6F626172")
{:ok, "foobar"}
"""
@spec decode16(binary) :: {:ok, binary} | :error
def decode16(string) when is_binary(string) do
{:ok, decode16!(string)}
rescue
ArgumentError -> :error
end
@doc """
Decodes a base 16 encoded string into a binary string.
An `ArgumentError` exception is raised if the padding is incorrect or
a non-alphabet character is present in the string.
## Examples
iex> Base.decode16!("666F6F626172")
"foobar"
"""
@spec decode16!(binary) :: binary
def decode16!(string) when is_binary(string) do
do_decode16(string, &dec16/1)
end
@doc """
Encodes a binary string into a base 64 encoded string.
## Examples
iex> Base.encode64("foobar")
"Zm9vYmFy"
"""
@spec encode64(binary) :: binary
def encode64(data) when is_binary(data) do
do_encode64(data, &enc64/1)
end
@doc """
Decodes a base 64 encoded string into a binary string.
The following alphabet is used both for encoding and decoding:
| Value | Encoding | Value | Encoding | Value | Encoding | Value | Encoding |
|------:|---------:|------:|---------:|------:|---------:|------:|---------:|
| 0| A| 17| R| 34| i| 51| z|
| 1| B| 18| S| 35| j| 52| 0|
| 2| C| 19| T| 36| k| 53| 1|
| 3| D| 20| U| 37| l| 54| 2|
| 4| E| 21| V| 38| m| 55| 3|
| 5| F| 22| W| 39| n| 56| 4|
| 6| G| 23| X| 40| o| 57| 5|
| 7| H| 24| Y| 41| p| 58| 6|
| 8| I| 25| Z| 42| q| 59| 7|
| 9| J| 26| a| 43| r| 60| 8|
| 10| K| 27| b| 44| s| 61| 9|
| 11| L| 28| c| 45| t| 62| +|
| 12| M| 29| d| 46| u| 63| /|
| 13| N| 30| e| 47| v| | |
| 14| O| 31| f| 48| w| (pad)| =|
| 15| P| 32| g| 49| x| | |
| 16| Q| 33| h| 50| y| | |
## Examples
iex> Base.decode64("Zm9vYmFy")
{:ok, "foobar"}
"""
@spec decode64(binary) :: {:ok, binary} | :error
def decode64(string) when is_binary(string) do
{:ok, do_decode64(string, &dec64/1)}
rescue
ArgumentError -> :error
end
@doc """
Decodes a base 64 encoded string into a binary string.
The following alphabet is used both for encoding and decoding:
An `ArgumentError` exception is raised if the padding is incorrect or
a non-alphabet character is present in the string.
## Examples
iex> Base.decode64!("Zm9vYmFy")
"foobar"
"""
@spec decode64!(binary) :: binary
def decode64!(string) when is_binary(string) do
do_decode64(string, &dec64/1)
end
@doc """
Encodes a binary string into a base 64 encoded string with URL and filename
safe alphabet.
## Examples
iex> Base.url_encode64(<<255,127,254,252>>)
"_3_-_A=="
"""
@spec url_encode64(binary) :: binary
def url_encode64(data) when is_binary(data) do
do_encode64(data, &enc64url/1)
end
@doc """
Decodes a base 64 encoded string with URL and filename safe alphabet
into a binary string.
The following alphabet is used both for encoding and decoding:
| Value | Encoding | Value | Encoding | Value | Encoding | Value | Encoding |
|------:|---------:|------:|---------:|------:|---------:|------:|---------:|
| 0| A| 17| R| 34| i| 51| z|
| 1| B| 18| S| 35| j| 52| 0|
| 2| C| 19| T| 36| k| 53| 1|
| 3| D| 20| U| 37| l| 54| 2|
| 4| E| 21| V| 38| m| 55| 3|
| 5| F| 22| W| 39| n| 56| 4|
| 6| G| 23| X| 40| o| 57| 5|
| 7| H| 24| Y| 41| p| 58| 6|
| 8| I| 25| Z| 42| q| 59| 7|
| 9| J| 26| a| 43| r| 60| 8|
| 10| K| 27| b| 44| s| 61| 9|
| 11| L| 28| c| 45| t| 62| -|
| 12| M| 29| d| 46| u| 63| _|
| 13| N| 30| e| 47| v| | |
| 14| O| 31| f| 48| w| (pad)| =|
| 15| P| 32| g| 49| x| | |
| 16| Q| 33| h| 50| y| | |
## Examples
iex> Base.url_decode64("_3_-_A==")
{:ok, <<255,127,254,252>>}
"""
@spec url_decode64(binary) :: {:ok, binary} | :error
def url_decode64(string) when is_binary(string) do
{:ok, do_decode64(string, &dec64url/1)}
rescue
ArgumentError -> :error
end
@doc """
Decodes a base 64 encoded string with URL and filename safe alphabet
into a binary string.
An `ArgumentError` exception is raised if the padding is incorrect or
a non-alphabet character is present in the string.
## Examples
iex> Base.url_decode64!("_3_-_A==")
<<255,127,254,252>>
"""
@spec url_decode64!(binary) :: binary
def url_decode64!(string) when is_binary(string) do
do_decode64(string, &dec64url/1)
end
@doc """
Encodes a binary string into a base 32 encoded string.
## Examples
iex> Base.encode32("foobar")
"MZXW6YTBOI======"
"""
@spec encode32(binary) :: binary
def encode32(data) when is_binary(data) do
do_encode32(data, &enc32/1)
end
@doc """
Decodes a base 32 encoded string into a binary string.
The following alphabet is used both for encoding and decoding:
| Value | Encoding | Value | Encoding | Value | Encoding | Value | Encoding |
|------:|---------:|------:|---------:|------:|---------:|------:|---------:|
| 0| A| 9| J| 18| S| 27| 3|
| 1| B| 10| K| 19| T| 28| 4|
| 2| C| 11| L| 20| U| 29| 5|
| 3| D| 12| M| 21| V| 30| 6|
| 4| E| 13| N| 22| W| 31| 7|
| 5| F| 14| O| 23| X| | |
| 6| G| 15| P| 24| Y| (pad)| =|
| 7| H| 16| Q| 25| Z| | |
| 8| I| 17| R| 26| 2| | |
## Examples
iex> Base.decode32("MZXW6YTBOI======")
{:ok, "foobar"}
"""
@spec decode32(binary) :: {:ok, binary} | :error
def decode32(string) do
{:ok, do_decode32(string, &dec32/1)}
rescue
ArgumentError -> :error
end
@doc """
Decodes a base 32 encoded string into a binary string.
An `ArgumentError` exception is raised if the padding is incorrect or
a non-alphabet character is present in the string.
## Examples
iex> Base.decode32!("MZXW6YTBOI======")
"foobar"
"""
@spec decode32!(binary) :: binary
def decode32!(string) do
do_decode32(string, &dec32/1)
end
@doc """
Encodes a binary string into a base 32 encoded string with an
extended hexadecimal alphabet.
## Examples
iex> Base.hex_encode32("foobar")
"CPNMUOJ1E8======"
"""
@spec hex_encode32(binary) :: binary
def hex_encode32(data) when is_binary(data) do
do_encode32(data, &enc32hex/1)
end
@doc """
Decodes a base 32 encoded string with extended hexadecimal alphabet
into a binary string.
The following alphabet is used both for encoding and decoding:
| Value | Encoding | Value | Encoding | Value | Encoding | Value | Encoding |
|------:|---------:|------:|---------:|------:|---------:|------:|---------:|
| 0| 0| 9| 9| 18| I| 27| R|
| 1| 1| 10| A| 19| J| 28| S|
| 2| 2| 11| B| 20| K| 29| T|
| 3| 3| 12| C| 21| L| 30| U|
| 4| 4| 13| D| 22| M| 31| V|
| 5| 5| 14| E| 23| N| | |
| 6| 6| 15| F| 24| O| (pad)| =|
| 7| 7| 16| G| 25| P| | |
| 8| 8| 17| H| 26| Q| | |
## Examples
iex> Base.hex_decode32("CPNMUOJ1E8======")
{:ok, "foobar"}
"""
@spec hex_decode32(binary) :: {:ok, binary} | :error
def hex_decode32(string) when is_binary(string) do
{:ok, do_decode32(string, &dec32hex/1)}
rescue
ArgumentError -> :error
end
@doc """
Decodes a base 32 encoded string with extended hexadecimal alphabet
into a binary string.
An `ArgumentError` exception is raised if the padding is incorrect or
a non-alphabet character is present in the string.
## Examples
iex> Base.hex_decode32!("CPNMUOJ1E8======")
"foobar"
"""
@spec hex_decode32!(binary) :: binary
def hex_decode32!(string) when is_binary(string) do
do_decode32(string, &dec32hex/1)
end
defp do_encode16(<<>>, _), do: <<>>
defp do_encode16(data, enc) do
for <<c::4 <- data>>, into: <<>>, do: <<enc.(c)::8>>
end
defp do_decode16(<<>>, _), do: <<>>
defp do_decode16(string, dec) when rem(byte_size(string), 2) == 0 do
for <<c1::8, c2::8 <- string>>, into: <<>> do
<<dec.(c1)::4, dec.(c2)::4>>
end
end
defp do_decode16(_, _) do
raise ArgumentError, message: "odd-length string"
end
defp do_encode64(<<>>, _), do: <<>>
defp do_encode64(data, enc) do
split = 3 * div(byte_size(data), 3)
<<main::[size(split), binary], rest::binary>> = data
main = for <<c::6 <- main>>, into: <<>>, do: <<enc.(c)::8>>
case rest do
<<c1::6, c2::6, c3::4>> ->
<<main::binary, enc.(c1)::8, enc.(c2)::8, enc.(bsl(c3, 2))::8, ?=>>
<<c1::6, c2::2>> ->
<<main::binary, enc.(c1)::8, enc.(bsl(c2, 4))::8, ?=, ?=>>
<<>> ->
main
end
end
defp do_decode64(<<>>, _), do: <<>>
defp do_decode64(string, dec) when rem(byte_size(string), 4) == 0 do
split = byte_size(string) - 4
<<main::[size(split), binary], rest::binary>> = string
main = for <<c::8 <- main>>, into: <<>>, do: <<dec.(c)::6>>
case rest do
<<c1::8, c2::8, ?=, ?=>> ->
<<main::binary, dec.(c1)::6, bsr(dec.(c2), 4)::2>>
<<c1::8, c2::8, c3::8, ?=>> ->
<<main::binary, dec.(c1)::6, dec.(c2)::6, bsr(dec.(c3), 2)::4>>
<<c1::8, c2::8, c3::8, c4::8>> ->
<<main::binary, dec.(c1)::6, dec.(c2)::6, dec.(c3)::6, dec.(c4)::6>>
<<>> ->
main
end
end
defp do_decode64(_, _) do
raise ArgumentError, message: "incorrect padding"
end
defp do_encode32(<<>>, _), do: <<>>
defp do_encode32(data, enc) do
split = 5 * div(byte_size(data), 5)
<<main::[size(split), binary], rest::binary>> = data
main = for <<c::5 <- main>>, into: <<>>, do: <<enc.(c)::8>>
case rest do
<<c1::5, c2::5, c3::5, c4::5, c5::5, c6::5, c7::2>> ->
<<main::binary,
enc.(c1)::8, enc.(c2)::8, enc.(c3)::8, enc.(c4)::8,
enc.(c5)::8, enc.(c6)::8, enc.(bsl(c7, 3))::8, ?=>>
<<c1::5, c2::5, c3::5, c4::5, c5::4>> ->
<<main::binary,
enc.(c1)::8, enc.(c2)::8, enc.(c3)::8, enc.(c4)::8,
enc.(bsl(c5, 1))::8, ?=, ?=, ?=>>
<<c1::5, c2::5, c3::5, c4::1>> ->
<<main::binary,
enc.(c1)::8, enc.(c2)::8, enc.(c3)::8, enc.(bsl(c4, 4))::8,
?=, ?=, ?=, ?=>>
<<c1::5, c2::3>> ->
<<main::binary,
enc.(c1)::8, enc.(bsl(c2, 2))::8, ?=, ?=,
?=, ?=, ?=, ?=>>
<<>> ->
main
end
end
defp do_decode32(<<>>, _), do: <<>>
defp do_decode32(string, dec) when rem(byte_size(string), 8) == 0 do
split = byte_size(string) - 8
<<main::[size(split), binary], rest::binary>> = string
main = for <<c::8 <- main>>, into: <<>>, do: <<dec.(c)::5>>
case rest do
<<c1::8, c2::8, ?=, ?=, ?=, ?=, ?=, ?=>> ->
<<main::binary, dec.(c1)::5, bsr(dec.(c2), 2)::3>>
<<c1::8, c2::8, c3::8, c4::8, ?=, ?=, ?=, ?=>> ->
<<main::binary,
dec.(c1)::5, dec.(c2)::5, dec.(c3)::5, bsr(dec.(c4), 4)::1>>
<<c1::8, c2::8, c3::8, c4::8, c5::8, ?=, ?=, ?=>> ->
<<main::binary,
dec.(c1)::5, dec.(c2)::5, dec.(c3)::5, dec.(c4)::5,
bsr(dec.(c5), 1)::4>>
<<c1::8, c2::8, c3::8, c4::8, c5::8, c6::8, c7::8, ?=>> ->
<<main::binary,
dec.(c1)::5, dec.(c2)::5, dec.(c3)::5, dec.(c4)::5,
dec.(c5)::5, dec.(c6)::5, bsr(dec.(c7), 3)::2>>
<<c1::8, c2::8, c3::8, c4::8, c5::8, c6::8, c7::8, c8::8>> ->
<<main::binary,
dec.(c1)::5, dec.(c2)::5, dec.(c3)::5, dec.(c4)::5,
dec.(c5)::5, dec.(c6)::5, dec.(c7)::5, dec.(c8)::5>>
<<>> ->
main
end
end
defp do_decode32(_, _) do
raise ArgumentError, message: "incorrect padding"
end
end
+28 -89
View File
@@ -1,6 +1,6 @@
defmodule Behaviour do
@moduledoc """
Utilities for defining behaviour intefaces.
Utilities for defining behaviour interfaces.
Behaviours can be referenced by other modules
to ensure they implement required callbacks.
@@ -12,7 +12,7 @@ defmodule Behaviour do
use Behaviour
@doc "Parses the given URL"
defcallback parse(uri_info :: URI.Info.t) :: URI.Info.t
defcallback parse(uri_info :: URI.t) :: URI.t
@doc "Defines a default port"
defcallback default_port() :: integer
@@ -46,62 +46,55 @@ defmodule Behaviour do
@doc """
Define a function callback according to the given type specification.
"""
defmacro defcallback({ :::, _, [fun, return_and_guard] }) do
{ return, guard } = split_return_and_guard(return_and_guard)
do_defcallback(fun, return, guard, __CALLER__)
end
defmacro defcallback(fun) do
do_defcallback(fun, quote(do: term), [], __CALLER__)
defmacro defcallback(spec) do
do_defcallback(split_spec(spec, quote(do: term)), __CALLER__)
end
@doc """
Define a macro callback according to the given type specification.
"""
defmacro defmacrocallback({ :::, _, [fun, return_and_guard] }) do
{ return, guard } = split_return_and_guard(return_and_guard)
do_defmacrocallback(fun, return, guard, __CALLER__)
defmacro defmacrocallback(spec) do
do_defmacrocallback(split_spec(spec, quote(do: Macro.t)), __CALLER__)
end
defmacro defmacrocallback(fun) do
do_defmacrocallback(fun, quote(do: Macro.t), [], __CALLER__)
defp split_spec({:when, _, [{:::, _, [spec, return]}, guard]}, _default) do
{spec, return, guard}
end
defp split_return_and_guard({ :when, _, [return, guard] }) do
{ return, guard }
defp split_spec({:when, _, [spec, guard]}, default) do
{spec, default, guard}
end
defp split_return_and_guard({ :|, meta, [left, right] }) do
{ return, guard } = split_return_and_guard(right)
{ { :|, meta, [left, return] }, guard }
defp split_spec({:::, _, [spec, return]}, _default) do
{spec, return, []}
end
defp split_return_and_guard(other) do
{ other, [] }
defp split_spec(spec, default) do
{spec, default, []}
end
defp do_defcallback(fun, return, guards, caller) do
case Macro.decompose_call(fun) do
{ name, args } ->
defp do_defcallback({spec, return, guards}, caller) do
case Macro.decompose_call(spec) 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(fun)}"
raise ArgumentError, message: "invalid syntax in defcallback #{Macro.to_string(spec)}"
end
end
defp do_defmacrocallback(fun, return, guards, caller) do
case Macro.decompose_call(fun) do
{ name, args } ->
defp do_defmacrocallback({spec, return, guards}, caller) do
case Macro.decompose_call(spec) do
{name, args} ->
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(fun)}"
raise ArgumentError, message: "invalid syntax in defmacrocallback #{Macro.to_string(spec)}"
end
end
defp do_callback(kind, name, args, docs_name, docs_arity, docs_args, return, guards, caller) do
defp do_callback(kind, name, args, docs_name, docs_arity, _docs_args, return, guards, caller) do
Enum.each args, fn
{ :::, _, [left, right] } ->
{:::, _, [left, right]} ->
ensure_not_default(left)
ensure_not_default(right)
left
@@ -111,79 +104,25 @@ defmodule Behaviour do
end
quote do
docs_args = unquote(Macro.escape(docs_args))
@callback unquote(name)(unquote_splicing(args)) :: unquote(return) when unquote(guards)
Behaviour.store_docs(__MODULE__, unquote(caller.line), unquote(kind),
unquote(docs_name), docs_args, unquote(docs_arity))
unquote(docs_name), unquote(docs_arity))
end
end
defp ensure_not_default({ ://, _, [_, _] }) do
raise ArgumentError, message: "default arguments // not supported in defcallback/defmacrocallback"
end
defp ensure_not_default({ :\\, _, [_, _] }) do
defp ensure_not_default({:\\, _, [_, _]}) do
raise ArgumentError, message: "default arguments \\\\ not supported in defcallback/defmacrocallback"
end
defp ensure_not_default(_), do: :ok
@doc false
def store_docs(module, line, kind, name, args, arity) do
def store_docs(module, line, kind, name, arity) do
doc = Module.get_attribute module, :doc
Module.delete_attribute module, :doc
signature = Enum.map(Stream.with_index(args), fn { arg, ix } ->
{ simplify_signature(arg, ix + 1), [line: line], nil }
end)
Module.put_attribute module, :behaviour_docs, { { name, arity }, line, kind, signature, doc }
Module.put_attribute module, :behaviour_docs, {{name, arity}, line, kind, doc}
end
defp simplify_signature({ :::, _, [var, _] }, i) do
simplify_signature(var, i)
end
defp simplify_signature({ :|, _, [first, _] }, i) do
simplify_signature(first, i)
end
defp simplify_signature({ :., _, [_, name] }, _i) do
name
end
defp simplify_signature({ :->, _, list }, i) when is_list(list) do
:"fun#{i}"
end
defp simplify_signature({ :.., _, list }, i) when is_list(list) do
:"range#{i}"
end
defp simplify_signature({ :<<>>, _, list }, i) when is_list(list) do
:"bitstring#{i}"
end
defp simplify_signature({ :{}, _, list }, i) when is_list(list) do
:"tuple#{i}"
end
defp simplify_signature({ name, _, args }, _i)
when is_atom(name) and (is_atom(args) or is_list(args)) do
name
end
defp simplify_signature({ ast, _, list }, i) when is_list(list) do
simplify_signature(ast, i)
end
defp simplify_signature(other, i) when is_integer(other), do: :"int#{i}"
defp simplify_signature(other, i) when is_boolean(other), do: :"bool#{i}"
defp simplify_signature(other, i) when is_atom(other), do: :"atom#{i}"
defp simplify_signature(other, i) when is_list(other), do: :"list#{i}"
defp simplify_signature(other, i) when is_tuple(other), do: :"tuple#{i}"
defp simplify_signature(_, i), do: :"arg#{i}"
@doc false
defmacro __using__(_) do
quote do
+2 -2
View File
@@ -1,6 +1,6 @@
defmodule Bitwise do
@moduledoc """
This module provide macros and operators for bitwise operators.
This module provides macros and operators for bitwise operators.
These macros can be used in guards.
The easiest way to use is to simply import them into
@@ -15,7 +15,7 @@ defmodule Bitwise do
You can select to include only or skip operators by passing options:
iex> use Bitwise, only_operators: true
...> 1 &&& 1
iex> 1 &&& 1
1
"""
+108 -110
View File
@@ -28,7 +28,7 @@ defmodule Code do
allowing them to be required again.
"""
def unload_files(files) do
:elixir_code_server.cast { :unload_files, files }
:elixir_code_server.cast {:unload_files, files}
end
@doc """
@@ -37,7 +37,7 @@ defmodule Code do
The path is expanded with `Path.expand/1` before being appended.
"""
def append_path(path) do
:code.add_pathz(Path.expand to_char_list(path))
:code.add_pathz(to_char_list(Path.expand path))
end
@doc """
@@ -46,7 +46,7 @@ defmodule Code do
The path is expanded with `Path.expand/1` before being prepended.
"""
def prepend_path(path) do
:code.add_patha(Path.expand to_char_list(path))
:code.add_patha(to_char_list(Path.expand path))
end
@doc """
@@ -55,15 +55,15 @@ defmodule Code do
The path is expanded with `Path.expand/1` before being deleted.
"""
def delete_path(path) do
:code.del_path(Path.expand to_char_list(path))
:code.del_path(to_char_list(Path.expand path))
end
@doc """
Evaluate the contents given by `string`.
Evaluate the contents given by `string`.
The `binding` argument is a keyword list of variable bindings.
The `opts` argument is a keyword list of environment options.
The `binding` argument is a keyword list of variable bindings.
The `opts` argument is a keyword list of environment options.
Those options can be:
* `:file` - the file to be considered in the evaluation
@@ -88,8 +88,8 @@ defmodule Code do
`:macros` will no longer auto-import `Kernel` macros like `if`, `case`,
etc.
Returns a tuple of the form `{ value, binding }`,
where `value` is the the value returned from evaluating `string`.
Returns a tuple of the form `{value, binding}`,
where `value` is the value returned from evaluating `string`.
If an error occurs while evaluating `string` an exception will be raised.
`binding` is a keyword list with the value of all variable bindings
@@ -117,18 +117,15 @@ defmodule Code do
"""
def eval_string(string, binding \\ [], opts \\ [])
def eval_string(string, binding, Macro.Env[] = env) do
do_eval_string(string, binding, env.to_keywords)
def eval_string(string, binding, %Macro.Env{} = env) do
{value, binding, _env, _scope} = :elixir.eval to_char_list(string), binding, Map.to_list(env)
{value, binding}
end
def eval_string(string, binding, opts) when is_list(opts) do
validate_eval_opts(opts)
do_eval_string(string, binding, opts)
end
defp do_eval_string(string, binding, opts) when is_list(binding) do
{ value, binding, _env, _scope } = :elixir.eval to_char_list(string), binding, opts
{ value, binding }
{value, binding, _env, _scope} = :elixir.eval to_char_list(string), binding, opts
{value, binding}
end
@doc """
@@ -139,31 +136,28 @@ defmodule Code do
## Examples
iex> contents = quote(hygiene: [vars: false], do: a + b)
...> Code.eval_quoted(contents, [a: 1, b: 2], file: __ENV__.file, line: __ENV__.line)
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)
...> Code.eval_quoted(contents, [a: 1, b: 2], __ENV__)
iex> Code.eval_quoted(contents, [a: 1, b: 2], __ENV__)
{3, [a: 1, b: 2]}
"""
def eval_quoted(quoted, binding \\ [], opts \\ [])
def eval_quoted(quoted, binding, Macro.Env[] = env) do
do_eval_quoted(quoted, binding, env.to_keywords)
def eval_quoted(quoted, binding, %Macro.Env{} = env) do
{value, binding, _env, _scope} = :elixir.eval_quoted quoted, binding, Map.to_list(env)
{value, binding}
end
def eval_quoted(quoted, binding, opts) when is_list(opts) do
validate_eval_opts(opts)
do_eval_quoted(quoted, binding, opts)
end
defp do_eval_quoted(quoted, binding, opts) when is_list(binding) do
{ value, binding, _env, _scope } = :elixir.eval_quoted quoted, binding, opts
{ value, binding }
{value, binding, _env, _scope} = :elixir.eval_quoted quoted, binding, opts
{value, binding}
end
defp validate_eval_opts(opts) do
@@ -182,32 +176,32 @@ defmodule Code do
end
defp validate_aliases(kind, aliases) do
valid = is_list(aliases) and Enum.all?(aliases, fn { k, v } ->
valid = is_list(aliases) and Enum.all?(aliases, fn {k, v} ->
is_atom(k) and is_atom(v)
end)
unless valid do
raise ArgumentError, message: "expected :#{kind} option given to eval in the format: [{ module, module }]"
raise ArgumentError, message: "expected :#{kind} option given to eval in the format: [{module, module}]"
end
end
defp validate_imports(kind, imports) do
valid = is_list(imports) and Enum.all?(imports, fn { k, v } ->
is_atom(k) and is_list(v) and Enum.all?(v, fn { name, arity } ->
valid = is_list(imports) and Enum.all?(imports, fn {k, v} ->
is_atom(k) and is_list(v) and Enum.all?(v, fn {name, arity} ->
is_atom(name) and is_integer(arity)
end)
end)
unless valid do
raise ArgumentError, message: "expected :#{kind} option given to eval in the format: [{ module, [{ name, arity }] }]"
raise ArgumentError, message: "expected :#{kind} option given to eval in the format: [{module, [{name, arity}]}]"
end
end
@doc """
Convert the given string to its quoted form.
Returns `{ :ok, quoted_form }`
if it succeeds, `{ :error, { line, error, token } }` otherwise.
Convert the given string to its quoted form.
Returns `{:ok, quoted_form}`
if it succeeds, `{:error, {line, error, token}}` otherwise.
## Options
@@ -228,17 +222,12 @@ defmodule Code do
def string_to_quoted(string, opts \\ []) when is_list(opts) do
file = Keyword.get opts, :file, "nofile"
line = Keyword.get opts, :line, 1
res = :elixir.string_to_quoted(to_char_list(string), line, file, opts)
case res do
{ :ok, forms } -> { :ok, forms }
_ -> res
end
:elixir.string_to_quoted(to_char_list(string), line, file, opts)
end
@doc """
Convert the given string to its quoted form.
Convert the given string to its quoted form.
It returns the ast if it succeeds,
raises an exception otherwise. The exception is a `TokenMissingError`
in case a token is missing (usually because the expression is incomplete),
@@ -253,33 +242,46 @@ defmodule Code do
end
@doc """
Load the given file.
Accepts `relative_to` as an argument to tell where
the file is located. If the file was already required/loaded, loads it again.
It returns a list of tuples `{ ModuleName, <<byte_code>> }`, one tuple for each
module defined in the file.
Evals the given file.
Notice that if `load_file` is invoked by different processes
concurrently, the target file will be invoked concurrently
many times. I.e. if `load_file` is called N times with
a given file, the given file will be loaded N times. Check
`require_file/2` if you don't want a file to be loaded concurrently.
Accepts `relative_to` as an argument to tell where the file is located.
While `load_file` loads a file and returns the loaded modules and their
byte code, `eval_file` simply evalutes the file contents and returns the
evaluation result and its bindings.
"""
def eval_file(file, relative_to \\ nil) do
file = find_file(file, relative_to)
eval_string File.read!(file), [], []
end
@doc """
Load the given file.
Accepts `relative_to` as an argument to tell where the file is located.
If the file was already required/loaded, loads it again.
It returns a list of tuples `{ModuleName, <<byte_code>>}`, one tuple for
each module defined in the file.
Notice that if `load_file` is invoked by different processes concurrently,
the target file will be loaded concurrently many times. Check `require_file/2`
if you don't want a file to be loaded concurrently.
"""
def load_file(file, relative_to \\ nil) when is_binary(file) do
file = find_file(file, relative_to)
:elixir_code_server.call { :acquire, file }
:elixir_code_server.call {:acquire, file}
loaded = :elixir_compiler.file file
:elixir_code_server.cast { :loaded, file }
:elixir_code_server.cast {:loaded, file}
loaded
end
@doc """
Require the given `file`.
Accepts `relative_to` as an argument to tell where
the file is located. The return value is the same as that of `load_file/2`. If
the file was already required/loaded, doesn't do anything and returns `nil`.
Requires the given `file`.
Accepts `relative_to` as an argument to tell where the file is located.
The return value is the same as that of `load_file/2`. If the file was already
required/loaded, doesn't do anything and returns `nil`.
Notice that if `require_file` is invoked by different processes concurrently,
the first process to invoke `require_file` acquires a lock and the remaining
@@ -287,25 +289,25 @@ defmodule Code do
N times with a given file, it will be loaded only once. The first process to
call `require_file` will get the list of loaded modules, others will get `nil`.
Check `load_file/2` if you want a file to be loaded concurrently.
Check `load_file/2` if you want a file to be loaded multiple times.
"""
def require_file(file, relative_to \\ nil) when is_binary(file) do
file = find_file(file, relative_to)
case :elixir_code_server.call({ :acquire, file }) do
case :elixir_code_server.call({:acquire, file}) do
:loaded ->
nil
{ :queued, ref } ->
receive do { :elixir_code_server, ^ref, :loaded } -> nil end
{:queued, ref} ->
receive do {:elixir_code_server, ^ref, :loaded} -> nil end
:proceed ->
loaded = :elixir_compiler.file file
:elixir_code_server.cast { :loaded, file }
:elixir_code_server.cast {:loaded, file}
loaded
end
end
@doc """
Load the compilation options from the code server.
Gets the compilation options from the code server.
Check `compiler_options/1` for more information.
"""
@@ -314,7 +316,7 @@ defmodule Code do
end
@doc """
Set compilation options.
Sets compilation options.
These options are global since they are stored by Elixir's Code Server.
@@ -334,15 +336,14 @@ defmodule Code do
"""
def compiler_options(opts) do
:elixir_code_server.cast { :compiler_options, opts }
:elixir_code_server.cast {:compiler_options, opts}
end
@doc """
Compile the given string.
Compiles the given string.
Returns a list of tuples where
the first element is the module name and the second one is its
binary.
Returns a list of tuples where the first element is the module name
and the second one is its byte code (as a binary).
For compiling many files at once, check `Kernel.ParallelCompiler.files/2`.
"""
@@ -351,26 +352,23 @@ defmodule Code do
end
@doc """
Compile the quoted expression.
Returns a list of tuples where
the first element is the module name and the second one is its
binary.
Compiles the quoted expression.
Returns a list of tuples where the first element is the module name and
the second one is its byte code (as a binary).
"""
def compile_quoted(quoted, file \\ "nofile") when is_binary(file) do
:elixir_compiler.quoted quoted, file
end
@doc """
Ensure the given module is loaded.
If the module is already
loaded, this works as no-op. If the module was not yet loaded,
it tries to load it.
Ensures the given module is loaded.
If it succeeds loading the module, it returns
`{ :module, module }`. If not, returns `{ :error, reason }` with
the error reason.
If the module is already loaded, this works as no-op. If the module
was not yet loaded, it tries to load it.
If it succeeds loading the module, it returns `{:module, module}`.
If not, returns `{:error, reason}` with the error reason.
## Code loading on the Erlang VM
@@ -385,60 +383,59 @@ defmodule Code do
module uses this function to check if a specific parser exists for a given
URI scheme.
## Code.ensure_compiled
## `Code.ensure_compiled/1`
Elixir also contains an `ensure_compiled/1` function that is a
superset of `ensure_loaded/1`.
Since Elixir's compilation happens in parallel, in some situations
you may need to use a module but that was not yet compiled, therefore
you may need to use a module that was not yet compiled, therefore
it can't even be loaded.
`ensure_compiled/1` halts the current process until the
module we are depending on is available.
In most cases, `ensure_loaded` is enough. `ensure_compiled`
must be used in some rare cases, usually involving macros
that need to invoke a module for callback information.
In most cases, `ensure_loaded/1` is enough. `ensure_compiled/1`
must be used in rare cases, usually involving macros that need to
invoke a module for callback information.
"""
def ensure_loaded(module) when is_atom(module) do
:code.ensure_loaded(module)
end
@doc """
Ensure the given module is loaded.
Ensures the given module is loaded.
Similar to `ensure_loaded/1`, but returns `true` if the module
is already loaded or was successfully loaded. Returns `false` otherwise.
is already loaded or was successfully loaded. Returns `false`
otherwise.
"""
def ensure_loaded?(module) do
match?({ :module, ^module }, ensure_loaded(module))
match?({:module, ^module}, ensure_loaded(module))
end
@doc """
Ensure the given module is compiled and loaded.
If the module
is already loaded, it works as no-op. If the module was not
loaded yet, it checks if it needs to be compiled first and
then tries to load it.
Ensures the given module is compiled and loaded.
If it succeeds loading the module, it returns
`{ :module, module }`. If not, returns `{ :error, reason }` with
the error reason.
If the module is already loaded, it works as no-op. If the module was
not loaded yet, it checks if it needs to be compiled first and then
tries to load it.
If it succeeds loading the module, it returns `{:module, module}`.
If not, returns `{:error, reason}` with the error reason.
Check `ensure_loaded/1` for more information on module loading
and when to use `ensure_loaded/1` or `ensure_compiled/1`.
"""
def ensure_compiled(module) when is_atom(module) do
case :code.ensure_loaded(module) do
{ :error, :nofile } = error ->
{:error, :nofile} = error ->
case :erlang.get(:elixir_ensure_compiled) do
:undefined -> error
_ ->
try do
module.__info__(:module)
{ :module, module }
{:module, module}
rescue
UndefinedFunctionError -> error
end
@@ -448,19 +445,20 @@ defmodule Code do
end
@doc """
Ensure the given module is compiled and loaded.
Ensures the given module is compiled and loaded.
Similar to `ensure_compiled/1`, but returns `true` if the module
is already loaded or was successfully loaded and compiled.
is already loaded or was successfully loaded and compiled.
Returns `false` otherwise.
"""
def ensure_compiled?(module) do
match?({ :module, ^module }, ensure_compiled(module))
match?({:module, ^module}, ensure_compiled(module))
end
## Helpers
# Finds the file given the relative_to path.
#
# If the file is found, returns its path in binary, fails otherwise.
defp find_file(file, relative_to) do
file = if relative_to do
+117
View File
@@ -0,0 +1,117 @@
defprotocol Collectable do
@moduledoc """
A protocol to traverse data structures.
The `Enum.into/2` function uses this protocol to insert an
enumerable into a collection:
iex> Enum.into([a: 1, b: 2], %{})
%{a: 1, b: 2}
If a collection implements both `Enumerable` and `Collectable`, both
operations can be combined with `Enum.traverse/2`:
iex> Enum.traverse(%{a: 1, b: 2}, fn {k, v} -> {k, v * 2} end)
%{a: 2, b: 4}
## Why Collectable?
The `Enumerable` protocol is useful to take values out of a collection.
In order to support a wide range of values, the functions provided by
the `Enumerable` protocol do not keep shape. For example, passing a
dictionary to `Enum.map/2` always returns a list.
This design is intentional. `Enumerable` was designed to support infinite
collections, resources and other structures with fixed shape. For example,
it doesn't make sense to insert values into a range, as it has a fixed
shape where just the range limits are stored.
The `Collectable` module was designed to fill the gap left by the
`Enumerable` protocol. It provides two functions: `into/1` and `empty/1`.
`into/1` can be seen as the opposite of `Enumerable.reduce/3`. If
`Enumerable` is about taking values out, `Collectable.into/1` is about
collecting those values into a structure.
`empty/1` receives a collectable and returns an empty version of the
same collectable. By combining the enumerable functionality with `into/1`
and `empty/1`, one can, for example, implement a traversal mechanism.
"""
@type command :: {:cont, term} | :done | :halt
@doc """
Receives a collectable structure and returns an empty one.
"""
@spec empty(t) :: t
def empty(collectable)
@doc """
Returns a function that collects values alongside
the initial accumulation value.
The returned function receives a collectable and injects a given
value into it for every `{:cont, term}` instruction.
`:done` is passed when no further values will be injected, useful
for closing resources and normalizing values. A collectable must
be returned on `:done`.
If injection is suddenly interrupted, `:halt` is passed and it can
return any value, as it won't be used.
"""
@spec into(t) :: {term, (term, command -> t | term)}
def into(collectable)
end
defimpl Collectable, for: List do
def empty(_list) do
[]
end
def into(original) do
{[], fn
list, {:cont, x} -> [x|list]
list, :done -> original ++ :lists.reverse(list)
_, :halt -> :ok
end}
end
end
defimpl Collectable, for: BitString do
def empty(_bitstring) do
""
end
def into(original) do
{original, fn
bitstring, {:cont, x} -> <<bitstring :: bits, x :: bits>>
bitstring, :done -> bitstring
_, :halt -> :ok
end}
end
end
defimpl Collectable, for: Function do
def empty(function) do
function
end
def into(function) do
{function, function}
end
end
defimpl Collectable, for: Map do
def empty(_map) do
%{}
end
def into(original) do
{original, fn
map, {:cont, {k, v}} -> :maps.put(k, v, map)
map, :done -> map
_, :halt -> :ok
end}
end
end
+100 -117
View File
@@ -1,5 +1,5 @@
defmodule Dict do
@moduledoc %S"""
@moduledoc ~S"""
This module specifies the Dict API expected to be
implemented by different dictionaries. It also provides
functions that redirect to the underlying Dict, allowing
@@ -12,7 +12,7 @@ defmodule Dict do
HashDict.new #=> creates an empty HashDict
In the examples below, `dict_impl` means a specific
`Dict` implementation, for example `HashDict` or `ListDict`.
`Dict` implementation, for example `HashDict` or `Map`.
## Protocols
@@ -21,15 +21,11 @@ defmodule Dict do
protocol:
iex> dict = dict_impl.new
...> dict = Dict.put(dict, :hello, :world)
...> dict[:hello]
iex> dict = Dict.put(dict, :hello, :world)
iex> dict[:hello]
:world
And the `Enumerable` protocol, allowing one to write:
Enum.each(dict, fn ({ k, v }) ->
IO.puts "#{k}: #{v}"
end)
As well as the `Enumerable` and `Collectable` protocols.
## Match
@@ -41,18 +37,15 @@ defmodule Dict do
@type key :: any
@type value :: any
@type t :: tuple | list
@type t :: list | map
defcallback new :: t
defcallback new(Enum.t) :: t
defcallback new(Enum.t, (any -> { key, value })) :: t
defcallback delete(t, key) :: t
defcallback drop(t, Enum.t) :: t
defcallback empty(t) :: t
defcallback equal?(t, t) :: boolean
defcallback get(t, key) :: value
defcallback get(t, key, value) :: value
defcallback fetch(t, key) :: { :ok, value } | :error
defcallback fetch(t, key) :: {:ok, value} | :error
defcallback fetch!(t, key) :: value | no_return
defcallback has_key?(t, key) :: boolean
defcallback keys(t) :: [key]
@@ -62,7 +55,6 @@ defmodule Dict do
defcallback pop(t, key, value) :: {value, t}
defcallback put(t, key, value) :: t
defcallback put_new(t, key, value) :: t
defcallback reduce(t, Enumerable.acc, Enumerable.reducer) :: Enumerable.result
defcallback size(t) :: non_neg_integer()
defcallback split(t, Enum.t) :: {t, t}
defcallback take(t, Enum.t) :: t
@@ -73,13 +65,18 @@ defmodule Dict do
defmacrop target(dict) do
quote do
cond do
is_tuple(unquote(dict)) ->
elem(unquote(dict), 0)
is_list(unquote(dict)) ->
ListDict
true ->
unsupported_dict(unquote(dict))
case unquote(dict) do
%{__struct__: x} when is_atom(x) ->
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 ->
unsupported_dict(x)
end
end
end
@@ -90,8 +87,8 @@ defmodule Dict do
## Examples
iex> d = dict_impl.new([a: 1, b: 2])
...> Enum.sort(Dict.keys(d))
iex> d = Enum.into([a: 1, b: 2], dict_impl.new)
iex> Enum.sort(Dict.keys(d))
[:a,:b]
"""
@@ -106,8 +103,8 @@ defmodule Dict do
## Examples
iex> d = dict_impl.new([a: 1, b: 2])
...> Enum.sort(Dict.values(d))
iex> d = Enum.into([a: 1, b: 2], dict_impl.new)
iex> Enum.sort(Dict.values(d))
[1,2]
"""
@@ -121,8 +118,8 @@ defmodule Dict do
## Examples
iex> d = dict_impl.new([a: 1, b: 2])
...> Dict.size(d)
iex> d = Enum.into([a: 1, b: 2], dict_impl.new)
iex> Dict.size(d)
2
"""
@@ -136,7 +133,7 @@ defmodule Dict do
## Examples
iex> d = dict_impl.new([a: 1])
iex> d = Enum.into([a: 1], dict_impl.new)
iex> Dict.has_key?(d, :a)
true
iex> Dict.has_key?(d, :b)
@@ -154,7 +151,7 @@ defmodule Dict do
## Examples
iex> d = dict_impl.new([a: 1])
iex> d = Enum.into([a: 1], dict_impl.new)
iex> Dict.get(d, :a)
1
iex> Dict.get(d, :b)
@@ -168,14 +165,14 @@ defmodule Dict do
end
@doc """
Returns `{ :ok, value }` associated with `key` in `dict`.
Returns `{:ok, value}` associated with `key` in `dict`.
If `dict` does not contain `key`, returns `:error`.
## Examples
iex> d = dict_impl.new([a: 1])
iex> d = Enum.into([a: 1], dict_impl.new)
iex> Dict.fetch(d, :a)
{ :ok, 1 }
{:ok, 1}
iex> Dict.fetch(d, :b)
:error
@@ -191,11 +188,9 @@ defmodule Dict do
## Examples
iex> d = dict_impl.new([a: 1])
iex> d = Enum.into([a: 1], dict_impl.new)
iex> Dict.fetch!(d, :a)
1
iex> Dict.fetch!(d, :b)
** (KeyError) key not found: :b
"""
@spec fetch!(t, key) :: value | no_return
@@ -209,9 +204,9 @@ defmodule Dict do
## Examples
iex> d = dict_impl.new([a: 1, b: 2])
...> d = Dict.put(d, :a, 3)
...> Dict.get(d, :a)
iex> d = Enum.into([a: 1, b: 2], dict_impl.new)
iex> d = Dict.put(d, :a, 3)
iex> Dict.get(d, :a)
3
"""
@@ -225,9 +220,9 @@ defmodule Dict do
## Examples
iex> d = dict_impl.new([a: 1, b: 2])
...> d = Dict.put_new(d, :a, 3)
...> Dict.get(d, :a)
iex> d = Enum.into([a: 1, b: 2], dict_impl.new)
iex> d = Dict.put_new(d, :a, 3)
iex> Dict.get(d, :a)
1
"""
@@ -242,13 +237,13 @@ defmodule Dict do
## Examples
iex> d = dict_impl.new([a: 1, b: 2])
...> d = Dict.delete(d, :a)
...> Dict.get(d, :a)
iex> d = Enum.into([a: 1, b: 2], dict_impl.new)
iex> d = Dict.delete(d, :a)
iex> Dict.get(d, :a)
nil
iex> d = dict_impl.new([b: 2])
...> Dict.delete(d, :a) == d
iex> d = Enum.into([b: 2], dict_impl.new)
iex> Dict.delete(d, :a) == d
true
"""
@@ -270,18 +265,18 @@ defmodule Dict do
## Examples
iex> d1 = dict_impl.new([a: 1, b: 2])
...> d2 = dict_impl.new([a: 3, d: 4])
...> d = Dict.merge(d1, d2)
...> [a: Dict.get(d, :a), b: Dict.get(d, :b), d: Dict.get(d, :d)]
iex> d1 = Enum.into([a: 1, b: 2], dict_impl.new)
iex> d2 = Enum.into([a: 3, d: 4], dict_impl.new)
iex> d = Dict.merge(d1, d2)
iex> [a: Dict.get(d, :a), b: Dict.get(d, :b), d: Dict.get(d, :d)]
[a: 3, b: 2, d: 4]
iex> d1 = dict_impl.new([a: 1, b: 2])
...> d2 = dict_impl.new([a: 3, d: 4])
...> d = Dict.merge(d1, d2, fn(_k, v1, v2) ->
iex> d1 = Enum.into([a: 1, b: 2], dict_impl.new)
iex> d2 = Enum.into([a: 3, d: 4], dict_impl.new)
iex> d = Dict.merge(d1, d2, fn(_k, v1, v2) ->
...> v1 + v2
...> end)
...> [a: Dict.get(d, :a), b: Dict.get(d, :b), d: Dict.get(d, :d)]
iex> [a: Dict.get(d, :a), b: Dict.get(d, :b), d: Dict.get(d, :d)]
[a: 4, b: 2, d: 4]
"""
@@ -293,8 +288,8 @@ defmodule Dict do
if target1 == target2 do
target1.merge(dict1, dict2, fun)
else
target2.reduce(dict2, { :cont, dict1 }, fn({ k, v }, acc) ->
{ :cont, target1.update(acc, k, v, fn(other) -> fun.(k, other, v) end) }
Enumerable.reduce(dict2, {:cont, dict1}, fn({k, v}, acc) ->
{:cont, target1.update(acc, k, v, fn(other) -> fun.(k, other, v) end)}
end) |> elem(1)
end
end
@@ -305,19 +300,19 @@ defmodule Dict do
## Examples
iex> dict = dict_impl.new [a: 1]
...> {v, d} = Dict.pop dict, :a
...> {v, Enum.sort(d)}
iex> dict = Enum.into([a: 1], dict_impl.new)
iex> {v, d} = Dict.pop dict, :a
iex> {v, Enum.sort(d)}
{1,[]}
iex> dict = dict_impl.new [a: 1]
...> {v, d} = Dict.pop dict, :b
...> {v, Enum.sort(d)}
iex> dict = Enum.into([a: 1], dict_impl.new)
iex> {v, d} = Dict.pop dict, :b
iex> {v, Enum.sort(d)}
{nil,[a: 1]}
iex> dict = dict_impl.new [a: 1]
...> {v, d} = Dict.pop dict, :b, 3
...> {v, Enum.sort(d)}
iex> dict = Enum.into([a: 1], dict_impl.new)
iex> {v, d} = Dict.pop dict, :b, 3
iex> {v, Enum.sort(d)}
{3,[a: 1]}
"""
@@ -332,9 +327,9 @@ defmodule Dict do
## Examples
iex> d = dict_impl.new([a: 1, b: 2])
...> d = Dict.update!(d, :a, fn(val) -> -val end)
...> Dict.get(d, :a)
iex> d = Enum.into([a: 1, b: 2], dict_impl.new)
iex> d = Dict.update!(d, :a, fn(val) -> -val end)
iex> Dict.get(d, :a)
-1
"""
@@ -350,9 +345,9 @@ defmodule Dict do
## Examples
iex> d = dict_impl.new([a: 1, b: 2])
...> d = Dict.update(d, :c, 3, fn(val) -> -val end)
...> Dict.get(d, :c)
iex> d = Enum.into([a: 1, b: 2], dict_impl.new)
iex> d = Dict.update(d, :c, 3, fn(val) -> -val end)
iex> Dict.get(d, :c)
3
"""
@@ -370,20 +365,20 @@ defmodule Dict do
## Examples
iex> d = dict_impl.new([a: 1, b: 2, c: 3, d: 4])
...> { d1, d2 } = Dict.split(d, [:a, :c, :e])
...> { Dict.to_list(d1) |> Enum.sort, Dict.to_list(d2) |> Enum.sort }
{ [a: 1, c: 3], [b: 2, d: 4] }
iex> d = Enum.into([a: 1, b: 2, c: 3, d: 4], dict_impl.new)
iex> {d1, d2} = Dict.split(d, [:a, :c, :e])
iex> {Dict.to_list(d1) |> Enum.sort, Dict.to_list(d2) |> Enum.sort}
{[a: 1, c: 3], [b: 2, d: 4]}
iex> d = dict_impl.new([])
...> { d1, d2 } = Dict.split(d, [:a, :c])
...> { Dict.to_list(d1), Dict.to_list(d2) }
{ [], [] }
iex> d = Enum.into([], dict_impl.new)
iex> {d1, d2} = Dict.split(d, [:a, :c])
iex> {Dict.to_list(d1), Dict.to_list(d2)}
{[], []}
iex> d = dict_impl.new([a: 1, b: 2])
...> { d1, d2 } = Dict.split(d, [:a, :b, :c])
...> { Dict.to_list(d1) |> Enum.sort, Dict.to_list(d2) }
{ [a: 1, b: 2], [] }
iex> d = Enum.into([a: 1, b: 2], dict_impl.new)
iex> {d1, d2} = Dict.split(d, [:a, :b, :c])
iex> {Dict.to_list(d1) |> Enum.sort, Dict.to_list(d2)}
{[a: 1, b: 2], []}
"""
@spec split(t, [key]) :: {t, t}
@@ -397,14 +392,14 @@ defmodule Dict do
## Examples
iex> d = dict_impl.new([a: 1, b: 2])
...> d = Dict.drop(d, [:a, :c, :d])
...> Dict.to_list(d)
iex> d = Enum.into([a: 1, b: 2], dict_impl.new)
iex> d = Dict.drop(d, [:a, :c, :d])
iex> Dict.to_list(d)
[b: 2]
iex> d = dict_impl.new([a: 1, b: 2])
...> d = Dict.drop(d, [:c, :d])
...> Dict.to_list(d) |> Enum.sort
iex> d = Enum.into([a: 1, b: 2], dict_impl.new)
iex> d = Dict.drop(d, [:c, :d])
iex> Dict.to_list(d) |> Enum.sort
[a: 1, b: 2]
"""
@@ -420,14 +415,12 @@ defmodule Dict do
## Examples
iex> d = dict_impl.new([a: 1, b: 2])
...>
...> d = Dict.take(d, [:a, :c, :d])
...> Dict.to_list(d)
iex> d = Enum.into([a: 1, b: 2], dict_impl.new)
iex> d = Dict.take(d, [:a, :c, :d])
iex> Dict.to_list(d)
[a: 1]
...>
...> d = Dict.take(d, [:c, :d])
...> Dict.to_list(d)
iex> d = Dict.take(d, [:c, :d])
iex> Dict.to_list(d)
[]
"""
@@ -436,17 +429,7 @@ defmodule Dict do
target(dict).take(dict, keys)
end
@doc """
Returns an empty dict of the same type as `dict`.
## Examples
iex> d = dict_impl.new([a: 1, b: 2])
...> e = Dict.empty(d)
...> Dict.to_list(e)
[]
"""
@doc false
@spec empty(t) :: t
def empty(dict) do
target(dict).empty(dict)
@@ -461,14 +444,14 @@ defmodule Dict do
## Examples
iex> a = dict_impl.new(a: 2, b: 3, f: 5, c: 123)
...> b = [a: 2, b: 3, f: 5, c: 123]
...> Dict.equal?(a, b)
iex> a = Enum.into([a: 2, b: 3, f: 5, c: 123], dict_impl.new)
iex> b = [a: 2, b: 3, f: 5, c: 123]
iex> Dict.equal?(a, b)
true
iex> a = dict_impl.new(a: 2, b: 3, f: 5, c: 123)
...> b = []
...> Dict.equal?(a, b)
iex> a = Enum.into([a: 2, b: 3, f: 5, c: 123], dict_impl.new)
iex> b = []
iex> Dict.equal?(a, b)
false
"""
@@ -482,10 +465,10 @@ defmodule Dict do
target1.equal?(dict1, dict2)
target1.size(dict1) == target2.size(dict2) ->
target2.reduce(dict2, { :cont, true }, fn({ k, v }, _acc) ->
Enumerable.reduce(dict2, {:cont, true}, fn({k, v}, _acc) ->
case target1.fetch(dict1, k) do
{ :ok, ^v } -> { :cont, true }
_ -> { :halt, false }
{:ok, ^v} -> {:cont, true}
_ -> {:halt, false}
end
end) |> elem(1)
+74 -22
View File
@@ -1,5 +1,5 @@
defmodule Dict.Behaviour do
@moduledoc """
@moduledoc ~S"""
Default implementations for some required functions in the `Dict` module.
This module makes it easier to create your own `Dict` compliant
@@ -12,7 +12,7 @@ defmodule Dict.Behaviour do
# implement required functions (see below)
# override default implementations if needed
# override default implementations if optimization is needed
end
The client module must contain the following functions:
@@ -22,10 +22,9 @@ defmodule Dict.Behaviour do
* `put/3`
* `reduce/3`
* `size/1`
* `update/4`
All of which are part of the `Dict` behaviour, so no extra functions are
actually required.
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:
@@ -39,10 +38,15 @@ defmodule Dict.Behaviour do
* `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.
@@ -63,25 +67,28 @@ defmodule Dict.Behaviour do
"""
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
{: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
{:ok, value} -> value
:error -> raise KeyError, key: key, term: dict
end
end
def has_key?(dict, key) do
match? { :ok, _ }, fetch(dict, key)
match? {:ok, _}, fetch(dict, key)
end
def put_new(dict, key, value) do
@@ -95,53 +102,98 @@ defmodule Dict.Behaviour do
def take(dict, keys) do
Enum.reduce keys, new, fn key, acc ->
case fetch(dict, key) do
{ :ok, value } -> put(acc, key, value)
{: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] }
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] }
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] }
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) ->
reduce(dict1, {:cont, true}, fn({k, v}, _acc) ->
case fetch(dict2, k) do
{ :ok, ^v } -> { :cont, true }
_ -> { :halt, false }
{: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)) }
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
has_key?: 2, put_new: 3, pop: 2, pop: 3, update: 4, update!: 3,
split: 2
end
end
end
+400 -212
View File
File diff suppressed because it is too large Load Diff
+367 -78
View File
@@ -28,27 +28,33 @@ defexception CompileError, [file: nil, line: nil, description: "compile error"]
end
end
defexception BadFunctionError, [actual: nil] do
defexception BadFunctionError, [term: nil] do
def message(exception) do
"expected a function, got: #{inspect(exception.actual)}"
"expected a function, got: #{inspect(exception.term)}"
end
end
defexception MatchError, [actual: nil] do
defexception BadStructError, [struct: nil, term: nil] do
def message(exception) do
"no match of right hand side value: #{inspect(exception.actual)}"
"expected a struct named #{inspect(exception.struct)}, got: #{inspect(exception.term)}"
end
end
defexception CaseClauseError, [actual: nil] do
defexception MatchError, [term: nil] do
def message(exception) do
"no case clause matching: #{inspect(exception.actual)}"
"no match of right hand side value: #{inspect(exception.term)}"
end
end
defexception TryClauseError, [actual: nil] do
defexception CaseClauseError, [term: nil] do
def message(exception) do
"no try clause matching: #{inspect(exception.actual)}"
"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
@@ -57,7 +63,7 @@ defexception BadArityError, [function: nil, args: nil] do
fun = exception.function
args = exception.args
insp = Enum.map_join(args, ", ", &inspect/1)
{ :arity, arity } = :erlang.fun_info(fun, :arity)
{:arity, arity} = :erlang.fun_info(fun, :arity)
"#{inspect(fun)} with arity #{arity} called with #{count(length(args), insp)}"
end
@@ -105,9 +111,26 @@ defexception ErlangError, [original: nil] do
end
end
defexception KeyError, key: nil do
defexception KeyError, key: nil, term: nil do
def message(exception) do
"key not found: #{inspect exception.key}"
"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
@@ -117,14 +140,14 @@ defexception Enum.EmptyError, message: "empty error"
defexception File.Error, [reason: nil, action: "", path: nil] do
def message(exception) do
formatted = iolist_to_binary(:file.format_error(reason exception))
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 = iolist_to_binary(:file.format_error(reason exception))
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}"
@@ -133,31 +156,54 @@ end
defmodule Exception do
@moduledoc """
Convenience functions to work with and pretty print
exceptions and stacktraces.
Functions to work with and pretty print exceptions.
Notice that stacktraces in Elixir are updated on errors.
For example, at any given moement, `System.stacktrace`
will return the stacktrace for the last error that ocurred
in the current process.
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.
That said, many of the functions in this module will
automatically calculate the stacktrace based on the caller,
when invoked without arguments, changing the value of
`System.stacktrace`. If instead you want to format the
stacktrace of the latest error, you should instead explicitly
pass the `System.stacktrace` as argument.
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.
"""
@type t :: tuple
@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 }
{module, function, arity_or_args, location} |
{function, arity_or_args, location}
@typep arity_or_args :: non_neg_integer | list
@typep location :: Keyword.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
@doc """
Gets the message for an exception.
"""
def message(exception) do
try do
exception.message
rescue
e ->
raise ArgumentError, message:
"Got #{inspect e.__record__(:name)} with message " <>
"\"#{message(e)}\" while retrieving message for #{inspect(exception)}"
end
end
@doc """
Normalizes an exception, converting Erlang exceptions
to Elixir exceptions.
@@ -165,88 +211,329 @@ defmodule Exception do
It takes the `kind` spilled by `catch` as an argument and
normalizes only `:error`, returning the untouched payload
for others.
"""
def normalize(:error, exception), do: normalize(exception)
def normalize(_kind, other), do: other
@doc """
Normalizes an exception, converting Erlang exceptions
to Elixir exceptions.
Useful when interfacing Erlang code with Elixir code.
The third argument, a stacktrace, is optional. If it is
not supplied `System.stacktrace/0` will sometimes be used
to get additional information for the `kind` `:error`. If
the stacktrace is unknown and `System.stacktrace/0` would
not return the stacktrace corresponding to the exception
an empty stacktrace, `[]`, must be used.
"""
def normalize(exception) when is_exception(exception) do
exception
@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.
def normalize(kind, payload, stacktrace \\ nil)
def normalize(:error, exception, stacktrace) do
if exception?(exception) do
exception
else
normalize_error(exception, stacktrace)
end
end
def normalize(:badarg) do
def normalize(_kind, payload, _stacktrace) do
payload
end
defp normalize_error(:badarg, _stacktrace) do
ArgumentError[]
end
def normalize(:badarith) do
defp normalize_error(:badarith, _stacktrace) do
ArithmeticError[]
end
def normalize(:system_limit) do
defp normalize_error(:system_limit, _stacktrace) do
SystemLimitError[]
end
def normalize({ :badarity, { fun, args } }) do
defp normalize_error({:badarity, {fun, args}}, _stacktrace) do
BadArityError[function: fun, args: args]
end
def normalize({ :badfun, actual }) do
BadFunctionError[actual: actual]
defp normalize_error({:badfun, term}, _stacktrace) do
BadFunctionError[term: term]
end
def normalize({ :badmatch, actual }) do
MatchError[actual: actual]
defp normalize_error({:badstruct, struct, term}, _stacktrace) do
BadStructError[struct: struct, term: term]
end
def normalize({ :case_clause, actual }) do
CaseClauseError[actual: actual]
defp normalize_error({:badmatch, term}, _stacktrace) do
MatchError[term: term]
end
def normalize({ :try_clause, actual }) do
TryClauseError[actual: actual]
defp normalize_error({:case_clause, term}, _stacktrace) do
CaseClauseError[term: term]
end
def normalize(:undef) do
{ mod, fun, arity } = from_stacktrace(:erlang.get_stacktrace)
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
def normalize(:function_clause) do
{ mod, fun, arity } = from_stacktrace(:erlang.get_stacktrace)
defp normalize_error(: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 }) do
defp normalize_error({:badarg, payload}, _stacktrace) do
ArgumentError[message: "argument error: #{inspect(payload)}"]
end
def normalize(other) do
defp normalize_error(other, _stacktrace) do
ErlangError[original: other]
end
@doc """
Receives an stacktrace entry and formats it into a string.
Normalizes and formats any throw, error and exit.
The message is formatted and displayed in the same
format as used by Elixir's CLI.
The third argument, a stacktrace, is optional. If it is
not supplied `System.stacktrace/0` will sometimes be used
to get additional information for the `kind` `:error`. If
the stacktrace is unknown and `System.stacktrace/0` would
not return the stacktrace corresponding to the exception
an empty stacktrace, `[]`, must be used.
"""
@spec format_banner(:error | :exit | :throw, any, stacktrace) :: 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)
end
def format_banner(:throw, reason, _stacktrace) do
"** (throw) " <> inspect(reason)
end
def format_banner(:exit, reason, _stacktrace) do
"** (exit) " <> format_exit(reason, <<"\n ">>)
end
@doc """
Normalizes and formats an exception and stacktrace.
Behaves the same as `format_banner/3` but also includes
the formatted stacktrace.
"""
@spec format(:error | :exit | :throw, any, stacktrace) :: String.t
def format(kind, exception, stacktrace \\ :erlang.get_stacktrace()) do
message = format_banner(kind, exception, stacktrace)
case stacktrace do
[] -> message
_ -> message <> "\n" <> format_stacktrace(stacktrace)
end
end
@doc """
Formats an exit, returns a string.
Often there are errors/exceptions inside exits. Exits are often
wrapped by the caller and provide stacktraces too. This function
formats exits in a way to nicely show the exit reason, caller
and stacktrace.
"""
@spec format_exit(any) :: String.t
def format_exit(reason) do
format_exit(reason, <<"\n ">>)
end
# 2-Tuple could be caused by an error if the second element is a stacktrace.
defp format_exit({exception, maybe_stacktrace} = reason, joiner)
when is_list(maybe_stacktrace) and maybe_stacktrace !== [] do
try do
Enum.map(maybe_stacktrace, &format_stacktrace_entry/1)
else
formatted_stacktrace ->
# Assume a non-empty list formattable as stacktrace is a
# stacktrace, so exit was caused by an error.
message = "an exception was raised:" <> joiner <>
format_banner(:error, exception, maybe_stacktrace)
Enum.join([message | formatted_stacktrace], joiner <> <<" ">>)
catch
:error, _ ->
# Not a stacktrace, was an exit.
format_exit_reason(reason)
end
end
# :supervisor.start_link returns this error reason when it fails to init
# because a child's start_link raises.
defp format_exit({:shutdown,
{:failed_to_start_child, child, {:EXIT, reason}}}, joiner) do
format_start_child(child, reason, joiner)
end
# :supervisor.start_link returns this error reason when it fails to init
# because a child's start_link returns {:error, reason}.
defp format_exit({:shutdown, {:failed_to_start_child, child, reason}},
joiner) do
format_start_child(child, reason, joiner)
end
# 2-Tuple could be an exit caused by mfa if second element is mfa, args
# must be a list of arguments - max length 255 due to max arity.
defp format_exit({reason2, {mod, fun, args}} = reason, joiner)
when length(args) < 256 do
try do
format_mfa(mod, fun, args)
else
mfa ->
# Assume tuple formattable as an mfa is an mfa, so exit was caused by
# failed mfa.
"exited in: " <> mfa <> joiner <>
"** (EXIT) " <> format_exit(reason2, joiner <> <<" ">>)
catch
:error, _ ->
# Not an mfa, was an exit.
format_exit_reason(reason)
end
end
defp format_exit(reason, _joiner) do
format_exit_reason(reason)
end
defp format_exit_reason(:normal), do: "normal"
defp format_exit_reason(:shutdown), do: "shutdown"
defp format_exit_reason({:shutdown, reason}) do
"shutdown: #{inspect(reason)}"
end
defp format_exit_reason(:timeout), do: "time out"
defp format_exit_reason(:killed), do: "killed"
defp format_exit_reason(:noconnection), do: "no connection"
defp format_exit_reason(:noproc) do
"no process"
end
defp format_exit_reason({:nodedown, node_name}) when is_atom(node_name) do
"no connection to #{node_name}"
end
# :gen_server exit reasons
defp format_exit_reason({:already_started, pid}) do
"already started: " <> inspect(pid)
end
defp format_exit_reason({:bad_return_value, value}) do
"bad return value: " <> inspect(value)
end
defp format_exit_reason({:bad_call, request}) do
"bad call: " <> inspect(request)
end
defp format_exit_reason({:bad_cast, request}) do
"bad cast: " <> inspect(request)
end
# :supervisor.start_link error reasons
# If value is a list will be be formatted by mfa exit in format_exit/1
defp format_exit_reason({:bad_return, {mod, :init, value}})
when is_atom(mod) do
format_mfa(mod, :init, 1) <> " returned a bad value: " <> inspect(value)
end
defp format_exit_reason({:bad_start_spec, start_spec}) do
"bad start spec: invalid children: " <> inspect(start_spec)
end
defp format_exit_reason({:start_spec, start_spec}) do
"bad start spec: " <> format_sup_spec(start_spec)
end
defp format_exit_reason({:supervisor_data, data}) do
"bad supervisor data: " <> format_sup_data(data)
end
defp format_exit_reason(reason), do: inspect(reason)
defp format_start_child(child, reason, joiner) do
"shutdown: failed to start child: " <> inspect(child) <> joiner <>
"** (EXIT) " <> format_exit(reason, joiner <> <<" ">>)
end
defp format_sup_data({:invalid_type, type}) do
"invalid type: " <> inspect(type)
end
defp format_sup_data({:invalid_strategy, strategy}) do
"invalid strategy: " <> inspect(strategy)
end
defp format_sup_data({:invalid_intensity, intensity}) do
"invalid intensity: " <> inspect(intensity)
end
defp format_sup_data({:invalid_period, period}) do
"invalid period: " <> inspect(period)
end
defp format_sup_data(other), do: inspect(other)
defp format_sup_spec({:invalid_child_spec, child_spec}) do
"invalid child spec: " <> inspect(child_spec)
end
defp format_sup_spec({:invalid_child_type, type}) do
"invalid child type: " <> inspect(type)
end
defp format_sup_spec({:invalid_mfa, mfa}) do
"invalid mfa: " <> inspect(mfa)
end
defp format_sup_spec({:invalid_restart_type, restart}) do
"invalid restart type: " <> inspect(restart)
end
defp format_sup_spec({:invalid_shutdown, shutdown}) do
"invalid shutdown: " <> inspect(shutdown)
end
defp format_sup_spec({:invalid_module, mod}) do
"invalid module: " <> inspect(mod)
end
defp format_sup_spec({:invalid_modules, modules}) do
"invalid modules: " <> inspect(modules)
end
defp format_sup_spec(other), do: inspect(other)
@doc """
Receives a stacktrace entry and formats it into a string.
"""
@spec format_stacktrace_entry(stacktrace_entry) :: String.t
def format_stacktrace_entry(entry)
# From Macro.Env.stacktrace
def format_stacktrace_entry({ module, :__MODULE__, 0, location }) do
def format_stacktrace_entry({module, :__MODULE__, 0, location}) do
format_location(location) <> inspect(module) <> " (module)"
end
# From :elixir_compiler_*
def format_stacktrace_entry({ _module, :__MODULE__, 1, location }) do
def format_stacktrace_entry({_module, :__MODULE__, 1, location}) do
format_location(location) <> "(module)"
end
# From :elixir_compiler_*
def format_stacktrace_entry({ _module, :__FILE__, 1, location }) do
def format_stacktrace_entry({_module, :__FILE__, 1, location}) do
format_location(location) <> "(file)"
end
@@ -260,7 +547,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_binary(app) <> ") "
:undefined -> ""
end
end
@@ -268,16 +555,14 @@ defmodule Exception do
@doc """
Formats the stacktrace.
A stacktrace must be given as an argument. If not, this function
calculates a new stacktrace based on the caller and formats it. As
a consequence, the value of `System.stacktrace` is changed.
A stacktrace must be given as an argument. If not, the stacktrace
is retrieved from `Process.info/2`.
"""
def format_stacktrace(trace \\ nil) do
trace = trace || try do
throw(:stacktrace)
catch
:stacktrace -> Enum.drop(:erlang.get_stacktrace, 1)
trace = trace || case Process.info(self, :current_stacktrace) do
{:current_stacktrace, t} -> Enum.drop(t, 3)
end
case trace do
[] -> "\n"
s -> " " <> Enum.map_join(s, "\n ", &format_stacktrace_entry(&1)) <> "\n"
@@ -294,7 +579,7 @@ defmodule Exception do
#=> "#Function<...>/1"
"""
def format_fa(fun, arity) do
def format_fa(fun, arity) when is_function(fun) do
"#{inspect fun}#{format_arity(arity)}"
end
@@ -307,8 +592,10 @@ defmodule Exception do
iex> Exception.format_mfa Foo, :bar, 1
"Foo.bar/1"
iex> Exception.format_mfa Foo, :bar, []
"Foo.bar()"
iex> Exception.format_mfa nil, :bar, []
"nil.bar()"
@@ -316,7 +603,7 @@ defmodule Exception do
where func is the name of the enclosing function. Convert to
"anonymous fn in func/arity"
"""
def format_mfa(module, fun, arity) when is_atom(fun) do
def format_mfa(module, fun, arity) when is_atom(module) and is_atom(fun) do
fun =
case inspect(fun) do
":" <> fun -> fun
@@ -332,11 +619,13 @@ defmodule Exception do
end
defp format_arity(arity) when is_list(arity) do
inspected = lc x inlist arity, do: inspect(x)
inspected = for x <- arity, do: inspect(x)
"(#{Enum.join(inspected, ", ")})"
end
defp format_arity(arity), do: "/#{arity}"
defp format_arity(arity) when is_integer(arity) do
"/" <> integer_to_binary(arity)
end
@doc """
Formats the given file and line as shown in stacktraces.
@@ -370,19 +659,19 @@ defmodule Exception do
end
end
defp format_location(opts) do
defp format_location(opts) when is_list(opts) do
format_file_line Keyword.get(opts, :file), Keyword.get(opts, :line), " "
end
defp from_stacktrace([{ module, function, args, _ }|_]) when is_list(args) do
{ module, function, length(args) }
defp from_stacktrace([{module, function, args, _}|_]) when is_list(args) do
{module, function, length(args)}
end
defp from_stacktrace([{ module, function, arity, _ }|_]) do
{ module, function, arity }
defp from_stacktrace([{module, function, arity, _}|_]) do
{module, function, arity}
end
defp from_stacktrace(_) do
{ nil, nil, nil }
{nil, nil, nil}
end
end
+469 -258
View File
File diff suppressed because it is too large Load Diff
+23 -13
View File
@@ -6,26 +6,29 @@ defmodule Float do
@doc """
Parses a binary into a float.
If successful, returns a tuple of the form `{ float, remainder_of_binary }`.
If successful, returns a tuple of the form `{float, remainder_of_binary}`.
Otherwise `:error`.
## Examples
iex> Float.parse("34")
{34.0,""}
iex> Float.parse("34.25")
{34.25,""}
iex> Float.parse("56.5xyz")
{56.5,"xyz"}
iex> Float.parse("pi")
:error
"""
@spec parse(binary) :: { float, binary } | :error
@spec parse(binary) :: {float, binary} | :error
def parse("-" <> binary) do
case parse_unsign(binary) do
:error -> :error
{ number, remainder } -> { -number, remainder }
{number, remainder} -> {-number, remainder}
end
end
@@ -37,7 +40,7 @@ defmodule Float do
defp parse_unsign(binary) when is_binary(binary) do
case Integer.parse binary do
:error -> :error
{ integer_part, after_integer } -> parse_unsign after_integer, integer_part
{integer_part, after_integer} -> parse_unsign after_integer, integer_part
end
end
@@ -47,7 +50,7 @@ defmodule Float do
end
defp parse_unsign(rest, int) do
{ :erlang.float(int), rest }
{:erlang.float(int), rest}
end
# Handle decimal points
@@ -62,14 +65,14 @@ defmodule Float do
# the function clause because the current approach copies a binary
# just on this branch. If we broke it apart in the function clause,
# the copy would happen when calling Integer.parse/1.
{ floatify(int, float, decimal), << ?e, after_e :: binary >> }
{ exponential, after_exponential } ->
{ floatify(int, float, decimal, exponential), after_exponential }
{floatify(int, float, decimal), << ?e, after_e :: binary >>}
{exponential, after_exponential} ->
{floatify(int, float, decimal, exponential), after_exponential}
end
end
defp parse_unsign(bitstring, float, decimal, int) do
{ floatify(int, float, decimal), bitstring }
{floatify(int, float, decimal), bitstring}
end
defp floatify(int, float, decimal, exponential \\ 0) do
@@ -82,7 +85,7 @@ defmodule Float do
# iex(1)> 0.0001 * 75
# 0.007500000000000001
# Due to IEEE 754 floating point standard
# http:\\docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html
# http://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html
final_decimal_places = decimal - exponential
if final_decimal_places > 0 do
@@ -94,14 +97,16 @@ defmodule Float do
end
@doc """
Round a float to the largest integer less than or equal to `num`
Rounds a float to the largest integer less than or equal to `num`.
## Examples
iex> Float.floor(34)
34
iex> Float.floor(34.25)
34
iex> Float.floor(-56.5)
-57
@@ -117,14 +122,16 @@ defmodule Float do
end
@doc """
Round a float to the largest integer greater than or equal to `num`
Rounds a float to the largest integer greater than or equal to `num`.
## Examples
iex> Float.ceil(34)
34
iex> Float.ceil(34.25)
35
iex> Float.ceil(-56.5)
-56
@@ -147,16 +154,19 @@ defmodule Float do
iex> Float.round(5.5674, 3)
5.567
iex> Float.round(5.5675, 3)
5.568
iex> Float.round(-5.5674, 3)
-5.567
iex> Float.round(-5.5675, 3)
-5.568
"""
@spec round(float, integer) :: float
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
end
+8 -8
View File
@@ -15,11 +15,11 @@ defmodule GenEvent.Behaviour do
# Callbacks
def init(_) do
{ :ok, [] }
{:ok, []}
end
def handle_event({:notification, x}, notifications) do
{ :ok, [x|notifications] }
{:ok, [x|notifications]}
end
def handle_call(:notifications, notifications) do
@@ -27,7 +27,7 @@ defmodule GenEvent.Behaviour do
end
end
{ :ok, pid } = :gen_event.start_link
{:ok, pid} = :gen_event.start_link
#=> {:ok,#PID<0.42.0>}
:gen_event.add_handler(pid, MyEventHandler, [])
@@ -63,22 +63,22 @@ defmodule GenEvent.Behaviour do
@doc false
def init(args) do
{ :ok, args }
{:ok, args}
end
@doc false
def handle_event(_event, state) do
{ :ok, state }
{:ok, state}
end
@doc false
def handle_call(_request, state) do
{ :ok, :ok, state }
{:ok, :ok, state}
end
@doc false
def handle_info(_msg, state) do
{ :ok, state }
{:ok, state}
end
@doc false
@@ -88,7 +88,7 @@ defmodule GenEvent.Behaviour do
@doc false
def code_change(_old, state, _extra) do
{ :ok, state }
{:ok, state}
end
defoverridable [init: 1,
-200
View File
@@ -1,200 +0,0 @@
defmodule GenFSM.Behaviour do
@moduledoc """
This module is a convenience for defining GenFSM callbacks in Elixir.
A finite state machine (FSM) is responsible for reacting to events received;
GenFSM is an OTP behaviour that encapsulates common FSM
functionalities.
## Example
Below is an example of a GenFSM that runs a very simple minded
coffee vending machine (CVM). The CVM treats all coins the same. If
you press the request button then the CVM will brew coffee, if you
have paid enough coins; if not, it will wait until you have inserted
enough coins and then it will instantly brew the coffee, since you
had already pressed the request button. As we told you - a very
simple minded CVM! And greedy too. If you insert more coins than you
need it will gladly eat them until you press the request button.
We will leave it to the service-minded reader to improve the way the CVM
works - we hereby declare a full disclaimer for any lawsuits that the
behaviour of the CVM in its original state might encur.
defmodule MyFsm do
use GenFSM.Behaviour
# keeping track of what is going on inside the CVM.
# 3 is the target price for a cup of coffee
defrecord StateData, coins: 0, price: 3
# API functions
def start_link() do
:gen_fsm.start_link({:local, :cvm}, __MODULE__, [], [])
end
def insert_coin() do
:gen_fsm.send_event(:cvm, :coin)
end
def request_coffee() do
:gen_fsm.send_event(:cvm, :request_coffee)
end
# Callbacks
def init(_args) do
{ :ok, :short_paid, StateData.new }
end
def short_paid(:coin, state_data = StateData[coins: c, price: p]) when c + 1 < p do
{ :next_state, :short_paid, state_data.coins(c + 1) }
end
def short_paid(:coin, state_data) do
{ :next_state, :paid_in_full, &state_data.update_coins(&1 + 1) }
end
def short_paid(:request_coffee, state_data) do
{ :next_state, :requested_short_paid, state_data }
end
def requested_short_paid(:request_coffee, state_data) do
{ :next_state, :requested_short_paid, state_data }
end
def requested_short_paid(:coin, state_data=StateData[coins: c, price: p]) when c+1 < p do
{ :next_state, :requested_short_paid, state_data.coins(c + 1) }
end
def requested_short_paid(:coin, _state_data) do
IO.puts "Here's your coffee!"
{ :next_state, :short_paid, StateData.new }
end
def paid_in_full(:coin, state_data) do
{ :next_state, :paid_in_full, &state_data.update_coins(&1 + 1) }
end
def paid_in_full(:request_coffee, _state_data) do
IO.puts "Here's your coffee!"
{ :next_state, :short_paid, StateData.new }
end
end
{ :ok, _pid } = MyFsm.start_link()
MyFsm.insert_coin
#=> :ok
MyFsm.insert_coin
#=> :ok
MyFsm.request_coffee
#=> :ok
MyFsm.insert_coin
#=> :ok
#=> Here's your coffee!
Notice we never call the GenFSM callbacks directly; they are called by
OTP whenever we interact with the server throught the API. `send_event` is
asynchronous, whereas `sync_send_event` is synchronous. For
a GenFSM, the different values a callback can return depend
on the type of callback.
State handling returns for `send_event` callbacks:
{ :next_state, next_state_name, new_state_data }
{ :next_state, next_state_name, new_state_data, timeout }
{ :next_state, next_state_name, new_state_data, :hibernate }
{ :stop, reason, new_state_data }
State handling returns for `sync_send_event` callbacks:
{ :reply, reply, next_state_name, new_state_data }
{ :reply, reply, next_state_name, new_state_data, timeout }
{ :reply, reply, next_state_name, new_state_data, :hibernate }
{ :next_state, next_state_name, new_state_data }
{ :next_state, next_state_name, new_state_data, timeout }
{ :next_state, next_state_name, new_state_data, :hibernate }
{ :stop, reason, reply, new_state_data }
{ :stop, reason, new_state_date }
There are 6 callbacks required to be implemented in a GenFsm plus 1
or 2 for each state. The `GenFSM.Behaviour` module defines
`handle_sync_event`, `handle_info`, `terminate` and `code_change`
for you. The list of callbacks are:
* `init(args)` - invoked when the FSM is started;
* `handle_sync_event(event, from, state_name, state_data)` - invoked to
handle `sync_send_all_state_event` messages;
* `handle_event(event, state_name, state_data)` - invoked to handle
`send_all_state_event` messages;
* `handle_info(msg, state_name, state_data)` - handle all other
messages which are normally received by processes;
* `terminate(reason, state_name, state_data)` - called when the FSM
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);
Unlike `GenServer` and `GenEvent`, the callback `init/1` is not
implemented by default, as it requires the next state to be returned.
For each state you need to define either or both of these:
* `state_name(event, state_data)` - invoked to handle
`send_event` messages;
* `state_name(event, from, state_data)`- invoked to handle
`sync_send_event` messages;
If you send asynchronous events you only need to implement the
`state_name/2` variant and vice-versa for synchronous events and
`state_name/3`. Keep in mind that if you mix `send_event` and
`sync_send_event` the best thing to do is to implement both
callbacks for all states.
Starting and sending messages to the GenFSM is done via Erlang's
`:gen_fsm` module. For more information, please refer to the
following:
* http://www.erlang.org/doc/man/gen_fsm.html
* http://www.erlang.org/doc/design_principles/fsm.html
* http://learnyousomeerlang.com/finite-state-machines
"""
@doc false
defmacro __using__(_) do
quote location: :keep do
@behaviour :gen_fsm
@doc false
def handle_event(event, state_name, state_data) do
{ :stop, {:bad_event, state_name, event}, state_data }
end
@doc false
def handle_sync_event(event, from, state_name, state_data) do
{ :stop, {:bad_sync_event, state_name, event}, state_data }
end
@doc false
def handle_info(_msg, state_name, state_data) do
{ :next_state, state_name, state_data }
end
@doc false
def terminate(_reason, _state_name, _state_data) do
:ok
end
@doc false
def code_change(_old, state_name, state_data, _extra) do
{ :ok, state_name, state_data }
end
defoverridable [handle_event: 3, handle_sync_event: 4,
handle_info: 3, terminate: 3, code_change: 4]
end
end
end
+18 -18
View File
@@ -17,7 +17,7 @@ defmodule GenServer.Behaviour do
# Callbacks
def handle_call(:pop, _from, [h|t]) do
{ :reply, h, t }
{:reply, h, t}
end
def handle_call(request, from, config) do
@@ -25,8 +25,8 @@ defmodule GenServer.Behaviour do
super(request, from, config)
end
def handle_cast({ :push, item }, config) do
{ :noreply, [item|config] }
def handle_cast({:push, item}, config) do
{:noreply, [item|config]}
end
def handle_cast(request, config) do
@@ -34,12 +34,12 @@ defmodule GenServer.Behaviour do
end
end
{ :ok, pid } = :gen_server.start_link(MyServer, [:hello], [])
{:ok, pid} = :gen_server.start_link(MyServer, [:hello], [])
:gen_server.call(pid, :pop)
#=> :hello
:gen_server.cast(pid, { :push, :world })
:gen_server.cast(pid, {:push, :world})
#=> :ok
:gen_server.call(pid, :pop)
@@ -51,14 +51,14 @@ defmodule GenServer.Behaviour do
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 }
{: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
@@ -90,22 +90,22 @@ defmodule GenServer.Behaviour do
@doc false
def init(args) do
{ :ok, args }
{:ok, args}
end
@doc false
def handle_call(request, _from, state) do
{ :stop, { :bad_call, request }, state }
{:stop, {:bad_call, request}, state}
end
@doc false
def handle_info(_msg, state) do
{ :noreply, state }
{:noreply, state}
end
@doc false
def handle_cast(msg, state) do
{ :stop, { :bad_cast, msg }, state }
{:stop, {:bad_cast, msg}, state}
end
@doc false
@@ -115,7 +115,7 @@ defmodule GenServer.Behaviour do
@doc false
def code_change(_old, state, _extra) do
{ :ok, state }
{:ok, state}
end
defoverridable [init: 1, handle_call: 3, handle_info: 2,
+57 -74
View File
@@ -2,8 +2,14 @@ defmodule HashDict do
@moduledoc """
A key-value store.
The `HashDict` is implemented using tries, which grows in
space as the number of keys grows, working well with both
The `HashDict` is represented internally as a struct, therefore
`%HashDict{}` can be used whenever there is a need to match
on any `HashDict`. Note though the struct fields are private and
must not be accessed directly. Instead, use the functions on this
or in the `Dict` module.
Implementation-wise, `HashDict` is implemented using tries, which
grows in space as the number of keys grows, working well with both
small and large set of keys. For more information about the
functions and their APIs, please consult the `Dict` module.
"""
@@ -15,131 +21,94 @@ defmodule HashDict do
@node_size 8
@node_template :erlang.make_tuple(@node_size, [])
defrecordp :trie, HashDict,
size: 0,
root: @node_template
@opaque t :: map
defstruct size: 0, root: @node_template
# Inline common instructions
@compile :inline_list_funcs
@compile { :inline, key_hash: 1, key_mask: 1, key_shift: 1 }
@compile {:inline, key_hash: 1, key_mask: 1, key_shift: 1}
@doc """
Creates a new empty dict.
"""
@spec new :: Dict.t
def new do
trie()
%HashDict{}
end
@doc """
Creates a new dict from the given enumerable.
## Examples
HashDict.new [{:b, 1}, {:a, 2}]
#=> #HashDict<[a: 2, b: 1]>
"""
@spec new(Enum.t) :: Dict.t
def new(enum) do
Enum.reduce enum, trie(), fn { k, v }, dict ->
put(dict, k, v)
end
def put(%HashDict{root: root, size: size}, key, value) do
{root, counter} = do_put(root, key, value, key_hash(key))
%HashDict{root: root, size: size + counter}
end
@doc """
Creates a new dict from the enumerable with the
help of the transformation function.
## Examples
HashDict.new ["a", "b"], fn x -> {x, x} end
#=> #HashDict<[{"a","a"},{"b","b"}]>
"""
@spec new(Enum.t, (term -> {key :: term, value ::term})) :: Dict.t
def new(enum, transform) when is_function(transform) do
Enum.reduce enum, trie(), fn i, dict ->
{ k, v } = transform.(i)
put(dict, k, v)
end
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))
%HashDict{root: root, size: size + counter}
end
def empty(trie()) do
trie()
def update(%HashDict{root: root, size: size}, key, initial, fun) when is_function(fun, 1) do
{root, counter} = do_update(root, key, fn -> initial end, fun, key_hash(key))
%HashDict{root: root, size: size + counter}
end
def put(trie(root: root, size: size), key, value) do
{ root, counter } = do_put(root, key, value, key_hash(key))
trie(root: root, size: size + counter)
end
def update!(trie(root: root, size: size), key, fun) when is_function(fun, 1) do
{ root, counter } = do_update(root, key, fn -> raise KeyError, key: key end, fun, key_hash(key))
trie(root: root, size: size + counter)
end
def update(trie(root: root, size: size), key, initial, fun) when is_function(fun, 1) do
{ root, counter } = do_update(root, key, fn -> initial end, fun, key_hash(key))
trie(root: root, size: size + counter)
end
def fetch(trie(root: root), key) do
def fetch(%HashDict{root: root}, key) do
do_fetch(root, key, key_hash(key))
end
def delete(dict, key) do
case dict_delete(dict, key) do
{ dict, _value } -> dict
{dict, _value} -> 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 }
{dict, value} -> {value, dict}
:error -> {default, dict}
end
end
def size(trie(size: size)) do
def size(%HashDict{size: size}) do
size
end
def reduce(trie(root: root), acc, fun) do
@doc false
def reduce(%HashDict{root: root}, acc, fun) do
do_reduce(root, acc, fun, @node_size, fn
{:suspend, acc} -> {:suspended, acc, &{ :done, elem(&1, 1) }}
{:suspend, acc} -> {:suspended, acc, &{:done, elem(&1, 1)}}
{:halt, acc} -> {:halted, acc}
{:cont, acc} -> {:done, acc}
end)
end
def split(dict, keys) do
Enum.reduce keys, { new, dict }, fn key, { inc, exc } = acc ->
Enum.reduce keys, {new, dict}, fn key, {inc, exc} = acc ->
case dict_delete(exc, key) do
{ exc, value } -> { put(inc, key, value), exc }
{exc, value} -> {put(inc, key, value), exc}
:error -> acc
end
end
end
def merge(trie(size: size1) = dict1, trie(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)) }
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(trie() = dict1, trie() = dict2, callback) do
reduce(dict2, { :cont, dict1 }, fn { k, v2 }, acc ->
{ :cont, update(acc, k, v2, &callback.(k, &1, v2)) }
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(trie(root: root, size: size), key) do
defp dict_delete(%HashDict{root: root, size: size}, key) do
case do_delete(root, key, key_hash(key)) do
{ root, value } -> { trie(root: root, size: size - 1), value }
{root, value} -> {%HashDict{root: root, size: size - 1}, value}
:error -> :error
end
end
@@ -278,11 +247,25 @@ end
defimpl Enumerable, for: HashDict do
def reduce(dict, acc, fun), do: HashDict.reduce(dict, acc, fun)
def member?(dict, { k, v }), do: { :ok, match?({ :ok, ^v }, HashDict.fetch(dict, k)) }
def member?(_dict, _), do: { :ok, false }
def count(dict), do: { :ok, HashDict.size(dict) }
def member?(dict, {k, v}), do: {:ok, match?({:ok, ^v}, HashDict.fetch(dict, k))}
def member?(_dict, _), do: {:ok, false}
def count(dict), do: {:ok, HashDict.size(dict)}
end
defimpl Access, for: HashDict do
def access(dict, key), do: HashDict.get(dict, key, nil)
end
defimpl Collectable, for: HashDict do
def empty(_dict) do
HashDict.new
end
def into(original) do
{original, fn
dict, {:cont, {k, v}} -> Dict.put(dict, k, v)
dict, :done -> dict
_, :halt -> :ok
end}
end
end
+50 -67
View File
@@ -2,6 +2,12 @@ defmodule HashSet do
@moduledoc """
A set store.
The `HashSet` is represented internally as a struct, therefore
`%HashSet{}` can be used whenever there is a need to match
on any `HashSet`. Note though the struct fields are private and
must not be accessed directly. Instead, use the functions on this
or in the `Set` module.
The `HashSet` is implemented using tries, which grows in
space as the number of keys grows, working well with both
small and large set of keys. For more information about the
@@ -15,70 +21,36 @@ defmodule HashSet do
@node_size 8
@node_template :erlang.make_tuple(@node_size, [])
defrecordp :trie, HashSet,
size: 0,
root: @node_template
@opaque t :: map
defstruct size: 0, root: @node_template
# Inline common instructions
@compile :inline_list_funcs
@compile { :inline, key_hash: 1, key_mask: 1, key_shift: 1 }
@compile {:inline, key_hash: 1, key_mask: 1, key_shift: 1}
@doc """
Creates a new empty set.
"""
@spec new :: Set.t
def new do
trie()
%HashSet{}
end
@doc """
Creates a new set from the given enumerable.
## Examples
HashSet.new [1, 2]
#=> #HashSet<[1, 2]>
"""
@spec new(Enum.t) :: Set.t
def new(enum) do
Enum.reduce enum, trie(), fn i, set ->
put(set, i)
end
end
@doc """
Creates a new set from the enumerable with the
help of the transformation function.
## Examples
HashSet.new [1, 2], &integer_to_binary/1
#=> #HashSet<[1, 2]>
"""
@spec new(Enum.t, (term -> term)) :: Set.t
def new(enum, transform) when is_function(transform) do
Enum.reduce enum, trie(), fn i, set ->
put(set, transform.(i))
end
end
def union(trie(size: size1) = set1, trie(size: size2) = set2) when size1 <= size2 do
def union(%HashSet{size: size1} = set1, %HashSet{size: size2} = set2) when size1 <= size2 do
set_fold set1, set2, fn v, acc -> put(acc, v) end
end
def union(trie() = set1, trie() = set2) do
def union(%HashSet{} = set1, %HashSet{} = set2) do
set_fold set2, set1, fn v, acc -> put(acc, v) end
end
def intersection(trie() = set1, trie() = set2) do
set_fold set1, trie(), fn v, acc ->
def intersection(%HashSet{} = set1, %HashSet{} = set2) do
set_fold set1, %HashSet{}, fn v, acc ->
if member?(set2, v), do: put(acc, v), else: acc
end
end
def difference(trie() = set1, trie() = set2) do
def difference(%HashSet{} = set1, %HashSet{} = set2) do
set_fold set2, set1, fn v, acc -> delete(acc, v) end
end
@@ -86,66 +58,63 @@ defmodule HashSet do
set_fold(set, [], &[&1|&2]) |> :lists.reverse
end
def equal?(trie(size: size1) = set1, trie(size: size2) = set2) do
def equal?(%HashSet{size: size1} = set1, %HashSet{size: size2} = set2) do
case size1 do
^size2 -> subset?(set1, set2)
_ -> false
end
end
def subset?(trie() = set1, trie() = set2) do
reduce(set1, { :cont, true }, fn member, acc ->
def subset?(%HashSet{} = set1, %HashSet{} = set2) do
reduce(set1, {:cont, true}, fn member, acc ->
case member?(set2, member) do
true -> { :cont, acc }
_ -> { :halt, false }
true -> {:cont, acc}
_ -> {:halt, false}
end
end) |> elem(1)
end
def disjoint?(trie() = set1, trie() = set2) do
reduce(set2, { :cont, true }, fn member, acc ->
def disjoint?(%HashSet{} = set1, %HashSet{} = set2) do
reduce(set2, {:cont, true}, fn member, acc ->
case member?(set1, member) do
false -> { :cont, acc }
_ -> { :halt, false }
false -> {:cont, acc}
_ -> {:halt, false}
end
end) |> elem(1)
end
def empty(trie()) do
trie()
end
def member?(trie(root: root), term) do
def member?(%HashSet{root: root}, term) do
do_member?(root, term, key_hash(term))
end
def put(trie(root: root, size: size), term) do
def put(%HashSet{root: root, size: size}, term) do
{root, counter} = do_put(root, term, key_hash(term))
trie(root: root, size: size + counter)
%HashSet{root: root, size: size + counter}
end
def delete(trie(root: root, size: size) = set, term) do
def delete(%HashSet{root: root, size: size} = set, term) do
case do_delete(root, term, key_hash(term)) do
{:ok, root} -> trie(root: root, size: size - 1)
{:ok, root} -> %HashSet{root: root, size: size - 1}
:error -> set
end
end
def reduce(trie(root: root), acc, fun) do
@doc false
def reduce(%HashSet{root: root}, acc, fun) do
do_reduce(root, acc, fun, @node_size, fn
{:suspend, acc} -> {:suspended, acc, &{ :done, elem(&1, 1) }}
{:suspend, acc} -> {:suspended, acc, &{:done, elem(&1, 1)}}
{:halt, acc} -> {:halted, acc}
{:cont, acc} -> {:done, acc}
end)
end
def size(trie(size: size)) do
def size(%HashSet{size: size}) do
size
end
## Set helpers
defp set_fold(trie(root: root), acc, fun) do
defp set_fold(%HashSet{root: root}, acc, fun) do
do_fold(root, acc, fun, @node_size)
end
@@ -278,6 +247,20 @@ end
defimpl Enumerable, for: HashSet do
def reduce(set, acc, fun), do: HashSet.reduce(set, acc, fun)
def member?(set, v), do: { :ok, HashSet.member?(set, v) }
def count(set), do: { :ok, HashSet.size(set) }
def member?(set, v), do: {:ok, HashSet.member?(set, v)}
def count(set), do: {:ok, HashSet.size(set)}
end
defimpl Collectable, for: HashSet do
def empty(_dict) do
HashSet.new
end
def into(original) do
{original, fn
set, {:cont, x} -> HashSet.put(set, x)
set, :done -> set
_, :halt -> :ok
end}
end
end
+120 -130
View File
@@ -1,14 +1,6 @@
import Kernel, except: [inspect: 1]
import Inspect.Algebra
defrecord Inspect.Opts,
records: true,
binaries: :infer,
char_lists: :infer,
limit: 50,
pretty: false,
width: 80
defprotocol Inspect do
@moduledoc """
The `Inspect` protocol is responsible for converting any Elixir
@@ -49,38 +41,25 @@ defprotocol Inspect do
## Error handling
In case there is an error while your structure is being inspected,
Elixir will automatically fall back to tuple inspection for records.
Elixir will automatically fall back to a raw representation.
You can however access the underlying error by invoking the Inspect
implementation directly. For example, to test Inspect.HashSet above,
you just need to do:
you can invoke it as:
Inspect.HashSet.inspect(HashSet.new, Inspect.Opts.new)
"""
# Handle structs in Any
@fallback_to_any true
def inspect(thing, opts)
end
defimpl Inspect, for: Atom do
require Macro
@doc """
Represents the atom as an Elixir term. The atoms `false`, `true`
and `nil` are simply quoted. Modules are properly represented
as modules using the dot notation.
Notice that in Elixir, all operators can be represented using
literal atoms (`:+`, `:-`, etc).
## Examples
iex> inspect(:foo)
":foo"
iex> inspect(nil)
"nil"
iex> inspect(Foo.Bar)
"Foo.Bar"
"""
def inspect(atom, _opts) do
inspect(atom)
end
@@ -89,18 +68,21 @@ defimpl Inspect, for: Atom do
def inspect(true), do: "true"
def inspect(nil), do: "nil"
def inspect(:""), do: ":\"\""
def inspect(Elixir), do: "Elixir"
def inspect(atom) do
binary = atom_to_binary(atom)
cond do
valid_ref_identifier?(binary) ->
if only_elixir?(binary) do
binary
else
"Elixir." <> rest = binary
rest
end
valid_atom_identifier?(binary) ->
":" <> binary
valid_ref_identifier?(binary) ->
"Elixir." <> rest = binary
rest
atom in [:{}, :<<>>, :...] ->
atom in [:%{}, :{}, :<<>>, :..., :%] ->
":" <> binary
atom in Macro.binary_ops or atom in Macro.unary_ops ->
":" <> binary
@@ -109,6 +91,10 @@ defimpl Inspect, for: Atom do
end
end
defp only_elixir?("Elixir." <> rest), do: only_elixir?(rest)
defp only_elixir?("Elixir"), do: true
defp only_elixir?(_), do: false
# Detect if atom is an atom alias (Elixir.Foo.Bar.Baz)
defp valid_ref_identifier?("Elixir" <> rest) do
@@ -154,22 +140,7 @@ defimpl Inspect, for: Atom do
end
defimpl Inspect, for: BitString do
@doc %S"""
Represents a string as itself escaping all necessary
characters. Binaries that contain non-printable characters
are printed using the bitstring syntax.
## Examples
iex> inspect("bar")
"\"bar\""
iex> inspect("f\"oo")
"\"f\\\"oo\""
iex> inspect(<<0,1,2>>)
"<<0, 1, 2>>"
"""
def inspect(thing, Inspect.Opts[binaries: bins] = opts) when is_binary(thing) 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, ?" >>
else
@@ -192,7 +163,7 @@ defimpl Inspect, for: BitString do
escape(t, char, << binary :: binary, ?\\, char >>)
end
defp escape(<<?#, ?{, t :: binary>>, char, binary) do
escape(t, char, << binary :: binary, ?\\, ?#, ?{ >>)
escape(t, char, << binary :: binary, ?\\, ?#, ?{>>)
end
defp escape(<<?\a, t :: binary>>, char, binary) do
escape(t, char, << binary :: binary, ?\\, ?a >>)
@@ -249,7 +220,7 @@ defimpl Inspect, for: BitString do
## Bitstrings
defp inspect_bitstring(bitstring, Inspect.Opts[] = opts) do
defp inspect_bitstring(bitstring, opts) do
each_bit(bitstring, opts.limit, "<<") <> ">>"
end
@@ -280,7 +251,7 @@ defimpl Inspect, for: BitString do
end
defimpl Inspect, for: List do
@doc %S"""
@doc ~S"""
Represents a list, checking if it can be printed or not.
If so, a single-quoted representation is returned,
otherwise the brackets syntax is used. Keywords are
@@ -290,8 +261,10 @@ defimpl Inspect, for: List do
iex> inspect('bar')
"'bar'"
iex> inspect([0|'bar'])
"[0, 98, 97, 114]"
iex> inspect([:foo,:bar])
"[:foo, :bar]"
@@ -299,10 +272,10 @@ defimpl Inspect, for: List do
def inspect([], _opts), do: "[]"
def inspect(thing, Inspect.Opts[char_lists: lists] = opts) do
def inspect(thing, %Inspect.Opts{char_lists: lists} = opts) do
cond do
lists == :as_char_lists or (lists == :infer and :io_lib.printable_list(thing)) ->
<< ?', Inspect.BitString.escape(String.from_char_list!(thing), ?') :: binary, ?' >>
lists == :as_char_lists or (lists == :infer and printable?(thing)) ->
<< ?', Inspect.BitString.escape(String.from_char_data!(thing), ?') :: binary, ?' >>
keyword?(thing) ->
surround_many("[", thing, "]", opts.limit, &keyword(&1, opts))
true ->
@@ -310,13 +283,25 @@ defimpl Inspect, for: List do
end
end
defp keyword({key, value}, opts) do
def keyword({key, value}, opts) do
concat(
key_to_binary(key) <> ": ",
to_doc(value, opts)
)
end
def keyword?([{key, _value} | rest]) when is_atom(key) do
case atom_to_list(key) do
'Elixir.' ++ _ -> false
_ -> keyword?(rest)
end
end
def keyword?([]), do: true
def keyword?(_other), do: false
## Private
defp key_to_binary(key) do
case Inspect.Atom.inspect(key) do
":" <> right -> right
@@ -324,34 +309,23 @@ defimpl Inspect, for: List do
end
end
defp keyword?([{ key, _value } | rest]) when is_atom(key) do
case atom_to_list(key) do
'Elixir.' ++ _ -> false
_ -> keyword?(rest)
end
end
defp keyword?([]), do: true
defp keyword?(_other), do: false
defp printable?([c|cs]) when is_integer(c) and c in 32..126, do: printable?(cs)
defp printable?([?\n|cs]), do: printable?(cs)
defp printable?([?\r|cs]), do: printable?(cs)
defp printable?([?\t|cs]), do: printable?(cs)
defp printable?([?\v|cs]), do: printable?(cs)
defp printable?([?\b|cs]), do: printable?(cs)
defp printable?([?\f|cs]), do: printable?(cs)
defp printable?([?\e|cs]), do: printable?(cs)
defp printable?([?\a|cs]), do: printable?(cs)
defp printable?([]), do: true
defp printable?(_), do: false
end
defimpl Inspect, for: Tuple do
@doc """
Represents tuples. If the tuple represents a record,
it shows it nicely formatted using the access syntax.
## Examples
iex> inspect({1, 2, 3})
"{1, 2, 3}"
iex> inspect(ArgumentError.new)
"ArgumentError[message: \\\"argument error\\\"]"
"""
def inspect({}, _opts), do: "{}"
def inspect(tuple, Inspect.Opts[] = opts) do
def inspect(tuple, opts) do
if opts.records do
record_inspect(tuple, opts)
else
@@ -361,7 +335,7 @@ defimpl Inspect, for: Tuple do
## Helpers
defp record_inspect(record, Inspect.Opts[] = opts) do
defp record_inspect(record, opts) do
[name|tail] = tuple_to_list(record)
if is_atom(name) && (fields = record_fields(name)) && (length(fields) == size(record) - 1) do
@@ -383,17 +357,17 @@ defimpl Inspect, for: Tuple do
end
end
defp surround_record(name, fields, tail, Inspect.Opts[] = opts) do
defp surround_record(name, fields, tail, opts) do
concat(
Inspect.Atom.inspect(name, opts),
surround_many("[", zip_fields(fields, tail), "]", opts.limit, &keyword(&1, opts))
)
end
defp zip_fields([{ key, _ }|tk], [value|tv]) do
defp zip_fields([{key, _}|tk], [value|tv]) do
case atom_to_binary(key) do
"_" <> _ -> zip_fields(tk, tv)
key -> [{ key, value }|zip_fields(tk, tv)]
key -> [{key, value}|zip_fields(tk, tv)]
end
end
@@ -401,61 +375,52 @@ defimpl Inspect, for: Tuple do
[]
end
defp keyword({ k, v }, opts) do
defp keyword({k, v}, opts) do
concat(k <> ": ", to_doc(v, opts))
end
end
defimpl Inspect, for: Map do
def inspect(map, opts) do
inspect(map, "", opts)
end
def inspect(map, name, opts) do
map = :maps.to_list(map)
surround_many("%" <> name <> "{", map, "}", opts.limit, traverse_fun(map, opts))
end
defp traverse_fun(list, opts) do
if Inspect.List.keyword?(list) do
&Inspect.List.keyword(&1, opts)
else
&to_map(&1, opts)
end
end
defp to_map({key, value}, opts) do
concat(
concat(to_doc(key, opts), " => "),
to_doc(value, opts)
)
end
end
defimpl Inspect, for: Integer do
@doc """
Represents the integer as a string.
## Examples
iex> inspect(1)
"1"
"""
def inspect(thing, _opts) do
integer_to_binary(thing)
end
end
defimpl Inspect, for: Float do
@doc """
Floats are represented using the shortened, correctly rounded string
that converts to float when read back with `binary_to_float/1`. This
is done via the Erlang implementation of _Printing Floating-Point
Numbers Quickly and Accurately_ in Proceedings of the SIGPLAN '96
Conference on Programming Language Design and Implementation.
## Examples
iex> inspect(1.0)
"1.0"
"""
def inspect(thing, _opts) do
iolist_to_binary(:io_lib_format.fwrite_g(thing))
iodata_to_binary(:io_lib_format.fwrite_g(thing))
end
end
defimpl Inspect, for: Regex do
@doc %S"""
Represents the Regex using the `%r""` syntax.
## Examples
iex> inspect(%r/foo/m)
"%r\"foo\"m"
"""
def inspect(regex, opts) when size(regex) == 5 do
concat ["%r", to_doc(Regex.source(regex), opts), Regex.opts(regex)]
end
def inspect(other, Inspect.Opts[] = opts) do
to_doc(other, opts.records(false))
def inspect(regex, opts) do
concat ["~r", to_doc(regex.source, opts), regex.opts]
end
end
@@ -481,37 +446,62 @@ defimpl Inspect, for: Function do
end
defp default_inspect(mod, fun_info) do
"#Function<#{uniq(fun_info)}/#{fun_info[:arity]} in #{Inspect.Atom.inspect(mod)}.#{extract_name(fun_info[:name])}>"
"#Function<#{uniq(fun_info)}/#{fun_info[:arity]} in " <>
"#{Inspect.Atom.inspect(mod)}#{extract_name(fun_info[:name])}>"
end
defp extract_name([]) do
""
end
defp extract_name(name) do
case :binary.split(atom_to_binary(name), "-", [:global]) do
["", name | _] -> name
_ -> name
name = atom_to_binary(name)
case :binary.split(name, "-", [:global]) do
["", name | _] -> "." <> name
_ -> "." <> name
end
end
defp uniq(fun_info) do
integer_to_binary(fun_info[:new_index]) <> "." <> integer_to_binary(fun_info[:uniq])
integer_to_binary(fun_info[:new_index]) <> "." <>
integer_to_binary(fun_info[:uniq])
end
end
defimpl Inspect, for: PID do
def inspect(pid, _opts) do
"#PID" <> iolist_to_binary(:erlang.pid_to_list(pid))
"#PID" <> iodata_to_binary(:erlang.pid_to_list(pid))
end
end
defimpl Inspect, for: Port do
def inspect(port, _opts) do
iolist_to_binary :erlang.port_to_list(port)
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" <> iolist_to_binary(rest)
"#Reference" <> iodata_to_binary(rest)
end
end
defimpl Inspect, for: Any do
def inspect(%{__struct__: struct} = map, opts) do
try do
struct.__struct__
rescue
_ -> Inspect.Map.inspect(map, opts)
else
dunder ->
if :maps.keys(dunder) == :maps.keys(map) do
Inspect.Map.inspect(:maps.remove(:__struct__, map),
Inspect.Atom.inspect(struct, opts), opts)
else
Inspect.Map.inspect(map, opts)
end
end
end
end
+106 -40
View File
@@ -1,5 +1,43 @@
defmodule Inspect.Opts do
@moduledoc """
Defines the Inspect.Opts used by the Inspect protocol.
The following fields are available:
* `:structs` - when false, structs are not formatted by the inspect protocol,
they are instead printed as maps, defaults to true;
* `:binaries` - when `:as_strings` all binaries will be printed as strings,
non-printable bytes will be escaped; when `:as_binaries` all
binaries will be printed in bit syntax; when the default
`:infer`, the binary will be printed as a string if it is
printable, otherwise in bit syntax;
* `:char_lists` - when `:as_char_lists` all lists will be printed as char lists,
non-printable elements will be escaped; when `:as_lists` all
lists will be printed as lists; when the default `:infer`, the
list will be printed as a char list if it is printable,
otherwise as list;
* `:limit` - limits the number of items that are printed for tuples, bitstrings,
and lists, does not apply to strings nor char lists, defaults to 50;
* `:pretty` - if set to true enables pretty printing, defaults to false;
* `:width` - defaults to the 80 characters;
"""
defstruct structs: true :: boolean,
binaries: :infer :: :infer | :as_binaries | :as_strings,
char_lists: :infer :: :infer | :as_lists | :as_char_lists,
limit: 50 :: pos_integer,
width: 80 :: pos_integer | :infinity,
pretty: false :: boolean,
records: true :: boolean
end
defmodule Inspect.Algebra do
@moduledoc %S"""
@moduledoc ~S"""
A set of functions for creating and manipulating algebra
documents, as described in ["Strictly Pretty" (2000) by Christian Lindig][0].
@@ -8,6 +46,7 @@ defmodule Inspect.Algebra do
iex> Inspect.Algebra.empty
:doc_nil
iex> "foo"
"foo"
@@ -73,14 +112,24 @@ defmodule Inspect.Algebra do
# Functional interface to `doc` records
@type t :: :doc_nil | :doc_line | doc_cons_t | doc_nest_t | doc_break_t | doc_group_t | binary
defrecordp :doc_cons, left: :doc_nil :: t, right: :doc_nil :: t
defrecordp :doc_nest, indent: 1 :: non_neg_integer, doc: :doc_nil :: t
defrecordp :doc_break, str: " " :: binary
defrecordp :doc_group, doc: :doc_nil :: t
@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
@typep doc_nest :: {:doc_nest, non_neg_integer, t}
Record.defrecordp :doc_nest, indent: 1, doc: :doc_nil
@typep doc_break :: {:doc_break, binary}
Record.defrecordp :doc_break, str: " "
@typep doc_group :: {:doc_group, t}
Record.defrecordp :doc_group, doc: :doc_nil
defmacrop is_doc(doc) do
if __CALLER__.in_guard? do
if Macro.Env.in_guard?(__CALLER__) do
do_is_doc(doc)
else
var = quote do: doc
@@ -105,26 +154,43 @@ defmodule Inspect.Algebra do
according to the inspect protocol.
"""
@spec to_doc(any, Inspect.Opts.t) :: t
def to_doc(arg, opts) when is_record(opts, Inspect.Opts) do
if is_tuple(arg) do
if elem(opts, Inspect.Opts.__record__(:index, :records)) do
try do
Inspect.inspect(arg, opts)
rescue
e ->
res = Inspect.Tuple.inspect(arg, opts)
IO.puts :stderr, "** (Inspect.Error) Got #{inspect e.__record__(:name)} with message " <>
"#{e.message} while inspecting #{pretty(res, opts.width)}"
res
end
else
Inspect.Tuple.inspect(arg, opts)
def to_doc(arg, %Inspect.Opts{} = opts) when is_record(arg) do
# IO.write :stderr, "inspect records is deprecated (just remove this code when protocols+records are removed)\n#{Exception.format_stacktrace}"
if opts.records do
try do
Inspect.inspect(arg, opts)
rescue
e ->
res = Inspect.Tuple.inspect(arg, opts)
raise ArgumentError, message:
"Got #{inspect e.__record__(:name)} with message " <>
"#{Exception.message(e)} while inspecting #{pretty(res, opts.width)}"
end
else
Inspect.inspect(arg, opts)
Inspect.Tuple.inspect(arg, opts)
end
end
def to_doc(%{__struct__: struct} = map, %Inspect.Opts{} = opts) when is_atom(struct) do
if opts.structs do
try do
Inspect.inspect(map, opts)
rescue
e ->
res = Inspect.Map.inspect(map, opts)
raise ArgumentError, message:
"Got #{inspect e.__record__(:name)} with message " <>
"\"#{Exception.message(e)}\" while inspecting #{pretty(res, opts.width)}"
end
else
Inspect.Map.inspect(map, opts)
end
end
def to_doc(arg, %Inspect.Opts{} = opts) do
Inspect.inspect(arg, opts)
end
@doc """
Returns `:doc_nil` which is a document entity used to represent
nothingness. Takes no arguments.
@@ -149,7 +215,7 @@ defmodule Inspect.Algebra do
"TastelessArtosis"
"""
@spec concat(t, t) :: doc_cons_t
@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)
end
@@ -157,7 +223,7 @@ defmodule Inspect.Algebra do
@doc """
Concatenates a list of documents.
"""
@spec concat([t]) :: doc_cons_t
@spec concat([t]) :: doc_cons
def concat(docs) do
folddoc(docs, &concat(&1, &2))
end
@@ -173,7 +239,7 @@ defmodule Inspect.Algebra do
" 6"
"""
@spec nest(t, non_neg_integer) :: doc_nest_t
@spec nest(t, non_neg_integer) :: doc_nest
def nest(x, 0) when is_doc(x) do
x
end
@@ -182,7 +248,7 @@ defmodule Inspect.Algebra do
doc_nest(indent: i, doc: x)
end
@doc %S"""
@doc ~S"""
Document entity representing a break. This break can
be rendered as a linebreak or as spaces, depending on the
`mode` of the chosen layout or the provided separator.
@@ -203,26 +269,26 @@ defmodule Inspect.Algebra do
"aaaaaaaaaaaaaaaaaaaa\nb"
"""
@spec break(binary) :: doc_break_t
@spec break(binary) :: doc_break
def break(s) when is_binary(s), do: doc_break(str: s)
@spec break() :: doc_break_t
@spec break() :: doc_break
def break(), do: doc_break(str: @break)
@doc """
Inserts a break between two docs. See `break/1` for more info.
"""
@spec glue(t, t) :: doc_cons_t
@spec glue(t, t) :: doc_cons
def glue(x, y), do: concat(x, concat(break, y))
@doc """
Inserts a break, passed as the second argument, between two docs,
the first and the third arguments.
"""
@spec glue(t, binary, t) :: doc_cons_t
@spec glue(t, binary, t) :: doc_cons
def glue(x, g, y) when is_binary(g), do: concat(x, concat(break(g), y))
@doc %S"""
@doc ~S"""
Returns a group containing the specified document.
## Examples
@@ -249,7 +315,7 @@ defmodule Inspect.Algebra do
"Hello,\nA B"
"""
@spec group(t) :: doc_group_t
@spec group(t) :: doc_group
def group(d) when is_doc(d) do
doc_group(doc: d)
end
@@ -264,10 +330,10 @@ defmodule Inspect.Algebra do
"Hughes Wadler"
"""
@spec space(t, t) :: doc_cons_t
@spec space(t, t) :: doc_cons
def space(x, y), do: concat(x, concat(" ", y))
@doc %S"""
@doc ~S"""
Inserts a mandatory linebreak between two document entities.
## Examples
@@ -277,7 +343,7 @@ defmodule Inspect.Algebra do
"Hughes\nWadler"
"""
@spec line(t, t) :: doc_cons_t
@spec line(t, t) :: doc_cons
def line(x, y), do: concat(x, concat(:doc_line, y))
@doc """
@@ -301,7 +367,7 @@ defmodule Inspect.Algebra do
# Elixir conveniences
@doc %S"""
@doc ~S"""
Surrounds a document with characters.
Puts the document between left and right enclosing and nesting it.
@@ -320,7 +386,7 @@ defmodule Inspect.Algebra do
group concat left, concat(nest(doc, @nesting), right)
end
@doc %S"""
@doc ~S"""
Maps and glues a collection of items together using the given separator
and surrounds them. A limit can be passed which, once reached, stops
gluing and outputs "..." instead.
@@ -398,7 +464,7 @@ defmodule Inspect.Algebra do
@typep mode :: :flat | :break
@doc false
@spec fits?(integer, [{ integer, mode, t }]) :: boolean
@spec fits?(integer, [{integer, mode, t}]) :: boolean
def fits?(w, _) when w < 0, do: false
def fits?(_, []), do: true
def fits?(_, [{_, _, :doc_line} | _]), do: true
@@ -411,7 +477,7 @@ defmodule Inspect.Algebra do
def fits?(_, [{_, :break, doc_break(str: _)} | _]), do: true
@doc false
@spec format(integer | :infinity, integer, [{ integer, mode, t }]) :: [binary]
@spec format(integer | :infinity, integer, [{integer, mode, t}]) :: [binary]
def format(_, _, []), do: []
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)
@@ -436,6 +502,6 @@ defmodule Inspect.Algebra do
@doc false
@spec render([binary]) :: binary
def render(sdoc) do
iolist_to_binary sdoc
iodata_to_binary sdoc
end
end
+8 -6
View File
@@ -26,26 +26,28 @@ defmodule Integer do
end
@doc """
Converts a binary to an integer.
If successful, returns a tuple of the form `{ integer, remainder_of_binary }`.
Converts a binary to an integer.
If successful, returns a tuple of the form `{integer, remainder_of_binary}`.
Otherwise `:error`.
## Examples
iex> Integer.parse("34")
{34,""}
iex> Integer.parse("34.5")
{34,".5"}
iex> Integer.parse("three")
:error
"""
@spec parse(binary) :: { integer, binary } | :error
@spec parse(binary) :: {integer, binary} | :error
def parse(<< ?-, bin :: binary >>) do
case do_parse(bin) do
:error -> :error
{ number, remainder } -> { -number, remainder }
{number, remainder} -> {-number, remainder}
end
end
@@ -65,6 +67,6 @@ defmodule Integer do
end
defp do_parse(bitstring, acc) do
{ acc, bitstring }
{acc, bitstring}
end
end
+132 -59
View File
@@ -1,11 +1,66 @@
defexception IO.StreamError, [:reason, :message] do
def exception(opts) do
reason = opts[:reason]
formatted = iolist_to_binary(:file.format_error(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.
@@ -15,22 +70,35 @@ defmodule IO do
For convenience, Elixir provides `:stdio` and `:stderr` as
shortcuts to Erlang's `:standard_io` and `:standard_error`.
The majority of the functions expect data encoded in UTF-8
and will do a conversion to string, via the `String.Chars`
protocol (as shown in typespecs).
The majority of the functions expect char data, i.e. strings or
lists of characters and strings. In case another type is given,
it will do a conversion to string via the `String.Chars` protocol
(as shown in typespecs).
The functions starting with `bin*` expects iodata as arguments,
i.e. iolists or binaries with no particular encoding.
The functions starting with `bin*` expects iodata as argument,
i.e. binaries or lists of bytes and binaries.
## IO devices
An IO device may be an atom or a pid. In case it is an atom,
the atom must be the name of a registered process. However,
there are three exceptions for this rule:
* `:standard_io` - when the `:standard_io` atom is given,
it is treated as a shortcut for `Process.group_leader`
* `:stdio` - is a shortcut for `:standard_io`
* `:stderr` - is a shortcut for `:standard_error`
"""
@type device :: atom | pid
@type chardata :: char_list | String.Chars.t
@type nodata :: { :error, term } | :eof
@type device :: atom | pid
@type nodata :: {:error, term} | :eof
import :erlang, only: [group_leader: 0]
defmacrop is_iolist(data) do
defmacrop is_iodata(data) do
quote do
is_list(unquote(data)) or is_binary(unquote(data))
end
@@ -48,7 +116,7 @@ defmodule IO do
for instance `{:error, :estale}` if reading from an
NFS file system.
"""
@spec read(device, :line | non_neg_integer) :: chardata | nodata
@spec read(device, :line | non_neg_integer) :: char_data | nodata
def read(device \\ group_leader, chars_or_line)
def read(device, :line) do
@@ -76,24 +144,22 @@ defmodule IO do
def binread(device, :line) do
case :file.read_line(map_dev(device)) do
{ :ok, data } -> data
{:ok, data} -> data
other -> other
end
end
def binread(device, count) when count >= 0 do
case :file.read(map_dev(device), count) do
{ :ok, data } -> data
{:ok, data} -> data
other -> other
end
end
@doc """
Writes the given argument to the given device.
By default the device is the standard output.
The argument is expected to be a chardata (i.e.
a char list or an unicode binary).
By default the device is the standard output.
It returns `:ok` if it succeeds.
## Examples
@@ -105,9 +171,9 @@ defmodule IO do
#=> "error"
"""
@spec write(device, chardata) :: :ok
@spec write(device, char_data | String.Chars.t) :: :ok
def write(device \\ group_leader(), item) do
:io.put_chars map_dev(device), to_chardata(item)
:io.put_chars map_dev(device), to_char_data(item)
end
@doc """
@@ -116,29 +182,32 @@ defmodule IO do
Check `write/2` for more information.
"""
@spec binwrite(device, iodata) :: :ok | { :error, term }
def binwrite(device \\ group_leader(), item) when is_iolist(item) do
@spec binwrite(device, iodata) :: :ok | {:error, term}
def binwrite(device \\ group_leader(), item) when is_iodata(item) do
:file.write map_dev(device), item
end
@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 chardata.
to be a char_data.
"""
@spec puts(device, chardata) :: :ok
@spec puts(device, char_data | String.Chars.t) :: :ok
def puts(device \\ group_leader(), item) do
erl_dev = map_dev(device)
:io.put_chars erl_dev, [to_chardata(item), ?\n]
:io.put_chars erl_dev, [to_char_data(item), ?\n]
end
@doc """
Inspects and writes the given argument to the device
followed by a newline. A set of options can be given.
Inspects and writes the given argument to the device.
It sets by default pretty printing to true and the
width to be the width of the device, with a minimum
of 80 characters.
It sets by default pretty printing to true and returns
the item itself.
Note this function does not use the IO device width
because some IO devices does not implement the
appropriate functions. Setting the width must be done
explicitly by passing the `:width` option.
## Examples
@@ -156,14 +225,6 @@ defmodule IO do
@spec inspect(device, term, Keyword.t) :: term
def inspect(device, item, opts) when is_list(opts) do
opts = Keyword.put_new(opts, :pretty, true)
unless Keyword.get(opts, :width) do
opts = case :io.columns(device) do
{ :ok, width } -> Keyword.put(opts, :width, width)
{ :error, _ } -> opts
end
end
puts device, Kernel.inspect(item, opts)
item
end
@@ -183,8 +244,8 @@ defmodule IO do
for instance `{:error, :estale}` if reading from an
NFS file system.
"""
@spec getn(chardata, pos_integer) :: chardata | nodata
@spec getn(device, chardata) :: chardata | nodata
@spec getn(char_data | String.Chars.t, pos_integer) :: char_data | nodata
@spec getn(device, char_data | String.Chars.t) :: char_data | nodata
def getn(prompt, count \\ 1)
def getn(prompt, count) when is_integer(count) do
@@ -201,9 +262,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, chardata, pos_integer) :: chardata | nodata
@spec getn(device, char_data | String.Chars.t, pos_integer) :: char_data | nodata
def getn(device, prompt, count) do
:io.get_chars(map_dev(device), to_chardata(prompt), count)
:io.get_chars(map_dev(device), to_char_data(prompt), count)
end
@doc """
@@ -218,15 +279,20 @@ defmodule IO do
for instance `{:error, :estale}` if reading from an
NFS file system.
"""
@spec gets(device, chardata) :: chardata | nodata
@spec gets(device, char_data | String.Chars.t) :: char_data | nodata
def gets(device \\ group_leader(), prompt) do
:io.get_line(map_dev(device), to_chardata(prompt))
:io.get_line(map_dev(device), to_char_data(prompt))
end
@doc """
Converts the io device into a Stream. The device is
iterated line by line if `:line` is given or by a given
number of codepoints.
Converts the io device into a `IO.Stream`.
An `IO.Stream` implements both `Enumerable` and
`Collectable`, allowing it to be used for both read
and write.
The device is iterated line by line if `:line` is given or
by a given number of codepoints.
This reads the IO as utf-8. Check out
`IO.binstream/2` to handle the IO as a raw binary.
@@ -244,51 +310,58 @@ defmodule IO do
"""
@spec stream(device, :line | pos_integer) :: Enumerable.t
def stream(device, line_or_codepoints) do
Stream.unfold(map_dev(device), &do_stream(&1, line_or_codepoints))
%IO.Stream{device: map_dev(device), raw: false, line_or_bytes: line_or_codepoints}
end
@doc """
Converts the io device into a Stream. The device is
iterated line by line or by a number of bytes. This
reads the IO as a raw binary.
Converts the IO device into a `IO.Stream`.
An `IO.Stream` implements both `Enumerable` and
`Collectable`, allowing it to be used for both read
and write.
The device is iterated line by line or by a number of bytes.
This reads the IO device as a raw binary.
Note that an IO stream has side effects and every time
you go over the stream you may get different results.
"""
@spec binstream(device, :line | pos_integer) :: Enumerable.t
def binstream(device, line_or_bytes) do
Stream.unfold(map_dev(device), &do_binstream(&1, line_or_bytes))
%IO.Stream{device: map_dev(device), raw: true, line_or_bytes: line_or_bytes}
end
@doc false
def do_stream(device, what) do
def each_stream(device, what) do
case read(device, what) do
:eof ->
nil
{ :error, reason } ->
{:error, reason} ->
raise IO.StreamError, reason: reason
data ->
{ data, device }
{data, device}
end
end
@doc false
def do_binstream(device, what) do
def each_binstream(device, what) do
case binread(device, what) do
:eof ->
nil
{ :error, reason } ->
{:error, reason} ->
raise IO.StreamError, reason: reason
data ->
{ data, device }
{data, device}
end
end
@compile {:inline, map_dev: 1, to_char_data: 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), do: other
defp map_dev(other) when is_atom(other) or is_pid(other) or is_tuple(other), do: other
defp to_chardata(list) when is_list(list), do: list
defp to_chardata(other), do: to_string(other)
defp to_char_data(list) when is_list(list), do: list
defp to_char_data(other), do: to_string(other)
end
+50 -44
View File
@@ -7,8 +7,8 @@ defmodule IO.ANSI.Sequence do
"\e[#{unquote(code)}#{unquote(terminator)}"
end
defp escape_sequence(<< unquote(atom_to_binary(name)), rest :: binary >>) do
{ "\e[#{unquote(code)}#{unquote(terminator)}", rest }
defp escape_sequence(unquote(atom_to_list(name))) do
unquote(name)()
end
end
end
@@ -17,7 +17,7 @@ end
defmodule IO.ANSI do
@moduledoc """
Functionality to render ANSI escape sequences
(http:\\en.wikipedia.org/wiki/ANSI_escape_code) — characters embedded
(http://en.wikipedia.org/wiki/ANSI_escape_code) — characters embedded
in text used to control formatting, color, and other output options
on video text terminals.
"""
@@ -28,12 +28,15 @@ defmodule IO.ANSI do
Checks whether the default I/O device is a terminal or a file.
Used to identify whether printing ANSI escape sequences will likely
be displayed as intended.
be displayed as intended. This is checked by sending a message to
the group leader. In case the group leader does not support the message,
it will likely lead to a timeout (and a slow down on execution time).
"""
@spec terminal? :: boolean
@spec terminal?(:io.device) :: boolean
def terminal?(device \\ :erlang.group_leader) do
match?({:ok, _}, :io.columns(device))
!match?({:win32, _}, :os.type()) and
match?({:ok, _}, :io.columns(device))
end
@doc "Resets all attributes"
@@ -72,7 +75,7 @@ defmodule IO.ANSI do
@doc "Sets primary (default) font"
defsequence :primary_font, 10
lc font_n inlist [1, 2, 3, 4, 5, 6, 7, 8, 9] do
for font_n <- [1, 2, 3, 4, 5, 6, 7, 8, 9] do
@doc "Sets alternative font #{font_n}"
defsequence :"font_#{font_n}", font_n + 10
end
@@ -92,7 +95,7 @@ defmodule IO.ANSI do
colors = [:black, :red, :green, :yellow, :blue, :magenta, :cyan, :white]
colors = Enum.zip(0..(length(colors)-1), colors)
lc { code, color } inlist colors do
for {code, color} <- colors do
@doc "Sets foreground color to #{color}"
defsequence color, code + 30
@@ -127,18 +130,11 @@ defmodule IO.ANSI do
@doc "Clear screen"
defsequence :clear, "2", "J"
# Catch spaces between codes
defp escape_sequence(<< ?\s, rest :: binary >>) do
escape_sequence(rest)
end
defp escape_sequence(other) do
[spec|_] = String.split(other, %r/(,|\})/)
raise ArgumentError, message: "invalid ANSI sequence specification: #{spec}"
raise ArgumentError, message: "invalid ANSI sequence specification: #{other}"
end
@doc %S"""
@doc ~S"""
Escapes a string by converting named ANSI sequences into actual ANSI codes.
The format for referring to sequences is `%{red}` and `%{red,bright}` (for
@@ -160,15 +156,15 @@ defmodule IO.ANSI do
"""
@spec escape(String.t, emit :: boolean) :: String.t
def escape(string, emit \\ terminal?) do
{rendered, emitted} = do_escape(string, false, emit, false, [])
if emitted and emit do
{rendered, emitted} = do_escape(string, emit, false, nil, [])
if emitted do
rendered <> reset
else
rendered
end
end
@doc %S"""
@doc ~S"""
Escapes a string by converting named ANSI sequences into actual ANSI codes.
The format for referring to sequences is `%{red}` and `%{red,bright}` (for
@@ -183,40 +179,50 @@ defmodule IO.ANSI do
iex> IO.ANSI.escape_fragment("Hello %{red,bright,green}yes", true)
"Hello \e[31m\e[1m\e[32myes"
iex> IO.ANSI.escape_fragment("%{reset}bye", true)
"\e[0mbye"
"""
@spec escape_fragment(String.t, emit :: boolean) :: String.t
def escape_fragment(string, emit \\ terminal?) do
{rendered, _emitted} = do_escape(string, false, emit, false, [])
rendered
{escaped, _emitted} = do_escape(string, emit, false, nil, [])
escaped
end
defp do_escape(<< ?%, ?{, rest :: binary >>, false, emit, _emitted, acc) do
do_escape_sequence(rest, emit, acc)
end
defp do_escape(<< ?,, rest :: binary >>, true, emit, _emitted, acc) do
do_escape_sequence(rest, emit, acc)
end
defp do_escape(<< ?\s, rest :: binary >>, true, emit, emitted, acc) do
do_escape(rest, true, emit, emitted, acc)
end
defp do_escape(<< ?}, rest :: binary >>, true, emit, emitted, acc) do
do_escape(rest, false, emit, emitted, acc)
end
defp do_escape(<< x :: [binary, size(1)], rest :: binary>>, false, emit, emitted, acc) do
do_escape(rest, false, emit, emitted, [x|acc])
end
defp do_escape("", false, _emit, emitted, acc) do
{iolist_to_binary(Enum.reverse(acc)), emitted}
end
defp do_escape(<<?}, t :: binary>>, emit, emitted, buffer, acc) when is_list(buffer) do
sequences =
buffer
|> Enum.reverse()
|> :string.tokens(',')
|> Enum.map(&(&1 |> :string.strip |> escape_sequence))
|> Enum.reverse()
defp do_escape_sequence(rest, emit, acc) do
{code, rest} = escape_sequence(rest)
if emit do
acc = [code|acc]
if emit and sequences != [] do
do_escape(t, emit, true, nil, sequences ++ acc)
else
do_escape(t, emit, emitted, nil, acc)
end
do_escape(rest, true, emit, true, acc)
end
defp do_escape(<<h, t :: binary>>, emit, emitted, buffer, acc) when is_list(buffer) do
do_escape(t, emit, emitted, [h|buffer], acc)
end
defp do_escape(<<>>, _emit, _emitted, buffer, _acc) when is_list(buffer) do
buffer = iodata_to_binary Enum.reverse(buffer)
raise ArgumentError, message: "missing } for escape fragment #{buffer}"
end
defp do_escape(<<?%, ?{, t :: binary>>, emit, emitted, nil, acc) do
do_escape(t, emit, emitted, [], acc)
end
defp do_escape(<<h, t :: binary>>, emit, emitted, nil, acc) do
do_escape(t, emit, emitted, nil, [h|acc])
end
defp do_escape(<<>>, _emit, emitted, nil, acc) do
{iodata_to_binary(Enum.reverse(acc)), emitted}
end
end
+137 -138
View File
@@ -3,33 +3,10 @@ defmodule IO.ANSI.Docs do
@bullets [?*, ?-, ?+]
@default_colors [ enabled: true,
doc_code: "cyan,bright",
doc_inline_code: "cyan",
doc_headings: "yellow,bright",
doc_title: "reverse,yellow,bright",
doc_bold: "bright",
doc_underline: "underline" ]
@doc """
Prints the head of the documentation (i.e. the function signature).
The default options used by this module.
See `print/3` for docs on the supported options.
"""
def print_heading(heading, colors \\ @default_colors) do
IO.puts IO.ANSI.reset
width = column_width()
padding = div(width + String.length(heading), 2)
heading = heading |> String.rjust(padding) |> String.ljust(width)
write(:doc_title, heading, colors)
end
@doc """
Prints the documentation body.
In addition to the priting string, takes a truth value for whether to use ANSI
escape codes, and a keyword list for the printing color settings. Supported
keys for the color settings are:
The supported values are:
* `:enabled` - toggles coloring on and off (true)
* `:doc_code` - code blocks (cyan, bright)
@@ -38,53 +15,82 @@ defmodule IO.ANSI.Docs do
* `:doc_title` - top level heading (reverse, yellow, bright)
* `:doc_bold` - bold text (bright)
* `:doc_underline` - underlined text (underline)
* `:width` - the width to format the text (80)
Values for the color settings are strings with comma-separated attributes.
Supported attributes are:
* Colors: `black red green yellow blue magenta cyan white`
* Intensity: `normal bright`
* Decoration: `underline reverse`
Values for the color settings are strings with
comma-separated ANSI values.
"""
def print(doc, colors \\ @default_colors) do
def default_options do
[enabled: true,
doc_code: "cyan,bright",
doc_inline_code: "cyan",
doc_headings: "yellow,bright",
doc_title: "reverse,yellow,bright",
doc_bold: "bright",
doc_underline: "underline",
width: 80]
end
@doc """
Prints the head of the documentation (i.e. the function signature).
See `default_options/0` for docs on the supported options.
"""
def print_heading(heading, options \\ []) do
IO.puts IO.ANSI.reset
options = Keyword.merge(default_options, options)
width = options[:width]
padding = div(width + String.length(heading), 2)
heading = heading |> String.rjust(padding) |> String.ljust(width)
write(:doc_title, heading, options)
end
@doc """
Prints the documentation body.
In addition to the priting string, takes a set of options
defined in `default_options/1`.
"""
def print(doc, options \\ []) do
options = Keyword.merge(default_options, options)
doc
|> String.split(["\r\n","\n"], trim: false)
|> Enum.map(&String.rstrip/1)
|> process("", colors)
|> process("", options)
end
defp process([], _indent, _colors), do: nil
defp process([], _indent, _options), do: nil
defp process(["# " <> heading | rest], _indent, colors) do
write_h1(String.strip(heading), colors)
process(rest, "", colors)
defp process(["# " <> heading | rest], _indent, options) do
write_h1(String.strip(heading), options)
process(rest, "", options)
end
defp process(["## " <> heading | rest], _indent, colors) do
write_h2(String.strip(heading), colors)
process(rest, "", colors)
defp process(["## " <> heading | rest], _indent, options) do
write_h2(String.strip(heading), options)
process(rest, "", options)
end
defp process(["### " <> heading | rest], indent, colors) do
write_h3(String.strip(heading), indent, colors)
process(rest, indent, colors)
defp process(["### " <> heading | rest], indent, options) do
write_h3(String.strip(heading), indent, options)
process(rest, indent, options)
end
defp process(["" | rest], indent, colors) do
process(rest, indent, colors)
defp process(["" | rest], indent, options) do
process(rest, indent, options)
end
defp process([" " <> line | rest], indent, colors) do
process_code(rest, [line], indent, colors)
defp process([" " <> line | rest], indent, options) do
process_code(rest, [line], indent, options)
end
defp process([line | rest], indent, colors) do
{ stripped, count } = strip_spaces(line, 0)
defp process([line | rest], indent, options) do
{stripped, count} = strip_spaces(line, 0)
case stripped do
<<bullet, ?\s, item :: binary >> when bullet in @bullets ->
process_list(item, rest, count, indent, colors)
process_list(item, rest, count, indent, options)
_ ->
process_text(rest, [line], indent, false, colors)
process_text(rest, [line], indent, false, options)
end
end
@@ -93,32 +99,32 @@ defmodule IO.ANSI.Docs do
end
defp strip_spaces(rest, acc) do
{ rest, acc }
{rest, acc}
end
## Headings
defp write_h1(heading, colors) do
write_h2(String.upcase(heading), colors)
defp write_h1(heading, options) do
write_h2(String.upcase(heading), options)
end
defp write_h2(heading, colors) do
write(:doc_headings, heading, colors)
defp write_h2(heading, options) do
write(:doc_headings, heading, options)
end
defp write_h3(heading, indent, colors) do
defp write_h3(heading, indent, options) do
IO.write(indent)
write(:doc_headings, heading, colors)
write(:doc_headings, heading, options)
end
## Lists
defp process_list(line, rest, count, indent, colors) do
defp process_list(line, rest, count, indent, options) do
IO.write indent <> "• "
{ contents, rest, done } = process_list_next(rest, count, false, [])
process_text(contents, [line], indent <> " ", true, colors)
{contents, rest, done} = process_list_next(rest, count, false, [])
process_text(contents, [line], indent <> " ", true, options)
if done, do: IO.puts(IO.ANSI.reset)
process(rest, indent, colors)
process(rest, indent, options)
end
# Process the thing after a list item entry. It can be either:
@@ -129,13 +135,13 @@ defmodule IO.ANSI.Docs do
#
defp process_list_next([" " <> _ = line | rest], count, _done, acc) do
case list_next(line, count) do
:done -> { Enum.reverse(acc), [line|rest], false }
:done -> {Enum.reverse(acc), [line|rest], false}
chopped -> process_list_next(rest, count, false, [chopped|acc])
end
end
defp process_list_next([<<bullet, ?\s, _ :: binary>> | _] = rest, _count, _done, acc) when bullet in @bullets do
{ Enum.reverse(acc), rest, false }
{Enum.reverse(acc), rest, false}
end
defp process_list_next(["" | rest], count, _done, acc) do
@@ -143,7 +149,7 @@ defmodule IO.ANSI.Docs do
end
defp process_list_next(rest, _count, done, acc) do
{ Enum.reverse(acc), rest, done }
{Enum.reverse(acc), rest, done}
end
defp list_next(<<bullet, ?\s, _ :: binary>>, 0) when bullet in @bullets, do: :done
@@ -156,69 +162,69 @@ defmodule IO.ANSI.Docs do
## Text (paragraphs / lists)
defp process_text(doc=["" | _], para, indent, from_list, colors) do
write_text(Enum.reverse(para), indent, from_list, colors)
process(doc, indent, colors)
defp process_text(doc=["" | _], para, indent, from_list, options) do
write_text(Enum.reverse(para), indent, from_list, options)
process(doc, indent, options)
end
defp process_text([], para, indent, from_list, colors) do
write_text(Enum.reverse(para), indent, from_list, colors)
defp process_text([], para, indent, from_list, options) do
write_text(Enum.reverse(para), indent, from_list, options)
end
defp process_text([line | rest], para, indent, true, colors) do
{ stripped, count } = strip_spaces(line, 0)
defp process_text([line | rest], para, indent, true, options) do
{stripped, count} = strip_spaces(line, 0)
case stripped do
<<bullet, ?\s, item :: binary>> when bullet in @bullets ->
write_text(Enum.reverse(para), indent, true, colors)
process_list(item, rest, count, indent, colors)
write_text(Enum.reverse(para), indent, true, options)
process_list(item, rest, count, indent, options)
_ ->
process_text(rest, [line | para], indent, true, colors)
process_text(rest, [line | para], indent, true, options)
end
end
defp process_text([line | rest], para, indent, from_list, colors) do
process_text(rest, [line | para], indent, from_list, colors)
defp process_text([line | rest], para, indent, from_list, options) do
process_text(rest, [line | para], indent, from_list, options)
end
defp write_text(lines, indent, from_list, colors) do
defp write_text(lines, indent, from_list, options) do
lines
|> Enum.join(" ")
|> handle_links
|> handle_inline(nil, [], [], colors)
|> String.split(%r{\s})
|> write_with_wrap(column_width() - size(indent), indent, from_list)
|> handle_inline(nil, [], [], options)
|> String.split(~r{\s})
|> write_with_wrap(options[:width] - size(indent), indent, from_list)
unless from_list, do: IO.puts(IO.ANSI.reset)
end
## Code blocks
defp process_code([], code, indent, colors) do
write_code(code, indent, colors)
defp process_code([], code, indent, options) do
write_code(code, indent, options)
end
# Blank line between code blocks
defp process_code([ "", " " <> line | rest ], code, indent, colors) do
process_code(rest, [line, "" | code], indent, colors)
defp process_code([ "", " " <> line | rest ], code, indent, options) do
process_code(rest, [line, "" | code], indent, options)
end
defp process_code([ " " <> line | rest ], code, indent, colors) do
process_code(rest, [line|code], indent, colors)
defp process_code([ " " <> line | rest ], code, indent, options) do
process_code(rest, [line|code], indent, options)
end
defp process_code(rest, code, indent, colors) do
write_code(code, indent, colors)
process(rest, indent, colors)
defp process_code(rest, code, indent, options) do
write_code(code, indent, options)
process(rest, indent, options)
end
defp write_code(code, indent, colors) do
write(:doc_code, "#{indent}┃ #{Enum.join(Enum.reverse(code), "\n#{indent}┃ ")}", colors)
defp write_code(code, indent, options) do
write(:doc_code, "#{indent}┃ #{Enum.join(Enum.reverse(code), "\n#{indent}┃ ")}", options)
end
## Helpers
defp write(style, string, colors) do
IO.puts color(style, colors) <> string <> IO.ANSI.reset
defp write(style, string, options) do
IO.puts color(style, options) <> string <> IO.ANSI.reset
IO.puts IO.ANSI.reset
end
@@ -227,7 +233,7 @@ defmodule IO.ANSI.Docs do
end
defp write_with_wrap(words, available, indent, first) do
{ words, rest } = take_words(words, available, [])
{words, rest} = take_words(words, available, [])
IO.puts (if first, do: "", else: indent) <> Enum.join(words, " ")
write_with_wrap(rest, available, indent, false)
end
@@ -242,16 +248,16 @@ defmodule IO.ANSI.Docs do
# No space but we got no words
acc == [] ->
{ [word], words }
{[word], words}
# Otherwise
true ->
{ Enum.reverse(acc), [word|words] }
{Enum.reverse(acc), [word|words]}
end
end
defp take_words([], _available, acc) do
{ Enum.reverse(acc), [] }
{Enum.reverse(acc), []}
end
defp length_without_escape(<< ?\e, ?[, _, _, ?m, rest :: binary >>, count) do
@@ -264,7 +270,7 @@ defmodule IO.ANSI.Docs do
defp length_without_escape(rest, count) do
case String.next_grapheme(rest) do
{ _, rest } -> length_without_escape(rest, count + 1)
{_, rest} -> length_without_escape(rest, count + 1)
nil -> count
end
end
@@ -276,16 +282,16 @@ defmodule IO.ANSI.Docs do
end
defp escape_underlines_in_link(text) do
case Regex.match?(%r{.*(https?\S*)}, text) do
case Regex.match?(~r{.*(https?\S*)}, text) do
true ->
Regex.replace(%r{_}, text, "\\\\_")
Regex.replace(~r{_}, text, "\\\\_")
_ ->
text
end
end
defp remove_square_brackets_in_link(text) do
Regex.replace(%r{\[(.*?)\]\((.*?)\)}, text, "\\1 (\\2)")
Regex.replace(~r{\[(.*?)\]\((.*?)\)}, text, "\\1 (\\2)")
end
# Single inline quotes.
@@ -295,66 +301,66 @@ defmodule IO.ANSI.Docs do
@spaced [?_, ?*]
# Clauses for handling spaces
defp handle_inline(<<?*, ?*, ?\s, rest :: binary>>, nil, buffer, acc, colors) do
handle_inline(rest, nil, [?\s, ?*, ?*|buffer], acc, colors)
defp handle_inline(<<?*, ?*, ?\s, rest :: binary>>, nil, buffer, acc, options) do
handle_inline(rest, nil, [?\s, ?*, ?*|buffer], acc, options)
end
defp handle_inline(<<mark, ?\s, rest :: binary>>, nil, buffer, acc, colors) when mark in @spaced do
handle_inline(rest, nil, [?\s, mark|buffer], acc, colors)
defp handle_inline(<<mark, ?\s, rest :: binary>>, nil, buffer, acc, options) when mark in @spaced do
handle_inline(rest, nil, [?\s, mark|buffer], acc, options)
end
defp handle_inline(<<?\s, ?*, ?*, rest :: binary>>, limit, buffer, acc, colors) do
handle_inline(rest, limit, [?*, ?*, ?\s|buffer], acc, colors)
defp handle_inline(<<?\s, ?*, ?*, rest :: binary>>, limit, buffer, acc, options) do
handle_inline(rest, limit, [?*, ?*, ?\s|buffer], acc, options)
end
defp handle_inline(<<?\s, mark, rest :: binary>>, limit, buffer, acc, colors) when mark in @spaced do
handle_inline(rest, limit, [mark, ?\s|buffer], acc, colors)
defp handle_inline(<<?\s, mark, rest :: binary>>, limit, buffer, acc, options) when mark in @spaced do
handle_inline(rest, limit, [mark, ?\s|buffer], acc, options)
end
# Clauses for handling escape
defp handle_inline(<<?\\, ?\\, rest :: binary>>, limit, buffer, acc, colors) do
handle_inline(rest, limit, [?\\|buffer], acc, colors)
defp handle_inline(<<?\\, ?\\, rest :: binary>>, limit, buffer, acc, options) do
handle_inline(rest, limit, [?\\|buffer], acc, options)
end
defp handle_inline(<<?\\, ?*, ?*, rest :: binary>>, limit, buffer, acc, colors) do
handle_inline(rest, limit, [?*, ?*|buffer], acc, colors)
defp handle_inline(<<?\\, ?*, ?*, rest :: binary>>, limit, buffer, acc, options) do
handle_inline(rest, limit, [?*, ?*|buffer], acc, options)
end
# A escape is not valid inside `
defp handle_inline(<<?\\, mark, rest :: binary>>, limit, buffer, acc, colors)
defp handle_inline(<<?\\, mark, rest :: binary>>, limit, buffer, acc, options)
when mark in [?_, ?*, ?`] and not(mark == limit and mark == ?`) do
handle_inline(rest, limit, [mark|buffer], acc, colors)
handle_inline(rest, limit, [mark|buffer], acc, options)
end
# Inline start
defp handle_inline(<<?*, ?*, rest :: binary>>, nil, buffer, acc, colors) when rest != "" do
handle_inline(rest, ?d, ["**"], [Enum.reverse(buffer)|acc], colors)
defp handle_inline(<<?*, ?*, rest :: binary>>, nil, buffer, acc, options) when rest != "" do
handle_inline(rest, ?d, ["**"], [Enum.reverse(buffer)|acc], options)
end
defp handle_inline(<<mark, rest :: binary>>, nil, buffer, acc, colors) when rest != "" and mark in @single do
handle_inline(rest, mark, [<<mark>>], [Enum.reverse(buffer)|acc], colors)
defp handle_inline(<<mark, rest :: binary>>, nil, buffer, acc, options) when rest != "" and mark in @single do
handle_inline(rest, mark, [<<mark>>], [Enum.reverse(buffer)|acc], options)
end
# Inline end
defp handle_inline(<<?*, ?*, rest :: binary>>, ?d, buffer, acc, colors) do
handle_inline(rest, nil, [], [inline_buffer(buffer, colors)|acc], colors)
defp handle_inline(<<?*, ?*, rest :: binary>>, ?d, buffer, acc, options) do
handle_inline(rest, nil, [], [inline_buffer(buffer, options)|acc], options)
end
defp handle_inline(<<mark, rest :: binary>>, mark, buffer, acc, colors) when mark in @single do
handle_inline(rest, nil, [], [inline_buffer(buffer, colors)|acc], colors)
defp handle_inline(<<mark, rest :: binary>>, mark, buffer, acc, options) when mark in @single do
handle_inline(rest, nil, [], [inline_buffer(buffer, options)|acc], options)
end
defp handle_inline(<<char, rest :: binary>>, mark, buffer, acc, colors) do
handle_inline(rest, mark, [char|buffer], acc, colors)
defp handle_inline(<<char, rest :: binary>>, mark, buffer, acc, options) do
handle_inline(rest, mark, [char|buffer], acc, options)
end
defp handle_inline(<<>>, _mark, buffer, acc, _colors) do
iolist_to_binary Enum.reverse([Enum.reverse(buffer)|acc])
defp handle_inline(<<>>, _mark, buffer, acc, _options) do
iodata_to_binary Enum.reverse([Enum.reverse(buffer)|acc])
end
defp inline_buffer(buffer, colors) do
defp inline_buffer(buffer, options) do
[h|t] = Enum.reverse([IO.ANSI.reset|buffer])
[color_for(h, colors)|t]
[color_for(h, options)|t]
end
defp color_for("`", colors), do: color(:doc_inline_code, colors)
@@ -367,11 +373,4 @@ defmodule IO.ANSI.Docs do
enabled = colors[:enabled]
IO.ANSI.escape_fragment("%{#{color}}", enabled)
end
defp column_width() do
case :io.columns do
{ :ok, width } -> min(width, 80)
_ -> 80
end
end
end
end
+569 -468
View File
File diff suppressed because it is too large Load Diff
+61 -70
View File
@@ -1,22 +1,22 @@
defmodule Kernel.CLI do
@moduledoc false
defrecord Config, commands: [], output: ".", compile: [],
halt: true, compiler_options: [], errors: [],
verbose_compile: false
@blank_config %{commands: [], output: ".", compile: [],
halt: true, compiler_options: [], errors: [],
verbose_compile: false}
@doc """
This is the API invoked by Elixir boot process.
"""
def main(argv) do
argv = lc arg inlist argv, do: String.from_char_list!(arg)
argv = for arg <- argv, do: String.from_char_data!(arg)
{ config, argv } = process_argv(argv, Kernel.CLI.Config.new)
{config, argv} = process_argv(argv, @blank_config)
System.argv(argv)
run fn ->
command_results = Enum.map(Enum.reverse(config.commands), &process_command(&1, config))
command_errors = lc { :error, msg } inlist command_results, do: msg
command_errors = for {:error, msg} <- command_results, do: msg
errors = Enum.reverse(config.errors) ++ command_errors
if errors != [] do
@@ -50,7 +50,7 @@ defmodule Kernel.CLI do
System.halt(0)
kind, reason ->
at_exit(1)
print_error(kind, Exception.normalize(kind, reason), System.stacktrace)
print_error(kind, reason, System.stacktrace)
System.halt(1)
end
end
@@ -60,12 +60,12 @@ defmodule Kernel.CLI do
defp at_exit(status) do
hooks = :elixir_code_server.call(:flush_at_exit)
lc hook inlist hooks do
for hook <- hooks do
try do
hook.(status)
catch
kind, reason ->
print_error(kind, Exception.normalize(kind, reason), System.stacktrace)
print_error(kind, reason, System.stacktrace)
end
end
@@ -76,35 +76,25 @@ defmodule Kernel.CLI do
defp shared_option?(list, config, callback) do
case process_shared(list, config) do
{ [h|hs], _ } when h == hd(list) ->
new_config = config.update_errors &["#{h} : Unknown option" | &1]
{[h|hs], _} when h == hd(list) ->
new_config = %{config | errors: ["#{h} : Unknown option" | config.errors]}
callback.(hs, new_config)
{ new_list, new_config } ->
{new_list, new_config} ->
callback.(new_list, new_config)
end
end
defp print_error(:error, exception, trace) do
IO.puts :stderr, "** (#{inspect exception.__record__(:name)}) #{exception.message}"
IO.puts :stderr, format_stacktrace(trace)
end
defp print_error(kind, reason, trace) do
IO.puts :stderr, "** #{kind} #{inspect(reason)}"
IO.puts :stderr, format_stacktrace(trace)
IO.puts :stderr, Exception.format(kind, reason, prune_stacktrace(trace))
end
defp format_stacktrace(stack) do
Exception.format_stacktrace(prune_stacktrace(stack))
end
@elixir_internals [:elixir_compiler, :elixir_module, :elixir_translator, :elixir_expand]
@elixir_internals [:elixir_compiler, :elixir_module]
defp prune_stacktrace([{ mod, _, _, _ }|t]) when mod in @elixir_internals do
defp prune_stacktrace([{mod, _, _, _}|t]) when mod in @elixir_internals do
prune_stacktrace(t)
end
defp prune_stacktrace([{ __MODULE__, :wrapper, 1, _ }|_]) do
defp prune_stacktrace([{__MODULE__, :wrapper, 1, _}|_]) do
[]
end
@@ -134,44 +124,44 @@ defmodule Kernel.CLI do
end
defp process_shared(["--app", h|t], config) do
process_shared t, config.update_commands &[{:app, h}|&1]
process_shared t, %{config | commands: &[{:app, h}|&1]}
end
defp process_shared(["--no-halt"|t], config) do
process_shared t, config.halt(false)
process_shared t, %{config | halt: false}
end
defp process_shared(["-e", h|t], config) do
process_shared t, config.update_commands &[{:eval, h}|&1]
process_shared t, %{config | commands: [{:eval, h} | config.commands]}
end
defp process_shared(["-r", h|t], config) do
process_shared t, config.update_commands &[{:require, h}|&1]
process_shared t, %{config | commands: [{:require, h} | config.commands]}
end
defp process_shared(["-pr", h|t], config) do
process_shared t, config.update_commands &[{:parallel_require, h}|&1]
process_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
end
defp process_shared([erl|t], config) when erl in ["--detached", "--hidden"] do
defp process_shared([erl|t], config) when erl in ["--detached", "--hidden", "--gen-debug"] do
process_shared t, config
end
defp process_shared(list, config) do
{ list, config }
{list, config}
end
# Process init options
defp process_argv(["--"|t], config) do
{ config, t }
{config, t}
end
defp process_argv(["+compile"|t], config) do
defp process_argv(["+elixirc"|t], config) do
process_compiler t, config
end
@@ -180,7 +170,7 @@ defmodule Kernel.CLI do
end
defp process_argv(["-S", h|t], config) do
{ config.update_commands(&[{:script, h}|&1]), t }
{%{config | commands: [{:script, h} | config.commands]}, t}
end
defp process_argv([h|t] = list, config) do
@@ -188,42 +178,42 @@ defmodule Kernel.CLI do
"-" <> _ ->
shared_option? list, config, &process_argv(&1, &2)
_ ->
{ config.update_commands(&[{:file, h}|&1]), t }
{%{config | commands: [{:file, h} | config.commands]}, t}
end
end
defp process_argv([], config) do
{ config, [] }
{config, []}
end
# Process compiler options
defp process_compiler(["--"|t], config) do
{ config, t }
{config, t}
end
defp process_compiler(["-o", h|t], config) do
process_compiler t, config.output(h)
process_compiler t, %{config | output: h}
end
defp process_compiler(["--no-docs"|t], config) do
process_compiler t, config.update_compiler_options(&[{:docs, false}|&1])
process_compiler t, %{config | compiler_options: [{:docs, false} | config.compiler_options]}
end
defp process_compiler(["--no-debug-info"|t], config) do
process_compiler t, config.update_compiler_options(&[{:debug_info, false}|&1])
process_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.update_compiler_options(&[{:ignore_module_conflict, true}|&1])
process_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.update_compiler_options(&[{:warnings_as_errors, true}|&1])
process_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)
process_compiler t, %{config | verbose_compile: true}
end
defp process_compiler([h|t] = list, config) do
@@ -232,18 +222,18 @@ defmodule Kernel.CLI do
shared_option? list, config, &process_compiler(&1, &2)
_ ->
pattern = if :filelib.is_dir(h), do: "#{h}/**/*.ex", else: h
process_compiler t, config.update_compile &[pattern|&1]
process_compiler t, %{config | compile: [pattern | config.compile]}
end
end
defp process_compiler([], config) do
{ config.update_commands(&[{:compile, config.compile}|&1]), [] }
{%{config | commands: [{:compile, config.compile}|config.commands]}, []}
end
# Process iex options
defp process_iex(["--"|t], config) do
{ config, t }
{config, t}
end
# This clause is here so that Kernel.CLI does not error out with "unknown
@@ -257,7 +247,7 @@ defmodule Kernel.CLI do
end
defp process_iex(["-S", h|t], config) do
{ config.update_commands(&[{:script, h}|&1]), t }
{%{config | commands: [{:script, h} | config.commands]}, t}
end
defp process_iex([h|t] = list, config) do
@@ -265,12 +255,12 @@ defmodule Kernel.CLI do
"-" <> _ ->
shared_option? list, config, &process_iex(&1, &2)
_ ->
{ config.update_commands(&[{:file, h}|&1]), t }
{%{config | commands: [{:file, h} | config.commands]}, t}
end
end
defp process_iex([], config) do
{ config, [] }
{config, []}
end
# Process commands
@@ -279,7 +269,7 @@ defmodule Kernel.CLI do
if Node.alive? do
wrapper fn -> Node.set_cookie(binary_to_atom(h)) end
else
{ :error, "--cookie : Cannot set cookie if the node is not alive (set --name or --sname)" }
{:error, "--cookie : Cannot set cookie if the node is not alive (set --name or --sname)"}
end
end
@@ -288,10 +278,11 @@ defmodule Kernel.CLI do
end
defp process_command({:app, app}, _config) when is_binary(app) do
case Application.Behaviour.start(binary_to_atom(app)) do
{ :error, reason } ->
{ :error, "--app : Could not start application #{app}: #{inspect reason}" }
:ok ->
case Application.ensure_all_started(binary_to_atom(app)) do
{:error, {app, reason}} ->
{:error, "--app : Could not start application #{app}: " <>
Application.format_error(reason)}
{:ok, _} ->
:ok
end
end
@@ -300,7 +291,7 @@ defmodule Kernel.CLI do
if exec = find_elixir_executable(file) do
wrapper fn -> Code.require_file(exec) end
else
{ :error, "-S : Could not find executable #{file}" }
{:error, "-S : Could not find executable #{file}"}
end
end
@@ -308,7 +299,7 @@ defmodule Kernel.CLI do
if :filelib.is_regular(file) do
wrapper fn -> Code.require_file(file) end
else
{ :error, "No file named #{file}" }
{:error, "No file named #{file}"}
end
end
@@ -320,7 +311,7 @@ defmodule Kernel.CLI do
if files != [] do
wrapper fn -> Enum.map files, &Code.require_file(&1) end
else
{ :error, "-r : No files matched pattern #{pattern}" }
{:error, "-r : No files matched pattern #{pattern}"}
end
end
@@ -332,7 +323,7 @@ defmodule Kernel.CLI do
if files != [] do
wrapper fn -> Kernel.ParallelRequire.files(files) end
else
{ :error, "-pr : No files matched pattern #{pattern}" }
{:error, "-pr : No files matched pattern #{pattern}"}
end
end
@@ -340,24 +331,24 @@ defmodule Kernel.CLI do
:filelib.ensure_dir(:filename.join(config.output, "."))
case match_regular_files(patterns) do
{ :ok, [] } ->
{ :error, "No files matched provided patterns" }
{ :ok, files } ->
{:ok, []} ->
{:error, "No files matched provided patterns"}
{:ok, files} ->
wrapper fn ->
Code.compiler_options(config.compiler_options)
Kernel.ParallelCompiler.files_to_path(files, config.output,
each_file: fn file -> if config.verbose_compile do IO.puts "Compiled #{file}" end end)
end
{ :missing, missing } ->
{ :error, "No files matched pattern(s) #{Enum.join(missing, ",")}" }
{:missing, missing} ->
{:error, "No files matched pattern(s) #{Enum.join(missing, ",")}"}
end
end
defp match_regular_files(patterns) do
matched_files = Enum.map patterns, fn(pattern) ->
case Path.wildcard(pattern) do
[] -> { :missing, pattern }
files -> { :ok, files }
[] -> {:missing, pattern}
files -> {:ok, files}
end
end
@@ -372,9 +363,9 @@ defmodule Kernel.CLI do
if missing_patterns == [] do
files = Enum.uniq(Enum.concat(files))
files = Enum.filter files, &:filelib.is_regular(&1)
{ :ok, files }
{:ok, files}
else
{ :missing, Enum.uniq(missing_patterns) }
{:missing, Enum.uniq(missing_patterns)}
end
end
@@ -389,7 +380,7 @@ defmodule Kernel.CLI do
# a .bat file that must be in the same directory as
# the actual Elixir executable.
case :os.type() do
{ :win32, _ } ->
{:win32, _} ->
exec = Path.rootname(exec)
if File.regular?(exec), do: exec
_ ->
+15 -12
View File
@@ -13,24 +13,27 @@ defmodule Kernel.ErrorHandler do
:error_handler.undefined_lambda(module, fun, args)
end
def release() do
# On release, no further allow elixir_ensure_compiled
# directives and revert to the original error handler.
# Note we should not delete the elixir_compiler_pid though,
# as we still want to send notifications to the compiler.
:erlang.erase(:elixir_ensure_compiled)
:erlang.process_flag(:error_handler, :error_handler)
:ok
end
defp ensure_loaded(module) do
case Code.ensure_loaded(module) do
{ :module, _ } -> []
{ :error, _ } ->
{:module, _} -> []
{:error, _} ->
parent = :erlang.get(:elixir_compiler_pid)
ref = :erlang.make_ref
send parent, { :waiting, self(), ref, module }
send parent, {:waiting, module, self(), ref, module}
:erlang.garbage_collect(self)
receive do
{ ^ref, :ready } ->
:ok
{ ^ref, :release } ->
# On release, no further allow elixir_ensure_compiled
# directives and revert to the original error handler.
# Note we should not delete the elixir_compiler_pid though,
# as we still want to send notifications to the compiler.
:erlang.erase(:elixir_ensure_compiled)
:erlang.process_flag(:error_handler, :error_handler)
{^ref, :ready} -> :ok
{^ref, :release} -> release()
end
end
end
+30 -30
View File
@@ -21,7 +21,7 @@ defmodule Kernel.LexicalTracker do
# scope may be long gone, so it has no associated PID.
if pid = to_pid(arg) do
ets = :gen_server.call(pid, :ets, @timeout)
:ets.match(ets, { :"$1", :_, :_ }) |> List.flatten
:ets.match(ets, {:"$1", :_, :_}) |> List.flatten
else
[]
end
@@ -30,7 +30,7 @@ defmodule Kernel.LexicalTracker do
defp to_pid(pid) when is_pid(pid), do: pid
defp to_pid(mod) when is_atom(mod) do
table = :elixir_module.data_table(mod)
[{ _, val }] = :ets.lookup(table, :__lexical_tracker)
[{_, val}] = :ets.lookup(table, :__lexical_tracker)
val
end
@@ -39,7 +39,7 @@ defmodule Kernel.LexicalTracker do
# Starts the tracker and returns its pid.
@doc false
def start_link do
{ :ok, pid } = :gen_server.start_link(__MODULE__, [], [])
{:ok, pid} = :gen_server.start_link(__MODULE__, [], [])
pid
end
@@ -50,27 +50,27 @@ defmodule Kernel.LexicalTracker do
@doc false
def add_import(pid, module, line, warn) do
:gen_server.cast(pid, { :add_import, module, line, warn })
:gen_server.cast(pid, {:add_import, module, line, warn})
end
@doc false
def add_alias(pid, module, line, warn) do
:gen_server.cast(pid, { :add_alias, module, line, warn })
:gen_server.cast(pid, {:add_alias, module, line, warn})
end
@doc false
def remote_dispatch(pid, module) do
:gen_server.cast(pid, { :remote_dispatch, module })
:gen_server.cast(pid, {:remote_dispatch, module})
end
@doc false
def import_dispatch(pid, module) do
:gen_server.cast(pid, { :import_dispatch, module })
:gen_server.cast(pid, {:import_dispatch, module})
end
@doc false
def alias_dispatch(pid, module) do
:gen_server.cast(pid, { :alias_dispatch, module })
:gen_server.cast(pid, {:alias_dispatch, module})
end
@doc false
@@ -86,8 +86,8 @@ defmodule Kernel.LexicalTracker do
defp unused(pid, pos) do
ets = :gen_server.call(pid, :ets, @timeout)
:ets.foldl(fn
{ module, _, _ } = tuple, acc when is_integer(:erlang.element(pos, tuple)) ->
[{ module, :erlang.element(pos, tuple) }|acc]
{module, _, _} = tuple, acc when is_integer(:erlang.element(pos, tuple)) ->
[{module, :erlang.element(pos, tuple)}|acc]
_, acc ->
acc
end, [], ets) |> Enum.sort
@@ -97,52 +97,52 @@ defmodule Kernel.LexicalTracker do
def init([]) do
{ :ok, :ets.new(:lexical, [:protected]) }
{:ok, :ets.new(:lexical, [:protected])}
end
def handle_call(:ets, _from, d) do
{ :reply, d, d }
{:reply, d, d}
end
def handle_call(request, _from, d) do
{ :stop, { :bad_call, request }, d }
{:stop, {:bad_call, request}, d}
end
def handle_cast({ :remote_dispatch, module }, d) do
def handle_cast({:remote_dispatch, module}, d) do
add_module(d, module)
{ :noreply, d }
{:noreply, d}
end
def handle_cast({ :import_dispatch, module }, d) do
def handle_cast({:import_dispatch, module}, d) do
add_dispatch(d, module, @import)
{ :noreply, d }
{:noreply, d}
end
def handle_cast({ :alias_dispatch, module }, d) do
def handle_cast({:alias_dispatch, module}, d) do
add_dispatch(d, module, @alias)
{ :noreply, d }
{:noreply, d}
end
def handle_cast({ :add_import, module, line, warn }, d) do
def handle_cast({:add_import, module, line, warn}, d) do
add_directive(d, module, line, warn, @import)
{ :noreply, d }
{:noreply, d}
end
def handle_cast({ :add_alias, module, line, warn }, d) do
def handle_cast({:add_alias, module, line, warn}, d) do
add_directive(d, module, line, warn, @alias)
{ :noreply, d }
{:noreply, d}
end
def handle_cast(:stop, d) do
{ :stop, :normal, d }
{:stop, :normal, d}
end
def handle_cast(msg, d) do
{ :stop, { :bad_cast, msg }, d }
{:stop, {:bad_cast, msg}, d}
end
def handle_info(_msg, d) do
{ :noreply, d }
{:noreply, d}
end
def terminate(_reason, _d) do
@@ -150,7 +150,7 @@ defmodule Kernel.LexicalTracker do
end
def code_change(_old, d, _extra) do
{ :ok, d }
{:ok, d}
end
# Callbacks helpers
@@ -160,16 +160,16 @@ defmodule Kernel.LexicalTracker do
# If the value is true, it was imported/aliased
# If the value is a line, it was imported/aliased and has a pending warning
defp add_module(d, module) do
:ets.insert_new(d, { module, false, false })
:ets.insert_new(d, {module, false, false})
end
defp add_dispatch(d, module, pos) do
:ets.update_element(d, module, { pos, true })
:ets.update_element(d, module, {pos, true})
end
defp add_directive(d, module, line, warn, pos) do
add_module(d, module)
marker = if warn, do: line, else: true
:ets.update_element(d, module, { pos, marker })
:ets.update_element(d, module, {pos, marker})
end
end
+112 -64
View File
@@ -43,30 +43,30 @@ defmodule Kernel.ParallelCompiler do
defp spawn_compilers(files, path, callbacks) do
Code.ensure_loaded(Kernel.ErrorHandler)
compiler_pid = self()
:elixir_code_server.cast({ :reset_warnings, compiler_pid })
:elixir_code_server.cast({:reset_warnings, compiler_pid})
schedulers = max(:erlang.system_info(:schedulers_online), 2)
result = spawn_compilers(files, files, path, callbacks, [], [], schedulers, [])
# In case --warning-as-errors is enabled and there was a warning,
# compilation status will be set to error and we fail with CompileError
case :elixir_code_server.call({ :compilation_status, compiler_pid }) do
case :elixir_code_server.call({:compilation_status, compiler_pid}) do
:ok -> result
:error -> raise CompileError, [], []
:error -> exit(1)
end
end
# We already have 4 currently running, don't spawn new ones
defp spawn_compilers(files, original, output, callbacks, waiting, queued, schedulers, result) when
defp spawn_compilers(entries, original, output, callbacks, waiting, queued, schedulers, result) when
length(queued) - length(waiting) >= schedulers do
wait_for_messages(files, original, output, callbacks, waiting, queued, schedulers, result)
wait_for_messages(entries, original, output, callbacks, waiting, queued, schedulers, result)
end
# Release waiting processes
defp spawn_compilers([h|t], original, output, callbacks, waiting, queued, schedulers, result) when is_pid(h) do
{ ^h, ref, _ } = List.keyfind(waiting, h, 0)
send h, { ref, :ready }
waiting = List.keydelete(waiting, h, 0)
{_kind, ^h, ref, _module} = List.keyfind(waiting, h, 1)
send h, {ref, :ready}
waiting = List.keydelete(waiting, h, 1)
spawn_compilers(t, original, output, callbacks, waiting, queued, schedulers, result)
end
@@ -74,7 +74,7 @@ defmodule Kernel.ParallelCompiler do
defp spawn_compilers([h|t], original, output, callbacks, waiting, queued, schedulers, result) do
parent = self()
{ pid, ref } =
{pid, ref} =
:erlang.spawn_monitor fn ->
# Notify Code.ensure_compiled/2 that we should
# attempt to compile the module by doing a dispatch.
@@ -90,24 +90,27 @@ defmodule Kernel.ParallelCompiler do
else
:elixir_compiler.file(h)
end
{ :compiled, h }
{:compiled, h}
catch
kind, reason ->
{ :failure, kind, reason, System.stacktrace }
{:failure, kind, reason, System.stacktrace}
end)
end
spawn_compilers(t, original, output, callbacks, waiting, [{pid, ref, h}|queued], schedulers, result)
spawn_compilers(t, original, output, callbacks, waiting,
[{pid, ref, h}|queued], schedulers, result)
end
# No more files, nothing waiting, queue is empty, we are done
defp spawn_compilers([], _original, _output, _callbacks, [], [], _schedulers, result), do: result
defp spawn_compilers([], _original, _output, _callbacks, [], [], _schedulers, result) do
for {:module, mod} <- result, do: mod
end
# Queued x, waiting for x: POSSIBLE ERROR! Release processes so we get the failures
defp spawn_compilers([], original, output, callbacks, waiting, queued, schedulers, result) when length(waiting) == length(queued) do
Enum.each queued, fn { child, _, _ } ->
{ ^child, ref, _ } = List.keyfind(waiting, child, 0)
send child, { ref, :release }
Enum.each queued, fn {child, _, _} ->
{_kind, ^child, ref, _module} = List.keyfind(waiting, child, 1)
send child, {ref, :release}
end
wait_for_messages([], original, output, callbacks, waiting, queued, schedulers, result)
end
@@ -118,82 +121,127 @@ defmodule Kernel.ParallelCompiler do
end
# Wait for messages from child processes
defp wait_for_messages(files, original, output, callbacks, waiting, queued, schedulers, result) do
defp wait_for_messages(entries, original, output, callbacks, waiting, queued, schedulers, result) do
receive do
{ :module_available, child, ref, file, module, binary } ->
{:struct_available, module} ->
available = for {:struct, pid, _, waiting_module} <- waiting,
module == waiting_module,
not pid in entries,
do: pid
spawn_compilers(available ++ entries, original, output, callbacks,
waiting, queued, schedulers, [{:struct, module}|result])
{:module_available, child, ref, file, module, binary} ->
if callback = Keyword.get(callbacks, :each_module) do
callback.(file, module, binary)
end
# Release the module loader which is waiting for an ack
send child, { ref, :ack }
send child, {ref, :ack}
available = lc { pid, _, waiting_module } inlist waiting,
waiting_module == module,
available = for {_kind, pid, _, waiting_module} <- waiting,
module == waiting_module,
not pid in entries,
do: pid
spawn_compilers(available ++ files, original, output, callbacks,
waiting, queued, schedulers, [module|result])
spawn_compilers(available ++ entries, original, output, callbacks,
waiting, queued, schedulers, [{:module, module}|result])
{ :waiting, child, ref, on } ->
# Oops, we already got this module. Do not put it on waiting.
if :lists.member(on, result) do
send child, { :release, ref }
{:waiting, kind, child, ref, on} ->
defined = fn {k, m} -> on == m and k in [kind, :module] end
# Oops, we already got it, do not put it on waiting.
if :lists.any(defined, result) do
send child, {ref, :ready}
else
waiting = [{ child, ref, on }|waiting]
waiting = [{kind, child, ref, on}|waiting]
end
spawn_compilers(files, original, output, callbacks, waiting, queued, schedulers, result)
{ :DOWN, _down_ref, :process, down_pid, { :compiled, file } } ->
spawn_compilers(entries, original, output, callbacks, waiting, queued, schedulers, result)
{:DOWN, _down_ref, :process, down_pid, {:compiled, file}} ->
if callback = Keyword.get(callbacks, :each_file) do
callback.(file)
end
# Sometimes we may have spurious entries in the waiting
# list because someone invoked try/rescue UndefinedFunctionError
new_waiting = List.keydelete(waiting, down_pid, 0)
new_entries = List.delete(entries, down_pid)
new_queued = List.keydelete(queued, down_pid, 0)
spawn_compilers(files, original, output, callbacks, new_waiting, new_queued, schedulers, result)
new_waiting = List.keydelete(waiting, down_pid, 1)
spawn_compilers(new_entries, original, output, callbacks, new_waiting, new_queued, schedulers, result)
{ :DOWN, down_ref, :process, _down_pid, { :failure, kind, reason, stacktrace } } ->
handle_failure(down_ref, kind, reason, stacktrace, files, waiting, queued)
wait_for_messages(files, original, output, callbacks, waiting, queued, schedulers, result)
{ :DOWN, down_ref, :process, _down_pid, other } ->
handle_failure(down_ref, :exit, other, [], files, waiting, queued)
wait_for_messages(files, original, output, callbacks, waiting, queued, schedulers, result)
{:DOWN, down_ref, :process, _down_pid, reason} ->
handle_failure(down_ref, reason, entries, waiting, queued)
wait_for_messages(entries, original, output, callbacks, waiting, queued, schedulers, result)
end
end
defp handle_failure(ref, kind, reason, stacktrace, files, waiting, queued) do
defp handle_failure(ref, reason, entries, waiting, queued) do
if file = find_failure(ref, queued) do
print_failure(file, reason)
if all_missing?(entries, waiting, queued) do
collect_failures(queued, length(queued) - 1)
end
exit(1)
end
end
defp find_failure(ref, queued) do
case List.keyfind(queued, ref, 1) do
{ child, ^ref, file } ->
if many_missing?(child, files, waiting, queued) do
IO.puts "== Compilation failed =="
IO.puts "Compilation failed on the following files:\n"
Enum.each Enum.reverse(queued), fn { pid, _ref, file } ->
case List.keyfind(waiting, pid, 0) do
{ ^pid, _, mod } -> IO.puts "* #{file} is missing module #{inspect mod}"
_ -> :ok
end
end
IO.puts "\nThe first failure is shown below..."
end
IO.puts "== Compilation error on file #{file} =="
:erlang.raise(kind, reason, stacktrace)
_ ->
:ok
{_child, ^ref, file} -> file
_ -> nil
end
end
defp many_missing?(child, files, waiting, queued) do
waiting_length = length(waiting)
defp print_failure(_file, {:compiled, _}) do
:ok
end
match?({ ^child, _, _ }, List.keyfind(waiting, child, 0)) and
waiting_length > 1 and files == [] and
waiting_length == length(queued)
defp print_failure(file, {:failure, kind, reason, stacktrace}) do
IO.puts "\n== Compilation error on file #{Path.relative_to_cwd(file)} =="
IO.puts Exception.format(kind, reason, prune_stacktrace(stacktrace))
end
defp print_failure(file, reason) do
IO.puts "\n== Compilation error on file #{Path.relative_to_cwd(file)} =="
IO.puts Exception.format(:exit, reason, [])
end
@elixir_internals [:elixir_compiler, :elixir_module, :elixir_translator, :elixir_expand]
defp prune_stacktrace([{mod, _, _, _}|t]) when mod in @elixir_internals do
prune_stacktrace(t)
end
defp prune_stacktrace([h|t]) do
[h|prune_stacktrace(t)]
end
defp prune_stacktrace([]) do
[]
end
defp all_missing?(entries, waiting, queued) do
entries == [] and waiting != [] and
length(waiting) == length(queued)
end
defp collect_failures(_queued, 0), do: :ok
defp collect_failures(queued, remaining) do
receive do
{:DOWN, down_ref, :process, _down_pid, reason} ->
if file = find_failure(down_ref, queued) do
print_failure(file, reason)
collect_failures(queued, remaining - 1)
else
collect_failures(queued, remaining)
end
after
# Give up if no failure appears in 5 seconds
5000 -> :ok
end
end
end
+9 -9
View File
@@ -33,9 +33,9 @@ defmodule Kernel.ParallelRequire do
compiler_pid = :erlang.get(:elixir_compiler_pid)
ensure_compiled = :erlang.get(:elixir_ensure_compiled)
{ :error_handler, handler } = :erlang.process_info(parent, :error_handler)
{:error_handler, handler} = :erlang.process_info(parent, :error_handler)
{ pid, ref } = :erlang.spawn_monitor fn ->
{pid, ref} = :erlang.spawn_monitor fn ->
if compiler_pid != :undefined do
:erlang.put(:elixir_compiler_pid, compiler_pid)
end
@@ -48,27 +48,27 @@ defmodule Kernel.ParallelRequire do
exit(try do
new = Code.require_file(h) || []
{ :required, Enum.map(new, &elem(&1, 0)), h }
{:required, Enum.map(new, &elem(&1, 0)), h}
catch
kind, reason ->
{ :failure, kind, reason, System.stacktrace }
{:failure, kind, reason, System.stacktrace}
end)
end
spawn_requires(t, [{ pid, ref }|waiting], callback, schedulers, result)
spawn_requires(t, [{pid, ref}|waiting], callback, schedulers, result)
end
defp wait_for_messages(files, waiting, callback, schedulers, result) do
receive do
{ :DOWN, ref, :process, pid, status } ->
tuple = { pid, ref }
{:DOWN, ref, :process, pid, status} ->
tuple = {pid, ref}
if tuple in waiting do
case status do
{ :required, mods, file } ->
{:required, mods, file} ->
callback.(file)
result = mods ++ result
waiting = List.delete(waiting, tuple)
{ :failure, kind, reason, stacktrace } ->
{:failure, kind, reason, stacktrace} ->
:erlang.raise(kind, reason, stacktrace)
other ->
:erlang.raise(:exit, other, [])
-413
View File
@@ -1,413 +0,0 @@
# This is an optimization Elixir runs on function clauses.
# Whenever a variables matches against a record (a tagged
# tuple), this information is stored in order to optimize
# record calls.
defmodule Kernel.RecordRewriter do
@moduledoc false
def optimize_clause(module, clause) do
optimize_clause(clause, module, :orddict.new)
end
## Clause
defp optimize_clause({ :clause, line, args, guards, body }, module, dict) do
{ args, dict } = optimize_args(args, module, dict)
{ body, dict, res } = optimize_body(body, module, dict, [])
{ { :clause, line, args, guards, body }, dict, res }
end
defp optimize_args(args, module, dict) do
Enum.map_reduce args, dict, fn(arg, acc) ->
{ new_arg, new_acc, _res } = optimize_expr(arg, module, acc)
{ new_arg, new_acc }
end
end
defp optimize_body([], _module, dict, _acc) do
{ [], dict, nil }
end
defp optimize_body([h], module, dict, acc) do
{ new_expr, new_dict, new_res } = optimize_expr(h, module, dict)
{ Enum.reverse([new_expr|acc]), new_dict, new_res }
end
defp optimize_body([h|t], module, dict, acc) do
{ new_expr, new_dict, _ } = optimize_expr(h, module, dict)
optimize_body(t, module, new_dict, [new_expr|acc])
end
## Record helpers
defp record_fields(record, record) do
fields = Module.get_attribute(record, :record_fields)
optimizable = Module.get_attribute(record, :record_optimizable)
unless nil?(fields) or nil?(optimizable) do
{ (lc { k, _ } inlist fields, do: k), optimizable }
end
end
defp record_fields(_module, record) do
if Code.ensure_loaded?(record) && function_exported?(record, :__record__, 1) do
try do
fields = lc { k, _ } inlist record.__record__(:fields), do: k
optimizable = record.__record__(:optimizable)
{ fields, optimizable }
rescue
[UndefinedFunctionError, FunctionClauseError] -> nil
end
end
end
defp record_field_info(function) do
case atom_to_list(function) do
'update' -> nil
'update_' ++ _field -> nil
_ -> { :accessor, function }
end
end
defp optimize_call(line, module, { record, _ } = res, left, { :atom, _, function }, args) do
case record_fields(module, record) do
{ fields, optimizable } ->
opt_call =
if { function, length(args) + 1 } in optimizable do
case record_field_info(function) do
{ kind, field } ->
if index = Enum.find_index(fields, &(field == &1)) do
optimize_record_accessor_call(line, res, kind, field, index, left, args)
end
nil ->
optimize_record_other_call(line, record, res, function, left, args)
end
end
opt_call || optimize_record_other_call(line, record, nil, function, left, args)
nil ->
nil
end
end
defp optimize_call(_line, _module, _res, _left, _right, _args) do
nil
end
defp optimize_record_accessor_call(line, _res, :accessor, _field, index, left, []) do
call = { :call, line,
{ :remote, line, { :atom, 0, :erlang }, { :atom, 0, :element } },
[{ :integer, 0, index + 2 }, left]
}
{ call, nil }
end
defp optimize_record_accessor_call(line, res, :accessor, _field, index, left, [arg]) do
call = { :call, line,
{ :remote, line, { :atom, 0, :erlang }, { :atom, 0, :setelement } },
[{ :integer, 0, index + 2 }, left, arg]
}
{ call, res }
end
defp optimize_record_other_call(line, record, res, function, left, args) do
call = { :call, line,
{ :remote, line, { :atom, 0, record }, { :atom, 0, function } },
args ++ [left]
}
{ call, res }
end
## Expr
defp optimize_expr({ :call, call_line,
{ :remote, _, { :atom, _, :erlang }, { :atom, _, :setelement } } = remote,
[{ :integer, _, pos } = integer, tuple, value] }, module, dict) when pos > 1 do
{ tuple, dict, res } = optimize_expr(tuple, module, dict)
{ value, dict, _ } = optimize_expr(value, module, dict)
{ { :call, call_line, remote, [integer, tuple, value] }, dict, res }
end
defp optimize_expr({ :call, call_line, { :remote, line, left, right }, args }, module, dict) do
{ left, dict, res } = optimize_expr(left, module, dict)
{ right, dict, _ } = optimize_expr(right, module, dict)
{ args, dict } = optimize_args(args, module, dict)
case optimize_call(call_line, module, res, left, right, args) do
{ call, call_res } ->
{ call, dict, call_res }
nil ->
{ { :call, call_line, { :remote, line, left, right }, args }, dict, nil }
end
end
defp optimize_expr({ :call, line, expr, args }, module, dict) do
{ expr, dict, _ } = optimize_expr(expr, module, dict)
{ args, dict } = optimize_args(args, module, dict)
{ { :call, line, expr, args }, dict, nil }
end
defp optimize_expr({ :match, line, left, right }, module, dict) do
{ left, dict, left_res } = optimize_expr(left, module, dict)
{ right, dict, right_res } = optimize_expr(right, module, dict)
match = { :match, line, left, right }
if left_res do
dict = assign_vars(extract_vars(right, []), dict, left_res)
end
if right_res do
dict = assign_vars(extract_vars(left, []), dict, right_res)
end
{ match, dict, right_res || left_res }
end
defp optimize_expr({ :op, line, op, left, right }, module, dict) do
{ left, dict, _ } = optimize_expr(left, module, dict)
{ right, dict, _ } = optimize_expr(right, module, dict)
{ { :op, line, op, left, right }, dict, nil }
end
defp optimize_expr({ :op, line, op, expr }, module, dict) do
{ expr, dict, _ } = optimize_expr(expr, module, dict)
{ { :op, line, op, expr }, dict, nil }
end
defp optimize_expr({ :bin, line, elements }, module, dict) do
{ elements, dict } = optimize_args(elements, module, dict)
{ { :bin, line, elements }, dict, nil }
end
defp optimize_expr({ :bin_element, line, expr, type1, type2 }, module, dict) do
{ expr, dict, _ } = optimize_expr(expr, module, dict)
{ { :bin_element, line, expr, type1, type2 }, dict, nil }
end
defp optimize_expr({ :cons, line, left, right }, module, dict) do
{ left, dict, _ } = optimize_expr(left, module, dict)
{ right, dict, _ } = optimize_expr(right, module, dict)
{ { :cons, line, left, right }, dict, nil }
end
defp optimize_expr({ :block, line, args }, module, dict) do
{ args, dict, res } = optimize_body(args, module, dict, [])
{ { :block, line, args }, dict, res }
end
defp optimize_expr({ :tuple, line, args }, module, dict) do
{ args, dict, args_res } = optimize_tuple_args(args, module, dict)
args_res = if Enum.any?(args_res), do: args_res, else: nil
res =
case args do
[{ :atom, _, atom }|_] -> atom
_ -> nil
end
{ { :tuple, line, args }, dict, { res, args_res } }
end
defp optimize_expr({ :var, _, name } = var, _module, dict) do
case :orddict.find(name, dict) do
{ :ok, res } -> { var, dict, res }
:error -> { var, dict, nil }
end
end
defp optimize_expr({ :case, line, expr, clauses }, module, dict) do
{ expr, dict, _ } = optimize_expr(expr, module, dict)
tuples = lc clause inlist clauses, do: optimize_clause(clause, module, dict)
clauses = lc { clause, _, _ } inlist tuples, do: clause
dict = join_dict(tuples)
res = join_result(tuples)
{ { :case, line, expr, clauses }, dict, res }
end
defp optimize_expr({ :receive, line, clauses }, module, dict) do
tuples = lc clause inlist clauses, do: optimize_clause(clause, module, dict)
clauses = lc { clause, _, _ } inlist tuples, do: clause
dict = join_dict(tuples)
res = join_result(tuples)
{ { :receive, line, clauses }, dict, res }
end
defp optimize_expr({ :receive, line, clauses, after_key, after_value }, module, dict) do
tuples = lc clause inlist clauses, do: optimize_clause(clause, module, dict)
clauses = lc { clause, _, _ } inlist tuples, do: clause
{ after_key, dict, _ } = optimize_expr(after_key, module, dict)
{ after_value, dict, res } = optimize_body(after_value, module, dict, [])
dict = join_dict(tuples, dict)
res = join_result(tuples, res)
{ { :receive, line, clauses, after_key, after_value }, dict, res }
end
defp optimize_expr({ :try, line, body, elses, catches, try_after }, module, dict) do
tuples = lc clause inlist catches, do: optimize_clause(clause, module, dict)
catches = lc { clause, _, _ } inlist tuples, do: clause
tuples = lc clause inlist elses, do: optimize_clause(clause, module, dict)
elses = lc { clause, _, _ } inlist tuples, do: clause
{ body, _, res } = optimize_body(body, module, dict, [])
res = join_result(tuples, res)
{ try_after, _, _ } = optimize_body(try_after, module, dict, [])
{ { :try, line, body, elses, catches, try_after }, dict, res }
end
defp optimize_expr({ :fun, line, { :function, receiver, name, arity } }, module, dict) do
{ receiver, dict, _ } = optimize_expr(receiver, module, dict)
{ name, dict, _ } = optimize_expr(name, module, dict)
{ arity, dict, _ } = optimize_expr(arity, module, dict)
{ { :fun, line, { :function, receiver, name, arity } }, dict, nil }
end
defp optimize_expr({ :fun, line, { :clauses, clauses } }, module, dict) do
clauses = lc clause inlist clauses do
{ clause, _, _ } = optimize_clause(clause, module, dict)
clause
end
{ { :fun, line, { :clauses, clauses } }, dict, nil }
end
defp optimize_expr({ comprehension, line, expr, args }, module, dict) when comprehension in [:lc, :bc] do
{ args, new_dict } = optimize_args(args, module, dict)
{ expr, _, _ } = optimize_expr(expr, module, new_dict)
{ { comprehension, line, expr, args }, dict, nil }
end
defp optimize_expr({ generate, line, left, right }, module, dict) when generate in [:generate, :b_generate] do
{ left, dict, _ } = optimize_expr(left, module, dict)
{ right, dict, _ } = optimize_expr(right, module, dict)
{ { generate, line, left, right }, dict, nil }
end
defp optimize_expr(other, _module, dict) when elem(other, 0) in [:string, :atom, :integer, :float, :nil, :fun] do
{ other, dict, nil }
end
## Helpers
defp optimize_tuple_args(args, module, dict) do
{ final_args, { final_dict, final_acc } } =
Enum.map_reduce args, { dict, [] }, fn(arg, { acc_dict, acc_res }) ->
{ new_arg, new_acc, res } = optimize_expr(arg, module, acc_dict)
{ new_arg, { new_acc, [res|acc_res] } }
end
{ final_args, final_dict, Enum.reverse(final_acc) }
end
defp assign_vars([key|t], dict, { _, value } = res) when is_list(key) and is_list(value) and length(key) == length(value) do
assign_vars t, assign_nested_vars(key, dict, value), res
end
defp assign_vars([key|t], dict, { value, _ } = res) when is_atom(key) and value != nil do
dict =
case :orddict.find(key, dict) do
{ :ok, ^res } ->
dict
{ :ok, { ^value, _ } } ->
:orddict.store(key, { value, nil }, dict)
{ :ok, _ } ->
# We are overriding a type of an existing variable,
# which means the source code is invalid.
:orddict.store(key, nil, dict)
:error ->
:orddict.store(key, res, dict)
end
assign_vars t, dict, res
end
defp assign_vars([_|t], dict, res) do
assign_vars t, dict, res
end
defp assign_vars([], dict, _res) do
dict
end
defp assign_nested_vars([vars|vt], dict, [res|rt]) do
assign_nested_vars(vt, assign_vars(vars, dict, res), rt)
end
defp assign_nested_vars([], dict, []) do
dict
end
defp extract_vars({ :match, _, left, right }, vars) do
vars = extract_vars(right, vars)
extract_vars(left, vars)
end
defp extract_vars({ :var, _, name }, vars) do
[name|vars]
end
defp extract_vars({ :tuple, _, args }, vars) do
[Enum.map(args, &extract_vars(&1, []))|vars]
end
defp extract_vars(_, vars) do
vars
end
defp join_dict([]) do
[]
end
defp join_dict([{ _, dict, _ }|t]) do
join_dict(t, dict)
end
defp join_dict([{ _, dict, _ }|t], other) do
other = Enum.reduce other, other, fn
{ key, { value, _ } = res }, acc ->
case :orddict.find(key, dict) do
{ :ok, ^res } -> acc
{ :ok, { ^value, _ } } -> :orddict.store(key, { value, nil }, acc)
{ :ok, _ } -> :orddict.store(key, nil, acc)
:error -> :orddict.erase(key, acc)
end
{ key, nil }, acc ->
:orddict.store(key, nil, acc)
end
join_dict(t, other)
end
defp join_dict([], other) do
other
end
defp join_result([]) do
[]
end
defp join_result([{ _, _, res }|t]) do
join_result(t, res)
end
defp join_result([{ _, _, res }|t], res) do
join_result(t, res)
end
defp join_result([{ _, _, { res, _ } }|t], { res, _ }) do
join_result(t, { res, nil })
end
defp join_result([{ _, _, _ }|_], _res) do
nil
end
defp join_result([], res) do
res
end
end
+273 -132
View File
@@ -20,22 +20,141 @@ defmodule Kernel.SpecialForms do
"""
@doc """
Defines a new tuple.
Creates a tuple.
Only two item tuples are considered literals in Elixir.
Therefore all other tuples are represented in the AST
as a call to the special form `:{}`.
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`.
## Examples
iex> { 1, 2, 3 }
{ 1, 2, 3 }
iex> quote do: { 1, 2, 3 }
{ :{}, [], [1,2,3] }
iex> {1, 2, 3}
{1, 2, 3}
iex> quote do: {1, 2, 3}
{:{}, [], [1,2,3]}
"""
defmacro unquote(:{})(args)
@doc """
Creates a map.
Maps are key-value stores where keys are compared
using the match operator (`===`). Maps can be created with
the `%{}` special form where keys are associated via `=>`:
%{1 => 2}
Maps also support the keyword notation, as other special forms,
as long as they are at the end of the argument list:
%{hello: :world, with: :keywords}
%{:hello => :world, with: :keywords}
If a map has duplicated keys, the last key will always have
higher precedence:
iex> %{a: :b, a: :c}
%{a: :c}
Conveniences for manipulating maps can be found in the
`Map` module.
## Access syntax
Besides the access functions available in the `Map` module,
like `Map.get/3` and `Map.fetch/2`, a map can be accessed using the
`.` operator:
iex> map = %{a: :b}
iex> map.a
:b
Note that the `.` operator expects the field to exist in the map.
If not, an `ArgumentError` is raised.
## Update syntax
Maps also support an update syntax:
iex> map = %{:a => :b}
iex> %{map | :a => :c}
%{:a => :c}
Notice the update syntax requires the given keys to exist.
Trying to update a key that does not exist will raise an `ArgumentError`.
## AST representation
Regardless if `=>` or the keywords syntax is used, Maps are
always represented internally as a list of two-items tuples
for simplicity:
iex> quote do: %{:a => :b, c: :d}
{:%{}, [], [{:a, :b}, {:c, :d}]}
"""
defmacro unquote(:%{})(args)
@doc """
Creates a struct.
A struct is a tagged map that allows developers to provide
default values for keys, tags to be used in polymorphic
dispatches and compile time assertions.
To define a struct, you just need to implement the `__struct__/0`
function in a module:
defmodule User do
def __struct__ do
%{name: "josé", age: 27}
end
end
Now a struct can be created as follow:
%User{}
Underneath, a struct is just a map with a `__struct__` field
pointing to the User module:
%User{} == %{__struct__: User, name: "josé", age: 27}
A struct also validates the given keys are part of the defined
struct. The example below will fail because there is no key
`:full_name` in the user struct:
%User{full_name: "José Valim"}
Note that a struct specifies a minimum set of keys required
for operations. Other keys can be added to structs via the
regular map operations:
user = %User{}
Map.put(user, :a_non_struct_key, :value)
An update operation specific for structs is also available:
%User{user | age: 28}
The syntax above will guarantee the given keys are valid at
compilation time and it will guarantee at runtime the given
argument is a struct, failing with `BadStructError` otherwise.
Check `Kernel.defprotocol/2` for more information on how structs
can be used with protocols for polymorphic dispatch. Also,
see `Kernel.struct/2` for examples on how to create and update
structs dynamically.
"""
defmacro unquote(:%)(struct, map)
@doc """
Defines a new bitstring.
@@ -108,7 +227,7 @@ defmodule Kernel.SpecialForms do
signedness, e.g.:
iex> <<val, _rest :: binary>> = <<-100, "foo">>
...> val
iex> val
156
Here, `val` is interpreted as unsigned.
@@ -200,12 +319,16 @@ defmodule Kernel.SpecialForms do
iex> Kernel.Sample
Kernel.Sample
iex> Kernel.length([1,2,3])
3
iex> Kernel.+(1, 2)
3
iex> Kernel."length"([1,2,3])
3
iex> Kernel.'+'(1, 2)
3
@@ -229,6 +352,7 @@ defmodule Kernel.SpecialForms do
iex> apply(:erlang, :+, [1,2])
3
iex> Module.concat(Kernel, Sample)
Kernel.Sample
@@ -331,7 +455,7 @@ defmodule Kernel.SpecialForms do
Elixir won't emit any warnings though, since the alias
was not explicitly defined.
Both warning behaviours could be changed by explicitily
Both warning behaviours could be changed by explicitly
setting the `:warn` option to true or false.
"""
defmacro alias(module, opts)
@@ -377,7 +501,7 @@ defmodule Kernel.SpecialForms do
for example:
iex> import List
...> flatten([1, [2], 3])
iex> flatten([1, [2], 3])
[1,2,3]
## Selector
@@ -443,7 +567,7 @@ defmodule Kernel.SpecialForms do
Elixir won't emit any warnings though, since the import
was not explicitly defined.
Both warning behaviours could be changed by explicitily
Both warning behaviours could be changed by explicitly
setting the `:warn` option to true or false.
## Ambiguous function/macro names
@@ -495,28 +619,28 @@ defmodule Kernel.SpecialForms do
value, instead of rebinding. This can be done with the `^` special form:
iex> x = 1
iex> ^x = 1
iex> ^x = 2
iex> ^x = List.first([1])
iex> ^x = List.first([2])
** (MatchError) no match of right hand side value: 2
Note that `^` always refers to the value of x prior to the match. The
following example will match:
iex> x = 0
iex> { x, ^x } = { 1, 0 }
iex> {x, ^x} = {1, 0}
iex> x
1
"""
defmacro ^(var)
@doc %S"""
@doc ~S"""
Gets the representation of any expression.
## Examples
iex> quote do: sum(1, 2, 3)
{ :sum, [], [1, 2, 3] }
quote do: sum(1, 2, 3)
#=> {:sum, [], [1, 2, 3]}
## Explanation
@@ -524,7 +648,7 @@ defmodule Kernel.SpecialForms do
The building block of Elixir macros is a tuple with three elements,
for example:
{ :sum, [], [1, 2, 3] }
{:sum, [], [1, 2, 3]}
The tuple above represents a function call to `sum` passing 1, 2 and
3 as arguments. The tuple elements are:
@@ -901,7 +1025,7 @@ defmodule Kernel.SpecialForms do
functions on the fly. Consider this example:
kv = [foo: 1, bar: 2]
Enum.each kv, fn { k, v } ->
Enum.each kv, fn {k, v} ->
def unquote(k)(), do: unquote(v)
end
@@ -910,7 +1034,7 @@ defmodule Kernel.SpecialForms do
functionality into a macro:
defmacro defkv(kv) do
Enum.map kv, fn { k, v } ->
Enum.map kv, fn {k, v} ->
quote do
def unquote(k)(), do: unquote(v)
end
@@ -937,7 +1061,7 @@ defmodule Kernel.SpecialForms do
defmacro defkv(kv) do
quote do
Enum.each unquote(kv), fn { k, v } ->
Enum.each unquote(kv), fn {k, v} ->
def unquote(k)(), do: unquote(v)
end
end
@@ -958,7 +1082,7 @@ defmodule Kernel.SpecialForms do
defmacro defkv(kv) do
quote bind_quoted: [kv: kv] do
Enum.each kv, fn { k, v } ->
Enum.each kv, fn {k, v} ->
def unquote(k)(), do: unquote(v)
end
end
@@ -983,13 +1107,13 @@ defmodule Kernel.SpecialForms do
Which would then return:
{ :sum, [], [1, { :value, [], quoted }, 3] }
{:sum, [], [1, {:value, [], quoted}, 3]}
Which is not the expected result. For this, we use unquote:
value = 13
quote do: sum(1, unquote(value), 3)
#=> { :sum, [], [1, 13, 3] }
#=> {:sum, [], [1, 13, 3]}
"""
defmacro unquote(:unquote)(expr)
@@ -1002,82 +1126,78 @@ defmodule Kernel.SpecialForms do
values = [2, 3, 4]
quote do: sum(1, unquote_splicing(values), 5)
#=> { :sum, [], [1, 2, 3, 4, 5] }
#=> {:sum, [], [1, 2, 3, 4, 5]}
"""
defmacro unquote(:unquote_splicing)(expr)
@doc """
List comprehensions allow you to quickly build a list from another list:
@doc ~S"""
Comprehensions allow you to quickly build a data structure from
an enumerable or a bitstring.
iex> lc n inlist [1, 2, 3, 4], do: n * 2
[2,4,6,8]
Let's start with an example:
A comprehension accepts many generators and also filters. Generators
are defined using both `inlist` and `inbits` operators, allowing you
to loop lists and bitstrings:
iex> for n <- [1, 2, 3, 4], do: n * 2
[2, 4, 6, 8]
A comprehension accepts many generators and filters. Enumerable
generators are defined using `<-`:
# A list generator:
iex> lc n inlist [1, 2, 3, 4], do: n * 2
[2,4,6,8]
# A bit string generator:
iex> lc <<n>> inbits <<1, 2, 3, 4>>, do: n * 2
[2,4,6,8]
# A generator from a variable:
iex> list = [1, 2, 3, 4]
...> lc n inlist list, do: n * 2
[2,4,6,8]
iex> for n <- [1, 2, 3, 4], do: n * 2
[2, 4, 6, 8]
# A comprehension with two generators
iex> lc x inlist [1, 2], y inlist [2, 3], do: x*y
[2,3,4,6]
iex> for x <- [1, 2], y <- [2, 3], do: x*y
[2, 3, 4, 6]
Filters can also be given:
# A comprehension with a generator and a filter
iex> lc n inlist [1, 2, 3, 4, 5, 6], rem(n, 2) == 0, do: n
[2,4,6]
iex> for n <- [1, 2, 3, 4, 5, 6], rem(n, 2) == 0, do: n
[2, 4, 6]
Bit string generators are quite useful when you need to
organize bit string streams:
Note generators can also be used to filter as it removes any value
that doesn't match the left side of `<-`:
iex> for {:user, name} <- [user: "jose", admin: "john", user: "eric"] do
...> String.upcase(name)
...> end
["JOSE", "ERIC"]
Bitstring generators are also supported and are very useful when you
need to organize bitstring streams:
iex> pixels = <<213, 45, 132, 64, 76, 32, 76, 0, 0, 234, 32, 15>>
iex> lc <<r::8, g::8, b::8>> inbits pixels, do: {r, g, b}
iex> for <<r::8, g::8, b::8 <- pixels >>, do: {r, g, b}
[{213,45,132},{64,76,32},{76,0,0},{234,32,15}]
Variable assignments in the `do` block of the comprehension
are not reflected outside of the block.
Variable assignments inside the comprehension, be it in generators,
filters or inside the block, are not reflected outside of the
comprehension.
Note: Unlike Erlang, Elixir comprehension filters
never behave as guards when it comes to errors. Errors in
list comprehensions will always be raised. Consider this
Erlang example:
## Into
erl> [I || I <- [1,2,3], hd(I)].
[]
In the examples above, the result returned by the comprehension was
always a list. The returned result can be configured by passing an
`:into` option, that accepts any structure as long as it implements
the `Collectable` protocol.
In Elixir, it will raise:
For example, we can use bitstring generators with the `:into` option
to easily remove all spaces in a string:
iex> lc i inlist [1,2,3], hd(i), do: i
** (ArgumentError) argument error
"""
defmacro lc(args)
@doc """
Defines a bit comprehension.
It follows the same syntax and behaviour as a list comprehension
but expects each element returned to be a bitstring. For example,
here is how to remove all spaces from a string:
iex> bc <<c>> inbits " hello world ", c != ? , do: <<c>>
iex> for <<c <- " hello world ">>, c != ?\s, into: "", do: <<c>>
"helloworld"
The `IO` module provides streams, that are both `Enumerable` and
`Collectable`, here is an upcase echo server using comprehensions:
for line <- IO.stream(:stdio, :line), into: IO.stream(:stdio, :line) do
String.upcase(line)
end
"""
defmacro bc(args)
defmacro for(args)
@doc """
Defines an anonymous function.
@@ -1099,16 +1219,18 @@ defmodule Kernel.SpecialForms do
and should not be invoked directly:
iex> quote do: (1; 2; 3)
{ :__block__, [], [1, 2, 3] }
{:__block__, [], [1, 2, 3]}
"""
defmacro __block__(args)
@doc """
Captures an anonymous function.
Captures or creates an anonymous function.
The most common format to capture a function is via module,
name and arity:
## Capture
The capture operator is most commonly used to capture a
function with given name and arity from a module:
iex> fun = &Kernel.is_atom/1
iex> fun.(:atom)
@@ -1116,66 +1238,56 @@ defmodule Kernel.SpecialForms do
iex> fun.("string")
false
Local functions, including private ones, and imported ones
can also be captured by omitting the module name:
In the example above, we captured `Kernel.is_atom/1` as an
anonymous function and then invoked it.
The capture operator can also be used to capture local functions,
including private ones, and imported functions by omitting the
module name:
&local_function/1
A capture also allows the captured functions to be partially
applied, for example:
## Anonymous functions
iex> fun = &is_record(&1, Range)
iex> fun.(1..3)
The capture operator can also be used to partially apply
functions, where `&1`, `&2` and so on can be used as value
placeholders. For example:
iex> double = &(&1 * 2)
iex> double.(2)
4
In other words, `&(&1 * 2)` is equivalent to `fn x -> x * 2 end`.
Another example using a local function:
iex> fun = &is_atom(&1)
iex> fun.(:atom)
true
In the example above, we use &1 as a placeholder, generating
a function with one argument. We could also use `&2` and `&3`
to delimit more arguments:
iex> fun = &is_record(&1, &2)
iex> fun.(1..3, Range)
true
Since operators are calls, they are also supported, although
they require explicit parentheses around:
iex> fun = &(&1 + &2)
iex> fun.(1, 2)
3
And even more complex call expressions:
The `&` operator can be used with more complex expressions:
iex> fun = &(&1 + &2 + &3)
iex> fun.(1, 2, 3)
6
Record-like calls are also allowed:
iex> fun = &(&1.first)
iex> fun.(1..3)
1
Remember tuple and lists are represented as calls in the AST and
therefore are also allowed:
As well as with lists and tuples:
iex> fun = &{&1, &2}
iex> fun.(1, 2)
{ 1, 2 }
{1, 2}
iex> fun = &[&1|&2]
iex> fun.(1, 2)
[1|2]
Anything that is not a call is not allowed though, examples:
The only restrictions when creating anonymous functions is that at
least one placeholder must be present, i.e. it must contain at least
`&1`:
# An atom is not a call
&:foo
# A var is not a call
var = 1
# No placeholder fails to compile
&var
# A block expression is not a call
# Block expressions are also not supported
&(foo(&1, &2); &3 + &4)
"""
@@ -1232,7 +1344,7 @@ defmodule Kernel.SpecialForms do
## Examples
case thing do
{ :selector, i, value } when is_integer(i) ->
{:selector, i, value} when is_integer(i) ->
value
value ->
value
@@ -1248,7 +1360,7 @@ defmodule Kernel.SpecialForms do
outer context:
case data do
{ :ok, value } -> value
{:ok, value} -> value
:error -> nil
end
@@ -1273,7 +1385,7 @@ defmodule Kernel.SpecialForms do
"""
defmacro case(condition, blocks)
@doc """
@doc ~S"""
Evaluate the given expressions and handle any error, exit
or throw that may have happened.
@@ -1286,10 +1398,10 @@ defmodule Kernel.SpecialForms do
IO.puts "Invalid argument given"
catch
value ->
IO.puts "caught \#{value}"
IO.puts "caught #{value}"
else
value ->
IO.puts "Success! The result was \#{value}"
IO.puts "Success! The result was #{value}"
after
IO.puts "This is printed regardless if it failed or succeed"
end
@@ -1335,10 +1447,39 @@ defmodule Kernel.SpecialForms do
x -> nil
end
## Catching exits and Erlang errors
## Erlang errors
The catch clause works exactly the same as in erlang. Therefore,
one can also handle exits/errors coming from Erlang as below:
Erlang errors are transformed into Elixir ones during rescue:
try do
:erlang.error(:badarg)
rescue
ArgumentError -> :ok
end
The most common Erlang errors will be transformed into their
Elixir counter-part. Those which are not will be transformed
into `ErlangError`:
try do
:erlang.error(:unknown)
rescue
ErlangError -> :ok
end
In fact, ErlangError can be used to rescue any error that is
not an Elixir error proper. For example, it can be used to rescue
the earlier `:badarg` error too, prior to transformation:
try do
:erlang.error(:badarg)
rescue
ErlangError -> :ok
end
## Catching throws and exits
The catch clause can be used to catch throws values and exits.
try do
exit(1)
@@ -1347,14 +1488,14 @@ defmodule Kernel.SpecialForms do
end
try do
error(:sample)
throw(:sample)
catch
:error, :sample ->
IO.puts "sample error"
:throw, :sample ->
IO.puts "sample thrown"
end
Although the second form should be avoided in favor of raise/rescue
control mechanisms.
catch values also support `:error`, as in Erlang, although it is
commonly avoided in favor of raise/rescue control mechanisms.
## Else clauses
@@ -1428,7 +1569,7 @@ defmodule Kernel.SpecialForms do
:exit_b
end
This means the VM nolonger needs to keep the stacktrace once inside
This means the VM no longer needs to keep the stacktrace once inside
an else clause and so tail recursion is possible when using a `try`
with a tail call as the final call inside an else clause. The same
is true for rescue and catch clauses.
@@ -1437,7 +1578,7 @@ defmodule Kernel.SpecialForms do
Since an expression inside `try` may not have been evaluated
due to an exception, any variable created inside `try` cannot
be accessed externaly. For instance:
be accessed externally. For instance:
try do
x = 1
@@ -1475,7 +1616,7 @@ defmodule Kernel.SpecialForms do
## Examples
receive do
{ :selector, i, value } when is_integer(i) ->
{:selector, i, value} when is_integer(i) ->
value
value when is_atom(value) ->
value
@@ -1487,7 +1628,7 @@ defmodule Kernel.SpecialForms do
received after the specified period of time:
receive do
{ :selector, i, value } when is_integer(i) ->
{:selector, i, value} when is_integer(i) ->
value
value when is_atom(value) ->
value
@@ -1499,7 +1640,7 @@ defmodule Kernel.SpecialForms do
end
The `after` clause can be specified even if there are no match clauses.
There are two special cases for the timout value given to after
There are two special cases for the timeout value given to `after`
* `:infinity` - The process should wait indefinitely for a matching
message, this is the same as not using a timeout.
+206 -176
View File
@@ -69,7 +69,7 @@ defmodule Kernel.Typespec do
Tuple :: tuple # a tuple of any size
| {} # empty tuple
| { TList }
| {TList}
TList :: Type
| Type, TList
@@ -119,7 +119,7 @@ defmodule Kernel.Typespec do
`iodata` | `iolist` &#124; `binary`
`iolist` | `maybe_improper_list(byte` &#124; `binary` &#124; `iolist, binary` &#124; `[])`
`module` | `atom`
`mfa` | `{ atom, atom, arity }`
`mfa` | `{atom, atom, arity}`
`arity` | `0..255`
`node` | `atom`
`timeout` | `:infinity` &#124; `non_neg_integer`
@@ -151,7 +151,7 @@ defmodule Kernel.Typespec do
Types can be parametrised by defining variables as parameters, these variables
can then be used to define the type.
@type dict(key, value) :: [{ key, value }]
@type dict(key, value) :: [{key, value}]
Types can also be defined for records, see `defrecord/3`.
@@ -269,12 +269,12 @@ defmodule Kernel.Typespec do
@doc """
Defines a `type`, `typep` or `opaque` by receiving Erlang's typespec.
"""
def define_type(caller, kind, { name, _, vars } = type) when kind in [:type, :typep, :opaque] do
{ kind, export } =
def define_type(caller, kind, {name, _, vars} = type) when kind in [:type, :typep, :opaque] do
{kind, export} =
case kind do
:type -> { :type, true }
:typep -> { :type, false }
:opaque -> { :opaque, true }
:type -> {:type, true}
:typep -> {:type, false}
:opaque -> {:opaque, true}
end
module = caller.module
@@ -283,7 +283,7 @@ defmodule Kernel.Typespec do
Module.compile_typespec module, kind, type
if export do
Module.compile_typespec(module, :export_type, [{ name, arity }])
Module.compile_typespec(module, :export_type, [{name, arity}])
end
define_doc(caller, kind, name, arity, export)
@@ -296,7 +296,7 @@ defmodule Kernel.Typespec do
if doc do
if export do
Module.add_doc(module, caller.line, kind, { name, arity }, doc)
Module.add_doc(module, caller.line, kind, {name, arity}, doc)
else
:elixir_errors.warn caller.line, caller.file, "type #{name}/#{arity} is private, " <>
"@typedoc's are always discarded for private types\n"
@@ -310,14 +310,14 @@ defmodule Kernel.Typespec do
Defines a `spec` by receiving Erlang's typespec.
"""
def define_spec(module, tuple, definition) do
Module.compile_typespec module, :spec, { tuple, definition }
Module.compile_typespec module, :spec, {tuple, definition}
end
@doc """
Defines a `callback` by receiving Erlang's typespec.
"""
def define_callback(module, tuple, definition) do
Module.compile_typespec module, :callback, { tuple, definition }
Module.compile_typespec module, :callback, {tuple, definition}
end
@doc """
@@ -326,7 +326,7 @@ defmodule Kernel.Typespec do
for modules being compiled.
"""
def defines_type?(module, name, arity) do
finder = &match?({ ^name, _, vars } when length(vars) == arity, &1)
finder = &match?({^name, _, vars} when length(vars) == arity, &1)
Enum.any?(Module.get_attribute(module, :type), finder) or
Enum.any?(Module.get_attribute(module, :opaque), finder)
end
@@ -336,7 +336,7 @@ defmodule Kernel.Typespec do
This function is only available for modules being compiled.
"""
def defines_spec?(module, name, arity) do
tuple = { name, arity }
tuple = {name, arity}
Enum.any?(Module.get_attribute(module, :spec), &match?(^tuple, &1))
end
@@ -345,69 +345,69 @@ defmodule Kernel.Typespec do
This function is only available for modules being compiled.
"""
def defines_callback?(module, name, arity) do
tuple = { name, arity }
tuple = {name, arity}
Enum.any?(Module.get_attribute(module, :callback), &match?(^tuple, &1))
end
@doc """
Converts a spec clause back to Elixir AST.
"""
def spec_to_ast(name, { :type, line, :fun, [{:type, _, :product, args}, result] }) do
def spec_to_ast(name, {:type, line, :fun, [{:type, _, :product, args}, result]}) do
meta = [line: line]
body = { name, meta, Enum.map(args, &typespec_to_ast/1) }
body = {name, meta, Enum.map(args, &typespec_to_ast/1)}
vars = args ++ [result]
|> Enum.flat_map(&collect_vars/1)
|> Enum.uniq
|> Enum.map(&{ &1, { :var, meta, nil } })
|> Enum.map(&{&1, {:var, meta, nil}})
result = if vars == [] do
typespec_to_ast(result)
spec = {:::, meta, [body, typespec_to_ast(result)]}
if vars == [] do
spec
else
{ :when, meta, [typespec_to_ast(result), vars] }
{:when, meta, [spec, vars]}
end
{ :::, meta, [body, result] }
end
def spec_to_ast(name, { :type, line, :fun, [] }) do
{ :::, [line: line], [{ name, [line: line], [] }, quote(do: term)] }
def spec_to_ast(name, {:type, line, :fun, []}) do
{:::, [line: line], [{name, [line: line], []}, quote(do: term)]}
end
def spec_to_ast(name, { :type, line, :bounded_fun, [{ :type, _, :fun, [{ :type, _, :product, args }, result] }, constraints] }) do
def spec_to_ast(name, {:type, line, :bounded_fun, [{:type, _, :fun, [{:type, _, :product, args}, result]}, constraints]}) do
guards =
lc {:type, _, :constraint, [{:atom, _, :is_subtype}, [{ :var, _, var }, type]]} inlist constraints do
{ var, typespec_to_ast(type) }
for {:type, _, :constraint, [{:atom, _, :is_subtype}, [{:var, _, var}, type]]} <- constraints do
{var, typespec_to_ast(type)}
end
meta = [line: line]
vars = args ++ [result]
|> Enum.flat_map(&collect_vars/1)
|> Enum.uniq
|> Kernel.--(Keyword.keys(guards))
|> Enum.map(&{ &1, { :var, meta, nil } })
|> Enum.flat_map(&collect_vars/1)
|> Enum.uniq
|> Kernel.--(Keyword.keys(guards))
|> Enum.map(&{&1, {:var, meta, nil}})
args = lc arg inlist args, do: typespec_to_ast(arg)
args = for arg <- args, do: typespec_to_ast(arg)
{ :::, meta, [
{ name, [line: line], args },
{ :when, meta, [typespec_to_ast(result), guards ++ vars] }
] }
{:when, meta, [
{:::, meta, [{name, [line: line], args}, typespec_to_ast(result)]},
guards ++ vars
]}
end
@doc """
Converts a type clause back to Elixir AST.
"""
def type_to_ast({ { :record, record }, fields, args }) when is_atom(record) do
fields = lc field inlist fields, do: typespec_to_ast(field)
args = lc arg inlist args, do: typespec_to_ast(arg)
type = { :{}, [], [record|fields] }
def type_to_ast({{:record, record}, fields, args}) when is_atom(record) do
fields = for field <- fields, do: typespec_to_ast(field)
args = for arg <- args, do: typespec_to_ast(arg)
type = {:{}, [], [record|fields]}
quote do: unquote(record)(unquote_splicing(args)) :: unquote(type)
end
def type_to_ast({ name, type, args }) do
args = lc arg inlist args, do: typespec_to_ast(arg)
def type_to_ast({name, type, args}) do
args = for arg <- args, do: typespec_to_ast(arg)
quote do: unquote(name)(unquote_splicing(args)) :: unquote(typespec_to_ast(type))
end
@@ -423,8 +423,8 @@ defmodule Kernel.Typespec do
@spec beam_typedocs(module | binary) :: [tuple] | nil
def beam_typedocs(module) when is_atom(module) or is_binary(module) do
case abstract_code(module) do
{ :ok, abstract_code } ->
type_docs = lc { :attribute, _, :typedoc, tup } inlist abstract_code, do: tup
{:ok, abstract_code} ->
type_docs = for {:attribute, _, :typedoc, tup} <- abstract_code, do: tup
List.flatten(type_docs)
_ ->
nil
@@ -443,15 +443,15 @@ defmodule Kernel.Typespec do
@spec beam_types(module | binary) :: [tuple] | nil
def beam_types(module) when is_atom(module) or is_binary(module) do
case abstract_code(module) do
{ :ok, abstract_code } ->
exported_types = lc { :attribute, _, :export_type, types } inlist abstract_code, do: types
{:ok, abstract_code} ->
exported_types = for {:attribute, _, :export_type, types} <- abstract_code, do: types
exported_types = List.flatten(exported_types)
lc { :attribute, _, kind, { name, _, args } = type } inlist abstract_code, kind in [:opaque, :type] do
for {:attribute, _, kind, {name, _, args} = type} <- abstract_code, kind in [:opaque, :type] do
cond do
kind == :opaque -> { :opaque, type }
{ name, length(args) } in exported_types -> { :type, type }
true -> { :typep, type }
kind == :opaque -> {:opaque, type}
{name, length(args)} in exported_types -> {:type, type}
true -> {:typep, type}
end
end
_ ->
@@ -489,8 +489,8 @@ defmodule Kernel.Typespec do
defp from_abstract_code(module, kind) do
case abstract_code(module) do
{ :ok, abstract_code } ->
lc { :attribute, _, abs_kind, value } inlist abstract_code, kind == abs_kind, do: value
{:ok, abstract_code} ->
for {:attribute, _, abs_kind, value} <- abstract_code, kind == abs_kind, do: value
:error ->
nil
end
@@ -498,8 +498,8 @@ defmodule Kernel.Typespec do
defp abstract_code(module) do
case :beam_lib.chunks(abstract_code_beam(module), [:abstract_code]) do
{:ok, { _, [{ :abstract_code, { _raw_abstract_v1, abstract_code } }] } } ->
{ :ok, abstract_code }
{:ok, {_, [{:abstract_code, {_raw_abstract_v1, abstract_code}}]}} ->
{:ok, abstract_code}
_ ->
:error
end
@@ -507,7 +507,7 @@ defmodule Kernel.Typespec do
defp abstract_code_beam(module) when is_atom(module) do
case :code.get_object_code(module) do
{ ^module, beam, _filename } -> beam
{^module, beam, _filename} -> beam
:error -> module
end
end
@@ -519,19 +519,19 @@ defmodule Kernel.Typespec do
## Macro callbacks
@doc false
def deftype(kind, { :::, _, [{ name, _, args }, definition] }, caller) when is_atom(name) and name != ::: do
def deftype(kind, {:::, _, [{name, _, args}, definition]}, caller) when is_atom(name) and name != ::: do
args =
if is_atom(args) do
[]
else
lc(arg inlist args, do: variable(arg))
for(arg <- args, do: variable(arg))
end
vars = lc { :var, _, var } inlist args, do: var
vars = for {:var, _, var} <- args, do: var
spec = typespec(definition, vars, caller)
vars = lc { :var, _, _ } = var inlist args, do: var
type = { name, spec, vars }
vars = for {:var, _, _} = var <- args, do: var
type = {name, spec, vars}
define_type(caller, kind, type)
end
@@ -542,9 +542,17 @@ defmodule Kernel.Typespec do
end
@doc false
def defspec(type, { :::, meta, [{ name, _, args }, return_and_guard] }, caller) when is_atom(name) and name != ::: do
def defspec(type, {:when, _meta, [spec, guard]}, caller) do
defspec(type, spec, guard, caller)
end
def defspec(type, spec, caller) do
defspec(type, spec, [], caller)
end
defp defspec(type, {:::, meta, [{name, _, args}, return]}, guard, caller) when is_atom(name) and name != ::: do
if is_atom(args), do: args = []
{ return, guard } = split_return_and_guard(return_and_guard)
unless Keyword.keyword?(guard) do
guard = Macro.to_string(guard)
@@ -554,70 +562,57 @@ defmodule Kernel.Typespec do
vars = Keyword.keys(guard)
constraints = guard_to_constraints(guard, vars, meta, caller)
spec = { :type, line(meta), :fun, fn_args(meta, args, return, vars, caller) }
spec = {:type, line(meta), :fun, fn_args(meta, args, return, vars, caller)}
if constraints != [] do
spec = { :type, line(meta), :bounded_fun, [spec, constraints] }
spec = {:type, line(meta), :bounded_fun, [spec, constraints]}
end
code = { { name, Kernel.length(args) }, spec }
code = {{name, Kernel.length(args)}, spec}
Module.compile_typespec(caller.module, type, code)
code
end
def defspec(_type, other, caller) do
spec = Macro.to_string(other)
defp defspec(_type, spec, _guard, caller) do
spec = Macro.to_string(spec)
compile_error caller, "invalid function type specification: #{spec}"
end
defp split_return_and_guard({ :when, _, [return, guard] }) do
{ return, guard }
end
defp split_return_and_guard({ :|, meta, [left, right] }) do
{ return, guard } = split_return_and_guard(right)
{ { :|, meta, [left, return] }, guard }
end
defp split_return_and_guard(other) do
{ other, [] }
end
defp guard_to_constraints(guard, vars, meta, caller) do
line = line(meta)
Enum.reduce(guard, [], fn
{ _name, { :var, _, context } }, acc when is_atom(context) ->
{_name, {:var, _, context}}, acc when is_atom(context) ->
acc
{ name, type }, acc ->
constraint = [{ :atom, line, :is_subtype }, [{:var, line, name}, typespec(type, vars, caller)]]
type = { :type, line, :constraint, constraint }
{name, type}, acc ->
constraint = [{:atom, line, :is_subtype}, [{:var, line, name}, typespec(type, vars, caller)]]
type = {:type, line, :constraint, constraint}
[type|acc]
end) |> Enum.reverse
end
## To AST conversion
defp collect_vars({ :ann_type, _line, args }) when is_list(args) do
defp collect_vars({:ann_type, _line, args}) when is_list(args) do
[]
end
defp collect_vars({ :type, _line, _kind, args }) when is_list(args) do
defp collect_vars({:type, _line, _kind, args}) when is_list(args) do
Enum.flat_map(args, &collect_vars/1)
end
defp collect_vars({ :remote_type, _line, args }) when is_list(args) do
defp collect_vars({:remote_type, _line, args}) when is_list(args) do
Enum.flat_map(args, &collect_vars/1)
end
defp collect_vars({ :typed_record_field, _line, type }) do
defp collect_vars({:typed_record_field, _line, type}) do
collect_vars(type)
end
defp collect_vars({ :paren_type, _line, [type] }) do
defp collect_vars({:paren_type, _line, [type]}) do
collect_vars(type)
end
defp collect_vars({ :var, _line, var }) do
defp collect_vars({:var, _line, var}) do
[erl_to_ex_var(var)]
end
@@ -625,28 +620,43 @@ defmodule Kernel.Typespec do
[]
end
defp typespec_to_ast({ :type, line, :tuple, :any }) do
{ :tuple, [line: line], [] }
defp typespec_to_ast({:type, line, :tuple, :any}) do
{:tuple, [line: line], []}
end
defp typespec_to_ast({ :type, line, :tuple, args }) do
args = lc arg inlist args, do: typespec_to_ast(arg)
{ :{}, [line: line], args }
defp typespec_to_ast({:type, line, :tuple, args}) do
args = for arg <- args, do: typespec_to_ast(arg)
{:{}, [line: line], args}
end
defp typespec_to_ast({ :type, _line, :list, [{ :type, _, :union, unions } = arg] }) do
defp typespec_to_ast({:type, _line, :list, [{:type, _, :union, unions} = arg]}) do
case unpack_typespec_kw(unions, []) do
{ :ok, ast } -> ast
{:ok, ast} -> ast
:error -> [typespec_to_ast(arg)]
end
end
defp typespec_to_ast({ :type, _line, :list, args }) do
lc arg inlist args, do: typespec_to_ast(arg)
defp typespec_to_ast({:type, _line, :list, args}) do
for arg <- args, do: typespec_to_ast(arg)
end
defp typespec_to_ast({ :type, line, :binary, [arg1, arg2] }) do
[arg1, arg2] = lc arg inlist [arg1, arg2], do: typespec_to_ast(arg)
defp typespec_to_ast({:type, line, :map, fields}) do
fields = Enum.map fields, fn {:type, _, :map_field_assoc, k, v} ->
{typespec_to_ast(k), typespec_to_ast(v)}
end
{struct, fields} = Keyword.pop(fields, :__struct__)
map = {:%{}, [line: line], fields}
if struct do
{:%, [line: line], [struct, map]}
else
map
end
end
defp typespec_to_ast({:type, line, :binary, [arg1, arg2]}) do
[arg1, arg2] = for arg <- [arg1, arg2], do: typespec_to_ast(arg)
cond do
arg2 == 0 ->
quote line: line, do: <<_ :: unquote(arg1)>>
@@ -657,75 +667,79 @@ defmodule Kernel.Typespec do
end
end
defp typespec_to_ast({ :type, line, :union, args }) do
args = lc arg inlist args, do: typespec_to_ast(arg)
Enum.reduce Enum.reverse(args), fn(arg, expr) -> { :|, [line: line], [arg, expr] } end
defp typespec_to_ast({:type, line, :union, args}) do
args = for arg <- args, do: typespec_to_ast(arg)
Enum.reduce Enum.reverse(args), fn(arg, expr) -> {:|, [line: line], [arg, expr]} end
end
defp typespec_to_ast({ :type, line, :fun, [{:type, _, :product, args}, result] }) do
args = lc arg inlist args, do: typespec_to_ast(arg)
[{ :->, [line: line], [args, typespec_to_ast(result)] }]
defp typespec_to_ast({:type, line, :fun, [{:type, _, :product, args}, result]}) do
args = for arg <- args, do: typespec_to_ast(arg)
[{:->, [line: line], [args, typespec_to_ast(result)]}]
end
defp typespec_to_ast({ :type, line, :fun, [args, result] }) do
[{ :->, [line: line], [[typespec_to_ast(args)], typespec_to_ast(result)] }]
defp typespec_to_ast({:type, line, :fun, [args, result]}) do
[{:->, [line: line], [[typespec_to_ast(args)], typespec_to_ast(result)]}]
end
defp typespec_to_ast({ :type, line, :fun, [] }) do
typespec_to_ast({ :type, line, :fun, [{:type, line, :any}, {:type, line, :any, []} ] })
defp typespec_to_ast({:type, line, :fun, []}) do
typespec_to_ast({:type, line, :fun, [{:type, line, :any}, {:type, line, :any, []} ]})
end
defp typespec_to_ast({ :type, line, :range, [left, right] }) do
{ :"..", [line: line], [typespec_to_ast(left), typespec_to_ast(right)] }
defp typespec_to_ast({:type, line, :range, [left, right]}) do
{:"..", [line: line], [typespec_to_ast(left), typespec_to_ast(right)]}
end
defp typespec_to_ast({ :type, line, name, args }) do
args = lc arg inlist args, do: typespec_to_ast(arg)
{ name, [line: line], args }
defp typespec_to_ast({:type, line, name, args}) do
args = for arg <- args, do: typespec_to_ast(arg)
{name, [line: line], args}
end
defp typespec_to_ast({ :var, line, var }) do
{ erl_to_ex_var(var), line, nil }
defp typespec_to_ast({:var, line, var}) do
{erl_to_ex_var(var), line, nil}
end
defp typespec_to_ast({ :op, line, op, arg }) do
{ op, [line: line], [typespec_to_ast(arg)] }
defp typespec_to_ast({:op, line, op, arg}) do
{op, [line: line], [typespec_to_ast(arg)]}
end
# Special shortcut(s)
defp typespec_to_ast({ :remote_type, line, [{:atom, _, :elixir}, {:atom, _, :char_list}, []] }) do
defp typespec_to_ast({:remote_type, line, [{:atom, _, :elixir}, {:atom, _, :char_list}, []]}) do
typespec_to_ast({:type, line, :char_list, []})
end
defp typespec_to_ast({ :remote_type, line, [{:atom, _, :elixir}, {:atom, _, :as_boolean}, [arg]] }) do
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
defp typespec_to_ast({ :remote_type, line, [mod, name, args] }) do
args = lc arg inlist args, do: typespec_to_ast(arg)
dot = { :., [line: line], [typespec_to_ast(mod), typespec_to_ast(name)] }
{ dot, [line: line], args }
defp typespec_to_ast({:remote_type, line, [mod, name, args]}) do
args = for arg <- args, do: typespec_to_ast(arg)
dot = {:., [line: line], [typespec_to_ast(mod), typespec_to_ast(name)]}
{dot, [line: line], args}
end
defp typespec_to_ast({ :ann_type, line, [var, type] }) do
{ :::, [line: line], [typespec_to_ast(var), typespec_to_ast(type)] }
defp typespec_to_ast({:ann_type, line, [var, type]}) do
{:::, [line: line], [typespec_to_ast(var), typespec_to_ast(type)]}
end
defp typespec_to_ast({ :typed_record_field,
{ :record_field, line, { :atom, line1, name } },
type }) do
typespec_to_ast({ :ann_type, line, [{ :var, line1, name }, type] })
defp typespec_to_ast({:typed_record_field,
{:record_field, line, {:atom, line1, name}},
type}) do
typespec_to_ast({:ann_type, line, [{:var, line1, name}, type]})
end
defp typespec_to_ast({ :type, _, :any }) do
defp typespec_to_ast({:type, _, :any}) do
quote do: ...
end
defp typespec_to_ast({ :paren_type, _, [type] }) do
defp typespec_to_ast({:paren_type, _, [type]}) do
typespec_to_ast(type)
end
defp typespec_to_ast({ t, _line, atom }) when is_atom(t) do
defp typespec_to_ast({t, _line, atom}) when is_atom(t) do
atom
end
@@ -744,16 +758,16 @@ defmodule Kernel.Typespec do
defp line(meta) do
case :lists.keyfind(:line, 1, meta) do
{ :line, line } -> line
{:line, line} -> line
false -> 0
end
end
# Handle unions
defp typespec({ :|, meta, [_, _] } = exprs, vars, caller) do
defp typespec({:|, meta, [_, _]} = exprs, vars, caller) do
exprs = collect_union(exprs)
union = lc e inlist exprs, do: typespec(e, vars, caller)
{ :type, line(meta), :union, union }
union = for e <- exprs, do: typespec(e, vars, caller)
{:type, line(meta), :union, union}
end
# Handle binaries
@@ -769,6 +783,18 @@ defmodule Kernel.Typespec do
{:type, line(meta), :binary, [{:integer, line(meta1), base}, {:integer, line(meta2), 0}]}
end
## Handle maps and structs
defp typespec({:%{}, meta, fields}, vars, caller) do
fields = Enum.map(fields, fn {k, v} ->
{:type, line(meta), :map_field_assoc, typespec(k, vars, caller), typespec(v, vars, caller)}
end)
{:type, line(meta), :map, fields}
end
defp typespec({:%, _, [name, {:%{}, meta, fields}]}, vars, caller) do
typespec({:%{}, meta, [{:__struct__, name}|fields]}, vars, caller)
end
# Handle ranges
defp typespec({:.., meta, args}, vars, caller) do
typespec({:range, meta, args}, vars, caller)
@@ -787,25 +813,25 @@ defmodule Kernel.Typespec do
# Handle funs
defp typespec([{:->, meta, [arguments, return]}], vars, caller) when is_list(arguments) do
args = fn_args(meta, arguments, return, vars, caller)
{ :type, line(meta), :fun, args }
{:type, line(meta), :fun, args}
end
# Handle type operator
defp typespec({:::, meta, [var, expr] }, vars, caller) do
defp typespec({:::, meta, [var, expr]}, vars, caller) do
left = typespec(var, [elem(var, 0)|vars], caller)
right = typespec(expr, vars, caller)
{ :ann_type, line(meta), [left, right] }
{:ann_type, line(meta), [left, right]}
end
# Handle unary ops
defp typespec({op, meta, [integer]}, _, _) when op in [:+, :-] and is_integer(integer) do
{ :op, line(meta), op, {:integer, line(meta), integer} }
{:op, line(meta), op, {:integer, line(meta), integer}}
end
# Handle access macro
defp typespec({{:., meta, [Kernel, :access]}, meta1, [target, args]}, vars, caller) do
access = {{:., meta, [Kernel, :access]}, meta1,
[target, args ++ [_: { :any, [], [] }]]}
[target, args ++ [_: {:any, [], []}]]}
typespec(Macro.expand(access, caller), vars, caller)
end
@@ -820,16 +846,16 @@ defmodule Kernel.Typespec do
# Handle tuples
defp typespec({:tuple, meta, args}, _vars, _caller) when args == [] or is_atom(args) do
{ :type, line(meta), :tuple, :any }
{:type, line(meta), :tuple, :any}
end
defp typespec({:{}, meta, t}, vars, caller) when is_list(t) do
args = lc e inlist t, do: typespec(e, vars, caller)
{ :type, line(meta), :tuple, args }
args = for e <- t, do: typespec(e, vars, caller)
{:type, line(meta), :tuple, args}
end
defp typespec({ left, right }, vars, caller) do
typespec({ :{}, [], [left, right] }, vars, caller)
defp typespec({left, right}, vars, caller) do
typespec({:{}, [], [left, right]}, vars, caller)
end
# Handle blocks
@@ -840,7 +866,7 @@ defmodule Kernel.Typespec do
# Handle variables or local calls
defp typespec({name, meta, atom}, vars, caller) when is_atom(atom) do
if name in vars do
{ :var, line(meta), name }
{:var, line(meta), name}
else
typespec({name, meta, []}, vars, caller)
end
@@ -849,51 +875,55 @@ defmodule Kernel.Typespec do
# Handle local calls
defp typespec({:string, meta, arguments}, vars, caller) do
:elixir_errors.warn caller.line, caller.file, "string() type use is discouraged. For character lists, use " <>
"char_list() type, for strings, String.t()\n#{Exception.format_stacktrace(caller.stacktrace)}"
arguments = lc arg inlist arguments, do: typespec(arg, vars, caller)
{ :type, line(meta), :string, arguments }
"char_list() type, for strings, String.t()\n#{Exception.format_stacktrace(Macro.Env.stacktrace(caller))}"
arguments = for arg <- arguments, do: typespec(arg, vars, caller)
{:type, line(meta), :string, arguments}
end
defp typespec({:char_list, _meta, arguments}, vars, caller) do
typespec((quote do: :elixir.char_list(unquote_splicing(arguments))), vars, caller)
defp typespec({:char_list, _meta, []}, vars, caller) do
typespec((quote do: :elixir.char_list()), vars, caller)
end
defp typespec({:as_boolean, _meta, arguments}, vars, caller) do
typespec((quote do: :elixir.as_boolean(unquote_splicing(arguments))), vars, caller)
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
defp typespec({name, meta, arguments}, vars, caller) do
arguments = lc arg inlist arguments, do: typespec(arg, vars, caller)
{ :type, line(meta), name, arguments }
arguments = for arg <- arguments, do: typespec(arg, vars, caller)
{:type, line(meta), name, arguments}
end
# Handle literals
defp typespec(atom, _, _) when is_atom(atom) do
{ :atom, 0, atom }
{:atom, 0, atom}
end
defp typespec(integer, _, _) when is_integer(integer) do
{ :integer, 0, integer }
{:integer, 0, integer}
end
defp typespec([], vars, caller) do
typespec({ nil, [], [] }, vars, caller)
typespec({nil, [], []}, vars, caller)
end
defp typespec([spec], vars, caller) do
typespec({ :list, [], [spec] }, vars, caller)
typespec({:list, [], [spec]}, vars, caller)
end
defp typespec([spec, {:"...", _, quoted}], vars, caller) when is_atom(quoted) do
typespec({ :nonempty_list, [], [spec] }, vars, caller)
typespec({:nonempty_list, [], [spec]}, vars, caller)
end
defp typespec(list, vars, caller) do
[h|t] = Enum.reverse(list)
union = Enum.reduce(t, validate_kw(h, list, caller), fn(x, acc) ->
{ :|, [], [validate_kw(x, list, caller), acc] }
{:|, [], [validate_kw(x, list, caller), acc]}
end)
typespec({ :list, [], [union] }, vars, caller)
typespec({:list, [], [union]}, vars, caller)
end
## Helpers
@@ -903,14 +933,14 @@ defmodule Kernel.Typespec do
end
defp remote_type({remote, meta, name, arguments}, vars, caller) do
arguments = lc arg inlist arguments, do: typespec(arg, vars, caller)
{ :remote_type, line(meta), [ remote, name, arguments ] }
arguments = for arg <- arguments, do: typespec(arg, vars, caller)
{:remote_type, line(meta), [ remote, name, arguments ]}
end
defp collect_union({ :|, _, [a, b] }), do: [a|collect_union(b)]
defp collect_union({:|, _, [a, b]}), do: [a|collect_union(b)]
defp collect_union(v), do: [v]
defp validate_kw({ key, _ } = t, _, _caller) when is_atom(key), do: t
defp validate_kw({key, _} = t, _, _caller) when is_atom(key), do: t
defp validate_kw(_, original, caller) do
compile_error(caller, "unexpected list in typespec: #{Macro.to_string original}")
end
@@ -923,24 +953,24 @@ defmodule Kernel.Typespec do
end
defp fn_args(meta, [{:"...", _, _}], _vars, _caller) do
{ :type, line(meta), :any }
{:type, line(meta), :any}
end
defp fn_args(meta, args, vars, caller) do
args = lc arg inlist args, do: typespec(arg, vars, caller)
{ :type, line(meta), :product, args }
args = for arg <- args, do: typespec(arg, vars, caller)
{:type, line(meta), :product, args}
end
defp variable({name, meta, _}) do
{:var, line(meta), name}
end
defp unpack_typespec_kw([{ :type, _, :tuple, [{ :atom, _, atom }, type] }|t], acc) do
defp unpack_typespec_kw([{:type, _, :tuple, [{:atom, _, atom}, type]}|t], acc) do
unpack_typespec_kw(t, [{atom, typespec_to_ast(type)}|acc])
end
defp unpack_typespec_kw([], acc) do
{ :ok, :lists.reverse(acc) }
{:ok, :lists.reverse(acc)}
end
defp unpack_typespec_kw(_, _acc) do
+83 -69
View File
@@ -23,27 +23,19 @@ defmodule Keyword do
in `Enum` and `List` can also be applied.
"""
@behaviour Dict
@type key :: atom
@type value :: any
@type t :: [{key, value}]
@type t(value) :: [{ key, value }]
@doc """
Creates a Keyword from an enum. Unlike `Keyword.new`
which behaves as a dict, `Keyword.from_enum` does not remove
duplicated entries.
"""
@spec from_enum(Enum.t) :: t
def from_enum(enum) do
Enum.to_list(enum)
end
@type t(value) :: [{key, value}]
@doc """
Checks if the given argument is a keywords list or not.
"""
@spec keyword?(term) :: boolean
def keyword?([{ key, _value } | rest]) when is_atom(key) do
def keyword?([{key, _value} | rest]) when is_atom(key) do
keyword?(rest)
end
@@ -59,8 +51,11 @@ defmodule Keyword do
end
@doc """
Creates a Keyword from an enumerable. Similar to dicts,
duplicated entries are removed, the latest one prevails.
Creates a keyword from an enumerable.
Duplicated entries are removed, the latest one prevails.
I.e. differently from `Enum.into(enumerable, [])`,
`Keyword.new(enumerable)` guarantees the keys are unique.
## Examples
@@ -70,15 +65,17 @@ defmodule Keyword do
"""
@spec new(Enum.t) :: t
def new(pairs) do
Enum.reduce pairs, [], fn { k, v }, keywords ->
Enum.reduce pairs, [], fn {k, v}, keywords ->
put(keywords, k, v)
end
end
@doc """
Creates a Keyword from an enumerable with the
help of the transformation function. Duplicated
entries are removed, the latest one prevails.
Creates a keyword from an enumerable via the transformation function.
Duplicated entries are removed, the latest one prevails.
I.e. differently from `Enum.into(enumerable, [], fun)`,
`Keyword.new(enumerable, fun)` guarantees the keys are unique.
## Examples
@@ -89,7 +86,7 @@ defmodule Keyword do
@spec new(Enum.t, ({key, value} -> {key, value})) :: t
def new(pairs, transform) do
Enum.reduce pairs, [], fn i, keywords ->
{ k, v } = transform.(i)
{k, v} = transform.(i)
put(keywords, k, v)
end
end
@@ -116,9 +113,9 @@ defmodule Keyword do
"""
@spec get(t, key) :: value
@spec get(t, key, value) :: value
def get(keywords, key, default \\ nil) when is_atom(key) do
def get(keywords, key, default \\ nil) when is_list(keywords) and is_atom(key) do
case :lists.keyfind(key, 1, keywords) do
{ ^key, value } -> value
{^key, value} -> value
false -> default
end
end
@@ -130,16 +127,16 @@ defmodule Keyword do
## Examples
iex> Keyword.fetch([a: 1], :a)
{ :ok, 1 }
{:ok, 1}
iex> Keyword.fetch([a: 1], :b)
:error
"""
@spec fetch(t, key) :: value
def fetch(keywords, key) when is_atom(key) do
@spec fetch(t, key) :: {:ok, value}
def fetch(keywords, key) when is_list(keywords) and is_atom(key) do
case :lists.keyfind(key, 1, keywords) do
{ ^key, value } -> { :ok, value }
{^key, value} -> {:ok, value}
false -> :error
end
end
@@ -154,14 +151,14 @@ defmodule Keyword do
1
iex> Keyword.fetch!([a: 1], :b)
** (KeyError) key not found: :b
** (KeyError) key :b not found in: [a: 1]
"""
@spec fetch!(t, key) :: value | no_return
def fetch!(keywords, key) when is_atom(key) do
def fetch!(keywords, key) when is_list(keywords) and is_atom(key) do
case :lists.keyfind(key, 1, keywords) do
{ ^key, value } -> value
false -> raise(KeyError, key: key)
{^key, value} -> value
false -> raise(KeyError, key: key, term: keywords)
end
end
@@ -175,8 +172,8 @@ defmodule Keyword do
"""
@spec get_values(t, key) :: [value]
def get_values(keywords, key) when is_atom(key) do
lc { k, v } inlist keywords, key == k, do: v
def get_values(keywords, key) when is_list(keywords) and is_atom(key) do
for {k, v} <- keywords, key == k, do: v
end
@doc """
@@ -193,8 +190,8 @@ defmodule Keyword do
"""
@spec keys(t) :: [key]
def keys(keywords) do
lc { key, _ } inlist keywords, do: key
def keys(keywords) when is_list(keywords) do
for {key, _} <- keywords, do: key
end
@doc """
@@ -207,8 +204,8 @@ defmodule Keyword do
"""
@spec values(t) :: [value]
def values(keywords) do
lc { _, value } inlist keywords, do: value
def values(keywords) when is_list(keywords) do
for {_, value} <- keywords, do: value
end
@doc """
@@ -228,8 +225,8 @@ defmodule Keyword do
"""
@spec delete(t, key, value) :: t
def delete(keywords, key, value) when is_atom(key) do
lc { k, v } = tuple inlist keywords, key != k or value != v, do: tuple
def delete(keywords, key, value) when is_list(keywords) and is_atom(key) do
for {k, v} = tuple <- keywords, key != k or value != v, do: tuple
end
@doc """
@@ -251,8 +248,8 @@ defmodule Keyword do
"""
@spec delete(t, key) :: t
def delete(keywords, key) when is_atom(key) do
lc { k, _ } = tuple inlist keywords, key != k, do: tuple
def delete(keywords, key) when is_list(keywords) and is_atom(key) do
for {k, _} = tuple <- keywords, key != k, do: tuple
end
@doc """
@@ -269,7 +266,7 @@ defmodule Keyword do
"""
@spec delete_first(t, key) :: t
def delete_first(keywords, key) when is_atom(key) do
def delete_first(keywords, key) when is_list(keywords) and is_atom(key) do
:lists.keydelete(key, 1, keywords)
end
@@ -289,7 +286,7 @@ defmodule Keyword do
"""
@spec put(t, key, value) :: t
def put(keywords, key, value) when is_atom(key) do
def put(keywords, key, value) when is_list(keywords) and is_atom(key) do
[{key, value}|delete(keywords, key)]
end
@@ -298,6 +295,7 @@ defmodule Keyword do
already exists.
## Examples
iex> Keyword.put_new([a: 1], :b, 2)
[b: 2, a: 1]
@@ -306,9 +304,9 @@ defmodule Keyword do
"""
@spec put_new(t, key, value) :: t
def put_new(keywords, key, value) when is_atom(key) do
def put_new(keywords, key, value) when is_list(keywords) and is_atom(key) do
case :lists.keyfind(key, 1, keywords) do
{ ^key, _ } -> keywords
{^key, _} -> keywords
false -> [{key, value}|keywords]
end
end
@@ -340,7 +338,7 @@ defmodule Keyword do
"""
@spec merge(t, t) :: t
def merge(d1, d2) when is_list(d1) and is_list(d2) do
d2 ++ lc({ k, _ } = tuple inlist d1, not has_key?(d2, k), do: tuple)
d2 ++ for({k, _} = tuple <- d1, not has_key?(d2, k), do: tuple)
end
@doc """
@@ -351,7 +349,7 @@ defmodule Keyword do
iex> Keyword.merge([a: 1, b: 2], [a: 3, d: 4], fn (_k, v1, v2) ->
...> v1 + v2
iex> end)
...> end)
[a: 4, b: 2, d: 4]
"""
@@ -360,7 +358,7 @@ defmodule Keyword do
do_merge(d2, d1, fun)
end
defp do_merge([{ k, v2 }|t], acc, fun) do
defp do_merge([{k, v2}|t], acc, fun) do
do_merge t, update(acc, k, v2, fn(v1) -> fun.(k, v1, v2) end), fun
end
@@ -381,7 +379,7 @@ defmodule Keyword do
"""
@spec has_key?(t, key) :: boolean
def has_key?(keywords, key) when is_atom(key) do
def has_key?(keywords, key) when is_list(keywords) and is_atom(key) do
:lists.keymember(key, 1, keywords)
end
@@ -395,20 +393,24 @@ defmodule Keyword do
[a: 2]
iex> Keyword.update!([a: 1], :b, &(&1 * 2))
** (KeyError) key not found: :b
** (KeyError) key :b not found in: [a: 1]
"""
@spec update!(t, key, (value -> value)) :: t | no_return
def update!([{key, value}|keywords], key, fun) do
def update!(keywords, key, fun) do
update!(keywords, key, fun, keywords)
end
defp update!([{key, value}|keywords], key, fun, _dict) do
[{key, fun.(value)}|delete(keywords, key)]
end
def update!([{_, _} = e|keywords], key, fun) do
[e|update!(keywords, key, fun)]
defp update!([{_, _} = e|keywords], key, fun, dict) do
[e|update!(keywords, key, fun, dict)]
end
def update!([], key, _fun) when is_atom(key) do
raise(KeyError, key: key)
defp update!([], key, _fun, dict) when is_atom(key) do
raise(KeyError, key: key, term: dict)
end
@doc """
@@ -445,24 +447,24 @@ defmodule Keyword do
iex> d = [a: 1, b: 2, c: 3, d: 4]
iex> Keyword.split(d, [:a, :c, :e])
{ [a: 1, c: 3], [b: 2, d: 4] }
{[a: 1, c: 3], [b: 2, d: 4]}
iex> d = [a: 1, b: 2, c: 3, d: 4, a: 5]
iex> Keyword.split(d, [:a, :c, :e])
{ [a: 1, c: 3, a: 5], [b: 2, d: 4] }
{[a: 1, c: 3, a: 5], [b: 2, d: 4]}
"""
def split(dict, keys) do
acc = { [], [] }
def split(keywords, keys) when is_list(keywords) do
acc = {[], []}
{ take, drop } = Enum.reduce dict, acc, fn({ k, v }, { take, drop }) ->
{take, drop} = Enum.reduce keywords, acc, fn({k, v}, {take, drop}) ->
case k in keys do
true -> { [{k, v}|take], drop }
false -> { take, [{k, v}|drop] }
true -> {[{k, v}|take], drop}
false -> {take, [{k, v}|drop]}
end
end
{ Enum.reverse(take), Enum.reverse(drop) }
{Enum.reverse(take), Enum.reverse(drop)}
end
@doc """
@@ -480,8 +482,8 @@ defmodule Keyword do
[a: 1, c: 3, a: 5]
"""
def take(dict, keys) do
lc { k, _ } = tuple inlist dict, k in keys, do: tuple
def take(keywords, keys) when is_list(keywords) do
for {k, _} = tuple <- keywords, k in keys, do: tuple
end
@doc """
@@ -499,8 +501,8 @@ defmodule Keyword do
[a: 1, c: 3, a: 5]
"""
def drop(dict, keys) do
lc { k, _ } = tuple inlist dict, not k in keys, do: tuple
def drop(keywords, keys) when is_list(keywords) do
for {k, _} = tuple <- keywords, not k in keys, do: tuple
end
@doc """
@@ -528,8 +530,8 @@ defmodule Keyword do
{1,[]}
"""
def pop(dict, key, default \\ nil) do
{ get(dict, key, default), delete(dict, key) }
def pop(keywords, key, default \\ nil) when is_list(keywords) do
{get(keywords, key, default), delete(keywords, key)}
end
@doc """
@@ -557,7 +559,19 @@ defmodule Keyword do
{1,[a: 2]}
"""
def pop_first(dict, key, default \\ nil) do
{ get(dict, key, default), delete_first(dict, key) }
def pop_first(keywords, key, default \\ nil) when is_list(keywords) do
{get(keywords, key, default), delete_first(keywords, key)}
end
# Dict callbacks
@doc false
def size(keyword) do
length(keyword)
end
@doc false
def to_list(keyword) do
keyword
end
end
+74 -8
View File
@@ -47,6 +47,7 @@ defmodule List do
iex> List.duplicate([1, 2], 2)
[[1,2],[1,2]]
"""
@spec duplicate(elem, non_neg_integer) :: [elem] when elem: var
def duplicate(elem, n) do
@@ -123,8 +124,10 @@ defmodule List do
iex> List.first([])
nil
iex> List.first([1])
1
iex> List.first([1, 2, 3])
1
@@ -140,8 +143,10 @@ defmodule List do
iex> List.last([])
nil
iex> List.last([1])
1
iex> List.last([1, 2, 3])
3
@@ -159,10 +164,10 @@ defmodule List do
## Examples
iex> List.keyfind([a: 1, b: 2], :a, 0)
{ :a, 1 }
{:a, 1}
iex> List.keyfind([a: 1, b: 2], 2, 1)
{ :b, 2 }
{:b, 2}
iex> List.keyfind([a: 1, b: 2], :c, 0)
nil
@@ -201,7 +206,7 @@ defmodule List do
## Examples
iex> List.keyreplace([a: 1, b: 2], :a, 0, { :a, 3 })
iex> List.keyreplace([a: 1, b: 2], :a, 0, {:a, 3})
[a: 3, b: 2]
"""
@@ -235,10 +240,10 @@ defmodule List do
## Examples
iex> List.keystore([a: 1, b: 2], :a, 0, { :a, 3 })
iex> List.keystore([a: 1, b: 2], :a, 0, {:a, 3})
[a: 3, b: 2]
iex> List.keystore([a: 1, b: 2], :c, 0, { :c, 3 })
iex> List.keystore([a: 1, b: 2], :c, 0, {:c, 3})
[a: 1, b: 2, c: 3]
"""
@@ -448,6 +453,67 @@ 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.
## Examples
iex> List.from_char_data("æß")
{:ok, 'æß'}
iex> List.from_char_data([?a, "bc"])
{:ok, 'abc'}
"""
@spec from_char_data(char_data) :: {:ok, char_list} | {:error, list, binary} | {:incomplete, list, binary}
def from_char_data(char_data) do
case :unicode.characters_to_list(char_data) do
result when is_list(result) ->
{:ok, result}
{:error, _, _} = error ->
error
{:incomplete, _, _} = incomplete ->
incomplete
end
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
def from_char_data!(char_data) do
case :unicode.characters_to_list(char_data) do
result when is_list(result) ->
result
{:error, encoded, rest} ->
raise UnicodeConversionError, encoded: encoded, rest: rest, kind: :invalid
{:incomplete, encoded, rest} ->
raise UnicodeConversionError, encoded: encoded, rest: rest, kind: :incomplete
end
end
## Helpers
# replace_at
@@ -531,15 +597,15 @@ defmodule List do
end
defp do_zip_each(_, nil) do
{ nil, nil }
{nil, nil}
end
defp do_zip_each([h|t], acc) do
{ t, [h|acc] }
{t, [h|acc]}
end
defp do_zip_each([], _) do
{ nil, nil }
{nil, nil}
end
defp to_list(tuple) when is_tuple(tuple), do: tuple_to_list(tuple)
+2 -2
View File
@@ -1,5 +1,5 @@
defprotocol List.Chars do
@moduledoc %S"""
@moduledoc ~S"""
The List.Chars protocol is responsible for
converting a structure to a list (only if applicable).
The only function required to be implemented is
@@ -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
String.to_char_list!(thing)
List.from_char_data!(thing)
end
def to_char_list(thing) do
-151
View File
@@ -1,151 +0,0 @@
defmodule ListDict do
@moduledoc """
A Dict implementation that works on lists of two-items tuples.
This dictionary is only recommended for keeping a small amount
of values. Other dict alternatives are more viable for keeping
any other amount than a handful.
For more information about the functions and their APIs, please
consult the `Dict` module.
"""
@doc """
Returns a new `ListDict`, i.e. an empty list.
"""
def new, do: []
@doc """
Creates a new `ListDict` from the given pairs.
"""
def new(pairs) do
Enum.to_list pairs
end
@doc """
Creates a new `ListDict` from the given pairs
via the given transformation function.
"""
def new(list, transform) when is_function(transform) do
Enum.map list, transform
end
def keys(dict) do
lc { key, _ } inlist dict, do: key
end
def values(dict) do
lc { _, value } inlist dict, do: value
end
def size(dict) do
length(dict)
end
def has_key?(dict, key)
def has_key?([{ key, _ }|_], key), do: true
def has_key?([{ _, _ }|t], key), do: has_key?(t, key)
def has_key?([], _key), do: false
def get(dict, key, default \\ nil)
def get([{ key, value }|_], key, _default), do: value
def get([{ _, _ }|t], key, default), do: get(t, key, default)
def get([], _key, default), do: default
def fetch(dict, key)
def fetch([{ key, value }|_], key), do: { :ok, value }
def fetch([{ _, _ }|t], key), do: fetch(t, key)
def fetch([], _key), do: :error
def fetch!(dict, key) do
case fetch(dict, key) do
{ :ok, value } -> value
:error -> raise(KeyError, key: key)
end
end
def pop(dict, key, default \\ nil) do
{ get(dict, key, default), delete(dict, key) }
end
def put(dict, key, val) do
[{key, val}|delete(dict, key)]
end
def put_new(dict, key, val) do
case has_key?(dict, key) do
true -> dict
false -> [{key, val}|dict]
end
end
def delete(dict, key)
def delete([{ key, _ }|t], key), do: t
def delete([{ _, _ } = h|t], key), do: [h|delete(t, key)]
def delete([], _key), do: []
def merge(dict, enum, callback \\ fn(_k, _v1, v2) -> v2 end) do
Enum.reduce enum, dict, fn { k, v2 }, acc ->
update(acc, k, v2, fn(v1) -> callback.(k, v1, v2) end)
end
end
def split(dict, keys) do
acc = { [], [] }
{take, drop} = Enum.reduce dict, acc, fn({ k, v }, { take, drop }) ->
if k in keys do
{ [{k, v}|take], drop }
else
{ take, [{k, v}|drop] }
end
end
{Enum.reverse(take), Enum.reverse(drop)}
end
def take(dict, keys) do
lc { k, _ } = tuple inlist dict, k in keys, do: tuple
end
def drop(dict, keys) do
lc { k, _ } = tuple inlist dict, not k in keys, do: tuple
end
def update!([{key, value}|dict], key, fun) do
[{key, fun.(value)}|delete(dict, key)]
end
def update!([{_, _} = e|dict], key, fun) do
[e|update!(dict, key, fun)]
end
def update!([], key, _fun) do
raise(KeyError, key: key)
end
def update([{key, value}|dict], key, _initial, fun) do
[{key, fun.(value)}|delete(dict, key)]
end
def update([{_, _} = e|dict], key, initial, fun) do
[e|update(dict, key, initial, fun)]
end
def update([], key, initial, _fun) do
[{key, initial}]
end
def empty(_dict), do: []
def equal?(dict, other) do
:lists.keysort(1, dict) === :lists.keysort(1, other)
end
def reduce(_, { :halt, acc }, _fun), do: { :halted, acc }
def reduce(list, { :suspend, acc }, fun), do: { :suspended, acc, &reduce(list, &1, fun) }
def reduce([], { :cont, acc }, _fun), do: { :done, acc }
def reduce([{_,_}=h|t], { :cont, acc }, fun), do: reduce(t, fun.(h, acc), fun)
def to_list(dict), do: dict
end
+164 -130
View File
@@ -6,14 +6,14 @@ defmodule Macro do
"""
@typedoc "Abstract Syntax Tree (AST)"
@type t :: expr | { t, t } | atom | number | binary | pid | fun | [t]
@type t :: expr | {t, t} | atom | number | binary | pid | fun | [t]
@typedoc "Expr node (remaining ones are literals)"
@type expr :: { expr | atom, Keyword.t, atom | [t] }
@type expr :: {expr | atom, Keyword.t, atom | [t]}
@binary_ops [:===, :!==,
:==, :!=, :<=, :>=,
:&&, :||, :<>, :++, :--, ://, :\\, :::, :<-, :.., :|>, :=~,
:&&, :||, :<>, :++, :--, :\\, :::, :<-, :.., :|>, :=~,
:<, :>, :->,
:+, :-, :*, :/, :=, :|, :.,
:and, :or, :xor, :when, :in, :inlist, :inbits,
@@ -27,10 +27,10 @@ defmodule Macro do
@doc false
defmacro unary_ops, do: @unary_ops
@spec binary_op_props(atom) :: { :left | :right, precedence :: integer }
@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 [:<-, :inlist, :inbits, :\\, :::] -> {:left, 40}
:| -> {:right, 50}
:when -> {:right, 70}
:= -> {:right, 80}
@@ -53,31 +53,30 @@ defmodule Macro do
Raises if the pipeline is ill-formed.
"""
@spec unpipe(Macro.t) :: [Macro.t]
def unpipe({ :|> , _, [left, right] }) do
[left|unpipe(right)]
def unpipe({:|> , _, [left, right]}) do
[{left, 0}|unpipe(right)]
end
def unpipe(other) do
[other]
[{other, 0}]
end
@doc """
Pipes `expr` into the `call_expr` as the
argument in the given `position`.
Pipes `expr` into the `call_args` at the given `position`.
"""
@spec pipe(Macro.t, Macro.t, integer) :: Macro.t | no_return
def pipe(expr, call_args, integer \\ 0)
def pipe(expr, call_args, position)
def pipe(expr, { :&, _, _ } = call_args, _integer) do
def pipe(expr, {:&, _, _} = call_args, _integer) do
raise ArgumentError, message: "cannot pipe #{to_string expr} into #{to_string call_args}"
end
def pipe(expr, { call, line, atom }, integer) when is_atom(atom) do
{ call, line, List.insert_at([], integer, expr) }
def pipe(expr, {call, line, atom}, integer) when is_atom(atom) do
{call, line, List.insert_at([], integer, expr)}
end
def pipe(expr, { call, line, args }, integer) when is_list(args) do
{ call, line, List.insert_at(args, integer, expr) }
def pipe(expr, {call, line, args}, integer) when is_list(args) do
{call, line, List.insert_at(args, integer, expr)}
end
def pipe(expr, call_args, _integer) do
@@ -104,16 +103,16 @@ defmodule Macro do
@spec update_meta(t, (Keyword.t -> Keyword.t)) :: t
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) }
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) }
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
lc x inlist list, do: update_meta(x, fun)
for x <- list, do: update_meta(x, fun)
end
def update_meta(other, _fun) do
@@ -129,30 +128,30 @@ defmodule Macro do
## Examples
iex> Macro.decompose_call(quote do: foo)
{ :foo, [] }
{:foo, []}
iex> Macro.decompose_call(quote do: foo())
{ :foo, [] }
{:foo, []}
iex> Macro.decompose_call(quote do: foo(1, 2, 3))
{ :foo, [1, 2, 3] }
{:foo, [1, 2, 3]}
iex> Macro.decompose_call(quote do: Elixir.M.foo(1, 2, 3))
{ { :__aliases__, [], [:Elixir, :M] }, :foo, [1, 2, 3] }
{{:__aliases__, [], [:Elixir, :M]}, :foo, [1, 2, 3]}
iex> Macro.decompose_call(quote do: 42)
:error
"""
@spec decompose_call(Macro.t) :: { atom, [Macro.t] } | { Macro.t, atom, [Macro.t] } | :error
def decompose_call({ { :., _, [remote, function] }, _, args }) when is_tuple(remote) or is_atom(remote),
do: { remote, function, args }
@spec decompose_call(Macro.t) :: {atom, [Macro.t]} | {Macro.t, atom, [Macro.t]} | :error
def decompose_call({{:., _, [remote, function]}, _, args}) when is_tuple(remote) or is_atom(remote),
do: {remote, function, args}
def decompose_call({ name, _, args }) when is_atom(name) and is_atom(args),
do: { name, [] }
def decompose_call({name, _, args}) when is_atom(name) and is_atom(args),
do: {name, []}
def decompose_call({ name, _, args }) when is_atom(name) and is_list(args),
do: { name, args }
def decompose_call({name, _, args}) when is_atom(name) and is_list(args),
do: {name, args}
def decompose_call(_),
do: :error
@@ -170,10 +169,10 @@ defmodule Macro do
iex> Macro.escape(:foo)
:foo
iex> Macro.escape({ :a, :b, :c })
{ :{}, [], [:a, :b, :c] }
iex> Macro.escape({:a, :b, :c})
{:{}, [], [:a, :b, :c]}
iex> Macro.escape({ :unquote, [], [1] }, unquote: true)
iex> Macro.escape({:unquote, [], [1]}, unquote: true)
1
"""
@@ -183,7 +182,7 @@ defmodule Macro do
elem(:elixir_quote.escape(expr, Keyword.get(opts, :unquote, false)), 0)
end
@doc %S"""
@doc ~S"""
Unescape the given chars.
This is the unescaping behaviour used by default in Elixir
@@ -195,7 +194,7 @@ defmodule Macro do
also escaped according to the latin1 set they represent.
This function is commonly used on sigil implementations
(like `%r`, `%s` and others) which receive a raw, unescaped
(like `~r`, `~s` and others) which receive a raw, unescaped
string.
## Examples
@@ -211,7 +210,7 @@ defmodule Macro do
:elixir_interpolation.unescape_chars(chars)
end
@doc %S"""
@doc ~S"""
Unescape the given chars according to the map given.
Check `unescape_string/1` if you want to use the same map
@@ -300,27 +299,27 @@ defmodule Macro do
def to_string(tree, fun \\ fn(_ast, string) -> string end)
# Variables
def to_string({ var, _, atom } = ast, fun) when is_atom(atom) do
def to_string({var, _, atom} = ast, fun) when is_atom(atom) do
fun.(ast, atom_to_binary(var))
end
# Aliases
def to_string({ :__aliases__, _, refs } = ast, fun) do
def to_string({:__aliases__, _, refs} = ast, fun) do
fun.(ast, Enum.map_join(refs, ".", &call_to_string(&1, fun)))
end
# Blocks
def to_string({ :__block__, _, [expr] } = ast, fun) do
def to_string({:__block__, _, [expr]} = ast, fun) do
fun.(ast, to_string(expr, fun))
end
def to_string({ :__block__, _, _ } = ast, fun) do
def to_string({:__block__, _, _} = ast, fun) do
block = adjust_new_lines block_to_string(ast, fun), "\n "
fun.(ast, "(\n " <> block <> "\n)")
end
# Bits containers
def to_string({ :<<>>, _, args } = ast, fun) do
def to_string({:<<>>, _, args} = ast, fun) do
fun.(ast, case Enum.map_join(args, ", ", &to_string(&1, fun)) do
"<" <> rest -> "<< <" <> rest <> " >>"
rest -> "<<" <> rest <> ">>"
@@ -328,33 +327,45 @@ defmodule Macro do
end
# Tuple containers
def to_string({ :{}, _, args } = ast, fun) do
def to_string({:{}, _, args} = ast, fun) do
tuple = "{" <> Enum.map_join(args, ", ", &to_string(&1, fun)) <> "}"
fun.(ast, tuple)
end
# Map containers
def to_string({:%{}, _, args} = ast, fun) do
map = "%{" <> map_to_string(args, fun) <> "}"
fun.(ast, map)
end
def to_string({:%, _, [structname, map]} = ast, fun) do
{:%{}, _, args} = map
struct = "%" <> to_string(structname, fun) <> "{" <> map_to_string(args, fun) <> "}"
fun.(ast, struct)
end
# Fn keyword
def to_string({ :fn, _, [{ :->, _, [_, tuple] }] = arrow } = ast, fun)
def to_string({:fn, _, [{:->, _, [_, tuple]}] = arrow} = ast, fun)
when not is_tuple(tuple) or elem(tuple, 0) != :__block__ do
fun.(ast, "fn " <> arrow_to_string(arrow, fun) <> " end")
end
def to_string({ :fn, _, [{ :->, _, _ }] = block } = ast, fun) do
def to_string({:fn, _, [{:->, _, _}] = block} = ast, fun) do
fun.(ast, "fn " <> block_to_string(block, fun) <> "\nend")
end
def to_string({ :fn, _, block } = ast, fun) do
def to_string({:fn, _, block} = ast, fun) do
block = adjust_new_lines block_to_string(block, fun), "\n "
fun.(ast, "fn\n " <> block <> "\nend")
end
# left -> right
def to_string([{ :->, _, _ }|_] = ast, fun) do
def to_string([{:->, _, _}|_] = ast, fun) do
fun.(ast, "(" <> arrow_to_string(ast, fun, true) <> ")")
end
# left when right
def to_string({ :when, _, [left, right] } = ast, fun) do
def to_string({:when, _, [left, right]} = ast, fun) do
if right != [] and Keyword.keyword?(right) do
right = kw_list_to_string(right, fun)
else
@@ -365,33 +376,38 @@ defmodule Macro do
end
# Binary ops
def to_string({ op, _, [left, right] } = ast, fun) when op in unquote(@binary_ops) do
def to_string({op, _, [left, right]} = ast, fun) when op in unquote(@binary_ops) do
fun.(ast, op_to_string(left, fun, op, :left) <> " #{op} " <> op_to_string(right, fun, op, :right))
end
# Splat when
def to_string({ :when, _, args } = ast, fun) do
{ left, right } = :elixir_utils.split_last(args)
def to_string({:when, _, args} = ast, fun) do
{left, right} = :elixir_utils.split_last(args)
fun.(ast, "(" <> Enum.map_join(left, ", ", &to_string(&1, fun)) <> ") when " <> to_string(right, fun))
end
# Unary ops
def to_string({ :not, _, [arg] } = ast, fun) do
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) <> ")")
end
def to_string({:not, _, [arg]} = ast, fun) do
fun.(ast, "not " <> to_string(arg, fun))
end
def to_string({ op, _, [arg] } = ast, fun) when op in unquote(@unary_ops) do
def to_string({op, _, [arg]} = ast, fun) when op in unquote(@unary_ops) do
fun.(ast, atom_to_binary(op) <> to_string(arg, fun))
end
# Access
def to_string({ { :., _, [Kernel, :access] }, _, [left, right] } = ast, fun) do
def to_string({{:., _, [Kernel, :access]}, _, [left, right]} = ast, fun) do
fun.(ast, to_string(left, fun) <> to_string(right, fun))
end
# All other calls
def to_string({ target, _, args } = ast, fun) when is_list(args) do
{ list, last } = :elixir_utils.split_last(args)
def to_string({target, _, args} = ast, fun) when is_list(args) do
{list, last} = :elixir_utils.split_last(args)
fun.(ast, case kw_blocks?(last) do
true -> call_to_string_with_args(target, list, fun) <> kw_blocks_to_string(last, fun)
false -> call_to_string_with_args(target, args, fun)
@@ -399,8 +415,8 @@ defmodule Macro do
end
# Two-item tuples
def to_string({ left, right }, fun) do
to_string({ :{}, [], [left, right] }, fun)
def to_string({left, right}, fun) do
to_string({:{}, [], [left, right]}, fun)
end
# Lists
@@ -409,7 +425,7 @@ defmodule Macro do
list == [] ->
"[]"
:io_lib.printable_list(list) ->
"'" <> Inspect.BitString.escape(String.from_char_list!(list), ?') <> "'"
"'" <> Inspect.BitString.escape(String.from_char_data!(list), ?') <> "'"
Keyword.keyword?(list) ->
"[" <> kw_list_to_string(list, fun) <> "]"
true ->
@@ -432,8 +448,8 @@ defmodule Macro do
defp module_to_string(other, fun), do: call_to_string(other, fun)
defp call_to_string(atom, _fun) when is_atom(atom), do: atom_to_binary(atom)
defp call_to_string({ :., _, [arg] }, fun), do: module_to_string(arg, fun) <> "."
defp call_to_string({ :., _, [left, right] }, fun), do: module_to_string(left, fun) <> "." <> call_to_string(right, fun)
defp call_to_string({:., _, [arg]}, fun), do: module_to_string(arg, fun) <> "."
defp call_to_string({:., _, [left, right]}, fun), do: module_to_string(left, fun) <> "." <> call_to_string(right, fun)
defp call_to_string(other, fun), do: to_string(other, fun)
defp call_to_string_with_args(target, args, fun) do
@@ -443,7 +459,7 @@ defmodule Macro do
end
defp args_to_string(args, fun) do
{ list, last } = :elixir_utils.split_last(args)
{list, last} = :elixir_utils.split_last(args)
if last != [] and Keyword.keyword?(last) do
args = Enum.map_join(list, ", ", &to_string(&1, fun))
@@ -468,32 +484,49 @@ defmodule Macro do
atom_to_binary(key) <> "\n " <> block <> "\n"
end
defp block_to_string([{ :->, _, _ }|_] = block, fun) do
Enum.map_join(block, "\n", fn({ :->, _, [left, right] }) ->
defp block_to_string([{:->, _, _}|_] = block, fun) do
Enum.map_join(block, "\n", fn({:->, _, [left, right]}) ->
left = comma_join_or_empty_paren(left, fun, false)
left <> "->\n " <> adjust_new_lines block_to_string(right, fun), "\n "
end)
end
defp block_to_string({ :__block__, _, exprs }, fun) do
defp block_to_string({:__block__, _, exprs}, fun) do
Enum.map_join(exprs, "\n", &to_string(&1, fun))
end
defp block_to_string(other, fun), do: to_string(other, fun)
defp map_to_string([{:|, _, [update_map, update_args]}], fun) do
to_string(update_map, fun) <> " | " <> map_to_string(update_args, fun)
end
defp map_to_string(list, fun) do
cond do
Keyword.keyword?(list) -> kw_list_to_string(list, fun)
true -> map_list_to_string(list, fun)
end
end
defp kw_list_to_string(list, fun) do
Enum.map_join(list, ", ", fn { key, value } ->
Enum.map_join(list, ", ", fn {key, value} ->
atom_to_binary(key) <> ": " <> to_string(value, fun)
end)
end
defp map_list_to_string(list, fun) do
Enum.map_join(list, ", ", fn {key, value} ->
to_string(key, fun) <> " => " <> to_string(value, fun)
end)
end
defp parenthise(expr, fun) do
"(" <> to_string(expr, fun) <> ")"
end
defp op_to_string({ op, _, [_, _] } = expr, fun, parent_op, side) when op in unquote(@binary_ops) do
{ parent_assoc, parent_prec } = binary_op_props(parent_op)
{ _, prec } = binary_op_props(op)
defp op_to_string({op, _, [_, _]} = expr, fun, parent_op, side) when op in unquote(@binary_ops) do
{parent_assoc, parent_prec} = binary_op_props(parent_op)
{_, prec} = binary_op_props(op)
cond do
parent_prec < prec -> to_string(expr, fun)
parent_prec > prec -> parenthise(expr, fun)
@@ -510,7 +543,7 @@ defmodule Macro do
defp op_to_string(expr, fun, _, _), do: to_string(expr, fun)
defp arrow_to_string(pairs, fun, paren \\ false) do
Enum.map_join(pairs, "; ", fn({ :->, _, [left, right] }) ->
Enum.map_join(pairs, "; ", fn({:->, _, [left, right]}) ->
left = comma_join_or_empty_paren(left, fun, paren)
left <> "-> " <> to_string(right, fun)
end)
@@ -524,11 +557,11 @@ defmodule Macro do
end
defp adjust_new_lines(block, replacement) do
bc <<x>> inbits block do
<< case x == ?\n do
for <<x <- block>>, into: "" do
case x == ?\n do
true -> replacement
false -> <<x>>
end :: binary >>
end
end
end
@@ -576,7 +609,7 @@ defmodule Macro do
The compilation will fail because `My.Module` when quoted
is not an atom, but a syntax tree as follow:
{:__aliases__, [], [:My, :Module] }
{:__aliases__, [], [:My, :Module]}
That said, we need to expand the aliases node above to an
atom, so we can retrieve its length. Expanding the node is
@@ -612,129 +645,129 @@ defmodule Macro do
elem(do_expand_once(ast, env), 0)
end
defp do_expand_once({ :__aliases__, _, _ } = original, env) do
defp do_expand_once({:__aliases__, _, _} = original, env) do
case :elixir_aliases.expand(original, env.aliases, env.macro_aliases, env.lexical_tracker) do
receiver when is_atom(receiver) ->
:elixir_lexical.record_remote(receiver, env.lexical_tracker)
{ receiver, true }
{receiver, true}
aliases ->
aliases = lc alias inlist aliases, do: elem(do_expand_once(alias, env), 0)
aliases = for alias <- aliases, do: elem(do_expand_once(alias, env), 0)
case :lists.all(&is_atom/1, aliases) do
true ->
receiver = :elixir_aliases.concat(aliases)
:elixir_lexical.record_remote(receiver, env.lexical_tracker)
{ receiver, true }
{receiver, true}
false ->
{ original, false }
{original, false}
end
end
end
# Expand @ calls
defp do_expand_once({ :@, _, [{ name, _, args }] } = original, env) when is_atom(args) or args == [] do
defp do_expand_once({:@, _, [{name, _, args}]} = original, env) when is_atom(args) or args == [] do
case (module = env.module) && Module.open?(module) do
true -> { Module.get_attribute(module, name), true }
false -> { original, false }
true -> {escape(Module.get_attribute(module, name)), true}
false -> {original, false}
end
end
# Expand pseudo-variables
defp do_expand_once({ :__MODULE__, _, atom }, env) when is_atom(atom),
do: { env.module, true }
defp do_expand_once({ :__DIR__, _, atom }, env) when is_atom(atom),
do: { :filename.dirname(env.file), true }
defp do_expand_once({ :__ENV__, _, atom }, env) when is_atom(atom),
do: { { :{}, [], tuple_to_list(env) }, true }
defp do_expand_once({ { :., _, [{ :__ENV__, _, atom }, field] }, _, [] } = original, env) when
defp do_expand_once({:__MODULE__, _, atom}, env) when is_atom(atom),
do: {env.module, true}
defp do_expand_once({:__DIR__, _, atom}, env) when is_atom(atom),
do: {:filename.dirname(env.file), true}
defp do_expand_once({:__ENV__, _, atom}, env) when is_atom(atom),
do: {{:%{}, [], Map.to_list(env)}, true}
defp do_expand_once({{:., _, [{:__ENV__, _, atom}, field]}, _, []} = original, env) when
is_atom(atom) and is_atom(field) do
case :erlang.function_exported(Macro.Env, field, 1) do
true -> { apply(env, field, []), true }
false -> { original, false }
if Map.has_key?(env, field) do
{Map.get(env, field), true}
else
{original, false}
end
end
# Expand possible macro import invocation
defp do_expand_once({ atom, meta, context } = original, env)
defp do_expand_once({atom, meta, context} = original, env)
when is_atom(atom) and is_list(meta) and is_atom(context) do
if :lists.member({ atom, Keyword.get(meta, :counter, context) }, env.vars) do
{ original, false }
if :lists.member({atom, Keyword.get(meta, :counter, context)}, env.vars) do
{original, false}
else
case do_expand_once({ atom, meta, [] }, env) do
{ _, true } = exp -> exp
{ _, false } -> { original, false }
case do_expand_once({atom, meta, []}, env) do
{_, true} = exp -> exp
{_, false} -> {original, false}
end
end
end
defp do_expand_once({ atom, meta, args } = original, env)
defp do_expand_once({atom, meta, args} = original, env)
when is_atom(atom) and is_list(args) and is_list(meta) do
arity = length(args)
if :elixir_import.special_form(atom, arity) do
{ original, false }
{original, false}
else
module = env.module
extra = if function_exported?(module, :__info__, 1) do
[{ module, module.__info__(:macros) }]
[{module, module.__info__(:macros)}]
else
[]
end
expand = :elixir_dispatch.expand_import(meta, { atom, length(args) }, args,
:elixir_env.ex_to_env(env), extra)
expand = :elixir_dispatch.expand_import(meta, {atom, length(args)}, args,
env, extra)
case expand do
{ :ok, receiver, quoted } ->
{:ok, receiver, quoted} ->
next = :elixir_counter.next
{ :elixir_quote.linify_with_context_counter(0, { receiver, next }, quoted), true }
{ :ok, _receiver } ->
{ original, false }
{:elixir_quote.linify_with_context_counter(0, {receiver, next}, quoted), true}
{:ok, _receiver} ->
{original, false}
:error ->
{ original, false }
{original, false}
end
end
end
# Expand possible macro require invocation
defp do_expand_once({ { :., _, [left, right] }, meta, args } = original, env) when is_atom(right) do
{ receiver, _ } = do_expand_once(left, env)
defp do_expand_once({{:., _, [left, right]}, meta, args} = original, env) when is_atom(right) do
{receiver, _} = do_expand_once(left, env)
case is_atom(receiver) do
false -> { original, false }
false -> {original, false}
true ->
expand = :elixir_dispatch.expand_require(meta, receiver, { right, length(args) },
args, :elixir_env.ex_to_env(env))
expand = :elixir_dispatch.expand_require(meta, receiver, {right, length(args)}, args, env)
case expand do
{ :ok, receiver, quoted } ->
{:ok, receiver, quoted} ->
next = :elixir_counter.next
{ :elixir_quote.linify_with_context_counter(0, { receiver, next }, quoted), true }
{:elixir_quote.linify_with_context_counter(0, {receiver, next}, quoted), true}
:error ->
{ original, false }
{original, false}
end
end
end
# Anything else is just returned
defp do_expand_once(other, _env), do: { other, false }
defp do_expand_once(other, _env), do: {other, false}
@doc """
Receives an AST node and expands it until it no longer represents
a macro.
Receives an AST node and expands it until it can no longer
be expanded.
Check `expand_once/2` for more information on how
expansion works.
This function uses `expand_once/2` under the hood. Check
`expand_once/2` for more information and exmaples.
"""
def expand(tree, env) do
expand_until({ tree, true }, env)
expand_until({tree, true}, env)
end
defp expand_until({ tree, true }, env) do
defp expand_until({tree, true}, env) do
expand_until(do_expand_once(tree, env), env)
end
defp expand_until({ tree, false }, _env) do
defp expand_until({tree, false}, _env) do
tree
end
@@ -744,22 +777,23 @@ defmodule Macro do
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 }`.
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
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
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({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(terms) when is_tuple(terms), do: {:unsafe, terms}
defp do_safe_term(_), do: nil
end
+56 -33
View File
@@ -1,6 +1,6 @@
defmodule Macro.Env do
@moduledoc """
A record that holds compile time environment information.
A struct that holds compile time environment information.
The current environment can be accessed at any time as
`__ENV__`. Inside macros, the caller environment can be
@@ -9,7 +9,7 @@ defmodule Macro.Env do
* `module` - the current module name.
* `file` - the current file name as a binary
* `line` - the current line as an integer
* `function` - a tuple as `{ atom, integer` }, where the first
* `function` - a tuple as `{atom, integer`}, where the first
element is the function name and the seconds its arity. Returns
`nil` if not inside a function
* `context` - the context of the environment. It can be nil
@@ -21,76 +21,99 @@ defmodule Macro.Env do
* `macros` - a list of macros imported from each module
* `macro_aliases` - a list of aliases defined inside the current macro
* `context_modules` - a list of modules defined in the current context
* `vars` - a list keeping all defined variables as { var, context }
* `vars` - a list keeping all defined variables as {var, context}
* `export_vars` - a list keeping all variables to be exported in a construct (may be nil)
* `lexical_tracker` - PID to the lexical tracker which is responsible to keep user info
* `local` - the module to expand local functions to
"""
@type name_arity :: { atom, non_neg_integer }
@type name_arity :: {atom, non_neg_integer}
@type file :: binary
@type line :: non_neg_integer
@type aliases :: [{ module, module }]
@type macro_aliases :: [{ module, { integer, module } }]
@type aliases :: [{module, module}]
@type macro_aliases :: [{module, {integer, module}}]
@type context :: :match | :guard | nil
@type requires :: [module]
@type functions :: [{ module, [name_arity] }]
@type macros :: [{ module, [name_arity] }]
@type functions :: [{module, [name_arity]}]
@type macros :: [{module, [name_arity]}]
@type context_modules :: [module]
@type vars :: [{ atom, atom | non_neg_integer }]
@type vars :: [{atom, atom | non_neg_integer}]
@type export_vars :: vars | nil
@type lexical_tracker :: pid
@type local :: module | nil
fields = [:module, :file, :line, :function, :context, :requires, :aliases, :functions,
:macros, :macro_aliases, :context_modules, :vars, :export_vars, :lexical_tracker,
:local]
@type t :: %{__struct__: __MODULE__,
module: module,
file: file,
line: line,
function: name_arity | nil,
context: context,
requires: requires,
aliases: aliases,
functions: functions,
macros: macros,
macro_aliases: aliases,
context_modules: context_modules,
vars: vars,
export_vars: export_vars,
lexical_tracker: lexical_tracker,
local: local}
types = quote do: [module: module, file: file, line: line,
function: name_arity, context: context, requires: requires, aliases: aliases,
functions: functions, macros: macros, macro_aliases: aliases,
context_modules: context_modules, vars: vars, export_vars: export_vars,
lexical_tracker: lexical_tracker, local: local]
Record.deffunctions(fields, __MODULE__)
Record.deftypes(fields, types, __MODULE__)
def __struct__ do
%{__struct__: __MODULE__,
module: nil,
file: "nofile",
line: 0,
function: nil,
context: nil,
requires: [],
aliases: [],
functions: [],
macros: [],
macro_aliases: [],
context_modules: [],
vars: [],
export_vars: nil,
lexical_tracker: nil,
local: nil}
end
@doc """
Returns a keyword list containing the file and line
information as keys.
"""
def location(record) do
[file: file(record), line: line(record)]
def location(%{__struct__: Macro.Env, file: file, line: line}) do
[file: file, line: line]
end
@doc """
Returns whether the compilation environment is currently
inside a guard.
"""
def in_guard?(record), do: context(record) == :guard
def in_guard?(%{__struct__: Macro.Env, context: context}), do: context == :guard
@doc """
Returns whether the compilation environment is currently
inside a match clause.
"""
def in_match?(record), do: context(record) == :match
def in_match?(%{__struct__: Macro.Env, context: context}), do: context == :match
@doc """
Returns the environment stacktrace.
"""
def stacktrace(record) do
def stacktrace(%{__struct__: Macro.Env} = env) do
cond do
nil?(record.module) ->
[{ :elixir_compiler, :__FILE__, 1, relative_location(record) }]
nil?(record.function) ->
[{ module(record), :__MODULE__, 0, relative_location(record) }]
nil?(env.module) ->
[{:elixir_compiler, :__FILE__, 1, relative_location(env)}]
nil?(env.function) ->
[{env.module, :__MODULE__, 0, relative_location(env)}]
true ->
{ name, arity } = record.function
[{ module(record), name, arity, relative_location(record) }]
{name, arity} = env.function
[{env.module, name, arity, relative_location(env)}]
end
end
defp relative_location(record) do
[file: Path.relative_to_cwd(file(record)), line: line(record)]
defp relative_location(env) do
[file: Path.relative_to_cwd(env.file), line: env.line]
end
end
+82
View File
@@ -0,0 +1,82 @@
defmodule Map do
@moduledoc """
A Dict implementation that works on maps.
Maps are key-value stores where keys are compared using
the match operator (`===`). Maps can be created with
the `%{}` special form defined in the `Kernel.SpecialForms`
module.
For more information about the functions in this module and
their APIs, please consult the `Dict` module.
"""
use Dict.Behaviour
defdelegate [keys(map), values(map), size(map), merge(map1, map2), to_list(map)], to: :maps
@doc """
Returns a new empty map.
"""
def new, do: %{}
def has_key?(map, key), do: :maps.is_key(key, map)
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
:maps.fold fn k, v2, acc ->
update(acc, k, v2, fn(v1) -> callback.(k, v1, v2) end)
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
end
+116 -93
View File
@@ -1,17 +1,11 @@
defmodule Module do
defmacrop is_env(env) do
quote do
is_tuple(unquote(env)) and size(unquote(env)) > 1 and elem(unquote(env), 0) == Macro.Env
end
end
@moduledoc %S'''
@moduledoc ~S'''
This module provides many functions to deal with modules during
compilation time. It allows a developer to dynamically attach
documentation, add, delete and register attributes and so forth.
After a module is compiled, using many of the functions in
this module will raise errors, since it is out of their purpose
this module will raise errors, since it is out of their scope
to inspect runtime data. Most of the runtime data can be inspected
via the `__info__(attr)` function attached to each compiled module.
@@ -24,7 +18,7 @@ defmodule Module do
A hook that will be invoked right after the current module is compiled.
Accepts a module or a tuple `{ <module>, <function atom> }`. The function
Accepts a module or a tuple `{<module>, <function atom>}`. The function
must take two arguments: the module environment and its bytecode.
When just a module is provided, the function is assumed to be
`__after_compile__/2`.
@@ -43,7 +37,7 @@ defmodule Module do
A hook that will be invoked before the module is compiled.
Accepts a module or a tuple `{ <module>, <function/macro atom> }`. The
Accepts a module or a tuple `{<module>, <function/macro atom>}`. The
function/macro must take one argument: the module environment. If it's a
macro, its returned value will be injected at the end of the module definition
before the compilation starts.
@@ -51,7 +45,7 @@ defmodule Module do
When just a module is provided, the function/macro is assumed to be
`__before_compile__/1`.
Note: differently from `@after_compile`, the callback function/macro must
Note: unlike `@after_compile`, the callback function/macro must
be placed in a separate module (because when the callback is invoked,
the current module does not yet exist).
@@ -94,7 +88,7 @@ defmodule Module do
### Example
defmodule M do
@compile { :inline, myfun: 1 }
@compile {:inline, myfun: 1}
def myfun(arg) do
to_string(arg)
@@ -164,31 +158,34 @@ defmodule Module do
* `@on_definition`
A hook that will be invoked after each function or macro in the current
A hook that will be invoked when each function or macro in the current
module is defined. Useful when annotating functions.
Accepts a module or a tuple `{ <module>, <function atom> }`. The function
Accepts a module or a tuple `{<module>, <function atom>}`. The function
must take 6 arguments:
- the module environment
- kind: `:def`, `:defp`, `:defmacro`, or `:defmacrop`
- function/macro name
- list of quoted arguments
- list of quoted guards
- quoted function body
- list of expanded arguments
- list of expanded guards
- expanded function body
Note the hook receives the expanded arguments and it is invoked before
the function is stored in the module. So `Module.defines?/2` will return
false for the first clause of every function.
If the function/macro being defined has multiple clauses, the hook will
be called for each clause.
Unlike other hooks, `@on_definition` will only invoke functions
and never macros. This is because the hook is invoked inside the context
of the function (and nested function definitions are not allowed in
Elixir).
When just a module is provided, the function is assumed to be
`__on_definition__/6`.
Note that you can't provide the current module to `@on_definition`
because the hook function will not be defined in time. Finally, since
the `on_definition` hook is executed inside the context of the defined
function (i.e. `env.function` returns the current function), the hook
can only be a function, not a macro.
### Example
defmodule H do
@@ -203,7 +200,7 @@ defmodule Module do
end
defmodule M do
@on_definition { H, :on_def }
@on_definition {H, :on_def}
def hello(arg) when is_binary(arg) or is_list(arg) do
"Hello" <> to_string(arg)
@@ -290,7 +287,7 @@ defmodule Module do
* `:docs` - list of all docstrings attached to functions and macros
using the `@doc` attribute
* `:moduledoc` - tuple `{ <line>, <doc> }` where `line` is the line on
* `:moduledoc` - tuple `{<line>, <doc>}` where `line` is the line on
which module definition starts and `doc` is the string
attached to the module using the `@moduledoc` attribute
@@ -308,15 +305,14 @@ defmodule Module do
and its attributes and functions can be modified.
"""
def open?(module) do
table = data_table_for(module)
table == :ets.info(table, :name)
:elixir_module.is_open(module)
end
@doc """
Evaluates the quoted contents in the given module's context.
A list of environment options can also be given as argument.
See `Code.eval_string` for more information.
See `Code.eval_string/3` for more information.
Raises an error if the module was already compiled.
@@ -342,20 +338,20 @@ defmodule Module do
"""
def eval_quoted(module, quoted, binding \\ [], opts \\ [])
def eval_quoted(env, quoted, binding, opts) when is_env(env) do
eval_quoted(env.module, quoted, binding, Keyword.merge(env.to_keywords, opts))
def eval_quoted(%Macro.Env{} = env, quoted, binding, opts) do
eval_quoted(env.module, quoted, binding, Keyword.merge(Map.to_list(env), opts))
end
def eval_quoted(module, quoted, binding, env) when is_env(env) do
eval_quoted(module, quoted, binding, env.to_keywords)
def eval_quoted(module, quoted, binding, %Macro.Env{} = env) do
eval_quoted(module, quoted, binding, Map.to_list(env))
end
def eval_quoted(module, quoted, binding, opts) do
assert_not_compiled!(:eval_quoted, module)
:elixir_def.reset_last(module)
{ value, binding, _env, _scope } =
{value, binding, _env, _scope} =
:elixir.eval_quoted quoted, binding, Keyword.put(opts, :module, module)
{ value, binding }
{value, binding}
end
@doc """
@@ -370,7 +366,7 @@ defmodule Module do
def world, do: true
end
Module.create(Hello, contents, __ENV__.location)
Module.create(Hello, contents, Macro.Env.location(__ENV__))
Hello.world #=> true
@@ -389,8 +385,8 @@ defmodule Module do
"""
def create(module, quoted, opts \\ [])
def create(module, quoted, env) when is_env(env) do
create(module, quoted, env.to_keywords)
def create(module, quoted, %Macro.Env{} = env) do
create(module, quoted, Map.to_list(env))
end
def create(module, quoted, opts) when is_atom(module) do
@@ -404,6 +400,7 @@ defmodule Module do
iex> Module.concat([Foo, Bar])
Foo.Bar
iex> Module.concat([Foo, "Bar"])
Foo.Bar
@@ -420,6 +417,7 @@ defmodule Module do
iex> Module.concat(Foo, Bar)
Foo.Bar
iex> Module.concat(Foo, "Bar")
Foo.Bar
@@ -493,7 +491,7 @@ defmodule Module do
## Examples
defmodule MyModule do
Module.add_doc(__MODULE__, __ENV__.line + 1, :def, { :version, 0 }, [], "Manually added docs")
Module.add_doc(__MODULE__, __ENV__.line + 1, :def, {:version, 0}, [], "Manually added docs")
def version, do: 1
end
@@ -501,7 +499,7 @@ defmodule Module do
def add_doc(module, line, kind, tuple, signature \\ [], doc)
def add_doc(_module, _line, kind, _tuple, _signature, doc) when kind in [:defp, :defmacrop, :typep] do
if doc, do: { :error, :private_doc }, else: :ok
if doc, do: {:error, :private_doc}, else: :ok
end
def add_doc(module, line, kind, tuple, signature, doc) when
@@ -509,50 +507,68 @@ defmodule Module do
assert_not_compiled!(:add_doc, module)
table = docs_table_for(module)
{ signature, _ } = Enum.map_reduce signature, 1, fn(x, acc) ->
{ simplify_signature(x, line, acc), acc + 1 }
{signature, _} = Enum.map_reduce signature, 1, fn(x, acc) ->
{simplify_signature(x, acc), acc + 1}
end
case :ets.lookup(table, tuple) do
[] ->
:ets.insert(table, { tuple, line, kind, signature, doc })
:ets.insert(table, {tuple, line, kind, signature, doc})
:ok
[{ tuple, line, _old_kind, old_sign, old_doc }] ->
[{tuple, line, _old_kind, old_sign, old_doc}] ->
:ets.insert(table, {
tuple,
line,
kind,
merge_signatures(old_sign, signature, 1),
if(nil?(doc), do: old_doc, else: doc)
})
})
:ok
end
end
# Simplify signatures to be stored in docs
defp simplify_signature({ :\\, defline, [left, right ] }, line, i) do
{ :\\, defline, [simplify_signature(left, line, i), right] }
defp simplify_signature({:\\, _, [left, right ]}, i) do
{:\\, [], [simplify_signature(left, i), right]}
end
defp simplify_signature({ var, line, atom }, _, _i) when is_atom(atom) do
case atom_to_list(var) do
[?_|_] -> { var, line, :guess }
_ -> { var, line, nil }
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))
{atom, [], nil}
end
defp simplify_signature({:=, _, [_, right]}, i) do
simplify_signature(right, i)
end
defp simplify_signature({var, _, atom}, _i) when is_atom(atom) do
case atom_to_binary(var) do
"_" <> rest -> {binary_to_atom(rest), [], Elixir}
_ -> {var, [], nil}
end
end
defp simplify_signature({ :=, _, [_, right] }, line, i) do
simplify_signature(right, line, i)
defp simplify_signature(other, i) when is_integer(other), do: {:"int#{i}", [], Elixir}
defp simplify_signature(other, i) when is_boolean(other), do: {:"bool#{i}", [], Elixir}
defp simplify_signature(other, i) when is_atom(other), do: {:"atom#{i}", [], Elixir}
defp simplify_signature(other, i) when is_list(other), do: {:"list#{i}", [], Elixir}
defp simplify_signature(other, i) when is_float(other), do: {:"float#{i}", [], Elixir}
defp simplify_signature(other, i) when is_binary(other), do: {:"binary#{i}", [], Elixir}
defp simplify_signature(_, i), do: {:"arg#{i}", [], Elixir}
defp downcase(<<c :: utf8, rest :: binary>>) when c >= ?A and c <= ?Z do
<<c + 32 :: utf8, downcase(rest) :: binary>>
end
defp simplify_signature(other, line, i) when is_integer(other), do: { :"int#{i}", line, :guess }
defp simplify_signature(other, line, i) when is_boolean(other), do: { :"bool#{i}", line, :guess }
defp simplify_signature(other, line, i) when is_atom(other), do: { :"atom#{i}", line, :guess }
defp simplify_signature(other, line, i) when is_list(other), do: { :"list#{i}", line, :guess }
defp simplify_signature(other, line, i) when is_float(other), do: { :"float#{i}", line, :guess }
defp simplify_signature(other, line, i) when is_binary(other), do: { :"binary#{i}", line, :guess }
defp simplify_signature(_, line, i), do: { :"arg#{i}", line, :guess }
defp downcase(<<c, rest :: binary>>) do
<<c, downcase(rest) :: binary>>
end
defp downcase(<<>>) do
<<>>
end
# Merge
@@ -564,23 +580,23 @@ defmodule Module do
[]
end
defp merge_signature({ :\\, line, [left, right] }, newer, i) do
{ :\\, line, [merge_signature(left, newer, i), right] }
defp merge_signature({:\\, line, [left, right]}, newer, i) do
{:\\, line, [merge_signature(left, newer, i), right]}
end
defp merge_signature(older, { :\\, _, [left, _] }, i) do
defp merge_signature(older, {:\\, _, [left, _]}, i) do
merge_signature(older, left, i)
end
# The older signature, when given, always have higher precedence
defp merge_signature({ _, _, nil } = older, _newer, _), do: older
defp merge_signature(_older, { _, _, nil } = newer, _), do: newer
defp merge_signature({_, _, nil} = older, _newer, _), do: older
defp merge_signature(_older, {_, _, nil} = newer, _), do: newer
# Both are a guess, so check if they are the same guess
defp merge_signature({ var, _, _ } = older, { var, _, _ }, _), do: older
defp merge_signature({var, _, _} = older, {var, _, _}, _), do: older
# Otherwise, returns a generic guess
defp merge_signature({ _, line, _ }, _newer, i), do: { :"arg#{i}", line, :guess }
defp merge_signature({_, line, _}, _newer, i), do: {:"arg#{i}", line, Elixir}
@doc """
Checks if the module defines the given function or macro.
@@ -589,9 +605,9 @@ defmodule Module do
## Examples
defmodule Example do
Module.defines? __MODULE__, { :version, 0 } #=> false
Module.defines? __MODULE__, {:version, 0} #=> false
def version, do: 1
Module.defines? __MODULE__, { :version, 0 } #=> true
Module.defines? __MODULE__, {:version, 0} #=> true
end
"""
@@ -609,9 +625,9 @@ defmodule Module do
## Examples
defmodule Example do
Module.defines? __MODULE__, { :version, 0 }, :defp #=> false
Module.defines? __MODULE__, {:version, 0}, :defp #=> false
def version, do: 1
Module.defines? __MODULE__, { :version, 0 }, :defp #=> false
Module.defines? __MODULE__, {:version, 0}, :defp #=> false
end
"""
@@ -619,7 +635,7 @@ defmodule Module do
assert_not_compiled!(:defines?, module)
table = function_table_for(module)
case :ets.lookup(table, tuple) do
[{ _, ^kind, _, _, _, _, _ }] -> true
[{_, ^kind, _, _, _, _, _}] -> true
_ -> false
end
end
@@ -638,7 +654,7 @@ defmodule Module do
def definitions_in(module) do
assert_not_compiled!(:definitions_in, module)
table = function_table_for(module)
lc { tuple, _, _, _, _, _, _ } inlist :ets.tab2list(table), do: tuple
for {tuple, _, _, _, _, _, _} <- :ets.tab2list(table), do: tuple
end
@doc """
@@ -657,22 +673,22 @@ defmodule Module do
def definitions_in(module, kind) do
assert_not_compiled!(:definitions_in, module)
table = function_table_for(module)
lc { tuple, stored_kind, _, _, _, _, _ } inlist :ets.tab2list(table), stored_kind == kind, do: tuple
for {tuple, stored_kind, _, _, _, _, _} <- :ets.tab2list(table), stored_kind == kind, do: tuple
end
@doc """
Makes the given functions in `module` overridable.
An overridable function is lazily defined, allowing a
developer to customize it. See `Kernel.defoverridable` for
developer to customize it. See `Kernel.defoverridable/1` for
more information and documentation.
"""
def make_overridable(module, tuples) do
assert_not_compiled!(:make_overridable, module)
lc tuple inlist tuples do
for tuple <- tuples do
case :elixir_def.lookup_definition(module, tuple) do
false ->
{ name, arity } = tuple
{name, arity} = tuple
raise "Cannot make function #{name}/#{arity} overridable because it was not defined"
clause ->
:elixir_def.delete_definition(module, tuple)
@@ -684,9 +700,9 @@ defmodule Module do
end
old = get_attribute(module, :__overridable)
merged = :orddict.update(tuple, fn({ count, _, _, _ }) ->
{ count + 1, clause, neighbours, false }
end, { 1, clause, neighbours, false }, old)
merged = :orddict.update(tuple, fn({count, _, _, _}) ->
{count + 1, clause, neighbours, false}
end, {1, clause, neighbours, false}, old)
put_attribute(module, :__overridable, merged)
end
@@ -703,7 +719,7 @@ defmodule Module do
@doc """
Puts an Erlang attribute to the given module with the given
key and value. The semantics of putting the attribute depends
if the attribute was registered or not via `register_attribute/2`.
if the attribute was registered or not via `register_attribute/3`.
## Examples
@@ -728,12 +744,12 @@ defmodule Module do
value
end
:ets.insert(table, { key, new })
:ets.insert(table, {key, new})
end
@doc """
Gets the given attribute from a module. If the attribute
was marked with `accumulate` with `Module.register_attribute`,
was marked with `accumulate` with `Module.register_attribute/3`,
a list is always returned.
The `@` macro compiles to a call to this function. For example,
@@ -777,10 +793,8 @@ defmodule Module do
cond do
:lists.member(key, acc) ->
[]
is_list(warn) && (entry = List.first(warn)) ->
opts = elem(entry, size(entry) - 1)
info = Exception.format_file_line(Keyword.get(opts, :file), Keyword.get(opts, :line))
:elixir_errors.warn info, "undefined module attribute @#{key}, " <>
is_list(warn) ->
:elixir_errors.warn warn_info(warn), "undefined module attribute @#{key}, " <>
"please remove access to @#{key} or explicitly set it to nil before access\n"
nil
true ->
@@ -789,6 +803,15 @@ defmodule Module do
end
end
defp warn_info([entry|_]) do
opts = elem(entry, size(entry) - 1)
Exception.format_file_line(Keyword.get(opts, :file), Keyword.get(opts, :line)) <> " "
end
defp warn_info([]) do
""
end
@doc """
Deletes all attributes that match the given key.
@@ -843,12 +866,12 @@ defmodule Module do
if Keyword.get(opts, :persist) do
old = :ets.lookup_element(table, :__persisted_attributes, 2)
:ets.insert(table, { :__persisted_attributes, [new|old] })
:ets.insert(table, {:__persisted_attributes, [new|old]})
end
if Keyword.get(opts, :accumulate) do
old = :ets.lookup_element(table, :__acc_attributes, 2)
:ets.insert(table, { :__acc_attributes, [new|old] })
:ets.insert(table, {:__acc_attributes, [new|old]})
end
end
@@ -872,13 +895,13 @@ defmodule Module do
module = env.module
line = env.line
arity = length(args)
pair = { name, arity }
pair = {name, arity}
doc = get_attribute(module, :doc)
case add_doc(module, line, kind, pair, args, doc) do
:ok ->
:ok
{ :error, :private_doc } ->
{:error, :private_doc} ->
:elixir_errors.warn line, env.file, "function #{name}/#{arity} is private, @doc's are always discarded for private functions\n"
end
@@ -898,13 +921,13 @@ defmodule Module do
[] -> [value]
end
:ets.insert(table, { key, new })
:ets.insert(table, {key, new})
end
## Helpers
defp normalize_attribute(:on_load, atom) when is_atom(atom) do
{ atom, 0 }
{atom, 0}
end
defp normalize_attribute(:behaviour, atom) when is_atom(atom) do
@@ -918,7 +941,7 @@ defmodule Module do
defp normalize_attribute(key, atom) when is_atom(atom) and
key in [:before_compile, :after_compile, :on_definition] do
{ atom, :"__#{key}__" }
{atom, :"__#{key}__"}
end
defp normalize_attribute(key, _value) when key in [:type, :typep, :export_type, :opaque, :callback] do
+71 -70
View File
@@ -12,21 +12,21 @@
# We also have can the following vertices:
#
# * `Module` - a module that was invoked via an import
# * `{ name, arity }` - a local function/arity pair
# * `{ :import, name, arity }` - an invoked function/arity import
# * `{name, arity}` - a local function/arity pair
# * `{:import, name, arity}` - an invoked function/arity import
#
# Each of those vertices can associate to other vertices
# as described below:
#
# * `Module`
# * in neighbours: `{ :import, name, arity }`
# * in neighbours: `{:import, name, arity}`
#
# * `{ name, arity }`
# * in neighbours: `:local`, `{ name, arity }`
# * out neighbours: `{ :import, name, arity }`
# * `{name, arity}`
# * in neighbours: `:local`, `{name, arity}`
# * out neighbours: `{:import, name, arity}`
#
# * `{ :import, name, arity }`
# * in neighbours: `{ name, arity }`
# * `{:import, name, arity}`
# * in neighbours: `{name, arity}`
# * out neighbours: `Module`
#
# Note that since this is required for bootstrap, we can't use
@@ -39,21 +39,21 @@ defmodule Module.LocalsTracker do
@type ref :: pid | module
@type name :: atom
@type name_arity :: { name, arity }
@type name_arity :: {name, arity}
@type local :: { name, arity }
@type import :: { :import, name, arity }
@type local :: {name, arity}
@type import :: {:import, name, arity}
# Public API
@doc """
Returns all imported modules that had the given
`{ name, arity }` invoked.
`{name, arity}` invoked.
"""
@spec imports_with_dispatch(ref, name_arity) :: [module]
def imports_with_dispatch(ref, { name, arity }) do
def imports_with_dispatch(ref, {name, arity}) do
d = :gen_server.call(to_pid(ref), :digraph, @timeout)
:digraph.out_neighbours(d, { :import, name, arity })
:digraph.out_neighbours(d, {:import, name, arity})
end
@doc """
@@ -71,7 +71,7 @@ defmodule Module.LocalsTracker do
defp reduce_reachable(d, vertex, vertices) do
neighbours = :digraph.out_neighbours(d, vertex)
neighbours = (lc { _, _ } = t inlist neighbours, do: t) |> :ordsets.from_list
neighbours = (for {_, _} = t <- neighbours, do: t) |> :ordsets.from_list
remaining = :ordsets.subtract(neighbours, vertices)
vertices = :ordsets.union(neighbours, vertices)
:lists.foldl(&reduce_reachable(d, &1, &2), vertices, remaining)
@@ -80,7 +80,7 @@ defmodule Module.LocalsTracker do
defp to_pid(pid) when is_pid(pid), do: pid
defp to_pid(mod) when is_atom(mod) do
table = :elixir_module.data_table(mod)
[{ _, val }] = :ets.lookup(table, :__locals_tracker)
[{_, val}] = :ets.lookup(table, :__locals_tracker)
val
end
@@ -89,7 +89,7 @@ defmodule Module.LocalsTracker do
# Starts the tracker and returns its pid.
@doc false
def start_link do
{ :ok, pid } = :gen_server.start_link(__MODULE__, [], [])
{:ok, pid} = :gen_server.start_link(__MODULE__, [], [])
pid
end
@@ -99,36 +99,36 @@ defmodule Module.LocalsTracker do
# a call is made to.
@doc false
def add_definition(pid, kind, tuple) when kind in [:def, :defp, :defmacro, :defmacrop] do
:gen_server.cast(pid, { :add_definition, kind, tuple })
:gen_server.cast(pid, {:add_definition, kind, tuple})
end
# Adds and tracks defaults for a definition into the tracker.
@doc false
def add_defaults(pid, kind, tuple, defaults) when kind in [:def, :defp, :defmacro, :defmacrop] do
:gen_server.cast(pid, { :add_defaults, kind, tuple, defaults })
:gen_server.cast(pid, {:add_defaults, kind, tuple, defaults})
end
# Adds a local dispatch to the given target.
def add_local(pid, to) when is_tuple(to) do
:gen_server.cast(pid, { :add_local, :local, to })
:gen_server.cast(pid, {:add_local, :local, to})
end
# Adds a local dispatch from-to the given target.
@doc false
def add_local(pid, from, to) when is_tuple(from) and is_tuple(to) do
:gen_server.cast(pid, { :add_local, from, to })
:gen_server.cast(pid, {:add_local, from, to})
end
# Adds a import dispatch to the given target.
@doc false
def add_import(pid, function, module, target) when is_atom(module) and is_tuple(target) do
:gen_server.cast(pid, { :add_import, function, module, target })
:gen_server.cast(pid, {:add_import, function, module, target})
end
# Yanks a local node. Returns its in and out vertices in a tuple.
@doc false
def yank(pid, local) do
:gen_server.call(to_pid(pid), { :yank, local }, @timeout)
:gen_server.call(to_pid(pid), {:yank, local}, @timeout)
end
# Reattach a previously yanked node
@@ -136,7 +136,7 @@ defmodule Module.LocalsTracker do
def reattach(pid, kind, tuple, neighbours) do
pid = to_pid(pid)
add_definition(pid, kind, tuple)
:gen_server.cast(pid, { :reattach, tuple, neighbours })
:gen_server.cast(pid, {:reattach, tuple, neighbours})
end
# Collecting all conflicting imports with the given functions
@@ -144,10 +144,11 @@ defmodule Module.LocalsTracker do
def collect_imports_conflicts(pid, all_defined) do
d = :gen_server.call(pid, :digraph, @timeout)
lc { name, arity } inlist all_defined,
n = :digraph.out_neighbours(d, { :import, name, arity }),
n != [] do
{ n, name, arity }
for {name, arity} <- all_defined,
:digraph.in_neighbours(d, {:import, name, arity}) != [],
n = :digraph.out_neighbours(d, {:import, name, arity}),
n != [] do
{n, name, arity}
end
end
@@ -160,40 +161,40 @@ defmodule Module.LocalsTracker do
:lists.foldl(&collect_unused_locals(&1, &2, reachable), [], private)
end
defp collect_unused_locals({ tuple, kind, 0 }, acc, reachable) do
defp collect_unused_locals({tuple, kind, 0}, acc, reachable) do
if :lists.member(tuple, reachable) do
acc
else
[{ :unused_def, tuple, kind }|acc]
[{:unused_def, tuple, kind}|acc]
end
end
defp collect_unused_locals({ tuple, kind, default }, acc, reachable) when default > 0 do
{ name, arity } = tuple
defp collect_unused_locals({tuple, kind, default}, acc, reachable) when default > 0 do
{name, arity} = tuple
min = arity - default
max = arity
invoked = lc { n, a } inlist reachable, n == name, a in min..max, do: a
invoked = for {n, a} <- reachable, n == name, a in min..max, do: a
if invoked == [] do
[{ :unused_def, tuple, kind }|acc]
[{:unused_def, tuple, kind}|acc]
else
case :lists.min(invoked) - min do
0 -> acc
^default -> [{ :unused_args, tuple }|acc]
unused_args -> [{ :unused_args, tuple, unused_args }|acc]
^default -> [{:unused_args, tuple}|acc]
unused_args -> [{:unused_args, tuple, unused_args}|acc]
end
end
end
@doc false
def cache_env(pid, env) do
:gen_server.call(pid, { :cache_env, env }, @timeout)
:gen_server.call(pid, {:cache_env, env}, @timeout)
end
@doc false
def get_cached_env(pid, ref) do
:gen_server.call(pid, { :get_cached_env, ref }, @timeout)
:gen_server.call(pid, {:get_cached_env, ref}, @timeout)
end
# Stops the gen server
@@ -207,78 +208,78 @@ defmodule Module.LocalsTracker do
def init([]) do
d = :digraph.new([:protected])
:digraph.add_vertex(d, :local)
{ :ok, { d, [] } }
{:ok, {d, []}}
end
def handle_call({ :cache_env, env }, _from, { d, cache }) do
def handle_call({:cache_env, env}, _from, {d, cache}) do
case cache do
[{i,^env}|_] ->
{ :reply, i, { d, cache } }
{:reply, i, {d, cache}}
t ->
i = length(t)
{ :reply, i, { d, [{i,env}|t] } }
{:reply, i, {d, [{i,env}|t]}}
end
end
def handle_call({ :get_cached_env, ref }, _from, { _, cache } = state) do
{ ^ref, env } = :lists.keyfind(ref, 1, cache)
{ :reply, env, state }
def handle_call({:get_cached_env, ref}, _from, {_, cache} = state) do
{^ref, env} = :lists.keyfind(ref, 1, cache)
{:reply, env, state}
end
def handle_call({ :yank, local }, _from, { d, _ } = state) do
def handle_call({:yank, local}, _from, {d, _} = state) do
in_vertices = :digraph.in_neighbours(d, local)
out_vertices = :digraph.out_neighbours(d, local)
:digraph.del_vertex(d, local)
{ :reply, { in_vertices, out_vertices }, state }
{:reply, {in_vertices, out_vertices}, state}
end
def handle_call(:digraph, _from, { d, _ } = state) do
{ :reply, d, state }
def handle_call(:digraph, _from, {d, _} = state) do
{:reply, d, state}
end
def handle_call(request, _from, state) do
{ :stop, { :bad_call, request }, state }
{:stop, {:bad_call, request}, state}
end
def handle_info(_msg, state) do
{ :noreply, state }
{:noreply, state}
end
def handle_cast({ :add_local, from, to }, { d, _ } = state) do
def handle_cast({:add_local, from, to}, {d, _} = state) do
handle_add_local(d, from, to)
{ :noreply, state }
{:noreply, state}
end
def handle_cast({ :add_import, function, module, { name, arity } }, { d, _ } = state) do
def handle_cast({:add_import, function, module, {name, arity}}, {d, _} = state) do
handle_import(d, function, module, name, arity)
{ :noreply, state }
{:noreply, state}
end
def handle_cast({ :add_definition, kind, tuple }, { d, _ } = state) do
def handle_cast({:add_definition, kind, tuple}, {d, _} = state) do
handle_add_definition(d, kind, tuple)
{ :noreply, state }
{:noreply, state}
end
def handle_cast({ :add_defaults, kind, { name, arity }, defaults }, { d, _ } = state) do
lc i inlist :lists.seq(arity - defaults, arity - 1) do
handle_add_definition(d, kind, { name, i })
handle_add_local(d, { name, i }, { name, i + 1 })
def handle_cast({:add_defaults, kind, {name, arity}, defaults}, {d, _} = state) do
for i <- :lists.seq(arity - defaults, arity - 1) do
handle_add_definition(d, kind, {name, i})
handle_add_local(d, {name, i}, {name, i + 1})
end
{ :noreply, state }
{:noreply, state}
end
def handle_cast({ :reattach, tuple, { in_neigh, out_neigh } }, { d, _ } = state) do
lc from inlist in_neigh, do: replace_edge(d, from, tuple)
lc to inlist out_neigh, do: replace_edge(d, tuple, to)
{ :noreply, state }
def handle_cast({:reattach, tuple, {in_neigh, out_neigh}}, {d, _} = state) do
for from <- in_neigh, do: replace_edge(d, from, tuple)
for to <- out_neigh, do: replace_edge(d, tuple, to)
{:noreply, state}
end
def handle_cast(:stop, state) do
{ :stop, :normal, state }
{:stop, :normal, state}
end
def handle_cast(msg, state) do
{ :stop, { :bad_cast, msg }, state }
{:stop, {:bad_cast, msg}, state}
end
def terminate(_reason, _state) do
@@ -286,13 +287,13 @@ defmodule Module.LocalsTracker do
end
def code_change(_old, state, _extra) do
{ :ok, state }
{:ok, state}
end
defp handle_import(d, function, module, name, arity) do
:digraph.add_vertex(d, module)
tuple = { :import, name, arity }
tuple = {:import, name, arity}
:digraph.add_vertex(d, tuple)
replace_edge!(d, tuple, module)
+73 -42
View File
@@ -8,10 +8,12 @@ defmodule Node do
about inlined functions, check out the `Kernel` module.
"""
@type t :: atom
@type t :: node
@doc """
Returns the current node. It returns the same as the built-in `node()`.
Returns the current node.
It returns the same as the built-in `node()`.
"""
@spec self :: t
def self do
@@ -19,8 +21,9 @@ defmodule Node do
end
@doc """
Returns `true` if the local node is alive; that is, if the node can be
part of a distributed system. Otherwise, it returns `false`.
Returns `true` if the local node is alive.
That is, if the node can be part of a distributed system.
"""
@spec alive? :: boolean
def alive? do
@@ -29,7 +32,9 @@ defmodule Node do
@doc """
Returns a list of all visible nodes in the system, excluding
the local node. Same as `list(:visible)`.
the local node.
Same as `list(:visible)`.
"""
@spec list :: [t]
def list do
@@ -37,8 +42,9 @@ defmodule Node do
end
@doc """
Returns a list of nodes according to argument given. The result
returned when the argument is a list, is the list of nodes
Returns a list of nodes according to argument given.
The result returned when the argument is a list, is the list of nodes
satisfying the disjunction(s) of the list elements.
See http://www.erlang.org/doc/man/erlang.html#nodes-1 for more info.
@@ -50,8 +56,10 @@ defmodule Node do
end
@doc """
Monitors the status of the node. If `flag` is `true`, monitoring is
turned on. If `flag` is `false`, monitoring is turned off.
Monitors the status of the node.
If `flag` is `true`, monitoring is turned on.
If `flag` is `false`, monitoring is turned off.
See http://www.erlang.org/doc/man/erlang.html#monitor_node-2 for more info.
"""
@@ -72,10 +80,28 @@ defmodule Node do
end
@doc """
Forces the disconnection of a node. This will appear to the `node` as if
the local node has crashed. This BIF is mainly used in the Erlang network
authentication protocols. Returns `true` if disconnection succeeds, otherwise
`false`. If the local node is not alive, the function returns `:ignored`.
Tries to set up a connection to node.
Returns `:pang` if it fails, or `:pong` if it is successful.
## Examples
iex> Node.ping(:unknown_node)
:pang
"""
@spec ping(t) :: :pong | :pang
def ping(node) do
:net_adm.ping(node)
end
@doc """
Forces the disconnection of a node.
This will appear to the `node` as if the local node has crashed.
This function is mainly used in the Erlang network authentication
protocols. Returns `true` if disconnection succeeds, otherwise `false`.
If the local node is not alive, the function returns `:ignored`.
See http://www.erlang.org/doc/man/erlang.html#disconnect_node-1 for more info.
"""
@@ -85,9 +111,10 @@ defmodule Node do
end
@doc """
Establishes a connection to `node`. Returns `true` if successful,
`false` if not, and the atom `:ignored` if the local node is not
alive.
Establishes a connection to `node`.
Returns `true` if successful, `false` if not, and the atom
`:ignored` if the local node is not alive.
See http://erlang.org/doc/man/net_kernel.html#connect_node-1 for more info.
"""
@@ -112,10 +139,11 @@ defmodule Node do
@doc """
Returns the pid of a new process started by the application of `fun`
on `node`. If `node` does not exist, a useless pid is returned.
on `node`.
Check http://www.erlang.org/doc/man/erlang.html#spawn_opt-3 for
the list of available options.
If `node` does not exist, a useless pid is returned. Check
http://www.erlang.org/doc/man/erlang.html#spawn_opt-3 for the list of
available options.
Inlined by the compiler.
"""
@@ -126,11 +154,11 @@ defmodule Node do
@doc """
Returns the pid of a new process started by the application of
`module.function(args)` on `node`. If `node` does not exists,
a useless pid is returned.
`module.function(args)` on `node`.
Check http://www.erlang.org/doc/man/erlang.html#spawn-4 for
the list of available options.
If `node` does not exist, a useless pid is returned. Check
http://www.erlang.org/doc/man/erlang.html#spawn-4 for the list of
available options.
Inlined by the compiler.
"""
@@ -141,11 +169,11 @@ defmodule Node do
@doc """
Returns the pid of a new process started by the application of
`module.function(args)` on `node`. If `node` does not exists,
a useless pid is returned.
`module.function(args)` on `node`.
Check http://www.erlang.org/doc/man/erlang.html#spawn_opt-5 for
the list of available options.
If `node` does not exist, a useless pid is returned. Check
http://www.erlang.org/doc/man/erlang.html#spawn_opt-5 for the list of
available options.
Inlined by the compiler.
"""
@@ -155,11 +183,11 @@ defmodule Node do
end
@doc """
Returns the pid of a new process started by the application of `fun`
on `node`. A link is created between the calling process and the
new process, atomically. If `node` does not exist, a useless pid is returned
(and due to the link, an exit signal with exit reason `:noconnection` will be
received).
Returns the pid of a new linked process started by the application of `fun` on `node`.
A link is created between the calling process and the new process, atomically.
If `node` does not exist, a useless pid is returned (and due to the link, an exit
signal with exit reason `:noconnection` will be received).
Inlined by the compiler.
"""
@@ -169,11 +197,12 @@ defmodule Node do
end
@doc """
Returns the pid of a new process started by the application of
`module.function(args)` on `node`. A link is created between the calling
process and the new process, atomically. If `node` does not exist, a useless
pid is returned (and due to the link, an exit signal with exit reason
`:noconnection` will be received).
Returns the pid of a new linked process started by the application of
`module.function(args)` on `node`.
A link is created between the calling process and the new process, atomically.
If `node` does not exist, a useless pid is returned (and due to the link, an exit
signal with exit reason `:noconnection` will be received).
Inlined by the compiler.
"""
@@ -183,9 +212,10 @@ defmodule Node do
end
@doc """
Sets the magic cookie of `node` to the atom `cookie`. The default node
is `Node.self`, the local node. If `node` is the local node, the function also
sets the cookie of all other unknown nodes to `cookie`.
Sets the magic cookie of `node` to the atom `cookie`.
The default node is `Node.self`, the local node. If `node` is the local node,
the function also sets the cookie of all other unknown nodes to `cookie`.
This function will raise `FunctionClauseError` if the given `node` is not alive.
"""
@@ -194,8 +224,9 @@ defmodule Node do
end
@doc """
Returns the magic cookie of the local node, if the node is alive;
otherwise `:nocookie`.
Returns the magic cookie of the local node.
Returns the cookie if the node is alive, otherwise `:nocookie`.
"""
def get_cookie() do
:erlang.get_cookie()
+121 -88
View File
@@ -4,79 +4,85 @@ defmodule OptionParser do
"""
@doc """
Parses `argv` and returns a tuple with the parsed options, its
arguments, and a list options that couldn't be parsed.
Parses `argv` into a keywords list.
It returns the parsed values, remaining arguments and the
invalid options.
## Examples
iex> OptionParser.parse(["--debug"])
{ [debug: true], [], [] }
{[debug: true], [], []}
iex> OptionParser.parse(["--source", "lib"])
{ [source: "lib"], [], [] }
{[source: "lib"], [], []}
iex> OptionParser.parse(["--source-path", "lib", "test/enum_test.exs", "--verbose"])
{ [source_path: "lib", verbose: true], ["test/enum_test.exs"], [] }
{[source_path: "lib", verbose: true], ["test/enum_test.exs"], []}
Notice how Elixir automatically translates the "--source-path"
switch to the underscored atom `:source_path`, which better follows
Elixir conventions.
By default, Elixir will try to automatically parse switches.
Switches without an argument, like `--debug` will automatically
be set to true. Switches followed by a value will be assigned
to the value, always as strings.
## Aliases
A set of aliases can be given as the second argument:
iex> OptionParser.parse(["-d"], aliases: [d: :debug])
{ [debug: true], [], [] }
Note Elixir also converts the switches to underscore atoms, as
`--source-path` becomes `:source_path`, to better suit Elixir
conventions.
## Switches
Extra information about switches can be given as arguments, too.
This is useful when a switch must behave as a boolean
or if duplicated switches should be kept, overridden or accumulated.
The following types are supported:
Many times though, it is better to explicitly list the available
switches and their formats. The following types are supported:
* `:boolean` - Marks the given switch as a boolean. Boolean switches
never consume the following value unless it is
`true` or `false`;
* `:integer` - Parses the switch as an integer;
* `:float` - Parses the switch as a float;
* `:string` - Returns the switch as a string;
If a switch can't be parsed, the option is returned in the invalid
options list (third element of the returned tuple).
The following extra options are supported:
The following extra "types" are supported:
* `:keep` - Keeps duplicated items in the list instead of overriding;
Examples:
iex> OptionParser.parse(["--unlock", "path/to/file"], switches: [unlock: :boolean])
{ [unlock: true], ["path/to/file"], [] }
{[unlock: true], ["path/to/file"], []}
iex> OptionParser.parse(["--unlock", "--limit", "0", "path/to/file"],
...> switches: [unlock: :boolean, limit: :integer])
{ [unlock: true, limit: 0], ["path/to/file"], [] }
{[unlock: true, limit: 0], ["path/to/file"], []}
iex> OptionParser.parse(["-limit", "3"], switches: [limit: :integer])
{ [limit: 3], [], [] }
iex> OptionParser.parse(["--limit", "3"], switches: [limit: :integer])
{[limit: 3], [], []}
iex> OptionParser.parse(["-limit", "yyz"], switches: [limit: :integer])
{ [], [], [limit: "yyz"] }
iex> OptionParser.parse(["--limit", "yyz"], switches: [limit: :integer])
{[], [], [limit: "yyz"]}
## Negation switches
Any switches starting with `--no-` are always considered to be
booleans and never parse the next value:
All switches starting with `--no-` are considered to be booleans and never
parse the next value:
iex> OptionParser.parse(["--no-op", "path/to/file"])
{ [no_op: true], ["path/to/file"], [] }
{[no_op: true], ["path/to/file"], []}
In case the negated switch exists as a boolean, it sets the boolean to false:
However, in case the base switch exists, it sets that particular switch to
false:
iex> OptionParser.parse(["--no-op", "path/to/file"], switches: [op: :boolean])
{ [op: false], ["path/to/file"], [] }
{[op: false], ["path/to/file"], []}
## Aliases
A set of aliases can be given as options too:
iex> OptionParser.parse(["-d"], aliases: [d: :debug])
{[debug: true], [], []}
"""
def parse(argv, opts \\ []) when is_list(argv) and is_list(opts) do
@@ -92,10 +98,11 @@ defmodule OptionParser do
## Example
iex> OptionParser.parse_head(["--source", "lib", "test/enum_test.exs", "--verbose"])
{ [source: "lib"], ["test/enum_test.exs", "--verbose"], [] }
{[source: "lib"], ["test/enum_test.exs", "--verbose"], []}
iex> OptionParser.parse_head(["--verbose", "--source", "lib", "test/enum_test.exs", "--unlock"])
{ [verbose: true, source: "lib"], ["test/enum_test.exs", "--unlock"], [] }
{[verbose: true, source: "lib"], ["test/enum_test.exs", "--unlock"], []}
"""
def parse_head(argv, opts \\ []) when is_list(argv) and is_list(opts) do
parse(argv, opts, false)
@@ -113,24 +120,15 @@ defmodule OptionParser do
parse(argv, aliases, switches, [], [], [], all)
end
defp parse(["--"|_] = value, _aliases, _switches, dict, _args, invalid, _all) do
{ Enum.reverse(dict), value, Enum.reverse(invalid) }
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, kinds, value } = normalize_option(option, switches, aliases)
if nil?(value) do
{ value, t } =
if :boolean in kinds do
{ true, t }
else
value_from_tail(t)
end
end
{ dict, invalid } = store_option(dict, invalid, option, value, kinds)
{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
@@ -139,89 +137,124 @@ defmodule OptionParser do
end
defp parse([], _, _switches, dict, args, invalid, true) do
{ Enum.reverse(dict), Enum.reverse(args), Enum.reverse(invalid) }
{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) }
{Enum.reverse(dict), value, Enum.reverse(invalid)}
end
defp value_from_tail(["-" <> _|_] = t), do: { true, t }
defp value_from_tail([h|t]), do: { h, t }
defp value_from_tail([]), do: { true, [] }
defp store_option(dict, invalid, option, value, kinds) do
{ invalid_option, value } =
{invalid_option, value} =
cond do
:invalid in kinds ->
{option, value}
:boolean in kinds ->
{ nil, value in [true, "true"] }
{nil, value in [true, "true"]}
:integer in kinds ->
case Integer.parse(value) do
{ value, "" } -> { nil, value }
_ -> { option, value }
{value, ""} -> {nil, value}
_ -> {option, value}
end
:float in kinds ->
case Float.parse(value) do
{ value, "" } -> { nil, value }
_ -> { option, value }
{value, ""} -> {nil, value}
_ -> {option, value}
end
true ->
{ nil, value }
{nil, value}
end
if invalid_option do
{ dict, [{ option, value }|invalid] }
{dict, [{option, value}|invalid]}
else
{ do_store_option(dict, option, value, kinds), invalid }
{do_store_option(dict, option, value, kinds), invalid}
end
end
defp do_store_option(dict, option, value, kinds) do
cond do
:keep in kinds ->
[{ option, value }|dict]
[{option, value}|dict]
true ->
[{ option, value }|Keyword.delete(dict, option)]
[{option, value}|Keyword.delete(dict, option)]
end
end
defp normalize_option(<<?-, option :: binary>>, switches, aliases) do
normalize_option(option, switches, aliases)
defp normalize_option(<<?-, option :: binary>>, value, switches, _aliases) do
normalize_option(get_negated(option, switches), value, switches)
end
defp normalize_option(option, switches, aliases) do
{ option, value } = split_option(option)
if non_neg = get_non_negated(option, aliases) do
kinds = List.wrap(switches[non_neg])
if :boolean in kinds do
{ non_neg, kinds, false }
else
{ get_aliased(option, aliases), [:boolean], true }
end
defp normalize_option(option, value, switches, aliases) do
option = get_option(option)
if alias = aliases[option] do
normalize_option({:default, alias}, value, switches)
else
atom = get_aliased(option, aliases)
{ atom, List.wrap(switches[atom]), value }
{option, [:invalid], value}
end
end
defp normalize_option({:negated, option}, _value, switches) do
kinds = List.wrap(switches[option])
cond do
:boolean in kinds ->
{option, kinds, false}
kinds == [] ->
{option, kinds, true}
true ->
{option, [:invalid], false}
end
end
defp normalize_option({:default, option}, value, switches) do
{option, List.wrap(switches[option]), value}
end
defp normalize_value(nil, kinds, t) do
cond do
:boolean in kinds ->
{true, kinds, t}
value_in_tail?(t) ->
[h|t] = t
{h, kinds, t}
kinds == [] ->
{true, kinds, t}
true ->
{true, [:invalid], t}
end
end
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 split_option(option) do
case :binary.split(option, "=") do
[h] -> { h, nil }
[h, t] -> { h, t }
[h] -> {h, nil}
[h, t] -> {h, t}
end
end
defp to_underscore(option) do
bc <<c>> inbits option, do: << if(c == ?-, do: ?_, else: c) >>
for <<c <- option>>, into: "", do: << if(c == ?-, do: ?_, else: c) >>
end
defp get_aliased(option, aliases) do
atom = option |> to_underscore |> binary_to_atom
aliases[atom] || atom
defp get_option(option) do
option |> to_underscore |> binary_to_atom
end
defp get_non_negated("no-" <> rest, aliases), do: get_aliased(rest, aliases)
defp get_non_negated(_, _), do: nil
defp get_negated("no-" <> rest = option, switches) do
negated = get_option(rest)
option = if Keyword.has_key?(switches, negated), do: negated, else: get_option(option)
{:negated, option}
end
defp get_negated(rest, _switches) do
{:default, get_option(rest)}
end
end
+127 -120
View File
@@ -3,9 +3,9 @@ defmodule Path do
This module provides conveniences for manipulating or
retrieving file system paths.
The functions in this module may receive a char list or
a binary as an argument and will return a value of the same
type.
The functions in this module may receive a char data as
argument (i.e. a string or a list of characters / string)
and will always return a string (encoded in UTF-8).
The majority of the functions in this module do not
interact with the file system, except for a few functions
@@ -13,9 +13,7 @@ defmodule Path do
"""
alias :filename, as: FN
@type t :: char_list | atom | binary
@type r :: char_list | binary
@type t :: char_data
@doc """
Converts the given path to an absolute one. Unlike
@@ -37,8 +35,9 @@ defmodule Path do
"D:/usr/local/../x"
"""
@spec absname(t) :: binary
def absname(path) do
FN.absname(path, get_cwd(path))
absname(path, System.cwd!)
end
@doc """
@@ -57,8 +56,39 @@ defmodule Path do
"bar/../x"
"""
@spec absname(t, t) :: binary
def absname(path, relative_to) do
FN.absname(path, relative_to)
path = String.from_char_data!(path)
case type(path) do
:relative -> join(relative_to, path)
:absolute ->
if :binary.last(path) == ?/ do
binary_part(path, 0, byte_size(path) - 1)
else
path
end
:volumerelative ->
relative_to = String.from_char_data!(relative_to)
absname_vr(split(path), split(relative_to), relative_to)
end
end
## Absolute path on current drive
defp absname_vr(["/"|rest], [volume|_], _relative),
do: join([volume|rest])
## Relative to current directory on current drive.
defp absname_vr([<<x, ?:>>|rest], [<<x, _ :: binary>>|_], relative),
do: absname(join(rest), relative)
## Relative to current directory on another drive.
defp absname_vr([<<x, ?:>>|name], _, _relative) do
cwd =
case :file.get_cwd([x, ?:]) do
{:ok, dir} -> String.from_char_data!(dir)
{:error, _} -> <<x, ?:, ?/>>
end
absname(join(name), cwd)
end
@doc """
@@ -71,8 +101,9 @@ defmodule Path do
"/foo/bar"
"""
@spec expand(t) :: binary
def expand(path) do
normalize FN.absname(expand_home(path), get_cwd(path))
normalize absname(expand_home(path), System.cwd!)
end
@doc """
@@ -97,8 +128,9 @@ defmodule Path do
"/foo/bar"
"""
@spec expand(t, t) :: binary
def expand(path, relative_to) do
normalize FN.absname(FN.absname(expand_home(path), expand_home(relative_to)), get_cwd(path))
normalize absname(absname(expand_home(path), expand_home(relative_to)), System.cwd!)
end
@doc """
@@ -119,9 +151,10 @@ defmodule Path do
Path.type("/bar/foo.ex") #=> :volumerelative
"""
@spec type(t) :: :absolute | :relative | :volumerelative
def type(name) when is_list(name) or is_binary(name) do
case :os.type() do
{ :win32, _ } -> win32_pathtype(name)
{:win32, _} -> win32_pathtype(name)
_ -> unix_pathtype(name)
end |> elem(0)
end
@@ -143,53 +176,50 @@ defmodule Path do
Path.relative("/bar/foo.ex") #=> "bar/foo.ex"
"""
@spec relative(t) :: binary
def relative(name) do
case :os.type() do
{ :win32, _ } -> win32_pathtype(name)
{:win32, _} -> win32_pathtype(name)
_ -> unix_pathtype(name)
end |> elem(1)
end |> elem(1) |> String.from_char_data!
end
defp unix_pathtype(<<?/, relative :: binary>>), do:
{ :absolute, relative }
{:absolute, relative}
defp unix_pathtype([?/|relative]), do:
{ :absolute, relative }
{:absolute, relative}
defp unix_pathtype([list|rest]) when is_list(list), do:
unix_pathtype(list ++ rest)
defp unix_pathtype([atom|rest]) when is_atom(atom), do:
unix_pathtype(atom_to_list(atom) ++ rest)
defp unix_pathtype(relative), do:
{ :relative, relative }
{:relative, relative}
@slash [?/, ?\\]
defp win32_pathtype([list|rest]) when is_list(list), do:
win32_pathtype(list++rest)
defp win32_pathtype([atom|rest]) when is_atom(atom), do:
win32_pathtype(atom_to_list(atom)++rest)
defp win32_pathtype([char, list|rest]) when is_list(list), do:
win32_pathtype([char|list++rest])
defp win32_pathtype(<<c1, c2, relative :: binary>>) when c1 in @slash and c2 in @slash, do:
{ :absolute, relative }
{:absolute, relative}
defp win32_pathtype(<<c, relative :: binary>>) when c in @slash, do:
{ :volumerelative, relative }
{:volumerelative, relative}
defp win32_pathtype(<<_letter, ?:, c, relative :: binary>>) when c in @slash, do:
{ :absolute, relative }
{:absolute, relative}
defp win32_pathtype(<<_letter, ?:, relative :: binary>>), do:
{ :volumerelative, relative }
{:volumerelative, relative}
defp win32_pathtype([c1, c2 | relative]) when c1 in @slash and c2 in @slash, do:
{ :absolute, relative }
{:absolute, relative}
defp win32_pathtype([c | relative]) when c in @slash, do:
{ :volumerelative, relative }
{:volumerelative, relative}
defp win32_pathtype([c1, c2, list|rest]) when is_list(list), do:
win32_pathtype([c1, c2|list++rest])
defp win32_pathtype([_letter, ?:, c | relative]) when c in @slash, do:
{ :absolute, relative }
{:absolute, relative}
defp win32_pathtype([_letter, ?: | relative]), do:
{ :volumerelative, relative }
{:volumerelative, relative}
defp win32_pathtype(relative), do:
{ :relative, relative }
{:relative, relative}
@doc """
Returns the given `path` relative to the given `from` path.
@@ -205,23 +235,18 @@ defmodule Path do
iex> Path.relative_to("/usr/local/foo", "/usr/local")
"foo"
iex> Path.relative_to("/usr/local/foo", "/")
"usr/local/foo"
iex> Path.relative_to("/usr/local/foo", "/etc")
"/usr/local/foo"
"""
def relative_to(path, from) when is_list(path) and is_binary(from) do
path = filename_string_to_binary(path)
relative_to(FN.split(path), FN.split(from), path)
end
def relative_to(path, from) when is_binary(path) and is_list(from) do
relative_to(FN.split(path), FN.split(filename_string_to_binary(from)), path)
end
@spec relative_to(t, t) :: binary
def relative_to(path, from) do
relative_to(FN.split(path), FN.split(from), path)
path = String.from_char_data!(path)
relative_to(split(path), split(from), path)
end
defp relative_to([h|t1], [h|t2], original) do
@@ -229,7 +254,7 @@ defmodule Path do
end
defp relative_to([_|_] = l1, [], _original) do
FN.join(l1)
join(l1)
end
defp relative_to(_, _, original) do
@@ -241,9 +266,10 @@ defmodule Path do
directory. If, for some reason, the current working directory
cannot be retrieved, returns the full path.
"""
@spec relative_to_cwd(t) :: binary
def relative_to_cwd(path) do
case :file.get_cwd do
{ :ok, base } -> relative_to(path, base)
{:ok, base} -> relative_to(path, String.from_char_data!(base))
_ -> path
end
end
@@ -264,8 +290,9 @@ defmodule Path do
""
"""
@spec basename(t) :: binary
def basename(path) do
FN.basename(path)
FN.basename(String.from_char_data!(path))
end
@doc """
@@ -277,14 +304,17 @@ defmodule Path do
iex> Path.basename("~/foo/bar.ex", ".ex")
"bar"
iex> Path.basename("~/foo/bar.exs", ".ex")
"bar.exs"
iex> Path.basename("~/foo/bar.old.ex", ".ex")
"bar.old"
"""
@spec basename(t, t) :: binary
def basename(path, extension) do
FN.basename(path, extension)
FN.basename(String.from_char_data!(path), String.from_char_data!(extension))
end
@doc """
@@ -298,8 +328,9 @@ defmodule Path do
#=> "/foo/bar"
"""
@spec dirname(t) :: binary
def dirname(path) do
FN.dirname(path)
FN.dirname(String.from_char_data!(path))
end
@doc """
@@ -309,12 +340,14 @@ defmodule Path do
iex> Path.extname("foo.erl")
".erl"
iex> Path.extname("~/foo/bar")
""
"""
@spec extname(t) :: binary
def extname(path) do
FN.extension(path)
FN.extension(String.from_char_data!(path))
end
@doc """
@@ -324,12 +357,14 @@ defmodule Path do
iex> Path.rootname("/foo/bar")
"/foo/bar"
iex> Path.rootname("/foo/bar.ex")
"/foo/bar"
"""
@spec rootname(t) :: binary
def rootname(path) do
FN.rootname(path)
FN.rootname(String.from_char_data!(path))
end
@doc """
@@ -340,34 +375,39 @@ defmodule Path do
iex> Path.rootname("/foo/bar.erl", ".erl")
"/foo/bar"
iex> Path.rootname("/foo/bar.erl", ".ex")
"/foo/bar.erl"
"""
@spec rootname(t, t) :: binary
def rootname(path, extension) do
FN.rootname(path, extension)
FN.rootname(String.from_char_data!(path), String.from_char_data!(extension))
end
@doc """
Returns a string with one or more path components joined by the path separator.
This function should be used to convert a list of strings to a path.
Note that any trailing slash is removed on join.
## Examples
iex> Path.join(["~", "foo"])
"~/foo"
iex> Path.join(["foo"])
"foo"
iex> Path.join(["/", "foo", "bar"])
iex> Path.join(["/", "foo", "bar/"])
"/foo/bar"
"""
@spec join([t]) :: binary
def join([name1, name2|rest]), do:
join([join(name1, name2)|rest])
def join([name]) when is_list(name), do:
binary_to_filename_string(do_join(filename_string_to_binary(name), <<>>, [], major_os_type()))
def join([name]) when is_binary(name), do:
do_join(name, <<>>, [], major_os_type())
def join([name]), do:
do_join(String.from_char_data!(name), <<>>, [], major_os_type())
@doc """
Joins two paths.
@@ -378,23 +418,9 @@ defmodule Path do
"foo/bar"
"""
def join(left, right) when is_binary(left) and is_binary(right), do:
do_join(left, Path.relative(right), [], major_os_type())
def join(left, right) when is_binary(left) and is_list(right), do:
join(left, filename_string_to_binary(right))
def join(left, right) when is_list(left) and is_binary(right), do:
join(filename_string_to_binary(left), right)
def join(left, right) when is_list(left) and is_list(right), do:
binary_to_filename_string join(filename_string_to_binary(left), filename_string_to_binary(right))
def join(left, right) when is_atom(left), do:
join(atom_to_binary(left), right)
def join(left, right) when is_atom(right), do:
join(left, atom_to_binary(right))
@spec join(t, t) :: binary
def join(left, right),
do: do_join(String.from_char_data!(left), relative(right), [], major_os_type())
defp major_os_type do
:os.type |> elem(0)
@@ -408,17 +434,26 @@ defmodule Path do
do_join(rest, relativename, [?/|result], os_type)
defp do_join(<<?/, rest :: binary>>, relativename, [?/|result], os_type), do:
do_join(rest, relativename, [?/|result], os_type)
defp do_join(<<>>, <<>>, result, _os_type), do:
iolist_to_binary(:lists.reverse(result))
defp do_join(<<>>, <<>>, result, os_type), do:
iodata_to_binary(maybe_remove_dirsep(result, os_type))
defp do_join(<<>>, relativename, [?:|rest], :win32), do:
do_join(relativename, <<>>, [?:|rest], :win32)
defp do_join(<<>>, relativename, [?/|result], os_type), do:
do_join(relativename, <<>>, [?/|result], os_type)
defp do_join(<<>>, relativename, result, os_type), do:
do_join(relativename, <<>>, [?/|result], os_type)
defp do_join(<<char, rest :: binary>>, relativename, result, os_type) when is_integer(char), do:
defp do_join(<<char, rest :: binary>>, relativename, result, os_type), do:
do_join(rest, relativename, [char|result], os_type)
defp maybe_remove_dirsep([?/, ?:, letter], :win32), do:
[letter, ?:, ?/]
defp maybe_remove_dirsep([?/], _), do:
[?/]
defp maybe_remove_dirsep([?/|name], _), do:
:lists.reverse(name)
defp maybe_remove_dirsep(name, _), do:
:lists.reverse(name)
@doc """
Returns a list with the path split by the path separator.
If an empty string is given, returns the root path.
@@ -427,17 +462,21 @@ defmodule Path do
iex> Path.split("")
[]
iex> Path.split("foo")
["foo"]
iex> Path.split("/foo/bar")
["/", "foo", "bar"]
"""
@spec split(t) :: [binary]
# Work around a bug in Erlang on UNIX
def split(""), do: []
def split(path) do
FN.split(path)
FN.split(String.from_char_data!(path))
end
@doc """
@@ -473,62 +512,30 @@ defmodule Path do
Path.wildcard("projects/*/ebin/**/*.{beam,app}")
"""
def wildcard(glob) when is_binary(glob) do
paths = :filelib.wildcard binary_to_filename_string(glob)
encoding = :file.native_name_encoding()
Enum.map paths, &flatten_filename_to_binary(&1, encoding)
end
def wildcard(glob) when is_list(glob) do
:filelib.wildcard glob
end
## Helpers
defp get_cwd(path) when is_list(path), do: System.cwd! |> binary_to_filename_string
defp get_cwd(_), do: System.cwd!
defp binary_to_filename_string(binary) do
case :unicode.characters_to_list(binary) do
{ :error, _, _ } ->
:erlang.error(:badarg)
list when is_list(list) ->
list
end
end
defp filename_string_to_binary(list) do
flatten_filename_to_binary(:filename.flatten(list), :file.native_name_encoding())
end
defp flatten_filename_to_binary(list, encoding) do
case :unicode.characters_to_binary(list, :unicode, encoding) do
{ :error, _, _ } ->
:erlang.error(:badarg)
bin when is_binary(bin) ->
bin
end
@spec wildcard(t) :: [binary]
def wildcard(glob) do
glob
|> List.from_char_data!
|> :filelib.wildcard
|> Enum.map(&String.from_char_data!/1)
end
# Normalize the given path by expanding "..", "." and "~".
defp expand_home(<<?~, rest :: binary>>) do
System.user_home! <> rest
defp expand_home(type) do
case String.from_char_data!(type) do
"~" <> rest -> System.user_home! <> rest
rest -> rest
end
end
defp expand_home('~' ++ rest) do
(System.user_home! |> binary_to_filename_string) ++ rest
end
defp normalize(path), do: normalize(split(path), [])
defp expand_home(other), do: other
defp normalize(path), do: normalize(FN.split(path), [])
defp normalize([top|t], [_|acc]) when top in ["..", '..'] do
defp normalize([".."|t], [_|acc]) do
normalize t, acc
end
defp normalize([top|t], acc) when top in [".", '.'] do
defp normalize(["."|t], acc) do
normalize t, acc
end
@@ -537,6 +544,6 @@ defmodule Path do
end
defp normalize([], acc) do
join Enum.reverse(acc)
join :lists.reverse(acc)
end
end
+71 -75
View File
@@ -62,6 +62,7 @@ defmodule Process do
"""
@spec delete :: [{term, term}]
def delete() do
IO.write :stderr, "Process.delete/0 is deprecated\n#{Exception.format_stacktrace}"
:erlang.erase()
end
@@ -103,24 +104,31 @@ defmodule Process do
:erlang.exit(pid, reason)
end
@doc """
Returns the pid (process identifier) of the calling process.
"""
@spec self() :: pid
def self() do
:erlang.self()
end
@doc """
Sends a message to the given process.
It does the same as `Kernel.send/2`.
If the option `:noconnect` is used and sending the message would require an
auto-connection to another node the message is not sent and `:noconnect` is
returned.
If the option `:nosuspend` is used and sending the message would cause the
sender to be suspended the message is not sent and `:nosuspend` is returned.
Otherwise the message is sent and `:ok` is returned.
## Examples
iex> Process.send({:name, :node_does_not_exist}, :hi, [:noconnect])
:noconnect
"""
@spec send(dest, msg) :: msg when
dest: pid | port | atom | { atom, node },
msg: any
def send(dest, msg) do
:erlang.send(dest, msg)
@spec send(dest, msg, [option]) :: result when
dest: pid | port | atom | {atom, node},
msg: any,
option: :noconnect | :nosuspend,
result: :ok | :noconnect | :nosuspend
def send(dest, msg, options) do
:erlang.send(dest, msg, options)
end
@doc """
@@ -152,21 +160,16 @@ defmodule Process do
@type spawn_opts :: [spawn_opt]
@doc """
Returns the pid of a new process started by the application of `fun`.
It behaves exactly the same as `Kernel.spawn/1`.
Spawns the given module and function passing the given args
according to the given options.
Inlined by the compiler.
"""
@spec spawn((() -> any)) :: pid
def spawn(fun) do
:erlang.spawn(fun)
end
@doc """
Returns the pid of a new process started by the application of `fun`.
The result depends on the given options. In particular,
if `:monitor` is given as an option, it will return a tuple
containing the pid and the monitoring reference, otherwise
just the spawned process pid.
It also accepts extra options, for the list of available options
check http://www.erlang.org/doc/man/erlang.html#spawn_opt-2
check http://www.erlang.org/doc/man/erlang.html#spawn_opt-4
Inlined by the compiler.
"""
@@ -176,81 +179,72 @@ defmodule Process do
end
@doc """
Returns the pid of a new process started by the application of
`module.function(args)`. The new process created will be placed in the system
scheduler queue and be run some time later.
Spawns the given module and function passing the given args
according to the given options.
It behaves exactly the same as the `Kernel.spawn/3` function.
Inlined by the compiler.
"""
@spec spawn(module, atom, [any]) :: pid
def spawn(mod, fun, args) do
:erlang.spawn(mod, fun, args)
end
@doc """
Returns the pid of a new process started by the application of
`module.function(args)`. The new process created will be placed in the system
scheduler queue and be run some time later.
The result depends on the given options. In particular,
if `:monitor` is given as an option, it will return a tuple
containing the pid and the monitoring reference, otherwise
just the spawned process pid.
It also accepts extra options, for the list of available options
check http://www.erlang.org/doc/man/erlang.html#spawn_opt-4
Inlined by the compiler.
"""
@spec spawn(module, atom, [any], spawn_opts) :: pid | {pid, reference}
@spec spawn(module, atom, list, spawn_opts) :: pid | {pid, reference}
def spawn(mod, fun, args, opts) do
:erlang.spawn_opt(mod, fun, args, opts)
end
@doc """
Returns the pid of a new process started by the application of `fun`.
A link is created between the calling process and the new
process, atomically.
@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
Inlined by the compiler.
"""
@spec spawn_link((() -> any)) :: pid
@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 """
Returns the pid of a new process started by the application of
`module.function(args)`. A link is created between the calling process
and the new process, atomically. Otherwise works like spawn/3.
Inlined by the compiler.
"""
@spec spawn_link(module, atom, [any]) :: pid
@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 """
Returns the pid of a new process started by the application of `fun`
and reference for a monitor created to the new process.
Inlined by the compiler.
"""
@spec spawn_monitor((() -> any)) :: {pid, reference}
@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 """
A new process is started by the application of `module.function(args)`
and the process is monitored at the same time. Returns the pid and a
reference for the monitor. Otherwise works like spawn/3.
Inlined by the compiler.
"""
@spec spawn_monitor(module, atom, [any]) :: {pid, reference}
@doc false
def spawn_monitor(mod, fun, args) do
# IO.write :stderr, "Process.spawn_monitor/3 is deprecated, use the one in Kernel instead\n#{Exception.format_stacktrace}"
:erlang.spawn_monitor(mod, fun, args)
end
@doc false
def self() do
# IO.write :stderr, "Process.self/0 is deprecated, use the one in Kernel instead\n#{Exception.format_stacktrace}"
:erlang.self()
end
@doc false
def send(dest, msg) do
# IO.write :stderr, "Process.send/2 is deprecated, use the one in Kernel instead\n#{Exception.format_stacktrace}"
:erlang.send(dest, msg)
end
@doc """
The calling process starts monitoring the item given.
It returns the monitor reference.
@@ -326,7 +320,9 @@ defmodule Process do
be an atom, can be used instead of the pid / port identifier with the
`Kernel.send/2` function.
See http://www.erlang.org/doc/man/erlang.html#register-2 for more info.
`Process.register/2` will fail with `ArgumentError` if the pid supplied
is no longer alive, (check with `alive?/1`) or if the name is
already registered (check with `registered?/1`).
"""
@spec register(pid | port, atom) :: true
def register(pid, name) do
@@ -428,7 +424,7 @@ defmodule Process do
nillify :erlang.process_info(pid, spec)
end
@compile { :inline, nillify: 1 }
@compile {:inline, nillify: 1}
defp nillify(:undefined), do: nil
defp nillify(other), do: other
end
+87 -95
View File
@@ -33,13 +33,32 @@ defmodule Protocol do
defp after_defprotocol do
quote unquote: false do
{ arg, bodies, rec } = Protocol.impl_for(__MODULE__)
# == Deprecated records handling ==
{arg, impl} = Protocol.rec_impl_for(__MODULE__)
Kernel.def impl_for(unquote(arg)) when Kernel.is_record(unquote(arg)), do: unquote(impl)
# == Deprecated records handling ==
@spec impl_for(term) :: module | nil
Kernel.def impl_for(data)
lc { guard, body } inlist bodies do
Kernel.def impl_for(unquote(arg)) when unquote(guard), do: unquote(body)
# Define the implementation for structs.
#
# It simply delegates to struct_impl_for which is then
# optimized during protocol consolidation.
Kernel.def impl_for(%{__struct__: struct}) when :erlang.is_atom(struct) do
struct_impl_for(struct)
end
# Define the implementation for builtins.
for {guard, mod} <- Protocol.builtin do
target = Module.concat(__MODULE__, mod)
Kernel.def impl_for(data) when :erlang.unquote(guard)(data) do
case impl_for?(unquote(target)) do
true -> unquote(target).__impl__(:name)
false -> any_impl_for
end
end
end
@spec impl_for!(term) :: module | no_return
@@ -47,27 +66,37 @@ defmodule Protocol do
impl_for(data) || raise(Protocol.UndefinedError, protocol: __MODULE__, value: data)
end
# Handle special Record type
Kernel.defp rec_impl_for(unquote(arg)), do: unquote(rec)
# Handle special Any type
# Internal handler for Any
if @fallback_to_any do
Kernel.defp any_impl_for do
try do
__MODULE__.Any.__impl__(:name)
catch
:error, :undef, [[{ __MODULE__.Any, :__impl__, [:name], _ }|_]|_] ->
nil
case impl_for?(__MODULE__.Any) do
true -> __MODULE__.Any.__impl__(:name)
false -> nil
end
end
else
Kernel.defp any_impl_for, do: nil
end
# Inline both helpers
@compile { :inline, any_impl_for: 0, rec_impl_for: 1 }
# Internal handler for Structs
Kernel.defp struct_impl_for(struct) do
target = Module.concat(__MODULE__, struct)
case impl_for?(target) do
true -> target.__impl__(:name)
false -> any_impl_for
end
end
if :code.ensure_loaded(Kernel.Typespec) == { :module, Kernel.Typespec } and
# Check if compilation is available internally
Kernel.defp impl_for?(target) do
Code.ensure_compiled?(target) and
function_exported?(target, :__impl__, 1)
end
# Inline any and struct implementations
@compile {:inline, any_impl_for: 0, struct_impl_for: 1, impl_for?: 1}
if :code.ensure_loaded(Kernel.Typespec) == {:module, Kernel.Typespec} and
not Kernel.Typespec.defines_type?(__MODULE__, :t, 0) do
@type t :: term
end
@@ -90,7 +119,7 @@ defmodule Protocol do
end
defp do_defimpl(protocol, [do: block, for: for]) when is_list(for) do
lc f inlist for, do: do_defimpl(protocol, [do: block, for: f])
for f <- for, do: do_defimpl(protocol, [do: block, for: f])
end
defp do_defimpl(protocol, [do: block, for: for]) do
@@ -124,7 +153,7 @@ defmodule Protocol do
@doc false
def assert_protocol(module) do
case Code.ensure_compiled(module) do
{ :module, ^module } -> nil
{:module, ^module} -> nil
_ -> raise ArgumentError, message: "#{inspect module} is not loaded"
end
@@ -139,87 +168,50 @@ defmodule Protocol do
# Builtin types.
@doc false
def builtin do
[ Tuple, Atom, List, BitString, Integer, Float,
Function, PID, Port, Reference, Any ]
[is_tuple: Tuple,
is_atom: Atom,
is_list: List,
is_map: Map,
is_bitstring: BitString,
is_integer: Integer,
is_float: Float,
is_function: Function,
is_pid: PID,
is_port: Port,
is_reference: Reference]
end
# Implements the function that detects the protocol and
# returns the module to dispatch to.
@doc false
def impl_for(current) do
arg = quote(do: arg)
all = [Record|builtin]
def rec_impl_for(current) do
all = [Any] ++ for {_guard, mod} <- builtin, do: mod
arg = quote do: arg
target = Module.concat(current, Tuple)
{ arg,
lc(mod inlist all, do: impl_for(current, mod, arg)),
rec_impl_for(current, arg) }
end
fallback = quote do
case impl_for?(unquote(target)) do
true -> unquote(target).__impl__(:name)
false -> any_impl_for
end
end
defp rec_impl_for(current, arg) do
fallback = impl_for(current, Tuple, arg) |> elem(1)
impl_for = quote do
atom = :erlang.element(1, unquote(arg))
quote do
target = Module.concat(unquote(current), unquote(arg))
try do
target.__impl__(:name)
catch
:error, :undef, [[{ ^target, :__impl__, [:name], _ }|_]|_] ->
case not(atom in unquote(all)) and match?('Elixir.' ++ _, atom_to_list(atom)) do
true ->
target = Module.concat(unquote(current), atom)
case impl_for?(target) do
true -> target.__impl__(:name)
false -> unquote(fallback)
end
false ->
unquote(fallback)
end
end
end
defp impl_for(current, Record, arg) do
fallback = impl_for(current, Tuple, arg) |> elem(1)
dispatch = quote do
atom = :erlang.element(1, unquote(arg))
case not(atom in unquote(builtin)) and match?('Elixir.' ++ _, atom_to_list(atom)) do
true -> rec_impl_for(atom)
false -> unquote(fallback)
end
end
quote do
{ is_record(unquote(arg)), unquote(dispatch) }
end
end
defp impl_for(current, Tuple, arg), do: impl_with_fallback(Tuple, :is_tuple, current, arg)
defp impl_for(current, Atom, arg), do: impl_with_fallback(Atom, :is_atom, current, arg)
defp impl_for(current, List, arg), do: impl_with_fallback(List, :is_list, current, arg)
defp impl_for(current, BitString, arg), do: impl_with_fallback(BitString, :is_bitstring, current, arg)
defp impl_for(current, Integer, arg), do: impl_with_fallback(Integer, :is_integer, current, arg)
defp impl_for(current, Float, arg), do: impl_with_fallback(Float, :is_float, current, arg)
defp impl_for(current, Function, arg), do: impl_with_fallback(Function, :is_function, current, arg)
defp impl_for(current, PID, arg), do: impl_with_fallback(PID, :is_pid, current, arg)
defp impl_for(current, Port, arg), do: impl_with_fallback(Port, :is_port, current, arg)
defp impl_for(current, Reference, arg), do: impl_with_fallback(Reference, :is_reference, current, arg)
defp impl_for(_current, Any, _arg) do
{ true, quote(do: any_impl_for) }
end
# Defines an implementation with fallback to the given module.
defp impl_with_fallback(mod, guard, current, arg) do
quote do
{ :erlang.unquote(guard)(unquote(arg)),
unquote(with_fallback(Module.concat(current, mod))) }
end
end
# Tries to dispatch to a given target, fallbacks to the
# given `fallback` implementation if the target does not exist.
defp with_fallback(target) when is_atom(target) do
quote do
try do
unquote(target).__impl__(:name)
catch
:error, :undef, [[{ unquote(target), :__impl__, [:name], _ }|_]|_] ->
any_impl_for
end
end
{arg, impl_for}
end
end
@@ -227,18 +219,18 @@ defmodule Protocol.DSL do
@moduledoc false
@doc false
defmacro def({ _, _, args }) when args == [] or is_atom(args) do
defmacro def({_, _, args}) when args == [] or is_atom(args) do
raise ArgumentError, message: "protocol functions expect at least one argument"
end
defmacro def({ name, _, args }) when is_atom(name) and is_list(args) do
defmacro def({name, _, args}) when is_atom(name) and is_list(args) do
arity = length(args)
type_args = lc _ inlist :lists.seq(2, arity), do: quote(do: term)
type_args = for _ <- :lists.seq(2, arity), do: quote(do: term)
type_args = [quote(do: t) | type_args]
call_args = lc i inlist :lists.seq(2, arity),
do: { binary_to_atom(<<?x, i + 64>>), [], __MODULE__ }
call_args = for i <- :lists.seq(2, arity),
do: {binary_to_atom(<<?x, i + 64>>), [], __MODULE__}
call_args = [quote(do: t) | call_args]
quote do
@@ -270,17 +262,17 @@ defmodule Protocol.DSL do
@doc false
def __spec__?(module, name, arity) do
case :code.ensure_loaded(Kernel.Typespec) do
{ :module, Kernel.Typespec } ->
tuple = { name, arity }
{:module, Kernel.Typespec} ->
tuple = {name, arity}
specs = Module.get_attribute(module, :spec)
found = lc { k, v } inlist specs, k == tuple do
found = for {k, v} <- specs, k == tuple do
Kernel.Typespec.define_callback(module, tuple, v)
true
end
found != []
{ :error, _ } ->
{:error, _} ->
true
end
end
+64 -81
View File
@@ -62,15 +62,15 @@ defmodule Protocol.Consolidation do
end
defp extract_matching_by_attribute(paths, prefix, callback) do
lc path inlist paths,
file inlist list_dir(path),
for path <- paths,
file <- list_dir(path),
mod = extract_from_file(path, file, prefix, callback),
do: mod
end
defp list_dir(path) when is_list(path) do
case :file.list_dir(path) do
{ :ok, files } -> files
{:ok, files} -> files
_ -> []
end
end
@@ -78,14 +78,14 @@ defmodule Protocol.Consolidation do
defp list_dir(path), do: list_dir(to_char_list(path))
defp extract_from_file(path, file, prefix, callback) do
if :lists.prefix(prefix, file) and Path.extname(file) == '.beam' do
extract_from_beam(Path.join(path, file), callback)
if :lists.prefix(prefix, file) and :filename.extension(file) == '.beam' do
extract_from_beam(:filename.join(path, file), callback)
end
end
defp extract_from_beam(file, callback) do
case :beam_lib.chunks(file, [:attributes]) do
{:ok, { module, [attributes: attributes] } } ->
{:ok, {module, [attributes: attributes]}} ->
callback.(module, attributes)
_ ->
nil
@@ -95,7 +95,7 @@ defmodule Protocol.Consolidation do
defmacrop if_ok(expr, call) do
quote do
case unquote(expr) do
{ :ok, var } -> unquote(Macro.pipe(quote(do: var), call))
{:ok, var} -> unquote(Macro.pipe(quote(do: var), call, 0))
other -> other
end
end
@@ -121,10 +121,13 @@ defmodule Protocol.Consolidation do
nor loads the new bytecode for the compiled module.
"""
@spec apply_to(module, [module]) ::
{ :ok, binary } |
{ :error, :not_a_protocol } |
{ :error, :no_beam_info }
{:ok, binary} |
{:error, :not_a_protocol} |
{:error, :no_beam_info}
def apply_to(protocol, types) when is_atom(protocol) do
raise ArgumentError, message: "consolidation is disabled as we can't consolidate records " <>
"and structs at once. Consolidation will be added back once " <>
"polymorphic records are removed"
ensure_protocol(protocol)
|> if_ok(change_debug_info types)
|> if_ok(compile)
@@ -133,16 +136,16 @@ defmodule Protocol.Consolidation do
# Ensure the given module is loaded and is a protocol.
defp ensure_protocol(protocol) do
case :beam_lib.chunks(beam_file(protocol), [:abstract_code, :attributes]) do
{ :ok, { ^protocol, [abstract_code: { _raw, abstract_code },
attributes: attributes] } } ->
{:ok, {^protocol, [abstract_code: {_raw, abstract_code},
attributes: attributes]}} ->
case attributes[:protocol] do
[fallback_to_any: any, consolidated: _] ->
{ :ok, { protocol, any, abstract_code } }
{:ok, {protocol, any, abstract_code}}
_ ->
{ :error, :not_a_protocol }
{:error, :not_a_protocol}
end
_ ->
{ :error, :no_beam_info }
{:error, :no_beam_info}
end
end
@@ -155,38 +158,40 @@ defmodule Protocol.Consolidation do
# Change the debug information to the optimized
# impl_for/1 dispatch version.
defp change_debug_info({ protocol, any, code }, types) do
defp change_debug_info({protocol, any, code}, types) do
types = if any, do: types, else: List.delete(types, Any)
records = types -- Protocol.builtin
builtin = Protocol.builtin -- (Protocol.builtin -- types)
builtin = if records != [], do: [Record|builtin], else: builtin
change_impl_for(code, protocol, builtin, records, false, [])
all = [Any] ++ for {_guard, mod} <- Protocol.builtin, do: mod
structs = types -- all
change_impl_for(code, protocol, types, structs, false, [])
end
defp change_impl_for([{ :attribute, line, :protocol, _ }|t], protocol, builtin, records, _, acc) do
attr = [fallback_to_any: Any in builtin, consolidated: true]
change_impl_for(t, protocol, builtin, records, true,
[{ :attribute, line, :protocol, attr }|acc])
defp change_impl_for([{:attribute, line, :protocol, opts}|t], protocol, types, structs, _, acc) do
opts = [fallback_to_any: opts[:fallback_to_any], consolidated: true]
change_impl_for(t, protocol, types, structs, true,
[{:attribute, line, :protocol, opts}|acc])
end
defp change_impl_for([{ :function, line, :impl_for, 1, _ }|t], protocol, builtin, records, is_protocol, acc) do
clauses = lc type inlist builtin, do: clause_for(type, protocol, line)
defp change_impl_for([{:function, line, :impl_for, 1, _}|t], protocol, types, structs, is_protocol, acc) do
fallback = if Any in types, do: Module.concat(protocol, Any), else: nil
unless Any in builtin do
clauses = clauses ++ [fallback_clause_for(nil, protocol, line)]
end
clauses = for {guard, mod} <- Protocol.builtin,
mod in types,
do: builtin_clause_for(mod, guard, protocol, line)
change_impl_for(t, protocol, builtin, records, is_protocol,
[{ :function, line, :impl_for, 1, clauses }|acc])
clauses = [struct_clause_for(line)|clauses] ++
[fallback_clause_for(fallback, protocol, line)]
change_impl_for(t, protocol, types, structs, is_protocol,
[{:function, line, :impl_for, 1, clauses}|acc])
end
defp change_impl_for([{ :function, line, :rec_impl_for, 1, _ }|t], protocol, builtin, records, is_protocol, acc) do
fallback = if Tuple in builtin, do: Module.concat(protocol, Tuple)
clauses = lc type inlist records, do: record_clause_for(type, protocol, line)
clauses = clauses ++ [fallback_clause_for(fallback, protocol, line)]
defp change_impl_for([{:function, line, :struct_impl_for, 1, _}|t], protocol, types, structs, is_protocol, acc) do
fallback = if Any in types, do: Module.concat(protocol, Any), else: nil
clauses = for struct <- structs, do: each_struct_clause_for(struct, protocol, line)
clauses = clauses ++ [fallback_clause_for(fallback, protocol, line)]
change_impl_for(t, protocol, builtin, records, is_protocol,
[{ :function, line, :rec_impl_for, 1, clauses }|acc])
change_impl_for(t, protocol, types, structs, is_protocol,
[{:function, line, :struct_impl_for, 1, clauses}|acc])
end
defp change_impl_for([h|t], protocol, info, types, is_protocol, acc) do
@@ -195,72 +200,50 @@ defmodule Protocol.Consolidation do
defp change_impl_for([], protocol, _info, _types, is_protocol, acc) do
if is_protocol do
{ :ok, { protocol, Enum.reverse(acc) } }
{:ok, {protocol, Enum.reverse(acc)}}
else
{ :error, :not_a_protocol }
{:error, :not_a_protocol}
end
end
defp clause_for(Tuple, protocol, line), do: builtin_clause_for(Tuple, :is_tuple, protocol, line)
defp clause_for(Atom, protocol, line), do: builtin_clause_for(Atom, :is_atom, protocol, line)
defp clause_for(List, protocol, line), do: builtin_clause_for(List, :is_list, protocol, line)
defp clause_for(BitString, protocol, line), do: builtin_clause_for(BitString, :is_bitstring, protocol, line)
defp clause_for(Integer, protocol, line), do: builtin_clause_for(Integer, :is_integer, protocol, line)
defp clause_for(Float, protocol, line), do: builtin_clause_for(Float, :is_float, protocol, line)
defp clause_for(Function, protocol, line), do: builtin_clause_for(Function, :is_function, protocol, line)
defp clause_for(PID, protocol, line), do: builtin_clause_for(PID, :is_pid, protocol, line)
defp clause_for(Port, protocol, line), do: builtin_clause_for(Port, :is_port, protocol, line)
defp clause_for(Reference, protocol, line), do: builtin_clause_for(Reference, :is_reference, protocol, line)
defp clause_for(Any, protocol, line) do
{:clause, line, [{:var, line, :_}], [],
[{ :atom, line, Module.concat(protocol, Any) }]}
end
defp clause_for(Record, _protocol, line) do
{:clause, line, [{:var, line, :x}],
[[{:op, line, :andalso,
{:call, line,
{:remote, line, {:atom, line, :erlang}, {:atom, line, :is_tuple}},
[{:var, line, :x}]},
{:call, line,
{:remote, line, {:atom, line, :erlang}, {:atom, line, :is_atom}},
[{:call, line,
{:remote, line, {:atom, line, :erlang}, {:atom, line, :element}},
[{:integer, line, 1}, {:var, line, :x}]
}]},
}]],
[{:call, line,
{:atom, line, :rec_impl_for},
[{:call, line,
{:remote, line, {:atom, line, :erlang}, {:atom, line, :element}},
[{:integer, line, 1}, {:var, line, :x}]}]}]}
end
defp builtin_clause_for(mod, guard, protocol, line) do
{:clause, line,
[{:var, line, :x}],
[[{:call, line,
{:remote, line, {:atom, line, :erlang}, {:atom, line, guard}},
[{:var, line, :x}],
}]],
}]],
[{:atom, line, Module.concat(protocol, mod)}]}
end
defp record_clause_for(other, protocol, line) do
defp struct_clause_for(line) do
{:clause, line,
[{:map, line, [
{:map_field_exact, line, {:atom, line, :__struct__}, {:var, line, :x}}
]}],
[[{:call, line,
{:remote, line, {:atom, line, :erlang}, {:atom, line, :is_atom}},
[{:var, line, :x}],
}]],
[{:call, line,
{:atom, line, :struct_impl_for},
[{:var, line, :x}]}]}
end
defp each_struct_clause_for(other, protocol, line) do
{:clause, line, [{:atom, line, other}], [],
[{:atom, line, Module.concat(protocol, other)}]}
end
defp fallback_clause_for(value, _protocol, line) do
{:clause, line, [{:var, line, :_}], [],
[{ :atom, line, value }]}
[{:atom, line, value}]}
end
# Finally compile the module and emit its bytecode.
defp compile({ protocol, code }) do
defp compile({protocol, code}) do
opts = if Code.compiler_options[:debug_info], do: [:debug_info], else: []
{ :ok, ^protocol, binary, _warnings } = :compile.forms(code, [:return|opts])
{ :ok, binary }
{:ok, ^protocol, binary, _warnings} = :compile.forms(code, [:return|opts])
{:ok, binary}
end
end
+43 -24
View File
@@ -1,34 +1,53 @@
defmodule Range do
@moduledoc """
Defines a Range.
A Range are represented internally as a struct. However,
the most common form of creating and matching on ranges
is via the `../2` macro, auto-imported from Kernel:
iex> range = 1..3
1..3
iex> first .. last = range
iex> first
1
iex> last
3
"""
@type t :: { Range, any, any }
@type t(first, last) :: { Range, first, last }
defstruct first: nil, last: nil
@type t(first, last) :: %{__struct__: Range, first: first, last: last}
@doc """
Creates a new range.
"""
def new(first, last) do
{ Range, first, last }
%Range{first: first, last: last}
end
@doc """
Returns the first item of the range.
"""
def first({ Range, first, _ }) do
first
end
Returns true if the given argument is a range.
## Examples
iex> Range.range?(1..3)
true
iex> Range.range?(0)
false
@doc """
Returns the last item of the range.
"""
def last({ Range, _, last }) do
last
end
def range?(%Range{}), do: true
def range?(_), do: false
end
defprotocol Range.Iterator do
@moduledoc """
A protocol used for iterating range elements.
"""
@doc """
Returns the function that calculates the next item.
"""
@@ -45,36 +64,36 @@ defimpl Enumerable, for: Range do
reduce(first, last, acc, fun, Range.Iterator.next(first, range), last >= first)
end
defp reduce(_x, _y, { :halt, acc }, _fun, _next, _up) do
{ :halted, acc }
defp reduce(_x, _y, {:halt, acc}, _fun, _next, _up) do
{:halted, acc}
end
defp reduce(x, y, { :suspend, acc }, fun, next, up) do
{ :suspended, acc, &reduce(x, y, &1, fun, next, up) }
defp reduce(x, y, {:suspend, acc}, fun, next, up) do
{:suspended, acc, &reduce(x, y, &1, fun, next, up)}
end
defp reduce(x, y, { :cont, acc }, fun, next, true) when x <= y do
defp reduce(x, y, {:cont, acc}, fun, next, true) when x <= y do
reduce(next.(x), y, fun.(x, acc), fun, next, true)
end
defp reduce(x, y, { :cont, acc }, fun, next, false) when x >= y do
defp reduce(x, y, {:cont, acc}, fun, next, false) when x >= y do
reduce(next.(x), y, fun.(x, acc), fun, next, false)
end
defp reduce(_, _, { :cont, acc }, _fun, _next, _up) do
{ :done, acc }
defp reduce(_, _, {:cont, acc}, _fun, _next, _up) do
{:done, acc}
end
def member?(first .. last, value) do
if first <= last do
{ :ok, first <= value and value <= last }
{:ok, first <= value and value <= last}
else
{ :ok, last <= value and value <= first }
{:ok, last <= value and value <= first}
end
end
def count(first .. _ = range) do
{ :ok, Range.Iterator.count(first, range) }
{:ok, Range.Iterator.count(first, range)}
end
end
+150 -875
View File
File diff suppressed because it is too large Load Diff
+153
View File
@@ -0,0 +1,153 @@
defmodule Record.Backend do
# Callback functions invoked by defrecord, defrecordp and friends.
@moduledoc false
@doc """
Normalizes a list of record or struct fields to have default values.
"""
def default_fields(type, fields) do
Enum.map(fields, fn
{ key, _ } = pair when is_atom(key) -> pair
key when is_atom(key) -> { key, nil }
other -> raise ArgumentError, message: "#{type} fields must be atoms, got: #{inspect other}"
end)
end
@doc """
Splits a keywords list into fields and types.
This logic is shared by records and structs.
"""
def split_fields_and_types(kv) do
if Keyword.keyword?(kv) do
split_fields_and_types(kv, [], [])
else
{kv, []}
end
end
defp split_fields_and_types([{field, {:::, _, [default, type]}}|t], fields, types) do
split_fields_and_types(t, [{field, default}|fields], [{field, type}|types])
end
defp split_fields_and_types([{field, default}|t], fields, types) do
split_fields_and_types(t, [{field, default}|fields], [{field, quote(do: term)}|types])
end
defp split_fields_and_types([field|t], fields, types) do
split_fields_and_types(t, [field|fields], [{field, quote(do: term)}|types])
end
defp split_fields_and_types([], fields, types) do
{:lists.reverse(fields), :lists.reverse(types)}
end
@doc """
Callback invoked from record/0 and record/1 macros.
"""
def access(atom, fields, args, caller) do
cond do
is_atom(args) ->
index(atom, fields, args)
Keyword.keyword?(args) ->
create(atom, fields, args, caller)
true ->
raise ArgumentError,
message: "expected arguments to be a compile time atom or keywords, got: #{Macro.to_string args}"
end
end
@doc """
Callback invoked from the record/2 macro.
"""
def access(atom, fields, record, args, caller) do
cond do
is_atom(args) ->
get(atom, fields, record, args)
Keyword.keyword?(args) ->
update(atom, fields, record, args, caller)
true ->
raise ArgumentError,
message: "expected arguments to be a compile time atom or keywords, got: #{Macro.to_string args}"
end
end
@doc """
Gets the index of field.
"""
def index(atom, fields, field) do
if index = find_index(fields, field, 0) do
index - 1 # Convert to Elixir index
else
raise ArgumentError, message: "record #{inspect atom} does not have the key: #{inspect field}"
end
end
@doc """
Creates a new record with the given default fields and keyword values.
"""
def create(atom, fields, keyword, caller) do
in_match = Macro.Env.in_match?(caller)
{match, remaining} =
Enum.map_reduce(fields, keyword, fn({field, default}, each_keyword) ->
new_fields =
case Keyword.has_key?(each_keyword, field) do
true -> Keyword.get(each_keyword, field)
false ->
case in_match do
true -> {:_, [], nil}
false -> Macro.escape(default)
end
end
{new_fields, Keyword.delete(each_keyword, field)}
end)
case remaining do
[] ->
{:{}, [], [atom|match]}
_ ->
keys = for {key, _} <- remaining, do: key
raise ArgumentError, message: "record #{inspect atom} does not have the key: #{inspect hd(keys)}"
end
end
@doc """
Updates a record given by var with the given keyword.
"""
def update(atom, fields, var, keyword, caller) do
if Macro.Env.in_match?(caller) do
raise ArgumentError, message: "cannot invoke update style macro inside match"
end
Enum.reduce keyword, var, fn({key, value}, acc) ->
index = find_index(fields, key, 0)
if index do
quote do
:erlang.setelement(unquote(index), unquote(acc), unquote(value))
end
else
raise ArgumentError, message: "record #{inspect atom} does not have the key: #{inspect key}"
end
end
end
@doc """
Gets a record key from the given var.
"""
def get(atom, fields, var, key) do
index = find_index(fields, key, 0)
if index do
quote do
:erlang.element(unquote(index), unquote(var))
end
else
raise ArgumentError, message: "record #{inspect atom} does not have the key: #{inspect key}"
end
end
defp find_index([{k, _}|_], k, i), do: i + 2
defp find_index([{_, _}|t], k, i), do: find_index(t, k, i + 1)
defp find_index([], _k, _i), do: nil
end
+526
View File
@@ -0,0 +1,526 @@
defmodule Record.Deprecated do
@moduledoc false
def defrecord(name, fields, opts) do
block = Keyword.get(opts, :do, nil)
record_check!(fields)
quote do
unquoted_fields = unquote(fields)
defmodule unquote(name) do
import Record.DSL
@record_fields []
@record_types []
Record.Deprecated.deffunctions(unquoted_fields, __ENV__)
value = unquote(block)
Record.Deprecated.deftypes(@record_fields, @record_types, __ENV__)
value
end
end
end
defp record_check!([{field, {:::, _, [_, _]}}|_]) when is_atom(field) do
raise ArgumentError, message: "typespecs are not supported inlined with defrecord, " <>
"please use record_type instead"
end
defp record_check!([_|t]), do: record_check!(t)
defp record_check!(_), do: :ok
def defrecordp(name, tag, fields) do
case recordp_split(fields, [], [], false) do
{:ok, fields, types, def_type} ->
types = Macro.escape(types)
# bind_quoted isn't available when bootstrapping record
quoted = quote [unquote: false] do
Record.Deprecated.defmacros(name, fields, __ENV__, tag)
if def_type do
type = binary_to_atom(atom_to_binary(name) <> "_t")
@typep unquote(type)() :: {unquote(tag || name), unquote_splicing(types)}
end
end
quote do
def_type = unquote(def_type)
fields = unquote(fields)
types = unquote(types)
tag = unquote(tag)
name = unquote(name)
unquote(quoted)
end
:error ->
quote do
name = unquote(name)
Record.Deprecated.defmacros(name, unquote(fields), __ENV__, unquote(tag))
end
end
end
defp recordp_split([{field, {:::, _, [default, type]}}|t], defaults, types, _) do
recordp_split(t, [{field, default}|defaults], [type|types], true)
end
defp recordp_split([other|t], defaults, types, def_type) do
recordp_split(t, [other|defaults], [quote(do: term)|types], def_type)
end
defp recordp_split([], defaults, types, def_type) do
{:ok, :lists.reverse(defaults), :lists.reverse(types), def_type}
end
defp recordp_split(_, _, _, _) do
:error
end
def deffunctions(values, env) do
values = for value <- values, do: convert_value(value)
escaped = Macro.escape(values)
contents = [
reflection(escaped),
initializer(escaped),
conversions(values),
record_optimizable(),
updater(values),
accessors(values, 1),
switch_recorder()
]
contents = [quote(do: @record_fields unquote(escaped))|contents]
# Special case for bootstrapping purposes
if env == Macro.Env do
Module.eval_quoted(env, contents, [], [])
else
Module.eval_quoted(env.module, contents, [], Macro.Env.location(env))
end
end
def deftypes(values, types, env) do
types = types || []
values = for value <- values do
{name, default} = convert_value(value)
{name, default, find_spec(types, name)}
end
contents = [
core_specs(values),
accessor_specs(values, 1, [])
]
# We need to handle bootstraping
cond do
:code.ensure_loaded(Kernel.Typespec) != {:module, Kernel.Typespec} ->
nil
env == Macro.Env ->
Module.eval_quoted(env, contents, [], [])
true ->
Module.eval_quoted(env.module, contents, [], Macro.Env.location(env))
end
end
def defmacros(name, values, env, tag \\ nil)
when is_atom(name) and is_list(values) and is_atom(tag) do
escaped = for value <- values do
{key, value} = convert_value(value)
{key, Macro.escape(value)}
end
tag = tag || name
contents = quote do
defmacrop unquote(name)() do
Record.Deprecated.access(unquote(tag), unquote(escaped), [], __CALLER__)
end
defmacrop unquote(name)(args) do
Record.Deprecated.access(unquote(tag), unquote(escaped), args, __CALLER__)
end
defmacrop unquote(name)(record, args) do
Record.Deprecated.dispatch(unquote(tag), unquote(escaped), record, args, __CALLER__)
end
end
Module.eval_quoted(env.module, contents, [], Macro.Env.location(env))
end
## Callbacks
defmacro __before_compile__(_) do
quote do
def __record__(:optimizable), do: @record_optimizable
end
end
def __on_definition__(env, kind, name, args, _guards, _body) do
tuple = {name, length(args)}
module = env.module
functions = Module.get_attribute(module, :record_optimizable)
functions =
if kind in [:def] and Module.get_attribute(module, :record_optimized) do
[tuple|functions]
else
List.delete(functions, tuple)
end
Module.put_attribute(module, :record_optimizable, functions)
end
def access(atom, fields, arg, _caller) when is_atom(arg) do
if index = find_index(fields, arg, 0) do
index + 1
else
raise ArgumentError, message: "record #{inspect atom} does not have the key: #{inspect arg}"
end
end
def access(atom, fields, keyword, caller) do
unless is_keyword(keyword) do
raise ArgumentError, message: "expected contents inside brackets to be a keyword list or an atom, got: #{inspect keyword}"
end
in_match = Macro.Env.in_match?(caller)
has_underscore_value = Keyword.has_key?(keyword, :_)
underscore_value = Keyword.get(keyword, :_, {:_, [], nil})
keyword = Keyword.delete keyword, :_
iterator = fn({field, default}, each_keyword) ->
new_fields =
case Keyword.has_key?(each_keyword, field) do
true -> Keyword.get(each_keyword, field)
false ->
case in_match or has_underscore_value do
true -> underscore_value
false -> Macro.escape(default)
end
end
{new_fields, Keyword.delete(each_keyword, field)}
end
{match, remaining} = :lists.mapfoldl(iterator, keyword, fields)
case remaining do
[] ->
{:{}, [], [atom|match]}
_ ->
keys = for {key, _} <- remaining, do: key
raise ArgumentError, message: "record #{inspect atom} does not have the key: #{inspect hd(keys)}"
end
end
def dispatch(atom, fields, record, args, caller) do
cond do
is_atom(args) ->
get(atom, fields, record, args)
is_keyword(args) ->
update(atom, fields, record, args, caller)
true ->
raise ArgumentError, message: "expected arguments to be a compile time atom or keywords"
end
end
defp update(atom, fields, var, keyword, caller) do
unless is_keyword(keyword) do
raise ArgumentError, message: "expected arguments to be compile time keywords"
end
if Macro.Env.in_match?(caller) do
raise ArgumentError, message: "cannot invoke update style macro inside match context"
end
Enum.reduce keyword, var, fn({key, value}, acc) ->
index = find_index(fields, key, 0)
if index do
quote do
:erlang.setelement(unquote(index + 2), unquote(acc), unquote(value))
end
else
raise ArgumentError, message: "record #{inspect atom} does not have the key: #{inspect key}"
end
end
end
defp get(atom, fields, var, key) do
index = find_index(fields, key, 0)
if index do
quote do
:erlang.element(unquote(index + 2), unquote(var))
end
else
raise ArgumentError, message: "record #{inspect atom} does not have the key: #{inspect key}"
end
end
## Function generation
defp reflection(values) do
quoted = for {k, _} <- values do
index = find_index(values, k, 0)
quote do
def __record__(:index, unquote(k)), do: unquote(index + 1)
end
end
quote do
unquote(quoted)
@doc false
def __record__(:index, _), do: nil
@doc false
def __record__(:index, arg, _), do: __record__(:index, arg)
@doc false
def __record__(kind, _), do: __record__(kind)
@doc false
def __record__(:name), do: __MODULE__
def __record__(:fields), do: unquote(values)
end
end
defp initializer(values) do
defaults = for {_, value} <- values, do: value
# For each value, define a piece of code that will receive
# an ordered dict of options (opts) and it will try to fetch
# the given key from the ordered dict, falling back to the
# default value if one does not exist.
atom_selective = for {k, v} <- values, do: initialize_lookup(k, v)
string_selective = for {k, v} <- values, do: initialize_lookup(atom_to_binary(k), v)
quote do
@doc false
def new(), do: new([])
@doc false
def new([]), do: {__MODULE__, unquote_splicing(defaults)}
def new([{key, _}|_] = opts) when is_atom(key), do: {__MODULE__, unquote_splicing(atom_selective)}
def new([{key, _}|_] = opts) when is_binary(key), do: {__MODULE__, unquote_splicing(string_selective)}
end
end
defp initialize_lookup(k, v) do
quote do
case :lists.keyfind(unquote(k), 1, opts) do
false -> unquote(v)
{_, v} -> v
end
end
end
defp conversions(values) do
sorted = for {k, _} <- values do
index = find_index(values, k, 0)
{k, quote(do: :erlang.element(unquote(index + 2), record))}
end
quote do
@doc false
def to_keywords(record) do
unquote(sorted)
end
end
end
defp accessors([{:__exception__, _}|t], 1) do
accessors(t, 2)
end
defp accessors([{key, _default}|t], i) do
update = binary_to_atom "update_" <> atom_to_binary(key)
contents = quote do
@doc false
def unquote(key)(record) do
:erlang.element(unquote(i + 1), record)
end
@doc false
def unquote(key)(value, record) do
:erlang.setelement(unquote(i + 1), record, value)
end
@doc false
def unquote(update)(function, record) do
:erlang.setelement(unquote(i + 1), record,
function.(:erlang.element(unquote(i + 1), record)))
end
end
[contents|accessors(t, i + 1)]
end
defp accessors([], _i) do
[]
end
# Define an updater method that receives a
# keyword list and updates the record.
defp updater(values) do
atom_fields =
for {key, _default} <- values, do: updater_lookup(key, key, values)
string_fields =
for {key, _default} <- values, do: updater_lookup(atom_to_binary(key), key, values)
atom_contents = quote do: {__MODULE__, unquote_splicing(atom_fields)}
string_contents = quote do: {__MODULE__, unquote_splicing(string_fields)}
quote do
@doc false
def update([], record) do
record
end
def update([{key, _}|_] = keywords, record) when is_atom(key) do
unquote(atom_contents)
end
def update([{key, _}|_] = keywords, record) when is_binary(key) do
unquote(string_contents)
end
end
end
defp updater_lookup(k, key, values) do
index = find_index(values, key, 0)
quote do
case :lists.keyfind(unquote(k), 1, keywords) do
false -> :erlang.element(unquote(index + 2), record)
{_, value} -> value
end
end
end
defp record_optimizable do
quote do
@record_optimized true
@record_optimizable []
@before_compile {unquote(__MODULE__), :__before_compile__}
@on_definition {unquote(__MODULE__), :__on_definition__}
end
end
defp switch_recorder do
quote do: @record_optimized false
end
## Types/specs generation
defp core_specs(values) do
types = for {_, _, spec} <- values, do: spec
options = for {k, _, v} <- values, do: {k, v}
quote do
unless Kernel.Typespec.defines_type?(__MODULE__, :t, 0) do
@type t :: {__MODULE__, unquote_splicing(types)}
end
unless Kernel.Typespec.defines_type?(__MODULE__, :options, 0) do
@type options :: unquote(options) | [{String.t, term}]
end
@spec new :: t
@spec new(options) :: t
@spec to_keywords(t) :: options
@spec update(options, t) :: t
@spec __record__(:name) :: atom
@spec __record__(:fields) :: [{atom, any}]
@spec __record__(:index, atom) :: non_neg_integer | nil
end
end
defp accessor_specs([{:__exception__, _, _}|t], 1, acc) do
accessor_specs(t, 2, acc)
end
defp accessor_specs([{key, _default, spec}|t], i, acc) do
update = binary_to_atom "update_" <> atom_to_binary(key)
contents = quote do
@spec unquote(key)(t) :: unquote(spec)
@spec unquote(key)(unquote(spec), t) :: t
@spec unquote(update)((unquote(spec) -> unquote(spec)), t) :: t
end
accessor_specs(t, i + 1, [contents | acc])
end
defp accessor_specs([], _i, acc), do: acc
## Helpers
defp is_keyword(list) when is_list(list), do: :lists.all(&is_keyword_tuple/1, list)
defp is_keyword(_), do: false
defp is_keyword_tuple({x, _}) when is_atom(x), do: true
defp is_keyword_tuple(_), do: false
defp convert_value(atom) when is_atom(atom), do: {atom, nil}
defp convert_value({atom, other}) when is_atom(atom), do:
{atom, check_value(atom, other)}
defp convert_value({field, _}), do:
raise(ArgumentError, message: "record field name has to be an atom, got #{inspect field}")
defp check_value(atom, other) when is_list(other) do
for(i <- other, do: check_value(atom, i))
other
end
defp check_value(atom, other) when is_tuple(other) do
for(i <- tuple_to_list(other), do: check_value(atom, i))
other
end
defp check_value(atom, other) when is_function(other) do
unless :erlang.fun_info(other, :env) == {:env, []} and
:erlang.fun_info(other, :type) == {:type, :external} do
raise ArgumentError, message: "record field default value #{inspect atom} can only contain " <>
"functions that point to an existing &Mod.fun/arity"
end
other
end
defp check_value(atom, other) when is_reference(other) or is_pid(other) or is_port(other) do
raise(ArgumentError, message: "record field default value #{inspect atom} cannot contain a reference, pid or port")
end
defp check_value(_atom, other), do: other
defp find_index([{k, _}|_], k, i), do: i
defp find_index([{_, _}|t], k, i), do: find_index(t, k, i + 1)
defp find_index([], _k, _i), do: nil
defp find_spec(types, name) do
matches = for {k, v} <- types, name == k, do: v
case matches do
[h|_] -> h
_ -> quote do: term
end
end
end
defmodule Record.DSL do
@moduledoc false
defmacro record_type(opts) when is_list(opts) do
escaped = for {k, v} <- opts, do: {k, Macro.escape(v)}
quote do
@record_types Keyword.merge(@record_types || [], unquote(escaped))
end
end
end
+26 -26
View File
@@ -3,8 +3,8 @@ defmodule Record.Extractor do
# Retrieve a record definition from an Erlang file using
# the same lookup as the *include* attribute from Erlang modules.
def retrieve(name, from: file) when is_binary(file) do
file = String.to_char_list!(file)
def extract(name, from: file) when is_binary(file) do
file = List.from_char_data!(file)
realfile =
case :code.where_is_file(file) do
@@ -12,26 +12,26 @@ defmodule Record.Extractor do
realfile -> realfile
end
retrieve_record(name, realfile)
extract_record(name, realfile)
end
# Retrieve a record definition from an Erlang file using
# the same lookup as the *include_lib* attribute from Erlang modules.
def retrieve(name, from_lib: file) when is_binary(file) do
[app|path] = :filename.split(String.to_char_list!(file))
def extract(name, from_lib: file) when is_binary(file) do
[app|path] = :filename.split(List.from_char_data!(file))
case :code.lib_dir(list_to_atom(app)) do
{ :error, _ } ->
{:error, _} ->
raise ArgumentError, message: "lib file #{file} could not be found"
libpath ->
retrieve_record name, :filename.join([libpath|path])
extract_record name, :filename.join([libpath|path])
end
end
# Retrieve the record with the given name from the given file
defp retrieve_record(name, file) do
defp extract_record(name, file) do
form = read_file(file)
records = retrieve_records(form)
records = extract_records(form)
if record = List.keyfind(records, name, 0) do
parse_record(record, form)
else
@@ -39,9 +39,9 @@ defmodule Record.Extractor do
end
end
# Parse the given file and retrieve all existent records.
defp retrieve_records(form) do
lc { :attribute, _, :record, record } inlist form, do: record
# Parse the given file and extract all existent records.
defp extract_records(form) do
for {:attribute, _, :record, record} <- form, do: record
end
# Read a file and return its abstract syntax form that also
@@ -49,7 +49,7 @@ defmodule Record.Extractor do
# by using Erlang's epp_dodger.
defp read_file(file) do
case :epp_dodger.quick_parse_file(file) do
{ :ok, form } ->
{:ok, form} ->
form
other ->
raise "error parsing file #{file}, got: #{inspect(other)}"
@@ -59,34 +59,34 @@ defmodule Record.Extractor do
# Parse a tuple with name and fields and returns a
# list of tuples where the first element is the field
# and the second is its default value.
defp parse_record({ _name, fields }, form) do
cons = List.foldr fields, { nil, 0 }, fn f, acc ->
{ :cons, 0, parse_field(f), acc }
defp parse_record({_name, fields}, form) do
cons = List.foldr fields, {nil, 0}, fn f, acc ->
{:cons, 0, parse_field(f), acc}
end
eval_record(cons, form)
end
defp parse_field({ :typed_record_field, record_field, _type }) do
defp parse_field({:typed_record_field, record_field, _type}) do
parse_field(record_field)
end
defp parse_field({ :record_field, _, key }) do
{ :tuple, 0, [key, {:atom, 0, :undefined}] }
defp parse_field({:record_field, _, key}) do
{:tuple, 0, [key, {:atom, 0, :undefined}]}
end
defp parse_field({ :record_field, _, key, value }) do
{ :tuple, 0, [key, value] }
defp parse_field({:record_field, _, key, value}) do
{:tuple, 0, [key, value]}
end
defp eval_record(cons, form) do
form = form ++
[ { :function, 0, :hello, 0, [
{ :clause, 0, [], [], [ cons ] } ] } ]
[ {:function, 0, :hello, 0, [
{:clause, 0, [], [], [ cons ]} ]} ]
{ :function, 0, :hello, 0, [
{ :clause, 0, [], [], [ record_ast ] } ] } = :erl_expand_records.module(form, []) |> List.last
{:function, 0, :hello, 0, [
{:clause, 0, [], [], [ record_ast ]} ]} = :erl_expand_records.module(form, []) |> List.last
{ :value, record, _ } = :erl_eval.expr(record_ast, [])
{:value, record, _} = :erl_eval.expr(record_ast, [])
record
end
end
+323 -145
View File
@@ -1,21 +1,29 @@
defmodule Regex do
@moduledoc %S"""
Regular expressions for Elixir built on top of the `re` module
in the Erlang Standard Library. More information can be found
in the [`re` documentation](http://www.erlang.org/doc/man/re.html).
@moduledoc ~S"""
Regular expressions for Elixir built on top of Erlang's `re` module.
As the `re` module, Regex is based on PCRE
(Perl Compatible Regular Expressions). More information can be
found in the [`re` documentation](http://www.erlang.org/doc/man/re.html).
Regular expressions in Elixir can be created using `Regex.compile!/2`
or using the special form with [`%r`](Kernel.html#sigil_r/2):
or using the special form with [`~r`](Kernel.html#sigil_r/2):
# A simple regular expressions that matches foo anywhere in the string
%r/foo/
~r/foo/
# A regular expression with case insensitive options
%r/foo/i
# A regular expression with case insensitive and unicode options
~r/foo/iu
The `re` module provides several options, the ones available in Elixir, followed by
their shortcut in parenthesis, are:
A Regex is represented internally as the `Regex` struct. Therefore,
`%Regex{}` can be used whenever there is a need to match on them.
## Modifiers
The modifiers available when creating a Regex are:
* `unicode` (u) - enables unicode specific patterns like \p. it expects valid unicode
strings to be given on match
* `caseless` (i) - add case insensitivity
* `dotall` (s) - causes dot to match newlines and also set newline to anycrlf.
The new line setting can be overridden by setting `(*CR)` or `(*LF)` or
@@ -27,7 +35,6 @@ defmodule Regex do
* `firstline` (f) - forces the unanchored pattern to match before or at the first
newline, though the matched text may continue over the newline
* `ungreedy` (r) - inverts the "greediness" of the regexp
* `groups` (g) - compiles with info about groups available
The options not available are:
@@ -37,13 +44,31 @@ defmodule Regex do
* `newline` - not available, use `(*CR)` or `(*LF)` or `(*CRLF)` or `(*ANYCRLF)`
or `(*ANY)` at the beginning of the regexp according to the re documentation
Most of the functions in this module accept either a binary or a char list
as subject. The result is based on the argument (a binary will return
a binary, a char list will return a char list).
## Captures
Many functions in this module allows what to capture in a regex
match via the `:capture` option. The supported values are:
* `:all` - all captured subpatterns including the complete matching string.
This is the default;
* `:first` - only the first captured subpattern, which is always the complete
matching part of the string. All explicitly captured subpatterns are
discarded;
* `:all_but_first`- all but the first matching subpattern, i.e. all explicitly
captured subpatterns, but not the complete matching part of
the string;
* `:none` - do not return matching subpatterns at all;
* `:all_names` - captures all names in the Regex;
* `list(binary)` - a list of named captures to capture;
"""
defrecordp :regex, Regex, [:re_pattern, :source, :options, :groups]
@type t :: { Regex, term, binary, binary, [atom] | nil }
defstruct re_pattern: nil :: term, source: "" :: binary, opts: "" :: binary
defexception CompileError, message: "regex could not be compiled"
@@ -51,32 +76,31 @@ defmodule Regex do
Compiles the regular expression.
The given options can either be a binary with the characters
representing the same regex options given to the `%r` sigil,
representing the same regex options given to the `~r` sigil,
or a list of options, as expected by the [Erlang `re` docs](http://www.erlang.org/doc/man/re.html).
It returns `{ :ok, regex }` in case of success,
`{ :error, reason }` otherwise.
It returns `{:ok, regex}` in case of success,
`{:error, reason}` otherwise.
## Examples
iex> Regex.compile("foo")
{:ok, %r"foo"}
{:ok, ~r"foo"}
iex> Regex.compile("*foo")
{:error, {'nothing to repeat', 0}}
"""
@spec compile(binary, binary | [term]) :: { :ok, t } | { :error, any }
@spec compile(binary, binary | [term]) :: {:ok, t} | {:error, any}
def compile(source, options \\ "")
def compile(source, options) when is_binary(options) do
case translate_options(options) do
{ :error, rest } ->
{ :error, { :invalid_option, rest } }
{:error, rest} ->
{:error, {:invalid_option, rest}}
translated_options ->
# Always use the unicode option, we don't have a latin1 legacy like
# Erlang.
compile(source, [:unicode|translated_options], options)
compile(source, translated_options, options)
end
end
@@ -85,12 +109,9 @@ defmodule Regex do
end
defp compile(source, opts, doc_opts) when is_binary(source) do
re_opts = opts -- [:groups]
groups = if opts != re_opts, do: parse_groups(source)
case :re.compile(source, re_opts) do
{ :ok, re_pattern } ->
{ :ok, regex(re_pattern: re_pattern, source: source, options: doc_opts, groups: groups) }
case :re.compile(source, opts) do
{:ok, re_pattern} ->
{:ok, %Regex{re_pattern: re_pattern, source: source, opts: doc_opts}}
error ->
error
end
@@ -102,8 +123,8 @@ defmodule Regex do
"""
def compile!(source, options \\ "") do
case compile(source, options) do
{ :ok, regex } -> regex
{ :error, { reason, at } } -> raise Regex.CompileError, message: "#{reason} at position #{at}"
{:ok, regex} -> regex
{:error, {reason, at}} -> raise Regex.CompileError, message: "#{reason} at position #{at}"
end
end
@@ -112,75 +133,95 @@ defmodule Regex do
## Examples
iex> Regex.match?(%r/foo/, "foo")
iex> Regex.match?(~r/foo/, "foo")
true
iex> Regex.match?(%r/foo/, "bar")
iex> Regex.match?(~r/foo/, "bar")
false
"""
def match?(regex(re_pattern: compiled), string) do
:re.run(string, compiled, [{ :capture, :none }]) == :match
def match?(%Regex{re_pattern: compiled}, string) when is_binary(string) do
:re.run(string, compiled, [{:capture, :none}]) == :match
end
@doc """
Returns true if the given argument is a regex.
## Examples
iex> Regex.regex?(~r/foo/)
true
iex> Regex.regex?(0)
false
"""
def regex?(%Regex{}), do: true
def regex?(_), do: false
@doc """
Runs the regular expression against the given string until the first match.
It returns a list with all captures or `nil` if no match occurred.
When the option `:capture` is set to `:groups`, it will capture all
the groups in the regex.
## Options
* `:return` - Set to `:index` to return indexes. Defaults to `:binary`;
* `:capture` - What to capture in the result. Check the moduledoc for Regex
to see the possible capture values;
## Examples
iex> Regex.run(%r/c(d)/, "abcd")
iex> Regex.run(~r/c(d)/, "abcd")
["cd", "d"]
iex> Regex.run(%r/e/, "abcd")
iex> Regex.run(~r/e/, "abcd")
nil
iex> Regex.run(%r/c(d)/, "abcd", return: :index)
iex> Regex.run(~r/c(d)/, "abcd", return: :index)
[{2,2},{3,1}]
"""
def run(regex, string, options \\ [])
def run(regex(re_pattern: compiled, groups: groups), string, options) do
return = Keyword.get(options, :return, return_for(string))
def run(%Regex{re_pattern: compiled}, string, options) when is_binary(string) do
return = Keyword.get(options, :return, :binary)
captures = Keyword.get(options, :capture, :all)
captures =
case Keyword.get(options, :capture, :all) do
:groups -> groups || raise ArgumentError, message: "regex was not compiled with g"
others -> others
end
case :re.run(string, compiled, [{ :capture, captures, return }]) do
case :re.run(string, compiled, [{:capture, captures, return}]) do
:nomatch -> nil
:match -> []
{ :match, results } -> results
{:match, results} -> results
end
end
@doc """
Returns the given captures as a keyword list or `nil` if no captures
are found. Requires the regex to be compiled with the groups option.
Returns the given captures as a map or `nil` if no captures are
found. The option `:return` can be set to `:index` to get indexes
back.
## Examples
iex> Regex.named_captures(%r/c(?<foo>d)/g, "abcd")
[foo: "d"]
iex> Regex.named_captures(%r/a(?<foo>b)c(?<bar>d)/g, "abcd")
[foo: "b", bar: "d"]
iex> Regex.named_captures(%r/a(?<foo>b)c(?<bar>d)/g, "efgh")
iex> Regex.named_captures(~r/c(?<foo>d)/, "abcd")
%{"foo" => "d"}
iex> Regex.named_captures(~r/a(?<foo>b)c(?<bar>d)/, "abcd")
%{"bar" => "d", "foo" => "b"}
iex> Regex.named_captures(~r/a(?<foo>b)c(?<bar>d)/, "efgh")
nil
"""
def named_captures(regex(groups: groups) = regex, string, options \\ []) do
options = Keyword.put_new(options, :capture, :groups)
def named_captures(regex, string, options \\ []) when is_binary(string) do
names = names(regex)
options = Keyword.put(options, :capture, names)
results = run(regex, string, options)
if results, do: Enum.zip(groups, results)
if results, do: Enum.zip(names, results) |> Enum.into(%{})
end
@doc """
Returns the underlying `re_pattern` in the regular expression.
"""
def re_pattern(regex(re_pattern: compiled)) do
def re_pattern(%Regex{re_pattern: compiled}) do
compiled
end
@@ -189,11 +230,11 @@ defmodule Regex do
## Examples
iex> Regex.source(%r(foo))
iex> Regex.source(~r(foo))
"foo"
"""
def source(regex(source: source)) do
def source(%Regex{source: source}) do
source
end
@@ -202,25 +243,26 @@ defmodule Regex do
## Examples
iex> Regex.opts(%r(foo)m)
iex> Regex.opts(~r(foo)m)
"m"
"""
def opts(regex(options: options)) do
options
def opts(%Regex{opts: opts}) do
opts
end
@doc """
Returns a list of named groups in the regex.
Returns a list of names in the regex.
## Examples
iex> Regex.groups(%r/(?<foo>bar)/g)
[:foo]
iex> Regex.names(~r/(?<foo>bar)/)
["foo"]
"""
def groups(regex(groups: groups)) do
groups
def names(%Regex{re_pattern: re_pattern}) do
{:namelist, names} = :re.inspect(re_pattern, :namelist)
names
end
@doc """
@@ -229,126 +271,276 @@ defmodule Regex do
where each entry in the primary list represents a match and each
entry in the secondary list represents the captured contents.
The captured contents defaults to `:all`, which includes the whole
regex match and each capture.
## Options
When the option `:capture` is set to `:groups`, it will capture all
the groups in the regex.
* `:return` - Set to `:index` to return indexes. Defaults to `:binary`;
* `:capture` - What to capture in the result. Check the moduledoc for Regex
to see the possible capture values;
## Examples
iex> Regex.scan(%r/c(d|e)/, "abcd abce")
iex> Regex.scan(~r/c(d|e)/, "abcd abce")
[["cd", "d"], ["ce", "e"]]
iex> Regex.scan(%r/c(?:d|e)/, "abcd abce")
iex> Regex.scan(~r/c(?:d|e)/, "abcd abce")
[["cd"], ["ce"]]
iex> Regex.scan(%r/e/, "abcd")
iex> Regex.scan(~r/e/, "abcd")
[]
"""
def scan(regex, string, options \\ [])
def scan(regex(re_pattern: compiled, groups: groups), string, options) do
return = Keyword.get(options, :return, return_for(string))
def scan(%Regex{re_pattern: compiled}, string, options) when is_binary(string) do
return = Keyword.get(options, :return, :binary)
captures = Keyword.get(options, :capture, :all)
options = [{:capture, captures, return}, :global]
captures =
case Keyword.get(options, :capture, :all) do
:groups -> groups || raise ArgumentError, message: "regex was not compiled with g"
others -> others
end
options = [{ :capture, captures, return }, :global]
case :re.run(string, compiled, options) do
:match -> []
:nomatch -> []
{ :match, results } -> results
{:match, results} -> results
end
end
@doc """
Splits the given target into the number of parts specified.
If no number of parts is given, it defaults to `:infinity`.
## Options
* `:parts` - when specified, splits the string into the
given number of parts. If not specified, `:parts`
is defaulted to `:infinity`, which will split the
string into the maximum number of parts possible
based on the given pattern.
* `:trim` - when true, remove blank strings from the result;
## Examples
iex> Regex.split(%r/-/, "a-b-c")
iex> Regex.split(~r/-/, "a-b-c")
["a","b","c"]
iex> Regex.split(%r/-/, "a-b-c", [parts: 2])
iex> Regex.split(~r/-/, "a-b-c", [parts: 2])
["a","b-c"]
iex> Regex.split(%r/-/, "abc")
iex> Regex.split(~r/-/, "abc")
["abc"]
iex> Regex.split(%r//, "abc")
iex> Regex.split(~r//, "abc")
["a", "b", "c", ""]
iex> Regex.split(%r//, "abc", trim: true)
iex> Regex.split(~r//, "abc", trim: true)
["a", "b", "c"]
"""
def split(regex, string, options \\ [])
def split(regex(re_pattern: compiled), string, options) do
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) -> p
true -> :infinity
Keyword.get(options, :global) == false -> 2
p = Keyword.get(options, :parts) -> if p == 0, do: :infinity, else: p
true -> :infinity
end
return = Keyword.get(options, :return, return_for(string))
opts = [return: return, parts: parts]
opts = [return: :binary, parts: parts]
splits = :re.split(string, compiled, opts)
if Keyword.get(options, :trim, false) do
lc split inlist splits, split != "", do: split
for split <- splits, split != "", do: split
else
splits
end
end
@doc %S"""
@doc ~S"""
Receives a regex, a binary and a replacement, returns a new
binary where the all matches are replaced by replacement.
Inside the replacement, you can either give `&` to access the
whole regular expression or `\N`, where `N` is in integer to access
a specific matching parens. You can also set `:global` to `false`
if you want to replace just the first occurrence.
The replacement can be either a string or a function. The string
is used as a replacement for every match and it allows specific
captures to be accessed via `\N`, where `N` is the capture. In
case `\0` is used, the whole match is inserted.
When the replacement is a function, the function may have arity
N where each argument maps to a capture, with the first argument
being the whole match. If the function expects more arguments
than captures found, the remaining arguments will receive `""`.
## Options
* `:global` - when `false`, replaces only the first occurrence
(defaults to true)
## Examples
iex> Regex.replace(%r/d/, "abc", "d")
iex> Regex.replace(~r/d/, "abc", "d")
"abc"
iex> Regex.replace(%r/b/, "abc", "d")
iex> Regex.replace(~r/b/, "abc", "d")
"adc"
iex> Regex.replace(%r/b/, "abc", "[&]")
"a[b]c"
iex> Regex.replace(%r/b/, "abc", "[\\&]")
"a[&]c"
iex> Regex.replace(%r/(b)/, "abc", "[\\1]")
iex> Regex.replace(~r/b/, "abc", "[\\0]")
"a[b]c"
iex> Regex.replace(~r/a(b|d)c/, "abcadc", "[\\1]")
"[b][d]"
iex> Regex.replace(~r/a(b|d)c/, "abcadc", fn _, x -> "[#{x}]" end)
"[b][d]"
"""
def replace(regex(re_pattern: compiled), string, replacement, options \\ []) do
opts = if Keyword.get(options, :global) != false, do: [:global], else: []
return = Keyword.get(options, :return, return_for(string))
opts = [{ :return, return }|opts]
:re.replace(string, compiled, replacement, opts)
def replace(regex, string, replacement, options \\ [])
def replace(regex, string, replacement, options) when is_binary(replacement) do
do_replace(regex, string, precompile_replacement(replacement), options)
end
{ :ok, pattern } = :re.compile(%S"[.^$*+?()[{\\\|\s#]", [:unicode])
def replace(regex, string, replacement, options) when is_function(replacement) do
{:arity, arity} = :erlang.fun_info(replacement, :arity)
do_replace(regex, string, {replacement, arity}, options)
end
defp do_replace(%Regex{re_pattern: compiled}, string, replacement, options) do
opts = if Keyword.get(options, :global) != false, do: [:global], else: []
opts = [{:capture, :all, :index}|opts]
case :re.run(string, compiled, opts) do
:nomatch ->
string
{:match, [mlist|t]} when is_list(mlist) ->
apply_list(string, replacement, [mlist|t]) |> iodata_to_binary
{:match, slist} ->
apply_list(string, replacement, [slist]) |> iodata_to_binary
end
end
defp precompile_replacement(""),
do: []
defp precompile_replacement(<<?\\, x, rest :: binary>>) when x < ?0 or x > ?9 do
case precompile_replacement(rest) do
[head | t] when is_binary(head) ->
[<<x, head :: binary>> | t]
other ->
[<<x>> | other]
end
end
defp precompile_replacement(<<?\\, rest :: binary>>) when byte_size(rest) > 0 do
{ns, rest} = pick_int(rest)
[list_to_integer(ns) | precompile_replacement(rest)]
end
defp precompile_replacement(<<x, rest :: binary>>) do
case precompile_replacement(rest) do
[head | t] when is_binary(head) ->
[<<x, head :: binary>> | t]
other ->
[<<x>> | other]
end
end
defp pick_int(<<x, rest :: binary>>) when x in ?0..?9 do
{found, rest} = pick_int(rest)
{[x|found], rest}
end
defp pick_int(bin) do
{[], bin}
end
defp apply_list(string, replacement, list) do
apply_list(string, string, 0, replacement, list)
end
defp apply_list(_, "", _, _, []) do
[]
end
defp apply_list(_, string, _, _, []) do
string
end
defp apply_list(whole, string, pos, replacement, [[{mpos, _} | _] | _] = list) when mpos > pos do
length = mpos - pos
<<untouched :: [size(length), binary], rest :: binary>> = string
[untouched | apply_list(whole, rest, mpos, replacement, list)]
end
defp apply_list(whole, string, pos, replacement, [[{mpos, length} | _] = head | tail]) when mpos == pos do
<<_ :: [size(length), binary], rest :: binary>> = string
new_data = apply_replace(whole, replacement, head)
[new_data | apply_list(whole, rest, pos + length, replacement, tail)]
end
defp apply_replace(string, {fun, arity}, indexes) do
apply(fun, get_indexes(string, indexes, arity))
end
defp apply_replace(_, [bin], _) when is_binary(bin) do
bin
end
defp apply_replace(string, repl, indexes) do
indexes = list_to_tuple(indexes)
for part <- repl do
cond do
is_binary(part) ->
part
part > tuple_size(indexes) ->
""
true ->
get_index(string, elem(indexes, part))
end
end
end
defp get_index(_string, {pos, _len}) when pos < 0 do
""
end
defp get_index(string, {pos, len}) do
<<_ :: [size(pos), binary], res :: [size(len), binary], _ :: binary>> = string
res
end
defp get_indexes(_string, _, 0) do
[]
end
defp get_indexes(string, [], arity) do
[""|get_indexes(string, [], arity - 1)]
end
defp get_indexes(string, [h|t], arity) do
[get_index(string, h)|get_indexes(string, t, arity - 1)]
end
{:ok, pattern} = :re.compile(~S"[.^$*+?()[{\\\|\s#]", [:unicode])
@escape_pattern pattern
@doc %S"""
@doc ~S"""
Escapes a string to be literally matched in a regex.
## Examples
iex> Regex.escape(".")
"\\."
iex> Regex.escape("\\what if")
"\\\\what\\ if"
"""
@spec escape(String.t | char_list) :: String.t | char_list
def escape(string) do
:re.replace(string, @escape_pattern, "\\\\&", [:global, { :return, return_for(string) }])
@spec escape(String.t) :: String.t
def escape(string) when is_binary(string) do
:re.replace(string, @escape_pattern, "\\\\&", [:global, {:return, :binary}])
end
# Helpers
@@ -365,32 +557,18 @@ defmodule Regex do
# Private Helpers
defp return_for(element) when is_binary(element), do: :binary
defp return_for(element) when is_list(element), do: :list
defp translate_options(<<?u, t :: binary>>) do
IO.write "The /u flag for regular expressions is no longer needed\n#{Exception.format_stacktrace}"
defp translate_options(<<?g, t :: binary>>) do
IO.write :stderr, "The /g flag for regular expressions is no longer needed\n#{Exception.format_stacktrace}"
translate_options(t)
end
defp translate_options(<<?u, t :: binary>>), do: [:unicode|translate_options(t)]
defp translate_options(<<?i, t :: binary>>), do: [:caseless|translate_options(t)]
defp translate_options(<<?x, t :: binary>>), do: [:extended|translate_options(t)]
defp translate_options(<<?f, t :: binary>>), do: [:firstline|translate_options(t)]
defp translate_options(<<?r, t :: binary>>), do: [:ungreedy|translate_options(t)]
defp translate_options(<<?s, t :: binary>>), do: [:dotall, {:newline, :anycrlf}|translate_options(t)]
defp translate_options(<<?m, t :: binary>>), do: [:multiline|translate_options(t)]
defp translate_options(<<?g, t :: binary>>), do: [:groups|translate_options(t)]
defp translate_options(<<>>), do: []
defp translate_options(rest), do: { :error, rest }
{ :ok, pattern } = :re.compile(%S"\(\?<(?<G>[^>]*)>")
@groups_pattern pattern
defp parse_groups(source) do
options = [:global, {:capture, ['G'], :binary}]
case :re.run(source, @groups_pattern, options) do
:nomatch -> []
{ :match, results } ->
lc [group] inlist results, do: binary_to_atom(group)
end
end
defp translate_options(rest), do: {:error, rest}
end
+54 -58
View File
@@ -1,5 +1,5 @@
defmodule Set do
@moduledoc %S"""
@moduledoc ~S"""
This module specifies the Set API expected to be
implemented by different representations.
@@ -12,44 +12,34 @@ defmodule Set do
HashSet.new #=> creates an empty HashSet
For simplicity's sake, in the examples below every time
`new` is used, it implies one of the module-specific
calls like above.
In the examples below, `set_impl` means a specific
`Set` implementation, for example `HashSet`.
## Protocols
Sets are required to implement the `Enumerable` protocol,
allowing one to write:
Enum.each(set, fn k ->
IO.inspect k
end)
Sets are required to implement both `Enumerable` and `Collectable`
protocols.
## Match
Sets are required to implement all operations
using the match (`===`) operator. Any deviation from
this behaviour should be avoided and explicitly documented.
Sets are required to implement all operations using the match (`===`)
operator.
"""
use Behaviour
@type value :: any
@type values :: [ value ]
@type t :: tuple
@type t :: map
defcallback new :: t
defcallback new(Enum.t) :: t
defcallback new(Enum.t, (any -> any)) :: t
defcallback delete(t, value) :: t
defcallback difference(t, t) :: t
defcallback disjoint?(t, t) :: boolean
defcallback empty(t) :: t
defcallback equal?(t, t) :: boolean
defcallback intersection(t, t) :: t
defcallback member?(t, value) :: boolean
defcallback put(t, value) :: t
defcallback reduce(t, Enumerable.acc, Enumerable.reducer) :: Enumerable.result
defcallback size(t) :: non_neg_integer
defcallback subset?(t, t) :: boolean
defcallback to_list(t) :: list()
@@ -57,10 +47,14 @@ defmodule Set do
defmacrop target(set) do
quote do
if is_tuple(unquote(set)) do
elem(unquote(set), 0)
else
unsupported_set(unquote(set))
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
end
end
@@ -70,13 +64,14 @@ defmodule Set do
## Examples
iex> s = HashSet.new([1, 2, 3])
...> Set.delete(s, 4) |> Enum.sort
iex> s = Enum.into([1, 2, 3], set_impl.new)
iex> Set.delete(s, 4) |> Enum.sort
[1, 2, 3]
iex> s = HashSet.new([1, 2, 3])
...> Set.delete(s, 2) |> Enum.sort
iex> s = Enum.into([1, 2, 3], set_impl.new)
iex> Set.delete(s, 2) |> Enum.sort
[1, 3]
"""
@spec delete(t, value) :: t
def delete(set, value) do
@@ -92,7 +87,7 @@ defmodule Set do
## Examples
iex> Set.difference(HashSet.new([1,2]), HashSet.new([2,3,4])) |> Enum.sort
iex> Set.difference(Enum.into([1,2], set_impl.new), Enum.into([2,3,4], set_impl.new)) |> Enum.sort
[1]
"""
@@ -104,8 +99,8 @@ defmodule Set do
if target1 == target2 do
target1.difference(set1, set2)
else
target2.reduce(set2, { :cont, set1 }, fn v, acc ->
{ :cont, target1.delete(acc, v) }
target2.reduce(set2, {:cont, set1}, fn v, acc ->
{:cont, target1.delete(acc, v)}
end) |> elem(1)
end
end
@@ -119,9 +114,10 @@ defmodule Set do
## Examples
iex> Set.disjoint?(HashSet.new([1, 2]), HashSet.new([3, 4]))
iex> Set.disjoint?(Enum.into([1, 2], set_impl.new), Enum.into([3, 4], set_impl.new))
true
iex> Set.disjoint?(HashSet.new([1, 2]), HashSet.new([2, 3]))
iex> Set.disjoint?(Enum.into([1, 2], set_impl.new), Enum.into([2, 3], set_impl.new))
false
"""
@@ -133,18 +129,16 @@ defmodule Set do
if target1 == target2 do
target1.disjoint?(set1, set2)
else
target2.reduce(set2, { :cont, true }, fn member, acc ->
target2.reduce(set2, {:cont, true}, fn member, acc ->
case target1.member?(set1, member) do
false -> { :cont, acc }
_ -> { :halt, false }
false -> {:cont, acc}
_ -> {:halt, false}
end
end) |> elem(1)
end
end
@doc """
Returns an empty set of the same type as `set`.
"""
@doc false
@spec empty(t) :: t
def empty(set) do
target(set).empty(set)
@@ -159,10 +153,10 @@ defmodule Set do
## Examples
iex> Set.equal?(HashSet.new([1, 2]), HashSet.new([2, 1, 1]))
iex> Set.equal?(Enum.into([1, 2], set_impl.new), Enum.into([2, 1, 1], set_impl.new))
true
iex> Set.equal?(HashSet.new([1, 2]), HashSet.new([3, 4]))
iex> Set.equal?(Enum.into([1, 2], set_impl.new), Enum.into([3, 4], set_impl.new))
false
"""
@@ -192,10 +186,10 @@ defmodule Set do
## Examples
iex> Set.intersection(HashSet.new([1,2]), HashSet.new([2,3,4])) |> Enum.sort
iex> Set.intersection(Enum.into([1,2], set_impl.new), Enum.into([2,3,4], set_impl.new)) |> Enum.sort
[2]
iex> Set.intersection(HashSet.new([1,2]), HashSet.new([3,4])) |> Enum.sort
iex> Set.intersection(Enum.into([1,2], set_impl.new), Enum.into([3,4], set_impl.new)) |> Enum.sort
[]
"""
@@ -207,8 +201,8 @@ defmodule Set do
if target1 == target2 do
target1.intersection(set1, set2)
else
target1.reduce(set1, { :cont, target1.empty(set1) }, fn v, acc ->
{ :cont, if(target2.member?(set2, v), do: target1.put(acc, v), else: acc) }
target1.reduce(set1, {:cont, Collectable.empty(set1)}, fn v, acc ->
{:cont, if(target2.member?(set2, v), do: target1.put(acc, v), else: acc)}
end) |> elem(1)
end
end
@@ -218,10 +212,10 @@ defmodule Set do
## Examples
iex> Set.member?(HashSet.new([1, 2, 3]), 2)
iex> Set.member?(Enum.into([1, 2, 3], set_impl.new), 2)
true
iex> Set.member?(HashSet.new([1, 2, 3]), 4)
iex> Set.member?(Enum.into([1, 2, 3], set_impl.new), 4)
false
"""
@@ -235,10 +229,10 @@ defmodule Set do
## Examples
iex> Set.put(HashSet.new([1, 2, 3]), 3) |> Enum.sort
iex> Set.put(Enum.into([1, 2, 3], set_impl.new), 3) |> Enum.sort
[1, 2, 3]
iex> Set.put(HashSet.new([1, 2, 3]), 4) |> Enum.sort
iex> Set.put(Enum.into([1, 2, 3], set_impl.new), 4) |> Enum.sort
[1, 2, 3, 4]
"""
@@ -252,7 +246,7 @@ defmodule Set do
## Examples
iex> Set.size(HashSet.new([1, 2, 3]))
iex> Set.size(Enum.into([1, 2, 3], set_impl.new))
3
"""
@@ -270,10 +264,12 @@ defmodule Set do
## Examples
iex> Set.subset?(HashSet.new([1, 2]), HashSet.new([1, 2, 3]))
iex> Set.subset?(Enum.into([1, 2], set_impl.new), Enum.into([1, 2, 3], set_impl.new))
true
iex> Set.subset?(HashSet.new([1, 2, 3]), HashSet.new([1, 2]))
iex> Set.subset?(Enum.into([1, 2, 3], set_impl.new), Enum.into([1, 2], set_impl.new))
false
"""
@spec subset?(t, t) :: boolean
def subset?(set1, set2) do
@@ -292,7 +288,7 @@ defmodule Set do
## Examples
iex> HashSet.to_list(HashSet.new([1, 2, 3])) |> Enum.sort
iex> set_impl.to_list(Enum.into([1, 2, 3], set_impl.new)) |> Enum.sort
[1,2,3]
"""
@@ -310,7 +306,7 @@ defmodule Set do
## Examples
iex> Set.union(HashSet.new([1,2]), HashSet.new([2,3,4])) |> Enum.sort
iex> Set.union(Enum.into([1,2], set_impl.new), Enum.into([2,3,4], set_impl.new)) |> Enum.sort
[1,2,3,4]
"""
@@ -322,17 +318,17 @@ defmodule Set do
if target1 == target2 do
target1.union(set1, set2)
else
target2.reduce(set2, { :cont, set1 }, fn v, acc ->
{ :cont, target1.put(acc, v) }
target2.reduce(set2, {:cont, set1}, fn v, acc ->
{:cont, target1.put(acc, v)}
end) |> elem(1)
end
end
defp do_subset?(target1, target2, set1, set2) do
target1.reduce(set1, { :cont, true }, fn member, acc ->
target1.reduce(set1, {:cont, true}, fn member, acc ->
case target2.member?(set2, member) do
true -> { :cont, acc }
_ -> { :halt, false }
true -> {:cont, acc}
_ -> {:halt, false}
end
end) |> elem(1)
end
+195 -183
View File
@@ -81,68 +81,59 @@ defmodule Stream do
This module also provides many convenience functions for creating streams,
like `Stream.cycle/1`, `Stream.unfold/2`, `Stream.resource/3` and more.
Note the functions in this module are guaranteed to return enumerables.
Since enumerables can have different shapes (structs, anonymous functions,
and so on), the functions in this module may return any of those shapes
and that it may change at any time. For example, a function that today
returns an anonymous function may return a struct in future releases.
"""
defrecord Lazy, enum: nil, funs: [], accs: [], after: [], last: nil
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] }
{reason, acc} = fun.(x, acc)
{reason, [acc]}
end)
end
def count(_lazy) do
{ :error, __MODULE__ }
{:error, __MODULE__}
end
def member?(_lazy, _value) do
{ :error, __MODULE__ }
{:error, __MODULE__}
end
defp do_reduce(Lazy[enum: enum, funs: funs, accs: accs, last: last, after: after_funs], acc, fun) do
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), after_funs,
last && { last, fun }, :lists.reverse(accs), acc)
do_each(&Enumerable.reduce(enum, &1, composed),
done && {done, fun}, :lists.reverse(accs), acc)
end
defp do_each(reduce, [], last, accs, { command, acc }) do
handle_reduce reduce.({ command, [acc|accs] }), [], last
end
defp do_each(reduce, after_funs, last, accs, { command, acc }) do
try do
handle_reduce reduce.({ command, [acc|accs] }), after_funs, last
catch
kind, reason ->
lc fun inlist after_funs, do: fun.()
:erlang.raise(kind, reason, :erlang.get_stacktrace)
end
end
defp handle_reduce(res, after_funs, last) do
case res do
{ :suspended, [acc|accs], continuation } ->
{ :suspended, acc, &do_each(continuation, after_funs, last, accs, &1) }
{ :halted, [acc|_] } ->
lc fun inlist after_funs, do: fun.()
{ :halted, acc }
{ :done, [acc|_] = accs } ->
case last do
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 ->
lc fun inlist after_funs, do: fun.()
{ :done, acc }
{ last, fun } ->
res = case last.(fun).(accs) do
{ :cont, [acc|_] } -> { :done, acc }
{ :halt, [acc|_] } -> { :halted, acc }
{ :suspend, [acc|_] } -> { :suspended, acc, &({ :done, &1 |> elem(1) }) }
{: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
lc fun inlist after_funs, do: fun.()
res
end
end
end
@@ -166,33 +157,13 @@ defmodule Stream do
defmacrop cont_with_acc(f, entry, h, n, t) do
quote do
{ reason, [h|t] } = unquote(f).(unquote(entry), [unquote(h)|unquote(t)])
{ reason, [h,unquote(n)|t] }
{reason, [h|t]} = unquote(f).(unquote(entry), [unquote(h)|unquote(t)])
{reason, [h,unquote(n)|t]}
end
end
## Transformers
@doc """
Executes the given function when the stream is done, halted
or an error happened during streaming. Useful for resource
clean up.
Callbacks registered later will be executed earlier.
## Examples
iex> stream = Stream.after [1,2,3], fn -> Process.put(:done, true) end
iex> Enum.to_list(stream)
[1,2,3]
iex> Process.get(:done)
true
"""
@spec unquote(:after)(Enumerable.t, (() -> term)) :: Enumerable.t
def unquote(:after)(Lazy[after: funs] = lazy, fun), do: lazy.after([fun|funs])
def unquote(:after)(enum, fun), do: Lazy[enum: enum, after: [fun]]
@doc """
Shortcut to `chunk(enum, n, n)`.
"""
@@ -216,10 +187,13 @@ defmodule Stream do
iex> Stream.chunk([1, 2, 3, 4, 5, 6], 2) |> Enum.to_list
[[1, 2], [3, 4], [5, 6]]
iex> Stream.chunk([1, 2, 3, 4, 5, 6], 3, 2) |> Enum.to_list
[[1, 2, 3], [3, 4, 5]]
iex> Stream.chunk([1, 2, 3, 4, 5, 6], 3, 2, [7]) |> Enum.to_list
[[1, 2, 3], [3, 4, 5], [5, 6, 7]]
iex> Stream.chunk([1, 2, 3, 4, 5, 6], 3, 3, []) |> Enum.to_list
[[1, 2, 3], [4, 5, 6]]
@@ -228,14 +202,14 @@ defmodule Stream do
@spec chunk(Enumerable.t, non_neg_integer, non_neg_integer, Enumerable.t | nil) :: Enumerable.t
def chunk(enum, n, step, pad \\ nil) when n > 0 and step > 0 do
limit = :erlang.max(n, step)
lazy enum, { [], 0 },
lazy enum, {[], 0},
fn(f1) -> R.chunk(n, step, limit, f1) end,
fn(f1) -> &do_chunk(&1, n, pad, f1) end
end
defp do_chunk(acc(h, { buffer, count } = old, t) = acc, n, pad, f1) do
defp do_chunk(acc(h, {buffer, count} = old, t) = acc, n, pad, f1) do
if nil?(pad) || count == 0 do
{ :cont, acc }
{:cont, acc}
else
buffer = :lists.reverse(buffer) ++ Enum.take(pad, n - count)
cont_with_acc(f1, buffer, h, old, t)
@@ -262,10 +236,10 @@ defmodule Stream do
end
defp do_chunk_by(acc(_, nil, _) = acc, _f1) do
{ :cont, acc }
{:cont, acc}
end
defp do_chunk_by(acc(h, { buffer, _ }, t), f1) do
defp do_chunk_by(acc(h, {buffer, _}, t), f1) do
cont_with_acc(f1, :lists.reverse(buffer), h, nil, t)
end
@@ -296,12 +270,12 @@ defmodule Stream do
def drop(enum, n) when n < 0 do
n = abs(n)
lazy enum, { 0, [], [] }, fn(f1) ->
lazy enum, {0, [], []}, fn(f1) ->
fn
entry, [h, { count, buf1, [] } | t] ->
entry, [h, {count, buf1, []} | t] ->
do_drop(:cont, n, entry, h, count, buf1, [], t)
entry, [h, { count, buf1, [next|buf2] } | t] ->
{ reason, [h|t] } = f1.(next, [h|t])
entry, [h, {count, buf1, [next|buf2]} | t] ->
{reason, [h|t]} = f1.(next, [h|t])
do_drop(reason, n, entry, h, count, buf1, buf2, t)
end
end
@@ -311,9 +285,9 @@ defmodule Stream do
buf1 = [entry|buf1]
count = count + 1
if count == n do
{ reason, [h, { 0, [], :lists.reverse(buf1) }|t] }
{reason, [h, {0, [], :lists.reverse(buf1)}|t]}
else
{ reason, [h, { count, buf1, buf2 }|t] }
{reason, [h, {count, buf1, buf2}|t]}
end
end
@@ -373,7 +347,7 @@ defmodule Stream do
"""
@spec flat_map(Enumerable.t, (element -> Enumerable.t)) :: Enumerable.t
def flat_map(enum, mapper) do
transform(enum, nil, fn val, nil -> { mapper.(val), nil } end)
transform(enum, nil, fn val, nil -> {mapper.(val), nil} end)
end
@doc """
@@ -410,6 +384,44 @@ defmodule Stream do
lazy enum, fn(f1) -> R.filter_map(filter, mapper, f1) end
end
@doc """
Injects the stream values into the given collectable as a side-effect.
This function is often used with `run/1` since any evaluation
is delayed until the stream is executed. See `run/1` for an example.
"""
@spec into(Enumerable.t, Collectable.t) :: Enumerable.t
def into(enum, collectable, transform \\ fn x -> x end) do
&do_into(enum, collectable, transform, &1, &2)
end
defp do_into(enum, collectable, transform, acc, fun) do
{initial, into} = Collectable.into(collectable)
composed = fn x, [acc|collectable] ->
collectable = into.(collectable, {:cont, transform.(x)})
{reason, acc} = fun.(x, acc)
{reason, [acc|collectable]}
end
do_into(&Enumerable.reduce(enum, &1, composed), initial, into, acc)
end
defp do_into(reduce, collectable, into, {command, acc}) do
try do
reduce.({command, [acc|collectable]})
catch
kind, reason ->
stacktrace = System.stacktrace
into.(collectable, :halt)
:erlang.raise(kind, reason, stacktrace)
else
{:suspended, [acc|collectable], continuation} ->
{:suspended, acc, &do_into(continuation, collectable, into, &1)}
{reason, [acc|collectable]} ->
into.(collectable, :done)
{reason, acc}
end
end
@doc """
Creates a stream that will apply the given function on
enumeration.
@@ -455,14 +467,14 @@ defmodule Stream do
stream = File.stream!("code")
|> Stream.map(&String.replace(&1, "#", "%"))
|> File.stream_to!("new")
|> Stream.into(File.stream!("new"))
No computation will be done until we call one of the Enum functions
or `Stream.run/1`.
"""
@spec run(Enumerable.t) :: :ok
def run(stream) do
Enumerable.reduce(stream, { :cont, nil }, fn(_, _) -> { :cont, nil } end)
Enumerable.reduce(stream, {:cont, nil}, fn(_, _) -> {:cont, nil} end)
:ok
end
@@ -526,7 +538,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: %Lazy{enum: []}
def take(enum, n) when n > 0 do
lazy enum, n, fn(f1) -> R.take(f1) end
@@ -537,15 +549,15 @@ defmodule Stream do
end
defp do_take(enum, n, acc, f) do
{ _, { _count, buf1, buf2 } } =
Enumerable.reduce(enum, { :cont, { 0, [], [] } }, fn
entry, { count, buf1, buf2 } ->
{_, {_count, buf1, buf2}} =
Enumerable.reduce(enum, {:cont, {0, [], []}}, fn
entry, {count, buf1, buf2} ->
buf1 = [entry|buf1]
count = count + 1
if count == n do
{ :cont, { 0, [], buf1 } }
{:cont, {0, [], buf1}}
else
{ :cont, { count, buf1, buf2 } }
{:cont, {count, buf1, buf2}}
end
end)
@@ -578,7 +590,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: %Lazy{enum: []}
@doc """
Lazily takes elements of the enumerable while the given
@@ -596,7 +608,6 @@ defmodule Stream do
lazy enum, fn(f1) -> R.take_while(fun, f1) end
end
@doc """
Transforms an existing stream.
@@ -618,14 +629,14 @@ defmodule Stream do
iex> enum = 1..100
iex> n = 3
iex> stream = Stream.transform(enum, 0, fn i, acc ->
...> if acc < n, do: { [i], acc + 1 }, else: { :halt, acc }
...> if acc < n, do: {[i], acc + 1}, else: {:halt, acc}
...> end)
iex> Enum.to_list(stream)
[1,2,3]
"""
@spec transform(Enumerable.t, acc, fun) :: Enumerable.t when
fun: (element, acc -> { Enumerable.t, acc } | { :halt, acc }),
fun: (element, acc -> {Enumerable.t, acc} | {:halt, acc}),
acc: any
def transform(enum, acc, reducer) do
&do_transform(enum, acc, reducer, &1, &2)
@@ -639,27 +650,27 @@ defmodule Stream do
end
defp do_transform(user_acc, user, fun, next_acc, next, inner_acc, inner) do
case next.({ :cont, next_acc }) do
{ :suspended, [val|next_acc], next } ->
case next.({:cont, next_acc}) do
{:suspended, [val|next_acc], next} ->
try do
user.(val, user_acc)
catch
kind, reason ->
next.({ :halt, next_acc })
next.({:halt, next_acc})
:erlang.raise(kind, reason, :erlang.get_stacktrace)
else
{ [], user_acc } ->
{[], user_acc} ->
do_transform(user_acc, user, fun, next_acc, next, inner_acc, inner)
{ list, user_acc } when is_list(list) ->
{list, user_acc} when is_list(list) ->
do_list_transform(user_acc, user, fun, next_acc, next, inner_acc, inner, &Enumerable.List.reduce(list, &1, fun))
{ :halt, _user_acc } ->
next.({ :halt, next_acc })
{ :halted, elem(inner_acc, 1) }
{ other, user_acc } ->
{:halt, _user_acc} ->
next.({:halt, next_acc})
{:halted, elem(inner_acc, 1)}
{other, user_acc} ->
do_other_transform(user_acc, user, fun, next_acc, next, inner_acc, inner, &Enumerable.reduce(other, &1, inner))
end
{ reason, _ } ->
{ reason, elem(inner_acc, 1) }
{reason, _} ->
{reason, elem(inner_acc, 1)}
end
end
@@ -668,16 +679,16 @@ defmodule Stream do
reduce.(inner_acc)
catch
kind, reason ->
next.({ :halt, next_acc })
next.({:halt, next_acc})
:erlang.raise(kind, reason, :erlang.get_stacktrace)
else
{ :done, acc } ->
do_transform(user_acc, user, fun, next_acc, next, { :cont, acc }, inner)
{ :halted, acc } ->
next.({ :halt, next_acc })
{ :halted, acc }
{ :suspended, acc, c } ->
{ :suspended, acc, &do_list_transform(user_acc, user, fun, next_acc, next, &1, inner, c) }
{:done, acc} ->
do_transform(user_acc, user, fun, next_acc, next, {:cont, acc}, inner)
{:halted, acc} ->
next.({:halt, next_acc})
{:halted, acc}
{:suspended, acc, c} ->
{:suspended, acc, &do_list_transform(user_acc, user, fun, next_acc, next, &1, inner, c)}
end
end
@@ -685,29 +696,29 @@ defmodule Stream do
try do
reduce.(inner_acc)
catch
{ :stream_transform, h } ->
next.({ :halt, next_acc })
{ :halted, h }
{:stream_transform, h} ->
next.({:halt, next_acc})
{:halted, h}
kind, reason ->
next.({ :halt, next_acc })
next.({:halt, next_acc})
:erlang.raise(kind, reason, :erlang.get_stacktrace)
else
{ _, acc } ->
do_transform(user_acc, user, fun, next_acc, next, { :cont, acc }, inner)
{ :suspended, acc, c } ->
{ :suspended, acc, &do_other_transform(user_acc, user, fun, next_acc, next, &1, inner, c) }
{_, acc} ->
do_transform(user_acc, user, fun, next_acc, next, {:cont, acc}, inner)
{:suspended, acc, c} ->
{:suspended, acc, &do_other_transform(user_acc, user, fun, next_acc, next, &1, inner, c)}
end
end
defp do_transform_each(x, acc, f) do
case f.(x, acc) do
{ :halt, h } -> throw({ :stream_transform, h })
{ _, _ } = o -> o
{:halt, h} -> throw({:stream_transform, h})
{_, _} = o -> o
end
end
defp do_transform_step(x, acc) do
{ :suspend, [x|acc] }
{:suspend, [x|acc]}
end
@doc """
@@ -807,19 +818,19 @@ defmodule Stream do
right_fun = &Enumerable.reduce(right, &1, step)
# Return a function as a lazy enumerator.
&do_zip([{ left_fun, [] }, { right_fun, [] }], &1, &2)
&do_zip([{left_fun, []}, {right_fun, []}], &1, &2)
end
defp do_zip(zips, { :halt, acc }, _fun) do
defp do_zip(zips, {:halt, acc}, _fun) do
do_zip_close(zips)
{ :halted, acc }
{:halted, acc}
end
defp do_zip(zips, { :suspend, acc }, fun) do
{ :suspended, acc, &do_zip(zips, &1, fun) }
defp do_zip(zips, {:suspend, acc}, fun) do
{:suspended, acc, &do_zip(zips, &1, fun)}
end
defp do_zip(zips, { :cont, acc }, callback) do
defp do_zip(zips, {:cont, acc}, callback) do
try do
do_zip(zips, acc, callback, [], [])
catch
@@ -827,36 +838,36 @@ defmodule Stream do
do_zip_close(zips)
:erlang.raise(kind, reason, :erlang.get_stacktrace)
else
{ :next, buffer, acc } ->
{:next, buffer, acc} ->
do_zip(buffer, acc, callback)
{ :done, _ } = o ->
{:done, _} = o ->
o
end
end
defp do_zip([{ fun, fun_acc }|t], acc, callback, list, buffer) do
case fun.({ :cont, fun_acc }) do
{ :suspended, [i|fun_acc], fun } ->
do_zip(t, acc, callback, [i|list], [{ fun, fun_acc }|buffer])
{ _, _ } ->
defp do_zip([{fun, fun_acc}|t], acc, callback, list, buffer) do
case fun.({:cont, fun_acc}) do
{:suspended, [i|fun_acc], fun} ->
do_zip(t, acc, callback, [i|list], [{fun, fun_acc}|buffer])
{_, _} ->
do_zip_close(:lists.reverse(buffer) ++ t)
{ :done, acc }
{:done, acc}
end
end
defp do_zip([], acc, callback, list, buffer) do
zipped = list_to_tuple(:lists.reverse(list))
{ :next, :lists.reverse(buffer), callback.(zipped, acc) }
{:next, :lists.reverse(buffer), callback.(zipped, acc)}
end
defp do_zip_close([]), do: :ok
defp do_zip_close([{ fun, acc }|t]) do
fun.({ :halt, acc })
defp do_zip_close([{fun, acc}|t]) do
fun.({:halt, acc})
do_zip_close(t)
end
defp do_zip_step(x, acc) do
{ :suspend, [x|acc] }
{:suspend, [x|acc]}
end
## Sources
@@ -880,22 +891,22 @@ defmodule Stream do
end
end
defp do_cycle(_reduce, _cycle, { :halt, acc }) do
{ :halted, acc }
defp do_cycle(_reduce, _cycle, {:halt, acc}) do
{:halted, acc}
end
defp do_cycle(reduce, cycle, { :suspend, acc }) do
{ :suspended, acc, &do_cycle(reduce, cycle, &1) }
defp do_cycle(reduce, cycle, {:suspend, acc}) do
{:suspended, acc, &do_cycle(reduce, cycle, &1)}
end
defp do_cycle(reduce, cycle, acc) do
case reduce.(acc) do
{ :done, acc } ->
do_cycle(cycle, cycle, { :cont, acc })
{ :halted, acc } ->
{ :halted, acc }
{ :suspended, acc, continuation } ->
{ :suspended, acc, &do_cycle(continuation, cycle, &1) }
{:done, acc} ->
do_cycle(cycle, cycle, {:cont, acc})
{:halted, acc} ->
{:halted, acc}
{:suspended, acc, continuation} ->
{:suspended, acc, &do_cycle(continuation, cycle, &1)}
end
end
@@ -911,12 +922,12 @@ defmodule Stream do
"""
@spec iterate(element, (element -> element)) :: Enumerable.t
def iterate(start_value, next_fun) do
unfold({ :ok, start_value}, fn
{ :ok, value } ->
{ value, { :next, value } }
{ :next, value } ->
unfold({:ok, start_value}, fn
{:ok, value} ->
{value, {:next, value}}
{:next, value} ->
next = next_fun.(value)
{ next, { :next, next } }
{next, {:next, next}}
end)
end
@@ -934,15 +945,15 @@ defmodule Stream do
&do_repeatedly(generator_fun, &1, &2)
end
defp do_repeatedly(generator_fun, { :suspend, acc }, fun) do
{ :suspended, acc, &do_repeatedly(generator_fun, &1, fun) }
defp do_repeatedly(generator_fun, {:suspend, acc}, fun) do
{:suspended, acc, &do_repeatedly(generator_fun, &1, fun)}
end
defp do_repeatedly(_generator_fun, { :halt, acc }, _fun) do
{ :halted, acc }
defp do_repeatedly(_generator_fun, {:halt, acc}, _fun) do
{:halted, acc}
end
defp do_repeatedly(generator_fun, { :cont, acc }, fun) do
defp do_repeatedly(generator_fun, {:cont, acc}, fun) do
do_repeatedly(generator_fun, fun.(generator_fun.(), acc), fun)
end
@@ -966,32 +977,32 @@ defmodule Stream do
Stream.resource(fn -> File.open!("sample") end,
fn file ->
case IO.read(file, :line) do
data when is_binary(data) -> { data, file }
data when is_binary(data) -> {data, file}
_ -> nil
end
end,
fn file -> File.close(file) end)
"""
@spec resource((() -> acc), (acc -> { element, acc } | nil), (acc -> term)) :: Enumerable.t
@spec resource((() -> acc), (acc -> {element, acc} | nil), (acc -> term)) :: Enumerable.t
def resource(start_fun, next_fun, after_fun) do
&do_resource(start_fun.(), next_fun, &1, &2, after_fun)
end
defp do_resource(next_acc, next_fun, { :suspend, acc }, fun, after_fun) do
{ :suspended, acc, &do_resource(next_acc, next_fun, &1, fun, after_fun) }
defp do_resource(next_acc, next_fun, {:suspend, acc}, fun, after_fun) do
{:suspended, acc, &do_resource(next_acc, next_fun, &1, fun, after_fun)}
end
defp do_resource(next_acc, _next_fun, { :halt, acc }, _fun, after_fun) do
defp do_resource(next_acc, _next_fun, {:halt, acc}, _fun, after_fun) do
after_fun.(next_acc)
{ :halted, acc }
{:halted, acc}
end
defp do_resource(next_acc, next_fun, { :cont, acc }, fun, after_fun) do
defp do_resource(next_acc, next_fun, {:cont, acc}, fun, after_fun) do
try do
case next_fun.(next_acc) do
nil -> nil
{ v, next_acc } -> { fun.(v, acc), next_acc }
{v, next_acc} -> {fun.(v, acc), next_acc}
end
catch
kind, reason ->
@@ -1000,8 +1011,8 @@ defmodule Stream do
else
nil ->
after_fun.(next_acc)
{ :done, acc }
{ acc, next_acc } ->
{:done, acc}
{acc, next_acc} ->
do_resource(next_acc, next_fun, acc, fun, after_fun)
end
end
@@ -1017,43 +1028,44 @@ defmodule Stream do
iex> Stream.unfold(5, fn 0 -> nil; n -> {n, n-1} end) |> Enum.to_list()
[5, 4, 3, 2, 1]
"""
@spec unfold(acc, (acc -> { element, acc } | nil)) :: Enumerable.t
@spec unfold(acc, (acc -> {element, acc} | nil)) :: Enumerable.t
def unfold(next_acc, next_fun) do
&do_unfold(next_acc, next_fun, &1, &2)
end
defp do_unfold(next_acc, next_fun, { :suspend, acc }, fun) do
{ :suspended, acc, &do_unfold(next_acc, next_fun, &1, fun) }
defp do_unfold(next_acc, next_fun, {:suspend, acc}, fun) do
{:suspended, acc, &do_unfold(next_acc, next_fun, &1, fun)}
end
defp do_unfold(_next_acc, _next_fun, { :halt, acc }, _fun) do
{ :halted, acc }
defp do_unfold(_next_acc, _next_fun, {:halt, acc}, _fun) do
{:halted, acc}
end
defp do_unfold(next_acc, next_fun, { :cont, acc }, fun) do
defp do_unfold(next_acc, next_fun, {:cont, acc}, fun) do
case next_fun.(next_acc) do
nil -> { :done, acc }
{ v, next_acc } -> do_unfold(next_acc, next_fun, fun.(v, acc), fun)
nil -> {:done, acc}
{v, next_acc} -> do_unfold(next_acc, next_fun, fun.(v, acc), fun)
end
end
## Helpers
@compile { :inline, lazy: 2, lazy: 3, lazy: 4 }
@compile {:inline, lazy: 2, lazy: 3, lazy: 4}
defp lazy(Lazy[funs: funs] = lazy, fun),
do: lazy.funs([fun|funs])
defp lazy(%Lazy{funs: funs} = lazy, fun),
do: %{lazy | funs: [fun|funs] }
defp lazy(enum, fun),
do: Lazy[enum: enum, funs: [fun]]
do: %Lazy{enum: enum, funs: [fun]}
defp lazy(Lazy[funs: funs, accs: accs] = lazy, acc, fun),
do: lazy.funs([fun|funs]).accs([acc|accs])
defp lazy(%Lazy{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: %Lazy{enum: enum, funs: [fun], accs: [acc]}
defp lazy(Lazy[last: nil, funs: funs, accs: accs] = lazy, acc, fun, last),
do: lazy.funs([fun|funs]).accs([acc|accs]).last(last)
defp lazy(enum, acc, fun, last),
do: Lazy[enum: enum, funs: [fun], accs: [acc], last: last]
defp lazy(%Lazy{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}
end
+21 -21
View File
@@ -4,22 +4,22 @@ defmodule Stream.Reducers do
defmacro chunk(n, step, limit, f \\ nil) do
quote do
fn entry, acc(h, { buffer, count }, t) ->
fn entry, acc(h, {buffer, count}, t) ->
buffer = [entry|buffer]
count = count + 1
new =
if count >= unquote(limit) do
left = count - unquote(step)
{ Enum.take(buffer, left), left }
{Enum.take(buffer, left), left}
else
{ buffer, count }
{buffer, count}
end
if count == unquote(n) do
cont_with_acc(unquote(f), :lists.reverse(buffer), h, new, t)
else
{ :cont, acc(h, new, t) }
{:cont, acc(h, new, t)}
end
end
end
@@ -28,15 +28,15 @@ defmodule Stream.Reducers do
defmacro chunk_by(callback, f \\ nil) do
quote do
fn
entry, acc(h, { buffer, value }, t) ->
entry, acc(h, {buffer, value}, t) ->
new_value = unquote(callback).(entry)
if new_value == value do
{ :cont, acc(h, { [entry|buffer], value }, t) }
{:cont, acc(h, {[entry|buffer], value}, t)}
else
cont_with_acc(unquote(f), :lists.reverse(buffer), h, { [entry], new_value }, t)
cont_with_acc(unquote(f), :lists.reverse(buffer), h, {[entry], new_value}, t)
end
entry, acc(h, nil, t) ->
{ :cont, acc(h, { [entry], unquote(callback).(entry) }, t) }
{:cont, acc(h, {[entry], unquote(callback).(entry)}, t)}
end
end
end
@@ -45,7 +45,7 @@ defmodule Stream.Reducers do
quote do
fn
_entry, acc(h, n, t) when n > 0 ->
{ :cont, acc(h, n-1, t) }
{:cont, acc(h, n-1, t)}
entry, acc(h, n, t) ->
cont_with_acc(unquote(f), entry, h, n, t)
end
@@ -56,7 +56,7 @@ defmodule Stream.Reducers do
quote do
fn entry, acc(h, bool, t) = orig ->
if bool and unquote(callback).(entry) do
{ :cont, orig }
{:cont, orig}
else
cont_with_acc(unquote(f), entry, h, false, t)
end
@@ -70,7 +70,7 @@ defmodule Stream.Reducers do
if unquote(callback).(entry) do
cont(unquote(f), entry, acc)
else
{ :cont, acc }
{:cont, acc}
end
end
end
@@ -82,7 +82,7 @@ defmodule Stream.Reducers do
if unquote(filter).(entry) do
cont(unquote(f), unquote(mapper).(entry), acc)
else
{ :cont, acc }
{:cont, acc}
end
end
end
@@ -102,7 +102,7 @@ defmodule Stream.Reducers do
unless unquote(callback).(entry) do
cont(unquote(f), entry, acc)
else
{ :cont, acc }
{:cont, acc}
end
end
end
@@ -112,10 +112,10 @@ defmodule Stream.Reducers do
quote do
fn
entry, acc(h, :first, t) ->
cont_with_acc(unquote(f), entry, h, { :ok, entry }, t)
entry, acc(h, { :ok, acc }, t) ->
cont_with_acc(unquote(f), entry, h, {:ok, entry}, t)
entry, acc(h, {:ok, acc}, t) ->
value = unquote(callback).(entry, acc)
cont_with_acc(unquote(f), value, h, { :ok, value }, t)
cont_with_acc(unquote(f), value, h, {:ok, value}, t)
end
end
end
@@ -135,7 +135,7 @@ defmodule Stream.Reducers do
if n >= 1 do
cont_with_acc(unquote(f), entry, h, n-1, t)
else
{ :halt, orig }
{:halt, orig}
end
end
end
@@ -148,7 +148,7 @@ defmodule Stream.Reducers do
when n === unquote(nth) ->
cont_with_acc(unquote(f), entry, h, 1, t)
entry, acc(h, n, t) ->
{ :cont, acc(h, n+1, t) }
{:cont, acc(h, n+1, t)}
end
end
end
@@ -159,7 +159,7 @@ defmodule Stream.Reducers do
if unquote(callback).(entry) do
cont(unquote(f), entry, acc)
else
{ :halt, acc }
{:halt, acc}
end
end
end
@@ -170,7 +170,7 @@ defmodule Stream.Reducers do
fn(entry, acc(h, prev, t) = acc) ->
value = unquote(callback).(entry)
if :lists.member(value, prev) do
{ :cont, acc }
{:cont, acc}
else
cont_with_acc(unquote(f), entry, h, [value|prev], t)
end
@@ -181,7 +181,7 @@ defmodule Stream.Reducers do
defmacro with_index(f \\ nil) do
quote do
fn(entry, acc(h, counter, t)) ->
cont_with_acc(unquote(f), { entry, counter }, h, counter + 1, t)
cont_with_acc(unquote(f), {entry, counter}, h, counter + 1, t)
end
end
end
+233 -138
View File
@@ -1,7 +1,7 @@
import Kernel, except: [length: 1]
defmodule String do
@moduledoc %S"""
@moduledoc ~S"""
A String in Elixir is a UTF-8 encoded binary.
## String and binary operations
@@ -47,7 +47,7 @@ defmodule String do
by the letter "e" followed by the accent ́:
iex> string = "\x{0065}\x{0301}"
...> byte_size(string)
iex> byte_size(string)
3
iex> String.length(string)
1
@@ -83,13 +83,14 @@ defmodule String do
iex> ?j
106
iex> ?é
233
Or also via pattern matching:
iex> << eacute :: utf8 >> = "é"
...> eacute
iex> eacute
233
As we have seen above, codepoints can be inserted into
@@ -159,8 +160,10 @@ defmodule String do
iex> String.split("foo bar")
["foo", "bar"]
iex> String.split("foo" <> <<194, 133>> <> "bar")
["foo", "bar"]
iex> String.split(" foo bar ")
["foo", "bar"]
@@ -168,13 +171,14 @@ defmodule String do
@spec split(t) :: [t]
defdelegate split(binary), to: String.Unicode
@doc %S"""
@doc ~S"""
Divides a string into substrings based on a pattern,
returning 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
default, unless the `global` option is set to `false`.
default, but can be controlled via the `parts: num` option.
If you pass `parts: :infinity`, it will return all possible parts.
Empty strings are only removed from the result if the
`trim` option is set to `true`.
@@ -185,8 +189,10 @@ defmodule String do
iex> String.split("a,b,c", ",")
["a", "b", "c"]
iex> String.split("a,b,c", ",", global: false)
iex> String.split("a,b,c", ",", parts: 2)
["a", "b,c"]
iex> String.split(" a b c ", " ", trim: true)
["a", "b", "c"]
@@ -197,22 +203,27 @@ defmodule String do
A regular expression:
iex> String.split("a,b,c", %r{,})
iex> String.split("a,b,c", ~r{,})
["a", "b", "c"]
iex> String.split("a,b,c", %r{,}, global: false)
iex> String.split("a,b,c", ~r{,}, parts: 2)
["a", "b,c"]
iex> String.split(" a b c ", %r{\s}, trim: true)
iex> String.split(" a b c ", ~r{\s}, trim: true)
["a", "b", "c"]
Splitting on empty patterns returns codepoints:
iex> String.split("abc", %r{})
iex> String.split("abc", ~r{})
["a", "b", "c", ""]
iex> String.split("abc", "")
["a", "b", "c", ""]
iex> String.split("abc", "", trim: true)
["a", "b", "c"]
iex> String.split("abc", "", global: false)
iex> String.split("abc", "", parts: 2)
["a", "bc"]
"""
@@ -222,23 +233,93 @@ defmodule String do
def split("", _pattern, _options), do: [""]
def split(binary, "", options), do: split(binary, %r"", options)
def split(binary, pattern, options) when is_regex(pattern) do
Regex.split(pattern, binary, options)
end
def split(binary, "", options), do: split(binary, ~r""u, options)
def split(binary, pattern, options) do
opts = if options[:global] != false, do: [:global], else: []
splits = :binary.split(binary, pattern, opts)
if Keyword.get(options, :trim, false) do
lc split inlist splits, split != "", do: split
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
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
:binary.split(binary, pattern, [:global])
else
:binary.split(binary, pattern, [])
end
end
if Keyword.get(options, :trim, false) do
for split <- splits, split != "", do: split
else
splits
end
end
end
defp split_parts("", _pattern, _num), do: [""]
defp split_parts(binary, pattern, num), do: split_parts(binary, pattern, num, [])
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])
end
end
@doc """
Splits a string into two at the specified offset. When the offset given is
negative, location is counted from the end of the string.
The offset is capped to the length of the string.
Returns a tuple with two elements.
## Examples
iex> String.split_at "sweetelixir", 5
{"sweet", "elixir"}
iex> String.split_at "sweetelixir", -6
{"sweet", "elixir"}
iex> String.split_at "abc", 0
{"", "abc"}
iex> String.split_at "abc", 1000
{"abc", ""}
iex> String.split_at "abc", -1000
{"", "abc"}
"""
@spec split_at(t, integer) :: {t, t}
def split_at(string, offset)
def split_at(binary, index) when index == 0, do:
{"", binary}
def split_at(binary, index) when index > 0, do:
do_split_at(next_grapheme(binary), 0, index, "")
def split_at(binary, index) when index < 0, do:
do_split_at(next_grapheme(binary), 0, max(0, byte_size(binary)+index), "")
defp do_split_at(nil, _, _, acc), do:
{acc, ""}
defp do_split_at({grapheme, rest}, current_pos, target_pos, acc) when current_pos < target_pos, do:
do_split_at(next_grapheme(rest), current_pos+1, target_pos, acc <> grapheme)
defp do_split_at({grapheme, rest}, pos, pos, acc), do:
{acc, grapheme <> rest}
@doc """
Convert all characters on the given string to uppercase.
@@ -246,8 +327,10 @@ defmodule String do
iex> String.upcase("abcd")
"ABCD"
iex> String.upcase("ab 123 xpto")
"AB 123 XPTO"
iex> String.upcase("josé")
"JOSÉ"
@@ -262,8 +345,10 @@ defmodule String do
iex> String.downcase("ABCD")
"abcd"
iex> String.downcase("AB 123 XPTO")
"ab 123 xpto"
iex> String.downcase("JOSÉ")
"josé"
@@ -284,15 +369,17 @@ defmodule String do
iex> String.capitalize("abcd")
"Abcd"
iex> String.capitalize("fin")
"Fin"
iex> String.capitalize("josé")
"José"
"""
@spec capitalize(t) :: t
def capitalize(string) when is_binary(string) do
{ char, rest } = String.Unicode.titlecase_once(string)
{char, rest} = String.Unicode.titlecase_once(string)
char <> downcase(rest)
end
@@ -333,7 +420,7 @@ defmodule String do
end
end
def rstrip(string, char) do
def rstrip(string, char) when is_integer(char) do
do_rstrip(string, "", char)
end
@@ -373,11 +460,11 @@ defmodule String do
@spec lstrip(t, char) :: t
def lstrip(<<char :: utf8, rest :: binary>>, char) do
def lstrip(<<char :: utf8, rest :: binary>>, char) when is_integer(char) do
<<lstrip(rest, char) :: binary>>
end
def lstrip(other, _char) do
def lstrip(other, char) when is_integer(char) do
other
end
@@ -413,7 +500,7 @@ defmodule String do
rstrip(lstrip(string, char), char)
end
@doc %S"""
@doc ~S"""
Returns a new string of length `len` with `subject` right justified and
padded with `padding`. If `padding` is not present, it defaults to
whitespace. When `len` is less than the length of `subject`, `subject` is
@@ -423,6 +510,7 @@ defmodule String do
iex> String.rjust("abc", 5)
" abc"
iex> String.rjust("abc", 5, ?-)
"--abc"
@@ -438,7 +526,7 @@ defmodule String do
do_justify(subject, len, padding, :right)
end
@doc %S"""
@doc ~S"""
Returns a new string of length `len` with `subject` left justified and padded
with `padding`. If `padding` is not present, it defaults to whitespace. When
`len` is less than the length of `subject`, `subject` is returned.
@@ -447,6 +535,7 @@ defmodule String do
iex> String.ljust("abc", 5)
"abc "
iex> String.ljust("abc", 5, ?-)
"abc--"
@@ -482,7 +571,7 @@ defmodule String do
end
end
@doc %S"""
@doc ~S"""
Returns a new binary based on `subject` by replacing the parts
matching `pattern` by `replacement`. By default, it replaces
all entries, except if the `global` option is set to `false`.
@@ -493,13 +582,14 @@ defmodule String do
iex> String.replace("a,b,c", ",", "-")
"a-b-c"
iex> String.replace("a,b,c", ",", "-", global: false)
"a-b,c"
The pattern can also be a regex. In those cases, one can give `\N`
in the `replacement` string to access a specific capture in the regex:
iex> String.replace("a,b,c", %r/,(.)/, ",\\1\\1")
iex> String.replace("a,b,c", ~r/,(.)/, ",\\1\\1")
"a,bb,cc"
Notice we had to escape the escape character `\`. By giving `&`,
@@ -510,8 +600,10 @@ defmodule String do
iex> String.replace("a,b,c", "b", "[]", insert_replaced: 1)
"a,[b],c"
iex> String.replace("a,b,c", ",", "[]", insert_replaced: 2)
"a[],b[],c"
iex> String.replace("a,b,c", ",", "[]", insert_replaced: [1, 1])
"a[,,]b[,,]c"
@@ -519,21 +611,19 @@ defmodule String do
@spec replace(t, t, t) :: t
@spec replace(t, t, t, Keyword.t) :: t
def replace(subject, pattern, replacement, options \\ [])
def replace(subject, pattern, replacement, options) when is_regex(pattern) do
Regex.replace(pattern, subject, replacement, global: options[:global])
end
def replace(subject, pattern, replacement, options) do
opts = translate_replace_options(options)
:binary.replace(subject, pattern, replacement, opts)
def replace(subject, pattern, replacement, options \\ []) when is_binary(replacement) do
if Regex.regex?(pattern) do
Regex.replace(pattern, subject, replacement, global: options[:global])
else
opts = translate_replace_options(options)
:binary.replace(subject, pattern, replacement, opts)
end
end
defp translate_replace_options(options) do
opts = if options[:global] != false, do: [:global], else: []
opts = if Keyword.get(options, :global) != false, do: [:global], else: []
if insert = options[:insert_replaced] do
if insert = Keyword.get(options, :insert_replaced) do
opts = [{:insert_replaced, insert}|opts]
end
@@ -547,8 +637,10 @@ defmodule String do
iex> String.reverse("abcd")
"dcba"
iex> String.reverse("hello world")
"dlrow olleh"
iex> String.reverse("hello ∂og")
"go∂ olleh"
@@ -562,7 +654,7 @@ defmodule String do
do_reverse(next_grapheme(rest), [grapheme|acc])
end
defp do_reverse(nil, acc), do: iolist_to_binary(acc)
defp do_reverse(nil, acc), do: iodata_to_binary(acc)
@doc """
Returns a binary `subject` duplicated `n` times.
@@ -571,8 +663,10 @@ defmodule String do
iex> String.duplicate("abc", 0)
""
iex> String.duplicate("abc", 1)
"abc"
iex> String.duplicate("abc", 2)
"abcabc"
@@ -589,8 +683,10 @@ defmodule String do
iex> String.codepoints("josé")
["j", "o", "s", "é"]
iex> String.codepoints("оптими зации")
["о","п","т","и","м","и"," ","з","а","ц","и","и"]
iex> String.codepoints("ἅἪῼ")
["ἅ","Ἢ","ῼ"]
@@ -613,24 +709,27 @@ defmodule String do
## Examples
iex> String.next_codepoint("josé")
{ "j", "osé" }
{"j", "osé"}
"""
@compile { :inline, next_codepoint: 1 }
@compile {:inline, next_codepoint: 1}
@spec next_codepoint(t) :: {codepoint, t} | nil
defdelegate next_codepoint(string), to: String.Unicode
@doc %S"""
@doc ~S"""
Checks whether `str` contains only valid characters.
## Examples
iex> String.valid?("a")
true
iex> String.valid?("ø")
true
iex> String.valid?(<<0xffff :: 16>>)
false
iex> String.valid?("asd" <> <<0xffff :: 16>>)
false
@@ -643,7 +742,7 @@ defmodule String do
?\x{6FFFE}, ?\x{6FFFF}, ?\x{7FFFE}, ?\x{7FFFF}, ?\x{8FFFE}, ?\x{8FFFF},
?\x{9FFFE}, ?\x{9FFFF}, ?\x{10FFFE}, ?\x{10FFFF} ]
lc noncharacter inlist noncharacters do
for noncharacter <- noncharacters do
def valid?(<< unquote(noncharacter) :: utf8, _ :: binary >>), do: false
end
@@ -651,7 +750,7 @@ defmodule String do
def valid?(<<>>), do: true
def valid?(_), do: false
@doc %S"""
@doc ~S"""
Checks whether `str` is a valid character.
All characters are codepoints, but some codepoints
@@ -664,8 +763,10 @@ defmodule String do
iex> String.valid_character?("a")
true
iex> String.valid_character?("ø")
true
iex> String.valid_character?("\x{ffff}")
false
@@ -699,11 +800,11 @@ defmodule String do
## Examples
iex> String.next_grapheme("josé")
{ "j", "osé" }
{"j", "osé"}
"""
@compile { :inline, next_grapheme: 1 }
@spec next_grapheme(t) :: { grapheme, t } | nil
@compile {:inline, next_grapheme: 1}
@spec next_grapheme(t) :: {grapheme, t} | nil
defdelegate next_grapheme(string), to: String.Graphemes
@doc """
@@ -714,6 +815,7 @@ defmodule String do
iex> String.first("elixir")
"e"
iex> String.first("եոգլի")
"ե"
@@ -721,7 +823,7 @@ defmodule String do
@spec first(t) :: grapheme | nil
def first(string) do
case next_grapheme(string) do
{ char, _ } -> char
{char, _} -> char
nil -> nil
end
end
@@ -734,6 +836,7 @@ defmodule String do
iex> String.last("elixir")
"r"
iex> String.last("եոգլի")
"ի"
@@ -756,6 +859,7 @@ defmodule String do
iex> String.length("elixir")
6
iex> String.length("եոգլի")
5
@@ -779,12 +883,16 @@ defmodule String do
iex> String.at("elixir", 0)
"e"
iex> String.at("elixir", 1)
"l"
iex> String.at("elixir", 10)
nil
iex> String.at("elixir", -1)
"r"
iex> String.at("elixir", -10)
nil
@@ -822,18 +930,25 @@ defmodule String do
iex> String.slice("elixir", 1, 3)
"lix"
iex> String.slice("elixir", 1, 10)
"lixir"
iex> String.slice("elixir", 10, 3)
nil
iex> String.slice("elixir", -4, 4)
"ixir"
iex> String.slice("elixir", -10, 3)
nil
iex> String.slice("a", 0, 1500)
"a"
iex> String.slice("a", 1, 1500)
""
iex> String.slice("a", 2, 1500)
nil
@@ -870,26 +985,34 @@ defmodule String do
iex> String.slice("elixir", 1..3)
"lix"
iex> String.slice("elixir", 1..10)
"lixir"
iex> String.slice("elixir", 10..3)
nil
iex> String.slice("elixir", -4..-1)
"ixir"
iex> String.slice("elixir", 2..-1)
"ixir"
iex> String.slice("elixir", -4..6)
"ixir"
iex> String.slice("elixir", -1..-4)
nil
iex> String.slice("elixir", -10..-7)
nil
iex> String.slice("a", 0..1500)
"a"
iex> String.slice("a", 1..1500)
""
iex> String.slice("a", 2..1500)
nil
@@ -913,7 +1036,7 @@ defmodule String do
last = total + last
end
if first > 0 do
if first >= 0 do
do_slice(next_grapheme(string), first, last, 0, "")
end
end
@@ -953,8 +1076,10 @@ defmodule String do
iex> String.starts_with? "elixir", "eli"
true
iex> String.starts_with? "elixir", ["erlang", "elixir"]
true
iex> String.starts_with? "elixir", ["erlang", "ruby"]
false
@@ -989,8 +1114,10 @@ defmodule String do
iex> String.ends_with? "language", "age"
true
iex> String.ends_with? "language", ["youth", "age"]
true
iex> String.ends_with? "language", ["youth", "elixir"]
false
@@ -1025,9 +1152,10 @@ defmodule String do
## Examples
iex> String.match?("foo", %r/foo/)
iex> String.match?("foo", ~r/foo/)
true
iex> String.match?("bar", %r/foo/)
iex> String.match?("bar", ~r/foo/)
false
"""
@@ -1045,8 +1173,10 @@ defmodule String do
iex> String.contains? "elixir of life", "of"
true
iex> String.contains? "elixir of life", ["life", "death"]
true
iex> String.contains? "elixir of life", ["death", "mercury"]
false
@@ -1073,126 +1203,91 @@ defmodule String do
raise ArgumentError
end
defexception UnicodeConversionError, [:encoded, :message] do
def exception(opts) do
UnicodeConversionError[
encoded: opts[:encoded],
message: "#{opts[:kind]} #{detail(opts[:rest])}"
]
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
defp detail(rest) when is_binary(rest) do
"encoding starting at #{inspect rest}"
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
defp detail([h|_]) do
"code point #{h}"
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 a string into a char list converting each codepoint to its
respective integer value.
Converts char data (a list of integers and strings) into a string.
If a string is given, returns the string itself.
## Examples
iex> String.to_char_list("æß")
{ :ok, 'æß' }
iex> String.to_char_list("abc")
{ :ok, 'abc' }
iex> String.from_char_data([0x00E6, 0x00DF])
{:ok, "æß"}
iex> String.from_char_data([0x0061, "bc"])
{:ok, "abc"}
"""
@spec to_char_list(String.t) :: { :ok, char_list } | { :error, list, binary } | { :incomplete, list, binary }
def to_char_list(string) when is_binary(string) do
case :unicode.characters_to_list(string) do
result when is_list(result) ->
{ :ok, result }
{ :error, _, _ } = error ->
error
{ :incomplete, _, _ } = incomplete ->
incomplete
end
@spec from_char_data(char_data) :: {:ok, String.t} | {:error, binary, binary} | {:incomplete, binary, binary}
def from_char_data(binary) when is_binary(binary) do
binary
end
@doc """
Converts a string into a char list converting each codepoint to its
respective integer value.
In case the conversion fails or is incomplete,
it raises a `String.UnicodeConversionError`.
## Examples
iex> String.to_char_list!("æß")
'æß'
iex> String.to_char_list!("abc")
'abc'
"""
@spec to_char_list!(String.t) :: char_list | no_return
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 list of integer codepoints to a string.
## Examples
iex> String.from_char_list([0x00E6, 0x00DF])
{ :ok, "æß" }
iex> String.from_char_list([0x0061, 0x0062, 0x0063])
{ :ok, "abc" }
"""
@spec from_char_list(char_list) :: { :ok, String.t } | { :error, binary, binary } | { :incomplete, binary, binary }
def from_char_list(list) when is_list(list) do
def from_char_data(list) when is_list(list) do
case :unicode.characters_to_binary(list) do
result when is_binary(result) ->
{ :ok, result }
{:ok, result}
{ :error, _, _ } = error ->
{:error, _, _} = error ->
error
{ :incomplete, _, _ } = incomplete ->
{:incomplete, _, _} = incomplete ->
incomplete
end
end
@doc """
Converts a list of integer codepoints to a string.
Converts char data (a list of integers and strings) into a string.
In case the conversion fails, it raises a `String.UnicodeConversionError`.
In case the conversion fails, it raises a `UnicodeConversionError`.
If a string is given, returns the string itself.
## Examples
iex> String.from_char_list!([0x00E6, 0x00DF])
iex> String.from_char_data!([0x00E6, 0x00DF])
"æß"
iex> String.from_char_list!([0x0061, 0x0062, 0x0063])
iex> String.from_char_data!([0x0061, "bc"])
"abc"
"""
@spec from_char_list!(char_list) :: String.t | no_return
def from_char_list!(list) when is_list(list) do
@spec from_char_data!(char_data) :: String.t | no_return
def from_char_data!(binary) when is_binary(binary) do
binary
end
def from_char_data!(list) when is_list(list) do
case :unicode.characters_to_binary(list) do
result when is_binary(result) ->
result
{ :error, encoded, rest } ->
{:error, encoded, rest} ->
raise UnicodeConversionError, encoded: encoded, rest: rest, kind: :invalid
{ :incomplete, encoded, rest } ->
{:incomplete, encoded, rest} ->
raise UnicodeConversionError, encoded: encoded, rest: rest, kind: :incomplete
end
end
+4 -3
View File
@@ -1,7 +1,7 @@
import Kernel, except: [to_string: 1]
defprotocol String.Chars do
@moduledoc %S"""
@moduledoc ~S"""
The String.Chars protocol is responsible for
converting a structure to a Binary (only if applicable).
The only function required to be implemented is
@@ -58,11 +58,12 @@ defimpl String.Chars, for: List do
iex> to_string('foo')
"foo"
iex> to_string(["foo", 'bar'])
"foobar"
"""
def to_string(char_list), do: String.from_char_list!(char_list)
def to_string(char_list), do: String.from_char_data!(char_list)
end
defimpl String.Chars, for: Integer do
@@ -79,6 +80,6 @@ defimpl String.Chars, for: Float do
Simply converts the float to a string.
"""
def to_string(thing) do
iolist_to_binary(:io_lib_format.fwrite_g(thing))
iodata_to_binary(:io_lib_format.fwrite_g(thing))
end
end
+340
View File
@@ -0,0 +1,340 @@
defmodule StringIO do
@moduledoc """
This module provides an IO device that wraps a string.
## Examples
iex> {:ok, pid} = StringIO.open("foo")
iex> IO.read(pid, 2)
"fo"
"""
use GenServer.Behaviour
@doc """
Creates an IO device.
If the `:capture_prompt` option is set to `true`,
prompts (specified as arguments to `IO.get*` functions)
are captured.
## Examples
iex> {:ok, pid} = StringIO.open("foo")
iex> IO.gets(pid, ">")
"foo"
iex> StringIO.contents(pid)
{"", ""}
iex> {:ok, pid} = StringIO.open("foo", capture_prompt: true)
iex> IO.gets(pid, ">")
"foo"
iex> StringIO.contents(pid)
{"", ">"}
"""
@spec open(binary, Keyword.t) :: {:ok, pid}
def open(string, options \\ []) when is_binary(string) do
:gen_server.start_link(__MODULE__, {string, options}, [])
end
@doc """
Returns current buffers.
## Examples
iex> {:ok, pid} = StringIO.open("in")
iex> IO.write(pid, "out")
iex> StringIO.contents(pid)
{"in", "out"}
"""
@spec contents(pid) :: {binary, binary}
def contents(pid) when is_pid(pid) do
:gen_server.call(pid, :contents)
end
@doc """
Stops the IO device and returns remaining buffers.
## Examples
iex> {:ok, pid} = StringIO.open("in")
iex> IO.write(pid, "out")
iex> StringIO.close(pid)
{:ok, {"in", "out"}}
"""
@spec close(pid) :: {:ok, {binary, binary}}
def close(pid) when is_pid(pid) do
:gen_server.call(pid, :close)
end
## callbacks
def init({string, options}) do
capture_prompt = options[:capture_prompt] || false
{:ok, %{input: string, output: "", capture_prompt: capture_prompt}}
end
def handle_info({:io_request, from, reply_as, req}, s) do
s = io_request(from, reply_as, req, s)
{:noreply, s}
end
def handle_info(msg, s) do
super(msg, s)
end
def handle_call(:contents, _from, %{input: input, output: output} = s) do
{:reply, {input, output}, s}
end
def handle_call(:close, _from, %{input: input, output: output} = s) do
{:stop, :normal, {:ok, {input, output}}, s}
end
def handle_call(request, from, s) do
super(request, from, s)
end
defp io_request(from, reply_as, req, s) do
{reply, s} = io_request(req, s)
io_reply(from, reply_as, to_reply(reply))
s
end
defp io_request({:put_chars, chars}, %{output: output} = s) do
{:ok, %{s | output: << output :: binary, String.from_char_data!(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 >>}}
end
defp io_request({:put_chars, _encoding, chars}, s) do
io_request({:put_chars, chars}, s)
end
defp io_request({:put_chars, _encoding, mod, func, args}, s) do
io_request({:put_chars, mod, func, args}, s)
end
defp io_request({:get_chars, prompt, n}, s) when n >= 0 do
io_request({:get_chars, :latin1, prompt, n}, s)
end
defp io_request({:get_chars, encoding, prompt, n}, s) when n >= 0 do
get_chars(encoding, prompt, n, s)
end
defp io_request({:get_line, prompt}, s) do
io_request({:get_line, :latin1, prompt}, s)
end
defp io_request({:get_line, encoding, prompt}, s) do
get_line(encoding, prompt, s)
end
defp io_request({:get_until, prompt, mod, fun, args}, s) do
io_request({:get_until, :latin1, prompt, mod, fun, args}, s)
end
defp io_request({:get_until, encoding, prompt, mod, fun, args}, s) do
get_until(encoding, prompt, mod, fun, args, s)
end
defp io_request({:get_password, encoding}, s) do
get_line(encoding, "", s)
end
defp io_request({:setopts, _opts}, s) do
{{:error, :enotsup}, s}
end
defp io_request(:getopts, s) do
{{:ok, [binary: true, encoding: :unicode]}, s}
end
defp io_request({:get_geometry, :columns}, s) do
{{:error, :enotsup}, s}
end
defp io_request({:get_geometry, :rows}, s) do
{{:error, :enotsup}, s}
end
defp io_request({:requests, reqs}, s) do
io_requests(reqs, {:ok, s})
end
defp io_request(_, s) do
{{:error, :request}, s}
end
## get_chars
defp get_chars(encoding, prompt, n,
%{input: input, output: output, capture_prompt: capture_prompt} = s) do
case do_get_chars(input, encoding, n) do
{:error, _} = error ->
{error, s}
{result, input} ->
if capture_prompt do
output = << output :: binary, String.from_char_data!(prompt) :: binary >>
end
{result, %{s | input: input, output: output}}
end
end
defp do_get_chars("", _encoding, _n) do
{:eof, ""}
end
defp do_get_chars(input, :latin1, n) when byte_size(input) < n do
{input, ""}
end
defp do_get_chars(input, :latin1, n) do
<< chars :: [ binary, size(n) ], rest :: binary >> = input
{chars, rest}
end
defp do_get_chars(input, encoding, n) do
try do
case :file_io_server.count_and_find(input, n, encoding) do
{buf_count, split_pos} when buf_count < n or split_pos == :none ->
{input, ""}
{_buf_count, split_pos} ->
<< chars :: [ binary, size(split_pos) ], rest :: binary >> = input
{chars, rest}
end
catch
:exit, :invalid_unicode ->
{:error, :invalid_unicode}
end
end
## get_line
defp get_line(encoding, prompt,
%{input: input, output: output, capture_prompt: capture_prompt} = s) do
case :unicode.characters_to_list(input, encoding) do
{:error, _, _} ->
{{:error, :collect_line}, s}
{:incomplete, _, _} ->
{{:error, :collect_line}, s}
chars ->
{result, input} = do_get_line(chars, encoding)
if capture_prompt do
output = << output :: binary, String.from_char_data!(prompt) :: binary >>
end
{result, %{s | input: input, output: output}}
end
end
defp do_get_line('', _encoding) do
{:eof, ""}
end
defp do_get_line(chars, encoding) do
{line, rest} = collect_line(chars)
{:unicode.characters_to_binary(line, encoding),
:unicode.characters_to_binary(rest, encoding)}
end
## get_until
defp get_until(encoding, prompt, mod, fun, args,
%{input: input, output: output, capture_prompt: capture_prompt} = s) do
case :unicode.characters_to_list(input, encoding) do
{:error, _, _} ->
{:error, s}
{:incomplete, _, _} ->
{:error, s}
chars ->
{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 >>
end
input =
case input do
:eof -> ""
_ -> :unicode.characters_to_binary(input, encoding)
end
{result, %{s | input: input, output: output}}
end
end
defp do_get_until(chars, encoding, mod, fun, args, continuation \\ [], count \\ 0)
defp do_get_until('', encoding, mod, fun, args, continuation, count) do
case apply(mod, fun, [continuation, :eof | args]) do
{:done, result, rest} ->
{result, rest, count + 1}
{:more, next_continuation} ->
do_get_until('', encoding, mod, fun, args, next_continuation, count + 1)
end
end
defp do_get_until(chars, encoding, mod, fun, args, continuation, count) do
{line, rest} = collect_line(chars)
case apply(mod, fun, [continuation, line | args]) do
{:done, result, rest1} ->
unless rest1 == :eof do
rest = rest1 ++ rest
end
{result, rest, count + 1}
{:more, next_continuation} ->
do_get_until(rest, encoding, mod, fun, args, next_continuation, count + 1)
end
end
## io_requests
defp io_requests([r|rs], {:ok, s}) do
io_requests(rs, io_request(r, s))
end
defp io_requests(_, result) do
result
end
## helpers
defp collect_line(chars) do
collect_line(chars, [])
end
defp collect_line([], stack) do
{:lists.reverse(stack), []}
end
defp collect_line([?\r, ?\n | rest], stack) do
{:lists.reverse([?\n|stack]), rest}
end
defp collect_line([?\n | rest], stack) do
{:lists.reverse([?\n|stack]), rest}
end
defp collect_line([h|t], stack) do
collect_line(t, [h|stack])
end
defp io_reply(from, reply_as, reply) 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(other), do: other
end
+5 -5
View File
@@ -25,7 +25,7 @@ defmodule Supervisor.Behaviour do
end
end
{ :ok, pid } = :supervisor.start_link(MyServer, [])
{:ok, pid} = :supervisor.start_link(MyServer, [])
"""
@@ -87,7 +87,7 @@ defmodule Supervisor.Behaviour do
maxR = Keyword.get(options, :max_restarts, 5)
maxS = Keyword.get(options, :max_seconds, 5)
{ :ok, { { strategy, maxR, maxS }, children } }
{:ok, {{strategy, maxR, maxS}, children}}
end
@child_doc """
@@ -122,7 +122,7 @@ defmodule Supervisor.Behaviour do
* `:transient` - the child process is restarted only if it
terminates abnormally, i.e. with another exit reason than
`:normal`, `:shutdown` or `{ :shutdown, term }`;
`:normal`, `:shutdown` or `{:shutdown, term}`;
## Shutdown values
@@ -178,7 +178,7 @@ defmodule Supervisor.Behaviour do
restart = Keyword.get(options, :restart, :permanent)
shutdown = Keyword.get(options, :shutdown, 5000)
{ id, { module, function, args },
restart, shutdown, type, modules }
{id, {module, function, args},
restart, shutdown, type, modules}
end
end
+54 -52
View File
@@ -1,13 +1,4 @@
defmodule System do
defexception NoHomeError,
message: "could not find the user home, please set the HOME environment variable"
defexception NoTmpDirError,
message: "could not get a writable temporary directory, please set the TMPDIR environment variable"
defexception NoAccessCwdError,
message: "could not get a current working directory, the current location is not accessible"
@moduledoc """
The System module provides access to variables used or
maintained by the VM and to functions that interact directly
@@ -20,7 +11,7 @@ defmodule System do
defp read_stripped(path) do
case :file.read_file(path) do
{ :ok, binary } ->
{:ok, binary} ->
strip_re(binary, "^\s+|\s+$")
_ -> ""
end
@@ -29,7 +20,7 @@ defmodule System do
# Read and strip the version from the `VERSION` file.
defmacrop get_version do
case read_stripped(:filename.join(__DIR__, "../../../VERSION")) do
"" -> raise ArgumentError, message: "could not read the version number from VERSION"
"" -> raise RuntimeError, message: "could not read the version number from VERSION"
data -> data
end
end
@@ -40,7 +31,7 @@ defmodule System do
defmacrop get_describe do
dirpath = :filename.join(__DIR__, "../../../.git")
case :file.read_file_info(dirpath) do
{ :ok, _ } ->
{:ok, _} ->
if :os.find_executable('git') do
data = :os.cmd('git describe --always --tags')
strip_re(data, "\n")
@@ -53,7 +44,7 @@ defmodule System do
# Get the date at compilation time.
defmacrop get_date do
iolist_to_binary :httpd_util.rfc1123_date
iodata_to_binary :httpd_util.rfc1123_date
end
@doc """
@@ -92,7 +83,7 @@ defmodule System do
"""
@spec argv([String.t]) :: :ok
def argv(args) do
:elixir_code_server.cast({ :argv, args })
:elixir_code_server.cast({:argv, args})
end
@doc """
@@ -103,7 +94,7 @@ defmodule System do
"""
def cwd do
case :file.get_cwd do
{ :ok, base } -> String.from_char_list!(base)
{:ok, base} -> String.from_char_data!(base)
_ -> nil
end
end
@@ -111,10 +102,11 @@ defmodule System do
@doc """
Current working directory, exception on error.
Returns the current working directory or raises `System.NoAccessCwdError`.
Returns the current working directory or raises `RuntimeError`.
"""
def cwd! do
cwd || raise NoAccessCwdError
cwd ||
raise RuntimeError, message: "could not get a current working directory, the current location is not accessible"
end
@doc """
@@ -125,7 +117,7 @@ defmodule System do
"""
def user_home do
case :os.type() do
{ :win32, _ } -> get_windows_home
{:win32, _} -> get_windows_home
_ -> get_unix_home
end
end
@@ -133,11 +125,12 @@ defmodule System do
@doc """
User home directory, exception on error.
Same as `user_home/0` but raises `System.NoHomeError`
Same as `user_home/0` but raises `RuntimeError`
instead of returning `nil` if no user home is set.
"""
def user_home! do
user_home || raise NoHomeError
user_home ||
raise RuntimeError, message: "could not find the user home, please set the HOME environment variable"
end
defp get_unix_home do
@@ -154,7 +147,7 @@ defmodule System do
)
end
@doc %S"""
@doc ~S"""
Writable temporary directory.
Returns a writable temporary directory.
@@ -179,11 +172,13 @@ defmodule System do
@doc """
Writable temporary directory, exception on error.
Same as `tmp_dir/0` but raises `System.NoTmpDirError`
Same as `tmp_dir/0` but raises `RuntimeError`
instead of returning `nil` if no temp dir is set.
"""
def tmp_dir! do
tmp_dir || raise NoTmpDirError
tmp_dir ||
raise RuntimeError, message: "could not get a writable temporary directory, " <>
"please set the TMPDIR environment variable"
end
defp write_env_tmp_dir(env) do
@@ -194,17 +189,15 @@ defmodule System do
end
defp write_tmp_dir(dir) do
case :file.read_file_info(dir) do
{:ok, info} ->
type_index = File.Stat.__record__(:index, :type)
access_index = File.Stat.__record__(:index, :access)
case { elem(info, type_index), elem(info, access_index) } do
{ :directory, access } when access in [:read_write, :write] ->
String.from_char_list!(dir)
case File.stat(dir) do
{:ok, stat} ->
case {stat.type, stat.access} do
{:directory, access} when access in [:read_write, :write] ->
String.from_char_data!(dir)
_ ->
nil
end
{ :error, _ } -> nil
{:error, _} -> nil
end
end
@@ -219,7 +212,7 @@ defmodule System do
as an argument.
"""
def at_exit(fun) when is_function(fun, 1) do
:elixir_code_server.cast { :at_exit, fun }
:elixir_code_server.cast {:at_exit, fun}
end
@doc """
@@ -230,9 +223,9 @@ defmodule System do
the result as a binary.
If `command` is a char list, a char list is returned.
Returns a binary otherwise.
Otherwise a string, correctly encoded in UTF-8, is expected.
"""
@spec cmd(binary) :: binary
@spec cmd(String.t) :: String.t
@spec cmd(char_list) :: char_list
def cmd(command) when is_list(command) do
@@ -240,9 +233,7 @@ defmodule System do
end
def cmd(command) when is_binary(command) do
# Notice we don't use unicode for conversion
# because the OS is expecting and returning raw bytes
:binary.list_to_bin :os.cmd(:binary.bin_to_list(command))
String.from_char_data! :os.cmd(List.from_char_data!(command))
end
@doc """
@@ -265,11 +256,9 @@ defmodule System do
end
def find_executable(program) when is_binary(program) do
# Notice we don't use unicode for conversion
# because the OS is expecting and returning raw bytes
case :os.find_executable(:binary.bin_to_list(program)) do
case :os.find_executable(List.from_char_data!(program)) do
false -> nil
other -> :binary.list_to_bin(other)
other -> String.from_char_data!(other)
end
end
@@ -279,12 +268,12 @@ defmodule System do
Returns a list of all environment variables. Each variable is given as a
`{name, value}` tuple where both `name` and `value` are strings.
"""
@spec get_env() :: [{String.t, String.t}]
@spec get_env() :: %{String.t => String.t}
def get_env do
Enum.map(:os.getenv, fn var ->
var = String.from_char_list! var
[k, v] = String.split var, "=", global: false
{k, v}
Enum.into(:os.getenv, %{}, fn var ->
var = String.from_char_data! var
[k, v] = String.split var, "=", parts: 2
{k, v}
end)
end
@@ -297,9 +286,9 @@ defmodule System do
"""
@spec get_env(binary) :: binary | nil
def get_env(varname) when is_binary(varname) do
case :os.getenv(String.to_char_list!(varname)) do
case :os.getenv(List.from_char_data!(varname)) do
false -> nil
other -> String.from_char_list!(other)
other -> String.from_char_data!(other)
end
end
@@ -312,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: iolist_to_binary(:os.getpid)
def get_pid, do: iodata_to_binary(:os.getpid)
@doc """
Set an environment variable value.
@@ -321,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 :binary.bin_to_list(varname), String.to_char_list!(value)
:os.putenv List.from_char_data!(varname), List.from_char_data!(value)
:ok
end
@@ -336,12 +325,25 @@ defmodule System do
Enum.each dict, fn {key, val} -> put_env key, val end
end
@doc """
Deletes an environment variable.
Removes the variable `varname` from the environment.
"""
@spec delete_env(String.t) :: :ok
def delete_env(varname) do
:os.unsetenv(List.from_char_data!(varname))
:ok
end
@doc """
Last exception stacktrace.
Note that the Erlang VM (and therefore this function) does not
return the current stacktrace but rather the stacktrace of the
latest exception.
Inlined by the compiler into `:erlang.get_stacktrace/0`.
"""
def stacktrace do
:erlang.get_stacktrace
@@ -359,7 +361,7 @@ defmodule System do
* If `:abort`, the runtime system aborts producing a core dump, if that is
enabled in the operating system;
* If a char list, an erlang crash dump is produced with status as slogan,
* If a string, an erlang crash dump is produced with status as slogan,
and then the runtime system exits with status code 1;
Note that on many platforms, only the status codes 0-255 are supported
@@ -383,6 +385,6 @@ defmodule System do
end
def halt(status) when is_binary(status) do
:erlang.halt(:binary.bin_to_list(status))
:erlang.halt(List.from_char_data!(status))
end
end
+14 -8
View File
@@ -9,10 +9,12 @@ defmodule Tuple do
Creates a tuple of size `size` containing the
given `data` at every position.
Inlined by the compiler.
## Examples
iex> Tuple.duplicate(:hello, 3)
{ :hello, :hello, :hello }
{:hello, :hello, :hello}
"""
@spec duplicate(term, non_neg_integer) :: tuple
@@ -27,11 +29,13 @@ defmodule Tuple do
Raises an `ArgumentError` if `index` is greater than the
length of `tuple`.
Inlined by the compiler.
## Examples
iex> tuple = { :bar, :baz }
...> Tuple.insert_at(tuple, 0, :foo)
{ :foo, :bar, :baz }
iex> tuple = {:bar, :baz}
iex> Tuple.insert_at(tuple, 0, :foo)
{:foo, :bar, :baz}
"""
@spec insert_at(tuple, non_neg_integer, term) :: tuple
@@ -43,14 +47,16 @@ defmodule Tuple do
Remove an element from a tuple.
Deletes the element at the zero-based `index` from `tuple`.
Raises an `ArgumentError` if `index` is greater than
Raises an `ArgumentError` if `index` is greater than
or equal to the length of `tuple`.
Inlined by the compiler.
## Examples
iex> tuple = { :foo, :bar, :baz }
...> Tuple.delete_at(tuple, 0)
{ :bar, :baz }
iex> tuple = {:foo, :bar, :baz}
iex> Tuple.delete_at(tuple, 0)
{:bar, :baz}
"""
@spec delete_at(tuple, non_neg_integer) :: tuple
+78 -64
View File
@@ -3,22 +3,22 @@ defmodule URI do
Utilities for working with and creating URIs.
"""
defrecord Info, [scheme: nil, path: nil, query: nil,
fragment: nil, authority: nil,
userinfo: nil, host: nil, port: nil]
defstruct scheme: nil, path: nil, query: nil,
fragment: nil, authority: nil,
userinfo: nil, host: nil, port: nil
import Bitwise
@ports [
{ "ftp", 21 },
{ "http", 80 },
{ "https", 443 },
{ "ldap", 389 },
{ "sftp", 22 },
{ "tftp", 69 },
]
@ports %{
"ftp" => 21,
"http" => 80,
"https" => 443,
"ldap" => 389,
"sftp" => 22,
"tftp" => 69,
}
Enum.each @ports, fn { scheme, port } ->
Enum.each @ports, fn {scheme, port} ->
def normalize_scheme(unquote(scheme)), do: unquote(scheme)
def default_port(unquote(scheme)), do: unquote(port)
end
@@ -45,16 +45,20 @@ defmodule URI do
"""
def default_port(scheme) when is_binary(scheme) do
{ :ok, dict } = :application.get_env(:elixir, :uri)
Dict.get(dict, scheme)
{:ok, dict} = Application.fetch_env(:elixir, :uri)
Map.get(dict, scheme)
end
@doc """
Registers a scheme with a default port.
It is recommended for this function to be invoked in your
application start callback in case you want to register
new URIs.
"""
def default_port(scheme, port) when is_binary(scheme) and port > 0 do
{ :ok, dict } = :application.get_env(:elixir, :uri)
:application.set_env(:elixir, :uri, Dict.put(dict, scheme, port))
{:ok, dict} = Application.fetch_env(:elixir, :uri)
Application.put_env(:elixir, :uri, Map.put(dict, scheme, port), persist: true)
end
@doc """
@@ -62,13 +66,14 @@ defmodule URI do
Takes an enumerable (containing a sequence of two-item tuples)
and returns a string of the form "key1=value1&key2=value2..." where
keys and values are URL encoded as per `encode/1`. Keys and values can
be any term that implements the `String.Chars` protocol (i.e. can be converted
to a binary).
keys and values are URL encoded as per `encode/1`.
Keys and values can be any term that implements the `String.Chars`
protocol, except lists which are explicitly forbidden.
## Examples
iex> hd = HashDict.new([{"foo", 1}, {"bar", "2"}])
iex> hd = %{"foo" => 1, "bar" => 2}
iex> URI.encode_query(hd)
"bar=2&foo=1"
@@ -76,28 +81,22 @@ defmodule URI do
def encode_query(l), do: Enum.map_join(l, "&", &pair/1)
@doc """
Decodes a query string into a `HashDict`.
Decodes a query string into a dictionary (by default uses a map).
Given a query string of the form "key1=value1&key2=value2...", produces a
`HashDict` with one entry for each key-value pair. Each key and value will be a
map with one entry for each key-value pair. Each key and value will be a
binary. Keys and values will be percent-unescaped.
Use `query_decoder/1` if you want to iterate over each value manually.
## Examples
iex> URI.decode_query("foo=1&bar=2") |> Dict.to_list
[{"bar", "2"}, {"foo", "1"}]
iex> hd = HashDict.new()
iex> URI.decode_query("foo=1&bar=2", hd) |> HashDict.keys
["bar", "foo"]
iex> URI.decode_query("foo=1&bar=2", hd) |> HashDict.values
["2", "1"]
iex> URI.decode_query("foo=1&bar=2")
%{"bar" => "2", "foo" => "1"}
"""
def decode_query(q, dict \\ HashDict.new) when is_binary(q) do
Enum.reduce query_decoder(q), dict, fn({ k, v }, acc) -> Dict.put(acc, k, v) end
def decode_query(q, dict \\ %{}) when is_binary(q) do
Enum.reduce query_decoder(q), dict, fn({k, v}, acc) -> Dict.put(acc, k, v) end
end
@doc """
@@ -119,19 +118,27 @@ defmodule URI do
end
defp do_decoder(q) do
{ first, next } =
{first, next} =
case :binary.split(q, "&") do
[first, rest] -> { first, rest }
[first] -> { first, "" }
[first, rest] -> {first, rest}
[first] -> {first, ""}
end
current =
case :binary.split(first, "=") do
[ key, value ] -> { decode(key), decode(value) }
[ key ] -> { decode(key), nil }
[ key, value ] -> {decode(key), decode(value)}
[ key ] -> {decode(key), nil}
end
{ current, next }
{current, next}
end
defp pair({k, _}) when is_list(k) do
raise ArgumentError, message: "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}"
end
defp pair({k, v}) do
@@ -143,11 +150,11 @@ defmodule URI do
## Example
iex> URI.encode("http://elixir-lang.com/getting_started/2.html")
"http%3A%2F%2Felixir-lang.com%2Fgetting_started%2F2.html"
iex> URI.encode("http://elixir-lang.org/getting_started/2.html")
"http%3A%2F%2Felixir-lang.org%2Fgetting_started%2F2.html"
"""
def encode(s), do: bc(<<c>> inbits s, do: <<percent(c) :: binary>>)
def encode(s), do: for(<<c <- s>>, into: "", do: percent(c))
defp percent(32), do: <<?+>>
defp percent(?-), do: <<?->>
@@ -171,23 +178,30 @@ defmodule URI do
## Examples
iex> URI.decode("http%3A%2F%2Felixir-lang.com")
"http://elixir-lang.com"
iex> URI.decode("http%3A%2F%2Felixir-lang.org")
"http://elixir-lang.org"
"""
def decode(<<?%, hex1, hex2, tail :: binary >>) do
<< bsl(hex2dec(hex1), 4) + hex2dec(hex2) >> <> decode(tail)
def decode(uri) do
decode(uri, uri)
end
def decode(<<head, tail :: binary >>) do
<<check_plus(head)>> <> decode(tail)
def decode(<<?%, hex1, hex2, tail :: binary >>, uri) do
<< bsl(hex2dec(hex1, uri), 4) + hex2dec(hex2, uri) >> <> decode(tail, uri)
end
def decode(<<>>), do: <<>>
def decode(<<head, tail :: binary >>, uri) do
<<check_plus(head)>> <> decode(tail, uri)
end
defp hex2dec(n) when n in ?A..?F, do: n - ?A + 10
defp hex2dec(n) when n in ?a..?f, do: n - ?a + 10
defp hex2dec(n) when n in ?0..?9, do: n - ?0
def decode(<<>>, _uri), do: <<>>
defp hex2dec(n, _uri) when n in ?A..?F, do: n - ?A + 10
defp hex2dec(n, _uri) when n in ?a..?f, do: n - ?a + 10
defp hex2dec(n, _uri) when n in ?0..?9, do: n - ?0
defp hex2dec(_n, uri) do
raise ArgumentError, message: "malformed URI #{inspect uri}"
end
defp check_plus(?+), do: 32
defp check_plus(c), do: c
@@ -203,18 +217,18 @@ defmodule URI do
## Examples
iex> URI.parse("http://elixir-lang.org/")
URI.Info[scheme: "http", path: "/", query: nil, fragment: nil,
authority: "elixir-lang.org", userinfo: nil,
host: "elixir-lang.org", port: 80]
%URI{scheme: "http", path: "/", query: nil, fragment: nil,
authority: "elixir-lang.org", userinfo: nil,
host: "elixir-lang.org", port: 80}
"""
def parse(s) when is_binary(s) do
# From http://tools.ietf.org/html/rfc3986#appendix-B
regex = %r/^(([^:\/?#]+):)?(\/\/([^\/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?/
regex = ~r/^(([^:\/?#]+):)?(\/\/([^\/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?/
parts = nillify(Regex.run(regex, s))
destructure [_, _, scheme, _, authority, path, _, query, _, fragment], parts
{ userinfo, host, port } = split_authority(authority)
{userinfo, host, port} = split_authority(authority)
if authority do
authority = ""
@@ -230,40 +244,40 @@ defmodule URI do
port = default_port(scheme)
end
URI.Info[
%URI{
scheme: scheme, path: path, query: query,
fragment: fragment, authority: authority,
userinfo: userinfo, host: host, port: port
]
}
end
# Split an authority into its userinfo, host and port parts.
defp split_authority(s) do
s = s || ""
components = Regex.run %r/(^(.*)@)?(\[[a-zA-Z0-9:.]*\]|[^:]*)(:(\d*))?/, s
components = Regex.run ~r/(^(.*)@)?(\[[a-zA-Z0-9:.]*\]|[^:]*)(:(\d*))?/, s
destructure [_, _, userinfo, host, _, port], nillify(components)
port = if port, do: binary_to_integer(port)
host = if host, do: host |> String.lstrip(?[) |> String.rstrip(?])
{ userinfo, host, port }
{userinfo, host, port}
end
# Regex.run returns empty strings sometimes. We want
# to replace those with nil for consistency.
defp nillify(l) do
lc s inlist l do
for s <- l do
if size(s) > 0, do: s, else: nil
end
end
end
defimpl String.Chars, for: URI.Info do
def to_string(URI.Info[] = uri) do
defimpl String.Chars, for: URI do
def to_string(uri) do
scheme = uri.scheme
if scheme && (port = URI.default_port(scheme)) do
if uri.port == port, do: uri = uri.port(nil)
if uri.port == port, do: uri = %{uri | port: nil}
end
result = ""
+254 -293
View File
@@ -1,17 +1,12 @@
defmodule Version do
@moduledoc %S"""
@moduledoc ~S"""
Functions for parsing and matching versions against requirements.
A version is a string in a specific format or a `Version.Schema`
A version is a string in a specific format or a `Version`
generated after parsing via `Version.parse/1`.
`Version` parsing and requirements follow
[SemVer 2.0 schema](http://semver.org/) and you will get
the most of Mix's version system by following it. In order
to support integration with projects that may
follow different versioning schemas, Elixir won't choke
on unknown versions, however you won't be able to use
Mix requirements with such unformatted versions.
[SemVer 2.0 schema](http://semver.org/).
## Versions
@@ -27,6 +22,12 @@ defmodule Version do
"1.0.0-alpha.3+20130417140000"
## Struct
The version is represented by the Version struct and it has its
fields named according to Semver: `:major`, `:minor`, `:patch`,
`:pre` and `:build`.
## Requirements
Requirements allow you to specify which versions of a given
@@ -52,197 +53,92 @@ defmodule Version do
"""
@type t :: String.t | Version.Schema.t
@type requirement :: String.t | Version.Requirement.t
@type matchable :: { major :: String.t | non_neg_integer,
minor :: non_neg_integer | nil,
patch :: non_neg_integer | nil,
pre :: [String.t] }
import Kernel, except: [match?: 2]
defstruct [:major, :minor, :patch, :pre, :build]
defrecord Schema, major: 0, minor: 0, patch: 0, pre: nil, build: nil, source: nil
defrecord Requirement, source: nil, matchspec: nil
@type version :: String.t | t
@type requirement :: String.t | Version.Requirement.t
@type matchable :: {major :: String.t | non_neg_integer,
minor :: non_neg_integer | nil,
patch :: non_neg_integer | nil,
pre :: [String.t]}
defexception InvalidRequirement, [:message] do
def exception(opts) do
message =
if is_binary opts[:reason] do
{ first, rest } = String.next_grapheme(opts[:reason])
String.downcase(first) <> rest
else
"invalid version specification"
end
InvalidRequirement[message: message]
end
defmodule Requirement do
@moduledoc false
defstruct [:source, :matchspec]
end
defexception InvalidRequirement, [:message]
defexception InvalidVersion, [: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.
Raises a `Version.InvalidRequirement` exception if `requirement` is not
parseable, or `Version.InvalidVersion` if `version` is not parseable.
If given an already parsed version and requirement this function won't
raise.
## Examples
iex> Version.match?("2.0", ">1.0")
iex> Version.match?("2.0.0", ">1.0.0")
true
iex> Version.match?("2.0", "==1.0")
iex> Version.match?("2.0.0", "==1.0.0")
false
iex> Version.match?("foo", "==1.0.0")
** (Version.InvalidVersion) foo
iex> Version.match?("2.0.0", "== ==1.0.0")
** (Version.InvalidRequirement) == ==1.0.0
"""
@spec match?(t, requirement) :: boolean
def match?(version, requirement) when is_binary(requirement) do
case Version.Parser.parse_requirement(requirement) do
{ :ok, req } ->
match?(version, req)
{ :error, reason } ->
raise InvalidRequirement, reason: reason
@spec match?(version, requirement) :: boolean
def match?(vsn, req) when is_binary(req) do
case parse_requirement(req) do
{:ok, req} ->
match?(vsn, req)
:error ->
raise InvalidRequirement, message: req
end
end
def match?(version, requirement) when is_binary(version) do
match?(parse(version), requirement)
end
def match?(Schema[] = version, Requirement[matchspec: spec]) do
case :ets.test_ms(to_matchable(version), spec) do
{ :ok, result } ->
result != false
{ :error, reason } ->
raise InvalidRequirement, reason: reason
end
def match?(version, %Requirement{matchspec: spec}) do
{:ok, result} = :ets.test_ms(to_matchable(version), spec)
result != false
end
@doc """
Check if a version string is compatible with [semver](http://semver.org/).
Compares two versions. Returns `:gt` if first version is greater than
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.
If given an already parsed version this function won't raise.
## Examples
iex> Version.valid?("2.0")
true
iex> Version.valid?("invalid")
false
iex> Version.compare("2.0.1-alpha1", "2.0.0")
:gt
iex> Version.compare("2.0.1+build0", "2.0.1")
:eq
iex> Version.compare("invalid", "2.0.1")
** (Version.InvalidVersion) invalid
"""
@spec valid?(String.t | Schema.t) :: boolean
def valid?(string) when is_binary(string) do
Version.Parser.valid_version?(string)
end
def valid?(Version.Schema[major: nil]), do: false
def valid?(Version.Schema[]), do: true
@spec valid_requirement?(String.t) :: boolean
def valid_requirement?(requirement) do
case Version.Parser.parse_requirement(requirement) do
{ :ok, _req } ->
true
{ :error, _reason } ->
false
end
end
@doc """
Parse a version string into a `Version.Schema`.
## Examples
iex> Version.parse("2.0.1-alpha1")
#Version.Schema<2.0.1-alpha1>
"""
@spec parse(String.t) :: Schema.t
def parse(string) when is_binary(string) do
case Version.Parser.parse_version(string) do
{ :ok, matchable } -> from_matchable(matchable).source(string).build(get_build(string))
{ :error, _ } -> Version.Schema[source: string]
end
end
defp get_build(string) do
case Regex.run(%r/\+([^\s]+)$/, string) do
nil ->
nil
[_, build] ->
build
end
end
@doc """
Convert a version to a `Version.matchable`
## Examples
iex> Version.to_matchable("2.0.1-alpha.1")
{2, 0, 1, ["alpha", 1]}
"""
@spec to_matchable(String.t | Schema.t) :: Version.matchable
def to_matchable(Schema[major: nil, source: source]) do
{ source, nil, nil, [] }
end
def to_matchable(Version.Schema[major: major, minor: minor, patch: patch, pre: nil]) do
{ major, minor, patch, [] }
end
def to_matchable(Version.Schema[major: major, minor: minor, patch: patch, pre: pre]) do
{ major, minor, patch, Version.Parser.parse_pre(pre) }
end
def to_matchable(string) do
to_matchable(parse(string))
end
@doc """
Convert a matchable to a `Version.Schema`.
## Examples
iex> Version.from_matchable({2, 0, 1, ["alpha", 1]})
#Version.Schema<2.0.1-alpha.1>
"""
@spec from_matchable(Version.matchable) :: Schema.t
def from_matchable({ source, nil, nil, nil }) when is_binary(source) do
Version.Schema[source: source]
end
def from_matchable({ major, minor, patch, pre }) do
source = "#{major}"
if minor do
source = "#{source}.#{minor}"
if patch do
source = "#{source}.#{patch}"
case pre do
[] ->
pre = nil
list ->
pre = Enum.join(list, ".")
source = "#{source}-#{pre}"
end
end
end
Version.Schema[major: major, minor: minor, patch: patch, pre: pre, source: source]
end
@spec compare(String.t | Schema.t, String.t | Schema.t) :: :gt | :eq | :lt
@spec compare(version, version) :: :gt | :eq | :lt
def compare(vsn1, vsn2) do
{ major1, minor1, patch1, pre1 } = to_matchable(vsn1)
{ major2, minor2, patch2, pre2 } = to_matchable(vsn2)
do_compare(to_matchable(vsn1), to_matchable(vsn2))
end
defp do_compare({major1, minor1, patch1, pre1}, {major2, minor2, patch2, pre2}) do
cond do
{ major1, minor1, patch1 } > { major2, minor2, patch2 } -> :gt
{ major1, minor1, patch1 } < { major2, minor2, patch2 } -> :lt
{major1, minor1, patch1} > {major2, minor2, patch2} -> :gt
{major1, minor1, patch1} < {major2, minor2, patch2} -> :lt
pre1 == [] and pre2 != [] -> :gt
pre1 != [] and pre2 == [] -> :lt
pre1 > pre2 -> :gt
@@ -251,6 +147,73 @@ defmodule Version do
end
end
@doc """
Parse a version string into a `Version`.
## Examples
iex> Version.parse("2.0.1-alpha1") |> elem(1)
#Version<2.0.1-alpha1>
iex> Version.parse("2.0-alpha1")
:error
"""
@spec parse(String.t) :: {:ok, t} | :error
def parse(string) when is_binary(string) do
case Version.Parser.parse_version(string) do
{:ok, {major, minor, patch, pre}} ->
vsn = %Version{major: major, minor: minor, patch: patch,
pre: pre, build: get_build(string)}
{:ok, vsn}
:error ->
:error
end
end
@doc """
Parse a version requirement string into a `Version.Requirement`.
## Examples
iex> Version.parse_requirement("== 2.0.1") |> elem(1)
#Version.Requirement<== 2.0.1>
iex> Version.parse_requirement("== == 2.0.1")
:error
"""
@spec parse_requirement(String.t) :: {:ok, Requirement.t} | :error
def parse_requirement(string) when is_binary(string) do
case Version.Parser.parse_requirement(string) do
{:ok, spec} ->
{:ok, %Requirement{source: string, matchspec: spec}}
:error ->
:error
end
end
defp to_matchable(%Version{major: major, minor: minor, patch: patch, pre: pre}) do
{major, minor, patch, pre}
end
defp to_matchable(string) do
case Version.Parser.parse_version(string) do
{:ok, vsn} -> vsn
:error -> raise InvalidVersion, message: string
end
end
defp get_build(string) do
case Regex.run(~r/\+([^\s]+)$/, string) do
nil ->
nil
[_, build] ->
build
end
end
defmodule Parser.DSL do
@moduledoc false
@@ -318,59 +281,67 @@ defmodule Version do
Enum.filter(Enum.reverse(acc), &(&1 != :' '))
end
@version_regex %r/^(\d+)(?:\.(\d+)(?:\.(\d+))?)?(?:\-([^\s]+))?(?:\+[^\d]+)?$/
@version_regex ~r/^
(\d+) # major
(?:\.(\d+))? # minor
(?:\.(\d+))? # patch
(?:\-([\d\w\.\-]+))? # pre
(?:\+([\d\w\-]+))? # build
$/x
@spec parse_requirement(String.t) :: { :ok, Version.Requirement.t } | { :error, binary | atom }
@spec parse_requirement(String.t) :: {:ok, Version.Requirement.t} | :error
def parse_requirement(source) do
lexed = lexer(source, [])
if valid_requirement?(lexed) do
spec = to_matchspec(lexed)
case :ets.test_ms({}, spec) do
{ :ok, _ } ->
{ :ok, Requirement[source: source, matchspec: spec] }
{ :error, errors } ->
{ :error, Enum.map(errors, fn { :error, reason } ->
to_string(reason)
end) }
end
else
{ :error, :invalid_requirement }
end
to_matchspec(lexed)
end
defp nillify(""), do: nil
defp nillify(o), do: o
@spec parse_version(String.t) :: { :ok, Version.matchable } | { :error, :invalid_version }
def parse_version(string) when is_binary(string) do
if valid_version?(string) do
destructure [_, major, minor, patch, pre], Regex.run(@version_regex, string)
@spec parse_version(String.t) :: {:ok, Version.matchable} | :error
def parse_version(string, approximate? \\ false) when is_binary(string) do
if parsed = Regex.run(@version_regex, string) do
destructure [_, major, minor, patch, pre], parsed
patch = nillify(patch)
pre = nillify(pre)
major = binary_to_integer(major)
minor = binary_to_integer(minor |> nillify || "0")
patch = binary_to_integer(patch |> nillify || "0")
pre = pre && parse_pre(pre) || []
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)
{ :ok, { major, minor, patch, pre } }
case parse_pre(pre) do
{:ok, pre} ->
{:ok, {major, minor, patch, pre}}
:error ->
:error
end
end
else
{ :error, :invalid_version }
:error
end
end
@spec parse_pre(String.t) :: [String.t | integer]
def parse_pre(pre) do
String.split(pre, ".") |> Enum.map fn piece ->
if piece =~ %r/^(0|[1-9][0-9]*)$/ do
binary_to_integer(piece)
else
piece
end
defp parse_pre(nil), do: {:ok, []}
defp parse_pre(pre), do: parse_pre(String.split(pre, "."), [])
defp parse_pre([piece|t], acc) do
cond do
piece =~ ~r/^(0|[1-9][0-9]*)$/ ->
parse_pre(t, [binary_to_integer(piece)|acc])
piece =~ ~r/^[0-9]*$/ ->
:error
true ->
parse_pre(t, [piece|acc])
end
end
defp parse_pre([], acc) do
{:ok, Enum.reverse(acc)}
end
defp valid_requirement?([]), do: false
defp valid_requirement?([a | next]), do: valid_requirement?(a, next)
@@ -379,11 +350,6 @@ defmodule Version do
true
end
# version version
defp valid_requirement?(a, [b | _]) when is_binary(a) and is_binary(b) do
false
end
# or <op> | and <op>
defp valid_requirement?(a, [b | next]) when is_atom(a) and is_atom(b) and a in [:'||', :'&&'] do
valid_requirement?(b, next)
@@ -399,117 +365,97 @@ defmodule Version do
valid_requirement?(b, next)
end
# <op> <version>; also checks operators work on valid versions
# <op> <version>
defp valid_requirement?(a, [b | next]) when is_atom(a) and is_binary(b) do
if valid_version?(b) do
valid_requirement?(b, next)
else
if a in [:'==', :'!='] and Regex.match? %r/^\w/, b do
valid_requirement?(b, next)
else
false
end
end
valid_requirement?(b, next)
end
defp valid_requirement?(_, _) do
false
end
@spec valid_version?(String.t) :: boolean
def valid_version?(string) do
Regex.match? %r/^\d+(\.\d+(\.\d+)?)?(\-[^\s]+)?(?:\+[^\s]+)?$/, string
end
defp approximate_upper(version) do
case version do
{major, _minor, nil, _} ->
{major + 1, 0, 0, [0]}
defp approximate(version) do
Version.from_matchable(case Regex.run(@version_regex, version) do
[_, major] ->
{ binary_to_integer(major) + 1, 0, 0, [0] }
[_, major, _] ->
{ binary_to_integer(major) + 1, 0, 0, [0] }
[_, major, minor, _] ->
{ binary_to_integer(major), binary_to_integer(minor) + 1, 0, [0] }
[_, major, minor, _, _] ->
{ binary_to_integer(major), binary_to_integer(minor) + 1, 0, [0] }
end)
{major, minor, _patch, _} ->
{major, minor + 1, 0, [0]}
end
end
defp to_matchspec(lexed) do
first = to_condition(lexed)
rest = Enum.drop(lexed, 2)
[{{ :'$1', :'$2', :'$3', :'$4' }, [to_condition(first, rest)], [:'$_'] }]
if valid_requirement?(lexed) do
first = to_condition(lexed)
rest = Enum.drop(lexed, 2)
{:ok, [{{:'$1', :'$2', :'$3', :'$4'}, [to_condition(first, rest)], [:'$_']}]}
else
:error
end
catch
:invalid_matchspec -> :error
end
defp to_condition([:'==', version | _]) do
version = Version.to_matchable(version)
{ :'==', :'$_', { :const, version } }
version = parse_condition(version)
{:'==', :'$_', {:const, version}}
end
defp to_condition([:'!=', version | _]) do
version = Version.to_matchable(version)
{ :'/=', :'$_', { :const, version } }
version = parse_condition(version)
{:'/=', :'$_', {:const, version}}
end
defp to_condition([:'~>', version | _]) do
from = Version.parse(version)
to = approximate(version)
from = parse_condition(version, true)
to = approximate_upper(from)
{ :andalso, to_condition([:'>=', to_string(from)]),
to_condition([:'<', to_string(to)]) }
{:andalso, to_condition([:'>=', matchable_to_string(from)]),
to_condition([:'<', matchable_to_string(to)])}
end
defp to_condition([:'>', version | _]) do
{ major, minor, patch, pre } = Version.to_matchable(version)
{major, minor, patch, pre} = parse_condition(version)
{ :andalso, { :not, { :is_binary, :'$1' } },
{ :orelse, { :'>', {{ :'$1', :'$2', :'$3' }},
{ :const, { major, minor, patch } } },
{ :andalso, { :'==', {{ :'$1', :'$2', :'$3' }},
{ :const, { major, minor, patch } } },
{ :orelse, { :andalso, { :'==', { :length, :'$4' }, 0 },
{ :'/=', length(pre), 0 } },
{ :andalso, { :'/=', length(pre), 0 },
{ :orelse, { :'>', { :length, :'$4' }, length(pre) },
{ :andalso, { :'==', { :length, :'$4' }, length(pre) },
{ :'>', :'$4', { :const, pre } } } } } } } } }
{:orelse, {:'>', {{:'$1', :'$2', :'$3'}},
{:const, {major, minor, patch}}},
{:andalso, {:'==', {{:'$1', :'$2', :'$3'}},
{:const, {major, minor, patch}}},
{:orelse, {:andalso, {:'==', {:length, :'$4'}, 0},
{:'/=', length(pre), 0}},
{:andalso, {:'/=', length(pre), 0},
{:orelse, {:'>', {:length, :'$4'}, length(pre)},
{:andalso, {:'==', {:length, :'$4'}, length(pre)},
{:'>', :'$4', {:const, pre}}}}}}}}
end
defp to_condition([:'>=', version | _]) do
matchable = Version.to_matchable(version)
matchable = parse_condition(version)
{ :orelse, { :andalso, { :not, { :is_binary, :'$1' } },
{ :'==', :'$_', { :const, matchable } } },
to_condition([:'>', version]) }
{:orelse, {:'==', :'$_', {:const, matchable}},
to_condition([:'>', version])}
end
defp to_condition([:'<', version | _]) do
{ major, minor, patch, pre } = Version.to_matchable(version)
{major, minor, patch, pre} = parse_condition(version)
{ :andalso, { :not, { :is_binary, :'$1' } },
{ :orelse, { :'<', {{ :'$1', :'$2', :'$3' }},
{ :const, { major, minor, patch } } },
{ :andalso, { :'==', {{ :'$1', :'$2', :'$3' }},
{ :const, { major, minor, patch } } },
{ :orelse, { :andalso, { :'/=', { :length, :'$4' }, 0 },
{ :'==', length(pre), 0 } },
{ :andalso, { :'/=', { :length, :'$4' }, 0 },
{ :orelse, { :'<', { :length, :'$4' }, length(pre) },
{ :andalso, { :'==', { :length, :'$4' }, length(pre) },
{ :'<', :'$4', { :const, pre } } } } } } } } }
{:orelse, {:'<', {{:'$1', :'$2', :'$3'}},
{:const, {major, minor, patch}}},
{:andalso, {:'==', {{:'$1', :'$2', :'$3'}},
{:const, {major, minor, patch}}},
{:orelse, {:andalso, {:'/=', {:length, :'$4'}, 0},
{:'==', length(pre), 0}},
{:andalso, {:'/=', {:length, :'$4'}, 0},
{:orelse, {:'<', {:length, :'$4'}, length(pre)},
{:andalso, {:'==', {:length, :'$4'}, length(pre)},
{:'<', :'$4', {:const, pre}}}}}}}}
end
defp to_condition([:'<=', version | _]) do
matchable = Version.to_matchable(version)
matchable = parse_condition(version)
{ :orelse, { :andalso, { :not, { :is_binary, :'$1' } },
{ :'==', :'$_', { :const, matchable } } },
to_condition([:'<', version]) }
{:orelse, {:'==', :'$_', {:const, matchable}},
to_condition([:'<', version])}
end
defp to_condition(current, []) do
@@ -517,35 +463,50 @@ defmodule Version do
end
defp to_condition(current, [:'&&', operator, version | rest]) do
to_condition({ :andalso, current, to_condition([operator, version]) }, rest)
to_condition({:andalso, current, to_condition([operator, version])}, rest)
end
defp to_condition(current, [:'||', operator, version | rest]) do
to_condition({ :orelse, current, to_condition([operator, version]) }, rest)
to_condition({:orelse, current, to_condition([operator, version])}, rest)
end
defp parse_condition(version, approximate? \\ false) do
case parse_version(version, approximate?) do
{:ok, version} -> version
:error -> throw :invalid_matchspec
end
end
defp matchable_to_string({major, minor, patch, pre}) do
patch = if patch, do: "#{patch}", else: "0"
pre = if pre != [], do: "-#{Enum.join(pre, ".")}"
"#{major}.#{minor}.#{patch}#{pre}"
end
end
end
defimpl String.Chars, for: Version.Schema do
def to_string(Version.Schema[source: source]) do
source
defimpl String.Chars, for: Version do
def to_string(version) do
pre = unless Enum.empty?(pre = version.pre), do: "-#{pre}"
build = if build = version.build, do: "+#{build}"
"#{version.major}.#{version.minor}.#{version.patch}#{pre}#{build}"
end
end
defimpl Inspect, for: Version.Schema do
defimpl Inspect, for: Version do
def inspect(self, _opts) do
"#Version.Schema<" <> to_string(self) <> ">"
"#Version<" <> to_string(self) <> ">"
end
end
defimpl String.Chars, for: Version.Requirement do
def to_string({ _, source, _ }) do
def to_string(%Version.Requirement{source: source}) do
source
end
end
defimpl Inspect, for: Version.Requirement do
def inspect({ _, source, _ }, _opts) do
def inspect(%Version.Requirement{source: source}, _opts) do
"#Version.Requirement<" <> source <> ">"
end
end
+6 -6
View File
@@ -2,11 +2,11 @@ defmodule Elixir.Mixfile do
use Mix.Project
def project do
[ app: :elixir,
version: System.version,
build_per_environment: false,
escript_embed_elixir: false,
escript_main_module: :elixir,
escript_emu_args: "%%! -noshell\n" ]
[app: :elixir,
version: System.version,
build_per_environment: false,
escript_embed_elixir: false,
escript_main_module: :elixir,
escript_emu_args: "%%! -noshell\n"]
end
end
-4
View File
@@ -1,5 +1,3 @@
{erl_first_files, ["elixir_transform"]}.
{erl_opts, [
warn_unused_vars,
warn_export_all,
@@ -23,5 +21,3 @@
{report, true},
{verbose, false}
]}.
{require_otp_vsn,"(R16B|R17A).*"}.
+93 -52
View File
@@ -9,8 +9,9 @@
-include("elixir.hrl").
%% Top level types
-export_type([char_list/0, as_boolean/1]).
-export_type([char_list/0, char_data/0, as_boolean/1]).
-type char_list() :: string().
-type char_data() :: unicode:chardata().
-type as_boolean(T) :: T.
%% OTP Application API
@@ -21,8 +22,17 @@ start(_Type, _Args) ->
%% Set the shell to unicode so printing inside scripts work
%% Those can take a while, so let's do it in a new process
spawn(fun() ->
io:setopts(standard_io, [binary,{encoding,unicode}]),
io:setopts(standard_error, [binary,{encoding,unicode}])
io:setopts(standard_io, [binary,{encoding,utf8}]),
io:setopts(standard_error, [{unicode,true}]),
case file:native_name_encoding() of
latin1 ->
io:format(standard_error,
"warning: the VM is running with native name encoding of latin1 which may cause "
"Elixir to malfunction as it expects utf8. Please ensure your locale is set to UTF-8 "
"(which can be verified by running \"locale\" in your shell)~n", []);
_ ->
ok
end
end),
elixir_sup:start_link().
@@ -47,59 +57,59 @@ start_cli() ->
%% EVAL HOOKS
env_for_eval(Opts) ->
env_for_eval(#elixir_env{
local = nil,
requires = elixir_dispatch:default_requires(),
functions = elixir_dispatch:default_functions(),
macros = elixir_dispatch:default_macros()
}, Opts).
env_for_eval((elixir_env:new())#{
local := nil,
requires := elixir_dispatch:default_requires(),
functions := elixir_dispatch:default_functions(),
macros := elixir_dispatch:default_macros()
}, Opts).
env_for_eval(Env, Opts) ->
Line = case lists:keyfind(line, 1, Opts) of
{ line, RawLine } when is_integer(RawLine) -> RawLine;
false -> Env#elixir_env.line
{line, RawLine} when is_integer(RawLine) -> RawLine;
false -> ?m(Env, line)
end,
File = case lists:keyfind(file, 1, Opts) of
{ file, RawFile } when is_binary(RawFile) -> RawFile;
false -> Env#elixir_env.file
{file, RawFile} when is_binary(RawFile) -> RawFile;
false -> ?m(Env, file)
end,
Local = case lists:keyfind(delegate_locals_to, 1, Opts) of
{ delegate_locals_to, LocalOpt } -> LocalOpt;
false -> Env#elixir_env.local
{delegate_locals_to, LocalOpt} -> LocalOpt;
false -> ?m(Env, local)
end,
Aliases = case lists:keyfind(aliases, 1, Opts) of
{ aliases, AliasesOpt } -> AliasesOpt;
false -> Env#elixir_env.aliases
{aliases, AliasesOpt} -> AliasesOpt;
false -> ?m(Env, aliases)
end,
Requires = case lists:keyfind(requires, 1, Opts) of
{ requires, List } -> ordsets:from_list(List);
false -> Env#elixir_env.requires
{requires, List} -> ordsets:from_list(List);
false -> ?m(Env, requires)
end,
Functions = case lists:keyfind(functions, 1, Opts) of
{ functions, FunctionsOpt } -> FunctionsOpt;
false -> Env#elixir_env.functions
{functions, FunctionsOpt} -> FunctionsOpt;
false -> ?m(Env, functions)
end,
Macros = case lists:keyfind(macros, 1, Opts) of
{ macros, MacrosOpt } -> MacrosOpt;
false -> Env#elixir_env.macros
{macros, MacrosOpt} -> MacrosOpt;
false -> ?m(Env, macros)
end,
Module = case lists:keyfind(module, 1, Opts) of
{ module, ModuleOpt } when is_atom(ModuleOpt) -> ModuleOpt;
{module, ModuleOpt} when is_atom(ModuleOpt) -> ModuleOpt;
false -> nil
end,
Env#elixir_env{
file=File, local=Local, module=Module,
macros=Macros, functions=Functions,
requires=Requires, aliases=Aliases, line=Line
}.
Env#{
file := File, local := Local, module := Module,
macros := Macros, functions := Functions,
requires := Requires, aliases := Aliases, line := Line
}.
%% String evaluation
@@ -108,7 +118,7 @@ eval(String, Binding) ->
eval(String, Binding, Opts) when is_list(Opts) ->
eval(String, Binding, env_for_eval(Opts));
eval(String, Binding, #elixir_env{line=Line,file=File} = E) when
eval(String, Binding, #{line := Line, file := File} = E) when
is_list(String), is_list(Binding), is_integer(Line), is_binary(File) ->
Forms = 'string_to_quoted!'(String, Line, File, []),
eval_forms(Forms, Binding, E).
@@ -117,58 +127,89 @@ eval(String, Binding, #elixir_env{line=Line,file=File} = E) when
eval_quoted(Tree, Binding, Opts) when is_list(Opts) ->
eval_quoted(Tree, Binding, env_for_eval(Opts));
eval_quoted(Tree, Binding, #elixir_env{line=Line} = E) ->
eval_quoted(Tree, Binding, #{line := Line} = E) ->
eval_forms(elixir_quote:linify(Line, Tree), Binding, E).
%% Handle forms evaluation. The main difference to
%% to eval_quoted is that it does not linefy the given
%% args.
eval_forms(Tree, Binding, #elixir_env{} = E) ->
eval_forms(Tree, Binding, E, elixir_env:env_to_scope(E));
eval_forms(Tree, Binding, Opts) when is_list(Opts) ->
eval_forms(Tree, Binding, env_for_eval(Opts)).
eval_forms(Tree, Binding, env_for_eval(Opts));
eval_forms(Tree, Binding, E) ->
eval_forms(Tree, Binding, E, elixir_env:env_to_scope(E)).
eval_forms(Tree, Binding, Env, Scope) ->
{ ParsedBinding, ParsedScope } = elixir_scope:load_binding(Binding, Scope),
ParsedEnv = Env#elixir_env{vars=[K || {K,_} <- ParsedScope#elixir_scope.vars]},
{ Erl, NewEnv, NewScope } = quoted_to_erl(Tree, ParsedEnv, ParsedScope),
{ParsedBinding, ParsedScope} = elixir_scope:load_binding(Binding, Scope),
ParsedEnv = Env#{vars := [K || {K,_} <- ParsedScope#elixir_scope.vars]},
{Erl, NewEnv, NewScope} = quoted_to_erl(Tree, ParsedEnv, ParsedScope),
case Erl of
{ atom, _, Atom } ->
{ Atom, Binding, NewEnv, NewScope };
{atom, _, Atom} ->
{Atom, Binding, NewEnv, NewScope};
_ ->
{ value, Value, NewBinding } = erl_eval:expr(Erl, ParsedBinding),
{ Value, elixir_scope:dump_binding(NewBinding, NewScope), NewEnv, NewScope }
{value, Value, NewBinding} = erl_eval(Erl, ParsedBinding),
{Value, elixir_scope:dump_binding(NewBinding, NewScope), NewEnv, NewScope}
end.
erl_eval(Erl, ParsedBinding) ->
% Below must be all one line for locations to be the same when the stacktrace
% needs to be extended to the full stacktrace.
try erl_eval:expr(Erl, ParsedBinding) catch Class:Exception -> erlang:raise(Class, Exception, get_stacktrace()) end.
get_stacktrace() ->
Stacktrace = erlang:get_stacktrace(),
% eval_eval and eval_bits can call :erlang.raise/3 without the full
% stacktrace. When this occurs re-add the current stacktrace so that no
% stack information is lost.
try
throw(stack)
catch
throw:stack ->
% Ignore stack item for current function.
[_ | CurrentStack] = erlang:get_stacktrace(),
get_stacktrace(Stacktrace, CurrentStack)
end.
% The stacktrace did not include the current stack, re-add it.
get_stacktrace([], CurrentStack) ->
CurrentStack;
% The stacktrace includes the current stack.
get_stacktrace(CurrentStack, CurrentStack) ->
CurrentStack;
get_stacktrace([StackItem | Stacktrace], CurrentStack) ->
[StackItem | get_stacktrace(Stacktrace, CurrentStack)].
%% Converts a quoted expression to erlang abstract format
quoted_to_erl(Quoted, Env) ->
quoted_to_erl(Quoted, Env, elixir_env:env_to_scope(Env)).
quoted_to_erl(Quoted, Env, Scope) ->
{ Expanded, NewEnv } = elixir_exp:expand(Quoted, Env),
{ Erl, NewScope } = elixir_translator:translate(Expanded, Scope),
{ Erl, NewEnv, NewScope }.
{Expanded, NewEnv} = elixir_exp:expand(Quoted, Env),
{Erl, NewScope} = elixir_translator:translate(Expanded, Scope),
{Erl, NewEnv, NewScope}.
%% Converts a given string (char list) into quote expression
string_to_quoted(String, StartLine, File, Opts) when is_integer(StartLine), is_binary(File) ->
case elixir_tokenizer:tokenize(String, StartLine, [{ file, File }|Opts]) of
{ ok, _Line, Tokens } ->
case elixir_tokenizer:tokenize(String, StartLine, [{file, File}|Opts]) of
{ok, _Line, Tokens} ->
try elixir_parser:parse(Tokens) of
{ ok, Forms } -> { ok, Forms };
{ error, { Line, _, [Error, Token] } } -> { error, { Line, Error, Token } }
{ok, Forms} -> {ok, Forms};
{error, {Line, _, [Error, Token]}} -> {error, {Line, to_binary(Error), to_binary(Token)}}
catch
{ error, { Line, _, [Error, Token] } } -> { error, { Line, Error, Token } }
{error, {Line, _, [Error, Token]}} -> {error, {Line, to_binary(Error), to_binary(Token)}}
end;
{ error, Reason, _Rest, _SoFar } -> { error, Reason }
{error, {Line, Error, Token}, _Rest, _SoFar} -> {error, {Line, to_binary(Error), to_binary(Token)}}
end.
'string_to_quoted!'(String, StartLine, File, Opts) ->
case string_to_quoted(String, StartLine, File, Opts) of
{ ok, Forms } ->
{ok, Forms} ->
Forms;
{ error, { Line, Error, Token } } ->
{error, {Line, Error, Token}} ->
elixir_errors:parse_error(Line, File, Error, Token)
end.
to_binary(List) when is_list(List) -> elixir_utils:characters_to_binary(List);
to_binary(Atom) when is_atom(Atom) -> atom_to_binary(Atom, utf8).
+19 -19
View File
@@ -11,19 +11,19 @@ inspect(Atom) when is_atom(Atom) ->
%% Store an alias in the given scope
store(_Meta, New, New, _TKV, Aliases, MacroAliases, _Lexical) ->
{ Aliases, MacroAliases };
{Aliases, MacroAliases};
store(Meta, New, Old, TKV, Aliases, MacroAliases, Lexical) ->
record_warn(Meta, New, TKV, Lexical),
{ store_alias(New, Old, Aliases),
store_macro_alias(Meta, New, Old, MacroAliases) }.
{store_alias(New, Old, Aliases),
store_macro_alias(Meta, New, Old, MacroAliases)}.
store_alias(New, Old, Aliases) ->
lists:keystore(New, 1, Aliases, { New, Old }).
lists:keystore(New, 1, Aliases, {New, Old}).
store_macro_alias(Meta, New, Old, Aliases) ->
case lists:keymember(context, 1, Meta) andalso
lists:keyfind(counter, 1, Meta) of
{ counter, Counter } when is_integer(Counter) ->
lists:keystore(New, 1, Aliases, { New, { Counter, Old } });
{counter, Counter} when is_integer(Counter) ->
lists:keystore(New, 1, Aliases, {New, {Counter, Old}});
_ ->
Aliases
end.
@@ -31,8 +31,8 @@ store_macro_alias(Meta, New, Old, Aliases) ->
record_warn(Meta, Ref, Opts, Lexical) ->
Warn =
case lists:keyfind(warn, 1, Opts) of
{ warn, false } -> false;
{ warn, true } -> true;
{warn, false} -> false;
{warn, true} -> true;
false -> not lists:keymember(context, 1, Meta)
end,
elixir_lexical:record_alias(Ref, ?line(Meta), Warn, Lexical).
@@ -40,23 +40,23 @@ record_warn(Meta, Ref, Opts, Lexical) ->
%% Expand an alias. It returns an atom (meaning that there
%% was an expansion) or a list of atoms.
expand({ '__aliases__', _Meta, ['Elixir'|_] = List }, _Aliases, _MacroAliases, _LexicalTracker) ->
expand({'__aliases__', _Meta, ['Elixir'|_] = List}, _Aliases, _MacroAliases, _LexicalTracker) ->
concat(List);
expand({ '__aliases__', Meta, _ } = Alias, Aliases, MacroAliases, LexicalTracker) ->
expand({'__aliases__', Meta, _} = Alias, Aliases, MacroAliases, LexicalTracker) ->
case lists:keyfind(alias, 1, Meta) of
{ alias, false } ->
{alias, false} ->
expand(Alias, MacroAliases, LexicalTracker);
{ alias, Atom } when is_atom(Atom) ->
{alias, Atom} when is_atom(Atom) ->
Atom;
false ->
expand(Alias, Aliases, LexicalTracker)
end.
expand({ '__aliases__', Meta, [H|T] }, Aliases, LexicalTracker) when is_atom(H) ->
expand({'__aliases__', Meta, [H|T]}, Aliases, LexicalTracker) when is_atom(H) ->
Lookup = list_to_atom("Elixir." ++ atom_to_list(H)),
Counter = case lists:keyfind(counter, 1, Meta) of
{ counter, C } -> C;
{counter, C} -> C;
_ -> nil
end,
case lookup(Lookup, Aliases, Counter) of
@@ -69,7 +69,7 @@ expand({ '__aliases__', Meta, [H|T] }, Aliases, LexicalTracker) when is_atom(H)
end
end;
expand({ '__aliases__', _Meta, List }, _Aliases, _LexicalTracker) ->
expand({'__aliases__', _Meta, List}, _Aliases, _LexicalTracker) ->
List.
%% Ensure a module is loaded before its usage.
@@ -80,11 +80,11 @@ ensure_loaded(Meta, Ref, E) ->
Ref:module_info(compile)
catch
error:undef ->
Kind = case lists:member(Ref, E#elixir_env.context_modules) of
Kind = case lists:member(Ref, ?m(E, context_modules)) of
true -> scheduled_module;
false -> unloaded_module
end,
elixir_errors:form_error(Meta, E#elixir_env.file, ?MODULE, { Kind, Ref })
elixir_errors:form_error(Meta, ?m(E, file), ?MODULE, {Kind, Ref})
end.
%% Receives an atom and returns the last bit as an alias.
@@ -129,8 +129,8 @@ to_partial(Arg) when is_binary(Arg) -> Arg.
lookup(Else, Dict, Counter) ->
case lists:keyfind(Else, 1, Dict) of
{ Else, { Counter, Value } } -> lookup(Value, Dict, Counter);
{ Else, Value } when is_atom(Value) -> lookup(Value, Dict, Counter);
{Else, {Counter, Value}} -> lookup(Value, Dict, Counter);
{Else, Value} when is_atom(Value) -> lookup(Value, Dict, Counter);
_ -> Else
end.
+67 -46
View File
@@ -1,37 +1,36 @@
%% Handle translation of Elixir literals to Erlang AST.
-module(elixir_bitstring).
-export([translate/3, expand/3]).
-export([translate/3, expand/3, has_size/1]).
-include("elixir.hrl").
%% Expansion
expand(Meta, Args, E) ->
case E#elixir_env.context of
case ?m(E, context) of
match ->
{ EArgs, EA } = expand_bitstr(fun elixir_exp:expand/2, Args, [], E),
{ { '<<>>', Meta, EArgs }, EA };
{EArgs, EA} = expand_bitstr(fun elixir_exp:expand/2, Args, [], E),
{{'<<>>', Meta, EArgs}, EA};
_ ->
{ EArgs, { EC, EV } } = expand_bitstr(fun elixir_exp:expand_arg/2, Args, [], { E, E }),
{ { '<<>>', Meta, EArgs }, elixir_env:mergea(EV, EC) }
{EArgs, {EC, EV}} = expand_bitstr(fun elixir_exp:expand_arg/2, Args, [], {E, E}),
{{'<<>>', Meta, EArgs}, elixir_env:mergea(EV, EC)}
end.
expand_bitstr(_Fun, [], Acc, E) ->
{ lists:reverse(Acc), E };
{lists:reverse(Acc), E};
expand_bitstr(Fun, [{'::',Meta,[Left,Right]}|T], Acc, E) ->
{ ELeft, EL } = Fun(Left, E),
{ELeft, EL} = Fun(Left, E),
%% Variables defined outside the binary can be accounted
%% on subparts, however we can't assign new variables.
case E of
{ ER, _ } -> ok; %% expand_arg, no assigns
_ -> ER = E#elixir_env{context=nil} %% expand_each, revert assigns
{ER, _} -> ok; %% expand_arg, no assigns
_ -> ER = E#{context := nil} %% expand_each, revert assigns
end,
ERight = expand_bit_info(Meta, Right, ER),
expand_bitstr(Fun, T, [{'::',Meta,[ELeft,ERight]}|Acc], EL);
expand_bitstr(Fun, [H|T], Acc, E) ->
{ Expr, ES } = Fun(H, E),
{Expr, ES} = Fun(H, E),
expand_bitstr(Fun, T, [Expr|Acc], ES).
%% Expand bit info
@@ -42,28 +41,28 @@ expand_bit_info(Meta, Info, E) when is_list(Info) ->
expand_bit_info(Meta, Info, E) ->
expand_bit_info(Meta, [Info], E).
expand_bit_info(Meta, [{ Expr, ExprMeta, Args }|T], Size, Types, E) when is_atom(Expr) ->
expand_bit_info(Meta, [{Expr, ExprMeta, Args}|T], Size, Types, E) when is_atom(Expr) ->
ListArgs = if is_atom(Args) -> []; is_list(Args) -> Args end,
case expand_bit_type_or_size(Expr, ListArgs) of
type ->
{ EArgs, EE } = elixir_exp:expand_args(ListArgs, E),
expand_bit_info(Meta, T, Size, [{ Expr, [], EArgs }|Types], EE);
{EArgs, EE} = elixir_exp:expand_args(ListArgs, E),
expand_bit_info(Meta, T, Size, [{Expr, [], EArgs}|Types], EE);
size ->
case Size of
default -> ok;
_ -> elixir_errors:compile_error(Meta, E#elixir_env.file, "duplicated size definition in bitstring")
_ -> elixir_errors:compile_error(Meta, ?m(E, file), "duplicated size definition in bitstring")
end,
{ EArgs, EE } = elixir_exp:expand_args(ListArgs, E),
expand_bit_info(Meta, T, { Expr, [], EArgs }, Types, EE);
{EArgs, EE} = elixir_exp:expand_args(ListArgs, E),
expand_bit_info(Meta, T, {Expr, [], EArgs}, Types, EE);
none ->
handle_unknown_bit_info(Meta, { Expr, ExprMeta, ListArgs }, T, Size, Types, E)
handle_unknown_bit_info(Meta, {Expr, ExprMeta, ListArgs}, T, Size, Types, E)
end;
expand_bit_info(Meta, [Int|T], Size, Types, E) when is_integer(Int) ->
expand_bit_info(Meta, [{ size, [], [Int] }|T], Size, Types, E);
expand_bit_info(Meta, [{size, [], [Int]}|T], Size, Types, E);
expand_bit_info(Meta, [Expr|_], _Size, _Types, E) ->
elixir_errors:compile_error(Meta, E#elixir_env.file,
elixir_errors:compile_error(Meta, ?m(E, file),
"unknown bitstring specifier ~ts", ['Elixir.Kernel':inspect(Expr)]);
expand_bit_info(_Meta, [], Size, Types, _) ->
@@ -90,10 +89,10 @@ expand_bit_type_or_size(unit, [_]) -> type;
expand_bit_type_or_size(size, [_]) -> size;
expand_bit_type_or_size(_, _) -> none.
handle_unknown_bit_info(Meta, { _, ExprMeta, _ } = Expr, T, Size, Types, E) ->
case 'Elixir.Macro':expand(Expr, elixir_env:env_to_ex({ ?line(ExprMeta), E })) of
handle_unknown_bit_info(Meta, {_, ExprMeta, _} = Expr, T, Size, Types, E) ->
case 'Elixir.Macro':expand(Expr, elixir_env:linify({?line(ExprMeta), E})) of
Expr ->
elixir_errors:compile_error(ExprMeta, E#elixir_env.file,
elixir_errors:compile_error(ExprMeta, ?m(E, file),
"unknown bitstring specifier ~ts", ['Elixir.Macro':to_string(Expr)]);
Other ->
List = case is_list(Other) of true -> Other; false -> [Other] end,
@@ -102,6 +101,13 @@ handle_unknown_bit_info(Meta, { _, ExprMeta, _ } = Expr, T, Size, Types, E) ->
%% Translation
has_size({bin, _, Elements}) ->
not lists:any(fun({bin_element, _Line, _Expr, Size, Types}) ->
(Types /= default) andalso (Size == default) andalso
lists:any(fun(X) -> lists:member(X, Types) end,
[bits, bytes, bitstring, binary])
end, Elements).
translate(Meta, Args, S) ->
case S#elixir_scope.context of
match ->
@@ -111,14 +117,14 @@ translate(Meta, Args, S) ->
end.
build_bitstr(Fun, Exprs, Meta, S) ->
{ Final, FinalS } = build_bitstr_each(Fun, Exprs, Meta, S, []),
{ { bin, ?line(Meta), lists:reverse(Final) }, FinalS }.
{Final, FinalS} = build_bitstr_each(Fun, Exprs, Meta, S, []),
{{bin, ?line(Meta), lists:reverse(Final)}, FinalS}.
build_bitstr_each(_Fun, [], _Meta, S, Acc) ->
{ Acc, S };
{Acc, S};
build_bitstr_each(Fun, [{'::',_,[H,V]}|T], Meta, S, Acc) ->
{ Size, Types } = extract_bit_info(Meta, V, S#elixir_scope{context=nil}),
{Size, Types} = extract_bit_info(Meta, V, S#elixir_scope{context=nil}),
build_bitstr_each(Fun, T, Meta, S, Acc, H, Size, Types);
build_bitstr_each(Fun, [H|T], Meta, S, Acc) ->
@@ -130,11 +136,11 @@ build_bitstr_each(Fun, T, Meta, S, Acc, H, default, Types) when is_binary(H) ->
true ->
%% See explanation in elixir_utils:elixir_to_erl/1 to know
%% why we can simply convert the binary to a list.
{ bin_element, ?line(Meta), { string, 0, binary_to_list(H) }, default, default };
{bin_element, ?line(Meta), {string, 0, binary_to_list(H)}, default, default};
false ->
case types_require_conversion(Types) of
true ->
{ bin_element, ?line(Meta), { string, 0, elixir_utils:characters_to_list(H) }, default, Types };
{bin_element, ?line(Meta), {string, 0, elixir_utils:characters_to_list(H)}, default, Types};
false ->
elixir_errors:compile_error(Meta, S#elixir_scope.file, "invalid types for literal string in <<>>. "
"Accepted types are: little, big, utf8, utf16, utf32, bits, bytes, binary, bitstring")
@@ -151,16 +157,16 @@ build_bitstr_each(_Fun, _T, Meta, S, _Acc, H, _Size, _Types) when is_list(H); is
['Elixir.Macro':to_string(H)]);
build_bitstr_each(Fun, T, Meta, S, Acc, H, Size, Types) ->
{ Expr, NS } = Fun(H, S),
{Expr, NS} = Fun(H, S),
case Expr of
{ bin, _, Elements } ->
{bin, _, Elements} ->
case (Size == default) andalso types_allow_splice(Types, Elements) of
true -> build_bitstr_each(Fun, T, Meta, NS, lists:reverse(Elements) ++ Acc);
false -> build_bitstr_each(Fun, T, Meta, NS, [{ bin_element, ?line(Meta), Expr, Size, Types }|Acc])
false -> build_bitstr_each(Fun, T, Meta, NS, [{bin_element, ?line(Meta), Expr, Size, Types}|Acc])
end;
_ ->
build_bitstr_each(Fun, T, Meta, NS, [{ bin_element, ?line(Meta), Expr, Size, Types }|Acc])
build_bitstr_each(Fun, T, Meta, NS, [{bin_element, ?line(Meta), Expr, Size, Types}|Acc])
end.
types_require_conversion([End|T]) when End == little; End == big -> types_require_conversion(T);
@@ -168,35 +174,50 @@ types_require_conversion([UTF|T]) when UTF == utf8; UTF == utf16; UTF == utf32 -
types_require_conversion([]) -> true;
types_require_conversion(_) -> false.
types_allow_splice([bytes], Elements) -> lists:all(fun has_default_size/1, Elements);
types_allow_splice([binary], Elements) -> lists:all(fun has_default_size/1, Elements);
types_allow_splice([bytes], Elements) -> is_byte_size(Elements, 0);
types_allow_splice([binary], Elements) -> is_byte_size(Elements, 0);
types_allow_splice([bits], _) -> true;
types_allow_splice([bitstring], _) -> true;
types_allow_splice(default, _) -> true;
types_allow_splice(_, _) -> false.
has_default_size({ bin_element, _, _, default, _ }) -> true;
has_default_size({ bin_element, _, _, _, _ }) -> false.
is_byte_size([Element|T], Acc) ->
case elem_size(Element) of
{unknown, Unit} when Unit rem 8 == 0 -> is_byte_size(T, Acc);
{unknown, _Unit} -> false;
{Size, Unit} -> is_byte_size(T, Size*Unit + Acc)
end;
is_byte_size([], Size) ->
Size rem 8 == 0.
elem_size({bin_element, _, _, default, _}) -> {0, 0};
elem_size({bin_element, _, _, {integer,_,Size}, Types}) -> {Size, unit_size(Types, 1)};
elem_size({bin_element, _, _, _Size, Types}) -> {unknown, unit_size(Types, 1)}.
unit_size([binary|T], _) -> unit_size(T, 8);
unit_size([{unit, Size}|_], _) -> Size;
unit_size([_|T], Guess) -> unit_size(T, Guess);
unit_size([], Guess) -> Guess.
%% Extra bitstring specifiers
extract_bit_info(Meta, [{ size, _, [Arg] }|T], S) ->
extract_bit_info(Meta, [{size, _, [Arg]}|T], S) ->
case elixir_translator:translate(Arg, S) of
{ { Kind, _, _ } = Size, _ } when Kind == integer; Kind == var ->
{ Size, extract_bit_type(Meta, T, S) };
{{Kind, _, _} = Size, _} when Kind == integer; Kind == var ->
{Size, extract_bit_type(Meta, T, S)};
_ ->
elixir_errors:compile_error(Meta, S#elixir_scope.file,
"size in bitstring expects an integer or a variable as argument, got: ~ts", ['Elixir.Macro':to_string(Arg)])
end;
extract_bit_info(Meta, T, S) ->
{ default, extract_bit_type(Meta, T, S) }.
{default, extract_bit_type(Meta, T, S)}.
extract_bit_type(Meta, [{ unit, _, [Arg] }|T], S) when is_integer(Arg) ->
[{ unit, Arg }|extract_bit_type(Meta, T, S)];
extract_bit_type(Meta, [{ unit, _, [Arg] }|_], S) ->
extract_bit_type(Meta, [{unit, _, [Arg]}|T], S) when is_integer(Arg) ->
[{unit, Arg}|extract_bit_type(Meta, T, S)];
extract_bit_type(Meta, [{unit, _, [Arg]}|_], S) ->
elixir_errors:compile_error(Meta, S#elixir_scope.file,
"unit in bitstring expects an integer as argument, got: ~ts", ['Elixir.Macro':to_string(Arg)]);
extract_bit_type(Meta, [{ Other, _, [] }|T], S) ->
extract_bit_type(Meta, [{Other, _, []}|T], S) ->
[Other|extract_bit_type(Meta, T, S)];
extract_bit_type(_Meta, [], _S) ->
[].
+8 -8
View File
@@ -19,14 +19,14 @@
'MACRO-defmacrop'(Caller, Call, Expr) -> define(Caller, defmacrop, Call, Expr).
'MACRO-defmodule'(_Caller, Alias, [{do,Block}]) ->
{ Escaped, _ } = elixir_quote:escape(Block, false),
{Escaped, _} = elixir_quote:escape(Block, false),
Args = [Alias, Escaped, [], env()],
{ { '.', [], [elixir_module, compile] }, [], Args }.
{{'.', [], [elixir_module, compile]}, [], Args}.
'__info__'(functions) ->
[];
'__info__'(macros) ->
[{ '@', 1 },
[{'@', 1},
{def,1},
{def,2},
{defmacro,1},
@@ -36,16 +36,16 @@
{defp,2}].
define({Line,E}, Kind, Call, Expr) ->
{ EscapedCall, UC } = elixir_quote:escape(Call, true),
{ EscapedExpr, UE } = elixir_quote:escape(Expr, true),
{EscapedCall, UC} = elixir_quote:escape(Call, true),
{EscapedExpr, UE} = elixir_quote:escape(Expr, true),
Args = [Line, Kind, not(UC or UE), EscapedCall, EscapedExpr, elixir_locals:cache_env(E)],
{ { '.', [], [elixir_def, store_definition] }, [], Args }.
{{'.', [], [elixir_def, store_definition]}, [], Args}.
unless_loaded(Fun, Args, Callback) ->
case code:is_loaded(?kernel) of
{ _, _} -> apply(?kernel, Fun, Args);
{_, _} -> apply(?kernel, Fun, Args);
false -> Callback()
end.
env() ->
{ '__ENV__', [], nil }.
{'__ENV__', [], nil}.
+65 -69
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/6, clauses/3, get_pairs/2, get_pairs/3,
-export([match/3, clause/7, clauses/4, get_pairs/2, get_pairs/3,
extract_splat_guards/1, extract_guards/1]).
-include("elixir.hrl").
@@ -11,9 +11,9 @@ get_pairs(Key, Clauses) ->
get_pairs(Key, Clauses, false).
get_pairs(Key, Clauses, AllowNil) ->
case lists:keyfind(Key, 1, Clauses) of
{ Key, Pairs } when is_list(Pairs) ->
[{ Key, Meta, Left, Right } || { '->', Meta, [Left, Right] } <- Pairs];
{ Key, nil } when AllowNil ->
{Key, Pairs} when is_list(Pairs) ->
[{Key, Meta, Left, Right} || {'->', Meta, [Left, Right]} <- Pairs];
{Key, nil} when AllowNil ->
[];
false ->
[]
@@ -23,17 +23,17 @@ get_pairs(Key, Clauses, AllowNil) ->
match(Fun, Args, #elixir_scope{context=Context, match_vars=MatchVars,
backup_vars=BackupVars, vars=Vars} = S) when Context /= match ->
{ Result, NewS } = match(Fun, Args, S#elixir_scope{context=match,
{Result, NewS} = match(Fun, Args, S#elixir_scope{context=match,
match_vars=ordsets:new(), backup_vars=Vars}),
{ Result, NewS#elixir_scope{context=Context,
match_vars=MatchVars, backup_vars=BackupVars} };
{Result, NewS#elixir_scope{context=Context,
match_vars=MatchVars, backup_vars=BackupVars}};
match(Fun, Args, S) -> Fun(Args, S).
%% Translate clauses with args, guards and expressions
clause(Line, Fun, Args, Expr, Guards, S) when is_integer(Line) ->
{ TArgs, SA } = match(Fun, Args, S#elixir_scope{extra_guards=[]}),
{ TExpr, SE } = elixir_translator:translate(Expr, SA#elixir_scope{extra_guards=nil}),
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,
@@ -43,50 +43,46 @@ clause(Line, Fun, Args, Expr, Guards, S) when is_integer(Line) ->
_ -> [translate_guard(Line, Guard, Extra, SG) || Guard <- Guards]
end,
{ { clause, Line, TArgs, TGuards, unblock(TExpr) }, SE }.
{{clause, Line, TArgs, TGuards, unblock(TExpr)}, SE}.
% Translate/Extract guards from the given expression.
translate_guard(Line, Guard, Extra, S) ->
[element(1, elixir_translator:translate(elixir_quote:linify(Line, Guard), S))|Extra].
extract_guards({ 'when', _, [Left, Right] }) -> { Left, extract_or_guards(Right) };
extract_guards(Else) -> { Else, [] }.
extract_guards({'when', _, [Left, Right]}) -> {Left, extract_or_guards(Right)};
extract_guards(Else) -> {Else, []}.
extract_or_guards({ 'when', _, [Left, Right] }) -> [Left|extract_or_guards(Right)];
extract_or_guards({'when', _, [Left, Right]}) -> [Left|extract_or_guards(Right)];
extract_or_guards(Term) -> [Term].
% Extract guards when multiple left side args are allowed.
extract_splat_guards([{ 'when', _, [_,_|_] = Args }]) ->
{ Left, Right } = elixir_utils:split_last(Args),
{ Left, extract_or_guards(Right) };
extract_splat_guards([{'when', _, [_,_|_] = Args}]) ->
{Left, Right} = elixir_utils:split_last(Args),
{Left, extract_or_guards(Right)};
extract_splat_guards(Else) ->
{ Else, [] }.
{Else, []}.
% Function for translating macros with match style like case and receive.
clauses(Meta, Clauses, #elixir_scope{export_vars=CV} = S) ->
{ TC, TS } = do_clauses(Meta, Clauses, S#elixir_scope{export_vars=[]}),
{ TC, TS#elixir_scope{export_vars=elixir_scope:merge_opt_vars(CV, TS#elixir_scope.export_vars)} }.
clauses(Meta, Clauses, Return, #elixir_scope{export_vars=CV} = S) ->
{TC, TS} = do_clauses(Meta, Clauses, Return, S#elixir_scope{export_vars=[]}),
{TC, TS#elixir_scope{export_vars=elixir_scope:merge_opt_vars(CV, TS#elixir_scope.export_vars)}}.
do_clauses(_Meta, [], S) ->
{ [], S };
do_clauses(_Meta, [], _Return, S) ->
{[], S};
% do_clauses(_Meta, [DecoupledClause], S) ->
% { TDecoupledClause, TS } = each_clause(DecoupledClause, S),
% { [TDecoupledClause], TS };
do_clauses(Meta, DecoupledClauses, S) ->
do_clauses(Meta, DecoupledClauses, Return, S) ->
% Transform tree just passing the variables counter forward
% and storing variables defined inside each clause.
Transformer = fun(X, {SAcc, VAcc}) ->
{ TX, TS } = each_clause(X, SAcc),
{ TX,
{ elixir_scope:mergec(S, TS), [TS#elixir_scope.export_vars|VAcc] } }
{TX, TS} = each_clause(X, Return, SAcc),
{TX,
{elixir_scope:mergec(S, TS), [TS#elixir_scope.export_vars|VAcc]}}
end,
{ TClauses, { TS, ReverseCV } } =
{TClauses, {TS, ReverseCV}} =
lists:mapfoldl(Transformer, {S, []}, DecoupledClauses),
% Now get all the variables defined inside each clause
@@ -98,7 +94,7 @@ do_clauses(Meta, DecoupledClauses, S) ->
% a list of tuples where the first element is the variable name,
% the second one is the new pointer to the variable and the third
% is the old pointer.
{ FinalVars, FS } = lists:mapfoldl(fun({ Key, Val }, Acc) ->
{FinalVars, FS} = lists:mapfoldl(fun({Key, Val}, Acc) ->
normalize_vars(Key, Val, Acc)
end, TS, AllVars),
@@ -108,9 +104,9 @@ do_clauses(Meta, DecoupledClauses, S) ->
expand_clauses(Line, [Clause|T], [ClauseVars|V], FinalVars, Acc, S) ->
case generate_match_vars(FinalVars, ClauseVars, [], []) of
{ [], [] } ->
{[], []} ->
expand_clauses(Line, T, V, FinalVars, [Clause|Acc], S);
{ Left, Right } ->
{Left, Right} ->
MatchExpr = generate_match(Line, Left, Right),
ClauseExprs = element(5, Clause),
[Final|RawClauseExprs] = lists:reverse(ClauseExprs),
@@ -118,19 +114,19 @@ expand_clauses(Line, [Clause|T], [ClauseVars|V], FinalVars, Acc, S) ->
% If the last sentence has a match clause, we need to assign its value
% in the variable list. If not, we insert the variable list before the
% final clause in order to keep it tail call optimized.
{ FinalClauseExprs, FS } = case has_match_tuple(Final) of
{FinalClauseExprs, FS} = case has_match_tuple(Final) of
true ->
case Final of
{ match, _, { var, _, UserVarName } = UserVar, _ } when UserVarName /= '_' ->
{ [UserVar,MatchExpr,Final|RawClauseExprs], S };
{match, _, {var, _, UserVarName} = UserVar, _} when UserVarName /= '_' ->
{[UserVar,MatchExpr,Final|RawClauseExprs], S};
_ ->
{ VarName, _, SS } = elixir_scope:build_var('_', S),
StorageVar = { var, Line, VarName },
StorageExpr = { match, Line, StorageVar, Final },
{ [StorageVar,MatchExpr,StorageExpr|RawClauseExprs], SS }
{VarName, _, SS} = elixir_scope:build_var('_', S),
StorageVar = {var, Line, VarName},
StorageExpr = {match, Line, StorageVar, Final},
{[StorageVar,MatchExpr,StorageExpr|RawClauseExprs], SS}
end;
false ->
{ [Final,MatchExpr|RawClauseExprs], S }
{[Final,MatchExpr|RawClauseExprs], S}
end,
FinalClause = setelement(5, Clause, lists:reverse(FinalClauseExprs)),
@@ -138,22 +134,22 @@ expand_clauses(Line, [Clause|T], [ClauseVars|V], FinalVars, Acc, S) ->
end;
expand_clauses(_Line, [], [], _FinalVars, Acc, S) ->
{ lists:reverse(Acc), S }.
{lists:reverse(Acc), S}.
% Handle each key/value clause pair and translate them accordingly.
each_clause({ do, Meta, [Condition], Expr }, S) ->
{ Arg, Guards } = extract_guards(Condition),
clause(?line(Meta), fun elixir_translator:translate_many/2, [Arg], Expr, Guards, S);
each_clause({do, Meta, [Condition], Expr}, Return, S) ->
{Arg, Guards} = extract_guards(Condition),
clause(?line(Meta), fun elixir_translator:translate_args/2, [Arg], Expr, Guards, Return, S);
each_clause({ else, Meta, [Condition], Expr }, S) ->
{ Arg, Guards } = extract_guards(Condition),
clause(?line(Meta), fun elixir_translator:translate_many/2, [Arg], Expr, Guards, S);
each_clause({else, Meta, [Condition], Expr}, Return, S) ->
{Arg, Guards} = extract_guards(Condition),
clause(?line(Meta), fun elixir_translator:translate_args/2, [Arg], Expr, Guards, Return, S);
each_clause({ 'after', Meta, [Condition], Expr }, S) ->
{ TCondition, SC } = elixir_translator:translate(Condition, S),
{ TExpr, SB } = elixir_translator:translate(Expr, SC),
{ { clause, ?line(Meta), [TCondition], [], unblock(TExpr) }, SB }.
each_clause({'after', Meta, [Condition], Expr}, Return, S) ->
{TCondition, SC} = elixir_translator:translate(Condition, S),
{TExpr, SB} = elixir_translator:translate_block(Expr, Return, SC),
{{clause, ?line(Meta), [TCondition], [], unblock(TExpr)}, SB}.
% Check if the given expression is a match tuple.
% This is a small optimization to allow us to change
@@ -167,7 +163,7 @@ has_match_tuple({'case', _, _, _}) ->
true;
has_match_tuple({match, _, _, _}) ->
true;
has_match_tuple({'fun', _, { clauses, _ }}) ->
has_match_tuple({'fun', _, {clauses, _}}) ->
false;
has_match_tuple(H) when is_tuple(H) ->
has_match_tuple(tuple_to_list(H));
@@ -183,39 +179,39 @@ normalize_vars(Key, Value, #elixir_scope{vars=Vars,export_vars=ClauseVars} = S)
VS = S#elixir_scope{
vars=orddict:store(Key, Value, Vars),
export_vars=orddict:store(Key, Value, ClauseVars)
},
},
Expr = case orddict:find(Key, Vars) of
{ ok, { PreValue, _ } } -> { var, 0, PreValue };
error -> { atom, 0, nil }
{ok, {PreValue, _}} -> {var, 0, PreValue};
error -> {atom, 0, nil}
end,
{ { Key, Value, Expr }, VS }.
{{Key, Value, Expr}, VS}.
% Generate match vars by checking if they were updated
% or not and assigning the previous value.
generate_match_vars([{ Key, Value, Expr }|T], ClauseVars, Left, Right) ->
generate_match_vars([{Key, Value, Expr}|T], ClauseVars, Left, Right) ->
case orddict:find(Key, ClauseVars) of
{ ok, Value } ->
{ok, Value} ->
generate_match_vars(T, ClauseVars, Left, Right);
{ ok, Clause } ->
{ok, Clause} ->
generate_match_vars(T, ClauseVars,
[{ var, 0, element(1, Value) }|Left],
[{ var, 0, element(1, Clause) }|Right]);
[{var, 0, element(1, Value)}|Left],
[{var, 0, element(1, Clause)}|Right]);
error ->
generate_match_vars(T, ClauseVars,
[{ var, 0, element(1, Value) }|Left], [Expr|Right])
[{var, 0, element(1, Value)}|Left], [Expr|Right])
end;
generate_match_vars([], _ClauseVars, Left, Right) ->
{ Left, Right }.
{Left, Right}.
generate_match(Line, [Left], [Right]) ->
{ match, Line, Left, Right };
{match, Line, Left, Right};
generate_match(Line, LeftVars, RightVars) ->
{ match, Line, { tuple, Line, LeftVars }, { tuple, Line, RightVars } }.
{match, Line, {tuple, Line, LeftVars}, {tuple, Line, RightVars}}.
unblock({ 'block', _, Exprs }) -> Exprs;
unblock({'block', _, Exprs}) -> Exprs;
unblock(Exprs) -> [Exprs].
+49 -49
View File
@@ -30,121 +30,121 @@ init(ok) ->
code:ensure_loaded('Elixir.Macro.Env'),
code:ensure_loaded('Elixir.Module.LocalsTracker'),
code:ensure_loaded('Elixir.Kernel.LexicalTracker'),
{ ok, #elixir_code_server{} }.
{ok, #elixir_code_server{}}.
handle_call({ acquire, Path }, From, Config) ->
handle_call({acquire, Path}, From, Config) ->
Current = Config#elixir_code_server.loaded,
case orddict:find(Path, Current) of
{ ok, true } ->
{ reply, loaded, Config };
{ ok, { Ref, List } } when is_list(List), is_reference(Ref) ->
Queued = orddict:store(Path, { Ref, [From|List] }, Current),
{ reply, { queued, Ref }, Config#elixir_code_server{loaded=Queued} };
{ok, true} ->
{reply, loaded, Config};
{ok, {Ref, List}} when is_list(List), is_reference(Ref) ->
Queued = orddict:store(Path, {Ref, [From|List]}, Current),
{reply, {queued, Ref}, Config#elixir_code_server{loaded=Queued}};
error ->
Queued = orddict:store(Path, { make_ref(), [] }, Current),
{ reply, proceed, Config#elixir_code_server{loaded=Queued} }
Queued = orddict:store(Path, {make_ref(), []}, Current),
{reply, proceed, Config#elixir_code_server{loaded=Queued}}
end;
handle_call(loaded, _From, Config) ->
{ reply, [F || { F, true } <- Config#elixir_code_server.loaded], Config };
{reply, [F || {F, true} <- Config#elixir_code_server.loaded], Config};
handle_call(at_exit, _From, Config) ->
{ reply, Config#elixir_code_server.at_exit, Config };
{reply, Config#elixir_code_server.at_exit, Config};
handle_call(flush_at_exit, _From, Config) ->
{ reply, Config#elixir_code_server.at_exit, Config#elixir_code_server{at_exit=[]} };
{reply, Config#elixir_code_server.at_exit, Config#elixir_code_server{at_exit=[]}};
handle_call(argv, _From, Config) ->
{ reply, Config#elixir_code_server.argv, Config };
{reply, Config#elixir_code_server.argv, Config};
handle_call(compiler_options, _From, Config) ->
{ reply, Config#elixir_code_server.compiler_options, Config };
{reply, Config#elixir_code_server.compiler_options, Config};
handle_call({ compilation_status, CompilerPid }, _From, Config) ->
handle_call({compilation_status, CompilerPid}, _From, Config) ->
CompilationStatusList = Config#elixir_code_server.compilation_status,
CompilationStatusListNew = orddict:erase(CompilerPid, CompilationStatusList),
CompilationStatus = orddict:fetch(CompilerPid, CompilationStatusList),
{ reply, CompilationStatus, Config#elixir_code_server{compilation_status=CompilationStatusListNew} };
{reply, CompilationStatus, Config#elixir_code_server{compilation_status=CompilationStatusListNew}};
handle_call(retrieve_module_name, _From, Config) ->
case Config#elixir_code_server.pool of
{ [H|T], Counter } ->
{ reply, module_tuple(H), Config#elixir_code_server{pool={T,Counter}} };
{ [], Counter } ->
{ reply, module_tuple(Counter), Config#elixir_code_server{pool={[],Counter+1}} }
{[H|T], Counter} ->
{reply, module_tuple(H), Config#elixir_code_server{pool={T,Counter}}};
{[], Counter} ->
{reply, module_tuple(Counter), Config#elixir_code_server{pool={[],Counter+1}}}
end;
handle_call(erl_compiler_options, _From, Config) ->
case Config#elixir_code_server.erl_compiler_options of
nil ->
Opts = erl_compiler_options(),
{ reply, Opts, Config#elixir_code_server{erl_compiler_options=Opts} };
{reply, Opts, Config#elixir_code_server{erl_compiler_options=Opts}};
Opts ->
{ reply, Opts, Config }
{reply, Opts, Config}
end;
handle_call(Request, _From, Config) ->
{ stop, { badcall, Request }, Config }.
{stop, {badcall, Request}, Config}.
handle_cast({ at_exit, AtExit }, Config) ->
{ noreply, Config#elixir_code_server{at_exit=[AtExit|Config#elixir_code_server.at_exit]} };
handle_cast({at_exit, AtExit}, Config) ->
{noreply, Config#elixir_code_server{at_exit=[AtExit|Config#elixir_code_server.at_exit]}};
handle_cast({ argv, Argv }, Config) ->
{ noreply, Config#elixir_code_server{argv=Argv} };
handle_cast({argv, Argv}, Config) ->
{noreply, Config#elixir_code_server{argv=Argv}};
handle_cast({ compiler_options, Options }, Config) ->
handle_cast({compiler_options, Options}, Config) ->
Final = orddict:merge(fun(_,_,V) -> V end, Config#elixir_code_server.compiler_options, Options),
{ noreply, Config#elixir_code_server{compiler_options=Final} };
{noreply, Config#elixir_code_server{compiler_options=Final}};
handle_cast({ register_warning, CompilerPid }, Config) ->
handle_cast({register_warning, CompilerPid}, Config) ->
CompilationStatusCurrent = Config#elixir_code_server.compilation_status,
CompilationStatusNew = orddict:store(CompilerPid, error, CompilationStatusCurrent),
case orddict:find(warnings_as_errors, Config#elixir_code_server.compiler_options) of
{ ok, true } -> { noreply, Config#elixir_code_server{compilation_status=CompilationStatusNew} };
_ -> { noreply, Config }
{ok, true} -> {noreply, Config#elixir_code_server{compilation_status=CompilationStatusNew}};
_ -> {noreply, Config}
end;
handle_cast({ reset_warnings, CompilerPid }, Config) ->
handle_cast({reset_warnings, CompilerPid}, Config) ->
CompilationStatusCurrent = Config#elixir_code_server.compilation_status,
CompilationStatusNew = orddict:store(CompilerPid, ok, CompilationStatusCurrent),
{ noreply, Config#elixir_code_server{compilation_status=CompilationStatusNew} };
{noreply, Config#elixir_code_server{compilation_status=CompilationStatusNew}};
handle_cast({ loaded, Path }, Config) ->
handle_cast({loaded, Path}, Config) ->
Current = Config#elixir_code_server.loaded,
case orddict:find(Path, Current) of
{ ok, true } ->
{ noreply, Config };
{ ok, { Ref, List } } when is_list(List), is_reference(Ref) ->
[Pid ! { elixir_code_server, Ref, loaded } || { Pid, _Tag } <- lists:reverse(List)],
{ok, true} ->
{noreply, Config};
{ok, {Ref, List}} when is_list(List), is_reference(Ref) ->
[Pid ! {elixir_code_server, Ref, loaded} || {Pid, _Tag} <- lists:reverse(List)],
Done = orddict:store(Path, true, Current),
{ noreply, Config#elixir_code_server{loaded=Done} };
{noreply, Config#elixir_code_server{loaded=Done}};
error ->
Done = orddict:store(Path, true, Current),
{ noreply, Config#elixir_code_server{loaded=Done} }
{noreply, Config#elixir_code_server{loaded=Done}}
end;
handle_cast({ unload_files, Files }, Config) ->
handle_cast({unload_files, Files}, Config) ->
Current = Config#elixir_code_server.loaded,
Unloaded = lists:foldl(fun(File, Acc) -> orddict:erase(File, Acc) end, Current, Files),
{ noreply, Config#elixir_code_server{loaded=Unloaded} };
{noreply, Config#elixir_code_server{loaded=Unloaded}};
handle_cast({ return_module_name, H }, #elixir_code_server{pool={T,Counter}} = Config) ->
{ noreply, Config#elixir_code_server{pool={[H|T],Counter}} };
handle_cast({return_module_name, H}, #elixir_code_server{pool={T,Counter}} = Config) ->
{noreply, Config#elixir_code_server{pool={[H|T],Counter}}};
handle_cast(Request, Config) ->
{ stop, { badcast, Request }, Config }.
{stop, {badcast, Request}, Config}.
handle_info(_Request, Config) ->
{ noreply, Config }.
{noreply, Config}.
terminate(_Reason, _Config) ->
ok.
code_change(_Old, Config, _Extra) ->
{ ok, Config }.
{ok, Config}.
module_tuple(I) ->
{ list_to_atom("elixir_compiler_" ++ integer_to_list(I)), I }.
{list_to_atom("elixir_compiler_" ++ integer_to_list(I)), I}.
erl_compiler_options() ->
Key = "ERL_COMPILER_OPTIONS",
+40 -60
View File
@@ -1,6 +1,6 @@
-module(elixir_compiler).
-export([get_opt/1, string/2, quoted/2, file/1, file_to_path/2]).
-export([core/0, module/4, eval_forms/3, format_error/1]).
-export([core/0, module/4, eval_forms/3]).
-include("elixir.hrl").
%% Public API
@@ -9,7 +9,7 @@ get_opt(Key) ->
Dict = elixir_code_server:call(compiler_options),
case lists:keyfind(Key, 1, Dict) of
false -> false;
{ Key, Value } -> Value
{Key, Value} -> Value
end.
%% Compilation entry points.
@@ -26,7 +26,7 @@ quoted(Forms, File) when is_binary(File) ->
elixir_lexical:run(File, fun
(Pid) ->
Env = elixir:env_for_eval([{line,1},{file,File}]),
eval_forms(Forms, [], Env#elixir_env{lexical_tracker=Pid})
eval_forms(Forms, [], Env#{lexical_tracker := Pid})
end),
lists:reverse(get(elixir_compiled))
after
@@ -35,7 +35,7 @@ quoted(Forms, File) when is_binary(File) ->
file(Relative) when is_binary(Relative) ->
File = filename:absname(Relative),
{ ok, Bin } = file:read_file(File),
{ok, Bin} = file:read_file(File),
string(elixir_utils:characters_to_list(Bin), File).
file_to_path(File, Path) when is_binary(File), is_binary(Path) ->
@@ -46,35 +46,35 @@ file_to_path(File, Path) when is_binary(File), is_binary(Path) ->
%% Evaluation
eval_forms(Forms, Vars, E) ->
case (E#elixir_env.module == nil) andalso allows_fast_compilation(Forms) of
case (?m(E, module) == nil) andalso allows_fast_compilation(Forms) of
true -> eval_compilation(Forms, Vars, E);
false -> code_loading_compilation(Forms, Vars, E)
end.
eval_compilation(Forms, Vars, E) ->
Binding = [{ Key, Value } || { _Name, _Kind, Key, Value } <- Vars],
{ Result, _Binding, EE, _S } = elixir:eval_forms(Forms, Binding, E),
{ Result, EE }.
Binding = [{Key, Value} || {_Name, _Kind, Key, Value} <- Vars],
{Result, _Binding, EE, _S} = elixir:eval_forms(Forms, Binding, E),
{Result, EE}.
code_loading_compilation(Forms, Vars, #elixir_env{line=Line} = E) ->
Dict = [{ { Name, Kind }, { Value, 0 } } || { Name, Kind, Value, _ } <- Vars],
code_loading_compilation(Forms, Vars, #{line := Line} = E) ->
Dict = [{{Name, Kind}, {Value, 0}} || {Name, Kind, Value, _} <- Vars],
S = elixir_env:env_to_scope_with_vars(E, Dict),
{ Expr, EE, _S } = elixir:quoted_to_erl(Forms, E, S),
{Expr, EE, _S} = elixir:quoted_to_erl(Forms, E, S),
{ Module, I } = retrieve_module_name(),
Fun = code_fun(E#elixir_env.module),
Form = code_mod(Fun, Expr, Line, E#elixir_env.file, Module, Vars),
Args = list_to_tuple([V || { _, _, _, V } <- Vars]),
{Module, I} = retrieve_module_name(),
Fun = code_fun(?m(E, module)),
Form = code_mod(Fun, Expr, Line, ?m(E, file), Module, Vars),
Args = list_to_tuple([V || {_, _, _, V} <- Vars]),
%% Pass { native, false } to speed up bootstrap
%% Pass {native, false} to speed up bootstrap
%% process when native is set to true
module(Form, E#elixir_env.file, [{native,false}], true, fun(_, Binary) ->
module(Form, ?m(E, file), [{native,false}], true, fun(_, Binary) ->
%% If we have labeled locals, anonymous functions
%% were created and therefore we cannot ditch the
%% module
Purgeable =
case beam_lib:chunks(Binary, [labeled_locals]) of
{ ok, { _, [{ labeled_locals, []}] } } -> true;
{ok, {_, [{labeled_locals, []}]}} -> true;
_ -> false
end,
dispatch_loaded(Module, Fun, Args, Purgeable, I, EE)
@@ -89,34 +89,34 @@ dispatch_loaded(Module, Fun, Args, Purgeable, I, E) ->
true ->
ok
end,
{ Res, E }.
{Res, E}.
code_fun(nil) -> '__FILE__';
code_fun(_) -> '__MODULE__'.
code_mod(Fun, Expr, Line, File, Module, Vars) when is_binary(File), is_integer(Line) ->
Tuple = { tuple, Line, [{ var, Line, K } || { _, _, K, _ } <- Vars] },
Tuple = {tuple, Line, [{var, Line, K} || {_, _, K, _} <- Vars]},
Relative = elixir_utils:relative_to_cwd(File),
[
{ attribute, Line, file, { elixir_utils:characters_to_list(Relative), 1 } },
{ attribute, Line, module, Module },
{ attribute, Line, export, [{ Fun, 1 }, { '__RELATIVE__', 0 }] },
{ function, Line, Fun, 1, [
{ clause, Line, [Tuple], [], [Expr] }
] },
{ function, Line, '__RELATIVE__', 0, [
{ clause, Line, [], [], [elixir_utils:elixir_to_erl(Relative)] }
] }
{attribute, Line, file, {elixir_utils:characters_to_list(Relative), 1}},
{attribute, Line, module, Module},
{attribute, Line, export, [{Fun, 1}, {'__RELATIVE__', 0}]},
{function, Line, Fun, 1, [
{clause, Line, [Tuple], [], [Expr]}
]},
{function, Line, '__RELATIVE__', 0, [
{clause, Line, [], [], [elixir_utils:elixir_to_erl(Relative)]}
]}
].
retrieve_module_name() ->
elixir_code_server:call(retrieve_module_name).
return_module_name(I) ->
elixir_code_server:cast({ return_module_name, I }).
elixir_code_server:cast({return_module_name, I}).
allows_fast_compilation({ '__block__', _, Exprs }) ->
allows_fast_compilation({'__block__', _, Exprs}) ->
lists:all(fun allows_fast_compilation/1, Exprs);
allows_fast_compilation({defmodule,_,_}) -> true;
allows_fast_compilation(_) -> false.
@@ -137,15 +137,11 @@ module(Forms, File, Opts, Callback) ->
module(Forms, File, RawOptions, Bootstrap, Callback) when
is_binary(File), is_list(Forms), is_list(RawOptions), is_boolean(Bootstrap), is_function(Callback) ->
{ Options, SkipNative } = compiler_options(Forms, RawOptions),
Options = RawOptions ++ elixir_code_server:call(erl_compiler_options),
Listname = elixir_utils:characters_to_list(File),
case compile:noenv_forms([no_auto_import()|Forms], [return,{source,Listname}|Options]) of
{ok, ModuleName, Binary, RawWarnings} ->
Warnings = case SkipNative of
true -> [{?MODULE,[{0,?MODULE,{skip_native,ModuleName}}]}|RawWarnings];
false -> RawWarnings
end,
{ok, ModuleName, Binary, Warnings} ->
format_warnings(Bootstrap, Warnings),
code:load_binary(ModuleName, Listname, Binary),
Callback(ModuleName, Binary);
@@ -154,28 +150,14 @@ module(Forms, File, RawOptions, Bootstrap, Callback) when
format_errors(Errors)
end.
compiler_options(Forms, Options) ->
EnvOptions = elixir_code_server:call(erl_compiler_options),
SkipNative = lists:member(native, EnvOptions) and contains_on_load(Forms),
case SkipNative or lists:member([{native,false}], Options) of
true -> { Options ++ lists:delete(native, EnvOptions), SkipNative };
false -> { Options ++ EnvOptions, SkipNative }
end.
contains_on_load([{ attribute, _, on_load, _ }|_]) -> true;
contains_on_load([_|T]) -> contains_on_load(T);
contains_on_load([]) -> false.
no_auto_import() ->
Bifs = [{ Name, Arity } || { Name, Arity } <- erlang:module_info(exports), erl_internal:bif(Name, Arity)],
{ attribute, 0, compile, { no_auto_import, Bifs } }.
{attribute, 0, compile, no_auto_import}.
%% CORE HANDLING
core() ->
application:start(elixir),
elixir_code_server:cast({ compiler_options, [{docs,false},{internal,true}] }),
elixir_code_server:cast({compiler_options, [{docs,false},{internal,true}]}),
[core_file(File) || File <- core_main()].
core_file(File) ->
@@ -192,12 +174,14 @@ core_file(File) ->
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/macro/env.ex">>,
<<"lib/elixir/lib/exception.ex">>,
<<"lib/elixir/lib/code.ex">>,
<<"lib/elixir/lib/module/locals_tracker.ex">>,
@@ -210,6 +194,7 @@ core_main() ->
<<"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">>,
@@ -217,7 +202,6 @@ core_main() ->
<<"lib/elixir/lib/kernel/cli.ex">>,
<<"lib/elixir/lib/kernel/error_handler.ex">>,
<<"lib/elixir/lib/kernel/parallel_compiler.ex">>,
<<"lib/elixir/lib/kernel/record_rewriter.ex">>,
<<"lib/elixir/lib/kernel/lexical_tracker.ex">>
].
@@ -228,12 +212,8 @@ binary_to_path({ModuleName, Binary}, CompilePath) ->
%% ERROR HANDLING
format_error({ skip_native, Module }) ->
io_lib:format("skipping native compilation for ~ts because it contains on_load attribute",
[elixir_aliases:inspect(Module)]).
format_errors([]) ->
exit({ nocompile, "compilation failed but no error was raised" });
exit({nocompile, "compilation failed but no error was raised"});
format_errors(Errors) ->
lists:foreach(fun ({File, Each}) ->
+6 -6
View File
@@ -13,24 +13,24 @@ start_link() ->
gen_server:start_link({local, ?MODULE}, ?MODULE, 0, []).
init(Counter) ->
{ ok, Counter }.
{ok, Counter}.
handle_call(next, _From, Counter) ->
{ reply, Counter, bump(Counter) };
{reply, Counter, bump(Counter)};
handle_call(Request, _From, Counter) ->
{ stop, { badcall, Request }, Counter }.
{stop, {badcall, Request}, Counter}.
handle_cast(Request, Counter) ->
{ stop, { badcast, Request }, Counter }.
{stop, {badcast, Request}, Counter}.
handle_info(_Request, Counter) ->
{ noreply, Counter }.
{noreply, Counter}.
terminate(_Reason, _Counter) ->
ok.
code_change(_Old, Counter, _Extra) ->
{ ok, Counter }.
{ok, Counter}.
bump(Counter) when Counter < ?limit ->
Counter + 1;
+102 -93
View File
@@ -18,8 +18,8 @@ clauses_table(Module) ->
ets:lookup_element(Module, ?clauses_attr, 2).
setup(Module) ->
ets:insert(Module, { ?attr, ets:new(Module, [set, public]) }),
ets:insert(Module, { ?clauses_attr, ets:new(Module, [bag, public]) }),
ets:insert(Module, {?attr, ets:new(Module, [set, public])}),
ets:insert(Module, {?clauses_attr, ets:new(Module, [bag, public])}),
reset_last(Module),
ok.
@@ -29,14 +29,14 @@ cleanup(Module) ->
%% Reset the last item. Useful when evaling code.
reset_last(Module) ->
ets:insert(table(Module), { last, [] }).
ets:insert(table(Module), {last, []}).
%% Looks up a definition from the database.
lookup_definition(Module, Tuple) ->
case ets:lookup(table(Module), Tuple) of
[Result] ->
CTable = clauses_table(Module),
{ Result, [Clause || { _, Clause } <- ets:lookup(CTable, Tuple)] };
{Result, [Clause || {_, Clause} <- ets:lookup(CTable, Tuple)]};
_ ->
false
end.
@@ -49,26 +49,26 @@ delete_definition(Module, Tuple) ->
% Each function is then added to the function table.
store_definition(Line, Kind, CheckClauses, Call, Body, Pos) ->
E = (elixir_locals:get_cached_env(Pos))#elixir_env{line=Line},
{ NameAndArgs, Guards } = elixir_clauses:extract_guards(Call),
E = (elixir_locals:get_cached_env(Pos))#{line := Line},
{NameAndArgs, Guards} = elixir_clauses:extract_guards(Call),
{ Name, Args } = case NameAndArgs of
{ N, _, A } when is_atom(N), is_atom(A) -> { N, [] };
{ N, _, A } when is_atom(N), is_list(A) -> { N, A };
_ -> elixir_errors:form_error(Line, E#elixir_env.file, ?MODULE, { invalid_def, Kind, NameAndArgs })
{Name, Args} = case NameAndArgs of
{N, _, A} when is_atom(N), is_atom(A) -> {N, []};
{N, _, A} when is_atom(N), is_list(A) -> {N, A};
_ -> elixir_errors:form_error(Line, ?m(E, file), ?MODULE, {invalid_def, Kind, NameAndArgs})
end,
%% Now that we have verified the call format,
%% extract meta information like file and context.
{ _, Meta, _ } = Call,
{_, Meta, _} = Call,
DoCheckClauses = (not lists:keymember(context, 1, Meta)) andalso (CheckClauses),
%% Check if there is a file information in the definition.
%% If so, we assume this come from another source and we need
%% to linify taking into account keep line numbers.
{ File, Key } = case lists:keyfind(file, 1, Meta) of
{ file, Bin } when is_binary(Bin) -> { Bin, keep };
_ -> { nil, line }
{File, Key} = case lists:keyfind(file, 1, Meta) of
{file, Bin} when is_binary(Bin) -> {Bin, keep};
_ -> {nil, line}
end,
LinifyArgs = elixir_quote:linify(Line, Key, Args),
@@ -79,20 +79,19 @@ store_definition(Line, Kind, CheckClauses, Call, Body, Pos) ->
store_definition(Line, Kind, DoCheckClauses, Name,
LinifyArgs, LinifyGuards, LinifyBody, File, E).
store_definition(Line, Kind, CheckClauses, Name, Args, Guards, Body, MetaFile, #elixir_env{module=Module} = ER) ->
store_definition(Line, Kind, CheckClauses, Name, Args, Guards, Body, MetaFile, #{module := Module} = ER) ->
Arity = length(Args),
Tuple = { Name, Arity },
E = ER#elixir_env{function=Tuple},
Tuple = {Name, Arity},
E = ER#{function := Tuple},
elixir_locals:record_definition(Tuple, Kind, Module),
Location = retrieve_location(Line, MetaFile, Module),
run_on_definition_callbacks(Kind, Line, Module, Name, Args, Guards, Body, E),
{ Function, Defaults, Super } = translate_definition(Kind, Line, Name, Args, Guards, Body, E),
{Function, Defaults, Super} = translate_definition(Kind, Line, Module, Name, Args, Guards, Body, E),
DefaultsLength = length(Defaults),
elixir_locals:record_defaults(Tuple, Kind, Module, DefaultsLength),
File = E#elixir_env.file,
File = ?m(E, file),
Table = table(Module),
CTable = clauses_table(Module),
@@ -104,7 +103,8 @@ store_definition(Line, Kind, CheckClauses, Name, Args, Guards, Body, MetaFile, #
[store_each(false, Kind, File, Location, Table, CTable, 0,
default_function_for(Kind, Name, Default)) || Default <- Defaults],
{ Name, Arity }.
make_struct_available(Kind, Module, Name, Args),
{Name, Arity}.
%% @on_definition
@@ -113,11 +113,19 @@ run_on_definition_callbacks(Kind, Line, Module, Name, Args, Guards, Expr, E) ->
true ->
ok;
_ ->
Env = elixir_env:env_to_ex({ Line, E }),
Env = elixir_env:linify({Line, E}),
Callbacks = 'Elixir.Module':get_attribute(Module, on_definition),
[Mod:Fun(Env, Kind, Name, Args, Guards, Expr) || { Mod, Fun } <- Callbacks]
[Mod:Fun(Env, Kind, Name, Args, Guards, Expr) || {Mod, Fun} <- Callbacks]
end.
make_struct_available(def, Module, '__struct__', []) ->
case erlang:get(elixir_compiler_pid) of
undefined -> ok;
Pid -> Pid ! {struct_available, Module}
end;
make_struct_available(_, _, _, _) ->
ok.
%% Retrieve location from meta file or @file, otherwise nil
retrieve_location(Line, File, Module) ->
@@ -126,69 +134,70 @@ retrieve_location(Line, File, Module) ->
X when X == nil; X == false ->
if
is_binary(File) ->
{ elixir_utils:characters_to_list(elixir_utils:relative_to_cwd(File)), Line };
{elixir_utils:characters_to_list(elixir_utils:relative_to_cwd(File)), Line};
true ->
nil
end;
X ->
X when is_binary(X) ->
'Elixir.Module':delete_attribute(Module, file),
{ X, 0 }
{elixir_utils:characters_to_list(X), 0}
end.
%% Compile super
compile_super(Module, true, #elixir_env{function=Function}) ->
compile_super(Module, true, #{function := Function}) ->
elixir_def_overridable:store(Module, Function, true);
compile_super(_Module, _, _E) -> ok.
%% Translate the given call and expression given
%% and then store it in memory.
translate_definition(Kind, Line, Name, Args, Guards, Body, E) when is_integer(Line) ->
translate_definition(Kind, Line, Module, Name, Args, Guards, Body, E) when is_integer(Line) ->
Arity = length(Args),
{EArgs, EGuards, EBody, _} = elixir_exp_clauses:def(fun elixir_def_defaults:expand/2,
Args, Guards, expr_from_body(Line, Body), E),
%% Macros receive a special argument on invocation. Notice it does
%% not affect the arity of the stored function, but the clause
%% already contains it.
AllArgs = case is_macro(Kind) of
true -> [{ '_@CALLER', [{line,Line}], nil }|Args];
false -> Args
EAllArgs = case is_macro(Kind) of
true -> [{'_@CALLER', [{line,Line}], nil}|EArgs];
false -> EArgs
end,
{ EArgs, EGuards, EBody, _ } = elixir_exp_clauses:def(fun elixir_def_defaults:expand/2,
AllArgs, Guards, expr_from_body(Line, Body), E),
S = elixir_env:env_to_scope(E),
{ Unpacked, Defaults } = elixir_def_defaults:unpack(Kind, Name, EArgs, S),
{ Clauses, Super } = translate_clause(Body, Line, Kind, Unpacked, EGuards, EBody, S),
{Unpacked, Defaults} = elixir_def_defaults:unpack(Kind, Name, EAllArgs, S),
{Clauses, Super} = translate_clause(Body, Line, Kind, Unpacked, EGuards, EBody, S),
Function = { function, Line, Name, Arity, Clauses },
{ Function, Defaults, Super }.
run_on_definition_callbacks(Kind, Line, Module, Name, EArgs, EGuards, EBody, E),
Function = {function, Line, Name, Arity, Clauses},
{Function, Defaults, Super}.
translate_clause(nil, _Line, _Kind, _Args, [], _Body, _S) ->
{ [], false };
{[], false};
translate_clause(nil, Line, Kind, _Args, _Guards, _Body, #elixir_scope{file=File}) ->
elixir_errors:form_error(Line, File, ?MODULE, { missing_do, Kind });
elixir_errors:form_error(Line, File, ?MODULE, {missing_do, Kind});
translate_clause(_, Line, Kind, Args, Guards, Body, S) ->
{ TClause, TS } = elixir_clauses:clause(Line,
fun elixir_translator:translate_many/2, Args, Body, Guards, S),
{TClause, TS} = elixir_clauses:clause(Line,
fun elixir_translator:translate_args/2, Args, Body, Guards, true, S),
%% Set __CALLER__ if used
FClause = case is_macro(Kind) andalso TS#elixir_scope.caller of
true ->
FBody = { 'match', Line,
{ 'var', Line, '__CALLER__' },
?wrap_call(Line, elixir_env, env_to_ex, [{ var, Line, '_@CALLER' }])
},
FBody = {'match', Line,
{'var', Line, '__CALLER__'},
elixir_utils:erl_call(Line, elixir_env, linify, [{var, Line, '_@CALLER'}])
},
setelement(5, TClause, [FBody|element(5, TClause)]);
false -> TClause
end,
{ [FClause], TS#elixir_scope.super }.
{[FClause], TS#elixir_scope.super}.
expr_from_body(_Line, nil) -> nil;
expr_from_body(_Line, [{ do, Expr }]) -> Expr;
expr_from_body(Line, Else) -> { 'try', [{line,Line}], [Else] }.
expr_from_body(_Line, [{do, Expr}]) -> Expr;
expr_from_body(Line, Else) -> {'try', [{line,Line}], [Else]}.
is_macro(defmacro) -> true;
is_macro(defmacrop) -> true;
@@ -205,18 +214,18 @@ unwrap_definitions(Module) ->
unwrap_definition([Fun|T], CTable, All, Exports, Private, Def, Defmacro, Functions, Tail) ->
Tuple = element(1, Fun),
Clauses = [Clause || { _, Clause } <- ets:lookup(CTable, Tuple)],
Clauses = [Clause || {_, Clause} <- ets:lookup(CTable, Tuple)],
{ NewFun, NewExports, NewPrivate, NewDef, NewDefmacro } =
{NewFun, NewExports, NewPrivate, NewDef, NewDefmacro} =
case Clauses of
[] -> { false, Exports, Private, Def, Defmacro };
[] -> {false, Exports, Private, Def, Defmacro};
_ -> unwrap_definition(element(2, Fun), Tuple, Fun, Exports, Private, Def, Defmacro)
end,
{ NewFunctions, NewTail } = case NewFun of
{NewFunctions, NewTail} = case NewFun of
false ->
NewAll = All,
{ Functions, Tail };
{Functions, Tail};
_ ->
NewAll = [Tuple|All],
function_for_stored_definition(NewFun, Clauses, Functions, Tail)
@@ -225,49 +234,49 @@ unwrap_definition([Fun|T], CTable, All, Exports, Private, Def, Defmacro, Functio
unwrap_definition(T, CTable, NewAll, NewExports, NewPrivate,
NewDef, NewDefmacro, NewFunctions, NewTail);
unwrap_definition([], _CTable, All, Exports, Private, Def, Defmacro, Functions, Tail) ->
{ All, Exports, Private, ordsets:from_list(Def),
ordsets:from_list(Defmacro), lists:reverse(Tail ++ Functions) }.
{All, Exports, Private, ordsets:from_list(Def),
ordsets:from_list(Defmacro), lists:reverse(Tail ++ Functions)}.
unwrap_definition(def, Tuple, Fun, Exports, Private, Def, Defmacro) ->
{ Fun, [Tuple|Exports], Private, [Tuple|Def], Defmacro };
unwrap_definition(defmacro, { Name, Arity } = Tuple, Fun, Exports, Private, Def, Defmacro) ->
Macro = { ?elixir_macro(Name), Arity + 1 },
{ setelement(1, Fun, Macro), [Macro|Exports], Private, Def, [Tuple|Defmacro] };
{Fun, [Tuple|Exports], Private, [Tuple|Def], Defmacro};
unwrap_definition(defmacro, {Name, Arity} = Tuple, Fun, Exports, Private, Def, Defmacro) ->
Macro = {elixir_utils:macro_name(Name), Arity + 1},
{setelement(1, Fun, Macro), [Macro|Exports], Private, Def, [Tuple|Defmacro]};
unwrap_definition(defp, Tuple, Fun, Exports, Private, Def, Defmacro) ->
%% { Name, Arity }, Kind, Line, Check, Defaults
Info = { Tuple, defp, element(3, Fun), element(5, Fun), element(7, Fun) },
{ Fun, Exports, [Info|Private], Def, Defmacro };
%% {Name, Arity}, Kind, Line, Check, Defaults
Info = {Tuple, defp, element(3, Fun), element(5, Fun), element(7, Fun)},
{Fun, Exports, [Info|Private], Def, Defmacro};
unwrap_definition(defmacrop, Tuple, Fun, Exports, Private, Def, Defmacro) ->
%% { Name, Arity }, Kind, Line, Check, Defaults
Info = { Tuple, defmacrop, element(3, Fun), element(5, Fun), element(7, Fun) },
{ false, Exports, [Info|Private], Def, Defmacro }.
%% {Name, Arity}, Kind, Line, Check, Defaults
Info = {Tuple, defmacrop, element(3, Fun), element(5, Fun), element(7, Fun)},
{false, Exports, [Info|Private], Def, Defmacro}.
%% Helpers
function_for_stored_definition({{Name,Arity}, _, Line, _, _, nil, _}, Clauses, Functions, Tail) ->
{ [{ function, Line, Name, Arity, Clauses }|Functions], Tail };
{[{function, Line, Name, Arity, Clauses}|Functions], Tail};
function_for_stored_definition({{Name,Arity}, _, Line, _, _, Location, _}, Clauses, Functions, Tail) ->
{ Functions, [
{ function, Line, Name, Arity, Clauses },
{ attribute, Line, file, Location } | Tail
] }.
{Functions, [
{function, Line, Name, Arity, Clauses},
{attribute, Line, file, Location} | Tail
]}.
default_function_for(Kind, Name, { clause, Line, Args, _Guards, _Exprs } = Clause)
default_function_for(Kind, Name, {clause, Line, Args, _Guards, _Exprs} = Clause)
when Kind == defmacro; Kind == defmacrop ->
{ function, Line, Name, length(Args) - 1, [Clause] };
{function, Line, Name, length(Args) - 1, [Clause]};
default_function_for(_, Name, { clause, Line, Args, _Guards, _Exprs } = Clause) ->
{ function, Line, Name, length(Args), [Clause] }.
default_function_for(_, Name, {clause, Line, Args, _Guards, _Exprs} = Clause) ->
{function, Line, Name, length(Args), [Clause]}.
%% Store each definition in the table.
%% This function also checks and emit warnings in case
%% the kind, of the visibility of the function changes.
store_each(Check, Kind, File, Location, Table, CTable, Defaults, {function, Line, Name, Arity, Clauses}) ->
Tuple = { Name, Arity },
Tuple = {Name, Arity},
case ets:lookup(Table, Tuple) of
[{ Tuple, StoredKind, StoredLine, StoredFile, StoredCheck, StoredLocation, StoredDefaults }] ->
[{Tuple, StoredKind, StoredLine, StoredFile, StoredCheck, StoredLocation, StoredDefaults}] ->
FinalLine = StoredLine,
FinalLocation = StoredLocation,
FinalDefaults = max(Defaults, StoredDefaults),
@@ -280,16 +289,16 @@ store_each(Check, Kind, File, Location, Table, CTable, Defaults, {function, Line
FinalLocation = Location,
FinalDefaults = Defaults
end,
Check andalso ets:insert(Table, { last, { Name, Arity } }),
ets:insert(CTable, [{ Tuple, Clause } || Clause <- Clauses ]),
ets:insert(Table, { Tuple, Kind, FinalLine, File, Check, FinalLocation, FinalDefaults }).
Check andalso ets:insert(Table, {last, {Name, Arity}}),
ets:insert(CTable, [{Tuple, Clause} || Clause <- Clauses ]),
ets:insert(Table, {Tuple, Kind, FinalLine, File, Check, FinalLocation, FinalDefaults}).
%% Validations
check_valid_kind(_Line, _File, _Name, _Arity, Kind, Kind) -> [];
check_valid_kind(Line, File, Name, Arity, Kind, StoredKind) ->
elixir_errors:form_error(Line, File, ?MODULE,
{ changed_kind, { Name, Arity, StoredKind, Kind } }).
{changed_kind, {Name, Arity, StoredKind, Kind}}).
check_valid_clause(Line, File, Name, Arity, Kind, Table, StoredLine, StoredFile) ->
case ets:lookup_element(Table, last, 2) of
@@ -297,29 +306,29 @@ check_valid_clause(Line, File, Name, Arity, Kind, Table, StoredLine, StoredFile)
[] -> [];
_ ->
Relative = elixir_utils:relative_to_cwd(StoredFile),
elixir_errors:handle_file_warning(File, { Line, ?MODULE,
{ ungrouped_clause, { Kind, Name, Arity, StoredLine, Relative } } })
elixir_errors:handle_file_warning(File, {Line, ?MODULE,
{ungrouped_clause, {Kind, Name, Arity, StoredLine, Relative}}})
end.
check_valid_defaults(_Line, _File, _Name, _Arity, _Kind, 0, _) -> [];
check_valid_defaults(Line, File, Name, Arity, Kind, _, 0) ->
elixir_errors:handle_file_warning(File, { Line, ?MODULE, { out_of_order_defaults, { Kind, Name, Arity } } });
elixir_errors:handle_file_warning(File, {Line, ?MODULE, {out_of_order_defaults, {Kind, Name, Arity}}});
check_valid_defaults(Line, File, Name, Arity, Kind, _, _) ->
elixir_errors:form_error(Line, File, ?MODULE, { clauses_with_defaults, { Kind, Name, Arity } }).
elixir_errors:form_error(Line, File, ?MODULE, {clauses_with_defaults, {Kind, Name, Arity}}).
check_previous_defaults(Table, Line, Name, Arity, Kind, Defaults, E) ->
Matches = ets:match(Table, { { Name, '$2' }, '$1', '_', '_', '_', '_', '$3' }),
Matches = ets:match(Table, {{Name, '$2'}, '$1', '_', '_', '_', '_', '$3'}),
[ begin
elixir_errors:form_error(Line, E#elixir_env.file, ?MODULE,
{ defs_with_defaults, Name, { Kind, Arity }, { K, A } })
elixir_errors:form_error(Line, ?m(E, file), ?MODULE,
{defs_with_defaults, Name, {Kind, Arity}, {K, A}})
end || [K, A, D] <- Matches, A /= Arity, D /= 0, defaults_conflict(A, D, Arity, Defaults)].
defaults_conflict(A, D, Arity, Defaults) ->
((Arity >= (A - D)) andalso (Arity < A)) orelse
((A >= (Arity - Defaults)) andalso (A < Arity)).
assert_no_aliases_name(Line, '__aliases__', [Atom], #elixir_env{file=File}) when is_atom(Atom) ->
elixir_errors:form_error(Line, File, ?MODULE, { no_alias, Atom });
assert_no_aliases_name(Line, '__aliases__', [Atom], #{file := File}) when is_atom(Atom) ->
elixir_errors:form_error(Line, File, ?MODULE, {no_alias, Atom});
assert_no_aliases_name(_Meta, _Aliases, _Args, _S) ->
ok.
@@ -329,17 +338,17 @@ assert_no_aliases_name(_Meta, _Aliases, _Args, _S) ->
format_error({no_module,{Kind,Name,Arity}}) ->
io_lib:format("cannot define function outside module, invalid scope for ~ts ~ts/~B", [Kind, Name, Arity]);
format_error({defs_with_defaults, Name, { Kind, Arity }, { K, A } }) when Arity > A ->
format_error({defs_with_defaults, Name, {Kind, Arity}, {K, A}}) when Arity > A ->
io_lib:format("~ts ~ts/~B defaults conflicts with ~ts ~ts/~B",
[Kind, Name, Arity, K, Name, A]);
format_error({defs_with_defaults, Name, { Kind, Arity }, { K, A } }) when Arity < A ->
format_error({defs_with_defaults, Name, {Kind, Arity}, {K, A}}) when Arity < A ->
io_lib:format("~ts ~ts/~B conflicts with defaults from ~ts ~ts/~B",
[Kind, Name, Arity, K, Name, A]);
format_error({clauses_with_defaults,{Kind,Name,Arity}}) ->
io_lib:format("~ts ~ts/~B has default values and multiple clauses, "
"use a separate clause for declaring defaults", [Kind, Name, Arity]);
"define a function head with the defaults", [Kind, Name, Arity]);
format_error({out_of_order_defaults,{Kind,Name,Arity}}) ->
io_lib:format("clause with defaults should be the first clause in ~ts ~ts/~B", [Kind, Name, Arity]);

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