Compare commits

...
258 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
Hugo Duksis 363b2b9352 Adds symlink to File
implementation of feature request #2114
2014-03-17 22:59:23 +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
Peter Minten f87e5d3c12 Improve Enumerable count/member? docs 2014-01-11 14:49:17 +01:00
334 changed files with 11573 additions and 9362 deletions
+86 -5
View File
@@ -1,3 +1,82 @@
# 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
@@ -37,12 +116,14 @@
* [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`
* [Kernel] `lc` and `bc` comprehensions are deprecated in favor of `for` (this is a soft deprecation, no warning will be emitted)
* [ListDict] `ListDict` is deprecated in favor of `Map` (this is a soft deprecation, no warning will be emitted)
* [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 (this is a soft deprecation, no warnings will be emitted)
* Backwards incompatible changes
* [ExUnit] Formatters are now required to be a GenEvent and `ExUnit.run/2` returns a map with results
@@ -791,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`
@@ -844,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
+19 -8
View File
@@ -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
.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
+2 -2
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/).
@@ -18,7 +18,7 @@ If tests pass, you are ready to move on to the [Getting Started guide][1] or to
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/OTP 17 [RELEASE CANDIDATE 1] [erts-6.0] [source] [64-bit] [smp:4:4] [async-threads:10] [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].
+1 -1
View File
@@ -1 +1 @@
0.13.0
0.13.2
+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 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.
@@ -95,4 +98,4 @@ 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 %*
)
)
+8 -5
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.
+1 -1
View File
@@ -1,2 +1,2 @@
@echo off
call "%~dp0\elixir.bat" +iex --erl "-user Elixir.IEx.CLI" --no-halt %
call "%~dp0\elixir.bat" +iex --erl "-user Elixir.IEx.CLI" --no-halt %*
+3 -4
View File
@@ -38,7 +38,6 @@ defmodule EEx do
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
@@ -108,7 +107,7 @@ defmodule EEx do
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
@@ -207,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
+39 -34
View File
@@ -1,53 +1,57 @@
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, chars }|t], buffer, scope, state) do
buffer = state.engine.handle_text(buffer, String.from_char_list!(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, 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.dict([]).line(line).start_line(start_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
@@ -63,23 +67,24 @@ defmodule EEx.Compiler do
defp wrap_expr(current, line, buffer, chars, state) do
new_lines = List.duplicate(?\n, line - state.line)
key = length(state.dict)
key = length(state.quoted)
placeholder = '__EEX__(' ++ integer_to_list(key) ++ ');'
{ current ++ placeholder ++ new_lines ++ chars, state.update_dict(&[{key, buffer}|&1]) }
{current ++ placeholder ++ new_lines ++ chars,
%{state | quoted: [{key, buffer}|state.quoted]}}
end
# Look text ahead on expressions
defp look_ahead_text([{ :text, text }, { :middle_expr, line, _, chars }|t]=list, start, contents) do
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 }
{contents ++ text ++ chars, line, t}
else
{ contents, start, list }
{contents, start, list}
end
end
defp look_ahead_text(t, start, contents) do
{ contents, start, t }
{contents, start, t}
end
defp only_spaces?(chars) do
@@ -88,24 +93,24 @@ defmodule EEx.Compiler do
# 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
+5 -5
View File
@@ -27,12 +27,12 @@ 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
@@ -91,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
+19 -19
View File
@@ -5,15 +5,15 @@ defmodule EEx.Tokenizer do
Tokenizes the given char list or binary.
It returns 4 different types of tokens as result:
* { :text, contents }
* { :expr, line, marker, contents }
* { :start_expr, line, marker, contents }
* { :middle_expr, line, marker, contents }
* { :end_expr, line, marker, contents }
* {:text, contents}
* {:expr, line, marker, contents}
* {:start_expr, line, marker, contents}
* {:middle_expr, line, marker, contents}
* {:end_expr, line, marker, contents}
"""
def tokenize(bin, line) when is_binary(bin) do
tokenize(String.to_char_list!(bin), line)
tokenize(List.from_char_data!(bin), line)
end
def tokenize(list, line) do
@@ -21,22 +21,22 @@ defmodule EEx.Tokenizer do
end
defp tokenize('<%%' ++ t, line, buffer, acc) do
{ buffer, new_line, rest } = tokenize_expr t, line, [?%, ?<|buffer]
{buffer, new_line, rest} = tokenize_expr t, line, [?%, ?<|buffer]
tokenize rest, new_line, [?>, ?%|buffer], acc
end
defp tokenize('<%#' ++ t, line, buffer, acc) do
{ _, new_line, rest } = tokenize_expr t, line, []
{_, new_line, rest} = tokenize_expr t, line, []
tokenize rest, new_line, buffer, acc
end
defp tokenize('<%' ++ t, line, buffer, acc) do
{ marker, t } = retrieve_marker(t)
{ expr, new_line, rest } = tokenize_expr t, line, []
{marker, t} = retrieve_marker(t)
{expr, new_line, rest} = tokenize_expr t, line, []
token = token_name(expr)
acc = tokenize_text(buffer, acc)
final = { token, line, marker, Enum.reverse(expr) }
final = {token, line, marker, Enum.reverse(expr)}
tokenize rest, new_line, [], [final | acc]
end
@@ -55,17 +55,17 @@ defmodule EEx.Tokenizer do
# 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
@@ -104,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)
@@ -130,14 +130,14 @@ 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
@@ -156,6 +156,6 @@ defmodule EEx.Tokenizer do
end
defp tokenize_text(buffer, acc) do
[{ :text, Enum.reverse(buffer) } | acc]
[{: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
+26 -26
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, 'foo' } ]
assert T.tokenize('foo', 1) == [ {:text, 'foo'} ]
end
test "simple strings" do
assert T.tokenize("foo", 1) == [ { :text, 'foo' } ]
assert T.tokenize("foo", 1) == [ {:text, 'foo'} ]
end
test "strings with embedded code" do
assert T.tokenize('foo <% bar %>', 1) == [ { :text, '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, '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, '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
@@ -43,57 +43,57 @@ baz %>
test "quotation" do
assert T.tokenize('foo <%% true %>', 1) == [
{ :text, 'foo <% true %>' }
{:text, 'foo <% true %>'}
]
end
test "quotation with do/end" do
assert T.tokenize('foo <%% true do %>bar<%% end %>', 1) == [
{ :text, 'foo <% true do %>bar<% end %>' }
{:text, 'foo <% true do %>bar<% end %>'}
]
end
test "comments" do
assert T.tokenize('foo <%# true %>', 1) == [
{ :text, 'foo ' }
{:text, 'foo '}
]
end
test "comments with do/end" do
assert T.tokenize('foo <%# true do %>bar<%# end %>', 1) == [
{ :text, 'foo bar' }
{:text, 'foo bar'}
]
end
test "strings with embedded do end" do
assert T.tokenize('foo <% if true do %>bar<% end %>', 1) == [
{ :text, 'foo ' },
{ :start_expr, 1, "", ' if true do ' },
{ :text, '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, 'foo ' },
{ :start_expr, 1, "", ' cond do ' },
{ :middle_expr, 1, "", ' false -> ' },
{ :text, 'bar' },
{ :middle_expr, 1, "", ' true -> ' },
{ :text, '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, 'foo ' },
{ :start_expr, 1, "", ' if true do ' },
{ :text, 'bar' },
{ :middle_expr, 1, "", ' else ' },
{ :text, '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
+29 -20
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,13 +16,13 @@ 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
@@ -282,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 = """
• <%= "•" %> •
@@ -320,38 +329,38 @@ 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
defmodule TestEngine do
@behaviour EEx.Engine
def handle_body(body) do
{ :wrapped, body }
{:wrapped, body}
end
def handle_text(buffer, text) do
@@ -364,11 +373,11 @@ foo
end
test "calls handle_body" do
assert { :wrapped, "foo" } = EEx.eval_string("foo", [], engine: TestEngine)
assert {:wrapped, "foo"} = EEx.eval_string("foo", [], engine: TestEngine)
end
defp assert_eval(expected, actual) do
result = EEx.eval_string(actual, [], file: __ENV__.file, engine: EEx.Engine)
defp assert_eval(expected, actual, binding \\ []) do
result = EEx.eval_string(actual, binding, file: __ENV__.file, engine: EEx.Engine)
assert result == expected
end
end
+1 -26
View File
@@ -1,11 +1,4 @@
-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, {
@@ -26,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,
+13 -7
View File
@@ -14,11 +14,11 @@ defprotocol Access do
iex> keywords[:a]
1
iex> map = %{ a: 1, b: 2 }
iex> map = %{a: 1, b: 2}
iex> map[:a]
1
iex> star_ratings = %{ 1.0 => "★", 1.5 => "★☆", 2.0 => "★★" }
iex> star_ratings = %{1.0 => "★", 1.5 => "★☆", 2.0 => "★★"}
iex> star_ratings[1.5]
"★☆"
@@ -34,16 +34,22 @@ defprotocol Access do
end
defimpl Access, for: List do
def access(dict, key)
def access([{ key, value }|_], key), do: value
def access([{ _, _ }|t], key), do: access(t, key)
def access([], _key), do: nil
def access(dict, key) when is_atom(key) do
case :lists.keyfind(key, 1, dict) do
{^key, value} -> value
false -> nil
end
end
def access(_dict, key) do
raise ArgumentError, message: "the access protocol for lists expect the key to be an atom, got: #{inspect key}"
end
end
defimpl Access, for: Map do
def access(map, key) do
case :maps.find(key, map) do
{ :ok, value } -> value
{:ok, value} -> value
:error -> nil
end
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 -49
View File
@@ -1,55 +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
"""
@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
+15 -15
View File
@@ -15,11 +15,11 @@ defmodule Base do
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}) ->
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)
@@ -61,9 +61,9 @@ defmodule Base do
{:ok, "foobar"}
"""
@spec decode16(binary) :: { :ok, binary } | :error
@spec decode16(binary) :: {:ok, binary} | :error
def decode16(string) when is_binary(string) do
{ :ok, decode16!(string) }
{:ok, decode16!(string)}
rescue
ArgumentError -> :error
end
@@ -130,9 +130,9 @@ defmodule Base do
{:ok, "foobar"}
"""
@spec decode64(binary) :: { :ok, binary } | :error
@spec decode64(binary) :: {:ok, binary} | :error
def decode64(string) when is_binary(string) do
{ :ok, do_decode64(string, &dec64/1) }
{:ok, do_decode64(string, &dec64/1)}
rescue
ArgumentError -> :error
end
@@ -203,9 +203,9 @@ defmodule Base do
{:ok, <<255,127,254,252>>}
"""
@spec url_decode64(binary) :: { :ok, binary } | :error
@spec url_decode64(binary) :: {:ok, binary} | :error
def url_decode64(string) when is_binary(string) do
{ :ok, do_decode64(string, &dec64url/1) }
{:ok, do_decode64(string, &dec64url/1)}
rescue
ArgumentError -> :error
end
@@ -265,9 +265,9 @@ defmodule Base do
{:ok, "foobar"}
"""
@spec decode32(binary) :: { :ok, binary } | :error
@spec decode32(binary) :: {:ok, binary} | :error
def decode32(string) do
{ :ok, do_decode32(string, &dec32/1) }
{:ok, do_decode32(string, &dec32/1)}
rescue
ArgumentError -> :error
end
@@ -328,9 +328,9 @@ defmodule Base do
{:ok, "foobar"}
"""
@spec hex_decode32(binary) :: { :ok, binary } | :error
@spec hex_decode32(binary) :: {:ok, binary} | :error
def hex_decode32(string) when is_binary(string) do
{ :ok, do_decode32(string, &dec32hex/1) }
{:ok, do_decode32(string, &dec32hex/1)}
rescue
ArgumentError -> :error
end
+15 -15
View File
@@ -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
@@ -57,34 +57,34 @@ defmodule Behaviour do
do_defmacrocallback(split_spec(spec, quote(do: Macro.t)), __CALLER__)
end
defp split_spec({ :when, _, [{ :::, _, [spec, return] }, guard] }, _default) do
{ spec, return, guard }
defp split_spec({:when, _, [{:::, _, [spec, return]}, guard]}, _default) do
{spec, return, guard}
end
defp split_spec({ :when, _, [spec, guard] }, default) do
{ spec, default, guard }
defp split_spec({:when, _, [spec, guard]}, default) do
{spec, default, guard}
end
defp split_spec({ :::, _, [spec, return] }, _default) do
{ spec, return, [] }
defp split_spec({:::, _, [spec, return]}, _default) do
{spec, return, []}
end
defp split_spec(spec, default) do
{ spec, default, [] }
{spec, default, []}
end
defp do_defcallback({ spec, return, guards }, caller) do
defp do_defcallback({spec, return, guards}, caller) do
case Macro.decompose_call(spec) do
{ name, args } ->
{name, args} ->
do_callback(:def, name, args, name, length(args), args, return, guards, caller)
_ ->
raise ArgumentError, message: "invalid syntax in defcallback #{Macro.to_string(spec)}"
end
end
defp do_defmacrocallback({ spec, return, guards }, caller) do
defp do_defmacrocallback({spec, return, guards}, caller) do
case Macro.decompose_call(spec) do
{ name, args } ->
{name, args} ->
do_callback(:defmacro, :"MACRO-#{name}", [quote(do: env :: Macro.Env.t)|args],
name, length(args), args, return, guards, caller)
_ ->
@@ -94,7 +94,7 @@ defmodule Behaviour 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
@@ -110,7 +110,7 @@ defmodule Behaviour do
end
end
defp ensure_not_default({ :\\, _, [_, _] }) do
defp ensure_not_default({:\\, _, [_, _]}) do
raise ArgumentError, message: "default arguments \\\\ not supported in defcallback/defmacrocallback"
end
@@ -120,7 +120,7 @@ defmodule Behaviour do
def store_docs(module, line, kind, name, arity) do
doc = Module.get_attribute module, :doc
Module.delete_attribute module, :doc
Module.put_attribute module, :behaviour_docs, { { name, arity }, line, kind, doc }
Module.put_attribute module, :behaviour_docs, {{name, arity}, line, kind, doc}
end
@doc false
+39 -39
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,7 +55,7 @@ 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 """
@@ -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,15 +117,15 @@ defmodule Code do
"""
def eval_string(string, binding \\ [], opts \\ [])
def eval_string(string, binding, Macro.Env[] = env) do
{ value, binding, _env, _scope } = :elixir.eval to_char_list(string), binding, env.to_keywords
{ value, binding }
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)
{ 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 """
@@ -149,15 +149,15 @@ defmodule Code do
"""
def eval_quoted(quoted, binding \\ [], opts \\ [])
def eval_quoted(quoted, binding, Macro.Env[] = env) do
{ value, binding, _env, _scope } = :elixir.eval_quoted quoted, binding, env.to_keywords
{ value, binding }
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)
{ 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
@@ -176,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.
Returns `{:ok, quoted_form}`
if it succeeds, `{:error, {line, error, token}}` otherwise.
## Options
@@ -261,7 +261,7 @@ defmodule Code do
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
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,
@@ -270,9 +270,9 @@ defmodule Code do
"""
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
@@ -294,14 +294,14 @@ defmodule Code do
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
@@ -336,7 +336,7 @@ defmodule Code do
"""
def compiler_options(opts) do
:elixir_code_server.cast { :compiler_options, opts }
:elixir_code_server.cast {:compiler_options, opts}
end
@doc """
@@ -367,8 +367,8 @@ defmodule Code do
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.
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
@@ -411,7 +411,7 @@ defmodule Code do
otherwise.
"""
def ensure_loaded?(module) do
match?({ :module, ^module }, ensure_loaded(module))
match?({:module, ^module}, ensure_loaded(module))
end
@doc """
@@ -421,21 +421,21 @@ defmodule Code do
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.
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
@@ -452,7 +452,7 @@ defmodule Code do
Returns `false` otherwise.
"""
def ensure_compiled?(module) do
match?({ :module, ^module }, ensure_compiled(module))
match?({:module, ^module}, ensure_compiled(module))
end
## Helpers
+14 -14
View File
@@ -11,7 +11,7 @@ defprotocol Collectable do
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)
iex> Enum.traverse(%{a: 1, b: 2}, fn {k, v} -> {k, v * 2} end)
%{a: 2, b: 4}
## Why Collectable?
@@ -38,7 +38,7 @@ defprotocol Collectable do
and `empty/1`, one can, for example, implement a traversal mechanism.
"""
@type command :: { :cont, term } | :done | :halt
@type command :: {:cont, term} | :done | :halt
@doc """
Receives a collectable structure and returns an empty one.
@@ -51,7 +51,7 @@ defprotocol Collectable do
the initial accumulation value.
The returned function receives a collectable and injects a given
value into it for every `{ :cont, term }` instruction.
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
@@ -60,7 +60,7 @@ defprotocol Collectable do
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) }
@spec into(t) :: {term, (term, command -> t | term)}
def into(collectable)
end
@@ -70,11 +70,11 @@ defimpl Collectable, for: List do
end
def into(original) do
{ [], fn
list, { :cont, x } -> [x|list]
{[], fn
list, {:cont, x} -> [x|list]
list, :done -> original ++ :lists.reverse(list)
_, :halt -> :ok
end }
end}
end
end
@@ -84,11 +84,11 @@ defimpl Collectable, for: BitString do
end
def into(original) do
{ original, fn
bitstring, { :cont, x } -> <<bitstring :: bits, x :: bits>>
{original, fn
bitstring, {:cont, x} -> <<bitstring :: bits, x :: bits>>
bitstring, :done -> bitstring
_, :halt -> :ok
end }
end}
end
end
@@ -98,7 +98,7 @@ defimpl Collectable, for: Function do
end
def into(function) do
{ function, function }
{function, function}
end
end
@@ -108,10 +108,10 @@ defimpl Collectable, for: Map do
end
def into(original) do
{ original, fn
map, { :cont, { k, v } } -> :maps.put(k, v, map)
{original, fn
map, {:cont, {k, v}} -> :maps.put(k, v, map)
map, :done -> map
_, :halt -> :ok
end }
end}
end
end
+20 -20
View File
@@ -37,16 +37,15 @@ defmodule Dict do
@type key :: any
@type value :: any
@type t :: tuple | list | map
@type t :: list | map
defcallback new :: 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]
@@ -72,9 +71,10 @@ defmodule Dict do
%{} ->
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) ->
ListDict
Keyword
x ->
unsupported_dict(x)
end
@@ -165,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 = Enum.into([a: 1], dict_impl.new)
iex> Dict.fetch(d, :a)
{ :ok, 1 }
{:ok, 1}
iex> Dict.fetch(d, :b)
:error
@@ -288,8 +288,8 @@ defmodule Dict do
if target1 == target2 do
target1.merge(dict1, dict2, fun)
else
Enumerable.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
@@ -366,19 +366,19 @@ defmodule Dict do
## Examples
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> {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 = Enum.into([], dict_impl.new)
iex> { d1, d2 } = Dict.split(d, [:a, :c])
iex> { Dict.to_list(d1), Dict.to_list(d2) }
{ [], [] }
iex> {d1, d2} = Dict.split(d, [:a, :c])
iex> {Dict.to_list(d1), Dict.to_list(d2)}
{[], []}
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], [] }
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}
@@ -465,10 +465,10 @@ defmodule Dict do
target1.equal?(dict1, dict2)
target1.size(dict1) == target2.size(dict2) ->
Enumerable.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)
+65 -18
View File
@@ -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,7 +22,6 @@ defmodule Dict.Behaviour do
* `put/3`
* `reduce/3`
* `size/1`
* `update/4`
All functions, except `reduce/3`, are required by the Dict behaviour.
`reduce/3` must be implemtented as per the Enumerable protocol.
@@ -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.
@@ -71,20 +75,20 @@ defmodule Dict.Behaviour do
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
{: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
@@ -98,27 +102,27 @@ 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
@@ -129,24 +133,67 @@ defmodule Dict.Behaviour do
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
+214 -205
View File
@@ -16,8 +16,8 @@ defprotocol Enumerable do
Internally, `Enum.map/2` is implemented as follows:
def map(enum, fun) do
reducer = fn x, acc -> { :cont, [fun.(x)|acc] } end
Enumerable.reduce(enum, { :cont, [] }, reducer) |> elem(1) |> :lists.reverse()
reducer = fn x, acc -> {:cont, [fun.(x)|acc]} end
Enumerable.reduce(enum, {:cont, []}, reducer) |> elem(1) |> :lists.reverse()
end
Notice the user given function is wrapped into a `reducer` function.
@@ -50,7 +50,7 @@ defprotocol Enumerable do
In case a reducer function returns a `:suspend` accumulator,
it must be explicitly handled by the caller and never leak.
"""
@type acc :: { :cont, term } | { :halt, term } | { :suspend, term }
@type acc :: {:cont, term} | {:halt, term} | {:suspend, term}
@typedoc """
The reducer function.
@@ -76,7 +76,7 @@ defprotocol Enumerable do
Furthermore, a `:suspend` call must always be followed by another call,
eventually halting or continuing until the end.
"""
@type result :: { :done, term } | { :halted, term } | { :suspended, term, continuation }
@type result :: {:done, term} | {:halted, term} | {:suspended, term, continuation}
@typedoc """
A partially applied reduce function.
@@ -102,10 +102,10 @@ defprotocol Enumerable do
As an example, here is the implementation of `reduce` for lists:
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 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)
"""
@spec reduce(t, acc, reducer) :: result
@@ -114,20 +114,29 @@ defprotocol Enumerable do
@doc """
Checks if a value exists within the collection.
It should return `{ :ok, boolean }` if membership can be tested
faster than linear time with the match (`===`) operator, otherwise
should return `{ :error, __MODULE__ }`.
It should return `{:ok, boolean}`.
If `{:error, __MODULE__}` is returned a default algorithm using `reduce` and
the match (`===`) operator is used. This algorithm runs in linear time.
Please force use of the default algorithm unless you can implement an
algorithm that is significantly faster.
"""
@spec member?(t, term) :: { :ok, boolean } | { :error, module }
@spec member?(t, term) :: {:ok, boolean} | {:error, module}
def member?(collection, value)
@doc """
Retrieves the collection's size.
Should return `{ :ok, size }` if the size is pre-calculated,
`{ :error, __MODULE__ }` otherwise.
It should return `{:ok, size}`.
If `{:error, __MODULE__}` is returned a default algorithm using `reduce` and
the match (`===`) operator is used. This algorithm runs in linear time.
Please force use of the default algorithm unless you can implement an
algorithm that is significantly faster.
"""
@spec count(t) :: { :ok, non_neg_integer } | { :error, module }
@spec count(t) :: {:ok, non_neg_integer} | {:error, module}
def count(collection)
end
@@ -142,10 +151,10 @@ defmodule Enum do
[2,4,6]
Some particular types, like dictionaries, yield a specific format on
enumeration. For dicts, the argument is always a `{ key, value }` tuple:
enumeration. For dicts, the argument is always a `{key, value}` tuple:
iex> dict = %{a: 1, b: 2}
iex> Enum.map(dict, fn { k, v } -> { k, v * 2 } end)
iex> Enum.map(dict, fn {k, v} -> {k, v * 2} end)
[a: 2, b: 4]
Note that the functions in the `Enum` module are eager: they always start
@@ -172,16 +181,16 @@ defmodule Enum do
require Stream.Reducers, as: R
defmacrop cont(_, entry, acc) do
quote do: { :cont, [unquote(entry)|unquote(acc)] }
quote do: {:cont, [unquote(entry)|unquote(acc)]}
end
defmacrop acc(h, n, _) do
quote do: { unquote(h), unquote(n) }
quote do: {unquote(h), unquote(n)}
end
defmacrop cont_with_acc(f, entry, h, n, _) do
quote do
{ :cont, { [unquote(entry)|unquote(h)], unquote(n) } }
{:cont, {[unquote(entry)|unquote(h)], unquote(n)}}
end
end
@@ -217,8 +226,8 @@ defmodule Enum do
end
def all?(collection, fun) do
Enumerable.reduce(collection, { :cont, true }, fn(entry, _) ->
if fun.(entry), do: { :cont, true }, else: { :halt, false }
Enumerable.reduce(collection, {:cont, true}, fn(entry, _) ->
if fun.(entry), do: {:cont, true}, else: {:halt, false}
end) |> elem(1)
end
@@ -254,8 +263,8 @@ defmodule Enum do
end
def any?(collection, fun) do
Enumerable.reduce(collection, { :cont, false }, fn(entry, _) ->
if fun.(entry), do: { :halt, true }, else: { :cont, false }
Enumerable.reduce(collection, {:cont, false}, fn(entry, _) ->
if fun.(entry), do: {:halt, true}, else: {:cont, false}
end) |> elem(1)
end
@@ -282,7 +291,7 @@ defmodule Enum do
@spec at(t, integer, default) :: element | default
def at(collection, n, default \\ nil) do
case fetch(collection, n) do
{ :ok, h } -> h
{:ok, h} -> h
:error -> default
end
end
@@ -326,8 +335,8 @@ defmodule Enum do
def chunk(coll, n, step, pad \\ nil) when n > 0 and step > 0 do
limit = :erlang.max(n, step)
{ _, { acc, { buffer, i } } } =
Enumerable.reduce(coll, { :cont, { [], { [], 0 } } }, R.chunk(n, step, limit))
{_, {acc, {buffer, i}}} =
Enumerable.reduce(coll, {:cont, {[], {[], 0}}}, R.chunk(n, step, limit))
if nil?(pad) || i == 0 do
:lists.reverse(acc)
@@ -348,11 +357,11 @@ defmodule Enum do
"""
@spec chunk_by(t, (element -> any)) :: [list]
def chunk_by(coll, fun) do
{ _, { acc, res } } =
Enumerable.reduce(coll, { :cont, { [], nil } }, R.chunk_by(fun))
{_, {acc, res}} =
Enumerable.reduce(coll, {:cont, {[], nil}}, R.chunk_by(fun))
case res do
{ buffer, _ } ->
{buffer, _} ->
:lists.reverse([:lists.reverse(buffer) | acc])
nil ->
[]
@@ -420,11 +429,11 @@ defmodule Enum do
def count(collection) do
case Enumerable.count(collection) do
{ :ok, value } when is_integer(value) ->
{:ok, value} when is_integer(value) ->
value
{ :error, module } ->
module.reduce(collection, { :cont, 0 }, fn
_, acc -> { :cont, acc + 1 }
{:error, module} ->
module.reduce(collection, {:cont, 0}, fn
_, acc -> {:cont, acc + 1}
end) |> elem(1)
end
end
@@ -441,8 +450,8 @@ defmodule Enum do
"""
@spec count(t, (element -> as_boolean(term))) :: non_neg_integer
def count(collection, fun) do
Enumerable.reduce(collection, { :cont, 0 }, fn(entry, acc) ->
{ :cont, if(fun.(entry), do: acc + 1, else: acc) }
Enumerable.reduce(collection, {:cont, 0}, fn(entry, acc) ->
{:cont, if(fun.(entry), do: acc + 1, else: acc)}
end) |> elem(1)
end
@@ -503,8 +512,8 @@ defmodule Enum do
end
def drop_while(collection, fun) do
{ _, { res, _ } } =
Enumerable.reduce(collection, { :cont, { [], true } }, R.drop_while(fun))
{_, {res, _}} =
Enumerable.reduce(collection, {:cont, {[], true}}, R.drop_while(fun))
:lists.reverse(res)
end
@@ -552,12 +561,12 @@ defmodule Enum do
end
def empty?(collection) do
Enumerable.reduce(collection, { :cont, true }, fn(_, _) -> { :halt, false } end) |> elem(1)
Enumerable.reduce(collection, {:cont, true}, fn(_, _) -> {:halt, false} end) |> elem(1)
end
@doc """
Finds the element at the given index (zero-based).
Returns `{ :ok, element }` if found, otherwise `:error`.
Returns `{:ok, element}` if found, otherwise `:error`.
A negative index can be passed, which means the collection is
enumerated once and the index is counted from the end (i.e.
@@ -566,33 +575,33 @@ defmodule Enum do
## Examples
iex> Enum.fetch([2, 4, 6], 0)
{ :ok, 2 }
{:ok, 2}
iex> Enum.fetch([2, 4, 6], 2)
{ :ok, 6 }
{:ok, 6}
iex> Enum.fetch([2, 4, 6], 4)
:error
"""
@spec fetch(t, integer) :: { :ok, element } | :error
@spec fetch(t, integer) :: {:ok, element} | :error
def fetch(collection, n) when is_list(collection) and n >= 0 do
do_fetch(collection, n)
end
def fetch(collection, n) when n >= 0 do
res =
Enumerable.reduce(collection, { :cont, 0 }, fn(entry, acc) ->
Enumerable.reduce(collection, {:cont, 0}, fn(entry, acc) ->
if acc == n do
{ :halt, entry }
{:halt, entry}
else
{ :cont, acc + 1 }
{:cont, acc + 1}
end
end)
case res do
{ :halted, entry } -> { :ok, entry }
{ :done, _ } -> :error
{:halted, entry} -> {:ok, entry}
{:done, _} -> :error
end
end
@@ -620,7 +629,7 @@ defmodule Enum do
@spec fetch!(t, integer) :: element | no_return
def fetch!(collection, n) do
case fetch(collection, n) do
{ :ok, h } -> h
{:ok, h} -> h
:error -> raise Enum.OutOfBoundsError
end
end
@@ -641,7 +650,7 @@ defmodule Enum do
end
def filter(collection, fun) do
Enumerable.reduce(collection, { :cont, [] }, R.filter(fun))
Enumerable.reduce(collection, {:cont, []}, R.filter(fun))
|> elem(1) |> :lists.reverse
end
@@ -660,7 +669,7 @@ defmodule Enum do
end
def filter_map(collection, filter, mapper) do
Enumerable.reduce(collection, { :cont, [] }, R.filter_map(filter, mapper))
Enumerable.reduce(collection, {:cont, []}, R.filter_map(filter, mapper))
|> elem(1) |> :lists.reverse
end
@@ -689,8 +698,8 @@ defmodule Enum do
end
def find(collection, ifnone, fun) do
Enumerable.reduce(collection, { :cont, ifnone }, fn(entry, ifnone) ->
if fun.(entry), do: { :halt, entry }, else: { :cont, ifnone }
Enumerable.reduce(collection, {:cont, ifnone}, fn(entry, ifnone) ->
if fun.(entry), do: {:halt, entry}, else: {:cont, ifnone}
end) |> elem(1)
end
@@ -716,9 +725,9 @@ defmodule Enum do
end
def find_value(collection, ifnone, fun) do
Enumerable.reduce(collection, { :cont, ifnone }, fn(entry, ifnone) ->
Enumerable.reduce(collection, {:cont, ifnone}, fn(entry, ifnone) ->
fun_entry = fun.(entry)
if fun_entry, do: { :halt, fun_entry }, else: { :cont, ifnone }
if fun_entry, do: {:halt, fun_entry}, else: {:cont, ifnone}
end) |> elem(1)
end
@@ -742,13 +751,13 @@ defmodule Enum do
def find_index(collection, fun) do
res =
Enumerable.reduce(collection, { :cont, 0 }, fn(entry, acc) ->
if fun.(entry), do: { :halt, acc }, else: { :cont, acc + 1 }
Enumerable.reduce(collection, {:cont, 0}, fn(entry, acc) ->
if fun.(entry), do: {:halt, acc}, else: {:cont, acc + 1}
end)
case res do
{ :halted, entry } -> entry
{ :done, _ } -> nil
{:halted, entry} -> entry
{:done, _} -> nil
end
end
@@ -787,26 +796,26 @@ defmodule Enum do
iex> enum = 1..100
iex> n = 3
iex> Enum.flat_map_reduce(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)
{ [1,2,3], 3 }
{[1,2,3], 3}
"""
@spec flat_map_reduce(t, acc, fun) :: { [any], any } when
fun: (element, acc -> { t, acc } | { :halt, acc }),
@spec flat_map_reduce(t, acc, fun) :: {[any], any} when
fun: (element, acc -> {t, acc} | {:halt, acc}),
acc: any
def flat_map_reduce(collection, acc, fun) do
{ _, { list, acc } } =
Enumerable.reduce(collection, { :cont, { [], acc } }, fn(entry, { list, acc }) ->
{_, {list, acc}} =
Enumerable.reduce(collection, {:cont, {[], acc}}, fn(entry, {list, acc}) ->
case fun.(entry, acc) do
{ :halt, acc } ->
{ :halt, { list, acc } }
{ entries, acc } ->
{ :cont, { reduce(entries, list, &[&1|&2]), acc } }
{:halt, acc} ->
{:halt, {list, acc}}
{entries, acc} ->
{:cont, {reduce(entries, list, &[&1|&2]), acc}}
end
end)
{ :lists.reverse(list), acc }
{:lists.reverse(list), acc}
end
@doc """
@@ -856,14 +865,14 @@ defmodule Enum do
list ++ to_list(collection)
end
def into(collection, %{} = map) when is_list(collection) and (map_size(map) == 0) do
def into(collection, %{} = map) when is_list(collection) and map_size(map) == 0 do
:maps.from_list(collection)
end
def into(collection, collectable) do
{ initial, fun } = Collectable.into(collectable)
{initial, fun} = Collectable.into(collectable)
into(collection, initial, fun, fn x, acc ->
fun.(acc, { :cont, x })
fun.(acc, {:cont, x})
end)
end
@@ -884,9 +893,9 @@ defmodule Enum do
end
def into(collection, collectable, transform) when is_function(transform, 1) do
{ initial, fun } = Collectable.into(collectable)
{initial, fun} = Collectable.into(collectable)
into(collection, initial, fun, fn x, acc ->
fun.(acc, { :cont, transform.(x) })
fun.(acc, {:cont, transform.(x)})
end)
end
@@ -944,7 +953,7 @@ defmodule Enum do
iex> Enum.map([1, 2, 3], fn(x) -> x * 2 end)
[2, 4, 6]
iex> Enum.map([a: 1, b: 2], fn({k, v}) -> { k, -v } end)
iex> Enum.map([a: 1, b: 2], fn({k, v}) -> {k, -v} end)
[a: -1, b: -2]
"""
@@ -954,7 +963,7 @@ defmodule Enum do
end
def map(collection, fun) do
Enumerable.reduce(collection, { :cont, [] }, R.map(fun)) |> elem(1) |> :lists.reverse
Enumerable.reduce(collection, {:cont, []}, R.map(fun)) |> elem(1) |> :lists.reverse
end
@doc """
@@ -993,13 +1002,13 @@ defmodule Enum do
the first element is the mapped collection and the second
one is the final accumulator.
For dicts, the first tuple element must be a `{ key, value }`
For dicts, the first tuple element must be a `{key, value}`
tuple.
## Examples
iex> Enum.map_reduce([1, 2, 3], 0, fn(x, acc) -> { x * 2, x + acc } end)
{ [2, 4, 6], 6 }
iex> Enum.map_reduce([1, 2, 3], 0, fn(x, acc) -> {x * 2, x + acc} end)
{[2, 4, 6], 6}
"""
@spec map_reduce(t, any, (element, any -> any)) :: any
@@ -1008,11 +1017,11 @@ defmodule Enum do
end
def map_reduce(collection, acc, fun) do
{ list, acc } = reduce(collection, { [], acc }, fn(entry, { list, acc }) ->
{ new_entry, acc } = fun.(entry, acc)
{ [new_entry|list], acc }
{list, acc} = reduce(collection, {[], acc}, fn(entry, {list, acc}) ->
{new_entry, acc} = fun.(entry, acc)
{[new_entry|list], acc}
end)
{ :lists.reverse(list), acc }
{:lists.reverse(list), acc}
end
@doc """
@@ -1042,9 +1051,9 @@ defmodule Enum do
"""
@spec max_by(t, (element -> any)) :: element | no_return
def max_by([h|t], fun) do
reduce(t, { h, fun.(h) }, fn(entry, { _, fun_max } = old) ->
reduce(t, {h, fun.(h)}, fn(entry, {_, fun_max} = old) ->
fun_entry = fun.(entry)
if(fun_entry > fun_max, do: { entry, fun_entry }, else: old)
if(fun_entry > fun_max, do: {entry, fun_entry}, else: old)
end) |> elem(0)
end
@@ -1055,16 +1064,16 @@ defmodule Enum do
def max_by(collection, fun) do
result =
reduce(collection, :first, fn
entry, { _, fun_max } = old ->
entry, {_, fun_max} = old ->
fun_entry = fun.(entry)
if(fun_entry > fun_max, do: { entry, fun_entry }, else: old)
if(fun_entry > fun_max, do: {entry, fun_entry}, else: old)
entry, :first ->
{ entry, fun.(entry) }
{entry, fun.(entry)}
end)
case result do
:first -> raise Enum.EmptyError
{ entry, _ } -> entry
{entry, _} -> entry
end
end
@@ -1091,12 +1100,12 @@ defmodule Enum do
def member?(collection, value) do
case Enumerable.member?(collection, value) do
{ :ok, value } when is_boolean(value) ->
{:ok, value} when is_boolean(value) ->
value
{ :error, module } ->
module.reduce(collection, { :cont, false }, fn
v, _ when v === value -> { :halt, true }
_, _ -> { :cont, false }
{:error, module} ->
module.reduce(collection, {:cont, false}, fn
v, _ when v === value -> {:halt, true}
_, _ -> {:cont, false}
end) |> elem(1)
end
end
@@ -1128,9 +1137,9 @@ defmodule Enum do
"""
@spec min_by(t, (element -> any)) :: element | no_return
def min_by([h|t], fun) do
reduce(t, { h, fun.(h) }, fn(entry, { _, fun_min } = old) ->
reduce(t, {h, fun.(h)}, fn(entry, {_, fun_min} = old) ->
fun_entry = fun.(entry)
if(fun_entry < fun_min, do: { entry, fun_entry }, else: old)
if(fun_entry < fun_min, do: {entry, fun_entry}, else: old)
end) |> elem(0)
end
@@ -1141,16 +1150,16 @@ defmodule Enum do
def min_by(collection, fun) do
result =
reduce(collection, :first, fn
entry, { _, fun_min } = old ->
entry, {_, fun_min} = old ->
fun_entry = fun.(entry)
if(fun_entry < fun_min, do: { entry, fun_entry }, else: old)
if(fun_entry < fun_min, do: {entry, fun_entry}, else: old)
entry, :first ->
{ entry, fun.(entry) }
{entry, fun.(entry)}
end)
case result do
:first -> raise Enum.EmptyError
{ entry, _ } -> entry
{entry, _} -> entry
end
end
@@ -1178,21 +1187,21 @@ defmodule Enum do
## Examples
iex> Enum.partition([1, 2, 3], fn(x) -> rem(x, 2) == 0 end)
{ [2], [1,3] }
{[2], [1,3]}
"""
@spec partition(t, (element -> any)) :: {list, list}
def partition(collection, fun) do
{ acc1, acc2 } =
reduce(collection, { [], [] }, fn(entry, { acc1, acc2 }) ->
{acc1, acc2} =
reduce(collection, {[], []}, fn(entry, {acc1, acc2}) ->
if fun.(entry) do
{ [entry|acc1], acc2 }
{[entry|acc1], acc2}
else
{ acc1, [entry|acc2] }
{acc1, [entry|acc2]}
end
end)
{ :lists.reverse(acc1), :lists.reverse(acc2) }
{:lists.reverse(acc1), :lists.reverse(acc2)}
end
@doc """
@@ -1206,10 +1215,10 @@ defmodule Enum do
## Examples
iex> Enum.group_by(~w{ant buffalo cat dingo}, &String.length/1)
%{ 3 => ["cat", "ant"], 7 => ["buffalo"], 5 => ["dingo"] }
%{3 => ["cat", "ant"], 7 => ["buffalo"], 5 => ["dingo"]}
"""
@spec group_by(t, (element -> any)) :: HashDict
@spec group_by(t, dict, (element -> any)) :: dict when dict: Dict.t
def group_by(collection, dict \\ %{}, fun) do
reduce(collection, dict, fn(entry, categories) ->
Dict.update(categories, fun.(entry), [entry], &[entry|&1])
@@ -1233,8 +1242,8 @@ defmodule Enum do
end
def reduce(collection, acc, fun) do
Enumerable.reduce(collection, { :cont, acc },
fn x, acc -> { :cont, fun.(x, acc) } end) |> elem(1)
Enumerable.reduce(collection, {:cont, acc},
fn x, acc -> {:cont, fun.(x, acc)} end) |> elem(1)
end
@doc """
@@ -1260,16 +1269,16 @@ defmodule Enum do
def reduce(collection, fun) do
result =
Enumerable.reduce(collection, { :cont, :first }, fn
Enumerable.reduce(collection, {:cont, :first}, fn
x, :first ->
{ :cont, { :acc, x } }
x, { :acc, acc } ->
{ :cont, { :acc, fun.(x, acc) } }
{:cont, {:acc, x}}
x, {:acc, acc} ->
{:cont, {:acc, fun.(x, acc)}}
end) |> elem(1)
case result do
:first -> raise Enum.EmptyError
{ :acc, acc } -> acc
{:acc, acc} -> acc
end
end
@@ -1288,7 +1297,7 @@ defmodule Enum do
end
def reject(collection, fun) do
Enumerable.reduce(collection, { :cont, [] }, R.reject(fun)) |> elem(1) |> :lists.reverse
Enumerable.reduce(collection, {:cont, []}, R.reject(fun)) |> elem(1) |> :lists.reverse
end
@doc """
@@ -1344,8 +1353,8 @@ defmodule Enum do
"""
@spec scan(t, (element, any -> any)) :: list
def scan(enum, fun) do
{ _, { res, _ } } =
Enumerable.reduce(enum, { :cont, { [], :first } }, R.scan_2(fun))
{_, {res, _}} =
Enumerable.reduce(enum, {:cont, {[], :first}}, R.scan_2(fun))
:lists.reverse(res)
end
@@ -1362,8 +1371,8 @@ defmodule Enum do
"""
@spec scan(t, any, (element, any -> any)) :: list
def scan(enum, acc, fun) do
{ _, { res, _ } } =
Enumerable.reduce(enum, { :cont, { [], acc } }, R.scan_3(fun))
{_, {res, _}} =
Enumerable.reduce(enum, {:cont, {[], acc}}, R.scan_3(fun))
:lists.reverse(res)
end
@@ -1389,7 +1398,7 @@ defmodule Enum do
@spec shuffle(t) :: list
def shuffle(collection) do
randomized = reduce(collection, [], fn x, acc ->
[{ :random.uniform, x }|acc]
[{:random.uniform, x}|acc]
end)
unwrap(:lists.keysort(1, randomized), [])
end
@@ -1407,7 +1416,7 @@ defmodule Enum do
@spec slice(t, integer, non_neg_integer) :: list
def slice(coll, start, count) when start < 0 do
{ list, new_start } = enumerate_and_count(coll, start)
{list, new_start} = enumerate_and_count(coll, start)
if new_start >= 0, do: slice(list, new_start, count)
end
@@ -1416,13 +1425,13 @@ defmodule Enum do
end
def slice(coll, start, count) when start >= 0 and count > 0 do
{ start, _, list } = Enumerable.reduce(coll, { :cont, { start, count, [] } }, fn
_entry, { start, count, _list } when start > 0 ->
{ :cont, { start-1, count, [] } }
entry, { start, count, list } when count > 1 ->
{ :cont, { start, count-1, [entry|list] } }
entry, { start, count, list } ->
{ :halt, { start, count, [entry|list] } }
{start, _, list} = Enumerable.reduce(coll, {:cont, {start, count, []}}, fn
_entry, {start, count, _list} when start > 0 ->
{:cont, {start-1, count, []}}
entry, {start, count, list} when count > 1 ->
{:cont, {start, count-1, [entry|list]}}
entry, {start, count, list} ->
{:halt, {start, count, [entry|list]}}
end) |> elem(1)
if start <= 0, do: :lists.reverse(list)
@@ -1430,8 +1439,8 @@ defmodule Enum do
def slice(coll, start, 0) do
res =
Enumerable.reduce(coll, { :cont, start }, fn _, start ->
if start > 0, do: { :cont, start-1 }, else: { :halt, [] }
Enumerable.reduce(coll, {:cont, start}, fn _, start ->
if start > 0, do: {:cont, start-1}, else: {:halt, []}
end) |> elem(1)
if is_list(res), do: res
end
@@ -1463,7 +1472,7 @@ defmodule Enum do
end
def slice(coll, first..last) do
{ list, count } = enumerate_and_count(coll, 0)
{list, count} = enumerate_and_count(coll, 0)
corr_first = if first >= 0, do: first, else: first + count
corr_last = if last >= 0, do: last, else: last + count
length = corr_last - corr_first + 1
@@ -1538,19 +1547,19 @@ defmodule Enum do
## Examples
iex> Enum.split([1, 2, 3], 2)
{ [1,2], [3] }
{[1,2], [3]}
iex> Enum.split([1, 2, 3], 10)
{ [1,2,3], [] }
{[1,2,3], []}
iex> Enum.split([1, 2, 3], 0)
{ [], [1,2,3] }
{[], [1,2,3]}
iex> Enum.split([1, 2, 3], -1)
{ [1,2], [3] }
{[1,2], [3]}
iex> Enum.split([1, 2, 3], -5)
{ [], [1,2,3] }
{[], [1,2,3]}
"""
@spec split(t, integer) :: {list, list}
@@ -1559,16 +1568,16 @@ defmodule Enum do
end
def split(collection, count) when count >= 0 do
{ _, list1, list2 } =
reduce(collection, { count, [], [] }, fn(entry, { counter, acc1, acc2 }) ->
{_, list1, list2} =
reduce(collection, {count, [], []}, fn(entry, {counter, acc1, acc2}) ->
if counter > 0 do
{ counter - 1, [entry|acc1], acc2 }
{counter - 1, [entry|acc1], acc2}
else
{ counter, acc1, [entry|acc2] }
{counter, acc1, [entry|acc2]}
end
end)
{ :lists.reverse(list1), :lists.reverse(list2) }
{:lists.reverse(list1), :lists.reverse(list2)}
end
def split(collection, count) when count < 0 do
@@ -1581,7 +1590,7 @@ defmodule Enum do
## Examples
iex> Enum.split_while([1, 2, 3, 4], fn(x) -> x < 3 end)
{ [1, 2], [3, 4] }
{[1, 2], [3, 4]}
"""
@spec split_while(t, (element -> as_boolean(term))) :: {list, list}
@@ -1590,15 +1599,15 @@ defmodule Enum do
end
def split_while(collection, fun) do
{ list1, list2 } =
reduce(collection, { [], [] }, fn
entry, { acc1, [] } ->
if(fun.(entry), do: { [entry|acc1], [] }, else: { acc1, [entry] })
entry, { acc1, acc2 } ->
{ acc1, [entry|acc2] }
{list1, list2} =
reduce(collection, {[], []}, fn
entry, {acc1, []} ->
if(fun.(entry), do: {[entry|acc1], []}, else: {acc1, [entry]})
entry, {acc1, acc2} ->
{acc1, [entry|acc2]}
end)
{ :lists.reverse(list1), :lists.reverse(list2) }
{:lists.reverse(list1), :lists.reverse(list2)}
end
@doc """
@@ -1635,19 +1644,19 @@ defmodule Enum do
end
def take(collection, count) when count > 0 do
{ _, { res, _ } } =
Enumerable.reduce(collection, { :cont, { [], count } }, fn(entry, { list, count }) ->
{_, {res, _}} =
Enumerable.reduce(collection, {:cont, {[], count}}, fn(entry, {list, count}) ->
if count > 1 do
{ :cont, { [entry|list], count - 1 } }
{:cont, {[entry|list], count - 1}}
else
{ :halt, { [entry|list], count } }
{:halt, {[entry|list], count}}
end
end)
:lists.reverse(res)
end
def take(collection, count) when count < 0 do
Stream.take(collection, count).({ :cont, [] }, &{ :cont, [&1|&2] })
Stream.take(collection, count).({:cont, []}, &{:cont, [&1|&2]})
|> elem(1) |> :lists.reverse
end
@@ -1664,8 +1673,8 @@ defmodule Enum do
@spec take_every(t, integer) :: list
def take_every(_collection, 0), do: []
def take_every(collection, nth) do
{ _, { res, _ } } =
Enumerable.reduce(collection, { :cont, { [], :first } }, R.take_every(nth))
{_, {res, _}} =
Enumerable.reduce(collection, {:cont, {[], :first}}, R.take_every(nth))
:lists.reverse(res)
end
@@ -1684,7 +1693,7 @@ defmodule Enum do
end
def take_while(collection, fun) do
Enumerable.reduce(collection, { :cont, [] }, R.take_while(fun))
Enumerable.reduce(collection, {:cont, []}, R.take_while(fun))
|> elem(1) |> :lists.reverse
end
@@ -1714,7 +1723,7 @@ defmodule Enum do
## Examples
iex> Enum.traverse(%{a: 1, b: 2}, fn { k, v } -> { k, v * 2 } end)
iex> Enum.traverse(%{a: 1, b: 2}, fn {k, v} -> {k, v * 2} end)
%{a: 2, b: 4}
"""
@@ -1748,8 +1757,8 @@ defmodule Enum do
end
def uniq(collection, fun) do
{ _, { list, _ } } =
Enumerable.reduce(collection, { :cont, { [], [] } }, R.uniq(fun))
{_, {list, _}} =
Enumerable.reduce(collection, {:cont, {[], []}}, R.uniq(fun))
:lists.reverse(list)
end
@@ -1774,7 +1783,7 @@ defmodule Enum do
end
def zip(coll1, coll2) do
Stream.zip(coll1, coll2).({ :cont, [] }, &{ :cont, [&1|&2] }) |> elem(1) |> :lists.reverse
Stream.zip(coll1, coll2).({:cont, []}, &{:cont, [&1|&2]}) |> elem(1) |> :lists.reverse
end
@doc """
@@ -1787,23 +1796,23 @@ defmodule Enum do
[{1,0},{2,1},{3,2}]
"""
@spec with_index(t) :: list({ element, non_neg_integer })
@spec with_index(t) :: list({element, non_neg_integer})
def with_index(collection) do
map_reduce(collection, 0, fn x, acc ->
{ { x, acc }, acc + 1 }
{{x, acc}, acc + 1}
end) |> elem(0)
end
## Helpers
@compile { :inline, to_string: 2 }
@compile {:inline, to_string: 2}
defp enumerate_and_count(collection, count) when is_list(collection) do
{ collection, length(collection) - abs(count) }
{collection, length(collection) - abs(count)}
end
defp enumerate_and_count(collection, count) do
map_reduce(collection, -abs(count), fn(x, acc) -> { x, acc + 1 } end)
map_reduce(collection, -abs(count), fn(x, acc) -> {x, acc + 1} end)
end
defp to_string(mapper, entry) do
@@ -1845,7 +1854,7 @@ defmodule Enum do
## fetch
defp do_fetch([h|_], 0), do: { :ok, h }
defp do_fetch([h|_], 0), do: {:ok, h}
defp do_fetch([_|t], n), do: do_fetch(t, n - 1)
defp do_fetch([], _), do: :error
@@ -1925,45 +1934,45 @@ defmodule Enum do
## sort
defp sort_reducer(entry, { :split, y, x, r, rs, bool }, fun) do
defp sort_reducer(entry, {:split, y, x, r, rs, bool}, fun) do
cond do
fun.(y, entry) == bool ->
{ :split, entry, y, [x|r], rs, bool }
{:split, entry, y, [x|r], rs, bool}
fun.(x, entry) == bool ->
{ :split, y, entry, [x|r], rs, bool }
{:split, y, entry, [x|r], rs, bool}
r == [] ->
{ :split, y, x, [entry], rs, bool }
{:split, y, x, [entry], rs, bool}
true ->
{ :pivot, y, x, r, rs, entry, bool }
{:pivot, y, x, r, rs, entry, bool}
end
end
defp sort_reducer(entry, { :pivot, y, x, r, rs, s, bool }, fun) do
defp sort_reducer(entry, {:pivot, y, x, r, rs, s, bool}, fun) do
cond do
fun.(y, entry) == bool ->
{ :pivot, entry, y, [x | r], rs, s, bool }
{:pivot, entry, y, [x | r], rs, s, bool}
fun.(x, entry) == bool ->
{ :pivot, y, entry, [x | r], rs, s, bool }
{:pivot, y, entry, [x | r], rs, s, bool}
fun.(s, entry) == bool ->
{ :split, entry, s, [], [[y, x | r] | rs], bool }
{:split, entry, s, [], [[y, x | r] | rs], bool}
true ->
{ :split, s, entry, [], [[y, x | r] | rs], bool }
{:split, s, entry, [], [[y, x | r] | rs], bool}
end
end
defp sort_reducer(entry, [x], fun) do
{ :split, entry, x, [], [], fun.(x, entry) }
{:split, entry, x, [], [], fun.(x, entry)}
end
defp sort_reducer(entry, acc, _fun) do
[entry|acc]
end
defp sort_terminator({ :split, y, x, r, rs, bool }, fun) do
defp sort_terminator({:split, y, x, r, rs, bool}, fun) do
sort_merge([[y, x | r] | rs], fun, bool)
end
defp sort_terminator({ :pivot, y, x, r, rs, s, bool }, fun) do
defp sort_terminator({:pivot, y, x, r, rs, s, bool}, fun) do
sort_merge([[s], [y, x | r] | rs], fun, bool)
end
@@ -2036,11 +2045,11 @@ defmodule Enum do
end
defp do_split(list, 0, acc) do
{ :lists.reverse(acc), list }
{:lists.reverse(acc), list}
end
defp do_split([], _, acc) do
{ :lists.reverse(acc), [] }
{:lists.reverse(acc), []}
end
defp do_split_reverse([h|t], counter, acc) when counter > 0 do
@@ -2048,11 +2057,11 @@ defmodule Enum do
end
defp do_split_reverse(list, 0, acc) do
{ :lists.reverse(list), acc }
{:lists.reverse(list), acc}
end
defp do_split_reverse([], _, acc) do
{ [], acc }
{[], acc}
end
## split_while
@@ -2061,12 +2070,12 @@ defmodule Enum do
if fun.(h) do
do_split_while(t, fun, [h|acc])
else
{ :lists.reverse(acc), [h|t] }
{:lists.reverse(acc), [h|t]}
end
end
defp do_split_while([], _, acc) do
{ :lists.reverse(acc), [] }
{:lists.reverse(acc), []}
end
## take
@@ -2114,7 +2123,7 @@ defmodule Enum do
## zip
defp do_zip([h1|next1], [h2|next2]) do
[{ h1, h2 }|do_zip(next1, next2)]
[{h1, h2}|do_zip(next1, next2)]
end
defp do_zip(_, []), do: []
@@ -2140,15 +2149,15 @@ defmodule Enum do
end
defimpl Enumerable, for: List do
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 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 member?(_list, _value),
do: { :error, __MODULE__ }
do: {:error, __MODULE__}
def count(_list),
do: { :error, __MODULE__ }
do: {:error, __MODULE__}
end
defimpl Enumerable, for: Map do
@@ -2156,21 +2165,21 @@ defimpl Enumerable, for: Map do
do_reduce(:maps.to_list(map), acc, fun)
end
defp do_reduce(_, { :halt, acc }, _fun), do: { :halted, acc }
defp do_reduce(list, { :suspend, acc }, fun), do: { :suspended, acc, &do_reduce(list, &1, fun) }
defp do_reduce([], { :cont, acc }, _fun), do: { :done, acc }
defp do_reduce([h|t], { :cont, acc }, fun), do: do_reduce(t, fun.(h, acc), fun)
defp do_reduce(_, {:halt, acc}, _fun), do: {:halted, acc}
defp do_reduce(list, {:suspend, acc}, fun), do: {:suspended, acc, &do_reduce(list, &1, fun)}
defp do_reduce([], {:cont, acc}, _fun), do: {:done, acc}
defp do_reduce([h|t], {:cont, acc}, fun), do: do_reduce(t, fun.(h, acc), fun)
def member?(map, { key, value }) do
{ :ok, match?({ :ok, ^value }, :maps.find(key, map)) }
def member?(map, {key, value}) do
{:ok, match?({:ok, ^value}, :maps.find(key, map))}
end
def member?(_map, _other) do
{ :ok, false }
{:ok, false}
end
def count(map) do
{ :ok, map_size(map) }
{:ok, map_size(map)}
end
end
@@ -2178,7 +2187,7 @@ defimpl Enumerable, for: Function do
def reduce(function, acc, fun),
do: function.(acc, fun)
def member?(_function, _value),
do: { :error, __MODULE__ }
do: {:error, __MODULE__}
def count(_function),
do: { :error, __MODULE__ }
do: {:error, __MODULE__}
end
+329 -51
View File
@@ -63,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
@@ -117,20 +117,37 @@ defexception KeyError, key: nil, term: nil do
end
end
defexception UnicodeConversionError, [:encoded, :message] do
def exception(opts) do
UnicodeConversionError[
encoded: opts[:encoded],
message: "#{opts[:kind]} #{detail(opts[:rest])}"
]
end
defp detail(rest) when is_binary(rest) do
"encoding starting at #{inspect rest}"
end
defp detail([h|_]) do
"code point #{h}"
end
end
defexception Enum.OutOfBoundsError, message: "out of bounds error"
defexception Enum.EmptyError, message: "empty error"
defexception File.Error, [reason: nil, action: "", path: nil] do
def message(exception) do
formatted = 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}"
@@ -154,15 +171,39 @@ defmodule Exception do
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.
@@ -170,74 +211,311 @@ defmodule Exception do
It takes the `kind` spilled by `catch` as an argument and
normalizes only `:error`, returning the untouched payload
for others.
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(:error, exception), do: normalize_error(exception)
def normalize(_kind, payload), do: payload
@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)
@doc false
def normalize(error) do
IO.write :stderr, "Exception.normalize/1 is deprecated, please use Exception.normalize/2 instead\n#{Exception.format_stacktrace}"
normalize_error(error)
def normalize(:error, exception, stacktrace) do
if exception?(exception) do
exception
else
normalize_error(exception, stacktrace)
end
end
defp normalize_error(exception) when is_exception(exception) do
exception
def normalize(_kind, payload, _stacktrace) do
payload
end
defp normalize_error(:badarg) do
defp normalize_error(:badarg, _stacktrace) do
ArgumentError[]
end
defp normalize_error(:badarith) do
defp normalize_error(:badarith, _stacktrace) do
ArithmeticError[]
end
defp normalize_error(:system_limit) do
defp normalize_error(:system_limit, _stacktrace) do
SystemLimitError[]
end
defp normalize_error({ :badarity, { fun, args } }) do
defp normalize_error({:badarity, {fun, args}}, _stacktrace) do
BadArityError[function: fun, args: args]
end
defp normalize_error({ :badfun, term }) do
defp normalize_error({:badfun, term}, _stacktrace) do
BadFunctionError[term: term]
end
defp normalize_error({ :badstruct, struct, term }) do
defp normalize_error({:badstruct, struct, term}, _stacktrace) do
BadStructError[struct: struct, term: term]
end
defp normalize_error({ :badmatch, term }) do
defp normalize_error({:badmatch, term}, _stacktrace) do
MatchError[term: term]
end
defp normalize_error({ :case_clause, term }) do
defp normalize_error({:case_clause, term}, _stacktrace) do
CaseClauseError[term: term]
end
defp normalize_error({ :try_clause, term }) do
defp normalize_error({:try_clause, term}, _stacktrace) do
TryClauseError[term: term]
end
defp normalize_error(:undef) do
{ mod, fun, arity } = from_stacktrace(:erlang.get_stacktrace)
defp normalize_error(:undef, stacktrace) do
{mod, fun, arity} = from_stacktrace(stacktrace || :erlang.get_stacktrace)
UndefinedFunctionError[module: mod, function: fun, arity: arity]
end
defp normalize_error(:function_clause) 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
defp normalize_error({ :badarg, payload }) do
defp normalize_error({:badarg, payload}, _stacktrace) do
ArgumentError[message: "argument error: #{inspect(payload)}"]
end
defp normalize_error(other) do
defp normalize_error(other, _stacktrace) do
ErlangError[original: other]
end
@doc """
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.
"""
@@ -245,31 +523,31 @@ defmodule Exception do
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
def format_stacktrace_entry({ module, fun, arity, location }) do
def format_stacktrace_entry({module, fun, arity, location}) do
format_application(module) <> format_location(location) <> format_mfa(module, fun, arity)
end
def format_stacktrace_entry({ fun, arity, location }) do
def format_stacktrace_entry({fun, arity, location}) do
format_location(location) <> format_fa(fun, arity)
end
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
@@ -277,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"
@@ -303,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
@@ -327,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
@@ -347,7 +623,9 @@ defmodule Exception do
"(#{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.
@@ -381,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
+320 -196
View File
File diff suppressed because it is too large Load Diff
+9 -9
View File
@@ -6,7 +6,7 @@ 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
@@ -24,11 +24,11 @@ defmodule Float do
: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
@@ -40,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
@@ -50,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
@@ -65,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
+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,
+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,
+44 -63
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,119 +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 false
@spec new(Enum.t) :: Dict.t
def new(enum) do
IO.write :stderr, "HashDict.new/1 is deprecated, please use Enum.into/2 instead\n#{Exception.format_stacktrace}"
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 false
@spec new(Enum.t, (term -> {key :: term, value ::term})) :: Dict.t
def new(enum, transform) when is_function(transform) do
IO.write :stderr, "HashDict.new/2 is deprecated, please use Enum.into/3 instead\n#{Exception.format_stacktrace}"
Enum.reduce enum, trie(), fn i, dict ->
{ k, v } = transform.(i)
put(dict, k, v)
end
end
def empty(trie()) do
IO.write :stderr, "HashDict.empty/1 is deprecated, please use Collectable.empty/1 instead\n#{Exception.format_stacktrace}"
trie()
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) = dict, key, fun) when is_function(fun, 1) do
{ root, counter } = do_update(root, key, fn -> raise KeyError, key: key, term: dict 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))
trie(root: root, size: size + counter)
%HashDict{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)
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 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
@doc false
def reduce(trie(root: root), acc, fun) do
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
@@ -266,9 +247,9 @@ 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
@@ -281,10 +262,10 @@ defimpl Collectable, for: HashDict do
end
def into(original) do
{ original, fn
dict, { :cont, { k, v } } -> Dict.put(dict, k, v)
{original, fn
dict, {:cont, {k, v}} -> Dict.put(dict, k, v)
dict, :done -> dict
_, :halt -> :ok
end }
end}
end
end
+38 -56
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,55 +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 false
@spec new(Enum.t) :: Set.t
def new(enum) do
IO.write :stderr, "HashSet.new/1 is deprecated, please use Enum.into/2 instead\n#{Exception.format_stacktrace}"
Enum.reduce enum, trie(), fn i, set ->
put(set, i)
end
end
@doc false
@spec new(Enum.t, (term -> term)) :: Set.t
def new(enum, transform) when is_function(transform) do
IO.write :stderr, "HashSet.new/2 is deprecated, please use Enum.into/3 instead\n#{Exception.format_stacktrace}"
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
@@ -71,68 +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
IO.write :stderr, "HashSet.empty/1 is deprecated, please use Collectable.empty/1 instead\n#{Exception.format_stacktrace}"
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
@doc false
def reduce(trie(root: root), acc, fun) do
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
@@ -265,8 +247,8 @@ 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
@@ -275,10 +257,10 @@ defimpl Collectable, for: HashSet do
end
def into(original) do
{ original, fn
set, { :cont, x } -> HashSet.put(set, x)
{original, fn
set, {:cont, x} -> HashSet.put(set, x)
set, :done -> set
_, :halt -> :ok
end }
end}
end
end
+57 -125
View File
@@ -1,15 +1,6 @@
import Kernel, except: [inspect: 1]
import Inspect.Algebra
defrecord Inspect.Opts,
records: true,
structs: 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
@@ -50,10 +41,11 @@ 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)
@@ -68,26 +60,6 @@ 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
@@ -96,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
@@ -116,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
@@ -161,24 +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
@@ -201,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 >>)
@@ -258,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
@@ -310,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 ->
@@ -328,14 +290,7 @@ defimpl Inspect, for: List do
)
end
defp key_to_binary(key) do
case Inspect.Atom.inspect(key) do
":" <> right -> right
other -> other
end
end
def keyword?([{ key, _value } | rest]) when is_atom(key) do
def keyword?([{key, _value} | rest]) when is_atom(key) do
case atom_to_list(key) do
'Elixir.' ++ _ -> false
_ -> keyword?(rest)
@@ -344,26 +299,33 @@ defimpl Inspect, for: List do
def keyword?([]), do: true
def keyword?(_other), do: false
## Private
defp key_to_binary(key) do
case Inspect.Atom.inspect(key) do
":" <> right -> right
other -> other
end
end
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
@@ -373,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
@@ -395,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
@@ -413,7 +375,7 @@ 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
@@ -445,51 +407,20 @@ defimpl Inspect, for: Map do
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) == 4 do
concat ["~r", to_doc(Regex.source(regex), opts), Regex.opts(regex)]
def inspect(regex, opts) do
concat ["~r", to_doc(regex.source, opts), regex.opts]
end
end
@@ -524,7 +455,8 @@ defimpl Inspect, for: Function do
end
defp extract_name(name) do
case :binary.split(atom_to_binary(name), "-", [:global]) do
name = atom_to_binary(name)
case :binary.split(name, "-", [:global]) do
["", name | _] -> "." <> name
_ -> "." <> name
end
@@ -538,20 +470,20 @@ 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
+77 -28
View File
@@ -1,3 +1,41 @@
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"""
A set of functions for creating and manipulating algebra
@@ -74,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
@@ -106,39 +154,40 @@ 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(arg) and is_record(opts, Inspect.Opts) do
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)
IO.puts :stderr, "** (Inspect.Error) Got #{inspect e.__record__(:name)} with message " <>
"#{e.message} while inspecting #{pretty(res, opts.width)}"
res
raise ArgumentError, message:
"Got #{inspect e.__record__(:name)} with message " <>
"#{Exception.message(e)} while inspecting #{pretty(res, opts.width)}"
end
else
Inspect.Tuple.inspect(arg, opts)
end
end
def to_doc(%{ __struct__: struct } = map, opts) when is_atom(struct) and is_record(opts, Inspect.Opts) do
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)
IO.puts :stderr, "** (Inspect.Error) Got #{inspect e.__record__(:name)} with message " <>
"#{e.message} while inspecting #{pretty(res, opts.width)}"
res
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, opts) when is_record(opts, Inspect.Opts) do
def to_doc(arg, %Inspect.Opts{} = opts) do
Inspect.inspect(arg, opts)
end
@@ -166,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
@@ -174,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
@@ -190,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
@@ -220,23 +269,23 @@ 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"""
@@ -266,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
@@ -281,7 +330,7 @@ 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"""
@@ -294,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 """
@@ -415,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
@@ -428,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)
@@ -453,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
+4 -4
View File
@@ -28,7 +28,7 @@ defmodule Integer do
@doc """
Converts a binary to an integer.
If successful, returns a tuple of the form `{ integer, remainder_of_binary }`.
If successful, returns a tuple of the form `{integer, remainder_of_binary}`.
Otherwise `:error`.
## Examples
@@ -43,11 +43,11 @@ defmodule Integer do
: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
@@ -67,6 +67,6 @@ defmodule Integer do
end
defp do_parse(bitstring, acc) do
{ acc, bitstring }
{acc, bitstring}
end
end
+64 -56
View File
@@ -1,7 +1,7 @@
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
@@ -25,13 +25,13 @@ defmodule IO.Stream do
stream
end
def into(%{ device: device, raw: raw } = stream) do
{ :ok, into(stream, device, raw) }
def into(%{device: device, raw: raw} = stream) do
{:ok, into(stream, device, raw)}
end
defp into(stream, device, raw) do
fn
:ok, { :cont, x } ->
:ok, {:cont, x} ->
case raw do
true -> IO.binwrite(device, x)
false -> IO.write(device, x)
@@ -42,7 +42,7 @@ defmodule IO.Stream do
end
defimpl Enumerable do
def reduce(%{ device: device, raw: raw, line_or_bytes: line_or_bytes }, acc, fun) 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)
@@ -52,11 +52,11 @@ defmodule IO.Stream do
end
def count(_stream) do
{ :error, __MODULE__ }
{:error, __MODULE__}
end
def member?(_stream, _term) do
{ :error, __MODULE__ }
{:error, __MODULE__}
end
end
end
@@ -70,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
@@ -103,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
@@ -131,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
@@ -160,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 """
@@ -171,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
@@ -211,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 } -> [width: width] ++ opts
{ :error, _ } -> opts
end
end
puts device, Kernel.inspect(item, opts)
item
end
@@ -238,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
@@ -256,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 """
@@ -273,9 +279,9 @@ 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 """
@@ -304,7 +310,7 @@ defmodule IO do
"""
@spec stream(device, :line | pos_integer) :: Enumerable.t
def stream(device, line_or_codepoints) do
%IO.Stream{ device: map_dev(device), raw: false, line_or_bytes: line_or_codepoints }
%IO.Stream{device: map_dev(device), raw: false, line_or_bytes: line_or_codepoints}
end
@doc """
@@ -322,7 +328,7 @@ defmodule IO do
"""
@spec binstream(device, :line | pos_integer) :: Enumerable.t
def binstream(device, line_or_bytes) do
%IO.Stream{ device: map_dev(device), raw: true, line_or_bytes: line_or_bytes }
%IO.Stream{device: map_dev(device), raw: true, line_or_bytes: line_or_bytes}
end
@doc false
@@ -330,10 +336,10 @@ defmodule IO do
case read(device, what) do
:eof ->
nil
{ :error, reason } ->
{:error, reason} ->
raise IO.StreamError, reason: reason
data ->
{ data, device }
{data, device}
end
end
@@ -342,18 +348,20 @@ defmodule IO 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) 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
+45 -40
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
@@ -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"
@@ -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)
for { code, color } <- colors do
for {code, color} <- colors do
@doc "Sets foreground color to #{color}"
defsequence color, code + 30
@@ -127,15 +130,8 @@ 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"""
@@ -160,8 +156,8 @@ 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
@@ -190,34 +186,43 @@ defmodule IO.ANSI do
"""
@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
+133 -134
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)
|> handle_inline(nil, [], [], options)
|> String.split(~r{\s})
|> write_with_wrap(column_width() - size(indent), indent, from_list)
|> 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
@@ -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
+257 -219
View File
File diff suppressed because it is too large Load Diff
+58 -67
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 = for arg <- 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 = for { :error, msg } <- 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
@@ -65,7 +65,7 @@ defmodule Kernel.CLI 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,23 +124,23 @@ 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
@@ -162,13 +152,13 @@ defmodule Kernel.CLI do
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(["+elixirc"|t], config) do
@@ -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.ensure_all_started(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
_ ->
+5 -5
View File
@@ -25,15 +25,15 @@ defmodule Kernel.ErrorHandler do
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, module, self(), ref, module }
send parent, {:waiting, module, self(), ref, module}
:erlang.garbage_collect(self)
receive do
{ ^ref, :ready } -> :ok
{ ^ref, :release } -> release()
{^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
+87 -56
View File
@@ -43,16 +43,16 @@ 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
@@ -64,8 +64,8 @@ defmodule Kernel.ParallelCompiler do
# Release waiting processes
defp spawn_compilers([h|t], original, output, callbacks, waiting, queued, schedulers, result) when is_pid(h) do
{ _kind, ^h, ref, _module } = List.keyfind(waiting, h, 1)
send h, { ref, :ready }
{_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,27 +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)
[{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
for { :module, mod } <- result, do: mod
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, _, _ } ->
{ _kind, ^child, ref, _module } = List.keyfind(waiting, child, 1)
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
@@ -123,44 +123,44 @@ defmodule Kernel.ParallelCompiler do
# Wait for messages from child processes
defp wait_for_messages(entries, original, output, callbacks, waiting, queued, schedulers, result) do
receive do
{ :struct_available, module } ->
available = for { :struct, pid, _, waiting_module } <- waiting,
{: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])
waiting, queued, schedulers, [{:struct, module}|result])
{ :module_available, child, ref, file, module, binary } ->
{: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 = for { _kind, pid, _, waiting_module } <- waiting,
available = for {_kind, pid, _, waiting_module} <- waiting,
module == waiting_module,
not pid in entries,
do: pid
spawn_compilers(available ++ entries, original, output, callbacks,
waiting, queued, schedulers, [{ :module, module }|result])
waiting, queued, schedulers, [{:module, module}|result])
{ :waiting, kind, child, ref, on } ->
defined = fn { k, m } -> on == m and k in [kind, :module] end
{: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 }
send child, {ref, :ready}
else
waiting = [{ kind, child, ref, on }|waiting]
waiting = [{kind, child, ref, on}|waiting]
end
spawn_compilers(entries, original, output, callbacks, waiting, queued, schedulers, result)
{ :DOWN, _down_ref, :process, down_pid, { :compiled, file } } ->
{:DOWN, _down_ref, :process, down_pid, {:compiled, file}} ->
if callback = Keyword.get(callbacks, :each_file) do
callback.(file)
end
@@ -172,45 +172,76 @@ defmodule Kernel.ParallelCompiler do
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, entries, waiting, queued)
wait_for_messages(entries, original, output, callbacks, waiting, queued, schedulers, result)
{ :DOWN, down_ref, :process, _down_pid, other } ->
handle_failure(down_ref, :exit, other, [], entries, waiting, queued)
{: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, entries, 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, entries, 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, 1) do
{ _, ^pid, _, mod } -> IO.puts "* #{file} is missing module #{inspect mod}"
_ -> :ok
end
end
IO.puts "\nOne of the failures 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, entries, waiting, queued) do
waiting_length = length(waiting)
defp print_failure(_file, {:compiled, _}) do
:ok
end
match?({ _, ^child, _, _ }, List.keyfind(waiting, child, 1)) and
waiting_length > 1 and entries == [] 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, [])
+44 -36
View File
@@ -33,11 +33,11 @@ defmodule Kernel.SpecialForms do
## Examples
iex> { 1, 2, 3 }
{ 1, 2, 3 }
iex> {1, 2, 3}
{1, 2, 3}
iex> quote do: { 1, 2, 3 }
{ :{}, [], [1,2,3] }
iex> quote do: {1, 2, 3}
{:{}, [], [1,2,3]}
"""
defmacro unquote(:{})(args)
@@ -49,19 +49,19 @@ defmodule Kernel.SpecialForms do
using the match operator (`===`). Maps can be created with
the `%{}` special form where keys are associated via `=>`:
%{ 1 => 2 }
%{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 }
%{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 }
iex> %{a: :b, a: :c}
%{a: :c}
Conveniences for manipulating maps can be found in the
`Map` module.
@@ -72,7 +72,7 @@ defmodule Kernel.SpecialForms do
like `Map.get/3` and `Map.fetch/2`, a map can be accessed using the
`.` operator:
iex> map = %{ a: :b }
iex> map = %{a: :b}
iex> map.a
:b
@@ -83,9 +83,9 @@ defmodule Kernel.SpecialForms do
Maps also support an update syntax:
iex> map = %{ :a => :b }
iex> %{ map | :a => :c }
%{ :a => :c }
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`.
@@ -96,8 +96,8 @@ defmodule Kernel.SpecialForms do
always represented internally as a list of two-items tuples
for simplicity:
iex> quote do: %{ :a => :b, c: :d }
{ :%{}, [], [{:a, :b}, {:c, :d}] }
iex> quote do: %{:a => :b, c: :d}
{:%{}, [], [{:a, :b}, {:c, :d}]}
"""
defmacro unquote(:%{})(args)
@@ -114,7 +114,7 @@ defmodule Kernel.SpecialForms do
defmodule User do
def __struct__ do
%{ name: "josé", age: 27 }
%{name: "josé", age: 27}
end
end
@@ -125,13 +125,13 @@ defmodule Kernel.SpecialForms do
Underneath, a struct is just a map with a `__struct__` field
pointing to the User module:
%User{} == %{ __struct__: User, name: "josé", age: 27 }
%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" }
%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
@@ -142,7 +142,7 @@ defmodule Kernel.SpecialForms do
An update operation specific for structs is also available:
%User{ user | age: 28 }
%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
@@ -627,7 +627,7 @@ defmodule Kernel.SpecialForms do
following example will match:
iex> x = 0
iex> { x, ^x } = { 1, 0 }
iex> {x, ^x} = {1, 0}
iex> x
1
@@ -640,7 +640,7 @@ defmodule Kernel.SpecialForms do
## Examples
quote do: sum(1, 2, 3)
#=> { :sum, [], [1, 2, 3] }
#=> {:sum, [], [1, 2, 3]}
## Explanation
@@ -648,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:
@@ -1025,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
@@ -1034,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
@@ -1061,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
@@ -1082,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
@@ -1107,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)
@@ -1126,7 +1126,7 @@ 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)
@@ -1157,6 +1157,14 @@ defmodule Kernel.SpecialForms do
iex> for n <- [1, 2, 3, 4, 5, 6], rem(n, 2) == 0, do: n
[2, 4, 6]
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:
@@ -1211,7 +1219,7 @@ 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)
@@ -1241,7 +1249,7 @@ defmodule Kernel.SpecialForms do
## Anonymous functions
The capture operator can be also be used to partially apply
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:
@@ -1266,7 +1274,7 @@ defmodule Kernel.SpecialForms do
iex> fun = &{&1, &2}
iex> fun.(1, 2)
{ 1, 2 }
{1, 2}
iex> fun = &[&1|&2]
iex> fun.(1, 2)
@@ -1336,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
@@ -1352,7 +1360,7 @@ defmodule Kernel.SpecialForms do
outer context:
case data do
{ :ok, value } -> value
{:ok, value} -> value
:error -> nil
end
@@ -1608,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
@@ -1620,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
+151 -143
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,39 +345,39 @@ 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}})
spec = { :::, meta, [body, typespec_to_ast(result)] }
spec = {:::, meta, [body, typespec_to_ast(result)]}
if vars == [] do
spec
else
{ :when, meta, [spec, vars] }
{:when, meta, [spec, vars]}
end
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 =
for { :type, _, :constraint, [{ :atom, _, :is_subtype }, [{ :var, _, var }, type]] } <- 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]
@@ -386,27 +386,27 @@ defmodule Kernel.Typespec do
|> Enum.flat_map(&collect_vars/1)
|> Enum.uniq
|> Kernel.--(Keyword.keys(guards))
|> Enum.map(&{ &1, { :var, meta, nil } })
|> Enum.map(&{&1, {:var, meta, nil}})
args = for arg <- args, do: typespec_to_ast(arg)
{ :when, meta, [
{ :::, meta, [{ name, [line: line], args }, typespec_to_ast(result)] },
{: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
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] }
type = {:{}, [], [record|fields]}
quote do: unquote(record)(unquote_splicing(args)) :: unquote(type)
end
def type_to_ast({ name, type, args }) do
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 = for { :attribute, _, :typedoc, tup } <- 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 = for { :attribute, _, :export_type, types } <- abstract_code, do: types
{:ok, abstract_code} ->
exported_types = for {:attribute, _, :export_type, types} <- abstract_code, do: types
exported_types = List.flatten(exported_types)
for { :attribute, _, kind, { name, _, args } = type } <- 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 } ->
for { :attribute, _, abs_kind, value } <- 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,7 +519,7 @@ 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
[]
@@ -527,11 +527,11 @@ defmodule Kernel.Typespec do
for(arg <- args, do: variable(arg))
end
vars = for { :var, _, var } <- args, do: var
vars = for {:var, _, var} <- args, do: var
spec = typespec(definition, vars, caller)
vars = for { :var, _, _ } = var <- args, do: var
type = { name, spec, vars }
vars = for {:var, _, _} = var <- args, do: var
type = {name, spec, vars}
define_type(caller, kind, type)
end
@@ -543,7 +543,7 @@ defmodule Kernel.Typespec do
@doc false
def defspec(type, { :when, _meta, [spec, guard] }, caller) do
def defspec(type, {:when, _meta, [spec, guard]}, caller) do
defspec(type, spec, guard, caller)
end
@@ -551,7 +551,7 @@ defmodule Kernel.Typespec do
defspec(type, spec, [], caller)
end
defp defspec(type, { :::, meta, [{ name, _, args }, return] }, guard, caller) when is_atom(name) and name != ::: do
defp defspec(type, {:::, meta, [{name, _, args}, return]}, guard, caller) when is_atom(name) and name != ::: do
if is_atom(args), do: args = []
unless Keyword.keyword?(guard) do
@@ -562,12 +562,12 @@ 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
@@ -581,38 +581,38 @@ defmodule Kernel.Typespec 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
@@ -620,42 +620,42 @@ 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
defp typespec_to_ast({:type, line, :tuple, args}) do
args = for arg <- args, do: typespec_to_ast(arg)
{ :{}, [line: line], args }
{:{}, [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
defp typespec_to_ast({:type, _line, :list, args}) do
for arg <- args, do: typespec_to_ast(arg)
end
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) }
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 }
{struct, fields} = Keyword.pop(fields, :__struct__)
map = {:%{}, [line: line], fields}
if struct do
{ :%, [line: line], [struct, map] }
{:%, [line: line], [struct, map]}
else
map
end
end
defp typespec_to_ast({ :type, line, :binary, [arg1, arg2] }) do
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 ->
@@ -667,75 +667,79 @@ defmodule Kernel.Typespec do
end
end
defp typespec_to_ast({ :type, line, :union, args }) do
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
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
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)] }]
[{:->, [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
defp typespec_to_ast({:type, line, name, args}) do
args = for arg <- args, do: typespec_to_ast(arg)
{ name, [line: line], args }
{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
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 }
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
@@ -754,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 = for e <- exprs, do: typespec(e, vars, caller)
{ :type, line(meta), :union, union }
{:type, line(meta), :union, union}
end
# Handle binaries
@@ -781,7 +785,7 @@ defmodule Kernel.Typespec do
## Handle maps and structs
defp typespec({:%{}, meta, fields}, vars, caller) do
fields = Enum.map(fields, fn { k, v } ->
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}
@@ -809,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
@@ -842,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 = for e <- t, do: typespec(e, vars, caller)
{ :type, line(meta), :tuple, args }
{: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
@@ -862,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
@@ -871,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)}"
"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 }
{: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 = for arg <- arguments, do: typespec(arg, vars, caller)
{ :type, line(meta), name, arguments }
{: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
@@ -926,13 +934,13 @@ defmodule Kernel.Typespec do
defp remote_type({remote, meta, name, arguments}, vars, caller) do
arguments = for arg <- arguments, do: typespec(arg, vars, caller)
{ :remote_type, line(meta), [ remote, name, arguments ] }
{: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
@@ -945,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 = for arg <- args, do: typespec(arg, vars, caller)
{ :type, line(meta), :product, args }
{: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
+42 -39
View File
@@ -23,28 +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
IO.write :stderr, "Keyword.from_enum/1 is deprecated, please use Enum.into/2 instead\n#{Exception.format_stacktrace}"
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
@@ -74,7 +65,7 @@ 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
@@ -95,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
@@ -124,7 +115,7 @@ defmodule Keyword do
@spec get(t, key, value) :: value
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
@@ -136,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
@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
@@ -166,7 +157,7 @@ defmodule Keyword do
@spec fetch!(t, key) :: value | no_return
def fetch!(keywords, key) when is_list(keywords) and is_atom(key) do
case :lists.keyfind(key, 1, keywords) do
{ ^key, value } -> value
{^key, value} -> value
false -> raise(KeyError, key: key, term: keywords)
end
end
@@ -182,7 +173,7 @@ defmodule Keyword do
"""
@spec get_values(t, key) :: [value]
def get_values(keywords, key) when is_list(keywords) and is_atom(key) do
for { k, v } <- keywords, key == k, do: v
for {k, v} <- keywords, key == k, do: v
end
@doc """
@@ -200,7 +191,7 @@ defmodule Keyword do
"""
@spec keys(t) :: [key]
def keys(keywords) when is_list(keywords) do
for { key, _ } <- keywords, do: key
for {key, _} <- keywords, do: key
end
@doc """
@@ -214,7 +205,7 @@ defmodule Keyword do
"""
@spec values(t) :: [value]
def values(keywords) when is_list(keywords) do
for { _, value } <- keywords, do: value
for {_, value} <- keywords, do: value
end
@doc """
@@ -235,7 +226,7 @@ defmodule Keyword do
"""
@spec delete(t, key, value) :: t
def delete(keywords, key, value) when is_list(keywords) and is_atom(key) do
for { k, v } = tuple <- keywords, key != k or value != v, do: tuple
for {k, v} = tuple <- keywords, key != k or value != v, do: tuple
end
@doc """
@@ -258,7 +249,7 @@ defmodule Keyword do
"""
@spec delete(t, key) :: t
def delete(keywords, key) when is_list(keywords) and is_atom(key) do
for { k, _ } = tuple <- keywords, key != k, do: tuple
for {k, _} = tuple <- keywords, key != k, do: tuple
end
@doc """
@@ -315,7 +306,7 @@ defmodule Keyword do
@spec put_new(t, key, value) :: t
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
@@ -347,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 ++ for({ k, _ } = tuple <- d1, not has_key?(d2, k), do: tuple)
d2 ++ for({k, _} = tuple <- d1, not has_key?(d2, k), do: tuple)
end
@doc """
@@ -367,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
@@ -456,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(keywords, keys) when is_list(keywords) do
acc = { [], [] }
acc = {[], []}
{ take, drop } = Enum.reduce keywords, 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 """
@@ -492,7 +483,7 @@ defmodule Keyword do
"""
def take(keywords, keys) when is_list(keywords) do
for { k, _ } = tuple <- keywords, k in keys, do: tuple
for {k, _} = tuple <- keywords, k in keys, do: tuple
end
@doc """
@@ -511,7 +502,7 @@ defmodule Keyword do
"""
def drop(keywords, keys) when is_list(keywords) do
for { k, _ } = tuple <- keywords, not k in keys, do: tuple
for {k, _} = tuple <- keywords, not k in keys, do: tuple
end
@doc """
@@ -540,7 +531,7 @@ defmodule Keyword do
"""
def pop(keywords, key, default \\ nil) when is_list(keywords) do
{ get(keywords, key, default), delete(keywords, key) }
{get(keywords, key, default), delete(keywords, key)}
end
@doc """
@@ -569,6 +560,18 @@ defmodule Keyword do
"""
def pop_first(keywords, key, default \\ nil) when is_list(keywords) do
{ get(keywords, key, default), delete_first(keywords, key) }
{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
+69 -8
View File
@@ -164,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
@@ -206,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]
"""
@@ -240,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]
"""
@@ -453,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
@@ -536,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)
+1 -1
View File
@@ -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
-156
View File
@@ -1,156 +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 false
def new(pairs) do
IO.write :stderr, "ListDict.new/1 is deprecated, please use Enum.into/2 instead\n#{Exception.format_stacktrace}"
Enum.to_list pairs
end
@doc false
def new(list, transform) when is_function(transform) do
IO.write :stderr, "ListDict.new/2 is deprecated, please use Enum.into/3 instead\n#{Exception.format_stacktrace}"
Enum.map list, transform
end
def keys(dict) do
for { key, _ } <- dict, do: key
end
def values(dict) do
for { _, value } <- 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, term: dict)
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
for { k, _ } = tuple <- dict, k in keys, do: tuple
end
def drop(dict, keys) do
for { k, _ } = tuple <- dict, not k in keys, do: tuple
end
def update!(list, key, fun) do
update!(list, key, fun, list)
end
defp update!([{key, value}|list], key, fun, _dict) do
[{key, fun.(value)}|delete(list, key)]
end
defp update!([{_, _} = e|list], key, fun, dict) do
[e|update!(list, key, fun, dict)]
end
defp update!([], key, _fun, dict) do
raise(KeyError, key: key, term: dict)
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
IO.write :stderr, "ListDict.empty/1 is deprecated, please use Collectable.empty/1 instead\n#{Exception.format_stacktrace}"
[]
end
def equal?(dict, other) do
:lists.keysort(1, dict) === :lists.keysort(1, other)
end
@doc false
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
+121 -121
View File
@@ -6,10 +6,10 @@ 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 [:===, :!==,
:==, :!=, :<=, :>=,
@@ -27,7 +27,7 @@ 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}
@@ -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,12 +103,12 @@ 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
@@ -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
"""
@@ -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,45 +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
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
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
@@ -377,38 +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({ unary, _, [{ binary, _, [_, _] } = arg] } = ast, fun)
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
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)
@@ -416,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
@@ -426,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 ->
@@ -449,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
@@ -460,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))
@@ -485,14 +484,14 @@ 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
@@ -510,13 +509,13 @@ defmodule Macro do
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 } ->
Enum.map_join(list, ", ", fn {key, value} ->
to_string(key, fun) <> " => " <> to_string(value, fun)
end)
end
@@ -525,9 +524,9 @@ defmodule Macro 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)
@@ -544,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)
@@ -610,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
@@ -646,11 +645,11 @@ 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 = for alias <- aliases, do: elem(do_expand_once(alias, env), 0)
@@ -658,100 +657,100 @@ defmodule Macro 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 can no longer
@@ -761,14 +760,14 @@ defmodule Macro do
`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
@@ -778,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
+8 -42
View File
@@ -13,46 +13,19 @@ defmodule Map do
use Dict.Behaviour
defdelegate [keys(map), values(map), size(map), merge(map1, map2)], to: :maps
defdelegate [keys(map), values(map), size(map), merge(map1, map2), to_list(map)], to: :maps
@doc """
Returns a new empty map.
"""
def new, do: %{}
@doc """
Creates a new map from the given pairs.
## Examples
iex> Map.new [{:b, 1}, {:a, 2}]
%{a: 2, b: 1}
"""
def new(pairs) do
:maps.from_list pairs
end
@doc """
Creates a new map from the given pairs
via the given transformation function.
## Examples
iex> Map.new ["a", "b"], fn x -> {x, x} end
%{"a" => "a", "b" => "b"}
"""
def new(list, transform) when is_function(transform) do
Enum.map(list, transform) |> :maps.from_list
end
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) }
{get(map, key, default), delete(map, key)}
end
def put(map, key, val) do
@@ -75,13 +48,13 @@ defmodule Map do
end
def split(map, keys) do
acc = { %{}, %{} }
acc = {%{}, %{}}
:maps.fold fn k, v, { take, drop } ->
:maps.fold fn k, v, {take, drop} ->
if k in keys do
{ put(take, k, v), drop }
{put(take, k, v), drop}
else
{ take, put(drop, k, v) }
{take, put(drop, k, v)}
end
end, acc, map
end
@@ -90,7 +63,7 @@ defmodule Map do
case :maps.find(key, map) do
:error ->
raise(KeyError, key: key, term: map)
{ :ok, val } ->
{:ok, val} ->
:maps.put(key, fun.(val), map)
end
end
@@ -99,18 +72,11 @@ defmodule Map do
case :maps.find(key, map) do
:error ->
:maps.put(key, initial, map)
{ :ok, val } ->
{:ok, val} ->
:maps.put(key, fun.(val), map)
end
end
def empty(_) do
IO.write :stderr, "Map.empty/1 is deprecated, please use Collectable.empty/1 instead\n#{Exception.format_stacktrace}"
%{}
end
def equal?(map, map), do: true
def equal?(_, _), do: false
def to_list(map), do: :maps.to_list map
end
+76 -70
View File
@@ -1,10 +1,4 @@
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'''
This module provides many functions to deal with modules during
compilation time. It allows a developer to dynamically attach
@@ -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.
@@ -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)
@@ -167,7 +161,7 @@ defmodule Module do
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
@@ -206,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)
@@ -293,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
@@ -344,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 """
@@ -372,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
@@ -391,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
@@ -497,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
@@ -505,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
@@ -513,56 +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, 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({ :\\, _, [left, right ] }, i) do
{ :\\, [], [simplify_signature(left, i), right] }
defp simplify_signature({:\\, _, [left, right ]}, i) do
{:\\, [], [simplify_signature(left, i), right]}
end
defp simplify_signature({ :%, _, [left, _] }, _i) when is_atom(left) do
defp simplify_signature({:%, _, [left, _]}, _i) when is_atom(left) do
last = List.last(String.split(atom_to_binary(left), "."))
atom = binary_to_atom(String.downcase(last))
{ atom, [], nil }
atom = binary_to_atom(downcase(last))
{atom, [], nil}
end
defp simplify_signature({ :=, _, [_, right] }, i) do
defp simplify_signature({:=, _, [_, right]}, i) do
simplify_signature(right, i)
end
defp simplify_signature({ var, _, atom }, _i) when is_atom(atom) do
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 }
"_" <> rest -> {binary_to_atom(rest), [], Elixir}
_ -> {var, [], nil}
end
end
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 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 downcase(<<c, rest :: binary>>) do
<<c, downcase(rest) :: binary>>
end
defp downcase(<<>>) do
<<>>
end
# Merge
@@ -574,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, Elixir }
defp merge_signature({_, line, _}, _newer, i), do: {:"arg#{i}", line, Elixir}
@doc """
Checks if the module defines the given function or macro.
@@ -599,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
"""
@@ -619,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
"""
@@ -629,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
@@ -648,7 +654,7 @@ defmodule Module do
def definitions_in(module) do
assert_not_compiled!(:definitions_in, module)
table = function_table_for(module)
for { tuple, _, _, _, _, _, _ } <- :ets.tab2list(table), do: tuple
for {tuple, _, _, _, _, _, _} <- :ets.tab2list(table), do: tuple
end
@doc """
@@ -667,7 +673,7 @@ defmodule Module do
def definitions_in(module, kind) do
assert_not_compiled!(:definitions_in, module)
table = function_table_for(module)
for { tuple, stored_kind, _, _, _, _, _ } <- :ets.tab2list(table), stored_kind == kind, do: tuple
for {tuple, stored_kind, _, _, _, _, _} <- :ets.tab2list(table), stored_kind == kind, do: tuple
end
@doc """
@@ -682,7 +688,7 @@ defmodule Module 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)
@@ -694,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
@@ -738,7 +744,7 @@ defmodule Module do
value
end
:ets.insert(table, { key, new })
:ets.insert(table, {key, new})
end
@doc """
@@ -860,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
@@ -889,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
@@ -915,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
@@ -935,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
+67 -67
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 = (for { _, _ } = t <- 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,11 +144,11 @@ defmodule Module.LocalsTracker do
def collect_imports_conflicts(pid, all_defined) do
d = :gen_server.call(pid, :digraph, @timeout)
for { name, arity } <- all_defined,
:digraph.in_neighbours(d, { :import, name, arity }) != [],
n = :digraph.out_neighbours(d, { :import, 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 }
{n, name, arity}
end
end
@@ -161,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 = for { n, a } <- 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
@@ -208,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
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 })
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
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 }
{: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
@@ -287,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)
+95 -79
View File
@@ -4,80 +4,85 @@ defmodule OptionParser do
"""
@doc """
Parses `argv` and returns a tuple with the parsed options, its
arguments, and a list of 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 is (the default);
* `: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], [], [] }
{[limit: 3], [], []}
iex> OptionParser.parse(["--limit", "yyz"], switches: [limit: :integer])
{ [], [], [limit: "yyz"] }
{[], [], [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
@@ -93,10 +98,10 @@ 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
@@ -115,25 +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, value } = split_option(option)
{ option, kinds, value } = normalize_option(option, value, 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
@@ -142,51 +137,47 @@ 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 }
{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
@@ -197,30 +188,55 @@ defmodule OptionParser do
defp normalize_option(option, value, switches, aliases) do
option = get_option(option)
if alias = aliases[option] do
normalize_option({ :default, alias }, value, switches)
normalize_option({:default, alias}, value, switches)
else
{ option, [:invalid], value }
{option, [:invalid], value}
end
end
defp normalize_option({ :negated, option }, _value, switches) do
defp normalize_option({:negated, option}, _value, switches) do
kinds = List.wrap(switches[option])
if :boolean in kinds do
{ option, kinds, false }
else
{ option, [:boolean], true }
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 }
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
@@ -235,10 +251,10 @@ defmodule OptionParser do
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 }
{:negated, option}
end
defp get_negated(rest, _switches) do
{ :default, get_option(rest) }
{:default, get_option(rest)}
end
end
+116 -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.
@@ -213,17 +243,10 @@ defmodule Path do
"/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
@@ -231,7 +254,7 @@ defmodule Path do
end
defp relative_to([_|_] = l1, [], _original) do
FN.join(l1)
join(l1)
end
defp relative_to(_, _, original) do
@@ -243,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
@@ -266,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 """
@@ -287,8 +312,9 @@ defmodule Path do
"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 """
@@ -302,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 """
@@ -318,8 +345,9 @@ defmodule Path do
""
"""
@spec extname(t) :: binary
def extname(path) do
FN.extension(path)
FN.extension(String.from_char_data!(path))
end
@doc """
@@ -334,8 +362,9 @@ defmodule Path do
"/foo/bar"
"""
@spec rootname(t) :: binary
def rootname(path) do
FN.rootname(path)
FN.rootname(String.from_char_data!(path))
end
@doc """
@@ -351,13 +380,16 @@ defmodule Path do
"/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
@@ -367,16 +399,15 @@ defmodule Path do
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.
@@ -387,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)
@@ -417,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.
@@ -444,11 +470,13 @@ defmodule Path do
["/", "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 """
@@ -484,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
@@ -548,6 +544,6 @@ defmodule Path do
end
defp normalize([], acc) do
join Enum.reverse(acc)
join :lists.reverse(acc)
end
end
+49 -82
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,26 +104,6 @@ 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`.
"""
@spec send(dest, msg) :: msg when
dest: pid | port | atom | { atom, node },
msg: any
def send(dest, msg) do
:erlang.send(dest, msg)
end
@doc """
Sends a message to the given process.
@@ -137,12 +118,12 @@ defmodule Process do
## Examples
iex> Process.send({ :name, :node_does_not_exist }, :hi, [:noconnect])
iex> Process.send({:name, :node_does_not_exist}, :hi, [:noconnect])
:noconnect
"""
@spec send(dest, msg, [option]) :: result when
dest: pid | port | atom | { atom, node },
dest: pid | port | atom | {atom, node},
msg: any,
option: :noconnect | :nosuspend,
result: :ok | :noconnect | :nosuspend
@@ -179,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.
"""
@@ -203,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.
@@ -457,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
+15 -15
View File
@@ -34,7 +34,7 @@ defmodule Protocol do
defp after_defprotocol do
quote unquote: false do
# == Deprecated records handling ==
{ arg, impl } = Protocol.rec_impl_for(__MODULE__)
{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 ==
@@ -45,12 +45,12 @@ defmodule Protocol do
#
# 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
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
for {guard, mod} <- Protocol.builtin do
target = Module.concat(__MODULE__, mod)
Kernel.def impl_for(data) when :erlang.unquote(guard)(data) do
@@ -94,9 +94,9 @@ defmodule Protocol do
end
# Inline any and struct implementations
@compile { :inline, any_impl_for: 0, struct_impl_for: 1, impl_for?: 1 }
@compile {:inline, any_impl_for: 0, struct_impl_for: 1, impl_for?: 1}
if :code.ensure_loaded(Kernel.Typespec) == { :module, Kernel.Typespec } and
if :code.ensure_loaded(Kernel.Typespec) == {:module, Kernel.Typespec} and
not Kernel.Typespec.defines_type?(__MODULE__, :t, 0) do
@type t :: term
end
@@ -153,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
@@ -185,7 +185,7 @@ defmodule Protocol do
# returns the module to dispatch to.
@doc false
def rec_impl_for(current) do
all = [Any] ++ for { _guard, mod } <- builtin, do: mod
all = [Any] ++ for {_guard, mod} <- builtin, do: mod
arg = quote do: arg
target = Module.concat(current, Tuple)
@@ -211,7 +211,7 @@ defmodule Protocol do
end
end
{ arg, impl_for }
{arg, impl_for}
end
end
@@ -219,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 = for _ <- :lists.seq(2, arity), do: quote(do: term)
type_args = [quote(do: t) | type_args]
call_args = for i <- :lists.seq(2, arity),
do: { binary_to_atom(<<?x, i + 64>>), [], __MODULE__ }
do: {binary_to_atom(<<?x, i + 64>>), [], __MODULE__}
call_args = [quote(do: t) | call_args]
quote do
@@ -262,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 = for { k, v } <- 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
+31 -31
View File
@@ -70,7 +70,7 @@ defmodule Protocol.Consolidation do
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,11 +121,11 @@ 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, "consolidation is disabled as we can't consolidate records " <>
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)
@@ -136,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
@@ -158,23 +158,23 @@ 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)
all = [Any] ++ for { _guard, mod } <- Protocol.builtin, do: mod
all = [Any] ++ for {_guard, mod} <- Protocol.builtin, do: mod
structs = types -- all
change_impl_for(code, protocol, types, structs, false, [])
end
defp change_impl_for([{ :attribute, line, :protocol, opts }|t], protocol, types, structs, _, acc) do
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])
[{:attribute, line, :protocol, opts}|acc])
end
defp change_impl_for([{ :function, line, :impl_for, 1, _ }|t], protocol, types, structs, is_protocol, acc) do
defp change_impl_for([{:function, line, :impl_for, 1, _}|t], protocol, types, structs, is_protocol, acc) do
fallback = if Any in types, do: Module.concat(protocol, Any), else: nil
clauses = for { guard, mod } <- Protocol.builtin,
clauses = for {guard, mod} <- Protocol.builtin,
mod in types,
do: builtin_clause_for(mod, guard, protocol, line)
@@ -182,16 +182,16 @@ defmodule Protocol.Consolidation do
[fallback_clause_for(fallback, protocol, line)]
change_impl_for(t, protocol, types, structs, is_protocol,
[{ :function, line, :impl_for, 1, clauses }|acc])
[{:function, line, :impl_for, 1, clauses}|acc])
end
defp change_impl_for([{ :function, line, :struct_impl_for, 1, _ }|t], protocol, types, structs, is_protocol, acc) do
defp change_impl_for([{:function, line, :struct_impl_for, 1, _}|t], protocol, types, structs, is_protocol, acc) do
fallback = if Any in types, do: Module.concat(protocol, Any), else: nil
clauses = for struct <- structs, do: each_struct_clause_for(struct, protocol, line)
clauses = clauses ++ [fallback_clause_for(fallback, protocol, line)]
change_impl_for(t, protocol, types, structs, is_protocol,
[{ :function, line, :struct_impl_for, 1, clauses }|acc])
[{:function, line, :struct_impl_for, 1, clauses}|acc])
end
defp change_impl_for([h|t], protocol, info, types, is_protocol, acc) do
@@ -200,9 +200,9 @@ 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
@@ -212,7 +212,7 @@ defmodule Protocol.Consolidation do
[[{:call, line,
{:remote, line, {:atom, line, :erlang}, {:atom, line, guard}},
[{:var, line, :x}],
}]],
}]],
[{:atom, line, Module.concat(protocol, mod)}]}
end
@@ -224,7 +224,7 @@ defmodule Protocol.Consolidation do
[[{:call, line,
{:remote, line, {:atom, line, :erlang}, {:atom, line, :is_atom}},
[{:var, line, :x}],
}]],
}]],
[{:call, line,
{:atom, line, :struct_impl_for},
[{:var, line, :x}]}]}
@@ -237,13 +237,13 @@ defmodule Protocol.Consolidation do
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
+33 -29
View File
@@ -1,30 +1,30 @@
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 }
end
@doc """
Returns the first item of the range.
"""
def first({ Range, first, _ }) do
first
end
@doc """
Returns the last item of the range.
"""
def last({ Range, _, last }) do
last
%Range{first: first, last: last}
end
@doc """
@@ -39,11 +39,15 @@ defmodule Range do
false
"""
def range?({ Range, _, _ }), do: true
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.
"""
@@ -60,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
+11 -21
View File
@@ -4,7 +4,7 @@ defmodule Record do
Records are simply tuples where the first element is an atom:
iex> Record.record? { User, "jose", 27 }
iex> Record.record? {User, "jose", 27}
true
This module provides conveniences for working with records at
@@ -51,13 +51,13 @@ defmodule Record do
## Examples
iex> record = { User, "jose", 27 }
iex> record = {User, "jose", 27}
iex> Record.record?(record, User)
true
"""
defmacro record?(data, kind) do
case __CALLER__.in_guard? do
case Macro.Env.in_guard?(__CALLER__) do
true ->
quote do
is_tuple(unquote(data)) and tuple_size(unquote(data)) > 0
@@ -79,7 +79,7 @@ defmodule Record do
## Examples
iex> record = { User, "jose", 27 }
iex> record = {User, "jose", 27}
iex> Record.record?(record)
true
iex> tuple = {}
@@ -88,7 +88,7 @@ defmodule Record do
"""
defmacro record?(data) do
case __CALLER__.in_guard? do
case Macro.Env.in_guard?(__CALLER__) do
true ->
quote do
is_tuple(unquote(data)) and tuple_size(unquote(data)) > 0
@@ -135,14 +135,14 @@ defmodule Record do
arities will be defined to manipulate the underlying record:
# To create records
user() #=> { :user, "José", 25 }
user(age: 26) #=> { :user, "José", 26 }
user() #=> {:user, "José", 25}
user(age: 26) #=> {:user, "José", 26}
# To get a field from the record
user(record, :name) #=> "José"
# To update the record
user(record, age: 26) #=> { :user, "José", 26 }
user(record, age: 26) #=> {:user, "José", 26}
By default, Elixir uses the record name as the first element of
the tuple (the tag). But it can be changed to something else:
@@ -151,18 +151,13 @@ defmodule Record do
Record.defrecord :user, User, name: nil
end
user() #=> { User, nil }
user() #=> {User, nil}
"""
defmacro defrecord(name, tag \\ nil, kv) do
kv = Macro.escape(kv, unquote: true)
quote bind_quoted: [name: name, tag: tag, kv: kv] do
tag = tag || name
kv = Macro.expand(kv, __ENV__)
{ fields, _types } = Record.Backend.split_fields_and_types(:defrecord, kv)
fields = Macro.escape(fields)
fields = Macro.escape Record.Backend.default_fields(:record, kv)
defmacro(unquote(name)(args \\ [])) do
Record.Backend.access(unquote(tag), unquote(fields), args, __CALLER__)
@@ -178,14 +173,9 @@ defmodule Record do
Same as `defrecord/3` but generates private macros.
"""
defmacro defrecordp(name, tag \\ nil, kv) do
kv = Macro.escape(kv, unquote: true)
quote bind_quoted: [name: name, tag: tag, kv: kv] do
tag = tag || name
kv = Macro.expand(kv, __ENV__)
{ fields, _types } = Record.Backend.split_fields_and_types(:defrecordp, kv)
fields = Macro.escape(fields)
fields = Macro.escape Record.Backend.default_fields(:record, kv)
defmacrop(unquote(name)(args \\ [])) do
Record.Backend.access(unquote(tag), unquote(fields), args, __CALLER__)
+36 -29
View File
@@ -2,37 +2,44 @@ 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(tag, kv) when is_list(kv) do
split_fields_and_types(tag, kv, [], [])
def split_fields_and_types(kv) do
if Keyword.keyword?(kv) do
split_fields_and_types(kv, [], [])
else
{kv, []}
end
end
def split_fields_and_types(tag, other) do
raise ArgumentError, message: "#{tag} fields must be a keyword list, got: #{Macro.to_string other}"
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(tag, [{ field, { :::, _, [default, type] }}|t], fields, types) do
split_fields_and_types(tag, t, [{ field, default }|fields], [{ field, type }|types])
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(tag, [{ field, default }|t], fields, types) when is_atom(field) do
split_fields_and_types(tag, t, [{ field, default }|fields], [{ field, quote(do: term) }|types])
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(tag, [field|t], fields, types) when is_atom(field) do
split_fields_and_types(tag, t, [{ field, nil }|fields], [{ field, quote(do: term) }|types])
end
defp split_fields_and_types(tag, [other|_], _fields, _types) do
raise ArgumentError, message: "#{tag} fields must be atoms, got: #{Macro.to_string other}"
end
defp split_fields_and_types(_tag, [], fields, types) do
{ :lists.reverse(fields), :lists.reverse(types) }
defp split_fields_and_types([], fields, types) do
{:lists.reverse(fields), :lists.reverse(types)}
end
@doc """
@@ -80,28 +87,28 @@ defmodule Record.Backend do
Creates a new record with the given default fields and keyword values.
"""
def create(atom, fields, keyword, caller) do
in_match = caller.in_match?
in_match = Macro.Env.in_match?(caller)
{ match, remaining } =
Enum.map_reduce(fields, keyword, fn({ field, default }, each_keyword) ->
{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 }
true -> {:_, [], nil}
false -> Macro.escape(default)
end
end
{ new_fields, Keyword.delete(each_keyword, field) }
{new_fields, Keyword.delete(each_keyword, field)}
end)
case remaining do
[] ->
{ :{}, [], [atom|match] }
{:{}, [], [atom|match]}
_ ->
keys = for { key, _ } <- remaining, do: key
keys = for {key, _} <- remaining, do: key
raise ArgumentError, message: "record #{inspect atom} does not have the key: #{inspect hd(keys)}"
end
end
@@ -110,11 +117,11 @@ defmodule Record.Backend do
Updates a record given by var with the given keyword.
"""
def update(atom, fields, var, keyword, caller) do
if caller.in_match? 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) ->
Enum.reduce keyword, var, fn({key, value}, acc) ->
index = find_index(fields, key, 0)
if index do
quote do
@@ -140,7 +147,7 @@ defmodule Record.Backend do
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, _}|_], 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
+54 -54
View File
@@ -22,7 +22,7 @@ defmodule Record.Deprecated do
end
end
defp record_check!([{ field, { :::, _, [_, _] }}|_]) when is_atom(field) do
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
@@ -32,7 +32,7 @@ defmodule Record.Deprecated do
def defrecordp(name, tag, fields) do
case recordp_split(fields, [], [], false) do
{ :ok, fields, types, def_type } ->
{:ok, fields, types, def_type} ->
types = Macro.escape(types)
# bind_quoted isn't available when bootstrapping record
@@ -41,7 +41,7 @@ defmodule Record.Deprecated do
if def_type do
type = binary_to_atom(atom_to_binary(name) <> "_t")
@typep unquote(type)() :: { unquote(tag || name), unquote_splicing(types) }
@typep unquote(type)() :: {unquote(tag || name), unquote_splicing(types)}
end
end
@@ -62,8 +62,8 @@ defmodule Record.Deprecated do
end
end
defp recordp_split([{ field, { :::, _, [default, type] }}|t], defaults, types, _) do
recordp_split(t, [{ field, default }|defaults], [type|types], true)
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
@@ -71,7 +71,7 @@ defmodule Record.Deprecated do
end
defp recordp_split([], defaults, types, def_type) do
{ :ok, :lists.reverse(defaults), :lists.reverse(types), def_type }
{:ok, :lists.reverse(defaults), :lists.reverse(types), def_type}
end
defp recordp_split(_, _, _, _) do
@@ -98,15 +98,15 @@ defmodule Record.Deprecated do
if env == Macro.Env do
Module.eval_quoted(env, contents, [], [])
else
Module.eval_quoted(env.module, contents, [], env.location)
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) }
{name, default} = convert_value(value)
{name, default, find_spec(types, name)}
end
contents = [
@@ -116,12 +116,12 @@ defmodule Record.Deprecated do
# We need to handle bootstraping
cond do
:code.ensure_loaded(Kernel.Typespec) != { :module, Kernel.Typespec } ->
:code.ensure_loaded(Kernel.Typespec) != {:module, Kernel.Typespec} ->
nil
env == Macro.Env ->
Module.eval_quoted(env, contents, [], [])
true ->
Module.eval_quoted(env.module, contents, [], env.location)
Module.eval_quoted(env.module, contents, [], Macro.Env.location(env))
end
end
@@ -129,8 +129,8 @@ defmodule Record.Deprecated do
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) }
{key, value} = convert_value(value)
{key, Macro.escape(value)}
end
tag = tag || name
@@ -149,7 +149,7 @@ defmodule Record.Deprecated do
end
end
Module.eval_quoted(env.module, contents, [], env.location)
Module.eval_quoted(env.module, contents, [], Macro.Env.location(env))
end
## Callbacks
@@ -161,7 +161,7 @@ defmodule Record.Deprecated do
end
def __on_definition__(env, kind, name, args, _guards, _body) do
tuple = { name, length(args) }
tuple = {name, length(args)}
module = env.module
functions = Module.get_attribute(module, :record_optimizable)
@@ -188,10 +188,10 @@ defmodule Record.Deprecated do
raise ArgumentError, message: "expected contents inside brackets to be a keyword list or an atom, got: #{inspect keyword}"
end
in_match = caller.in_match?
in_match = Macro.Env.in_match?(caller)
has_underscore_value = Keyword.has_key?(keyword, :_)
underscore_value = Keyword.get(keyword, :_, { :_, [], nil })
underscore_value = Keyword.get(keyword, :_, {:_, [], nil})
keyword = Keyword.delete keyword, :_
iterator = fn({field, default}, each_keyword) ->
@@ -205,16 +205,16 @@ defmodule Record.Deprecated do
end
end
{ new_fields, Keyword.delete(each_keyword, field) }
{new_fields, Keyword.delete(each_keyword, field)}
end
{ match, remaining } = :lists.mapfoldl(iterator, keyword, fields)
{match, remaining} = :lists.mapfoldl(iterator, keyword, fields)
case remaining do
[] ->
{ :{}, [], [atom|match] }
{:{}, [], [atom|match]}
_ ->
keys = for { key, _ } <- remaining, do: key
keys = for {key, _} <- remaining, do: key
raise ArgumentError, message: "record #{inspect atom} does not have the key: #{inspect hd(keys)}"
end
end
@@ -235,11 +235,11 @@ defmodule Record.Deprecated do
raise ArgumentError, message: "expected arguments to be compile time keywords"
end
if caller.in_match? do
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) ->
Enum.reduce keyword, var, fn({key, value}, acc) ->
index = find_index(fields, key, 0)
if index do
quote do
@@ -265,7 +265,7 @@ defmodule Record.Deprecated do
## Function generation
defp reflection(values) do
quoted = for { k, _ } <- values do
quoted = for {k, _} <- values do
index = find_index(values, k, 0)
quote do
def __record__(:index, unquote(k)), do: unquote(index + 1)
@@ -290,23 +290,23 @@ defmodule Record.Deprecated do
end
defp initializer(values) do
defaults = for { _, value } <- values, do: value
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)
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) }
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
@@ -320,9 +320,9 @@ defmodule Record.Deprecated do
end
defp conversions(values) do
sorted = for { k, _ } <- values do
sorted = for {k, _} <- values do
index = find_index(values, k, 0)
{ k, quote(do: :erlang.element(unquote(index + 2), record)) }
{k, quote(do: :erlang.element(unquote(index + 2), record))}
end
quote do
@@ -333,11 +333,11 @@ defmodule Record.Deprecated do
end
end
defp accessors([{ :__exception__, _ }|t], 1) do
defp accessors([{:__exception__, _}|t], 1) do
accessors(t, 2)
end
defp accessors([{ key, _default }|t], i) do
defp accessors([{key, _default}|t], i) do
update = binary_to_atom "update_" <> atom_to_binary(key)
contents = quote do
@@ -374,8 +374,8 @@ defmodule Record.Deprecated do
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) }
atom_contents = quote do: {__MODULE__, unquote_splicing(atom_fields)}
string_contents = quote do: {__MODULE__, unquote_splicing(string_fields)}
quote do
@doc false
@@ -407,8 +407,8 @@ defmodule Record.Deprecated do
quote do
@record_optimized true
@record_optimizable []
@before_compile { unquote(__MODULE__), :__before_compile__ }
@on_definition { unquote(__MODULE__), :__on_definition__ }
@before_compile {unquote(__MODULE__), :__before_compile__}
@on_definition {unquote(__MODULE__), :__on_definition__}
end
end
@@ -419,12 +419,12 @@ defmodule Record.Deprecated do
## Types/specs generation
defp core_specs(values) do
types = for { _, _, spec } <- values, do: spec
options = for { k, _, v } <- values, do: { k, v }
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) }
@type t :: {__MODULE__, unquote_splicing(types)}
end
unless Kernel.Typespec.defines_type?(__MODULE__, :options, 0) do
@@ -441,11 +441,11 @@ defmodule Record.Deprecated do
end
end
defp accessor_specs([{ :__exception__, _, _ }|t], 1, acc) do
defp accessor_specs([{:__exception__, _, _}|t], 1, acc) do
accessor_specs(t, 2, acc)
end
defp accessor_specs([{ key, _default, spec }|t], i, acc) do
defp accessor_specs([{key, _default, spec}|t], i, acc) do
update = binary_to_atom "update_" <> atom_to_binary(key)
contents = quote do
@@ -464,15 +464,15 @@ defmodule Record.Deprecated do
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({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) 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({atom, other}) when is_atom(atom), do:
{atom, check_value(atom, other)}
defp convert_value({ field, _ }), do:
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
@@ -486,8 +486,8 @@ defmodule Record.Deprecated do
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
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
@@ -500,12 +500,12 @@ defmodule Record.Deprecated do
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, _}|_], 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
matches = for {k, v} <- types, name == k, do: v
case matches do
[h|_] -> h
_ -> quote do: term
@@ -517,7 +517,7 @@ 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) }
escaped = for {k, v} <- opts, do: {k, Macro.escape(v)}
quote do
@record_types Keyword.merge(@record_types || [], unquote(escaped))
+18 -18
View File
@@ -4,7 +4,7 @@ defmodule Record.Extractor do
# Retrieve a record definition from an Erlang file using
# the same lookup as the *include* attribute from Erlang modules.
def extract(name, from: file) when is_binary(file) do
file = String.to_char_list!(file)
file = List.from_char_data!(file)
realfile =
case :code.where_is_file(file) do
@@ -18,10 +18,10 @@ defmodule Record.Extractor do
# Retrieve a record definition from an Erlang file using
# the same lookup as the *include_lib* attribute from Erlang modules.
def extract(name, from_lib: file) when is_binary(file) do
[app|path] = :filename.split(String.to_char_list!(file))
[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 ->
extract_record name, :filename.join([libpath|path])
@@ -41,7 +41,7 @@ defmodule Record.Extractor do
# Parse the given file and extract all existent records.
defp extract_records(form) do
for { :attribute, _, :record, record } <- form, do: record
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
+216 -86
View File
@@ -1,8 +1,10 @@
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).
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):
@@ -10,12 +12,18 @@ defmodule Regex do
# A simple regular expressions that matches foo anywhere in the string
~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
@@ -54,14 +62,13 @@ defmodule Regex do
* `:none` - do not return matching subpatterns at all;
* `:groups` - captures only named captures in the Regex;
* `:all_names` - captures all names in the Regex;
* `list(binary)` - a list of named captures to capture;
"""
defrecordp :regex, Regex, [:re_pattern, :source, :options]
@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"
@@ -72,8 +79,8 @@ defmodule Regex do
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
@@ -84,18 +91,16 @@ defmodule Regex do
{: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
@@ -105,8 +110,8 @@ defmodule Regex do
defp compile(source, opts, doc_opts) when is_binary(source) do
case :re.compile(source, opts) do
{ :ok, re_pattern } ->
{ :ok, regex(re_pattern: re_pattern, source: source, options: doc_opts) }
{:ok, re_pattern} ->
{:ok, %Regex{re_pattern: re_pattern, source: source, opts: doc_opts}}
error ->
error
end
@@ -118,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
@@ -135,8 +140,8 @@ defmodule Regex do
false
"""
def match?(regex(re_pattern: compiled), string) when is_binary(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 """
@@ -151,7 +156,7 @@ defmodule Regex do
false
"""
def regex?(regex()), do: true
def regex?(%Regex{}), do: true
def regex?(_), do: false
@doc """
@@ -178,49 +183,45 @@ defmodule Regex do
"""
def run(regex, string, options \\ [])
def run(regex(re_pattern: compiled) = regex, string, options) when is_binary(string) do
return = Keyword.get(options, :return, :binary)
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(regex)
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. The option `:return` can be set to `:index` to get indexes
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)/, "abcd")
[foo: "d"]
%{"foo" => "d"}
iex> Regex.named_captures(~r/a(?<foo>b)c(?<bar>d)/, "abcd")
[bar: "d", foo: "b"]
%{"bar" => "d", "foo" => "b"}
iex> Regex.named_captures(~r/a(?<foo>b)c(?<bar>d)/, "efgh")
nil
"""
def named_captures(regex, string, options \\ []) when is_binary(string) do
options = [capture: :groups] ++ options
names = names(regex)
options = Keyword.put(options, :capture, names)
results = run(regex, string, options)
if results, do: Enum.zip(groups(regex), 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
@@ -233,7 +234,7 @@ defmodule Regex do
"foo"
"""
def source(regex(source: source)) do
def source(%Regex{source: source}) do
source
end
@@ -246,22 +247,22 @@ defmodule Regex do
"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)/)
[:foo]
iex> Regex.names(~r/(?<foo>bar)/)
["foo"]
"""
def groups(regex(re_pattern: re_pattern)) do
{ :namelist, groups } = :re.inspect(re_pattern, :namelist)
for group <- groups, do: binary_to_atom(group)
def names(%Regex{re_pattern: re_pattern}) do
{:namelist, names} = :re.inspect(re_pattern, :namelist)
names
end
@doc """
@@ -290,20 +291,15 @@ defmodule Regex do
"""
def scan(regex, string, options \\ [])
def scan(regex(re_pattern: compiled) = regex, string, options) when is_binary(string) do
return = Keyword.get(options, :return, :binary)
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(regex)
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
@@ -312,11 +308,11 @@ defmodule Regex do
## Options
* `:global` - splits the string in all parts unless set to `false`,
which leads the string to be split in 2. Defaults to true;
* `:parts` - when specified, splits the string into maximum the number
of given parts;
* `: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;
@@ -341,12 +337,15 @@ defmodule Regex do
def split(regex, string, options \\ [])
def split(regex(re_pattern: compiled), string, options) when is_binary(string) 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
opts = [return: :binary, parts: parts]
@@ -363,10 +362,20 @@ defmodule Regex do
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
@@ -376,25 +385,145 @@ defmodule Regex do
iex> Regex.replace(~r/b/, "abc", "d")
"adc"
iex> Regex.replace(~r/b/, "abc", "[&]")
iex> Regex.replace(~r/b/, "abc", "[\\0]")
"a[b]c"
iex> Regex.replace(~r/b/, "abc", "[\\&]")
"a[&]c"
iex> Regex.replace(~r/a(b|d)c/, "abcadc", "[\\1]")
"[b][d]"
iex> Regex.replace(~r/(b)/, "abc", "[\\1]")
"a[b]c"
iex> Regex.replace(~r/a(b|d)c/, "abcadc", fn _, x -> "[#{x}]" end)
"[b][d]"
"""
def replace(regex, string, replacement, options \\ [])
def replace(regex(re_pattern: compiled), string, replacement, options) when is_binary(string) do
opts = if Keyword.get(options, :global) != false, do: [:global], else: []
opts = [{ :return, :binary }|opts]
:re.replace(string, compiled, replacement, opts)
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"""
@@ -411,7 +540,7 @@ defmodule Regex do
"""
@spec escape(String.t) :: String.t
def escape(string) when is_binary(string) do
:re.replace(string, @escape_pattern, "\\\\&", [:global, { :return, :binary }])
:re.replace(string, @escape_pattern, "\\\\&", [:global, {:return, :binary}])
end
# Helpers
@@ -433,6 +562,7 @@ defmodule Regex do
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)]
@@ -440,5 +570,5 @@ defmodule Regex do
defp translate_options(<<?s, t :: binary>>), do: [:dotall, {:newline, :anycrlf}|translate_options(t)]
defp translate_options(<<?m, t :: binary>>), do: [:multiline|translate_options(t)]
defp translate_options(<<>>), do: []
defp translate_options(rest), do: { :error, rest }
defp translate_options(rest), do: {:error, rest}
end
+14 -14
View File
@@ -30,13 +30,12 @@ defmodule Set do
@type value :: any
@type values :: [ value ]
@type t :: map | tuple
@type t :: map
defcallback new :: 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
@@ -52,6 +51,7 @@ defmodule 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)
@@ -99,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
@@ -129,10 +129,10 @@ 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
@@ -201,8 +201,8 @@ defmodule Set do
if target1 == target2 do
target1.intersection(set1, set2)
else
target1.reduce(set1, { :cont, Collectable.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
@@ -318,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
+152 -149
View File
@@ -89,47 +89,50 @@ defmodule Stream do
returns an anonymous function may return a struct in future releases.
"""
defrecord Lazy, enum: nil, funs: [], accs: [], done: 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, done: done], 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),
done && { done, fun }, :lists.reverse(accs), acc)
done && {done, fun}, :lists.reverse(accs), acc)
end
defp do_each(reduce, done, accs, { command, acc }) do
case reduce.({ command, [acc|accs] }) do
{ :suspended, [acc|accs], continuation } ->
{ :suspended, acc, &do_each(continuation, done, accs, &1) }
{ :halted, [acc|_] } ->
{ :halted, acc }
{ :done, [acc|_] = accs } ->
defp do_each(reduce, done, accs, {command, acc}) do
case reduce.({command, [acc|accs]}) do
{:suspended, [acc|accs], continuation} ->
{:suspended, acc, &do_each(continuation, done, accs, &1)}
{:halted, [acc|_]} ->
{:halted, acc}
{:done, [acc|_] = accs} ->
case done do
nil ->
{ :done, acc }
{ done, fun } ->
{: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) }) }
{:cont, [acc|_]} -> {:done, acc}
{:halt, [acc|_]} -> {:halted, acc}
{:suspend, [acc|_]} -> {:suspended, acc, &({:done, elem(&1, 1)})}
end
end
end
@@ -154,8 +157,8 @@ 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
@@ -199,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)
@@ -233,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
@@ -267,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
@@ -282,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
@@ -344,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 """
@@ -393,29 +396,29 @@ defmodule Stream do
end
defp do_into(enum, collectable, transform, acc, fun) do
{ initial, into } = Collectable.into(collectable)
{initial, into} = Collectable.into(collectable)
composed = fn x, [acc|collectable] ->
collectable = into.(collectable, { :cont, transform.(x) })
{ reason, acc } = fun.(x, acc)
{ reason, [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
defp do_into(reduce, collectable, into, {command, acc}) do
try do
reduce.({ command, [acc|collectable] })
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] } ->
{:suspended, [acc|collectable], continuation} ->
{:suspended, acc, &do_into(continuation, collectable, into, &1)}
{reason, [acc|collectable]} ->
into.(collectable, :done)
{ reason, acc }
{reason, acc}
end
end
@@ -471,7 +474,7 @@ defmodule Stream do
"""
@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
@@ -535,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
@@ -546,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)
@@ -587,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
@@ -626,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)
@@ -647,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
@@ -676,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
@@ -693,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 """
@@ -815,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
@@ -835,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
@@ -888,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
@@ -919,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
@@ -942,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
@@ -974,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 ->
@@ -1008,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
@@ -1027,42 +1030,42 @@ defmodule Stream do
[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[done: nil, funs: funs, accs: accs] = lazy, acc, fun, done),
do: lazy.funs([fun|funs]).accs([acc|accs]).done(done)
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]
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
+136 -106
View File
@@ -177,7 +177,8 @@ defmodule String do
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`.
@@ -189,7 +190,7 @@ 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)
@@ -205,7 +206,7 @@ defmodule String do
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)
@@ -222,7 +223,7 @@ defmodule String do
iex> String.split("abc", "", trim: true)
["a", "b", "c"]
iex> String.split("abc", "", global: false)
iex> String.split("abc", "", parts: 2)
["a", "bc"]
"""
@@ -232,14 +233,25 @@ defmodule String do
def split("", _pattern, _options), do: [""]
def split(binary, "", options), do: split(binary, ~r"", options)
def split(binary, "", options), do: split(binary, ~r""u, options)
def split(binary, pattern, options) do
if options[:global] != nil do
IO.write :stderr, "Support for :global in String.split/3 is deprecated, please use :parts instead\n#{Exception.format_stacktrace}"
end
if Regex.regex?(pattern) do
Regex.split(pattern, binary, options)
else
opts = if options[:global] != false, do: [:global], else: []
splits = :binary.split(binary, pattern, opts)
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
@@ -249,6 +261,65 @@ defmodule String do
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.
@@ -308,7 +379,7 @@ defmodule String do
"""
@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
@@ -540,9 +611,7 @@ 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) do
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
@@ -552,9 +621,9 @@ defmodule String do
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
@@ -585,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.
@@ -640,10 +709,10 @@ 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
@@ -731,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 """
@@ -754,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
@@ -1134,130 +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.from_char_data([0x00E6, 0x00DF])
{:ok, "æß"}
iex> String.to_char_list("abc")
{ :ok, 'abc' }
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
+2 -2
View File
@@ -63,7 +63,7 @@ defimpl String.Chars, for: List do
"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
@@ -80,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
+97 -102
View File
@@ -4,7 +4,7 @@ defmodule StringIO do
## Examples
iex> { :ok, pid } = StringIO.open("foo")
iex> {:ok, pid} = StringIO.open("foo")
iex> IO.read(pid, 2)
"fo"
@@ -12,8 +12,6 @@ defmodule StringIO do
use GenServer.Behaviour
defrecordp :state, input: "", output: "", capture_prompt: false
@doc """
Creates an IO device.
@@ -23,22 +21,22 @@ defmodule StringIO do
## Examples
iex> { :ok, pid } = StringIO.open("foo")
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> {:ok, pid} = StringIO.open("foo", capture_prompt: true)
iex> IO.gets(pid, ">")
"foo"
iex> StringIO.contents(pid)
{ "", ">" }
{"", ">"}
"""
@spec open(binary, Keyword.t) :: { :ok, pid }
@spec open(binary, Keyword.t) :: {:ok, pid}
def open(string, options \\ []) when is_binary(string) do
:gen_server.start_link(__MODULE__, { string, options }, [])
:gen_server.start_link(__MODULE__, {string, options}, [])
end
@doc """
@@ -46,13 +44,13 @@ defmodule StringIO do
## Examples
iex> { :ok, pid } = StringIO.open("in")
iex> {:ok, pid} = StringIO.open("in")
iex> IO.write(pid, "out")
iex> StringIO.contents(pid)
{ "in", "out" }
{"in", "out"}
"""
@spec contents(pid) :: { binary, binary }
@spec contents(pid) :: {binary, binary}
def contents(pid) when is_pid(pid) do
:gen_server.call(pid, :contents)
end
@@ -62,39 +60,39 @@ defmodule StringIO do
## Examples
iex> { :ok, pid } = StringIO.open("in")
iex> {:ok, pid} = StringIO.open("in")
iex> IO.write(pid, "out")
iex> StringIO.close(pid)
{ :ok, { "in", "out" } }
{:ok, {"in", "out"}}
"""
@spec close(pid) :: { :ok, { binary, binary } }
@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
def init({string, options}) do
capture_prompt = options[:capture_prompt] || false
{ :ok, state(input: string, capture_prompt: capture_prompt) }
{:ok, %{input: string, output: "", capture_prompt: capture_prompt}}
end
def handle_info({ :io_request, from, reply_as, req }, s) do
def handle_info({:io_request, from, reply_as, req}, s) do
s = io_request(from, reply_as, req, s)
{ :noreply, s }
{:noreply, s}
end
def handle_info(msg, s) do
super(msg, s)
end
def handle_call(:contents, _from, state(input: input, output: output) = s) do
{ :reply, { input, output }, s }
def handle_call(:contents, _from, %{input: input, output: output} = s) do
{:reply, {input, output}, s}
end
def handle_call(:close, _from, state(input: input, output: output) = s) do
{ :stop, :normal, { :ok, { input, output } }, s }
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
@@ -102,168 +100,168 @@ defmodule StringIO do
end
defp io_request(from, reply_as, req, s) do
{ reply, s } = io_request(req, s)
{reply, s} = io_request(req, s)
io_reply(from, reply_as, to_reply(reply))
s
end
defp io_request({ :put_chars, chars }, state(output: output) = s) do
{ :ok, state(s, output: << output :: binary, to_binary(chars) :: binary >>) }
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 }, state(output: output) = s) do
defp io_request({:put_chars, m, f, as}, %{output: output} = s) do
chars = apply(m, f, as)
{ :ok, state(s, output: << output :: binary, to_binary(chars) :: binary >>) }
{: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)
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)
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)
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
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)
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
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)
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
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
defp io_request({:get_password, encoding}, s) do
get_line(encoding, "", s)
end
defp io_request({ :setopts, _opts }, s) do
{ { :error, :enotsup }, s }
defp io_request({:setopts, _opts}, s) do
{{:error, :enotsup}, s}
end
defp io_request(:getopts, s) do
{ { :ok, [binary: true, encoding: :unicode] }, s }
{{:ok, [binary: true, encoding: :unicode]}, s}
end
defp io_request({ :get_geometry, :columns }, s) do
{ { :error, :enotsup }, s }
defp io_request({:get_geometry, :columns}, s) do
{{:error, :enotsup}, s}
end
defp io_request({ :get_geometry, :rows }, s) do
{ { :error, :enotsup }, s }
defp io_request({:get_geometry, :rows}, s) do
{{:error, :enotsup}, s}
end
defp io_request({ :requests, reqs }, s) do
io_requests(reqs, { :ok, s })
defp io_request({:requests, reqs}, s) do
io_requests(reqs, {:ok, s})
end
defp io_request(_, s) do
{ { :error, :request }, s }
{{:error, :request}, s}
end
## get_chars
defp get_chars(encoding, prompt, n,
state(input: input, output: output, capture_prompt: capture_prompt) = s) do
%{input: input, output: output, capture_prompt: capture_prompt} = s) do
case do_get_chars(input, encoding, n) do
{ :error, _ } = error ->
{ error, s }
{ result, input } ->
{:error, _} = error ->
{error, s}
{result, input} ->
if capture_prompt do
output = << output :: binary, to_binary(prompt) :: binary >>
output = << output :: binary, String.from_char_data!(prompt) :: binary >>
end
{ result, state(s, input: input, output: output) }
{result, %{s | input: input, output: output}}
end
end
defp do_get_chars("", _encoding, _n) do
{ :eof, "" }
{:eof, ""}
end
defp do_get_chars(input, :latin1, n) when byte_size(input) < n do
{ input, "" }
{input, ""}
end
defp do_get_chars(input, :latin1, n) do
<< chars :: [ binary, size(n) ], rest :: binary >> = input
{ chars, rest }
{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 } ->
{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 }
{chars, rest}
end
catch
:exit, :invalid_unicode ->
{ :error, :invalid_unicode }
{:error, :invalid_unicode}
end
end
## get_line
defp get_line(encoding, prompt,
state(input: input, output: output, capture_prompt: capture_prompt) = s) do
%{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 }
{:error, _, _} ->
{{:error, :collect_line}, s}
{:incomplete, _, _} ->
{{:error, :collect_line}, s}
chars ->
{ result, input } = do_get_line(chars, encoding)
{result, input} = do_get_line(chars, encoding)
if capture_prompt do
output = << output :: binary, to_binary(prompt) :: binary >>
output = << output :: binary, String.from_char_data!(prompt) :: binary >>
end
{ result, state(s, input: input, output: output) }
{result, %{s | input: input, output: output}}
end
end
defp do_get_line('', _encoding) do
{ :eof, "" }
{: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) }
{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,
state(input: input, output: output, capture_prompt: capture_prompt) = s) do
%{input: input, output: output, capture_prompt: capture_prompt} = s) do
case :unicode.characters_to_list(input, encoding) do
{ :error, _, _ } ->
{ :error, s }
{ :incomplete, _, _ } ->
{ :error, s }
{:error, _, _} ->
{:error, s}
{:incomplete, _, _} ->
{:error, s}
chars ->
{ result, input, count } = do_get_until(chars, encoding, mod, fun, args)
{result, input, count} = do_get_until(chars, encoding, mod, fun, args)
if capture_prompt do
output = << output :: binary, :binary.copy(to_binary(prompt), count) :: binary >>
output = << output :: binary, :binary.copy(String.from_char_data!(prompt), count) :: binary >>
end
input =
@@ -272,7 +270,7 @@ defmodule StringIO do
_ -> :unicode.characters_to_binary(input, encoding)
end
{ result, state(s, input: input, output: output) }
{result, %{s | input: input, output: output}}
end
end
@@ -280,30 +278,30 @@ defmodule StringIO do
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 } ->
{: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)
{line, rest} = collect_line(chars)
case apply(mod, fun, [continuation, line | args]) do
{ :done, result, rest1 } ->
{:done, result, rest1} ->
unless rest1 == :eof do
rest = rest1 ++ rest
end
{ result, rest, count + 1 }
{ :more, next_continuation } ->
{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
defp io_requests([r|rs], {:ok, s}) do
io_requests(rs, io_request(r, s))
end
@@ -318,15 +316,15 @@ defmodule StringIO do
end
defp collect_line([], stack) do
{ :lists.reverse(stack), [] }
{:lists.reverse(stack), []}
end
defp collect_line([?\r, ?\n | rest], stack) do
{ :lists.reverse([?\n|stack]), rest }
{:lists.reverse([?\n|stack]), rest}
end
defp collect_line([?\n | rest], stack) do
{ :lists.reverse([?\n|stack]), rest }
{:lists.reverse([?\n|stack]), rest}
end
defp collect_line([h|t], stack) do
@@ -334,12 +332,9 @@ defmodule StringIO do
end
defp io_reply(from, reply_as, reply) do
send from, { :io_reply, reply_as, reply }
send from, {:io_reply, reply_as, reply}
end
defp to_binary(list) when is_list(list), do: String.from_char_list!(list)
defp to_binary(binary) when is_binary(binary), do: binary
defp to_reply(list) when is_list(list), do: String.from_char_list!(list)
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
+30 -36
View File
@@ -11,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
@@ -31,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")
@@ -44,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 """
@@ -83,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 """
@@ -94,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
@@ -117,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
@@ -189,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
@@ -214,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 """
@@ -225,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
@@ -235,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 """
@@ -260,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
@@ -274,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
@@ -292,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
@@ -307,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.
@@ -316,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
@@ -338,7 +332,7 @@ defmodule System do
"""
@spec delete_env(String.t) :: :ok
def delete_env(varname) do
:os.unsetenv(String.to_char_list!(varname))
:os.unsetenv(List.from_char_data!(varname))
:ok
end
@@ -367,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
@@ -391,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
+11 -5
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 }
iex> tuple = {:bar, :baz}
iex> Tuple.insert_at(tuple, 0, :foo)
{ :foo, :bar, :baz }
{:foo, :bar, :baz}
"""
@spec insert_at(tuple, non_neg_integer, term) :: tuple
@@ -46,11 +50,13 @@ defmodule Tuple do
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 }
iex> tuple = {:foo, :bar, :baz}
iex> Tuple.delete_at(tuple, 0)
{ :bar, :baz }
{:bar, :baz}
"""
@spec delete_at(tuple, non_neg_integer) :: tuple
+38 -34
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 """
@@ -80,7 +84,7 @@ defmodule URI do
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.
@@ -92,7 +96,7 @@ defmodule URI do
"""
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
Enum.reduce query_decoder(q), dict, fn({k, v}, acc) -> Dict.put(acc, k, v) end
end
@doc """
@@ -114,19 +118,19 @@ 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
@@ -213,9 +217,9 @@ 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
@@ -224,7 +228,7 @@ defmodule URI do
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 = ""
@@ -240,11 +244,11 @@ 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.
@@ -256,7 +260,7 @@ defmodule URI do
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
@@ -268,12 +272,12 @@ defmodule URI do
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 = ""
+93 -86
View File
@@ -2,7 +2,7 @@ defmodule Version do
@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
@@ -22,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
@@ -47,17 +53,20 @@ 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, :minor, :patch, :pre, :build, :source]
defrecord Requirement, [:source, :matchspec]
@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]}
defmodule Requirement do
@moduledoc false
defstruct [:source, :matchspec]
end
defexception InvalidRequirement, [:message]
defexception InvalidVersion, [:message]
@@ -86,18 +95,18 @@ defmodule Version do
** (Version.InvalidRequirement) == ==1.0.0
"""
@spec match?(t, requirement) :: boolean
@spec match?(version, requirement) :: boolean
def match?(vsn, req) when is_binary(req) do
case parse_requirement(req) do
{ :ok, req } ->
{:ok, req} ->
match?(vsn, req)
:error ->
raise InvalidRequirement, message: req
end
end
def match?(version, Requirement[matchspec: spec]) do
{ :ok, result } = :ets.test_ms(to_matchable(version), spec)
def match?(version, %Requirement{matchspec: spec}) do
{:ok, result} = :ets.test_ms(to_matchable(version), spec)
result != false
end
@@ -121,15 +130,15 @@ defmodule Version do
** (Version.InvalidVersion) invalid
"""
@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
do_compare(to_matchable(vsn1), to_matchable(vsn2))
end
defp do_compare({ major1, minor1, patch1, pre1 }, { major2, minor2, patch2, pre2 }) do
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
@@ -139,31 +148,31 @@ defmodule Version do
end
@doc """
Parse a version string into a `Version.Schema`.
Parse a version string into a `Version`.
## Examples
iex> Version.parse("2.0.1-alpha1") |> elem(1)
#Version.Schema<2.0.1-alpha1>
#Version<2.0.1-alpha1>
iex> Version.parse("2.0-alpha1")
:error
"""
@spec parse(String.t) :: { :ok, Schema.t } | :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.Schema[major: major, minor: minor, patch: patch,
pre: pre, source: string, build: get_build(string)]
{ :ok, vsn }
{: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.Schema`.
Parse a version requirement string into a `Version.Requirement`.
## Examples
@@ -174,23 +183,23 @@ defmodule Version do
:error
"""
@spec parse_requirement(String.t) :: { :ok, Requirement.t } | :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] }
{:ok, spec} ->
{:ok, %Requirement{source: string, matchspec: spec}}
:error ->
:error
end
end
defp to_matchable(Schema[major: major, minor: minor, patch: patch, pre: pre]) do
{ major, minor, patch, pre }
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
{:ok, vsn} -> vsn
:error -> raise InvalidVersion, message: string
end
end
@@ -304,8 +313,8 @@ defmodule Version do
patch = patch && binary_to_integer(patch)
case parse_pre(pre) do
{ :ok, pre } ->
{ :ok, { major, minor, patch, pre } }
{:ok, pre} ->
{:ok, {major, minor, patch, pre}}
:error ->
:error
end
@@ -315,7 +324,7 @@ defmodule Version do
end
end
defp parse_pre(nil), do: { :ok, [] }
defp parse_pre(nil), do: {:ok, []}
defp parse_pre(pre), do: parse_pre(String.split(pre, "."), [])
defp parse_pre([piece|t], acc) do
@@ -330,7 +339,7 @@ defmodule Version do
end
defp parse_pre([], acc) do
{ :ok, Enum.reverse(acc) }
{:ok, Enum.reverse(acc)}
end
defp valid_requirement?([]), do: false
@@ -367,11 +376,11 @@ defmodule Version do
defp approximate_upper(version) do
case version do
{ major, _minor, nil, _ } ->
{ major + 1, 0, 0, [0] }
{major, _minor, nil, _} ->
{major + 1, 0, 0, [0]}
{ major, minor, _patch, _ } ->
{ major, minor + 1, 0, [0] }
{major, minor, _patch, _} ->
{major, minor + 1, 0, [0]}
end
end
@@ -379,7 +388,7 @@ defmodule Version do
if valid_requirement?(lexed) do
first = to_condition(lexed)
rest = Enum.drop(lexed, 2)
{ :ok, [{{ :'$1', :'$2', :'$3', :'$4' }, [to_condition(first, rest)], [:'$_'] }] }
{:ok, [{{:'$1', :'$2', :'$3', :'$4'}, [to_condition(first, rest)], [:'$_']}]}
else
:error
end
@@ -389,68 +398,64 @@ defmodule Version do
defp to_condition([:'==', version | _]) do
version = parse_condition(version)
{ :'==', :'$_', { :const, version } }
{:'==', :'$_', {:const, version}}
end
defp to_condition([:'!=', version | _]) do
version = parse_condition(version)
{ :'/=', :'$_', { :const, version } }
{:'/=', :'$_', {:const, version}}
end
defp to_condition([:'~>', version | _]) do
from = parse_condition(version, true)
to = approximate_upper(from)
{ :andalso, to_condition([:'>=', matchable_to_string(from)]),
to_condition([:'<', matchable_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 } = parse_condition(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 = 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 } = parse_condition(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 = 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
@@ -458,21 +463,21 @@ 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
{:ok, version} -> version
:error -> throw :invalid_matchspec
end
end
defp matchable_to_string({ major, minor, patch, pre }) do
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}"
@@ -480,26 +485,28 @@ defmodule Version do
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(Version.Requirement[source: source]) do
def to_string(%Version.Requirement{source: source}) do
source
end
end
defimpl Inspect, for: Version.Requirement do
def inspect(Version.Requirement[source: 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
-2
View File
@@ -21,5 +21,3 @@
{report, true},
{verbose, false}
]}.
{require_otp_vsn,"(17).*"}.
+89 -51
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
@@ -22,7 +23,16 @@ start(_Type, _Args) ->
%% Those can take a while, so let's do it in a new process
spawn(fun() ->
io:setopts(standard_io, [binary,{encoding,utf8}]),
io:setopts(standard_error, [{unicode,true}])
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,59 +127,87 @@ 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, to_binary(Error), to_binary(Token) } }
{ok, Forms} -> {ok, Forms};
{error, {Line, _, [Error, Token]}} -> {error, {Line, to_binary(Error), to_binary(Token)}}
catch
{ error, { Line, _, [Error, Token] } } -> { error, { Line, to_binary(Error), to_binary(Token) } }
{error, {Line, _, [Error, Token]}} -> {error, {Line, to_binary(Error), to_binary(Token)}}
end;
{ error, { Line, Error, Token }, _Rest, _SoFar } -> { error, { Line, to_binary(Error), to_binary(Token) } }
{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.
+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.
+42 -42
View File
@@ -5,32 +5,32 @@
%% 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
@@ -41,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, _) ->
@@ -89,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,
@@ -101,8 +101,8 @@ 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 }) ->
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])
@@ -117,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) ->
@@ -136,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")
@@ -157,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);
@@ -201,23 +201,23 @@ 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}.
+58 -58
View File
@@ -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, 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}),
{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,46 +43,46 @@ clause(Line, Fun, Args, Expr, Guards, Return, 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, 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)} }.
{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, [], _Return, S) ->
{ [], S };
{[], 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, Return, 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
@@ -94,7 +94,7 @@ do_clauses(Meta, DecoupledClauses, Return, 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),
@@ -104,9 +104,9 @@ do_clauses(Meta, DecoupledClauses, Return, 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),
@@ -114,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)),
@@ -134,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 }, Return, S) ->
{ Arg, Guards } = extract_guards(Condition),
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 }, Return, S) ->
{ Arg, Guards } = extract_guards(Condition),
each_clause({else, Meta, [Condition], Expr}, Return, S) ->
{Arg, Guards} = extract_guards(Condition),
clause(?line(Meta), fun elixir_translator:translate_args/2, [Arg], Expr, Guards, Return, S);
each_clause({ 'after', Meta, [Condition], Expr }, Return, S) ->
{ TCondition, SC } = elixir_translator:translate(Condition, S),
{ TExpr, SB } = elixir_translator:translate_block(Expr, Return, 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
@@ -163,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));
@@ -179,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",
+37 -34
View File
@@ -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.
@@ -140,7 +140,7 @@ module(Forms, File, RawOptions, Bootstrap, Callback) when
Options = RawOptions ++ elixir_code_server:call(erl_compiler_options),
Listname = elixir_utils:characters_to_list(File),
case compile:noenv_forms(Forms, [no_auto_import,return,{source,Listname}|Options]) of
case compile:noenv_forms([no_auto_import()|Forms], [return,{source,Listname}|Options]) of
{ok, ModuleName, Binary, Warnings} ->
format_warnings(Bootstrap, Warnings),
code:load_binary(ModuleName, Listname, Binary),
@@ -150,11 +150,14 @@ module(Forms, File, RawOptions, Bootstrap, Callback) when
format_errors(Errors)
end.
no_auto_import() ->
{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) ->
@@ -171,6 +174,7 @@ 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">>,
@@ -178,7 +182,6 @@ core_main() ->
<<"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,7 +213,7 @@ binary_to_path({ModuleName, Binary}, CompilePath) ->
%% ERROR HANDLING
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;
+87 -87
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,19 +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),
{ Function, Defaults, Super } = translate_definition(Kind, Line, Module, Name, Args, Guards, Body, E),
{Function, Defaults, Super} = translate_definition(Kind, Line, Module, Name, Args, Guards, Body, E),
DefaultsLength = length(Defaults),
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 +104,7 @@ store_definition(Line, Kind, CheckClauses, Name, Args, Guards, Body, MetaFile, #
default_function_for(Kind, Name, Default)) || Default <- Defaults],
make_struct_available(Kind, Module, Name, Args),
{ Name, Arity }.
{Name, Arity}.
%% @on_definition
@@ -113,15 +113,15 @@ 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 }
Pid -> Pid ! {struct_available, Module}
end;
make_struct_available(_, _, _, _) ->
ok.
@@ -134,18 +134,18 @@ 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.
@@ -155,49 +155,49 @@ compile_super(_Module, _, _E) -> ok.
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,
{EArgs, EGuards, EBody, _} = elixir_exp_clauses:def(fun elixir_def_defaults:expand/2,
Args, Guards, expr_from_body(Line, Body), E),
%% Macros receive a special argument on invocation. Notice it does
%% not affect the arity of the stored function, but the clause
%% already contains it.
EAllArgs = case is_macro(Kind) of
true -> [{ '_@CALLER', [{line,Line}], nil }|EArgs];
true -> [{'_@CALLER', [{line,Line}], nil}|EArgs];
false -> EArgs
end,
S = elixir_env:env_to_scope(E),
{ Unpacked, Defaults } = elixir_def_defaults:unpack(Kind, Name, EAllArgs, 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),
run_on_definition_callbacks(Kind, Line, Module, Name, EArgs, EGuards, EBody, E),
Function = { function, Line, Name, Arity, Clauses },
{ Function, Defaults, Super }.
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,
{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;
@@ -214,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)
@@ -234,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),
@@ -289,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
@@ -306,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.
@@ -338,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]);
+16 -16
View File
@@ -5,10 +5,10 @@
expand(Args, E) ->
lists:mapfoldl(fun
({ '\\\\', Meta, [Left, Right] }, Acc) ->
{ ELeft, EL } = elixir_exp:expand(Left, Acc),
{ ERight, _ } = elixir_exp:expand(Right, Acc#elixir_env{context=nil}),
{ { '\\\\', Meta, [ELeft, ERight] }, EL };
({'\\\\', Meta, [Left, Right]}, Acc) ->
{ELeft, EL} = elixir_exp:expand(Left, Acc),
{ERight, _} = elixir_exp:expand(Right, Acc#{context := nil}),
{{'\\\\', Meta, [ELeft, ERight]}, EL};
(Left, Acc) ->
elixir_exp:expand(Left, Acc)
end, E, Args).
@@ -23,24 +23,24 @@ unpack(Kind, Name, Args, S) ->
%% clauses and a list of clauses for the default calls.
unpack_each(Kind, Name, [{'\\\\', Line, [Expr, _]}|T] = List, Acc, Clauses, S) ->
Base = build_match(Acc, Line, []),
{ Args, Invoke } = extract_defaults(List, Line, length(Base), [], []),
{Args, Invoke} = extract_defaults(List, Line, length(Base), [], []),
{ DefArgs, SA } = elixir_clauses:match(fun elixir_translator:translate_args/2, Base ++ Args, S),
{ DefInvoke, _ } = elixir_translator:translate_args(Base ++ Invoke, SA),
{DefArgs, SA} = elixir_clauses:match(fun elixir_translator:translate_args/2, Base ++ Args, S),
{DefInvoke, _} = elixir_translator:translate_args(Base ++ Invoke, SA),
Call = { call, Line,
{ atom, Line, name_for_kind(Kind, Name) },
Call = {call, Line,
{atom, Line, name_for_kind(Kind, Name)},
DefInvoke
},
},
Clause = { clause, Line, DefArgs, [], [Call] },
Clause = {clause, Line, DefArgs, [], [Call]},
unpack_each(Kind, Name, T, [Expr|Acc], [Clause|Clauses], S);
unpack_each(Kind, Name, [H|T], Acc, Clauses, S) ->
unpack_each(Kind, Name, T, [H|Acc], Clauses, S);
unpack_each(_Kind, _Name, [], Acc, Clauses, _S) ->
{ lists:reverse(Acc), lists:reverse(Clauses) }.
{lists:reverse(Acc), lists:reverse(Clauses)}.
% Extract default values from args following the current default clause.
@@ -48,21 +48,21 @@ extract_defaults([{'\\\\', _, [_Expr, Default]}|T], Line, Counter, NewArgs, NewI
extract_defaults(T, Line, Counter, NewArgs, [Default|NewInvoke]);
extract_defaults([_|T], Line, Counter, NewArgs, NewInvoke) ->
H = { ?atom_concat(["_@D", Counter]), Line, nil },
H = {elixir_utils:atom_concat(["_@D", Counter]), Line, nil},
extract_defaults(T, Line, Counter + 1, [H|NewArgs], [H|NewInvoke]);
extract_defaults([], _Line, _Counter, NewArgs, NewInvoke) ->
{ lists:reverse(NewArgs), lists:reverse(NewInvoke) }.
{lists:reverse(NewArgs), lists:reverse(NewInvoke)}.
% Build matches for all the previous argument until the current default clause.
build_match([], _Line, Acc) -> Acc;
build_match([_|T], Line, Acc) ->
Var = { ?atom_concat(["_@D", length(T)]), Line, nil },
Var = {elixir_utils:atom_concat(["_@D", length(T)]), Line, nil},
build_match(T, Line, [Var|Acc]).
% Given the invoked function name based on the kind
name_for_kind(Kind, Name) when Kind == defmacro; Kind == defmacrop -> ?elixir_macro(Name);
name_for_kind(Kind, Name) when Kind == defmacro; Kind == defmacrop -> elixir_utils:macro_name(Name);
name_for_kind(_Kind, Name) -> Name.
+20 -20
View File
@@ -8,15 +8,15 @@ overridable(Module) ->
ets:lookup_element(elixir_module:data_table(Module), '__overridable', 2).
overridable(Module, Value) ->
ets:insert(elixir_module:data_table(Module), { '__overridable', Value }).
ets:insert(elixir_module:data_table(Module), {'__overridable', Value}).
%% Check if an overridable function is defined.
ensure_defined(Meta, Module, Tuple, S) ->
Overridable = overridable(Module),
case orddict:find(Tuple, Overridable) of
{ ok, { _, _, _, _ } } -> ok;
_ -> elixir_errors:form_error(Meta, S#elixir_scope.file, ?MODULE, { no_super, Module, Tuple })
{ok, {_, _, _, _}} -> ok;
_ -> elixir_errors:form_error(Meta, S#elixir_scope.file, ?MODULE, {no_super, Module, Tuple})
end.
%% Gets the name based on the function and stored overridables
@@ -24,33 +24,33 @@ ensure_defined(Meta, Module, Tuple, S) ->
name(Module, Function) ->
name(Module, Function, overridable(Module)).
name(_Module, { Name, _ } = Function, Overridable) ->
{ Count, _, _, _ } = orddict:fetch(Function, Overridable),
?atom_concat([Name, " (overridable ", Count, ")"]).
name(_Module, {Name, _} = Function, Overridable) ->
{Count, _, _, _} = orddict:fetch(Function, Overridable),
elixir_utils:atom_concat([Name, " (overridable ", Count, ")"]).
%% Store
store(Module, Function, GenerateName) ->
Overridable = overridable(Module),
case orddict:fetch(Function, Overridable) of
{ _Count, _Clause, _Neighbours, true } -> ok;
{ Count, Clause, Neighbours, false } ->
overridable(Module, orddict:store(Function, { Count, Clause, Neighbours, true }, Overridable)),
{ { { Name, Arity }, Kind, Line, File, _Check, Location, Defaults }, Clauses } = Clause,
{_Count, _Clause, _Neighbours, true} -> ok;
{Count, Clause, Neighbours, false} ->
overridable(Module, orddict:store(Function, {Count, Clause, Neighbours, true}, Overridable)),
{{{Name, Arity}, Kind, Line, File, _Check, Location, Defaults}, Clauses} = Clause,
{ FinalKind, FinalName } = case GenerateName of
true -> { defp, name(Module, Function, Overridable) };
false -> { Kind, Name }
{FinalKind, FinalName} = case GenerateName of
true -> {defp, name(Module, Function, Overridable)};
false -> {Kind, Name}
end,
case code:is_loaded('Elixir.Module.LocalsTracker') of
{ _, _ } ->
'Elixir.Module.LocalsTracker':reattach(Module, Kind, { Name, Arity }, Neighbours);
{_, _} ->
'Elixir.Module.LocalsTracker':reattach(Module, Kind, {Name, Arity}, Neighbours);
_ ->
ok
end,
Def = { function, Line, FinalName, Arity, Clauses },
Def = {function, Line, FinalName, Arity, Clauses},
elixir_def:store_each(false, FinalKind, File, Location,
elixir_def:table(Module), elixir_def:clauses_table(Module), Defaults, Def)
end.
@@ -58,18 +58,18 @@ store(Module, Function, GenerateName) ->
%% Store pending declarations that were not manually made concrete.
store_pending(Module) ->
[store(Module, X, false) || { X, { _, _, _, false } } <- overridable(Module),
[store(Module, X, false) || {X, {_, _, _, false}} <- overridable(Module),
not 'Elixir.Module':'defines?'(Module, X)].
%% Error handling
format_error({ no_super, Module, { Name, Arity } }) ->
Bins = [format_fa(X) || { X, { _, _, _, _ } } <- overridable(Module)],
format_error({no_super, Module, {Name, Arity}}) ->
Bins = [format_fa(X) || {X, {_, _, _, _}} <- overridable(Module)],
Joined = 'Elixir.Enum':join(Bins, <<", ">>),
io_lib:format("no super defined for ~ts/~B in module ~ts. Overridable functions available are: ~ts",
[Name, Arity, elixir_aliases:inspect(Module), Joined]).
format_fa({ Name, Arity }) ->
format_fa({Name, Arity}) ->
A = atom_to_binary(Name, utf8),
B = integer_to_binary(Arity),
<< A/binary, $/, B/binary >>.
+247 -220
View File
@@ -10,30 +10,31 @@
-include("elixir.hrl").
-import(ordsets, [is_element/2]).
%% Module macros
-define(kernel, 'Elixir.Kernel').
-define(map, 'Elixir.Map').
-define(node, 'Elixir.Node').
-define(process, 'Elixir.Process').
-define(system, 'Elixir.System').
-define(tuple, 'Elixir.Tuple').
default_functions() ->
[ { ?kernel, elixir_imported_functions() } ].
[ {?kernel, elixir_imported_functions()} ].
default_macros() ->
[ { ?kernel, elixir_imported_macros() } ].
[ {?kernel, elixir_imported_macros()} ].
default_requires() ->
[ 'Elixir.Kernel', 'Elixir.Kernel.Typespec' ].
find_import(Meta, Name, Arity, E) ->
Tuple = { Name, Arity },
Tuple = {Name, Arity},
case find_dispatch(Meta, Tuple, [], E) of
{ function, Receiver } ->
elixir_lexical:record_import(Receiver, E#elixir_env.lexical_tracker),
elixir_locals:record_import(Tuple, Receiver, E#elixir_env.module, E#elixir_env.function),
{function, Receiver} ->
elixir_lexical:record_import(Receiver, ?m(E, lexical_tracker)),
elixir_locals:record_import(Tuple, Receiver, ?m(E, module), ?m(E, function)),
Receiver;
{ macro, Receiver } ->
elixir_lexical:record_import(Receiver, E#elixir_env.lexical_tracker),
elixir_locals:record_import(Tuple, Receiver, E#elixir_env.module, E#elixir_env.function),
{macro, Receiver} ->
elixir_lexical:record_import(Receiver, ?m(E, lexical_tracker)),
elixir_locals:record_import(Tuple, Receiver, ?m(E, module), ?m(E, function)),
Receiver;
_ ->
false
@@ -42,47 +43,47 @@ find_import(Meta, Name, Arity, E) ->
%% Function retrieval
import_function(Meta, Name, Arity, E) ->
Tuple = { Name, Arity },
Tuple = {Name, Arity},
case find_dispatch(Meta, Tuple, [], E) of
{ function, Receiver } ->
elixir_lexical:record_import(Receiver, E#elixir_env.lexical_tracker),
elixir_locals:record_import(Tuple, Receiver, E#elixir_env.module, E#elixir_env.function),
{function, Receiver} ->
elixir_lexical:record_import(Receiver, ?m(E, lexical_tracker)),
elixir_locals:record_import(Tuple, Receiver, ?m(E, module), ?m(E, function)),
remote_function(Receiver, Name, Arity, E);
{ macro, _Receiver } ->
{macro, _Receiver} ->
false;
{ import, Receiver } ->
{import, Receiver} ->
require_function(Meta, Receiver, Name, Arity, E);
false ->
case elixir_import:special_form(Name, Arity) of
true -> false;
false ->
elixir_locals:record_local(Tuple, E#elixir_env.module, E#elixir_env.function),
{ local, Name, Arity }
elixir_locals:record_local(Tuple, ?m(E, module), ?m(E, function)),
{local, Name, Arity}
end
end.
require_function(_Meta, Receiver, Name, Arity, E) ->
case is_element({ Name, Arity }, get_optional_macros(Receiver)) of
case is_element({Name, Arity}, get_optional_macros(Receiver)) of
true -> false;
false -> remote_function(Receiver, Name, Arity, E)
end.
remote_function(Receiver, Name, Arity, E) ->
elixir_lexical:record_remote(Receiver, E#elixir_env.lexical_tracker),
elixir_lexical:record_remote(Receiver, ?m(E, lexical_tracker)),
case inline(Receiver, Name, Arity) of
{ AR, AN } -> { remote, AR, AN, Arity };
false -> { remote, Receiver, Name, Arity }
{AR, AN} -> {remote, AR, AN, Arity};
false -> {remote, Receiver, Name, Arity}
end.
%% Dispatches
dispatch_import(Meta, Name, Args, E, Callback) ->
Arity = length(Args),
case expand_import(Meta, { Name, Arity }, Args, E, []) of
{ ok, Receiver, Quoted } ->
case expand_import(Meta, {Name, Arity}, Args, E, []) of
{ok, Receiver, Quoted} ->
expand_quoted(Meta, Receiver, Name, Arity, Quoted, E);
{ ok, Receiver } ->
elixir_exp:expand({ { '.', [], [Receiver, Name] }, Meta, Args }, E);
{ok, Receiver} ->
elixir_exp:expand({{'.', [], [Receiver, Name]}, Meta, Args}, E);
error ->
Callback()
end.
@@ -91,11 +92,11 @@ dispatch_require(Meta, Receiver, Name, Args, E, Callback) when is_atom(Receiver)
Arity = length(Args),
case rewrite(Receiver, Name, Args, Arity) of
{ ok, AR, AN, AA } ->
{ok, AR, AN, AA} ->
Callback(AR, AN, AA);
false ->
case expand_require(Meta, Receiver, { Name, Arity }, Args, E) of
{ ok, Receiver, Quoted } -> expand_quoted(Meta, Receiver, Name, Arity, Quoted, E);
case expand_require(Meta, Receiver, {Name, Arity}, Args, E) of
{ok, Receiver, Quoted} -> expand_quoted(Meta, Receiver, Name, Arity, Quoted, E);
error -> Callback(Receiver, Name, Args)
end
end;
@@ -105,23 +106,23 @@ dispatch_require(_Meta, Receiver, Name, Args, _E, Callback) ->
%% Macros expansion
expand_import(Meta, { Name, Arity } = Tuple, Args, E, Extra) ->
Module = E#elixir_env.module,
expand_import(Meta, {Name, Arity} = Tuple, Args, E, Extra) ->
Module = ?m(E, module),
Dispatch = find_dispatch(Meta, Tuple, Extra, E),
Function = E#elixir_env.function,
Function = ?m(E, function),
Local = (Function /= nil) andalso (Function /= Tuple) andalso
elixir_locals:macro_for(Module, Name, Arity),
case Dispatch of
%% In case it is an import, we dispatch the import.
{ import, _ } ->
{import, _} ->
do_expand_import(Meta, Tuple, Args, Module, E, Dispatch);
%% There is a local and an import. This is a conflict unless
%% the receiver is the same as module (happens on bootstrap).
{ _, Receiver } when Local /= false, Receiver /= Module ->
Error = { macro_conflict, { Receiver, Name, Arity } },
elixir_errors:form_error(Meta, E#elixir_env.file, ?MODULE, Error);
{_, Receiver} when Local /= false, Receiver /= Module ->
Error = {macro_conflict, {Receiver, Name, Arity}},
elixir_errors:form_error(Meta, ?m(E, file), ?MODULE, Error);
%% There is no local. Dispatch the import.
_ when Local == false ->
@@ -130,33 +131,33 @@ expand_import(Meta, { Name, Arity } = Tuple, Args, E, Extra) ->
%% Dispatch to the local.
_ ->
elixir_locals:record_local(Tuple, Module, Function),
{ ok, Module, expand_macro_fun(Meta, Local(), Module, Name, Args, E) }
{ok, Module, expand_macro_fun(Meta, Local(), Module, Name, Args, E)}
end.
do_expand_import(Meta, { Name, Arity } = Tuple, Args, Module, E, Result) ->
do_expand_import(Meta, {Name, Arity} = Tuple, Args, Module, E, Result) ->
case Result of
{ function, Receiver } ->
elixir_lexical:record_import(Receiver, E#elixir_env.lexical_tracker),
elixir_locals:record_import(Tuple, Receiver, Module, E#elixir_env.function),
{function, Receiver} ->
elixir_lexical:record_import(Receiver, ?m(E, lexical_tracker)),
elixir_locals:record_import(Tuple, Receiver, Module, ?m(E, function)),
case rewrite(Receiver, Name, Args, Arity) of
{ ok, AR, AN, AA } ->
{ ok, AR, { { '.', [], [AR, AN] }, [], AA } };
{ok, AR, AN, AA} ->
{ok, AR, {{'.', [], [AR, AN]}, [], AA}};
false ->
{ ok, Receiver }
{ok, Receiver}
end;
{ macro, Receiver } ->
elixir_lexical:record_import(Receiver, E#elixir_env.lexical_tracker),
elixir_locals:record_import(Tuple, Receiver, Module, E#elixir_env.function),
{ ok, Receiver, expand_macro_named(Meta, Receiver, Name, Arity, Args, E) };
{ import, Receiver } ->
{macro, Receiver} ->
elixir_lexical:record_import(Receiver, ?m(E, lexical_tracker)),
elixir_locals:record_import(Tuple, Receiver, Module, ?m(E, function)),
{ok, Receiver, expand_macro_named(Meta, Receiver, Name, Arity, Args, E)};
{import, Receiver} ->
case expand_require([{require,false}|Meta], Receiver, Tuple, Args, E) of
{ ok, _, _ } = Response -> Response;
error -> { ok, Receiver }
{ok, _, _} = Response -> Response;
error -> {ok, Receiver}
end;
false when Module == ?kernel ->
case rewrite(Module, Name, Args, Arity) of
{ ok, AR, AN, AA } ->
{ ok, AR, { { '.', [], [AR, AN] }, [], AA } };
{ok, AR, AN, AA} ->
{ok, AR, {{'.', [], [AR, AN]}, [], AA}};
false ->
error
end;
@@ -164,19 +165,19 @@ do_expand_import(Meta, { Name, Arity } = Tuple, Args, Module, E, Result) ->
error
end.
expand_require(Meta, Receiver, { Name, Arity } = Tuple, Args, E) ->
Module = E#elixir_env.module,
expand_require(Meta, Receiver, {Name, Arity} = Tuple, Args, E) ->
Module = ?m(E, module),
case is_element(Tuple, get_optional_macros(Receiver)) of
true ->
Requires = E#elixir_env.requires,
Requires = ?m(E, requires),
case (Receiver == Module) orelse is_element(Receiver, Requires) orelse skip_require(Meta) of
true ->
elixir_lexical:record_remote(Receiver, E#elixir_env.lexical_tracker),
{ ok, Receiver, expand_macro_named(Meta, Receiver, Name, Arity, Args, E) };
elixir_lexical:record_remote(Receiver, ?m(E, lexical_tracker)),
{ok, Receiver, expand_macro_named(Meta, Receiver, Name, Arity, Args, E)};
false ->
Info = { unrequired_module, { Receiver, Name, length(Args), Requires } },
elixir_errors:form_error(Meta, E#elixir_env.file, ?MODULE, Info)
Info = {unrequired_module, {Receiver, Name, length(Args), Requires}},
elixir_errors:form_error(Meta, ?m(E, file), ?MODULE, Info)
end;
false ->
error
@@ -186,18 +187,20 @@ expand_require(Meta, Receiver, { Name, Arity } = Tuple, Args, E) ->
expand_macro_fun(Meta, Fun, Receiver, Name, Args, E) ->
Line = ?line(Meta),
EArg = { Line, E },
EArg = {Line, E},
try
apply(Fun, [EArg|Args])
catch
Kind:Reason ->
Info = [{ Receiver, Name, length(Args), location(Line, E) }, mfa(Line, E)],
erlang:raise(Kind, Reason, prune_stacktrace(Info, erlang:get_stacktrace(), EArg))
Arity = length(Args),
MFA = {Receiver, elixir_utils:macro_name(Name), Arity+1},
Info = [{Receiver, Name, Arity, [{file, "expanding macro"}]}, caller(Line, E)],
erlang:raise(Kind, Reason, prune_stacktrace(erlang:get_stacktrace(), MFA, Info, EArg))
end.
expand_macro_named(Meta, Receiver, Name, Arity, Args, E) ->
ProperName = ?elixir_macro(Name),
ProperName = elixir_utils:macro_name(Name),
ProperArity = Arity + 1,
Fun = fun Receiver:ProperName/ProperArity,
expand_macro_fun(Meta, Fun, Receiver, Name, Args, E).
@@ -208,59 +211,61 @@ expand_quoted(Meta, Receiver, Name, Arity, Quoted, E) ->
try
elixir_exp:expand(
elixir_quote:linify_with_context_counter(Line, { Receiver, Next }, Quoted),
elixir_quote:linify_with_context_counter(Line, {Receiver, Next}, Quoted),
E)
catch
Kind:Reason ->
Info = [{ Receiver, Name, Arity, location(Line, E) }, mfa(Line, E)],
erlang:raise(Kind, Reason, prune_stacktrace(Info, erlang:get_stacktrace(), nil))
MFA = {Receiver, elixir_utils:macro_name(Name), Arity+1},
Info = [{Receiver, Name, Arity, [{file, "expanding macro"}]}, caller(Line, E)],
erlang:raise(Kind, Reason, prune_stacktrace(erlang:get_stacktrace(), MFA, Info, nil))
end.
mfa(Line, #elixir_env{module=nil} = E) ->
{ elixir_compiler, '__FILE__', 2, location(Line, E) };
mfa(Line, #elixir_env{module=Module, function=nil} = E) ->
{ Module, '__MODULE__', 0, location(Line, E) };
mfa(Line, #elixir_env{module=Module, function={ Name, Arity }} = E) ->
{ Module, Name, Arity, location(Line, E) }.
caller(Line, #{module := nil} = E) ->
{elixir_compiler, '__FILE__', 2, location(Line, E)};
caller(Line, #{module := Module, function := nil} = E) ->
{Module, '__MODULE__', 0, location(Line, E)};
caller(Line, #{module := Module, function := {Name, Arity}} = E) ->
{Module, Name, Arity, location(Line, E)}.
location(Line, E) ->
[{ file, elixir_utils:characters_to_list(E#elixir_env.file) }, { line, Line }].
[{file, elixir_utils:characters_to_list(elixir_utils:relative_to_cwd(?m(E, file)))},
{line, Line}].
%% Helpers
skip_require(Meta) ->
lists:keyfind(require, 1, Meta) == { require, false }.
lists:keyfind(require, 1, Meta) == {require, false}.
find_dispatch(Meta, Tuple, Extra, E) ->
case is_import(Meta) of
{ import, _ } = Import ->
{import, _} = Import ->
Import;
false ->
Funs = E#elixir_env.functions,
Macs = Extra ++ E#elixir_env.macros,
Funs = ?m(E, functions),
Macs = Extra ++ ?m(E, macros),
FunMatch = find_dispatch(Tuple, Funs),
MacMatch = find_dispatch(Tuple, Macs),
case { FunMatch, MacMatch } of
{ [], [Receiver] } -> { macro, Receiver };
{ [Receiver], [] } -> { function, Receiver };
{ [], [] } -> false;
case {FunMatch, MacMatch} of
{[], [Receiver]} -> {macro, Receiver};
{[Receiver], []} -> {function, Receiver};
{[], []} -> false;
_ ->
{ Name, Arity } = Tuple,
{Name, Arity} = Tuple,
[First, Second|_] = FunMatch ++ MacMatch,
Error = { ambiguous_call, { First, Second, Name, Arity } },
elixir_errors:form_error(Meta, E#elixir_env.file, ?MODULE, Error)
Error = {ambiguous_call, {First, Second, Name, Arity}},
elixir_errors:form_error(Meta, ?m(E, file), ?MODULE, Error)
end
end.
find_dispatch(Tuple, List) ->
[Receiver || { Receiver, Set } <- List, is_element(Tuple, Set)].
[Receiver || {Receiver, Set} <- List, is_element(Tuple, Set)].
is_import(Meta) ->
case lists:keyfind(import, 1, Meta) of
{ import, _ } = Import ->
{import, _} = Import ->
case lists:keyfind(context, 1, Meta) of
{ context, _ } -> Import;
{context, _} -> Import;
false ->
false
end;
@@ -268,28 +273,31 @@ is_import(Meta) ->
false
end.
%% We've reached the invoked macro, skip it with the rest
prune_stacktrace(Info, [{ _, _, [E|_], _ }|_], E) ->
% %% We've reached the macro wrapper fun, skip it with the rest
prune_stacktrace([{_, _, [E|_], _}|_], _MFA, Info, E) ->
Info;
%% We've reached the invoked macro, skip it
prune_stacktrace([{M, F, A, _}|_], {M, F, A}, Info, _E) ->
Info;
%% We've reached the elixir_dispatch internals, skip it with the rest
prune_stacktrace(Info, [{ Mod, _, _, _ }|_], _) when Mod == elixir_dispatch; Mod == elixir_exp ->
prune_stacktrace([{Mod, _, _, _}|_], _MFA, Info, _E) when Mod == elixir_dispatch; Mod == elixir_exp ->
Info;
prune_stacktrace(Info, [H|T], E) ->
[H|prune_stacktrace(Info, T, E)];
prune_stacktrace(Info, [], _) ->
prune_stacktrace([H|T], MFA, Info, E) ->
[H|prune_stacktrace(T, MFA, Info, E)];
prune_stacktrace([], _MFA, Info, _E) ->
Info.
%% ERROR HANDLING
format_error({ unrequired_module, { Receiver, Name, Arity, _Required }}) ->
format_error({unrequired_module, {Receiver, Name, Arity, _Required}}) ->
Module = elixir_aliases:inspect(Receiver),
io_lib:format("you must require ~ts before invoking the macro ~ts.~ts/~B",
[Module, Module, Name, Arity]);
format_error({ macro_conflict, { Receiver, Name, Arity } }) ->
format_error({macro_conflict, {Receiver, Name, Arity}}) ->
io_lib:format("call to local macro ~ts/~B conflicts with imported ~ts.~ts/~B, "
"please rename the local macro or remove the conflicting import",
[Name, Arity, elixir_aliases:inspect(Receiver), Name, Arity]);
format_error({ ambiguous_call, { Mod1, Mod2, Name, Arity }}) ->
format_error({ambiguous_call, {Mod1, Mod2, Name, Arity}}) ->
io_lib:format("function ~ts/~B imported from both ~ts and ~ts, call is ambiguous",
[Name, Arity, elixir_aliases:inspect(Mod1), elixir_aliases:inspect(Mod2)]).
@@ -300,13 +308,13 @@ get_optional_macros(erlang) -> [];
get_optional_macros(Receiver) ->
case code:ensure_loaded(Receiver) of
{ module, Receiver } ->
{module, Receiver} ->
try
Receiver:'__info__'(macros)
catch
error:undef -> []
end;
{ error, _ } -> []
{error, _} -> []
end.
elixir_imported_functions() ->
@@ -323,137 +331,156 @@ elixir_imported_macros() ->
error:undef -> []
end.
rewrite(?kernel, atom_to_binary, [Arg], 1) ->
{ ok, erlang, atom_to_binary, [Arg, utf8] };
rewrite(?kernel, binary_to_atom, [Arg], 1) ->
{ ok, erlang, binary_to_atom, [Arg, utf8] };
rewrite(?kernel, binary_to_existing_atom, [Arg], 1) ->
{ ok, erlang, binary_to_existing_atom, [Arg, utf8] };
rewrite(?kernel, elem, [Tuple, Index], 2) ->
{ ok, erlang, element, [increment(Index), Tuple] };
rewrite(?kernel, set_elem, [Tuple, Index, Value], 2) ->
{ ok, erlang, setelement, [increment(Index), Tuple, Value] };
rewrite(?process, monitor, [Arg], 1) ->
{ ok, erlang, monitor, [process, Arg] };
rewrite(?kernel, atom_to_binary, [Arg], _) ->
{ok, erlang, atom_to_binary, [Arg, utf8]};
rewrite(?kernel, binary_to_atom, [Arg], _) ->
{ok, erlang, binary_to_atom, [Arg, utf8]};
rewrite(?kernel, binary_to_existing_atom, [Arg], _) ->
{ok, erlang, binary_to_existing_atom, [Arg, utf8]};
rewrite(?kernel, elem, [Tuple, Index], _) ->
{ok, erlang, element, [increment(Index), Tuple]};
rewrite(?kernel, set_elem, [Tuple, Index, Value], _) ->
{ok, erlang, setelement, [increment(Index), Tuple, Value]};
rewrite(?process, monitor, [Arg], _) ->
{ok, erlang, monitor, [process, Arg]};
rewrite(?tuple, insert_at, [Tuple, Index, Term], _) ->
{ok, erlang, insert_element, [increment(Index), Tuple, Term]};
rewrite(?tuple, delete_at, [Tuple, Index], _) ->
{ok, erlang, delete_element, [increment(Index), Tuple]};
rewrite(?tuple, duplicate, [Data, Size], _) ->
{ok, erlang, make_tuple, [Size, Data]};
rewrite(?map, 'has_key?', [Map, Key], _) ->
{ok, maps, is_key, [Key, Map]};
rewrite(?map, fetch, [Map, Key], _) ->
{ok, maps, find, [Key, Map]};
rewrite(?map, put, [Map, Key, Value], _) ->
{ok, maps, put, [Key, Value, Map]};
rewrite(?map, delete, [Map, Key], _) ->
{ok, maps, remove, [Key, Map]};
rewrite(Receiver, Name, Args, Arity) ->
case inline(Receiver, Name, Arity) of
{ AR, AN } -> { ok, AR, AN, Args };
false -> false
{AR, AN} -> {ok, AR, AN, Args};
false -> false
end.
increment(Number) when is_number(Number) ->
Number + 1;
increment(Other) ->
{ { '.', [], [erlang, '+'] }, [], [Other, 1] }.
{{'.', [], [erlang, '+']}, [], [Other, 1]}.
inline(?kernel, '+', 2) -> { erlang, '+' };
inline(?kernel, '-', 2) -> { erlang, '-' };
inline(?kernel, '+', 1) -> { erlang, '+' };
inline(?kernel, '-', 1) -> { erlang, '-' };
inline(?kernel, '*', 2) -> { erlang, '*' };
inline(?kernel, '/', 2) -> { erlang, '/' };
inline(?kernel, '++', 2) -> { erlang, '++' };
inline(?kernel, '--', 2) -> { erlang, '--' };
inline(?kernel, 'xor', 2) -> { erlang, 'xor' };
inline(?kernel, 'not', 1) -> { erlang, 'not' };
inline(?kernel, '<', 2) -> { erlang, '<' };
inline(?kernel, '>', 2) -> { erlang, '>' };
inline(?kernel, '<=', 2) -> { erlang, '=<' };
inline(?kernel, '>=', 2) -> { erlang, '>=' };
inline(?kernel, '==', 2) -> { erlang, '==' };
inline(?kernel, '!=', 2) -> { erlang, '/=' };
inline(?kernel, '===', 2) -> { erlang, '=:=' };
inline(?kernel, '!==', 2) -> { erlang, '=/=' };
inline(?kernel, abs, 1) -> { erlang, abs };
inline(?kernel, apply, 2) -> { erlang, apply };
inline(?kernel, apply, 3) -> { erlang, apply };
inline(?kernel, atom_to_list, 1) -> { erlang, atom_to_list };
inline(?kernel, binary_part, 3) -> { erlang, binary_part };
inline(?kernel, binary_to_float, 1) -> { erlang, binary_to_float };
inline(?kernel, binary_to_float, 2) -> { erlang, binary_to_float };
inline(?kernel, binary_to_integer, 1) -> { erlang, binary_to_integer };
inline(?kernel, binary_to_integer, 2) -> { erlang, binary_to_integer };
inline(?kernel, bit_size, 1) -> { erlang, bit_size };
inline(?kernel, bitstring_to_list, 1) -> { erlang, bitstring_to_list };
inline(?kernel, byte_size, 1) -> { erlang, byte_size };
inline(?kernel, 'div', 2) -> { erlang, 'div' };
inline(?kernel, exit, 1) -> { erlang, exit };
inline(?kernel, float_to_binary, 1) -> { erlang, float_to_binary };
inline(?kernel, float_to_list, 1) -> { erlang, float_to_list };
inline(?kernel, hd, 1) -> { erlang, hd };
inline(?kernel, integer_to_binary, 1) -> { erlang, integer_to_binary };
inline(?kernel, integer_to_binary, 2) -> { erlang, integer_to_binary };
inline(?kernel, integer_to_list, 1) -> { erlang, integer_to_list };
inline(?kernel, integer_to_list, 2) -> { erlang, integer_to_list };
inline(?kernel, iolist_size, 1) -> { erlang, iolist_size };
inline(?kernel, iolist_to_binary, 1) -> { erlang, iolist_to_binary };
inline(?kernel, is_atom, 1) -> { erlang, is_atom };
inline(?kernel, is_binary, 1) -> { erlang, is_binary };
inline(?kernel, is_bitstring, 1) -> { erlang, is_bitstring };
inline(?kernel, is_boolean, 1) -> { erlang, is_boolean };
inline(?kernel, is_float, 1) -> { erlang, is_float };
inline(?kernel, is_function, 1) -> { erlang, is_function };
inline(?kernel, is_function, 2) -> { erlang, is_function };
inline(?kernel, is_integer, 1) -> { erlang, is_integer };
inline(?kernel, is_list, 1) -> { erlang, is_list };
inline(?kernel, is_map, 1) -> { erlang, is_map };
inline(?kernel, is_number, 1) -> { erlang, is_number };
inline(?kernel, is_pid, 1) -> { erlang, is_pid };
inline(?kernel, is_port, 1) -> { erlang, is_port };
inline(?kernel, is_reference, 1) -> { erlang, is_reference };
inline(?kernel, is_tuple, 1) -> { erlang, is_tuple };
inline(?kernel, length, 1) -> { erlang, length };
inline(?kernel, list_to_atom, 1) -> { erlang, list_to_atom };
inline(?kernel, list_to_bitstring, 1) -> { erlang, list_to_bitstring };
inline(?kernel, list_to_existing_atom, 1) -> { erlang, list_to_existing_atom };
inline(?kernel, list_to_float, 1) -> { erlang, list_to_float };
inline(?kernel, list_to_integer, 1) -> { erlang, list_to_integer };
inline(?kernel, list_to_integer, 2) -> { erlang, list_to_integer };
inline(?kernel, list_to_tuple, 1) -> { erlang, list_to_tuple };
inline(?kernel, make_ref, 0) -> { erlang, make_ref };
inline(?kernel, map_size, 1) -> { erlang, map_size };
inline(?kernel, max, 2) -> { erlang, max };
inline(?kernel, min, 2) -> { erlang, min };
inline(?kernel, node, 0) -> { erlang, node };
inline(?kernel, node, 1) -> { erlang, node };
inline(?kernel, 'rem', 2) -> { erlang, 'rem' };
inline(?kernel, round, 1) -> { erlang, round };
inline(?kernel, self, 0) -> { erlang, self };
inline(?kernel, send, 2) -> { erlang, send };
inline(?kernel, size, 1) -> { erlang, size };
inline(?kernel, spawn, 1) -> { erlang, spawn };
inline(?kernel, spawn, 3) -> { erlang, spawn };
inline(?kernel, spawn_link, 1) -> { erlang, spawn_link };
inline(?kernel, spawn_link, 3) -> { erlang, spawn_link };
inline(?kernel, throw, 1) -> { erlang, throw };
inline(?kernel, tl, 1) -> { erlang, tl };
inline(?kernel, trunc, 1) -> { erlang, trunc };
inline(?kernel, tuple_size, 1) -> { erlang, tuple_size };
inline(?kernel, tuple_to_list, 1) -> { erlang, tuple_to_list };
inline(?kernel, '+', 2) -> {erlang, '+'};
inline(?kernel, '-', 2) -> {erlang, '-'};
inline(?kernel, '+', 1) -> {erlang, '+'};
inline(?kernel, '-', 1) -> {erlang, '-'};
inline(?kernel, '*', 2) -> {erlang, '*'};
inline(?kernel, '/', 2) -> {erlang, '/'};
inline(?kernel, '++', 2) -> {erlang, '++'};
inline(?kernel, '--', 2) -> {erlang, '--'};
inline(?kernel, 'xor', 2) -> {erlang, 'xor'};
inline(?kernel, 'not', 1) -> {erlang, 'not'};
inline(?kernel, '<', 2) -> {erlang, '<'};
inline(?kernel, '>', 2) -> {erlang, '>'};
inline(?kernel, '<=', 2) -> {erlang, '=<'};
inline(?kernel, '>=', 2) -> {erlang, '>='};
inline(?kernel, '==', 2) -> {erlang, '=='};
inline(?kernel, '!=', 2) -> {erlang, '/='};
inline(?kernel, '===', 2) -> {erlang, '=:='};
inline(?kernel, '!==', 2) -> {erlang, '=/='};
inline(?kernel, abs, 1) -> {erlang, abs};
inline(?kernel, apply, 2) -> {erlang, apply};
inline(?kernel, apply, 3) -> {erlang, apply};
inline(?kernel, atom_to_list, 1) -> {erlang, atom_to_list};
inline(?kernel, binary_part, 3) -> {erlang, binary_part};
inline(?kernel, binary_to_float, 1) -> {erlang, binary_to_float};
inline(?kernel, binary_to_float, 2) -> {erlang, binary_to_float};
inline(?kernel, binary_to_integer, 1) -> {erlang, binary_to_integer};
inline(?kernel, binary_to_integer, 2) -> {erlang, binary_to_integer};
inline(?kernel, bit_size, 1) -> {erlang, bit_size};
inline(?kernel, bitstring_to_list, 1) -> {erlang, bitstring_to_list};
inline(?kernel, byte_size, 1) -> {erlang, byte_size};
inline(?kernel, 'div', 2) -> {erlang, 'div'};
inline(?kernel, exit, 1) -> {erlang, exit};
inline(?kernel, float_to_binary, 1) -> {erlang, float_to_binary};
inline(?kernel, float_to_list, 1) -> {erlang, float_to_list};
inline(?kernel, hd, 1) -> {erlang, hd};
inline(?kernel, integer_to_binary, 1) -> {erlang, integer_to_binary};
inline(?kernel, integer_to_binary, 2) -> {erlang, integer_to_binary};
inline(?kernel, integer_to_list, 1) -> {erlang, integer_to_list};
inline(?kernel, integer_to_list, 2) -> {erlang, integer_to_list};
inline(?kernel, iodata_length, 1) -> {erlang, iolist_size};
inline(?kernel, iodata_to_binary, 1) -> {erlang, iolist_to_binary};
inline(?kernel, is_atom, 1) -> {erlang, is_atom};
inline(?kernel, is_binary, 1) -> {erlang, is_binary};
inline(?kernel, is_bitstring, 1) -> {erlang, is_bitstring};
inline(?kernel, is_boolean, 1) -> {erlang, is_boolean};
inline(?kernel, is_float, 1) -> {erlang, is_float};
inline(?kernel, is_function, 1) -> {erlang, is_function};
inline(?kernel, is_function, 2) -> {erlang, is_function};
inline(?kernel, is_integer, 1) -> {erlang, is_integer};
inline(?kernel, is_list, 1) -> {erlang, is_list};
inline(?kernel, is_map, 1) -> {erlang, is_map};
inline(?kernel, is_number, 1) -> {erlang, is_number};
inline(?kernel, is_pid, 1) -> {erlang, is_pid};
inline(?kernel, is_port, 1) -> {erlang, is_port};
inline(?kernel, is_reference, 1) -> {erlang, is_reference};
inline(?kernel, is_tuple, 1) -> {erlang, is_tuple};
inline(?kernel, length, 1) -> {erlang, length};
inline(?kernel, list_to_atom, 1) -> {erlang, list_to_atom};
inline(?kernel, list_to_bitstring, 1) -> {erlang, list_to_bitstring};
inline(?kernel, list_to_existing_atom, 1) -> {erlang, list_to_existing_atom};
inline(?kernel, list_to_float, 1) -> {erlang, list_to_float};
inline(?kernel, list_to_integer, 1) -> {erlang, list_to_integer};
inline(?kernel, list_to_integer, 2) -> {erlang, list_to_integer};
inline(?kernel, list_to_tuple, 1) -> {erlang, list_to_tuple};
inline(?kernel, make_ref, 0) -> {erlang, make_ref};
inline(?kernel, map_size, 1) -> {erlang, map_size};
inline(?kernel, max, 2) -> {erlang, max};
inline(?kernel, min, 2) -> {erlang, min};
inline(?kernel, node, 0) -> {erlang, node};
inline(?kernel, node, 1) -> {erlang, node};
inline(?kernel, 'rem', 2) -> {erlang, 'rem'};
inline(?kernel, round, 1) -> {erlang, round};
inline(?kernel, self, 0) -> {erlang, self};
inline(?kernel, send, 2) -> {erlang, send};
inline(?kernel, size, 1) -> {erlang, size};
inline(?kernel, spawn, 1) -> {erlang, spawn};
inline(?kernel, spawn, 3) -> {erlang, spawn};
inline(?kernel, spawn_link, 1) -> {erlang, spawn_link};
inline(?kernel, spawn_link, 3) -> {erlang, spawn_link};
inline(?kernel, spawn_monitor, 1) -> {erlang, spawn_monitor};
inline(?kernel, spawn_monitor, 3) -> {erlang, spawn_monitor};
inline(?kernel, throw, 1) -> {erlang, throw};
inline(?kernel, tl, 1) -> {erlang, tl};
inline(?kernel, trunc, 1) -> {erlang, trunc};
inline(?kernel, tuple_size, 1) -> {erlang, tuple_size};
inline(?kernel, tuple_to_list, 1) -> {erlang, tuple_to_list};
inline(?process, exit, 2) -> { erlang, exit };
inline(?process, spawn, 1) -> { erlang, spawn };
inline(?process, spawn, 2) -> { erlang, spawn_opt };
inline(?process, spawn, 3) -> { erlang, spawn };
inline(?process, spawn, 4) -> { erlang, spawn_opt };
inline(?process, spawn_link, 1) -> { erlang, spawn_link };
inline(?process, spawn_link, 3) -> { erlang, spawn_link };
inline(?process, spawn_monitor, 1) -> { erlang, spawn_monitor };
inline(?process, spawn_monitor, 3) -> { erlang, spawn_monitor };
inline(?process, demonitor, 1) -> { erlang, demonitor };
inline(?process, demonitor, 2) -> { erlang, demonitor };
inline(?process, link, 1) -> { erlang, link };
inline(?process, unlink, 1) -> { erlang, unlink };
inline(?process, exit, 2) -> {erlang, exit};
inline(?process, spawn, 2) -> {erlang, spawn_opt};
inline(?process, spawn, 4) -> {erlang, spawn_opt};
inline(?process, demonitor, 1) -> {erlang, demonitor};
inline(?process, demonitor, 2) -> {erlang, demonitor};
inline(?process, link, 1) -> {erlang, link};
inline(?process, unlink, 1) -> {erlang, unlink};
inline(?node, spawn, 2) -> { erlang, spawn };
inline(?node, spawn, 3) -> { erlang, spawn_opt };
inline(?node, spawn, 4) -> { erlang, spawn };
inline(?node, spawn, 5) -> { erlang, spawn_opt };
inline(?node, spawn_link, 2) -> { erlang, spawn_link };
inline(?node, spawn_link, 4) -> { erlang, spawn_link };
inline(?node, spawn_monitor, 2) -> { erlang, spawn_monitor };
inline(?node, spawn_monitor, 4) -> { erlang, spawn_monitor };
inline(?node, spawn, 2) -> {erlang, spawn};
inline(?node, spawn, 3) -> {erlang, spawn_opt};
inline(?node, spawn, 4) -> {erlang, spawn};
inline(?node, spawn, 5) -> {erlang, spawn_opt};
inline(?node, spawn_link, 2) -> {erlang, spawn_link};
inline(?node, spawn_link, 4) -> {erlang, spawn_link};
inline(?node, spawn_monitor, 2) -> {erlang, spawn_monitor};
inline(?node, spawn_monitor, 4) -> {erlang, spawn_monitor};
inline(?system, stacktrace, 0) -> { erlang, get_stacktrace };
inline(?system, stacktrace, 0) -> {erlang, get_stacktrace};
inline(?map, keys, 1) -> {maps, keys};
inline(?map, merge, 2) -> {maps, merge};
inline(?map, size, 1) -> {maps, size};
inline(?map, values, 1) -> {maps, values};
inline(?map, to_list, 1) -> {maps, to_list};
inline(_, _, _) -> false.
+33 -20
View File
@@ -1,46 +1,59 @@
-module(elixir_env).
-include("elixir.hrl").
-export([ex_to_env/1, env_to_scope/1, env_to_scope_with_vars/2, env_to_ex/1]).
-export([new/0, linify/1, env_to_scope/1, env_to_scope_with_vars/2]).
-export([mergea/2, mergev/2, merge_vars/2, merge_opt_vars/2]).
%% Conversion in between #elixir_env, #elixir_scope and Macro.Env
new() ->
#{'__struct__' => 'Elixir.Macro.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 some constructs
lexical_tracker => nil, %% holds the lexical tracker pid
local => nil}. %% the module to delegate local functions to
env_to_ex({ Line, #elixir_env{} = Env }) ->
erlang:setelement(1, Env#elixir_env{line=Line}, 'Elixir.Macro.Env').
linify({Line, Env}) ->
Env#{line := Line}.
ex_to_env(Env) when element(1, Env) == 'Elixir.Macro.Env' ->
erlang:setelement(1, Env, elixir_env).
env_to_scope(#{module := Module, file := File, function := Function, context := Context}) ->
#elixir_scope{module=Module, file=File, function=Function, context=Context}.
env_to_scope(#elixir_env{module=Module,file=File,function=Function,context=Context}) ->
#elixir_scope{module=Module,file=File,function=Function,context=Context}.
env_to_scope_with_vars(#elixir_env{} = Env, Vars) ->
env_to_scope_with_vars(Env, Vars) ->
(env_to_scope(Env))#elixir_scope{
vars=orddict:from_list(Vars),
counter=[{'_',length(Vars)}]
}.
}.
%% SCOPE MERGING
%% Receives two scopes and return a new scope based on the second
%% with their variables merged.
mergev(E1, E2) when is_list(E1) ->
E2#elixir_env{
vars=merge_vars(E1, E2#elixir_env.vars),
export_vars=merge_opt_vars(E1, E2#elixir_env.export_vars)
};
E2#{
vars := merge_vars(E1, ?m(E2, vars)),
export_vars := merge_opt_vars(E1, ?m(E2, export_vars))
};
mergev(E1, E2) ->
E2#elixir_env{
vars=merge_vars(E1#elixir_env.vars, E2#elixir_env.vars),
export_vars=merge_opt_vars(E1#elixir_env.export_vars, E2#elixir_env.export_vars)
}.
E2#{
vars := merge_vars(?m(E1, vars), ?m(E2, vars)),
export_vars := merge_opt_vars(?m(E1, export_vars), ?m(E2, export_vars))
}.
%% Receives two scopes and return the later scope
%% keeping the variables from the first (imports
%% and everything else are passed forward).
mergea(E1, E2) ->
E2#elixir_env{vars=E1#elixir_env.vars}.
E2#{vars := ?m(E1, vars)}.
merge_vars(V1, V2) -> ordsets:union(V1, V2).
+27 -19
View File
@@ -13,7 +13,7 @@ warn(Warning) ->
CompilerPid = get(elixir_compiler_pid),
if
CompilerPid =/= undefined ->
elixir_code_server:cast({ register_warning, CompilerPid });
elixir_code_server:cast({register_warning, CompilerPid});
true -> false
end,
io:put_chars(standard_error, Warning).
@@ -42,7 +42,7 @@ compile_error(Meta, File, Format, Args) when is_list(Format) ->
%% Raised on tokenizing/parsing
-spec parse_error(line_or_meta(), binary(), iolist() | atom(), [] | iolist()) -> no_return().
-spec parse_error(line_or_meta(), binary(), binary(), binary()) -> no_return().
parse_error(Meta, File, Error, <<>>) ->
Message = case Error of
@@ -51,9 +51,17 @@ parse_error(Meta, File, Error, <<>>) ->
end,
raise(Meta, File, 'Elixir.TokenMissingError', Message);
%% Show a nicer message for missing end tokens
parse_error(Meta, File, <<"syntax error before: ">>, <<"'end'">>) ->
raise(Meta, File, 'Elixir.SyntaxError', <<"unexpected token: end">>);
%% Binaries are wrapped in [<<...>>], so we need to unwrap them
parse_error(Meta, File, Error, <<"[<<", Token/binary>>) when is_binary(Error) ->
Rest = binary_part(Token, 0, byte_size(Token) - 3),
Message = <<Error / binary, Rest / binary >>,
raise(Meta, File, 'Elixir.SyntaxError', Message);
%% Everything else is fine as is
parse_error(Meta, File, Error, Token) when is_binary(Error), is_binary(Token) ->
Message = <<Error / binary, Token / binary >>,
raise(Meta, File, 'Elixir.SyntaxError', Message).
@@ -68,26 +76,26 @@ deprecation(Meta, File, Message, Args) ->
%% Handle warnings and errors (called during module compilation)
%% Ignore on bootstrap
handle_file_warning(true, _File, { _Line, sys_core_fold, nomatch_guard }) -> [];
handle_file_warning(true, _File, { _Line, sys_core_fold, { nomatch_shadow, _ } }) -> [];
handle_file_warning(true, _File, {_Line, sys_core_fold, nomatch_guard}) -> [];
handle_file_warning(true, _File, {_Line, sys_core_fold, {nomatch_shadow, _}}) -> [];
%% Ignore always
handle_file_warning(_, _File, { _Line, sys_core_fold, useless_building }) -> [];
handle_file_warning(_, _File, {_Line, sys_core_fold, useless_building}) -> [];
%% This is an Erlang bug, it considers { tuple, _ }.call to always fail
handle_file_warning(_, _File, { _Line, v3_kernel, bad_call }) -> [];
%% This is an Erlang bug, it considers {tuple, _}.call to always fail
handle_file_warning(_, _File, {_Line, v3_kernel, bad_call}) -> [];
%% We handle unused local warnings ourselves
handle_file_warning(_, _File, { _Line, erl_lint, { unused_function, _ } }) -> [];
handle_file_warning(_, _File, {_Line, erl_lint, {unused_function, _}}) -> [];
%% Make no_effect clauses pretty
handle_file_warning(_, File, { Line, sys_core_fold, { no_effect, { erlang, F, A } } }) ->
{ Fmt, Args } = case erl_internal:comp_op(F, A) of
true -> { "use of operator ~ts has no effect", [translate_comp_op(F)] };
handle_file_warning(_, File, {Line, sys_core_fold, {no_effect, {erlang, F, A}}}) ->
{Fmt, Args} = case erl_internal:comp_op(F, A) of
true -> {"use of operator ~ts has no effect", [translate_comp_op(F)]};
false ->
case erl_internal:bif(F, A) of
false -> { "the call to :erlang.~ts/~B has no effect", [F,A] };
true -> { "the call to ~ts/~B has no effect", [F,A] }
false -> {"the call to :erlang.~ts/~B has no effect", [F,A]};
true -> {"the call to ~ts/~B has no effect", [F,A]}
end
end,
Message = io_lib:format(Fmt, Args),
@@ -95,10 +103,10 @@ handle_file_warning(_, File, { Line, sys_core_fold, { no_effect, { erlang, F, A
%% Rewrite undefined behaviour to check for protocols
handle_file_warning(_, File, {Line,erl_lint,{undefined_behaviour_func,{Fun,Arity},Module}}) ->
{ DefKind, Def, DefArity } =
{DefKind, Def, DefArity} =
case atom_to_list(Fun) of
"MACRO-" ++ Rest -> { macro, list_to_atom(Rest), Arity - 1 };
_ -> { function, Fun, Arity }
"MACRO-" ++ Rest -> {macro, list_to_atom(Rest), Arity - 1};
_ -> {function, Fun, Arity}
end,
Kind = protocol_or_behaviour(Module),
@@ -124,7 +132,7 @@ handle_file_warning(_, _File, {_Line,erl_lint,{shadowed_var,_Var,_Where}}) ->
%% Properly format other unused vars
handle_file_warning(_, File, {Line,erl_lint,{unused_var,Var}}) ->
Message = format_error(erl_lint, { unused_var, format_var(Var) }),
Message = format_error(erl_lint, {unused_var, format_var(Var)}),
warn(Line, File, Message);
%% Default behaviour
@@ -195,10 +203,10 @@ protocol_or_behaviour(Module) ->
is_protocol(Module) ->
case code:ensure_loaded(Module) of
{ module, _ } ->
{module, _} ->
erlang:function_exported(Module, '__protocol__', 1) andalso
Module:'__protocol__'(name) == Module;
{ error, _ } ->
{error, _} ->
false
end.
+219 -218
View File
@@ -5,233 +5,233 @@
%% =
expand({ '=', Meta, [Left, Right] }, E) ->
expand({'=', Meta, [Left, Right]}, E) ->
assert_no_guard_scope(Meta, '=', E),
{ ERight, ER } = expand(Right, E),
{ ELeft, EL } = elixir_exp_clauses:match(fun expand/2, Left, E),
{ { '=', Meta, [ELeft, ERight] }, elixir_env:mergev(EL, ER) };
{ERight, ER} = expand(Right, E),
{ELeft, EL} = elixir_exp_clauses:match(fun expand/2, Left, E),
{{'=', Meta, [ELeft, ERight]}, elixir_env:mergev(EL, ER)};
%% Literal operators
expand({ '{}', Meta, Args }, E) ->
{ EArgs, EA } = expand_args(Args, E),
{ { '{}', Meta, EArgs }, EA };
expand({'{}', Meta, Args}, E) ->
{EArgs, EA} = expand_args(Args, E),
{{'{}', Meta, EArgs}, EA};
expand({ '%{}', Meta, Args }, E) ->
expand({'%{}', Meta, Args}, E) ->
elixir_map:expand_map(Meta, Args, E);
expand({ '%', Meta, [Left, Right] }, E) ->
expand({'%', Meta, [Left, Right]}, E) ->
elixir_map:expand_struct(Meta, Left, Right, E);
expand({ '<<>>', Meta, Args }, E) ->
expand({'<<>>', Meta, Args}, E) ->
elixir_bitstring:expand(Meta, Args, E);
%% Other operators
expand({ '__op__', Meta, [_, _] = Args }, E) ->
{ EArgs, EA } = expand_args(Args, E),
{ { '__op__', Meta, EArgs }, EA };
expand({'__op__', Meta, [_, _] = Args}, E) ->
{EArgs, EA} = expand_args(Args, E),
{{'__op__', Meta, EArgs}, EA};
expand({ '__op__', Meta, [_, _, _] = Args }, E) ->
{ EArgs, EA } = expand_args(Args, E),
{ { '__op__', Meta, EArgs }, EA };
expand({'__op__', Meta, [_, _, _] = Args}, E) ->
{EArgs, EA} = expand_args(Args, E),
{{'__op__', Meta, EArgs}, EA};
expand({ '->', Meta, _Args }, E) ->
compile_error(Meta, E#elixir_env.file, "unhandled operator ->");
expand({'->', Meta, _Args}, E) ->
compile_error(Meta, ?m(E, file), "unhandled operator ->");
%% __block__
expand({ '__block__', _Meta, [] }, E) ->
{ nil, E };
expand({ '__block__', _Meta, [Arg] }, E) ->
expand({'__block__', _Meta, []}, E) ->
{nil, E};
expand({'__block__', _Meta, [Arg]}, E) ->
expand(Arg, E);
expand({ '__block__', Meta, Args }, E) when is_list(Args) ->
{ EArgs, EA } = expand_many(Args, E),
{ { '__block__', Meta, EArgs }, EA };
expand({'__block__', Meta, Args}, E) when is_list(Args) ->
{EArgs, EA} = expand_many(Args, E),
{{'__block__', Meta, EArgs}, EA};
%% __aliases__
expand({ '__aliases__', _, _ } = Alias, E) ->
case elixir_aliases:expand(Alias, E#elixir_env.aliases,
E#elixir_env.macro_aliases, E#elixir_env.lexical_tracker) of
expand({'__aliases__', _, _} = Alias, E) ->
case elixir_aliases:expand(Alias, ?m(E, aliases),
?m(E, macro_aliases), ?m(E, lexical_tracker)) of
Receiver when is_atom(Receiver) ->
elixir_lexical:record_remote(Receiver, E#elixir_env.lexical_tracker),
{ Receiver, E };
elixir_lexical:record_remote(Receiver, ?m(E, lexical_tracker)),
{Receiver, E};
Aliases ->
{ EAliases, EA } = expand_args(Aliases, E),
{EAliases, EA} = expand_args(Aliases, E),
case lists:all(fun is_atom/1, EAliases) of
true ->
Receiver = elixir_aliases:concat(EAliases),
elixir_lexical:record_remote(Receiver, E#elixir_env.lexical_tracker),
{ Receiver, EA };
elixir_lexical:record_remote(Receiver, ?m(E, lexical_tracker)),
{Receiver, EA};
false ->
{ { { '.', [], [elixir_aliases, concat] }, [], [EAliases] }, EA }
{{{'.', [], [elixir_aliases, concat]}, [], [EAliases]}, EA}
end
end;
%% alias
expand({ alias, Meta, [Ref] }, E) ->
expand({ alias, Meta, [Ref,[]] }, E);
expand({ alias, Meta, [Ref, KV] }, E) ->
expand({alias, Meta, [Ref]}, E) ->
expand({alias, Meta, [Ref,[]]}, E);
expand({alias, Meta, [Ref, KV]}, E) ->
assert_no_match_or_guard_scope(Meta, alias, E),
{ ERef, ER } = expand(Ref, E),
{ EKV, ET } = expand_opts(Meta, alias, [as, warn], no_alias_opts(KV), ER),
{ERef, ER} = expand(Ref, E),
{EKV, ET} = expand_opts(Meta, alias, [as, warn], no_alias_opts(KV), ER),
if
is_atom(ERef) ->
{ { alias, Meta, [ERef, EKV] },
expand_alias(Meta, true, ERef, EKV, ET) };
{{alias, Meta, [ERef, EKV]},
expand_alias(Meta, true, ERef, EKV, ET)};
true ->
compile_error(Meta, E#elixir_env.file,
compile_error(Meta, ?m(E, file),
"invalid argument for alias, expected a compile time atom or alias, got: ~ts",
['Elixir.Kernel':inspect(ERef)])
end;
expand({ require, Meta, [Ref] }, E) ->
expand({ require, Meta, [Ref, []] }, E);
expand({ require, Meta, [Ref, KV] }, E) ->
expand({require, Meta, [Ref]}, E) ->
expand({require, Meta, [Ref, []]}, E);
expand({require, Meta, [Ref, KV]}, E) ->
assert_no_match_or_guard_scope(Meta, require, E),
{ ERef, ER } = expand(Ref, E),
{ EKV, ET } = expand_opts(Meta, require, [as, warn], no_alias_opts(KV), ER),
{ERef, ER} = expand(Ref, E),
{EKV, ET} = expand_opts(Meta, require, [as, warn], no_alias_opts(KV), ER),
if
is_atom(ERef) ->
elixir_aliases:ensure_loaded(Meta, ERef, ET),
{ { require, Meta, [ERef, EKV] },
expand_require(Meta, ERef, EKV, ET) };
{{require, Meta, [ERef, EKV]},
expand_require(Meta, ERef, EKV, ET)};
true ->
compile_error(Meta, E#elixir_env.file,
compile_error(Meta, ?m(E, file),
"invalid argument for require, expected a compile time atom or alias, got: ~ts",
['Elixir.Kernel':inspect(ERef)])
end;
expand({ import, Meta, [Left] }, E) ->
expand({ import, Meta, [Left, []]}, E);
expand({import, Meta, [Left]}, E) ->
expand({import, Meta, [Left, []]}, E);
expand({ import, Meta, [Ref, KV] }, E) ->
expand({import, Meta, [Ref, KV]}, E) ->
assert_no_match_or_guard_scope(Meta, import, E),
{ ERef, ER } = expand(Ref, E),
{ EKV, ET } = expand_opts(Meta, import, [only, except, warn], KV, ER),
{ERef, ER} = expand(Ref, E),
{EKV, ET} = expand_opts(Meta, import, [only, except, warn], KV, ER),
if
is_atom(ERef) ->
elixir_aliases:ensure_loaded(Meta, ERef, ET),
{ Functions, Macros } = elixir_import:import(Meta, ERef, EKV, ET),
{ { import, Meta, [ERef, EKV] },
expand_require(Meta, ERef, EKV, ET#elixir_env{functions=Functions, macros=Macros}) };
{Functions, Macros} = elixir_import:import(Meta, ERef, EKV, ET),
{{import, Meta, [ERef, EKV]},
expand_require(Meta, ERef, EKV, ET#{functions := Functions, macros := Macros})};
true ->
compile_error(Meta, E#elixir_env.file,
compile_error(Meta, ?m(E, file),
"invalid argument for import, expected a compile time atom or alias, got: ~ts",
['Elixir.Kernel':inspect(ERef)])
end;
%% Pseudo vars
expand({ '__MODULE__', _, Atom }, E) when is_atom(Atom) ->
{ E#elixir_env.module, E };
expand({ '__DIR__', _, Atom }, E) when is_atom(Atom) ->
{ filename:dirname(E#elixir_env.file), E };
expand({ '__CALLER__', _, Atom } = Caller, E) when is_atom(Atom) ->
{ Caller, E };
expand({ '__ENV__', Meta, Atom }, E) when is_atom(Atom) ->
Env = elixir_env:env_to_ex({ ?line(Meta), E }),
{ { '{}', [], tuple_to_list(Env) }, E };
expand({ { '.', DotMeta, [{ '__ENV__', Meta, Atom }, Field] }, CallMeta, [] }, E) when is_atom(Atom), is_atom(Field) ->
Env = elixir_env:env_to_ex({ ?line(Meta), E }),
case erlang:function_exported('Elixir.Macro.Env', Field, 1) of
true -> { Env:Field(), E };
false -> { { { '.', DotMeta, [{ '{}', [], tuple_to_list(Env) }, Field] }, CallMeta, [] }, E }
expand({'__MODULE__', _, Atom}, E) when is_atom(Atom) ->
{?m(E, module), E};
expand({'__DIR__', _, Atom}, E) when is_atom(Atom) ->
{filename:dirname(?m(E, file)), E};
expand({'__CALLER__', _, Atom} = Caller, E) when is_atom(Atom) ->
{Caller, E};
expand({'__ENV__', Meta, Atom}, E) when is_atom(Atom) ->
Env = elixir_env:linify({?line(Meta), E}),
{{'%{}', [], maps:to_list(Env)}, E};
expand({{'.', DotMeta, [{'__ENV__', Meta, Atom}, Field]}, CallMeta, []}, E) when is_atom(Atom), is_atom(Field) ->
Env = elixir_env:linify({?line(Meta), E}),
case maps:is_key(Field, Env) of
true -> {maps:get(Field, Env), E};
false -> {{{'.', DotMeta, [{'%{}', [], maps:to_list(Env)}, Field]}, CallMeta, []}, E}
end;
%% Quote
expand({ Unquote, Meta, [_] }, E) when Unquote == unquote; Unquote == unquote_splicing ->
compile_error(Meta, E#elixir_env.file, "~p called outside quote", [Unquote]);
expand({Unquote, Meta, [_]}, E) when Unquote == unquote; Unquote == unquote_splicing ->
compile_error(Meta, ?m(E, file), "~p called outside quote", [Unquote]);
expand({ quote, Meta, [Opts] }, E) when is_list(Opts) ->
expand({quote, Meta, [Opts]}, E) when is_list(Opts) ->
case lists:keyfind(do, 1, Opts) of
{ do, Do } ->
expand({ quote, Meta, [lists:keydelete(do, 1, Opts), [{do,Do}]] }, E);
{do, Do} ->
expand({quote, Meta, [lists:keydelete(do, 1, Opts), [{do,Do}]]}, E);
false ->
compile_error(Meta, E#elixir_env.file, "missing do keyword in quote")
compile_error(Meta, ?m(E, file), "missing do keyword in quote")
end;
expand({ quote, Meta, [_] }, E) ->
compile_error(Meta, E#elixir_env.file, "invalid arguments for quote");
expand({quote, Meta, [_]}, E) ->
compile_error(Meta, ?m(E, file), "invalid arguments for quote");
expand({ quote, Meta, [KV, Do] }, E) when is_list(Do) ->
expand({quote, Meta, [KV, Do]}, E) when is_list(Do) ->
Exprs =
case lists:keyfind(do, 1, Do) of
{ do, Expr } -> Expr;
{do, Expr} -> Expr;
false -> compile_error(Meta, E#elixir_scope.file, "missing do keyword in quote")
end,
ValidOpts = [hygiene, context, var_context, location, line, unquote, bind_quoted],
{ EKV, ET } = expand_opts(Meta, quote, ValidOpts, KV, E),
{EKV, ET} = expand_opts(Meta, quote, ValidOpts, KV, E),
Hygiene = case lists:keyfind(hygiene, 1, EKV) of
{ hygiene, List } when is_list(List) ->
{hygiene, List} when is_list(List) ->
List;
false ->
[]
end,
Context = case lists:keyfind(context, 1, EKV) of
{ context, Atom } when is_atom(Atom) ->
{context, Atom} when is_atom(Atom) ->
Atom;
{ context, Ctx } ->
compile_error(Meta, E#elixir_env.file, "invalid :context for quote, "
{context, Ctx} ->
compile_error(Meta, ?m(E, file), "invalid :context for quote, "
"expected a compile time atom or alias, got: ~ts", ['Elixir.Kernel':inspect(Ctx)]);
false ->
case E#elixir_env.module of
case ?m(E, module) of
nil -> 'Elixir';
Mod -> Mod
end
end,
Vars = lists:keyfind(vars, 1, Hygiene) /= { vars, false },
Aliases = lists:keyfind(aliases, 1, Hygiene) /= { aliases, false },
Imports = lists:keyfind(imports, 1, Hygiene) /= { imports, false },
Vars = lists:keyfind(vars, 1, Hygiene) /= {vars, false},
Aliases = lists:keyfind(aliases, 1, Hygiene) /= {aliases, false},
Imports = lists:keyfind(imports, 1, Hygiene) /= {imports, false},
Keep = lists:keyfind(location, 1, EKV) == { location, keep },
Keep = lists:keyfind(location, 1, EKV) == {location, keep},
Line = proplists:get_value(line, EKV, false),
{ Binding, DefaultUnquote } = case lists:keyfind(bind_quoted, 1, EKV) of
{ bind_quoted, BQ } -> { BQ, false };
false -> { nil, true }
{Binding, DefaultUnquote} = case lists:keyfind(bind_quoted, 1, EKV) of
{bind_quoted, BQ} -> {BQ, false};
false -> {nil, true}
end,
Unquote = case lists:keyfind(unquote, 1, EKV) of
{ unquote, Bool } when is_boolean(Bool) -> Bool;
{unquote, Bool} when is_boolean(Bool) -> Bool;
false -> DefaultUnquote
end,
Q = #elixir_quote{vars_hygiene=Vars, line=Line, keep=Keep, unquote=Unquote,
aliases_hygiene=Aliases, imports_hygiene=Imports, context=Context},
{ Quoted, _Q } = elixir_quote:quote(Exprs, Binding, Q, ET),
{Quoted, _Q} = elixir_quote:quote(Exprs, Binding, Q, ET),
expand(Quoted, ET);
expand({ quote, Meta, [_, _] }, E) ->
compile_error(Meta, E#elixir_env.file, "invalid arguments for quote");
expand({quote, Meta, [_, _]}, E) ->
compile_error(Meta, ?m(E, file), "invalid arguments for quote");
%% Functions
expand({ '&', _, [Arg] } = Original, E) when is_integer(Arg) ->
{ Original, E };
expand({ '&', Meta, [Arg] }, E) ->
expand({'&', _, [Arg]} = Original, E) when is_integer(Arg) ->
{Original, E};
expand({'&', Meta, [Arg]}, E) ->
assert_no_match_or_guard_scope(Meta, '&', E),
case elixir_fn:capture(Meta, Arg, E) of
{ local, Fun, Arity } ->
{ { '&', Meta, [{ '/', [], [{ Fun, [], nil }, Arity] }] }, E };
{ expanded, Expr, EE } ->
{local, Fun, Arity} ->
{{'&', Meta, [{'/', [], [{Fun, [], nil}, Arity]}]}, E};
{expanded, Expr, EE} ->
expand(Expr, EE)
end;
expand({ fn, Meta, Pairs }, E) ->
expand({fn, Meta, Pairs}, E) ->
assert_no_match_or_guard_scope(Meta, fn, E),
elixir_fn:expand(Meta, Pairs, E);
@@ -239,82 +239,83 @@ expand({ fn, Meta, Pairs }, E) ->
expand({'case', Meta, [Expr, KV]}, E) ->
assert_no_match_or_guard_scope(Meta, 'case', E),
{ EExpr, EE } = expand(Expr, E),
{ EClauses, EC } = elixir_exp_clauses:'case'(Meta, KV, EE),
{ { 'case', Meta, [EExpr, EClauses] }, EC };
{EExpr, EE} = expand(Expr, E),
{EClauses, EC} = elixir_exp_clauses:'case'(Meta, KV, EE),
{{'case', Meta, [EExpr, EClauses]}, EC};
expand({'receive', Meta, [KV]}, E) ->
assert_no_match_or_guard_scope(Meta, 'receive', E),
{ EClauses, EC } = elixir_exp_clauses:'receive'(Meta, KV, E),
{ { 'receive', Meta, [EClauses] }, EC };
{EClauses, EC} = elixir_exp_clauses:'receive'(Meta, KV, E),
{{'receive', Meta, [EClauses]}, EC};
expand({'try', Meta, [KV]}, E) ->
assert_no_match_or_guard_scope(Meta, 'try', E),
{ EClauses, EC } = elixir_exp_clauses:'try'(Meta, KV, E),
{ { 'try', Meta, [EClauses] }, EC };
{EClauses, EC} = elixir_exp_clauses:'try'(Meta, KV, E),
{{'try', Meta, [EClauses]}, EC};
%% Comprehensions
expand({ Kind, Meta, Args }, E) when is_list(Args), (Kind == lc) orelse (Kind == bc) ->
expand({Kind, Meta, Args}, E) when is_list(Args), (Kind == lc) orelse (Kind == bc) ->
elixir_errors:deprecation(Meta, ?m(E, file), "~ts is deprecated, please use for comprehensions instead", [Kind]),
expand_comprehension(Meta, Kind, Args, E);
expand({ for, Meta, Args }, E) when is_list(Args) ->
expand({for, Meta, Args}, E) when is_list(Args) ->
elixir_for:expand(Meta, Args, E);
%% Super
expand({ super, Meta, Args }, E) when is_list(Args) ->
expand({super, Meta, Args}, E) when is_list(Args) ->
assert_no_match_or_guard_scope(Meta, super, E),
{ EArgs, EA } = expand_args(Args, E),
{ { super, Meta, EArgs }, EA };
{EArgs, EA} = expand_args(Args, E),
{{super, Meta, EArgs}, EA};
%% Vars
expand({ '^', Meta, [Arg] }, #elixir_env{context=match} = E) ->
expand({'^', Meta, [Arg]}, #{context := match} = E) ->
case expand(Arg, E) of
{ { Name, _, Kind } = EArg, EA } when is_atom(Name), is_atom(Kind) ->
{ { '^', Meta, [EArg] }, EA };
{{Name, _, Kind} = EArg, EA} when is_atom(Name), is_atom(Kind) ->
{{'^', Meta, [EArg]}, EA};
_ ->
Msg = "invalid argument for unary operator ^, expected an existing variable, got: ^~ts",
compile_error(Meta, E#elixir_env.file, Msg, ['Elixir.Macro':to_string(Arg)])
compile_error(Meta, ?m(E, file), Msg, ['Elixir.Macro':to_string(Arg)])
end;
expand({ '^', Meta, [Arg] }, E) ->
compile_error(Meta, E#elixir_env.file,
expand({'^', Meta, [Arg]}, E) ->
compile_error(Meta, ?m(E, file),
"cannot use ^~ts outside of match clauses", ['Elixir.Macro':to_string(Arg)]);
expand({ '_', _, Kind } = Var, E) when is_atom(Kind) ->
{ Var, E };
expand({ Name, Meta, Kind } = Var, #elixir_env{context=match, export_vars=Export} = E) when is_atom(Name), is_atom(Kind) ->
Pair = { Name, var_kind(Meta, Kind) },
NewVars = ordsets:add_element(Pair, E#elixir_env.vars),
NewExport = case (Export /= nil) andalso (lists:keyfind(export, 1, Meta) /= { export, false }) of
expand({'_', _, Kind} = Var, E) when is_atom(Kind) ->
{Var, E};
expand({Name, Meta, Kind} = Var, #{context := match, export_vars := Export} = E) when is_atom(Name), is_atom(Kind) ->
Pair = {Name, var_kind(Meta, Kind)},
NewVars = ordsets:add_element(Pair, ?m(E, vars)),
NewExport = case (Export /= nil) andalso (lists:keyfind(export, 1, Meta) /= {export, false}) of
true -> ordsets:add_element(Pair, Export);
false -> Export
end,
{ Var, E#elixir_env{vars=NewVars, export_vars=NewExport} };
expand({ Name, Meta, Kind } = Var, #elixir_env{vars=Vars} = E) when is_atom(Name), is_atom(Kind) ->
case lists:member({ Name, var_kind(Meta, Kind) }, Vars) of
{Var, E#{vars := NewVars, export_vars := NewExport}};
expand({Name, Meta, Kind} = Var, #{vars := Vars} = E) when is_atom(Name), is_atom(Kind) ->
case lists:member({Name, var_kind(Meta, Kind)}, Vars) of
true ->
{ Var, E };
{Var, E};
false ->
VarMeta = lists:keyfind(var, 1, Meta),
if
VarMeta == { var, true } ->
VarMeta == {var, true} ->
Extra = case Kind of
nil -> "";
_ -> io_lib:format(" (context ~ts)", [elixir_aliases:inspect(Kind)])
end,
compile_error(Meta, E#elixir_env.file, "expected var ~ts~ts to expand to an existing "
compile_error(Meta, ?m(E, file), "expected var ~ts~ts to expand to an existing "
"variable or be a part of a match", [Name, Extra]);
true ->
expand({ Name, Meta, [] }, E)
expand({Name, Meta, []}, E)
end
end;
%% Local calls
expand({ Atom, Meta, Args }, E) when is_atom(Atom), is_list(Meta), is_list(Args) ->
expand({Atom, Meta, Args}, E) when is_atom(Atom), is_list(Meta), is_list(Args) ->
assert_no_ambiguous_op(Atom, Meta, Args, E),
elixir_dispatch:dispatch_import(Meta, Atom, Args, E, fun() ->
@@ -323,9 +324,9 @@ expand({ Atom, Meta, Args }, E) when is_atom(Atom), is_list(Meta), is_list(Args)
%% Remote calls
expand({ { '.', DotMeta, [Left, Right] }, Meta, Args }, E)
expand({{'.', DotMeta, [Left, Right]}, Meta, Args}, E)
when (is_tuple(Left) orelse is_atom(Left)), is_atom(Right), is_list(Meta), is_list(Args) ->
{ ELeft, EL } = expand(Left, E),
{ELeft, EL} = expand(Left, E),
elixir_dispatch:dispatch_require(Meta, ELeft, Right, Args, EL, fun(AR, AF, AA) ->
expand_remote(AR, DotMeta, AF, Meta, AA, E, EL)
@@ -333,66 +334,66 @@ expand({ { '.', DotMeta, [Left, Right] }, Meta, Args }, E)
%% Anonymous calls
expand({ { '.', DotMeta, [Expr] }, Meta, Args }, E) when is_list(Args) ->
{ EExpr, EE } = expand(Expr, E),
expand({{'.', DotMeta, [Expr]}, Meta, Args}, E) when is_list(Args) ->
{EExpr, EE} = expand(Expr, E),
if
is_atom(EExpr) ->
compile_error(Meta, E#elixir_env.file, "invalid function call :~ts.()", [EExpr]);
compile_error(Meta, ?m(E, file), "invalid function call :~ts.()", [EExpr]);
true ->
{ EArgs, EA } = expand_args(Args, elixir_env:mergea(E, EE)),
{ { { '.', DotMeta, [EExpr] }, Meta, EArgs }, elixir_env:mergev(EE, EA) }
{EArgs, EA} = expand_args(Args, elixir_env:mergea(E, EE)),
{{{'.', DotMeta, [EExpr]}, Meta, EArgs}, elixir_env:mergev(EE, EA)}
end;
%% Invalid calls
expand({ _, Meta, Args } = Invalid, E) when is_list(Meta) and is_list(Args) ->
compile_error(Meta, E#elixir_env.file, "invalid call ~ts",
expand({_, Meta, Args} = Invalid, E) when is_list(Meta) and is_list(Args) ->
compile_error(Meta, ?m(E, file), "invalid call ~ts",
['Elixir.Macro':to_string(Invalid)]);
expand({ _, _, _ } = Tuple, E) ->
compile_error([{line,0}], E#elixir_env.file, "invalid quoted expression: ~ts",
expand({_, _, _} = Tuple, E) ->
compile_error([{line,0}], ?m(E, file), "invalid quoted expression: ~ts",
['Elixir.Kernel':inspect(Tuple, [{records,false}])]);
%% Literals
expand({ Left, Right }, E) ->
{ [ELeft, ERight], EE } = expand_args([Left, Right], E),
{ { ELeft, ERight }, EE };
expand({Left, Right}, E) ->
{[ELeft, ERight], EE} = expand_args([Left, Right], E),
{{ELeft, ERight}, EE};
expand(List, #elixir_env{context=match} = E) when is_list(List) ->
expand(List, #{context := match} = E) when is_list(List) ->
expand_list(List, fun expand/2, E, []);
expand(List, E) when is_list(List) ->
{ EArgs, { EC, EV } } = expand_list(List, fun expand_arg/2, {E, E}, []),
{ EArgs, elixir_env:mergea(EV, EC) };
{EArgs, {EC, EV}} = expand_list(List, fun expand_arg/2, {E, E}, []),
{EArgs, elixir_env:mergea(EV, EC)};
expand(Function, E) when is_function(Function) ->
case (erlang:fun_info(Function, type) == { type, external }) andalso
(erlang:fun_info(Function, env) == { env, [] }) of
case (erlang:fun_info(Function, type) == {type, external}) andalso
(erlang:fun_info(Function, env) == {env, []}) of
true ->
{ Function, E };
{Function, E};
false ->
compile_error([{line,0}], E#elixir_env.file,
compile_error([{line,0}], ?m(E, file),
"invalid quoted expression: ~ts", ['Elixir.Kernel':inspect(Function)])
end;
expand(Other, E) when is_number(Other); is_atom(Other); is_binary(Other); is_pid(Other) ->
{ Other, E };
{Other, E};
expand(Other, E) ->
compile_error([{line,0}], E#elixir_env.file,
compile_error([{line,0}], ?m(E, file),
"invalid quoted expression: ~ts", ['Elixir.Kernel':inspect(Other)]).
%% Helpers
expand_list([{ '|', Meta, [_, _] = Args }], Fun, Acc, List) ->
{ EArgs, EAcc } = lists:mapfoldl(Fun, Acc, Args),
expand_list([], Fun, EAcc, [{ '|', Meta, EArgs }|List]);
expand_list([{'|', Meta, [_, _] = Args}], Fun, Acc, List) ->
{EArgs, EAcc} = lists:mapfoldl(Fun, Acc, Args),
expand_list([], Fun, EAcc, [{'|', Meta, EArgs}|List]);
expand_list([H|T], Fun, Acc, List) ->
{ EArg, EAcc } = Fun(H, Acc),
{EArg, EAcc} = Fun(H, Acc),
expand_list(T, Fun, EAcc, [EArg|List]);
expand_list([], _Fun, Acc, List) ->
{ lists:reverse(List), Acc }.
{lists:reverse(List), Acc}.
expand_many(Args, E) ->
lists:mapfoldl(fun expand/2, E, Args).
@@ -411,22 +412,22 @@ expand_many(Args, E) ->
%%
%% However, lexical information is.
expand_arg(Arg, Acc) when is_number(Arg); is_atom(Arg); is_binary(Arg); is_pid(Arg) ->
{ Arg, Acc };
expand_arg(Arg, { Acc1, Acc2 }) ->
{ EArg, EAcc } = expand(Arg, Acc1),
{ EArg, { elixir_env:mergea(Acc1, EAcc), elixir_env:mergev(Acc2, EAcc) } }.
{Arg, Acc};
expand_arg(Arg, {Acc1, Acc2}) ->
{EArg, EAcc} = expand(Arg, Acc1),
{EArg, {elixir_env:mergea(Acc1, EAcc), elixir_env:mergev(Acc2, EAcc)}}.
expand_args(Args, #elixir_env{context=match} = E) ->
expand_args(Args, #{context := match} = E) ->
expand_many(Args, E);
expand_args(Args, E) ->
{ EArgs, { EC, EV } } = lists:mapfoldl(fun expand_arg/2, {E, E}, Args),
{ EArgs, elixir_env:mergea(EV, EC) }.
{EArgs, {EC, EV}} = lists:mapfoldl(fun expand_arg/2, {E, E}, Args),
{EArgs, elixir_env:mergea(EV, EC)}.
%% Match/var helpers
var_kind(Meta, Kind) ->
case lists:keyfind(counter, 1, Meta) of
{ counter, Counter } -> Counter;
{counter, Counter} -> Counter;
false -> Kind
end.
@@ -434,10 +435,10 @@ var_kind(Meta, Kind) ->
assert_no_ambiguous_op(Name, Meta, [Arg], E) ->
case lists:keyfind(ambiguous_op, 1, Meta) of
{ ambiguous_op, Kind } ->
case lists:member({ Name, Kind }, E#elixir_env.vars) of
{ambiguous_op, Kind} ->
case lists:member({Name, Kind}, ?m(E, vars)) of
true ->
compile_error(Meta, E#elixir_env.file, "\"~ts ~ts\" looks like a function call but "
compile_error(Meta, ?m(E, file), "\"~ts ~ts\" looks like a function call but "
"there is a variable named \"~ts\", please use explicit parenthesis or even spaces",
[Name, 'Elixir.Macro':to_string(Arg), Name]);
false ->
@@ -449,59 +450,59 @@ assert_no_ambiguous_op(Name, Meta, [Arg], E) ->
assert_no_ambiguous_op(_Atom, _Meta, _Args, _E) ->
ok.
expand_local(Meta, Name, Args, #elixir_env{local=nil, function=nil} = E) ->
{ EArgs, EA } = expand_args(Args, E),
{ { Name, Meta, EArgs }, EA };
expand_local(Meta, Name, Args, #elixir_env{local=nil, module=Module, function=Function} = E) ->
elixir_locals:record_local({ Name, length(Args) }, Module, Function),
{ EArgs, EA } = expand_args(Args, E),
{ { Name, Meta, EArgs }, EA };
expand_local(Meta, Name, Args, #{local := nil, function := nil} = E) ->
{EArgs, EA} = expand_args(Args, E),
{{Name, Meta, EArgs}, EA};
expand_local(Meta, Name, Args, #{local := nil, module := Module, function := Function} = E) ->
elixir_locals:record_local({Name, length(Args)}, Module, Function),
{EArgs, EA} = expand_args(Args, E),
{{Name, Meta, EArgs}, EA};
expand_local(Meta, Name, Args, E) ->
expand({ { '.', Meta, [E#elixir_env.local, Name] }, Meta, Args }, E).
expand({{'.', Meta, [?m(E, local), Name]}, Meta, Args}, E).
%% Remote
expand_remote(Receiver, DotMeta, Right, Meta, Args, E, EL) ->
if
is_atom(Receiver) -> elixir_lexical:record_remote(Receiver, E#elixir_env.lexical_tracker);
is_atom(Receiver) -> elixir_lexical:record_remote(Receiver, ?m(E, lexical_tracker));
true -> ok
end,
{ EArgs, EA } = expand_args(Args, E),
{ { { '.', DotMeta, [Receiver, Right] }, Meta, EArgs }, elixir_env:mergev(EL, EA) }.
{EArgs, EA} = expand_args(Args, E),
{{{'.', DotMeta, [Receiver, Right]}, Meta, EArgs}, elixir_env:mergev(EL, EA)}.
%% Lexical helpers
expand_opts(Meta, Kind, Allowed, Opts, E) ->
{ EOpts, EE } = expand(Opts, E),
{EOpts, EE} = expand(Opts, E),
validate_opts(Meta, Kind, Allowed, EOpts, EE),
{ EOpts, EE }.
{EOpts, EE}.
validate_opts(Meta, Kind, Allowed, Opts, E) when is_list(Opts) ->
[begin
compile_error(Meta, E#elixir_env.file,
compile_error(Meta, ?m(E, file),
"unsupported option ~ts given to ~s", ['Elixir.Kernel':inspect(Key), Kind])
end || { Key, _ } <- Opts, not lists:member(Key, Allowed)];
end || {Key, _} <- Opts, not lists:member(Key, Allowed)];
validate_opts(Meta, Kind, _Allowed, _Opts, S) ->
compile_error(Meta, S#elixir_scope.file, "invalid options for ~s, expected a keyword list", [Kind]).
no_alias_opts(KV) when is_list(KV) ->
case lists:keyfind(as, 1, KV) of
{ as, As } -> lists:keystore(as, 1, KV, { as, no_alias_expansion(As) });
{as, As} -> lists:keystore(as, 1, KV, {as, no_alias_expansion(As)});
false -> KV
end;
no_alias_opts(KV) -> KV.
no_alias_expansion({ '__aliases__', Meta, [H|T] }) when (H /= 'Elixir') and is_atom(H) ->
{ '__aliases__', Meta, ['Elixir',H|T] };
no_alias_expansion({'__aliases__', Meta, [H|T]}) when (H /= 'Elixir') and is_atom(H) ->
{'__aliases__', Meta, ['Elixir',H|T]};
no_alias_expansion(Other) ->
Other.
expand_require(Meta, Ref, KV, E) ->
RE = E#elixir_env{requires=ordsets:add_element(Ref, E#elixir_env.requires)},
RE = E#{requires := ordsets:add_element(Ref, ?m(E, requires))},
expand_alias(Meta, false, Ref, KV, RE).
expand_alias(Meta, IncludeByDefault, Ref, KV, #elixir_env{context_modules=Context} = E) ->
expand_alias(Meta, IncludeByDefault, Ref, KV, #{context_modules := Context} = E) ->
New = expand_as(lists:keyfind(as, 1, KV), Meta, IncludeByDefault, Ref, E),
%% Add the alias to context_modules if defined is true.
@@ -509,51 +510,51 @@ expand_alias(Meta, IncludeByDefault, Ref, KV, #elixir_env{context_modules=Contex
%% module in context modules.
NewContext =
case lists:keyfind(defined, 1, Meta) of
{ defined, Mod } when is_atom(Mod) -> [Mod|Context];
{defined, Mod} when is_atom(Mod) -> [Mod|Context];
false -> Context
end,
{ Aliases, MacroAliases } = elixir_aliases:store(Meta, New, Ref, KV, E#elixir_env.aliases,
E#elixir_env.macro_aliases, E#elixir_env.lexical_tracker),
{Aliases, MacroAliases} = elixir_aliases:store(Meta, New, Ref, KV, ?m(E, aliases),
?m(E, macro_aliases), ?m(E, lexical_tracker)),
E#elixir_env{aliases=Aliases, macro_aliases=MacroAliases, context_modules=NewContext}.
E#{aliases := Aliases, macro_aliases := MacroAliases, context_modules := NewContext}.
expand_as({ as, true }, _Meta, _IncludeByDefault, Ref, _E) ->
expand_as({as, true}, _Meta, _IncludeByDefault, Ref, _E) ->
elixir_aliases:last(Ref);
expand_as({ as, false }, _Meta, _IncludeByDefault, Ref, _E) ->
expand_as({as, false}, _Meta, _IncludeByDefault, Ref, _E) ->
Ref;
expand_as({ as, Atom }, Meta, _IncludeByDefault, _Ref, E) when is_atom(Atom) ->
expand_as({as, Atom}, Meta, _IncludeByDefault, _Ref, E) when is_atom(Atom) ->
case length(string:tokens(atom_to_list(Atom), ".")) of
1 -> compile_error(Meta, E#elixir_env.file,
1 -> compile_error(Meta, ?m(E, file),
"invalid value for keyword :as, expected an alias, got atom: ~ts", [elixir_aliases:inspect(Atom)]);
2 -> Atom;
_ -> compile_error(Meta, E#elixir_env.file,
_ -> compile_error(Meta, ?m(E, file),
"invalid value for keyword :as, expected an alias, got nested alias: ~ts", [elixir_aliases:inspect(Atom)])
end;
expand_as(false, _Meta, IncludeByDefault, Ref, _E) ->
if IncludeByDefault -> elixir_aliases:last(Ref);
true -> Ref
end;
expand_as({ as, Other }, Meta, _IncludeByDefault, _Ref, E) ->
compile_error(Meta, E#elixir_env.file,
expand_as({as, Other}, Meta, _IncludeByDefault, _Ref, E) ->
compile_error(Meta, ?m(E, file),
"invalid value for keyword :as, expected an alias, got: ~ts", ['Elixir.Macro':to_string(Other)]).
%% Comprehensions
expand_comprehension(Meta, Kind, Args, E) ->
case elixir_utils:split_last(Args) of
{ Cases, [{do,Expr}] } ->
{ ECases, EC } = lists:mapfoldl(fun expand_comprehension_clause/2, E, Cases),
{ EExpr, _ } = expand(Expr, EC),
{ { Kind, Meta, ECases ++ [[{do,EExpr}]] }, E };
{Cases, [{do,Expr}]} ->
{ECases, EC} = lists:mapfoldl(fun expand_comprehension_clause/2, E, Cases),
{EExpr, _} = expand(Expr, EC),
{{Kind, Meta, ECases ++ [[{do,EExpr}]]}, E};
_ ->
compile_error(Meta, E#elixir_env.file, "missing do keyword in comprehension ~ts", [Kind])
compile_error(Meta, ?m(E, file), "missing do keyword in comprehension ~ts", [Kind])
end.
expand_comprehension_clause({Gen, Meta, [Left, Right]}, E) when Gen == inbits; Gen == inlist ->
{ ERight, ER } = expand(Right, E),
{ ELeft, EL } = elixir_exp_clauses:match(fun expand/2, Left, E),
{ { Gen, Meta, [ELeft, ERight] }, elixir_env:mergev(EL, ER) };
{ERight, ER} = expand(Right, E),
{ELeft, EL} = elixir_exp_clauses:match(fun expand/2, Left, E),
{{Gen, Meta, [ELeft, ERight]}, elixir_env:mergev(EL, ER)};
expand_comprehension_clause(X, E) ->
expand(X, E).
@@ -562,9 +563,9 @@ expand_comprehension_clause(X, E) ->
assert_no_match_or_guard_scope(Meta, Kind, E) ->
assert_no_match_scope(Meta, Kind, E),
assert_no_guard_scope(Meta, Kind, E).
assert_no_match_scope(Meta, _Kind, #elixir_env{context=match,file=File}) ->
assert_no_match_scope(Meta, _Kind, #{context := match, file := File}) ->
compile_error(Meta, File, "invalid expression in match");
assert_no_match_scope(_Meta, _Kind, _E) -> [].
assert_no_guard_scope(Meta, _Kind, #elixir_env{context=guard,file=File}) ->
assert_no_guard_scope(Meta, _Kind, #{context := guard, file := File}) ->
compile_error(Meta, File, "invalid expression in guard");
assert_no_guard_scope(_Meta, _Kind, _E) -> [].
+97 -97
View File
@@ -5,130 +5,130 @@
-import(elixir_errors, [compile_error/3, compile_error/4]).
-include("elixir.hrl").
match(Fun, Expr, #elixir_env{context=Context} = E) ->
{ EExpr, EE } = Fun(Expr, E#elixir_env{context=match}),
{ EExpr, EE#elixir_env{context=Context} }.
match(Fun, Expr, #{context := Context} = E) ->
{EExpr, EE} = Fun(Expr, E#{context := match}),
{EExpr, EE#{context := Context}}.
def(Fun, Args, Guards, Body, E) ->
{ EArgs, EA } = match(Fun, Args, E),
{ EGuards, EG } = guard(Guards, EA#elixir_env{context=guard}),
{ EBody, EB } = elixir_exp:expand(Body, EG#elixir_env{context=E#elixir_env.context}),
{ EArgs, EGuards, EBody, EB }.
{EArgs, EA} = match(Fun, Args, E),
{EGuards, EG} = guard(Guards, EA#{context := guard}),
{EBody, EB} = elixir_exp:expand(Body, EG#{context := ?m(E, context)}),
{EArgs, EGuards, EBody, EB}.
clause(Meta, Kind, Fun, { '->', ClauseMeta, [_, _] } = Clause, E) when is_function(Fun, 3) ->
clause(Meta, Kind, Fun, {'->', ClauseMeta, [_, _]} = Clause, E) when is_function(Fun, 3) ->
clause(Meta, Kind, fun(X, Acc) -> Fun(ClauseMeta, X, Acc) end, Clause, E);
clause(_Meta, _Kind, Fun, { '->', Meta, [Left, Right] }, E) ->
{ ELeft, EL } = head(Fun, Left, E),
{ ERight, ER } = elixir_exp:expand(Right, EL),
{ { '->', Meta, [ELeft, ERight] }, ER };
clause(_Meta, _Kind, Fun, {'->', Meta, [Left, Right]}, E) ->
{ELeft, EL} = head(Fun, Left, E),
{ERight, ER} = elixir_exp:expand(Right, EL),
{{'->', Meta, [ELeft, ERight]}, ER};
clause(Meta, Kind, _Fun, _, E) ->
compile_error(Meta, E#elixir_env.file, "expected -> clauses in ~ts", [Kind]).
compile_error(Meta, ?m(E, file), "expected -> clauses in ~ts", [Kind]).
head(Fun, [{ 'when', Meta, [_,_|_] = All }], E) ->
{ Args, Guard } = elixir_utils:split_last(All),
{ EArgs, EA } = match(Fun, Args, E),
{ EGuard, EG } = guard(Guard, EA#elixir_env{context=guard}),
{ [{ 'when', Meta, EArgs ++ [EGuard] }], EG#elixir_env{context=E#elixir_env.context} };
head(Fun, [{'when', Meta, [_,_|_] = All}], E) ->
{Args, Guard} = elixir_utils:split_last(All),
{EArgs, EA} = match(Fun, Args, E),
{EGuard, EG} = guard(Guard, EA#{context := guard}),
{[{'when', Meta, EArgs ++ [EGuard]}], EG#{context := ?m(E, context)}};
head(Fun, Args, E) ->
match(Fun, Args, E).
guard({ 'when', Meta, [Left, Right] }, E) ->
{ ELeft, EL } = guard(Left, E),
{ ERight, ER } = guard(Right, EL),
{ { 'when', Meta, [ELeft, ERight] }, ER };
guard({'when', Meta, [Left, Right]}, E) ->
{ELeft, EL} = guard(Left, E),
{ERight, ER} = guard(Right, EL),
{{'when', Meta, [ELeft, ERight]}, ER};
guard(Other, E) ->
elixir_exp:expand(Other, E).
%% Case
'case'(Meta, [], E) ->
compile_error(Meta, E#elixir_env.file, "missing do keyword in case");
compile_error(Meta, ?m(E, file), "missing do keyword in case");
'case'(Meta, KV, E) when not is_list(KV) ->
compile_error(Meta, E#elixir_env.file, "invalid arguments for case");
compile_error(Meta, ?m(E, file), "invalid arguments for case");
'case'(Meta, KV, E) ->
EE = E#elixir_env{export_vars=[]},
{ EClauses, EVars } = lists:mapfoldl(fun(X, Acc) -> do_case(Meta, X, Acc, EE) end, [], KV),
{ EClauses, elixir_env:mergev(EVars, E) }.
EE = E#{export_vars := []},
{EClauses, EVars} = lists:mapfoldl(fun(X, Acc) -> do_case(Meta, X, Acc, EE) end, [], KV),
{EClauses, elixir_env:mergev(EVars, E)}.
do_case(Meta, { 'do', _ } = Do, Acc, E) ->
do_case(Meta, {'do', _} = Do, Acc, E) ->
expand_with_export(Meta, 'case', expand_arg(Meta, 'case', 'do'), Do, Acc, E);
do_case(Meta, { Key, _ }, _Acc, E) ->
compile_error(Meta, E#elixir_env.file, "unexpected keyword ~ts in case", [Key]).
do_case(Meta, {Key, _}, _Acc, E) ->
compile_error(Meta, ?m(E, file), "unexpected keyword ~ts in case", [Key]).
%% Receive
'receive'(Meta, [], E) ->
compile_error(Meta, E#elixir_env.file, "missing do or after keyword in receive");
compile_error(Meta, ?m(E, file), "missing do or after keyword in receive");
'receive'(Meta, KV, E) when not is_list(KV) ->
compile_error(Meta, E#elixir_env.file, "invalid arguments for receive");
compile_error(Meta, ?m(E, file), "invalid arguments for receive");
'receive'(Meta, KV, E) ->
EE = E#elixir_env{export_vars=[]},
{ EClauses, EVars } = lists:mapfoldl(fun(X, Acc) -> do_receive(Meta, X, Acc, EE) end, [], KV),
{ EClauses, elixir_env:mergev(EVars, E) }.
EE = E#{export_vars := []},
{EClauses, EVars} = lists:mapfoldl(fun(X, Acc) -> do_receive(Meta, X, Acc, EE) end, [], KV),
{EClauses, elixir_env:mergev(EVars, E)}.
do_receive(_Meta, { 'do', nil } = Do, Acc, _E) ->
{ Do, Acc };
do_receive(Meta, { 'do', _ } = Do, Acc, E) ->
do_receive(_Meta, {'do', nil} = Do, Acc, _E) ->
{Do, Acc};
do_receive(Meta, {'do', _} = Do, Acc, E) ->
expand_with_export(Meta, 'receive', expand_arg(Meta, 'receive', 'do'), Do, Acc, E);
do_receive(_Meta, { 'after', [{ '->', Meta, [[Left], Right] }] }, Acc, E) ->
{ ELeft, EL } = elixir_exp:expand(Left, E),
{ ERight, ER } = elixir_exp:expand(Right, EL),
EClause = { 'after', [{ '->', Meta, [[ELeft], ERight] }] },
{ EClause, elixir_env:merge_vars(Acc, ER#elixir_env.export_vars) };
do_receive(Meta, { 'after', _ }, _Acc, E) ->
compile_error(Meta, E#elixir_env.file, "expected a single -> clause for after in receive");
do_receive(Meta, { Key, _ }, _Acc, E) ->
compile_error(Meta, E#elixir_env.file, "unexpected keyword ~ts in receive", [Key]).
do_receive(_Meta, {'after', [{'->', Meta, [[Left], Right]}]}, Acc, E) ->
{ELeft, EL} = elixir_exp:expand(Left, E),
{ERight, ER} = elixir_exp:expand(Right, EL),
EClause = {'after', [{'->', Meta, [[ELeft], ERight]}]},
{EClause, elixir_env:merge_vars(Acc, ?m(ER, export_vars))};
do_receive(Meta, {'after', _}, _Acc, E) ->
compile_error(Meta, ?m(E, file), "expected a single -> clause for after in receive");
do_receive(Meta, {Key, _}, _Acc, E) ->
compile_error(Meta, ?m(E, file), "unexpected keyword ~ts in receive", [Key]).
%% Try
'try'(Meta, [], E) ->
compile_error(Meta, E#elixir_env.file, "missing do keywords in try");
compile_error(Meta, ?m(E, file), "missing do keywords in try");
'try'(Meta, KV, E) when not is_list(KV) ->
elixir_errors:compile_error(Meta, E#elixir_env.file, "invalid arguments for try");
elixir_errors:compile_error(Meta, ?m(E, file), "invalid arguments for try");
'try'(Meta, KV, E) ->
{ lists:map(fun(X) -> do_try(Meta, X, E) end, KV), E }.
{lists:map(fun(X) -> do_try(Meta, X, E) end, KV), E}.
do_try(_Meta, { 'do', Expr }, E) ->
{ EExpr, _ } = elixir_exp:expand(Expr, E),
{ 'do', EExpr };
do_try(_Meta, { 'after', Expr }, E) ->
{ EExpr, _ } = elixir_exp:expand(Expr, E),
{ 'after', EExpr };
do_try(Meta, { 'else', _ } = Else, E) ->
do_try(_Meta, {'do', Expr}, E) ->
{EExpr, _} = elixir_exp:expand(Expr, E),
{'do', EExpr};
do_try(_Meta, {'after', Expr}, E) ->
{EExpr, _} = elixir_exp:expand(Expr, E),
{'after', EExpr};
do_try(Meta, {'else', _} = Else, E) ->
expand_without_export(Meta, 'try', expand_arg(Meta, 'try', 'else'), Else, E);
do_try(Meta, { 'catch', _ } = Catch, E) ->
do_try(Meta, {'catch', _} = Catch, E) ->
expand_without_export(Meta, 'try', fun elixir_exp:expand_args/2, Catch, E);
do_try(Meta, { 'rescue', _ } = Rescue, E) ->
do_try(Meta, {'rescue', _} = Rescue, E) ->
expand_without_export(Meta, 'try', fun expand_rescue/3, Rescue, E);
do_try(Meta, { Key, _ }, E) ->
compile_error(Meta, E#elixir_env.file, "unexpected keyword ~ts in try", [Key]).
do_try(Meta, {Key, _}, E) ->
compile_error(Meta, ?m(E, file), "unexpected keyword ~ts in try", [Key]).
expand_rescue(Meta, [Arg], E) ->
case expand_rescue(Arg, E) of
{ EArg, EA } ->
{ [EArg], EA };
{EArg, EA} ->
{[EArg], EA};
false ->
compile_error(Meta, E#elixir_env.file, "invalid rescue clause. The clause should "
compile_error(Meta, ?m(E, file), "invalid rescue clause. The clause should "
"match on an alias, a variable or be in the `var in [alias]` format")
end;
expand_rescue(Meta, _, E) ->
compile_error(Meta, E#elixir_env.file, "expected one arg for rescue clauses (->) in try").
compile_error(Meta, ?m(E, file), "expected one arg for rescue clauses (->) in try").
%% rescue var => var in _
expand_rescue({ Name, _, Atom } = Var, E) when is_atom(Name), is_atom(Atom) ->
expand_rescue({ in, [], [Var, { '_', [], E#elixir_env.module }] }, E);
expand_rescue({Name, _, Atom} = Var, E) when is_atom(Name), is_atom(Atom) ->
expand_rescue({in, [], [Var, {'_', [], ?m(E, module)}]}, E);
%% rescue var in [Exprs]
expand_rescue({ in, Meta, [Left, Right] }, E) ->
{ ERight, ER } = elixir_exp:expand(Right, E#elixir_env{context=nil}),
{ ELeft, EL } = elixir_exp:expand(Left, ER#elixir_env{context=match}),
expand_rescue({in, Meta, [Left, Right]}, E) ->
{ERight, ER} = elixir_exp:expand(Right, E#{context := nil}),
{ELeft, EL} = elixir_exp:expand(Left, ER#{context := match}),
case ELeft of
{ Name, _, Atom } when is_atom(Name), is_atom(Atom) ->
{Name, _, Atom} when is_atom(Name), is_atom(Atom) ->
case normalize_rescue(ERight) of
false -> false;
Other -> { { in, Meta, [ELeft, Other] }, EL }
Other -> {{in, Meta, [ELeft, Other]}, EL}
end;
_ ->
false
@@ -136,27 +136,27 @@ expand_rescue({ in, Meta, [Left, Right] }, E) ->
%% rescue Error => _ in [Error]
expand_rescue(Arg, E) ->
expand_rescue({ in, [], [{ '_', [], E#elixir_env.module }, Arg] }, E).
expand_rescue({in, [], [{'_', [], ?m(E, module)}, Arg]}, E).
normalize_rescue({ '_', _, Atom } = N) when is_atom(Atom) -> N;
normalize_rescue({'_', _, Atom} = N) when is_atom(Atom) -> N;
normalize_rescue(Atom) when is_atom(Atom) -> [Atom];
normalize_rescue(Other) ->
is_list(Other)
andalso lists:all(fun is_var_or_atom/1, Other)
andalso Other.
is_var_or_atom({ Name, _, Atom }) when is_atom(Name), is_atom(Atom) -> true;
is_var_or_atom({Name, _, Atom}) when is_atom(Name), is_atom(Atom) -> true;
is_var_or_atom(Atom) when is_atom(Atom) -> true;
is_var_or_atom(_) -> false.
%% Expansion helpers
export_vars({ Left, Meta, Right }) when is_atom(Left), is_list(Meta), is_atom(Right) ->
{ Left, [{export,false}|Meta], Right };
export_vars({ Left, Meta, Right }) ->
{ export_vars(Left), Meta, export_vars(Right) };
export_vars({ Left, Right }) ->
{ export_vars(Left), export_vars(Right) };
export_vars({Left, Meta, Right}) when is_atom(Left), is_list(Meta), is_atom(Right) ->
{Left, [{export,false}|Meta], Right};
export_vars({Left, Meta, Right}) ->
{export_vars(Left), Meta, export_vars(Right)};
export_vars({Left, Right}) ->
{export_vars(Left), export_vars(Right)};
export_vars(List) when is_list(List) ->
[export_vars(X) || X <- List];
export_vars(Other) ->
@@ -167,34 +167,34 @@ export_vars(Other) ->
expand_arg(Meta, Kind, Key) ->
fun
([Arg], E) ->
{ EArg, EA } = elixir_exp:expand(Arg, E),
{ [EArg], EA };
{EArg, EA} = elixir_exp:expand(Arg, E),
{[EArg], EA};
(_, E) ->
compile_error(Meta, E#elixir_env.file, "expected one arg for ~ts clauses (->) in ~ts", [Key, Kind])
compile_error(Meta, ?m(E, file), "expected one arg for ~ts clauses (->) in ~ts", [Key, Kind])
end.
%% Expands all -> pairs in a given key keeping the overall vars.
expand_with_export(Meta, Kind, Fun, { Key, Clauses }, Acc, E) when is_list(Clauses) ->
expand_with_export(Meta, Kind, Fun, {Key, Clauses}, Acc, E) when is_list(Clauses) ->
EFun =
case lists:keyfind(export_head, 1, Meta) of
{ export_head, true } -> Fun;
{export_head, true} -> Fun;
_ -> fun(ExportArgs, ExportE) -> Fun(export_vars(ExportArgs), ExportE) end
end,
Transformer = fun(Clause, Vars) ->
{ EClause, EC } = clause(Meta, Kind, EFun, Clause, E),
{ EClause, elixir_env:merge_vars(Vars, EC#elixir_env.export_vars) }
{EClause, EC} = clause(Meta, Kind, EFun, Clause, E),
{EClause, elixir_env:merge_vars(Vars, ?m(EC, export_vars))}
end,
{ EClauses, EVars } = lists:mapfoldl(Transformer, Acc, Clauses),
{ { Key, EClauses }, EVars };
expand_with_export(Meta, Kind, _Fun, { Key, _ }, _Acc, E) ->
compile_error(Meta, E#elixir_env.file, "expected -> clauses for ~ts in ~ts", [Key, Kind]).
{EClauses, EVars} = lists:mapfoldl(Transformer, Acc, Clauses),
{{Key, EClauses}, EVars};
expand_with_export(Meta, Kind, _Fun, {Key, _}, _Acc, E) ->
compile_error(Meta, ?m(E, file), "expected -> clauses for ~ts in ~ts", [Key, Kind]).
%% Expands all -> pairs in a given key but do not keep the overall vars.
expand_without_export(Meta, Kind, Fun, { Key, Clauses }, E) when is_list(Clauses) ->
expand_without_export(Meta, Kind, Fun, {Key, Clauses}, E) when is_list(Clauses) ->
Transformer = fun(Clause) ->
{ EClause, _ } = clause(Meta, Kind, Fun, Clause, E),
{EClause, _} = clause(Meta, Kind, Fun, Clause, E),
EClause
end,
{ Key, lists:map(Transformer, Clauses) };
expand_without_export(Meta, Kind, _Fun, { Key, _ }, E) ->
compile_error(Meta, E#elixir_env.file, "expected -> clauses for ~ts in ~ts", [Key, Kind]).
{Key, lists:map(Transformer, Clauses)};
expand_without_export(Meta, Kind, _Fun, {Key, _}, E) ->
compile_error(Meta, ?m(E, file), "expected -> clauses for ~ts in ~ts", [Key, Kind]).
+69 -63
View File
@@ -4,74 +4,74 @@
-include("elixir.hrl").
translate(Meta, Clauses, S) ->
Transformer = fun({ '->', CMeta, [ArgsWithGuards, Expr] }, Acc) ->
{ Args, Guards } = elixir_clauses:extract_splat_guards(ArgsWithGuards),
{ TClause, TS } = elixir_clauses:clause(?line(CMeta), fun translate_fn_match/2,
Transformer = fun({'->', CMeta, [ArgsWithGuards, Expr]}, Acc) ->
{Args, Guards} = elixir_clauses:extract_splat_guards(ArgsWithGuards),
{TClause, TS } = elixir_clauses:clause(?line(CMeta), fun translate_fn_match/2,
Args, Expr, Guards, true, Acc),
{ TClause, elixir_scope:mergef(S, TS) }
{TClause, elixir_scope:mergef(S, TS)}
end,
{ TClauses, NS } = lists:mapfoldl(Transformer, S, Clauses),
Arities = [length(Args) || { clause, _Line, Args, _Guards, _Exprs } <- TClauses],
{TClauses, NS} = lists:mapfoldl(Transformer, S, Clauses),
Arities = [length(Args) || {clause, _Line, Args, _Guards, _Exprs} <- TClauses],
case lists:usort(Arities) of
[_] ->
{ { 'fun', ?line(Meta), { clauses, TClauses } }, NS };
{{'fun', ?line(Meta), {clauses, TClauses}}, NS};
_ ->
compile_error(Meta, S#elixir_scope.file,
"cannot mix clauses with different arities in function definition")
end.
translate_fn_match(Arg, S) ->
{ TArg, TS } = elixir_translator:translate_args(Arg, S#elixir_scope{backup_vars=orddict:new()}),
{ TArg, TS#elixir_scope{backup_vars=S#elixir_scope.backup_vars} }.
{TArg, TS} = elixir_translator:translate_args(Arg, S#elixir_scope{backup_vars=orddict:new()}),
{TArg, TS#elixir_scope{backup_vars=S#elixir_scope.backup_vars}}.
%% Expansion
expand(Meta, Clauses, E) when is_list(Clauses) ->
Transformer = fun(Clause) ->
{ EClause, _ } = elixir_exp_clauses:clause(Meta, fn, fun elixir_exp:expand_args/2, Clause, E),
{EClause, _} = elixir_exp_clauses:clause(Meta, fn, fun elixir_exp:expand_args/2, Clause, E),
EClause
end,
{ { fn, Meta, lists:map(Transformer, Clauses) }, E }.
{{fn, Meta, lists:map(Transformer, Clauses)}, E}.
%% Capture
capture(Meta, { '/', _, [{ { '.', _, [_, F] } = Dot, RequireMeta , [] }, A] }, E) when is_atom(F), is_integer(A) ->
Args = [{ '&', [], [X] } || X <- lists:seq(1, A)],
capture_require(Meta, { Dot, RequireMeta, Args }, E, true);
capture(Meta, {'/', _, [{{'.', _, [_, F]} = Dot, RequireMeta , []}, A]}, E) when is_atom(F), is_integer(A) ->
Args = [{'&', [], [X]} || X <- lists:seq(1, A)],
capture_require(Meta, {Dot, RequireMeta, Args}, E, true);
capture(Meta, { '/', _, [{ F, _, C }, A] }, E) when is_atom(F), is_integer(A), is_atom(C) ->
capture(Meta, {'/', _, [{F, _, C}, A]}, E) when is_atom(F), is_integer(A), is_atom(C) ->
ImportMeta =
case lists:keyfind(import_fa, 1, Meta) of
{ import_fa, { Receiver, Context } } ->
{import_fa, {Receiver, Context}} ->
lists:keystore(context, 1,
lists:keystore(import, 1, Meta, { import, Receiver }),
{ context, Context }
lists:keystore(import, 1, Meta, {import, Receiver}),
{context, Context}
);
false -> Meta
end,
Args = [{ '&', [], [X] } || X <- lists:seq(1, A)],
capture_import(Meta, { F, ImportMeta, Args }, E, true);
Args = [{'&', [], [X]} || X <- lists:seq(1, A)],
capture_import(Meta, {F, ImportMeta, Args}, E, true);
capture(Meta, { { '.', _, [_, Fun] }, _, Args } = Expr, E) when is_atom(Fun), is_list(Args) ->
capture(Meta, {{'.', _, [_, Fun]}, _, Args} = Expr, E) when is_atom(Fun), is_list(Args) ->
capture_require(Meta, Expr, E, is_sequential_and_not_empty(Args));
capture(Meta, { { '.', _, [_] }, _, Args } = Expr, E) when is_list(Args) ->
capture(Meta, {{'.', _, [_]}, _, Args} = Expr, E) when is_list(Args) ->
do_capture(Meta, Expr, E, false);
capture(Meta, { '__block__', _, [Expr] }, E) ->
capture(Meta, {'__block__', _, [Expr]}, E) ->
capture(Meta, Expr, E);
capture(Meta, { '__block__', _, _ } = Expr, E) ->
capture(Meta, {'__block__', _, _} = Expr, E) ->
Message = "invalid args for &, block expressions are not allowed, got: ~ts",
compile_error(Meta, E#elixir_env.file, Message, ['Elixir.Macro':to_string(Expr)]);
compile_error(Meta, ?m(E, file), Message, ['Elixir.Macro':to_string(Expr)]);
capture(Meta, { Atom, _, Args } = Expr, E) when is_atom(Atom), is_list(Args) ->
capture(Meta, {Atom, _, Args} = Expr, E) when is_atom(Atom), is_list(Args) ->
capture_import(Meta, Expr, E, is_sequential_and_not_empty(Args));
capture(Meta, { Left, Right }, E) ->
capture(Meta, { '{}', Meta, [Left, Right] }, E);
capture(Meta, {Left, Right}, E) ->
capture(Meta, {'{}', Meta, [Left, Right]}, E);
capture(Meta, List, E) when is_list(List) ->
do_capture(Meta, List, E, is_sequential_and_not_empty(List));
@@ -79,80 +79,86 @@ capture(Meta, List, E) when is_list(List) ->
capture(Meta, Arg, E) ->
invalid_capture(Meta, Arg, E).
capture_import(Meta, { Atom, ImportMeta, Args } = Expr, E, Sequential) ->
capture_import(Meta, {Atom, ImportMeta, Args} = Expr, E, Sequential) ->
Res = Sequential andalso
elixir_dispatch:import_function(ImportMeta, Atom, length(Args), E),
handle_capture(Res, Meta, Expr, E, Sequential).
capture_require(Meta, { { '.', _, [Left, Right] }, RequireMeta, Args } = Expr, E, Sequential) ->
{ Mod, EE } = elixir_exp:expand(Left, E),
Res = Sequential andalso is_atom(Mod) andalso
elixir_dispatch:require_function(RequireMeta, Mod, Right, length(Args), EE),
capture_require(Meta, {{'.', _, [Left, Right]}, RequireMeta, Args} = Expr, E, Sequential) ->
{Mod, EE} = elixir_exp:expand(Left, E),
Res = Sequential andalso case Mod of
{Name, _, Context} when is_atom(Name), is_atom(Context) ->
{remote, Mod, Right, length(Args)};
_ when is_atom(Mod) ->
elixir_dispatch:require_function(RequireMeta, Mod, Right, length(Args), EE);
_ ->
false
end,
handle_capture(Res, Meta, Expr, EE, Sequential).
handle_capture({ local, Fun, Arity }, _Meta, _Expr, _E, _Sequential) ->
{ local, Fun, Arity };
handle_capture({ remote, Receiver, Fun, Arity }, Meta, _Expr, E, _Sequential) ->
Tree = { { '.', [], [erlang, make_fun] }, Meta, [Receiver, Fun, Arity] },
{ expanded, Tree, E };
handle_capture({local, Fun, Arity}, _Meta, _Expr, _E, _Sequential) ->
{local, Fun, Arity};
handle_capture({remote, Receiver, Fun, Arity}, Meta, _Expr, E, _Sequential) ->
Tree = {{'.', [], [erlang, make_fun]}, Meta, [Receiver, Fun, Arity]},
{expanded, Tree, E};
handle_capture(false, Meta, Expr, E, Sequential) ->
do_capture(Meta, Expr, E, Sequential).
do_capture(Meta, Expr, E, Sequential) ->
case do_escape(Expr, elixir_counter:next(), E, []) of
{ _, [] } when not Sequential ->
{_, []} when not Sequential ->
invalid_capture(Meta, Expr, E);
{ EExpr, EDict } ->
{EExpr, EDict} ->
EVars = validate(Meta, EDict, 1, E),
Fn = { fn, Meta, [{ '->', Meta, [EVars, EExpr]}]},
{ expanded, Fn, E }
Fn = {fn, Meta, [{'->', Meta, [EVars, EExpr]}]},
{expanded, Fn, E}
end.
invalid_capture(Meta, Arg, E) ->
Message = "invalid args for &, expected an expression in the format of &Mod.fun/arity, "
"&local/arity or a capture containing at least one argument as &1, got: ~ts",
compile_error(Meta, E#elixir_env.file, Message, ['Elixir.Macro':to_string(Arg)]).
compile_error(Meta, ?m(E, file), Message, ['Elixir.Macro':to_string(Arg)]).
validate(Meta, [{ Pos, Var }|T], Pos, E) ->
validate(Meta, [{Pos, Var}|T], Pos, E) ->
[Var|validate(Meta, T, Pos + 1, E)];
validate(Meta, [{ Pos, _ }|_], Expected, E) ->
compile_error(Meta, E#elixir_env.file, "capture &~B cannot be defined without &~B", [Pos, Expected]);
validate(Meta, [{Pos, _}|_], Expected, E) ->
compile_error(Meta, ?m(E, file), "capture &~B cannot be defined without &~B", [Pos, Expected]);
validate(_Meta, [], _Pos, _E) ->
[].
do_escape({ '&', _, [Pos] }, Counter, _E, Dict) when is_integer(Pos), Pos > 0 ->
Var = { list_to_atom([$x, $@+Pos]), [{ counter, Counter }], elixir_fn },
{ Var, orddict:store(Pos, Var, Dict) };
do_escape({'&', _, [Pos]}, Counter, _E, Dict) when is_integer(Pos), Pos > 0 ->
Var = {list_to_atom([$x, $@+Pos]), [{counter, Counter}], elixir_fn},
{Var, orddict:store(Pos, Var, Dict)};
do_escape({ '&', Meta, [Pos] }, _Counter, E, _Dict) when is_integer(Pos) ->
compile_error(Meta, E#elixir_env.file, "capture &~B is not allowed", [Pos]);
do_escape({'&', Meta, [Pos]}, _Counter, E, _Dict) when is_integer(Pos) ->
compile_error(Meta, ?m(E, file), "capture &~B is not allowed", [Pos]);
do_escape({ '&', Meta, _ } = Arg, _Counter, E, _Dict) ->
do_escape({'&', Meta, _} = Arg, _Counter, E, _Dict) ->
Message = "nested captures via & are not allowed: ~ts",
compile_error(Meta, E#elixir_env.file, Message, ['Elixir.Macro':to_string(Arg)]);
compile_error(Meta, ?m(E, file), Message, ['Elixir.Macro':to_string(Arg)]);
do_escape({ Left, Meta, Right }, Counter, E, Dict0) ->
{ TLeft, Dict1 } = do_escape(Left, Counter, E, Dict0),
{ TRight, Dict2 } = do_escape(Right, Counter, E, Dict1),
{ { TLeft, Meta, TRight }, Dict2 };
do_escape({Left, Meta, Right}, Counter, E, Dict0) ->
{TLeft, Dict1} = do_escape(Left, Counter, E, Dict0),
{TRight, Dict2} = do_escape(Right, Counter, E, Dict1),
{{TLeft, Meta, TRight}, Dict2};
do_escape({ Left, Right }, Counter, E, Dict0) ->
{ TLeft, Dict1 } = do_escape(Left, Counter, E, Dict0),
{ TRight, Dict2 } = do_escape(Right, Counter, E, Dict1),
{ { TLeft, TRight }, Dict2 };
do_escape({Left, Right}, Counter, E, Dict0) ->
{TLeft, Dict1} = do_escape(Left, Counter, E, Dict0),
{TRight, Dict2} = do_escape(Right, Counter, E, Dict1),
{{TLeft, TRight}, Dict2};
do_escape(List, Counter, E, Dict) when is_list(List) ->
lists:mapfoldl(fun(X, Acc) -> do_escape(X, Counter, E, Acc) end, Dict, List);
do_escape(Other, _Counter, _E, Dict) ->
{ Other, Dict }.
{Other, Dict}.
is_sequential_and_not_empty([]) -> false;
is_sequential_and_not_empty(List) -> is_sequential(List, 1).
is_sequential([{ '&', _, [Int] }|T], Int) ->
is_sequential([{'&', _, [Int]}|T], Int) ->
is_sequential(T, Int + 1);
is_sequential([], _Int) -> true;
is_sequential(_, _Int) -> false.
+107 -107
View File
@@ -5,42 +5,42 @@
%% Expansion
expand(Meta, Args, E) ->
{ Cases, Block } =
{Cases, Block} =
case elixir_utils:split_last(Args) of
{ OuterCases, OuterOpts } when is_list(OuterOpts) ->
{OuterCases, OuterOpts} when is_list(OuterOpts) ->
case elixir_utils:split_last(OuterCases) of
{ InnerCases, InnerOpts } when is_list(InnerOpts) ->
{ InnerCases, InnerOpts ++ OuterOpts };
{InnerCases, InnerOpts} when is_list(InnerOpts) ->
{InnerCases, InnerOpts ++ OuterOpts};
_ ->
{ OuterCases, OuterOpts }
{OuterCases, OuterOpts}
end;
_ ->
{ Args, [] }
{Args, []}
end,
{ Expr, Opts } =
{Expr, Opts} =
case lists:keyfind(do, 1, Block) of
{ do, Do } -> { Do, lists:keydelete(do, 1, Block) };
_ -> elixir_errors:compile_error(Meta, E#elixir_env.file,
{do, Do} -> {Do, lists:keydelete(do, 1, Block)};
_ -> elixir_errors:compile_error(Meta, ?m(E, file),
"missing do keyword in for comprehension")
end,
{ EOpts, EO } = elixir_exp:expand(Opts, E),
{ ECases, EC } = lists:mapfoldl(fun expand/2, EO, Cases),
{ EExpr, _ } = elixir_exp:expand(Expr, EC),
{ { for, Meta, ECases ++ [[{do,EExpr}|EOpts]] }, E }.
{EOpts, EO} = elixir_exp:expand(Opts, E),
{ECases, EC} = lists:mapfoldl(fun expand/2, EO, Cases),
{EExpr, _} = elixir_exp:expand(Expr, EC),
{{for, Meta, ECases ++ [[{do,EExpr}|EOpts]]}, E}.
expand({'<-', Meta, [Left, Right]}, E) ->
{ ERight, ER } = elixir_exp:expand(Right, E),
{ ELeft, EL } = elixir_exp_clauses:match(fun elixir_exp:expand/2, Left, E),
{ { '<-', Meta, [ELeft, ERight] }, elixir_env:mergev(EL, ER) };
expand({ '<<>>', Meta, Args } = X, E) when is_list(Args) ->
{ERight, ER} = elixir_exp:expand(Right, E),
{ELeft, EL} = elixir_exp_clauses:match(fun elixir_exp:expand/2, Left, E),
{{'<-', Meta, [ELeft, ERight]}, elixir_env:mergev(EL, ER)};
expand({'<<>>', Meta, Args} = X, E) when is_list(Args) ->
case elixir_utils:split_last(Args) of
{ LeftStart, {'<-', OpMeta, [LeftEnd, Right] } } ->
{ ERight, ER } = elixir_exp:expand(Right, E),
Left = { '<<>>', Meta, LeftStart ++ [LeftEnd] },
{ ELeft, EL } = elixir_exp_clauses:match(fun elixir_exp:expand/2, Left, E),
{ { '<<>>', [], [ { '<-', OpMeta, [ELeft, ERight] }] }, elixir_env:mergev(EL, ER) };
{LeftStart, {'<-', OpMeta, [LeftEnd, Right]}} ->
{ERight, ER} = elixir_exp:expand(Right, E),
Left = {'<<>>', Meta, LeftStart ++ [LeftEnd]},
{ELeft, EL} = elixir_exp_clauses:match(fun elixir_exp:expand/2, Left, E),
{{'<<>>', [], [ {'<-', OpMeta, [ELeft, ERight]}]}, elixir_env:mergev(EL, ER)};
_ ->
elixir_exp:expand(X, E)
end;
@@ -51,40 +51,40 @@ expand(X, E) ->
translate(Meta, Args, #elixir_scope{return=Return} = RS) ->
S = RS#elixir_scope{return=true},
{ AccName, _, SA } = elixir_scope:build_var('_', S),
{ VarName, _, SV } = elixir_scope:build_var('_', SA),
{AccName, _, SA} = elixir_scope:build_var('_', S),
{VarName, _, SV} = elixir_scope:build_var('_', SA),
Line = ?line(Meta),
Acc = { var, Line, AccName },
Var = { var, Line, VarName },
Acc = {var, Line, AccName},
Var = {var, Line, VarName},
{ Cases, [{do,Expr}|Opts] } = elixir_utils:split_last(Args),
{Cases, [{do,Expr}|Opts]} = elixir_utils:split_last(Args),
{ TInto, SI } =
{TInto, SI} =
case lists:keyfind(into, 1, Opts) of
{ into, Into } -> elixir_translator:translate(Into, SV);
false when Return -> { { nil, Line }, SV };
false -> { false, SV }
{into, Into} -> elixir_translator:translate(Into, SV);
false when Return -> {{nil, Line}, SV};
false -> {false, SV}
end,
{ TCases, SC } = translate_gen(Meta, Cases, [], SI),
{ TExpr, SE } = elixir_translator:translate_block(Expr, Return, SC),
SF = elixir_scope:mergef(SI, SE),
{TCases, SC} = translate_gen(Meta, Cases, [], SI),
{TExpr, SE} = elixir_translator:translate_block(Expr, Return, SC),
SF = elixir_scope:mergec(SI, SE),
case comprehension_expr(TInto, TExpr) of
{ inline, TIntoExpr } ->
{ build_inline(Line, TCases, TIntoExpr, TInto, Var, Acc, SE), SF };
{ into, TIntoExpr } ->
{inline, TIntoExpr} ->
{build_inline(Line, TCases, TIntoExpr, TInto, Var, Acc, SE), SF};
{into, TIntoExpr} ->
build_into(Line, TCases, TIntoExpr, TInto, Var, Acc, SF)
end.
translate_gen(ForMeta, [{ '<-', Meta, [Left, Right] }|T], Acc, S) ->
{ TLeft, TRight, TFilters, TT, TS } = translate_gen(Meta, Left, Right, T, S),
TAcc = [{ enum, Meta, TLeft, TRight, TFilters }|Acc],
translate_gen(ForMeta, [{'<-', Meta, [Left, Right]}|T], Acc, S) ->
{TLeft, TRight, TFilters, TT, TS} = translate_gen(Meta, Left, Right, T, S),
TAcc = [{enum, Meta, TLeft, TRight, TFilters}|Acc],
translate_gen(ForMeta, TT, TAcc, TS);
translate_gen(ForMeta, [{ '<<>>', _, [ { '<-', Meta, [Left, Right] } ] }|T], Acc, S) ->
{ TLeft, TRight, TFilters, TT, TS } = translate_gen(Meta, Left, Right, T, S),
TAcc = [{ bin, Meta, TLeft, TRight, TFilters }|Acc],
translate_gen(ForMeta, [{'<<>>', _, [ {'<-', Meta, [Left, Right]} ]}|T], Acc, S) ->
{TLeft, TRight, TFilters, TT, TS} = translate_gen(Meta, Left, Right, T, S),
TAcc = [{bin, Meta, TLeft, TRight, TFilters}|Acc],
case elixir_bitstring:has_size(TLeft) of
true -> translate_gen(ForMeta, TT, TAcc, TS);
false ->
@@ -92,55 +92,55 @@ translate_gen(ForMeta, [{ '<<>>', _, [ { '<-', Meta, [Left, Right] } ] }|T], Acc
"bitstring fields without size are not allowed in bitstring generators")
end;
translate_gen(_ForMeta, [], Acc, S) ->
{ lists:reverse(Acc), S };
{lists:reverse(Acc), S};
translate_gen(ForMeta, _, _, S) ->
elixir_errors:compile_error(ForMeta, S#elixir_scope.file,
"for comprehensions must start with a generator").
translate_gen(_Meta, Left, Right, T, S) ->
{ TRight, SR } = elixir_translator:translate(Right, S),
{ TLeft, SL } = elixir_clauses:match(fun elixir_translator:translate/2, Left, SR),
{ TT, { TFilters, TS } } = translate_filters(T, SL),
{ TLeft, TRight, TFilters, TT, TS }.
{TRight, SR} = elixir_translator:translate(Right, S),
{TLeft, SL} = elixir_clauses:match(fun elixir_translator:translate/2, Left, SR),
{TT, {TFilters, TS}} = translate_filters(T, SL),
{TLeft, TRight, TFilters, TT, TS}.
translate_filters(T, S) ->
{ Filters, Rest } = collect_filters(T, []),
{ Rest, lists:mapfoldr(fun translate_filter/2, S, Filters) }.
{Filters, Rest} = collect_filters(T, []),
{Rest, lists:mapfoldr(fun translate_filter/2, S, Filters)}.
translate_filter(Filter, S) ->
{ TFilter, TS } = elixir_translator:translate(Filter, S),
{TFilter, TS} = elixir_translator:translate(Filter, S),
case elixir_utils:returns_boolean(TFilter) of
true ->
{ { nil, TFilter }, TS };
{{nil, TFilter}, TS};
false ->
{ Name, _, VS } = elixir_scope:build_var('_', TS),
{ { { var, 0, Name }, TFilter }, VS }
{Name, _, VS} = elixir_scope:build_var('_', TS),
{{{var, 0, Name}, TFilter}, VS}
end.
collect_filters([{ '<-', _, [_, _] }|_] = T, Acc) ->
{ Acc, T };
collect_filters([{ '<<>>', _, [{ '<-', _, [_, _] }] }|_] = T, Acc) ->
{ Acc, T };
collect_filters([{'<-', _, [_, _]}|_] = T, Acc) ->
{Acc, T};
collect_filters([{'<<>>', _, [{'<-', _, [_, _]}]}|_] = T, Acc) ->
{Acc, T};
collect_filters([H|T], Acc) ->
collect_filters(T, [H|Acc]);
collect_filters([], Acc) ->
{ Acc, [] }.
{Acc, []}.
%% If all we have is one enum generator, we check if it is a list
%% for optimization otherwise fallback to the reduce generator.
build_inline(Line, [{ enum, Meta, Left, Right, Filters }] = Orig, Expr, Into, Var, Acc, S) ->
build_inline(Line, [{enum, Meta, Left, Right, Filters}] = Orig, Expr, Into, Var, Acc, S) ->
case Right of
{ cons, _, _, _ } ->
{cons, _, _, _} ->
build_comprehension(Line, Orig, Expr, Into);
{ Other, _, _ } when Other == tuple; Other == map; Other == integer; Other == atom ->
{Other, _, _} when Other == tuple; Other == map ->
build_reduce(Orig, Expr, Into, Acc, S);
_ ->
Clauses = [{ enum, Meta, Left, Var, Filters }],
Clauses = [{enum, Meta, Left, Var, Filters}],
{'case', -1, Right, [
{clause, -1,
[Var],
[[?wrap_call(Line, erlang, is_list, [Var])]],
[[elixir_utils:erl_call(Line, erlang, is_list, [Var])]],
[build_comprehension(Line, Clauses, Expr, Into)]},
{clause, -1,
[Var],
@@ -156,16 +156,16 @@ build_inline(Line, Clauses, Expr, Into, _Var, Acc, S) ->
end.
build_into(Line, Clauses, Expr, Into, Fun, Acc, S) ->
{ Kind, SK } = build_var(Line, S),
{ Reason, SR } = build_var(Line, SK),
{ Stack, ST } = build_var(Line, SR),
{ Done, SD } = build_var(Line, ST),
{Kind, SK} = build_var(Line, S),
{Reason, SR} = build_var(Line, SK),
{Stack, ST} = build_var(Line, SR),
{Done, SD} = build_var(Line, ST),
IntoExpr = {call, Line, Fun, [Acc, pair(Line, cont, Expr)]},
MatchExpr = {match, Line,
{tuple, Line, [Acc, Fun]},
?wrap_call(Line, 'Elixir.Collectable', into, [Into])
},
elixir_utils:erl_call(Line, 'Elixir.Collectable', into, [Into])
},
TryExpr =
{'try', Line,
@@ -177,12 +177,12 @@ build_into(Line, Clauses, Expr, Into, Fun, Acc, S) ->
[{clause, Line,
[{tuple, Line, [Kind, Reason, {var, Line, '_'}]}],
[],
[{match, Line, Stack, ?wrap_call(Line, erlang, get_stacktrace, [])},
[{match, Line, Stack, elixir_utils:erl_call(Line, erlang, get_stacktrace, [])},
{call, Line, Fun, [Acc, {atom, Line, halt}]},
?wrap_call(Line, erlang, raise, [Kind, Reason, Stack])]}],
elixir_utils:erl_call(Line, erlang, raise, [Kind, Reason, Stack])]}],
[]},
{ {block, Line, [MatchExpr, TryExpr]}, SD }.
{{block, Line, [MatchExpr, TryExpr]}, SD}.
%% Helpers
@@ -190,14 +190,14 @@ build_reduce(Clauses, Expr, false, Acc, S) ->
build_reduce_clause(Clauses, Expr, {nil, 0}, Acc, S);
build_reduce(Clauses, Expr, {nil, Line} = Into, Acc, S) ->
ListExpr = {cons, Line, Expr, Acc},
?wrap_call(Line, lists, reverse,
elixir_utils:erl_call(Line, lists, reverse,
[build_reduce_clause(Clauses, ListExpr, Into, Acc, S)]);
build_reduce(Clauses, Expr, {bin, _, _} = Into, Acc, S) ->
{bin, Line, Elements} = Expr,
BinExpr = {bin, Line, [{bin_element, Line, Acc, default, [bitstring]}|Elements]},
build_reduce_clause(Clauses, BinExpr, Into, Acc, S).
build_reduce_clause([{ enum, Meta, Left, Right, Filters }|T], Expr, Arg, Acc, S) ->
build_reduce_clause([{enum, Meta, Left, Right, Filters}|T], Expr, Arg, Acc, S) ->
Line = ?line(Meta),
Inner = build_reduce_clause(T, Expr, Acc, Acc, S),
@@ -219,15 +219,15 @@ build_reduce_clause([{ enum, Meta, Left, Right, Filters }|T], Expr, Arg, Acc, S)
[join_filters(Line, Filters, True, False)]}|Clauses0],
Args = [Right, pair(Line, cont, Arg), {'fun', Line, {clauses, Clauses1}}],
Tuple = ?wrap_call(Line, 'Elixir.Enumerable', reduce, Args),
Tuple = elixir_utils:erl_call(Line, 'Elixir.Enumerable', reduce, Args),
%% Use -1 because in case of no returns we don't care about the result
?wrap_call(-1, erlang, element, [{integer, Line, 2}, Tuple]);
elixir_utils:erl_call(-1, erlang, element, [{integer, Line, 2}, Tuple]);
build_reduce_clause([{ bin, Meta, Left, Right, Filters }|T], Expr, Arg, Acc, S) ->
build_reduce_clause([{bin, Meta, Left, Right, Filters}|T], Expr, Arg, Acc, S) ->
Line = ?line(Meta),
{ Tail, ST } = build_var(Line, S),
{ Fun, SF } = build_var(Line, ST),
{Tail, ST} = build_var(Line, S),
{Fun, SF} = build_var(Line, ST),
True = build_reduce_clause(T, Expr, Acc, Acc, SF),
False = Acc,
@@ -235,9 +235,9 @@ build_reduce_clause([{ bin, Meta, Left, Right, Filters }|T], Expr, Arg, Acc, S)
{bin, _, Elements} = Left,
BinMatch =
{bin, Line, Elements ++ [{ bin_element, Line, Tail, default, [bitstring] }]},
{bin, Line, Elements ++ [{bin_element, Line, Tail, default, [bitstring]}]},
NoVarMatch =
{bin, Line, no_var(Elements) ++ [{ bin_element, Line, Tail, default, [bitstring] }]},
{bin, Line, no_var(Elements) ++ [{bin_element, Line, Tail, default, [bitstring]}]},
Clauses =
[{clause, Line,
@@ -251,7 +251,7 @@ build_reduce_clause([{ bin, Meta, Left, Right, Filters }|T], Expr, Arg, Acc, S)
[Acc]},
{clause, -1,
[Tail, {var, Line, '_'}], [],
[?wrap_call(Line, erlang, error, [pair(Line, badarg, Tail)])]}],
[elixir_utils:erl_call(Line, erlang, error, [pair(Line, badarg, Tail)])]}],
{call, Line,
{named_fun, Line, element(3, Fun), Clauses},
@@ -267,13 +267,13 @@ pair(Line, Atom, Arg) ->
{tuple, Line, [{atom, Line, Atom}, Arg]}.
build_var(Line, S) ->
{ Name, _, ST } = elixir_scope:build_var('_', S),
{ { var, Line, Name }, ST }.
{Name, _, ST} = elixir_scope:build_var('_', S),
{{var, Line, Name}, ST}.
no_var(Elements) ->
[{bin_element, Line, no_var_expr(Expr), Size, Types} ||
{bin_element, Line, Expr, Size, Types} <- Elements].
no_var_expr({ var, Line, _ }) ->
no_var_expr({var, Line, _}) ->
{var, Line, '_'}.
build_comprehension(Line, Clauses, Expr, false) ->
@@ -284,7 +284,7 @@ build_comprehension(Line, Clauses, Expr, false) ->
build_comprehension(Line, Clauses, Expr, Into) ->
{comprehension_kind(Into), Line, Expr, comprehension_clause(Clauses)}.
comprehension_clause([{ Kind, Meta, Left, Right, Filters }|T]) ->
comprehension_clause([{Kind, Meta, Left, Right, Filters}|T]) ->
Line = ?line(Meta),
[{comprehension_generator(Kind), Line, Left, Right}] ++
comprehension_filter(Line, Filters) ++
@@ -292,26 +292,26 @@ comprehension_clause([{ Kind, Meta, Left, Right, Filters }|T]) ->
comprehension_clause([]) ->
[].
comprehension_kind({ nil, _ }) -> lc;
comprehension_kind({ bin, _, [] }) -> bc.
comprehension_kind({nil, _}) -> lc;
comprehension_kind({bin, _, []}) -> bc.
comprehension_generator(enum) -> generate;
comprehension_generator(bin) -> b_generate.
comprehension_expr({ bin, _, [] }, { bin, _, _ } = Expr) ->
{ inline, Expr };
comprehension_expr({ bin, Line, [] }, Expr) ->
BinExpr = { bin, Line, [{ bin_element, Line, Expr, default, [bitstring] }] },
{ inline, BinExpr };
comprehension_expr({ nil, _ }, Expr) ->
{ inline, Expr };
comprehension_expr({bin, _, []}, {bin, _, _} = Expr) ->
{inline, Expr};
comprehension_expr({bin, Line, []}, Expr) ->
BinExpr = {bin, Line, [{bin_element, Line, Expr, default, [bitstring]}]},
{inline, BinExpr};
comprehension_expr({nil, _}, Expr) ->
{inline, Expr};
comprehension_expr(false, Expr) ->
{ inline, Expr };
{inline, Expr};
comprehension_expr(_, Expr) ->
{ into, Expr }.
{into, Expr}.
comprehension_filter(Line, Filters) ->
[join_filter(Line, Filter, { atom, Line, true }, { atom, Line, false }) ||
[join_filter(Line, Filter, {atom, Line, true}, {atom, Line, false}) ||
Filter <- lists:reverse(Filters)].
join_filters(_Line, [], True, _False) ->
@@ -321,18 +321,18 @@ join_filters(Line, [H|T], True, False) ->
join_filter(Line, Filter, Acc, False)
end, join_filter(Line, H, True, False), T).
join_filter(Line, { nil, Filter }, True, False) ->
join_filter(Line, {nil, Filter}, True, False) ->
{'case', Line, Filter, [
{clause, Line, [{atom, Line, true}], [], [True] },
{clause, Line, [{atom, Line, false}], [], [False] }
] };
join_filter(Line, { Var, Filter }, True, False) ->
{clause, Line, [{atom, Line, true}], [], [True]},
{clause, Line, [{atom, Line, false}], [], [False]}
]};
join_filter(Line, {Var, Filter}, True, False) ->
Guard =
{op, Line, 'orelse',
{op, Line, '==', Var, {atom, Line, false}},
{op, Line, '==', Var, {atom, Line, nil}}},
{'case', Line, Filter, [
{clause, Line, [Var], [[Guard]], [False] },
{clause, Line, [{var, Line, '_'}], [], [True] }
] }.
{clause, Line, [Var], [[Guard]], [False]},
{clause, Line, [{var, Line, '_'}], [], [True]}
]}.
+32 -32
View File
@@ -10,28 +10,28 @@
import(Meta, Ref, Opts, E) ->
Res =
case keyfind(only, Opts) of
{ only, functions } ->
{ import_functions(Meta, Ref, Opts, E),
E#elixir_env.macros };
{ only, macros } ->
{ E#elixir_env.functions,
import_macros(true, Meta, Ref, Opts, E) };
{ only, List } when is_list(List) ->
{ import_functions(Meta, Ref, Opts, E),
import_macros(false, Meta, Ref, Opts, E) };
{only, functions} ->
{import_functions(Meta, Ref, Opts, E),
?m(E, macros)};
{only, macros} ->
{?m(E, functions),
import_macros(true, Meta, Ref, Opts, E)};
{only, List} when is_list(List) ->
{import_functions(Meta, Ref, Opts, E),
import_macros(false, Meta, Ref, Opts, E)};
false ->
{ import_functions(Meta, Ref, Opts, E),
import_macros(false, Meta, Ref, Opts, E) }
{import_functions(Meta, Ref, Opts, E),
import_macros(false, Meta, Ref, Opts, E)}
end,
record_warn(Meta, Ref, Opts, E),
Res.
import_functions(Meta, Ref, Opts, E) ->
calculate(Meta, Ref, Opts, E#elixir_env.functions, E, fun() -> get_functions(Ref) end).
calculate(Meta, Ref, Opts, ?m(E, functions), E, fun() -> get_functions(Ref) end).
import_macros(Force, Meta, Ref, Opts, E) ->
calculate(Meta, Ref, Opts, E#elixir_env.macros, E, fun() ->
calculate(Meta, Ref, Opts, ?m(E, macros), E, fun() ->
case Force of
true -> get_macros(Meta, Ref, E);
false -> get_optional_macros(Ref)
@@ -41,29 +41,29 @@ import_macros(Force, Meta, Ref, Opts, E) ->
record_warn(Meta, Ref, Opts, E) ->
Warn =
case keyfind(warn, 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_import(Ref, ?line(Meta), Warn, E#elixir_env.lexical_tracker).
elixir_lexical:record_import(Ref, ?line(Meta), Warn, ?m(E, lexical_tracker)).
%% Calculates the imports based on only and except
calculate(Meta, Key, Opts, Old, E, Existing) ->
New = case keyfind(only, Opts) of
{ only, Only } when is_list(Only) ->
{only, Only} when is_list(Only) ->
case Only -- get_exports(Key) of
[{Name,Arity}|_] ->
Tuple = { invalid_import, { Key, Name, Arity } },
elixir_errors:form_error(Meta, E#elixir_env.file, ?MODULE, Tuple);
Tuple = {invalid_import, {Key, Name, Arity}},
elixir_errors:form_error(Meta, ?m(E, file), ?MODULE, Tuple);
_ ->
intersection(Only, Existing())
end;
_ ->
case keyfind(except, Opts) of
false -> remove_underscored(Existing());
{ except, [] } -> remove_underscored(Existing());
{ except, Except } when is_list(Except) ->
{except, []} -> remove_underscored(Existing());
{except, Except} when is_list(Except) ->
case keyfind(Key, Old) of
false -> remove_underscored(Existing()) -- Except;
{Key,OldImports} -> OldImports -- Except
@@ -78,8 +78,8 @@ calculate(Meta, Key, Opts, Old, E, Existing) ->
case Final of
[] -> keydelete(Key, Old);
_ ->
ensure_no_special_form_conflict(Meta, E#elixir_env.file, Key, Final),
[{ Key, Final }|keydelete(Key, Old)]
ensure_no_special_form_conflict(Meta, ?m(E, file), Key, Final),
[{Key, Final}|keydelete(Key, Old)]
end.
%% Retrieve functions and macros from modules
@@ -103,19 +103,19 @@ get_macros(Meta, Module, E) ->
Module:'__info__'(macros)
catch
error:undef ->
Tuple = { no_macros, Module },
elixir_errors:form_error(Meta, E#elixir_env.file, ?MODULE, Tuple)
Tuple = {no_macros, Module},
elixir_errors:form_error(Meta, ?m(E, file), ?MODULE, Tuple)
end.
get_optional_macros(Module) ->
case code:ensure_loaded(Module) of
{ module, Module } ->
{module, Module} ->
try
Module:'__info__'(macros)
catch
error:undef -> []
end;
{ error, _ } -> []
{error, _} -> []
end.
%% VALIDATION HELPERS
@@ -123,7 +123,7 @@ get_optional_macros(Module) ->
ensure_no_special_form_conflict(Meta, File, Key, [{Name,Arity}|T]) ->
case special_form(Name, Arity) of
true ->
Tuple = { special_form_conflict, { Key, Name, Arity } },
Tuple = {special_form_conflict, {Key, Name, Arity}},
elixir_errors:form_error(Meta, File, ?MODULE, Tuple);
false ->
ensure_no_special_form_conflict(Meta, File, Key, T)
@@ -141,7 +141,7 @@ format_error({special_form_conflict,{Receiver, Name, Arity}}) ->
io_lib:format("cannot import ~ts.~ts/~B because it conflicts with Elixir special forms",
[elixir_aliases:inspect(Receiver), Name, Arity]);
format_error({ no_macros, Module }) ->
format_error({no_macros, Module}) ->
io_lib:format("could not load macros from module ~ts", [elixir_aliases:inspect(Module)]).
%% LIST HELPERS
@@ -163,7 +163,7 @@ intersection([], _All) -> [].
%% Internal funs that are never imported etc.
remove_underscored(List) ->
lists:filter(fun({ Name, _ }) ->
lists:filter(fun({Name, _}) ->
case atom_to_list(Name) of
"_" ++ _ -> false;
_ -> true
@@ -171,8 +171,8 @@ remove_underscored(List) ->
end, List).
remove_internals(Set) ->
ordsets:del_element({ module_info, 1 },
ordsets:del_element({ module_info, 0 }, Set)).
ordsets:del_element({module_info, 1},
ordsets:del_element({module_info, 0}, Set)).
%% Special forms
+9 -9
View File
@@ -17,13 +17,13 @@ extract(Line, _Scope, _Interpol, [], Buffer, 0, Output, []) ->
finish_extraction(Line, Buffer, Output, []);
extract(Line, _Scope, _Interpol, [], _Buffer, 0, _Output, Last) ->
{ error, { string, Line, io_lib:format("missing terminator: ~ts", [[Last]]), [] } };
{error, {string, Line, io_lib:format("missing terminator: ~ts", [[Last]]), []}};
extract(Line, _Scope, _Interpol, [Last|Remaining], Buffer, 0, Output, Last) ->
finish_extraction(Line, Buffer, Output, Remaining);
extract(Line, _Scope, _Interpol, [], _Buffer, _Search, _Output, Last) ->
{ error, { string, Line, io_lib:format("missing terminator: ~ts", [[Last]]), [] } };
{error, {string, Line, io_lib:format("missing terminator: ~ts", [[Last]]), []}};
%% Going through the string
@@ -46,13 +46,13 @@ extract(Line, Scope, true, [$#, ${|Rest], Buffer, Search, Output, Last) ->
Output1 = build_string(Line, Buffer, Output),
case elixir_tokenizer:tokenize(Rest, Line, Scope) of
{ error, { EndLine, _, "}" }, [$}|NewRest], Tokens } ->
{error, {EndLine, _, "}"}, [$}|NewRest], Tokens} ->
Output2 = build_interpol(Line, Tokens, Output1),
extract(EndLine, Scope, true, NewRest, [], Search, Output2, Last);
{ error, Reason, _, _ } ->
{ error, Reason };
{ ok, _EndLine, _ } ->
{ error, { string, Line, "missing interpolation terminator: }", [] } }
{error, Reason, _, _} ->
{error, Reason};
{ok, _EndLine, _} ->
{error, {string, Line, "missing interpolation terminator:}", []}}
end;
%% Matching () [] {} <> inside sigils
@@ -173,11 +173,11 @@ finish_extraction(Line, Buffer, Output, Remaining) ->
[] -> Final = [<<>>];
Final -> []
end,
{ Line, lists:reverse(Final), Remaining }.
{Line, lists:reverse(Final), Remaining}.
build_string(_Line, [], Output) -> Output;
build_string(_Line, Buffer, Output) ->
[elixir_utils:characters_to_binary(lists:reverse(Buffer))|Output].
build_interpol(Line, Buffer, Output) ->
[{ Line, lists:reverse(Buffer) }|Output].
[{Line, lists:reverse(Buffer)}|Output].

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