Compare commits

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

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

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

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

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

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

We can work around this issue by adding a bit of indirection, like
`nil?(List.first([nil])) == true`.
2014-02-03 13:45:27 +01:00
José Valim 22ebd5f2d6 Fix build and update CHANGELOG 2014-02-03 10:09:24 +01:00
José Valim 8744abc39f Slightly reword version error messages 2014-02-03 09:45:36 +01:00
José Valim 1574b9380b Use throw/catch only inside matchspec. Keep to_matchspec/1 and from_matchspec/1 as public 2014-02-03 09:37:55 +01:00
José Valim 66441fb2cb Merge pull request #2028 from ericmj/version-semver
Enforce semantic versioning
2014-02-02 23:53:53 -08:00
Eric Meadows-Jönsson 2466307b74 :override option should override the version requirement 2014-02-02 22:14:56 +01:00
Eric Meadows-Jönsson 6a5b25045c Mix error messages should be capitalized 2014-02-02 20:30:53 +01:00
Eric Meadows-Jönsson 742a922b90 Only allow semver versions for mix projects 2014-02-02 20:29:38 +01:00
Eric Meadows-Jönsson 2e2d534257 Better Mix errors when version or requirements are invalid 2014-02-02 20:29:38 +01:00
Eric Meadows-Jönsson 271700692a Simplify Version interface
* Remove the validation functions in favor of parsing functions returning
error tuples.
* `match?/2` and `compare/2` only raises on parse errors, if given already
parsed version and requirement they will not raise.
2014-02-02 20:28:46 +01:00
Eric Meadows-Jönsson 6bc699672b Only allow semver versions in Version
Functions in Version that previously raised on invalid versions
or requirements now returns an error tuple.
2014-02-02 20:28:46 +01:00
José Valim c07b0d0912 Remove deprecate code 2014-02-02 20:22:28 +01:00
Eric Meadows-Jönsson 61cac4eebc Enable erlang 17.0 support and squelch some warnings 2014-02-02 20:20:19 +01:00
José Valim b723da6574 Start 0.12.4-dev 2014-02-02 19:55:12 +01:00
José Valim 0159de8a21 Release v0.12.3 2014-02-02 19:52:10 +01:00
José Valim c961a73c42 Fix build and remove warnings 2014-02-02 17:55:51 +01:00
José Valim 2bafb1704b Print warning when inspect fails 2014-02-02 17:45:54 +01:00
José Valim cf84ed3881 Only justify applications in IEx stacktraces 2014-02-02 17:36:46 +01:00
José Valim 95de5bad78 Merge branch 'iex-exceptions-take-2' of github.com:pragdave/elixir into pragdave-iex-exceptions-take-2 2014-02-02 16:22:07 +01:00
José Valim 7cdb3b09d9 Remove valid_requirement?/1 from CHANGELOG 2014-02-02 13:34:10 +01:00
Bradley Smith b3cf44631c remove all deprecated // and replace with \ 2014-02-02 13:32:56 +01:00
José Valim e3d96c349f Remove tty error handler and simply point to print report on bad return 2014-02-01 17:31:19 +01:00
José Valim f0e42f0654 Merge pull request #2017 from jeregrine/improve_error_on_bad_return
Improve error on bad return
2014-02-01 06:45:58 -08:00
José Valim bf1da40976 Raise proper error on invalid function in quoted expressions, closes #2020 2014-02-01 15:42:22 +01:00
José Valim 9994dba0f7 Update CHANGELOG 2014-02-01 12:58:57 +01:00
José Valim 294885be1a Default build_per_environment to true 2014-02-01 12:55:30 +01:00
José Valim 82cdc51ca8 Deprecate Record.import/2
This is part of the preparation for maps.
2014-02-01 12:21:14 +01:00
José Valim e855a85c85 Merge branch 'issue-2022-warnings' of github.com:ShaneWilton/elixir into ShaneWilton-issue-2022-warnings
Conflicts:
	lib/elixir/test/elixir/kernel/warning_test.exs
	lib/iex/test/iex/helpers_test.exs
2014-02-01 09:35:49 +01:00
José Valim b9c49c93b8 Raise early on pipes into & special form 2014-01-31 16:33:39 +01:00
José Valim dc393ed14a No need to store environment in .compile.lock 2014-01-31 16:25:56 +01:00
José Valim cbae2aecfc Remove m/0 helper
The goal is to come up with a general
IEx module for runtime statistics.
2014-01-31 15:50:20 +01:00
José Valim 66e0c53593 Revert "Deprecate =~"
This reverts commit e42e3e55ca.
2014-01-31 09:15:06 +01:00
José Valim d7bf2aa783 Lexer's valid_required? can now be private 2014-01-31 09:12:32 +01:00
Eric Meadows-Jönsson 90d0e76eb6 Fix test in previous commit 2014-01-31 00:19:45 +01:00
Eric Meadows-Jönsson 6f3c635f6f Add Version.valid_requirement?/1 2014-01-31 00:19:04 +01:00
José Valim 2b12cb4a7e Remove unecessary lexical check in parser 2014-01-30 22:04:39 +01:00
José Valim 1a9fb9caca Fix fixtures copying on mix tests 2014-01-30 10:14:42 +01:00
José Valim 2da92475ba Remove Unix specific behaviour from File.cp/3 and File.cr_r/3
Previously those functions would behave as in their Unix counterpart
where copying behaved differently depending if the destination was
an existing directory or not. We have explicitly disallowed this
behaviour and added the proper notes to the docs.

Closes #2024.
2014-01-30 09:59:11 +01:00
Shane 36d1dcd9cc Prefix all warnings with [warning]
We expose :elixir_errors.warn/2 and :elixir_errors.warn/3
The first is to be used when we have detailed caller information,
such as from a stacktrace. The second is useful when our only
context is a line number and filename.

Note also that the test for module redfinition is performed in
helpers_test.exs rather than in warning_test.exs Perhaps this is
something that should be moved.
2014-01-29 11:35:43 -05:00
José Valim eb62c3bd27 IEx now loads the .iex.exs file instead of the .iex one 2014-01-29 12:52:00 +01:00
José Valim 05cd7f170f Fix list with keywords expansion
The list `[1, 2, three: :four]` now correctly expands
to `[1, 2, {:three, :four}]`.
2014-01-28 20:59:07 +01:00
José Valim 0d4655d1db Generalize unary operators followed by int have higher precedence 2014-01-28 19:54:09 +01:00
José Valim e42e3e55ca Deprecate =~
Use String.contains?/2 when the right side is a string or
String.match?/2 when the right side is a regex.

The goal of this deprecation is to make space for the upcoming
`~` and `~=` operators.
2014-01-28 12:50:39 +01:00
José Valim 2a6039da98 Improve error messages for bad arity errors 2014-01-28 12:07:10 +01:00
José Valim 33035e9978 Address undefined macro if on bootstraping 2014-01-28 11:25:35 +01:00
José Valim 5f682023a6 Deprecate usage of @behavior in favor of @behaviour 2014-01-28 11:21:55 +01:00
José Valim fe2e4a1010 Move from // to \\ 2014-01-28 10:50:00 +01:00
José Valim 50e479332c Allow operators to be captured, example: &+/2 2014-01-27 15:35:35 +01:00
José Valim 49c3770d87 Remove uneeded mix fixtures 2014-01-27 12:38:00 +01:00
José Valim ae24cad4b8 Limit the allowed sigils 2014-01-27 12:32:21 +01:00
José Valim a8eab99e44 Make explicit is_range / is_regex are allowed in guards 2014-01-26 20:28:10 +01:00
José Valim b6d97c3f1d Fix bootstraping issues 2014-01-25 12:26:42 +01:00
José Valim 6daca1b630 Update CHANGELOG 2014-01-25 12:14:15 +01:00
José Valim 9a3695c949 Only accept stacktraces in Module.get_attribute/3 2014-01-25 12:08:56 +01:00
José Valim 9499b9f9be Merge pull request #2018 from mururu/fix-warning-filename
Make sure unintialized @attribute warnings points to the correct file
2014-01-25 02:42:03 -08:00
José Valim 5fdc6b55ee Improve coverage for Set and Dict with mixed types 2014-01-25 11:40:47 +01:00
Yuki Ito 3f2b44f983 Make sure unintialized @attribute warnings points to the correct file 2014-01-25 19:36:15 +09:00
José Valim 18a8272fe5 Improve contracts for Set
Similar to how it has been done to Dict, functions in the
Set are polymorphic but Set implementations should not provide
polymorphic functions.

This reduces the duplication inside each set implementation
and provides both strict and polymorphic interfaces.
2014-01-25 11:15:39 +01:00
Jason S 070f6a00a8 Improve Error on bad_return from Application.Behaviour.Start 2014-01-24 18:08:40 -06:00
José Valim 12dabedf0d Improve contracts for Dict
1. Functions like merge/2 and equal?/2 are only polymorphic in the
Dict module. Implementations of the Dict module only need to
care about equality and merging of the same types.

2. take/2, drop/2 and split/2 accept any Enum as argument.
2014-01-24 12:16:40 +01:00
José Valim 24da600a72 Document inlined functions are BIFs 2014-01-24 11:37:43 +01:00
pragdave 84561408d1 Respond to review comments 2014-01-23 12:45:49 -08:00
pragdave 9065839328 Don't use Regex module inside Exception, as it may not be available 2014-01-23 12:39:32 -08:00
pragdave 3f30bf156c Add exception formatting and colorization to iex 2014-01-23 10:30:41 -08:00
José Valim b659ecfa7e Remove pre-copying of dev dependencies
The current code was copying manifests but they got
recent access times which caused bugs. Also, a dependency
can have different results depending on each dependencies
are available and which are not, so simply copying the
compiled dep can lead to weird errors.
2014-01-23 18:38:57 +01:00
José Valim c240a49f04 Ensure doc_line resets algebra formatting line buffer, closes #2014 2014-01-23 09:09:56 +01:00
José Valim e89b37190e Ensure File.cp_r/3 respects trailing / in sources 2014-01-23 00:14:46 +01:00
José Valim ff61a74a8a Raise on unknown dependencies for deps.get and deps.update 2014-01-22 20:30:26 +01:00
José Valim 39db5a1efd Clean up app.start tests 2014-01-22 19:47:08 +01:00
José Valim 0802014779 No longer compile unicode in parallel
Compiling in parallel only bought us 2s (out of 24s) since
compiling graphemes is what takes most of the time but it
considerably increases memory usage, by about 30%, since both
compilation targets are loaded into memory at once.
2014-01-22 17:51:24 +01:00
José Valim bfc30274ff Add Process.send/2 and Process.send_after/3 2014-01-22 16:58:02 +01:00
Eric Meadows-Jönsson bec7a0becb Store Mix.Dep records in Mix.Dep.deps 2014-01-22 16:30:07 +01:00
José Valim fe8fc1be25 Inline some process and node calls, closes #1808 2014-01-22 15:06:35 +01:00
Eric Meadows-Jönsson bcb7cff5f2 Add Version.compare/2 2014-01-22 14:36:41 +01:00
José Valim 8023c29c3f Consistently inline functions in the Kernel module by the compiler 2014-01-22 14:11:48 +01:00
José Valim 147fafb067 Update CHANGELOG 2014-01-22 12:01:55 +01:00
José Valim 674f4a2cb3 Do not erase elixir_compiler_pid 2014-01-22 10:47:17 +01:00
José Valim b326fe12d9 Unify deps.get and deps.update workflows 2014-01-22 10:00:08 +01:00
José Valim 263f0d1c81 Rename Mix.Deps.Retriever to Mix.Deps.Loader 2014-01-22 09:30:45 +01:00
José Valim 47ae5b4758 Move Elixir version mismatch to a warning, closes #1951 2014-01-22 09:28:51 +01:00
José Valim 25c54dad84 Remove duplicated type assertions 2014-01-21 21:08:43 +01:00
José Valim 48b8efe9c6 Ensure proper tokenization of the ... identifier 2014-01-21 20:50:43 +01:00
Eric Meadows-Jönsson 5d34fcdccd Clear project stack in mix teardown 2014-01-21 12:51:43 +01:00
José Valim e63546bda6 Ensure proper dependency on bootstrap 2014-01-21 10:25:31 +01:00
José Valim 04c8ca9718 Deprecate keywords, getter and list getter functionalities of defrecordp
Those functionalities can still be simulated via pattern matching
and perform basic operations. The reason for this deprecation is
that we are starting to unify defrecord/defrecordp syntaxes, so
instead of doing a big change at once, we are first bringing them
to a common set of functionalities to easy migration in the future.
2014-01-21 10:15:35 +01:00
José Valim 08d69e3975 Add a test for records with default function fields 2014-01-21 09:56:16 +01:00
José Valim bfc165c46d Merge pull request #2012 from cabo/master
Fix functions as default values in records
2014-01-21 00:55:50 -08:00
Carsten Bormann 9764f3f384 Fix functions as default values in records
successful check_value/2 needs to return its second argument
2014-01-20 20:42:46 +01:00
José Valim b1915b1ad6 Convert HashSet to use the new trie implementation 2014-01-20 13:58:43 +01:00
José Valim 5991eb04fa Update CHANGELOG 2014-01-20 12:24:22 +01:00
José Valim b3e07652cc Range is no longer a record
The goal is to simplify and allow us to extend ranges in the future.
Also, incentivate people to use `first .. last` in pattern matching
instead of `Range[first: first, last: last]`.
2014-01-20 12:24:22 +01:00
José Valim cad7551caf Inspect :... and :foo@bar as valid atoms 2014-01-20 12:24:22 +01:00
José Valim 8ac132fee1 Fix HashDict.pop signature 2014-01-20 12:24:22 +01:00
José Valim ec94c93b24 Remove ** from operators list for now 2014-01-20 12:24:22 +01:00
José Valim 96ed1ff950 Merge pull request #2009 from xumingthepoet/master
minor typo in doc
2014-01-19 11:37:01 -08:00
xuming 9d4f15c1c9 minor typo in doc 2014-01-20 01:09:47 +08:00
José Valim 6bcb7adeb4 Clean up DictTest implementation
Before this commit, DictTest used too many helper functions
and  used assertions in the opposite order.
2014-01-19 15:25:35 +01:00
José Valim d311e10bea Change HashDict implementation to use tries with compound nodes
The new implementation is basically a tree of tuples (tries) but
they allow compond nodes (a node with a key-value pair and a
sub-tree). This removes the need for node-expansion and also simplifies
the design structure to not require multiple stages nor buckets.

The end result is a faster HashDict implementation (about 10% faster
on access, 30% on updates), which is still small for small dicts
but about 30% bigger for large dicts (in terms of memory) and with
a much simpler implementation.
2014-01-19 13:58:45 +01:00
José Valim adf7834529 Ensure ExUnit doesn't hang on linked failures
Thanks to @pminten for reporting this issue.
2014-01-18 19:41:19 +01:00
José Valim c837d8e1d3 Merge pull request #2004 from rcoppolo/deprecate-term-binary-in-kernel
Deprecates binary_to_term and term_to_binary in Kernel.
2014-01-18 00:56:18 -08:00
Ryan Coppolo ce61c60af5 Deprecates binary_to_term and term_to_binary in Kernel. 2014-01-17 16:08:15 -08:00
José Valim dc0dd43406 Simplify diverge/override mechanism in Mix.Deps 2014-01-17 17:47:13 +01:00
Eric Meadows-Jönsson 6c2744d373 Add index reflection to private records
Example:
defrecordp :user, [:name, age]
user(:age) #=> 2
2014-01-17 01:24:43 +01:00
José Valim f671e1204c Change precedence of <- to be the same as other generators 2014-01-16 20:08:55 +01:00
José Valim c0af2622af List iex -S mix at the bottom of mix help 2014-01-16 13:20:30 +01:00
José Valim 6b511cc844 Remove @hidden in mix tasks
Instead, we can use the presence or abscence of @shortdoc
to specify if the task should be shown or not.
2014-01-16 12:58:48 +01:00
José Valim 4ade0f98aa Remove deprecated code 2014-01-16 10:26:48 +01:00
José Valim 67a672e6ed Guarantee nullary funs/macros are allowed in guards, closes #1997 2014-01-16 10:00:39 +01:00
José Valim 54e8ea13f4 Ensure heredocs are allowed in calls, closes #1998 2014-01-16 09:49:13 +01:00
José Valim 3110be1ca5 Bump to 0.12.3-dev 2014-01-15 16:05:04 +01:00
320 changed files with 15457 additions and 12016 deletions
+1 -3
View File
@@ -6,6 +6,4 @@ notifications:
- jose.valim@plataformatec.com.br
- eric.meadows.jonsson@gmail.com
otp_release:
- R16B02
- R16B01
- R16B
- 17.0
+117 -3
View File
@@ -1,9 +1,123 @@
# v0.13.0 (2014-04-20)
* Enhancements
* [Base] Add `Base` module which does conversions to bases 16, 32, hex32, 64 and url64
* [Code] Add `Code.eval_file/2`
* [Collectable] Add the `Collectable` protocol that empowers `Enum.into/2` and `Stream.into/2` and the `:into` option in comprehensions
* [Collectable] Implement `Collectable` for lists, dicts, bitstrings, functions and provide both `File.Stream` and `IO.Stream`
* [EEx] Add `handle_body/1` callback to `EEx.Engine`
* [Enum] Add `Enum.group_by/2`, `Enum.into/2`, `Enum.into/3`, `Enum.traverse/2` and `Enum.sum/2`
* [ExUnit] Randomize cases and tests suite runs, allow seed configuration and the `--seed` flag via `mix test`
* [ExUnit] Support `--only` for filtering when running tests with `mix test`
* [ExUnit] Raise an error if another `capture_io` process already captured the device
* [ExUnit] Improve formatter to show source code and rely on lhs and rhs (instead of expected and actual)
* [IEx] Allow prompt configuration with the `:prompt` option
* [IEx] Use werl on Windows
* [Kernel] Support `ERL_PATH` in `bin/elixir`
* [Kernel] Support interpolation in keyword syntax
* [Map] Add a Map module and support 17.0 maps and structs
* [Mix] Add dependency option `:only` to specify the dependency environment. `mix deps.get` and `mix deps.update` works accross all environment unless `--only` is specified
* [Mix] Add `Mix.Shell.prompt/1`
* [Mix] Ensure the project is compiled in case Mix' CLI cannot find a task
* [Node] Add `Node.ping/1`
* [Process] Include `Process.send/3` and support the `--gen-debug` option
* [Regex] Regexes no longer need the "g" option when there is a need to use named captures
* [Stream] Add `Stream.into/2` and `Stream.into/3`
* [StringIO] Add a `StringIO` module that allows a String to be used as IO device
* [System] Add `System.delete_env/1` to remove a variable from the environment
* Bug fixes
* [CLI] Ensure `--app` is handled as an atom before processing
* [ExUnit] Ensure `ExUnit.Assertions` does not emit compiler warnings for `assert_receive`
* [Kernel] Ensure the same pid is not queued twice in the parallel compiler
* [Macro] `Macro.to_string/2` considers proper precedence when translating `!(foo > bar)` into a string
* [Mix] Automatically recompile on outdated Elixir version and show proper error messages
* [Mix] Ensure generated `.app` file includes core dependencies
* [Mix] Allow a dependency with no SCM to be overridden
* [Mix] Allow queries in `mix local.install` URL
* [OptionParser] Do not recognize undefined aliases as switches
* 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
# v0.12.5 (2014-03-09)
* Bug fixes
* [Kernel] Ensure `try` does not generate an after clause. Generating an after clause forbade clauses in the `else` part from being tail recursive. This should improve performance and memory consumption of `Stream` functions
* [Mix] Automatically recompile on outdated Elixir version and show proper error messages
* Deprecations
* [File] `File.stream_to!/3` is deprecated
* [GenFSM] `GenFSM` is deprecated
* [Kernel] `%` for sigils is deprecated in favor of `~`
* [Kernel] `is_range/1` and `is_regex/1` are deprecated in favor of `Range.range?/1` and `Regex.regex?/1`
* [Stream] `Stream.after/1` is deprecated
* [URI] `URI.decode_query/1` is deprecated in favor of `URI.decode_query/2` with explicit dict argument
* [URI] Passing lists as key or values in `URI.encode_query/1` is deprecated
* Backwards incompatible changes
* [Mix] Remove `MIX_GIT_FORCE_HTTPS` as Git itself already provides mechanisms for doing so
# v0.12.4 (2014-02-12)
* Enhancements
* [Mix] `mix deps.get` and `mix deps.update` no longer compile dependencies afterwards. Instead, they mark the dependencies which are going to be automatically compiled next time `deps.check` is invoked (which is done automatically by most mix tasks). This means users should have a better workflow when migrating in between environments
* Deprecations
* [Kernel] `//` for default arguments is deprecated in favor of `\\`
* [Kernel] Using `%` for sigils is deprecated in favor of `~`. This is a soft deprecation, no warnings will be emitted for it in this release
* [Kernel] Using `^` inside function clause heads is deprecated, please use a guard instead
* Backwards incompatible changes
* [ExUnit] `CaptureIO` returns an empty string instead of nil when there is no capture
* [Version] The `Version` module now only works with SemVer. The functions `Version.parse/1` and `Version.parse_requirement/1` now return `{:ok,res} | :error` for the cases you want to handle non SemVer cases manually. All other functions will trigger errors on non semantics versions
# v0.12.3 (2014-02-02)
* Enhancements
* [Kernel] Warnings now are explicitly tagged with "warning:" in messages
* [Kernel] Explicit functions inlined by the compiler, including operators. This means that `Kernel.+/2` will now expand to `:erlang.+/2` and so on
* [Mix] Do not fail if a Mix dependency relies on an outdated Elixir version
* [Process] Add `Process.send/2` and `Process.send_after/3`
* [Version] Add `Version.compare/2`
* Bug fixes
* [Atom] Inspect `:...` and `:foo@bar` without quoting
* [Keyword] The list `[1, 2, three: :four]` now correctly expands to `[1, 2, {:three, :four}]`
* [Kernel] Ensure undefined `@attributes` shows proper stacktrace in warnings
* [Kernel] Guarantee nullary funs/macros are allowed in guards
* [Process] Ensure monitoring functions are inlined by the compiler
* Deprecations
* [IEx] The helper `m/0` has been deprecated. The goal is to group all runtime statistic related helpers into a single module
* [Kernel] `binary_to_term/1`, `binary_to_term/2`, `term_to_binary/1` and `term_to_binary/2` are deprecated in favor of their counterparts in the `:erlang` module
* [Kernel] `//` for default arguments is deprecated in favor of `\\`. This is a soft deprecation, no warnings will be emitted for it in this release
* [Kernel] Deprecated `@behavior` in favor of `@behaviour`
* [Record] `to_keywords`, `getter` and `list getter` functionalities in `defrecordp` are deprecated
* [Record] `Record.import/2` is deprecated
* Backwards incompatible changes
* [Dict] Implementations of `equal?/2` and `merge/2` in `HashDict` and `ListDict` are no longer polymorphic. To get polymorphism, use the functions in `Dict` instead
* [File] `File.cp/3` and `File.cp_r/3` no longer carry Unix semantics where the function behaves differently if the destination is an existing previous directory or not. It now always copies source to destination, doing it recursively in the latter
* [IEx] IEx now loads the `.iex.exs` file instead of `.iex`
* [Kernel] Remove `**` from the list of allowed operators
* [Kernel] Limit sigils delimiters to one of the following: `<>`, `{}`, `[]`, `()`, `||`, `//`, `"` and `'`
* [Range] `Range` is no longer a record, instead use `first .. last` if you need pattern matching
* [Set] Implementations of `difference/2`, `disjoint?/2`, `equal?/2`, `intersection/2`, `subset?/2` and `union/2` in `HashSet` are no longer polymorphic. To get polymorphism, use the functions in `Set` instead
# v0.12.2 (2014-01-15)
* Enhancements
* [EEx] Allow `EEx.AssignsEngine` to accept any Dict
* [Enum] Add `Enum.flat_map_reduce/3`
* [ExUnit] Support @moduletag in ExUnit cases
* [ExUnit] Support `@moduletag` in ExUnit cases
* [Kernel] Improve stacktraces to be relative to the compilation path and include the related application
* [Stream] Add `Stream.transform/3`
@@ -18,7 +132,7 @@
* Deprecations
* [Enum] Deprecate `Enum.first/1` in favor of `Enum.at/2` and `List.first/1`
* [Kernel] Deprecate continuable heredocs. In previous versions, Elixir would continue parsing on the same line the heredoc started, this behavior has been deprecated
* [Kernel] Deprecate continuable heredocs. In previous versions, Elixir would continue parsing on the same line the heredoc started, this behaviour has been deprecated
* [Kernel] `is_alive/0` is deprecated in favor of `Node.alive?`
* [Kernel] `Kernel.inspect/2` with `Inspect.Opts[]` is deprecated in favor of `Inspect.Algebra.to_doc/2`
* [Kernel] `Kernel.inspect/2` with `:raw` option is deprecated, use `:records` option instead
@@ -718,7 +832,7 @@
* Backwards incompatible changes
* [Kernel] Compile files now follow `Elixir-ModuleName` convention to solve issues with Erlang embedded mode. This removes the `__MAIN__` pseudo-variable as modules are now located inside `Elixir` namespace
* [Kernel] `__using__` callback triggered by `use` now receives just one argument. Caller information can be accessed via macros using `__CALLER__`
* [Kernel] Comprehensions syntax changed to be more compatible with Erlang behavior
* [Kernel] Comprehensions syntax changed to be more compatible with Erlang behaviour
* [Kernel] loop and recur are removed in favor of recursion with named functions
* [Module] Removed data functions in favor of unifying the attributes API
+4 -8
View File
@@ -1,5 +1,5 @@
REBAR := rebar
ELIXIRC := bin/elixirc --verbose --ignore-module-conflict $(ELIXIRC_OPTS)
ELIXIRC := bin/elixirc --verbose --ignore-module-conflict
ERLC := erlc -I lib/elixir/include
ERL := erl -I lib/elixir/include -noshell -pa lib/elixir/ebin
VERSION := $(strip $(shell cat VERSION))
@@ -11,7 +11,7 @@ INSTALL_DIR = $(INSTALL) -m755 -d
INSTALL_DATA = $(INSTALL) -m644
INSTALL_PROGRAM = $(INSTALL) -m755
.PHONY: install compile erlang elixir dialyze test clean docs release_docs release_zip release_erl
.PHONY: install compile erlang elixir dialyze test clean docs release_docs release_zip
.NOTPARALLEL: compile
#==> Templates
@@ -76,7 +76,7 @@ unicode: $(UNICODE)
$(UNICODE): lib/elixir/unicode/*
@ echo "==> unicode (compile)";
@ echo "This step can take up to a minute to compile in order to embed the Unicode database"
$(Q) cd lib/elixir && ../../$(ELIXIRC) unicode/graphemes.ex unicode/unicode.ex -o ebin;
$(Q) cd lib/elixir && ../../$(ELIXIRC) unicode/unicode.ex -o ebin;
$(eval $(call APP_TEMPLATE,ex_unit,ExUnit))
$(eval $(call APP_TEMPLATE,eex,EEx))
@@ -127,17 +127,13 @@ docs: compile ../ex_doc/bin/ex_doc
release_zip: compile
rm -rf v$(VERSION).zip
zip -9 -r v$(VERSION).zip bin CHANGELOG.md LEGAL lib/*/ebin LICENSE README.md rel VERSION
zip -9 -r v$(VERSION).zip bin CHANGELOG.md LEGAL lib/*/ebin LICENSE README.md VERSION
release_docs: docs
cd ../docs
rm -rf ../docs/master
mv docs ../docs/master
release_erl: compile
$(Q) rm -rf rel/elixir
$(Q) cd rel && ../rebar generate
#==> Tests tasks
test: test_erlang test_elixir
+2 -2
View File
@@ -16,9 +16,9 @@ If Elixir fails to build (specifically when pulling in a new version via git), b
If tests pass, you are ready to move on to the [Getting Started guide][1] or to try Interactive Elixir by running: `bin/iex` in your terminal.
However, if tests fail, it is likely you have an outdated Erlang version (Elixir requires Erlang R16B or later). You can check your Erlang version by calling `erl` in the command line. You will see some information as follows:
However, if tests fail, it is likely you have an outdated Erlang version (Elixir requires Erlang 17.0 or later). You can check your Erlang version by calling `erl` in the command line. You will see some information as follows:
Erlang R16B (erts-5.10.1) [source] [64-bit] [smp:2:2] [rq:2] [async-threads:0] [hipe] [kernel-poll:false]
Erlang/OTP 17 [RELEASE CANDIDATE 1] [erts-6.0] [source] [64-bit] [smp:4:4] [async-threads:10] [hipe] [kernel-poll:false]
If you have the correct version and tests still fail, feel free to [open an issue][2].
+7 -9
View File
@@ -4,25 +4,23 @@ This document simply outlines the release process:
1) Remove `-dev` extension from VERSION
2) Run `make clean test` to ensure all tests pass from scratch and the CI is green
2) Ensure CHANGELOG is updated and timestamp
3) Ensure CHANGELOG is updated and tag release version with timestamp in it
3) Commit changes above with title "Release vVERSION" and generate new tag
4) Commit changes above with title "Release vVERSION"
4) Run `make clean test` to ensure all tests pass from scratch and the CI is green
5) Push master and create tag vVERSION from master branch
5) Push master and tags
6) Release new docs with `make release_docs`, move docs to `docs/stable`
7) Release new zip with `make release_zip`, push new zip to GitHub Releases
8) Push package to expm with `expm publish package.exs`
8) Merge master into stable branch and push it
9) Merge master into stable branch and push it
9) After release, bump versions, add `-dev` back and commit
10) After release, bump versions, add `-dev` back and commit
11) `make release_docs` once again and push `elixir-lang.github.com`
10) `make release_docs` once again and push it to `elixir-lang.github.com`
## Places where version is mentioned
+1 -1
View File
@@ -1 +1 @@
0.12.2
0.13.0
+21 -7
View File
@@ -17,6 +17,7 @@ if [ $# -eq 0 ] || [ "$1" = "--help" ] || [ "$1" = "-h" ]; then
--hidden Makes a hidden node
--detached Starts the Erlang VM detached from console
--no-halt Does not halt the Erlang VM after execution
--gen-debug Turns on default debugging for all GenServers
** Options marked with (*) can be given more than once
** Options given after the .exs file or -- are passed down to the executed code
@@ -34,6 +35,7 @@ readlink_f () {
fi
}
MODE="elixir"
ERL=""
I=1
@@ -41,7 +43,13 @@ while [ $I -le $# ]; do
S=1
eval "PEEK=\${$I}"
case "$PEEK" in
-v|--compile|--no-halt|+iex|+compile)
+iex)
MODE="iex"
;;
+elixirc)
MODE="elixirc"
;;
-v|--compile|--no-halt)
;;
-e|-r|-pr|-pa|-pz|--remsh|--app)
S=2
@@ -64,6 +72,9 @@ while [ $I -le $# ]; do
eval "VAL=\${$I}"
ERL="$ERL "$VAL""
;;
--gen-debug)
ERL="$ERL -generic_debug"
;;
*)
break
;;
@@ -73,11 +84,14 @@ done
SELF=$(readlink_f "$0")
SCRIPT_PATH=$(dirname "$SELF")
if [ "$ELIXIR_NO_CLI" != "1" ]; then ERL="$ERL -s elixir start_cli"; fi
if [ "$MODE" != "iex" ]; then ERL="$ERL -s elixir start_cli"; fi
if [ -f "$SCRIPT_PATH/../releases/RELEASES" ] && [ -f "$SCRIPT_PATH/erl" ]
then
exec "$SCRIPT_PATH"/erl -pa "$SCRIPT_PATH"/../lib/*/ebin -noshell $ELIXIR_ERL_OPTS $ERL -boot start -extra "$@"
else
exec erl -pa "$SCRIPT_PATH"/../lib/*/ebin -noshell $ELIXIR_ERL_OPTS $ERL -extra "$@"
if [ -z "$ERL_PATH" ]; then
if [ -f "$SCRIPT_PATH/../releases/RELEASES" ] && [ -f "$SCRIPT_PATH/erl" ]; then
ERL_PATH="$SCRIPT_PATH"/erl
else
ERL_PATH=erl
fi
fi
exec "$ERL_PATH" -pa "$SCRIPT_PATH"/../lib/*/ebin -noshell $ELIXIR_ERL_OPTS $ERL -extra "$@"
+12 -2
View File
@@ -24,6 +24,7 @@ echo --cookie cookie Sets a cookie for this distributed node
echo --hidden Makes a hidden node
echo --detached Starts the Erlang VM detached from console
echo --no-halt Does not halt the Erlang VM after execution
echo --gen-debug Turns on default debugging for all GenServers
echo.
echo ** Options marked with (*) can be given more than once
echo ** Options given after the .exs file or -- are passed down to the executed code
@@ -44,6 +45,9 @@ set originPath=%~dp0
rem Optional parameters before the "-extra" parameter
set beforeExtra=
rem Flag which determines whether or not to use werl vs erl
set useWerl=0
rem Recursive loop called for each parameter that parses the cmd line parameters
:startloop
set par="%1"
@@ -56,6 +60,8 @@ if "%par%"=="""" (
rem if no parameters defined - special case for parameter that is already quoted
goto :expand_erl_libs
)
rem ******* EXECUTION OPTIONS **********************
IF "%par%"==""+iex"" (Set useWerl=1)
rem ******* ERLANG PARAMETERS **********************
IF NOT "%par%"=="%par:--detached=%" (Set parsErlang=%parsErlang% -detached)
IF NOT "%par%"=="%par:--hidden=%" (Set parsErlang=%parsErlang% -hidden)
@@ -63,6 +69,7 @@ IF NOT "%par%"=="%par:--cookie=%" (Set parsErlang=%parsErlang% -setcookie %1 &
IF NOT "%par%"=="%par:--sname=%" (Set parsErlang=%parsErlang% -sname %1 && shift)
IF NOT "%par%"=="%par:--name=%" (Set parsErlang=%parsErlang% -name %1 && shift)
IF NOT "%par%"=="%par:--erl=%" (Set beforeExtra=%beforeExtra% %~1 && shift)
IF NOT "%par%"=="%par:--gen-debug=%" (Set parsErlang=%parsErlang% -generic_debug)
rem ******* elixir parameters **********************
rem Note: we don't have to do anything with options that don't take an argument
IF NOT "%par%"=="%par:-e=%" (shift)
@@ -84,5 +91,8 @@ for /d %%d in ("%originPath%..\lib\*.") do (
)
SETLOCAL disabledelayedexpansion
:run
erl %ext_libs% -noshell %ELIXIR_ERL_OPTS% %parsErlang% -s elixir start_cli %beforeExtra% -extra %*
IF %useWerl% EQU 1 (
werl %ext_libs% -noshell %ELIXIR_ERL_OPTS% %parsErlang% -s elixir start_cli %beforeExtra% -extra %*
) ELSE (
erl %ext_libs% -noshell %ELIXIR_ERL_OPTS% %parsErlang% -s elixir start_cli %beforeExtra% -extra %*
)
+1 -1
View File
@@ -26,4 +26,4 @@ readlink_f () {
SELF=$(readlink_f "$0")
SCRIPT_PATH=$(dirname "$SELF")
exec "$SCRIPT_PATH"/elixir +compile "$@"
exec "$SCRIPT_PATH"/elixir +elixirc "$@"
+1 -1
View File
@@ -20,4 +20,4 @@ echo ** Options marked with (*) can be given more than once
echo ** Options given after -- are passed down to the executed code
echo ** Options can be passed to the erlang runtime using ELIXIR_ERL_OPTS" >&2
:run
call "%~dp0\elixir.bat" +compile %*
call "%~dp0\elixir.bat" +elixirc %*
+4 -3
View File
@@ -16,13 +16,14 @@ if [ $# -gt 0 ] && ([ "$1" = "--help" ] || [ "$1" = "-h" ]); then
--cookie \"cookie\" Sets a cookie for this distributed node
--hidden Makes a hidden node
--detached Starts the Erlang VM detached from console
--gen-debug Turns on default debugging for all GenServers
--remsh \"name\" Connects to a node using a remote shell
--dot-iex \"path\" Overrides default .iex file and uses path instead;
--dot-iex \"path\" Overrides default .iex.exs file and uses path instead;
path can be empty, then no file will be loaded
** Options marked with (*) can be given more than once
** Options given after the .exs file or -- are passed down to the executed code
** Options can be passed to the erlang runtime using ELIXIR_ERL_OPTS or --erl" >&2
** Options can be passed to the VM using ELIXIR_ERL_OPTS or --erl" >&2
exit 1
fi
@@ -38,4 +39,4 @@ readlink_f () {
SELF=$(readlink_f "$0")
SCRIPT_PATH=$(dirname "$SELF")
ELIXIR_NO_CLI=1 exec "$SCRIPT_PATH"/elixir --no-halt --erl "-user Elixir.IEx.CLI" +iex "$@"
exec "$SCRIPT_PATH"/elixir --no-halt --erl "-user Elixir.IEx.CLI" +iex "$@"
+1 -1
View File
@@ -1,2 +1,2 @@
@echo off
call "%~dp0\elixir.bat" +iex --no-halt -e "IEx.start" %*
call "%~dp0\elixir.bat" +iex --erl "-user Elixir.IEx.CLI" --no-halt %
+22 -23
View File
@@ -1,12 +1,12 @@
defexception EEx.SyntaxError, message: nil
defmodule EEx do
@moduledoc %S"""
@moduledoc ~S"""
EEx stands for Embedded Elixir. It allows you to embed
Elixir code inside a string in a robust way:
EEx.eval_string "foo <%= bar %>", [bar: "baz"]
#=> "foo baz"
iex> EEx.eval_string "foo <%= bar %>", [bar: "baz"]
"foo baz"
## API
@@ -31,7 +31,7 @@ defmodule EEx do
All functions in this module accepts EEx-related options.
They are:
* `:line` - the line to be used as the template start.
Defaults to 1;
* `:file` - the file to be used in the template.
@@ -79,12 +79,12 @@ defmodule EEx do
An example is the `@` macro which allows easy data access
in a template:
EEx.eval_string "<%= @foo %>", assigns: [foo: 1]
#=> 1
iex> EEx.eval_string "<%= @foo %>", assigns: [foo: 1]
"1"
In other words, <%= @foo %> is simply translated to:
<%= Keyword.get assigns, :foo %>
<%= Dict.get assigns, :foo %>
The assigns extension is useful when the number of variables
required by the template is not specified at compilation time.
@@ -97,15 +97,15 @@ defmodule EEx do
## Examples
defmodule Sample do
require EEx
EEx.function_from_string :def, :sample, "<%= a + b %>", [:a, :b]
end
Sample.sample(1, 2) #=> "3"
iex> defmodule Sample do
...> require EEx
...> EEx.function_from_string :def, :sample, "<%= a + b %>", [:a, :b]
...> end
iex> Sample.sample(1, 2)
"3"
"""
defmacro function_from_string(kind, name, source, args // [], options // []) do
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
@@ -141,7 +141,7 @@ defmodule EEx do
Sample.sample(1, 2) #=> "3"
"""
defmacro function_from_file(kind, name, file, args // [], options // []) 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
@@ -159,7 +159,7 @@ defmodule EEx do
Get a string `source` and generate a quoted expression
that can be evaluated by Elixir or compiled to a function.
"""
def compile_string(source, options // []) do
def compile_string(source, options \\ []) do
EEx.Compiler.compile(source, options)
end
@@ -167,7 +167,7 @@ defmodule EEx do
Get a `filename` and generate a quoted expression
that can be evaluated by Elixir or compiled to a function.
"""
def compile_file(filename, options // []) do
def compile_file(filename, options \\ []) do
options = Keyword.merge options, [file: filename, line: 1]
compile_string(File.read!(filename), options)
end
@@ -177,11 +177,11 @@ defmodule EEx do
## Examples
EEx.eval_string "foo <%= bar %>", [bar: "baz"]
#=> "foo baz"
iex> EEx.eval_string "foo <%= bar %>", [bar: "baz"]
"foo baz"
"""
def eval_string(source, bindings // [], options // []) do
def eval_string(source, bindings \\ [], options \\ []) do
compiled = compile_string(source, options)
do_eval(compiled, bindings, options)
end
@@ -195,11 +195,10 @@ defmodule EEx do
foo <%= bar %>
# iex
EEx.eval_file "sample.ex", [bar: "baz"]
#=> "foo baz"
EEx.eval_file "sample.ex", [bar: "baz"] #=> "foo baz"
"""
def eval_file(filename, bindings // [], options // []) do
def eval_file(filename, bindings \\ [], options \\ []) do
options = Keyword.put options, :file, filename
compiled = compile_file(filename, options)
do_eval(compiled, bindings, options)
+21 -21
View File
@@ -17,8 +17,8 @@ defmodule EEx.Compiler do
# Generates the buffers by handling each expression from the tokenizer
defp generate_buffer([{ :text, _line, chars }|t], buffer, scope, state) do
buffer = state.engine.handle_text(buffer, chars)
defp generate_buffer([{ :text, chars }|t], buffer, scope, state) do
buffer = state.engine.handle_text(buffer, String.from_char_list!(chars))
generate_buffer(t, buffer, scope, state)
end
@@ -28,8 +28,9 @@ defmodule EEx.Compiler do
generate_buffer(t, buffer, scope, state)
end
defp generate_buffer([{ :start_expr, line, mark, chars }|t], buffer, scope, state) do
{ contents, t } = generate_buffer(t, "", [chars|scope], state.dict([]).line(line).start_line(line))
defp generate_buffer([{ :start_expr, start_line, mark, chars }|t], buffer, scope, state) do
{ contents, line, t } = look_ahead_text(t, start_line, chars)
{ contents, t } = generate_buffer(t, "", [contents|scope], state.dict([]).line(line).start_line(start_line))
buffer = state.engine.handle_expr(buffer, mark, contents)
generate_buffer(t, buffer, scope, state)
end
@@ -50,8 +51,8 @@ defmodule EEx.Compiler do
raise EEx.SyntaxError, message: "unexpected token: #{inspect chars} at line #{inspect line}"
end
defp generate_buffer([], buffer, [], _state) do
buffer
defp generate_buffer([], buffer, [], state) do
state.engine.handle_body(buffer)
end
defp generate_buffer([], _buffer, _scope, _state) do
@@ -62,28 +63,27 @@ 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)
placeholder = '__EEX__(' ++ integer_to_list(key) ++ ');'
{ current ++ placeholder ++ new_lines ++ chars, state.update_dict(&[{key, buffer}|&1]) }
end
if state.dict == [] and empty?(buffer) do
{ current ++ new_lines ++ chars, state }
# Look text ahead on expressions
defp look_ahead_text([{ :text, text }, { :middle_expr, line, _, chars }|t]=list, start, contents) do
if only_spaces?(text) do
{ contents ++ text ++ chars, line, t }
else
key = length(state.dict)
placeholder = '__EEX__(' ++ integer_to_list(key) ++ ');'
{ current ++ placeholder ++ new_lines ++ chars, state.update_dict(&[{key, buffer}|&1]) }
{ contents, start, list }
end
end
# Check if the syntax node represents an empty string
defp empty?(bin) when is_binary(bin) do
bc(<<c>> inbits bin, not c in [?\s, ?\t, ?\r, ?\n], do: <<c>>) == ""
defp look_ahead_text(t, start, contents) do
{ contents, start, t }
end
defp empty?({ :<>, _, [left, right] }) do
empty?(left) and empty?(right)
end
defp empty?(_) do
false
defp only_spaces?(chars) do
Enum.all?(chars, &(&1 in [?\s, ?\t, ?\r, ?\n]))
end
# Changes placeholder to real expression
+19 -6
View File
@@ -1,7 +1,11 @@
defmodule EEx.Engine do
@moduledoc %S"""
@moduledoc ~S"""
This is the basic EEx engine that ships with Elixir.
An engine needs to implement two functions:
An engine needs to implement three functions:
* `handle_body(quoted)` - receives the final built quoted
expression, should do final post-processing and return a
quoted expression;
* `handle_text(buffer, text)` - it receives the buffer,
the text and must return a new quoted expression;
@@ -10,11 +14,11 @@ defmodule EEx.Engine do
the marker, the expr and must return a new quoted expression;
The marker is what follows exactly after `<%`. For example,
`<% foo %>` has an empty marker, but `<%= foo %>` has `'='`
`<% foo %>` has an empty marker, but `<%= foo %>` has `"="`
as marker. The allowed markers so far are:
* `''`
* `'='`
* `""`
* `"="`
Read `handle_expr/3` below for more information about the markers
implemented by default by this engine.
@@ -22,9 +26,18 @@ defmodule EEx.Engine do
use Behaviour
defcallback handle_body(Macro.t) :: Macro.t
defcallback handle_text(Macro.t, binary) :: Macro.t
defcallback handle_expr(Macro.t, binary, Macro.t) :: Macro.t
@doc """
The default implementation implementation simply returns the
given expression.
"""
def handle_body(quoted) do
quoted
end
@doc """
The default implementation simply concatenates text to the buffer.
"""
@@ -54,4 +67,4 @@ defmodule EEx.Engine do
tmp
end
end
end
end
+10 -6
View File
@@ -2,7 +2,7 @@ defmodule EEx.TransformerEngine do
@moduledoc """
An abstract engine that is meant to be used and
built upon in other modules. This engine implements
the `EEx.Engine` behavior and provides a `transform`
the `EEx.Engine` behaviour and provides a `transform`
overridable directive that allows a developer to
customize the expression returned by the engine.
@@ -13,7 +13,11 @@ defmodule EEx.TransformerEngine do
@doc false
defmacro __using__(_) do
quote do
@behavior EEx.Engine
@behaviour EEx.Engine
def handle_body(body) do
EEx.Engine.handle_body(body)
end
def handle_text(buffer, text) do
EEx.Engine.handle_text(buffer, text)
@@ -32,14 +36,14 @@ defmodule EEx.TransformerEngine do
end
defp transform(list) when is_list(list) do
lc i inlist list, do: transform(i)
for i <- list, do: transform(i)
end
defp transform(other) do
other
end
defoverridable [transform: 1, handle_expr: 3, handle_text: 2]
defoverridable [transform: 1, handle_body: 1, handle_expr: 3, handle_text: 2]
end
end
end
@@ -59,8 +63,8 @@ defmodule EEx.AssignsEngine do
use EEx.AssignsEngine
end
EEx.eval_string("<%= @foo %>", assigns: [foo: 1])
#=> 1
iex> EEx.eval_string("<%= @foo %>", assigns: [foo: 1])
"1"
In the example above, we can access the value `foo` under
the binding `assigns` using `@foo`. This is useful when
+24 -22
View File
@@ -5,9 +5,10 @@ defmodule EEx.Tokenizer do
Tokenizes the given char list or binary.
It returns 4 different types of tokens as result:
* { :text, line, contents }
* { :text, contents }
* { :expr, line, marker, contents }
* { :start_expr, line, marker, contents }
* { :middle_expr, line, marker, contents }
* { :end_expr, line, marker, contents }
"""
@@ -16,39 +17,39 @@ defmodule EEx.Tokenizer do
end
def tokenize(list, line) do
Enum.reverse(tokenize(list, line, line, [], []))
Enum.reverse(tokenize(list, line, [], []))
end
defp tokenize('<%%' ++ t, current_line, line, buffer, acc) do
defp tokenize('<%%' ++ t, line, buffer, acc) do
{ buffer, new_line, rest } = tokenize_expr t, line, [?%, ?<|buffer]
tokenize rest, current_line, new_line, [?>, ?%|buffer], acc
tokenize rest, new_line, [?>, ?%|buffer], acc
end
defp tokenize('<%#' ++ t, current_line, line, buffer, acc) do
defp tokenize('<%#' ++ t, line, buffer, acc) do
{ _, new_line, rest } = tokenize_expr t, line, []
tokenize rest, current_line, new_line, buffer, acc
tokenize rest, new_line, buffer, acc
end
defp tokenize('<%' ++ t, current_line, line, buffer, acc) do
defp tokenize('<%' ++ t, line, buffer, acc) do
{ marker, t } = retrieve_marker(t)
{ expr, new_line, rest } = tokenize_expr t, line, []
token = token_name(expr)
acc = tokenize_text(current_line, buffer, acc)
acc = tokenize_text(buffer, acc)
final = { token, line, marker, Enum.reverse(expr) }
tokenize rest, new_line, new_line, [], [final | acc]
tokenize rest, new_line, [], [final | acc]
end
defp tokenize('\n' ++ t, current_line, line, buffer, acc) do
tokenize t, current_line, line + 1, [?\n|buffer], acc
defp tokenize('\n' ++ t, line, buffer, acc) do
tokenize t, line + 1, [?\n|buffer], acc
end
defp tokenize([h|t], current_line, line, buffer, acc) do
tokenize t, current_line, line, [h|buffer], acc
defp tokenize([h|t], line, buffer, acc) do
tokenize t, line, [h|buffer], acc
end
defp tokenize([], current_line, _line, buffer, acc) do
tokenize_text(current_line, buffer, acc)
defp tokenize([], _line, buffer, acc) do
tokenize_text(buffer, acc)
end
# Retrieve marker for <%
@@ -140,20 +141,21 @@ defmodule EEx.Tokenizer do
end
defp check_spaces(string, token) do
if only_spaces?(string), do: token, else: :expr
if Enum.all?(string, &(&1 in [?\s, ?\t])) do
token
else
:expr
end
end
defp only_spaces?([h|t]) when h in [?\s, ?\t], do: only_spaces?(t)
defp only_spaces?(other), do: other == []
# Tokenize the buffered text by appending
# it to the given accumulator.
defp tokenize_text(_line, [], acc) do
defp tokenize_text([], acc) do
acc
end
defp tokenize_text(line, buffer, acc) do
[{ :text, line, String.from_char_list!(Enum.reverse(buffer)) } | acc]
defp tokenize_text(buffer, acc) do
[{ :text, Enum.reverse(buffer) } | acc]
end
end
+2 -2
View File
@@ -2,6 +2,6 @@ defmodule EEx.Mixfile do
use Mix.Project
def project do
[app: :eex, version: System.version]
[app: :eex, version: System.version, build_per_environment: false]
end
end
end
+5 -6
View File
@@ -7,20 +7,19 @@ defmodule EEx.SmartEngineTest do
assert_eval "foo bar", "foo bar"
end
test "evaluates with assigns as a ListDict" do
test "evaluates with assigns as keywords" do
assert_eval "1", "<%= @foo %>", assigns: [foo: 1]
end
test "evaluates with assigns as a HashDict" do
assigns = HashDict.new [foo: 1]
assert_eval "1", "<%= @foo %>", assigns: assigns
test "evaluates with assigns as a map" do
assert_eval "1", "<%= @foo %>", assigns: %{foo: 1}
end
test "evaluates with loops" do
assert_eval "1\n2\n3\n", "<%= lc x inlist [1, 2, 3] do %><%= x %>\n<% end %>"
assert_eval "1\n2\n3\n", "<%= for x <- [1, 2, 3] do %><%= x %>\n<% end %>"
end
defp assert_eval(expected, actual, binding // []) do
defp assert_eval(expected, actual, binding \\ []) do
result = EEx.eval_string(actual, binding, file: __ENV__.file)
assert result == expected
end
+20 -20
View File
@@ -5,23 +5,23 @@ defmodule EEx.TokenizerTest do
require EEx.Tokenizer, as: T
test "simple chars lists" do
assert T.tokenize('foo', 1) == [ { :text, 1, "foo" } ]
assert T.tokenize('foo', 1) == [ { :text, 'foo' } ]
end
test "simple strings" do
assert T.tokenize("foo", 1) == [ { :text, 1, "foo" } ]
assert T.tokenize("foo", 1) == [ { :text, 'foo' } ]
end
test "strings with embedded code" do
assert T.tokenize('foo <% bar %>', 1) == [ { :text, 1, "foo " }, { :expr, 1, "", ' bar ' } ]
assert T.tokenize('foo <% bar %>', 1) == [ { :text, 'foo ' }, { :expr, 1, "", ' bar ' } ]
end
test "strings with embedded equals code" do
assert T.tokenize('foo <%= bar %>', 1) == [ { :text, 1, "foo " }, { :expr, 1, "=", ' bar ' } ]
assert T.tokenize('foo <%= bar %>', 1) == [ { :text, 'foo ' }, { :expr, 1, "=", ' bar ' } ]
end
test "strings with more than one line" do
assert T.tokenize('foo\n<%= bar %>', 1) == [ { :text, 1, "foo\n" }, { :expr, 2, "=", ' bar ' } ]
assert T.tokenize('foo\n<%= bar %>', 1) == [ { :text, 'foo\n' }, { :expr, 2, "=", ' bar ' } ]
end
test "strings with more than one line and expression with more than one line" do
@@ -33,66 +33,66 @@ baz %>
'''
assert T.tokenize(string, 1) == [
{:text, 1, "foo "},
{:text, 'foo '},
{:expr, 1, "=", ' bar\n\nbaz '},
{:text, 3, "\n"},
{:text, '\n'},
{:expr, 4, "", ' foo '},
{:text, 4, "\n"}
{:text, '\n'}
]
end
test "quotation" do
assert T.tokenize('foo <%% true %>', 1) == [
{ :text, 1, "foo <% true %>" }
{ :text, 'foo <% true %>' }
]
end
test "quotation with do/end" do
assert T.tokenize('foo <%% true do %>bar<%% end %>', 1) == [
{ :text, 1, "foo <% true do %>bar<% end %>" }
{ :text, 'foo <% true do %>bar<% end %>' }
]
end
test "comments" do
assert T.tokenize('foo <%# true %>', 1) == [
{ :text, 1, "foo " }
{ :text, 'foo ' }
]
end
test "comments with do/end" do
assert T.tokenize('foo <%# true do %>bar<%# end %>', 1) == [
{ :text, 1, "foo bar" }
{ :text, 'foo bar' }
]
end
test "strings with embedded do end" do
assert T.tokenize('foo <% if true do %>bar<% end %>', 1) == [
{ :text, 1, "foo " },
{ :text, 'foo ' },
{ :start_expr, 1, "", ' if true do ' },
{ :text, 1, "bar" },
{ :text, 'bar' },
{ :end_expr, 1, "", ' end ' }
]
end
test "strings with embedded -> end" do
assert T.tokenize('foo <% cond do %><% false -> %>bar<% true -> %>baz<% end %>', 1) == [
{ :text, 1, "foo " },
{ :text, 'foo ' },
{ :start_expr, 1, "", ' cond do ' },
{ :middle_expr, 1, "", ' false -> ' },
{ :text, 1, "bar" },
{ :text, 'bar' },
{ :middle_expr, 1, "", ' true -> ' },
{ :text, 1, "baz" },
{ :text, 'baz' },
{ :end_expr, 1, "", ' end ' }
]
end
test "strings with embedded keywords blocks" do
assert T.tokenize('foo <% if true do %>bar<% else %>baz<% end %>', 1) == [
{ :text, 1, "foo " },
{ :text, 'foo ' },
{ :start_expr, 1, "", ' if true do ' },
{ :text, 1, "bar" },
{ :text, 'bar' },
{ :middle_expr, 1, "", ' else ' },
{ :text, 1, "baz" },
{ :text, 'baz' },
{ :end_expr, 1, "", ' end ' }
]
end
+24 -1
View File
@@ -29,7 +29,7 @@ defmodule EExText.Compiled do
try do
:erlang.error "failed"
catch
:error, _, stack -> stack
:error, _ -> System.stacktrace
end
end
end
@@ -45,6 +45,9 @@ end
defmodule EExTest do
use ExUnit.Case, async: true
doctest EEx
doctest EEx.AssignsEngine
test "evaluates simple string" do
assert_eval "foo bar", "foo bar"
end
@@ -344,6 +347,26 @@ foo
}
end
defmodule TestEngine do
@behaviour EEx.Engine
def handle_body(body) do
{ :wrapped, body }
end
def handle_text(buffer, text) do
EEx.Engine.handle_text(buffer, text)
end
def handle_expr(buffer, mark, expr) do
EEx.Engine.handle_expr(buffer, mark, expr)
end
end
test "calls handle_body" do
assert { :wrapped, "foo" } = EEx.eval_string("foo", [], engine: TestEngine)
end
defp assert_eval(expected, actual) do
result = EEx.eval_string(actual, [], file: __ENV__.file, engine: EEx.Engine)
assert result == expected
+4 -2
View File
@@ -10,10 +10,11 @@
-record(elixir_scope, {
context=nil, %% can be match, guards or nil
extra=nil, %% extra information about the context, like fn_match and do_match
extra=nil, %% extra information about the context, like fn_match and map_key
noname=false, %% when true, don't add new names (used by try)
super=false, %% when true, it means super was invoked
caller=false, %% when true, it means caller was invoked
return=true, %% when true, the return value is used
module=nil, %% the current module
function=nil, %% the current function
vars=[], %% a dict of defined variables and their alias
@@ -80,7 +81,8 @@
-define(is_atom(S), ?is_identifier(S) orelse (S == $@)).
-define(is_identifier(S), ?is_digit(S) orelse ?is_upcase(S) orelse ?is_downcase(S) orelse (S == $_)).
-define(is_terminator(S), (S == $?) orelse (S == $!) orelse (S == $:)).
-define(is_sigil(S), (S == $/) orelse (S == $<) orelse (S == $") orelse (S == $') orelse
(S == $[) orelse (S == $() orelse (S == ${) orelse (S == $|)).
%% Quotes
-define(is_quote(S), S == $" orelse S == $').
+25 -20
View File
@@ -7,6 +7,22 @@ defprotocol Access do
is translated to `access foo, bar` which, by default,
invokes the `Access.access` protocol.
This protocol is implemented by default for Lists, Maps
and dictionary like types:
iex> keywords = [a: 1, b: 2]
iex> keywords[:a]
1
iex> map = %{ a: 1, b: 2 }
iex> map[:a]
1
iex> star_ratings = %{ 1.0 => "★", 1.5 => "★☆", 2.0 => "★★" }
iex> star_ratings[1.5]
"★☆"
The key access must be implemented using the `===` operator.
This protocol is limited and is implemented only for the
following built-in types: keywords, records and functions.
"""
@@ -18,33 +34,22 @@ defprotocol Access do
end
defimpl Access, for: List do
@doc """
Access the given key in a tuple list.
The key is found via the `===` operator.
## Examples
iex> keywords = [a: 1, b: 2]
...> keywords[:a]
1
iex> star_ratings = [{1.0, "★"}, {1.5, "★☆"}, {2.0, "★★"}]
...> star_ratings[1.5]
"★☆"
"""
def access(dict, key)
def access([{ key, value }|_], key), do: value
def access([{ _, _ }|t], key), do: access(t, key)
def access([], _key), do: nil
end
defimpl Access, for: Map do
def access(map, key) do
case :maps.find(key, map) do
{ :ok, value } -> value
:error -> nil
end
end
end
defimpl Access, for: Atom do
@doc """
The access protocol can only be accessed by atoms
at compilation time. If we reach this, we should raise
an exception.
"""
def access(nil, _) do
nil
end
+1 -42
View File
@@ -48,51 +48,10 @@ defmodule Application.Behaviour do
"""
# Starts the given application and all of its dependencies that
# have not been started yet recursively.
#
# ## Supported types
#
# When starting an application, a type can be given:
#
# * `:permanent` - If a permanent application terminates, all other
# applications and the runtime system are also terminated;
# * `:transient` - If a transient application terminates with reason
# `:normal`, this is reported but no other applications are terminated.
# If a transient application terminates abnormally, all other
# applications and the runtime system are also terminated;
# * `:temporary` - If a temporary application terminates, this is reported
# but no other applications are terminated.
#
# The type only applies to the application being started. Its dependencies
# are all started with default type (which is :temporary).
#
# Note that transient mode is of little practical use, since when a
# supervision tree terminates, the reason is set to shutdown, not normal.
#
# ## Examples
#
# Application.Behaviour.start(:my_app)
#
@doc false
def start(app, type // :temporary) do
case :application.start(app, type) do
{ :error, { :not_started, dep } } ->
case start(dep) do
:ok -> start(app, type)
other -> other
end
{ :error, { :already_started, _ } } ->
:ok
other ->
other
end
end
@doc false
defmacro __using__(_) do
quote location: :keep do
@behavior :application
@behaviour :application
@doc false
def stop(_state) do
+464
View File
@@ -0,0 +1,464 @@
defmodule Base do
import Bitwise
@moduledoc """
This module provides data encoding and decoding functions
according to [RFC 4648](http://tools.ietf.org/html/rfc4648).
This document defines the commonly used base 64, base 32, and base
16 encoding schemes.
"""
b16_alphabet = Enum.with_index '0123456789ABCDEF'
b64_alphabet = Enum.with_index 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
b64url_alphabet = Enum.with_index 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_'
b32_alphabet = Enum.with_index 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'
b32hex_alphabet = Enum.with_index '0123456789ABCDEFGHIJKLMNOPQRSTUV'
Enum.each [ { :enc16, :dec16, b16_alphabet },
{ :enc64, :dec64, b64_alphabet },
{ :enc32, :dec32, b32_alphabet },
{ :enc64url, :dec64url, b64url_alphabet },
{ :enc32hex, :dec32hex, b32hex_alphabet } ], fn({enc, dec, alphabet}) ->
for {encoding, value} <- alphabet do
defp unquote(enc)(unquote(value)), do: unquote(encoding)
defp unquote(dec)(unquote(encoding)), do: unquote(value)
end
defp unquote(dec)(c) do
raise ArgumentError, message: "non-alphabet digit found: #{<<c>>}"
end
end
@doc """
Encodes a binary string into a base 16 encoded string.
## Examples
iex> Base.encode16("foobar")
"666F6F626172"
"""
@spec encode16(binary) :: binary
def encode16(data) when is_binary(data) do
do_encode16(data, &enc16/1)
end
@doc """
Decodes a base 16 encoded string into a binary string.
The following alphabet is used both for encoding and decoding:
| Value | Encoding | Value | Encoding | Value | Encoding | Value | Encoding |
|------:|---------:|------:|---------:|------:|---------:|------:|---------:|
| 0| 0| 4| 4| 8| 8| 12| C|
| 1| 1| 5| 5| 9| 9| 13| D|
| 2| 2| 6| 6| 10| A| 14| E|
| 3| 3| 7| 7| 11| B| 15| F|
## Examples
iex> Base.decode16("666F6F626172")
{:ok, "foobar"}
"""
@spec decode16(binary) :: { :ok, binary } | :error
def decode16(string) when is_binary(string) do
{ :ok, decode16!(string) }
rescue
ArgumentError -> :error
end
@doc """
Decodes a base 16 encoded string into a binary string.
An `ArgumentError` exception is raised if the padding is incorrect or
a non-alphabet character is present in the string.
## Examples
iex> Base.decode16!("666F6F626172")
"foobar"
"""
@spec decode16!(binary) :: binary
def decode16!(string) when is_binary(string) do
do_decode16(string, &dec16/1)
end
@doc """
Encodes a binary string into a base 64 encoded string.
## Examples
iex> Base.encode64("foobar")
"Zm9vYmFy"
"""
@spec encode64(binary) :: binary
def encode64(data) when is_binary(data) do
do_encode64(data, &enc64/1)
end
@doc """
Decodes a base 64 encoded string into a binary string.
The following alphabet is used both for encoding and decoding:
| Value | Encoding | Value | Encoding | Value | Encoding | Value | Encoding |
|------:|---------:|------:|---------:|------:|---------:|------:|---------:|
| 0| A| 17| R| 34| i| 51| z|
| 1| B| 18| S| 35| j| 52| 0|
| 2| C| 19| T| 36| k| 53| 1|
| 3| D| 20| U| 37| l| 54| 2|
| 4| E| 21| V| 38| m| 55| 3|
| 5| F| 22| W| 39| n| 56| 4|
| 6| G| 23| X| 40| o| 57| 5|
| 7| H| 24| Y| 41| p| 58| 6|
| 8| I| 25| Z| 42| q| 59| 7|
| 9| J| 26| a| 43| r| 60| 8|
| 10| K| 27| b| 44| s| 61| 9|
| 11| L| 28| c| 45| t| 62| +|
| 12| M| 29| d| 46| u| 63| /|
| 13| N| 30| e| 47| v| | |
| 14| O| 31| f| 48| w| (pad)| =|
| 15| P| 32| g| 49| x| | |
| 16| Q| 33| h| 50| y| | |
## Examples
iex> Base.decode64("Zm9vYmFy")
{:ok, "foobar"}
"""
@spec decode64(binary) :: { :ok, binary } | :error
def decode64(string) when is_binary(string) do
{ :ok, do_decode64(string, &dec64/1) }
rescue
ArgumentError -> :error
end
@doc """
Decodes a base 64 encoded string into a binary string.
The following alphabet is used both for encoding and decoding:
An `ArgumentError` exception is raised if the padding is incorrect or
a non-alphabet character is present in the string.
## Examples
iex> Base.decode64!("Zm9vYmFy")
"foobar"
"""
@spec decode64!(binary) :: binary
def decode64!(string) when is_binary(string) do
do_decode64(string, &dec64/1)
end
@doc """
Encodes a binary string into a base 64 encoded string with URL and filename
safe alphabet.
## Examples
iex> Base.url_encode64(<<255,127,254,252>>)
"_3_-_A=="
"""
@spec url_encode64(binary) :: binary
def url_encode64(data) when is_binary(data) do
do_encode64(data, &enc64url/1)
end
@doc """
Decodes a base 64 encoded string with URL and filename safe alphabet
into a binary string.
The following alphabet is used both for encoding and decoding:
| Value | Encoding | Value | Encoding | Value | Encoding | Value | Encoding |
|------:|---------:|------:|---------:|------:|---------:|------:|---------:|
| 0| A| 17| R| 34| i| 51| z|
| 1| B| 18| S| 35| j| 52| 0|
| 2| C| 19| T| 36| k| 53| 1|
| 3| D| 20| U| 37| l| 54| 2|
| 4| E| 21| V| 38| m| 55| 3|
| 5| F| 22| W| 39| n| 56| 4|
| 6| G| 23| X| 40| o| 57| 5|
| 7| H| 24| Y| 41| p| 58| 6|
| 8| I| 25| Z| 42| q| 59| 7|
| 9| J| 26| a| 43| r| 60| 8|
| 10| K| 27| b| 44| s| 61| 9|
| 11| L| 28| c| 45| t| 62| -|
| 12| M| 29| d| 46| u| 63| _|
| 13| N| 30| e| 47| v| | |
| 14| O| 31| f| 48| w| (pad)| =|
| 15| P| 32| g| 49| x| | |
| 16| Q| 33| h| 50| y| | |
## Examples
iex> Base.url_decode64("_3_-_A==")
{:ok, <<255,127,254,252>>}
"""
@spec url_decode64(binary) :: { :ok, binary } | :error
def url_decode64(string) when is_binary(string) do
{ :ok, do_decode64(string, &dec64url/1) }
rescue
ArgumentError -> :error
end
@doc """
Decodes a base 64 encoded string with URL and filename safe alphabet
into a binary string.
An `ArgumentError` exception is raised if the padding is incorrect or
a non-alphabet character is present in the string.
## Examples
iex> Base.url_decode64!("_3_-_A==")
<<255,127,254,252>>
"""
@spec url_decode64!(binary) :: binary
def url_decode64!(string) when is_binary(string) do
do_decode64(string, &dec64url/1)
end
@doc """
Encodes a binary string into a base 32 encoded string.
## Examples
iex> Base.encode32("foobar")
"MZXW6YTBOI======"
"""
@spec encode32(binary) :: binary
def encode32(data) when is_binary(data) do
do_encode32(data, &enc32/1)
end
@doc """
Decodes a base 32 encoded string into a binary string.
The following alphabet is used both for encoding and decoding:
| Value | Encoding | Value | Encoding | Value | Encoding | Value | Encoding |
|------:|---------:|------:|---------:|------:|---------:|------:|---------:|
| 0| A| 9| J| 18| S| 27| 3|
| 1| B| 10| K| 19| T| 28| 4|
| 2| C| 11| L| 20| U| 29| 5|
| 3| D| 12| M| 21| V| 30| 6|
| 4| E| 13| N| 22| W| 31| 7|
| 5| F| 14| O| 23| X| | |
| 6| G| 15| P| 24| Y| (pad)| =|
| 7| H| 16| Q| 25| Z| | |
| 8| I| 17| R| 26| 2| | |
## Examples
iex> Base.decode32("MZXW6YTBOI======")
{:ok, "foobar"}
"""
@spec decode32(binary) :: { :ok, binary } | :error
def decode32(string) do
{ :ok, do_decode32(string, &dec32/1) }
rescue
ArgumentError -> :error
end
@doc """
Decodes a base 32 encoded string into a binary string.
An `ArgumentError` exception is raised if the padding is incorrect or
a non-alphabet character is present in the string.
## Examples
iex> Base.decode32!("MZXW6YTBOI======")
"foobar"
"""
@spec decode32!(binary) :: binary
def decode32!(string) do
do_decode32(string, &dec32/1)
end
@doc """
Encodes a binary string into a base 32 encoded string with an
extended hexadecimal alphabet.
## Examples
iex> Base.hex_encode32("foobar")
"CPNMUOJ1E8======"
"""
@spec hex_encode32(binary) :: binary
def hex_encode32(data) when is_binary(data) do
do_encode32(data, &enc32hex/1)
end
@doc """
Decodes a base 32 encoded string with extended hexadecimal alphabet
into a binary string.
The following alphabet is used both for encoding and decoding:
| Value | Encoding | Value | Encoding | Value | Encoding | Value | Encoding |
|------:|---------:|------:|---------:|------:|---------:|------:|---------:|
| 0| 0| 9| 9| 18| I| 27| R|
| 1| 1| 10| A| 19| J| 28| S|
| 2| 2| 11| B| 20| K| 29| T|
| 3| 3| 12| C| 21| L| 30| U|
| 4| 4| 13| D| 22| M| 31| V|
| 5| 5| 14| E| 23| N| | |
| 6| 6| 15| F| 24| O| (pad)| =|
| 7| 7| 16| G| 25| P| | |
| 8| 8| 17| H| 26| Q| | |
## Examples
iex> Base.hex_decode32("CPNMUOJ1E8======")
{:ok, "foobar"}
"""
@spec hex_decode32(binary) :: { :ok, binary } | :error
def hex_decode32(string) when is_binary(string) do
{ :ok, do_decode32(string, &dec32hex/1) }
rescue
ArgumentError -> :error
end
@doc """
Decodes a base 32 encoded string with extended hexadecimal alphabet
into a binary string.
An `ArgumentError` exception is raised if the padding is incorrect or
a non-alphabet character is present in the string.
## Examples
iex> Base.hex_decode32!("CPNMUOJ1E8======")
"foobar"
"""
@spec hex_decode32!(binary) :: binary
def hex_decode32!(string) when is_binary(string) do
do_decode32(string, &dec32hex/1)
end
defp do_encode16(<<>>, _), do: <<>>
defp do_encode16(data, enc) do
for <<c::4 <- data>>, into: <<>>, do: <<enc.(c)::8>>
end
defp do_decode16(<<>>, _), do: <<>>
defp do_decode16(string, dec) when rem(byte_size(string), 2) == 0 do
for <<c1::8, c2::8 <- string>>, into: <<>> do
<<dec.(c1)::4, dec.(c2)::4>>
end
end
defp do_decode16(_, _) do
raise ArgumentError, message: "odd-length string"
end
defp do_encode64(<<>>, _), do: <<>>
defp do_encode64(data, enc) do
split = 3 * div(byte_size(data), 3)
<<main::[size(split), binary], rest::binary>> = data
main = for <<c::6 <- main>>, into: <<>>, do: <<enc.(c)::8>>
case rest do
<<c1::6, c2::6, c3::4>> ->
<<main::binary, enc.(c1)::8, enc.(c2)::8, enc.(bsl(c3, 2))::8, ?=>>
<<c1::6, c2::2>> ->
<<main::binary, enc.(c1)::8, enc.(bsl(c2, 4))::8, ?=, ?=>>
<<>> ->
main
end
end
defp do_decode64(<<>>, _), do: <<>>
defp do_decode64(string, dec) when rem(byte_size(string), 4) == 0 do
split = byte_size(string) - 4
<<main::[size(split), binary], rest::binary>> = string
main = for <<c::8 <- main>>, into: <<>>, do: <<dec.(c)::6>>
case rest do
<<c1::8, c2::8, ?=, ?=>> ->
<<main::binary, dec.(c1)::6, bsr(dec.(c2), 4)::2>>
<<c1::8, c2::8, c3::8, ?=>> ->
<<main::binary, dec.(c1)::6, dec.(c2)::6, bsr(dec.(c3), 2)::4>>
<<c1::8, c2::8, c3::8, c4::8>> ->
<<main::binary, dec.(c1)::6, dec.(c2)::6, dec.(c3)::6, dec.(c4)::6>>
<<>> ->
main
end
end
defp do_decode64(_, _) do
raise ArgumentError, message: "incorrect padding"
end
defp do_encode32(<<>>, _), do: <<>>
defp do_encode32(data, enc) do
split = 5 * div(byte_size(data), 5)
<<main::[size(split), binary], rest::binary>> = data
main = for <<c::5 <- main>>, into: <<>>, do: <<enc.(c)::8>>
case rest do
<<c1::5, c2::5, c3::5, c4::5, c5::5, c6::5, c7::2>> ->
<<main::binary,
enc.(c1)::8, enc.(c2)::8, enc.(c3)::8, enc.(c4)::8,
enc.(c5)::8, enc.(c6)::8, enc.(bsl(c7, 3))::8, ?=>>
<<c1::5, c2::5, c3::5, c4::5, c5::4>> ->
<<main::binary,
enc.(c1)::8, enc.(c2)::8, enc.(c3)::8, enc.(c4)::8,
enc.(bsl(c5, 1))::8, ?=, ?=, ?=>>
<<c1::5, c2::5, c3::5, c4::1>> ->
<<main::binary,
enc.(c1)::8, enc.(c2)::8, enc.(c3)::8, enc.(bsl(c4, 4))::8,
?=, ?=, ?=, ?=>>
<<c1::5, c2::3>> ->
<<main::binary,
enc.(c1)::8, enc.(bsl(c2, 2))::8, ?=, ?=,
?=, ?=, ?=, ?=>>
<<>> ->
main
end
end
defp do_decode32(<<>>, _), do: <<>>
defp do_decode32(string, dec) when rem(byte_size(string), 8) == 0 do
split = byte_size(string) - 8
<<main::[size(split), binary], rest::binary>> = string
main = for <<c::8 <- main>>, into: <<>>, do: <<dec.(c)::5>>
case rest do
<<c1::8, c2::8, ?=, ?=, ?=, ?=, ?=, ?=>> ->
<<main::binary, dec.(c1)::5, bsr(dec.(c2), 2)::3>>
<<c1::8, c2::8, c3::8, c4::8, ?=, ?=, ?=, ?=>> ->
<<main::binary,
dec.(c1)::5, dec.(c2)::5, dec.(c3)::5, bsr(dec.(c4), 4)::1>>
<<c1::8, c2::8, c3::8, c4::8, c5::8, ?=, ?=, ?=>> ->
<<main::binary,
dec.(c1)::5, dec.(c2)::5, dec.(c3)::5, dec.(c4)::5,
bsr(dec.(c5), 1)::4>>
<<c1::8, c2::8, c3::8, c4::8, c5::8, c6::8, c7::8, ?=>> ->
<<main::binary,
dec.(c1)::5, dec.(c2)::5, dec.(c3)::5, dec.(c4)::5,
dec.(c5)::5, dec.(c6)::5, bsr(dec.(c7), 3)::2>>
<<c1::8, c2::8, c3::8, c4::8, c5::8, c6::8, c7::8, c8::8>> ->
<<main::binary,
dec.(c1)::5, dec.(c2)::5, dec.(c3)::5, dec.(c4)::5,
dec.(c5)::5, dec.(c6)::5, dec.(c7)::5, dec.(c8)::5>>
<<>> ->
main
end
end
defp do_decode32(_, _) do
raise ArgumentError, message: "incorrect padding"
end
end
+25 -82
View File
@@ -1,6 +1,6 @@
defmodule Behaviour do
@moduledoc """
Utilities for defining behaviour intefaces.
Utilities for defining behaviour interfaces.
Behaviours can be referenced by other modules
to ensure they implement required callbacks.
@@ -46,60 +46,53 @@ defmodule Behaviour do
@doc """
Define a function callback according to the given type specification.
"""
defmacro defcallback({ :::, _, [fun, return_and_guard] }) do
{ return, guard } = split_return_and_guard(return_and_guard)
do_defcallback(fun, return, guard, __CALLER__)
end
defmacro defcallback(fun) do
do_defcallback(fun, quote(do: term), [], __CALLER__)
defmacro defcallback(spec) do
do_defcallback(split_spec(spec, quote(do: term)), __CALLER__)
end
@doc """
Define a macro callback according to the given type specification.
"""
defmacro defmacrocallback({ :::, _, [fun, return_and_guard] }) do
{ return, guard } = split_return_and_guard(return_and_guard)
do_defmacrocallback(fun, return, guard, __CALLER__)
defmacro defmacrocallback(spec) do
do_defmacrocallback(split_spec(spec, quote(do: Macro.t)), __CALLER__)
end
defmacro defmacrocallback(fun) do
do_defmacrocallback(fun, quote(do: Macro.t), [], __CALLER__)
defp split_spec({ :when, _, [{ :::, _, [spec, return] }, guard] }, _default) do
{ spec, return, guard }
end
defp split_return_and_guard({ :when, _, [return, guard] }) do
{ return, guard }
defp split_spec({ :when, _, [spec, guard] }, default) do
{ spec, default, guard }
end
defp split_return_and_guard({ :|, meta, [left, right] }) do
{ return, guard } = split_return_and_guard(right)
{ { :|, meta, [left, return] }, guard }
defp split_spec({ :::, _, [spec, return] }, _default) do
{ spec, return, [] }
end
defp split_return_and_guard(other) do
{ other, [] }
defp split_spec(spec, default) do
{ spec, default, [] }
end
defp do_defcallback(fun, return, guards, caller) do
case Macro.decompose_call(fun) do
defp do_defcallback({ spec, return, guards }, caller) do
case Macro.decompose_call(spec) do
{ name, args } ->
do_callback(:def, name, args, name, length(args), args, return, guards, caller)
_ ->
raise ArgumentError, message: "invalid syntax in defcallback #{Macro.to_string(fun)}"
raise ArgumentError, message: "invalid syntax in defcallback #{Macro.to_string(spec)}"
end
end
defp do_defmacrocallback(fun, return, guards, caller) do
case Macro.decompose_call(fun) do
defp do_defmacrocallback({ spec, return, guards }, caller) do
case Macro.decompose_call(spec) do
{ name, args } ->
do_callback(:defmacro, :"MACRO-#{name}", [quote(do: env :: Macro.Env.t)|args],
name, length(args), args, return, guards, caller)
_ ->
raise ArgumentError, message: "invalid syntax in defmacrocallback #{Macro.to_string(fun)}"
raise ArgumentError, message: "invalid syntax in defmacrocallback #{Macro.to_string(spec)}"
end
end
defp do_callback(kind, name, args, docs_name, docs_arity, docs_args, return, guards, caller) do
defp do_callback(kind, name, args, docs_name, docs_arity, _docs_args, return, guards, caller) do
Enum.each args, fn
{ :::, _, [left, right] } ->
ensure_not_default(left)
@@ -111,75 +104,25 @@ defmodule Behaviour do
end
quote do
docs_args = unquote(Macro.escape(docs_args))
@callback unquote(name)(unquote_splicing(args)) :: unquote(return) when unquote(guards)
Behaviour.store_docs(__MODULE__, unquote(caller.line), unquote(kind),
unquote(docs_name), docs_args, unquote(docs_arity))
unquote(docs_name), unquote(docs_arity))
end
end
defp ensure_not_default({ ://, _, [_, _] }) do
raise ArgumentError, message: "default arguments // not supported in defcallback/defmacrocallback"
defp ensure_not_default({ :\\, _, [_, _] }) do
raise ArgumentError, message: "default arguments \\\\ not supported in defcallback/defmacrocallback"
end
defp ensure_not_default(_), do: :ok
@doc false
def store_docs(module, line, kind, name, args, arity) do
def store_docs(module, line, kind, name, arity) do
doc = Module.get_attribute module, :doc
Module.delete_attribute module, :doc
signature = Enum.map(Stream.with_index(args), fn { arg, ix } ->
{ simplify_signature(arg, ix + 1), [line: line], nil }
end)
Module.put_attribute module, :behaviour_docs, { { name, arity }, line, kind, signature, doc }
Module.put_attribute module, :behaviour_docs, { { name, arity }, line, kind, doc }
end
defp simplify_signature({ :::, _, [var, _] }, i) do
simplify_signature(var, i)
end
defp simplify_signature({ :|, _, [first, _] }, i) do
simplify_signature(first, i)
end
defp simplify_signature({ :., _, [_, name] }, _i) do
name
end
defp simplify_signature({ :->, _, list }, i) when is_list(list) do
:"fun#{i}"
end
defp simplify_signature({ :.., _, list }, i) when is_list(list) do
:"range#{i}"
end
defp simplify_signature({ :<<>>, _, list }, i) when is_list(list) do
:"bitstring#{i}"
end
defp simplify_signature({ :{}, _, list }, i) when is_list(list) do
:"tuple#{i}"
end
defp simplify_signature({ name, _, args }, _i)
when is_atom(name) and (is_atom(args) or is_list(args)) do
name
end
defp simplify_signature({ ast, _, list }, i) when is_list(list) do
simplify_signature(ast, i)
end
defp simplify_signature(other, i) when is_integer(other), do: :"int#{i}"
defp simplify_signature(other, i) when is_boolean(other), do: :"bool#{i}"
defp simplify_signature(other, i) when is_atom(other), do: :"atom#{i}"
defp simplify_signature(other, i) when is_list(other), do: :"list#{i}"
defp simplify_signature(other, i) when is_tuple(other), do: :"tuple#{i}"
defp simplify_signature(_, i), do: :"arg#{i}"
@doc false
defmacro __using__(_) do
quote do
+2 -2
View File
@@ -1,6 +1,6 @@
defmodule Bitwise do
@moduledoc """
This module provide macros and operators for bitwise operators.
This module provides macros and operators for bitwise operators.
These macros can be used in guards.
The easiest way to use is to simply import them into
@@ -15,7 +15,7 @@ defmodule Bitwise do
You can select to include only or skip operators by passing options:
iex> use Bitwise, only_operators: true
...> 1 &&& 1
iex> 1 &&& 1
1
"""
+87 -89
View File
@@ -10,7 +10,7 @@ defmodule Code do
Utilities for managing code compilation, code evaluation and code loading.
This module complements [Erlang's code module](http://www.erlang.org/doc/man/code.html)
to add behavior which is specific to Elixir.
to add behaviour which is specific to Elixir.
"""
@doc """
@@ -59,11 +59,11 @@ defmodule Code do
end
@doc """
Evaluate the contents given by `string`.
Evaluate the contents given by `string`.
The `binding` argument is a keyword list of variable bindings.
The `opts` argument is a keyword list of environment options.
The `binding` argument is a keyword list of variable bindings.
The `opts` argument is a keyword list of environment options.
Those options can be:
* `:file` - the file to be considered in the evaluation
@@ -115,18 +115,15 @@ defmodule Code do
{3, [a: 1, b: 2]}
"""
def eval_string(string, binding // [], opts // [])
def eval_string(string, binding \\ [], opts \\ [])
def eval_string(string, binding, Macro.Env[] = env) do
do_eval_string(string, binding, env.to_keywords)
{ value, binding, _env, _scope } = :elixir.eval to_char_list(string), binding, env.to_keywords
{ value, binding }
end
def eval_string(string, binding, opts) when is_list(opts) do
validate_eval_opts(opts)
do_eval_string(string, binding, opts)
end
defp do_eval_string(string, binding, opts) when is_list(binding) do
{ value, binding, _env, _scope } = :elixir.eval to_char_list(string), binding, opts
{ value, binding }
end
@@ -139,29 +136,26 @@ defmodule Code do
## Examples
iex> contents = quote(hygiene: [vars: false], do: a + b)
...> Code.eval_quoted(contents, [a: 1, b: 2], file: __ENV__.file, line: __ENV__.line)
iex> Code.eval_quoted(contents, [a: 1, b: 2], file: __ENV__.file, line: __ENV__.line)
{3, [a: 1, b: 2]}
For convenience, you can pass `__ENV__` as the `opts` argument and
all options will be automatically extracted from the current environment:
iex> contents = quote(hygiene: [vars: false], do: a + b)
...> Code.eval_quoted(contents, [a: 1, b: 2], __ENV__)
iex> Code.eval_quoted(contents, [a: 1, b: 2], __ENV__)
{3, [a: 1, b: 2]}
"""
def eval_quoted(quoted, binding // [], opts // [])
def eval_quoted(quoted, binding \\ [], opts \\ [])
def eval_quoted(quoted, binding, Macro.Env[] = env) do
do_eval_quoted(quoted, binding, env.to_keywords)
{ value, binding, _env, _scope } = :elixir.eval_quoted quoted, binding, env.to_keywords
{ value, binding }
end
def eval_quoted(quoted, binding, opts) when is_list(opts) do
validate_eval_opts(opts)
do_eval_quoted(quoted, binding, opts)
end
defp do_eval_quoted(quoted, binding, opts) when is_list(binding) do
{ value, binding, _env, _scope } = :elixir.eval_quoted quoted, binding, opts
{ value, binding }
end
@@ -204,8 +198,8 @@ defmodule Code do
end
@doc """
Convert the given string to its quoted form.
Convert the given string to its quoted form.
Returns `{ :ok, quoted_form }`
if it succeeds, `{ :error, { line, error, token } }` otherwise.
@@ -225,20 +219,15 @@ defmodule Code do
`Macro.to_string/2`, which converts a quoted form to a string/binary
representation.
"""
def string_to_quoted(string, opts // []) when is_list(opts) do
def string_to_quoted(string, opts \\ []) when is_list(opts) do
file = Keyword.get opts, :file, "nofile"
line = Keyword.get opts, :line, 1
res = :elixir.string_to_quoted(to_char_list(string), line, file, opts)
case res do
{ :ok, forms } -> { :ok, forms }
_ -> res
end
:elixir.string_to_quoted(to_char_list(string), line, file, opts)
end
@doc """
Convert the given string to its quoted form.
Convert the given string to its quoted form.
It returns the ast if it succeeds,
raises an exception otherwise. The exception is a `TokenMissingError`
in case a token is missing (usually because the expression is incomplete),
@@ -246,27 +235,40 @@ defmodule Code do
Check `string_to_quoted/2` for options information.
"""
def string_to_quoted!(string, opts // []) when is_list(opts) do
def string_to_quoted!(string, opts \\ []) when is_list(opts) do
file = Keyword.get opts, :file, "nofile"
line = Keyword.get opts, :line, 1
:elixir.string_to_quoted!(to_char_list(string), line, file, opts)
end
@doc """
Load the given file.
Accepts `relative_to` as an argument to tell where
the file is located. If the file was already required/loaded, loads it again.
It returns a list of tuples `{ ModuleName, <<byte_code>> }`, one tuple for each
module defined in the file.
Evals the given file.
Notice that if `load_file` is invoked by different processes
concurrently, the target file will be invoked concurrently
many times. I.e. if `load_file` is called N times with
a given file, the given file will be loaded N times. Check
`require_file/2` if you don't want a file to be loaded concurrently.
Accepts `relative_to` as an argument to tell where the file is located.
While `load_file` loads a file and returns the loaded modules and their
byte code, `eval_file` simply evalutes the file contents and returns the
evaluation result and its bindings.
"""
def load_file(file, relative_to // nil) when is_binary(file) do
def eval_file(file, relative_to \\ nil) do
file = find_file(file, relative_to)
eval_string File.read!(file), [], []
end
@doc """
Load the given file.
Accepts `relative_to` as an argument to tell where the file is located.
If the file was already required/loaded, loads it again.
It returns a list of tuples `{ ModuleName, <<byte_code>> }`, one tuple for
each module defined in the file.
Notice that if `load_file` is invoked by different processes concurrently,
the target file will be loaded concurrently many times. Check `require_file/2`
if you don't want a file to be loaded concurrently.
"""
def load_file(file, relative_to \\ nil) when is_binary(file) do
file = find_file(file, relative_to)
:elixir_code_server.call { :acquire, file }
loaded = :elixir_compiler.file file
@@ -275,11 +277,11 @@ defmodule Code do
end
@doc """
Require the given `file`.
Accepts `relative_to` as an argument to tell where
the file is located. The return value is the same as that of `load_file/2`. If
the file was already required/loaded, doesn't do anything and returns `nil`.
Requires the given `file`.
Accepts `relative_to` as an argument to tell where the file is located.
The return value is the same as that of `load_file/2`. If the file was already
required/loaded, doesn't do anything and returns `nil`.
Notice that if `require_file` is invoked by different processes concurrently,
the first process to invoke `require_file` acquires a lock and the remaining
@@ -287,9 +289,9 @@ defmodule Code do
N times with a given file, it will be loaded only once. The first process to
call `require_file` will get the list of loaded modules, others will get `nil`.
Check `load_file/2` if you want a file to be loaded concurrently.
Check `load_file/2` if you want a file to be loaded multiple times.
"""
def require_file(file, relative_to // nil) when is_binary(file) do
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
@@ -305,7 +307,7 @@ defmodule Code do
end
@doc """
Load the compilation options from the code server.
Gets the compilation options from the code server.
Check `compiler_options/1` for more information.
"""
@@ -314,7 +316,7 @@ defmodule Code do
end
@doc """
Set compilation options.
Sets compilation options.
These options are global since they are stored by Elixir's Code Server.
@@ -338,39 +340,35 @@ defmodule Code do
end
@doc """
Compile the given string.
Compiles the given string.
Returns a list of tuples where
the first element is the module name and the second one is its
binary.
Returns a list of tuples where the first element is the module name
and the second one is its byte code (as a binary).
For compiling many files at once, check `Kernel.ParallelCompiler.files/2`.
"""
def compile_string(string, file // "nofile") when is_binary(file) do
def compile_string(string, file \\ "nofile") when is_binary(file) do
:elixir_compiler.string to_char_list(string), file
end
@doc """
Compile the quoted expression.
Returns a list of tuples where
the first element is the module name and the second one is its
binary.
Compiles the quoted expression.
Returns a list of tuples where the first element is the module name and
the second one is its byte code (as a binary).
"""
def compile_quoted(quoted, file // "nofile") when is_binary(file) do
def compile_quoted(quoted, file \\ "nofile") when is_binary(file) do
:elixir_compiler.quoted quoted, file
end
@doc """
Ensure the given module is loaded.
If the module is already
loaded, this works as no-op. If the module was not yet loaded,
it tries to load it.
Ensures the given module is loaded.
If it succeeds loading the module, it returns
`{ :module, module }`. If not, returns `{ :error, reason }` with
the error reason.
If the module is already loaded, this works as no-op. If the module
was not yet loaded, it tries to load it.
If it succeeds loading the module, it returns `{ :module, module }`.
If not, returns `{ :error, reason }` with the error reason.
## Code loading on the Erlang VM
@@ -385,47 +383,46 @@ defmodule Code do
module uses this function to check if a specific parser exists for a given
URI scheme.
## Code.ensure_compiled
## `Code.ensure_compiled/1`
Elixir also contains an `ensure_compiled/1` function that is a
superset of `ensure_loaded/1`.
Since Elixir's compilation happens in parallel, in some situations
you may need to use a module but that was not yet compiled, therefore
you may need to use a module that was not yet compiled, therefore
it can't even be loaded.
`ensure_compiled/1` halts the current process until the
module we are depending on is available.
In most cases, `ensure_loaded` is enough. `ensure_compiled`
must be used in some rare cases, usually involving macros
that need to invoke a module for callback information.
In most cases, `ensure_loaded/1` is enough. `ensure_compiled/1`
must be used in rare cases, usually involving macros that need to
invoke a module for callback information.
"""
def ensure_loaded(module) when is_atom(module) do
:code.ensure_loaded(module)
end
@doc """
Ensure the given module is loaded.
Ensures the given module is loaded.
Similar to `ensure_loaded/1`, but returns `true` if the module
is already loaded or was successfully loaded. Returns `false` otherwise.
is already loaded or was successfully loaded. Returns `false`
otherwise.
"""
def ensure_loaded?(module) do
match?({ :module, ^module }, ensure_loaded(module))
end
@doc """
Ensure the given module is compiled and loaded.
If the module
is already loaded, it works as no-op. If the module was not
loaded yet, it checks if it needs to be compiled first and
then tries to load it.
Ensures the given module is compiled and loaded.
If it succeeds loading the module, it returns
`{ :module, module }`. If not, returns `{ :error, reason }` with
the error reason.
If the module is already loaded, it works as no-op. If the module was
not loaded yet, it checks if it needs to be compiled first and then
tries to load it.
If it succeeds loading the module, it returns `{ :module, module }`.
If not, returns `{ :error, reason }` with the error reason.
Check `ensure_loaded/1` for more information on module loading
and when to use `ensure_loaded/1` or `ensure_compiled/1`.
@@ -448,10 +445,10 @@ defmodule Code do
end
@doc """
Ensure the given module is compiled and loaded.
Ensures the given module is compiled and loaded.
Similar to `ensure_compiled/1`, but returns `true` if the module
is already loaded or was successfully loaded and compiled.
is already loaded or was successfully loaded and compiled.
Returns `false` otherwise.
"""
def ensure_compiled?(module) do
@@ -461,6 +458,7 @@ defmodule Code do
## Helpers
# Finds the file given the relative_to path.
#
# If the file is found, returns its path in binary, fails otherwise.
defp find_file(file, relative_to) do
file = if relative_to do
+117
View File
@@ -0,0 +1,117 @@
defprotocol Collectable do
@moduledoc """
A protocol to traverse data structures.
The `Enum.into/2` function uses this protocol to insert an
enumerable into a collection:
iex> Enum.into([a: 1, b: 2], %{})
%{a: 1, b: 2}
If a collection implements both `Enumerable` and `Collectable`, both
operations can be combined with `Enum.traverse/2`:
iex> Enum.traverse(%{ a: 1, b: 2 }, fn { k, v } -> { k, v * 2 } end)
%{a: 2, b: 4}
## Why Collectable?
The `Enumerable` protocol is useful to take values out of a collection.
In order to support a wide range of values, the functions provided by
the `Enumerable` protocol do not keep shape. For example, passing a
dictionary to `Enum.map/2` always returns a list.
This design is intentional. `Enumerable` was designed to support infinite
collections, resources and other structures with fixed shape. For example,
it doesn't make sense to insert values into a range, as it has a fixed
shape where just the range limits are stored.
The `Collectable` module was designed to fill the gap left by the
`Enumerable` protocol. It provides two functions: `into/1` and `empty/1`.
`into/1` can be seen as the opposite of `Enumerable.reduce/3`. If
`Enumerable` is about taking values out, `Collectable.into/1` is about
collecting those values into a structure.
`empty/1` receives a collectable and returns an empty version of the
same collectable. By combining the enumerable functionality with `into/1`
and `empty/1`, one can, for example, implement a traversal mechanism.
"""
@type command :: { :cont, term } | :done | :halt
@doc """
Receives a collectable structure and returns an empty one.
"""
@spec empty(t) :: t
def empty(collectable)
@doc """
Returns a function that collects values alongside
the initial accumulation value.
The returned function receives a collectable and injects a given
value into it for every `{ :cont, term }` instruction.
`:done` is passed when no further values will be injected, useful
for closing resources and normalizing values. A collectable must
be returned on `:done`.
If injection is suddenly interrupted, `:halt` is passed and it can
return any value, as it won't be used.
"""
@spec into(t) :: { term, (term, command -> t | term) }
def into(collectable)
end
defimpl Collectable, for: List do
def empty(_list) do
[]
end
def into(original) do
{ [], fn
list, { :cont, x } -> [x|list]
list, :done -> original ++ :lists.reverse(list)
_, :halt -> :ok
end }
end
end
defimpl Collectable, for: BitString do
def empty(_bitstring) do
""
end
def into(original) do
{ original, fn
bitstring, { :cont, x } -> <<bitstring :: bits, x :: bits>>
bitstring, :done -> bitstring
_, :halt -> :ok
end }
end
end
defimpl Collectable, for: Function do
def empty(function) do
function
end
def into(function) do
{ function, function }
end
end
defimpl Collectable, for: Map do
def empty(_map) do
%{}
end
def into(original) do
{ original, fn
map, { :cont, { k, v } } -> :maps.put(k, v, map)
map, :done -> map
_, :halt -> :ok
end }
end
end
+135 -153
View File
@@ -1,5 +1,5 @@
defmodule Dict do
@moduledoc %S"""
@moduledoc ~S"""
This module specifies the Dict API expected to be
implemented by different dictionaries. It also provides
functions that redirect to the underlying Dict, allowing
@@ -12,7 +12,7 @@ defmodule Dict do
HashDict.new #=> creates an empty HashDict
In the examples below, `dict_impl` means a specific
`Dict` implementation, for example `HashDict` or `ListDict`.
`Dict` implementation, for example `HashDict` or `Map`.
## Protocols
@@ -21,34 +21,27 @@ defmodule Dict do
protocol:
iex> dict = dict_impl.new
...> dict = Dict.put(dict, :hello, :world)
...> dict[:hello]
iex> dict = Dict.put(dict, :hello, :world)
iex> dict[:hello]
:world
And the `Enumerable` protocol, allowing one to write:
Enum.each(dict, fn ({ k, v }) ->
IO.puts "#{k}: #{v}"
end)
As well as the `Enumerable` and `Collectable` protocols.
## Match
Dictionaries are required to implement all operations
using the match (`===`) operator. Any deviation from
this behaviour should be avoided and explicitly documented.
using the match (`===`) operator.
"""
use Behaviour
@type key :: any
@type value :: any
@type keys :: [ key ]
@type t :: tuple | list
@type t :: tuple | list | map
defcallback new :: t
defcallback new(Keyword.t) :: t
defcallback delete(t, key) :: t
defcallback drop(t, keys) :: t
defcallback drop(t, Enum.t) :: t
defcallback empty(t) :: t
defcallback equal?(t, t) :: boolean
defcallback get(t, key) :: value
@@ -56,7 +49,7 @@ defmodule Dict do
defcallback fetch(t, key) :: { :ok, value } | :error
defcallback fetch!(t, key) :: value | no_return
defcallback has_key?(t, key) :: boolean
defcallback keys(t) :: list(key)
defcallback keys(t) :: [key]
defcallback merge(t, t) :: t
defcallback merge(t, t, (key, value, value -> value)) :: t
defcallback pop(t, key) :: {value, t}
@@ -64,8 +57,8 @@ defmodule Dict do
defcallback put(t, key, value) :: t
defcallback put_new(t, key, value) :: t
defcallback size(t) :: non_neg_integer()
defcallback split(t, keys) :: {t, t}
defcallback take(t, keys) :: t
defcallback split(t, Enum.t) :: {t, t}
defcallback take(t, Enum.t) :: t
defcallback to_list(t) :: list()
defcallback update(t, key, value, (value -> value)) :: t
defcallback update!(t, key, (value -> value)) :: t | no_return
@@ -73,13 +66,17 @@ defmodule Dict do
defmacrop target(dict) do
quote do
cond do
is_tuple(unquote(dict)) ->
elem(unquote(dict), 0)
is_list(unquote(dict)) ->
case unquote(dict) do
%{__struct__: x} when is_atom(x) ->
x
%{} ->
Map
x when is_tuple(x) ->
elem(x, 0)
x when is_list(x) ->
ListDict
true ->
unsupported_dict(unquote(dict))
x ->
unsupported_dict(x)
end
end
end
@@ -90,8 +87,8 @@ defmodule Dict do
## Examples
iex> d = dict_impl.new([a: 1, b: 2])
...> Enum.sort(Dict.keys(d))
iex> d = Enum.into([a: 1, b: 2], dict_impl.new)
iex> Enum.sort(Dict.keys(d))
[:a,:b]
"""
@@ -106,8 +103,8 @@ defmodule Dict do
## Examples
iex> d = dict_impl.new([a: 1, b: 2])
...> Enum.sort(Dict.values(d))
iex> d = Enum.into([a: 1, b: 2], dict_impl.new)
iex> Enum.sort(Dict.values(d))
[1,2]
"""
@@ -121,8 +118,8 @@ defmodule Dict do
## Examples
iex> d = dict_impl.new([a: 1, b: 2])
...> Dict.size(d)
iex> d = Enum.into([a: 1, b: 2], dict_impl.new)
iex> Dict.size(d)
2
"""
@@ -136,7 +133,7 @@ defmodule Dict do
## Examples
iex> d = dict_impl.new([a: 1])
iex> d = Enum.into([a: 1], dict_impl.new)
iex> Dict.has_key?(d, :a)
true
iex> Dict.has_key?(d, :b)
@@ -154,7 +151,7 @@ defmodule Dict do
## Examples
iex> d = dict_impl.new([a: 1])
iex> d = Enum.into([a: 1], dict_impl.new)
iex> Dict.get(d, :a)
1
iex> Dict.get(d, :b)
@@ -163,7 +160,7 @@ defmodule Dict do
3
"""
@spec get(t, key, value) :: value
def get(dict, key, default // nil) do
def get(dict, key, default \\ nil) do
target(dict).get(dict, key, default)
end
@@ -173,7 +170,7 @@ defmodule Dict do
## Examples
iex> d = dict_impl.new([a: 1])
iex> d = Enum.into([a: 1], dict_impl.new)
iex> Dict.fetch(d, :a)
{ :ok, 1 }
iex> Dict.fetch(d, :b)
@@ -191,11 +188,9 @@ defmodule Dict do
## Examples
iex> d = dict_impl.new([a: 1])
iex> d = Enum.into([a: 1], dict_impl.new)
iex> Dict.fetch!(d, :a)
1
iex> Dict.fetch!(d, :b)
** (KeyError) key not found: :b
"""
@spec fetch!(t, key) :: value | no_return
@@ -209,9 +204,9 @@ defmodule Dict do
## Examples
iex> d = dict_impl.new([a: 1, b: 2])
...> d = Dict.put(d, :a, 3)
...> Dict.get(d, :a)
iex> d = Enum.into([a: 1, b: 2], dict_impl.new)
iex> d = Dict.put(d, :a, 3)
iex> Dict.get(d, :a)
3
"""
@@ -225,9 +220,9 @@ defmodule Dict do
## Examples
iex> d = dict_impl.new([a: 1, b: 2])
...> d = Dict.put_new(d, :a, 3)
...> Dict.get(d, :a)
iex> d = Enum.into([a: 1, b: 2], dict_impl.new)
iex> d = Dict.put_new(d, :a, 3)
iex> Dict.get(d, :a)
1
"""
@@ -242,13 +237,13 @@ defmodule Dict do
## Examples
iex> d = dict_impl.new([a: 1, b: 2])
...> d = Dict.delete(d, :a)
...> Dict.get(d, :a)
iex> d = Enum.into([a: 1, b: 2], dict_impl.new)
iex> d = Dict.delete(d, :a)
iex> Dict.get(d, :a)
nil
iex> d = dict_impl.new([b: 2])
...> Dict.delete(d, :a) == d
iex> d = Enum.into([b: 2], dict_impl.new)
iex> Dict.delete(d, :a) == d
true
"""
@@ -258,49 +253,45 @@ defmodule Dict do
end
@doc """
Merges the given `enum` into `dict`. If one of the `enum` keys
already exists in `dict`, the `dict` value is replaced by the `enum`
value.
Merges the dict `b` into dict `a`.
The `enum` must yield tuples with two elements on enumeration,
where the first element represents the key and the second the value.
If one of the dict `b` entries already exists in the `dict`,
the functions in entries in `b` have higher precedence unless a
function is given to resolve conflicts.
Notice this function is polymorphic as it merges dicts of any
type. Each dict implementation also provides a `merge` function,
but they can only merge dicts of the same type.
## Examples
iex> d1 = dict_impl.new([a: 1, b: 2])
...> d2 = dict_impl.new([a: 3, d: 4])
...> d = Dict.merge(d1, d2)
...> [a: Dict.get(d, :a), b: Dict.get(d, :b), d: Dict.get(d, :d)]
iex> d1 = Enum.into([a: 1, b: 2], dict_impl.new)
iex> d2 = Enum.into([a: 3, d: 4], dict_impl.new)
iex> d = Dict.merge(d1, d2)
iex> [a: Dict.get(d, :a), b: Dict.get(d, :b), d: Dict.get(d, :d)]
[a: 3, b: 2, d: 4]
"""
@spec merge(t, t) :: t
def merge(dict, enum) do
merge(dict, enum, fn(_k, _v1, v2) -> v2 end)
end
@doc """
Merges the given `enum` into `dict`. If one of the `enum` entries
already exists in `dict`, the given function is invoked to resolve
the conflict.
The `enum` must yield tuples with two elements on enumeration,
where the first element represents the key and the second the value.
## Examples
iex> d1 = dict_impl.new([a: 1, b: 2])
...> d2 = dict_impl.new([a: 3, d: 4])
...> d = Dict.merge(d1, d2, fn(_k, v1, v2) ->
iex> d1 = Enum.into([a: 1, b: 2], dict_impl.new)
iex> d2 = Enum.into([a: 3, d: 4], dict_impl.new)
iex> d = Dict.merge(d1, d2, fn(_k, v1, v2) ->
...> v1 + v2
...> end)
...> [a: Dict.get(d, :a), b: Dict.get(d, :b), d: Dict.get(d, :d)]
iex> [a: Dict.get(d, :a), b: Dict.get(d, :b), d: Dict.get(d, :d)]
[a: 4, b: 2, d: 4]
"""
@spec merge(t, t, (key, value, value -> value)) :: t
def merge(dict, enum, fun) do
target(dict).merge(dict, enum, fun)
def merge(dict1, dict2, fun \\ fn(_k, _v1, v2) -> v2 end) do
target1 = target(dict1)
target2 = target(dict2)
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) }
end) |> elem(1)
end
end
@doc """
@@ -309,24 +300,24 @@ defmodule Dict do
## Examples
iex> dict = dict_impl.new [a: 1]
...> {v, d} = Dict.pop dict, :a
...> {v, Enum.sort(d)}
iex> dict = Enum.into([a: 1], dict_impl.new)
iex> {v, d} = Dict.pop dict, :a
iex> {v, Enum.sort(d)}
{1,[]}
iex> dict = dict_impl.new [a: 1]
...> {v, d} = Dict.pop dict, :b
...> {v, Enum.sort(d)}
iex> dict = Enum.into([a: 1], dict_impl.new)
iex> {v, d} = Dict.pop dict, :b
iex> {v, Enum.sort(d)}
{nil,[a: 1]}
iex> dict = dict_impl.new [a: 1]
...> {v, d} = Dict.pop dict, :b, 3
...> {v, Enum.sort(d)}
iex> dict = Enum.into([a: 1], dict_impl.new)
iex> {v, d} = Dict.pop dict, :b, 3
iex> {v, Enum.sort(d)}
{3,[a: 1]}
"""
@spec pop(t, key, value) :: {value, t}
def pop(dict, key, default // nil) do
def pop(dict, key, default \\ nil) do
target(dict).pop(dict, key, default)
end
@@ -336,9 +327,9 @@ defmodule Dict do
## Examples
iex> d = dict_impl.new([a: 1, b: 2])
...> d = Dict.update!(d, :a, fn(val) -> -val end)
...> Dict.get(d, :a)
iex> d = Enum.into([a: 1, b: 2], dict_impl.new)
iex> d = Dict.update!(d, :a, fn(val) -> -val end)
iex> Dict.get(d, :a)
-1
"""
@@ -347,11 +338,6 @@ defmodule Dict do
target(dict).update!(dict, key, fun)
end
@doc false
def update(dict, key, fun) do
target(dict).update(dict, key, fun)
end
@doc """
Update a value in `dict` by calling `fun` on the value to get a new value. If
`key` is not present in `dict` then `initial` will be stored as the first
@@ -359,9 +345,9 @@ defmodule Dict do
## Examples
iex> d = dict_impl.new([a: 1, b: 2])
...> d = Dict.update(d, :c, 3, fn(val) -> -val end)
...> Dict.get(d, :c)
iex> d = Enum.into([a: 1, b: 2], dict_impl.new)
iex> d = Dict.update(d, :c, 3, fn(val) -> -val end)
iex> Dict.get(d, :c)
3
"""
@@ -379,23 +365,23 @@ defmodule Dict do
## Examples
iex> d = dict_impl.new([a: 1, b: 2, c: 3, d: 4])
...> { d1, d2 } = Dict.split(d, [:a, :c, :e])
...> { Dict.to_list(d1), Dict.to_list(d2) }
iex> d = Enum.into([a: 1, b: 2, c: 3, d: 4], dict_impl.new)
iex> { d1, d2 } = Dict.split(d, [:a, :c, :e])
iex> { Dict.to_list(d1) |> Enum.sort, Dict.to_list(d2) |> Enum.sort }
{ [a: 1, c: 3], [b: 2, d: 4] }
iex> d = dict_impl.new([])
...> { d1, d2 } = Dict.split(d, [:a, :c])
...> { Dict.to_list(d1), Dict.to_list(d2) }
iex> d = Enum.into([], dict_impl.new)
iex> { d1, d2 } = Dict.split(d, [:a, :c])
iex> { Dict.to_list(d1), Dict.to_list(d2) }
{ [], [] }
iex> d = dict_impl.new([a: 1, b: 2])
...> { d1, d2 } = Dict.split(d, [:a, :b, :c])
...> { Dict.to_list(d1), 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], [] }
"""
@spec split(t, keys) :: {t, t}
@spec split(t, [key]) :: {t, t}
def split(dict, keys) do
target(dict).split(dict, keys)
end
@@ -406,89 +392,85 @@ defmodule Dict do
## Examples
iex> d = dict_impl.new([a: 1, b: 2])
...> d = Dict.drop(d, [:a, :c, :d])
...> Dict.to_list(d)
iex> d = Enum.into([a: 1, b: 2], dict_impl.new)
iex> d = Dict.drop(d, [:a, :c, :d])
iex> Dict.to_list(d)
[b: 2]
iex> d = dict_impl.new([a: 1, b: 2])
...> d = Dict.drop(d, [:c, :d])
...> Dict.to_list(d)
iex> d = Enum.into([a: 1, b: 2], dict_impl.new)
iex> d = Dict.drop(d, [:c, :d])
iex> Dict.to_list(d) |> Enum.sort
[a: 1, b: 2]
"""
@spec drop(t, keys) :: t
@spec drop(t, [key]) :: t
def drop(dict, keys) do
target(dict).drop(dict, keys)
end
@doc """
Returns a new dict where only the keys in `keys` from `dict` are
included.
Returns a new dict where only the keys in `keys` from `dict` are included.
Any non-member keys are ignored.
## Examples
iex> d = dict_impl.new([a: 1, b: 2])
...>
...> d = Dict.take(d, [:a, :c, :d])
...> Dict.to_list(d)
iex> d = Enum.into([a: 1, b: 2], dict_impl.new)
iex> d = Dict.take(d, [:a, :c, :d])
iex> Dict.to_list(d)
[a: 1]
...>
...> d = Dict.take(d, [:c, :d])
...> Dict.to_list(d)
iex> d = Dict.take(d, [:c, :d])
iex> Dict.to_list(d)
[]
"""
@spec take(t, keys) :: t
@spec take(t, [key]) :: t
def take(dict, keys) do
target(dict).take(dict, keys)
end
@doc """
Returns an empty dict of the same type as `dict`.
## Examples
iex> d = dict_impl.new([a: 1, b: 2])
...> e = Dict.empty(d)
...> Dict.to_list(e)
[]
"""
@doc false
@spec empty(t) :: t
def empty(dict) do
target(dict).empty(dict)
end
@doc """
Check if two dicts are equal using `===`. If the dicts are
of different types, they are first converted to lists.
Check if two dicts are equal using `===`.
Notice this function is polymorphic as it compares dicts of any
type. Each dict implementation also provides an `equal?` function,
but they can only compare dicts of the same type.
## Examples
iex> a = dict_impl.new(a: 2, b: 3, f: 5, c: 123)
...> b = ListDict.new(a: 2, b: 3, f: 5, c: 123)
...> Dict.equal?(a, b)
iex> a = Enum.into([a: 2, b: 3, f: 5, c: 123], dict_impl.new)
iex> b = [a: 2, b: 3, f: 5, c: 123]
iex> Dict.equal?(a, b)
true
iex> a = dict_impl.new(a: 2, b: 3, f: 5, c: 123)
...> b = []
...> Dict.equal?(a, b)
iex> a = Enum.into([a: 2, b: 3, f: 5, c: 123], dict_impl.new)
iex> b = []
iex> Dict.equal?(a, b)
false
"""
@spec equal?(t, t) :: boolean
def equal?(a, b) do
a_target = target(a)
b_target = target(b)
def equal?(dict1, dict2) do
target1 = target(dict1)
target2 = target(dict2)
cond do
a_target == b_target ->
a_target.equal?(a, b)
target1 == target2 ->
target1.equal?(dict1, dict2)
a_target.size(a) == b_target.size(b) ->
ListDict.equal?(a_target.to_list(a), b_target.to_list(b))
target1.size(dict1) == target2.size(dict2) ->
Enumerable.reduce(dict2, { :cont, true }, fn({ k, v }, _acc) ->
case target1.fetch(dict1, k) do
{ :ok, ^v } -> { :cont, true }
_ -> { :halt, false }
end
end) |> elem(1)
true ->
false
+25 -26
View File
@@ -1,5 +1,5 @@
defmodule Dict.Behaviour do
@moduledoc """
@moduledoc ~S"""
Default implementations for some required functions in the `Dict` module.
This module makes it easier to create your own `Dict` compliant
@@ -24,8 +24,8 @@ defmodule Dict.Behaviour do
* `size/1`
* `update/4`
All of which are part of the `Dict` behaviour, so no extra functions are
actually required.
All functions, except `reduce/3`, are required by the Dict behaviour.
`reduce/3` must be implemtented as per the Enumerable protocol.
Based on these functions, `Dict.Behaviour` generates default implementations
for the following functions:
@@ -50,7 +50,7 @@ defmodule Dict.Behaviour do
Note you can also test your custom module via `Dict`'s doctests:
defmodule MyDict do
def new(keywords // []) do
def new(keywords \\ []) do
...
end
end
@@ -63,10 +63,13 @@ defmodule Dict.Behaviour do
"""
defmacro __using__(_) do
# Use this import to guarantee proper code expansion
import Kernel, except: [size: 1]
quote do
@behaviour Dict
def get(dict, key, default // nil) do
def get(dict, key, default \\ nil) do
case fetch(dict, key) do
{ :ok, value } -> value
:error -> default
@@ -76,7 +79,7 @@ defmodule Dict.Behaviour do
def fetch!(dict, key) do
case fetch(dict, key) do
{ :ok, value } -> value
:error -> raise KeyError, key: key
:error -> raise KeyError, key: key, term: dict
end
end
@@ -88,21 +91,16 @@ defmodule Dict.Behaviour do
update(dict, key, value, fn(v) -> v end)
end
def drop(dict, []), do: dict
def drop(dict, [key|keys]) do
drop(delete(dict, key), keys)
def drop(dict, keys) do
Enum.reduce keys, dict, &delete(&2, &1)
end
def take(dict, keys) do
take(dict, keys, new)
end
defp take(_dict, [], acc), do: acc
defp take(dict, [key|keys], acc) do
case fetch(dict, key) do
{ :ok, value } -> take(dict, keys, put(acc, key, value))
:error -> take(dict, keys, acc)
Enum.reduce keys, new, fn key, acc ->
case fetch(dict, key) do
{ :ok, value } -> put(acc, key, value)
:error -> acc
end
end
end
@@ -125,24 +123,25 @@ defmodule Dict.Behaviour do
end
def equal?(dict1, dict2) do
# Use this import to avoid conflicts in the user code
import Kernel, except: [size: 1]
case size(dict1) == size(dict2) do
false -> false
true ->
reduce(dict1, { :cont, true }, fn({ k, v }, _acc) ->
unless fetch(dict2, k) == { :ok, v } do
{ :halt, false }
else
{ :cont, true }
case fetch(dict2, k) do
{ :ok, ^v } -> { :cont, true }
_ -> { :halt, false }
end
end) |> elem(1)
end
end
def merge(dict, enumerable, callback // fn(_k, _v1, v2) -> v2 end) do
Enum.reduce(enumerable, dict, fn({key, value}, acc) ->
update(acc, key, value, fn(v1) -> callback.(key, v1, value) end)
end)
def merge(dict1, dict2, fun \\ fn(_k, _v1, v2) -> v2 end) do
reduce(dict1, { :cont, dict2 }, fn { k, v1 }, acc ->
{ :cont, update(acc, k, v1, &fun.(k, v1, &1)) }
end) |> elem(1)
end
defoverridable merge: 2, merge: 3, equal?: 2, to_list: 1, keys: 1,
+214 -49
View File
@@ -144,7 +144,7 @@ defmodule Enum do
Some particular types, like dictionaries, yield a specific format on
enumeration. For dicts, the argument is always a `{ key, value }` tuple:
iex> dict = HashDict.new [a: 1, b: 2]
iex> dict = %{a: 1, b: 2}
iex> Enum.map(dict, fn { k, v } -> { k, v * 2 } end)
[a: 2, b: 4]
@@ -202,6 +202,7 @@ defmodule Enum do
iex> Enum.all?([1, 2, 3])
true
iex> Enum.all?([1, nil, 3])
false
@@ -209,7 +210,7 @@ defmodule Enum do
@spec all?(t) :: boolean
@spec all?(t, (element -> as_boolean(term))) :: boolean
def all?(collection, fun // fn(x) -> x end)
def all?(collection, fun \\ fn(x) -> x end)
def all?(collection, fun) when is_list(collection) do
do_all?(collection, fun)
@@ -238,6 +239,7 @@ defmodule Enum do
iex> Enum.any?([false, false, false])
false
iex> Enum.any?([false, true, false])
true
@@ -245,7 +247,7 @@ defmodule Enum do
@spec any?(t) :: boolean
@spec any?(t, (element -> as_boolean(term))) :: boolean
def any?(collection, fun // fn(x) -> x end)
def any?(collection, fun \\ fn(x) -> x end)
def any?(collection, fun) when is_list(collection) do
do_any?(collection, fun)
@@ -265,17 +267,20 @@ defmodule Enum do
iex> Enum.at([2, 4, 6], 0)
2
iex> Enum.at([2, 4, 6], 2)
6
iex> Enum.at([2, 4, 6], 4)
nil
iex> Enum.at([2, 4, 6], 4, :none)
:none
"""
@spec at(t, integer) :: element | nil
@spec at(t, integer, default) :: element | default
def at(collection, n, default // nil) do
def at(collection, n, default \\ nil) do
case fetch(collection, n) do
{ :ok, h } -> h
:error -> default
@@ -305,17 +310,20 @@ defmodule Enum do
iex> Enum.chunk([1, 2, 3, 4, 5, 6], 2)
[[1, 2], [3, 4], [5, 6]]
iex> Enum.chunk([1, 2, 3, 4, 5, 6], 3, 2)
[[1, 2, 3], [3, 4, 5]]
iex> Enum.chunk([1, 2, 3, 4, 5, 6], 3, 2, [7])
[[1, 2, 3], [3, 4, 5], [5, 6, 7]]
iex> Enum.chunk([1, 2, 3, 4, 5, 6], 3, 3, [])
[[1, 2, 3], [4, 5, 6]]
"""
@spec chunk(t, non_neg_integer, non_neg_integer) :: [list]
@spec chunk(t, non_neg_integer, non_neg_integer, t | nil) :: [list]
def chunk(coll, n, step, pad // nil) when n > 0 and step > 0 do
def chunk(coll, n, step, pad \\ nil) when n > 0 and step > 0 do
limit = :erlang.max(n, step)
{ _, { acc, { buffer, i } } } =
@@ -412,11 +420,6 @@ defmodule Enum do
def count(collection) do
case Enumerable.count(collection) do
value when is_integer(value) ->
IO.write "Expected #{inspect Enumerable.impl_for(collection)}.count/1 to return " <>
"{ :ok, boolean } if pre-calculated, otherwise { :error, module }, got " <>
"an integer\n#{Exception.format_stacktrace}"
value
{ :ok, value } when is_integer(value) ->
value
{ :error, module } ->
@@ -431,6 +434,7 @@ defmodule Enum do
`fun` returns `true`.
## Examples
iex> Enum.count([1, 2, 3, 4, 5], fn(x) -> rem(x, 2) == 0 end)
2
@@ -454,10 +458,13 @@ defmodule Enum do
iex> Enum.drop([1, 2, 3], 2)
[3]
iex> Enum.drop([1, 2, 3], 10)
[]
iex> Enum.drop([1, 2, 3], 0)
[1,2,3]
iex> Enum.drop([1, 2, 3], -1)
[1,2]
@@ -488,6 +495,7 @@ defmodule Enum do
iex> Enum.drop_while([1, 2, 3, 4, 5], fn(x) -> x < 3 end)
[3,4,5]
"""
@spec drop_while(t, (element -> as_boolean(term))) :: list
def drop_while(collection, fun) when is_list(collection) do
@@ -533,6 +541,7 @@ defmodule Enum do
iex> Enum.empty?([])
true
iex> Enum.empty?([1, 2, 3])
false
@@ -558,8 +567,10 @@ defmodule Enum do
iex> Enum.fetch([2, 4, 6], 0)
{ :ok, 2 }
iex> Enum.fetch([2, 4, 6], 2)
{ :ok, 6 }
iex> Enum.fetch([2, 4, 6], 4)
:error
@@ -626,7 +637,7 @@ defmodule Enum do
"""
@spec filter(t, (element -> as_boolean(term))) :: list
def filter(collection, fun) when is_list(collection) do
lc item inlist collection, fun.(item), do: item
for item <- collection, fun.(item), do: item
end
def filter(collection, fun) do
@@ -645,7 +656,7 @@ defmodule Enum do
"""
@spec filter_map(t, (element -> as_boolean(term)), (element -> element)) :: list
def filter_map(collection, filter, mapper) when is_list(collection) do
lc item inlist collection, filter.(item), do: mapper.(item)
for item <- collection, filter.(item), do: mapper.(item)
end
def filter_map(collection, filter, mapper) do
@@ -671,7 +682,7 @@ defmodule Enum do
"""
@spec find(t, (element -> any)) :: element | nil
@spec find(t, default, (element -> any)) :: element | default
def find(collection, ifnone // nil, fun)
def find(collection, ifnone \\ nil, fun)
def find(collection, ifnone, fun) when is_list(collection) do
do_find(collection, ifnone, fun)
@@ -698,7 +709,7 @@ defmodule Enum do
"""
@spec find_value(t, (element -> any)) :: any | :nil
@spec find_value(t, any, (element -> any)) :: any | :nil
def find_value(collection, ifnone // nil, fun)
def find_value(collection, ifnone \\ nil, fun)
def find_value(collection, ifnone, fun) when is_list(collection) do
do_find_value(collection, ifnone, fun)
@@ -741,20 +752,6 @@ defmodule Enum do
end
end
@doc false
@spec first(t) :: :nil | element
def first(list) when is_list(list) do
IO.write "Enum.first/1 is deprecated, please use Enum.at/2 or List.first/1 instead\n#{Exception.format_stacktrace}"
List.first(list)
end
def first(collection) do
IO.write "Enum.first/1 is deprecated, please use Enum.at/2 instead\n#{Exception.format_stacktrace}"
Enumerable.reduce(collection, { :cont, nil }, fn(entry, _) ->
{ :halt, entry }
end) |> elem(1)
end
@doc """
Returns a new collection appending the result of invoking `fun`
on each corresponding item of `collection`.
@@ -795,7 +792,7 @@ defmodule Enum do
{ [1,2,3], 3 }
"""
@spec flat_map_reduce(t, acc, fun) :: list when
@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
@@ -821,8 +818,10 @@ defmodule Enum do
iex> Enum.intersperse([1, 2, 3], 0)
[1, 0, 2, 0, 3]
iex> Enum.intersperse([1], 0)
[1]
iex> Enum.intersperse([], 0)
[]
@@ -840,6 +839,70 @@ defmodule Enum do
end
end
@doc """
Inserts the given enumerable into a collectable.
## Examples
iex> Enum.into([1, 2], [0])
[0, 1, 2]
iex> Enum.into([a: 1, b: 2], %{})
%{a: 1, b: 2}
"""
@spec into(Enumerable.t, Collectable.t) :: Collectable.t
def into(collection, list) when is_list(list) do
list ++ to_list(collection)
end
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)
into(collection, initial, fun, fn x, acc ->
fun.(acc, { :cont, x })
end)
end
@doc """
Inserts the given enumerable into a collectable
according to the transformation function.
## Examples
iex> Enum.into([2, 3], [3], fn x -> x * 3 end)
[3, 6, 9]
"""
@spec into(Enumerable.t, Collectable.t, (term -> term)) :: Collectable.t
def into(collection, list, transform) when is_list(list) and is_function(transform, 1) do
list ++ map(collection, transform)
end
def into(collection, collectable, transform) when is_function(transform, 1) do
{ initial, fun } = Collectable.into(collectable)
into(collection, initial, fun, fn x, acc ->
fun.(acc, { :cont, transform.(x) })
end)
end
defp into(collection, initial, fun, callback) do
try do
reduce(collection, initial, callback)
catch
kind, reason ->
stacktrace = System.stacktrace
fun.(initial, :halt)
:erlang.raise(kind, reason, stacktrace)
else
acc -> fun.(acc, :done)
end
end
@doc """
Joins the given `collection` according to `joiner`.
`joiner` can be either a binary or a list and the
@@ -854,13 +917,14 @@ defmodule Enum do
iex> Enum.join([1, 2, 3])
"123"
iex> Enum.join([1, 2, 3], " = ")
"1 = 2 = 3"
"""
@spec join(t) :: String.t
@spec join(t, String.t) :: String.t
def join(collection, joiner // "")
def join(collection, joiner \\ "")
def join(collection, joiner) when is_binary(joiner) do
reduce(collection, "", fn
@@ -886,7 +950,7 @@ defmodule Enum do
"""
@spec map(t, (element -> any)) :: list
def map(collection, fun) when is_list(collection) do
lc item inlist collection, do: fun.(item)
for item <- collection, do: fun.(item)
end
def map(collection, fun) do
@@ -907,13 +971,14 @@ defmodule Enum do
iex> Enum.map_join([1, 2, 3], &(&1 * 2))
"246"
iex> Enum.map_join([1, 2, 3], " = ", &(&1 * 2))
"2 = 4 = 6"
"""
@spec map_join(t, (element -> any)) :: String.t
@spec map_join(t, String.t, (element -> any)) :: String.t
def map_join(collection, joiner // "", mapper)
def map_join(collection, joiner \\ "", mapper)
def map_join(collection, joiner, mapper) when is_binary(joiner) do
reduce(collection, "", fn
@@ -1014,6 +1079,7 @@ defmodule Enum do
iex> Enum.member?(1..10, 5)
true
iex> Enum.member?([:a, :b, :c], :d)
false
@@ -1025,17 +1091,12 @@ defmodule Enum do
def member?(collection, value) do
case Enumerable.member?(collection, value) do
value when is_boolean(value) ->
IO.write "Expected #{inspect Enumerable.impl_for(collection)}.member?/2 to return " <>
"{ :ok, boolean } if faster than linear, otherwise { :error, __MODULE__ }, " <>
"got a boolean\n#{Exception.format_stacktrace}"
value
{ :ok, value } when is_boolean(value) ->
value
{ :error, module } ->
module.reduce(collection, { :cont, false }, fn
^value, _ -> { :halt, true }
_, _ -> { :cont, false }
v, _ when v === value -> { :halt, true }
_, _ -> { :cont, false }
end) |> elem(1)
end
end
@@ -1093,6 +1154,22 @@ defmodule Enum do
end
end
@doc """
Returns the sum of all values.
Raises `ArithmeticError` if collection contains a non-numeric value.
## Examples
iex> Enum.sum([1, 2, 3])
6
"""
@spec sum(t) :: number
def sum(collection) do
reduce(collection, 0, &+/2)
end
@doc """
Partitions `collection` into two collections, where the first one contains elements
for which `fun` returns a truthy value, and the second one -- for which `fun`
@@ -1118,6 +1195,27 @@ defmodule Enum do
{ :lists.reverse(acc1), :lists.reverse(acc2) }
end
@doc """
Splits `collection` into groups based on `fun`.
The result is a dict (by default a map) where each key is
a group and each value is a list of elements from `collection`
for which `fun` returned that group. Ordering is not necessarily
preserved.
## Examples
iex> Enum.group_by(~w{ant buffalo cat dingo}, &String.length/1)
%{ 3 => ["cat", "ant"], 7 => ["buffalo"], 5 => ["dingo"] }
"""
@spec group_by(t, (element -> any)) :: HashDict
def group_by(collection, dict \\ %{}, fun) do
reduce(collection, dict, fn(entry, categories) ->
Dict.update(categories, fun.(entry), [entry], &[entry|&1])
end)
end
@doc """
Invokes `fun` for each element in the collection passing that element and the
accumulator `acc` as arguments. `fun`'s return value is stored in `acc`.
@@ -1186,7 +1284,7 @@ defmodule Enum do
"""
@spec reject(t, (element -> as_boolean(term))) :: list
def reject(collection, fun) when is_list(collection) do
lc item inlist collection, !fun.(item), do: item
for item <- collection, !fun.(item), do: item
end
def reject(collection, fun) do
@@ -1282,9 +1380,9 @@ defmodule Enum do
## Examples
iex(1)> Enum.shuffle([1, 2, 3])
iex> Enum.shuffle([1, 2, 3])
[3, 2, 1]
iex(2)> Enum.shuffle([1, 2, 3])
iex> Enum.shuffle([1, 2, 3])
[3, 1, 2]
"""
@@ -1375,12 +1473,14 @@ defmodule Enum do
end
@doc """
Returns a sorted list of collection elements. Uses the merge sort algorithm.
Sorts the collection according to Elixir's term ordering.
Uses the merge sort algorithm.
## Examples
iex> Enum.sort([3, 2, 1])
[1,2,3]
[1, 2, 3]
"""
@spec sort(t) :: list
@@ -1393,14 +1493,27 @@ defmodule Enum do
end
@doc """
Returns a list of collection elements sorted by the given function.
Sorts the collection by the given function.
Uses the merge sort algorithm.
This function uses the merge sort algorithm. The given function
must return false if the first argument is less than right one.
## Examples
iex> Enum.sort([1, 2, 3], &(&1 > &2))
[3,2,1]
[3, 2, 1]
The sorting algorithm will be stable as long as the given function
returns true for values considered equal:
iex> Enum.sort ["some", "kind", "of", "monster"], &(byte_size(&1) <= byte_size(&2))
["of", "some", "kind", "monster"]
If the function does not return true, the sorting is not stable and
the order of equal terms may be shuffled:
iex> Enum.sort ["some", "kind", "of", "monster"], &(byte_size(&1) < byte_size(&2))
["of", "kind", "some", "monster"]
"""
@spec sort(t, (element, element -> boolean)) :: list
@@ -1426,12 +1539,16 @@ defmodule Enum do
iex> Enum.split([1, 2, 3], 2)
{ [1,2], [3] }
iex> Enum.split([1, 2, 3], 10)
{ [1,2,3], [] }
iex> Enum.split([1, 2, 3], 0)
{ [], [1,2,3] }
iex> Enum.split([1, 2, 3], -1)
{ [1,2], [3] }
iex> Enum.split([1, 2, 3], -5)
{ [], [1,2,3] }
@@ -1465,6 +1582,7 @@ defmodule Enum do
iex> Enum.split_while([1, 2, 3, 4], fn(x) -> x < 3 end)
{ [1, 2], [3, 4] }
"""
@spec split_while(t, (element -> as_boolean(term))) :: {list, list}
def split_while(collection, fun) when is_list(collection) do
@@ -1495,10 +1613,13 @@ defmodule Enum do
iex> Enum.take([1, 2, 3], 2)
[1,2]
iex> Enum.take([1, 2, 3], 10)
[1,2,3]
iex> Enum.take([1, 2, 3], 0)
[]
iex> Enum.take([1, 2, 3], -1)
[3]
@@ -1577,7 +1698,7 @@ defmodule Enum do
"""
@spec to_list(t) :: [term]
def to_list(collection) when is_list collection do
def to_list(collection) when is_list(collection) do
collection
end
@@ -1585,6 +1706,27 @@ defmodule Enum do
reverse(collection) |> :lists.reverse
end
@doc """
Traverses the given enumerable keeping its shape.
It also expects the enumerable to implement the `Collectable` protocol.
## Examples
iex> Enum.traverse(%{a: 1, b: 2}, fn { k, v } -> { k, v * 2 } end)
%{a: 2, b: 4}
"""
@spec traverse(Enumerable.t, (term -> term)) :: Collectable.t
def traverse(collection, transform) when is_list(collection) do
:lists.map(transform, collection)
end
def traverse(collection, transform) do
into(collection, Collectable.empty(collection), transform)
end
@doc """
Enumerates the collection, removing all duplicated items.
@@ -1599,7 +1741,7 @@ defmodule Enum do
"""
@spec uniq(t) :: list
@spec uniq(t, (element -> term)) :: list
def uniq(collection, fun // fn x -> x end)
def uniq(collection, fun \\ fn x -> x end)
def uniq(collection, fun) when is_list(collection) do
do_uniq(collection, [], fun)
@@ -2009,6 +2151,29 @@ defimpl Enumerable, for: List do
do: { :error, __MODULE__ }
end
defimpl Enumerable, for: Map do
def reduce(map, acc, fun) 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)
def member?(map, { key, value }) do
{ :ok, match?({ :ok, ^value }, :maps.find(key, map)) }
end
def member?(_map, _other) do
{ :ok, false }
end
def count(map) do
{ :ok, map_size(map) }
end
end
defimpl Enumerable, for: Function do
def reduce(function, acc, fun),
do: function.(acc, fun)
+111 -99
View File
@@ -10,53 +10,66 @@ defexception SystemLimitError, message: "a system limit has been reached"
defexception SyntaxError, [file: nil, line: nil, description: "syntax error"] do
def message(exception) do
Exception.format_file_line(Path.relative_to_cwd(exception.file), exception.line) <>
exception.description
" " <> exception.description
end
end
defexception TokenMissingError, [file: nil, line: nil, description: "expression is incomplete"] do
def message(exception) do
Exception.format_file_line(Path.relative_to_cwd(exception.file), exception.line) <>
exception.description
" " <> exception.description
end
end
defexception CompileError, [file: nil, line: nil, description: "compile error"] do
def message(exception) do
Exception.format_file_line(Path.relative_to_cwd(exception.file), exception.line) <>
exception.description
" " <> exception.description
end
end
defexception BadFunctionError, [actual: nil] do
defexception BadFunctionError, [term: nil] do
def message(exception) do
"expected a function, got: #{inspect(exception.actual)}"
"expected a function, got: #{inspect(exception.term)}"
end
end
defexception MatchError, [actual: nil] do
defexception BadStructError, [struct: nil, term: nil] do
def message(exception) do
"no match of right hand side value: #{inspect(exception.actual)}"
"expected a struct named #{inspect(exception.struct)}, got: #{inspect(exception.term)}"
end
end
defexception CaseClauseError, [actual: nil] do
defexception MatchError, [term: nil] do
def message(exception) do
"no case clause matching: #{inspect(exception.actual)}"
"no match of right hand side value: #{inspect(exception.term)}"
end
end
defexception TryClauseError, [actual: nil] do
defexception CaseClauseError, [term: nil] do
def message(exception) do
"no try clause matching: #{inspect(exception.actual)}"
"no case clause matching: #{inspect(exception.term)}"
end
end
defexception TryClauseError, [term: nil] do
def message(exception) do
"no try clause matching: #{inspect(exception.term)}"
end
end
defexception BadArityError, [function: nil, args: nil] do
def message(exception) do
args = Enum.map_join(exception.args, ", ", &inspect/1)
"bad arity error: #{inspect(exception.function)} called with (#{args})"
fun = exception.function
args = exception.args
insp = Enum.map_join(args, ", ", &inspect/1)
{ :arity, arity } = :erlang.fun_info(fun, :arity)
"#{inspect(fun)} with arity #{arity} called with #{count(length(args), insp)}"
end
defp count(0, _insp), do: "no arguments"
defp count(1, insp), do: "1 argument (#{insp})"
defp count(x, insp), do: "#{x} arguments (#{insp})"
end
defexception UndefinedFunctionError, [module: nil, function: nil, arity: nil] do
@@ -98,9 +111,9 @@ defexception ErlangError, [original: nil] do
end
end
defexception KeyError, key: nil do
defexception KeyError, key: nil, term: nil do
def message(exception) do
"key not found: #{inspect exception.key}"
"key #{inspect exception.key} not found in: #{inspect exception.term}"
end
end
@@ -126,22 +139,30 @@ end
defmodule Exception do
@moduledoc """
Convenience functions to work with and pretty print
exceptions and stacktraces.
Functions to work with and pretty print exceptions.
Notice that stacktraces in Elixir are updated on errors.
For example, at any given moement, `System.stacktrace`
will return the stacktrace for the last error that ocurred
in the current process.
Note that stacktraces in Elixir are updated on throw,
errors and exits. For example, at any given moment,
`System.stacktrace` will return the stacktrace for the
last throw/error/exit that ocurred in the current process.
That said, many of the functions in this module will
automatically calculate the stacktrace based on the caller,
when invoked without arguments, changing the value of
`System.stacktrace`. If instead you want to format the
stacktrace of the latest error, you should instead explicitly
pass the `System.stacktrace` as argument.
Finally note developers should not rely on the particular
format of the `format` functions provided by this module.
They may be changed in future releases in order to better
suit Elixir's tool chain. In other words, by using the
functions in this module it is guarantee you will format
exceptions as in the current Elixir version being used.
"""
@type stacktrace :: [stacktrace_entry]
@type stacktrace_entry ::
{ module, function, arity_or_args, location } |
{ function, arity_or_args, location }
@typep arity_or_args :: non_neg_integer | list
@typep location :: Keyword.t
@doc """
Normalizes an exception, converting Erlang exceptions
to Elixir exceptions.
@@ -150,72 +171,77 @@ defmodule Exception do
normalizes only `:error`, returning the untouched payload
for others.
"""
def normalize(:error, exception), do: normalize(exception)
def normalize(_kind, other), do: other
def normalize(:error, exception), do: normalize_error(exception)
def normalize(_kind, payload), do: payload
@doc """
Normalizes an exception, converting Erlang exceptions
to Elixir exceptions.
@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)
end
Useful when interfacing Erlang code with Elixir code.
"""
def normalize(exception) when is_exception(exception) do
defp normalize_error(exception) when is_exception(exception) do
exception
end
def normalize(:badarg) do
defp normalize_error(:badarg) do
ArgumentError[]
end
def normalize(:badarith) do
defp normalize_error(:badarith) do
ArithmeticError[]
end
def normalize(:system_limit) do
defp normalize_error(:system_limit) do
SystemLimitError[]
end
def normalize({ :badarity, { fun, args } }) do
defp normalize_error({ :badarity, { fun, args } }) do
BadArityError[function: fun, args: args]
end
def normalize({ :badfun, actual }) do
BadFunctionError[actual: actual]
defp normalize_error({ :badfun, term }) do
BadFunctionError[term: term]
end
def normalize({ :badmatch, actual }) do
MatchError[actual: actual]
defp normalize_error({ :badstruct, struct, term }) do
BadStructError[struct: struct, term: term]
end
def normalize({ :case_clause, actual }) do
CaseClauseError[actual: actual]
defp normalize_error({ :badmatch, term }) do
MatchError[term: term]
end
def normalize({ :try_clause, actual }) do
TryClauseError[actual: actual]
defp normalize_error({ :case_clause, term }) do
CaseClauseError[term: term]
end
def normalize(:undef) do
defp normalize_error({ :try_clause, term }) do
TryClauseError[term: term]
end
defp normalize_error(:undef) do
{ mod, fun, arity } = from_stacktrace(:erlang.get_stacktrace)
UndefinedFunctionError[module: mod, function: fun, arity: arity]
end
def normalize(:function_clause) do
defp normalize_error(:function_clause) do
{ mod, fun, arity } = from_stacktrace(:erlang.get_stacktrace)
FunctionClauseError[module: mod, function: fun, arity: arity]
end
def normalize({ :badarg, payload }) do
defp normalize_error({ :badarg, payload }) do
ArgumentError[message: "argument error: #{inspect(payload)}"]
end
def normalize(other) do
defp normalize_error(other) do
ErlangError[original: other]
end
@doc """
Receives a tuple representing a stacktrace entry and formats it.
Receives a stacktrace entry and formats it into a string.
"""
@spec format_stacktrace_entry(stacktrace_entry) :: String.t
def format_stacktrace_entry(entry)
# From Macro.Env.stacktrace
@@ -233,11 +259,11 @@ defmodule Exception 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
@@ -255,43 +281,18 @@ defmodule Exception do
calculates a new stacktrace based on the caller and formats it. As
a consequence, the value of `System.stacktrace` is changed.
"""
def format_stacktrace(trace // nil) do
def format_stacktrace(trace \\ nil) do
trace = trace || try do
throw(:stacktrace)
catch
:stacktrace -> Enum.drop(:erlang.get_stacktrace, 1)
end
case trace do
[] -> "\n"
s -> " " <> Enum.map_join(s, "\n ", &format_stacktrace_entry(&1)) <> "\n"
end
end
@doc """
Formats the caller, i.e. the first entry in 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.
Notice that due to tail call optimization, the stacktrace
may not report the direct caller of the function.
"""
def format_caller(trace // nil) do
trace = trace || try do
throw(:stacktrace)
catch
:stacktrace -> Enum.drop(:erlang.get_stacktrace, 1)
end
if entry = Enum.at(trace, 1) do
format_stacktrace_entry(entry)
else
"nofile:0: "
end
end
@doc """
Receives an anonymous function and arity and formats it as
shown in stacktraces. The arity may also be a list of arguments.
@@ -303,12 +304,7 @@ defmodule Exception do
"""
def format_fa(fun, arity) do
if is_list(arity) do
inspected = lc x inlist arity, do: inspect(x)
"#{inspect fun}(#{Enum.join(inspected, ", ")})"
else
"#{inspect fun}/#{arity}"
end
"#{inspect fun}#{format_arity(arity)}"
end
@doc """
@@ -320,27 +316,39 @@ defmodule Exception do
iex> Exception.format_mfa Foo, :bar, 1
"Foo.bar/1"
iex> Exception.format_mfa Foo, :bar, []
"Foo.bar()"
iex> Exception.format_mfa nil, :bar, []
"nil.bar()"
Anonymous functions are reported as -func/arity-anonfn-count-,
where func is the name of the enclosing function. Convert to
"anonymous fn in func/arity"
"""
def format_mfa(module, fun, arity) do
def format_mfa(module, fun, arity) when is_atom(fun) do
fun =
case inspect(fun) do
<< ?:, erl :: binary >> -> erl
elixir -> elixir
":" <> fun -> fun
fun -> fun
end
if is_list(arity) do
inspected = lc x inlist arity, do: inspect(x)
"#{inspect module}.#{fun}(#{Enum.join(inspected, ", ")})"
else
"#{inspect module}.#{fun}/#{arity}"
case match?("\"-" <> _, fun) and String.split(fun, "-") do
[ "\"", outer_fun, "fun", _count, "\"" ] ->
"anonymous fn#{format_arity(arity)} in #{inspect module}.#{outer_fun}"
_ ->
"#{inspect module}.#{fun}#{format_arity(arity)}"
end
end
defp format_arity(arity) when is_list(arity) do
inspected = for x <- arity, do: inspect(x)
"(#{Enum.join(inspected, ", ")})"
end
defp format_arity(arity), do: "/#{arity}"
@doc """
Formats the given file and line as shown in stacktraces.
If any of the values are nil, they are omitted.
@@ -348,21 +356,25 @@ defmodule Exception do
## Examples
iex> Exception.format_file_line("foo", 1)
"foo:1: "
"foo:1:"
iex> Exception.format_file_line("foo", nil)
"foo: "
"foo:"
iex> Exception.format_file_line(nil, nil)
""
"""
def format_file_line(file, line) do
format_file_line(file, line, "")
end
defp format_file_line(file, line, suffix) do
if file do
if line && line != 0 do
"#{file}:#{line}: "
"#{file}:#{line}:#{suffix}"
else
"#{file}: "
"#{file}:#{suffix}"
end
else
""
@@ -370,7 +382,7 @@ defmodule Exception do
end
defp format_location(opts) do
format_file_line Keyword.get(opts, :file), Keyword.get(opts, :line)
format_file_line Keyword.get(opts, :file), Keyword.get(opts, :line), " "
end
defp from_stacktrace([{ module, function, args, _ }|_]) when is_list(args) do
+199 -131
View File
@@ -1,3 +1,5 @@
require Record
defrecord File.Stat, Record.extract(:file_info, from_lib: "kernel/include/file.hrl") do
@moduledoc """
A record responsible to hold file information. Its fields are:
@@ -31,18 +33,99 @@ defrecord File.Stat, Record.extract(:file_info, from_lib: "kernel/include/file.h
"""
end
defmodule File do
defmodule File.Stream do
@moduledoc """
Defines a `File.Stream` struct returned by `File.stream!/2`.
The following fields are public:
* `path` - the file path
* `modes` - the file modes
* `raw` - a boolean indicating if bin functions should be used
* `line_or_bytes` - if reading should read lines or a given amount of bytes
"""
defstruct path: nil, modes: [], line_or_bytes: :line, raw: true
defimpl Collectable do
def empty(stream) do
stream
end
def into(%{ path: path, modes: modes, raw: raw } = stream) do
modes = for mode <- modes, not mode in [:read], do: mode
case :file.open(path, [:write|modes]) do
{ :ok, device } ->
{ :ok, into(device, stream, raw) }
{ :error, reason } ->
raise File.Error, reason: reason, action: "stream", path: path
end
end
defp into(device, stream, raw) do
fn
:ok, { :cont, x } ->
case raw do
true -> IO.binwrite(device, x)
false -> IO.write(device, x)
end
:ok, :done ->
:file.close(device)
stream
:ok, :halt ->
:file.close(device)
end
end
end
defimpl Enumerable do
def reduce(%{ path: path, modes: modes, line_or_bytes: line_or_bytes, raw: raw }, acc, fun) do
modes = for mode <- modes, not mode in [:write, :append], do: mode
start_fun =
fn ->
case :file.open(path, modes) do
{ :ok, device } -> device
{ :error, reason } ->
raise File.Error, reason: reason, action: "stream", path: path
end
end
next_fun =
case raw do
true -> &IO.each_binstream(&1, line_or_bytes)
false -> &IO.each_stream(&1, line_or_bytes)
end
Stream.resource(start_fun, next_fun, &:file.close/1).(acc, fun)
end
def count(_stream) do
{ :error, __MODULE__ }
end
def member?(_stream, _term) do
{ :error, __MODULE__ }
end
end
end
defmodule File do
@moduledoc ~S"""
This module contains functions to manipulate files.
Some of those functions are low-level, allowing the user
to interact with the file or IO devices, like `open/2`,
`copy/3` and others. This module also provides higher
level functions that works with filenames and have their naming
level functions that work with filenames and have their naming
based on UNIX variants. For example, one can copy a file
via `cp/3` and remove files and directories recursively
via `rm_rf/1`
## Encoding
In order to write and read files, one must use the functions
in the `IO` module. By default, a file is opened in binary mode
which requires the functions `IO.binread/2` and `IO.binwrite/2`
@@ -51,6 +134,8 @@ defmodule File do
`IO.write/2` functions must be used as they are responsible for
doing the proper conversions and data guarantees.
## API
Most of the functions in this module return `:ok` or
`{ :ok, result }` in case of success, `{ :error, reason }`
otherwise. Those function are also followed by a variant
@@ -74,6 +159,23 @@ defmodule File do
to react if the file does not exist. The latter should be used
when the developer expects his software to fail in case the
file cannot be read (i.e. it is literally an exception).
## Processes and raw files
Every time a file is opened, Elixir spawns a new process. Writing
to a file is equivalent to sending messages to that process that
writes to the file descriptor.
This means files can be passed between nodes and message passing
guarantees they can write to the same file in a network.
However, you may not always want to pay the price for this abstraction.
In such cases, a file can be opened in `:raw` mode. The options `:read_ahead`
and `:delayed_write` are also useful when operating large files or
working with files in tight loops.
Check http://www.erlang.org/doc/man/file.html#open-2 for more information
about such options and other performance considerations.
"""
alias :file, as: F
@@ -209,7 +311,7 @@ defmodule File do
@doc """
Returns information about the `path`. If it exists, it
returns a `{ :ok, info }` tuple, where info is a
`File.Info` record. Retuns `{ :error, reason }` with
`File.Stat` record. Returns `{ :error, reason }` with
the same reasons as `read/1` if a failure occurs.
## Options
@@ -220,7 +322,7 @@ defmodule File do
Default is `:local`.
"""
def stat(path, opts // []) do
def stat(path, opts \\ []) do
case F.read_file_info(path, opts) do
{:ok, fileinfo} ->
{:ok, set_elem(fileinfo, 0, File.Stat) }
@@ -233,7 +335,7 @@ defmodule File do
Same as `stat/2` but returns the `File.Stat` directly and
throws `File.Error` if an error is returned.
"""
def stat!(path, opts // []) do
def stat!(path, opts \\ []) do
case stat(path, opts) do
{:ok, info} -> info
{:error, reason} ->
@@ -245,7 +347,7 @@ defmodule File do
Writes the given `File.Stat` back to the filesystem at the given
path. Returns `:ok` or `{ :error, reason }`.
"""
def write_stat(path, File.Stat[] = stat, opts // []) do
def write_stat(path, File.Stat[] = stat, opts \\ []) do
F.write_file_info(path, set_elem(stat, 0, :file_info), opts)
end
@@ -253,7 +355,7 @@ defmodule File do
Same as `write_stat/3` but raises an exception if it fails.
Returns `:ok` otherwise.
"""
def write_stat!(path, File.Stat[] = stat, opts // []) do
def write_stat!(path, File.Stat[] = stat, opts \\ []) do
case write_stat(path, stat, opts) do
:ok -> :ok
{ :error, reason } ->
@@ -265,7 +367,7 @@ defmodule File do
Updates modification time (mtime) and access time (atime) of
the given file. File is created if it doesn’t exist.
"""
def touch(path, time // :calendar.local_time) do
def touch(path, time \\ :calendar.local_time) do
case F.change_time(path, time) do
{ :error, :enoent } ->
write(path, "")
@@ -279,7 +381,7 @@ defmodule File do
Same as `touch/2` but raises an exception if it fails.
Returns `:ok` otherwise.
"""
def touch!(path, time // :calendar.local_time) do
def touch!(path, time \\ :calendar.local_time) do
case touch(path, time) do
:ok -> :ok
{ :error, reason } ->
@@ -309,7 +411,7 @@ defmodule File do
Typical error reasons are the same as in `open/2`,
`read/1` and `write/3`.
"""
def copy(source, destination, bytes_count // :infinity) do
def copy(source, destination, bytes_count \\ :infinity) do
F.copy(source, destination, bytes_count)
end
@@ -317,7 +419,7 @@ defmodule File do
The same as `copy/3` but raises an `File.CopyError` if it fails.
Returns the `bytes_copied` otherwise.
"""
def copy!(source, destination, bytes_count // :infinity) do
def copy!(source, destination, bytes_count \\ :infinity) do
case copy(source, destination, bytes_count) do
{ :ok, bytes_count } -> bytes_count
{ :error, reason } ->
@@ -329,11 +431,6 @@ defmodule File do
@doc """
Copies the contents in `source` to `destination` preserving its mode.
Similar to the command `cp` in Unix systems, this function
behaves differently depending if `destination` is a directory
or not. In particular, if `destination` is a directory, it
copies the contents of `source` to `destination/source`.
If a file already exists in the destination, it invokes a
callback which should return `true` if the existing file
should be overwritten, `false` otherwise. It defaults to return `true`.
@@ -344,17 +441,14 @@ defmodule File do
If you want to copy contents from an io device to another device
or do a straight copy from a source to a destination without
preserving modes, check `copy/3` instead.
"""
def cp(source, destination, callback // fn(_, _) -> true end) do
output =
if dir?(destination) do
mkdir(destination)
FN.join(destination, FN.basename(source))
else
destination
end
case do_cp_file(source, output, callback, []) do
Note: The command `cp` in Unix systems behaves differently depending
if `destination` is an existing directory or not. We have chosen to
explicitly disallow this behaviour. If destination is a directory, an
error will be returned.
"""
def cp(source, destination, callback \\ fn(_, _) -> true end) do
case do_cp_file(source, destination, callback, []) do
{ :error, reason, _ } -> { :error, reason }
_ -> :ok
end
@@ -364,7 +458,7 @@ defmodule File do
The same as `cp/3`, but raises `File.CopyError` if it fails.
Returns the list of copied files otherwise.
"""
def cp!(source, destination, callback // fn(_, _) -> true end) do
def cp!(source, destination, callback \\ fn(_, _) -> true end) do
case cp(source, destination, callback) do
:ok -> :ok
{ :error, reason } ->
@@ -374,16 +468,12 @@ defmodule File do
end
end
@doc %S"""
@doc ~S"""
Copies the contents in source to destination.
Similar to the command `cp -r` in Unix systems,
this function behaves differently depending
if `source` and `destination` are a file or a directory.
If both are files, it simply copies `source` to
`destination`. However, if `destination` is a directory,
it copies the contents of `source` to `destination/source`
recursively.
If the source is a file, it copies `source` to
`destination`. If the source is a directory, it copies
the contents inside source into the destination.
If a file already exists in the destination,
it invokes a callback which should return
@@ -403,33 +493,26 @@ defmodule File do
success with all files and directories copied in no
specific order, `{ :error, reason }` otherwise.
Note: The command `cp` in Unix systems behaves differently
depending if `destination` is an existing directory or not.
We have chosen to explicitly disallow this behaviour.
## Examples
# Copies "a.txt" to "tmp/a.txt"
File.cp_r "a.txt", "tmp"
# Copies all files in "samples" to "tmp/samples"
File.cp_r "samples", "tmp"
# Copies "a.txt" to "tmp"
File.cp_r "a.txt", "tmp.txt"
# Copies all files in "samples" to "tmp"
File.cp_r "samples/.", "tmp"
File.cp_r "samples", "tmp"
# Same as before, but asks the user how to proceed in case of conflicts
File.cp_r "samples/.", "tmp", fn(source, destination) ->
File.cp_r "samples", "tmp", fn(source, destination) ->
IO.gets("Overwriting #{destination} by #{source}. Type y to confirm.") == "y"
end
"""
def cp_r(source, destination, callback // fn(_, _) -> true end) when is_function(callback) do
output =
if dir?(destination) || dir?(source) do
mkdir(destination)
FN.join(destination, FN.basename(source))
else
destination
end
case do_cp_r(source, output, callback, []) do
def cp_r(source, destination, callback \\ fn(_, _) -> true end) when is_function(callback) do
case do_cp_r(source, destination, callback, []) do
{ :error, _, _ } = error -> error
res -> { :ok, res }
end
@@ -439,7 +522,7 @@ defmodule File do
The same as `cp_r/3`, but raises `File.CopyError` if it fails.
Returns the list of copied files otherwise.
"""
def cp_r!(source, destination, callback // fn(_, _) -> true end) do
def cp_r!(source, destination, callback \\ fn(_, _) -> true end) do
case cp_r(source, destination, callback) do
{ :ok, files } -> files
{ :error, reason, file } ->
@@ -532,9 +615,17 @@ defmodule File do
end
@doc """
Writes `content` to the file `path`. The file is created if it
does not exist. If it exists, the previous contents are overwritten.
Returns `:ok` if successful, or `{:error, reason}` if an error occurs.
Writes `content` to the file `path`.
The file is created if it does not exist. If it exists, the previous
contents are overwritten. Returns `:ok` if successful, or `{:error, reason}`
if an error occurs.
**Warning:** Every time this function is invoked, a file descriptor is opened
and a new process is spawned to write to the file. For this reason, if you are
doing multiple writes in a loop, opening the file via `File.open/2` and using
the functions in `IO` to write to the file will yield much better performance
then calling this function multiple times.
Typical error reasons are:
@@ -545,16 +636,17 @@ defmodule File do
* :eacces - Missing permission for writing the file or searching one of the parent directories.
* :eisdir - The named file is a directory.
Check `File.open/2` for existing modes.
The writing is automatically done in `:raw` mode. Check
`File.open/2` for other available options.
"""
def write(path, content, modes // []) do
F.write_file(path, content, modes)
def write(path, content, modes \\ []) do
F.write_file(path, content, [:raw|modes])
end
@doc """
Same as `write/3` but raises an exception if it fails, returns `:ok` otherwise.
"""
def write!(path, content, modes // []) do
def write!(path, content, modes \\ []) do
case F.write_file(path, content, modes) do
:ok -> :ok
{ :error, reason } ->
@@ -729,7 +821,7 @@ defmodule File do
end
end
@doc """
@doc ~S"""
Opens the given `path` according to the given list of modes.
In order to write and read files, one must use the functions
@@ -752,7 +844,7 @@ defmodule File do
* `:exclusive` - The file, when opened for writing, is created if it does not exist.
If the file exists, open will return { :error, :eexist }.
* `:charlist` - When this term is given, read operations on the file will return char lists rather than binaries;
* `:char_list` - When this term is given, read operations on the file will return char lists rather than binaries;
* `:compressed` - Makes it possible to read or write gzip compressed files.
The compressed option must be combined with either read or write, but not both.
@@ -765,10 +857,8 @@ defmodule File do
or if data is read by a function that returns data in a format that cannot cope
with the character range of the data, an error occurs and the file will be closed.
If a function is given two modes (instead of a list), it dispatches to `open/3`.
Check http://www.erlang.org/doc/man/file.html#open-2 for more information about
other options like `read_ahead` and `delayed_write`.
other options like `:read_ahead` and `:delayed_write`.
This function returns:
@@ -788,7 +878,7 @@ defmodule File do
File.close(file)
"""
def open(path, modes // [])
def open(path, modes \\ [])
def open(path, modes) when is_list(modes) do
F.open(path, open_defaults(modes, true))
@@ -808,7 +898,7 @@ defmodule File do
It returns `{ :ok, function_result }` in case of success,
`{ :error, reason }` otherwise.
Do not use this function with :delayed_write option
Do not use this function with `:delayed_write` option
since automatically closing the file may fail
(as writes are delayed).
@@ -835,7 +925,7 @@ defmodule File do
Same as `open/2` but raises an error if file could not be opened.
Returns the `io_device` otherwise.
"""
def open!(path, modes // []) do
def open!(path, modes \\ []) do
case open(path, modes) do
{ :ok, device } -> device
{ :error, reason } ->
@@ -922,7 +1012,7 @@ defmodule File do
It returns `{ :ok, [files] }` in case of success,
`{ :error, reason }` otherwise.
"""
def ls(path // ".") do
def ls(path \\ ".") do
case F.list_dir(path) do
{ :ok, file_list } -> { :ok, Enum.map(file_list, &to_string(&1)) }
{ :error, _ } = error -> error
@@ -933,7 +1023,7 @@ defmodule File do
The same as `ls/1` but raises `File.Error`
in case of an error.
"""
def ls!(dir // ".") do
def ls!(dir \\ ".") do
case ls(dir) do
{ :ok, value } -> value
{ :error, reason } ->
@@ -954,74 +1044,46 @@ defmodule File do
end
@doc """
Opens the file at the given `path` with the given `modes` and
returns a stream for each `:line` (default) or for a given number
of bytes given by `line_or_bytes`.
Returns a `File.Stream` for the given `path` with the given `modes`.
The returned stream will fail for the same reasons as
`File.open!/2`. Note that the file is opened only and every time
streaming begins.
The stream implements both `Enumerable` and `Collectable` protocols,
which means it can be used both for read and write.
Note that stream by default uses `IO.binread/2` unless
the file is opened with an encoding, then the slower `IO.read/2`
is used to do the proper data conversion and guarantees.
The `line_or_byte` argument configures how the file is read when
streaming, by `:line` (default) or by a given number of bytes.
Operating the stream can fail on open for the same reasons as
`File.open!/2`. Note that the file is automatically opened only and
every time streaming begins. There is no need to pass `:read` and
`:write` modes, as those are automatically set by Elixir.
## Raw files
Since Elixir controls when the streamed file is opened, the underlying
device cannot be shared and as such it is convenient to open the file
in raw mode for performance reasons. Therefore, Elixir **will** open
streams in `:raw` mode with the `:read_ahead` option unless an encoding
is specified.
One may also consider passing the `:delayed_write` option if the stream
is meant to be written to under a tight loop.
"""
def stream!(path, modes // [], line_or_bytes // :line) do
def stream!(path, modes \\ [], line_or_bytes \\ :line) do
modes = open_defaults(modes, true)
bin = nil? List.keyfind(modes, :encoding, 0)
raw = :lists.keyfind(:encoding, 1, modes) == false
start_fun =
fn ->
case F.open(path, modes) do
{ :ok, device } -> device
{ :error, reason } ->
raise File.Error, reason: reason, action: "stream", path: to_string(path)
modes =
if raw do
if :lists.keyfind(:read_ahead, 1, modes) do
[:raw|modes]
else
[:raw, :read_ahead|modes]
end
else
modes
end
next_fun =
case bin do
true -> &IO.do_binstream(&1, line_or_bytes)
false -> &IO.do_stream(&1, line_or_bytes)
end
Stream.resource(start_fun, next_fun, &F.close/1)
end
@doc """
Receives a stream and returns a new stream that will open the file
at the given `path` for write with the extra `modes` and write
each value to the file.
The returned stream will fail for the same reasons as
`File.open!/2`. Note that the file is opened only and every time
streaming begins.
Note that stream by default uses `IO.binwrite/2` unless
the file is opened with an encoding, then the slower `IO.write/2`
is used to do the proper data conversion and guarantees.
"""
def stream_to!(stream, path, modes // []) do
modes = open_defaults([:write|List.delete(modes, :write)], true)
bin = nil? List.keyfind(modes, :encoding, 0)
fn acc, f ->
case F.open(path, modes) do
{ :ok, device } ->
each =
case bin do
true -> &IO.binwrite(device, &1)
false -> &IO.write(device, &1)
end
stream
|> Stream.each(each)
|> Stream.after(fn -> F.close(device) end)
|> Enumerable.Stream.Lazy.reduce(acc, f)
{ :error, reason } ->
raise File.Error, reason: reason, action: "stream_to", path: to_string(path)
end
end
%File.Stream{ path: to_string(path), modes: modes, raw: raw, line_or_bytes: line_or_bytes }
end
@doc """
@@ -1086,7 +1148,9 @@ defmodule File do
## Helpers
defp open_defaults([:charlist|t], _add_binary) do
@read_ahead 64*1024
defp open_defaults([:char_list|t], _add_binary) do
open_defaults(t, false)
end
@@ -1094,6 +1158,10 @@ defmodule File do
open_defaults([{ :encoding, :utf8 }|t], add_binary)
end
defp open_defaults([:read_ahead|t], add_binary) do
open_defaults([{ :read_ahead, @read_ahead }|t], add_binary)
end
defp open_defaults([h|t], add_binary) do
[h|open_defaults(t, add_binary)]
end
+14 -4
View File
@@ -13,10 +13,13 @@ defmodule Float do
iex> Float.parse("34")
{34.0,""}
iex> Float.parse("34.25")
{34.25,""}
iex> Float.parse("56.5xyz")
{56.5,"xyz"}
iex> Float.parse("pi")
:error
@@ -72,7 +75,7 @@ defmodule Float do
{ floatify(int, float, decimal), bitstring }
end
defp floatify(int, float, decimal, exponential // 0) do
defp floatify(int, float, decimal, exponential \\ 0) do
multiplier = if int < 0, do: -1.0, else: 1.0
# Try to ensure the minimum amount of rounding errors
@@ -94,14 +97,16 @@ defmodule Float do
end
@doc """
Round a float to the largest integer less than or equal to `num`
Rounds a float to the largest integer less than or equal to `num`.
## Examples
iex> Float.floor(34)
34
iex> Float.floor(34.25)
34
iex> Float.floor(-56.5)
-57
@@ -117,14 +122,16 @@ defmodule Float do
end
@doc """
Round a float to the largest integer greater than or equal to `num`
Rounds a float to the largest integer greater than or equal to `num`.
## Examples
iex> Float.ceil(34)
34
iex> Float.ceil(34.25)
35
iex> Float.ceil(-56.5)
-56
@@ -147,16 +154,19 @@ defmodule Float do
iex> Float.round(5.5674, 3)
5.567
iex> Float.round(5.5675, 3)
5.568
iex> Float.round(-5.5674, 3)
-5.567
iex> Float.round(-5.5675, 3)
-5.568
"""
@spec round(float, integer) :: float
def round(number, precision) when is_float(number) and is_integer(precision) and precision in 0..15 do
Kernel.round(number * :math.pow(10, precision)) / :math.pow(10, precision)
end
end
-200
View File
@@ -1,200 +0,0 @@
defmodule GenFSM.Behaviour do
@moduledoc """
This module is a convenience for defining GenFSM callbacks in Elixir.
A finite state machine (FSM) is responsible for reacting to events received;
GenFSM is an OTP behaviour that encapsulates common FSM
functionalities.
## Example
Below is an example of a GenFSM that runs a very simple minded
coffee vending machine (CVM). The CVM treats all coins the same. If
you press the request button then the CVM will brew coffee, if you
have paid enough coins; if not, it will wait until you have inserted
enough coins and then it will instantly brew the coffee, since you
had already pressed the request button. As we told you - a very
simple minded CVM! And greedy too. If you insert more coins than you
need it will gladly eat them until you press the request button.
We will leave it to the service-minded reader to improve the way the CVM
works - we hereby declare a full disclaimer for any lawsuits that the
behaviour of the CVM in its original state might encur.
defmodule MyFsm do
use GenFSM.Behaviour
# keeping track of what is going on inside the CVM.
# 3 is the target price for a cup of coffee
defrecord StateData, coins: 0, price: 3
# API functions
def start_link() do
:gen_fsm.start_link({:local, :cvm}, __MODULE__, [], [])
end
def insert_coin() do
:gen_fsm.send_event(:cvm, :coin)
end
def request_coffee() do
:gen_fsm.send_event(:cvm, :request_coffee)
end
# Callbacks
def init(_args) do
{ :ok, :short_paid, StateData.new }
end
def short_paid(:coin, state_data = StateData[coins: c, price: p]) when c + 1 < p do
{ :next_state, :short_paid, state_data.coins(c + 1) }
end
def short_paid(:coin, state_data) do
{ :next_state, :paid_in_full, &state_data.update_coins(&1 + 1) }
end
def short_paid(:request_coffee, state_data) do
{ :next_state, :requested_short_paid, state_data }
end
def requested_short_paid(:request_coffee, state_data) do
{ :next_state, :requested_short_paid, state_data }
end
def requested_short_paid(:coin, state_data=StateData[coins: c, price: p]) when c+1 < p do
{ :next_state, :requested_short_paid, state_data.coins(c + 1) }
end
def requested_short_paid(:coin, _state_data) do
IO.puts "Here's your coffee!"
{ :next_state, :short_paid, StateData.new }
end
def paid_in_full(:coin, state_data) do
{ :next_state, :paid_in_full, &state_data.update_coins(&1 + 1) }
end
def paid_in_full(:request_coffee, _state_data) do
IO.puts "Here's your coffee!"
{ :next_state, :short_paid, StateData.new }
end
end
{ :ok, _pid } = MyFsm.start_link()
MyFsm.insert_coin
#=> :ok
MyFsm.insert_coin
#=> :ok
MyFsm.request_coffee
#=> :ok
MyFsm.insert_coin
#=> :ok
#=> Here's your coffee!
Notice we never call the GenFSM callbacks directly; they are called by
OTP whenever we interact with the server throught the API. `send_event` is
asynchronous, whereas `sync_send_event` is synchronous. For
a GenFSM, the different values a callback can return depend
on the type of callback.
State handling returns for `send_event` callbacks:
{ :next_state, next_state_name, new_state_data }
{ :next_state, next_state_name, new_state_data, timeout }
{ :next_state, next_state_name, new_state_data, :hibernate }
{ :stop, reason, new_state_data }
State handling returns for `sync_send_event` callbacks:
{ :reply, reply, next_state_name, new_state_data }
{ :reply, reply, next_state_name, new_state_data, timeout }
{ :reply, reply, next_state_name, new_state_data, :hibernate }
{ :next_state, next_state_name, new_state_data }
{ :next_state, next_state_name, new_state_data, timeout }
{ :next_state, next_state_name, new_state_data, :hibernate }
{ :stop, reason, reply, new_state_data }
{ :stop, reason, new_state_date }
There are 6 callbacks required to be implemented in a GenFsm plus 1
or 2 for each state. The `GenFSM.Behaviour` module defines
`handle_sync_event`, `handle_info`, `terminate` and `code_change`
for you. The list of callbacks are:
* `init(args)` - invoked when the FSM is started;
* `handle_sync_event(event, from, state_name, state_data)` - invoked to
handle `sync_send_all_state_event` messages;
* `handle_event(event, state_name, state_data)` - invoked to handle
`send_all_state_event` messages;
* `handle_info(msg, state_name, state_data)` - handle all other
messages which are normally received by processes;
* `terminate(reason, state_name, state_data)` - called when the FSM
is about to terminate, useful for cleaning up;
* `code_change(old_vsn, state, extra)` - called when the application
code is being upgraded live (hot code swap);
Unlike `GenServer` and `GenEvent`, the callback `init/1` is not
implemented by default, as it requires the next state to be returned.
For each state you need to define either or both of these:
* `state_name(event, state_data)` - invoked to handle
`send_event` messages;
* `state_name(event, from, state_data)`- invoked to handle
`sync_send_event` messages;
If you send asynchronous events you only need to implement the
`state_name/2` variant and vice-versa for synchronous events and
`state_name/3`. Keep in mind that if you mix `send_event` and
`sync_send_event` the best thing to do is to implement both
callbacks for all states.
Starting and sending messages to the GenFSM is done via Erlang's
`:gen_fsm` module. For more information, please refer to the
following:
* http://www.erlang.org/doc/man/gen_fsm.html
* http://www.erlang.org/doc/design_principles/fsm.html
* http://learnyousomeerlang.com/finite-state-machines
"""
@doc false
defmacro __using__(_) do
quote location: :keep do
@behavior :gen_fsm
@doc false
def handle_event(event, state_name, state_data) do
{ :stop, {:bad_event, state_name, event}, state_data }
end
@doc false
def handle_sync_event(event, from, state_name, state_data) do
{ :stop, {:bad_sync_event, state_name, event}, state_data }
end
@doc false
def handle_info(_msg, state_name, state_data) do
{ :next_state, state_name, state_data }
end
@doc false
def terminate(_reason, _state_name, _state_data) do
:ok
end
@doc false
def code_change(_old, state_name, state_data, _extra) do
{ :ok, state_name, state_data }
end
defoverridable [handle_event: 3, handle_sync_event: 4,
handle_info: 3, terminate: 3, code_change: 4]
end
end
end
+1 -1
View File
@@ -86,7 +86,7 @@ defmodule GenServer.Behaviour do
@doc false
defmacro __using__(_) do
quote location: :keep do
@behavior :gen_server
@behaviour :gen_server
@doc false
def init(args) do
+180 -378
View File
@@ -2,46 +2,14 @@ defmodule HashDict do
@moduledoc """
A key-value store.
The `HashDict` is meant to work well with both small and
large set of keys and it is an implementation of the `Dict`
behaviour. For more information about the functions and
their APIs, please consult the `Dict` module.
The `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.
"""
use Dict.Behaviour
# A dictionary (key-value) implementation based on dynamic hashing.
#
# This implementation is based on hash tries. We first start with
# a set of 8 buckets and expand when the density is about 5 entries
# per bucket. We use bit shifting to make rehashing faster on
# expansion.
#
# Compared to dict, it provides many enhancements:
#
# 1. HashDict buckets are ordered sets, this gives us faster access
# and modification times
#
# 2. It uses phash2 to calculate the hash (instead of phash)
#
# 3. The dictionary first starts with a single bucket, instead of
# a set of 8 buckets. This allow us to skip hashing altogher
# for small dictionaries, providing faster operations and
# reducing memory consumption
#
# 4. Once we reach 8 elements, the dictionary is promoted to a
# set of buckets
# The ordered record contains a single bucket
@ordered_threshold 8
defrecordp :ordered, HashDict,
size: 0,
bucket: []
# The bucketed record contains a series of buckets.
@expand_load 5
@contract_load 2
@node_bitmap 0b111
@node_shift 3
@node_size 8
@@ -49,431 +17,251 @@ defmodule HashDict do
defrecordp :trie, HashDict,
size: 0,
depth: 0,
expand_on: @node_size * @expand_load,
contract_on: @contract_load,
root: @node_template
import Bitwise
# Let's inline common instructions
# Inline common instructions
@compile :inline_list_funcs
@compile { :inline, bucket_hash: 1, bucket_index: 1, bucket_nth_index: 2, bucket_next: 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
ordered()
trie()
end
@doc """
Creates a new dict from the given enumerable.
## Examples
HashDict.new [{:b, 1}, {:a, 2}]
#=> #HashDict<[a: 2, b: 1]>
"""
@spec new(list({key :: term, value :: term})) :: Dict.t
def new(pairs) do
Enum.reduce pairs, ordered(), fn { k, v }, dict ->
@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
end
@doc """
Creates a new dict from the enumerable with the
help of the transformation function.
## Examples
HashDict.new ["a", "b"], fn x -> {x, x} end
#=> #HashDict<[{"a","a"},{"b","b"}]>
"""
@spec new(list, (term -> {key :: term, value ::term})) :: Dict.t
def new(list, transform) when is_function(transform) do
Enum.reduce list, new(), fn i, dict ->
@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 put(dict, key, value) do
{ dict, _ } = dict_put(dict, key, { :put, value })
dict
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 update!(dict, key, fun) when is_function(fun, 1) do
case dict_put(dict, key, { :update, nil, fun }) do
{ dict, 0 } ->
dict
{ _dict, 1 } ->
raise KeyError, key: key
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(dict, key, initial, fun) when is_function(fun, 1) do
{ dict, _ } = dict_put(dict, key, { :update, initial, fun })
dict
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,
fun, key_hash(key))
trie(root: root, size: size + counter)
end
def fetch(ordered(bucket: bucket), key) do
bucket_get(bucket, key)
def update(trie(root: root, size: size), key, initial, fun) when is_function(fun, 1) do
{ root, counter } = do_update(root, key, fn -> initial end, fun, key_hash(key))
trie(root: root, size: size + counter)
end
def fetch(trie(root: root, depth: depth), key) do
bucket_get(node_bucket(root, depth, bucket_hash(key)), key)
end
def pop(dict, key, default // nil) do
case dict_delete(dict, key) do
{ dict, _, 0 } -> { default, dict }
{ dict, value, _ } -> { value, dict }
end
def fetch(trie(root: root), key) do
do_fetch(root, key, key_hash(key))
end
def delete(dict, key) do
{ dict, _, _ } = dict_delete(dict, key)
dict
end
def size(dict) do
elem(dict, 1)
end
def empty(_) do
ordered()
end
def merge(dict, enum, callback // fn(_k, _v1, v2) -> v2 end)
def merge(dict1, dict2, callback) when is_record(dict1, HashDict) and is_record(dict2, HashDict) and elem(dict1, 1) < elem(dict2, 1) do
dict_fold dict1, dict2, fn [k|v1], acc ->
update(acc, k, v1, &callback.(k, v1, &1))
case dict_delete(dict, key) do
{ dict, _value } -> dict
:error -> dict
end
end
def merge(dict1, dict2, callback) when is_record(dict1, HashDict) and is_record(dict2, HashDict) do
dict_fold dict2, dict1, fn [k|v2], acc ->
update(acc, k, v2, &callback.(k, &1, v2))
def pop(dict, key, default \\ nil) do
case dict_delete(dict, key) do
{ dict, value } -> { value, dict }
:error -> { default, dict }
end
end
def merge(dict, enumerable, callback) when is_record(dict, HashDict) do
super(dict, enumerable, callback)
end
def split(dict, keys) do
split(keys, new, dict)
end
defp split([], including, excluding) do
{ including, excluding }
end
defp split([key|keys], including, excluding) do
case dict_delete(excluding, key) do
{ excluding, _, 0 } -> split(keys, including, excluding)
{ excluding, value, _ } -> split(keys, put(including, key, value), excluding)
end
def size(trie(size: size)) do
size
end
@doc false
def reduce(ordered(bucket: bucket), acc, fun) do
bucket_reduce(bucket, acc, fun)
def reduce(trie(root: root), acc, fun) do
do_reduce(root, acc, fun, @node_size, fn
{:suspend, acc} -> {:suspended, acc, &{ :done, elem(&1, 1) }}
{:halt, acc} -> {:halted, acc}
{:cont, acc} -> {:done, acc}
end)
end
def reduce(trie(root: root, depth: depth), acc, fun) do
node_reduce(root, depth, acc, fun, @node_size,
fn { :cont, acc }, _f -> { :done, acc } end)
end
## Dict-wide functions
defp dict_fold(ordered(bucket: bucket), acc, fun) do
bucket_fold(bucket, acc, fun)
end
defp dict_fold(trie(root: root, depth: depth), acc, fun) do
node_fold(root, depth, acc, fun, @node_size)
end
defp dict_put(ordered(size: @ordered_threshold, bucket: bucket), key, value) do
root = node_relocate(bucket, 0)
dict_put(trie(size: @ordered_threshold, root: root), key, value)
end
defp dict_put(ordered(size: size, bucket: bucket) = dict, key, value) do
{ new, count } = bucket_put(bucket, key, value)
{ ordered(dict, size: size + count, bucket: new), count }
end
defp dict_put(trie(root: root, depth: depth, size: size, expand_on: size, contract_on: contract_on) = dict, key, value) do
root = node_expand(root, depth, depth + 1)
dict = trie(dict, root: root, depth: depth + 1,
expand_on: size * @node_size, contract_on: contract_on * @node_size)
dict_put(dict, key, value)
end
defp dict_put(trie(root: root, size: size, depth: depth) = dict, key, value) do
pos = bucket_hash(key)
{ root, count } = node_put(root, depth, pos, key, value)
{ trie(dict, size: size + count, root: root), count }
end
defp dict_delete(ordered(bucket: bucket, size: size) = dict, key) do
case bucket_delete(bucket, key) do
{ _, value, 0 } ->
{ dict, value, 0 }
{ new_bucket, value, -1 } ->
{ ordered(dict, size: size - 1, bucket: new_bucket), value, -1 }
def split(dict, keys) do
Enum.reduce keys, { new, dict }, fn key, { inc, exc } = acc ->
case dict_delete(exc, key) do
{ exc, value } -> { put(inc, key, value), exc }
:error -> acc
end
end
end
defp dict_delete(trie(root: root, size: size, depth: depth) = dict, key) do
pos = bucket_hash(key)
case node_delete(root, depth, pos, key) do
{ _, value, 0 } ->
{ dict, value, 0 }
{ root, value, -1 } ->
{ if depth > 0 and trie(dict, :contract_on) == size do
root = node_contract(root, depth)
trie(dict,
root: root,
size: size - 1,
depth: depth - 1,
contract_on: div(size, @node_size),
expand_on: div(trie(dict, :expand_on), @node_size))
else
trie(dict, size: size - 1, root: root)
end, value, -1 }
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)) }
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)) }
end) |> elem(1)
end
## General helpers
defp dict_delete(trie(root: root, size: size), key) do
case do_delete(root, key, key_hash(key)) do
{ root, value } -> { trie(root: root, size: size - 1), value }
:error -> :error
end
end
## Bucket helpers
## Dict manipulation
# Get value from the bucket
defp bucket_get([[k|_]|_bucket], key) when k > key do
:error
defp do_fetch(node, key, hash) do
index = key_mask(hash)
case elem(node, index) do
[^key|v] -> {:ok, v}
{^key, v, _} -> {:ok, v}
{_, _, n} -> do_fetch(n, key, key_shift(hash))
_ -> :error
end
end
defp bucket_get([[key|value]|_bucket], key) do
{ :ok, value }
defp do_put(node, key, value, hash) do
index = key_mask(hash)
case elem(node, index) do
[] ->
{set_elem(node, index, [key|value]), 1}
[^key|_] ->
{set_elem(node, index, [key|value]), 0}
[k|v] ->
n = set_elem(@node_template, key_mask(key_shift(hash)), [key|value])
{set_elem(node, index, {k, v, n}), 1}
{^key, _, n} ->
{set_elem(node, index, {key, value, n}), 0}
{k, v, n} ->
{n, counter} = do_put(n, key, value, key_shift(hash))
{set_elem(node, index, {k, v, n}), counter}
end
end
defp bucket_get([_e|bucket], key) do
bucket_get(bucket, key)
defp do_update(node, key, initial, fun, hash) do
index = key_mask(hash)
case elem(node, index) do
[] ->
{set_elem(node, index, [key|initial.()]), 1}
[^key|value] ->
{set_elem(node, index, [key|fun.(value)]), 0}
[k|v] ->
n = set_elem(@node_template, key_mask(key_shift(hash)), [key|initial.()])
{set_elem(node, index, {k, v, n}), 1}
{^key, value, n} ->
{set_elem(node, index, {key, fun.(value), n}), 0}
{k, v, n} ->
{n, counter} = do_update(n, key, initial, fun, key_shift(hash))
{set_elem(node, index, {k, v, n}), counter}
end
end
defp bucket_get([], _key) do
:error
defp do_delete(node, key, hash) do
index = key_mask(hash)
case elem(node, index) do
[] ->
:error
[^key|value] ->
{set_elem(node, index, []), value}
[_|_] ->
:error
{^key, value, n} ->
{set_elem(node, index, do_compact_node(n)), value}
{k, v, n} ->
case do_delete(n, key, key_shift(hash)) do
{@node_template, value} ->
{set_elem(node, index, [k|v]), value}
{n, value} ->
{set_elem(node, index, {k, v, n}), value}
:error ->
:error
end
end
end
# Puts a value in the bucket
defp bucket_put([[k|_]|_]=bucket, key, { :put, value }) when k > key do
{ [[key|value]|bucket], 1 }
Enum.each 0..(@node_size - 1), fn index ->
defp do_compact_node(node) when elem(node, unquote(index)) != [] do
case elem(node, unquote(index)) do
[k|v] ->
case set_elem(node, unquote(index), []) do
@node_template -> [k|v]
n -> {k, v, n}
end
{k, v, n} ->
{k, v, set_elem(node, unquote(index), do_compact_node(n))}
end
end
end
defp bucket_put([[k|_]|_]=bucket, key, { :update, initial, _fun }) when k > key do
{ [[key|initial]|bucket], 1 }
## Dict reduce
defp do_reduce_each(_node, {:halt, acc}, _fun, _next) do
{:halted, acc}
end
defp bucket_put([[key|_]|bucket], key, { :put, value }) do
{ [[key|value]|bucket], 0 }
defp do_reduce_each(node, {:suspend, acc}, fun, next) do
{:suspended, acc, &do_reduce_each(node, &1, fun, next)}
end
defp bucket_put([[key|value]|bucket], key, { :update, _initial, fun }) do
{ [[key|fun.(value)]|bucket], 0 }
defp do_reduce_each([], acc, _fun, next) do
next.(acc)
end
defp bucket_put([e|bucket], key, value) do
{ rest, count } = bucket_put(bucket, key, value)
{ [e|rest], count }
defp do_reduce_each([k|v], {:cont, acc}, fun, next) do
next.(fun.({k,v}, acc))
end
defp bucket_put([], key, { :put, value }) do
{ [[key|value]], 1 }
defp do_reduce_each({k, v, n}, {:cont, acc}, fun, next) do
do_reduce(n, fun.({k, v}, acc), fun, @node_size, next)
end
defp bucket_put([], key, { :update, initial, _fun }) do
{ [[key|initial]], 1 }
defp do_reduce(node, acc, fun, count, next) when count > 0 do
do_reduce_each(:erlang.element(count, node), acc, fun, &do_reduce(node, &1, fun, count - 1, next))
end
# Puts a value in the bucket without returning
# the operation value
defp bucket_put!([[k|_]|_]=bucket, key, value) when k > key, do: [[key|value]|bucket]
defp bucket_put!([[key|_]|bucket], key, value), do: [[key|value]|bucket]
defp bucket_put!([e|bucket], key, value), do: [e|bucket_put!(bucket, key, value)]
defp bucket_put!([], key, value), do: [[key|value]]
# Deletes a key from the bucket
defp bucket_delete([[k|_]|_]=bucket, key) when k > key do
{ bucket, nil, 0 }
defp do_reduce(_node, acc, _fun, 0, next) do
next.(acc)
end
defp bucket_delete([[key|value]|bucket], key) do
{ bucket, value, -1 }
end
## Key operations
defp bucket_delete([e|bucket], key) do
{ rest, value, count } = bucket_delete(bucket, key)
{ [e|rest], value, count }
end
import Bitwise
defp bucket_delete([], _key) do
{ [], nil, 0 }
end
# Reduces the bucket
defp bucket_reduce(_, { :halt, acc }, _fun), do: { :halted, acc }
defp bucket_reduce(list, { :suspend, acc }, fun), do: { :suspended, acc, &bucket_reduce(list, &1, fun) }
defp bucket_reduce([[k|v]|t], { :cont, acc }, fun), do: bucket_reduce(t, fun.({ k, v }, acc), fun)
defp bucket_reduce([], { :cont, acc }, _fun), do: { :done, acc }
defp bucket_fold(bucket, acc, fun) do
:lists.foldl(fun, acc, bucket)
end
defp bucket_hash(key) do
defp key_hash(key) do
:erlang.phash2(key)
end
defp bucket_index(hash) do
defp key_mask(hash) do
hash &&& @node_bitmap
end
defp bucket_nth_index(hash, n) do
(hash >>> (@node_shift * n)) &&& @node_bitmap
end
defp bucket_next(hash) do
defp key_shift(hash) do
hash >>> @node_shift
end
## Node helpers
# Gets a bucket from the node
defp node_bucket(node, 0, hash) do
elem(node, bucket_index(hash))
end
defp node_bucket(node, depth, hash) do
child = elem(node, bucket_index(hash))
node_bucket(child, depth - 1, bucket_next(hash))
end
# Puts a key-value into a node
defp node_put(node, 0, hash, key, value) do
pos = bucket_index(hash)
{ new, count } = bucket_put(elem(node, pos), key, value)
{ set_elem(node, pos, new), count }
end
defp node_put(node, depth, hash, key, value) do
pos = bucket_index(hash)
{ new, count } = node_put(elem(node, pos), depth - 1, bucket_next(hash), key, value)
{ set_elem(node, pos, new), count }
end
# Deletes a key from the bucket
defp node_delete(node, 0, hash, key) do
pos = bucket_index(hash)
case bucket_delete(elem(node, pos), key) do
{ _, value, 0 } -> { node, value, 0 }
{ new, value, -1 } -> { set_elem(node, pos, new), value, -1 }
end
end
defp node_delete(node, depth, hash, key) do
pos = bucket_index(hash)
case node_delete(elem(node, pos), depth - 1, bucket_next(hash), key) do
{ _, value, 0 } -> { node, value, 0 }
{ new, value, -1 } -> { set_elem(node, pos, new), value, -1 }
end
end
# Reduces a node recursively
defp node_reduce(_, -1, { :halt, acc }, _fun, _count, _next) do
{ :halted, acc }
end
defp node_reduce(list, -1, { :suspend, acc }, fun, count, next) do
{ :suspended, acc, &node_reduce(list, -1, &1, fun, count, next) }
end
defp node_reduce([[k|v]|t], -1, { :cont, acc }, fun, _count, next) do
node_reduce(t, -1, fun.({ k, v }, acc), fun, _count, next)
end
defp node_reduce([], -1, { :cont, _ } = acc, fun, _count, next) do
next.(acc, fun)
end
defp node_reduce(node, depth, acc, fun, count, next) when count >= 1 do
node_reduce(:erlang.element(count, node), depth - 1, acc, fun, @node_size,
&node_reduce(node, depth, &1, &2, count - 1, next))
end
defp node_reduce(_node, _, acc, fun, 0, next) do
next.(acc, fun)
end
# Folds a node recursively
defp node_fold(bucket, -1, acc, fun, _) do
bucket_fold(bucket, acc, fun)
end
defp node_fold(node, depth, acc, fun, count) when count >= 1 do
acc = node_fold(:erlang.element(count, node), depth - 1, acc, fun, @node_size)
node_fold(node, depth, acc, fun, count - 1)
end
defp node_fold(_node, _, acc, _fun, 0) do
acc
end
# Node resizing
defp node_expand({ b1, b2, b3, b4, b5, b6, b7, b8 }, 0, n) do
{ node_relocate(b1, n), node_relocate(b2, n), node_relocate(b3, n),
node_relocate(b4, n), node_relocate(b5, n), node_relocate(b6, n),
node_relocate(b7, n), node_relocate(b8, n) }
end
defp node_expand({ b1, b2, b3, b4, b5, b6, b7, b8 }, depth, n) do
depth = depth - 1
{ node_expand(b1, depth, n), node_expand(b2, depth, n), node_expand(b3, depth, n),
node_expand(b4, depth, n), node_expand(b5, depth, n), node_expand(b6, depth, n),
node_expand(b7, depth, n), node_expand(b8, depth, n) }
end
defp node_contract({ b1, b2, b3, b4, b5, b6, b7, b8 }, depth) when depth > 0 do
depth = depth - 1
{ node_contract(b1, depth), node_contract(b2, depth), node_contract(b3, depth),
node_contract(b4, depth), node_contract(b5, depth), node_contract(b6, depth),
node_contract(b7, depth), node_contract(b8, depth) }
end
defp node_contract({ b1, b2, b3, b4, b5, b6, b7, b8 }, 0) do
b1 |> each_contract(b2) |> each_contract(b3) |> each_contract(b4)
|> each_contract(b5) |> each_contract(b6) |> each_contract(b7)
|> each_contract(b8)
end
defp each_contract([[k|_v]=e|acc], [[key|_value]|_]=bucket) when k < key, do: [e|each_contract(acc, bucket)]
defp each_contract(acc, [e|bucket]), do: [e|each_contract(acc, bucket)]
defp each_contract([], bucket), do: bucket
defp each_contract(acc, []), do: acc
defp node_relocate(node // @node_template, bucket, n) do
:lists.foldl fn [key|value], acc ->
pos = key |> bucket_hash() |> bucket_nth_index(n)
set_elem(acc, pos, bucket_put!(elem(acc, pos), key, value))
end, node, bucket
end
end
defimpl Enumerable, for: HashDict do
@@ -486,3 +274,17 @@ end
defimpl Access, for: HashDict do
def access(dict, key), do: HashDict.get(dict, key, nil)
end
defimpl Collectable, for: HashDict do
def empty(_dict) do
HashDict.new
end
def into(original) do
{ original, fn
dict, { :cont, { k, v } } -> Dict.put(dict, k, v)
dict, :done -> dict
_, :halt -> :ok
end }
end
end
+178 -404
View File
@@ -2,230 +2,83 @@ defmodule HashSet do
@moduledoc """
A set store.
The `HashSet` is meant to work well with both small and
large sets. It is an implementation of the `Set` behaviour.
For more information about the functions and their APIs,
please consult 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
functions and their APIs, please consult the `Set` module.
"""
@behaviour Set
# The ordered record contains a single bucket.
@ordered_threshold 8
defrecordp :ordered, HashSet,
size: 0,
bucket: []
# The bucketed record contains a series of buckets.
@expand_load 5
@contract_load 2
@node_bitmap 0b1111
@node_shift 4
@node_size 16
@node_bitmap 0b111
@node_shift 3
@node_size 8
@node_template :erlang.make_tuple(@node_size, [])
@expand_default (@node_size * @expand_load)
@contract_default @contract_load
defrecordp :trie, HashSet,
size: 0,
depth: 0,
expand_on: @expand_default,
contract_on: @contract_default,
root: @node_template
import Bitwise
# Inline common instructions
@compile :inline_list_funcs
@compile { :inline, bucket_hash: 1, bucket_index: 1, bucket_nth_index: 2, bucket_next: 1 }
@compile { :inline, key_hash: 1, key_mask: 1, key_shift: 1 }
@doc """
Creates a new empty set.
"""
def new() do
ordered()
@spec new :: Set.t
def new do
trie()
end
@doc """
Creates a new set from the given enumerable.
## Examples
iex> HashSet.new [1, 1, 2, 3, 3] |> HashSet.to_list
[1,2,3]
"""
def new(members) do
Enum.reduce members, ordered(), fn member, set ->
put(set, member)
@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
def union(set1, set2) when is_record(set1, HashSet) and is_record(set2, HashSet) and elem(set1, 1) <= elem(set2, 1) do
set_fold set1, set2, fn v1, acc ->
put(acc, v1)
@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(set1, set2) when is_record(set1, HashSet) and is_record(set2, HashSet) do
set_fold set2, set1, fn v, acc ->
put(acc, v)
def union(trie(size: size1) = set1, trie(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
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 ->
if member?(set2, v), do: put(acc, v), else: acc
end
end
def intersection(set1, set2) when is_record(set1, HashSet) and is_record(set2, HashSet) and elem(set1, 1) <= elem(set2, 1) do
set_filter set1, fn e -> set_member?(set2, e) end
end
def intersection(set1, set2) when is_record(set1, HashSet) and is_record(set2, HashSet) do
set_filter set2, fn e -> set_member?(set1, e) end
end
def difference(set1, set2) when is_record(set1, HashSet) and is_record(set2, HashSet) do
set_filter set1, fn m -> not set_member?(set2, m) end
end
def member?(set, member) when is_record(set, HashSet) do
set_member?(set, member)
end
def empty(_) do
ordered()
end
def size(set) do
elem(set, 1)
end
def to_list(ordered(bucket: bucket)) do
bucket
def difference(trie() = set1, trie() = set2) do
set_fold set2, set1, fn v, acc -> delete(acc, v) end
end
def to_list(set) do
set_fold(set, [], &[&1|&2]) |> :lists.reverse
end
def put(set, member) do
{ set, _ } = set_put(set, member)
set
end
def delete(set, member) do
{ set, _ } = set_delete(set, member)
set
end
def equal?(set1, set2) do
size = elem(set1, 1)
case elem(set2, 1) do
^size ->
set_equal?(set1, set2)
_ ->
false
def equal?(trie(size: size1) = set1, trie(size: size2) = set2) do
case size1 do
^size2 -> subset?(set1, set2)
_ -> false
end
end
def subset?(set1, set2) do
set_equal?(set1, set2)
end
def disjoint?(set1, set2) do
set_disjoint?(set1, set2)
end
@doc false
def reduce(ordered(bucket: bucket), acc, fun) do
bucket_reduce(bucket, acc, fun)
end
def reduce(trie(root: root, depth: depth), acc, fun) do
node_reduce(root, depth, acc, fun, @node_size,
fn { :cont, acc }, _f -> { :done, acc } end)
end
## HashSet-wide functions
defp set_filter(ordered(bucket: bucket, size: size), fun) do
{ new, removed_count } = bucket_filter(bucket, fun, [], 0)
ordered(bucket: new, size: size - removed_count)
end
defp set_filter(trie(root: root, depth: depth, size: size) = set, fun) do
{ new, removed_count } = node_filter(root, depth, fun, @node_size)
if depth > 0 and trie(set, :contract_on) >= (size - removed_count) do
contract_trie(trie(root: new,
size: size - removed_count,
depth: depth,
contract_on: trie(set, :contract_on),
expand_on: trie(set, :expand_on)))
else
trie(size: size - removed_count, root: new, depth: depth)
end
end
defp set_put(ordered(size: @ordered_threshold, bucket: bucket), member) do
root = node_relocate(bucket, 0)
set_put(trie(size: @ordered_threshold, root: root), member)
end
defp set_put(ordered(size: size, bucket: bucket) = set, member) do
{ new, count } = bucket_put(bucket, member)
{ ordered(set, size: size + count, bucket: new), count }
end
defp set_put(trie(root: root, depth: depth, size: size, expand_on: size, contract_on: contract_on) = set, member) do
root = node_expand(root, depth, depth + 1)
set = trie(set, root: root, depth: depth + 1,
expand_on: size * @node_size, contract_on: contract_on * @node_size)
set_put(set, member)
end
defp set_put(trie(root: root, size: size, depth: depth) = set, member) do
pos = bucket_hash(member)
{ root, count } = node_put(root, depth, pos, member)
{ trie(set, size: size + count, root: root), count }
end
defp set_member?(ordered(bucket: bucket), member) do
:lists.member(member, bucket)
end
defp set_member?(trie(root: root, depth: depth), member) do
:lists.member(member, node_bucket(root, depth, bucket_hash(member)))
end
defp set_delete(ordered(bucket: bucket, size: size) = set, member) do
case bucket_delete(bucket, member) do
{ _, 0 } ->
{ set, 0 }
{ new_bucket, -1 } ->
{ ordered(set, size: size - 1, bucket: new_bucket), -1 }
end
end
defp set_delete(trie(root: root, size: size, depth: depth) = set, member) do
pos = bucket_hash(member)
case node_delete(root, depth, pos, member) do
{ _, 0 } ->
{ set, 0 }
{ root, -1 } ->
{ if depth > 0 and trie(set, :contract_on) == size do
root = node_contract(root, depth)
trie(set,
root: root,
size: size - 1,
depth: depth - 1,
contract_on: div(size, @node_size),
expand_on: div(trie(set, :expand_on), @node_size))
else
trie(set, size: size - 1, root: root)
end, -1 }
end
end
defp set_equal?(set1, set2) do
def subset?(trie() = set1, trie() = set2) do
reduce(set1, { :cont, true }, fn member, acc ->
case member?(set2, member) do
true -> { :cont, acc }
@@ -234,273 +87,180 @@ defmodule HashSet do
end) |> elem(1)
end
defp set_disjoint?(set1, set2) do
reduce(set1, { :cont, true }, fn member, acc ->
case member?(set2, member) do
def disjoint?(trie() = set1, trie() = set2) do
reduce(set2, { :cont, true }, fn member, acc ->
case member?(set1, member) do
false -> { :cont, acc }
_ -> { :halt, false }
end
end) |> elem(1)
end
defp set_fold(ordered(bucket: bucket), acc, fun) do
bucket_fold(bucket, acc, fun)
def empty(trie()) do
IO.write :stderr, "HashSet.empty/1 is deprecated, please use Collectable.empty/1 instead\n#{Exception.format_stacktrace}"
trie()
end
defp set_fold(trie(root: root, depth: depth), acc, fun) do
node_fold(root, depth, acc, fun, @node_size)
def member?(trie(root: root), term) do
do_member?(root, term, key_hash(term))
end
## Bucket helpers
def put(trie(root: root, size: size), term) do
{root, counter} = do_put(root, term, key_hash(term))
trie(root: root, size: size + counter)
end
defp bucket_filter([e|bucket], fun, acc, count) do
case fun.(e) do
true -> bucket_filter(bucket, fun, [e|acc], count)
false -> bucket_filter(bucket, fun, acc, count + 1)
def delete(trie(root: root, size: size) = set, term) do
case do_delete(root, term, key_hash(term)) do
{:ok, root} -> trie(root: root, size: size - 1)
:error -> set
end
end
defp bucket_filter([], _fun, acc, count) do
{ :lists.reverse(acc), count }
@doc false
def reduce(trie(root: root), acc, fun) do
do_reduce(root, acc, fun, @node_size, fn
{:suspend, acc} -> {:suspended, acc, &{ :done, elem(&1, 1) }}
{:halt, acc} -> {:halted, acc}
{:cont, acc} -> {:done, acc}
end)
end
defp bucket_put([m|_]=bucket, member) when m > member do
{ [member|bucket], 1 }
def size(trie(size: size)) do
size
end
defp bucket_put([member|bucket], member) do
{ [member|bucket], 0 }
## Set helpers
defp set_fold(trie(root: root), acc, fun) do
do_fold(root, acc, fun, @node_size)
end
defp bucket_put([e|bucket], member) do
{ rest, count } = bucket_put(bucket, member)
{ [e|rest], count }
end
## Set manipulation
defp bucket_put([], member) do
{ [member], 1 }
end
defp bucket_put!([m|_]=bucket, member) when m > member, do: [member|bucket]
defp bucket_put!([member|bucket], member), do: [member|bucket]
defp bucket_put!([e|bucket], member), do: [e|bucket_put!(bucket, member)]
defp bucket_put!([], member), do: [member]
# Deletes a key from the bucket
defp bucket_delete([m,_|_]=bucket, member) when m > member do
{ bucket, 0 }
end
defp bucket_delete([member|bucket], member) do
{ bucket, -1 }
end
defp bucket_delete([e|bucket], member) do
{ rest, count } = bucket_delete(bucket, member)
{ [e|rest], count }
end
defp bucket_delete([], _member) do
{ [], 0 }
end
defp bucket_reduce(_, { :halt, acc }, _fun), do: { :halted, acc }
defp bucket_reduce(list, { :suspend, acc }, fun), do: { :suspended, acc, &bucket_reduce(list, &1, fun) }
defp bucket_reduce([h|t], { :cont, acc }, fun), do: bucket_reduce(t, fun.(h, acc), fun)
defp bucket_reduce([], { :cont, acc }, _fun), do: { :done, acc }
defp bucket_fold(bucket, acc, fun) do
:lists.foldl(fun, acc, bucket)
end
defp bucket_hash(key) do
:erlang.phash2(key)
end
defp bucket_nth_index(hash, n) do
(hash >>> (@node_shift * n)) &&& @node_bitmap
end
defp bucket_index(hash) do
hash &&& @node_bitmap
end
defp bucket_next(hash) do
hash >>> @node_shift
end
# Trie resizing
defp contract_trie(trie(depth: 0) = set) do
set
end
defp contract_trie(trie(root: root, depth: depth, size: size, contract_on: contract_on, expand_on: expand_on) = set) when size <= contract_on do
new_contract_on = div(contract_on, @node_size)
new_expand_on = div(expand_on, @node_size)
if new_contract_on == 0, do: new_contract_on = @contract_default
if new_expand_on == 0, do: new_expand_on = @expand_default
contract_trie(trie(set, root: node_contract(root, depth),
size: size,
depth: depth - 1,
contract_on: new_contract_on,
expand_on: new_expand_on))
end
defp contract_trie(set) do
set
end
# Node helpers
# Gets a bucket from the node
defp node_bucket(node, 0, hash) do
elem(node, bucket_index(hash))
end
defp node_bucket(node, depth, hash) do
child = elem(node, bucket_index(hash))
node_bucket(child, depth - 1, bucket_next(hash))
end
defp node_put(node, 0, hash, member) do
pos = bucket_index(hash)
{ new, count } = bucket_put(elem(node, pos), member)
{ set_elem(node, pos, new), count }
end
defp node_put(node, depth, hash, member) do
pos = bucket_index(hash)
{ new, count } = node_put(elem(node, pos), depth - 1, bucket_next(hash), member)
{ set_elem(node, pos, new), count }
end
# Deletes a key from the bucket
defp node_delete(node, 0, hash, member) do
pos = bucket_index(hash)
case bucket_delete(elem(node, pos), member) do
{ _, 0 } -> { node, 0 }
{ new, -1 } -> { set_elem(node, pos, new), -1 }
defp do_member?(node, term, hash) do
index = key_mask(hash)
case elem(node, index) do
[] -> false
[^term|_] -> true
[_] -> false
[_|n] -> do_member?(n, term, key_shift(hash))
end
end
defp node_delete(node, depth, hash, member) do
pos = bucket_index(hash)
case node_delete(elem(node, pos), depth - 1, bucket_next(hash), member) do
{ _, 0 } -> { node, 0 }
{ new, -1 } -> { set_elem(node, pos, new), -1 }
defp do_put(node, term, hash) do
index = key_mask(hash)
case elem(node, index) do
[] ->
{set_elem(node, index, [term]), 1}
[^term|_] ->
{node, 0}
[t] ->
n = set_elem(@node_template, key_mask(key_shift(hash)), [term])
{set_elem(node, index, [t|n]), 1}
[t|n] ->
{n, counter} = do_put(n, term, key_shift(hash))
{set_elem(node, index, [t|n]), counter}
end
end
# Reduces a node recursively
defp node_reduce(_, -1, { :halt, acc }, _fun, _count, _next) do
{ :halted, acc }
defp do_delete(node, term, hash) do
index = key_mask(hash)
case elem(node, index) do
[] ->
:error
[^term] ->
{:ok, set_elem(node, index, [])}
[_] ->
:error
[^term|n] ->
{:ok, set_elem(node, index, do_compact_node(n))}
[t|n] ->
case do_delete(n, term, key_shift(hash)) do
{:ok, @node_template} ->
{:ok, set_elem(node, index, [t])}
{:ok, n} ->
{:ok, set_elem(node, index, [t|n])}
:error ->
:error
end
end
end
defp node_reduce(list, -1, { :suspend, acc }, fun, count, next) do
{ :suspended, acc, &node_reduce(list, -1, &1, fun, count, next) }
Enum.each 0..(@node_size - 1), fn index ->
defp do_compact_node(node) when elem(node, unquote(index)) != [] do
case elem(node, unquote(index)) do
[t] ->
case set_elem(node, unquote(index), []) do
@node_template -> [t]
n -> [t|n]
end
[t|n] ->
[t|set_elem(node, unquote(index), do_compact_node(n))]
end
end
end
defp node_reduce([h|t], -1, { :cont, acc }, fun, _count, next) do
node_reduce(t, -1, fun.(h, acc), fun, _count, next)
## Set fold
defp do_fold_each([], acc, _fun), do: acc
defp do_fold_each([t], acc, fun), do: fun.(t, acc)
defp do_fold_each([t|n], acc, fun), do: do_fold(n, fun.(t, acc), fun, @node_size)
defp do_fold(node, acc, fun, count) when count > 0 do
acc = do_fold_each(:erlang.element(count, node), acc, fun)
do_fold(node, acc, fun, count - 1)
end
defp node_reduce([], -1, { :cont, _ } = acc, fun, _count, next) do
next.(acc, fun)
end
defp node_reduce(node, depth, acc, fun, count, next) when count >= 1 do
node_reduce(:erlang.element(count, node), depth - 1, acc, fun, @node_size,
&node_reduce(node, depth, &1, &2, count - 1, next))
end
defp node_reduce(_node, _, acc, fun, 0, next) do
next.(acc, fun)
end
# Folds a node recursively
defp node_fold(bucket, -1, acc, fun, _) do
bucket_fold(bucket, acc, fun)
end
defp node_fold(node, depth, acc, fun, count) when count >= 1 do
acc = node_fold(:erlang.element(count, node), depth - 1, acc, fun, @node_size)
node_fold(node, depth, acc, fun, count - 1)
end
defp node_fold(_node, _, acc, _fun, 0) do
defp do_fold(_node, acc, _fun, 0) do
acc
end
defp node_filter(bucket, -1, fun, _) do
bucket_filter(bucket, fun, [], 0)
## Set reduce
defp do_reduce_each(_node, {:halt, acc}, _fun, _next) do
{:halted, acc}
end
defp node_filter(node, depth, fun, count) when count >= 1 do
case node_filter(:erlang.element(count, node), depth - 1, fun, @node_size) do
{ _, 0 } ->
node_filter(node, depth, fun, count - 1)
{ new_element, count1 } ->
{ new_node, count2 } = node_filter(:erlang.setelement(count, node, new_element), depth, fun, count - 1)
{ new_node, count1 + count2 }
end
defp do_reduce_each(node, {:suspend, acc}, fun, next) do
{:suspended, acc, &do_reduce_each(node, &1, fun, next)}
end
defp node_filter(node, _, _fun, 0) do
{ node, 0 }
defp do_reduce_each([], acc, _fun, next) do
next.(acc)
end
defp node_relocate(node // @node_template, bucket, n) do
:lists.foldl fn member, acc ->
pos = member |> bucket_hash() |> bucket_nth_index(n)
set_elem(acc, pos, bucket_put!(elem(acc, pos), member))
end, node, bucket
defp do_reduce_each([t], {:cont, acc}, fun, next) do
next.(fun.(t, acc))
end
# Node resizing
defp node_expand({ b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12, b13, b14, b15, b16 }, 0, n) do
{ node_relocate(b1, n), node_relocate(b2, n), node_relocate(b3, n),
node_relocate(b4, n), node_relocate(b5, n), node_relocate(b6, n),
node_relocate(b7, n), node_relocate(b8, n), node_relocate(b9, n),
node_relocate(b10, n), node_relocate(b11, n), node_relocate(b12, n),
node_relocate(b13, n), node_relocate(b14, n), node_relocate(b15, n),
node_relocate(b16, n) }
defp do_reduce_each([t|n], {:cont, acc}, fun, next) do
do_reduce(n, fun.(t, acc), fun, @node_size, next)
end
defp node_expand({ b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12, b13, b14, b15, b16 }, depth, n) do
depth = depth - 1
{ node_expand(b1, depth, n), node_expand(b2, depth, n), node_expand(b3, depth, n),
node_expand(b4, depth, n), node_expand(b5, depth, n), node_expand(b6, depth, n),
node_expand(b7, depth, n), node_expand(b8, depth, n), node_expand(b9, depth, n),
node_expand(b10, depth, n), node_expand(b11, depth, n), node_expand(b12, depth, n),
node_expand(b13, depth, n), node_expand(b14, depth, n), node_expand(b15, depth, n),
node_expand(b16, depth, n) }
defp do_reduce(node, acc, fun, count, next) when count > 0 do
do_reduce_each(:erlang.element(count, node), acc, fun, &do_reduce(node, &1, fun, count - 1, next))
end
defp node_contract({ b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12, b13, b14, b15, b16 }, depth) when depth > 0 do
depth = depth - 1
{ node_contract(b1, depth), node_contract(b2, depth), node_contract(b3, depth),
node_contract(b4, depth), node_contract(b5, depth), node_contract(b6, depth),
node_contract(b7, depth), node_contract(b8, depth), node_contract(b9, depth),
node_contract(b10, depth), node_contract(b11, depth), node_contract(b12, depth),
node_contract(b13, depth), node_contract(b14, depth), node_contract(b15, depth),
node_contract(b16, depth) }
defp do_reduce(_node, acc, _fun, 0, next) do
next.(acc)
end
defp node_contract({ b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12, b13, b14, b15, b16 }, 0) do
b1 |> each_contract(b2) |> each_contract(b3) |> each_contract(b4)
|> each_contract(b5) |> each_contract(b6) |> each_contract(b7)
|> each_contract(b8) |> each_contract(b9) |> each_contract(b10)
|> each_contract(b11) |> each_contract(b12) |> each_contract(b13)
|> each_contract(b14) |> each_contract(b15) |> each_contract(b16)
## Key operations
import Bitwise
defp key_hash(key) do
:erlang.phash2(key)
end
defp each_contract([m1|acc], [m2|_]=bucket) when m1 < m2, do: [m1|each_contract(acc, bucket)]
defp each_contract(acc, [m|bucket]), do: [m|each_contract(acc, bucket)]
defp each_contract([], bucket), do: bucket
defp each_contract(acc, []), do: acc
defp key_mask(hash) do
hash &&& @node_bitmap
end
defp key_shift(hash) do
hash >>> @node_shift
end
end
defimpl Enumerable, for: HashSet do
@@ -508,3 +268,17 @@ defimpl Enumerable, for: HashSet do
def member?(set, v), do: { :ok, HashSet.member?(set, v) }
def count(set), do: { :ok, HashSet.size(set) }
end
defimpl Collectable, for: HashSet do
def empty(_dict) do
HashSet.new
end
def into(original) do
{ original, fn
set, { :cont, x } -> HashSet.put(set, x)
set, :done -> set
_, :halt -> :ok
end }
end
end
+90 -27
View File
@@ -3,6 +3,7 @@ import Inspect.Algebra
defrecord Inspect.Opts,
records: true,
structs: true,
binaries: :infer,
char_lists: :infer,
limit: 50,
@@ -57,6 +58,10 @@ defprotocol Inspect do
Inspect.HashSet.inspect(HashSet.new, Inspect.Opts.new)
"""
# Handle structs in Any
@fallback_to_any true
def inspect(thing, opts)
end
@@ -75,8 +80,10 @@ defimpl Inspect, for: Atom do
iex> inspect(:foo)
":foo"
iex> inspect(nil)
"nil"
iex> inspect(Foo.Bar)
"Foo.Bar"
@@ -100,7 +107,7 @@ defimpl Inspect, for: Atom do
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
@@ -127,16 +134,21 @@ defimpl Inspect, for: Atom do
# Detect if atom
defp valid_atom_identifier?(<<h, t :: binary>>) when h in ?a..?z or h in ?A..?Z or h == ?_ do
case valid_identifier?(t) do
<<>> -> true
<<??>> -> true
<<?!>> -> true
_ -> false
end
valid_atom_piece?(t)
end
defp valid_atom_identifier?(_), do: false
defp valid_atom_piece?(t) do
case valid_identifier?(t) do
<<>> -> true
<<??>> -> true
<<?!>> -> true
<<?@, t::binary>> -> valid_atom_piece?(t)
_ -> false
end
end
defp valid_identifier?(<<h, t :: binary>>)
when h in ?a..?z
when h in ?A..?Z
@@ -149,7 +161,7 @@ defimpl Inspect, for: Atom do
end
defimpl Inspect, for: BitString do
@doc %S"""
@doc ~S"""
Represents a string as itself escaping all necessary
characters. Binaries that contain non-printable characters
are printed using the bitstring syntax.
@@ -158,8 +170,10 @@ defimpl Inspect, for: BitString do
iex> inspect("bar")
"\"bar\""
iex> inspect("f\"oo")
"\"f\\\"oo\""
iex> inspect(<<0,1,2>>)
"<<0, 1, 2>>"
@@ -275,7 +289,7 @@ defimpl Inspect, for: BitString do
end
defimpl Inspect, for: List do
@doc %S"""
@doc ~S"""
Represents a list, checking if it can be printed or not.
If so, a single-quoted representation is returned,
otherwise the brackets syntax is used. Keywords are
@@ -285,8 +299,10 @@ defimpl Inspect, for: List do
iex> inspect('bar')
"'bar'"
iex> inspect([0|'bar'])
"[0, 98, 97, 114]"
iex> inspect([:foo,:bar])
"[:foo, :bar]"
@@ -305,7 +321,7 @@ defimpl Inspect, for: List do
end
end
defp keyword({key, value}, opts) do
def keyword({key, value}, opts) do
concat(
key_to_binary(key) <> ": ",
to_doc(value, opts)
@@ -319,15 +335,15 @@ defimpl Inspect, for: List do
end
end
defp 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)
end
end
defp keyword?([]), do: true
defp keyword?(_other), do: false
def keyword?([]), do: true
def keyword?(_other), do: false
end
defimpl Inspect, for: Tuple do
@@ -339,6 +355,7 @@ defimpl Inspect, for: Tuple do
iex> inspect({1, 2, 3})
"{1, 2, 3}"
iex> inspect(ArgumentError.new)
"ArgumentError[message: \\\"argument error\\\"]"
@@ -401,6 +418,32 @@ defimpl Inspect, for: Tuple do
end
end
defimpl Inspect, for: Map do
def inspect(map, opts) do
inspect(map, "", opts)
end
def inspect(map, name, opts) do
map = :maps.to_list(map)
surround_many("%" <> name <> "{", map, "}", opts.limit, traverse_fun(map, opts))
end
defp traverse_fun(list, opts) do
if Inspect.List.keyword?(list) do
&Inspect.List.keyword(&1, opts)
else
&to_map(&1, opts)
end
end
defp to_map({key, value}, opts) do
concat(
concat(to_doc(key, opts), " => "),
to_doc(value, opts)
)
end
end
defimpl Inspect, for: Integer do
@doc """
Represents the integer as a string.
@@ -436,21 +479,17 @@ defimpl Inspect, for: Float do
end
defimpl Inspect, for: Regex do
@doc %S"""
Represents the Regex using the `%r""` syntax.
@doc ~S"""
Represents the Regex using the `~r""` syntax.
## Examples
iex> inspect(%r/foo/m)
"%r\"foo\"m"
iex> inspect(~r/foo/m)
"~r\"foo\"m"
"""
def inspect(regex, opts) when size(regex) == 5 do
concat ["%r", to_doc(Regex.source(regex), opts), Regex.opts(regex)]
end
def inspect(other, Inspect.Opts[] = opts) do
to_doc(other, opts.records(false))
def inspect(regex, opts) when size(regex) == 4 do
concat ["~r", to_doc(Regex.source(regex), opts), Regex.opts(regex)]
end
end
@@ -476,18 +515,24 @@ defimpl Inspect, for: Function do
end
defp default_inspect(mod, fun_info) do
"#Function<#{uniq(fun_info)} in #{Inspect.Atom.inspect(mod)}.#{extract_name(fun_info[:name])}>"
"#Function<#{uniq(fun_info)}/#{fun_info[:arity]} in " <>
"#{Inspect.Atom.inspect(mod)}#{extract_name(fun_info[:name])}>"
end
defp extract_name([]) do
""
end
defp extract_name(name) do
case :binary.split(atom_to_binary(name), "-", [:global]) do
["", name | _] -> name
_ -> name
["", name | _] -> "." <> name
_ -> "." <> name
end
end
defp uniq(fun_info) do
integer_to_binary(fun_info[:new_index]) <> "." <> integer_to_binary(fun_info[:uniq])
integer_to_binary(fun_info[:new_index]) <> "." <>
integer_to_binary(fun_info[:uniq])
end
end
@@ -510,6 +555,24 @@ defimpl Inspect, for: Reference do
end
end
defimpl Inspect, for: Any do
def inspect(%{__struct__: struct} = map, opts) do
try do
struct.__struct__
rescue
_ -> Inspect.Map.inspect(map, opts)
else
dunder ->
if :maps.keys(dunder) == :maps.keys(map) do
Inspect.Map.inspect(:maps.remove(:__struct__, map),
Inspect.Atom.inspect(struct, opts), opts)
else
Inspect.Map.inspect(map, opts)
end
end
end
end
defimpl Inspect, for: HashDict do
def inspect(dict, opts) do
concat ["#HashDict<", Inspect.List.inspect(HashDict.to_list(dict), opts), ">"]
+48 -23
View File
@@ -1,5 +1,5 @@
defmodule Inspect.Algebra do
@moduledoc %S"""
@moduledoc ~S"""
A set of functions for creating and manipulating algebra
documents, as described in ["Strictly Pretty" (2000) by Christian Lindig][0].
@@ -8,6 +8,7 @@ defmodule Inspect.Algebra do
iex> Inspect.Algebra.empty
:doc_nil
iex> "foo"
"foo"
@@ -73,7 +74,7 @@ defmodule Inspect.Algebra do
# Functional interface to `doc` records
@type t :: :doc_nil | doc_cons_t | doc_nest_t | doc_break_t | doc_group_t | binary
@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
@@ -94,7 +95,7 @@ defmodule Inspect.Algebra do
defp do_is_doc(doc) do
quote do
is_binary(unquote(doc)) or
unquote(doc) == :doc_nil or
unquote(doc) in [:doc_nil, :doc_line] or
(is_tuple(unquote(doc)) and
elem(unquote(doc), 0) in [:doc_cons, :doc_nest, :doc_break, :doc_group])
end
@@ -105,22 +106,42 @@ defmodule Inspect.Algebra do
according to the inspect protocol.
"""
@spec to_doc(any, Inspect.Opts.t) :: t
def to_doc(arg, opts) when is_record(opts, Inspect.Opts) do
if is_tuple(arg) do
if elem(opts, Inspect.Opts.__record__(:index, :records)) do
try do
Inspect.inspect(arg, opts)
catch
_, _ -> Inspect.Tuple.inspect(arg, opts)
end
else
Inspect.Tuple.inspect(arg, opts)
def to_doc(arg, opts) when is_record(arg) and is_record(opts, Inspect.Opts) do
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
end
else
Inspect.inspect(arg, opts)
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
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
end
else
Inspect.Map.inspect(map, opts)
end
end
def to_doc(arg, opts) when is_record(opts, Inspect.Opts) do
Inspect.inspect(arg, opts)
end
@doc """
Returns `:doc_nil` which is a document entity used to represent
nothingness. Takes no arguments.
@@ -178,7 +199,7 @@ defmodule Inspect.Algebra do
doc_nest(indent: i, doc: x)
end
@doc %S"""
@doc ~S"""
Document entity representing a break. This break can
be rendered as a linebreak or as spaces, depending on the
`mode` of the chosen layout or the provided separator.
@@ -218,7 +239,7 @@ defmodule Inspect.Algebra do
@spec glue(t, binary, t) :: doc_cons_t
def glue(x, g, y) when is_binary(g), do: concat(x, concat(break(g), y))
@doc %S"""
@doc ~S"""
Returns a group containing the specified document.
## Examples
@@ -263,7 +284,7 @@ defmodule Inspect.Algebra do
@spec space(t, t) :: doc_cons_t
def space(x, y), do: concat(x, concat(" ", y))
@doc %S"""
@doc ~S"""
Inserts a mandatory linebreak between two document entities.
## Examples
@@ -274,7 +295,7 @@ defmodule Inspect.Algebra do
"""
@spec line(t, t) :: doc_cons_t
def line(x, y), do: concat(x, concat(@newline, y))
def line(x, y), do: concat(x, concat(:doc_line, y))
@doc """
Folds a list of document entities into a document entity
@@ -297,7 +318,7 @@ defmodule Inspect.Algebra do
# Elixir conveniences
@doc %S"""
@doc ~S"""
Surrounds a document with characters.
Puts the document between left and right enclosing and nesting it.
@@ -316,7 +337,7 @@ defmodule Inspect.Algebra do
group concat left, concat(nest(doc, @nesting), right)
end
@doc %S"""
@doc ~S"""
Maps and glues a collection of items together using the given separator
and surrounds them. A limit can be passed which, once reached, stops
gluing and outputs "..." instead.
@@ -337,7 +358,7 @@ defmodule Inspect.Algebra do
"""
@spec surround_many(binary, [any], binary, integer | :infinity, (term -> t), binary) :: t
def surround_many(left, docs, right, limit, fun, separator // @surround_separator)
def surround_many(left, docs, right, limit, fun, separator \\ @surround_separator)
def surround_many(left, [], right, _, _fun, _) do
concat(left, right)
@@ -397,6 +418,7 @@ defmodule Inspect.Algebra do
@spec fits?(integer, [{ integer, mode, t }]) :: boolean
def fits?(w, _) when w < 0, do: false
def fits?(_, []), do: true
def fits?(_, [{_, _, :doc_line} | _]), do: true
def fits?(w, [{_, _, :doc_nil} | t]), do: fits?(w, t)
def fits?(w, [{i, m, doc_cons(left: x, right: y)} | t]), do: fits?(w, [{i, m, x} | [{i, m, y} | t]])
def fits?(w, [{i, m, doc_nest(indent: j, doc: x)} | t]), do: fits?(w, [{i + j, m, x} | t])
@@ -408,6 +430,7 @@ defmodule Inspect.Algebra do
@doc false
@spec format(integer | :infinity, integer, [{ integer, mode, t }]) :: [binary]
def format(_, _, []), do: []
def format(w, _, [{i, _, :doc_line} | t]), do: [indent(i) | format(w, i, t)]
def format(w, k, [{_, _, :doc_nil} | t]), do: format(w, k, t)
def format(w, k, [{i, m, doc_cons(left: x, right: y)} | t]), do: format(w, k, [{i, m, x} | [{i, m, y} | t]])
def format(w, k, [{i, m, doc_nest(indent: j, doc: x)} | t]), do: format(w, k, [{i + j, m, x} | t])
@@ -420,11 +443,13 @@ defmodule Inspect.Algebra do
if w == :infinity or fits?(w - k, t) do
[s | format(w, k, t)]
else
prefix = :binary.copy(" ", i)
[@newline, prefix | format(w, i, t)]
[indent(i) | format(w, i, t)]
end
end
defp indent(0), do: @newline
defp indent(i), do: @newline <> :binary.copy(" ", i)
@doc false
@spec render([binary]) :: binary
def render(sdoc) do
+4 -2
View File
@@ -26,8 +26,8 @@ defmodule Integer do
end
@doc """
Converts a binary to an integer.
Converts a binary to an integer.
If successful, returns a tuple of the form `{ integer, remainder_of_binary }`.
Otherwise `:error`.
@@ -35,8 +35,10 @@ defmodule Integer do
iex> Integer.parse("34")
{34,""}
iex> Integer.parse("34.5")
{34,".5"}
iex> Integer.parse("three")
:error
+85 -20
View File
@@ -6,6 +6,61 @@ defexception IO.StreamError, [:reason, :message] do
end
end
defmodule IO.Stream do
@moduledoc """
Defines a `IO.Stream` struct returned by `IO.stream/2` and `IO.binstream/2`.
The following fields are public:
* `device` - the IO device
* `raw` - a boolean indicating if bin functions should be used
* `line_or_bytes` - if reading should read lines or a given amount of bytes
"""
defstruct device: nil, raw: true, line_or_bytes: :line
defimpl Collectable do
def empty(stream) do
stream
end
def into(%{ device: device, raw: raw } = stream) do
{ :ok, into(stream, device, raw) }
end
defp into(stream, device, raw) do
fn
:ok, { :cont, x } ->
case raw do
true -> IO.binwrite(device, x)
false -> IO.write(device, x)
end
:ok, _ -> stream
end
end
end
defimpl Enumerable do
def reduce(%{ device: device, raw: raw, line_or_bytes: line_or_bytes }, acc, fun) do
next_fun =
case raw do
true -> &IO.each_binstream(&1, line_or_bytes)
false -> &IO.each_stream(&1, line_or_bytes)
end
Stream.unfold(device, next_fun).(acc, fun)
end
def count(_stream) do
{ :error, __MODULE__ }
end
def member?(_stream, _term) do
{ :error, __MODULE__ }
end
end
end
defmodule IO do
@moduledoc """
Functions handling IO.
@@ -49,7 +104,7 @@ defmodule IO do
NFS file system.
"""
@spec read(device, :line | non_neg_integer) :: chardata | nodata
def read(device // group_leader, chars_or_line)
def read(device \\ group_leader, chars_or_line)
def read(device, :line) do
:io.get_line(map_dev(device), '')
@@ -72,7 +127,7 @@ defmodule IO do
NFS file system.
"""
@spec binread(device, :line | non_neg_integer) :: iodata | nodata
def binread(device // group_leader, chars_or_line)
def binread(device \\ group_leader, chars_or_line)
def binread(device, :line) do
case :file.read_line(map_dev(device)) do
@@ -106,7 +161,7 @@ defmodule IO do
"""
@spec write(device, chardata) :: :ok
def write(device // group_leader(), item) do
def write(device \\ group_leader(), item) do
:io.put_chars map_dev(device), to_chardata(item)
end
@@ -117,7 +172,7 @@ 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
def binwrite(device \\ group_leader(), item) when is_iolist(item) do
:file.write map_dev(device), item
end
@@ -127,7 +182,7 @@ defmodule IO do
to be a chardata.
"""
@spec puts(device, chardata) :: :ok
def puts(device // group_leader(), item) do
def puts(device \\ group_leader(), item) do
erl_dev = map_dev(device)
:io.put_chars erl_dev, [to_chardata(item), ?\n]
end
@@ -146,7 +201,7 @@ defmodule IO do
"""
@spec inspect(term, Keyword.t) :: term
def inspect(item, opts // []) do
def inspect(item, opts \\ []) do
inspect group_leader(), item, opts
end
@@ -159,7 +214,7 @@ defmodule IO do
unless Keyword.get(opts, :width) do
opts = case :io.columns(device) do
{ :ok, width } -> Keyword.put(opts, :width, width)
{ :ok, width } -> [width: width] ++ opts
{ :error, _ } -> opts
end
end
@@ -185,7 +240,7 @@ defmodule IO do
"""
@spec getn(chardata, pos_integer) :: chardata | nodata
@spec getn(device, chardata) :: chardata | nodata
def getn(prompt, count // 1)
def getn(prompt, count \\ 1)
def getn(prompt, count) when is_integer(count) do
getn(group_leader, prompt, count)
@@ -219,14 +274,19 @@ defmodule IO do
NFS file system.
"""
@spec gets(device, chardata) :: chardata | nodata
def gets(device // group_leader(), prompt) do
def gets(device \\ group_leader(), prompt) do
:io.get_line(map_dev(device), to_chardata(prompt))
end
@doc """
Converts the io device into a Stream. The device is
iterated line by line if `:line` is given or by a given
number of codepoints.
Converts the io device into a `IO.Stream`.
An `IO.Stream` implements both `Enumerable` and
`Collectable`, allowing it to be used for both read
and write.
The device is iterated line by line if `:line` is given or
by a given number of codepoints.
This reads the IO as utf-8. Check out
`IO.binstream/2` to handle the IO as a raw binary.
@@ -244,24 +304,29 @@ defmodule IO do
"""
@spec stream(device, :line | pos_integer) :: Enumerable.t
def stream(device, line_or_codepoints) do
Stream.unfold(map_dev(device), &do_stream(&1, line_or_codepoints))
%IO.Stream{ device: map_dev(device), raw: false, line_or_bytes: line_or_codepoints }
end
@doc """
Converts the io device into a Stream. The device is
iterated line by line or by a number of bytes. This
reads the IO as a raw binary.
Converts the IO device into a `IO.Stream`.
An `IO.Stream` implements both `Enumerable` and
`Collectable`, allowing it to be used for both read
and write.
The device is iterated line by line or by a number of bytes.
This reads the IO device as a raw binary.
Note that an IO stream has side effects and every time
you go over the stream you may get different results.
"""
@spec binstream(device, :line | pos_integer) :: Enumerable.t
def binstream(device, line_or_bytes) do
Stream.unfold(map_dev(device), &do_binstream(&1, line_or_bytes))
%IO.Stream{ device: map_dev(device), raw: true, line_or_bytes: line_or_bytes }
end
@doc false
def do_stream(device, what) do
def each_stream(device, what) do
case read(device, what) do
:eof ->
nil
@@ -273,7 +338,7 @@ defmodule IO do
end
@doc false
def do_binstream(device, what) do
def each_binstream(device, what) do
case binread(device, what) do
:eof ->
nil
@@ -287,7 +352,7 @@ defmodule IO do
# Map the Elixir names for standard io and error to Erlang names
defp map_dev(:stdio), do: :standard_io
defp map_dev(:stderr), do: :standard_error
defp map_dev(other), do: other
defp map_dev(other) when is_atom(other) or is_pid(other) or is_tuple(other), do: other
defp to_chardata(list) when is_list(list), do: list
defp to_chardata(other), do: to_string(other)
+10 -9
View File
@@ -1,7 +1,7 @@
defmodule IO.ANSI.Sequence do
@moduledoc false
defmacro defsequence(name, code // "", terminator // "m") do
defmacro defsequence(name, code \\ "", terminator \\ "m") do
quote bind_quoted: [name: name, code: code, terminator: terminator] do
def unquote(name)() do
"\e[#{unquote(code)}#{unquote(terminator)}"
@@ -32,7 +32,7 @@ defmodule IO.ANSI do
"""
@spec terminal? :: boolean
@spec terminal?(:io.device) :: boolean
def terminal?(device // :erlang.group_leader) do
def terminal?(device \\ :erlang.group_leader) do
match?({:ok, _}, :io.columns(device))
end
@@ -72,7 +72,7 @@ defmodule IO.ANSI do
@doc "Sets primary (default) font"
defsequence :primary_font, 10
lc font_n inlist [1, 2, 3, 4, 5, 6, 7, 8, 9] do
for font_n <- [1, 2, 3, 4, 5, 6, 7, 8, 9] do
@doc "Sets alternative font #{font_n}"
defsequence :"font_#{font_n}", font_n + 10
end
@@ -92,7 +92,7 @@ defmodule IO.ANSI do
colors = [:black, :red, :green, :yellow, :blue, :magenta, :cyan, :white]
colors = Enum.zip(0..(length(colors)-1), colors)
lc { code, color } inlist colors do
for { code, color } <- colors do
@doc "Sets foreground color to #{color}"
defsequence color, code + 30
@@ -134,11 +134,11 @@ defmodule IO.ANSI do
end
defp escape_sequence(other) do
[spec|_] = String.split(other, %r/(,|\})/)
[spec|_] = String.split(other, ~r/(,|\})/)
raise ArgumentError, message: "invalid ANSI sequence specification: #{spec}"
end
@doc %S"""
@doc ~S"""
Escapes a string by converting named ANSI sequences into actual ANSI codes.
The format for referring to sequences is `%{red}` and `%{red,bright}` (for
@@ -159,7 +159,7 @@ defmodule IO.ANSI do
"""
@spec escape(String.t, emit :: boolean) :: String.t
def escape(string, emit // terminal?) do
def escape(string, emit \\ terminal?) do
{rendered, emitted} = do_escape(string, false, emit, false, [])
if emitted and emit do
rendered <> reset
@@ -168,7 +168,7 @@ defmodule IO.ANSI do
end
end
@doc %S"""
@doc ~S"""
Escapes a string by converting named ANSI sequences into actual ANSI codes.
The format for referring to sequences is `%{red}` and `%{red,bright}` (for
@@ -183,12 +183,13 @@ defmodule IO.ANSI do
iex> IO.ANSI.escape_fragment("Hello %{red,bright,green}yes", true)
"Hello \e[31m\e[1m\e[32myes"
iex> IO.ANSI.escape_fragment("%{reset}bye", true)
"\e[0mbye"
"""
@spec escape_fragment(String.t, emit :: boolean) :: String.t
def escape_fragment(string, emit // terminal?) do
def escape_fragment(string, emit \\ terminal?) do
{rendered, _emitted} = do_escape(string, false, emit, false, [])
rendered
end
+6 -6
View File
@@ -16,7 +16,7 @@ defmodule IO.ANSI.Docs do
See `print/3` for docs on the supported options.
"""
def print_heading(heading, colors // @default_colors) do
def print_heading(heading, colors \\ @default_colors) do
IO.puts IO.ANSI.reset
width = column_width()
padding = div(width + String.length(heading), 2)
@@ -46,7 +46,7 @@ defmodule IO.ANSI.Docs do
* Intensity: `normal bright`
* Decoration: `underline reverse`
"""
def print(doc, colors // @default_colors) do
def print(doc, colors \\ @default_colors) do
doc
|> String.split(["\r\n","\n"], trim: false)
|> Enum.map(&String.rstrip/1)
@@ -185,7 +185,7 @@ defmodule IO.ANSI.Docs do
|> Enum.join(" ")
|> handle_links
|> handle_inline(nil, [], [], colors)
|> String.split(%r{\s})
|> String.split(~r{\s})
|> write_with_wrap(column_width() - size(indent), indent, from_list)
unless from_list, do: IO.puts(IO.ANSI.reset)
@@ -276,16 +276,16 @@ defmodule IO.ANSI.Docs do
end
defp escape_underlines_in_link(text) do
case Regex.match?(%r{.*(https?\S*)}, text) do
case Regex.match?(~r{.*(https?\S*)}, text) do
true ->
Regex.replace(%r{_}, text, "\\\\_")
Regex.replace(~r{_}, text, "\\\\_")
_ ->
text
end
end
defp remove_square_brackets_in_link(text) do
Regex.replace(%r{\[(.*?)\]\((.*?)\)}, text, "\\1 (\\2)")
Regex.replace(~r{\[(.*?)\]\((.*?)\)}, text, "\\1 (\\2)")
end
# Single inline quotes.
+838 -681
View File
File diff suppressed because it is too large Load Diff
+8 -8
View File
@@ -9,14 +9,14 @@ defmodule Kernel.CLI do
This is the API invoked by Elixir boot process.
"""
def main(argv) do
argv = lc arg inlist argv, do: String.from_char_list!(arg)
argv = for arg <- argv, do: String.from_char_list!(arg)
{ config, argv } = process_argv(argv, Kernel.CLI.Config.new)
System.argv(argv)
run fn ->
command_results = Enum.map(Enum.reverse(config.commands), &process_command(&1, config))
command_errors = lc { :error, msg } inlist command_results, do: msg
command_errors = for { :error, msg } <- command_results, do: msg
errors = Enum.reverse(config.errors) ++ command_errors
if errors != [] do
@@ -34,7 +34,7 @@ defmodule Kernel.CLI do
This function is used by Elixir's CLI and also
by escripts generated by Elixir.
"""
def run(fun, halt // true) do
def run(fun, halt \\ true) do
try do
fun.()
if halt do
@@ -60,7 +60,7 @@ defmodule Kernel.CLI do
defp at_exit(status) do
hooks = :elixir_code_server.call(:flush_at_exit)
lc hook inlist hooks do
for hook <- hooks do
try do
hook.(status)
catch
@@ -157,7 +157,7 @@ defmodule Kernel.CLI do
process_shared t, config
end
defp process_shared([erl|t], config) when erl in ["--detached", "--hidden"] do
defp process_shared([erl|t], config) when erl in ["--detached", "--hidden", "--gen-debug"] do
process_shared t, config
end
@@ -171,7 +171,7 @@ defmodule Kernel.CLI do
{ config, t }
end
defp process_argv(["+compile"|t], config) do
defp process_argv(["+elixirc"|t], config) do
process_compiler t, config
end
@@ -288,10 +288,10 @@ defmodule Kernel.CLI do
end
defp process_command({:app, app}, _config) when is_binary(app) do
case Application.Behaviour.start(binary_to_atom(app)) do
case :application.ensure_all_started(binary_to_atom(app)) do
{ :error, reason } ->
{ :error, "--app : Could not start application #{app}: #{inspect reason}" }
:ok ->
{ :ok, _ } ->
:ok
end
end
+13 -10
View File
@@ -13,24 +13,27 @@ defmodule Kernel.ErrorHandler do
:error_handler.undefined_lambda(module, fun, args)
end
def release() do
# On release, no further allow elixir_ensure_compiled
# directives and revert to the original error handler.
# Note we should not delete the elixir_compiler_pid though,
# as we still want to send notifications to the compiler.
:erlang.erase(:elixir_ensure_compiled)
:erlang.process_flag(:error_handler, :error_handler)
:ok
end
defp ensure_loaded(module) do
case Code.ensure_loaded(module) do
{ :module, _ } -> []
{ :error, _ } ->
parent = :erlang.get(:elixir_compiler_pid)
ref = :erlang.make_ref
send parent, { :waiting, self(), ref, module }
send parent, { :waiting, module, self(), ref, module }
:erlang.garbage_collect(self)
receive do
{ ^ref, :ready } ->
:ok
{ ^ref, :release } ->
# On release, get rid of the compiler pid,
# no further allow elixir_ensure_compiled directives
# and revert to the original error handler.
:erlang.erase(:elixir_compiler_pid)
:erlang.erase(:elixir_ensure_compiled)
:erlang.process_flag(:error_handler, :error_handler)
{ ^ref, :ready } -> :ok
{ ^ref, :release } -> release()
end
end
end
+51 -34
View File
@@ -24,7 +24,7 @@ defmodule Kernel.ParallelCompiler do
The compiler doesn't care about the return values of the callbacks.
Returns the modules generated by each compiled file.
"""
def files(files, callbacks // [])
def files(files, callbacks \\ [])
def files(files, callbacks) when is_list(callbacks) do
spawn_compilers(files, nil, callbacks)
@@ -34,7 +34,7 @@ defmodule Kernel.ParallelCompiler do
Compiles the given files to the given path.
Read `files/2` for more information.
"""
def files_to_path(files, path, callbacks // [])
def files_to_path(files, path, callbacks \\ [])
def files_to_path(files, path, callbacks) when is_binary(path) and is_list(callbacks) do
spawn_compilers(files, path, callbacks)
@@ -57,16 +57,16 @@ defmodule Kernel.ParallelCompiler do
end
# We already have 4 currently running, don't spawn new ones
defp spawn_compilers(files, original, output, callbacks, waiting, queued, schedulers, result) when
defp spawn_compilers(entries, original, output, callbacks, waiting, queued, schedulers, result) when
length(queued) - length(waiting) >= schedulers do
wait_for_messages(files, original, output, callbacks, waiting, queued, schedulers, result)
wait_for_messages(entries, original, output, callbacks, waiting, queued, schedulers, result)
end
# Release waiting processes
defp spawn_compilers([h|t], original, output, callbacks, waiting, queued, schedulers, result) when is_pid(h) do
{ ^h, ref, _ } = List.keyfind(waiting, h, 0)
{ _kind, ^h, ref, _module } = List.keyfind(waiting, h, 1)
send h, { ref, :ready }
waiting = List.keydelete(waiting, h, 0)
waiting = List.keydelete(waiting, h, 1)
spawn_compilers(t, original, output, callbacks, waiting, queued, schedulers, result)
end
@@ -97,16 +97,19 @@ defmodule Kernel.ParallelCompiler do
end)
end
spawn_compilers(t, original, output, callbacks, waiting, [{pid, ref, h}|queued], schedulers, result)
spawn_compilers(t, original, output, callbacks, waiting,
[{ pid, ref, h }|queued], schedulers, result)
end
# No more files, nothing waiting, queue is empty, we are done
defp spawn_compilers([], _original, _output, _callbacks, [], [], _schedulers, result), do: result
defp spawn_compilers([], _original, _output, _callbacks, [], [], _schedulers, result) do
for { :module, mod } <- result, do: mod
end
# Queued x, waiting for x: POSSIBLE ERROR! Release processes so we get the failures
defp spawn_compilers([], original, output, callbacks, waiting, queued, schedulers, result) when length(waiting) == length(queued) do
Enum.each queued, fn { child, _, _ } ->
{ ^child, ref, _ } = List.keyfind(waiting, child, 0)
{ _kind, ^child, ref, _module } = List.keyfind(waiting, child, 1)
send child, { ref, :release }
end
wait_for_messages([], original, output, callbacks, waiting, queued, schedulers, result)
@@ -118,8 +121,17 @@ defmodule Kernel.ParallelCompiler do
end
# Wait for messages from child processes
defp wait_for_messages(files, original, output, callbacks, waiting, queued, schedulers, result) do
defp wait_for_messages(entries, original, output, callbacks, waiting, queued, schedulers, result) do
receive do
{ :struct_available, module } ->
available = for { :struct, pid, _, waiting_module } <- waiting,
module == waiting_module,
not pid in entries,
do: pid
spawn_compilers(available ++ entries, original, output, callbacks,
waiting, queued, schedulers, [{ :struct, module }|result])
{ :module_available, child, ref, file, module, binary } ->
if callback = Keyword.get(callbacks, :each_module) do
callback.(file, module, binary)
@@ -128,21 +140,25 @@ defmodule Kernel.ParallelCompiler do
# Release the module loader which is waiting for an ack
send child, { ref, :ack }
available = lc { pid, _, waiting_module } inlist waiting,
waiting_module == module,
available = for { _kind, pid, _, waiting_module } <- waiting,
module == waiting_module,
not pid in entries,
do: pid
spawn_compilers(available ++ files, original, output, callbacks,
waiting, queued, schedulers, [module|result])
spawn_compilers(available ++ entries, original, output, callbacks,
waiting, queued, schedulers, [{ :module, module }|result])
{ :waiting, child, ref, on } ->
# Oops, we already got this module. Do not put it on waiting.
if :lists.member(on, result) do
send child, { :release, ref }
{ :waiting, kind, child, ref, on } ->
defined = fn { k, m } -> on == m and k in [kind, :module] end
# Oops, we already got it, do not put it on waiting.
if :lists.any(defined, result) do
send child, { ref, :ready }
else
waiting = [{ child, ref, on }|waiting]
waiting = [{ kind, child, ref, on }|waiting]
end
spawn_compilers(files, original, output, callbacks, waiting, queued, schedulers, result)
spawn_compilers(entries, original, output, callbacks, waiting, queued, schedulers, result)
{ :DOWN, _down_ref, :process, down_pid, { :compiled, file } } ->
if callback = Keyword.get(callbacks, :each_file) do
@@ -151,35 +167,36 @@ defmodule Kernel.ParallelCompiler do
# Sometimes we may have spurious entries in the waiting
# list because someone invoked try/rescue UndefinedFunctionError
new_waiting = List.keydelete(waiting, down_pid, 0)
new_entries = List.delete(entries, down_pid)
new_queued = List.keydelete(queued, down_pid, 0)
spawn_compilers(files, original, output, callbacks, new_waiting, new_queued, schedulers, result)
new_waiting = List.keydelete(waiting, down_pid, 1)
spawn_compilers(new_entries, original, output, callbacks, new_waiting, new_queued, schedulers, result)
{ :DOWN, down_ref, :process, _down_pid, { :failure, kind, reason, stacktrace } } ->
handle_failure(down_ref, kind, reason, stacktrace, files, waiting, queued)
wait_for_messages(files, original, output, callbacks, waiting, queued, schedulers, result)
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, [], files, waiting, queued)
wait_for_messages(files, original, output, callbacks, waiting, queued, schedulers, result)
handle_failure(down_ref, :exit, other, [], entries, waiting, queued)
wait_for_messages(entries, original, output, callbacks, waiting, queued, schedulers, result)
end
end
defp handle_failure(ref, kind, reason, stacktrace, files, waiting, queued) do
defp handle_failure(ref, kind, reason, stacktrace, entries, waiting, queued) do
case List.keyfind(queued, ref, 1) do
{ child, ^ref, file } ->
if many_missing?(child, files, waiting, queued) do
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, 0) do
{ ^pid, _, mod } -> IO.puts "* #{file} is missing module #{inspect mod}"
case List.keyfind(waiting, pid, 1) do
{ _, ^pid, _, mod } -> IO.puts "* #{file} is missing module #{inspect mod}"
_ -> :ok
end
end
IO.puts "\nThe first failure is shown below..."
IO.puts "\nOne of the failures is shown below..."
end
IO.puts "== Compilation error on file #{file} =="
@@ -189,11 +206,11 @@ defmodule Kernel.ParallelCompiler do
end
end
defp many_missing?(child, files, waiting, queued) do
defp many_missing?(child, entries, waiting, queued) do
waiting_length = length(waiting)
match?({ ^child, _, _ }, List.keyfind(waiting, child, 0)) and
waiting_length > 1 and files == [] and
match?({ _, ^child, _, _ }, List.keyfind(waiting, child, 1)) and
waiting_length > 1 and entries == [] and
waiting_length == length(queued)
end
end
+1 -1
View File
@@ -13,7 +13,7 @@ defmodule Kernel.ParallelRequire do
Returns the modules generated by each required file.
"""
def files(files, callback // default_callback) do
def files(files, callback \\ default_callback) do
schedulers = max(:erlang.system_info(:schedulers_online), 2)
spawn_requires(files, [], callback, schedulers, [])
end
-413
View File
@@ -1,413 +0,0 @@
# This is an optimization Elixir runs on function clauses.
# Whenever a variables matches against a record (a tagged
# tuple), this information is stored in order to optimize
# record calls.
defmodule Kernel.RecordRewriter do
@moduledoc false
def optimize_clause(module, clause) do
optimize_clause(clause, module, :orddict.new)
end
## Clause
defp optimize_clause({ :clause, line, args, guards, body }, module, dict) do
{ args, dict } = optimize_args(args, module, dict)
{ body, dict, res } = optimize_body(body, module, dict, [])
{ { :clause, line, args, guards, body }, dict, res }
end
defp optimize_args(args, module, dict) do
Enum.map_reduce args, dict, fn(arg, acc) ->
{ new_arg, new_acc, _res } = optimize_expr(arg, module, acc)
{ new_arg, new_acc }
end
end
defp optimize_body([], _module, dict, _acc) do
{ [], dict, nil }
end
defp optimize_body([h], module, dict, acc) do
{ new_expr, new_dict, new_res } = optimize_expr(h, module, dict)
{ Enum.reverse([new_expr|acc]), new_dict, new_res }
end
defp optimize_body([h|t], module, dict, acc) do
{ new_expr, new_dict, _ } = optimize_expr(h, module, dict)
optimize_body(t, module, new_dict, [new_expr|acc])
end
## Record helpers
defp record_fields(record, record) do
fields = Module.get_attribute(record, :record_fields)
optimizable = Module.get_attribute(record, :record_optimizable)
unless nil?(fields) or nil?(optimizable) do
{ (lc { k, _ } inlist fields, do: k), optimizable }
end
end
defp record_fields(_module, record) do
if Code.ensure_loaded?(record) && function_exported?(record, :__record__, 1) do
try do
fields = lc { k, _ } inlist record.__record__(:fields), do: k
optimizable = record.__record__(:optimizable)
{ fields, optimizable }
rescue
[UndefinedFunctionError, FunctionClauseError] -> nil
end
end
end
defp record_field_info(function) do
case atom_to_list(function) do
'update' -> nil
'update_' ++ _field -> nil
_ -> { :accessor, function }
end
end
defp optimize_call(line, module, { record, _ } = res, left, { :atom, _, function }, args) do
case record_fields(module, record) do
{ fields, optimizable } ->
opt_call =
if :lists.member({ function, length(args) + 1 }, optimizable) do
case record_field_info(function) do
{ kind, field } ->
if index = Enum.find_index(fields, &(field == &1)) do
optimize_record_accessor_call(line, res, kind, field, index, left, args)
end
nil ->
optimize_record_other_call(line, record, res, function, left, args)
end
end
opt_call || optimize_record_other_call(line, record, nil, function, left, args)
nil ->
nil
end
end
defp optimize_call(_line, _module, _res, _left, _right, _args) do
nil
end
defp optimize_record_accessor_call(line, _res, :accessor, _field, index, left, []) do
call = { :call, line,
{ :remote, line, { :atom, 0, :erlang }, { :atom, 0, :element } },
[{ :integer, 0, index + 2 }, left]
}
{ call, nil }
end
defp optimize_record_accessor_call(line, res, :accessor, _field, index, left, [arg]) do
call = { :call, line,
{ :remote, line, { :atom, 0, :erlang }, { :atom, 0, :setelement } },
[{ :integer, 0, index + 2 }, left, arg]
}
{ call, res }
end
defp optimize_record_other_call(line, record, res, function, left, args) do
call = { :call, line,
{ :remote, line, { :atom, 0, record }, { :atom, 0, function } },
args ++ [left]
}
{ call, res }
end
## Expr
defp optimize_expr({ :call, call_line,
{ :remote, _, { :atom, _, :erlang }, { :atom, _, :setelement } } = remote,
[{ :integer, _, pos } = integer, tuple, value] }, module, dict) when pos > 1 do
{ tuple, dict, res } = optimize_expr(tuple, module, dict)
{ value, dict, _ } = optimize_expr(value, module, dict)
{ { :call, call_line, remote, [integer, tuple, value] }, dict, res }
end
defp optimize_expr({ :call, call_line, { :remote, line, left, right }, args }, module, dict) do
{ left, dict, res } = optimize_expr(left, module, dict)
{ right, dict, _ } = optimize_expr(right, module, dict)
{ args, dict } = optimize_args(args, module, dict)
case optimize_call(call_line, module, res, left, right, args) do
{ call, call_res } ->
{ call, dict, call_res }
nil ->
{ { :call, call_line, { :remote, line, left, right }, args }, dict, nil }
end
end
defp optimize_expr({ :call, line, expr, args }, module, dict) do
{ expr, dict, _ } = optimize_expr(expr, module, dict)
{ args, dict } = optimize_args(args, module, dict)
{ { :call, line, expr, args }, dict, nil }
end
defp optimize_expr({ :match, line, left, right }, module, dict) do
{ left, dict, left_res } = optimize_expr(left, module, dict)
{ right, dict, right_res } = optimize_expr(right, module, dict)
match = { :match, line, left, right }
if left_res do
dict = assign_vars(extract_vars(right, []), dict, left_res)
end
if right_res do
dict = assign_vars(extract_vars(left, []), dict, right_res)
end
{ match, dict, right_res || left_res }
end
defp optimize_expr({ :op, line, op, left, right }, module, dict) do
{ left, dict, _ } = optimize_expr(left, module, dict)
{ right, dict, _ } = optimize_expr(right, module, dict)
{ { :op, line, op, left, right }, dict, nil }
end
defp optimize_expr({ :op, line, op, expr }, module, dict) do
{ expr, dict, _ } = optimize_expr(expr, module, dict)
{ { :op, line, op, expr }, dict, nil }
end
defp optimize_expr({ :bin, line, elements }, module, dict) do
{ elements, dict } = optimize_args(elements, module, dict)
{ { :bin, line, elements }, dict, nil }
end
defp optimize_expr({ :bin_element, line, expr, type1, type2 }, module, dict) do
{ expr, dict, _ } = optimize_expr(expr, module, dict)
{ { :bin_element, line, expr, type1, type2 }, dict, nil }
end
defp optimize_expr({ :cons, line, left, right }, module, dict) do
{ left, dict, _ } = optimize_expr(left, module, dict)
{ right, dict, _ } = optimize_expr(right, module, dict)
{ { :cons, line, left, right }, dict, nil }
end
defp optimize_expr({ :block, line, args }, module, dict) do
{ args, dict, res } = optimize_body(args, module, dict, [])
{ { :block, line, args }, dict, res }
end
defp optimize_expr({ :tuple, line, args }, module, dict) do
{ args, dict, args_res } = optimize_tuple_args(args, module, dict)
args_res = if Enum.any?(args_res), do: args_res, else: nil
res =
case args do
[{ :atom, _, atom }|_] -> atom
_ -> nil
end
{ { :tuple, line, args }, dict, { res, args_res } }
end
defp optimize_expr({ :var, _, name } = var, _module, dict) do
case :orddict.find(name, dict) do
{ :ok, res } -> { var, dict, res }
:error -> { var, dict, nil }
end
end
defp optimize_expr({ :case, line, expr, clauses }, module, dict) do
{ expr, dict, _ } = optimize_expr(expr, module, dict)
tuples = lc clause inlist clauses, do: optimize_clause(clause, module, dict)
clauses = lc { clause, _, _ } inlist tuples, do: clause
dict = join_dict(tuples)
res = join_result(tuples)
{ { :case, line, expr, clauses }, dict, res }
end
defp optimize_expr({ :receive, line, clauses }, module, dict) do
tuples = lc clause inlist clauses, do: optimize_clause(clause, module, dict)
clauses = lc { clause, _, _ } inlist tuples, do: clause
dict = join_dict(tuples)
res = join_result(tuples)
{ { :receive, line, clauses }, dict, res }
end
defp optimize_expr({ :receive, line, clauses, after_key, after_value }, module, dict) do
tuples = lc clause inlist clauses, do: optimize_clause(clause, module, dict)
clauses = lc { clause, _, _ } inlist tuples, do: clause
{ after_key, dict, _ } = optimize_expr(after_key, module, dict)
{ after_value, dict, res } = optimize_body(after_value, module, dict, [])
dict = join_dict(tuples, dict)
res = join_result(tuples, res)
{ { :receive, line, clauses, after_key, after_value }, dict, res }
end
defp optimize_expr({ :try, line, body, elses, catches, try_after }, module, dict) do
tuples = lc clause inlist catches, do: optimize_clause(clause, module, dict)
catches = lc { clause, _, _ } inlist tuples, do: clause
tuples = lc clause inlist elses, do: optimize_clause(clause, module, dict)
elses = lc { clause, _, _ } inlist tuples, do: clause
{ body, _, res } = optimize_body(body, module, dict, [])
res = join_result(tuples, res)
{ try_after, _, _ } = optimize_body(try_after, module, dict, [])
{ { :try, line, body, elses, catches, try_after }, dict, res }
end
defp optimize_expr({ :fun, line, { :function, receiver, name, arity } }, module, dict) do
{ receiver, dict, _ } = optimize_expr(receiver, module, dict)
{ name, dict, _ } = optimize_expr(name, module, dict)
{ arity, dict, _ } = optimize_expr(arity, module, dict)
{ { :fun, line, { :function, receiver, name, arity } }, dict, nil }
end
defp optimize_expr({ :fun, line, { :clauses, clauses } }, module, dict) do
clauses = lc clause inlist clauses do
{ clause, _, _ } = optimize_clause(clause, module, dict)
clause
end
{ { :fun, line, { :clauses, clauses } }, dict, nil }
end
defp optimize_expr({ comprehension, line, expr, args }, module, dict) when comprehension in [:lc, :bc] do
{ args, new_dict } = optimize_args(args, module, dict)
{ expr, _, _ } = optimize_expr(expr, module, new_dict)
{ { comprehension, line, expr, args }, dict, nil }
end
defp optimize_expr({ generate, line, left, right }, module, dict) when generate in [:generate, :b_generate] do
{ left, dict, _ } = optimize_expr(left, module, dict)
{ right, dict, _ } = optimize_expr(right, module, dict)
{ { generate, line, left, right }, dict, nil }
end
defp optimize_expr(other, _module, dict) when elem(other, 0) in [:string, :atom, :integer, :float, :nil, :fun] do
{ other, dict, nil }
end
## Helpers
defp optimize_tuple_args(args, module, dict) do
{ final_args, { final_dict, final_acc } } =
Enum.map_reduce args, { dict, [] }, fn(arg, { acc_dict, acc_res }) ->
{ new_arg, new_acc, res } = optimize_expr(arg, module, acc_dict)
{ new_arg, { new_acc, [res|acc_res] } }
end
{ final_args, final_dict, Enum.reverse(final_acc) }
end
defp assign_vars([key|t], dict, { _, value } = res) when is_list(key) and is_list(value) and length(key) == length(value) do
assign_vars t, assign_nested_vars(key, dict, value), res
end
defp assign_vars([key|t], dict, { value, _ } = res) when is_atom(key) and value != nil do
dict =
case :orddict.find(key, dict) do
{ :ok, ^res } ->
dict
{ :ok, { ^value, _ } } ->
:orddict.store(key, { value, nil }, dict)
{ :ok, _ } ->
# We are overriding a type of an existing variable,
# which means the source code is invalid.
:orddict.store(key, nil, dict)
:error ->
:orddict.store(key, res, dict)
end
assign_vars t, dict, res
end
defp assign_vars([_|t], dict, res) do
assign_vars t, dict, res
end
defp assign_vars([], dict, _res) do
dict
end
defp assign_nested_vars([vars|vt], dict, [res|rt]) do
assign_nested_vars(vt, assign_vars(vars, dict, res), rt)
end
defp assign_nested_vars([], dict, []) do
dict
end
defp extract_vars({ :match, _, left, right }, vars) do
vars = extract_vars(right, vars)
extract_vars(left, vars)
end
defp extract_vars({ :var, _, name }, vars) do
[name|vars]
end
defp extract_vars({ :tuple, _, args }, vars) do
[Enum.map(args, &extract_vars(&1, []))|vars]
end
defp extract_vars(_, vars) do
vars
end
defp join_dict([]) do
[]
end
defp join_dict([{ _, dict, _ }|t]) do
join_dict(t, dict)
end
defp join_dict([{ _, dict, _ }|t], other) do
other = Enum.reduce other, other, fn
{ key, { value, _ } = res }, acc ->
case :orddict.find(key, dict) do
{ :ok, ^res } -> acc
{ :ok, { ^value, _ } } -> :orddict.store(key, { value, nil }, acc)
{ :ok, _ } -> :orddict.store(key, nil, acc)
:error -> :orddict.erase(key, acc)
end
{ key, nil }, acc ->
:orddict.store(key, nil, acc)
end
join_dict(t, other)
end
defp join_dict([], other) do
other
end
defp join_result([]) do
[]
end
defp join_result([{ _, _, res }|t]) do
join_result(t, res)
end
defp join_result([{ _, _, res }|t], res) do
join_result(t, res)
end
defp join_result([{ _, _, { res, _ } }|t], { res, _ }) do
join_result(t, { res, nil })
end
defp join_result([{ _, _, _ }|_], _res) do
nil
end
defp join_result([], res) do
res
end
end
+246 -113
View File
@@ -20,22 +20,141 @@ defmodule Kernel.SpecialForms do
"""
@doc """
Defines a new tuple.
Creates a tuple.
Only two item tuples are considered literals in Elixir.
Therefore all other tuples are represented in the AST
as a call to the special form `:{}`.
Conveniences for manipulating tuples can be found in the
`Tuple` module. Some functions for working with tuples are
also available in `Kernel`, namely `Kernel.elem/2`,
`Kernel.set_elem/3` and `Kernel.tuple_size/1`.
## Examples
iex> { 1, 2, 3 }
{ 1, 2, 3 }
iex> quote do: { 1, 2, 3 }
{ :{}, [], [1,2,3] }
"""
defmacro unquote(:{})(args)
@doc """
Creates a map.
Maps are key-value stores where keys are compared
using the match operator (`===`). Maps can be created with
the `%{}` special form where keys are associated via `=>`:
%{ 1 => 2 }
Maps also support the keyword notation, as other special forms,
as long as they are at the end of the argument list:
%{ hello: :world, with: :keywords }
%{ :hello => :world, with: :keywords }
If a map has duplicated keys, the last key will always have
higher precedence:
iex> %{ a: :b, a: :c }
%{ a: :c }
Conveniences for manipulating maps can be found in the
`Map` module.
## Access syntax
Besides the access functions available in the `Map` module,
like `Map.get/3` and `Map.fetch/2`, a map can be accessed using the
`.` operator:
iex> map = %{ a: :b }
iex> map.a
:b
Note that the `.` operator expects the field to exist in the map.
If not, an `ArgumentError` is raised.
## Update syntax
Maps also support an update syntax:
iex> map = %{ :a => :b }
iex> %{ map | :a => :c }
%{ :a => :c }
Notice the update syntax requires the given keys to exist.
Trying to update a key that does not exist will raise an `ArgumentError`.
## AST representation
Regardless if `=>` or the keywords syntax is used, Maps are
always represented internally as a list of two-items tuples
for simplicity:
iex> quote do: %{ :a => :b, c: :d }
{ :%{}, [], [{:a, :b}, {:c, :d}] }
"""
defmacro unquote(:%{})(args)
@doc """
Creates a struct.
A struct is a tagged map that allows developers to provide
default values for keys, tags to be used in polymorphic
dispatches and compile time assertions.
To define a struct, you just need to implement the `__struct__/0`
function in a module:
defmodule User do
def __struct__ do
%{ name: "josé", age: 27 }
end
end
Now a struct can be created as follow:
%User{}
Underneath, a struct is just a map with a `__struct__` field
pointing to the User module:
%User{} == %{ __struct__: User, name: "josé", age: 27 }
A struct also validates the given keys are part of the defined
struct. The example below will fail because there is no key
`:full_name` in the user struct:
%User{ full_name: "José Valim" }
Note that a struct specifies a minimum set of keys required
for operations. Other keys can be added to structs via the
regular map operations:
user = %User{}
Map.put(user, :a_non_struct_key, :value)
An update operation specific for structs is also available:
%User{ user | age: 28 }
The syntax above will guarantee the given keys are valid at
compilation time and it will guarantee at runtime the given
argument is a struct, failing with `BadStructError` otherwise.
Check `Kernel.defprotocol/2` for more information on how structs
can be used with protocols for polymorphic dispatch. Also,
see `Kernel.struct/2` for examples on how to create and update
structs dynamically.
"""
defmacro unquote(:%)(struct, map)
@doc """
Defines a new bitstring.
@@ -108,7 +227,7 @@ defmodule Kernel.SpecialForms do
signedness, e.g.:
iex> <<val, _rest :: binary>> = <<-100, "foo">>
...> val
iex> val
156
Here, `val` is interpreted as unsigned.
@@ -200,12 +319,16 @@ defmodule Kernel.SpecialForms do
iex> Kernel.Sample
Kernel.Sample
iex> Kernel.length([1,2,3])
3
iex> Kernel.+(1, 2)
3
iex> Kernel."length"([1,2,3])
3
iex> Kernel.'+'(1, 2)
3
@@ -229,6 +352,7 @@ defmodule Kernel.SpecialForms do
iex> apply(:erlang, :+, [1,2])
3
iex> Module.concat(Kernel, Sample)
Kernel.Sample
@@ -331,7 +455,7 @@ defmodule Kernel.SpecialForms do
Elixir won't emit any warnings though, since the alias
was not explicitly defined.
Both warning behaviors could be changed by explicitily
Both warning behaviours could be changed by explicitly
setting the `:warn` option to true or false.
"""
defmacro alias(module, opts)
@@ -377,7 +501,7 @@ defmodule Kernel.SpecialForms do
for example:
iex> import List
...> flatten([1, [2], 3])
iex> flatten([1, [2], 3])
[1,2,3]
## Selector
@@ -443,7 +567,7 @@ defmodule Kernel.SpecialForms do
Elixir won't emit any warnings though, since the import
was not explicitly defined.
Both warning behaviors could be changed by explicitily
Both warning behaviours could be changed by explicitly
setting the `:warn` option to true or false.
## Ambiguous function/macro names
@@ -495,8 +619,8 @@ defmodule Kernel.SpecialForms do
value, instead of rebinding. This can be done with the `^` special form:
iex> x = 1
iex> ^x = 1
iex> ^x = 2
iex> ^x = List.first([1])
iex> ^x = List.first([2])
** (MatchError) no match of right hand side value: 2
Note that `^` always refers to the value of x prior to the match. The
@@ -510,13 +634,13 @@ defmodule Kernel.SpecialForms do
"""
defmacro ^(var)
@doc %S"""
@doc ~S"""
Gets the representation of any expression.
## Examples
iex> quote do: sum(1, 2, 3)
{ :sum, [], [1, 2, 3] }
quote do: sum(1, 2, 3)
#=> { :sum, [], [1, 2, 3] }
## Explanation
@@ -1007,77 +1131,65 @@ defmodule Kernel.SpecialForms do
"""
defmacro unquote(:unquote_splicing)(expr)
@doc """
List comprehensions allow you to quickly build a list from another list:
@doc ~S"""
Comprehensions allow you to quickly build a data structure from
an enumerable or a bitstring.
iex> lc n inlist [1, 2, 3, 4], do: n * 2
[2,4,6,8]
Let's start with an example:
A comprehension accepts many generators and also filters. Generators
are defined using both `inlist` and `inbits` operators, allowing you
to loop lists and bitstrings:
iex> for n <- [1, 2, 3, 4], do: n * 2
[2, 4, 6, 8]
A comprehension accepts many generators and filters. Enumerable
generators are defined using `<-`:
# A list generator:
iex> lc n inlist [1, 2, 3, 4], do: n * 2
[2,4,6,8]
# A bit string generator:
iex> lc <<n>> inbits <<1, 2, 3, 4>>, do: n * 2
[2,4,6,8]
# A generator from a variable:
iex> list = [1, 2, 3, 4]
...> lc n inlist list, do: n * 2
[2,4,6,8]
iex> for n <- [1, 2, 3, 4], do: n * 2
[2, 4, 6, 8]
# A comprehension with two generators
iex> lc x inlist [1, 2], y inlist [2, 3], do: x*y
[2,3,4,6]
iex> for x <- [1, 2], y <- [2, 3], do: x*y
[2, 3, 4, 6]
Filters can also be given:
# A comprehension with a generator and a filter
iex> lc n inlist [1, 2, 3, 4, 5, 6], rem(n, 2) == 0, do: n
[2,4,6]
iex> for n <- [1, 2, 3, 4, 5, 6], rem(n, 2) == 0, do: n
[2, 4, 6]
Bit string generators are quite useful when you need to
organize bit string streams:
Bitstring generators are also supported and are very useful when you
need to organize bitstring streams:
iex> pixels = <<213, 45, 132, 64, 76, 32, 76, 0, 0, 234, 32, 15>>
iex> lc <<r::8, g::8, b::8>> inbits pixels, do: {r, g, b}
iex> for <<r::8, g::8, b::8 <- pixels >>, do: {r, g, b}
[{213,45,132},{64,76,32},{76,0,0},{234,32,15}]
Variable assignments in the `do` block of the comprehension
are not reflected outside of the block.
Variable assignments inside the comprehension, be it in generators,
filters or inside the block, are not reflected outside of the
comprehension.
Note: Unlike Erlang, Elixir comprehension filters
never behave as guards when it comes to errors. Errors in
list comprehensions will always be raised. Consider this
Erlang example:
## Into
erl> [I || I <- [1,2,3], hd(I)].
[]
In the examples above, the result returned by the comprehension was
always a list. The returned result can be configured by passing an
`:into` option, that accepts any structure as long as it implements
the `Collectable` protocol.
In Elixir, it will raise:
For example, we can use bitstring generators with the `:into` option
to easily remove all spaces in a string:
iex> lc i inlist [1,2,3], hd(i), do: i
** (ArgumentError) argument error
"""
defmacro lc(args)
@doc """
Defines a bit comprehension.
It follows the same syntax and behaviour as a list comprehension
but expects each element returned to be a bitstring. For example,
here is how to remove all spaces from a string:
iex> bc <<c>> inbits " hello world ", c != ? , do: <<c>>
iex> for <<c <- " hello world ">>, c != ?\s, into: "", do: <<c>>
"helloworld"
The `IO` module provides streams, that are both `Enumerable` and
`Collectable`, here is an upcase echo server using comprehensions:
for line <- IO.stream(:stdio, :line), into: IO.stream(:stdio, :line) do
String.upcase(line)
end
"""
defmacro bc(args)
defmacro for(args)
@doc """
Defines an anonymous function.
@@ -1105,10 +1217,12 @@ defmodule Kernel.SpecialForms do
defmacro __block__(args)
@doc """
Captures an anonymous function.
Captures or creates an anonymous function.
The most common format to capture a function is via module,
name and arity:
## Capture
The capture operator is most commonly used to capture a
function with given name and arity from a module:
iex> fun = &Kernel.is_atom/1
iex> fun.(:atom)
@@ -1116,47 +1230,39 @@ defmodule Kernel.SpecialForms do
iex> fun.("string")
false
Local functions, including private ones, and imported ones
can also be captured by omitting the module name:
In the example above, we captured `Kernel.is_atom/1` as an
anonymous function and then invoked it.
The capture operator can also be used to capture local functions,
including private ones, and imported functions by omitting the
module name:
&local_function/1
A capture also allows the captured functions to be partially
applied, for example:
## Anonymous functions
iex> fun = &is_record(&1, Range)
iex> fun.(1..3)
The capture operator can be also be used to partially apply
functions, where `&1`, `&2` and so on can be used as value
placeholders. For example:
iex> double = &(&1 * 2)
iex> double.(2)
4
In other words, `&(&1 * 2)` is equivalent to `fn x -> x * 2 end`.
Another example using a local function:
iex> fun = &is_atom(&1)
iex> fun.(:atom)
true
In the example above, we use &1 as a placeholder, generating
a function with one argument. We could also use `&2` and `&3`
to delimit more arguments:
iex> fun = &is_record(&1, &2)
iex> fun.(1..3, Range)
true
Since operators are calls, they are also supported, although
they require explicit parentheses around:
iex> fun = &(&1 + &2)
iex> fun.(1, 2)
3
And even more complex call expressions:
The `&` operator can be used with more complex expressions:
iex> fun = &(&1 + &2 + &3)
iex> fun.(1, 2, 3)
6
Record-like calls are also allowed:
iex> fun = &(&1.first)
iex> fun.(1..3)
1
Remember tuple and lists are represented as calls in the AST and
therefore are also allowed:
As well as with lists and tuples:
iex> fun = &{&1, &2}
iex> fun.(1, 2)
@@ -1166,16 +1272,14 @@ defmodule Kernel.SpecialForms do
iex> fun.(1, 2)
[1|2]
Anything that is not a call is not allowed though, examples:
The only restrictions when creating anonymous functions is that at
least one placeholder must be present, i.e. it must contain at least
`&1`:
# An atom is not a call
&:foo
# A var is not a call
var = 1
# No placeholder fails to compile
&var
# A block expression is not a call
# Block expressions are also not supported
&(foo(&1, &2); &3 + &4)
"""
@@ -1273,7 +1377,7 @@ defmodule Kernel.SpecialForms do
"""
defmacro case(condition, blocks)
@doc """
@doc ~S"""
Evaluate the given expressions and handle any error, exit
or throw that may have happened.
@@ -1286,10 +1390,10 @@ defmodule Kernel.SpecialForms do
IO.puts "Invalid argument given"
catch
value ->
IO.puts "caught \#{value}"
IO.puts "caught #{value}"
else
value ->
IO.puts "Success! The result was \#{value}"
IO.puts "Success! The result was #{value}"
after
IO.puts "This is printed regardless if it failed or succeed"
end
@@ -1335,10 +1439,39 @@ defmodule Kernel.SpecialForms do
x -> nil
end
## Catching exits and Erlang errors
## Erlang errors
The catch clause works exactly the same as in erlang. Therefore,
one can also handle exits/errors coming from Erlang as below:
Erlang errors are transformed into Elixir ones during rescue:
try do
:erlang.error(:badarg)
rescue
ArgumentError -> :ok
end
The most common Erlang errors will be transformed into their
Elixir counter-part. Those which are not will be transformed
into `ErlangError`:
try do
:erlang.error(:unknown)
rescue
ErlangError -> :ok
end
In fact, ErlangError can be used to rescue any error that is
not an Elixir error proper. For example, it can be used to rescue
the earlier `:badarg` error too, prior to transformation:
try do
:erlang.error(:badarg)
rescue
ErlangError -> :ok
end
## Catching throws and exits
The catch clause can be used to catch throws values and exits.
try do
exit(1)
@@ -1347,14 +1480,14 @@ defmodule Kernel.SpecialForms do
end
try do
error(:sample)
throw(:sample)
catch
:error, :sample ->
IO.puts "sample error"
:throw, :sample ->
IO.puts "sample thrown"
end
Although the second form should be avoided in favor of raise/rescue
control mechanisms.
catch values also support `:error`, as in Erlang, although it is
commonly avoided in favor of raise/rescue control mechanisms.
## Else clauses
@@ -1428,7 +1561,7 @@ defmodule Kernel.SpecialForms do
:exit_b
end
This means the VM nolonger needs to keep the stacktrace once inside
This means the VM no longer needs to keep the stacktrace once inside
an else clause and so tail recursion is possible when using a `try`
with a tail call as the final call inside an else clause. The same
is true for rescue and catch clauses.
@@ -1437,7 +1570,7 @@ defmodule Kernel.SpecialForms do
Since an expression inside `try` may not have been evaluated
due to an exception, any variable created inside `try` cannot
be accessed externaly. For instance:
be accessed externally. For instance:
try do
x = 1
@@ -1499,7 +1632,7 @@ defmodule Kernel.SpecialForms do
end
The `after` clause can be specified even if there are no match clauses.
There are two special cases for the timout value given to after
There are two special cases for the timeout value given to `after`
* `:infinity` - The process should wait indefinitely for a matching
message, this is the same as not using a timeout.
+96 -83
View File
@@ -298,7 +298,7 @@ defmodule Kernel.Typespec do
if export do
Module.add_doc(module, caller.line, kind, { name, arity }, doc)
else
:elixir_errors.warn "#{caller.file}:#{caller.line}: type #{name}/#{arity} is private, " <>
:elixir_errors.warn caller.line, caller.file, "type #{name}/#{arity} is private, " <>
"@typedoc's are always discarded for private types\n"
end
end
@@ -361,13 +361,13 @@ defmodule Kernel.Typespec do
|> Enum.uniq
|> Enum.map(&{ &1, { :var, meta, nil } })
result = if vars == [] do
typespec_to_ast(result)
else
{ :when, meta, [typespec_to_ast(result), vars] }
end
spec = { :::, meta, [body, typespec_to_ast(result)] }
{ :::, meta, [body, result] }
if vars == [] do
spec
else
{ :when, meta, [spec, vars] }
end
end
def spec_to_ast(name, { :type, line, :fun, [] }) do
@@ -376,23 +376,23 @@ defmodule Kernel.Typespec do
def spec_to_ast(name, { :type, line, :bounded_fun, [{ :type, _, :fun, [{ :type, _, :product, args }, result] }, constraints] }) do
guards =
lc {:type, _, :constraint, [{:atom, _, :is_subtype}, [{ :var, _, var }, type]]} inlist constraints do
for { :type, _, :constraint, [{ :atom, _, :is_subtype }, [{ :var, _, var }, type]] } <- constraints do
{ var, typespec_to_ast(type) }
end
meta = [line: line]
vars = args ++ [result]
|> Enum.flat_map(&collect_vars/1)
|> Enum.uniq
|> Kernel.--(Keyword.keys(guards))
|> Enum.map(&{ &1, { :var, meta, nil } })
|> Enum.flat_map(&collect_vars/1)
|> Enum.uniq
|> Kernel.--(Keyword.keys(guards))
|> Enum.map(&{ &1, { :var, meta, nil } })
args = lc arg inlist args, do: typespec_to_ast(arg)
args = for arg <- args, do: typespec_to_ast(arg)
{ :::, meta, [
{ name, [line: line], args },
{ :when, meta, [typespec_to_ast(result), guards ++ vars] }
{ :when, meta, [
{ :::, meta, [{ name, [line: line], args }, typespec_to_ast(result)] },
guards ++ vars
] }
end
@@ -400,14 +400,14 @@ defmodule Kernel.Typespec do
Converts a type clause back to Elixir AST.
"""
def type_to_ast({ { :record, record }, fields, args }) when is_atom(record) do
fields = lc field inlist fields, do: typespec_to_ast(field)
args = lc arg inlist args, do: typespec_to_ast(arg)
fields = for field <- fields, do: typespec_to_ast(field)
args = for arg <- args, do: typespec_to_ast(arg)
type = { :{}, [], [record|fields] }
quote do: unquote(record)(unquote_splicing(args)) :: unquote(type)
end
def type_to_ast({ name, type, args }) do
args = lc arg inlist args, do: typespec_to_ast(arg)
args = for arg <- args, do: typespec_to_ast(arg)
quote do: unquote(name)(unquote_splicing(args)) :: unquote(typespec_to_ast(type))
end
@@ -424,7 +424,7 @@ defmodule Kernel.Typespec do
def beam_typedocs(module) when is_atom(module) or is_binary(module) do
case abstract_code(module) do
{ :ok, abstract_code } ->
type_docs = lc { :attribute, _, :typedoc, tup } inlist abstract_code, do: tup
type_docs = for { :attribute, _, :typedoc, tup } <- abstract_code, do: tup
List.flatten(type_docs)
_ ->
nil
@@ -444,10 +444,10 @@ defmodule Kernel.Typespec do
def beam_types(module) when is_atom(module) or is_binary(module) do
case abstract_code(module) do
{ :ok, abstract_code } ->
exported_types = lc { :attribute, _, :export_type, types } inlist abstract_code, do: types
exported_types = for { :attribute, _, :export_type, types } <- abstract_code, do: types
exported_types = List.flatten(exported_types)
lc { :attribute, _, kind, { name, _, args } = type } inlist abstract_code, kind in [:opaque, :type] do
for { :attribute, _, kind, { name, _, args } = type } <- abstract_code, kind in [:opaque, :type] do
cond do
kind == :opaque -> { :opaque, type }
{ name, length(args) } in exported_types -> { :type, type }
@@ -490,7 +490,7 @@ defmodule Kernel.Typespec do
defp from_abstract_code(module, kind) do
case abstract_code(module) do
{ :ok, abstract_code } ->
lc { :attribute, _, abs_kind, value } inlist abstract_code, kind == abs_kind, do: value
for { :attribute, _, abs_kind, value } <- abstract_code, kind == abs_kind, do: value
:error ->
nil
end
@@ -498,7 +498,7 @@ 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, { _raw_abstract_v1, abstract_code } }] } } ->
{ :ok, abstract_code }
_ ->
:error
@@ -519,13 +519,21 @@ defmodule Kernel.Typespec do
## Macro callbacks
@doc false
def deftype(kind, { :::, _, [type, definition] }, caller) do
do_deftype(kind, type, definition, caller)
end
def deftype(kind, { :::, _, [{ name, _, args }, definition] }, caller) when is_atom(name) and name != ::: do
args =
if is_atom(args) do
[]
else
for(arg <- args, do: variable(arg))
end
def deftype(kind, {name, _meta, args} = type, caller)
when is_atom(name) and not is_list(args) do
do_deftype(kind, type, { :term, [line: caller.line], nil }, caller)
vars = for { :var, _, var } <- args, do: var
spec = typespec(definition, vars, caller)
vars = for { :var, _, _ } = var <- args, do: var
type = { name, spec, vars }
define_type(caller, kind, type)
end
def deftype(_kind, other, caller) do
@@ -533,27 +541,18 @@ defmodule Kernel.Typespec do
compile_error caller, "invalid type specification: #{type_spec}"
end
defp do_deftype(kind, { name, _, args }, definition, caller) do
args =
if is_atom(args) do
[]
else
lc(arg inlist args, do: variable(arg))
end
@doc false
vars = lc { :var, _, var } inlist args, do: var
spec = typespec(definition, vars, caller)
vars = lc { :var, _, _ } = var inlist args, do: var
type = { name, spec, vars }
define_type(caller, kind, type)
def defspec(type, { :when, _meta, [spec, guard] }, caller) do
defspec(type, spec, guard, caller)
end
@doc false
def defspec(type, { :::, meta, [{ name, _, args }, return_and_guard] }, caller) do
def defspec(type, spec, caller) do
defspec(type, spec, [], caller)
end
defp defspec(type, { :::, meta, [{ name, _, args }, return] }, guard, caller) when is_atom(name) and name != ::: do
if is_atom(args), do: args = []
{ return, guard } = split_return_and_guard(return_and_guard)
unless Keyword.keyword?(guard) do
guard = Macro.to_string(guard)
@@ -573,24 +572,11 @@ defmodule Kernel.Typespec do
code
end
def defspec(_type, other, caller) do
spec = Macro.to_string(other)
defp defspec(_type, spec, _guard, caller) do
spec = Macro.to_string(spec)
compile_error caller, "invalid function type specification: #{spec}"
end
defp split_return_and_guard({ :when, _, [return, guard] }) do
{ return, guard }
end
defp split_return_and_guard({ :|, meta, [left, right] }) do
{ return, guard } = split_return_and_guard(right)
{ { :|, meta, [left, return] }, guard }
end
defp split_return_and_guard(other) do
{ other, [] }
end
defp guard_to_constraints(guard, vars, meta, caller) do
line = line(meta)
@@ -622,11 +608,11 @@ defmodule Kernel.Typespec 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
@@ -639,7 +625,7 @@ defmodule Kernel.Typespec do
end
defp typespec_to_ast({ :type, line, :tuple, args }) do
args = lc arg inlist args, do: typespec_to_ast(arg)
args = for arg <- args, do: typespec_to_ast(arg)
{ :{}, [line: line], args }
end
@@ -651,11 +637,26 @@ defmodule Kernel.Typespec do
end
defp typespec_to_ast({ :type, _line, :list, args }) do
lc arg inlist args, do: typespec_to_ast(arg)
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) }
end
{ struct, fields } = Keyword.pop(fields, :__struct__)
map = { :%{}, [line: line], fields }
if struct do
{ :%, [line: line], [struct, map] }
else
map
end
end
defp typespec_to_ast({ :type, line, :binary, [arg1, arg2] }) do
[arg1, arg2] = lc arg inlist [arg1, arg2], do: typespec_to_ast(arg)
[arg1, arg2] = for arg <- [arg1, arg2], do: typespec_to_ast(arg)
cond do
arg2 == 0 ->
quote line: line, do: <<_ :: unquote(arg1)>>
@@ -667,12 +668,12 @@ defmodule Kernel.Typespec do
end
defp typespec_to_ast({ :type, line, :union, args }) do
args = lc arg inlist args, do: typespec_to_ast(arg)
args = for arg <- args, do: typespec_to_ast(arg)
Enum.reduce Enum.reverse(args), fn(arg, expr) -> { :|, [line: line], [arg, expr] } end
end
defp typespec_to_ast({ :type, line, :fun, [{:type, _, :product, args}, result] }) do
args = lc arg inlist args, do: typespec_to_ast(arg)
args = for arg <- args, do: typespec_to_ast(arg)
[{ :->, [line: line], [args, typespec_to_ast(result)] }]
end
@@ -689,7 +690,7 @@ defmodule Kernel.Typespec do
end
defp typespec_to_ast({ :type, line, name, args }) do
args = lc arg inlist args, do: typespec_to_ast(arg)
args = for arg <- args, do: typespec_to_ast(arg)
{ name, [line: line], args }
end
@@ -711,7 +712,7 @@ defmodule Kernel.Typespec do
end
defp typespec_to_ast({ :remote_type, line, [mod, name, args] }) do
args = lc arg inlist args, do: typespec_to_ast(arg)
args = for arg <- args, do: typespec_to_ast(arg)
dot = { :., [line: line], [typespec_to_ast(mod), typespec_to_ast(name)] }
{ dot, [line: line], args }
end
@@ -721,16 +722,16 @@ defmodule Kernel.Typespec do
end
defp typespec_to_ast({ :typed_record_field,
{ :record_field, line, { :atom, line1, name }},
{ :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
@@ -761,7 +762,7 @@ defmodule Kernel.Typespec do
# Handle unions
defp typespec({ :|, meta, [_, _] } = exprs, vars, caller) do
exprs = collect_union(exprs)
union = lc e inlist exprs, do: typespec(e, vars, caller)
union = for e <- exprs, do: typespec(e, vars, caller)
{ :type, line(meta), :union, union }
end
@@ -778,8 +779,20 @@ defmodule Kernel.Typespec do
{:type, line(meta), :binary, [{:integer, line(meta1), base}, {:integer, line(meta2), 0}]}
end
## Handle maps and structs
defp typespec({:%{}, meta, fields}, vars, caller) do
fields = Enum.map(fields, fn { k, v } ->
{:type, line(meta), :map_field_assoc, typespec(k, vars, caller), typespec(v, vars, caller)}
end)
{:type, line(meta), :map, fields}
end
defp typespec({:%, _, [name, {:%{}, meta, fields}]}, vars, caller) do
typespec({:%{}, meta, [{:__struct__, name}|fields]}, vars, caller)
end
# Handle ranges
defp typespec({:"..", meta, args}, vars, caller) do
defp typespec({:.., meta, args}, vars, caller) do
typespec({:range, meta, args}, vars, caller)
end
@@ -800,7 +813,7 @@ defmodule Kernel.Typespec do
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] }
@@ -833,7 +846,7 @@ defmodule Kernel.Typespec do
end
defp typespec({:{}, meta, t}, vars, caller) when is_list(t) do
args = lc e inlist t, do: typespec(e, vars, caller)
args = for e <- t, do: typespec(e, vars, caller)
{ :type, line(meta), :tuple, args }
end
@@ -857,9 +870,9 @@ defmodule Kernel.Typespec do
# Handle local calls
defp typespec({:string, meta, arguments}, vars, caller) do
:elixir_errors.warn "warning: string() type use is discouraged. For character lists, use " <>
:elixir_errors.warn caller.line, caller.file, "string() type use is discouraged. For character lists, use " <>
"char_list() type, for strings, String.t()\n#{Exception.format_stacktrace(caller.stacktrace)}"
arguments = lc arg inlist arguments, do: typespec(arg, vars, caller)
arguments = for arg <- arguments, do: typespec(arg, vars, caller)
{ :type, line(meta), :string, arguments }
end
@@ -872,7 +885,7 @@ defmodule Kernel.Typespec do
end
defp typespec({name, meta, arguments}, vars, caller) do
arguments = lc arg inlist arguments, do: typespec(arg, vars, caller)
arguments = for arg <- arguments, do: typespec(arg, vars, caller)
{ :type, line(meta), name, arguments }
end
@@ -912,7 +925,7 @@ defmodule Kernel.Typespec do
end
defp remote_type({remote, meta, name, arguments}, vars, caller) do
arguments = lc arg inlist arguments, do: typespec(arg, vars, caller)
arguments = for arg <- arguments, do: typespec(arg, vars, caller)
{ :remote_type, line(meta), [ remote, name, arguments ] }
end
@@ -936,7 +949,7 @@ defmodule Kernel.Typespec do
end
defp fn_args(meta, args, vars, caller) do
args = lc arg inlist args, do: typespec(arg, vars, caller)
args = for arg <- args, do: typespec(arg, vars, caller)
{ :type, line(meta), :product, args }
end
+57 -46
View File
@@ -36,6 +36,7 @@ defmodule Keyword do
"""
@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
@@ -59,8 +60,11 @@ defmodule Keyword do
end
@doc """
Creates a Keyword from an enumerable. Similar to dicts,
duplicated entries are removed, the latest one prevails.
Creates a keyword from an enumerable.
Duplicated entries are removed, the latest one prevails.
I.e. differently from `Enum.into(enumerable, [])`,
`Keyword.new(enumerable)` guarantees the keys are unique.
## Examples
@@ -76,9 +80,11 @@ defmodule Keyword do
end
@doc """
Creates a Keyword from an enumerable with the
help of the transformation function. Duplicated
entries are removed, the latest one prevails.
Creates a keyword from an enumerable via the transformation function.
Duplicated entries are removed, the latest one prevails.
I.e. differently from `Enum.into(enumerable, [], fun)`,
`Keyword.new(enumerable, fun)` guarantees the keys are unique.
## Examples
@@ -116,7 +122,7 @@ defmodule Keyword do
"""
@spec get(t, key) :: value
@spec get(t, key, value) :: value
def get(keywords, key, default // nil) when is_atom(key) do
def get(keywords, key, default \\ nil) when is_list(keywords) and is_atom(key) do
case :lists.keyfind(key, 1, keywords) do
{ ^key, value } -> value
false -> default
@@ -137,7 +143,7 @@ defmodule Keyword do
"""
@spec fetch(t, key) :: value
def fetch(keywords, key) when is_atom(key) do
def fetch(keywords, key) when is_list(keywords) and is_atom(key) do
case :lists.keyfind(key, 1, keywords) do
{ ^key, value } -> { :ok, value }
false -> :error
@@ -154,14 +160,14 @@ defmodule Keyword do
1
iex> Keyword.fetch!([a: 1], :b)
** (KeyError) key not found: :b
** (KeyError) key :b not found in: [a: 1]
"""
@spec fetch!(t, key) :: value | no_return
def fetch!(keywords, key) when is_atom(key) do
def fetch!(keywords, key) when is_list(keywords) and is_atom(key) do
case :lists.keyfind(key, 1, keywords) do
{ ^key, value } -> value
false -> raise(KeyError, key: key)
false -> raise(KeyError, key: key, term: keywords)
end
end
@@ -175,8 +181,8 @@ defmodule Keyword do
"""
@spec get_values(t, key) :: [value]
def get_values(keywords, key) when is_atom(key) do
lc { k, v } inlist keywords, key == k, do: v
def get_values(keywords, key) when is_list(keywords) and is_atom(key) do
for { k, v } <- keywords, key == k, do: v
end
@doc """
@@ -193,8 +199,8 @@ defmodule Keyword do
"""
@spec keys(t) :: [key]
def keys(keywords) do
lc { key, _ } inlist keywords, do: key
def keys(keywords) when is_list(keywords) do
for { key, _ } <- keywords, do: key
end
@doc """
@@ -207,8 +213,8 @@ defmodule Keyword do
"""
@spec values(t) :: [value]
def values(keywords) do
lc { _, value } inlist keywords, do: value
def values(keywords) when is_list(keywords) do
for { _, value } <- keywords, do: value
end
@doc """
@@ -228,8 +234,8 @@ defmodule Keyword do
"""
@spec delete(t, key, value) :: t
def delete(keywords, key, value) when is_atom(key) do
lc { k, v } = tuple inlist keywords, key != k or value != v, do: tuple
def delete(keywords, key, value) when is_list(keywords) and is_atom(key) do
for { k, v } = tuple <- keywords, key != k or value != v, do: tuple
end
@doc """
@@ -251,8 +257,8 @@ defmodule Keyword do
"""
@spec delete(t, key) :: t
def delete(keywords, key) when is_atom(key) do
lc { k, _ } = tuple inlist keywords, key != k, do: tuple
def delete(keywords, key) when is_list(keywords) and is_atom(key) do
for { k, _ } = tuple <- keywords, key != k, do: tuple
end
@doc """
@@ -269,7 +275,7 @@ defmodule Keyword do
"""
@spec delete_first(t, key) :: t
def delete_first(keywords, key) when is_atom(key) do
def delete_first(keywords, key) when is_list(keywords) and is_atom(key) do
:lists.keydelete(key, 1, keywords)
end
@@ -289,7 +295,7 @@ defmodule Keyword do
"""
@spec put(t, key, value) :: t
def put(keywords, key, value) when is_atom(key) do
def put(keywords, key, value) when is_list(keywords) and is_atom(key) do
[{key, value}|delete(keywords, key)]
end
@@ -298,6 +304,7 @@ defmodule Keyword do
already exists.
## Examples
iex> Keyword.put_new([a: 1], :b, 2)
[b: 2, a: 1]
@@ -306,7 +313,7 @@ defmodule Keyword do
"""
@spec put_new(t, key, value) :: t
def put_new(keywords, key, value) when is_atom(key) do
def put_new(keywords, key, value) when is_list(keywords) and is_atom(key) do
case :lists.keyfind(key, 1, keywords) do
{ ^key, _ } -> keywords
false -> [{key, value}|keywords]
@@ -324,7 +331,7 @@ defmodule Keyword do
"""
@spec equal?(t, t) :: boolean
def equal?(left, right) do
def equal?(left, right) when is_list(left) and is_list(right) do
:lists.sort(left) == :lists.sort(right)
end
@@ -339,8 +346,8 @@ defmodule Keyword do
"""
@spec merge(t, t) :: t
def merge(d1, d2) do
d2 ++ lc({ k, _ } = tuple inlist d1, not has_key?(d2, k), do: tuple)
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)
end
@doc """
@@ -351,12 +358,12 @@ defmodule Keyword do
iex> Keyword.merge([a: 1, b: 2], [a: 3, d: 4], fn (_k, v1, v2) ->
...> v1 + v2
iex> end)
...> end)
[a: 4, b: 2, d: 4]
"""
@spec merge(t, t, (key, value, value -> value)) :: t
def merge(d1, d2, fun) do
def merge(d1, d2, fun) when is_list(d1) and is_list(d2) do
do_merge(d2, d1, fun)
end
@@ -381,7 +388,7 @@ defmodule Keyword do
"""
@spec has_key?(t, key) :: boolean
def has_key?(keywords, key) when is_atom(key) do
def has_key?(keywords, key) when is_list(keywords) and is_atom(key) do
:lists.keymember(key, 1, keywords)
end
@@ -395,20 +402,24 @@ defmodule Keyword do
[a: 2]
iex> Keyword.update!([a: 1], :b, &(&1 * 2))
** (KeyError) key not found: :b
** (KeyError) key :b not found in: [a: 1]
"""
@spec update!(t, key, (value -> value)) :: t | no_return
def update!([{key, value}|keywords], key, fun) do
def update!(keywords, key, fun) do
update!(keywords, key, fun, keywords)
end
defp update!([{key, value}|keywords], key, fun, _dict) do
[{key, fun.(value)}|delete(keywords, key)]
end
def update!([{_, _} = e|keywords], key, fun) do
[e|update!(keywords, key, fun)]
defp update!([{_, _} = e|keywords], key, fun, dict) do
[e|update!(keywords, key, fun, dict)]
end
def update!([], key, _fun) when is_atom(key) do
raise(KeyError, key: key)
defp update!([], key, _fun, dict) when is_atom(key) do
raise(KeyError, key: key, term: dict)
end
@doc """
@@ -452,11 +463,11 @@ defmodule Keyword do
{ [a: 1, c: 3, a: 5], [b: 2, d: 4] }
"""
def split(dict, keys) do
def split(keywords, keys) when is_list(keywords) do
acc = { [], [] }
{ take, drop } = Enum.reduce dict, acc, fn({ k, v }, { take, drop }) ->
case :lists.member(k, keys) do
{ 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] }
end
@@ -480,8 +491,8 @@ defmodule Keyword do
[a: 1, c: 3, a: 5]
"""
def take(dict, keys) do
lc { k, _ } = tuple inlist dict, :lists.member(k, keys), do: tuple
def take(keywords, keys) when is_list(keywords) do
for { k, _ } = tuple <- keywords, k in keys, do: tuple
end
@doc """
@@ -499,8 +510,8 @@ defmodule Keyword do
[a: 1, c: 3, a: 5]
"""
def drop(dict, keys) do
lc { k, _ } = tuple inlist dict, not :lists.member(k, keys), do: tuple
def drop(keywords, keys) when is_list(keywords) do
for { k, _ } = tuple <- keywords, not k in keys, do: tuple
end
@doc """
@@ -528,8 +539,8 @@ defmodule Keyword do
{1,[]}
"""
def pop(dict, key, default // nil) do
{ get(dict, key, default), delete(dict, key) }
def pop(keywords, key, default \\ nil) when is_list(keywords) do
{ get(keywords, key, default), delete(keywords, key) }
end
@doc """
@@ -557,7 +568,7 @@ defmodule Keyword do
{1,[a: 2]}
"""
def pop_first(dict, key, default // nil) do
{ get(dict, key, default), delete_first(dict, key) }
def pop_first(keywords, key, default \\ nil) when is_list(keywords) do
{ get(keywords, key, default), delete_first(keywords, key) }
end
end
+6 -1
View File
@@ -47,6 +47,7 @@ defmodule List do
iex> List.duplicate([1, 2], 2)
[[1,2],[1,2]]
"""
@spec duplicate(elem, non_neg_integer) :: [elem] when elem: var
def duplicate(elem, n) do
@@ -123,8 +124,10 @@ defmodule List do
iex> List.first([])
nil
iex> List.first([1])
1
iex> List.first([1, 2, 3])
1
@@ -140,8 +143,10 @@ defmodule List do
iex> List.last([])
nil
iex> List.last([1])
1
iex> List.last([1, 2, 3])
3
@@ -169,7 +174,7 @@ defmodule List do
"""
@spec keyfind([tuple], any, non_neg_integer, any) :: any
def keyfind(list, key, position, default // nil) do
def keyfind(list, key, position, default \\ nil) do
:lists.keyfind(key, position + 1, list) || default
end
+1 -1
View File
@@ -1,5 +1,5 @@
defprotocol List.Chars do
@moduledoc %S"""
@moduledoc ~S"""
The List.Chars protocol is responsible for
converting a structure to a list (only if applicable).
The only function required to be implemented is
+33 -23
View File
@@ -15,27 +15,24 @@ defmodule ListDict do
"""
def new, do: []
@doc """
Creates a new `ListDict` from the given pairs.
"""
@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 """
Creates a new `ListDict` from the given pairs
via the given transformation function.
"""
@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
lc { key, _ } inlist dict, do: key
for { key, _ } <- dict, do: key
end
def values(dict) do
lc { _, value } inlist dict, do: value
for { _, value } <- dict, do: value
end
def size(dict) do
@@ -47,7 +44,7 @@ defmodule ListDict do
def has_key?([{ _, _ }|t], key), do: has_key?(t, key)
def has_key?([], _key), do: false
def get(dict, key, default // nil)
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
@@ -60,11 +57,11 @@ defmodule ListDict do
def fetch!(dict, key) do
case fetch(dict, key) do
{ :ok, value } -> value
:error -> raise(KeyError, key: key)
:error -> raise(KeyError, key: key, term: dict)
end
end
def pop(dict, key, default // nil) do
def pop(dict, key, default \\ nil) do
{ get(dict, key, default), delete(dict, key) }
end
@@ -84,7 +81,7 @@ defmodule ListDict do
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
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
@@ -94,7 +91,7 @@ defmodule ListDict do
acc = { [], [] }
{take, drop} = Enum.reduce dict, acc, fn({ k, v }, { take, drop }) ->
if :lists.member(k, keys) do
if k in keys do
{ [{k, v}|take], drop }
else
{ take, [{k, v}|drop] }
@@ -105,23 +102,27 @@ defmodule ListDict do
end
def take(dict, keys) do
lc { k, _ } = tuple inlist dict, :lists.member(k, keys), do: tuple
for { k, _ } = tuple <- dict, k in keys, do: tuple
end
def drop(dict, keys) do
lc { k, _ } = tuple inlist dict, not :lists.member(k, keys), do: tuple
for { k, _ } = tuple <- dict, not k in keys, do: tuple
end
def update!([{key, value}|dict], key, fun) do
[{key, fun.(value)}|delete(dict, key)]
def update!(list, key, fun) do
update!(list, key, fun, list)
end
def update!([{_, _} = e|dict], key, fun) do
[e|update!(dict, key, fun)]
defp update!([{key, value}|list], key, fun, _dict) do
[{key, fun.(value)}|delete(list, key)]
end
def update!([], key, _fun) do
raise(KeyError, key: key)
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
@@ -136,11 +137,20 @@ defmodule ListDict do
[{key, initial}]
end
def empty(_dict), do: []
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
+74 -44
View File
@@ -13,7 +13,7 @@ defmodule Macro do
@binary_ops [:===, :!==,
:==, :!=, :<=, :>=,
:&&, :||, :<>, :++, :--, :**, ://, :::, :<-, :.., :|>, :=~,
:&&, :||, :<>, :++, :--, :\\, :::, :<-, :.., :|>, :=~,
:<, :>, :->,
:+, :-, :*, :/, :=, :|, :.,
:and, :or, :xor, :when, :in, :inlist, :inbits,
@@ -30,16 +30,16 @@ defmodule Macro do
@spec binary_op_props(atom) :: { :left | :right, precedence :: integer }
defp binary_op_props(o) do
case o do
::: -> {:right, 40}
o when o in [:inlist, :inbits, ://, :|] -> {:right, 50}
o when o in [:<-, :inlist, :inbits, :\\, :::] -> {:left, 40}
:| -> {:right, 50}
:when -> {:right, 70}
:= -> {:right, 80}
o when o in [:||, :|||, :or, :xor] -> {:left, 130}
o when o in [:&&, :&&&, :and] -> {:left, 140}
o when o in [:==, :!=, :<, :<=, :>=, :>, :=~, :===, :!==] -> {:left, 150}
o when o in [:<-, :|>, :<<<, :>>>] -> {:right, 160}
o when o in [:|>, :<<<, :>>>] -> {:right, 160}
:in -> {:left, 170}
o when o in [:++, :--, :**, :.., :<>] -> {:right, 200}
o when o in [:++, :--, :.., :<>] -> {:right, 200}
o when o in [:+, :-] -> {:left, 210}
o when o in [:*, :/] -> {:left, 220}
:^^^ -> {:left, 250}
@@ -66,7 +66,11 @@ defmodule Macro do
argument in 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, integer \\ 0)
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) }
@@ -109,7 +113,7 @@ defmodule Macro do
end
def update_meta(list, fun) when is_list(list) do
lc x inlist list, do: update_meta(x, fun)
for x <- list, do: update_meta(x, fun)
end
def update_meta(other, _fun) do
@@ -175,24 +179,23 @@ defmodule Macro do
"""
@spec escape(term) :: Macro.t
@spec escape(term, Keyword.t) :: Macro.t
def escape(expr, opts // []) do
def escape(expr, opts \\ []) do
elem(:elixir_quote.escape(expr, Keyword.get(opts, :unquote, false)), 0)
end
@doc %S"""
@doc ~S"""
Unescape the given chars.
This is the unescaping behavior
used by default in Elixir single- and double-quoted strings.
Check `unescape_string/2` for information on how to customize
the escaping map.
This is the unescaping behaviour used by default in Elixir
single- and double-quoted strings. Check `unescape_string/2`
for information on how to customize the escaping map.
In this setup, Elixir will escape the following: `\a`, `\b`,
`\d`, `\e`, `\f`, `\n`, `\r`, `\s`, `\t` and `\v`. Octals are
also escaped according to the latin1 set they represent.
This function is commonly used on sigil implementations
(like `%r`, `%s` and others) which receive a raw, unescaped
(like `~r`, `~s` and others) which receive a raw, unescaped
string.
## Examples
@@ -208,7 +211,7 @@ defmodule Macro do
:elixir_interpolation.unescape_chars(chars)
end
@doc %S"""
@doc ~S"""
Unescape the given chars according to the map given.
Check `unescape_string/1` if you want to use the same map
@@ -294,7 +297,7 @@ defmodule Macro do
"""
@spec to_string(Macro.t) :: String.t
@spec to_string(Macro.t, (Macro.t, String.t -> String.t)) :: String.t
def to_string(tree, fun // fn(_ast, string) -> string end)
def to_string(tree, fun \\ fn(_ast, string) -> string end)
# Variables
def to_string({ var, _, atom } = ast, fun) when is_atom(atom) do
@@ -330,6 +333,18 @@ defmodule Macro do
fun.(ast, tuple)
end
# Map containers
def to_string({ :%{}, _, args } = ast, fun) do
map = "%{" <> map_to_string(args, fun) <> "}"
fun.(ast, map)
end
def to_string({ :%, _, [structname, map] } = ast, fun) do
{ :%{}, _, args } = map
struct = "%" <> to_string(structname, fun) <> "{" <> map_to_string(args, fun) <> "}"
fun.(ast, struct)
end
# Fn keyword
def to_string({ :fn, _, [{ :->, _, [_, tuple] }] = arrow } = ast, fun)
when not is_tuple(tuple) or elem(tuple, 0) != :__block__ do
@@ -362,7 +377,7 @@ defmodule Macro do
end
# Binary ops
def to_string({ op, _, [left, right] } = ast, fun) when op in @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
@@ -373,11 +388,16 @@ defmodule Macro do
end
# Unary ops
def to_string({ unary, _, [{ binary, _, [_, _] } = arg] } = ast, fun)
when unary in unquote(@unary_ops) and binary in unquote(@binary_ops) do
fun.(ast, atom_to_binary(unary) <> "(" <> to_string(arg, fun) <> ")")
end
def to_string({ :not, _, [arg] } = ast, fun) do
fun.(ast, "not " <> to_string(arg, fun))
end
def to_string({ op, _, [arg] } = ast, fun) when op in @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
@@ -421,7 +441,7 @@ defmodule Macro do
@kw_keywords [:do, :catch, :rescue, :after, :else]
defp kw_blocks?([_|_] = kw) do
Enum.all?(kw, &match?({x, _} when x in @kw_keywords, &1))
Enum.all?(kw, &match?({x, _} when x in unquote(@kw_keywords), &1))
end
defp kw_blocks?(_), do: false
@@ -478,17 +498,34 @@ defmodule Macro do
defp block_to_string(other, fun), do: to_string(other, fun)
defp map_to_string([{:|, _, [update_map, update_args]}], fun) do
to_string(update_map, fun) <> " | " <> map_to_string(update_args, fun)
end
defp map_to_string(list, fun) do
cond do
Keyword.keyword?(list) -> kw_list_to_string(list, fun)
true -> map_list_to_string(list, fun)
end
end
defp kw_list_to_string(list, fun) do
Enum.map_join(list, ", ", fn { key, value } ->
atom_to_binary(key) <> ": " <> to_string(value, fun)
end)
end
defp map_list_to_string(list, fun) do
Enum.map_join(list, ", ", fn { key, value } ->
to_string(key, fun) <> " => " <> to_string(value, fun)
end)
end
defp parenthise(expr, fun) do
"(" <> to_string(expr, fun) <> ")"
end
defp op_to_string({ op, _, [_, _] } = expr, fun, parent_op, side) when op in @binary_ops do
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
@@ -506,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
defp arrow_to_string(pairs, fun, paren \\ false) do
Enum.map_join(pairs, "; ", fn({ :->, _, [left, right] }) ->
left = comma_join_or_empty_paren(left, fun, paren)
left <> "-> " <> to_string(right, fun)
@@ -521,11 +558,11 @@ defmodule Macro do
end
defp adjust_new_lines(block, replacement) do
bc <<x>> inbits block do
<< case x == ?\n do
for <<x <- block>>, into: "" do
case x == ?\n do
true -> replacement
false -> <<x>>
end :: binary >>
end
end
end
@@ -615,7 +652,7 @@ defmodule Macro do
:elixir_lexical.record_remote(receiver, env.lexical_tracker)
{ receiver, true }
aliases ->
aliases = lc alias inlist aliases, do: elem(do_expand_once(alias, env), 0)
aliases = for alias <- aliases, do: elem(do_expand_once(alias, env), 0)
case :lists.all(&is_atom/1, aliases) do
true ->
@@ -641,12 +678,6 @@ defmodule Macro do
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({ :__FILE__, _, atom }, env) when is_atom(atom) do
IO.write "__FILE__ is deprecated, please use __DIR__ or __ENV__.file instead\n#{Exception.format_stacktrace}"
{ env.file, true }
end
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
@@ -672,19 +703,18 @@ defmodule Macro do
defp do_expand_once({ atom, meta, args } = original, env)
when is_atom(atom) and is_list(args) and is_list(meta) do
module = env.module
extra = if function_exported?(module, :__info__, 1) do
[{ module, module.__info__(:macros) }]
else
[]
end
arity = length(args)
if :elixir_import.special_form(atom, arity) do
{ original, false }
else
module = env.module
extra = if function_exported?(module, :__info__, 1) do
[{ module, module.__info__(:macros) }]
else
[]
end
expand = :elixir_dispatch.expand_import(meta, { atom, length(args) }, args,
:elixir_env.ex_to_env(env), extra)
@@ -724,11 +754,11 @@ defmodule Macro do
defp do_expand_once(other, _env), do: { other, false }
@doc """
Receives an AST node and expands it until it no longer represents
a macro.
Receives an AST node and expands it until it can no longer
be expanded.
Check `expand_once/2` for more information on how
expansion works.
This function uses `expand_once/2` under the hood. Check
`expand_once/2` for more information and exmaples.
"""
def expand(tree, env) do
expand_until({ tree, true }, env)
@@ -754,7 +784,7 @@ defmodule Macro do
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
+7 -3
View File
@@ -81,12 +81,16 @@ defmodule Macro.Env do
def stacktrace(record) do
cond do
nil?(record.module) ->
[{ :elixir_compiler, :__FILE__, 1, location(record) }]
[{ :elixir_compiler, :__FILE__, 1, relative_location(record) }]
nil?(record.function) ->
[{ module(record), :__MODULE__, 0, location(record) }]
[{ module(record), :__MODULE__, 0, relative_location(record) }]
true ->
{ name, arity } = record.function
[{ module(record), name, arity, location(record) }]
[{ module(record), name, arity, relative_location(record) }]
end
end
defp relative_location(record) do
[file: Path.relative_to_cwd(file(record)), line: line(record)]
end
end
+116
View File
@@ -0,0 +1,116 @@
defmodule Map do
@moduledoc """
A Dict implementation that works on maps.
Maps are key-value stores where keys are compared using
the match operator (`===`). Maps can be created with
the `%{}` special form defined in the `Kernel.SpecialForms`
module.
For more information about the functions in this module and
their APIs, please consult the `Dict` module.
"""
use Dict.Behaviour
defdelegate [keys(map), values(map), size(map), merge(map1, map2)], to: :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) }
end
def put(map, key, val) do
:maps.put(key, val, map)
end
def put_new(map, key, val) do
case has_key?(map, key) do
true -> map
false -> :maps.put(key, val, map)
end
end
def delete(map, key), do: :maps.remove(key, map)
def merge(map1, map2, callback) do
:maps.fold fn k, v2, acc ->
update(acc, k, v2, fn(v1) -> callback.(k, v1, v2) end)
end, map1, map2
end
def split(map, keys) do
acc = { %{}, %{} }
:maps.fold fn k, v, { take, drop } ->
if k in keys do
{ put(take, k, v), drop }
else
{ take, put(drop, k, v) }
end
end, acc, map
end
def update!(map, key, fun) do
case :maps.find(key, map) do
:error ->
raise(KeyError, key: key, term: map)
{ :ok, val } ->
:maps.put(key, fun.(val), map)
end
end
def update(map, key, initial, fun) do
case :maps.find(key, map) do
:error ->
:maps.put(key, initial, map)
{ :ok, val } ->
:maps.put(key, fun.(val), map)
end
end
def 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
+81 -63
View File
@@ -5,13 +5,13 @@ defmodule Module do
end
end
@moduledoc %S'''
@moduledoc ~S'''
This module provides many functions to deal with modules during
compilation time. It allows a developer to dynamically attach
documentation, add, delete and register attributes and so forth.
After a module is compiled, using many of the functions in
this module will raise errors, since it is out of their purpose
this module will raise errors, since it is out of their scope
to inspect runtime data. Most of the runtime data can be inspected
via the `__info__(attr)` function attached to each compiled module.
@@ -51,7 +51,7 @@ defmodule Module do
When just a module is provided, the function/macro is assumed to be
`__before_compile__/1`.
Note: differently from `@after_compile`, the callback function/macro must
Note: unlike `@after_compile`, the callback function/macro must
be placed in a separate module (because when the callback is invoked,
the current module does not yet exist).
@@ -164,7 +164,7 @@ defmodule Module do
* `@on_definition`
A hook that will be invoked after each function or macro in the current
A hook that will be invoked when each function or macro in the current
module is defined. Useful when annotating functions.
Accepts a module or a tuple `{ <module>, <function atom> }`. The function
@@ -173,22 +173,25 @@ defmodule Module do
- the module environment
- kind: `:def`, `:defp`, `:defmacro`, or `:defmacrop`
- function/macro name
- list of quoted arguments
- list of quoted guards
- quoted function body
- list of expanded arguments
- list of expanded guards
- expanded function body
Note the hook receives the expanded arguments and it is invoked before
the function is stored in the module. So `Module.defines?/2` will return
false for the first clause of every function.
If the function/macro being defined has multiple clauses, the hook will
be called for each clause.
Unlike other hooks, `@on_definition` will only invoke functions
and never macros. This is because the hook is invoked inside the context
of the function (and nested function definitions are not allowed in
Elixir).
When just a module is provided, the function is assumed to be
`__on_definition__/6`.
Note that you can't provide the current module to `@on_definition`
because the hook function will not be defined in time. Finally, since
the `on_definition` hook is executed inside the context of the defined
function (i.e. `env.function` returns the current function), the hook
can only be a function, not a macro.
### Example
defmodule H do
@@ -257,7 +260,7 @@ defmodule Module do
* `@typep` - defines a private type to be used in `@spec`
* `@opaque` - defines an opaque type to be used in `@spec`
* `@spec` - provides a specification for a function
* `@callback` - provides a specification for the behavior callback
* `@callback` - provides a specification for the behaviour callback
In addition to the built-in attributes outlined above, custom attributes may
also be added. A custom attribute is any valid identifier prefixed with an
@@ -308,15 +311,14 @@ defmodule Module do
and its attributes and functions can be modified.
"""
def open?(module) do
table = data_table_for(module)
table == :ets.info(table, :name)
:elixir_module.is_open(module)
end
@doc """
Evaluates the quoted contents in the given module's context.
A list of environment options can also be given as argument.
See `Code.eval_string` for more information.
See `Code.eval_string/3` for more information.
Raises an error if the module was already compiled.
@@ -340,7 +342,7 @@ defmodule Module do
Foo.sum(1, 2) #=> 3
"""
def eval_quoted(module, quoted, binding // [], opts // [])
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))
@@ -387,7 +389,7 @@ defmodule Module do
when defining the module, while `defmodule` automatically
shares the same environment.
"""
def create(module, quoted, opts // [])
def create(module, quoted, opts \\ [])
def create(module, quoted, env) when is_env(env) do
create(module, quoted, env.to_keywords)
@@ -404,6 +406,7 @@ defmodule Module do
iex> Module.concat([Foo, Bar])
Foo.Bar
iex> Module.concat([Foo, "Bar"])
Foo.Bar
@@ -420,6 +423,7 @@ defmodule Module do
iex> Module.concat(Foo, Bar)
Foo.Bar
iex> Module.concat(Foo, "Bar")
Foo.Bar
@@ -498,7 +502,7 @@ defmodule Module do
end
"""
def add_doc(module, line, kind, tuple, signature // [], doc)
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
@@ -510,7 +514,7 @@ defmodule Module do
table = docs_table_for(module)
{ signature, _ } = Enum.map_reduce signature, 1, fn(x, acc) ->
{ simplify_signature(x, line, acc), acc + 1 }
{ simplify_signature(x, acc), acc + 1 }
end
case :ets.lookup(table, tuple) do
@@ -531,28 +535,34 @@ defmodule Module do
# Simplify signatures to be stored in docs
defp simplify_signature({ ://, defline, [left, right ] }, line, i) do
{ ://, defline, [simplify_signature(left, line, i), right] }
defp simplify_signature({ :\\, _, [left, right ] }, i) do
{ :\\, [], [simplify_signature(left, i), right] }
end
defp simplify_signature({ var, line, atom }, _, _i) when is_atom(atom) do
case atom_to_list(var) do
[?_|_] -> { var, line, :guess }
_ -> { var, line, nil }
defp simplify_signature({ :%, _, [left, _] }, _i) when is_atom(left) do
last = List.last(String.split(atom_to_binary(left), "."))
atom = binary_to_atom(String.downcase(last))
{ atom, [], nil }
end
defp simplify_signature({ :=, _, [_, right] }, i) do
simplify_signature(right, i)
end
defp simplify_signature({ var, _, atom }, _i) when is_atom(atom) do
case atom_to_binary(var) do
"_" <> rest -> { binary_to_atom(rest), [], Elixir }
_ -> { var, [], nil }
end
end
defp simplify_signature({ :=, _, [_, right] }, line, i) do
simplify_signature(right, line, i)
end
defp simplify_signature(other, line, i) when is_integer(other), do: { :"int#{i}", line, :guess }
defp simplify_signature(other, line, i) when is_boolean(other), do: { :"bool#{i}", line, :guess }
defp simplify_signature(other, line, i) when is_atom(other), do: { :"atom#{i}", line, :guess }
defp simplify_signature(other, line, i) when is_list(other), do: { :"list#{i}", line, :guess }
defp simplify_signature(other, line, i) when is_float(other), do: { :"float#{i}", line, :guess }
defp simplify_signature(other, line, i) when is_binary(other), do: { :"binary#{i}", line, :guess }
defp simplify_signature(_, line, i), do: { :"arg#{i}", line, :guess }
defp 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 }
# Merge
@@ -564,11 +574,11 @@ 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
@@ -580,7 +590,7 @@ defmodule Module do
defp merge_signature({ var, _, _ } = older, { var, _, _ }, _), do: older
# Otherwise, returns a generic guess
defp merge_signature({ _, line, _ }, _newer, i), do: { :"arg#{i}", line, :guess }
defp merge_signature({ _, line, _ }, _newer, i), do: { :"arg#{i}", line, Elixir }
@doc """
Checks if the module defines the given function or macro.
@@ -631,14 +641,14 @@ defmodule Module do
defmodule Example do
def version, do: 1
Module.definitions_in __MODULE__ #=> [{:version,1}]
Module.definitions_in __MODULE__ #=> [{:version,0}]
end
"""
def definitions_in(module) do
assert_not_compiled!(:definitions_in, module)
table = function_table_for(module)
lc { tuple, _, _, _, _, _, _ } inlist :ets.tab2list(table), do: tuple
for { tuple, _, _, _, _, _, _ } <- :ets.tab2list(table), do: tuple
end
@doc """
@@ -649,7 +659,7 @@ defmodule Module do
defmodule Example do
def version, do: 1
Module.definitions_in __MODULE__, :def #=> [{:version,1}]
Module.definitions_in __MODULE__, :def #=> [{:version,0}]
Module.definitions_in __MODULE__, :defp #=> []
end
@@ -657,19 +667,19 @@ defmodule Module do
def definitions_in(module, kind) do
assert_not_compiled!(:definitions_in, module)
table = function_table_for(module)
lc { tuple, stored_kind, _, _, _, _, _ } inlist :ets.tab2list(table), stored_kind == kind, do: tuple
for { tuple, stored_kind, _, _, _, _, _ } <- :ets.tab2list(table), stored_kind == kind, do: tuple
end
@doc """
Makes the given functions in `module` overridable.
An overridable function is lazily defined, allowing a
developer to customize it. See `Kernel.defoverridable` for
developer to customize it. See `Kernel.defoverridable/1` for
more information and documentation.
"""
def make_overridable(module, tuples) do
assert_not_compiled!(:make_overridable, module)
lc tuple inlist tuples do
for tuple <- tuples do
case :elixir_def.lookup_definition(module, tuple) do
false ->
{ name, arity } = tuple
@@ -703,7 +713,7 @@ defmodule Module do
@doc """
Puts an Erlang attribute to the given module with the given
key and value. The semantics of putting the attribute depends
if the attribute was registered or not via `register_attribute/2`.
if the attribute was registered or not via `register_attribute/3`.
## Examples
@@ -733,7 +743,7 @@ defmodule Module do
@doc """
Gets the given attribute from a module. If the attribute
was marked with `accumulate` with `Module.register_attribute`,
was marked with `accumulate` with `Module.register_attribute/3`,
a list is always returned.
The `@` macro compiles to a call to this function. For example,
@@ -745,12 +755,11 @@ defmodule Module do
Module.get_attribute(__MODULE__, :foo, true)
Notice the third argument is used to indicate if a warning
should be emitted when the attribute was not previously defined.
`warn` may also be a stacktrace, which will be used in the
warning. The default value for `warn` is false for direct calls
but the `@foo` macro sets it to the proper stacktrace automatically,
warning every time `@foo` is used but not set previously.
Notice the third argument may be given to indicate a stacktrace
to be emitted when the attribute was not previously defined.
The default value for `warn` is nil for direct calls but the `@foo`
macro sets it to the proper stacktrace automatically, warning
every time `@foo` is used but not set previously.
## Examples
@@ -764,8 +773,9 @@ defmodule Module do
end
"""
@spec get_attribute(module, atom, warn :: boolean | [tuple]) :: term
def get_attribute(module, key, warn // false) when is_atom(key) do
@spec get_attribute(module, atom, warn :: nil | [tuple]) :: term
def get_attribute(module, key, warn \\ nil) when
is_atom(key) and (is_list(warn) or nil?(warn)) do
assert_not_compiled!(:get_attribute, module)
table = data_table_for(module)
@@ -777,9 +787,8 @@ defmodule Module do
cond do
:lists.member(key, acc) ->
[]
warn ->
stack = is_list(warn) and warn
:elixir_errors.warn "#{Exception.format_caller(stack)} undefined module attribute @#{key}, " <>
is_list(warn) ->
:elixir_errors.warn warn_info(warn), "undefined module attribute @#{key}, " <>
"please remove access to @#{key} or explicitly set it to nil before access\n"
nil
true ->
@@ -788,6 +797,15 @@ defmodule Module do
end
end
defp warn_info([entry|_]) do
opts = elem(entry, size(entry) - 1)
Exception.format_file_line(Keyword.get(opts, :file), Keyword.get(opts, :line)) <> " "
end
defp warn_info([]) do
""
end
@doc """
Deletes all attributes that match the given key.
@@ -878,7 +896,7 @@ defmodule Module do
:ok ->
:ok
{ :error, :private_doc } ->
:elixir_errors.warn "#{env.file}:#{line} function #{name}/#{arity} is private, @doc's are always discarded for private functions\n"
:elixir_errors.warn line, env.file, "function #{name}/#{arity} is private, @doc's are always discarded for private functions\n"
end
delete_attribute(module, :doc)
@@ -906,7 +924,7 @@ defmodule Module do
{ atom, 0 }
end
defp normalize_attribute(kind, atom) when kind in [:behavior, :behaviour] and is_atom(atom) do
defp normalize_attribute(:behaviour, atom) when is_atom(atom) do
Code.ensure_compiled(atom)
atom
end
+11 -10
View File
@@ -34,8 +34,8 @@
defmodule Module.LocalsTracker do
@moduledoc false
@timeout 30_000
@behavior :gen_server
@timeout 30_000
@behaviour :gen_server
@type ref :: pid | module
@type name :: atom
@@ -71,7 +71,7 @@ defmodule Module.LocalsTracker do
defp reduce_reachable(d, vertex, vertices) do
neighbours = :digraph.out_neighbours(d, vertex)
neighbours = (lc { _, _ } = t inlist neighbours, do: t) |> :ordsets.from_list
neighbours = (for { _, _ } = t <- neighbours, do: t) |> :ordsets.from_list
remaining = :ordsets.subtract(neighbours, vertices)
vertices = :ordsets.union(neighbours, vertices)
:lists.foldl(&reduce_reachable(d, &1, &2), vertices, remaining)
@@ -144,9 +144,10 @@ defmodule Module.LocalsTracker do
def collect_imports_conflicts(pid, all_defined) do
d = :gen_server.call(pid, :digraph, @timeout)
lc { name, arity } inlist all_defined,
n = :digraph.out_neighbours(d, { :import, name, arity }),
n != [] do
for { name, arity } <- all_defined,
:digraph.in_neighbours(d, { :import, name, arity }) != [],
n = :digraph.out_neighbours(d, { :import, name, arity }),
n != [] do
{ n, name, arity }
end
end
@@ -173,7 +174,7 @@ defmodule Module.LocalsTracker do
min = arity - default
max = arity
invoked = lc { n, a } inlist reachable, n == name, a in min..max, do: a
invoked = for { n, a } <- reachable, n == name, a in min..max, do: a
if invoked == [] do
[{ :unused_def, tuple, kind }|acc]
@@ -260,7 +261,7 @@ defmodule Module.LocalsTracker do
end
def handle_cast({ :add_defaults, kind, { name, arity }, defaults }, { d, _ } = state) do
lc i inlist :lists.seq(arity - defaults, arity - 1) do
for i <- :lists.seq(arity - defaults, arity - 1) do
handle_add_definition(d, kind, { name, i })
handle_add_local(d, { name, i }, { name, i + 1 })
end
@@ -268,8 +269,8 @@ defmodule Module.LocalsTracker do
end
def handle_cast({ :reattach, tuple, { in_neigh, out_neigh } }, { d, _ } = state) do
lc from inlist in_neigh, do: replace_edge(d, from, tuple)
lc to inlist out_neigh, do: replace_edge(d, tuple, to)
for from <- in_neigh, do: replace_edge(d, from, tuple)
for to <- out_neigh, do: replace_edge(d, tuple, to)
{ :noreply, state }
end
+92 -44
View File
@@ -1,12 +1,19 @@
defmodule Node do
@moduledoc """
Functions related to Erlang nodes.
Functions related to VM nodes.
Some of the functions in this module are inlined by the compiler,
similar to functions in the `Kernel` module and they are explicitly
marked in their docs as "inlined by the compiler". For more information
about inlined functions, check out the `Kernel` module.
"""
@type t :: atom
@type t :: node
@doc """
Returns the current node. It returns the same as the built-in `node()`.
Returns the current node.
It returns the same as the built-in `node()`.
"""
@spec self :: t
def self do
@@ -14,8 +21,9 @@ defmodule Node do
end
@doc """
Returns `true` if the local node is alive; that is, if the node can be
part of a distributed system. Otherwise, it returns `false`.
Returns `true` if the local node is alive.
That is, if the node can be part of a distributed system.
"""
@spec alive? :: boolean
def alive? do
@@ -24,7 +32,9 @@ defmodule Node do
@doc """
Returns a list of all visible nodes in the system, excluding
the local node. Same as `list(:visible)`.
the local node.
Same as `list(:visible)`.
"""
@spec list :: [t]
def list do
@@ -32,8 +42,9 @@ defmodule Node do
end
@doc """
Returns a list of nodes according to argument given. The result
returned when the argument is a list, is the list of nodes
Returns a list of nodes according to argument given.
The result returned when the argument is a list, is the list of nodes
satisfying the disjunction(s) of the list elements.
See http://www.erlang.org/doc/man/erlang.html#nodes-1 for more info.
@@ -45,8 +56,10 @@ defmodule Node do
end
@doc """
Monitors the status of the node. If `flag` is `true`, monitoring is
turned on. If `flag` is `false`, monitoring is turned off.
Monitors the status of the node.
If `flag` is `true`, monitoring is turned on.
If `flag` is `false`, monitoring is turned off.
See http://www.erlang.org/doc/man/erlang.html#monitor_node-2 for more info.
"""
@@ -67,10 +80,28 @@ defmodule Node do
end
@doc """
Forces the disconnection of a node. This will appear to the `node` as if
the local node has crashed. This BIF is mainly used in the Erlang network
authentication protocols. Returns `true` if disconnection succeeds, otherwise
`false`. If the local node is not alive, the function returns `:ignored`.
Tries to set up a connection to node.
Returns `:pang` if it fails, or `:pong` if it is successful.
## Examples
iex> Node.ping(:unknown_node)
:pang
"""
@spec ping(t) :: :pong | :pang
def ping(node) do
:net_adm.ping(node)
end
@doc """
Forces the disconnection of a node.
This will appear to the `node` as if the local node has crashed.
This function is mainly used in the Erlang network authentication
protocols. Returns `true` if disconnection succeeds, otherwise `false`.
If the local node is not alive, the function returns `:ignored`.
See http://www.erlang.org/doc/man/erlang.html#disconnect_node-1 for more info.
"""
@@ -80,9 +111,10 @@ defmodule Node do
end
@doc """
Establishes a connection to `node`. Returns `true` if successful,
`false` if not, and the atom `:ignored` if the local node is not
alive.
Establishes a connection to `node`.
Returns `true` if successful, `false` if not, and the atom
`:ignored` if the local node is not alive.
See http://erlang.org/doc/man/net_kernel.html#connect_node-1 for more info.
"""
@@ -97,6 +129,8 @@ defmodule Node do
Check http://www.erlang.org/doc/man/erlang.html#spawn-2 for
the list of available options.
Inlined by the compiler.
"""
@spec spawn(t, (() -> any)) :: pid
def spawn(node, fun) do
@@ -105,10 +139,13 @@ defmodule Node do
@doc """
Returns the pid of a new process started by the application of `fun`
on `node`. If `node` does not exist, a useless pid is returned.
on `node`.
Check http://www.erlang.org/doc/man/erlang.html#spawn_opt-3 for
the list of available options.
If `node` does not exist, a useless pid is returned. Check
http://www.erlang.org/doc/man/erlang.html#spawn_opt-3 for the list of
available options.
Inlined by the compiler.
"""
@spec spawn(t, (() -> any), Process.spawn_opts) :: pid | {pid, reference}
def spawn(node, fun, opts) do
@@ -117,11 +154,13 @@ defmodule Node do
@doc """
Returns the pid of a new process started by the application of
`module.function(args)` on `node`. If `node` does not exists,
a useless pid is returned.
`module.function(args)` on `node`.
Check http://www.erlang.org/doc/man/erlang.html#spawn-4 for
the list of available options.
If `node` does not exist, a useless pid is returned. Check
http://www.erlang.org/doc/man/erlang.html#spawn-4 for the list of
available options.
Inlined by the compiler.
"""
@spec spawn(t, module, atom, [any]) :: pid
def spawn(node, module, fun, args) do
@@ -130,11 +169,13 @@ defmodule Node do
@doc """
Returns the pid of a new process started by the application of
`module.function(args)` on `node`. If `node` does not exists,
a useless pid is returned.
`module.function(args)` on `node`.
Check http://www.erlang.org/doc/man/erlang.html#spawn_opt-5 for
the list of available options.
If `node` does not exist, a useless pid is returned. Check
http://www.erlang.org/doc/man/erlang.html#spawn_opt-5 for the list of
available options.
Inlined by the compiler.
"""
@spec spawn(t, module, atom, [any], Process.spawn_opts) :: pid | {pid, reference}
def spawn(node, module, fun, args, opts) do
@@ -142,11 +183,13 @@ defmodule Node do
end
@doc """
Returns the pid of a new process started by the application of `fun`
on `node`. A link is created between the calling process and the
new process, atomically. If `node` does not exist, a useless pid is returned
(and due to the link, an exit signal with exit reason `:noconnection` will be
received).
Returns the pid of a new linked process started by the application of `fun` on `node`.
A link is created between the calling process and the new process, atomically.
If `node` does not exist, a useless pid is returned (and due to the link, an exit
signal with exit reason `:noconnection` will be received).
Inlined by the compiler.
"""
@spec spawn_link(t, (() -> any)) :: pid
def spawn_link(node, fun) do
@@ -154,11 +197,14 @@ defmodule Node do
end
@doc """
Returns the pid of a new process started by the application of
`module.function(args)` on `node`. A link is created between the calling
process and the new process, atomically. If `node` does not exist, a useless
pid is returned (and due to the link, an exit signal with exit reason
`:noconnection` will be received).
Returns the pid of a new linked process started by the application of
`module.function(args)` on `node`.
A link is created between the calling process and the new process, atomically.
If `node` does not exist, a useless pid is returned (and due to the link, an exit
signal with exit reason `:noconnection` will be received).
Inlined by the compiler.
"""
@spec spawn_link(t, module, atom, [any]) :: pid
def spawn_link(node, module, fun, args) do
@@ -166,19 +212,21 @@ defmodule Node do
end
@doc """
Sets the magic cookie of `node` to the atom `cookie`. The default node
is `Node.self`, the local node. If `node` is the local node, the function also
sets the cookie of all other unknown nodes to `cookie`.
Sets the magic cookie of `node` to the atom `cookie`.
The default node is `Node.self`, the local node. If `node` is the local node,
the function also sets the cookie of all other unknown nodes to `cookie`.
This function will raise `FunctionClauseError` if the given `node` is not alive.
"""
def set_cookie(node // Node.self, cookie) when is_atom(cookie) do
def set_cookie(node \\ Node.self, cookie) when is_atom(cookie) do
:erlang.set_cookie(node, cookie)
end
@doc """
Returns the magic cookie of the local node, if the node is alive;
otherwise `:nocookie`.
Returns the magic cookie of the local node.
Returns the cookie if the node is alive, otherwise `:nocookie`.
"""
def get_cookie() do
:erlang.get_cookie()
+44 -27
View File
@@ -5,7 +5,7 @@ defmodule OptionParser do
@doc """
Parses `argv` and returns a tuple with the parsed options, its
arguments, and a list options that couldn't be parsed.
arguments, and a list of options that couldn't be parsed.
## Examples
@@ -42,6 +42,7 @@ defmodule OptionParser do
`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);
If a switch can't be parsed, the option is returned in the invalid
options list (third element of the returned tuple).
@@ -59,10 +60,10 @@ defmodule OptionParser do
...> switches: [unlock: :boolean, limit: :integer])
{ [unlock: true, limit: 0], ["path/to/file"], [] }
iex> OptionParser.parse(["-limit", "3"], switches: [limit: :integer])
iex> OptionParser.parse(["--limit", "3"], switches: [limit: :integer])
{ [limit: 3], [], [] }
iex> OptionParser.parse(["-limit", "yyz"], switches: [limit: :integer])
iex> OptionParser.parse(["--limit", "yyz"], switches: [limit: :integer])
{ [], [], [limit: "yyz"] }
## Negation switches
@@ -79,7 +80,7 @@ defmodule OptionParser do
{ [op: false], ["path/to/file"], [] }
"""
def parse(argv, opts // []) when is_list(argv) and is_list(opts) do
def parse(argv, opts \\ []) when is_list(argv) and is_list(opts) do
parse(argv, opts, true)
end
@@ -96,8 +97,9 @@ defmodule OptionParser do
iex> OptionParser.parse_head(["--verbose", "--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
def parse_head(argv, opts \\ []) when is_list(argv) and is_list(opts) do
parse(argv, opts, false)
end
@@ -118,7 +120,8 @@ defmodule OptionParser do
end
defp parse(["-" <> option|t], aliases, switches, dict, args, invalid, all) do
{ option, kinds, value } = normalize_option(option, switches, aliases)
{ option, value } = split_option(option)
{ option, kinds, value } = normalize_option(option, value, switches, aliases)
if nil?(value) do
{ value, t } =
@@ -153,6 +156,8 @@ defmodule OptionParser do
defp store_option(dict, invalid, option, value, kinds) do
{ invalid_option, value } =
cond do
:invalid in kinds ->
{ option, value }
:boolean in kinds ->
{ nil, value in [true, "true"] }
:integer in kinds ->
@@ -185,27 +190,33 @@ defmodule OptionParser do
end
end
defp normalize_option(<<?-, option :: binary>>, switches, aliases) do
normalize_option(option, switches, aliases)
defp normalize_option(<<?-, option :: binary>>, value, switches, _aliases) do
normalize_option(get_negated(option, switches), value, switches)
end
defp normalize_option(option, switches, aliases) do
{ option, value } = split_option(option)
if non_neg = get_non_negated(option, aliases) do
kinds = List.wrap(switches[non_neg])
if :boolean in kinds do
{ non_neg, kinds, false }
else
{ get_aliased(option, aliases), [:boolean], true }
end
defp normalize_option(option, value, switches, aliases) do
option = get_option(option)
if alias = aliases[option] do
normalize_option({ :default, alias }, value, switches)
else
atom = get_aliased(option, aliases)
{ atom, List.wrap(switches[atom]), value }
{ option, [:invalid], value }
end
end
defp normalize_option({ :negated, option }, _value, switches) do
kinds = List.wrap(switches[option])
if :boolean in kinds do
{ option, kinds, false }
else
{ option, [:boolean], true }
end
end
defp normalize_option({ :default, option }, value, switches) do
{ option, List.wrap(switches[option]), value }
end
defp split_option(option) do
case :binary.split(option, "=") do
[h] -> { h, nil }
@@ -214,14 +225,20 @@ defmodule OptionParser do
end
defp to_underscore(option) do
bc <<c>> inbits option, do: << if(c == ?-, do: ?_, else: c) >>
for <<c <- option>>, into: "", do: << if(c == ?-, do: ?_, else: c) >>
end
defp get_aliased(option, aliases) do
atom = option |> to_underscore |> binary_to_atom
aliases[atom] || atom
defp get_option(option) do
option |> to_underscore |> binary_to_atom
end
defp get_non_negated("no-" <> rest, aliases), do: get_aliased(rest, aliases)
defp get_non_negated(_, _), do: nil
defp get_negated("no-" <> rest = option, switches) do
negated = get_option(rest)
option = if Keyword.has_key?(switches, negated), do: negated, else: get_option(option)
{ :negated, option }
end
defp get_negated(rest, _switches) do
{ :default, get_option(rest) }
end
end
+13 -11
View File
@@ -205,8 +205,10 @@ defmodule Path do
iex> Path.relative_to("/usr/local/foo", "/usr/local")
"foo"
iex> Path.relative_to("/usr/local/foo", "/")
"usr/local/foo"
iex> Path.relative_to("/usr/local/foo", "/etc")
"/usr/local/foo"
@@ -277,8 +279,10 @@ defmodule Path do
iex> Path.basename("~/foo/bar.ex", ".ex")
"bar"
iex> Path.basename("~/foo/bar.exs", ".ex")
"bar.exs"
iex> Path.basename("~/foo/bar.old.ex", ".ex")
"bar.old"
@@ -309,6 +313,7 @@ defmodule Path do
iex> Path.extname("foo.erl")
".erl"
iex> Path.extname("~/foo/bar")
""
@@ -324,6 +329,7 @@ defmodule Path do
iex> Path.rootname("/foo/bar")
"/foo/bar"
iex> Path.rootname("/foo/bar.ex")
"/foo/bar"
@@ -340,6 +346,7 @@ defmodule Path do
iex> Path.rootname("/foo/bar.erl", ".erl")
"/foo/bar"
iex> Path.rootname("/foo/bar.erl", ".ex")
"/foo/bar.erl"
@@ -356,8 +363,10 @@ defmodule Path do
iex> Path.join(["~", "foo"])
"~/foo"
iex> Path.join(["foo"])
"foo"
iex> Path.join(["/", "foo", "bar"])
"/foo/bar"
@@ -408,8 +417,8 @@ 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(maybe_remove_dirsep(result, os_type))
defp do_join(<<>>, <<>>, result, _os_type), do:
iolist_to_binary(:lists.reverse(result))
defp do_join(<<>>, relativename, [?:|rest], :win32), do:
do_join(relativename, <<>>, [?:|rest], :win32)
defp do_join(<<>>, relativename, [?/|result], os_type), do:
@@ -419,15 +428,6 @@ defmodule Path do
defp do_join(<<char, rest :: binary>>, relativename, result, os_type) when is_integer(char), 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.
@@ -436,8 +436,10 @@ defmodule Path do
iex> Path.split("")
[]
iex> Path.split("foo")
["foo"]
iex> Path.split("/foo/bar")
["/", "foo", "bar"]
+1 -1
View File
@@ -20,7 +20,7 @@ defmodule Port do
@doc """
See http://www.erlang.org/doc/man/erlang.html#port_command-2.
"""
def command(port, data, options // []) do
def command(port, data, options \\ []) do
:erlang.port_command(port, data, options)
end
+105 -17
View File
@@ -1,11 +1,11 @@
defmodule Process do
@moduledoc """
This module provides convenience functions around processes and
the process dictionary. In Erlang, most of these functions are
auto-imported, but in Elixir they are grouped in a module for
convenience. Notice that these functions, different from Erlang's,
always return nil instead of undefined. You can use their Erlang
version if you want the undefined value.
Conveniences for working with processes and the process dictionary.
Some of the functions in this module are inlined by the compiler,
similar to functions in the `Kernel` module and they are explicitly
marked in their docs as "inlined by the compiler". For more information
about inlined functions, check out the `Kernel` module.
"""
@doc """
@@ -32,7 +32,7 @@ defmodule Process do
"""
@spec get(term) :: term
@spec get(term, default :: term) :: term
def get(key, default // nil) do
def get(key, default \\ nil) do
case :erlang.get(key) do
:undefined ->
default
@@ -76,21 +76,23 @@ defmodule Process do
@doc """
Sends an exit signal with the given reason to the pid.
The following behavior applies if reason is any term except `:normal` or `:kill`:
The following behaviour applies if reason is any term except `:normal` or `:kill`:
1) If pid is not trapping exits, pid will exit with the given reason;
2) If pid is trapping exits, the exit signal is transformed into a message
{'EXIT', from, reason} and delivered to the message queue of pid;
{:EXIT, from, reason} and delivered to the message queue of pid;
3) If reason is the atom `:normal`, pid will not exit. If it is trapping exits,
the exit signal is transformed into a message {'EXIT', from, :normal} and
the exit signal is transformed into a message {:EXIT, from, :normal} and
delivered to its message queue;
4) If reason is the atom `:kill`, that is if `exit(pid, :kill)` is called, an
untrappable exit signal is sent to pid which will unconditionally exit with
exit reason `:killed`.
Inlined by the compiler.
## Examples
Process.exit(pid, :kill)
@@ -110,12 +112,64 @@ defmodule Process do
end
@doc """
Returns the pid of a new process started by the application of `fun`.
It behaves exactly the same as `Kernel.spawn/1`.
Sends a message to the given process.
It does the same as `Kernel.send/2`.
"""
@spec spawn((() -> any)) :: pid
def spawn(fun) do
:erlang.spawn(fun)
@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.
If the option `:noconnect` is used and sending the message would require an
auto-connection to another node the message is not sent and `:noconnect` is
returned.
If the option `:nosuspend` is used and sending the message would cause the
sender to be suspended the message is not sent and `:nosuspend` is returned.
Otherwise the message is sent and `:ok` is returned.
## Examples
iex> Process.send({ :name, :node_does_not_exist }, :hi, [:noconnect])
:noconnect
"""
@spec send(dest, msg, [option]) :: result when
dest: pid | port | atom | { atom, node },
msg: any,
option: :noconnect | :nosuspend,
result: :ok | :noconnect | :nosuspend
def send(dest, msg, options) do
:erlang.send(dest, msg, options)
end
@doc """
Sends `msg` to `dest` after `time` millisecons.
If `dest` is a pid, it has to be a pid of a local process, dead or alive.
If `dest` is an atom, it is supposed to be the name of a registered process
which is looked up at the time of delivery. No error is given if the name does
not refer to a process.
This function returns a timer reference, which can be read or canceled with
`:erlang.read_timer/1`, `:erlang.start_timer/3` and `:erlang.cancel_timer/1`.
Note `time` cannot be greater than `4294967295`.
Finally, the timer will be automatically canceled if the given `dest` is a pid
which is not alive or when the given pid exits. Note that timers will not be
automatically canceled when `dest` is an atom (as the atom resolution is done
on delivery).
"""
@spec send_after(pid | atom, term, non_neg_integer) :: reference
def send_after(dest, msg, time) do
:erlang.send_after(time, dest, msg)
end
@type spawn_opt :: :link | :monitor | {:priority, :low | :normal | :high} |
@@ -124,11 +178,24 @@ defmodule Process do
{:min_bin_vheap_size, non_neg_integer}
@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`.
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`.
It also accepts extra options, for the list of available options
check http://www.erlang.org/doc/man/erlang.html#spawn_opt-2
Inlined by the compiler.
"""
@spec spawn((() -> any), spawn_opts) :: pid | {pid, reference}
def spawn(fun, opts) do
@@ -141,6 +208,8 @@ defmodule Process do
scheduler queue and be run some time later.
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
@@ -155,6 +224,7 @@ defmodule Process do
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}
def spawn(mod, fun, args, opts) do
@@ -165,6 +235,8 @@ defmodule Process do
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.
Inlined by the compiler.
"""
@spec spawn_link((() -> any)) :: pid
def spawn_link(fun) do
@@ -175,6 +247,8 @@ defmodule Process do
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
def spawn_link(mod, fun, args) do
@@ -184,6 +258,8 @@ defmodule Process do
@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}
def spawn_monitor(fun) do
@@ -194,6 +270,8 @@ defmodule Process do
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}
def spawn_monitor(mod, fun, args) do
@@ -205,6 +283,8 @@ defmodule Process do
It returns the monitor reference.
See http://www.erlang.org/doc/man/erlang.html#monitor-2 for more info.
Inlined by the compiler.
"""
@spec monitor(pid | {reg_name :: atom, node :: atom} | reg_name :: atom) :: reference
def monitor(item) do
@@ -217,10 +297,12 @@ defmodule Process do
If the monitoring is already turned off, nothing happens.
See http://www.erlang.org/doc/man/erlang.html#demonitor-2 for more info.
Inlined by the compiler.
"""
@spec demonitor(reference) :: true
@spec demonitor(reference, options :: [:flush | :info]) :: boolean
def demonitor(monitor_ref, options // []) do
def demonitor(monitor_ref, options \\ []) do
:erlang.demonitor(monitor_ref, options)
end
@@ -244,6 +326,8 @@ defmodule Process do
(or port) `pid`, if there is not such a link already.
See http://www.erlang.org/doc/man/erlang.html#link-1 for more info.
Inlined by the compiler.
"""
@spec link(pid | port) :: true
def link(pid) do
@@ -256,6 +340,8 @@ defmodule Process do
fail, even if there is no link or `id` does not exist
See http://www.erlang.org/doc/man/erlang.html#unlink-1 for more info.
Inlined by the compiler.
"""
@spec unlink(pid | port) :: true
def unlink(pid) do
@@ -267,7 +353,9 @@ defmodule Process do
be an atom, can be used instead of the pid / port identifier with the
`Kernel.send/2` function.
See http://www.erlang.org/doc/man/erlang.html#register-2 for more info.
`Process.register/2` will fail with `ArgumentError` if the pid supplied
is no longer alive, (check with `alive?/1`) or if the name is
already registered (check with `registered?/1`).
"""
@spec register(pid | port, atom) :: true
def register(pid, name) do
+79 -87
View File
@@ -33,13 +33,32 @@ defmodule Protocol do
defp after_defprotocol do
quote unquote: false do
{ arg, bodies, rec } = Protocol.impl_for(__MODULE__)
# == Deprecated records handling ==
{ arg, impl } = Protocol.rec_impl_for(__MODULE__)
Kernel.def impl_for(unquote(arg)) when Kernel.is_record(unquote(arg)), do: unquote(impl)
# == Deprecated records handling ==
@spec impl_for(term) :: module | nil
Kernel.def impl_for(data)
lc { guard, body } inlist bodies do
Kernel.def impl_for(unquote(arg)) when unquote(guard), do: unquote(body)
# Define the implementation for structs.
#
# It simply delegates to struct_impl_for which is then
# optimized during protocol consolidation.
Kernel.def impl_for(%{ __struct__: struct }) when :erlang.is_atom(struct) do
struct_impl_for(struct)
end
# Define the implementation for builtins.
for { guard, mod } <- Protocol.builtin do
target = Module.concat(__MODULE__, mod)
Kernel.def impl_for(data) when :erlang.unquote(guard)(data) do
case impl_for?(unquote(target)) do
true -> unquote(target).__impl__(:name)
false -> any_impl_for
end
end
end
@spec impl_for!(term) :: module | no_return
@@ -47,25 +66,35 @@ defmodule Protocol do
impl_for(data) || raise(Protocol.UndefinedError, protocol: __MODULE__, value: data)
end
# Handle special Record type
Kernel.defp rec_impl_for(unquote(arg)), do: unquote(rec)
# Handle special Any type
# Internal handler for Any
if @fallback_to_any do
Kernel.defp any_impl_for do
try do
__MODULE__.Any.__impl__(:name)
catch
:error, :undef, [[{ __MODULE__.Any, :__impl__, [:name], _ }|_]|_] ->
nil
case impl_for?(__MODULE__.Any) do
true -> __MODULE__.Any.__impl__(:name)
false -> nil
end
end
else
Kernel.defp any_impl_for, do: nil
end
# Inline both helpers
@compile { :inline, any_impl_for: 0, rec_impl_for: 1 }
# Internal handler for Structs
Kernel.defp struct_impl_for(struct) do
target = Module.concat(__MODULE__, struct)
case impl_for?(target) do
true -> target.__impl__(:name)
false -> any_impl_for
end
end
# Check if compilation is available internally
Kernel.defp impl_for?(target) do
Code.ensure_compiled?(target) and
function_exported?(target, :__impl__, 1)
end
# Inline any and struct implementations
@compile { :inline, any_impl_for: 0, struct_impl_for: 1, impl_for?: 1 }
if :code.ensure_loaded(Kernel.Typespec) == { :module, Kernel.Typespec } and
not Kernel.Typespec.defines_type?(__MODULE__, :t, 0) do
@@ -90,7 +119,7 @@ defmodule Protocol do
end
defp do_defimpl(protocol, [do: block, for: for]) when is_list(for) do
lc f inlist for, do: do_defimpl(protocol, [do: block, for: f])
for f <- for, do: do_defimpl(protocol, [do: block, for: f])
end
defp do_defimpl(protocol, [do: block, for: for]) do
@@ -139,87 +168,50 @@ defmodule Protocol do
# Builtin types.
@doc false
def builtin do
[ Tuple, Atom, List, BitString, Integer, Float,
Function, PID, Port, Reference, Any ]
[is_tuple: Tuple,
is_atom: Atom,
is_list: List,
is_map: Map,
is_bitstring: BitString,
is_integer: Integer,
is_float: Float,
is_function: Function,
is_pid: PID,
is_port: Port,
is_reference: Reference]
end
# Implements the function that detects the protocol and
# returns the module to dispatch to.
@doc false
def impl_for(current) do
arg = quote(do: arg)
all = [Record|builtin]
def rec_impl_for(current) do
all = [Any] ++ for { _guard, mod } <- builtin, do: mod
arg = quote do: arg
target = Module.concat(current, Tuple)
{ arg,
lc(mod inlist all, do: impl_for(current, mod, arg)),
rec_impl_for(current, arg) }
end
fallback = quote do
case impl_for?(unquote(target)) do
true -> unquote(target).__impl__(:name)
false -> any_impl_for
end
end
defp rec_impl_for(current, arg) do
fallback = impl_for(current, Tuple, arg) |> elem(1)
impl_for = quote do
atom = :erlang.element(1, unquote(arg))
quote do
target = Module.concat(unquote(current), unquote(arg))
try do
target.__impl__(:name)
catch
:error, :undef, [[{ ^target, :__impl__, [:name], _ }|_]|_] ->
case not(atom in unquote(all)) and match?('Elixir.' ++ _, atom_to_list(atom)) do
true ->
target = Module.concat(unquote(current), atom)
case impl_for?(target) do
true -> target.__impl__(:name)
false -> unquote(fallback)
end
false ->
unquote(fallback)
end
end
end
defp impl_for(current, Record, arg) do
fallback = impl_for(current, Tuple, arg) |> elem(1)
dispatch = quote do
atom = :erlang.element(1, unquote(arg))
case not(atom in unquote(builtin)) and match?('Elixir.' ++ _, atom_to_list(atom)) do
true -> rec_impl_for(atom)
false -> unquote(fallback)
end
end
quote do
{ is_record(unquote(arg)), unquote(dispatch) }
end
end
defp impl_for(current, Tuple, arg), do: impl_with_fallback(Tuple, :is_tuple, current, arg)
defp impl_for(current, Atom, arg), do: impl_with_fallback(Atom, :is_atom, current, arg)
defp impl_for(current, List, arg), do: impl_with_fallback(List, :is_list, current, arg)
defp impl_for(current, BitString, arg), do: impl_with_fallback(BitString, :is_bitstring, current, arg)
defp impl_for(current, Integer, arg), do: impl_with_fallback(Integer, :is_integer, current, arg)
defp impl_for(current, Float, arg), do: impl_with_fallback(Float, :is_float, current, arg)
defp impl_for(current, Function, arg), do: impl_with_fallback(Function, :is_function, current, arg)
defp impl_for(current, PID, arg), do: impl_with_fallback(PID, :is_pid, current, arg)
defp impl_for(current, Port, arg), do: impl_with_fallback(Port, :is_port, current, arg)
defp impl_for(current, Reference, arg), do: impl_with_fallback(Reference, :is_reference, current, arg)
defp impl_for(_current, Any, _arg) do
{ true, quote(do: any_impl_for) }
end
# Defines an implementation with fallback to the given module.
defp impl_with_fallback(mod, guard, current, arg) do
quote do
{ :erlang.unquote(guard)(unquote(arg)),
unquote(with_fallback(Module.concat(current, mod))) }
end
end
# Tries to dispatch to a given target, fallbacks to the
# given `fallback` implementation if the target does not exist.
defp with_fallback(target) when is_atom(target) do
quote do
try do
unquote(target).__impl__(:name)
catch
:error, :undef, [[{ unquote(target), :__impl__, [:name], _ }|_]|_] ->
any_impl_for
end
end
{ arg, impl_for }
end
end
@@ -234,10 +226,10 @@ defmodule Protocol.DSL do
defmacro def({ name, _, args }) when is_atom(name) and is_list(args) do
arity = length(args)
type_args = lc _ inlist :lists.seq(2, arity), do: quote(do: term)
type_args = for _ <- :lists.seq(2, arity), do: quote(do: term)
type_args = [quote(do: t) | type_args]
call_args = lc i inlist :lists.seq(2, arity),
call_args = for i <- :lists.seq(2, arity),
do: { binary_to_atom(<<?x, i + 64>>), [], __MODULE__ }
call_args = [quote(do: t) | call_args]
@@ -274,7 +266,7 @@ defmodule Protocol.DSL do
tuple = { name, arity }
specs = Module.get_attribute(module, :spec)
found = lc { k, v } inlist specs, k == tuple do
found = for { k, v } <- specs, k == tuple do
Kernel.Typespec.define_callback(module, tuple, v)
true
end
+42 -59
View File
@@ -62,8 +62,8 @@ defmodule Protocol.Consolidation do
end
defp extract_matching_by_attribute(paths, prefix, callback) do
lc path inlist paths,
file inlist list_dir(path),
for path <- paths,
file <- list_dir(path),
mod = extract_from_file(path, file, prefix, callback),
do: mod
end
@@ -125,6 +125,9 @@ defmodule Protocol.Consolidation do
{ :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 " <>
"and structs at once. Consolidation will be added back once " <>
"polymorphic records are removed"
ensure_protocol(protocol)
|> if_ok(change_debug_info types)
|> if_ok(compile)
@@ -157,36 +160,38 @@ defmodule Protocol.Consolidation do
# impl_for/1 dispatch version.
defp change_debug_info({ protocol, any, code }, types) do
types = if any, do: types, else: List.delete(types, Any)
records = types -- Protocol.builtin
builtin = Protocol.builtin -- (Protocol.builtin -- types)
builtin = if records != [], do: [Record|builtin], else: builtin
change_impl_for(code, protocol, builtin, records, false, [])
all = [Any] ++ for { _guard, mod } <- Protocol.builtin, do: mod
structs = types -- all
change_impl_for(code, protocol, types, structs, false, [])
end
defp change_impl_for([{ :attribute, line, :protocol, _ }|t], protocol, builtin, records, _, acc) do
attr = [fallback_to_any: Any in builtin, consolidated: true]
change_impl_for(t, protocol, builtin, records, true,
[{ :attribute, line, :protocol, attr }|acc])
defp change_impl_for([{ :attribute, line, :protocol, opts }|t], protocol, types, structs, _, acc) do
opts = [fallback_to_any: opts[:fallback_to_any], consolidated: true]
change_impl_for(t, protocol, types, structs, true,
[{ :attribute, line, :protocol, opts }|acc])
end
defp change_impl_for([{ :function, line, :impl_for, 1, _ }|t], protocol, builtin, records, is_protocol, acc) do
clauses = lc type inlist builtin, do: clause_for(type, protocol, line)
defp change_impl_for([{ :function, line, :impl_for, 1, _ }|t], protocol, types, structs, is_protocol, acc) do
fallback = if Any in types, do: Module.concat(protocol, Any), else: nil
unless Any in builtin do
clauses = clauses ++ [fallback_clause_for(nil, protocol, line)]
end
clauses = for { guard, mod } <- Protocol.builtin,
mod in types,
do: builtin_clause_for(mod, guard, protocol, line)
change_impl_for(t, protocol, builtin, records, is_protocol,
clauses = [struct_clause_for(line)|clauses] ++
[fallback_clause_for(fallback, protocol, line)]
change_impl_for(t, protocol, types, structs, is_protocol,
[{ :function, line, :impl_for, 1, clauses }|acc])
end
defp change_impl_for([{ :function, line, :rec_impl_for, 1, _ }|t], protocol, builtin, records, is_protocol, acc) do
fallback = if Tuple in builtin, do: Module.concat(protocol, Tuple)
clauses = lc type inlist records, do: record_clause_for(type, protocol, line)
clauses = clauses ++ [fallback_clause_for(fallback, protocol, line)]
defp change_impl_for([{ :function, line, :struct_impl_for, 1, _ }|t], protocol, types, structs, is_protocol, acc) do
fallback = if Any in types, do: Module.concat(protocol, Any), else: nil
clauses = for struct <- structs, do: each_struct_clause_for(struct, protocol, line)
clauses = clauses ++ [fallback_clause_for(fallback, protocol, line)]
change_impl_for(t, protocol, builtin, records, is_protocol,
[{ :function, line, :rec_impl_for, 1, clauses }|acc])
change_impl_for(t, protocol, types, structs, is_protocol,
[{ :function, line, :struct_impl_for, 1, clauses }|acc])
end
defp change_impl_for([h|t], protocol, info, types, is_protocol, acc) do
@@ -201,42 +206,6 @@ defmodule Protocol.Consolidation do
end
end
defp clause_for(Tuple, protocol, line), do: builtin_clause_for(Tuple, :is_tuple, protocol, line)
defp clause_for(Atom, protocol, line), do: builtin_clause_for(Atom, :is_atom, protocol, line)
defp clause_for(List, protocol, line), do: builtin_clause_for(List, :is_list, protocol, line)
defp clause_for(BitString, protocol, line), do: builtin_clause_for(BitString, :is_bitstring, protocol, line)
defp clause_for(Integer, protocol, line), do: builtin_clause_for(Integer, :is_integer, protocol, line)
defp clause_for(Float, protocol, line), do: builtin_clause_for(Float, :is_float, protocol, line)
defp clause_for(Function, protocol, line), do: builtin_clause_for(Function, :is_function, protocol, line)
defp clause_for(PID, protocol, line), do: builtin_clause_for(PID, :is_pid, protocol, line)
defp clause_for(Port, protocol, line), do: builtin_clause_for(Port, :is_port, protocol, line)
defp clause_for(Reference, protocol, line), do: builtin_clause_for(Reference, :is_reference, protocol, line)
defp clause_for(Any, protocol, line) do
{:clause, line, [{:var, line, :_}], [],
[{ :atom, line, Module.concat(protocol, Any) }]}
end
defp clause_for(Record, _protocol, line) do
{:clause, line, [{:var, line, :x}],
[[{:op, line, :andalso,
{:call, line,
{:remote, line, {:atom, line, :erlang}, {:atom, line, :is_tuple}},
[{:var, line, :x}]},
{:call, line,
{:remote, line, {:atom, line, :erlang}, {:atom, line, :is_atom}},
[{:call, line,
{:remote, line, {:atom, line, :erlang}, {:atom, line, :element}},
[{:integer, line, 1}, {:var, line, :x}]
}]},
}]],
[{:call, line,
{:atom, line, :rec_impl_for},
[{:call, line,
{:remote, line, {:atom, line, :erlang}, {:atom, line, :element}},
[{:integer, line, 1}, {:var, line, :x}]}]}]}
end
defp builtin_clause_for(mod, guard, protocol, line) do
{:clause, line,
[{:var, line, :x}],
@@ -247,7 +216,21 @@ defmodule Protocol.Consolidation do
[{:atom, line, Module.concat(protocol, mod)}]}
end
defp record_clause_for(other, protocol, line) do
defp struct_clause_for(line) do
{:clause, line,
[{:map, line, [
{:map_field_exact, line, {:atom, line, :__struct__}, {:var, line, :x}}
]}],
[[{:call, line,
{:remote, line, {:atom, line, :erlang}, {:atom, line, :is_atom}},
[{:var, line, :x}],
}]],
[{:call, line,
{:atom, line, :struct_impl_for},
[{:var, line, :x}]}]}
end
defp each_struct_clause_for(other, protocol, line) do
{:clause, line, [{:atom, line, other}], [],
[{:atom, line, Module.concat(protocol, other)}]}
end
+43 -4
View File
@@ -1,7 +1,46 @@
defrecord Range, [:first, :last] do
defmodule Range do
@moduledoc """
Defines a Range.
"""
@type t :: { Range, any, any }
@type t(first, last) :: { Range, first, 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
end
@doc """
Returns true if the given argument is a range.
## Examples
iex> Range.range?(1..3)
true
iex> Range.range?(0)
false
"""
def range?({ Range, _, _ }), do: true
def range?(_), do: false
end
defprotocol Range.Iterator do
@@ -55,7 +94,7 @@ defimpl Enumerable, for: Range do
end
defimpl Range.Iterator, for: Integer do
def next(first, Range[last: last]) when is_integer(last) do
def next(first, _ .. last) when is_integer(last) do
if last >= first do
&(&1 + 1)
else
@@ -63,7 +102,7 @@ defimpl Range.Iterator, for: Integer do
end
end
def count(first, Range[last: last]) when is_integer(last) do
def count(first, _ .. last) when is_integer(last) do
if last >= first do
last - first + 1
else
@@ -75,7 +114,7 @@ end
defimpl Inspect, for: Range do
import Inspect.Algebra
def inspect(Range[first: first, last: last], opts) do
def inspect(first .. last, opts) do
concat [to_doc(first, opts), "..", to_doc(last, opts)]
end
end
+154 -900
View File
File diff suppressed because it is too large Load Diff
+146
View File
@@ -0,0 +1,146 @@
defmodule Record.Backend do
# Callback functions invoked by defrecord, defrecordp and friends.
@moduledoc false
@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, [], [])
end
def split_fields_and_types(tag, other) do
raise ArgumentError, message: "#{tag} fields must be a keyword list, got: #{Macro.to_string other}"
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])
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])
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) }
end
@doc """
Callback invoked from record/0 and record/1 macros.
"""
def access(atom, fields, args, caller) do
cond do
is_atom(args) ->
index(atom, fields, args)
Keyword.keyword?(args) ->
create(atom, fields, args, caller)
true ->
raise ArgumentError,
message: "expected arguments to be a compile time atom or keywords, got: #{Macro.to_string args}"
end
end
@doc """
Callback invoked from the record/2 macro.
"""
def access(atom, fields, record, args, caller) do
cond do
is_atom(args) ->
get(atom, fields, record, args)
Keyword.keyword?(args) ->
update(atom, fields, record, args, caller)
true ->
raise ArgumentError,
message: "expected arguments to be a compile time atom or keywords, got: #{Macro.to_string args}"
end
end
@doc """
Gets the index of field.
"""
def index(atom, fields, field) do
if index = find_index(fields, field, 0) do
index - 1 # Convert to Elixir index
else
raise ArgumentError, message: "record #{inspect atom} does not have the key: #{inspect field}"
end
end
@doc """
Creates a new record with the given default fields and keyword values.
"""
def create(atom, fields, keyword, caller) do
in_match = caller.in_match?
{ match, remaining } =
Enum.map_reduce(fields, keyword, fn({ field, default }, each_keyword) ->
new_fields =
case Keyword.has_key?(each_keyword, field) do
true -> Keyword.get(each_keyword, field)
false ->
case in_match do
true -> { :_, [], nil }
false -> Macro.escape(default)
end
end
{ new_fields, Keyword.delete(each_keyword, field) }
end)
case remaining do
[] ->
{ :{}, [], [atom|match] }
_ ->
keys = for { key, _ } <- remaining, do: key
raise ArgumentError, message: "record #{inspect atom} does not have the key: #{inspect hd(keys)}"
end
end
@doc """
Updates a record given by var with the given keyword.
"""
def update(atom, fields, var, keyword, caller) do
if caller.in_match? do
raise ArgumentError, message: "cannot invoke update style macro inside match"
end
Enum.reduce keyword, var, fn({ key, value }, acc) ->
index = find_index(fields, key, 0)
if index do
quote do
:erlang.setelement(unquote(index), unquote(acc), unquote(value))
end
else
raise ArgumentError, message: "record #{inspect atom} does not have the key: #{inspect key}"
end
end
end
@doc """
Gets a record key from the given var.
"""
def get(atom, fields, var, key) do
index = find_index(fields, key, 0)
if index do
quote do
:erlang.element(unquote(index), unquote(var))
end
else
raise ArgumentError, message: "record #{inspect atom} does not have the key: #{inspect key}"
end
end
defp find_index([{ k, _ }|_], k, i), do: i + 2
defp find_index([{ _, _ }|t], k, i), do: find_index(t, k, i + 1)
defp find_index([], _k, _i), do: nil
end
+526
View File
@@ -0,0 +1,526 @@
defmodule Record.Deprecated do
@moduledoc false
def defrecord(name, fields, opts) do
block = Keyword.get(opts, :do, nil)
record_check!(fields)
quote do
unquoted_fields = unquote(fields)
defmodule unquote(name) do
import Record.DSL
@record_fields []
@record_types []
Record.Deprecated.deffunctions(unquoted_fields, __ENV__)
value = unquote(block)
Record.Deprecated.deftypes(@record_fields, @record_types, __ENV__)
value
end
end
end
defp record_check!([{ field, { :::, _, [_, _] }}|_]) when is_atom(field) do
raise ArgumentError, message: "typespecs are not supported inlined with defrecord, " <>
"please use record_type instead"
end
defp record_check!([_|t]), do: record_check!(t)
defp record_check!(_), do: :ok
def defrecordp(name, tag, fields) do
case recordp_split(fields, [], [], false) do
{ :ok, fields, types, def_type } ->
types = Macro.escape(types)
# bind_quoted isn't available when bootstrapping record
quoted = quote [unquote: false] do
Record.Deprecated.defmacros(name, fields, __ENV__, tag)
if def_type do
type = binary_to_atom(atom_to_binary(name) <> "_t")
@typep unquote(type)() :: { unquote(tag || name), unquote_splicing(types) }
end
end
quote do
def_type = unquote(def_type)
fields = unquote(fields)
types = unquote(types)
tag = unquote(tag)
name = unquote(name)
unquote(quoted)
end
:error ->
quote do
name = unquote(name)
Record.Deprecated.defmacros(name, unquote(fields), __ENV__, unquote(tag))
end
end
end
defp recordp_split([{ field, { :::, _, [default, type] }}|t], defaults, types, _) do
recordp_split(t, [{ field, default }|defaults], [type|types], true)
end
defp recordp_split([other|t], defaults, types, def_type) do
recordp_split(t, [other|defaults], [quote(do: term)|types], def_type)
end
defp recordp_split([], defaults, types, def_type) do
{ :ok, :lists.reverse(defaults), :lists.reverse(types), def_type }
end
defp recordp_split(_, _, _, _) do
:error
end
def deffunctions(values, env) do
values = for value <- values, do: convert_value(value)
escaped = Macro.escape(values)
contents = [
reflection(escaped),
initializer(escaped),
conversions(values),
record_optimizable(),
updater(values),
accessors(values, 1),
switch_recorder()
]
contents = [quote(do: @record_fields unquote(escaped))|contents]
# Special case for bootstrapping purposes
if env == Macro.Env do
Module.eval_quoted(env, contents, [], [])
else
Module.eval_quoted(env.module, contents, [], env.location)
end
end
def deftypes(values, types, env) do
types = types || []
values = for value <- values do
{ name, default } = convert_value(value)
{ name, default, find_spec(types, name) }
end
contents = [
core_specs(values),
accessor_specs(values, 1, [])
]
# We need to handle bootstraping
cond do
:code.ensure_loaded(Kernel.Typespec) != { :module, Kernel.Typespec } ->
nil
env == Macro.Env ->
Module.eval_quoted(env, contents, [], [])
true ->
Module.eval_quoted(env.module, contents, [], env.location)
end
end
def defmacros(name, values, env, tag \\ nil)
when is_atom(name) and is_list(values) and is_atom(tag) do
escaped = for value <- values do
{ key, value } = convert_value(value)
{ key, Macro.escape(value) }
end
tag = tag || name
contents = quote do
defmacrop unquote(name)() do
Record.Deprecated.access(unquote(tag), unquote(escaped), [], __CALLER__)
end
defmacrop unquote(name)(args) do
Record.Deprecated.access(unquote(tag), unquote(escaped), args, __CALLER__)
end
defmacrop unquote(name)(record, args) do
Record.Deprecated.dispatch(unquote(tag), unquote(escaped), record, args, __CALLER__)
end
end
Module.eval_quoted(env.module, contents, [], env.location)
end
## Callbacks
defmacro __before_compile__(_) do
quote do
def __record__(:optimizable), do: @record_optimizable
end
end
def __on_definition__(env, kind, name, args, _guards, _body) do
tuple = { name, length(args) }
module = env.module
functions = Module.get_attribute(module, :record_optimizable)
functions =
if kind in [:def] and Module.get_attribute(module, :record_optimized) do
[tuple|functions]
else
List.delete(functions, tuple)
end
Module.put_attribute(module, :record_optimizable, functions)
end
def access(atom, fields, arg, _caller) when is_atom(arg) do
if index = find_index(fields, arg, 0) do
index + 1
else
raise ArgumentError, message: "record #{inspect atom} does not have the key: #{inspect arg}"
end
end
def access(atom, fields, keyword, caller) do
unless is_keyword(keyword) do
raise ArgumentError, message: "expected contents inside brackets to be a keyword list or an atom, got: #{inspect keyword}"
end
in_match = caller.in_match?
has_underscore_value = Keyword.has_key?(keyword, :_)
underscore_value = Keyword.get(keyword, :_, { :_, [], nil })
keyword = Keyword.delete keyword, :_
iterator = fn({field, default}, each_keyword) ->
new_fields =
case Keyword.has_key?(each_keyword, field) do
true -> Keyword.get(each_keyword, field)
false ->
case in_match or has_underscore_value do
true -> underscore_value
false -> Macro.escape(default)
end
end
{ new_fields, Keyword.delete(each_keyword, field) }
end
{ match, remaining } = :lists.mapfoldl(iterator, keyword, fields)
case remaining do
[] ->
{ :{}, [], [atom|match] }
_ ->
keys = for { key, _ } <- remaining, do: key
raise ArgumentError, message: "record #{inspect atom} does not have the key: #{inspect hd(keys)}"
end
end
def dispatch(atom, fields, record, args, caller) do
cond do
is_atom(args) ->
get(atom, fields, record, args)
is_keyword(args) ->
update(atom, fields, record, args, caller)
true ->
raise ArgumentError, message: "expected arguments to be a compile time atom or keywords"
end
end
defp update(atom, fields, var, keyword, caller) do
unless is_keyword(keyword) do
raise ArgumentError, message: "expected arguments to be compile time keywords"
end
if caller.in_match? do
raise ArgumentError, message: "cannot invoke update style macro inside match context"
end
Enum.reduce keyword, var, fn({ key, value }, acc) ->
index = find_index(fields, key, 0)
if index do
quote do
:erlang.setelement(unquote(index + 2), unquote(acc), unquote(value))
end
else
raise ArgumentError, message: "record #{inspect atom} does not have the key: #{inspect key}"
end
end
end
defp get(atom, fields, var, key) do
index = find_index(fields, key, 0)
if index do
quote do
:erlang.element(unquote(index + 2), unquote(var))
end
else
raise ArgumentError, message: "record #{inspect atom} does not have the key: #{inspect key}"
end
end
## Function generation
defp reflection(values) do
quoted = for { k, _ } <- values do
index = find_index(values, k, 0)
quote do
def __record__(:index, unquote(k)), do: unquote(index + 1)
end
end
quote do
unquote(quoted)
@doc false
def __record__(:index, _), do: nil
@doc false
def __record__(:index, arg, _), do: __record__(:index, arg)
@doc false
def __record__(kind, _), do: __record__(kind)
@doc false
def __record__(:name), do: __MODULE__
def __record__(:fields), do: unquote(values)
end
end
defp initializer(values) do
defaults = for { _, value } <- values, do: value
# For each value, define a piece of code that will receive
# an ordered dict of options (opts) and it will try to fetch
# the given key from the ordered dict, falling back to the
# default value if one does not exist.
atom_selective = for { k, v } <- values, do: initialize_lookup(k, v)
string_selective = for { k, v } <- values, do: initialize_lookup(atom_to_binary(k), v)
quote do
@doc false
def new(), do: new([])
@doc false
def new([]), do: { __MODULE__, unquote_splicing(defaults) }
def new([{key, _}|_] = opts) when is_atom(key), do: { __MODULE__, unquote_splicing(atom_selective) }
def new([{key, _}|_] = opts) when is_binary(key), do: { __MODULE__, unquote_splicing(string_selective) }
end
end
defp initialize_lookup(k, v) do
quote do
case :lists.keyfind(unquote(k), 1, opts) do
false -> unquote(v)
{_, v} -> v
end
end
end
defp conversions(values) do
sorted = for { k, _ } <- values do
index = find_index(values, k, 0)
{ k, quote(do: :erlang.element(unquote(index + 2), record)) }
end
quote do
@doc false
def to_keywords(record) do
unquote(sorted)
end
end
end
defp accessors([{ :__exception__, _ }|t], 1) do
accessors(t, 2)
end
defp accessors([{ key, _default }|t], i) do
update = binary_to_atom "update_" <> atom_to_binary(key)
contents = quote do
@doc false
def unquote(key)(record) do
:erlang.element(unquote(i + 1), record)
end
@doc false
def unquote(key)(value, record) do
:erlang.setelement(unquote(i + 1), record, value)
end
@doc false
def unquote(update)(function, record) do
:erlang.setelement(unquote(i + 1), record,
function.(:erlang.element(unquote(i + 1), record)))
end
end
[contents|accessors(t, i + 1)]
end
defp accessors([], _i) do
[]
end
# Define an updater method that receives a
# keyword list and updates the record.
defp updater(values) do
atom_fields =
for {key, _default} <- values, do: updater_lookup(key, key, values)
string_fields =
for {key, _default} <- values, do: updater_lookup(atom_to_binary(key), key, values)
atom_contents = quote do: { __MODULE__, unquote_splicing(atom_fields) }
string_contents = quote do: { __MODULE__, unquote_splicing(string_fields) }
quote do
@doc false
def update([], record) do
record
end
def update([{key, _}|_] = keywords, record) when is_atom(key) do
unquote(atom_contents)
end
def update([{key, _}|_] = keywords, record) when is_binary(key) do
unquote(string_contents)
end
end
end
defp updater_lookup(k, key, values) do
index = find_index(values, key, 0)
quote do
case :lists.keyfind(unquote(k), 1, keywords) do
false -> :erlang.element(unquote(index + 2), record)
{_, value} -> value
end
end
end
defp record_optimizable do
quote do
@record_optimized true
@record_optimizable []
@before_compile { unquote(__MODULE__), :__before_compile__ }
@on_definition { unquote(__MODULE__), :__on_definition__ }
end
end
defp switch_recorder do
quote do: @record_optimized false
end
## Types/specs generation
defp core_specs(values) do
types = for { _, _, spec } <- values, do: spec
options = for { k, _, v } <- values, do: { k, v }
quote do
unless Kernel.Typespec.defines_type?(__MODULE__, :t, 0) do
@type t :: { __MODULE__, unquote_splicing(types) }
end
unless Kernel.Typespec.defines_type?(__MODULE__, :options, 0) do
@type options :: unquote(options) | [{String.t, term}]
end
@spec new :: t
@spec new(options) :: t
@spec to_keywords(t) :: options
@spec update(options, t) :: t
@spec __record__(:name) :: atom
@spec __record__(:fields) :: [{atom, any}]
@spec __record__(:index, atom) :: non_neg_integer | nil
end
end
defp accessor_specs([{ :__exception__, _, _ }|t], 1, acc) do
accessor_specs(t, 2, acc)
end
defp accessor_specs([{ key, _default, spec }|t], i, acc) do
update = binary_to_atom "update_" <> atom_to_binary(key)
contents = quote do
@spec unquote(key)(t) :: unquote(spec)
@spec unquote(key)(unquote(spec), t) :: t
@spec unquote(update)((unquote(spec) -> unquote(spec)), t) :: t
end
accessor_specs(t, i + 1, [contents | acc])
end
defp accessor_specs([], _i, acc), do: acc
## Helpers
defp is_keyword(list) when is_list(list), do: :lists.all(&is_keyword_tuple/1, list)
defp is_keyword(_), do: false
defp is_keyword_tuple({ x, _ }) when is_atom(x), do: true
defp is_keyword_tuple(_), do: false
defp convert_value(atom) when is_atom(atom), do: { atom, nil }
defp convert_value({ atom, other }) when is_atom(atom), do:
{ atom, check_value(atom, other) }
defp convert_value({ field, _ }), do:
raise(ArgumentError, message: "record field name has to be an atom, got #{inspect field}")
defp check_value(atom, other) when is_list(other) do
for(i <- other, do: check_value(atom, i))
other
end
defp check_value(atom, other) when is_tuple(other) do
for(i <- tuple_to_list(other), do: check_value(atom, i))
other
end
defp check_value(atom, other) when is_function(other) do
unless :erlang.fun_info(other, :env) == { :env, [] } and
:erlang.fun_info(other, :type) == { :type, :external } do
raise ArgumentError, message: "record field default value #{inspect atom} can only contain " <>
"functions that point to an existing &Mod.fun/arity"
end
other
end
defp check_value(atom, other) when is_reference(other) or is_pid(other) or is_port(other) do
raise(ArgumentError, message: "record field default value #{inspect atom} cannot contain a reference, pid or port")
end
defp check_value(_atom, other), do: other
defp find_index([{ k, _ }|_], k, i), do: i
defp find_index([{ _, _ }|t], k, i), do: find_index(t, k, i + 1)
defp find_index([], _k, _i), do: nil
defp find_spec(types, name) do
matches = for { k, v } <- types, name == k, do: v
case matches do
[h|_] -> h
_ -> quote do: term
end
end
end
defmodule Record.DSL do
@moduledoc false
defmacro record_type(opts) when is_list(opts) do
escaped = for { k, v } <- opts, do: { k, Macro.escape(v) }
quote do
@record_types Keyword.merge(@record_types || [], unquote(escaped))
end
end
end
+9 -9
View File
@@ -3,7 +3,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 retrieve(name, from: file) when is_binary(file) do
def extract(name, from: file) when is_binary(file) do
file = String.to_char_list!(file)
realfile =
@@ -12,26 +12,26 @@ defmodule Record.Extractor do
realfile -> realfile
end
retrieve_record(name, realfile)
extract_record(name, realfile)
end
# Retrieve a record definition from an Erlang file using
# the same lookup as the *include_lib* attribute from Erlang modules.
def retrieve(name, from_lib: file) when is_binary(file) do
def extract(name, from_lib: file) when is_binary(file) do
[app|path] = :filename.split(String.to_char_list!(file))
case :code.lib_dir(list_to_atom(app)) do
{ :error, _ } ->
raise ArgumentError, message: "lib file #{file} could not be found"
libpath ->
retrieve_record name, :filename.join([libpath|path])
extract_record name, :filename.join([libpath|path])
end
end
# Retrieve the record with the given name from the given file
defp retrieve_record(name, file) do
defp extract_record(name, file) do
form = read_file(file)
records = retrieve_records(form)
records = extract_records(form)
if record = List.keyfind(records, name, 0) do
parse_record(record, form)
else
@@ -39,9 +39,9 @@ defmodule Record.Extractor do
end
end
# Parse the given file and retrieve all existent records.
defp retrieve_records(form) do
lc { :attribute, _, :record, record } inlist form, do: record
# Parse the given file and extract all existent records.
defp extract_records(form) do
for { :attribute, _, :record, record } <- form, do: record
end
# Read a file and return its abstract syntax form that also
+146 -98
View File
@@ -1,17 +1,17 @@
defmodule Regex do
@moduledoc %S"""
@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 in Elixir can be created using `Regex.compile!/2`
or using the special form with [`%r`](Kernel.html#sigil_r/2):
or using the special form with [`~r`](Kernel.html#sigil_r/2):
# A simple regular expressions that matches foo anywhere in the string
%r/foo/
~r/foo/
# A regular expression with case insensitive options
%r/foo/i
~r/foo/i
The `re` module provides several options, the ones available in Elixir, followed by
their shortcut in parenthesis, are:
@@ -27,7 +27,6 @@ defmodule Regex do
* `firstline` (f) - forces the unanchored pattern to match before or at the first
newline, though the matched text may continue over the newline
* `ungreedy` (r) - inverts the "greediness" of the regexp
* `groups` (g) - compiles with info about groups available
The options not available are:
@@ -37,12 +36,31 @@ defmodule Regex do
* `newline` - not available, use `(*CR)` or `(*LF)` or `(*CRLF)` or `(*ANYCRLF)`
or `(*ANY)` at the beginning of the regexp according to the re documentation
Most of the functions in this module accept either a binary or a char list
as subject. The result is based on the argument (a binary will return
a binary, a char list will return a char list).
## Captures
Many functions in this module allows what to capture in a regex
match via the `:capture` option. The supported values are:
* `:all` - all captured subpatterns including the complete matching string.
This is the default;
* `:first` - only the first captured subpattern, which is always the complete
matching part of the string. All explicitly captured subpatterns are
discarded;
* `:all_but_first`- all but the first matching subpattern, i.e. all explicitly
captured subpatterns, but not the complete matching part of
the string;
* `:none` - do not return matching subpatterns at all;
* `:groups` - captures only named captures in the Regex;
* `list(binary)` - a list of named captures to capture;
"""
defrecordp :regex, Regex, [:re_pattern, :source, :options, :groups]
defrecordp :regex, Regex, [:re_pattern, :source, :options]
@type t :: { Regex, term, binary, binary, [atom] | nil }
defexception CompileError, message: "regex could not be compiled"
@@ -51,7 +69,7 @@ defmodule Regex do
Compiles the regular expression.
The given options can either be a binary with the characters
representing the same regex options given to the `%r` sigil,
representing the same regex options given to the `~r` sigil,
or a list of options, as expected by the [Erlang `re` docs](http://www.erlang.org/doc/man/re.html).
It returns `{ :ok, regex }` in case of success,
@@ -60,13 +78,14 @@ defmodule Regex do
## Examples
iex> Regex.compile("foo")
{:ok, %r"foo"}
{:ok, ~r"foo"}
iex> Regex.compile("*foo")
{:error, {'nothing to repeat', 0}}
"""
@spec compile(binary, binary | [term]) :: { :ok, t } | { :error, any }
def compile(source, options // "")
def compile(source, options \\ "")
def compile(source, options) when is_binary(options) do
case translate_options(options) do
@@ -85,12 +104,9 @@ defmodule Regex do
end
defp compile(source, opts, doc_opts) when is_binary(source) do
re_opts = opts -- [:groups]
groups = if opts != re_opts, do: parse_groups(source)
case :re.compile(source, re_opts) do
case :re.compile(source, opts) do
{ :ok, re_pattern } ->
{ :ok, regex(re_pattern: re_pattern, source: source, options: doc_opts, groups: groups) }
{ :ok, regex(re_pattern: re_pattern, source: source, options: doc_opts) }
error ->
error
end
@@ -100,7 +116,7 @@ defmodule Regex do
Compiles the regular expression according to the given options.
Fails with `Regex.CompileError` if the regex cannot be compiled.
"""
def compile!(source, options // "") 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}"
@@ -112,41 +128,62 @@ defmodule Regex do
## Examples
iex> Regex.match?(%r/foo/, "foo")
iex> Regex.match?(~r/foo/, "foo")
true
iex> Regex.match?(%r/foo/, "bar")
iex> Regex.match?(~r/foo/, "bar")
false
"""
def match?(regex(re_pattern: compiled), string) do
def match?(regex(re_pattern: compiled), string) when is_binary(string) do
:re.run(string, compiled, [{ :capture, :none }]) == :match
end
@doc """
Returns true if the given argument is a regex.
## Examples
iex> Regex.regex?(~r/foo/)
true
iex> Regex.regex?(0)
false
"""
def regex?(regex()), do: true
def regex?(_), do: false
@doc """
Runs the regular expression against the given string until the first match.
It returns a list with all captures or `nil` if no match occurred.
When the option `:capture` is set to `:groups`, it will capture all
the groups in the regex.
## Options
* `:return` - Set to `:index` to return indexes. Defaults to `:binary`;
* `:capture` - What to capture in the result. Check the moduledoc for Regex
to see the possible capture values;
## Examples
iex> Regex.run(%r/c(d)/, "abcd")
iex> Regex.run(~r/c(d)/, "abcd")
["cd", "d"]
iex> Regex.run(%r/e/, "abcd")
iex> Regex.run(~r/e/, "abcd")
nil
iex> Regex.run(%r/c(d)/, "abcd", return: :index)
iex> Regex.run(~r/c(d)/, "abcd", return: :index)
[{2,2},{3,1}]
"""
def run(regex, string, options // [])
def run(regex, string, options \\ [])
def run(regex(re_pattern: compiled, groups: groups), string, options) do
return = Keyword.get(options, :return, return_for(string))
def run(regex(re_pattern: compiled) = regex, string, options) when is_binary(string) do
return = Keyword.get(options, :return, :binary)
captures =
case Keyword.get(options, :capture, :all) do
:groups -> groups || raise ArgumentError, message: "regex was not compiled with g"
:groups -> groups(regex)
others -> others
end
@@ -159,22 +196,25 @@ defmodule Regex do
@doc """
Returns the given captures as a keyword list or `nil` if no captures
are found. Requires the regex to be compiled with the groups option.
are found. The option `:return` can be set to `:index` to get indexes
back.
## Examples
iex> Regex.named_captures(%r/c(?<foo>d)/g, "abcd")
iex> Regex.named_captures(~r/c(?<foo>d)/, "abcd")
[foo: "d"]
iex> Regex.named_captures(%r/a(?<foo>b)c(?<bar>d)/g, "abcd")
[foo: "b", bar: "d"]
iex> Regex.named_captures(%r/a(?<foo>b)c(?<bar>d)/g, "efgh")
iex> Regex.named_captures(~r/a(?<foo>b)c(?<bar>d)/, "abcd")
[bar: "d", foo: "b"]
iex> Regex.named_captures(~r/a(?<foo>b)c(?<bar>d)/, "efgh")
nil
"""
def named_captures(regex(groups: groups) = regex, string, options // []) do
options = Keyword.put_new(options, :capture, :groups)
def named_captures(regex, string, options \\ []) when is_binary(string) do
options = [capture: :groups] ++ options
results = run(regex, string, options)
if results, do: Enum.zip(groups, results)
if results, do: Enum.zip(groups(regex), results)
end
@doc """
@@ -189,7 +229,7 @@ defmodule Regex do
## Examples
iex> Regex.source(%r(foo))
iex> Regex.source(~r(foo))
"foo"
"""
@@ -202,7 +242,7 @@ defmodule Regex do
## Examples
iex> Regex.opts(%r(foo)m)
iex> Regex.opts(~r(foo)m)
"m"
"""
@@ -215,12 +255,13 @@ defmodule Regex do
## Examples
iex> Regex.groups(%r/(?<foo>bar)/g)
iex> Regex.groups(~r/(?<foo>bar)/)
[:foo]
"""
def groups(regex(groups: groups)) do
groups
def groups(regex(re_pattern: re_pattern)) do
{ :namelist, groups } = :re.inspect(re_pattern, :namelist)
for group <- groups, do: binary_to_atom(group)
end
@doc """
@@ -229,30 +270,32 @@ defmodule Regex do
where each entry in the primary list represents a match and each
entry in the secondary list represents the captured contents.
The captured contents defaults to `:all`, which includes the whole
regex match and each capture.
## Options
When the option `:capture` is set to `:groups`, it will capture all
the groups in the regex.
* `:return` - Set to `:index` to return indexes. Defaults to `:binary`;
* `:capture` - What to capture in the result. Check the moduledoc for Regex
to see the possible capture values;
## Examples
iex> Regex.scan(%r/c(d|e)/, "abcd abce")
iex> Regex.scan(~r/c(d|e)/, "abcd abce")
[["cd", "d"], ["ce", "e"]]
iex> Regex.scan(%r/c(?:d|e)/, "abcd abce")
iex> Regex.scan(~r/c(?:d|e)/, "abcd abce")
[["cd"], ["ce"]]
iex> Regex.scan(%r/e/, "abcd")
iex> Regex.scan(~r/e/, "abcd")
[]
"""
def scan(regex, string, options // [])
def scan(regex, string, options \\ [])
def scan(regex(re_pattern: compiled, groups: groups), string, options) do
return = Keyword.get(options, :return, return_for(string))
def scan(regex(re_pattern: compiled) = regex, string, options) when is_binary(string) do
return = Keyword.get(options, :return, :binary)
captures =
case Keyword.get(options, :capture, :all) do
:groups -> groups || raise ArgumentError, message: "regex was not compiled with g"
:groups -> groups(regex)
others -> others
end
@@ -266,24 +309,39 @@ defmodule Regex do
@doc """
Splits the given target into the number of parts specified.
If no number of parts is given, it defaults to `:infinity`.
## 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;
* `:trim` - when true, remove blank strings from the result;
## Examples
iex> Regex.split(%r/-/, "a-b-c")
iex> Regex.split(~r/-/, "a-b-c")
["a","b","c"]
iex> Regex.split(%r/-/, "a-b-c", [parts: 2])
iex> Regex.split(~r/-/, "a-b-c", [parts: 2])
["a","b-c"]
iex> Regex.split(%r/-/, "abc")
iex> Regex.split(~r/-/, "abc")
["abc"]
iex> Regex.split(%r//, "abc")
iex> Regex.split(~r//, "abc")
["a", "b", "c", ""]
iex> Regex.split(%r//, "abc", trim: true)
iex> Regex.split(~r//, "abc", trim: true)
["a", "b", "c"]
"""
def split(regex, string, options // [])
def split(regex, string, options \\ [])
def split(regex(re_pattern: compiled), string, options) do
def split(regex(re_pattern: compiled), string, options) when is_binary(string) do
parts =
cond do
Keyword.get(options, :global) == false -> 2
@@ -291,18 +349,17 @@ defmodule Regex do
true -> :infinity
end
return = Keyword.get(options, :return, return_for(string))
opts = [return: return, parts: parts]
opts = [return: :binary, parts: parts]
splits = :re.split(string, compiled, opts)
if Keyword.get(options, :trim, false) do
lc split inlist splits, split != "", do: split
for split <- splits, split != "", do: split
else
splits
end
end
@doc %S"""
@doc ~S"""
Receives a regex, a binary and a replacement, returns a new
binary where the all matches are replaced by replacement.
@@ -313,42 +370,48 @@ defmodule Regex do
## Examples
iex> Regex.replace(%r/d/, "abc", "d")
iex> Regex.replace(~r/d/, "abc", "d")
"abc"
iex> Regex.replace(%r/b/, "abc", "d")
iex> Regex.replace(~r/b/, "abc", "d")
"adc"
iex> Regex.replace(%r/b/, "abc", "[&]")
iex> Regex.replace(~r/b/, "abc", "[&]")
"a[b]c"
iex> Regex.replace(%r/b/, "abc", "[\\&]")
iex> Regex.replace(~r/b/, "abc", "[\\&]")
"a[&]c"
iex> Regex.replace(%r/(b)/, "abc", "[\\1]")
iex> Regex.replace(~r/(b)/, "abc", "[\\1]")
"a[b]c"
"""
def replace(regex(re_pattern: compiled), string, replacement, options // []) do
opts = if Keyword.get(options, :global) != false, do: [:global], else: []
return = Keyword.get(options, :return, return_for(string))
opts = [{ :return, return }|opts]
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)
end
{ :ok, pattern } = :re.compile(%S"[.^$*+?()[{\\\|\s#]", [:unicode])
{ :ok, pattern } = :re.compile(~S"[.^$*+?()[{\\\|\s#]", [:unicode])
@escape_pattern pattern
@doc %S"""
@doc ~S"""
Escapes a string to be literally matched in a regex.
## Examples
iex> Regex.escape(".")
"\\."
iex> Regex.escape("\\what if")
"\\\\what\\ if"
"""
@spec escape(String.t | char_list) :: String.t | char_list
def escape(string) do
:re.replace(string, @escape_pattern, "\\\\&", [:global, { :return, return_for(string) }])
@spec escape(String.t) :: String.t
def escape(string) when is_binary(string) do
:re.replace(string, @escape_pattern, "\\\\&", [:global, { :return, :binary }])
end
# Helpers
@@ -365,32 +428,17 @@ defmodule Regex do
# Private Helpers
defp return_for(element) when is_binary(element), do: :binary
defp return_for(element) when is_list(element), do: :list
defp translate_options(<<?u, t :: binary>>) do
IO.write "The /u flag for regular expressions is no longer needed\n#{Exception.format_stacktrace}"
defp translate_options(<<?g, t :: binary>>) do
IO.write :stderr, "The /g flag for regular expressions is no longer needed\n#{Exception.format_stacktrace}"
translate_options(t)
end
defp translate_options(<<?i, t :: binary>>), do: [:caseless|translate_options(t)]
defp translate_options(<<?x, t :: binary>>), do: [:extended|translate_options(t)]
defp translate_options(<<?f, t :: binary>>), do: [:firstline|translate_options(t)]
defp translate_options(<<?r, t :: binary>>), do: [:ungreedy|translate_options(t)]
defp translate_options(<<?s, t :: binary>>), do: [:dotall, {:newline, :anycrlf}|translate_options(t)]
defp translate_options(<<?m, t :: binary>>), do: [:multiline|translate_options(t)]
defp translate_options(<<?g, t :: binary>>), do: [:groups|translate_options(t)]
defp translate_options(<<>>), do: []
defp translate_options(rest), do: { :error, rest }
{ :ok, pattern } = :re.compile(%S"\(\?<(?<G>[^>]*)>")
@groups_pattern pattern
defp parse_groups(source) do
options = [:global, {:capture, ['G'], :binary}]
case :re.run(source, @groups_pattern, options) do
:nomatch -> []
{ :match, results } ->
lc [group] inlist results, do: binary_to_atom(group)
end
end
end
+140 -49
View File
@@ -1,5 +1,5 @@
defmodule Set do
@moduledoc %S"""
@moduledoc ~S"""
This module specifies the Set API expected to be
implemented by different representations.
@@ -12,32 +12,27 @@ defmodule Set do
HashSet.new #=> creates an empty HashSet
For simplicity's sake, in the examples below every time
`new` is used, it implies one of the module-specific
calls like above.
In the examples below, `set_impl` means a specific
`Set` implementation, for example `HashSet`.
## Protocols
Sets are required to implement the `Enumerable` protocol,
allowing one to write:
Enum.each(set, fn k ->
IO.inspect k
end)
Sets are required to implement both `Enumerable` and `Collectable`
protocols.
## Match
Sets are required to implement all operations
using the match (`===`) operator. Any deviation from
this behaviour should be avoided and explicitly documented.
Sets are required to implement all operations using the match (`===`)
operator.
"""
use Behaviour
@type value :: any
@type values :: [ value ]
@type t :: tuple
@type t :: map | tuple
defcallback new :: t
defcallback delete(t, value) :: t
defcallback difference(t, t) :: t
defcallback disjoint?(t, t) :: boolean
@@ -53,10 +48,13 @@ defmodule Set do
defmacrop target(set) do
quote do
if is_tuple(unquote(set)) do
elem(unquote(set), 0)
else
unsupported_set(unquote(set))
case unquote(set) do
%{__struct__: x} when is_atom(x) ->
x
x when is_tuple(x) ->
elem(x, 0)
x ->
unsupported_set(x)
end
end
end
@@ -66,13 +64,14 @@ defmodule Set do
## Examples
iex> s = HashSet.new([1, 2, 3])
...> Set.delete(s, 4) |> HashSet.to_list
iex> s = Enum.into([1, 2, 3], set_impl.new)
iex> Set.delete(s, 4) |> Enum.sort
[1, 2, 3]
iex> s = HashSet.new([1, 2, 3])
...> Set.delete(s, 2) |> HashSet.to_list
iex> s = Enum.into([1, 2, 3], set_impl.new)
iex> Set.delete(s, 2) |> Enum.sort
[1, 3]
"""
@spec delete(t, value) :: t
def delete(set, value) do
@@ -82,73 +81,130 @@ defmodule Set do
@doc """
Returns a set that is `set1` without the members of `set2`.
Notice this function is polymorphic as it calculates the difference
for of any type. Each set implementation also provides a `difference`
function, but they can only work with sets of the same type.
## Examples
iex> Set.difference(HashSet.new([1,2]), HashSet.new([2,3,4])) |> HashSet.to_list
iex> Set.difference(Enum.into([1,2], set_impl.new), Enum.into([2,3,4], set_impl.new)) |> Enum.sort
[1]
"""
@spec difference(t, t) :: t
def difference(set1, set2) do
target(set1).difference(set1, set2)
target1 = target(set1)
target2 = target(set2)
if target1 == target2 do
target1.difference(set1, set2)
else
target2.reduce(set2, { :cont, set1 }, fn v, acc ->
{ :cont, target1.delete(acc, v) }
end) |> elem(1)
end
end
@doc """
Checks if `set1` and `set2` have no members in common.
Notice this function is polymorphic as it checks for disjoint sets of
any type. Each set implementation also provides a `disjoint?` function,
but they can only work with sets of the same type.
## Examples
iex> Set.disjoint?(HashSet.new([1, 2]), HashSet.new([3, 4]))
iex> Set.disjoint?(Enum.into([1, 2], set_impl.new), Enum.into([3, 4], set_impl.new))
true
iex> Set.disjoint?(HashSet.new([1, 2]), HashSet.new([2, 3]))
iex> Set.disjoint?(Enum.into([1, 2], set_impl.new), Enum.into([2, 3], set_impl.new))
false
"""
@spec disjoint?(t, t) :: boolean
def disjoint?(set1, set2) do
target(set1).disjoint?(set1, set2)
target1 = target(set1)
target2 = target(set2)
if target1 == target2 do
target1.disjoint?(set1, set2)
else
target2.reduce(set2, { :cont, true }, fn member, acc ->
case target1.member?(set1, member) do
false -> { :cont, acc }
_ -> { :halt, false }
end
end) |> elem(1)
end
end
@doc """
Returns an empty set of the same type as `set`.
"""
@doc false
@spec empty(t) :: t
def empty(set) do
target(set).empty(set)
end
@doc """
Checks if `set1` and `set2` are equal.
Check if two sets are equal using `===`.
Notice this function is polymorphic as it compares sets of
any type. Each set implementation also provides an `equal?`
function, but they can only work with sets of the same type.
## Examples
iex> Set.equal?(HashSet.new([1, 2]), HashSet.new([2, 1, 1]))
iex> Set.equal?(Enum.into([1, 2], set_impl.new), Enum.into([2, 1, 1], set_impl.new))
true
iex> Set.equal?(HashSet.new([1, 2]), HashSet.new([3, 4]))
iex> Set.equal?(Enum.into([1, 2], set_impl.new), Enum.into([3, 4], set_impl.new))
false
"""
@spec equal?(t, t) :: boolean
def equal?(set1, set2) do
target(set1).equal?(set1, set2)
target1 = target(set1)
target2 = target(set2)
cond do
target1 == target2 ->
target1.equal?(set1, set2)
target1.size(set1) == target2.size(set2) ->
do_subset?(target1, target2, set1, set2)
true ->
false
end
end
@doc """
Returns a set containing only members in common between `set1` and `set2`.
Notice this function is polymorphic as it calculates the intersection of
any type. Each set implementation also provides a `intersection` function,
but they can only work with sets of the same type.
## Examples
iex> Set.intersection(HashSet.new([1,2]), HashSet.new([2,3,4])) |> HashSet.to_list
iex> Set.intersection(Enum.into([1,2], set_impl.new), Enum.into([2,3,4], set_impl.new)) |> Enum.sort
[2]
iex> Set.intersection(HashSet.new([1,2]), HashSet.new([3,4])) |> HashSet.to_list
iex> Set.intersection(Enum.into([1,2], set_impl.new), Enum.into([3,4], set_impl.new)) |> Enum.sort
[]
"""
@spec intersection(t, t) :: t
def intersection(set1, set2) do
target(set1).intersection(set1, set2)
target1 = target(set1)
target2 = target(set2)
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) }
end) |> elem(1)
end
end
@doc """
@@ -156,10 +212,10 @@ defmodule Set do
## Examples
iex> Set.member?(HashSet.new([1, 2, 3]), 2)
iex> Set.member?(Enum.into([1, 2, 3], set_impl.new), 2)
true
iex> Set.member?(HashSet.new([1, 2, 3]), 4)
iex> Set.member?(Enum.into([1, 2, 3], set_impl.new), 4)
false
"""
@@ -173,10 +229,10 @@ defmodule Set do
## Examples
iex> Set.put(HashSet.new([1, 2, 3]), 3) |> Set.to_list
iex> Set.put(Enum.into([1, 2, 3], set_impl.new), 3) |> Enum.sort
[1, 2, 3]
iex> Set.put(HashSet.new([1, 2, 3]), 4) |> Set.to_list
iex> Set.put(Enum.into([1, 2, 3], set_impl.new), 4) |> Enum.sort
[1, 2, 3, 4]
"""
@@ -190,7 +246,7 @@ defmodule Set do
## Examples
iex> Set.size(HashSet.new([1, 2, 3]))
iex> Set.size(Enum.into([1, 2, 3], set_impl.new))
3
"""
@@ -202,16 +258,29 @@ defmodule Set do
@doc """
Checks if `set1`'s members are all contained in `set2`.
Notice this function is polymorphic as it checks the subset for
any type. Each set implementation also provides a `subset?` function,
but they can only work with sets of the same type.
## Examples
iex> Set.subset?(HashSet.new([1, 2]), HashSet.new([1, 2, 3]))
iex> Set.subset?(Enum.into([1, 2], set_impl.new), Enum.into([1, 2, 3], set_impl.new))
true
iex> Set.subset?(HashSet.new([1, 2, 3]), HashSet.new([1, 2]))
iex> Set.subset?(Enum.into([1, 2, 3], set_impl.new), Enum.into([1, 2], set_impl.new))
false
"""
@spec subset?(t, t) :: boolean
def subset?(set1, set2) do
target(set1).subset?(set1, set2)
target1 = target(set1)
target2 = target(set2)
if target1 == target2 do
target1.subset?(set1, set2)
else
do_subset?(target1, target2, set1, set2)
end
end
@doc """
@@ -219,7 +288,7 @@ defmodule Set do
## Examples
iex> HashSet.to_list(HashSet.new([1, 2, 3]))
iex> set_impl.to_list(Enum.into([1, 2, 3], set_impl.new)) |> Enum.sort
[1,2,3]
"""
@@ -231,15 +300,37 @@ defmodule Set do
@doc """
Returns a set containing all members of `set1` and `set2`.
Notice this function is polymorphic as it calculates the union of
any type. Each set implementation also provides a `union` function,
but they can only work with sets of the same type.
## Examples
iex> Set.union(HashSet.new([1,2]), HashSet.new([2,3,4])) |> HashSet.to_list
iex> Set.union(Enum.into([1,2], set_impl.new), Enum.into([2,3,4], set_impl.new)) |> Enum.sort
[1,2,3,4]
"""
@spec union(t, t) :: t
def union(set1, set2) do
target(set1).union(set1, set2)
target1 = target(set1)
target2 = target(set2)
if target1 == target2 do
target1.union(set1, set2)
else
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 ->
case target2.member?(set2, member) do
true -> { :cont, acc }
_ -> { :halt, false }
end
end) |> elem(1)
end
defp unsupported_set(set) do
+66 -57
View File
@@ -81,9 +81,15 @@ defmodule Stream do
This module also provides many convenience functions for creating streams,
like `Stream.cycle/1`, `Stream.unfold/2`, `Stream.resource/3` and more.
Note the functions in this module are guaranteed to return enumerables.
Since enumerables can have different shapes (structs, anonymous functions,
and so on), the functions in this module may return any of those shapes
and that it may change at any time. For example, a function that today
returns an anonymous function may return a struct in future releases.
"""
defrecord Lazy, enum: nil, funs: [], accs: [], after: [], last: nil
defrecord Lazy, enum: nil, funs: [], accs: [], done: nil
defimpl Enumerable, for: Lazy do
@compile :inline_list_funs
@@ -103,46 +109,28 @@ defmodule Stream do
{ :error, __MODULE__ }
end
defp do_reduce(Lazy[enum: enum, funs: funs, accs: accs, last: last, after: after_funs], acc, fun) do
defp do_reduce(Lazy[enum: enum, funs: funs, accs: accs, done: done], acc, fun) do
composed = :lists.foldl(fn fun, acc -> fun.(acc) end, fun, funs)
do_each(&Enumerable.reduce(enum, &1, composed), after_funs,
last && { last, fun }, :lists.reverse(accs), acc)
do_each(&Enumerable.reduce(enum, &1, composed),
done && { done, fun }, :lists.reverse(accs), acc)
end
defp do_each(reduce, [], last, accs, { command, acc }) do
handle_reduce reduce.({ command, [acc|accs] }), [], last
end
defp do_each(reduce, after_funs, last, accs, { command, acc }) do
try do
handle_reduce reduce.({ command, [acc|accs] }), after_funs, last
catch
kind, reason ->
lc fun inlist after_funs, do: fun.()
:erlang.raise(kind, reason, :erlang.get_stacktrace)
end
end
defp handle_reduce(res, after_funs, last) do
case res do
defp do_each(reduce, done, accs, { command, acc }) do
case reduce.({ command, [acc|accs] }) do
{ :suspended, [acc|accs], continuation } ->
{ :suspended, acc, &do_each(continuation, after_funs, last, accs, &1) }
{ :suspended, acc, &do_each(continuation, done, accs, &1) }
{ :halted, [acc|_] } ->
lc fun inlist after_funs, do: fun.()
{ :halted, acc }
{ :done, [acc|_] = accs } ->
case last do
case done do
nil ->
lc fun inlist after_funs, do: fun.()
{ :done, acc }
{ last, fun } ->
res = case last.(fun).(accs) do
{ done, fun } ->
case done.(fun).(accs) do
{ :cont, [acc|_] } -> { :done, acc }
{ :halt, [acc|_] } -> { :halted, acc }
{ :suspend, [acc|_] } -> { :suspended, acc, &({ :done, &1 |> elem(1) }) }
{ :suspend, [acc|_] } -> { :suspended, acc, &({ :done, elem(&1, 1) }) }
end
lc fun inlist after_funs, do: fun.()
res
end
end
end
@@ -173,26 +161,6 @@ defmodule Stream do
## Transformers
@doc """
Executes the given function when the stream is done, halted
or an error happened during streaming. Useful for resource
clean up.
Callbacks registered later will be executed earlier.
## Examples
iex> stream = Stream.after [1,2,3], fn -> Process.put(:done, true) end
iex> Enum.to_list(stream)
[1,2,3]
iex> Process.get(:done)
true
"""
@spec unquote(:after)(Enumerable.t, (() -> term)) :: Enumerable.t
def unquote(:after)(Lazy[after: funs] = lazy, fun), do: lazy.after([fun|funs])
def unquote(:after)(enum, fun), do: Lazy[enum: enum, after: [fun]]
@doc """
Shortcut to `chunk(enum, n, n)`.
"""
@@ -216,17 +184,20 @@ defmodule Stream do
iex> Stream.chunk([1, 2, 3, 4, 5, 6], 2) |> Enum.to_list
[[1, 2], [3, 4], [5, 6]]
iex> Stream.chunk([1, 2, 3, 4, 5, 6], 3, 2) |> Enum.to_list
[[1, 2, 3], [3, 4, 5]]
iex> Stream.chunk([1, 2, 3, 4, 5, 6], 3, 2, [7]) |> Enum.to_list
[[1, 2, 3], [3, 4, 5], [5, 6, 7]]
iex> Stream.chunk([1, 2, 3, 4, 5, 6], 3, 3, []) |> Enum.to_list
[[1, 2, 3], [4, 5, 6]]
"""
@spec chunk(Enumerable.t, non_neg_integer, non_neg_integer) :: Enumerable.t
@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
def chunk(enum, n, step, pad \\ nil) when n > 0 and step > 0 do
limit = :erlang.max(n, step)
lazy enum, { [], 0 },
fn(f1) -> R.chunk(n, step, limit, f1) end,
@@ -410,6 +381,44 @@ defmodule Stream do
lazy enum, fn(f1) -> R.filter_map(filter, mapper, f1) end
end
@doc """
Injects the stream values into the given collectable as a side-effect.
This function is often used with `run/1` since any evaluation
is delayed until the stream is executed. See `run/1` for an example.
"""
@spec into(Enumerable.t, Collectable.t) :: Enumerable.t
def into(enum, collectable, transform \\ fn x -> x end) do
&do_into(enum, collectable, transform, &1, &2)
end
defp do_into(enum, collectable, transform, acc, fun) do
{ initial, into } = Collectable.into(collectable)
composed = fn x, [acc|collectable] ->
collectable = into.(collectable, { :cont, transform.(x) })
{ reason, acc } = fun.(x, acc)
{ reason, [acc|collectable] }
end
do_into(&Enumerable.reduce(enum, &1, composed), initial, into, acc)
end
defp do_into(reduce, collectable, into, { command, acc }) do
try do
reduce.({ command, [acc|collectable] })
catch
kind, reason ->
stacktrace = System.stacktrace
into.(collectable, :halt)
:erlang.raise(kind, reason, stacktrace)
else
{ :suspended, [acc|collectable], continuation } ->
{ :suspended, acc, &do_into(continuation, collectable, into, &1) }
{ reason, [acc|collectable] } ->
into.(collectable, :done)
{ reason, acc }
end
end
@doc """
Creates a stream that will apply the given function on
enumeration.
@@ -455,7 +464,7 @@ defmodule Stream do
stream = File.stream!("code")
|> Stream.map(&String.replace(&1, "#", "%"))
|> File.stream_to!("new")
|> Stream.into(File.stream!("new"))
No computation will be done until we call one of the Enum functions
or `Stream.run/1`.
@@ -596,7 +605,6 @@ defmodule Stream do
lazy enum, fn(f1) -> R.take_while(fun, f1) end
end
@doc """
Transforms an existing stream.
@@ -729,7 +737,7 @@ defmodule Stream do
"""
@spec uniq(Enumerable.t) :: Enumerable.t
@spec uniq(Enumerable.t, (element -> term)) :: Enumerable.t
def uniq(enum, fun // fn x -> x end) do
def uniq(enum, fun \\ fn x -> x end) do
lazy enum, [], fn f1 -> R.uniq(fun, f1) end
end
@@ -1017,6 +1025,7 @@ defmodule Stream do
iex> Stream.unfold(5, fn 0 -> nil; n -> {n, n-1} end) |> Enum.to_list()
[5, 4, 3, 2, 1]
"""
@spec unfold(acc, (acc -> { element, acc } | nil)) :: Enumerable.t
def unfold(next_acc, next_fun) do
@@ -1052,8 +1061,8 @@ defmodule Stream do
defp lazy(enum, acc, fun),
do: Lazy[enum: enum, funs: [fun], accs: [acc]]
defp lazy(Lazy[last: nil, funs: funs, accs: accs] = lazy, acc, fun, last),
do: lazy.funs([fun|funs]).accs([acc|accs]).last(last)
defp lazy(enum, acc, fun, last),
do: Lazy[enum: enum, funs: [fun], accs: [acc], last: last]
defp lazy(Lazy[done: nil, funs: funs, accs: accs] = lazy, acc, fun, done),
do: lazy.funs([fun|funs]).accs([acc|accs]).done(done)
defp lazy(enum, acc, fun, done),
do: Lazy[enum: enum, funs: [fun], accs: [acc], done: done]
end
+15 -15
View File
@@ -2,7 +2,7 @@ defmodule Stream.Reducers do
# Collection of reducers shared by Enum and Stream.
@moduledoc false
defmacro chunk(n, step, limit, f // nil) do
defmacro chunk(n, step, limit, f \\ nil) do
quote do
fn entry, acc(h, { buffer, count }, t) ->
buffer = [entry|buffer]
@@ -25,7 +25,7 @@ defmodule Stream.Reducers do
end
end
defmacro chunk_by(callback, f // nil) do
defmacro chunk_by(callback, f \\ nil) do
quote do
fn
entry, acc(h, { buffer, value }, t) ->
@@ -41,7 +41,7 @@ defmodule Stream.Reducers do
end
end
defmacro drop(f // nil) do
defmacro drop(f \\ nil) do
quote do
fn
_entry, acc(h, n, t) when n > 0 ->
@@ -52,7 +52,7 @@ defmodule Stream.Reducers do
end
end
defmacro drop_while(callback, f // nil) do
defmacro drop_while(callback, f \\ nil) do
quote do
fn entry, acc(h, bool, t) = orig ->
if bool and unquote(callback).(entry) do
@@ -64,7 +64,7 @@ defmodule Stream.Reducers do
end
end
defmacro filter(callback, f // nil) do
defmacro filter(callback, f \\ nil) do
quote do
fn(entry, acc) ->
if unquote(callback).(entry) do
@@ -76,7 +76,7 @@ defmodule Stream.Reducers do
end
end
defmacro filter_map(filter, mapper, f // nil) do
defmacro filter_map(filter, mapper, f \\ nil) do
quote do
fn(entry, acc) ->
if unquote(filter).(entry) do
@@ -88,7 +88,7 @@ defmodule Stream.Reducers do
end
end
defmacro map(callback, f // nil) do
defmacro map(callback, f \\ nil) do
quote do
fn(entry, acc) ->
cont(unquote(f), unquote(callback).(entry), acc)
@@ -96,7 +96,7 @@ defmodule Stream.Reducers do
end
end
defmacro reject(callback, f // nil) do
defmacro reject(callback, f \\ nil) do
quote do
fn(entry, acc) ->
unless unquote(callback).(entry) do
@@ -108,7 +108,7 @@ defmodule Stream.Reducers do
end
end
defmacro scan_2(callback, f // nil) do
defmacro scan_2(callback, f \\ nil) do
quote do
fn
entry, acc(h, :first, t) ->
@@ -120,7 +120,7 @@ defmodule Stream.Reducers do
end
end
defmacro scan_3(callback, f // nil) do
defmacro scan_3(callback, f \\ nil) do
quote do
fn(entry, acc(h, acc, t)) ->
value = unquote(callback).(entry, acc)
@@ -129,7 +129,7 @@ defmodule Stream.Reducers do
end
end
defmacro take(f // nil) do
defmacro take(f \\ nil) do
quote do
fn(entry, acc(h, n, t) = orig) ->
if n >= 1 do
@@ -141,7 +141,7 @@ defmodule Stream.Reducers do
end
end
defmacro take_every(nth, f // nil) do
defmacro take_every(nth, f \\ nil) do
quote do
fn
entry, acc(h, n, t) when n === :first
@@ -153,7 +153,7 @@ defmodule Stream.Reducers do
end
end
defmacro take_while(callback, f // nil) do
defmacro take_while(callback, f \\ nil) do
quote do
fn(entry, acc) ->
if unquote(callback).(entry) do
@@ -165,7 +165,7 @@ defmodule Stream.Reducers do
end
end
defmacro uniq(callback, f // nil) do
defmacro uniq(callback, f \\ nil) do
quote do
fn(entry, acc(h, prev, t) = acc) ->
value = unquote(callback).(entry)
@@ -178,7 +178,7 @@ defmodule Stream.Reducers do
end
end
defmacro with_index(f // nil) 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)
+126 -44
View File
@@ -1,7 +1,7 @@
import Kernel, except: [length: 1]
defmodule String do
@moduledoc %S"""
@moduledoc ~S"""
A String in Elixir is a UTF-8 encoded binary.
## String and binary operations
@@ -47,7 +47,7 @@ defmodule String do
by the letter "e" followed by the accent ́:
iex> string = "\x{0065}\x{0301}"
...> byte_size(string)
iex> byte_size(string)
3
iex> String.length(string)
1
@@ -83,13 +83,14 @@ defmodule String do
iex> ?j
106
iex> ?é
233
Or also via pattern matching:
iex> << eacute :: utf8 >> = "é"
...> eacute
iex> eacute
233
As we have seen above, codepoints can be inserted into
@@ -159,8 +160,10 @@ defmodule String do
iex> String.split("foo bar")
["foo", "bar"]
iex> String.split("foo" <> <<194, 133>> <> "bar")
["foo", "bar"]
iex> String.split(" foo bar ")
["foo", "bar"]
@@ -168,7 +171,7 @@ defmodule String do
@spec split(t) :: [t]
defdelegate split(binary), to: String.Unicode
@doc %S"""
@doc ~S"""
Divides a string into substrings based on a pattern,
returning a list of these substrings. The pattern can
be a string, a list of strings or a regular expression.
@@ -185,8 +188,10 @@ defmodule String do
iex> String.split("a,b,c", ",")
["a", "b", "c"]
iex> String.split("a,b,c", ",", global: false)
["a", "b,c"]
iex> String.split(" a b c ", " ", trim: true)
["a", "b", "c"]
@@ -197,45 +202,50 @@ defmodule String do
A regular expression:
iex> String.split("a,b,c", %r{,})
iex> String.split("a,b,c", ~r{,})
["a", "b", "c"]
iex> String.split("a,b,c", %r{,}, global: false)
iex> String.split("a,b,c", ~r{,}, global: false)
["a", "b,c"]
iex> String.split(" a b c ", %r{\s}, trim: true)
iex> String.split(" a b c ", ~r{\s}, trim: true)
["a", "b", "c"]
Splitting on empty patterns returns codepoints:
iex> String.split("abc", %r{})
iex> String.split("abc", ~r{})
["a", "b", "c", ""]
iex> String.split("abc", "")
["a", "b", "c", ""]
iex> String.split("abc", "", trim: true)
["a", "b", "c"]
iex> String.split("abc", "", global: false)
["a", "bc"]
"""
@spec split(t, t | [t] | Regex.t) :: [t]
@spec split(t, t | [t] | Regex.t, Keyword.t) :: [t]
def split(binary, pattern, options // [])
def split(binary, pattern, options \\ [])
def split("", _pattern, _options), do: [""]
def split(binary, "", options), do: split(binary, %r"", options)
def split(binary, pattern, options) when is_regex(pattern) do
Regex.split(pattern, binary, options)
end
def split(binary, "", options), do: split(binary, ~r"", options)
def split(binary, pattern, options) do
opts = if options[:global] != false, do: [:global], else: []
splits = :binary.split(binary, pattern, opts)
if Keyword.get(options, :trim, false) do
lc split inlist splits, split != "", do: split
if Regex.regex?(pattern) do
Regex.split(pattern, binary, options)
else
splits
opts = if options[:global] != false, do: [:global], else: []
splits = :binary.split(binary, pattern, opts)
if Keyword.get(options, :trim, false) do
for split <- splits, split != "", do: split
else
splits
end
end
end
@@ -246,8 +256,10 @@ defmodule String do
iex> String.upcase("abcd")
"ABCD"
iex> String.upcase("ab 123 xpto")
"AB 123 XPTO"
iex> String.upcase("josé")
"JOSÉ"
@@ -262,8 +274,10 @@ defmodule String do
iex> String.downcase("ABCD")
"abcd"
iex> String.downcase("AB 123 XPTO")
"ab 123 xpto"
iex> String.downcase("JOSÉ")
"josé"
@@ -284,8 +298,10 @@ defmodule String do
iex> String.capitalize("abcd")
"Abcd"
iex> String.capitalize("fin")
"Fin"
iex> String.capitalize("josé")
"José"
@@ -333,7 +349,7 @@ defmodule String do
end
end
def rstrip(string, char) do
def rstrip(string, char) when is_integer(char) do
do_rstrip(string, "", char)
end
@@ -373,11 +389,11 @@ defmodule String do
@spec lstrip(t, char) :: t
def lstrip(<<char :: utf8, rest :: binary>>, char) do
def lstrip(<<char :: utf8, rest :: binary>>, char) when is_integer(char) do
<<lstrip(rest, char) :: binary>>
end
def lstrip(other, _char) do
def lstrip(other, char) when is_integer(char) do
other
end
@@ -413,7 +429,7 @@ defmodule String do
rstrip(lstrip(string, char), char)
end
@doc %S"""
@doc ~S"""
Returns a new string of length `len` with `subject` right justified and
padded with `padding`. If `padding` is not present, it defaults to
whitespace. When `len` is less than the length of `subject`, `subject` is
@@ -423,6 +439,7 @@ defmodule String do
iex> String.rjust("abc", 5)
" abc"
iex> String.rjust("abc", 5, ?-)
"--abc"
@@ -438,7 +455,7 @@ defmodule String do
do_justify(subject, len, padding, :right)
end
@doc %S"""
@doc ~S"""
Returns a new string of length `len` with `subject` left justified and padded
with `padding`. If `padding` is not present, it defaults to whitespace. When
`len` is less than the length of `subject`, `subject` is returned.
@@ -447,6 +464,7 @@ defmodule String do
iex> String.ljust("abc", 5)
"abc "
iex> String.ljust("abc", 5, ?-)
"abc--"
@@ -482,7 +500,7 @@ defmodule String do
end
end
@doc %S"""
@doc ~S"""
Returns a new binary based on `subject` by replacing the parts
matching `pattern` by `replacement`. By default, it replaces
all entries, except if the `global` option is set to `false`.
@@ -493,13 +511,14 @@ defmodule String do
iex> String.replace("a,b,c", ",", "-")
"a-b-c"
iex> String.replace("a,b,c", ",", "-", global: false)
"a-b,c"
The pattern can also be a regex. In those cases, one can give `\N`
in the `replacement` string to access a specific capture in the regex:
iex> String.replace("a,b,c", %r/,(.)/, ",\\1\\1")
iex> String.replace("a,b,c", ~r/,(.)/, ",\\1\\1")
"a,bb,cc"
Notice we had to escape the escape character `\`. By giving `&`,
@@ -510,8 +529,10 @@ defmodule String do
iex> String.replace("a,b,c", "b", "[]", insert_replaced: 1)
"a,[b],c"
iex> String.replace("a,b,c", ",", "[]", insert_replaced: 2)
"a[],b[],c"
iex> String.replace("a,b,c", ",", "[]", insert_replaced: [1, 1])
"a[,,]b[,,]c"
@@ -519,15 +540,15 @@ defmodule String do
@spec replace(t, t, t) :: t
@spec replace(t, t, t, Keyword.t) :: t
def replace(subject, pattern, replacement, options // [])
def replace(subject, pattern, replacement, options) when is_regex(pattern) do
Regex.replace(pattern, subject, replacement, global: options[:global])
end
def replace(subject, pattern, replacement, options \\ [])
def replace(subject, pattern, replacement, options) do
opts = translate_replace_options(options)
:binary.replace(subject, pattern, replacement, opts)
if Regex.regex?(pattern) do
Regex.replace(pattern, subject, replacement, global: options[:global])
else
opts = translate_replace_options(options)
:binary.replace(subject, pattern, replacement, opts)
end
end
defp translate_replace_options(options) do
@@ -547,8 +568,10 @@ defmodule String do
iex> String.reverse("abcd")
"dcba"
iex> String.reverse("hello world")
"dlrow olleh"
iex> String.reverse("hello ∂og")
"go∂ olleh"
@@ -571,8 +594,10 @@ defmodule String do
iex> String.duplicate("abc", 0)
""
iex> String.duplicate("abc", 1)
"abc"
iex> String.duplicate("abc", 2)
"abcabc"
@@ -589,8 +614,10 @@ defmodule String do
iex> String.codepoints("josé")
["j", "o", "s", "é"]
iex> String.codepoints("оптими зации")
["о","п","т","и","м","и"," ","з","а","ц","и","и"]
iex> String.codepoints("ἅἪῼ")
["ἅ","Ἢ","ῼ"]
@@ -620,17 +647,20 @@ defmodule String do
@spec next_codepoint(t) :: {codepoint, t} | nil
defdelegate next_codepoint(string), to: String.Unicode
@doc %S"""
@doc ~S"""
Checks whether `str` contains only valid characters.
## Examples
iex> String.valid?("a")
true
iex> String.valid?("ø")
true
iex> String.valid?(<<0xffff :: 16>>)
false
iex> String.valid?("asd" <> <<0xffff :: 16>>)
false
@@ -643,7 +673,7 @@ defmodule String do
?\x{6FFFE}, ?\x{6FFFF}, ?\x{7FFFE}, ?\x{7FFFF}, ?\x{8FFFE}, ?\x{8FFFF},
?\x{9FFFE}, ?\x{9FFFF}, ?\x{10FFFE}, ?\x{10FFFF} ]
lc noncharacter inlist noncharacters do
for noncharacter <- noncharacters do
def valid?(<< unquote(noncharacter) :: utf8, _ :: binary >>), do: false
end
@@ -651,7 +681,7 @@ defmodule String do
def valid?(<<>>), do: true
def valid?(_), do: false
@doc %S"""
@doc ~S"""
Checks whether `str` is a valid character.
All characters are codepoints, but some codepoints
@@ -664,8 +694,10 @@ defmodule String do
iex> String.valid_character?("a")
true
iex> String.valid_character?("ø")
true
iex> String.valid_character?("\x{ffff}")
false
@@ -714,6 +746,7 @@ defmodule String do
iex> String.first("elixir")
"e"
iex> String.first("եոգլի")
"ե"
@@ -734,6 +767,7 @@ defmodule String do
iex> String.last("elixir")
"r"
iex> String.last("եոգլի")
"ի"
@@ -756,6 +790,7 @@ defmodule String do
iex> String.length("elixir")
6
iex> String.length("եոգլի")
5
@@ -779,12 +814,16 @@ defmodule String do
iex> String.at("elixir", 0)
"e"
iex> String.at("elixir", 1)
"l"
iex> String.at("elixir", 10)
nil
iex> String.at("elixir", -1)
"r"
iex> String.at("elixir", -10)
nil
@@ -822,18 +861,25 @@ defmodule String do
iex> String.slice("elixir", 1, 3)
"lix"
iex> String.slice("elixir", 1, 10)
"lixir"
iex> String.slice("elixir", 10, 3)
nil
iex> String.slice("elixir", -4, 4)
"ixir"
iex> String.slice("elixir", -10, 3)
nil
iex> String.slice("a", 0, 1500)
"a"
iex> String.slice("a", 1, 1500)
""
iex> String.slice("a", 2, 1500)
nil
@@ -870,26 +916,34 @@ defmodule String do
iex> String.slice("elixir", 1..3)
"lix"
iex> String.slice("elixir", 1..10)
"lixir"
iex> String.slice("elixir", 10..3)
nil
iex> String.slice("elixir", -4..-1)
"ixir"
iex> String.slice("elixir", 2..-1)
"ixir"
iex> String.slice("elixir", -4..6)
"ixir"
iex> String.slice("elixir", -1..-4)
nil
iex> String.slice("elixir", -10..-7)
nil
iex> String.slice("a", 0..1500)
"a"
iex> String.slice("a", 1..1500)
""
iex> String.slice("a", 2..1500)
nil
@@ -913,7 +967,7 @@ defmodule String do
last = total + last
end
if first > 0 do
if first >= 0 do
do_slice(next_grapheme(string), first, last, 0, "")
end
end
@@ -953,8 +1007,10 @@ defmodule String do
iex> String.starts_with? "elixir", "eli"
true
iex> String.starts_with? "elixir", ["erlang", "elixir"]
true
iex> String.starts_with? "elixir", ["erlang", "ruby"]
false
@@ -974,7 +1030,7 @@ defmodule String do
end
defp do_starts_with(string, prefix) when is_binary(prefix) do
match?({0, _}, :binary.match(string, prefix))
Kernel.match?({0, _}, :binary.match(string, prefix))
end
defp do_starts_with(_, _) do
@@ -989,8 +1045,10 @@ defmodule String do
iex> String.ends_with? "language", "age"
true
iex> String.ends_with? "language", ["youth", "age"]
true
iex> String.ends_with? "language", ["youth", "elixir"]
false
@@ -1021,27 +1079,47 @@ defmodule String do
end
@doc """
Returns `true` if `string` contains match, otherwise `false`.
Check if `string` matches the given regular expression.
## Examples
iex> String.match?("foo", ~r/foo/)
true
iex> String.match?("bar", ~r/foo/)
false
"""
@spec match?(t, Regex.t) :: boolean
def match?(string, regex) do
Regex.match?(regex, string)
end
@doc """
Check if `string` contains any of the given `contents`.
`matches` can be either a single string or a list of strings.
## Examples
iex> String.contains? "elixir of life", "of"
true
iex> String.contains? "elixir of life", ["life", "death"]
true
iex> String.contains? "elixir of life", ["death", "mercury"]
false
"""
@spec contains?(t, t | [t]) :: boolean
def contains?(string, matches) when is_list(matches) do
Enum.any?(matches, &do_contains(string, &1))
def contains?(string, contents) when is_list(contents) do
Enum.any?(contents, &do_contains(string, &1))
end
def contains?(string, match) do
do_contains(string, match)
def contains?(string, content) do
do_contains(string, content)
end
defp do_contains(string, "") when is_binary(string) do
@@ -1081,6 +1159,7 @@ defmodule String do
iex> String.to_char_list("æß")
{ :ok, 'æß' }
iex> String.to_char_list("abc")
{ :ok, 'abc' }
@@ -1110,6 +1189,7 @@ defmodule String do
iex> String.to_char_list!("æß")
'æß'
iex> String.to_char_list!("abc")
'abc'
@@ -1135,6 +1215,7 @@ defmodule String do
iex> String.from_char_list([0x00E6, 0x00DF])
{ :ok, "æß" }
iex> String.from_char_list([0x0061, 0x0062, 0x0063])
{ :ok, "abc" }
@@ -1162,6 +1243,7 @@ defmodule String do
iex> String.from_char_list!([0x00E6, 0x00DF])
"æß"
iex> String.from_char_list!([0x0061, 0x0062, 0x0063])
"abc"
+2 -1
View File
@@ -1,7 +1,7 @@
import Kernel, except: [to_string: 1]
defprotocol String.Chars do
@moduledoc %S"""
@moduledoc ~S"""
The String.Chars protocol is responsible for
converting a structure to a Binary (only if applicable).
The only function required to be implemented is
@@ -58,6 +58,7 @@ defimpl String.Chars, for: List do
iex> to_string('foo')
"foo"
iex> to_string(["foo", 'bar'])
"foobar"
+345
View File
@@ -0,0 +1,345 @@
defmodule StringIO do
@moduledoc """
This module provides an IO device that wraps a string.
## Examples
iex> { :ok, pid } = StringIO.open("foo")
iex> IO.read(pid, 2)
"fo"
"""
use GenServer.Behaviour
defrecordp :state, input: "", output: "", capture_prompt: false
@doc """
Creates an IO device.
If the `:capture_prompt` option is set to `true`,
prompts (specified as arguments to `IO.get*` functions)
are captured.
## Examples
iex> { :ok, pid } = StringIO.open("foo")
iex> IO.gets(pid, ">")
"foo"
iex> StringIO.contents(pid)
{ "", "" }
iex> { :ok, pid } = StringIO.open("foo", capture_prompt: true)
iex> IO.gets(pid, ">")
"foo"
iex> StringIO.contents(pid)
{ "", ">" }
"""
@spec open(binary, Keyword.t) :: { :ok, pid }
def open(string, options \\ []) when is_binary(string) do
:gen_server.start_link(__MODULE__, { string, options }, [])
end
@doc """
Returns current buffers.
## Examples
iex> { :ok, pid } = StringIO.open("in")
iex> IO.write(pid, "out")
iex> StringIO.contents(pid)
{ "in", "out" }
"""
@spec contents(pid) :: { binary, binary }
def contents(pid) when is_pid(pid) do
:gen_server.call(pid, :contents)
end
@doc """
Stops the IO device and returns remaining buffers.
## Examples
iex> { :ok, pid } = StringIO.open("in")
iex> IO.write(pid, "out")
iex> StringIO.close(pid)
{ :ok, { "in", "out" } }
"""
@spec close(pid) :: { :ok, { binary, binary } }
def close(pid) when is_pid(pid) do
:gen_server.call(pid, :close)
end
## callbacks
def init({ string, options }) do
capture_prompt = options[:capture_prompt] || false
{ :ok, state(input: string, capture_prompt: capture_prompt) }
end
def handle_info({ :io_request, from, reply_as, req }, s) do
s = io_request(from, reply_as, req, s)
{ :noreply, s }
end
def handle_info(msg, s) do
super(msg, s)
end
def handle_call(:contents, _from, state(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 }
end
def handle_call(request, from, s) do
super(request, from, s)
end
defp io_request(from, reply_as, req, s) do
{ reply, s } = io_request(req, s)
io_reply(from, reply_as, to_reply(reply))
s
end
defp io_request({ :put_chars, chars }, state(output: output) = s) do
{ :ok, state(s, output: << output :: binary, to_binary(chars) :: binary >>) }
end
defp io_request({ :put_chars, m, f, as }, state(output: output) = s) do
chars = apply(m, f, as)
{ :ok, state(s, output: << output :: binary, to_binary(chars) :: binary >>) }
end
defp io_request({ :put_chars, _encoding, chars }, s) do
io_request({ :put_chars, chars }, s)
end
defp io_request({ :put_chars, _encoding, mod, func, args }, s) do
io_request({ :put_chars, mod, func, args }, s)
end
defp io_request({ :get_chars, prompt, n }, s) when n >= 0 do
io_request({ :get_chars, :latin1, prompt, n }, s)
end
defp io_request({ :get_chars, encoding, prompt, n }, s) when n >= 0 do
get_chars(encoding, prompt, n, s)
end
defp io_request({ :get_line, prompt }, s) do
io_request({ :get_line, :latin1, prompt }, s)
end
defp io_request({ :get_line, encoding, prompt }, s) do
get_line(encoding, prompt, s)
end
defp io_request({ :get_until, prompt, mod, fun, args }, s) do
io_request({ :get_until, :latin1, prompt, mod, fun, args }, s)
end
defp io_request({ :get_until, encoding, prompt, mod, fun, args }, s) do
get_until(encoding, prompt, mod, fun, args, s)
end
defp io_request({ :get_password, encoding }, s) do
get_line(encoding, "", s)
end
defp io_request({ :setopts, _opts }, s) do
{ { :error, :enotsup }, s }
end
defp io_request(:getopts, s) do
{ { :ok, [binary: true, encoding: :unicode] }, s }
end
defp io_request({ :get_geometry, :columns }, s) do
{ { :error, :enotsup }, s }
end
defp io_request({ :get_geometry, :rows }, s) do
{ { :error, :enotsup }, s }
end
defp io_request({ :requests, reqs }, s) do
io_requests(reqs, { :ok, s })
end
defp io_request(_, s) do
{ { :error, :request }, s }
end
## get_chars
defp get_chars(encoding, prompt, n,
state(input: input, output: output, capture_prompt: capture_prompt) = s) do
case do_get_chars(input, encoding, n) do
{ :error, _ } = error ->
{ error, s }
{ result, input } ->
if capture_prompt do
output = << output :: binary, to_binary(prompt) :: binary >>
end
{ result, state(s, input: input, output: output) }
end
end
defp do_get_chars("", _encoding, _n) do
{ :eof, "" }
end
defp do_get_chars(input, :latin1, n) when byte_size(input) < n do
{ input, "" }
end
defp do_get_chars(input, :latin1, n) do
<< chars :: [ binary, size(n) ], rest :: binary >> = input
{ chars, rest }
end
defp do_get_chars(input, encoding, n) do
try do
case :file_io_server.count_and_find(input, n, encoding) do
{ buf_count, split_pos } when buf_count < n or split_pos == :none ->
{ input, "" }
{ _buf_count, split_pos } ->
<< chars :: [ binary, size(split_pos) ], rest :: binary >> = input
{ chars, rest }
end
catch
:exit, :invalid_unicode ->
{ :error, :invalid_unicode }
end
end
## get_line
defp get_line(encoding, prompt,
state(input: input, output: output, capture_prompt: capture_prompt) = s) do
case :unicode.characters_to_list(input, encoding) do
{ :error, _, _ } ->
{ { :error, :collect_line }, s }
{ :incomplete, _, _ } ->
{ { :error, :collect_line }, s }
chars ->
{ result, input } = do_get_line(chars, encoding)
if capture_prompt do
output = << output :: binary, to_binary(prompt) :: binary >>
end
{ result, state(s, input: input, output: output) }
end
end
defp do_get_line('', _encoding) do
{ :eof, "" }
end
defp do_get_line(chars, encoding) do
{ line, rest } = collect_line(chars)
{ :unicode.characters_to_binary(line, encoding),
:unicode.characters_to_binary(rest, encoding) }
end
## get_until
defp get_until(encoding, prompt, mod, fun, args,
state(input: input, output: output, capture_prompt: capture_prompt) = s) do
case :unicode.characters_to_list(input, encoding) do
{ :error, _, _ } ->
{ :error, s }
{ :incomplete, _, _ } ->
{ :error, s }
chars ->
{ result, input, count } = do_get_until(chars, encoding, mod, fun, args)
if capture_prompt do
output = << output :: binary, :binary.copy(to_binary(prompt), count) :: binary >>
end
input =
case input do
:eof -> ""
_ -> :unicode.characters_to_binary(input, encoding)
end
{ result, state(s, input: input, output: output) }
end
end
defp do_get_until(chars, encoding, mod, fun, args, continuation \\ [], count \\ 0)
defp do_get_until('', encoding, mod, fun, args, continuation, count) do
case apply(mod, fun, [continuation, :eof | args]) do
{ :done, result, rest } ->
{ result, rest, count + 1 }
{ :more, next_continuation } ->
do_get_until('', encoding, mod, fun, args, next_continuation, count + 1)
end
end
defp do_get_until(chars, encoding, mod, fun, args, continuation, count) do
{ line, rest } = collect_line(chars)
case apply(mod, fun, [continuation, line | args]) do
{ :done, result, rest1 } ->
unless rest1 == :eof do
rest = rest1 ++ rest
end
{ result, rest, count + 1 }
{ :more, next_continuation } ->
do_get_until(rest, encoding, mod, fun, args, next_continuation, count + 1)
end
end
## io_requests
defp io_requests([r|rs], { :ok, s }) do
io_requests(rs, io_request(r, s))
end
defp io_requests(_, result) do
result
end
## helpers
defp collect_line(chars) do
collect_line(chars, [])
end
defp collect_line([], stack) do
{ :lists.reverse(stack), [] }
end
defp collect_line([?\r, ?\n | rest], stack) do
{ :lists.reverse([?\n|stack]), rest }
end
defp collect_line([?\n | rest], stack) do
{ :lists.reverse([?\n|stack]), rest }
end
defp collect_line([h|t], stack) do
collect_line(t, [h|stack])
end
defp io_reply(from, reply_as, reply) do
send from, { :io_reply, reply_as, reply }
end
defp to_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(other), do: other
end
+2 -2
View File
@@ -152,7 +152,7 @@ defmodule Supervisor.Behaviour do
#{@child_doc}
"""
def worker(module, args, options // []) do
def worker(module, args, options \\ []) do
child(:worker, module, args, options)
end
@@ -166,7 +166,7 @@ defmodule Supervisor.Behaviour do
#{@child_doc}
"""
def supervisor(module, args, options // []) do
def supervisor(module, args, options \\ []) do
options = Keyword.update(options, :shutdown, :infinity, fn(x) -> x end)
child(:supervisor, module, args, options)
end
+26 -18
View File
@@ -1,13 +1,4 @@
defmodule System do
defexception NoHomeError,
message: "could not find the user home, please set the HOME environment variable"
defexception NoTmpDirError,
message: "could not get a writable temporary directory, please set the TMPDIR environment variable"
defexception NoAccessCwdError,
message: "could not get a current working directory, the current location is not accessible"
@moduledoc """
The System module provides access to variables used or
maintained by the VM and to functions that interact directly
@@ -29,7 +20,7 @@ defmodule System do
# Read and strip the version from the `VERSION` file.
defmacrop get_version do
case read_stripped(:filename.join(__DIR__, "../../../VERSION")) do
"" -> raise ArgumentError, message: "could not read the version number from VERSION"
"" -> raise RuntimeError, message: "could not read the version number from VERSION"
data -> data
end
end
@@ -111,10 +102,11 @@ defmodule System do
@doc """
Current working directory, exception on error.
Returns the current working directory or raises `System.NoAccessCwdError`.
Returns the current working directory or raises `RuntimeError`.
"""
def cwd! do
cwd || raise NoAccessCwdError
cwd ||
raise RuntimeError, message: "could not get a current working directory, the current location is not accessible"
end
@doc """
@@ -133,11 +125,12 @@ defmodule System do
@doc """
User home directory, exception on error.
Same as `user_home/0` but raises `System.NoHomeError`
Same as `user_home/0` but raises `RuntimeError`
instead of returning `nil` if no user home is set.
"""
def user_home! do
user_home || raise NoHomeError
user_home ||
raise RuntimeError, message: "could not find the user home, please set the HOME environment variable"
end
defp get_unix_home do
@@ -154,7 +147,7 @@ defmodule System do
)
end
@doc %S"""
@doc ~S"""
Writable temporary directory.
Returns a writable temporary directory.
@@ -179,11 +172,13 @@ defmodule System do
@doc """
Writable temporary directory, exception on error.
Same as `tmp_dir/0` but raises `System.NoTmpDirError`
Same as `tmp_dir/0` but raises `RuntimeError`
instead of returning `nil` if no temp dir is set.
"""
def tmp_dir! do
tmp_dir || raise NoTmpDirError
tmp_dir ||
raise RuntimeError, message: "could not get a writable temporary directory, " <>
"please set the TMPDIR environment variable"
end
defp write_env_tmp_dir(env) do
@@ -336,12 +331,25 @@ defmodule System do
Enum.each dict, fn {key, val} -> put_env key, val end
end
@doc """
Deletes an environment variable.
Removes the variable `varname` from the environment.
"""
@spec delete_env(String.t) :: :ok
def delete_env(varname) do
:os.unsetenv(String.to_char_list!(varname))
:ok
end
@doc """
Last exception stacktrace.
Note that the Erlang VM (and therefore this function) does not
return the current stacktrace but rather the stacktrace of the
latest exception.
Inlined by the compiler into `:erlang.get_stacktrace/0`.
"""
def stacktrace do
:erlang.get_stacktrace
@@ -376,7 +384,7 @@ defmodule System do
"""
@spec halt() :: no_return
@spec halt(non_neg_integer | binary | :abort) :: no_return
def halt(status // 0)
def halt(status \\ 0)
def halt(status) when is_integer(status) or status == :abort do
:erlang.halt(status)
+3 -3
View File
@@ -30,7 +30,7 @@ defmodule Tuple do
## Examples
iex> tuple = { :bar, :baz }
...> Tuple.insert_at(tuple, 0, :foo)
iex> Tuple.insert_at(tuple, 0, :foo)
{ :foo, :bar, :baz }
"""
@@ -43,13 +43,13 @@ defmodule Tuple do
Remove an element from a tuple.
Deletes the element at the zero-based `index` from `tuple`.
Raises an `ArgumentError` if `index` is greater than
Raises an `ArgumentError` if `index` is greater than
or equal to the length of `tuple`.
## Examples
iex> tuple = { :foo, :bar, :baz }
...> Tuple.delete_at(tuple, 0)
iex> Tuple.delete_at(tuple, 0)
{ :bar, :baz }
"""
+40 -30
View File
@@ -62,13 +62,14 @@ defmodule URI do
Takes an enumerable (containing a sequence of two-item tuples)
and returns a string of the form "key1=value1&key2=value2..." where
keys and values are URL encoded as per `encode/1`. Keys and values can
be any term that implements the `String.Chars` protocol (i.e. can be converted
to a binary).
keys and values are URL encoded as per `encode/1`.
Keys and values can be any term that implements the `String.Chars`
protocol, except lists which are explicitly forbidden.
## Examples
iex> hd = HashDict.new([{"foo", 1}, {"bar", "2"}])
iex> hd = %{"foo" => 1, "bar" => 2}
iex> URI.encode_query(hd)
"bar=2&foo=1"
@@ -76,7 +77,7 @@ defmodule URI do
def encode_query(l), do: Enum.map_join(l, "&", &pair/1)
@doc """
Decodes a query string into a `HashDict`.
Decodes a query string into a dictionary (by default uses a map).
Given a query string of the form "key1=value1&key2=value2...", produces a
`HashDict` with one entry for each key-value pair. Each key and value will be a
@@ -86,17 +87,11 @@ defmodule URI do
## Examples
iex> URI.decode_query("foo=1&bar=2") |> Dict.to_list
[{"bar", "2"}, {"foo", "1"}]
iex> hd = HashDict.new()
iex> URI.decode_query("foo=1&bar=2", hd) |> HashDict.keys
["bar", "foo"]
iex> URI.decode_query("foo=1&bar=2", hd) |> HashDict.values
["2", "1"]
iex> URI.decode_query("foo=1&bar=2")
%{"bar" => "2", "foo" => "1"}
"""
def decode_query(q, dict // HashDict.new) when is_binary(q) do
def decode_query(q, dict \\ %{}) when is_binary(q) do
Enum.reduce query_decoder(q), dict, fn({ k, v }, acc) -> Dict.put(acc, k, v) end
end
@@ -134,6 +129,14 @@ defmodule URI do
{ current, next }
end
defp pair({k, _}) when is_list(k) do
raise ArgumentError, message: "encode_query/1 keys cannot be lists, got: #{inspect k}"
end
defp pair({_, v}) when is_list(v) do
raise ArgumentError, message: "encode_query/1 values cannot be lists, got: #{inspect v}"
end
defp pair({k, v}) do
encode(to_string(k)) <> "=" <> encode(to_string(v))
end
@@ -143,11 +146,11 @@ defmodule URI do
## Example
iex> URI.encode("http://elixir-lang.com/getting_started/2.html")
"http%3A%2F%2Felixir-lang.com%2Fgetting_started%2F2.html"
iex> URI.encode("http://elixir-lang.org/getting_started/2.html")
"http%3A%2F%2Felixir-lang.org%2Fgetting_started%2F2.html"
"""
def encode(s), do: bc(<<c>> inbits s, do: <<percent(c) :: binary>>)
def encode(s), do: for(<<c <- s>>, into: "", do: percent(c))
defp percent(32), do: <<?+>>
defp percent(?-), do: <<?->>
@@ -171,23 +174,30 @@ defmodule URI do
## Examples
iex> URI.decode("http%3A%2F%2Felixir-lang.com")
"http://elixir-lang.com"
iex> URI.decode("http%3A%2F%2Felixir-lang.org")
"http://elixir-lang.org"
"""
def decode(<<?%, hex1, hex2, tail :: binary >>) do
<< bsl(hex2dec(hex1), 4) + hex2dec(hex2) >> <> decode(tail)
def decode(uri) do
decode(uri, uri)
end
def decode(<<head, tail :: binary >>) do
<<check_plus(head)>> <> decode(tail)
def decode(<<?%, hex1, hex2, tail :: binary >>, uri) do
<< bsl(hex2dec(hex1, uri), 4) + hex2dec(hex2, uri) >> <> decode(tail, uri)
end
def decode(<<>>), do: <<>>
def decode(<<head, tail :: binary >>, uri) do
<<check_plus(head)>> <> decode(tail, uri)
end
defp hex2dec(n) when n in ?A..?F, do: n - ?A + 10
defp hex2dec(n) when n in ?a..?f, do: n - ?a + 10
defp hex2dec(n) when n in ?0..?9, do: n - ?0
def decode(<<>>, _uri), do: <<>>
defp hex2dec(n, _uri) when n in ?A..?F, do: n - ?A + 10
defp hex2dec(n, _uri) when n in ?a..?f, do: n - ?a + 10
defp hex2dec(n, _uri) when n in ?0..?9, do: n - ?0
defp hex2dec(_n, uri) do
raise ArgumentError, message: "malformed URI #{inspect uri}"
end
defp check_plus(?+), do: 32
defp check_plus(c), do: c
@@ -210,7 +220,7 @@ defmodule URI do
"""
def parse(s) when is_binary(s) do
# From http://tools.ietf.org/html/rfc3986#appendix-B
regex = %r/^(([^:\/?#]+):)?(\/\/([^\/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?/
regex = ~r/^(([^:\/?#]+):)?(\/\/([^\/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?/
parts = nillify(Regex.run(regex, s))
destructure [_, _, scheme, _, authority, path, _, query, _, fragment], parts
@@ -240,7 +250,7 @@ defmodule URI do
# Split an authority into its userinfo, host and port parts.
defp split_authority(s) do
s = s || ""
components = Regex.run %r/(^(.*)@)?(\[[a-zA-Z0-9:.]*\]|[^:]*)(:(\d*))?/, s
components = Regex.run ~r/(^(.*)@)?(\[[a-zA-Z0-9:.]*\]|[^:]*)(:(\d*))?/, s
destructure [_, _, userinfo, host, _, port], nillify(components)
port = if port, do: binary_to_integer(port)
@@ -252,7 +262,7 @@ defmodule URI do
# Regex.run returns empty strings sometimes. We want
# to replace those with nil for consistency.
defp nillify(l) do
lc s inlist l do
for s <- l do
if size(s) > 0, do: s, else: nil
end
end
+189 -215
View File
@@ -1,17 +1,12 @@
defmodule Version do
@moduledoc %S"""
@moduledoc ~S"""
Functions for parsing and matching versions against requirements.
A version is a string in a specific format or a `Version.Schema`
generated after parsing via `Version.parse/1`.
`Version` parsing and requirements follow
[SemVer 2.0 schema](http://semver.org/) and you will get
the most of Mix's version system by following it. In order
to support integration with projects that may
follow different versioning schemas, Elixir won't choke
on unknown versions, however you won't be able to use
Mix requirements with such unformatted versions.
[SemVer 2.0 schema](http://semver.org/).
## Versions
@@ -54,7 +49,6 @@ 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,
@@ -62,98 +56,147 @@ defmodule Version do
import Kernel, except: [match?: 2]
defrecord Schema, major: 0, minor: 0, patch: 0, pre: nil, build: nil, source: nil
defrecord Requirement, source: nil, matchspec: nil
defrecord Schema, [:major, :minor, :patch, :pre, :build, :source]
defrecord Requirement, [:source, :matchspec]
defexception InvalidRequirement, [:message] do
def exception(opts) do
message =
if is_binary opts[:reason] do
{ first, rest } = String.next_grapheme(opts[:reason])
String.downcase(first) <> rest
else
"invalid version specification"
end
InvalidRequirement[message: message]
end
end
defexception InvalidRequirement, [:message]
defexception InvalidVersion, [:message]
@doc """
Check if the given version matches the specification.
Returns `true` if `version` satisfies `requirement`, `false` otherwise.
Raises a `Version.InvalidRequirement` exception if `requirement` is not parseable.
Raises a `Version.InvalidRequirement` exception if `requirement` is not
parseable, or `Version.InvalidVersion` if `version` is not parseable.
If given an already parsed version and requirement this function won't
raise.
## Examples
iex> Version.match?("2.0", ">1.0")
iex> Version.match?("2.0.0", ">1.0.0")
true
iex> Version.match?("2.0", "==1.0")
iex> Version.match?("2.0.0", "==1.0.0")
false
iex> Version.match?("foo", "==1.0.0")
** (Version.InvalidVersion) foo
iex> Version.match?("2.0.0", "== ==1.0.0")
** (Version.InvalidRequirement) == ==1.0.0
"""
@spec match?(t, requirement) :: boolean
def match?(version, requirement) when is_binary(requirement) do
case Version.Parser.parse_requirement(requirement) do
def match?(vsn, req) when is_binary(req) do
case parse_requirement(req) do
{ :ok, req } ->
match?(version, req)
{ :error, reason } ->
raise InvalidRequirement, reason: reason
match?(vsn, req)
:error ->
raise InvalidRequirement, message: req
end
end
def match?(version, requirement) when is_binary(version) do
match?(parse(version), requirement)
end
def match?(Schema[] = version, Requirement[matchspec: spec]) do
case :ets.test_ms(to_matchable(version), spec) do
{ :ok, result } ->
result != false
{ :error, reason } ->
raise InvalidRequirement, reason: reason
end
def match?(version, Requirement[matchspec: spec]) do
{ :ok, result } = :ets.test_ms(to_matchable(version), spec)
result != false
end
@doc """
Check if a version string is compatible with [semver](http://semver.org/).
Compares two versions. Returns `:gt` if first version is greater than
the second and `:lt` for vice versa. If the two versions are equal `:eq`
is returned
Raises a `Version.InvalidVersion` exception if `version` is not parseable.
If given an already parsed version this function won't raise.
## Examples
iex> Version.valid?("2.0")
true
iex> Version.valid?("invalid")
false
iex> Version.compare("2.0.1-alpha1", "2.0.0")
:gt
iex> Version.compare("2.0.1+build0", "2.0.1")
:eq
iex> Version.compare("invalid", "2.0.1")
** (Version.InvalidVersion) invalid
"""
@spec valid?(String.t | Schema.t) :: boolean
def valid?(string) when is_binary(string) do
Version.Parser.valid_version?(string)
@spec compare(String.t | Schema.t, String.t | Schema.t) :: :gt | :eq | :lt
def compare(vsn1, vsn2) do
do_compare(to_matchable(vsn1), to_matchable(vsn2))
end
def valid?(Version.Schema[major: nil]), do: false
def valid?(Version.Schema[]), do: true
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
pre1 == [] and pre2 != [] -> :gt
pre1 != [] and pre2 == [] -> :lt
pre1 > pre2 -> :gt
pre1 < pre2 -> :lt
true -> :eq
end
end
@doc """
Parse a version string into a `Version.Schema`.
## Examples
iex> Version.parse("2.0.1-alpha1")
iex> Version.parse("2.0.1-alpha1") |> elem(1)
#Version.Schema<2.0.1-alpha1>
iex> Version.parse("2.0-alpha1")
:error
"""
@spec parse(String.t) :: Schema.t
@spec parse(String.t) :: { :ok, Schema.t } | :error
def parse(string) when is_binary(string) do
case Version.Parser.parse_version(string) do
{ :ok, matchable } -> from_matchable(matchable).source(string).build(get_build(string))
{ :error, _ } -> Version.Schema[source: string]
{ :ok, { major, minor, patch, pre } } ->
vsn = Version.Schema[major: major, minor: minor, patch: patch,
pre: pre, source: string, build: get_build(string)]
{ :ok, vsn }
:error ->
:error
end
end
@doc """
Parse a version requirement string into a `Version.Schema`.
## Examples
iex> Version.parse_requirement("== 2.0.1") |> elem(1)
#Version.Requirement<== 2.0.1>
iex> Version.parse_requirement("== == 2.0.1")
:error
"""
@spec parse_requirement(String.t) :: { :ok, Requirement.t } | :error
def parse_requirement(string) when is_binary(string) do
case Version.Parser.parse_requirement(string) do
{ :ok, spec } ->
{ :ok, Requirement[source: string, matchspec: spec] }
:error ->
:error
end
end
defp to_matchable(Schema[major: major, minor: minor, patch: patch, pre: pre]) do
{ major, minor, patch, pre }
end
defp to_matchable(string) do
case Version.Parser.parse_version(string) do
{ :ok, vsn } -> vsn
:error -> raise InvalidVersion, message: string
end
end
defp get_build(string) do
case Regex.run(%r/\+([^\s]+)$/, string) do
case Regex.run(~r/\+([^\s]+)$/, string) do
nil ->
nil
@@ -162,69 +205,6 @@ defmodule Version do
end
end
@doc """
Convert a version to a `Version.matchable`
## Examples
iex> Version.to_matchable("2.0.1-alpha.1")
{2, 0, 1, ["alpha", 1]}
"""
@spec to_matchable(String.t | Schema.t) :: Version.matchable
def to_matchable(Schema[major: nil, source: source]) do
{ source, nil, nil, [] }
end
def to_matchable(Version.Schema[major: major, minor: minor, patch: patch, pre: nil]) do
{ major, minor, patch, [] }
end
def to_matchable(Version.Schema[major: major, minor: minor, patch: patch, pre: pre]) do
{ major, minor, patch, Version.Parser.parse_pre(pre) }
end
def to_matchable(string) do
to_matchable(parse(string))
end
@doc """
Convert a matchable to a `Version.Schema`.
## Examples
iex> Version.from_matchable({2, 0, 1, ["alpha", 1]})
#Version.Schema<2.0.1-alpha.1>
"""
@spec from_matchable(Version.matchable) :: Schema.t
def from_matchable({ source, nil, nil, nil }) when is_binary(source) do
Version.Schema[source: source]
end
def from_matchable({ major, minor, patch, pre }) do
source = "#{major}"
if minor do
source = "#{source}.#{minor}"
if patch do
source = "#{source}.#{patch}"
case pre do
[] ->
pre = nil
list ->
pre = Enum.join(list, ".")
source = "#{source}-#{pre}"
end
end
end
Version.Schema[major: major, minor: minor, patch: patch, pre: pre, source: source]
end
defmodule Parser.DSL do
@moduledoc false
@@ -292,78 +272,75 @@ defmodule Version do
Enum.filter(Enum.reverse(acc), &(&1 != :' '))
end
@version_regex %r/^(\d+)(?:\.(\d+)(?:\.(\d+))?)?(?:\-([^\s]+))?(?:\+[^\d]+)?$/
@version_regex ~r/^
(\d+) # major
(?:\.(\d+))? # minor
(?:\.(\d+))? # patch
(?:\-([\d\w\.\-]+))? # pre
(?:\+([\d\w\-]+))? # build
$/x
@spec parse_requirement(String.t) :: { :ok, Version.Requirement.t } | { :error, binary | atom }
@spec parse_requirement(String.t) :: {:ok, Version.Requirement.t} | :error
def parse_requirement(source) do
lexed = lexer(source, [])
if valid_requirement?(lexed) do
spec = to_matchspec(lexed)
case :ets.test_ms({}, spec) do
{ :ok, _ } ->
{ :ok, Requirement[source: source, matchspec: spec] }
{ :error, errors } ->
{ :error, Enum.map(errors, fn { :error, reason } ->
to_string(reason)
end) }
end
else
{ :error, :invalid_requirement }
end
to_matchspec(lexed)
end
defp nillify(""), do: nil
defp nillify(o), do: o
@spec parse_version(String.t) :: { :ok, Version.matchable } | { :error, :invalid_version }
def parse_version(string) when is_binary(string) do
if valid_version?(string) do
destructure [_, major, minor, patch, pre], Regex.run(@version_regex, string)
@spec parse_version(String.t) :: {:ok, Version.matchable} | :error
def parse_version(string, approximate? \\ false) when is_binary(string) do
if parsed = Regex.run(@version_regex, string) do
destructure [_, major, minor, patch, pre], parsed
patch = nillify(patch)
pre = nillify(pre)
major = binary_to_integer(major)
minor = binary_to_integer(minor |> nillify || "0")
patch = binary_to_integer(patch |> nillify || "0")
pre = pre && parse_pre(pre) || []
{ :ok, { major, minor, patch, pre } }
else
{ :error, :invalid_version }
end
end
@doc false
def parse_pre(pre) do
String.split(pre, ".") |> Enum.map fn piece ->
if piece =~ %r/^(0|[1-9][0-9]*)$/ do
binary_to_integer(piece)
if nil?(minor) or (nil?(patch) and not approximate?) do
:error
else
piece
major = binary_to_integer(major)
minor = binary_to_integer(minor)
patch = patch && binary_to_integer(patch)
case parse_pre(pre) do
{ :ok, pre } ->
{ :ok, { major, minor, patch, pre } }
:error ->
:error
end
end
else
:error
end
end
@spec valid_requirement?(list) :: boolean
def valid_requirement?([]) do
false
defp parse_pre(nil), do: { :ok, [] }
defp parse_pre(pre), do: parse_pre(String.split(pre, "."), [])
defp parse_pre([piece|t], acc) do
cond do
piece =~ ~r/^(0|[1-9][0-9]*)$/ ->
parse_pre(t, [binary_to_integer(piece)|acc])
piece =~ ~r/^[0-9]*$/ ->
:error
true ->
parse_pre(t, [piece|acc])
end
end
def valid_requirement?([a | next]) do
valid_requirement?(a, next)
defp parse_pre([], acc) do
{ :ok, Enum.reverse(acc) }
end
defp valid_requirement?([]), do: false
defp valid_requirement?([a | next]), do: valid_requirement?(a, next)
# it must finish with a version
defp valid_requirement?(a, []) when is_binary(a) do
true
end
# version version
defp valid_requirement?(a, [b | _]) when is_binary(a) and is_binary(b) do
false
end
# or <op> | and <op>
defp valid_requirement?(a, [b | next]) when is_atom(a) and is_atom(b) and a in [:'||', :'&&'] do
valid_requirement?(b, next)
@@ -379,73 +356,57 @@ defmodule Version do
valid_requirement?(b, next)
end
# <op> <version>; also checks operators work on valid versions
# <op> <version>
defp valid_requirement?(a, [b | next]) when is_atom(a) and is_binary(b) do
if valid_version?(b) do
valid_requirement?(b, next)
else
if a in [:'==', :'!='] and Regex.match? %r/^\w/, b do
valid_requirement?(b, next)
else
false
end
end
valid_requirement?(b, next)
end
defp valid_requirement?(_, _) do
false
end
@spec valid_version?(String.t) :: boolean
def valid_version?(string) do
Regex.match? %r/^\d+(\.\d+(\.\d+)?)?(\-[^\s]+)?(?:\+[^\s]+)?$/, string
end
defp approximate_upper(version) do
case version do
{ major, _minor, nil, _ } ->
{ major + 1, 0, 0, [0] }
defp approximate(version) do
Version.from_matchable(case Regex.run(@version_regex, version) do
[_, major] ->
{ binary_to_integer(major) + 1, 0, 0, [0] }
[_, major, _] ->
{ binary_to_integer(major) + 1, 0, 0, [0] }
[_, major, minor, _] ->
{ binary_to_integer(major), binary_to_integer(minor) + 1, 0, [0] }
[_, major, minor, _, _] ->
{ binary_to_integer(major), binary_to_integer(minor) + 1, 0, [0] }
end)
{ major, minor, _patch, _ } ->
{ major, minor + 1, 0, [0] }
end
end
defp to_matchspec(lexed) do
first = to_condition(lexed)
rest = Enum.drop(lexed, 2)
[{{ :'$1', :'$2', :'$3', :'$4' }, [to_condition(first, rest)], [:'$_'] }]
if valid_requirement?(lexed) do
first = to_condition(lexed)
rest = Enum.drop(lexed, 2)
{ :ok, [{{ :'$1', :'$2', :'$3', :'$4' }, [to_condition(first, rest)], [:'$_'] }] }
else
:error
end
catch
:invalid_matchspec -> :error
end
defp to_condition([:'==', version | _]) do
version = Version.to_matchable(version)
version = parse_condition(version)
{ :'==', :'$_', { :const, version } }
end
defp to_condition([:'!=', version | _]) do
version = Version.to_matchable(version)
version = parse_condition(version)
{ :'/=', :'$_', { :const, version } }
end
defp to_condition([:'~>', version | _]) do
from = Version.parse(version)
to = approximate(version)
from = parse_condition(version, true)
to = approximate_upper(from)
{ :andalso, to_condition([:'>=', to_string(from)]),
to_condition([:'<', to_string(to)]) }
{ :andalso, to_condition([:'>=', matchable_to_string(from)]),
to_condition([:'<', matchable_to_string(to)]) }
end
defp to_condition([:'>', version | _]) do
{ major, minor, patch, pre } = Version.to_matchable(version)
{ major, minor, patch, pre } = parse_condition(version)
{ :andalso, { :not, { :is_binary, :'$1' } },
{ :orelse, { :'>', {{ :'$1', :'$2', :'$3' }},
@@ -461,7 +422,7 @@ defmodule Version do
end
defp to_condition([:'>=', version | _]) do
matchable = Version.to_matchable(version)
matchable = parse_condition(version)
{ :orelse, { :andalso, { :not, { :is_binary, :'$1' } },
{ :'==', :'$_', { :const, matchable } } },
@@ -469,7 +430,7 @@ defmodule Version do
end
defp to_condition([:'<', version | _]) do
{ major, minor, patch, pre } = Version.to_matchable(version)
{ major, minor, patch, pre } = parse_condition(version)
{ :andalso, { :not, { :is_binary, :'$1' } },
{ :orelse, { :'<', {{ :'$1', :'$2', :'$3' }},
@@ -485,7 +446,7 @@ defmodule Version do
end
defp to_condition([:'<=', version | _]) do
matchable = Version.to_matchable(version)
matchable = parse_condition(version)
{ :orelse, { :andalso, { :not, { :is_binary, :'$1' } },
{ :'==', :'$_', { :const, matchable } } },
@@ -503,6 +464,19 @@ defmodule Version do
defp to_condition(current, [:'||', operator, version | rest]) do
to_condition({ :orelse, current, to_condition([operator, version]) }, rest)
end
defp parse_condition(version, approximate? \\ false) do
case parse_version(version, approximate?) do
{ :ok, version } -> version
:error -> throw :invalid_matchspec
end
end
defp matchable_to_string({ major, minor, patch, pre }) do
patch = if patch, do: "#{patch}", else: "0"
pre = if pre != [], do: "-#{Enum.join(pre, ".")}"
"#{major}.#{minor}.#{patch}#{pre}"
end
end
end
@@ -519,13 +493,13 @@ defimpl Inspect, for: Version.Schema do
end
defimpl String.Chars, for: Version.Requirement do
def to_string({ _, source, _ }) do
def to_string(Version.Requirement[source: source]) do
source
end
end
defimpl Inspect, for: Version.Requirement do
def inspect({ _, source, _ }, _opts) do
def inspect(Version.Requirement[source: source], _opts) do
"#Version.Requirement<" <> source <> ">"
end
end
+1
View File
@@ -4,6 +4,7 @@ defmodule Elixir.Mixfile do
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" ]
+1 -3
View File
@@ -1,5 +1,3 @@
{erl_first_files, ["elixir_transform"]}.
{erl_opts, [
warn_unused_vars,
warn_export_all,
@@ -24,4 +22,4 @@
{verbose, false}
]}.
{require_otp_vsn,"(R16B|R17A).*"}.
{require_otp_vsn,"(17).*"}.
+8 -5
View File
@@ -21,8 +21,8 @@ start(_Type, _Args) ->
%% Set the shell to unicode so printing inside scripts work
%% Those can take a while, so let's do it in a new process
spawn(fun() ->
io:setopts(standard_io, [binary,{encoding,unicode}]),
io:setopts(standard_error, [binary,{encoding,unicode}])
io:setopts(standard_io, [binary,{encoding,utf8}]),
io:setopts(standard_error, [{unicode,true}])
end),
elixir_sup:start_link().
@@ -158,11 +158,11 @@ string_to_quoted(String, StartLine, File, Opts) when is_integer(StartLine), is_b
{ ok, _Line, Tokens } ->
try elixir_parser:parse(Tokens) of
{ ok, Forms } -> { ok, Forms };
{ error, { Line, _, [Error, Token] } } -> { error, { Line, Error, Token } }
{ error, { Line, _, [Error, Token] } } -> { error, { Line, to_binary(Error), to_binary(Token) } }
catch
{ error, { Line, _, [Error, Token] } } -> { error, { Line, Error, Token } }
{ error, { Line, _, [Error, Token] } } -> { error, { Line, to_binary(Error), to_binary(Token) } }
end;
{ error, Reason, _Rest, _SoFar } -> { error, Reason }
{ error, { Line, Error, Token }, _Rest, _SoFar } -> { error, { Line, to_binary(Error), to_binary(Token) } }
end.
'string_to_quoted!'(String, StartLine, File, Opts) ->
@@ -172,3 +172,6 @@ string_to_quoted(String, StartLine, File, Opts) when is_integer(StartLine), is_b
{ error, { Line, Error, Token } } ->
elixir_errors:parse_error(Line, File, Error, Token)
end.
to_binary(List) when is_list(List) -> elixir_utils:characters_to_binary(List);
to_binary(Atom) when is_atom(Atom) -> atom_to_binary(Atom, utf8).
+27 -6
View File
@@ -1,6 +1,5 @@
%% Handle translation of Elixir literals to Erlang AST.
-module(elixir_bitstring).
-export([translate/3, expand/3]).
-export([translate/3, expand/3, has_size/1]).
-include("elixir.hrl").
%% Expansion
@@ -102,6 +101,13 @@ handle_unknown_bit_info(Meta, { _, ExprMeta, _ } = Expr, T, Size, Types, E) ->
%% Translation
has_size({ bin, _, Elements }) ->
not lists:any(fun({ bin_element, _Line, _Expr, Size, Types }) ->
(Types /= default) andalso (Size == default) andalso
lists:any(fun(X) -> lists:member(X, Types) end,
[bits, bytes, bitstring, binary])
end, Elements).
translate(Meta, Args, S) ->
case S#elixir_scope.context of
match ->
@@ -168,15 +174,30 @@ types_require_conversion([UTF|T]) when UTF == utf8; UTF == utf16; UTF == utf32 -
types_require_conversion([]) -> true;
types_require_conversion(_) -> false.
types_allow_splice([bytes], Elements) -> lists:all(fun has_default_size/1, Elements);
types_allow_splice([binary], Elements) -> lists:all(fun has_default_size/1, Elements);
types_allow_splice([bytes], Elements) -> is_byte_size(Elements, 0);
types_allow_splice([binary], Elements) -> is_byte_size(Elements, 0);
types_allow_splice([bits], _) -> true;
types_allow_splice([bitstring], _) -> true;
types_allow_splice(default, _) -> true;
types_allow_splice(_, _) -> false.
has_default_size({ bin_element, _, _, default, _ }) -> true;
has_default_size({ bin_element, _, _, _, _ }) -> false.
is_byte_size([Element|T], Acc) ->
case elem_size(Element) of
{unknown, Unit} when Unit rem 8 == 0 -> is_byte_size(T, Acc);
{unknown, _Unit} -> false;
{Size, Unit} -> is_byte_size(T, Size*Unit + Acc)
end;
is_byte_size([], Size) ->
Size rem 8 == 0.
elem_size({bin_element, _, _, default, _}) -> {0, 0};
elem_size({bin_element, _, _, {integer,_,Size}, Types}) -> {Size, unit_size(Types, 1)};
elem_size({bin_element, _, _, _Size, Types}) -> {unknown, unit_size(Types, 1)}.
unit_size([binary|T], _) -> unit_size(T, 8);
unit_size([{unit, Size}|_], _) -> Size;
unit_size([_|T], Guess) -> unit_size(T, Guess);
unit_size([], Guess) -> Guess.
%% Extra bitstring specifiers
+14 -18
View File
@@ -1,7 +1,7 @@
%% Handle code related to args, guard and -> matching for case,
%% fn, receive and friends. try is handled in elixir_try.
-module(elixir_clauses).
-export([match/3, clause/6, clauses/3, get_pairs/2, get_pairs/3,
-export([match/3, clause/7, clauses/4, get_pairs/2, get_pairs/3,
extract_splat_guards/1, extract_guards/1]).
-include("elixir.hrl").
@@ -31,9 +31,9 @@ match(Fun, Args, S) -> Fun(Args, S).
%% Translate clauses with args, guards and expressions
clause(Line, Fun, Args, Expr, Guards, S) when is_integer(Line) ->
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(Expr, SA#elixir_scope{extra_guards=nil}),
{ 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,
@@ -66,22 +66,18 @@ extract_splat_guards(Else) ->
% Function for translating macros with match style like case and receive.
clauses(Meta, Clauses, #elixir_scope{export_vars=CV} = S) ->
{ TC, TS } = do_clauses(Meta, Clauses, S#elixir_scope{export_vars=[]}),
clauses(Meta, Clauses, Return, #elixir_scope{export_vars=CV} = S) ->
{ TC, TS } = do_clauses(Meta, Clauses, Return, S#elixir_scope{export_vars=[]}),
{ TC, TS#elixir_scope{export_vars=elixir_scope:merge_opt_vars(CV, TS#elixir_scope.export_vars)} }.
do_clauses(_Meta, [], S) ->
do_clauses(_Meta, [], _Return, S) ->
{ [], S };
% do_clauses(_Meta, [DecoupledClause], S) ->
% { TDecoupledClause, TS } = each_clause(DecoupledClause, S),
% { [TDecoupledClause], TS };
do_clauses(Meta, DecoupledClauses, S) ->
do_clauses(Meta, DecoupledClauses, Return, S) ->
% Transform tree just passing the variables counter forward
% and storing variables defined inside each clause.
Transformer = fun(X, {SAcc, VAcc}) ->
{ TX, TS } = each_clause(X, SAcc),
{ TX, TS } = each_clause(X, Return, SAcc),
{ TX,
{ elixir_scope:mergec(S, TS), [TS#elixir_scope.export_vars|VAcc] } }
end,
@@ -142,17 +138,17 @@ expand_clauses(_Line, [], [], _FinalVars, Acc, S) ->
% Handle each key/value clause pair and translate them accordingly.
each_clause({ do, Meta, [Condition], Expr }, S) ->
each_clause({ do, Meta, [Condition], Expr }, Return, S) ->
{ Arg, Guards } = extract_guards(Condition),
clause(?line(Meta), fun elixir_translator:translate_many/2, [Arg], Expr, Guards, S);
clause(?line(Meta), fun elixir_translator:translate_args/2, [Arg], Expr, Guards, Return, S);
each_clause({ else, Meta, [Condition], Expr }, S) ->
each_clause({ else, Meta, [Condition], Expr }, Return, S) ->
{ Arg, Guards } = extract_guards(Condition),
clause(?line(Meta), fun elixir_translator:translate_many/2, [Arg], Expr, Guards, S);
clause(?line(Meta), fun elixir_translator:translate_args/2, [Arg], Expr, Guards, Return, S);
each_clause({ 'after', Meta, [Condition], Expr }, S) ->
each_clause({ 'after', Meta, [Condition], Expr }, Return, S) ->
{ TCondition, SC } = elixir_translator:translate(Condition, S),
{ TExpr, SB } = elixir_translator:translate(Expr, SC),
{ 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.
+1 -1
View File
@@ -2,7 +2,7 @@
-export([call/1, cast/1]).
-export([start_link/0, init/1, handle_call/3, handle_cast/2,
handle_info/2, terminate/2, code_change/3]).
-behavior(gen_server).
-behaviour(gen_server).
-define(timeout, 30000).
-record(elixir_code_server, {
+8 -31
View File
@@ -1,6 +1,6 @@
-module(elixir_compiler).
-export([get_opt/1, string/2, quoted/2, file/1, file_to_path/2]).
-export([core/0, module/4, eval_forms/3, format_error/1]).
-export([core/0, module/4, eval_forms/3]).
-include("elixir.hrl").
%% Public API
@@ -137,15 +137,11 @@ module(Forms, File, Opts, Callback) ->
module(Forms, File, RawOptions, Bootstrap, Callback) when
is_binary(File), is_list(Forms), is_list(RawOptions), is_boolean(Bootstrap), is_function(Callback) ->
{ Options, SkipNative } = compiler_options(Forms, RawOptions),
Options = RawOptions ++ elixir_code_server:call(erl_compiler_options),
Listname = elixir_utils:characters_to_list(File),
case compile:noenv_forms([no_auto_import()|Forms], [return,{source,Listname}|Options]) of
{ok, ModuleName, Binary, RawWarnings} ->
Warnings = case SkipNative of
true -> [{?MODULE,[{0,?MODULE,{skip_native,ModuleName}}]}|RawWarnings];
false -> RawWarnings
end,
case compile:noenv_forms(Forms, [no_auto_import,return,{source,Listname}|Options]) of
{ok, ModuleName, Binary, Warnings} ->
format_warnings(Bootstrap, Warnings),
code:load_binary(ModuleName, Listname, Binary),
Callback(ModuleName, Binary);
@@ -154,23 +150,6 @@ module(Forms, File, RawOptions, Bootstrap, Callback) when
format_errors(Errors)
end.
compiler_options(Forms, Options) ->
EnvOptions = elixir_code_server:call(erl_compiler_options),
SkipNative = lists:member(native, EnvOptions) and contains_on_load(Forms),
case SkipNative or lists:member([{native,false}], Options) of
true -> { Options ++ lists:delete(native, EnvOptions), SkipNative };
false -> { Options ++ EnvOptions, SkipNative }
end.
contains_on_load([{ attribute, _, on_load, _ }|_]) -> true;
contains_on_load([_|T]) -> contains_on_load(T);
contains_on_load([]) -> false.
no_auto_import() ->
Bifs = [{ Name, Arity } || { Name, Arity } <- erlang:module_info(exports), erl_internal:bif(Name, Arity)],
{ attribute, 0, compile, { no_auto_import, Bifs } }.
%% CORE HANDLING
core() ->
@@ -186,7 +165,7 @@ core_file(File) ->
catch
Kind:Reason ->
io:format("~p: ~p~nstacktrace: ~p~n", [Kind, Reason, erlang:get_stacktrace()]),
exit(1)
erlang:halt(1)
end.
core_main() ->
@@ -196,6 +175,8 @@ core_main() ->
<<"lib/elixir/lib/module.ex">>,
<<"lib/elixir/lib/list.ex">>,
<<"lib/elixir/lib/record.ex">>,
<<"lib/elixir/lib/record/backend.ex">>,
<<"lib/elixir/lib/record/deprecated.ex">>,
<<"lib/elixir/lib/macro.ex">>,
<<"lib/elixir/lib/macro/env.ex">>,
<<"lib/elixir/lib/exception.ex">>,
@@ -210,6 +191,7 @@ core_main() ->
<<"lib/elixir/lib/regex.ex">>,
<<"lib/elixir/lib/string.ex">>,
<<"lib/elixir/lib/string/chars.ex">>,
<<"lib/elixir/lib/collectable.ex">>,
<<"lib/elixir/lib/io.ex">>,
<<"lib/elixir/lib/path.ex">>,
<<"lib/elixir/lib/system.ex">>,
@@ -217,7 +199,6 @@ core_main() ->
<<"lib/elixir/lib/kernel/cli.ex">>,
<<"lib/elixir/lib/kernel/error_handler.ex">>,
<<"lib/elixir/lib/kernel/parallel_compiler.ex">>,
<<"lib/elixir/lib/kernel/record_rewriter.ex">>,
<<"lib/elixir/lib/kernel/lexical_tracker.ex">>
].
@@ -228,10 +209,6 @@ binary_to_path({ModuleName, Binary}, CompilePath) ->
%% ERROR HANDLING
format_error({ skip_native, Module }) ->
io_lib:format("skipping native compilation for ~ts because it contains on_load attribute",
[elixir_aliases:inspect(Module)]).
format_errors([]) ->
exit({ nocompile, "compilation failed but no error was raised" });
+1 -1
View File
@@ -1,7 +1,7 @@
-module(elixir_counter).
-export([start_link/0, init/1, handle_call/3, handle_cast/2,
handle_info/2, terminate/2, code_change/3, next/0]).
-behavior(gen_server).
-behaviour(gen_server).
-define(timeout, 1000). %% 1 second
-define(limit, 4294967296). %% 2^32
+20 -11
View File
@@ -86,8 +86,7 @@ store_definition(Line, Kind, CheckClauses, Name, Args, Guards, Body, MetaFile, #
elixir_locals:record_definition(Tuple, Kind, Module),
Location = retrieve_location(Line, MetaFile, Module),
run_on_definition_callbacks(Kind, Line, Module, Name, Args, Guards, Body, E),
{ Function, Defaults, Super } = translate_definition(Kind, Line, Name, Args, Guards, Body, E),
{ Function, Defaults, Super } = translate_definition(Kind, Line, Module, Name, Args, Guards, Body, E),
DefaultsLength = length(Defaults),
elixir_locals:record_defaults(Tuple, Kind, Module, DefaultsLength),
@@ -104,6 +103,7 @@ store_definition(Line, Kind, CheckClauses, Name, Args, Guards, Body, MetaFile, #
[store_each(false, Kind, File, Location, Table, CTable, 0,
default_function_for(Kind, Name, Default)) || Default <- Defaults],
make_struct_available(Kind, Module, Name, Args),
{ Name, Arity }.
%% @on_definition
@@ -118,6 +118,14 @@ run_on_definition_callbacks(Kind, Line, Module, Name, Args, Guards, Expr, E) ->
[Mod:Fun(Env, Kind, Name, Args, Guards, Expr) || { Mod, Fun } <- Callbacks]
end.
make_struct_available(def, Module, '__struct__', []) ->
case erlang:get(elixir_compiler_pid) of
undefined -> ok;
Pid -> Pid ! { struct_available, Module }
end;
make_struct_available(_, _, _, _) ->
ok.
%% Retrieve location from meta file or @file, otherwise nil
retrieve_location(Line, File, Module) ->
@@ -144,24 +152,25 @@ compile_super(_Module, _, _E) -> ok.
%% Translate the given call and expression given
%% and then store it in memory.
translate_definition(Kind, Line, Name, Args, Guards, Body, E) when is_integer(Line) ->
translate_definition(Kind, Line, Module, Name, Args, Guards, Body, E) when is_integer(Line) ->
Arity = length(Args),
{ EArgs, EGuards, EBody, _ } = elixir_exp_clauses:def(fun elixir_def_defaults:expand/2,
Args, Guards, expr_from_body(Line, Body), E),
%% Macros receive a special argument on invocation. Notice it does
%% not affect the arity of the stored function, but the clause
%% already contains it.
AllArgs = case is_macro(Kind) of
true -> [{ '_@CALLER', [{line,Line}], nil }|Args];
false -> Args
EAllArgs = case is_macro(Kind) of
true -> [{ '_@CALLER', [{line,Line}], nil }|EArgs];
false -> EArgs
end,
{ EArgs, EGuards, EBody, _ } = elixir_exp_clauses:def(fun elixir_def_defaults:expand/2,
AllArgs, Guards, expr_from_body(Line, Body), E),
S = elixir_env:env_to_scope(E),
{ Unpacked, Defaults } = elixir_def_defaults:unpack(Kind, Name, EArgs, S),
{ 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 }.
@@ -171,7 +180,7 @@ translate_clause(nil, Line, Kind, _Args, _Guards, _Body, #elixir_scope{file=File
elixir_errors:form_error(Line, File, ?MODULE, { missing_do, Kind });
translate_clause(_, Line, Kind, Args, Guards, Body, S) ->
{ TClause, TS } = elixir_clauses:clause(Line,
fun elixir_translator:translate_many/2, Args, Body, Guards, S),
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
+5 -5
View File
@@ -5,10 +5,10 @@
expand(Args, E) ->
lists:mapfoldl(fun
({ '//', Meta, [Left, Right] }, Acc) ->
({ '\\\\', 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, [ELeft, ERight] }, EL };
(Left, Acc) ->
elixir_exp:expand(Left, Acc)
end, E, Args).
@@ -21,11 +21,11 @@ unpack(Kind, Name, Args, S) ->
%% Unpack default from given args.
%% Returns the given arguments without their default
%% clauses and a list of clauses for the default calls.
unpack_each(Kind, Name, [{'//', Line, [Expr, _]}|T] = List, Acc, Clauses, S) ->
unpack_each(Kind, Name, [{'\\\\', Line, [Expr, _]}|T] = List, Acc, Clauses, S) ->
Base = build_match(Acc, Line, []),
{ Args, Invoke } = extract_defaults(List, Line, length(Base), [], []),
{ DefArgs, SA } = elixir_clauses:match(fun elixir_translator:translate_many/2, Base ++ Args, S),
{ 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,
@@ -44,7 +44,7 @@ unpack_each(_Kind, _Name, [], Acc, Clauses, _S) ->
% Extract default values from args following the current default clause.
extract_defaults([{'//', _, [_Expr, Default]}|T], Line, Counter, NewArgs, NewInvoke) ->
extract_defaults([{'\\\\', _, [_Expr, Default]}|T], Line, Counter, NewArgs, NewInvoke) ->
extract_defaults(T, Line, Counter, NewArgs, [Default|NewInvoke]);
extract_defaults([_|T], Line, Counter, NewArgs, NewInvoke) ->
+170 -104
View File
@@ -9,14 +9,19 @@
find_import/4, format_error/1]).
-include("elixir.hrl").
-import(ordsets, [is_element/2]).
%% Module macros
-define(kernel, 'Elixir.Kernel').
-define(node, 'Elixir.Node').
-define(process, 'Elixir.Process').
-define(system, 'Elixir.System').
default_functions() ->
[ { ?kernel, elixir_imported_functions() } ].
default_macros() ->
[ { ?kernel, elixir_imported_macros() } ].
default_requires() ->
[ 'Elixir.Integer', 'Elixir.Kernel', 'Elixir.Kernel.Typespec', 'Elixir.Record' ].
[ 'Elixir.Kernel', 'Elixir.Kernel.Typespec' ].
find_import(Meta, Name, Arity, E) ->
Tuple = { Name, Arity },
@@ -64,13 +69,10 @@ require_function(_Meta, Receiver, Name, Arity, E) ->
remote_function(Receiver, Name, Arity, E) ->
elixir_lexical:record_remote(Receiver, E#elixir_env.lexical_tracker),
Tuple = { Name, Arity },
Final =
case (Receiver == ?kernel) andalso is_element(Tuple, in_erlang_functions()) of
true -> erlang;
false -> Receiver
end,
{ remote, Final, Name, Arity }.
case inline(Receiver, Name, Arity) of
{ AR, AN } -> { remote, AR, AN, Arity };
false -> { remote, Receiver, Name, Arity }
end.
%% Dispatches
@@ -87,19 +89,19 @@ dispatch_import(Meta, Name, Args, E, Callback) ->
dispatch_require(Meta, Receiver, Name, Args, E, Callback) when is_atom(Receiver) ->
Arity = length(Args),
Tuple = { Name, Arity },
case (Receiver == ?kernel) andalso is_element(Tuple, in_erlang_functions()) of
true -> Callback(erlang);
case rewrite(Receiver, Name, Args, Arity) of
{ ok, AR, AN, AA } ->
Callback(AR, AN, AA);
false ->
case expand_require(Meta, Receiver, Tuple, Args, E) of
case expand_require(Meta, Receiver, { Name, Arity }, Args, E) of
{ ok, Receiver, Quoted } -> expand_quoted(Meta, Receiver, Name, Arity, Quoted, E);
error -> Callback(Receiver)
error -> Callback(Receiver, Name, Args)
end
end;
dispatch_require(_Meta, Receiver, _Name, _Args, _E, Callback) ->
Callback(Receiver).
dispatch_require(_Meta, Receiver, Name, Args, _E, Callback) ->
Callback(Receiver, Name, Args).
%% Macros expansion
@@ -111,13 +113,13 @@ expand_import(Meta, { Name, Arity } = Tuple, Args, E, Extra) ->
elixir_locals:macro_for(Module, Name, Arity),
case Dispatch of
%% In case it is an import, or the receive is the same as the
%% current module (happens in bootstraping), we dispatch the import.
{ Kind, Receiver } when Kind == import; Receiver == Module ->
%% In case it is an import, we dispatch the import.
{ import, _ } ->
do_expand_import(Meta, Tuple, Args, Module, E, Dispatch);
%% There is a local and an import. This is a conflict.
{ _, Receiver } when Local /= false ->
%% 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);
@@ -136,11 +138,12 @@ do_expand_import(Meta, { Name, Arity } = Tuple, Args, Module, E, Result) ->
{ function, Receiver } ->
elixir_lexical:record_import(Receiver, E#elixir_env.lexical_tracker),
elixir_locals:record_import(Tuple, Receiver, Module, E#elixir_env.function),
Endpoint = case (Receiver == ?kernel) andalso is_element(Tuple, in_erlang_functions()) of
true -> erlang;
false -> Receiver
end,
{ ok, Endpoint };
case rewrite(Receiver, Name, Args, Arity) of
{ ok, AR, AN, AA } ->
{ ok, AR, { { '.', [], [AR, AN] }, [], AA } };
false ->
{ 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),
@@ -150,6 +153,13 @@ do_expand_import(Meta, { Name, Arity } = Tuple, Args, Module, E, Result) ->
{ 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 } };
false ->
error
end;
false ->
error
end.
@@ -303,7 +313,7 @@ elixir_imported_functions() ->
try
?kernel:'__info__'(functions)
catch
error:undef -> in_erlang_functions()
error:undef -> []
end.
elixir_imported_macros() ->
@@ -313,81 +323,137 @@ elixir_imported_macros() ->
error:undef -> []
end.
%% Functions imported from Erlang module. MUST BE SORTED.
%% TODO: Inline operators here as well once bug in erl_eval is fixed.
in_erlang_functions() ->
[
{ abs, 1 },
{ apply, 2 },
{ apply, 3 },
{ atom_to_list, 1 },
{ binary_part, 3 },
{ binary_to_float, 1 },
{ binary_to_float, 2 },
{ binary_to_integer, 1 },
{ binary_to_integer, 2 },
{ binary_to_term, 1 },
{ binary_to_term, 2 },
{ bit_size, 1 },
{ bitstring_to_list, 1 },
{ byte_size, 1 },
% { date, 0 },
{ exit, 1 },
% { float, 1 },
{ float_to_binary, 1 },
% { float_to_binary, 2 },
{ float_to_list, 1 },
% { float_to_list, 2 },
{ hd, 1 },
{ integer_to_binary, 1 },
{ integer_to_binary, 2 },
{ integer_to_list, 1 },
{ integer_to_list, 2 },
{ iolist_size, 1 },
{ iolist_to_binary, 1 },
{ is_atom, 1 },
{ is_binary, 1 },
{ is_bitstring, 1 },
{ is_boolean, 1 },
{ is_float, 1 },
{ is_function, 1 },
{ is_function, 2 },
{ is_integer, 1 },
{ is_list, 1 },
{ is_number, 1 },
{ is_pid, 1 },
{ is_port, 1 },
{ is_reference, 1 },
{ is_tuple, 1 },
{ length, 1 },
{ list_to_atom, 1 },
{ list_to_bitstring, 1 },
{ list_to_existing_atom, 1 },
{ list_to_float, 1 },
{ list_to_integer, 1 },
{ list_to_integer, 2 },
{ list_to_tuple, 1 },
{ make_ref, 0 },
{ max, 2 },
{ min, 2 },
{ node, 0 },
{ node, 1 },
% { now, 0 },
{ round, 1 },
{ self, 0 },
{ send, 2 },
{ size, 1 },
{ spawn, 1 },
{ spawn, 3 },
{ spawn_link, 1 },
{ spawn_link, 3 },
% { split_binary, 2 },
{ term_to_binary, 1 },
{ term_to_binary, 2 },
{ throw, 1 },
% { time, 0 },
{ tl, 1 },
{ trunc, 1 },
{ tuple_size, 1 },
{ tuple_to_list, 1 }
].
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(Receiver, Name, Args, Arity) ->
case inline(Receiver, Name, Arity) of
{ AR, AN } -> { ok, AR, AN, Args };
false -> false
end.
increment(Number) when is_number(Number) ->
Number + 1;
increment(Other) ->
{ { '.', [], [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(?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(?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(_, _, _) -> false.
+2 -2
View File
@@ -36,8 +36,8 @@ mergev(E1, E2) ->
}.
%% Receives two scopes and return the later scope
%% keeping the variables from the first (counters,
%% imports and everything else are passed forward).
%% keeping the variables from the first (imports
%% and everything else are passed forward).
mergea(E1, E2) ->
E2#elixir_env{vars=E1#elixir_env.vars}.
+27 -31
View File
@@ -2,11 +2,13 @@
% This is not exposed in the Elixir language.
-module(elixir_errors).
-export([compile_error/3, compile_error/4,
form_error/4, parse_error/4, warn/1,
form_error/4, parse_error/4, warn/2, warn/3,
handle_file_warning/2, handle_file_warning/3, handle_file_error/2,
deprecation/3, deprecation/4]).
-include("elixir.hrl").
-type line_or_meta() :: integer() | list().
warn(Warning) ->
CompilerPid = get(elixir_compiler_pid),
if
@@ -16,15 +18,21 @@ warn(Warning) ->
end,
io:put_chars(standard_error, Warning).
warn(Caller, Warning) ->
warn([Caller, "warning: ", Warning]).
warn(Line, File, Warning) ->
warn(file_format(Line, File, "warning: " ++ Warning)).
%% Raised during expansion/translation/compilation.
-spec form_error(list(), binary(), module(), any()) -> no_return().
-spec form_error(line_or_meta(), binary(), module(), any()) -> no_return().
form_error(Meta, File, Module, Desc) ->
compile_error(Meta, File, format_error(Module, Desc)).
-spec compile_error(list(), binary(), iolist()) -> no_return().
-spec compile_error(list(), binary(), iolist(), list()) -> no_return().
-spec compile_error(line_or_meta(), binary(), iolist()) -> no_return().
-spec compile_error(line_or_meta(), binary(), iolist(), list()) -> no_return().
compile_error(Meta, File, Message) when is_list(Message) ->
raise(Meta, File, 'Elixir.CompileError', elixir_utils:characters_to_binary(Message)).
@@ -34,30 +42,20 @@ compile_error(Meta, File, Format, Args) when is_list(Format) ->
%% Raised on tokenizing/parsing
-spec parse_error(non_neg_integer() | list(), file:filename_all(), iolist() | atom(), [] | iolist()) -> no_return().
-spec parse_error(line_or_meta(), binary(), iolist() | atom(), [] | iolist()) -> no_return().
parse_error(Meta, File, Error, []) ->
parse_error(Meta, File, Error, <<>>) ->
Message = case Error of
"syntax error before: " -> <<"syntax error: expression is incomplete">>;
_ -> iolist_to_binary(Error)
<<"syntax error before: ">> -> <<"syntax error: expression is incomplete">>;
_ -> Error
end,
raise(Meta, File, 'Elixir.TokenMissingError', Message);
parse_error(Meta, File, "syntax error before: ", "'end'") ->
parse_error(Meta, File, <<"syntax error before: ">>, <<"'end'">>) ->
raise(Meta, File, 'Elixir.SyntaxError', <<"unexpected token: end">>);
parse_error(Meta, File, Error, Token) ->
BinError = if
is_atom(Error) -> atom_to_binary(Error, utf8);
true -> iolist_to_binary(Error)
end,
BinToken = if
Token == [] -> <<>>;
true -> elixir_utils:characters_to_binary(Token)
end,
Message = <<BinError / binary, BinToken / binary >>,
parse_error(Meta, File, Error, Token) when is_binary(Error), is_binary(Token) ->
Message = <<Error / binary, Token / binary >>,
raise(Meta, File, 'Elixir.SyntaxError', Message).
%% Shows a deprecation message
@@ -93,7 +91,7 @@ handle_file_warning(_, File, { Line, sys_core_fold, { no_effect, { erlang, F, A
end
end,
Message = io_lib:format(Fmt, Args),
warn(file_format(Line, File, Message));
warn(Line, File, Message);
%% Rewrite undefined behaviour to check for protocols
handle_file_warning(_, File, {Line,erl_lint,{undefined_behaviour_func,{Fun,Arity},Module}}) ->
@@ -106,14 +104,14 @@ handle_file_warning(_, File, {Line,erl_lint,{undefined_behaviour_func,{Fun,Arity
Kind = protocol_or_behaviour(Module),
Raw = "undefined ~ts ~ts ~ts/~B (for ~ts ~ts)",
Message = io_lib:format(Raw, [Kind, DefKind, Def, DefArity, Kind, elixir_aliases:inspect(Module)]),
warn(file_format(Line, File, Message));
warn(Line, File, Message);
handle_file_warning(_, File, {Line,erl_lint,{undefined_behaviour,Module}}) ->
case elixir_compiler:get_opt(internal) of
true -> [];
false ->
Message = io_lib:format("behaviour ~ts undefined", [elixir_aliases:inspect(Module)]),
warn(file_format(Line, File, Message))
warn(Line, File, Message)
end;
%% Ignore unused vars at "weird" lines (<= 0)
@@ -127,12 +125,12 @@ 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) }),
warn(file_format(Line, File, Message));
warn(Line, File, Message);
%% Default behavior
%% Default behaviour
handle_file_warning(_, File, {Line,Module,Desc}) ->
Message = format_error(Module, Desc),
warn(file_format(Line, File, Message)).
warn(Line, File, Message).
handle_file_warning(File, Desc) ->
handle_file_warning(false, File, Desc).
@@ -198,10 +196,8 @@ protocol_or_behaviour(Module) ->
is_protocol(Module) ->
case code:ensure_loaded(Module) of
{ module, _ } ->
case erlang:function_exported(Module, '__protocol__', 1) of
true -> Module:'__protocol__'(name) == Module;
false -> false
end;
erlang:function_exported(Module, '__protocol__', 1) andalso
Module:'__protocol__'(name) == Module;
{ error, _ } ->
false
end.
+24 -16
View File
@@ -1,5 +1,5 @@
-module(elixir_exp).
-export([expand/2, expand_many/2, expand_args/2, expand_arg/2]).
-export([expand/2, expand_args/2, expand_arg/2]).
-import(elixir_errors, [compile_error/3, compile_error/4]).
-include("elixir.hrl").
@@ -17,6 +17,12 @@ expand({ '{}', Meta, Args }, E) ->
{ EArgs, EA } = expand_args(Args, E),
{ { '{}', Meta, EArgs }, EA };
expand({ '%{}', Meta, Args }, E) ->
elixir_map:expand_map(Meta, Args, E);
expand({ '%', Meta, [Left, Right] }, E) ->
elixir_map:expand_struct(Meta, Left, Right, E);
expand({ '<<>>', Meta, Args }, E) ->
elixir_bitstring:expand(Meta, Args, E);
@@ -79,7 +85,8 @@ expand({ alias, Meta, [Ref, KV] }, E) ->
expand_alias(Meta, true, ERef, EKV, ET) };
true ->
compile_error(Meta, E#elixir_env.file,
"invalid arguments for alias, expected a compile time atom or alias as argument")
"invalid argument for alias, expected a compile time atom or alias, got: ~ts",
['Elixir.Kernel':inspect(ERef)])
end;
expand({ require, Meta, [Ref] }, E) ->
@@ -97,7 +104,8 @@ expand({ require, Meta, [Ref, KV] }, E) ->
expand_require(Meta, ERef, EKV, ET) };
true ->
compile_error(Meta, E#elixir_env.file,
"invalid arguments for require, expected a compile time atom or alias as argument")
"invalid argument for require, expected a compile time atom or alias, got: ~ts",
['Elixir.Kernel':inspect(ERef)])
end;
expand({ import, Meta, [Left] }, E) ->
@@ -115,16 +123,15 @@ expand({ import, Meta, [Ref, KV] }, E) ->
{ { import, Meta, [ERef, EKV] },
expand_require(Meta, ERef, EKV, ET#elixir_env{functions=Functions, macros=Macros}) };
true ->
compile_error(Meta, E#elixir_env.file, "invalid name for import, expected a compile time atom or alias")
compile_error(Meta, E#elixir_env.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({ '__FILE__', Meta, Atom }, E) when is_atom(Atom) ->
elixir_errors:deprecation(Meta, E#elixir_env.file, "__FILE__ is deprecated, please use __DIR__ or __ENV__.file instead"),
{ E#elixir_env.file, 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) ->
@@ -175,8 +182,9 @@ expand({ quote, Meta, [KV, Do] }, E) when is_list(Do) ->
Context = case lists:keyfind(context, 1, EKV) of
{ context, Atom } when is_atom(Atom) ->
Atom;
{ context, _ } ->
compile_error(Meta, E#elixir_env.file, "invalid :context for quote, expected a compile time atom or an alias");
{ context, Ctx } ->
compile_error(Meta, E#elixir_env.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
nil -> 'Elixir';
@@ -250,6 +258,9 @@ expand({'try', Meta, [KV]}, E) ->
expand({ Kind, Meta, Args }, E) when is_list(Args), (Kind == lc) orelse (Kind == bc) ->
expand_comprehension(Meta, Kind, Args, E);
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) ->
@@ -296,9 +307,6 @@ expand({ Name, Meta, Kind } = Var, #elixir_env{vars=Vars} = E) when is_atom(Name
compile_error(Meta, E#elixir_env.file, "expected var ~ts~ts to expand to an existing "
"variable or be a part of a match", [Name, Extra]);
E#elixir_env.context == guard ->
compile_error(Meta, E#elixir_env.file, "unknown variable ~ts or cannot invoke "
"function ~ts/0 inside guard", [Name, Name]);
true ->
expand({ Name, Meta, [] }, E)
end
@@ -319,8 +327,8 @@ 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),
elixir_dispatch:dispatch_require(Meta, ELeft, Right, Args, EL, fun(Receiver) ->
expand_remote(Receiver, DotMeta, Right, Meta, Args, E, EL)
elixir_dispatch:dispatch_require(Meta, ELeft, Right, Args, EL, fun(AR, AF, AA) ->
expand_remote(AR, DotMeta, AF, Meta, AA, E, EL)
end);
%% Anonymous calls
@@ -402,14 +410,14 @@ expand_many(Args, E) ->
%% 3
%%
%% However, lexical information is.
expand_arg(Arg, Acc) when is_number(Arg); is_atom(Arg); is_binary(Arg); is_pid(Arg); is_function(Arg) ->
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) } }.
expand_args(Args, #elixir_env{context=match} = E) ->
lists:mapfoldl(fun expand/2, E, Args);
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) }.

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