Compare commits

...
323 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
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 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 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
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 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 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
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
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 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
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 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
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 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 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
José Valim 8e5fec95fa Automatically recompile on outdated elixir version 2014-02-12 14:19:36 +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 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 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
José Valim ea34b7a302 Only raise on conflicts coming from functions 2014-02-09 21:20:24 +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
José Valim 7e021d9bbb Ensure deps can run after mix deps.get 2014-02-08 20:02:22 +01:00
Eric Meadows-Jönsson 4a4a407644 Fix dependency compilation order 2014-02-08 19:41:50 +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 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 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
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
José Valim f3aad51922 Add a test to ensure we can still inspect "outdated" functions 2014-02-06 21:11:38 +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 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 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 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
254 changed files with 10520 additions and 6439 deletions
+1 -6
View File
@@ -6,9 +6,4 @@ notifications:
- jose.valim@plataformatec.com.br
- eric.meadows.jonsson@gmail.com
otp_release:
- 17.0-rc1
- R16B03-1
- R16B03
- R16B02
- R16B01
- R16B
- 17.0
+49
View File
@@ -1,3 +1,52 @@
# 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
+3 -7
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
@@ -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.5
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 %*
+3 -2
View File
@@ -16,13 +16,14 @@ if [ $# -gt 0 ] && ([ "$1" = "--help" ] || [ "$1" = "-h" ]); then
--cookie \"cookie\" Sets a cookie for this distributed node
--hidden Makes a hidden node
--detached Starts the Erlang VM detached from console
--gen-debug Turns on default debugging for all GenServers
--remsh \"name\" Connects to a node using a remote shell
--dot-iex \"path\" Overrides default .iex.exs file and uses path instead;
path can be empty, then no file will be loaded
** Options marked with (*) can be given more than once
** Options given after the .exs file or -- are passed down to the executed code
** Options can be passed to the erlang runtime using ELIXIR_ERL_OPTS or --erl" >&2
** Options can be passed to the VM using ELIXIR_ERL_OPTS or --erl" >&2
exit 1
fi
@@ -38,4 +39,4 @@ readlink_f () {
SELF=$(readlink_f "$0")
SCRIPT_PATH=$(dirname "$SELF")
ELIXIR_NO_CLI=1 exec "$SCRIPT_PATH"/elixir --no-halt --erl "-user Elixir.IEx.CLI" +iex "$@"
exec "$SCRIPT_PATH"/elixir --no-halt --erl "-user Elixir.IEx.CLI" +iex "$@"
+1 -1
View File
@@ -1,2 +1,2 @@
@echo off
call "%~dp0\elixir.bat" +iex --no-halt -e "IEx.start" %*
call "%~dp0\elixir.bat" +iex --erl "-user Elixir.IEx.CLI" --no-halt %
+15 -16
View File
@@ -5,8 +5,8 @@ defmodule EEx do
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,12 +97,12 @@ 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
@@ -177,8 +177,8 @@ defmodule EEx do
## Examples
EEx.eval_string "foo <%= bar %>", [bar: "baz"]
#=> "foo baz"
iex> EEx.eval_string "foo <%= bar %>", [bar: "baz"]
"foo baz"
"""
def eval_string(source, bindings \\ [], options \\ []) do
@@ -195,8 +195,7 @@ defmodule EEx do
foo <%= bar %>
# iex
EEx.eval_file "sample.ex", [bar: "baz"]
#=> "foo baz"
EEx.eval_file "sample.ex", [bar: "baz"] #=> "foo baz"
"""
def eval_file(filename, bindings \\ [], options \\ []) do
+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
+18 -5
View File
@@ -1,7 +1,11 @@
defmodule EEx.Engine do
@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
+8 -4
View File
@@ -15,6 +15,10 @@ defmodule EEx.TransformerEngine do
quote do
@behaviour EEx.Engine
def handle_body(body) do
EEx.Engine.handle_body(body)
end
def handle_text(buffer, text) do
EEx.Engine.handle_text(buffer, text)
end
@@ -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
+4 -5
View File
@@ -7,17 +7,16 @@ defmodule EEx.SmartEngineTest do
assert_eval "foo bar", "foo bar"
end
test "evaluates with assigns as a ListDict" do
test "evaluates with assigns as keywords" do
assert_eval "1", "<%= @foo %>", assigns: [foo: 1]
end
test "evaluates with assigns as a HashDict" do
assigns = HashDict.new [foo: 1]
assert_eval "1", "<%= @foo %>", assigns: assigns
test "evaluates with assigns as a map" do
assert_eval "1", "<%= @foo %>", assigns: %{foo: 1}
end
test "evaluates with loops" do
assert_eval "1\n2\n3\n", "<%= lc x inlist [1, 2, 3] do %><%= x %>\n<% end %>"
assert_eval "1\n2\n3\n", "<%= for x <- [1, 2, 3] do %><%= x %>\n<% end %>"
end
defp assert_eval(expected, actual, binding \\ []) do
+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
+2 -1
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
+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
-41
View File
@@ -48,47 +48,6 @@ 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
+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
+23 -84
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,17 +104,12 @@ defmodule Behaviour do
end
quote do
docs_args = unquote(Macro.escape(docs_args))
@callback unquote(name)(unquote_splicing(args)) :: unquote(return) when unquote(guards)
Behaviour.store_docs(__MODULE__, unquote(caller.line), unquote(kind),
unquote(docs_name), docs_args, unquote(docs_arity))
unquote(docs_name), unquote(docs_arity))
end
end
defp ensure_not_default({ ://, _, [_, _] }) do
raise ArgumentError, message: "default arguments // not supported in defcallback/defmacrocallback"
end
defp ensure_not_default({ :\\, _, [_, _] }) do
raise ArgumentError, message: "default arguments \\\\ not supported in defcallback/defmacrocallback"
end
@@ -129,61 +117,12 @@ defmodule Behaviour do
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
"""
+78 -80
View File
@@ -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
@@ -118,15 +118,12 @@ defmodule Code do
def eval_string(string, binding \\ [], opts \\ [])
def eval_string(string, binding, Macro.Env[] = env) do
do_eval_string(string, binding, env.to_keywords)
{ 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, 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.
@@ -228,17 +222,12 @@ defmodule Code do
def string_to_quoted(string, opts \\ []) when is_list(opts) do
file = Keyword.get opts, :file, "nofile"
line = Keyword.get opts, :line, 1
res = :elixir.string_to_quoted(to_char_list(string), line, file, opts)
case res do
{ :ok, forms } -> { :ok, forms }
_ -> res
end
:elixir.string_to_quoted(to_char_list(string), line, file, opts)
end
@doc """
Convert the given string to its quoted form.
Convert the given string to its quoted form.
It returns the ast if it succeeds,
raises an exception otherwise. The exception is a `TokenMissingError`
in case a token is missing (usually because the expression is incomplete),
@@ -253,18 +242,31 @@ defmodule Code do
end
@doc """
Load the given file.
Accepts `relative_to` as an argument to tell where
the file is located. If the file was already required/loaded, loads it again.
It returns a list of tuples `{ ModuleName, <<byte_code>> }`, one tuple for each
module defined in the file.
Evals the given file.
Notice that if `load_file` is invoked by different processes
concurrently, the target file will be invoked concurrently
many times. I.e. if `load_file` is called N times with
a given file, the given file will be loaded N times. Check
`require_file/2` if you don't want a file to be loaded concurrently.
Accepts `relative_to` as an argument to tell where the file is located.
While `load_file` loads a file and returns the loaded modules and their
byte code, `eval_file` simply evalutes the file contents and returns the
evaluation result and its bindings.
"""
def eval_file(file, relative_to \\ nil) do
file = find_file(file, relative_to)
eval_string File.read!(file), [], []
end
@doc """
Load the given file.
Accepts `relative_to` as an argument to tell where the file is located.
If the file was already required/loaded, loads it again.
It returns a list of tuples `{ ModuleName, <<byte_code>> }`, one tuple for
each module defined in the file.
Notice that if `load_file` is invoked by different processes concurrently,
the target file will be loaded concurrently many times. Check `require_file/2`
if you don't want a file to be loaded concurrently.
"""
def load_file(file, relative_to \\ nil) when is_binary(file) do
file = find_file(file, relative_to)
@@ -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,7 +289,7 @@ defmodule Code do
N times with a given file, it will be loaded only once. The first process to
call `require_file` will get the list of loaded modules, others will get `nil`.
Check `load_file/2` if you want a file to be loaded concurrently.
Check `load_file/2` if you want a file to be loaded multiple times.
"""
def require_file(file, relative_to \\ nil) when is_binary(file) do
file = find_file(file, relative_to)
@@ -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,11 +340,10 @@ 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`.
"""
@@ -351,26 +352,23 @@ defmodule Code do
end
@doc """
Compile the quoted expression.
Returns a list of tuples where
the first element is the module name and the second one is its
binary.
Compiles the quoted expression.
Returns a list of tuples where the first element is the module name and
the second one is its byte code (as a binary).
"""
def compile_quoted(quoted, file \\ "nofile") when is_binary(file) do
:elixir_compiler.quoted quoted, file
end
@doc """
Ensure the given module is loaded.
If the module is already
loaded, this works as no-op. If the module was not yet loaded,
it tries to load it.
Ensures the given module is loaded.
If it succeeds loading the module, it returns
`{ :module, module }`. If not, returns `{ :error, reason }` with
the error reason.
If the module is already loaded, this works as no-op. If the module
was not yet loaded, it tries to load it.
If it succeeds loading the module, it returns `{ :module, module }`.
If not, returns `{ :error, reason }` with the error reason.
## Code loading on the Erlang VM
@@ -385,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
+88 -105
View File
@@ -12,7 +12,7 @@ defmodule Dict do
HashDict.new #=> creates an empty HashDict
In the examples below, `dict_impl` means a specific
`Dict` implementation, for example `HashDict` or `ListDict`.
`Dict` implementation, for example `HashDict` or `Map`.
## Protocols
@@ -21,15 +21,11 @@ defmodule Dict do
protocol:
iex> dict = dict_impl.new
...> dict = Dict.put(dict, :hello, :world)
...> dict[:hello]
iex> dict = Dict.put(dict, :hello, :world)
iex> dict[:hello]
:world
And the `Enumerable` protocol, allowing one to write:
Enum.each(dict, fn ({ k, v }) ->
IO.puts "#{k}: #{v}"
end)
As well as the `Enumerable` and `Collectable` protocols.
## Match
@@ -41,11 +37,9 @@ defmodule Dict do
@type key :: any
@type value :: any
@type t :: tuple | list
@type t :: tuple | list | map
defcallback new :: t
defcallback new(Enum.t) :: t
defcallback new(Enum.t, (any -> { key, value })) :: t
defcallback delete(t, key) :: t
defcallback drop(t, Enum.t) :: t
defcallback empty(t) :: t
@@ -62,7 +56,6 @@ defmodule Dict do
defcallback pop(t, key, value) :: {value, t}
defcallback put(t, key, value) :: t
defcallback put_new(t, key, value) :: t
defcallback reduce(t, Enumerable.acc, Enumerable.reducer) :: Enumerable.result
defcallback size(t) :: non_neg_integer()
defcallback split(t, Enum.t) :: {t, t}
defcallback take(t, Enum.t) :: t
@@ -73,13 +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)
@@ -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
"""
@@ -270,18 +265,18 @@ defmodule Dict do
## Examples
iex> d1 = dict_impl.new([a: 1, b: 2])
...> d2 = dict_impl.new([a: 3, d: 4])
...> d = Dict.merge(d1, d2)
...> [a: Dict.get(d, :a), b: Dict.get(d, :b), d: Dict.get(d, :d)]
iex> d1 = Enum.into([a: 1, b: 2], dict_impl.new)
iex> d2 = Enum.into([a: 3, d: 4], dict_impl.new)
iex> d = Dict.merge(d1, d2)
iex> [a: Dict.get(d, :a), b: Dict.get(d, :b), d: Dict.get(d, :d)]
[a: 3, b: 2, d: 4]
iex> d1 = dict_impl.new([a: 1, b: 2])
...> d2 = dict_impl.new([a: 3, d: 4])
...> d = Dict.merge(d1, d2, fn(_k, v1, v2) ->
iex> d1 = Enum.into([a: 1, b: 2], dict_impl.new)
iex> d2 = Enum.into([a: 3, d: 4], dict_impl.new)
iex> d = Dict.merge(d1, d2, fn(_k, v1, v2) ->
...> v1 + v2
...> end)
...> [a: Dict.get(d, :a), b: Dict.get(d, :b), d: Dict.get(d, :d)]
iex> [a: Dict.get(d, :a), b: Dict.get(d, :b), d: Dict.get(d, :d)]
[a: 4, b: 2, d: 4]
"""
@@ -293,7 +288,7 @@ defmodule Dict do
if target1 == target2 do
target1.merge(dict1, dict2, fun)
else
target2.reduce(dict2, { :cont, dict1 }, fn({ k, v }, acc) ->
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
@@ -305,19 +300,19 @@ defmodule Dict do
## Examples
iex> dict = dict_impl.new [a: 1]
...> {v, d} = Dict.pop dict, :a
...> {v, Enum.sort(d)}
iex> dict = Enum.into([a: 1], dict_impl.new)
iex> {v, d} = Dict.pop dict, :a
iex> {v, Enum.sort(d)}
{1,[]}
iex> dict = dict_impl.new [a: 1]
...> {v, d} = Dict.pop dict, :b
...> {v, Enum.sort(d)}
iex> dict = Enum.into([a: 1], dict_impl.new)
iex> {v, d} = Dict.pop dict, :b
iex> {v, Enum.sort(d)}
{nil,[a: 1]}
iex> dict = dict_impl.new [a: 1]
...> {v, d} = Dict.pop dict, :b, 3
...> {v, Enum.sort(d)}
iex> dict = Enum.into([a: 1], dict_impl.new)
iex> {v, d} = Dict.pop dict, :b, 3
iex> {v, Enum.sort(d)}
{3,[a: 1]}
"""
@@ -332,9 +327,9 @@ defmodule Dict do
## Examples
iex> d = dict_impl.new([a: 1, b: 2])
...> d = Dict.update!(d, :a, fn(val) -> -val end)
...> Dict.get(d, :a)
iex> d = Enum.into([a: 1, b: 2], dict_impl.new)
iex> d = Dict.update!(d, :a, fn(val) -> -val end)
iex> Dict.get(d, :a)
-1
"""
@@ -350,9 +345,9 @@ defmodule Dict do
## Examples
iex> d = dict_impl.new([a: 1, b: 2])
...> d = Dict.update(d, :c, 3, fn(val) -> -val end)
...> Dict.get(d, :c)
iex> d = Enum.into([a: 1, b: 2], dict_impl.new)
iex> d = Dict.update(d, :c, 3, fn(val) -> -val end)
iex> Dict.get(d, :c)
3
"""
@@ -370,19 +365,19 @@ defmodule Dict do
## Examples
iex> d = dict_impl.new([a: 1, b: 2, c: 3, d: 4])
...> { d1, d2 } = Dict.split(d, [:a, :c, :e])
...> { Dict.to_list(d1) |> Enum.sort, Dict.to_list(d2) |> Enum.sort }
iex> d = Enum.into([a: 1, b: 2, c: 3, d: 4], dict_impl.new)
iex> { d1, d2 } = Dict.split(d, [:a, :c, :e])
iex> { Dict.to_list(d1) |> Enum.sort, Dict.to_list(d2) |> Enum.sort }
{ [a: 1, c: 3], [b: 2, d: 4] }
iex> d = dict_impl.new([])
...> { d1, d2 } = Dict.split(d, [:a, :c])
...> { Dict.to_list(d1), Dict.to_list(d2) }
iex> d = Enum.into([], dict_impl.new)
iex> { d1, d2 } = Dict.split(d, [:a, :c])
iex> { Dict.to_list(d1), Dict.to_list(d2) }
{ [], [] }
iex> d = dict_impl.new([a: 1, b: 2])
...> { d1, d2 } = Dict.split(d, [:a, :b, :c])
...> { Dict.to_list(d1) |> Enum.sort, Dict.to_list(d2) }
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], [] }
"""
@@ -397,14 +392,14 @@ defmodule Dict do
## Examples
iex> d = dict_impl.new([a: 1, b: 2])
...> d = Dict.drop(d, [:a, :c, :d])
...> Dict.to_list(d)
iex> d = Enum.into([a: 1, b: 2], dict_impl.new)
iex> d = Dict.drop(d, [:a, :c, :d])
iex> Dict.to_list(d)
[b: 2]
iex> d = dict_impl.new([a: 1, b: 2])
...> d = Dict.drop(d, [:c, :d])
...> Dict.to_list(d) |> Enum.sort
iex> d = Enum.into([a: 1, b: 2], dict_impl.new)
iex> d = Dict.drop(d, [:c, :d])
iex> Dict.to_list(d) |> Enum.sort
[a: 1, b: 2]
"""
@@ -420,14 +415,12 @@ defmodule Dict do
## Examples
iex> d = dict_impl.new([a: 1, b: 2])
...>
...> d = Dict.take(d, [:a, :c, :d])
...> Dict.to_list(d)
iex> d = Enum.into([a: 1, b: 2], dict_impl.new)
iex> d = Dict.take(d, [:a, :c, :d])
iex> Dict.to_list(d)
[a: 1]
...>
...> d = Dict.take(d, [:c, :d])
...> Dict.to_list(d)
iex> d = Dict.take(d, [:c, :d])
iex> Dict.to_list(d)
[]
"""
@@ -436,17 +429,7 @@ defmodule Dict do
target(dict).take(dict, keys)
end
@doc """
Returns an empty dict of the same type as `dict`.
## Examples
iex> d = dict_impl.new([a: 1, b: 2])
...> e = Dict.empty(d)
...> Dict.to_list(e)
[]
"""
@doc false
@spec empty(t) :: t
def empty(dict) do
target(dict).empty(dict)
@@ -461,14 +444,14 @@ defmodule Dict do
## Examples
iex> a = dict_impl.new(a: 2, b: 3, f: 5, c: 123)
...> b = [a: 2, b: 3, f: 5, c: 123]
...> Dict.equal?(a, b)
iex> a = Enum.into([a: 2, b: 3, f: 5, c: 123], dict_impl.new)
iex> b = [a: 2, b: 3, f: 5, c: 123]
iex> Dict.equal?(a, b)
true
iex> a = dict_impl.new(a: 2, b: 3, f: 5, c: 123)
...> b = []
...> Dict.equal?(a, b)
iex> a = Enum.into([a: 2, b: 3, f: 5, c: 123], dict_impl.new)
iex> b = []
iex> Dict.equal?(a, b)
false
"""
@@ -482,7 +465,7 @@ defmodule Dict do
target1.equal?(dict1, dict2)
target1.size(dict1) == target2.size(dict2) ->
target2.reduce(dict2, { :cont, true }, fn({ k, v }, _acc) ->
Enumerable.reduce(dict2, { :cont, true }, fn({ k, v }, _acc) ->
case target1.fetch(dict1, k) do
{ :ok, ^v } -> { :cont, true }
_ -> { :halt, false }
+4 -4
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:
@@ -79,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
+204 -25
View File
@@ -144,8 +144,8 @@ 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> Enum.map(dict, fn { k, v } -> { k, v * 2 } end) |> Enum.sort
iex> dict = %{a: 1, b: 2}
iex> Enum.map(dict, fn { k, v } -> { k, v * 2 } end)
[a: 2, b: 4]
Note that the functions in the `Enum` module are eager: they always start
@@ -202,6 +202,7 @@ defmodule Enum do
iex> Enum.all?([1, 2, 3])
true
iex> Enum.all?([1, nil, 3])
false
@@ -238,6 +239,7 @@ defmodule Enum do
iex> Enum.any?([false, false, false])
false
iex> Enum.any?([false, true, false])
true
@@ -265,10 +267,13 @@ 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
@@ -305,10 +310,13 @@ 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]]
@@ -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
@@ -807,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)
[]
@@ -826,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
@@ -840,6 +917,7 @@ defmodule Enum do
iex> Enum.join([1, 2, 3])
"123"
iex> Enum.join([1, 2, 3], " = ")
"1 = 2 = 3"
@@ -872,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
@@ -893,6 +971,7 @@ 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"
@@ -1000,6 +1079,7 @@ defmodule Enum do
iex> Enum.member?(1..10, 5)
true
iex> Enum.member?([:a, :b, :c], :d)
false
@@ -1011,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
v, _ when v === value -> { :halt, true }
_, _ -> { :cont, false }
_, _ -> { :cont, false }
end) |> elem(1)
end
end
@@ -1079,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`
@@ -1104,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`.
@@ -1172,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
@@ -1268,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]
"""
@@ -1361,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
@@ -1379,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
@@ -1412,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] }
@@ -1451,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
@@ -1481,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]
@@ -1563,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
@@ -1571,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.
@@ -1995,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)
+61 -50
View File
@@ -28,27 +28,33 @@ defexception CompileError, [file: nil, line: nil, description: "compile error"]
end
end
defexception BadFunctionError, [actual: nil] do
defexception BadFunctionError, [term: nil] do
def message(exception) do
"expected a function, got: #{inspect(exception.actual)}"
"expected a function, got: #{inspect(exception.term)}"
end
end
defexception MatchError, [actual: nil] do
defexception BadStructError, [struct: nil, term: nil] do
def message(exception) do
"no match of right hand side value: #{inspect(exception.actual)}"
"expected a struct named #{inspect(exception.struct)}, got: #{inspect(exception.term)}"
end
end
defexception CaseClauseError, [actual: nil] do
defexception MatchError, [term: nil] do
def message(exception) do
"no case clause matching: #{inspect(exception.actual)}"
"no match of right hand side value: #{inspect(exception.term)}"
end
end
defexception TryClauseError, [actual: nil] do
defexception CaseClauseError, [term: nil] do
def message(exception) do
"no try clause matching: #{inspect(exception.actual)}"
"no case clause matching: #{inspect(exception.term)}"
end
end
defexception TryClauseError, [term: nil] do
def message(exception) do
"no try clause matching: #{inspect(exception.term)}"
end
end
@@ -105,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
@@ -133,20 +139,19 @@ 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]
@@ -166,71 +171,75 @@ 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 an stacktrace entry and formats it into a string.
Receives a stacktrace entry and formats it into a string.
"""
@spec format_stacktrace_entry(stacktrace_entry) :: String.t
def format_stacktrace_entry(entry)
@@ -250,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
@@ -307,8 +316,10 @@ defmodule Exception do
iex> Exception.format_mfa Foo, :bar, 1
"Foo.bar/1"
iex> Exception.format_mfa Foo, :bar, []
"Foo.bar()"
iex> Exception.format_mfa nil, :bar, []
"nil.bar()"
@@ -332,7 +343,7 @@ defmodule Exception do
end
defp format_arity(arity) when is_list(arity) do
inspected = lc x inlist arity, do: inspect(x)
inspected = for x <- arity, do: inspect(x)
"(#{Enum.join(inspected, ", ")})"
end
+162 -64
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
@@ -513,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:
@@ -526,10 +636,11 @@ 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)
F.write_file(path, content, [:raw|modes])
end
@doc """
@@ -710,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
@@ -733,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.
@@ -746,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`.
Check http://www.erlang.org/doc/man/file.html#open-2 for more information about
other options like `:read_ahead` and `:delayed_write`.
This function returns:
@@ -789,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).
@@ -935,63 +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
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 false
def stream_to!(stream, path, modes \\ []) do
IO.write :stderr, "File.stream_to!/3 is deprecated and will be removed/1\n#{Exception.format_stacktrace}"
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 """
@@ -1056,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
@@ -1064,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
@@ -82,7 +85,7 @@ defmodule Float do
# iex(1)> 0.0001 * 75
# 0.007500000000000001
# Due to IEEE 754 floating point standard
# http:\\docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html
# http://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html
final_decimal_places = decimal - exponential
if final_decimal_places > 0 do
@@ -94,14 +97,16 @@ defmodule Float do
end
@doc """
Round a float to the largest integer less than or equal to `num`
Rounds a float to the largest integer less than or equal to `num`.
## Examples
iex> Float.floor(34)
34
iex> Float.floor(34.25)
34
iex> Float.floor(-56.5)
-57
@@ -117,14 +122,16 @@ defmodule Float do
end
@doc """
Round a float to the largest integer greater than or equal to `num`
Rounds a float to the largest integer greater than or equal to `num`.
## Examples
iex> Float.ceil(34)
34
iex> Float.ceil(34.25)
35
iex> Float.ceil(-56.5)
-56
@@ -147,16 +154,19 @@ defmodule Float do
iex> Float.round(5.5674, 3)
5.567
iex> Float.round(5.5675, 3)
5.568
iex> Float.round(-5.5674, 3)
-5.567
iex> Float.round(-5.5675, 3)
-5.568
"""
@spec round(float, integer) :: float
def round(number, precision) when is_float(number) and is_integer(precision) and precision in 0..15 do
Kernel.round(number * :math.pow(10, precision)) / :math.pow(10, precision)
end
end
-202
View File
@@ -1,202 +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(__MODULE__, [], [])
end
def insert_coin(pid) do
:gen_fsm.send_event(pid, :coin)
end
def request_coffee(pid) do
:gen_fsm.send_event(pid, :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(pid)
#=> :ok
MyFsm.insert_coin(pid)
#=> :ok
MyFsm.request_coffee(pid)
#=> :ok
MyFsm.insert_coin(pid)
#=> :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
IO.puts :stderr, "warning: GenFSM is deprecated and it will be removed in upcoming releases\n#{Exception.format_stacktrace}"
@behaviour :gen_fsm
@doc false
def handle_event(event, state_name, state_data) do
{ :stop, {:bad_event, state_name, event}, state_data }
end
@doc false
def handle_sync_event(event, from, state_name, state_data) do
{ :stop, {:bad_sync_event, state_name, event}, state_data }
end
@doc false
def handle_info(_msg, state_name, state_data) do
{ :next_state, state_name, state_data }
end
@doc false
def terminate(_reason, _state_name, _state_data) do
:ok
end
@doc false
def code_change(_old, state_name, state_data, _extra) do
{ :ok, state_name, state_data }
end
defoverridable [handle_event: 3, handle_sync_event: 4,
handle_info: 3, terminate: 3, code_change: 4]
end
end
end
+23 -21
View File
@@ -31,34 +31,19 @@ defmodule HashDict do
trie()
end
@doc """
Creates a new dict from the given enumerable.
## Examples
HashDict.new [{:b, 1}, {:a, 2}]
#=> #HashDict<[a: 2, b: 1]>
"""
@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"}]>
"""
@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)
@@ -66,6 +51,7 @@ defmodule HashDict do
end
def empty(trie()) do
IO.write :stderr, "HashDict.empty/1 is deprecated, please use Collectable.empty/1 instead\n#{Exception.format_stacktrace}"
trie()
end
@@ -74,8 +60,9 @@ defmodule HashDict do
trie(root: root, size: size + counter)
end
def update!(trie(root: root, size: size), key, fun) when is_function(fun, 1) do
{ root, counter } = do_update(root, key, fn -> raise KeyError, key: key end, fun, key_hash(key))
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
@@ -106,6 +93,7 @@ defmodule HashDict do
size
end
@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) }}
@@ -286,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
+20 -19
View File
@@ -31,34 +31,19 @@ defmodule HashSet do
trie()
end
@doc """
Creates a new set from the given enumerable.
## Examples
HashSet.new [1, 2]
#=> #HashSet<[1, 2]>
"""
@doc false
@spec new(Enum.t) :: Set.t
def new(enum) do
IO.write :stderr, "HashSet.new/1 is deprecated, please use Enum.into/2 instead\n#{Exception.format_stacktrace}"
Enum.reduce enum, trie(), fn i, set ->
put(set, i)
end
end
@doc """
Creates a new set from the enumerable with the
help of the transformation function.
## Examples
HashSet.new [1, 2], &integer_to_binary/1
#=> #HashSet<[1, 2]>
"""
@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
@@ -112,6 +97,7 @@ defmodule HashSet do
end
def empty(trie()) do
IO.write :stderr, "HashSet.empty/1 is deprecated, please use Collectable.empty/1 instead\n#{Exception.format_stacktrace}"
trie()
end
@@ -131,6 +117,7 @@ defmodule HashSet do
end
end
@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) }}
@@ -281,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
+72 -14
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
@@ -163,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>>"
@@ -290,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]"
@@ -310,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)
@@ -324,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
@@ -344,6 +355,7 @@ defimpl Inspect, for: Tuple do
iex> inspect({1, 2, 3})
"{1, 2, 3}"
iex> inspect(ArgumentError.new)
"ArgumentError[message: \\\"argument error\\\"]"
@@ -406,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.
@@ -450,13 +488,9 @@ defimpl Inspect, for: Regex do
"~r\"foo\"m"
"""
def inspect(regex, opts) when size(regex) == 5 do
def inspect(regex, opts) when size(regex) == 4 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))
end
end
defimpl Inspect, for: Function do
@@ -481,18 +515,24 @@ defimpl Inspect, for: Function do
end
defp default_inspect(mod, fun_info) do
"#Function<#{uniq(fun_info)}/#{fun_info[:arity]} in #{Inspect.Atom.inspect(mod)}.#{extract_name(fun_info[:name])}>"
"#Function<#{uniq(fun_info)}/#{fun_info[:arity]} in " <>
"#{Inspect.Atom.inspect(mod)}#{extract_name(fun_info[:name])}>"
end
defp extract_name([]) do
""
end
defp extract_name(name) do
case :binary.split(atom_to_binary(name), "-", [:global]) do
["", name | _] -> name
_ -> name
["", name | _] -> "." <> 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
@@ -515,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), ">"]
+32 -15
View File
@@ -8,6 +8,7 @@ defmodule Inspect.Algebra do
iex> Inspect.Algebra.empty
:doc_nil
iex> "foo"
"foo"
@@ -105,26 +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)
rescue
e ->
res = Inspect.Tuple.inspect(arg, opts)
IO.puts :stderr, "** (Inspect.Error) Got #{inspect e.__record__(:name)} with message " <>
"#{e.message} while inspecting #{pretty(res, opts.width)}"
res
end
else
Inspect.Tuple.inspect(arg, opts)
def to_doc(arg, 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.
+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
+77 -12
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.
@@ -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
@@ -224,9 +279,14 @@ defmodule IO do
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)
+4 -3
View File
@@ -17,7 +17,7 @@ end
defmodule IO.ANSI do
@moduledoc """
Functionality to render ANSI escape sequences
(http:\\en.wikipedia.org/wiki/ANSI_escape_code) — characters embedded
(http://en.wikipedia.org/wiki/ANSI_escape_code) — characters embedded
in text used to control formatting, color, and other output options
on video text terminals.
"""
@@ -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
@@ -183,6 +183,7 @@ 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"
+282 -201
View File
@@ -46,8 +46,10 @@ defmodule Kernel do
iex> abs(-3.33)
3.33
iex> abs(-3)
3
"""
@spec abs(number) :: number
def abs(number) do
@@ -175,7 +177,7 @@ defmodule Kernel do
## Examples
iex> :my_atom
...> binary_to_existing_atom("my_atom")
iex> binary_to_existing_atom("my_atom")
:my_atom
iex> binary_to_existing_atom("this_atom_will_never_exist")
@@ -245,6 +247,7 @@ defmodule Kernel do
iex> bit_size(<<433::16, 3::3>>)
19
iex> bit_size(<<1, 2, 3>>)
24
@@ -268,9 +271,11 @@ defmodule Kernel do
end
@doc """
Returns an integer which is the number of bytes needed to contain `bitstring`.
(That is, if the number of bits in `bitstring` is not divisible by 8, the resulting
number of bytes will be rounded up.)
Returns the number of bytes needed to contain `bitstring`.
That is, if the number of bits in `bitstring` is not divisible by 8,
the resulting number of bytes will be rounded up. This operation
happens in constant time.
Allowed in guard tests. Inlined by the compiler.
@@ -278,6 +283,7 @@ defmodule Kernel do
iex> byte_size(<<433::16, 3::3>>)
3
iex> byte_size(<<1, 2, 3>>)
3
@@ -369,7 +375,8 @@ defmodule Kernel do
## Examples
float_to_binary 7.1, [decimals: 2, compact: true] #=> "7.1"
iex> float_to_binary 7.1, [decimals: 2, compact: true]
"7.1"
"""
@spec float_to_binary(float, list) :: binary
@@ -389,7 +396,8 @@ defmodule Kernel do
## Examples
float_to_list 7.1, [decimals: 2, compact: true] #=> '7.1'
iex> float_to_list 7.1, [decimals: 2, compact: true]
'7.1'
"""
@spec float_to_list(float, list) :: char_list
@@ -505,13 +513,13 @@ defmodule Kernel do
## Examples
iex> bin1 = <<1, 2, 3>>
...> bin2 = <<4, 5>>
...> bin3 = <<6>>
...> iolist_to_binary([bin1, 1, [2, 3, bin2], 4|bin3])
iex> bin2 = <<4, 5>>
iex> bin3 = <<6>>
iex> iolist_to_binary([bin1, 1, [2, 3, bin2], 4|bin3])
<<1,2,3,1,2,3,4,5,4,6>>
iex> bin = <<1, 2, 3>>
...> iolist_to_binary(bin)
iex> iolist_to_binary(bin)
<<1,2,3>>
"""
@@ -665,6 +673,16 @@ defmodule Kernel do
:erlang.is_tuple(term)
end
@doc """
Returns `true` if `term` is a map; otherwise returns `false`.
Allowed in guard tests. Inlined by the compiler.
"""
@spec is_map(term) :: boolean
def is_map(term) do
:erlang.is_map(term)
end
@doc """
Returns the length of `list`.
@@ -674,6 +692,7 @@ defmodule Kernel do
iex> length([1, 2, 3, 4, 5, 6, 7, 8, 9])
9
"""
@spec length(list) :: non_neg_integer
def length(list) do
@@ -689,6 +708,7 @@ defmodule Kernel do
iex> list_to_atom('elixir')
:elixir
"""
@spec list_to_atom(char_list) :: atom
def list_to_atom(list) do
@@ -704,9 +724,9 @@ defmodule Kernel do
## Examples
iex> bin1 = <<1, 2, 3>>
...> bin2 = <<4, 5>>
...> bin3 = <<6, 7::4>>
...> list_to_bitstring([bin1, 1, [2, 3, bin2], 4|bin3])
iex> bin2 = <<4, 5>>
iex> bin3 = <<6, 7::4>>
iex> list_to_bitstring([bin1, 1, [2, 3, bin2], 4|bin3])
<<1,2,3,1,2,3,4,5,4,6,7::size(4)>>
"""
@@ -735,6 +755,7 @@ defmodule Kernel do
iex> list_to_float('2.2017764e+0')
2.2017764
"""
@spec list_to_float(list) :: float
def list_to_float(list) do
@@ -750,6 +771,7 @@ defmodule Kernel do
iex> list_to_integer('123')
123
"""
@spec list_to_integer(list) :: integer
def list_to_integer(list) do
@@ -765,6 +787,7 @@ defmodule Kernel do
iex> list_to_integer('3FF', 16)
1023
"""
@spec list_to_integer(list, non_neg_integer) :: integer
def list_to_integer(list, base) do
@@ -780,6 +803,7 @@ defmodule Kernel do
iex> list_to_tuple([:share, [:elixir, 163]])
{:share, [:elixir, 163]}
"""
@spec list_to_tuple(list) :: tuple
def list_to_tuple(list) do
@@ -804,6 +828,18 @@ defmodule Kernel do
:erlang.make_ref()
end
@doc """
Returns the size of a map.
This operation happens in constant time.
Allowed in guard tests. Inlined by the compiler.
"""
@spec map_size(map) :: non_neg_integer
def map_size(map) do
:erlang.map_size(map)
end
@doc """
Return the biggest of the two given terms according to
Erlang's term ordering. If the terms compare equal, the
@@ -930,7 +966,7 @@ defmodule Kernel do
@doc """
Returns the size of the given argument, which must be a tuple or a binary.
Prefer using `tuple_size` or `byte_size` insted.
Prefer using `tuple_size/1` or `byte_size/1` instead.
Allowed in guard tests. Inlined by the compiler.
"""
@@ -1024,7 +1060,7 @@ defmodule Kernel do
end
@doc """
A non-local return from a function. Check `try/2` for more information.
A non-local return from a function. Check `Kernel.SpecialForms.try/1` for more information.
Inlined by the compiler.
"""
@@ -1062,6 +1098,8 @@ defmodule Kernel do
@doc """
Returns the size of a tuple.
This operation happens in constant time.
Allowed in guard tests. Inlined by the compiler.
"""
@spec tuple_size(tuple) :: non_neg_integer
@@ -1171,6 +1209,7 @@ defmodule Kernel do
iex> 1 / 2
0.5
iex> 2 / 1
2.0
@@ -1229,6 +1268,7 @@ defmodule Kernel do
iex> true xor false
true
iex> true xor true
false
@@ -1364,6 +1404,7 @@ defmodule Kernel do
iex> 1 != 2
true
iex> 1 != 1.0
false
@@ -1427,7 +1468,7 @@ defmodule Kernel do
## Example
iex> tuple = { :foo, :bar, 3 }
...> elem(tuple, 1)
iex> elem(tuple, 1)
:bar
"""
@@ -1444,7 +1485,7 @@ defmodule Kernel do
## Example
iex> tuple = { :foo, :bar, 3 }
...> set_elem(tuple, 0, :baz)
iex> set_elem(tuple, 0, :baz)
{ :baz, :bar, 3 }
"""
@@ -1496,6 +1537,7 @@ defmodule Kernel do
iex> !Enum.empty?([])
false
iex> !List.first([])
true
@@ -1532,7 +1574,7 @@ defmodule Kernel do
long as the first part is a literal binary:
iex> "foo" <> x = "foobar"
...> x
iex> x
"bar"
"""
@@ -1671,6 +1713,7 @@ defmodule Kernel do
iex> is_exception((fn -> ArithmeticError.new end).())
true
iex> is_exception((fn -> 1 end).())
false
@@ -1698,10 +1741,11 @@ defmodule Kernel do
## Examples
defrecord Config, sample: nil
is_record(Config.new, Config) #=> true
is_record(Config.new, List) #=> false
iex> defrecord Config, sample: nil
iex> is_record(Config.new, Config)
true
iex> is_record(Config.new, List)
false
"""
defmacro is_record(thing, kind) do
@@ -1739,24 +1783,6 @@ defmodule Kernel do
end
end
@doc false
defmacro is_regex(thing) do
IO.puts :stderr, "is_regex/1 is deprecated in favor of Regex.regex?/1\n" <>
Exception.format_stacktrace(__CALLER__.stacktrace)
quote do
Kernel.is_record(unquote(thing), Regex)
end
end
@doc false
defmacro is_range(thing) do
IO.puts :stderr, "is_range/1 is deprecated in favor of Range.range?/1\n" <>
Exception.format_stacktrace(__CALLER__.stacktrace)
quote do
Kernel.is_record(unquote(thing), Range)
end
end
@doc """
Matches the term on the left against the regular expression or string on the
right. Returns true if `left` matches `right` (if it's a regular expression)
@@ -1795,8 +1821,8 @@ defmodule Kernel do
The following options are supported:
* `:records` - when false, records are not formatted by the inspect protocol,
they are instead printed as just tuples, defaults to true;
* `:structs` - when false, structs are not formatted by the inspect protocol,
they are instead printed as maps, defaults to true;
* `:binaries` - when `:as_strings` all binaries will be printed as strings,
non-printable bytes will be escaped; when `:as_binaries` all
@@ -1815,8 +1841,7 @@ defmodule Kernel do
* `:pretty` - if set to true enables pretty printing, defaults to false;
* `:width` - the width available for inspect to layout the data structure
representation. Defaults to the smaller of 80 or the terminal width;
* `:width` - defaults to the 80 characters;
## Examples
@@ -1826,12 +1851,6 @@ defmodule Kernel do
iex> inspect [1, 2, 3, 4, 5], limit: 3
"[1, 2, 3, ...]"
iex> inspect(ArgumentError[])
"ArgumentError[message: \"argument error\"]"
iex> inspect(ArgumentError[], records: false)
"{ArgumentError, :__exception__, \"argument error\"}"
iex> inspect("josé" <> <<0>>)
"<<106, 111, 115, 195, 169, 0>>"
@@ -1845,7 +1864,7 @@ defmodule Kernel do
representation of an Elixir term. In such cases, the inspected result
must start with `#`. For example, inspecting a function will return:
inspect &(&1 + &2)
inspect fn a, b -> a + b end
#=> #Function<...>
"""
@@ -1859,6 +1878,55 @@ defmodule Kernel do
Inspect.Algebra.pretty(Inspect.Algebra.to_doc(arg, opts), limit)
end
@doc """
Creates and updates structs.
The struct argument may be an atom (which defines `defstruct`)
or a struct itself. The second argument is any Enumerable that
emits two-item tuples (key-value) during enumeration.
If one of the keys in the Enumerable does not exist in the struct,
they are automatically discarded.
This function is useful for dynamically creating and updating
structs.
## Example
defmodule User do
defstruct name: "jose"
end
struct(User)
#=> %User{name: "jose"}
opts = [name: "eric"]
user = struct(User, opts)
#=> %User{name: "eric"}
struct(user, unknown: "value")
#=> %User{name: "eric"}
"""
def struct(struct, kv \\ [])
def struct(struct, []) when is_atom(struct) do
apply(struct, :__struct__, [])
end
def struct(struct, kv) when is_atom(struct) do
struct(apply(struct, :__struct__, []), kv)
end
def struct(%{ __struct__: _ } = struct, kv) do
Enum.reduce(kv, struct, fn { k, v }, acc ->
case :maps.is_key(k, acc) and k != :__struct__ do
true -> :maps.put(k, v, acc)
false -> acc
end
end)
end
@doc """
Converts the argument to a string according to the String.Chars protocol.
This is the function invoked when there is string interpolation.
@@ -1897,6 +1965,7 @@ defmodule Kernel do
iex> nil?(1)
false
iex> nil?(nil)
true
@@ -1913,8 +1982,10 @@ defmodule Kernel do
iex> match?(1, 1)
true
iex> match?(1, 2)
false
iex> match?({1, _}, {1, 2})
true
@@ -1933,6 +2004,7 @@ defmodule Kernel do
iex> match?(x, 1)
true
iex> binding([:x]) == []
true
@@ -2143,13 +2215,13 @@ defmodule Kernel do
end
defp do_binding(context, vars, in_match) do
lc { v, c } inlist vars, c == context, v != :_@CALLER do
for { v, c } <- vars, c == context, v != :_@CALLER do
{ v, wrap_binding(in_match, { v, [], c }) }
end
end
defp do_binding(list, context, vars, in_match) do
lc { v, c } inlist vars, c == context, :lists.member(v, list) do
for { v, c } <- vars, c == context, :lists.member(v, list) do
{ v, wrap_binding(in_match, { v, [], c }) }
end
end
@@ -2299,6 +2371,7 @@ defmodule Kernel do
iex> unless(Enum.empty?([]), do: "Hello")
nil
iex> unless(Enum.empty?([1,2,3]), do: "Hello")
"Hello"
@@ -2320,7 +2393,7 @@ defmodule Kernel do
## Examples
iex> destructure([x, y, z], [1, 2, 3, 4, 5])
...> {x, y, z}
iex> {x, y, z}
{1, 2, 3}
Notice in the example above, even though the right
@@ -2329,7 +2402,7 @@ defmodule Kernel do
are simply assigned to nil:
iex> destructure([x, y, z], [1])
...> {x, y, z}
iex> {x, y, z}
{1, nil, nil}
The left side supports any expression you would use
@@ -2353,7 +2426,7 @@ defmodule Kernel do
unquote(item) = nil
end
end
{ :case, [{:export_all,true}|meta], args }
{ :case, [{:export_head,true}|meta], args }
end
end
@@ -2365,10 +2438,13 @@ defmodule Kernel do
iex> 0 in 1..3
false
iex> 1 in 1..3
true
iex> 2 in 1..3
true
iex> 3 in 1..3
true
@@ -2447,35 +2523,52 @@ defmodule Kernel do
end
@doc """
`|>` is called the pipeline operator as it is useful
to write pipeline style expressions. This operator
introduces the expression on the left as the first
argument to the function call on the right.
`|>` is the pipe operator.
This operator introduces the expression on the left as
the first argument to the function call on the right.
## Examples
iex> [1, [2], 3] |> List.flatten |> Enum.map(&(&1 * 2))
[2,4,6]
iex> [1, [2], 3] |> List.flatten()
[1, 2, 3]
The expression above is simply translated to:
The example above is the same as calling `List.flatten([1, [2], 3])`,
i.e. the argument on the left side of `|>` is introduced as the first
argument of the function call on the right side.
Enum.map(List.flatten([1, [2], 3]), &(&1 * 2))
This pattern is mostly useful when there is a desire to execute
a bunch of operations, resembling a pipeline:
Be aware of operator precedence when using this operator.
iex> [1, [2], 3] |> List.flatten |> Enum.map(fn x -> x * 2 end)
[2, 4, 6]
The example above will pass the list to `List.flatten/1`, then get
the flattened list and pass to `Enum.map/2`, which will multiply
each entry in the list per two.
In other words, the expression above simply translates to:
Enum.map(List.flatten([1, [2], 3]), fn x -> x * 2 end)
Beware of operator precedence when using the pipe operator.
For example, the following expression:
String.graphemes "Hello" |> Enum.reverse
Is translated to:
Translates to:
String.graphemes("Hello" |> Enum.reverse)
Which will result in an error as Enumerable protocol
is not defined for binaries. Adding explicit parenthesis
resolves the ambiguity:
Which will result in an error as Enumerable protocol is not defined
for binaries. Adding explicit parenthesis resolves the ambiguity:
String.graphemes("Hello") |> Enum.reverse
Or, even better:
"Hello" |> String.graphemes |> Enum.reverse
"""
defmacro left |> right do
:lists.foldl fn x, acc -> Macro.pipe(acc, x) end, left, Macro.unpipe(right)
@@ -2523,53 +2616,10 @@ defmodule Kernel do
The usage of this protocol is to access a raw value in a
keyword list.
sample = [a: 1, b: 2, c: 3]
sample[:b] #=> 2
iex> sample = [a: 1, b: 2, c: 3]
iex> sample[:b]
2
## Aliases
Whenever invoked on an alias or an atom, the access protocol is
expanded at compilation time rather than on runtime. This feature
is used by records to allow a developer to match against an specific
part of a record:
def increment(State[counter: counter, other: 13] = state) do
state.counter(counter + 1)
end
In the example above, we use the Access protocol to match the
counter field in the record `State`. Considering the record
definition is as follows:
defrecord State, counter: 0, other: nil
The clause above is translated to:
def increment({ State, counter, 13 } = state) do
state.counter(counter + 1)
end
The same pattern can be used to create a new record:
def new_state(counter) do
State[counter: counter]
end
The example above is faster than `State.new(counter: :counter)` because
the record is expanded at compilation time and not at runtime. If a field
is not specified on creation, it will have its default value.
Finally, as in Erlang, Elixir also allows the following syntax:
new_uri = State[_: 1]
In this case **all** fields will be set to `1`. Notice that,
as in Erlang, in case an expression is given, it will be
evaluated multiple times:
new_uri = State[_: IO.puts "Hello"]
In this case, `"Hello"` will be printed twice (one per each field).
"""
defmacro access(element, args) when is_list(args) do
caller = __CALLER__
@@ -2596,7 +2646,7 @@ defmodule Kernel do
end
end
Record.access(atom, fields, args, caller)
Record.Deprecated.access(atom, fields, args, caller)
false ->
case caller.in_match? or caller.in_guard? do
true -> raise ArgumentError, message: "dynamic access cannot be invoked inside match and guard clauses"
@@ -2617,7 +2667,7 @@ defmodule Kernel do
## Examples
iex> x = 1
...> x in [1, 2, 3]
iex> x in [1, 2, 3]
true
This macro simply translates the expression above to:
@@ -2626,7 +2676,7 @@ defmodule Kernel do
## Guards
The in operator can be used on guard clauses as long as the
The `in` operator can be used on guard clauses as long as the
right side is a range or a list. Elixir will then expand the
operator to a valid guard expression. For example:
@@ -2779,11 +2829,11 @@ defmodule Kernel do
## Examples
defmodule Foo do
def bar, do: :baz
end
Foo.bar #=> :baz
iex> defmodule Foo do
...> def bar, do: :baz
...> end
iex> Foo.bar
:baz
## Nesting
@@ -3035,6 +3085,10 @@ defmodule Kernel do
end
@doc ~S"""
Note: Records are mostly deprecated and being slowly removed from
the language. The set of supported record operations will be
maintained in the Record module.
Exports a module with a record definition and runtime operations.
Please see the `Record` module's documentation for an introduction
@@ -3084,22 +3138,6 @@ defmodule Kernel do
user.update_age(fn(old) -> old + 1 end)
## Types
Every record defines a type named `t` that can be accessed in typespecs.
Those types can be specified inside the record definition:
defrecord User do
record_type name: string, age: integer
end
All fields without a specified type are assumed to have type `term`.
Assuming the `User` record defined above, it could be used in typespecs
as follow:
@spec handle_user(User.t) :: boolean()
## Runtime introspection
At runtime, developers can use `__record__` to get information
@@ -3120,42 +3158,19 @@ defmodule Kernel do
User.__record__(:index, :unknown)
#=> nil
## Compile-time introspection
At compile time, one can access the following information about the record
from within the record module:
* `@record_fields` — a keyword list of record fields with defaults
* `@record_types` — a keyword list of record fields with types
For example:
defrecord Foo, bar: nil do
record_type bar: nil | integer
IO.inspect @record_fields
IO.inspect @record_types
end
Prints out:
[bar: nil]
[bar: {:|,[line: ...],[nil,{:integer,[line: ...],nil}]}]
Where the last line is a quoted representation of
[bar: nil | integer]
"""
defmacro defrecord(name, fields, do_block \\ [])
defmacro defrecord(name, fields, do_block) do
defmacro defrecord(name, fields, do_block \\ []) do
case is_list(fields) and Keyword.get(fields, :do, false) do
false -> Record.defrecord(name, fields, do_block)
other -> Record.defrecord(name, Keyword.delete(fields, :do), do: other)
false -> Record.Deprecated.defrecord(name, fields, do_block)
other -> Record.Deprecated.defrecord(name, Keyword.delete(fields, :do), do: other)
end
end
@doc ~S"""
Note: Records are mostly deprecated and being slowly removed from
the language. The set of supported record operations will be
maintained in the Record module.
Defines a set of private macros to manipulate a record definition.
This macro defines a set of macros private to the current module to
@@ -3196,23 +3211,80 @@ defmodule Kernel do
state() #=> { MyServer, nil }
## Types
`defrecordp` allows a developer to generate a type
automatically by simply providing a type to its fields.
The following definition:
defrecordp :user,
name: "José" :: binary,
age: 25 :: integer
Will generate the following type:
@typep user_t :: { :user, binary, integer }
"""
defmacro defrecordp(name, tag \\ nil, fields) do
Record.defrecordp(name, Macro.expand(tag, __CALLER__), fields)
Record.Deprecated.defrecordp(name, Macro.expand(tag, __CALLER__), fields)
end
@doc """
Defines a struct for the current module.
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, a developer needs to only define
a function named `__struct__/0` that returns a map with the
structs field. This macro is a convenience for defining such
function, with the addition of a type `t`.
For more information about structs, please check
`Kernel.SpecialForms.%/2`.
## Examples
defmodule MyRange do
defstruct first: nil, last: nil
end
Notice `defstruct` requires a keyword list of fields and values
at expansion time. In other words, `defstruct` works with quoted
expressions. In other words, a struct defined like:
defmodule MyRange do
defstruct first: nil, last: 1 + 1
end
Will execute `1 + 1` every time the struct is built. This also
implies the following leads to an error:
defmodule MyRange do
my_fields = [first: nil, last: nil]
defstruct my_fields
end
## Types
`defstruct` automatically generates a type `t` unless one exists.
The following definition:
defmodule User do
defstruct name: "José" :: String.t,
age: 25 :: integer
end
Generates a type as follows:
@type t :: %User{name: String.t, age: integer}
In case a struct does not declare a field type, it defaults to `term`.
"""
defmacro defstruct(kv) do
kv = Macro.escape(kv, unquote: true)
quote bind_quoted: [kv: kv] do
# Expand possible macros that return KVs.
kv = Macro.expand(kv, __ENV__)
{ fields, types } = Record.Backend.split_fields_and_types(:defstruct, kv)
if :code.ensure_loaded(Kernel.Typespec) == { :module, Kernel.Typespec } and
not Kernel.Typespec.defines_type?(__MODULE__, :t, 0) do
@type t :: %{ unquote_splicing(types), __struct__: __MODULE__ }
end
def __struct__() do
%{ unquote_splicing(fields), __struct__: __MODULE__ }
end
end
end
@doc ~S"""
@@ -3279,7 +3351,7 @@ defmodule Kernel do
end)
fields = quote do: [__exception__: :__exception__] ++ unquote(fields)
record = Record.defrecord(name, fields, do_block)
record = Record.Deprecated.defrecord(name, fields, do_block)
quote do
{ :module, name, _, _ } = unquote(record)
@@ -3291,8 +3363,10 @@ defmodule Kernel do
end
@doc """
Defines a module as a protocol and specifies the API that
should be defined by its implementations.
Defines a protocol.
A protocol specifies an API that should be defined by its
implementations.
## Examples
@@ -3334,7 +3408,7 @@ defmodule Kernel do
And we would have to define the implementation for all types.
The supported types available are:
* Record (see below)
* Structs (see below)
* Tuple
* Atom
* List
@@ -3343,23 +3417,24 @@ defmodule Kernel do
* Float
* Function
* PID
* Map
* Port
* Reference
* Any (see below)
## Protocols + Records
## Protocols + Structs
The real benefit of protocols comes when mixed with records.
The real benefit of protocols comes when mixed with structs.
For instance, Elixir ships with many data types implemented as
records, like `HashDict` and `HashSet`. We can implement the
structs, like `HashDict` and `HashSet`. We can implement the
`Blank` protocol for those types as well:
defimpl Blank, for: HashDict do
defimpl Blank, for: [HashDict, HashSet] do
def blank?(dict), do: Dict.empty?(dict)
end
Since records are tuples, if a protocol is not found a given
type, it will fallback to `Tuple`.
If a protocol is not found for a given type, it will fallback to
`Any`.
## Fallback to any
@@ -3393,8 +3468,8 @@ defmodule Kernel do
## Types
As in records, defining a protocol automatically defines a type
named `t`, which can be used as:
Defining a protocol automatically defines a type named `t`, which
can be used as:
@spec present?(Blank.t) :: boolean
def present?(blank) do
@@ -3580,7 +3655,7 @@ defmodule Kernel do
append_first = Keyword.get(opts, :append_first, false)
lc fun inlist List.wrap(funs) do
for fun <- List.wrap(funs) do
{ name, args } =
case Macro.decompose_call(fun) do
{ _, _ } = pair -> pair
@@ -3612,6 +3687,7 @@ defmodule Kernel do
iex> ~S(foo)
"foo"
iex> ~S(f\#{o}o)
"f\\\#{o}o"
@@ -3628,6 +3704,7 @@ defmodule Kernel do
iex> ~s(foo)
"foo"
iex> ~s(f\#{:o}o)
"foo"
@@ -3644,6 +3721,7 @@ defmodule Kernel do
iex> ~C(foo)
'foo'
iex> ~C(f\#{o}o)
'f\\\#{o}o'
@@ -3660,6 +3738,7 @@ defmodule Kernel do
iex> ~c(foo)
'foo'
iex> ~c(f\#{:o}o)
'foo'
@@ -3724,8 +3803,10 @@ defmodule Kernel do
iex> ~w(foo \#{:bar} baz)
["foo", "bar", "baz"]
iex> ~w(--source test/enum_test.exs)
["--source", "test/enum_test.exs"]
iex> ~w(foo bar baz)a
[:foo, :bar, :baz]
@@ -3774,14 +3855,14 @@ defmodule Kernel do
true ->
case mod do
?s -> String.split(string)
?a -> lc p inlist String.split(string), do: binary_to_atom(p)
?c -> lc p inlist String.split(string), do: String.to_char_list!(p)
?a -> for p <- String.split(string), do: binary_to_atom(p)
?c -> for p <- String.split(string), do: String.to_char_list!(p)
end
false ->
case mod do
?s -> quote do: String.split(unquote(string))
?a -> quote do: lc(p inlist String.split(unquote(string)), do: binary_to_atom(p))
?c -> quote do: lc(p inlist String.split(unquote(string)), do: String.to_char_list!(p))
?a -> quote do: for(p <- String.split(unquote(string)), do: binary_to_atom(p))
?c -> quote do: for(p <- String.split(unquote(string)), do: String.to_char_list!(p))
end
end
end
+7 -7
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
@@ -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, no further allow elixir_ensure_compiled
# directives and revert to the original error handler.
# Note we should not delete the elixir_compiler_pid though,
# as we still want to send notifications to the compiler.
:erlang.erase(:elixir_ensure_compiled)
:erlang.process_flag(:error_handler, :error_handler)
{ ^ref, :ready } -> :ok
{ ^ref, :release } -> release()
end
end
end
+49 -33
View File
@@ -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,36 +167,36 @@ defmodule Kernel.ParallelCompiler do
# Sometimes we may have spurious entries in the waiting
# list because someone invoked try/rescue UndefinedFunctionError
new_files = List.delete(files, down_pid)
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(new_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} =="
@@ -190,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
-413
View File
@@ -1,413 +0,0 @@
# This is an optimization Elixir runs on function clauses.
# Whenever a variables matches against a record (a tagged
# tuple), this information is stored in order to optimize
# record calls.
defmodule Kernel.RecordRewriter do
@moduledoc false
def optimize_clause(module, clause) do
optimize_clause(clause, module, :orddict.new)
end
## Clause
defp optimize_clause({ :clause, line, args, guards, body }, module, dict) do
{ args, dict } = optimize_args(args, module, dict)
{ body, dict, res } = optimize_body(body, module, dict, [])
{ { :clause, line, args, guards, body }, dict, res }
end
defp optimize_args(args, module, dict) do
Enum.map_reduce args, dict, fn(arg, acc) ->
{ new_arg, new_acc, _res } = optimize_expr(arg, module, acc)
{ new_arg, new_acc }
end
end
defp optimize_body([], _module, dict, _acc) do
{ [], dict, nil }
end
defp optimize_body([h], module, dict, acc) do
{ new_expr, new_dict, new_res } = optimize_expr(h, module, dict)
{ Enum.reverse([new_expr|acc]), new_dict, new_res }
end
defp optimize_body([h|t], module, dict, acc) do
{ new_expr, new_dict, _ } = optimize_expr(h, module, dict)
optimize_body(t, module, new_dict, [new_expr|acc])
end
## Record helpers
defp record_fields(record, record) do
fields = Module.get_attribute(record, :record_fields)
optimizable = Module.get_attribute(record, :record_optimizable)
unless nil?(fields) or nil?(optimizable) do
{ (lc { k, _ } inlist fields, do: k), optimizable }
end
end
defp record_fields(_module, record) do
if Code.ensure_loaded?(record) && function_exported?(record, :__record__, 1) do
try do
fields = lc { k, _ } inlist record.__record__(:fields), do: k
optimizable = record.__record__(:optimizable)
{ fields, optimizable }
rescue
[UndefinedFunctionError, FunctionClauseError] -> nil
end
end
end
defp record_field_info(function) do
case atom_to_list(function) do
'update' -> nil
'update_' ++ _field -> nil
_ -> { :accessor, function }
end
end
defp optimize_call(line, module, { record, _ } = res, left, { :atom, _, function }, args) do
case record_fields(module, record) do
{ fields, optimizable } ->
opt_call =
if { function, length(args) + 1 } in optimizable do
case record_field_info(function) do
{ kind, field } ->
if index = Enum.find_index(fields, &(field == &1)) do
optimize_record_accessor_call(line, res, kind, field, index, left, args)
end
nil ->
optimize_record_other_call(line, record, res, function, left, args)
end
end
opt_call || optimize_record_other_call(line, record, nil, function, left, args)
nil ->
nil
end
end
defp optimize_call(_line, _module, _res, _left, _right, _args) do
nil
end
defp optimize_record_accessor_call(line, _res, :accessor, _field, index, left, []) do
call = { :call, line,
{ :remote, line, { :atom, 0, :erlang }, { :atom, 0, :element } },
[{ :integer, 0, index + 2 }, left]
}
{ call, nil }
end
defp optimize_record_accessor_call(line, res, :accessor, _field, index, left, [arg]) do
call = { :call, line,
{ :remote, line, { :atom, 0, :erlang }, { :atom, 0, :setelement } },
[{ :integer, 0, index + 2 }, left, arg]
}
{ call, res }
end
defp optimize_record_other_call(line, record, res, function, left, args) do
call = { :call, line,
{ :remote, line, { :atom, 0, record }, { :atom, 0, function } },
args ++ [left]
}
{ call, res }
end
## Expr
defp optimize_expr({ :call, call_line,
{ :remote, _, { :atom, _, :erlang }, { :atom, _, :setelement } } = remote,
[{ :integer, _, pos } = integer, tuple, value] }, module, dict) when pos > 1 do
{ tuple, dict, res } = optimize_expr(tuple, module, dict)
{ value, dict, _ } = optimize_expr(value, module, dict)
{ { :call, call_line, remote, [integer, tuple, value] }, dict, res }
end
defp optimize_expr({ :call, call_line, { :remote, line, left, right }, args }, module, dict) do
{ left, dict, res } = optimize_expr(left, module, dict)
{ right, dict, _ } = optimize_expr(right, module, dict)
{ args, dict } = optimize_args(args, module, dict)
case optimize_call(call_line, module, res, left, right, args) do
{ call, call_res } ->
{ call, dict, call_res }
nil ->
{ { :call, call_line, { :remote, line, left, right }, args }, dict, nil }
end
end
defp optimize_expr({ :call, line, expr, args }, module, dict) do
{ expr, dict, _ } = optimize_expr(expr, module, dict)
{ args, dict } = optimize_args(args, module, dict)
{ { :call, line, expr, args }, dict, nil }
end
defp optimize_expr({ :match, line, left, right }, module, dict) do
{ left, dict, left_res } = optimize_expr(left, module, dict)
{ right, dict, right_res } = optimize_expr(right, module, dict)
match = { :match, line, left, right }
if left_res do
dict = assign_vars(extract_vars(right, []), dict, left_res)
end
if right_res do
dict = assign_vars(extract_vars(left, []), dict, right_res)
end
{ match, dict, right_res || left_res }
end
defp optimize_expr({ :op, line, op, left, right }, module, dict) do
{ left, dict, _ } = optimize_expr(left, module, dict)
{ right, dict, _ } = optimize_expr(right, module, dict)
{ { :op, line, op, left, right }, dict, nil }
end
defp optimize_expr({ :op, line, op, expr }, module, dict) do
{ expr, dict, _ } = optimize_expr(expr, module, dict)
{ { :op, line, op, expr }, dict, nil }
end
defp optimize_expr({ :bin, line, elements }, module, dict) do
{ elements, dict } = optimize_args(elements, module, dict)
{ { :bin, line, elements }, dict, nil }
end
defp optimize_expr({ :bin_element, line, expr, type1, type2 }, module, dict) do
{ expr, dict, _ } = optimize_expr(expr, module, dict)
{ { :bin_element, line, expr, type1, type2 }, dict, nil }
end
defp optimize_expr({ :cons, line, left, right }, module, dict) do
{ left, dict, _ } = optimize_expr(left, module, dict)
{ right, dict, _ } = optimize_expr(right, module, dict)
{ { :cons, line, left, right }, dict, nil }
end
defp optimize_expr({ :block, line, args }, module, dict) do
{ args, dict, res } = optimize_body(args, module, dict, [])
{ { :block, line, args }, dict, res }
end
defp optimize_expr({ :tuple, line, args }, module, dict) do
{ args, dict, args_res } = optimize_tuple_args(args, module, dict)
args_res = if Enum.any?(args_res), do: args_res, else: nil
res =
case args do
[{ :atom, _, atom }|_] -> atom
_ -> nil
end
{ { :tuple, line, args }, dict, { res, args_res } }
end
defp optimize_expr({ :var, _, name } = var, _module, dict) do
case :orddict.find(name, dict) do
{ :ok, res } -> { var, dict, res }
:error -> { var, dict, nil }
end
end
defp optimize_expr({ :case, line, expr, clauses }, module, dict) do
{ expr, dict, _ } = optimize_expr(expr, module, dict)
tuples = lc clause inlist clauses, do: optimize_clause(clause, module, dict)
clauses = lc { clause, _, _ } inlist tuples, do: clause
dict = join_dict(tuples)
res = join_result(tuples)
{ { :case, line, expr, clauses }, dict, res }
end
defp optimize_expr({ :receive, line, clauses }, module, dict) do
tuples = lc clause inlist clauses, do: optimize_clause(clause, module, dict)
clauses = lc { clause, _, _ } inlist tuples, do: clause
dict = join_dict(tuples)
res = join_result(tuples)
{ { :receive, line, clauses }, dict, res }
end
defp optimize_expr({ :receive, line, clauses, after_key, after_value }, module, dict) do
tuples = lc clause inlist clauses, do: optimize_clause(clause, module, dict)
clauses = lc { clause, _, _ } inlist tuples, do: clause
{ after_key, dict, _ } = optimize_expr(after_key, module, dict)
{ after_value, dict, res } = optimize_body(after_value, module, dict, [])
dict = join_dict(tuples, dict)
res = join_result(tuples, res)
{ { :receive, line, clauses, after_key, after_value }, dict, res }
end
defp optimize_expr({ :try, line, body, elses, catches, try_after }, module, dict) do
tuples = lc clause inlist catches, do: optimize_clause(clause, module, dict)
catches = lc { clause, _, _ } inlist tuples, do: clause
tuples = lc clause inlist elses, do: optimize_clause(clause, module, dict)
elses = lc { clause, _, _ } inlist tuples, do: clause
{ body, _, res } = optimize_body(body, module, dict, [])
res = join_result(tuples, res)
{ try_after, _, _ } = optimize_body(try_after, module, dict, [])
{ { :try, line, body, elses, catches, try_after }, dict, res }
end
defp optimize_expr({ :fun, line, { :function, receiver, name, arity } }, module, dict) do
{ receiver, dict, _ } = optimize_expr(receiver, module, dict)
{ name, dict, _ } = optimize_expr(name, module, dict)
{ arity, dict, _ } = optimize_expr(arity, module, dict)
{ { :fun, line, { :function, receiver, name, arity } }, dict, nil }
end
defp optimize_expr({ :fun, line, { :clauses, clauses } }, module, dict) do
clauses = lc clause inlist clauses do
{ clause, _, _ } = optimize_clause(clause, module, dict)
clause
end
{ { :fun, line, { :clauses, clauses } }, dict, nil }
end
defp optimize_expr({ comprehension, line, expr, args }, module, dict) when comprehension in [:lc, :bc] do
{ args, new_dict } = optimize_args(args, module, dict)
{ expr, _, _ } = optimize_expr(expr, module, new_dict)
{ { comprehension, line, expr, args }, dict, nil }
end
defp optimize_expr({ generate, line, left, right }, module, dict) when generate in [:generate, :b_generate] do
{ left, dict, _ } = optimize_expr(left, module, dict)
{ right, dict, _ } = optimize_expr(right, module, dict)
{ { generate, line, left, right }, dict, nil }
end
defp optimize_expr(other, _module, dict) when elem(other, 0) in [:string, :atom, :integer, :float, :nil, :fun] do
{ other, dict, nil }
end
## Helpers
defp optimize_tuple_args(args, module, dict) do
{ final_args, { final_dict, final_acc } } =
Enum.map_reduce args, { dict, [] }, fn(arg, { acc_dict, acc_res }) ->
{ new_arg, new_acc, res } = optimize_expr(arg, module, acc_dict)
{ new_arg, { new_acc, [res|acc_res] } }
end
{ final_args, final_dict, Enum.reverse(final_acc) }
end
defp assign_vars([key|t], dict, { _, value } = res) when is_list(key) and is_list(value) and length(key) == length(value) do
assign_vars t, assign_nested_vars(key, dict, value), res
end
defp assign_vars([key|t], dict, { value, _ } = res) when is_atom(key) and value != nil do
dict =
case :orddict.find(key, dict) do
{ :ok, ^res } ->
dict
{ :ok, { ^value, _ } } ->
:orddict.store(key, { value, nil }, dict)
{ :ok, _ } ->
# We are overriding a type of an existing variable,
# which means the source code is invalid.
:orddict.store(key, nil, dict)
:error ->
:orddict.store(key, res, dict)
end
assign_vars t, dict, res
end
defp assign_vars([_|t], dict, res) do
assign_vars t, dict, res
end
defp assign_vars([], dict, _res) do
dict
end
defp assign_nested_vars([vars|vt], dict, [res|rt]) do
assign_nested_vars(vt, assign_vars(vars, dict, res), rt)
end
defp assign_nested_vars([], dict, []) do
dict
end
defp extract_vars({ :match, _, left, right }, vars) do
vars = extract_vars(right, vars)
extract_vars(left, vars)
end
defp extract_vars({ :var, _, name }, vars) do
[name|vars]
end
defp extract_vars({ :tuple, _, args }, vars) do
[Enum.map(args, &extract_vars(&1, []))|vars]
end
defp extract_vars(_, vars) do
vars
end
defp join_dict([]) do
[]
end
defp join_dict([{ _, dict, _ }|t]) do
join_dict(t, dict)
end
defp join_dict([{ _, dict, _ }|t], other) do
other = Enum.reduce other, other, fn
{ key, { value, _ } = res }, acc ->
case :orddict.find(key, dict) do
{ :ok, ^res } -> acc
{ :ok, { ^value, _ } } -> :orddict.store(key, { value, nil }, acc)
{ :ok, _ } -> :orddict.store(key, nil, acc)
:error -> :orddict.erase(key, acc)
end
{ key, nil }, acc ->
:orddict.store(key, nil, acc)
end
join_dict(t, other)
end
defp join_dict([], other) do
other
end
defp join_result([]) do
[]
end
defp join_result([{ _, _, res }|t]) do
join_result(t, res)
end
defp join_result([{ _, _, res }|t], res) do
join_result(t, res)
end
defp join_result([{ _, _, { res, _ } }|t], { res, _ }) do
join_result(t, { res, nil })
end
defp join_result([{ _, _, _ }|_], _res) do
nil
end
defp join_result([], res) do
res
end
end
+241 -108
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 behaviours could be changed by explicitily
Both warning behaviours could be changed by explicitly
setting the `:warn` option to true or false.
"""
defmacro alias(module, opts)
@@ -377,7 +501,7 @@ defmodule Kernel.SpecialForms do
for example:
iex> import List
...> flatten([1, [2], 3])
iex> flatten([1, [2], 3])
[1,2,3]
## Selector
@@ -443,7 +567,7 @@ defmodule Kernel.SpecialForms do
Elixir won't emit any warnings though, since the import
was not explicitly defined.
Both warning behaviours could be changed by explicitily
Both warning behaviours could be changed by explicitly
setting the `:warn` option to true or false.
## Ambiguous function/macro names
@@ -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.
+78 -56
View File
@@ -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
@@ -524,13 +524,13 @@ defmodule Kernel.Typespec do
if is_atom(args) do
[]
else
lc(arg inlist args, do: variable(arg))
for(arg <- args, do: variable(arg))
end
vars = lc { :var, _, var } inlist args, do: var
vars = for { :var, _, var } <- args, do: var
spec = typespec(definition, vars, caller)
vars = lc { :var, _, _ } = var inlist args, do: var
vars = for { :var, _, _ } = var <- args, do: var
type = { name, spec, vars }
define_type(caller, kind, type)
@@ -542,9 +542,17 @@ defmodule Kernel.Typespec do
end
@doc false
def defspec(type, { :::, meta, [{ name, _, args }, return_and_guard] }, caller) when is_atom(name) and name != ::: do
def defspec(type, { :when, _meta, [spec, guard] }, caller) do
defspec(type, spec, guard, caller)
end
def defspec(type, spec, caller) do
defspec(type, spec, [], caller)
end
defp defspec(type, { :::, meta, [{ name, _, args }, return] }, guard, caller) when is_atom(name) and name != ::: do
if is_atom(args), do: args = []
{ return, guard } = split_return_and_guard(return_and_guard)
unless Keyword.keyword?(guard) do
guard = Macro.to_string(guard)
@@ -564,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)
@@ -630,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
@@ -642,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)>>
@@ -658,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
@@ -680,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
@@ -702,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
@@ -752,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
@@ -769,6 +779,18 @@ defmodule Kernel.Typespec do
{:type, line(meta), :binary, [{:integer, line(meta1), base}, {:integer, line(meta2), 0}]}
end
## Handle maps and structs
defp typespec({:%{}, meta, fields}, vars, caller) do
fields = Enum.map(fields, fn { k, v } ->
{:type, line(meta), :map_field_assoc, typespec(k, vars, caller), typespec(v, vars, caller)}
end)
{:type, line(meta), :map, fields}
end
defp typespec({:%, _, [name, {:%{}, meta, fields}]}, vars, caller) do
typespec({:%{}, meta, [{:__struct__, name}|fields]}, vars, caller)
end
# Handle ranges
defp typespec({:.., meta, args}, vars, caller) do
typespec({:range, meta, args}, vars, caller)
@@ -824,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
@@ -850,7 +872,7 @@ defmodule Kernel.Typespec do
defp typespec({:string, meta, arguments}, vars, caller) do
:elixir_errors.warn caller.line, caller.file, "string() type use is discouraged. For character lists, use " <>
"char_list() type, for strings, String.t()\n#{Exception.format_stacktrace(caller.stacktrace)}"
arguments = lc arg inlist arguments, do: typespec(arg, vars, caller)
arguments = for arg <- arguments, do: typespec(arg, vars, caller)
{ :type, line(meta), :string, arguments }
end
@@ -863,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
@@ -903,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
@@ -927,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
+53 -42
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]
@@ -340,7 +347,7 @@ defmodule Keyword do
"""
@spec merge(t, t) :: t
def merge(d1, d2) when is_list(d1) and is_list(d2) do
d2 ++ lc({ k, _ } = tuple inlist d1, not has_key?(d2, k), do: tuple)
d2 ++ for({ k, _ } = tuple <- d1, not has_key?(d2, k), do: tuple)
end
@doc """
@@ -351,7 +358,7 @@ defmodule Keyword do
iex> Keyword.merge([a: 1, b: 2], [a: 3, d: 4], fn (_k, v1, v2) ->
...> v1 + v2
iex> end)
...> end)
[a: 4, b: 2, d: 4]
"""
@@ -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,10 +463,10 @@ 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 }) ->
{ 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] }
@@ -480,8 +491,8 @@ defmodule Keyword do
[a: 1, c: 3, a: 5]
"""
def take(dict, keys) do
lc { k, _ } = tuple inlist dict, k in keys, do: tuple
def take(keywords, keys) when is_list(keywords) do
for { k, _ } = tuple <- keywords, k in keys, do: tuple
end
@doc """
@@ -499,8 +510,8 @@ defmodule Keyword do
[a: 1, c: 3, a: 5]
"""
def drop(dict, keys) do
lc { k, _ } = tuple inlist dict, not k in keys, do: tuple
def drop(keywords, keys) when is_list(keywords) do
for { k, _ } = tuple <- keywords, not k in keys, do: tuple
end
@doc """
@@ -528,8 +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
+5
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
+24 -19
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
@@ -60,7 +57,7 @@ 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
@@ -105,23 +102,27 @@ defmodule ListDict do
end
def take(dict, keys) do
lc { k, _ } = tuple inlist dict, k in keys, do: tuple
for { k, _ } = tuple <- dict, k in keys, do: tuple
end
def drop(dict, keys) do
lc { k, _ } = tuple inlist dict, not k in 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,12 +137,16 @@ 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 }
+46 -12
View File
@@ -13,7 +13,7 @@ defmodule Macro do
@binary_ops [:===, :!==,
:==, :!=, :<=, :>=,
:&&, :||, :<>, :++, :--, ://, :\\, :::, :<-, :.., :|>, :=~,
:&&, :||, :<>, :++, :--, :\\, :::, :<-, :.., :|>, :=~,
:<, :>, :->,
:+, :-, :*, :/, :=, :|, :.,
:and, :or, :xor, :when, :in, :inlist, :inbits,
@@ -30,7 +30,7 @@ defmodule Macro do
@spec binary_op_props(atom) :: { :left | :right, precedence :: integer }
defp binary_op_props(o) do
case o do
o when o in [:<-, :inlist, :inbits, ://, :\\, :::] -> {:left, 40}
o when o in [:<-, :inlist, :inbits, :\\, :::] -> {:left, 40}
:| -> {:right, 50}
:when -> {:right, 70}
:= -> {:right, 80}
@@ -113,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
@@ -333,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
@@ -376,6 +388,11 @@ 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
@@ -481,12 +498,29 @@ 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
@@ -524,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
@@ -618,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 ->
@@ -720,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)
@@ -750,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
+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
+50 -40
View File
@@ -11,7 +11,7 @@ defmodule Module do
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
@@ -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.
@@ -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
@@ -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
@@ -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.
@@ -638,7 +648,7 @@ defmodule Module do
def definitions_in(module) do
assert_not_compiled!(:definitions_in, module)
table = function_table_for(module)
lc { tuple, _, _, _, _, _, _ } inlist :ets.tab2list(table), do: tuple
for { tuple, _, _, _, _, _, _ } <- :ets.tab2list(table), do: tuple
end
@doc """
@@ -657,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,
+9 -9
View File
@@ -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,10 +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,
:digraph.in_neighbours(d, { :import, name, arity }) != [],
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
@@ -174,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]
@@ -261,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
@@ -269,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
+73 -42
View File
@@ -8,10 +8,12 @@ defmodule Node do
about inlined functions, check out the `Kernel` module.
"""
@type t :: atom
@type t :: node
@doc """
Returns the current node. It returns the same as the built-in `node()`.
Returns the current node.
It returns the same as the built-in `node()`.
"""
@spec self :: t
def self do
@@ -19,8 +21,9 @@ defmodule Node do
end
@doc """
Returns `true` if the local node is alive; that is, if the node can be
part of a distributed system. Otherwise, it returns `false`.
Returns `true` if the local node is alive.
That is, if the node can be part of a distributed system.
"""
@spec alive? :: boolean
def alive? do
@@ -29,7 +32,9 @@ defmodule Node do
@doc """
Returns a list of all visible nodes in the system, excluding
the local node. Same as `list(:visible)`.
the local node.
Same as `list(:visible)`.
"""
@spec list :: [t]
def list do
@@ -37,8 +42,9 @@ defmodule Node do
end
@doc """
Returns a list of nodes according to argument given. The result
returned when the argument is a list, is the list of nodes
Returns a list of nodes according to argument given.
The result returned when the argument is a list, is the list of nodes
satisfying the disjunction(s) of the list elements.
See http://www.erlang.org/doc/man/erlang.html#nodes-1 for more info.
@@ -50,8 +56,10 @@ defmodule Node do
end
@doc """
Monitors the status of the node. If `flag` is `true`, monitoring is
turned on. If `flag` is `false`, monitoring is turned off.
Monitors the status of the node.
If `flag` is `true`, monitoring is turned on.
If `flag` is `false`, monitoring is turned off.
See http://www.erlang.org/doc/man/erlang.html#monitor_node-2 for more info.
"""
@@ -72,10 +80,28 @@ defmodule Node do
end
@doc """
Forces the disconnection of a node. This will appear to the `node` as if
the local node has crashed. This BIF is mainly used in the Erlang network
authentication protocols. Returns `true` if disconnection succeeds, otherwise
`false`. If the local node is not alive, the function returns `:ignored`.
Tries to set up a connection to node.
Returns `:pang` if it fails, or `:pong` if it is successful.
## Examples
iex> Node.ping(:unknown_node)
:pang
"""
@spec ping(t) :: :pong | :pang
def ping(node) do
:net_adm.ping(node)
end
@doc """
Forces the disconnection of a node.
This will appear to the `node` as if the local node has crashed.
This function is mainly used in the Erlang network authentication
protocols. Returns `true` if disconnection succeeds, otherwise `false`.
If the local node is not alive, the function returns `:ignored`.
See http://www.erlang.org/doc/man/erlang.html#disconnect_node-1 for more info.
"""
@@ -85,9 +111,10 @@ defmodule Node do
end
@doc """
Establishes a connection to `node`. Returns `true` if successful,
`false` if not, and the atom `:ignored` if the local node is not
alive.
Establishes a connection to `node`.
Returns `true` if successful, `false` if not, and the atom
`:ignored` if the local node is not alive.
See http://erlang.org/doc/man/net_kernel.html#connect_node-1 for more info.
"""
@@ -112,10 +139,11 @@ defmodule Node do
@doc """
Returns the pid of a new process started by the application of `fun`
on `node`. If `node` does not exist, a useless pid is returned.
on `node`.
Check http://www.erlang.org/doc/man/erlang.html#spawn_opt-3 for
the list of available options.
If `node` does not exist, a useless pid is returned. Check
http://www.erlang.org/doc/man/erlang.html#spawn_opt-3 for the list of
available options.
Inlined by the compiler.
"""
@@ -126,11 +154,11 @@ defmodule Node do
@doc """
Returns the pid of a new process started by the application of
`module.function(args)` on `node`. If `node` does not exists,
a useless pid is returned.
`module.function(args)` on `node`.
Check http://www.erlang.org/doc/man/erlang.html#spawn-4 for
the list of available options.
If `node` does not exist, a useless pid is returned. Check
http://www.erlang.org/doc/man/erlang.html#spawn-4 for the list of
available options.
Inlined by the compiler.
"""
@@ -141,11 +169,11 @@ defmodule Node do
@doc """
Returns the pid of a new process started by the application of
`module.function(args)` on `node`. If `node` does not exists,
a useless pid is returned.
`module.function(args)` on `node`.
Check http://www.erlang.org/doc/man/erlang.html#spawn_opt-5 for
the list of available options.
If `node` does not exist, a useless pid is returned. Check
http://www.erlang.org/doc/man/erlang.html#spawn_opt-5 for the list of
available options.
Inlined by the compiler.
"""
@@ -155,11 +183,11 @@ defmodule Node do
end
@doc """
Returns the pid of a new process started by the application of `fun`
on `node`. A link is created between the calling process and the
new process, atomically. If `node` does not exist, a useless pid is returned
(and due to the link, an exit signal with exit reason `:noconnection` will be
received).
Returns the pid of a new linked process started by the application of `fun` on `node`.
A link is created between the calling process and the new process, atomically.
If `node` does not exist, a useless pid is returned (and due to the link, an exit
signal with exit reason `:noconnection` will be received).
Inlined by the compiler.
"""
@@ -169,11 +197,12 @@ defmodule Node do
end
@doc """
Returns the pid of a new process started by the application of
`module.function(args)` on `node`. A link is created between the calling
process and the new process, atomically. If `node` does not exist, a useless
pid is returned (and due to the link, an exit signal with exit reason
`:noconnection` will be received).
Returns the pid of a new linked process started by the application of
`module.function(args)` on `node`.
A link is created between the calling process and the new process, atomically.
If `node` does not exist, a useless pid is returned (and due to the link, an exit
signal with exit reason `:noconnection` will be received).
Inlined by the compiler.
"""
@@ -183,9 +212,10 @@ defmodule Node do
end
@doc """
Sets the magic cookie of `node` to the atom `cookie`. The default node
is `Node.self`, the local node. If `node` is the local node, the function also
sets the cookie of all other unknown nodes to `cookie`.
Sets the magic cookie of `node` to the atom `cookie`.
The default node is `Node.self`, the local node. If `node` is the local node,
the function also sets the cookie of all other unknown nodes to `cookie`.
This function will raise `FunctionClauseError` if the given `node` is not alive.
"""
@@ -194,8 +224,9 @@ defmodule Node do
end
@doc """
Returns the magic cookie of the local node, if the node is alive;
otherwise `:nocookie`.
Returns the magic cookie of the local node.
Returns the cookie if the node is alive, otherwise `:nocookie`.
"""
def get_cookie() do
:erlang.get_cookie()
+42 -25
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
@@ -96,6 +97,7 @@ 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
parse(argv, opts, false)
@@ -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
+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"
@@ -427,8 +436,10 @@ defmodule Path do
iex> Path.split("")
[]
iex> Path.split("foo")
["foo"]
iex> Path.split("/foo/bar")
["/", "foo", "bar"]
+30 -1
View File
@@ -123,6 +123,33 @@ defmodule Process 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.
@@ -326,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
+1
View File
@@ -34,6 +34,7 @@ defmodule Range do
iex> Range.range?(1..3)
true
iex> Range.range?(0)
false
+155 -789
View File
@@ -1,833 +1,199 @@
defmodule Record do
@moduledoc ~S"""
Functions for working with Records.
@moduledoc """
Module to work, define and import records.
A record is a tagged tuple which contains one or more elements
where the first element is an atom. We can manually create a record
by simply defining such a tuple:
Records are simply tuples where the first element is an atom:
iex> record = { User, "José", 25 }
iex> is_record(record, User)
iex> Record.record? { User, "jose", 27 }
true
However, manually constructing tuples can be quite error prone.
If we need to add a new field to our User, it would require
us to carefully change all places where the tuple is used.
Furthermore, as more items are added to the tuple, they
lose semantic value.
This module provides conveniences for working with records at
compilation time, where compile-time field names are used to
manipulate the tuples, providing fast operations on top of
the tuples compact structure.
This module solves these problems by allowing us to name each
element and encapsulate the generation and manipulation of
such tuples. Much of the functionality provided by this module happens
at compilation time, meaning they don't add any runtime overhead while
considerably improving the quality of our code.
In Elixir, records are used in two situations:
For these reasons, Records are frequently used in Elixir and
are also very useful when combined with Protocols. This module
provides different mechanisms for working with records and we
are going to explore them in the following sections.
1. To work with short, internal data. See `Inspect.Algebra`
implementation for a good example;
## defrecordp
2. To interface with Erlang records;
The simplest way of working with records is via `defrecordp`:
The macros `defrecord/3` and `defrecordp/3` can be used to create
records while `extract/2` can be used to extract records from Erlang
files.
"""
@doc """
Extracts record information from an Erlang file.
Returns a quoted expression containing the fields as a list
of tuples. It expects the record name to be an atom and the
library path to be a string at expansion time.
## Examples
iex> Record.extract(:file_info, from_lib: "kernel/include/file.hrl")
[size: :undefined, type: :undefined, access: :undefined, atime: :undefined,
mtime: :undefined, ctime: :undefined, mode: :undefined, links: :undefined,
major_device: :undefined, minor_device: :undefined, inode: :undefined,
uid: :undefined, gid: :undefined]
"""
defmacro extract(name, opts) when is_atom(name) and is_list(opts) do
Macro.escape Record.Extractor.extract(name, opts)
end
@doc """
Checks if the given `data` is a record of `kind`.
This is implemented as a macro so it can be used in guard clauses.
## Examples
iex> record = { User, "jose", 27 }
iex> Record.record?(record, User)
true
"""
defmacro record?(data, kind) do
case __CALLER__.in_guard? do
true ->
quote do
is_tuple(unquote(data)) and tuple_size(unquote(data)) > 0
and :erlang.element(1, unquote(data)) == unquote(kind)
end
false ->
quote do
result = unquote(data)
is_tuple(result) and tuple_size(result) > 0
and :erlang.element(1, result) == unquote(kind)
end
end
end
@doc """
Checks if the given `data` is a record.
This is implemented as a macro so it can be used in guard clauses.
## Examples
iex> record = { User, "jose", 27 }
iex> Record.record?(record)
true
iex> tuple = {}
iex> Record.record?(tuple)
false
"""
defmacro record?(data) do
case __CALLER__.in_guard? do
true ->
quote do
is_tuple(unquote(data)) and tuple_size(unquote(data)) > 0
and is_atom(:erlang.element(1, unquote(data)))
end
false ->
quote do
result = unquote(data)
is_tuple(result) and tuple_size(result) > 0
and is_atom(:erlang.element(1, result))
end
end
end
@doc false
def defmacros(name, values, env, tag \\ nil) do
Record.Deprecated.defmacros(name, values, env, tag)
end
@doc false
def deftypes(values, types, env) do
Record.Deprecated.deftypes(values, types, env)
end
@doc false
def deffunctions(values, env) do
Record.Deprecated.deffunctions(values, env)
end
@doc """
Defines a set of macros to create and access a record.
The macros are going to have `name`, a tag (which defaults)
to the name if none is given, and a set of fields given by
`kv`.
## Examples
defmodule User do
defrecordp :user, name: "José", age: 25
Record.defrecord :user, [name: "José", age: "25"]
end
In the example above, `defrecordp` is going to generate a set of
macros named `user` that allows us to create, update and match
on a record. Our record is going to have two fields, a `name` with
default value of "José" and `age` with default value 25.
Let's see some examples:
In the example above, a set of macros named `user` but with different
arities will be defined to manipulate the underlying record:
# To create records
user() #=> { :user, "José", 25 }
user(age: 26) #=> { :user, "José", 26 }
By using the `user` macro, we no longer need to explicitly create
a tuple with all elements. It also allows us to create and modify
values by name:
# To get a field from the record
user(record, :name) #=> "José"
# Create a new record
sample_user = user()
# To update the record
user(record, age: 26) #=> { :user, "José", 26 }
# And now change its age to 26
user(sample_user, age: 26)
By default, Elixir uses the record name as the first element of
the tuple (the tag). But it can be changed to something else:
Since `user` is a macro, all the work happens at compilation time.
This means all operations, like changing the `age` above, work
as a simple tuple operation at runtime:
# This update operation...
user(sample_user, age: 26)
# Literally translates to this one:
set_elem(sample_user, 2, 26)
For this reason, the following operation is not allowed as all
values need to be explicit:
new_values = [age: 26]
user(sample_user, new_values)
As the name says, `defrecordp` is useful when you don't want to
expose the record definition. The `user` macro used above, for
example, is only available inside the `User` module and nowhere else.
You can find more information in `Kernel.defrecordp/3` docs.
## defrecord
By using `defrecord`, a developer can make a Record definition
available everywhere within Elixir code. Let's see an example:
defrecord User, name: "José", age: 25
User[] #=> User[name: "José", age: 25]
User[age: 26] #=> User[name: "José", age: 26]
All the functionality discussed above happens at compilation time.
This means that both `user(age: 26)` and `User[age: 26]` are expanded
into a tuple at compile time.
However, there are some situations where we want to set or update
fields dynamically. `defrecord` (and not `defrecordp` ) supports
this behaviour out of the box:
defrecord User, name: "José", age: 25
opts = [name: "Hello"]
user = User.new(opts)
#=> User[name: "Hello", age: 25]
user.update(age: 26)
#=> User[name: "Hello", age: 26]
All the calls above happen at runtime. It gives Elixir records
flexibility at the cost of performance since there is more work
happening at runtime.
The above calls (`new` and `update`) can accept both atom and string
keys for field names, however not both at the same time. This feature
allows to "sanitize" untrusted dictionaries and initialize/update
records without using `Kernel.binary_to_existing_atom/1`.
To sum up, `defrecordp` should be used when you don't want to expose
the record information while `defrecord` should be used whenever you
want to share a record within your code or with other libraries or
whenever you need to dynamically set or update fields.
The standard library contains excellent examples of both use cases,
with [`HashDict`](HashDict.html) being implemented with `defrecordp`
and [`Range`](Range.html) with `defrecord`.
You can learn more about records in the `Kernel.defrecord/3` docs. Now
let's discuss the usefulness of combining records with protocols.
## Protocols
Developers can extend existing protocols by creating their own
records and implementing the desired protocols. For instance,
imagine that you have created a new representation for storing
date and time, represented by the year, the week of the year
and the week day:
defrecord WeekDate, year: nil, week: nil, week_day: nil
Now we want this date to be represented as a string and this
can be done by implementing the `String.Chars` protocol for
our record:
defimpl String.Chars, for: WeekDate do
def to_string(WeekDate[year: year, week: week, week_day: day]) do
"#{year}-W#{week}-#{day}"
end
defmodule User do
Record.defrecord :user, User, name: nil
end
Now we can explicitly convert our `WeekDate`:
to_string WeekDate[year: 2013, week: 26, week_day: 4]
"2013-W26-4"
A protocol can be implemented for any record, whether
generated with `defrecordp` or `defrecord`.
"""
@type t :: tuple
@doc """
Extract record information from an Erlang file.
Returns the fields as a list of tuples.
## Examples
Record.extract(:file_info, from_lib: "kernel/include/file.hrl")
#=> [size: :undefined, type: :undefined, access: :undefined, atime: :undefined,
mtime: :undefined, ctime: :undefined, mode: :undefined, links: :undefined,
major_device: :undefined, minor_device: :undefined, inode: :undefined,
uid: :undefined, gid: :undefined]
defrecord FileInfo, Record.extract(:file_info, from_lib: "kernel/include/file.hrl")
user() #=> { User, nil }
"""
def extract(name, opts) do
Record.Extractor.retrieve(name, opts)
end
defmacro defrecord(name, tag \\ nil, kv) do
kv = Macro.escape(kv, unquote: true)
@doc false
def defrecord(name, fields, opts) do
block = Keyword.get(opts, :do, nil)
record_check!(fields)
quote bind_quoted: [name: name, tag: tag, kv: kv] do
tag = tag || name
kv = Macro.expand(kv, __ENV__)
quote do
unquoted_fields = unquote(fields)
{ fields, _types } = Record.Backend.split_fields_and_types(:defrecord, kv)
fields = Macro.escape(fields)
defmodule unquote(name) do
import Record.DSL
@record_fields []
@record_types []
Record.deffunctions(unquoted_fields, __ENV__)
value = unquote(block)
Record.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
@doc false
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.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.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
@doc """
Define record functions skipping the module definition.
This is called directly by `Kernel.defrecord/3`. It expects the record
values, a set of options and the module environment.
## Examples
defmodule CustomRecord do
Record.deffunctions [:name, :age], __ENV__
Record.deftypes [:name, :age], [name: :binary, age: :integer], __ENV__
defmacro(unquote(name)(args \\ [])) do
Record.Backend.access(unquote(tag), unquote(fields), args, __CALLER__)
end
"""
def deffunctions(values, env) do
values = lc value inlist 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)
defmacro(unquote(name)(record, args)) do
Record.Backend.access(unquote(tag), unquote(fields), record, args, __CALLER__)
end
end
end
@doc """
Define types and specs for the record.
Same as `defrecord/3` but generates private macros.
"""
def deftypes(values, types, env) do
types = types || []
values = lc value inlist values do
{ name, default } = convert_value(value)
{ name, default, find_spec(types, name) }
end
defmacro defrecordp(name, tag \\ nil, kv) do
kv = Macro.escape(kv, unquote: true)
contents = [
core_specs(values),
accessor_specs(values, 1, [])
]
quote bind_quoted: [name: name, tag: tag, kv: kv] do
tag = tag || name
kv = Macro.expand(kv, __ENV__)
# 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
{ fields, _types } = Record.Backend.split_fields_and_types(:defrecordp, kv)
fields = Macro.escape(fields)
@doc """
Define macros for manipulating records.
This is called
directly by `Kernel.defrecordp/3`. It expects the macro name, the
record values and the environment.
## Examples
defmodule CustomRecord do
Record.defmacros :user, [:name, :age], __ENV__
defmacrop(unquote(name)(args \\ [])) do
Record.Backend.access(unquote(tag), unquote(fields), args, __CALLER__)
end
"""
def defmacros(name, values, env, tag \\ nil)
when is_atom(name) and is_list(values) and is_atom(tag) do
escaped = lc value inlist values do
{ key, value } = convert_value(value)
{ key, Macro.escape(value) }
end
tag = tag || name
contents = quote do
defmacrop unquote(name)() do
Record.access(unquote(tag), unquote(escaped), [], __CALLER__)
defmacrop(unquote(name)(record, args)) do
Record.Backend.access(unquote(tag), unquote(fields), record, args, __CALLER__)
end
defmacrop unquote(name)(args) do
Record.access(unquote(tag), unquote(escaped), args, __CALLER__)
end
defmacrop unquote(name)(record, args) do
Record.dispatch(unquote(tag), unquote(escaped), record, args, __CALLER__)
end
end
Module.eval_quoted(env.module, contents, [], env.location)
end
## Callbacks
# Store all optimizable fields in the record as well
@doc false
defmacro __before_compile__(_) do
quote do
def __record__(:optimizable), do: @record_optimizable
end
end
# Store fields that can be optimized and that cannot be
# optimized as they are overridden
@doc false
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
@doc false
# Implements the access macro used by records.
# It returns the index of the given field.
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
# Implements the access macro used by records.
# It returns a quoted expression that defines
# a record or a match in case the record is
# inside a match.
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 = lc { key, _ } inlist remaining, do: key
raise ArgumentError, message: "record #{inspect atom} does not have the key: #{inspect hd(keys)}"
end
end
# Dispatch the call to either get, update or to_list depending on the args given.
@doc false
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, list or keywords"
end
end
# Implements the update macro defined by defmacros.
# It returns a quoted expression that represents
# the access given by the keywords.
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
# Implements the get macro defined by defmacros.
# It returns a quoted expression that represents
# getting the value of a given field.
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
# Define __record__/1, __record__/2, __record__/3 as reflection
# functions that return the record names and fields.
#
# Note that fields are *not* keywords. They are in the same
# order as given as parameter and reflects the order of the
# fields in the tuple.
#
# ## Examples
#
# defrecord FileInfo, atime: nil, mtime: nil
#
# FileInfo.__record__(:name) #=> FileInfo
# FileInfo.__record__(:fields) #=> [atime: nil, mtime: nil]
# FileInfo.__record__(:index, :atime) #=> 1
# FileInfo.__record__(:index, :mtime) #=> 2
#
defp reflection(values) do
quoted = lc { k, _ } inlist 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
# Define initializers methods. For a declaration like:
#
# defrecord FileInfo, atime: nil, mtime: nil
#
# It will define three methods:
#
# def new() do
# new([])
# end
#
# def new([]) do
# { FileInfo, nil, nil }
# end
#
# def new(opts) do
# { FileInfo, Keyword.get(opts, :atime), Keyword.get(opts, :mtime) }
# end
#
defp initializer(values) do
defaults = lc { _, value } inlist 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 = lc { k, v } inlist values, do: initialize_lookup(k, v)
string_selective = lc { k, v } inlist 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
# Define converters method(s). For a declaration like:
#
# defrecord FileInfo, atime: nil, mtime: nil
#
# It will define one method, to_keywords, which will return a Keyword
#
# [atime: nil, mtime: nil]
#
defp conversions(values) do
sorted = lc { k, _ } inlist 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
# Implement accessors. For a declaration like:
#
# defrecord FileInfo, atime: nil, mtime: nil
#
# It will define four methods:
#
# def atime(record) do
# elem(record, 1)
# end
#
# def mtime(record) do
# elem(record, 2)
# end
#
# def atime(value, record) do
# set_elem(record, 1, value)
# end
#
# def mtime(record) do
# set_elem(record, 2, value)
# end
#
# def atime(callback, record) do
# set_elem(record, 1, callback.(elem(record, 1)))
# end
#
# def mtime(callback, record) do
# set_elem(record, 2, callback.(elem(record, 2)))
# 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 =
lc {key, _default} inlist values, do: updater_lookup(key, key, values)
string_fields =
lc {key, _default} inlist 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 = lc { _, _, spec } inlist values, do: spec
options = lc { k, _, v } inlist 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
lc(i inlist other, do: check_value(atom, i))
other
end
defp check_value(atom, other) when is_tuple(other) do
lc(i inlist 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 = lc { k, v } inlist types, name == k, do: v
case matches do
[h|_] -> h
_ -> quote do: term
end
end
end
defmodule Record.DSL do
@moduledoc false
@doc """
Defines the type for each field in the record.
Expects a keyword list.
"""
defmacro record_type(opts) when is_list(opts) do
escaped = lc { k, v } inlist opts, do: { k, Macro.escape(v) }
quote do
@record_types Keyword.merge(@record_types || [], unquote(escaped))
end
end
end
+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
+97 -64
View File
@@ -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,9 +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
## 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"
@@ -58,6 +79,7 @@ defmodule Regex do
iex> Regex.compile("foo")
{:ok, ~r"foo"}
iex> Regex.compile("*foo")
{:error, {'nothing to repeat', 0}}
@@ -82,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
@@ -111,12 +130,12 @@ defmodule Regex do
iex> Regex.match?(~r/foo/, "foo")
true
iex> Regex.match?(~r/foo/, "bar")
false
"""
def match?(regex(re_pattern: compiled), string) do
_ = return_for(string)
def match?(regex(re_pattern: compiled), string) when is_binary(string) do
:re.run(string, compiled, [{ :capture, :none }]) == :match
end
@@ -127,6 +146,7 @@ defmodule Regex do
iex> Regex.regex?(~r/foo/)
true
iex> Regex.regex?(0)
false
@@ -138,27 +158,32 @@ defmodule Regex do
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")
["cd", "d"]
iex> Regex.run(~r/e/, "abcd")
nil
iex> Regex.run(~r/c(d)/, "abcd", return: :index)
[{2,2},{3,1}]
"""
def run(regex, string, options \\ [])
def run(regex(re_pattern: compiled, groups: groups), string, options) do
return = Keyword.get(options, :return, return_for(string))
def run(regex(re_pattern: compiled) = 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
@@ -171,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 """
@@ -227,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 """
@@ -241,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")
[["cd", "d"], ["ce", "e"]]
iex> Regex.scan(~r/c(?:d|e)/, "abcd abce")
[["cd"], ["ce"]]
iex> Regex.scan(~r/e/, "abcd")
[]
"""
def scan(regex, string, options \\ [])
def scan(regex(re_pattern: compiled, groups: groups), string, options) do
return = Keyword.get(options, :return, return_for(string))
def scan(regex(re_pattern: compiled) = 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
@@ -278,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")
["a","b","c"]
iex> Regex.split(~r/-/, "a-b-c", [parts: 2])
["a","b-c"]
iex> Regex.split(~r/-/, "abc")
["abc"]
iex> Regex.split(~r//, "abc")
["a", "b", "c", ""]
iex> Regex.split(~r//, "abc", trim: true)
["a", "b", "c"]
"""
def split(regex, string, options \\ [])
def split(regex(re_pattern: compiled), string, options) do
def split(regex(re_pattern: compiled), string, options) when is_binary(string) do
parts =
cond do
Keyword.get(options, :global) == false -> 2
@@ -303,12 +349,11 @@ 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
@@ -327,20 +372,25 @@ defmodule Regex do
iex> Regex.replace(~r/d/, "abc", "d")
"abc"
iex> Regex.replace(~r/b/, "abc", "d")
"adc"
iex> Regex.replace(~r/b/, "abc", "[&]")
"a[b]c"
iex> Regex.replace(~r/b/, "abc", "[\\&]")
"a[&]c"
iex> Regex.replace(~r/(b)/, "abc", "[\\1]")
"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
@@ -354,13 +404,14 @@ defmodule Regex do
iex> Regex.escape(".")
"\\."
iex> Regex.escape("\\what if")
"\\\\what\\ if"
"""
@spec escape(String.t) :: String.t
def escape(string) do
:re.replace(string, @escape_pattern, "\\\\&", [:global, { :return, return_for(string) }])
def escape(string) when is_binary(string) do
:re.replace(string, @escape_pattern, "\\\\&", [:global, { :return, :binary }])
end
# Helpers
@@ -377,35 +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
IO.write :stderr, "Passing char lists to Regex is deprecated, please use binaries instead\n#{Exception.format_stacktrace}"
:list
end
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
+41 -45
View File
@@ -12,35 +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 new(Enum.t) :: t
defcallback new(Enum.t, (any -> any)) :: t
defcallback delete(t, value) :: t
defcallback difference(t, t) :: t
defcallback disjoint?(t, t) :: boolean
@@ -49,7 +41,6 @@ defmodule Set do
defcallback intersection(t, t) :: t
defcallback member?(t, value) :: boolean
defcallback put(t, value) :: t
defcallback reduce(t, Enumerable.acc, Enumerable.reducer) :: Enumerable.result
defcallback size(t) :: non_neg_integer
defcallback subset?(t, t) :: boolean
defcallback to_list(t) :: list()
@@ -57,10 +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
@@ -70,13 +64,14 @@ defmodule Set do
## Examples
iex> s = HashSet.new([1, 2, 3])
...> Set.delete(s, 4) |> Enum.sort
iex> s = Enum.into([1, 2, 3], set_impl.new)
iex> Set.delete(s, 4) |> Enum.sort
[1, 2, 3]
iex> s = HashSet.new([1, 2, 3])
...> Set.delete(s, 2) |> Enum.sort
iex> s = Enum.into([1, 2, 3], set_impl.new)
iex> Set.delete(s, 2) |> Enum.sort
[1, 3]
"""
@spec delete(t, value) :: t
def delete(set, value) do
@@ -92,7 +87,7 @@ defmodule Set do
## Examples
iex> Set.difference(HashSet.new([1,2]), HashSet.new([2,3,4])) |> Enum.sort
iex> Set.difference(Enum.into([1,2], set_impl.new), Enum.into([2,3,4], set_impl.new)) |> Enum.sort
[1]
"""
@@ -119,9 +114,10 @@ defmodule Set do
## Examples
iex> Set.disjoint?(HashSet.new([1, 2]), HashSet.new([3, 4]))
iex> Set.disjoint?(Enum.into([1, 2], set_impl.new), Enum.into([3, 4], set_impl.new))
true
iex> Set.disjoint?(HashSet.new([1, 2]), HashSet.new([2, 3]))
iex> Set.disjoint?(Enum.into([1, 2], set_impl.new), Enum.into([2, 3], set_impl.new))
false
"""
@@ -142,9 +138,7 @@ defmodule Set do
end
end
@doc """
Returns an empty set of the same type as `set`.
"""
@doc false
@spec empty(t) :: t
def empty(set) do
target(set).empty(set)
@@ -159,10 +153,10 @@ defmodule Set do
## Examples
iex> Set.equal?(HashSet.new([1, 2]), HashSet.new([2, 1, 1]))
iex> Set.equal?(Enum.into([1, 2], set_impl.new), Enum.into([2, 1, 1], set_impl.new))
true
iex> Set.equal?(HashSet.new([1, 2]), HashSet.new([3, 4]))
iex> Set.equal?(Enum.into([1, 2], set_impl.new), Enum.into([3, 4], set_impl.new))
false
"""
@@ -192,10 +186,10 @@ defmodule Set do
## Examples
iex> Set.intersection(HashSet.new([1,2]), HashSet.new([2,3,4])) |> Enum.sort
iex> Set.intersection(Enum.into([1,2], set_impl.new), Enum.into([2,3,4], set_impl.new)) |> Enum.sort
[2]
iex> Set.intersection(HashSet.new([1,2]), HashSet.new([3,4])) |> Enum.sort
iex> Set.intersection(Enum.into([1,2], set_impl.new), Enum.into([3,4], set_impl.new)) |> Enum.sort
[]
"""
@@ -207,7 +201,7 @@ defmodule Set do
if target1 == target2 do
target1.intersection(set1, set2)
else
target1.reduce(set1, { :cont, target1.empty(set1) }, fn v, acc ->
target1.reduce(set1, { :cont, Collectable.empty(set1) }, fn v, acc ->
{ :cont, if(target2.member?(set2, v), do: target1.put(acc, v), else: acc) }
end) |> elem(1)
end
@@ -218,10 +212,10 @@ defmodule Set do
## Examples
iex> Set.member?(HashSet.new([1, 2, 3]), 2)
iex> Set.member?(Enum.into([1, 2, 3], set_impl.new), 2)
true
iex> Set.member?(HashSet.new([1, 2, 3]), 4)
iex> Set.member?(Enum.into([1, 2, 3], set_impl.new), 4)
false
"""
@@ -235,10 +229,10 @@ defmodule Set do
## Examples
iex> Set.put(HashSet.new([1, 2, 3]), 3) |> Enum.sort
iex> Set.put(Enum.into([1, 2, 3], set_impl.new), 3) |> Enum.sort
[1, 2, 3]
iex> Set.put(HashSet.new([1, 2, 3]), 4) |> Enum.sort
iex> Set.put(Enum.into([1, 2, 3], set_impl.new), 4) |> Enum.sort
[1, 2, 3, 4]
"""
@@ -252,7 +246,7 @@ defmodule Set do
## Examples
iex> Set.size(HashSet.new([1, 2, 3]))
iex> Set.size(Enum.into([1, 2, 3], set_impl.new))
3
"""
@@ -270,10 +264,12 @@ defmodule Set do
## Examples
iex> Set.subset?(HashSet.new([1, 2]), HashSet.new([1, 2, 3]))
iex> Set.subset?(Enum.into([1, 2], set_impl.new), Enum.into([1, 2, 3], set_impl.new))
true
iex> Set.subset?(HashSet.new([1, 2, 3]), HashSet.new([1, 2]))
iex> Set.subset?(Enum.into([1, 2, 3], set_impl.new), Enum.into([1, 2], set_impl.new))
false
"""
@spec subset?(t, t) :: boolean
def subset?(set1, set2) do
@@ -292,7 +288,7 @@ defmodule Set do
## Examples
iex> HashSet.to_list(HashSet.new([1, 2, 3])) |> Enum.sort
iex> set_impl.to_list(Enum.into([1, 2, 3], set_impl.new)) |> Enum.sort
[1,2,3]
"""
@@ -310,7 +306,7 @@ defmodule Set do
## Examples
iex> Set.union(HashSet.new([1,2]), HashSet.new([2,3,4])) |> Enum.sort
iex> Set.union(Enum.into([1,2], set_impl.new), Enum.into([2,3,4], set_impl.new)) |> Enum.sort
[1,2,3,4]
"""
+75 -46
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,18 +161,6 @@ defmodule Stream do
## Transformers
@doc false
@spec unquote(:after)(Enumerable.t, (() -> term)) :: Enumerable.t
def unquote(:after)(Lazy[after: funs] = lazy, fun) do
IO.write :stderr, "Stream.after/1 is deprecated and will be removed/1\n#{Exception.format_stacktrace}"
lazy.after([fun|funs])
end
def unquote(:after)(enum, fun) do
IO.write :stderr, "Stream.after/1 is deprecated and will be removed/1\n#{Exception.format_stacktrace}"
Lazy[enum: enum, after: [fun]]
end
@doc """
Shortcut to `chunk(enum, n, n)`.
"""
@@ -208,10 +184,13 @@ defmodule Stream do
iex> Stream.chunk([1, 2, 3, 4, 5, 6], 2) |> Enum.to_list
[[1, 2], [3, 4], [5, 6]]
iex> Stream.chunk([1, 2, 3, 4, 5, 6], 3, 2) |> Enum.to_list
[[1, 2, 3], [3, 4, 5]]
iex> Stream.chunk([1, 2, 3, 4, 5, 6], 3, 2, [7]) |> Enum.to_list
[[1, 2, 3], [3, 4, 5], [5, 6, 7]]
iex> Stream.chunk([1, 2, 3, 4, 5, 6], 3, 3, []) |> Enum.to_list
[[1, 2, 3], [4, 5, 6]]
@@ -402,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.
@@ -439,6 +456,18 @@ defmodule Stream do
This is useful when a stream needs to be run, for side effects,
and there is no interest in its return result.
## Examples
Open up a file, replace all `#` by `%` and stream to another file
without loading the whole file in memory:
stream = File.stream!("code")
|> Stream.map(&String.replace(&1, "#", "%"))
|> Stream.into(File.stream!("new"))
No computation will be done until we call one of the Enum functions
or `Stream.run/1`.
"""
@spec run(Enumerable.t) :: :ok
def run(stream) do
@@ -576,7 +605,6 @@ defmodule Stream do
lazy enum, fn(f1) -> R.take_while(fun, f1) end
end
@doc """
Transforms an existing stream.
@@ -997,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
@@ -1032,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
+69 -4
View File
@@ -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"]
@@ -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"]
@@ -199,8 +204,10 @@ defmodule String do
iex> String.split("a,b,c", ~r{,})
["a", "b", "c"]
iex> String.split("a,b,c", ~r{,}, global: false)
["a", "b,c"]
iex> String.split(" a b c ", ~r{\s}, trim: true)
["a", "b", "c"]
@@ -208,10 +215,13 @@ defmodule String do
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"]
@@ -232,7 +242,7 @@ defmodule String do
splits = :binary.split(binary, pattern, opts)
if Keyword.get(options, :trim, false) do
lc split inlist splits, split != "", do: split
for split <- splits, split != "", do: split
else
splits
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é"
@@ -423,6 +439,7 @@ defmodule String do
iex> String.rjust("abc", 5)
" abc"
iex> String.rjust("abc", 5, ?-)
"--abc"
@@ -447,6 +464,7 @@ defmodule String do
iex> String.ljust("abc", 5)
"abc "
iex> String.ljust("abc", 5, ?-)
"abc--"
@@ -493,6 +511,7 @@ defmodule String do
iex> String.replace("a,b,c", ",", "-")
"a-b-c"
iex> String.replace("a,b,c", ",", "-", global: false)
"a-b,c"
@@ -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"
@@ -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("ἅἪῼ")
["ἅ","Ἢ","ῼ"]
@@ -627,10 +654,13 @@ defmodule String do
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
@@ -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
@@ -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
@@ -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
@@ -1027,6 +1085,7 @@ defmodule String do
iex> String.match?("foo", ~r/foo/)
true
iex> String.match?("bar", ~r/foo/)
false
@@ -1045,8 +1104,10 @@ defmodule String do
iex> String.contains? "elixir of life", "of"
true
iex> String.contains? "elixir of life", ["life", "death"]
true
iex> String.contains? "elixir of life", ["death", "mercury"]
false
@@ -1098,6 +1159,7 @@ defmodule String do
iex> String.to_char_list("æß")
{ :ok, 'æß' }
iex> String.to_char_list("abc")
{ :ok, 'abc' }
@@ -1127,6 +1189,7 @@ defmodule String do
iex> String.to_char_list!("æß")
'æß'
iex> String.to_char_list!("abc")
'abc'
@@ -1152,6 +1215,7 @@ defmodule String do
iex> String.from_char_list([0x00E6, 0x00DF])
{ :ok, "æß" }
iex> String.from_char_list([0x0061, 0x0062, 0x0063])
{ :ok, "abc" }
@@ -1179,6 +1243,7 @@ defmodule String do
iex> String.from_char_list!([0x00E6, 0x00DF])
"æß"
iex> String.from_char_list!([0x0061, 0x0062, 0x0063])
"abc"
+1
View File
@@ -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
+13
View File
@@ -331,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
+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 }
"""
+19 -29
View File
@@ -62,27 +62,22 @@ 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"
"""
def encode_query(l), do: Enum.map_join(l, "&", &pair/1)
@doc false
def decode_query(q) do
IO.write :stderr, "URI.decode_query/1 is deprecated, please use URI.decode_query/2 with an explicit argument\n#{Exception.format_stacktrace}"
decode_query(q, HashDict.new)
end
@doc """
Decodes a query string into a dictionary.
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
@@ -92,14 +87,11 @@ defmodule URI do
## Examples
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) 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
@@ -137,14 +129,12 @@ defmodule URI do
{ current, next }
end
defp pair({k, v}) when is_list(k) do
IO.write :stderr, "Passing list keys to URI.encode_query/1 is deprecated\n#{Exception.format_stacktrace}"
encode(to_string(k)) <> "=" <> encode(to_string(v))
defp pair({k, _}) when is_list(k) do
raise ArgumentError, message: "encode_query/1 keys cannot be lists, got: #{inspect k}"
end
defp pair({k, v}) when is_list(v) do
IO.write :stderr, "Passing list values to URI.encode_query/1 is deprecated\n#{Exception.format_stacktrace}"
encode(to_string(k)) <> "=" <> encode(to_string(v))
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
@@ -156,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: <<?->>
@@ -184,8 +174,8 @@ 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(uri) do
@@ -272,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
+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|17).*"}.
{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.
+7 -30
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() ->
@@ -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" });
+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
+1 -7
View File
@@ -5,12 +5,6 @@
expand(Args, E) ->
lists:mapfoldl(fun
({ '//', Meta, [Left, Right] }, Acc) ->
elixir_errors:deprecation(Meta, E#elixir_env.file, "using // for default arguments is deprecated, "
"please use \\\\ instead"),
{ ELeft, EL } = elixir_exp:expand(Left, Acc),
{ ERight, _ } = elixir_exp:expand(Right, Acc#elixir_env{context=nil}),
{ { '\\\\', Meta, [ELeft, ERight] }, EL };
({ '\\\\', Meta, [Left, Right] }, Acc) ->
{ ELeft, EL } = elixir_exp:expand(Left, Acc),
{ ERight, _ } = elixir_exp:expand(Right, Acc#elixir_env{context=nil}),
@@ -31,7 +25,7 @@ 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,
+6 -1
View File
@@ -14,6 +14,7 @@
-define(kernel, 'Elixir.Kernel').
-define(node, 'Elixir.Node').
-define(process, 'Elixir.Process').
-define(system, 'Elixir.System').
default_functions() ->
[ { ?kernel, elixir_imported_functions() } ].
@@ -113,7 +114,7 @@ expand_import(Meta, { Name, Arity } = Tuple, Args, E, Extra) ->
case Dispatch of
%% In case it is an import, we dispatch the import.
{ Kind, _ } when Kind == import ->
{ import, _ } ->
do_expand_import(Meta, Tuple, Args, Module, E, Dispatch);
%% There is a local and an import. This is a conflict unless
@@ -395,6 +396,7 @@ 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 };
@@ -409,6 +411,7 @@ 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 };
@@ -451,4 +454,6 @@ 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}.
+14 -24
View File
@@ -7,6 +7,8 @@
deprecation/3, deprecation/4]).
-include("elixir.hrl").
-type line_or_meta() :: integer() | list().
warn(Warning) ->
CompilerPid = get(elixir_compiler_pid),
if
@@ -24,13 +26,13 @@ warn(Line, File, 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)).
@@ -40,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
@@ -204,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.
+21 -7
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,7 +123,9 @@ 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
@@ -172,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';
@@ -247,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) ->
@@ -403,7 +417,7 @@ expand_arg(Arg, { Acc1, Acc2 }) ->
{ 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) }.
+3 -3
View File
@@ -98,7 +98,7 @@ do_try(_Meta, { 'after', Expr }, E) ->
do_try(Meta, { 'else', _ } = Else, E) ->
expand_without_export(Meta, 'try', expand_arg(Meta, 'try', 'else'), Else, E);
do_try(Meta, { 'catch', _ } = Catch, E) ->
expand_without_export(Meta, 'try', fun elixir_exp:expand_many/2, Catch, E);
expand_without_export(Meta, 'try', fun elixir_exp:expand_args/2, Catch, E);
do_try(Meta, { 'rescue', _ } = Rescue, E) ->
expand_without_export(Meta, 'try', fun expand_rescue/3, Rescue, E);
do_try(Meta, { Key, _ }, E) ->
@@ -176,8 +176,8 @@ expand_arg(Meta, Kind, Key) ->
%% Expands all -> pairs in a given key keeping the overall vars.
expand_with_export(Meta, Kind, Fun, { Key, Clauses }, Acc, E) when is_list(Clauses) ->
EFun =
case lists:keyfind(export_all, 1, Meta) of
{ export_all, true } -> Fun;
case lists:keyfind(export_head, 1, Meta) of
{ export_head, true } -> Fun;
_ -> fun(ExportArgs, ExportE) -> Fun(export_vars(ExportArgs), ExportE) end
end,
Transformer = fun(Clause, Vars) ->
+7 -6
View File
@@ -6,15 +6,16 @@
translate(Meta, Clauses, S) ->
Transformer = fun({ '->', CMeta, [ArgsWithGuards, Expr] }, Acc) ->
{ Args, Guards } = elixir_clauses:extract_splat_guards(ArgsWithGuards),
{ TClause, TS } = elixir_clauses:clause(?line(CMeta), fun translate_fn_match/2, Args, Expr, Guards, Acc),
{ TClause, TS } = elixir_clauses:clause(?line(CMeta), fun translate_fn_match/2,
Args, Expr, Guards, true, Acc),
{ TClause, elixir_scope:mergef(S, TS) }
end,
{ TClauses, NS } = lists:mapfoldl(Transformer, S, Clauses),
Arities = [length(Args) || { clause, _Line, Args, _Guards, _Exprs } <- TClauses],
case length(lists:usort(Arities)) of
1 ->
case lists:usort(Arities) of
[_] ->
{ { 'fun', ?line(Meta), { clauses, TClauses } }, NS };
_ ->
compile_error(Meta, S#elixir_scope.file,
@@ -22,14 +23,14 @@ translate(Meta, Clauses, S) ->
end.
translate_fn_match(Arg, S) ->
{ TArg, TS } = elixir_translator:translate_many(Arg, S#elixir_scope{extra=fn_match}),
{ TArg, TS#elixir_scope{extra=S#elixir_scope.extra} }.
{ TArg, TS } = elixir_translator:translate_args(Arg, S#elixir_scope{backup_vars=orddict:new()}),
{ TArg, TS#elixir_scope{backup_vars=S#elixir_scope.backup_vars} }.
%% Expansion
expand(Meta, Clauses, E) when is_list(Clauses) ->
Transformer = fun(Clause) ->
{ EClause, _ } = elixir_exp_clauses:clause(Meta, fn, fun elixir_exp:expand_many/2, Clause, E),
{ EClause, _ } = elixir_exp_clauses:clause(Meta, fn, fun elixir_exp:expand_args/2, Clause, E),
EClause
end,
{ { fn, Meta, lists:map(Transformer, Clauses) }, E }.
+338
View File
@@ -0,0 +1,338 @@
-module(elixir_for).
-export([expand/3, translate/3]).
-include("elixir.hrl").
%% Expansion
expand(Meta, Args, E) ->
{ Cases, Block } =
case elixir_utils:split_last(Args) of
{ OuterCases, OuterOpts } when is_list(OuterOpts) ->
case elixir_utils:split_last(OuterCases) of
{ InnerCases, InnerOpts } when is_list(InnerOpts) ->
{ InnerCases, InnerOpts ++ OuterOpts };
_ ->
{ OuterCases, OuterOpts }
end;
_ ->
{ Args, [] }
end,
{ Expr, Opts } =
case lists:keyfind(do, 1, Block) of
{ do, Do } -> { Do, lists:keydelete(do, 1, Block) };
_ -> elixir_errors:compile_error(Meta, E#elixir_env.file,
"missing do keyword in for comprehension")
end,
{ EOpts, EO } = elixir_exp:expand(Opts, E),
{ ECases, EC } = lists:mapfoldl(fun expand/2, EO, Cases),
{ EExpr, _ } = elixir_exp:expand(Expr, EC),
{ { for, Meta, ECases ++ [[{do,EExpr}|EOpts]] }, E }.
expand({'<-', Meta, [Left, Right]}, E) ->
{ ERight, ER } = elixir_exp:expand(Right, E),
{ ELeft, EL } = elixir_exp_clauses:match(fun elixir_exp:expand/2, Left, E),
{ { '<-', Meta, [ELeft, ERight] }, elixir_env:mergev(EL, ER) };
expand({ '<<>>', Meta, Args } = X, E) when is_list(Args) ->
case elixir_utils:split_last(Args) of
{ LeftStart, {'<-', OpMeta, [LeftEnd, Right] } } ->
{ ERight, ER } = elixir_exp:expand(Right, E),
Left = { '<<>>', Meta, LeftStart ++ [LeftEnd] },
{ ELeft, EL } = elixir_exp_clauses:match(fun elixir_exp:expand/2, Left, E),
{ { '<<>>', [], [ { '<-', OpMeta, [ELeft, ERight] }] }, elixir_env:mergev(EL, ER) };
_ ->
elixir_exp:expand(X, E)
end;
expand(X, E) ->
elixir_exp:expand(X, E).
%% Translation
translate(Meta, Args, #elixir_scope{return=Return} = RS) ->
S = RS#elixir_scope{return=true},
{ AccName, _, SA } = elixir_scope:build_var('_', S),
{ VarName, _, SV } = elixir_scope:build_var('_', SA),
Line = ?line(Meta),
Acc = { var, Line, AccName },
Var = { var, Line, VarName },
{ Cases, [{do,Expr}|Opts] } = elixir_utils:split_last(Args),
{ TInto, SI } =
case lists:keyfind(into, 1, Opts) of
{ into, Into } -> elixir_translator:translate(Into, SV);
false when Return -> { { nil, Line }, SV };
false -> { false, SV }
end,
{ TCases, SC } = translate_gen(Meta, Cases, [], SI),
{ TExpr, SE } = elixir_translator:translate_block(Expr, Return, SC),
SF = elixir_scope:mergef(SI, SE),
case comprehension_expr(TInto, TExpr) of
{ inline, TIntoExpr } ->
{ build_inline(Line, TCases, TIntoExpr, TInto, Var, Acc, SE), SF };
{ into, TIntoExpr } ->
build_into(Line, TCases, TIntoExpr, TInto, Var, Acc, SF)
end.
translate_gen(ForMeta, [{ '<-', Meta, [Left, Right] }|T], Acc, S) ->
{ TLeft, TRight, TFilters, TT, TS } = translate_gen(Meta, Left, Right, T, S),
TAcc = [{ enum, Meta, TLeft, TRight, TFilters }|Acc],
translate_gen(ForMeta, TT, TAcc, TS);
translate_gen(ForMeta, [{ '<<>>', _, [ { '<-', Meta, [Left, Right] } ] }|T], Acc, S) ->
{ TLeft, TRight, TFilters, TT, TS } = translate_gen(Meta, Left, Right, T, S),
TAcc = [{ bin, Meta, TLeft, TRight, TFilters }|Acc],
case elixir_bitstring:has_size(TLeft) of
true -> translate_gen(ForMeta, TT, TAcc, TS);
false ->
elixir_errors:compile_error(Meta, S#elixir_scope.file,
"bitstring fields without size are not allowed in bitstring generators")
end;
translate_gen(_ForMeta, [], Acc, S) ->
{ lists:reverse(Acc), S };
translate_gen(ForMeta, _, _, S) ->
elixir_errors:compile_error(ForMeta, S#elixir_scope.file,
"for comprehensions must start with a generator").
translate_gen(_Meta, Left, Right, T, S) ->
{ TRight, SR } = elixir_translator:translate(Right, S),
{ TLeft, SL } = elixir_clauses:match(fun elixir_translator:translate/2, Left, SR),
{ TT, { TFilters, TS } } = translate_filters(T, SL),
{ TLeft, TRight, TFilters, TT, TS }.
translate_filters(T, S) ->
{ Filters, Rest } = collect_filters(T, []),
{ Rest, lists:mapfoldr(fun translate_filter/2, S, Filters) }.
translate_filter(Filter, S) ->
{ TFilter, TS } = elixir_translator:translate(Filter, S),
case elixir_utils:returns_boolean(TFilter) of
true ->
{ { nil, TFilter }, TS };
false ->
{ Name, _, VS } = elixir_scope:build_var('_', TS),
{ { { var, 0, Name }, TFilter }, VS }
end.
collect_filters([{ '<-', _, [_, _] }|_] = T, Acc) ->
{ Acc, T };
collect_filters([{ '<<>>', _, [{ '<-', _, [_, _] }] }|_] = T, Acc) ->
{ Acc, T };
collect_filters([H|T], Acc) ->
collect_filters(T, [H|Acc]);
collect_filters([], Acc) ->
{ Acc, [] }.
%% If all we have is one enum generator, we check if it is a list
%% for optimization otherwise fallback to the reduce generator.
build_inline(Line, [{ enum, Meta, Left, Right, Filters }] = Orig, Expr, Into, Var, Acc, S) ->
case Right of
{ cons, _, _, _ } ->
build_comprehension(Line, Orig, Expr, Into);
{ Other, _, _ } when Other == tuple; Other == map; Other == integer; Other == atom ->
build_reduce(Orig, Expr, Into, Acc, S);
_ ->
Clauses = [{ enum, Meta, Left, Var, Filters }],
{'case', -1, Right, [
{clause, -1,
[Var],
[[?wrap_call(Line, erlang, is_list, [Var])]],
[build_comprehension(Line, Clauses, Expr, Into)]},
{clause, -1,
[Var],
[],
[build_reduce(Clauses, Expr, Into, Acc, S)]}
]}
end;
build_inline(Line, Clauses, Expr, Into, _Var, Acc, S) ->
case lists:all(fun(Clause) -> element(1, Clause) == bin end, Clauses) of
true -> build_comprehension(Line, Clauses, Expr, Into);
false -> build_reduce(Clauses, Expr, Into, Acc, S)
end.
build_into(Line, Clauses, Expr, Into, Fun, Acc, S) ->
{ Kind, SK } = build_var(Line, S),
{ Reason, SR } = build_var(Line, SK),
{ Stack, ST } = build_var(Line, SR),
{ Done, SD } = build_var(Line, ST),
IntoExpr = {call, Line, Fun, [Acc, pair(Line, cont, Expr)]},
MatchExpr = {match, Line,
{tuple, Line, [Acc, Fun]},
?wrap_call(Line, 'Elixir.Collectable', into, [Into])
},
TryExpr =
{'try', Line,
[build_reduce_clause(Clauses, IntoExpr, Acc, Acc, SD)],
[{clause, Line,
[Done],
[],
[{call, Line, Fun, [Done, {atom, Line, done}]}]}],
[{clause, Line,
[{tuple, Line, [Kind, Reason, {var, Line, '_'}]}],
[],
[{match, Line, Stack, ?wrap_call(Line, erlang, get_stacktrace, [])},
{call, Line, Fun, [Acc, {atom, Line, halt}]},
?wrap_call(Line, erlang, raise, [Kind, Reason, Stack])]}],
[]},
{ {block, Line, [MatchExpr, TryExpr]}, SD }.
%% Helpers
build_reduce(Clauses, Expr, false, Acc, S) ->
build_reduce_clause(Clauses, Expr, {nil, 0}, Acc, S);
build_reduce(Clauses, Expr, {nil, Line} = Into, Acc, S) ->
ListExpr = {cons, Line, Expr, Acc},
?wrap_call(Line, lists, reverse,
[build_reduce_clause(Clauses, ListExpr, Into, Acc, S)]);
build_reduce(Clauses, Expr, {bin, _, _} = Into, Acc, S) ->
{bin, Line, Elements} = Expr,
BinExpr = {bin, Line, [{bin_element, Line, Acc, default, [bitstring]}|Elements]},
build_reduce_clause(Clauses, BinExpr, Into, Acc, S).
build_reduce_clause([{ enum, Meta, Left, Right, Filters }|T], Expr, Arg, Acc, S) ->
Line = ?line(Meta),
Inner = build_reduce_clause(T, Expr, Acc, Acc, S),
True = pair(Line, cont, Inner),
False = pair(Line, cont, Acc),
Clauses0 =
case is_var(Left) of
true -> [];
false ->
[{clause, -1,
[{var, Line, '_'}, Acc], [],
[False]}]
end,
Clauses1 =
[{clause, Line,
[Left, Acc], [],
[join_filters(Line, Filters, True, False)]}|Clauses0],
Args = [Right, pair(Line, cont, Arg), {'fun', Line, {clauses, Clauses1}}],
Tuple = ?wrap_call(Line, 'Elixir.Enumerable', reduce, Args),
%% Use -1 because in case of no returns we don't care about the result
?wrap_call(-1, erlang, element, [{integer, Line, 2}, Tuple]);
build_reduce_clause([{ bin, Meta, Left, Right, Filters }|T], Expr, Arg, Acc, S) ->
Line = ?line(Meta),
{ Tail, ST } = build_var(Line, S),
{ Fun, SF } = build_var(Line, ST),
True = build_reduce_clause(T, Expr, Acc, Acc, SF),
False = Acc,
{bin, _, Elements} = Left,
BinMatch =
{bin, Line, Elements ++ [{ bin_element, Line, Tail, default, [bitstring] }]},
NoVarMatch =
{bin, Line, no_var(Elements) ++ [{ bin_element, Line, Tail, default, [bitstring] }]},
Clauses =
[{clause, Line,
[BinMatch, Acc], [],
[{call, Line, Fun, [Tail, join_filters(Line, Filters, True, False)]}]},
{clause, -1,
[NoVarMatch, Acc], [],
[{call, Line, Fun, [Tail, False]}]},
{clause, -1,
[{bin, Line, []}, Acc], [],
[Acc]},
{clause, -1,
[Tail, {var, Line, '_'}], [],
[?wrap_call(Line, erlang, error, [pair(Line, badarg, Tail)])]}],
{call, Line,
{named_fun, Line, element(3, Fun), Clauses},
[Right, Arg]};
build_reduce_clause([], Expr, _Arg, _Acc, _S) ->
Expr.
is_var({var, _, _}) -> true;
is_var(_) -> false.
pair(Line, Atom, Arg) ->
{tuple, Line, [{atom, Line, Atom}, Arg]}.
build_var(Line, S) ->
{ Name, _, ST } = elixir_scope:build_var('_', S),
{ { var, Line, Name }, ST }.
no_var(Elements) ->
[{bin_element, Line, no_var_expr(Expr), Size, Types} ||
{bin_element, Line, Expr, Size, Types} <- Elements].
no_var_expr({ var, Line, _ }) ->
{var, Line, '_'}.
build_comprehension(Line, Clauses, Expr, false) ->
{block, Line, [
build_comprehension(Line, Clauses, Expr, {nil, Line}),
{nil, Line}
]};
build_comprehension(Line, Clauses, Expr, Into) ->
{comprehension_kind(Into), Line, Expr, comprehension_clause(Clauses)}.
comprehension_clause([{ Kind, Meta, Left, Right, Filters }|T]) ->
Line = ?line(Meta),
[{comprehension_generator(Kind), Line, Left, Right}] ++
comprehension_filter(Line, Filters) ++
comprehension_clause(T);
comprehension_clause([]) ->
[].
comprehension_kind({ nil, _ }) -> lc;
comprehension_kind({ bin, _, [] }) -> bc.
comprehension_generator(enum) -> generate;
comprehension_generator(bin) -> b_generate.
comprehension_expr({ bin, _, [] }, { bin, _, _ } = Expr) ->
{ inline, Expr };
comprehension_expr({ bin, Line, [] }, Expr) ->
BinExpr = { bin, Line, [{ bin_element, Line, Expr, default, [bitstring] }] },
{ inline, BinExpr };
comprehension_expr({ nil, _ }, Expr) ->
{ inline, Expr };
comprehension_expr(false, Expr) ->
{ inline, Expr };
comprehension_expr(_, Expr) ->
{ into, Expr }.
comprehension_filter(Line, Filters) ->
[join_filter(Line, Filter, { atom, Line, true }, { atom, Line, false }) ||
Filter <- lists:reverse(Filters)].
join_filters(_Line, [], True, _False) ->
True;
join_filters(Line, [H|T], True, False) ->
lists:foldl(fun(Filter, Acc) ->
join_filter(Line, Filter, Acc, False)
end, join_filter(Line, H, True, False), T).
join_filter(Line, { nil, Filter }, True, False) ->
{'case', Line, Filter, [
{clause, Line, [{atom, Line, true}], [], [True] },
{clause, Line, [{atom, Line, false}], [], [False] }
] };
join_filter(Line, { Var, Filter }, True, False) ->
Guard =
{op, Line, 'orelse',
{op, Line, '==', Var, {atom, Line, false}},
{op, Line, '==', Var, {atom, Line, nil}}},
{'case', Line, Filter, [
{clause, Line, [Var], [[Guard]], [False] },
{clause, Line, [{var, Line, '_'}], [], [True] }
] }.
+34 -31
View File
@@ -176,35 +176,38 @@ remove_internals(Set) ->
%% Special forms
special_form('&',1) -> true;
special_form('^',1) -> true;
special_form('=',2) -> true;
special_form('__op__',2) -> true;
special_form('__op__',3) -> true;
special_form('__block__',_) -> true;
special_form('->',_) -> true;
special_form('<<>>',_) -> true;
special_form('{}',_) -> true;
special_form('alias',1) -> true;
special_form('alias',2) -> true;
special_form('require',1) -> true;
special_form('require',2) -> true;
special_form('import',1) -> true;
special_form('import',2) -> true;
special_form('__ENV__',0) -> true;
special_form('__CALLER__',0) -> true;
special_form('__MODULE__',0) -> true;
special_form('__DIR__',0) -> true;
special_form('__aliases__',_) -> true;
special_form('quote',1) -> true;
special_form('quote',2) -> true;
special_form('unquote',1) -> true;
special_form('unquote_splicing',1) -> true;
special_form('fn',_) -> true;
special_form('super',_) -> true;
special_form('bc',_) -> true;
special_form('lc',_) -> true;
special_form('case',2) -> true;
special_form('try',2) -> true;
special_form('receive',1) -> true;
special_form('&', 1) -> true;
special_form('^', 1) -> true;
special_form('=', 2) -> true;
special_form('%', 2) -> true;
special_form('__op__', 2) -> true;
special_form('__op__', 3) -> true;
special_form('__block__', _) -> true;
special_form('->', _) -> true;
special_form('<<>>', _) -> true;
special_form('{}', _) -> true;
special_form('%{}', _) -> true;
special_form('alias', 1) -> true;
special_form('alias', 2) -> true;
special_form('require', 1) -> true;
special_form('require', 2) -> true;
special_form('import', 1) -> true;
special_form('import', 2) -> true;
special_form('__ENV__', 0) -> true;
special_form('__CALLER__', 0) -> true;
special_form('__MODULE__', 0) -> true;
special_form('__DIR__', 0) -> true;
special_form('__aliases__', _) -> true;
special_form('quote', 1) -> true;
special_form('quote', 2) -> true;
special_form('unquote', 1) -> true;
special_form('unquote_splicing', 1) -> true;
special_form('fn', _) -> true;
special_form('super', _) -> true;
special_form('for', _) -> true;
special_form('bc', _) -> true;
special_form('lc', _) -> true;
special_form('case', 2) -> true;
special_form('try', 2) -> true;
special_form('receive', 1) -> true;
special_form(_, _) -> false.
+6
View File
@@ -27,6 +27,12 @@ extract(Line, _Scope, _Interpol, [], _Buffer, _Search, _Output, Last) ->
%% Going through the string
extract(Line, Scope, Interpol, [$\\, $\n|Rest], Buffer, Search, Output, Last) ->
extract(Line+1, Scope, Interpol, Rest, Buffer, Search, Output, Last);
extract(Line, Scope, Interpol, [$\\, $\r, $\n|Rest], Buffer, Search, Output, Last) ->
extract(Line+1, Scope, Interpol, Rest, Buffer, Search, Output, Last);
extract(Line, Scope, Interpol, [$\n|Rest], Buffer, Search, Output, Last) ->
extract(Line+1, Scope, Interpol, Rest, [$\n|Buffer], Search, Output, Last);
+8 -9
View File
@@ -6,13 +6,12 @@
record_definition/3, record_defaults/4,
ensure_no_function_conflict/4, warn_unused_local/3, format_error/1
]).
-export([macro_for/3, function_for/3]).
-export([macro_for/3, local_for/3, local_for/4]).
-include("elixir.hrl").
-define(attr, '__locals_tracker').
-define(tracker, 'Elixir.Module.LocalsTracker').
macro_for(nil, _Name, _Arity) -> false;
macro_for(Module, Name, Arity) ->
Tuple = { Name, Arity },
try elixir_def:lookup_definition(Module, Tuple) of
@@ -25,13 +24,13 @@ macro_for(Module, Name, Arity) ->
error:badarg -> false
end.
function_for(Module, Name, Arity) ->
local_for(Module, Name, Arity) ->
local_for(Module, Name, Arity, nil).
local_for(Module, Name, Arity, Given) ->
Tuple = { Name, Arity },
case elixir_def:lookup_definition(Module, Tuple) of
{ { Tuple, _, Line, _, _, _, _ }, Clauses } when Clauses /= [] ->
%% There is no need to record such calls
%% since they come from funtions that were
%% already analyzed at compilation time.
{ { Tuple, Kind, Line, _, _, _, _ }, [_|_] = Clauses }
when Given == nil; Kind == Given ->
get_function(Line, Module, Clauses);
_ ->
[_|T] = erlang:get_stacktrace(),
@@ -40,14 +39,14 @@ function_for(Module, Name, Arity) ->
get_function(Line, Module, Clauses) ->
RewrittenClauses = [rewrite_clause(Clause, Module) || Clause <- Clauses],
Fun = { 'fun', Line, {clauses, RewrittenClauses } },
Fun = { 'fun', Line, { clauses, RewrittenClauses } },
{ value, Result, _Binding } = erl_eval:exprs([Fun], []),
Result.
rewrite_clause({ call, Line, { atom, Line, RawName }, Args }, Module) ->
Remote = { remote, Line,
{ atom, Line, ?MODULE },
{ atom, Line, function_for }
{ atom, Line, local_for }
},
%% If we have a macro, its arity in the table is
+175
View File
@@ -0,0 +1,175 @@
-module(elixir_map).
-export([expand_map/3, translate_map/3, expand_struct/4, translate_struct/4]).
-import(elixir_errors, [compile_error/4]).
-include("elixir.hrl").
expand_map(Meta, [{ '|', UpdateMeta, [Left, Right]}], E) ->
{ [ELeft|ERight], EA } = elixir_exp:expand_args([Left|Right], E),
{ { '%{}', Meta, [{ '|', UpdateMeta, [ELeft, ERight] }] }, EA };
expand_map(Meta, Args, E) ->
{ EArgs, EA } = elixir_exp:expand_args(Args, E),
{ { '%{}', Meta, EArgs }, EA }.
expand_struct(Meta, Left, Right, E) ->
{ [ELeft, ERight], EE } = elixir_exp:expand_args([Left, Right], E),
case is_atom(ELeft) of
true -> ok;
false ->
compile_error(Meta, E#elixir_env.file, "expected struct name to be a compile "
"time atom or alias, got: ~ts", ['Elixir.Macro':to_string(ELeft)])
end,
EMeta =
case lists:member(ELeft, E#elixir_env.context_modules) of
true ->
case (ELeft == E#elixir_env.module) and
(E#elixir_env.function == nil) of
true ->
compile_error(Meta, E#elixir_env.file,
"cannot access struct ~ts in body of the module that defines it as "
"the struct fields are not yet accessible",
[elixir_aliases:inspect(ELeft)]);
false ->
[{ struct, context }|Meta]
end;
false ->
Meta
end,
case ERight of
{ '%{}', _, _ } -> ok;
_ -> compile_error(Meta, E#elixir_env.file,
"expected struct to be followed by a map, got: ~ts",
['Elixir.Macro':to_string(ERight)])
end,
{ { '%', EMeta, [ELeft, ERight] }, EE }.
translate_map(Meta, Args, S) ->
{ Assocs, TUpdate, US } = extract_assoc_update(Args, S),
translate_map(Meta, Assocs, TUpdate, US).
translate_struct(Meta, Name, { '%{}', MapMeta, Args }, S) ->
{ Assocs, TUpdate, US } = extract_assoc_update(Args, S),
Struct = load_struct(Meta, Name, S),
case is_map(Struct) of
true ->
assert_struct_keys(Meta, Name, Struct, Assocs, S);
false ->
compile_error(Meta, S#elixir_scope.file, "expected ~ts.__struct__/0 to "
"return a map, got: ~ts", [elixir_aliases:inspect(Name), 'Elixir.Kernel':inspect(Struct)])
end,
if
TUpdate /= nil ->
Line = ?line(Meta),
{ VarName, _, VS } = elixir_scope:build_var('_', US),
Var = { var, Line, VarName },
Map = { map, Line, [{ map_field_exact, Line, { atom, Line, '__struct__' }, { atom, Line, Name }}] },
Match = { match, Line, Var, Map },
Error = { tuple, Line, [{ atom, Line, badstruct }, { atom, Line, Name }, Var] },
{ TMap, TS } = translate_map(MapMeta, Assocs, Var, VS),
{ { 'case', Line, TUpdate, [
{ clause, Line, [Match], [], [TMap] },
{ clause, Line, [Var], [], [?wrap_call(Line, erlang, error, [Error])] }
] }, TS };
S#elixir_scope.context == match ->
translate_map(MapMeta, Assocs ++ [{'__struct__', Name}], nil, US);
true ->
Keys = [K || {K,_} <- Assocs],
{ StructAssocs, _ } = elixir_quote:escape(maps:to_list(maps:without(Keys, Struct)), false),
translate_map(MapMeta, StructAssocs ++ Assocs ++ [{'__struct__', Name}], nil, US)
end.
%% Helpers
load_struct(Meta, Name, S) ->
Local =
elixir_module:is_open(Name) andalso
(case lists:keyfind(struct, 1, Meta) of
{ struct, context } -> true;
_ -> wait_for_struct(Name)
end),
try
case Local of
true ->
try
(elixir_locals:local_for(Name, '__struct__', 0, def))()
catch
error:undef -> Name:'__struct__'();
error:badarg -> Name:'__struct__'()
end;
false ->
Name:'__struct__'()
end
catch
error:undef ->
Inspected = elixir_aliases:inspect(Name),
compile_error(Meta, S#elixir_scope.file, "~ts.__struct__/0 is undefined, "
"cannot expand struct ~ts", [Inspected, Inspected])
end.
wait_for_struct(Module) ->
case erlang:get(elixir_compiler_pid) of
undefined ->
false;
Pid ->
Ref = erlang:make_ref(),
Pid ! { waiting, struct, self(), Ref, Module },
receive
{ Ref, ready } ->
true;
{ Ref, release } ->
'Elixir.Kernel.ErrorHandler':release(),
false
end
end.
translate_map(Meta, Assocs, TUpdate, #elixir_scope{extra=Extra} = S) ->
{ Op, KeyFun, ValFun } = extract_key_val_op(TUpdate, S),
Line = ?line(Meta),
{ TArgs, SA } = lists:mapfoldl(fun
({ Key, Value }, Acc) ->
{ TKey, Acc1 } = KeyFun(Key, Acc),
{ TValue, Acc2 } = ValFun(Value, Acc1#elixir_scope{extra=Extra}),
{ { Op, ?line(Meta), TKey, TValue }, Acc2 };
(Other, _Acc) ->
compile_error(Meta, S#elixir_scope.file, "expected key-value pairs in map, got: ~ts",
['Elixir.Macro':to_string(Other)])
end, S, Assocs),
build_map(Line, TUpdate, TArgs, SA).
extract_assoc_update([{ '|', _Meta, [Update, Args] }], S) ->
{ TArg, SA } = elixir_translator:translate_arg(Update, S, S),
{ Args, TArg, SA };
extract_assoc_update(Args, SA) -> { Args, nil, SA }.
extract_key_val_op(_TUpdate, #elixir_scope{context=match}) ->
{ map_field_exact,
fun(X, Acc) -> elixir_translator:translate(X, Acc#elixir_scope{extra=map_key}) end,
fun elixir_translator:translate/2 };
extract_key_val_op(TUpdate, S) ->
KS = #elixir_scope{extra=map_key},
Op = if TUpdate == nil -> map_field_assoc; true -> map_field_exact end,
{ Op,
fun(X, Acc) -> elixir_translator:translate_arg(X, Acc, KS) end,
fun(X, Acc) -> elixir_translator:translate_arg(X, Acc, S) end }.
build_map(Line, nil, TArgs, SA) -> { { map, Line, TArgs }, SA };
build_map(Line, TUpdate, TArgs, SA) -> { { map, Line, TUpdate, TArgs }, SA }.
assert_struct_keys(Meta, Name, Struct, Assocs, S) ->
[begin
compile_error(Meta, S#elixir_scope.file, "unknown key ~ts for struct ~ts",
['Elixir.Kernel':inspect(Key), elixir_aliases:inspect(Name)])
end || { Key, _ } <- Assocs, not maps:is_key(Key, Struct)].
+18 -21
View File
@@ -1,5 +1,5 @@
-module(elixir_module).
-export([compile/4, data_table/1, docs_table/1,
-export([compile/4, data_table/1, docs_table/1, is_open/1,
format_error/1, eval_callbacks/5]).
-include("elixir.hrl").
@@ -17,6 +17,9 @@ data_table(Module) ->
docs_table(Module) ->
ets:lookup_element(Module, ?docs_attr, 2).
is_open(Module) ->
Module == ets:info(Module, name).
%% Compilation hook
compile(Module, Block, Vars, #elixir_env{line=Line} = Env) when is_atom(Module) ->
@@ -127,12 +130,7 @@ eval_form(Line, Module, Block, Vars, E) ->
%% Return the form with exports and function declarations.
functions_form(Line, File, Module, BaseAll, BaseExport, Def, Defmacro, RawFunctions) ->
BaseFunctions = case elixir_compiler:get_opt(internal) of
true -> RawFunctions;
false -> record_rewrite_functions(Module, RawFunctions)
end,
functions_form(Line, File, Module, BaseAll, BaseExport, Def, Defmacro, BaseFunctions) ->
Info = add_info_function(Line, File, Module, BaseExport, Def, Defmacro),
All = [{ '__info__', 1 }|BaseAll],
@@ -143,17 +141,6 @@ functions_form(Line, File, Module, BaseAll, BaseExport, Def, Defmacro, RawFuncti
{ attribute, Line, export, lists:sort(Export) } | Functions
] }.
record_rewrite_functions(Module, Functions) ->
lists:map(fun
({ function, Line, Name, Arity, Clauses }) ->
Rewriten = [begin
{ C, _, _ } = 'Elixir.Kernel.RecordRewriter':optimize_clause(Module, Clause),
C
end || Clause <- Clauses],
{ function, Line, Name, Arity, Rewriten };
(Other) -> Other
end, Functions).
%% Add attributes handling to the form
attributes_form(Line, _File, Module, Current) ->
@@ -268,16 +255,24 @@ load_form(Line, Forms, Opts, #elixir_env{file=File} = E) ->
end).
check_module_availability(Line, File, Module) ->
Reserved = ['Elixir.Atom', 'Elixir.BitString', 'Elixir.Function',
'Elixir.PID', 'Elixir.Reference', 'Elixir.Any'],
case lists:member(Module, Reserved) of
true -> elixir_errors:handle_file_error(File, { Line, ?MODULE, { module_reserved, Module } });
false -> ok
end,
case elixir_compiler:get_opt(ignore_module_conflict) of
false ->
case code:ensure_loaded(Module) of
{ module, _ } ->
elixir_errors:handle_file_warning(File, { Line, ?MODULE, { module_defined, Module } });
{ error, _ } ->
[]
{ error, _ } ->
ok
end;
true ->
[]
ok
end.
warn_invalid_clauses(_Line, _File, 'Elixir.Kernel.SpecialForms', _All) -> ok;
@@ -407,6 +402,8 @@ format_error({ invalid_module, Module}) ->
io_lib:format("invalid module name: ~p", [Module]);
format_error({ module_defined, Module }) ->
io_lib:format("redefining module ~ts", [elixir_aliases:inspect(Module)]);
format_error({ module_reserved, Module }) ->
io_lib:format("module ~ts is reserved and cannot be defined", [elixir_aliases:inspect(Module)]);
format_error({ module_in_definition, Module }) ->
io_lib:format("cannot define module ~ts because it is currently being defined",
[elixir_aliases:inspect(Module)]).
+167 -98
View File
@@ -1,38 +1,37 @@
Nonterminals
grammar expr_list
expr container_expr block_expr no_parens_expr identifier_expr max_expr
bracket_expr bracket_at_expr base_expr matched_expr unmatched_expr
expr container_expr block_expr no_parens_expr no_parens_one_expr access_expr
bracket_expr bracket_at_expr matched_expr unmatched_expr max_expr
op_expr matched_op_expr no_parens_op_expr
comp_op_eol at_op_eol unary_op_eol and_op_eol or_op_eol
add_op_eol mult_op_eol exp_op_eol two_op_eol pipe_op_eol stab_op_eol
arrow_op_eol match_op_eol when_op_eol in_op_eol in_match_op_eol
arrow_op_eol match_op_eol when_op_eol in_op_eol in_match_op_eol type_op_eol
open_paren close_paren empty_paren
open_bracket close_bracket
open_curly close_curly
open_bit close_bit
container_args_base container_args
call_args_parens_base call_args_parens parens_call
list list_args open_bracket close_bracket
tuple open_curly close_curly
bit_string open_bit close_bit
map map_op map_close map_args map_expr struct_op
assoc_op_eol assoc_expr assoc_base assoc_update assoc_update_kw assoc
container_args_base container_args call_args_parens_base call_args_parens parens_call
call_args_no_parens_one call_args_no_parens_expr call_args_no_parens_comma_expr
call_args_no_parens_all call_args_no_parens_many call_args_no_parens_many_strict
stab stab_eol stab_expr stab_maybe_expr stab_parens_many
kw_eol kw_expr kw_comma kw
call_args_no_parens_kw_expr call_args_no_parens_kw_comma call_args_no_parens_kw
kw_eol kw_base kw call_args_no_parens_kw_expr call_args_no_parens_kw
dot_op dot_alias dot_identifier dot_op_identifier dot_do_identifier
dot_paren_identifier dot_bracket_identifier
var list list_args bit_string tuple
do_block fn_eol do_eol end_eol block_eol block_item block_list
.
Terminals
identifier kw_identifier bracket_identifier
identifier kw_identifier kw_identifier_string bracket_identifier
paren_identifier do_identifier block_identifier
fn 'end' aliases
number signed_number atom atom_string bin_string list_string sigil
dot_call_op op_identifier
comp_op at_op unary_op and_op or_op arrow_op match_op in_op in_match_op
dual_op add_op mult_op exp_op two_op pipe_op stab_op when_op
comp_op at_op unary_op and_op or_op arrow_op match_op in_op in_match_op type_op
dual_op add_op mult_op exp_op two_op pipe_op stab_op when_op assoc_op
'true' 'false' 'nil' 'do' eol ',' '.'
'(' ')' '[' ']' '{' '}' '<<' '>>'
'(' ')' '[' ']' '{' '}' '<<' '>>' '%{}' '%'
.
Rootsymbol grammar.
@@ -41,13 +40,17 @@ Rootsymbol grammar.
Expect 2.
%% Changes in ops and precedence should be reflected on lib/elixir/lib/macro.ex
%% Note though the operator => in practice has lower precedence than all others.
%% Its entry in the table is only to support the %{ user | foo => bar } syntax.
Left 5 do.
Right 10 stab_op_eol. %% ->
Left 20 ','.
Left 40 in_match_op_eol. %% <-, inlist, inbits, //, \\, :: (allowed in matches along =)
Right 50 pipe_op_eol. %% |
Right 70 when_op_eol. %% when
Right 80 match_op_eol. %% =
Left 40 in_match_op_eol. %% <-, inlist, inbits, \\ (allowed in matches along =)
Right 50 when_op_eol. %% when
Right 60 type_op_eol. %% ::
Right 70 pipe_op_eol. %% |
Right 80 assoc_op_eol. %% =>
Right 90 match_op_eol. %% =
Left 130 or_op_eol. %% ||, |||, or, xor
Left 140 and_op_eol. %% &&, &&&, and
Left 150 comp_op_eol. %% <, >, <=, >=, ==, !=, =~, ===, !==
@@ -60,8 +63,8 @@ Left 250 exp_op_eol. %% ^ (op) (e.g ^^^)
Nonassoc 300 unary_op_eol. %% +, -, !, ^, not, &, ~~~
Left 310 dot_call_op.
Left 310 dot_op. %% .
Nonassoc 320 at_op_eol. %% @ (op)
Nonassoc 330 var.
Nonassoc 320 at_op_eol. %% @
Nonassoc 330 dot_identifier.
%%% MAIN FLOW OF EXPRESSIONS
@@ -105,13 +108,12 @@ expr -> unmatched_expr : '$1'.
%% segments and act accordingly.
matched_expr -> matched_expr matched_op_expr : build_op(element(1, '$2'), '$1', element(2, '$2')).
matched_expr -> matched_expr no_parens_op_expr : build_op(element(1, '$2'), '$1', element(2, '$2')).
matched_expr -> unary_op_eol number : build_unary_op('$1', ?exprs('$2')).
matched_expr -> unary_op_eol matched_expr : build_unary_op('$1', '$2').
matched_expr -> unary_op_eol no_parens_expr : build_unary_op('$1', '$2').
matched_expr -> at_op_eol matched_expr : build_unary_op('$1', '$2').
matched_expr -> at_op_eol no_parens_expr : build_unary_op('$1', '$2').
matched_expr -> bracket_at_expr : '$1'.
matched_expr -> identifier_expr : '$1'.
matched_expr -> no_parens_one_expr : '$1'.
matched_expr -> access_expr : '$1'.
no_parens_expr -> dot_op_identifier call_args_no_parens_many_strict : build_identifier('$1', '$2').
no_parens_expr -> dot_identifier call_args_no_parens_many_strict : build_identifier('$1', '$2').
@@ -137,6 +139,7 @@ op_expr -> and_op_eol expr : { '$1', '$2' }.
op_expr -> or_op_eol expr : { '$1', '$2' }.
op_expr -> in_op_eol expr : { '$1', '$2' }.
op_expr -> in_match_op_eol expr : { '$1', '$2' }.
op_expr -> type_op_eol expr : { '$1', '$2' }.
op_expr -> when_op_eol expr : { '$1', '$2' }.
op_expr -> pipe_op_eol expr : { '$1', '$2' }.
op_expr -> comp_op_eol expr : { '$1', '$2' }.
@@ -151,6 +154,7 @@ no_parens_op_expr -> and_op_eol no_parens_expr : { '$1', '$2' }.
no_parens_op_expr -> or_op_eol no_parens_expr : { '$1', '$2' }.
no_parens_op_expr -> in_op_eol no_parens_expr : { '$1', '$2' }.
no_parens_op_expr -> in_match_op_eol no_parens_expr : { '$1', '$2' }.
no_parens_op_expr -> type_op_eol no_parens_expr : { '$1', '$2' }.
no_parens_op_expr -> pipe_op_eol no_parens_expr : { '$1', '$2' }.
no_parens_op_expr -> comp_op_eol no_parens_expr : { '$1', '$2' }.
no_parens_op_expr -> arrow_op_eol no_parens_expr : { '$1', '$2' }.
@@ -168,56 +172,61 @@ matched_op_expr -> and_op_eol matched_expr : { '$1', '$2' }.
matched_op_expr -> or_op_eol matched_expr : { '$1', '$2' }.
matched_op_expr -> in_op_eol matched_expr : { '$1', '$2' }.
matched_op_expr -> in_match_op_eol matched_expr : { '$1', '$2' }.
matched_op_expr -> type_op_eol matched_expr : { '$1', '$2' }.
matched_op_expr -> when_op_eol matched_expr : { '$1', '$2' }.
matched_op_expr -> pipe_op_eol matched_expr : { '$1', '$2' }.
matched_op_expr -> comp_op_eol matched_expr : { '$1', '$2' }.
matched_op_expr -> arrow_op_eol matched_expr : { '$1', '$2' }.
identifier_expr -> dot_op_identifier call_args_no_parens_one : build_identifier('$1', '$2').
identifier_expr -> dot_identifier call_args_no_parens_one : build_identifier('$1', '$2').
identifier_expr -> dot_do_identifier : build_identifier('$1', nil).
identifier_expr -> var : build_identifier('$1', nil).
identifier_expr -> max_expr : '$1'.
no_parens_one_expr -> dot_op_identifier call_args_no_parens_one : build_identifier('$1', '$2').
no_parens_one_expr -> dot_identifier call_args_no_parens_one : build_identifier('$1', '$2').
no_parens_one_expr -> dot_do_identifier : build_identifier('$1', nil).
no_parens_one_expr -> dot_identifier : build_identifier('$1', nil).
max_expr -> fn_eol stab end_eol : build_fn('$1', build_stab(lists:reverse('$2'))).
max_expr -> open_paren stab close_paren : build_stab(lists:reverse('$2')).
max_expr -> bracket_expr : '$1'.
%% From this point on, we just have constructs that can be
%% used with the access syntax. Notice that (dot_)identifier
%% is not included in this list simply because the tokenizer
%% marks identifiers followed by brackets as bracket_identifier.
access_expr -> bracket_at_expr : '$1'.
access_expr -> bracket_expr : '$1'.
access_expr -> unary_op_eol number : build_unary_op('$1', ?exprs('$2')).
access_expr -> fn_eol stab end_eol : build_fn('$1', build_stab(reverse('$2'))).
access_expr -> open_paren stab close_paren : build_stab(reverse('$2')).
access_expr -> number : ?exprs('$1').
access_expr -> signed_number : { element(4, '$1'), meta('$1'), ?exprs('$1') }.
access_expr -> list : element(1, '$1').
access_expr -> map : '$1'.
access_expr -> tuple : '$1'.
access_expr -> 'true' : ?id('$1').
access_expr -> 'false' : ?id('$1').
access_expr -> 'nil' : ?id('$1').
access_expr -> bin_string : build_bin_string('$1').
access_expr -> list_string : build_list_string('$1').
access_expr -> bit_string : '$1'.
access_expr -> sigil : build_sigil('$1').
access_expr -> max_expr : '$1'.
%% Aliases and properly formed calls. Used by map_expr.
max_expr -> atom : ?exprs('$1').
max_expr -> atom_string : build_atom_string('$1').
max_expr -> parens_call call_args_parens : build_identifier('$1', '$2').
max_expr -> parens_call call_args_parens call_args_parens : build_nested_parens('$1', '$2', '$3').
max_expr -> dot_alias : '$1'.
max_expr -> base_expr : '$1'.
bracket_expr -> dot_bracket_identifier list : build_access(build_identifier('$1', nil), '$2').
bracket_expr -> max_expr list : build_access('$1', '$2').
bracket_expr -> access_expr list : build_access('$1', '$2').
bracket_at_expr -> at_op_eol dot_bracket_identifier list :
build_access(build_unary_op('$1', build_identifier('$2', nil)), '$3').
bracket_at_expr -> at_op_eol max_expr list :
bracket_at_expr -> at_op_eol access_expr list :
build_access(build_unary_op('$1', '$2'), '$3').
bracket_at_expr -> bracket_at_expr list :
build_access('$1', '$2').
base_expr -> atom : ?exprs('$1').
base_expr -> number : ?exprs('$1').
base_expr -> signed_number : { element(4, '$1'), meta('$1'), ?exprs('$1') }.
base_expr -> list : element(1, '$1').
base_expr -> tuple : '$1'.
base_expr -> 'true' : ?id('$1').
base_expr -> 'false' : ?id('$1').
base_expr -> 'nil' : ?id('$1').
base_expr -> aliases : { '__aliases__', meta('$1', 0), ?exprs('$1') }.
base_expr -> bin_string : build_bin_string('$1').
base_expr -> list_string : build_list_string('$1').
base_expr -> atom_string : build_atom_string('$1').
base_expr -> bit_string : '$1'.
base_expr -> sigil : build_sigil('$1').
%% Blocks
do_block -> do_eol 'end' : [[{do,nil}]].
do_block -> do_eol stab end_eol : [[{ do, build_stab(lists:reverse('$2')) }]].
do_block -> do_eol stab end_eol : [[{ do, build_stab(reverse('$2')) }]].
do_block -> do_eol block_list 'end' : [[{ do, nil }|'$2']].
do_block -> do_eol stab_eol block_list 'end' : [[{ do, build_stab(lists:reverse('$2')) }|'$3']].
do_block -> do_eol stab_eol block_list 'end' : [[{ do, build_stab(reverse('$2')) }|'$3']].
fn_eol -> 'fn' : '$1'.
fn_eol -> 'fn' eol : '$1'.
@@ -249,7 +258,7 @@ stab_expr -> stab_parens_many when_op expr stab_op_eol stab_maybe_expr :
stab_maybe_expr -> 'expr' : '$1'.
stab_maybe_expr -> '$empty' : nil.
block_item -> block_eol stab_eol : { ?exprs('$1'), build_stab(lists:reverse('$2')) }.
block_item -> block_eol stab_eol : { ?exprs('$1'), build_stab(reverse('$2')) }.
block_item -> block_eol : { ?exprs('$1'), nil }.
block_list -> block_item : ['$1'].
@@ -257,8 +266,6 @@ block_list -> block_item block_list : ['$1'|'$2'].
%% Helpers
var -> dot_identifier : '$1'.
open_paren -> '(' : '$1'.
open_paren -> '(' eol : '$1'.
close_paren -> ')' : '$1'.
@@ -266,20 +273,20 @@ close_paren -> eol ')' : '$2'.
empty_paren -> open_paren ')' : '$1'.
open_bracket -> '[' : '$1'.
open_bracket -> '[' eol : '$1'.
close_bracket -> ']' : '$1'.
close_bracket -> eol ']' : '$2'.
open_bracket -> '[' : '$1'.
open_bracket -> '[' eol : '$1'.
close_bracket -> ']' : '$1'.
close_bracket -> eol ']' : '$2'.
open_bit -> '<<' : '$1'.
open_bit -> '<<' eol : '$1'.
close_bit -> '>>' : '$1'.
close_bit -> eol '>>' : '$2'.
open_bit -> '<<' : '$1'.
open_bit -> '<<' eol : '$1'.
close_bit -> '>>' : '$1'.
close_bit -> eol '>>' : '$2'.
open_curly -> '{' : '$1'.
open_curly -> '{' eol : '$1'.
close_curly -> '}' : '$1'.
close_curly -> eol '}' : '$2'.
close_curly -> '}' : '$1'.
close_curly -> eol '}' : '$2'.
% Operators
@@ -320,6 +327,9 @@ in_op_eol -> in_op eol : '$1'.
in_match_op_eol -> in_match_op : '$1'.
in_match_op_eol -> in_match_op eol : '$1'.
type_op_eol -> type_op : '$1'.
type_op_eol -> type_op eol : '$1'.
when_op_eol -> when_op : '$1'.
when_op_eol -> when_op eol : '$1'.
@@ -343,6 +353,7 @@ dot_op -> '.' eol : '$1'.
dot_identifier -> identifier : '$1'.
dot_identifier -> matched_expr dot_op identifier : build_dot('$2', '$1', '$3').
dot_alias -> aliases : { '__aliases__', meta('$1', 0), ?exprs('$1') }.
dot_alias -> matched_expr dot_op aliases : build_dot_alias('$2', '$1', '$3').
dot_op_identifier -> op_identifier : '$1'.
@@ -377,8 +388,8 @@ call_args_no_parens_one -> matched_expr : ['$1'].
call_args_no_parens_one -> no_parens_expr : ['$1'].
call_args_no_parens_many -> matched_expr ',' call_args_no_parens_kw : ['$1', '$3'].
call_args_no_parens_many -> call_args_no_parens_comma_expr : lists:reverse('$1').
call_args_no_parens_many -> call_args_no_parens_comma_expr ',' call_args_no_parens_kw : lists:reverse(['$3'|'$1']).
call_args_no_parens_many -> call_args_no_parens_comma_expr : reverse('$1').
call_args_no_parens_many -> call_args_no_parens_comma_expr ',' call_args_no_parens_kw : reverse(['$3'|'$1']).
call_args_no_parens_many_strict -> call_args_no_parens_many : '$1'.
call_args_no_parens_many_strict -> empty_paren : throw_no_parens_strict('$1').
@@ -410,36 +421,35 @@ call_args_parens_base -> call_args_parens_base ',' container_expr : ['$3'|'$1'].
call_args_parens -> empty_paren : [].
call_args_parens -> open_paren no_parens_expr close_paren : ['$2'].
call_args_parens -> open_paren kw close_paren : ['$2'].
call_args_parens -> open_paren call_args_parens_base close_paren : lists:reverse('$2').
call_args_parens -> open_paren call_args_parens_base ',' kw close_paren : lists:reverse(['$4'|'$2']).
call_args_parens -> open_paren call_args_parens_base close_paren : reverse('$2').
call_args_parens -> open_paren call_args_parens_base ',' kw close_paren : reverse(['$4'|'$2']).
% KV
kw_eol -> kw_identifier : '$1'.
kw_eol -> kw_identifier eol : '$1'.
kw_eol -> kw_identifier : ?exprs('$1').
kw_eol -> kw_identifier eol : ?exprs('$1').
kw_eol -> kw_identifier_string : build_atom_string('$1').
kw_eol -> kw_identifier_string eol : build_atom_string('$1').
kw_expr -> kw_eol container_expr : { ?exprs('$1'),'$2' }.
kw_comma -> kw_expr ',' : ['$1'].
kw_comma -> kw_comma kw_expr ',' : ['$2'|'$1'].
kw_base -> kw_eol container_expr : [{ '$1', '$2' }].
kw_base -> kw_base ',' kw_eol container_expr : [{ '$3', '$4' }|'$1'].
kw -> kw_expr : ['$1'].
kw -> kw_comma : lists:reverse('$1').
kw -> kw_comma kw_expr : lists:reverse(['$2'|'$1']).
kw -> kw_base : reverse('$1').
kw -> kw_base ',' : reverse('$1').
call_args_no_parens_kw_expr -> kw_eol call_args_no_parens_expr : { ?exprs('$1'),'$2' }.
call_args_no_parens_kw_comma -> call_args_no_parens_kw_expr : ['$1'].
call_args_no_parens_kw_comma -> call_args_no_parens_kw_expr ',' call_args_no_parens_kw_comma : ['$1'|'$3'].
call_args_no_parens_kw -> call_args_no_parens_kw_comma : '$1'.
call_args_no_parens_kw_expr -> kw_eol call_args_no_parens_expr : { '$1','$2' }.
call_args_no_parens_kw -> call_args_no_parens_kw_expr : ['$1'].
call_args_no_parens_kw -> call_args_no_parens_kw_expr ',' call_args_no_parens_kw : ['$1'|'$3'].
% Lists
list_args -> kw : '$1'.
list_args -> container_args_base : lists:reverse('$1').
list_args -> container_args_base ',' : lists:reverse('$1').
list_args -> container_args_base ',' kw : lists:reverse('$1') ++ '$3'.
list_args -> container_args_base : reverse('$1').
list_args -> container_args_base ',' : reverse('$1').
list_args -> container_args_base ',' kw : reverse('$1', '$3').
list -> open_bracket ']' : { [], ?line('$1') }.
list -> open_bracket list_args close_bracket : { '$2', ?line('$1') }.
list -> open_bracket ']' : build_list('$1', []).
list -> open_bracket list_args close_bracket : build_list('$1', '$2').
% Tuple
@@ -448,19 +458,67 @@ tuple -> open_curly container_args close_curly : build_tuple('$1', '$2').
% Bitstrings
bit_string -> open_bit '>>' : { '<<>>', meta('$1'), [] }.
bit_string -> open_bit container_args close_bit : { '<<>>', meta('$1'), '$2' }.
bit_string -> open_bit '>>' : build_bit('$1', []).
bit_string -> open_bit container_args close_bit : build_bit('$1', '$2').
% Map and structs
map_expr -> max_expr : '$1'.
map_expr -> dot_identifier : build_identifier('$1', nil).
map_expr -> at_op_eol map_expr : build_unary_op('$1', '$2').
assoc_op_eol -> assoc_op : '$1'.
assoc_op_eol -> assoc_op eol : '$1'.
assoc_expr -> container_expr assoc_op_eol container_expr : { '$1', '$3' }.
assoc_expr -> map_expr : '$1'.
assoc_update -> matched_expr pipe_op_eol matched_expr assoc_op_eol matched_expr : { '$2', '$1', [{ '$3', '$5' }] }.
assoc_update -> unmatched_expr pipe_op_eol expr assoc_op_eol expr : { '$2', '$1', [{ '$3', '$5' }] }.
assoc_update -> matched_expr pipe_op_eol map_expr : { '$2', '$1', ['$3'] }.
assoc_update_kw -> matched_expr pipe_op_eol kw : { '$2', '$1', '$3' }.
assoc_update_kw -> unmatched_expr pipe_op_eol kw : { '$2', '$1', '$3' }.
assoc_base -> assoc_expr : ['$1'].
assoc_base -> assoc_base ',' assoc_expr : ['$3'|'$1'].
assoc -> assoc_base : reverse('$1').
assoc -> assoc_base ',' : reverse('$1').
map_op -> '%{}' : '$1'.
map_op -> '%{}' eol : '$1'.
map_close -> kw close_curly : '$1'.
map_close -> assoc close_curly : '$1'.
map_close -> assoc_base ',' kw close_curly : reverse('$1', '$3').
map_args -> open_curly '}' : build_map('$1', []).
map_args -> open_curly map_close : build_map('$1', '$2').
map_args -> open_curly assoc_update close_curly : build_map_update('$1', '$2', []).
map_args -> open_curly assoc_update ',' close_curly : build_map_update('$1', '$2', []).
map_args -> open_curly assoc_update ',' map_close : build_map_update('$1', '$2', '$4').
map_args -> open_curly assoc_update_kw close_curly : build_map_update('$1', '$2', []).
struct_op -> '%' : '$1'.
struct_op -> '%' eol : '$1'.
map -> map_op map_args : '$2'.
map -> struct_op map_expr map_args : { '%', meta('$1'), ['$2', '$3'] }.
map -> struct_op map_expr eol map_args : { '%', meta('$1'), ['$2', '$4'] }.
Erlang code.
-define(id(Node), element(1, Node)).
-define(line(Node), element(2, Node)).
-define(exprs(Node), element(3, Node)).
-define(rearrange_uop(Op), Op == 'not' orelse Op == '!').
-define(rearrange_uop(Op), (Op == 'not' orelse Op == '!')).
-define(is_atom_string(Atom), (Atom == atom_string orelse Atom == kw_identifier_string)).
%% The following directive is needed for (significantly) faster
%% compilation of the generated .erl file by the HiPE compiler
-compile([{hipe,[{regalloc,linear_scan}]}]).
-import(lists, [reverse/1, reverse/2]).
meta(Line, Counter) -> [{counter,Counter}|meta(Line)].
meta(Line) when is_integer(Line) -> [{line,Line}];
@@ -483,12 +541,23 @@ build_op({ _Kind, Line, Op }, Left, Right) ->
build_unary_op({ _Kind, Line, Op }, Expr) ->
{ Op, meta(Line), [Expr] }.
build_list(Marker, Args) ->
{ Args, ?line(Marker) }.
build_tuple(_Marker, [Left, Right]) ->
{ Left, Right };
build_tuple(Marker, Args) ->
{ '{}', meta(Marker), Args }.
build_bit(Marker, Args) ->
{ '<<>>', meta(Marker), Args }.
build_map(Marker, Args) ->
{ '%{}', meta(Marker), Args }.
build_map_update(Marker, { Pipe, Left, Right }, Extra) ->
{ '%{}', meta(Marker), [build_op(Pipe, Left, Right ++ Extra)] }.
%% Blocks
build_block([{Op,_,[_]}]=Exprs) when ?rearrange_uop(Op) -> { '__block__', [], Exprs };
@@ -564,9 +633,9 @@ build_list_string({ list_string, Line, Args }) ->
Meta = meta(Line),
{ { '.', Meta, ['Elixir.String', 'to_char_list!'] }, Meta, [{ '<<>>', Meta, string_parts(Args) }] }.
build_atom_string({ atom_string, _Line, Safe, [H] }) when is_binary(H) ->
build_atom_string({ Atom, _Line, Safe, [H] }) when ?is_atom_string(Atom) andalso is_binary(H) ->
Op = binary_to_atom_op(Safe), erlang:Op(H, utf8);
build_atom_string({ atom_string, Line, Safe, Args }) ->
build_atom_string({ Atom, Line, Safe, Args }) when ?is_atom_string(Atom) ->
Meta = meta(Line),
{ { '.', Meta, [erlang, binary_to_atom_op(Safe)] }, Meta, [{ '<<>>', Meta, string_parts(Args) }, utf8] }.
@@ -597,15 +666,15 @@ build_stab(Else) ->
build_block(Else).
build_stab(Old, [{ '->', New, [Left, Right] }|T], Marker, Temp, Acc) ->
H = { '->', Old, [Marker, build_block(lists:reverse(Temp))] },
H = { '->', Old, [Marker, build_block(reverse(Temp))] },
build_stab(New, T, Left, [Right], [H|Acc]);
build_stab(Meta, [H|T], Marker, Temp, Acc) ->
build_stab(Meta, T, Marker, [H|Temp], Acc);
build_stab(Meta, [], Marker, Temp, Acc) ->
H = { '->', Meta, [Marker, build_block(lists:reverse(Temp))] },
lists:reverse([H|Acc]).
H = { '->', Meta, [Marker, build_block(reverse(Temp))] },
reverse([H|Acc]).
%% Every time the parser sees a (unquote_splicing())
%% it assumes that a block is being spliced, wrapping
@@ -628,7 +697,7 @@ unwrap_when(Args) ->
end.
to_block([One]) when not is_list(One) -> One;
to_block(Other) -> { '__block__', [], lists:reverse(Other) }.
to_block(Other) -> { '__block__', [], reverse(Other) }.
%% Errors
+5 -2
View File
@@ -240,13 +240,16 @@ do_quote({ Left, Right }, Q, E) ->
{ TRight, RQ } = do_quote(Right, LQ, E),
{ { TLeft, TRight }, RQ };
do_quote(Map, #elixir_quote{escape=true} = Q, E) when is_map(Map) ->
{ TT, TQ } = do_quote(maps:to_list(Map), Q, E),
{ { '%{}', [], TT }, TQ };
do_quote(Tuple, #elixir_quote{escape=true} = Q, E) when is_tuple(Tuple) ->
{ TT, TQ } = do_quote(tuple_to_list(Tuple), Q, E),
{ { '{}', [], TT }, TQ };
do_quote(List, #elixir_quote{escape=true} = Q, E) when is_list(List) ->
% The improper case is pretty inefficient, but improper lists are hopefully
% rare.
% The improper case is pretty inefficient, but improper lists are are.
case reverse_improper(List) of
{ L } -> do_splice(L, Q, E);
{ L, R } ->
+49 -61
View File
@@ -1,10 +1,11 @@
-module(elixir_tokenizer).
-include("elixir.hrl").
-export([tokenize/3]).
-import(elixir_interpolation, [unescape_chars/1, unescape_tokens/1]).
-import(elixir_interpolation, [unescape_tokens/1]).
-define(container(T1, T2),
T1 == ${, T2 == $}
T1 == ${, T2 == $};
T1 == $[, T2 == $]
).
-define(at_op(T),
@@ -72,13 +73,14 @@
-define(in_match_op(T1, T2),
T1 == $<, T2 == $-;
T1 == $/, T2 == $/;
T1 == $\\, T2 == $\\;
T1 == $:, T2 == $:).
T1 == $\\, T2 == $\\).
-define(stab_op(T1, T2),
T1 == $-, T2 == $>).
-define(type_op(T1, T2),
T1 == $:, T2 == $:).
-define(pipe_op(T1),
T == $|).
@@ -137,31 +139,6 @@ tokenize([$#|String], Line, Scope, Tokens) ->
% Sigils
tokenize([$%,S,H,H,H|T] = Original, Line, Scope, Tokens) when ?is_quote(H), ?is_upcase(S) orelse ?is_downcase(S) ->
io:format(standard_error, "~ts:~p: warning: using % for sigils is deprecated, "
"please use ~~ instead~n", [Scope#elixir_tokenizer.file, Line]),
case extract_heredoc_with_interpolation(Line, Scope, ?is_downcase(S), T, H) of
{ ok, NewLine, Parts, Rest } ->
{ Final, Modifiers } = collect_modifiers(Rest, []),
tokenize(Final, NewLine, Scope, [{ sigil, Line, S, Parts, Modifiers }|Tokens]);
{ error, Reason } ->
{ error, Reason, Original, Tokens }
end;
tokenize([$%,S,H|T] = Original, Line, Scope, Tokens) when ?is_sigil(H), ?is_upcase(S) orelse ?is_downcase(S) ->
io:format(standard_error, "~ts:~p: warning: using % for sigils is deprecated, "
"please use ~~ instead~n", [Scope#elixir_tokenizer.file, Line]),
case elixir_interpolation:extract(Line, Scope, ?is_downcase(S), T, sigil_terminator(H)) of
{ NewLine, Parts, Rest } ->
{ Final, Modifiers } = collect_modifiers(Rest, []),
tokenize(Final, NewLine, Scope, [{ sigil, Line, S, Parts, Modifiers }|Tokens]);
{ error, Reason } ->
Sigil = [$%,S,H],
interpolation_error(Reason, Original, Tokens, " (for sigil ~ts starting at line ~B)", [Sigil, Line])
end;
tokenize([$~,S,H,H,H|T] = Original, Line, Scope, Tokens) when ?is_quote(H), ?is_upcase(S) orelse ?is_downcase(S) ->
case extract_heredoc_with_interpolation(Line, Scope, ?is_downcase(S), T, H) of
{ ok, NewLine, Parts, Rest } ->
@@ -270,18 +247,30 @@ tokenize([$:,T|String] = Original, Line, Scope, Tokens) when ?is_atom_start(T) -
{ error, Reason, Original, Tokens }
end;
% Atom identifiers/operators
% %% Special atom identifiers / operators
tokenize(":..." ++ Rest, Line, Scope, Tokens) ->
tokenize(Rest, Line, Scope, [{ atom, Line, '...' }|Tokens]);
% ## Containers
tokenize(":<<>>" ++ Rest, Line, Scope, Tokens) ->
tokenize(Rest, Line, Scope, [{ atom, Line, '<<>>' }|Tokens]);
tokenize(":%{}" ++ Rest, Line, Scope, Tokens) ->
tokenize(Rest, Line, Scope, [{ atom, Line, '%{}' }|Tokens]);
tokenize(":%" ++ Rest, Line, Scope, Tokens) ->
tokenize(Rest, Line, Scope, [{ atom, Line, '%' }|Tokens]);
tokenize([$:,T1,T2|Rest], Line, Scope, Tokens) when ?container(T1, T2) ->
tokenize(Rest, Line, Scope, [{ atom, Line, list_to_atom([T1,T2]) }|Tokens]);
tokenize("...:" ++ Rest, Line, Scope, Tokens) when ?is_space(hd(Rest)) ->
tokenize(Rest, Line, Scope, [{ kw_identifier, Line, '...' }|Tokens]);
tokenize("<<>>:" ++ Rest, Line, Scope, Tokens) when ?is_space(hd(Rest)) ->
tokenize(Rest, Line, Scope, [{ kw_identifier, Line, '<<>>' }|Tokens]);
tokenize("%{}:" ++ Rest, Line, Scope, Tokens) when ?is_space(hd(Rest)) ->
tokenize(Rest, Line, Scope, [{ kw_identifier, Line, '%{}' }|Tokens]);
tokenize("%:" ++ Rest, Line, Scope, Tokens) when ?is_space(hd(Rest)) ->
tokenize(Rest, Line, Scope, [{ kw_identifier, Line, '%' }|Tokens]);
tokenize([T1,T2,$:|Rest], Line, Scope, Tokens) when ?container(T1, T2), ?is_space(hd(Rest)) ->
tokenize(Rest, Line, Scope, [{ kw_identifier, Line, list_to_atom([T1,T2]) }|Tokens]);
% ## Three Token Operators
tokenize([$:,T1,T2,T3|Rest], Line, Scope, Tokens) when
?unary_op3(T1, T2, T3); ?comp_op3(T1, T2, T3); ?and_op3(T1, T2, T3); ?or_op3(T1, T2, T3);
@@ -291,7 +280,7 @@ tokenize([$:,T1,T2,T3|Rest], Line, Scope, Tokens) when
% ## Two Token Operators
tokenize([$:,T1,T2|Rest], Line, Scope, Tokens) when
?comp_op2(T1, T2); ?and_op(T1, T2); ?or_op(T1, T2); ?arrow_op(T1, T2);
?in_match_op(T1, T2); ?two_op(T1, T2); ?stab_op(T1, T2) ->
?in_match_op(T1, T2); ?two_op(T1, T2); ?stab_op(T1, T2); ?type_op(T1, T2) ->
tokenize(Rest, Line, Scope, [{ atom, Line, list_to_atom([T1,T2]) }|Tokens]);
% ## Single Token Operators
@@ -355,6 +344,9 @@ tokenize([T|Rest], Line, Scope, Tokens) when T == $(;
Token = { list_to_atom([T]), Line },
handle_terminator(Rest, Line, Scope, Token, Tokens);
tokenize("=>" ++ Rest, Line, Scope, Tokens) ->
tokenize(Rest, Line, Scope, add_token_with_nl({ assoc_op, Line, '=>' }, Tokens));
% ## Two Token Operators
tokenize([T1,T2|Rest], Line, Scope, Tokens) when ?two_op(T1, T2) ->
handle_op(Rest, Line, two_op, list_to_atom([T1, T2]), Scope, Tokens);
@@ -374,6 +366,9 @@ tokenize([T1,T2|Rest], Line, Scope, Tokens) when ?or_op(T1, T2) ->
tokenize([T1,T2|Rest], Line, Scope, Tokens) when ?in_match_op(T1, T2) ->
handle_op(Rest, Line, in_match_op, list_to_atom([T1, T2]), Scope, Tokens);
tokenize([T1,T2|Rest], Line, Scope, Tokens) when ?type_op(T1, T2) ->
handle_op(Rest, Line, type_op, list_to_atom([T1, T2]), Scope, Tokens);
tokenize([T1,T2|Rest], Line, Scope, Tokens) when ?stab_op(T1, T2) ->
handle_op(Rest, Line, stab_op, list_to_atom([T1, T2]), Scope, Tokens);
@@ -400,7 +395,13 @@ tokenize([T|Rest], Line, Scope, Tokens) when ?match_op(T) ->
tokenize([T|Rest], Line, Scope, Tokens) when ?pipe_op(T) ->
handle_op(Rest, Line, pipe_op, list_to_atom([T]), Scope, Tokens);
% Dot
% Others
tokenize([$%|T], Line, Scope, Tokens) ->
case strip_space(T, 0) of
{ [${|_] = Rest, Counter } -> tokenize(Rest, Line + Counter, Scope, [{ '%{}', Line }|Tokens]);
{ Rest, Counter } -> tokenize(Rest, Line + Counter, Scope, [{ '%', Line }|Tokens])
end;
tokenize([$.|T], Line, Scope, Tokens) ->
{ Rest, Counter } = strip_space(T, 0),
@@ -495,17 +496,9 @@ handle_strings(T, Line, H, Scope, Tokens) ->
{ error, Reason } ->
interpolation_error(Reason, [H|T], Tokens, " (for string starting at line ~B)", [Line]);
{ NewLine, Parts, [$:|Rest] } when ?is_space(hd(Rest)) ->
case Parts of
[Bin] when is_binary(Bin) ->
case unsafe_to_atom(unescape_chars(Bin), Line, Scope) of
{ ok, Atom } ->
tokenize(Rest, NewLine, Scope, [{ kw_identifier, Line, Atom }|Tokens]);
{ error, Reason } ->
{ error, Reason, [H|T], Tokens }
end;
_ ->
{ error, { Line, "invalid interpolation in key ", [$"|T] }, [H|T], Tokens }
end;
Unescaped = unescape_tokens(Parts),
ExistingAtomsOnly = Scope#elixir_tokenizer.existing_atoms_only,
tokenize(Rest, NewLine, Scope, [{ kw_identifier_string, Line, ExistingAtomsOnly, Unescaped }|Tokens]);
{ NewLine, Parts, Rest } ->
Token = { string_type(H), Line, unescape_tokens(Parts) },
tokenize(Rest, NewLine, Scope, [Token|Tokens])
@@ -515,8 +508,8 @@ handle_unary_op([$:|Rest], Line, _Kind, Op, Scope, Tokens) when ?is_space(hd(Res
tokenize(Rest, Line, Scope, [{ kw_identifier, Line, Op }|Tokens]);
handle_unary_op(Rest, Line, Kind, Op, Scope, Tokens) ->
case strip_space(Rest, 0) of
{ [$/|_], _ } -> tokenize(Rest, Line, Scope, [{ identifier, Line, Op }|Tokens]);
case strip_horizontal_space(Rest) of
[$/|_] -> tokenize(Rest, Line, Scope, [{ identifier, Line, Op }|Tokens]);
_ -> tokenize(Rest, Line, Scope, [{ Kind, Line, Op }|Tokens])
end.
@@ -524,8 +517,8 @@ handle_op([$:|Rest], Line, _Kind, Op, Scope, Tokens) when ?is_space(hd(Rest)) ->
tokenize(Rest, Line, Scope, [{ kw_identifier, Line, Op }|Tokens]);
handle_op(Rest, Line, Kind, Op, Scope, Tokens) ->
case strip_space(Rest, 0) of
{ [$/|_], _ } -> tokenize(Rest, Line, Scope, [{ identifier, Line, Op }|Tokens]);
case strip_horizontal_space(Rest) of
[$/|_] -> tokenize(Rest, Line, Scope, [{ identifier, Line, Op }|Tokens]);
_ -> tokenize(Rest, Line, Scope, add_token_with_nl({ Kind, Line, Op }, Tokens))
end.
@@ -538,7 +531,7 @@ handle_dot([$.,T1,T2,T3|Rest], Line, Scope, Tokens) when
% ## Two Token Operators
handle_dot([$.,T1,T2|Rest], Line, Scope, Tokens) when
?comp_op2(T1, T2); ?and_op(T1, T2); ?or_op(T1, T2); ?arrow_op(T1, T2);
?in_match_op(T1, T2); ?two_op(T1, T2); ?stab_op(T1, T2) ->
?in_match_op(T1, T2); ?two_op(T1, T2); ?stab_op(T1, T2); ?type_op(T1, T2) ->
handle_call_identifier(Rest, Line, list_to_atom([T1, T2]), Scope, Tokens);
% ## Single Token Operators
@@ -781,7 +774,7 @@ tokenize_any_identifier(Original, Line, Scope, Tokens) ->
tokenize_kw_or_other([$:,H|T], _Kind, Line, Atom, _Tokens) when ?is_space(H) ->
{ identifier, [H|T], { kw_identifier, Line, Atom } };
tokenize_kw_or_other([$:,H|T], _Kind, Line, Atom, Tokens) when ?is_atom_start(H) ->
tokenize_kw_or_other([$:,H|T], _Kind, Line, Atom, Tokens) when ?is_atom_start(H); ?is_digit(H) ->
Original = atom_to_list(Atom) ++ [$:],
Reason = { Line, "keyword argument must be followed by space after: ", Original },
{ error, Reason, Original ++ [H|T], Tokens };
@@ -887,16 +880,13 @@ terminator('<<') -> '>>'.
check_keyword(_Line, _Atom, [{ '.', _ }|_]) ->
nomatch;
check_keyword(Line, do, [{ Identifier, Line, Atom }|T]) when Identifier == identifier ->
{ ok, [{ do, Line }, { do_identifier, Line, Atom }|T] };
check_keyword(DoLine, do, [{ Identifier, Line, Atom }|T]) when Identifier == identifier ->
{ ok, add_token_with_nl({ do, DoLine }, [{ do_identifier, Line, Atom }|T]) };
check_keyword(Line, do, Tokens) ->
case do_keyword_valid(Tokens) of
true -> { ok, [{ do, Line }|Tokens] };
true -> { ok, add_token_with_nl({ do, Line }, Tokens) };
false -> { error, "do" }
end;
check_keyword(Line, Atom, Tokens) ->
case keyword(Atom) of
false -> nomatch;
@@ -915,13 +905,11 @@ do_keyword_valid([{ Atom, _ }|_]) ->
false -> true;
_ -> keyword(Atom) == false
end;
do_keyword_valid(_) ->
true.
% Regular keywords
keyword('fn') -> token;
keyword('do') -> token;
keyword('end') -> token;
keyword('true') -> token;
keyword('false') -> token;
+94 -37
View File
@@ -1,18 +1,20 @@
%% Translate Elixir quoted expressions to Erlang Abstract Format.
%% Expects the tree to be expanded.
-module(elixir_translator).
-export([translate_many/2, translate/2, translate_arg/3, translate_args/2]).
-export([translate/2, translate_arg/3, translate_args/2, translate_block/3]).
-import(elixir_scope, [mergev/2, mergec/2, mergef/2]).
-import(elixir_errors, [compile_error/3, compile_error/4]).
-include("elixir.hrl").
translate_many(Forms, S) ->
lists:mapfoldl(fun translate/2, S, Forms).
%% =
translate({ '=', Meta, [Left, Right] }, S) ->
{ TRight, SR } = translate(Right, S),
Return = case Left of
{ '_', _, Atom } when is_atom(Atom) -> false;
_ -> true
end,
{ TRight, SR } = translate_block(Right, Return, S),
{ TLeft, SL } = elixir_clauses:match(fun translate/2, Left, SR),
{ { match, ?line(Meta), TLeft, TRight }, SL };
@@ -22,14 +24,20 @@ translate({ '{}', Meta, Args }, S) when is_list(Args) ->
{ TArgs, SE } = translate_args(Args, S),
{ { tuple, ?line(Meta), TArgs }, SE };
translate({ '%{}', Meta, Args }, S) when is_list(Args) ->
elixir_map:translate_map(Meta, Args, S);
translate({ '%', Meta, [Left, Right] }, S) ->
elixir_map:translate_struct(Meta, Left, Right, S);
translate({ '<<>>', Meta, Args }, S) when is_list(Args) ->
elixir_bitstring:translate(Meta, Args, S);
%% Blocks and scope rewriters
%% Blocks
translate({ '__block__', Meta, Args }, S) when is_list(Args) ->
{ TArgs, NS } = translate_many(Args, S),
{ { block, ?line(Meta), TArgs }, NS };
translate({ '__block__', Meta, Args }, #elixir_scope{return=Return} = S) when is_list(Args) ->
{ TArgs, SA } = translate_block(Args, [], Return, S#elixir_scope{return=true}),
{ { block, ?line(Meta), TArgs }, SA };
%% Erlang op
@@ -64,7 +72,8 @@ translate({ fn, Meta, Clauses }, S) ->
%% Case
translate({'case', Meta, [Expr, KV]}, S) when is_list(KV) ->
translate({'case', Meta, [Expr, KV]}, #elixir_scope{return=Return} = RS) when is_list(KV) ->
S = RS#elixir_scope{noname=true, return=true},
Clauses = elixir_clauses:get_pairs(do, KV),
{ TExpr, NS } = translate(Expr, S),
@@ -73,18 +82,18 @@ translate({'case', Meta, [Expr, KV]}, S) when is_list(KV) ->
false -> Clauses
end,
{ TClauses, TS } = elixir_clauses:clauses(Meta, RClauses, NS),
{ TClauses, TS } = elixir_clauses:clauses(Meta, RClauses, Return, NS),
{ { 'case', ?line(Meta), TExpr, TClauses }, TS };
%% Try
translate({'try', Meta, [Clauses]}, RS) when is_list(Clauses) ->
S = RS#elixir_scope{noname=true},
translate({'try', Meta, [Clauses]}, #elixir_scope{return=Return} = RS) when is_list(Clauses) ->
S = RS#elixir_scope{noname=true, return=true},
Do = proplists:get_value('do', Clauses, nil),
{ TDo, SB } = elixir_translator:translate(Do, S),
Catch = [Tuple || { X, _ } = Tuple <- Clauses, X == 'rescue' orelse X == 'catch'],
{ TCatch, SC } = elixir_try:clauses(Meta, Catch, mergec(S, SB)),
{ TCatch, SC } = elixir_try:clauses(Meta, Catch, Return, mergec(S, SB)),
case lists:keyfind('after', 1, Clauses) of
{ 'after', After } ->
@@ -95,23 +104,24 @@ translate({'try', Meta, [Clauses]}, RS) when is_list(Clauses) ->
end,
Else = elixir_clauses:get_pairs(else, Clauses),
{ TElse, SE } = elixir_clauses:clauses(Meta, Else, mergec(S, SA)),
{ TElse, SE } = elixir_clauses:clauses(Meta, Else, Return, mergec(S, SA)),
SF = (mergec(S, SE))#elixir_scope{noname=RS#elixir_scope.noname},
{ { 'try', ?line(Meta), unblock(TDo), TElse, TCatch, TAfter }, SF };
%% Receive
translate({'receive', Meta, [KV] }, S) when is_list(KV) ->
translate({'receive', Meta, [KV] }, #elixir_scope{return=Return} = RS) when is_list(KV) ->
S = RS#elixir_scope{return=true},
Do = elixir_clauses:get_pairs(do, KV, true),
case lists:keyfind('after', 1, KV) of
false ->
{ TClauses, SC } = elixir_clauses:clauses(Meta, Do, S),
{ TClauses, SC } = elixir_clauses:clauses(Meta, Do, Return, S),
{ { 'receive', ?line(Meta), TClauses }, SC };
_ ->
After = elixir_clauses:get_pairs('after', KV),
{ TClauses, SC } = elixir_clauses:clauses(Meta, Do ++ After, S),
{ TClauses, SC } = elixir_clauses:clauses(Meta, Do ++ After, Return, S),
{ FClauses, TAfter } = elixir_utils:split_last(TClauses),
{ _, _, [FExpr], _, FAfter } = TAfter,
{ { 'receive', ?line(Meta), FClauses, FExpr, FAfter }, SC }
@@ -122,6 +132,9 @@ translate({'receive', Meta, [KV] }, S) when is_list(KV) ->
translate({ Kind, Meta, Args }, S) when is_list(Args), (Kind == lc) orelse (Kind == bc) ->
translate_comprehension(Meta, Kind, Args, S);
translate({ for, Meta, Args }, S) when is_list(Args) ->
elixir_for:translate(Meta, Args, S);
%% Super
translate({ super, Meta, Args }, S) when is_list(Args) ->
@@ -144,21 +157,6 @@ translate({ super, Meta, Args }, S) when is_list(Args) ->
%% Variables
translate({ '^', Meta, [ { Name, VarMeta, Kind } = Var ] },
#elixir_scope{extra=fn_match, extra_guards=Extra} = S) when is_atom(Name), is_atom(Kind) ->
Tuple = { Name, var_kind(VarMeta, Kind) },
case orddict:find(Tuple, S#elixir_scope.backup_vars) of
{ ok, { Value, _Counter } } ->
elixir_errors:deprecation(Meta, S#elixir_scope.file, "using ^ inside function clause heads is "
"deprecated, please use a guard instead"),
Line = ?line(Meta),
{ TVar, TS } = translate(Var, S),
Guard = { op, Line, '=:=', { var, ?line(Meta), Value }, TVar },
{ TVar, TS#elixir_scope{extra_guards=[Guard|Extra]} };
error ->
compile_error(Meta, S#elixir_scope.file, "unbound variable ^~ts", [Name])
end;
translate({ '^', Meta, [ { Name, VarMeta, Kind } ] }, #elixir_scope{context=match} = S) when is_atom(Name), is_atom(Kind) ->
Tuple = { Name, var_kind(VarMeta, Kind) },
case orddict:find(Tuple, S#elixir_scope.backup_vars) of
@@ -174,6 +172,9 @@ translate({ '_', Meta, Kind }, #elixir_scope{context=match} = S) when is_atom(Ki
translate({ '_', Meta, Kind }, S) when is_atom(Kind) ->
compile_error(Meta, S#elixir_scope.file, "unbound variable _");
translate({ Name, Meta, Kind }, #elixir_scope{extra=map_key} = S) when is_atom(Name), is_atom(Kind) ->
compile_error(Meta, S#elixir_scope.file, "illegal use of variable ~ts in map key", [Name]);
translate({ Name, Meta, Kind }, S) when is_atom(Name), is_atom(Kind) ->
elixir_scope:translate_var(Meta, Name, var_kind(Meta, Kind), S);
@@ -208,8 +209,9 @@ translate({ { '.', _, [Left, Right] }, Meta, Args }, S)
{ TLeft, SL } = translate(Left, S),
{ TArgs, SA } = translate_args(Args, mergec(S, SL)),
Line = ?line(Meta),
Arity = length(Args),
Line = ?line(Meta),
Arity = length(Args),
TRight = { atom, Line, Right },
%% We need to rewrite erlang function calls as operators
%% because erl_eval chokes on them. We can remove this
@@ -221,7 +223,35 @@ translate({ { '.', _, [Left, Right] }, Meta, Args }, S)
{ list_to_tuple([op, Line, Right] ++ TArgs), mergev(SL, SA) };
false ->
assert_allowed_in_context(Meta, Left, Right, Arity, S),
{ { call, Line, { remote, Line, TLeft, { atom, 0, Right } }, TArgs }, mergev(SL, SA) }
SC = mergev(SL, SA),
case not is_atom(Left) andalso (Arity == 0) of
true ->
{ Var, _, SV } = elixir_scope:build_var('_', SC),
TVar = { var, Line, Var },
TMap = { tuple, Line, [
{ atom, Line, 'Elixir.KeyError' },
{ atom, Line, '__exception__' },
TRight,
TVar] },
{ { 'case', -1, TLeft, [
{ clause, -1,
[{ map, Line, [{ map_field_exact, Line, TRight, TVar }] }],
[],
[TVar] },
{ clause, -1,
[{ match, Line, { map, Line, [] }, TVar }],
[],
[?wrap_call(Line, erlang, error, [TMap])] },
{ clause, -1,
[TVar],
[],
[{ call, Line, { remote, Line, TVar, TRight }, [] }] }
] }, SV };
false ->
{ { call, Line, { remote, Line, TLeft, TRight }, TArgs }, SC }
end
end;
%% Anonymous function calls
@@ -295,6 +325,7 @@ unblock({ 'block', _, Exprs }) -> Exprs;
unblock(Expr) -> [Expr].
%% Translate args
translate_arg(Arg, Acc, S) when is_number(Arg); is_atom(Arg); is_binary(Arg); is_pid(Arg); is_function(Arg) ->
{ TArg, _ } = translate(Arg, S),
{ TArg, Acc };
@@ -303,11 +334,37 @@ translate_arg(Arg, Acc, S) ->
{ TArg, mergev(Acc, TAcc) }.
translate_args(Args, #elixir_scope{context=match} = S) ->
translate_many(Args, S);
lists:mapfoldl(fun translate/2, S, Args);
translate_args(Args, S) ->
lists:mapfoldl(fun(X, Acc) -> translate_arg(X, Acc, S) end, S, Args).
%% Translate blocks
translate_block([], Acc, _Return, S) ->
{ lists:reverse(Acc), S };
translate_block([H], Acc, Return, S) ->
{ TH, TS } = translate_block(H, Return, S),
translate_block([], [TH|Acc], Return, TS);
translate_block([H|T], Acc, Return, S) ->
{ TH, TS } = translate_block(H, false, S),
translate_block(T, [TH|Acc], Return, TS).
translate_block(Expr, Return, S) ->
case (Return == false) andalso handles_no_return(Expr) of
true -> translate(Expr, S#elixir_scope{return=Return});
false -> translate(Expr, S)
end.
%% Expressions that can handle no return may receive
%% return=false but must always return return=true.
handles_no_return({ 'try', _, [_] }) -> true;
handles_no_return({ 'for', _, [_|_] }) -> true;
handles_no_return({ 'case', _, [_, _] }) -> true;
handles_no_return({ 'receive', _, [_] }) -> true;
handles_no_return({ '__block__', _, [_|_] }) -> true;
handles_no_return(_) -> false.
%% Comprehensions
translate_comprehension(Meta, Kind, Args, S) ->
+77 -68
View File
@@ -1,58 +1,58 @@
-module(elixir_try).
-export([clauses/3, format_error/1]).
-export([clauses/4, format_error/1]).
-include("elixir.hrl").
clauses(_Meta, Clauses, S) ->
clauses(_Meta, Clauses, Return, S) ->
Catch = elixir_clauses:get_pairs('catch', Clauses),
Rescue = elixir_clauses:get_pairs(rescue, Clauses),
Transformer = fun(X, SAcc) ->
{ TX, TS } = each_clause(X, SAcc),
{ TX, TS } = each_clause(X, Return, SAcc),
{ TX, elixir_scope:mergec(S, TS) }
end,
lists:mapfoldl(Transformer, S, Rescue ++ Catch).
each_clause({ 'catch', Meta, Raw, Expr }, S) ->
each_clause({ 'catch', Meta, Raw, Expr }, Return, S) ->
{ Args, Guards } = elixir_clauses:extract_splat_guards(Raw),
Final = case Args of
[X] -> [throw, X, { '_', Meta, nil }];
[X,Y] -> [X, Y, { '_', Meta, nil }];
[_,_,_] -> Args;
_ ->
[X] -> [throw, X, { '_', Meta, nil }];
[X,Y] -> [X, Y, { '_', Meta, nil }];
_ ->
elixir_errors:compile_error(Meta, S#elixir_scope.file, "too many arguments given for catch")
end,
Condition = [{ '{}', Meta, Final }],
elixir_clauses:clause(?line(Meta), fun elixir_translator:translate_many/2, Condition, Expr, Guards, S);
elixir_clauses:clause(?line(Meta), fun elixir_translator:translate_args/2,
Condition, Expr, Guards, Return, S);
each_clause({ rescue, Meta, [{ in, _, [Left, Right]}], Expr }, S) ->
each_clause({ rescue, Meta, [{ in, _, [Left, Right]}], Expr }, Return, S) ->
case Left of
{ '_', _, LAtom } when is_atom(LAtom) ->
{ VarName, _, CS } = elixir_scope:build_var('_', S),
{ Clause, _ } = rescue_guards(Meta, { VarName, Meta, nil }, Right, S),
each_clause({ 'catch', Meta, Clause, Expr }, CS);
each_clause({ 'catch', Meta, Clause, Expr }, Return, CS);
_ ->
{ Clause, Safe } = rescue_guards(Meta, Left, Right, S),
case Safe of
true ->
each_clause({ 'catch', Meta, Clause, Expr }, S);
each_clause({ 'catch', Meta, Clause, Expr }, Return, S);
false ->
{ VarName, _, CS } = elixir_scope:build_var('_', S),
ClauseVar = { VarName, Meta, nil },
{ FinalClause, _ } = rescue_guards(Meta, ClauseVar, Right, S),
Match = { '=', Meta, [
Left,
{ { '.', Meta, ['Elixir.Exception', normalize] }, Meta, [ClauseVar] }
{ { '.', Meta, ['Elixir.Exception', normalize] }, Meta, [error, ClauseVar] }
] },
FinalExpr = prepend_to_block(Meta, Match, Expr),
each_clause({ 'catch', Meta, FinalClause, FinalExpr }, CS)
each_clause({ 'catch', Meta, FinalClause, FinalExpr }, Return, CS)
end
end;
each_clause({rescue,Meta,_,_}, S) ->
each_clause({ rescue, Meta, _, _ }, _Return, S) ->
elixir_errors:compile_error(Meta, S#elixir_scope.file, "invalid arguments for rescue in try");
each_clause({Key,Meta,_,_}, S) ->
each_clause({ Key, Meta, _, _ }, _Return, S) ->
elixir_errors:compile_error(Meta, S#elixir_scope.file, "invalid key ~ts in try", [Key]).
%% Helpers
@@ -71,8 +71,8 @@ rescue_guards(Meta, Var, Guards, S) ->
IsException = { erl(Meta, '=='), Meta, [
{ erl(Meta, element), Meta, [2, Var] }, '__exception__'
] },
OrElse = join(Meta, fun do_or/3, Elixir),
[join(Meta, fun do_and/3, [IsTuple, IsException, OrElse])|Erlang]
OrElse = join(Meta, fun erl_or/3, Elixir),
[join(Meta, fun erl_and/3, [IsTuple, IsException, OrElse])|Erlang]
end,
{
[{ 'when', Meta, [error, Var, join_when(Meta, Final)] }],
@@ -87,12 +87,12 @@ rescue_each_var(Meta, ClauseVar, Guards) ->
case Vars == [] of
true -> { [], [] };
false ->
Elixir = [exception_compare(Meta, ClauseVar, Var) || Var <- Vars],
Elixir = [erl_exception_compare(Meta, ClauseVar, Var) || Var <- Vars],
Erlang = lists:map(fun(Rescue) ->
Compares = [{ erl(Meta, '=='), Meta, [Rescue, Var] } || Var <- Vars],
do_and(Meta,
erlang_rescue_guard_for(Meta, ClauseVar, Rescue),
join(Meta, fun do_or/3, Compares))
erl_and(Meta,
erl_rescue_guard_for(Meta, ClauseVar, Rescue),
join(Meta, fun erl_or/3, Compares))
end, erlang_rescues()),
{ Elixir, Erlang }
end.
@@ -111,21 +111,21 @@ rescue_each_ref(Meta, Var, [H|T], Elixir, Erlang, _Safe, S) when
H == 'Elixir.BadArityError'; H == 'Elixir.BadFunctionError';
H == 'Elixir.MatchError'; H == 'Elixir.CaseClauseError';
H == 'Elixir.TryClauseError'; H == 'Elixir.FunctionClauseError';
H == 'Elixir.SystemLimitError' ->
Expr = do_or(Meta,
erlang_rescue_guard_for(Meta, Var, H),
exception_compare(Meta, Var, H)),
H == 'Elixir.SystemLimitError'; H == 'Elixir.BadStructError' ->
Expr = erl_or(Meta,
erl_rescue_guard_for(Meta, Var, H),
erl_exception_compare(Meta, Var, H)),
rescue_each_ref(Meta, Var, T, Elixir, [Expr|Erlang], false, S);
rescue_each_ref(Meta, Var, [H|T], Elixir, Erlang, Safe, S) when is_atom(H) ->
rescue_each_ref(Meta, Var, T, [exception_compare(Meta, Var, H)|Elixir], Erlang, Safe, S);
rescue_each_ref(Meta, Var, T, [erl_exception_compare(Meta, Var, H)|Elixir], Erlang, Safe, S);
rescue_each_ref(Meta, Var, [H|T], Elixir, Erlang, Safe, S) ->
case elixir_translator:translate(H, S) of
{ { atom, _, Atom }, _ } ->
rescue_each_ref(Meta, Var, [Atom|T], Elixir, Erlang, Safe, S);
_ ->
rescue_each_ref(Meta, Var, T, [exception_compare(Meta, Var, H)|Elixir], Erlang, Safe, S)
rescue_each_ref(Meta, Var, T, [erl_exception_compare(Meta, Var, H)|Elixir], Erlang, Safe, S)
end;
rescue_each_ref(_, _, [], Elixir, Erlang, Safe, _) ->
@@ -135,64 +135,70 @@ rescue_each_ref(_, _, [], Elixir, Erlang, Safe, _) ->
erlang_rescues() ->
[
'Elixir.UndefinedFunctionError', 'Elixir.ArgumentError', 'Elixir.ArithmeticError', 'Elixir.BadArityError',
'Elixir.BadFunctionError', 'Elixir.MatchError', 'Elixir.CaseClauseError', 'Elixir.TryClauseError',
'Elixir.FunctionClauseError', 'Elixir.SystemLimitError', 'Elixir.ErlangError'
'Elixir.UndefinedFunctionError', 'Elixir.ArgumentError', 'Elixir.ArithmeticError',
'Elixir.BadArityError', 'Elixir.BadFunctionError', 'Elixir.MatchError',
'Elixir.CaseClauseError', 'Elixir.TryClauseError', 'Elixir.FunctionClauseError',
'Elixir.SystemLimitError', 'Elixir.ErlangError', 'Elixir.BadStructError'
].
erlang_rescue_guard_for(Meta, Var, List) when is_list(List) ->
join(Meta, fun do_or/3, [erlang_rescue_guard_for(Meta, Var, X) || X <- List]);
erl_rescue_guard_for(Meta, Var, List) when is_list(List) ->
join(Meta, fun erl_or/3, [erl_rescue_guard_for(Meta, Var, X) || X <- List]);
erlang_rescue_guard_for(Meta, Var, 'Elixir.UndefinedFunctionError') ->
erl_rescue_guard_for(Meta, Var, 'Elixir.UndefinedFunctionError') ->
{ erl(Meta, '=='), Meta, [Var, undef] };
erlang_rescue_guard_for(Meta, Var, 'Elixir.FunctionClauseError') ->
erl_rescue_guard_for(Meta, Var, 'Elixir.FunctionClauseError') ->
{ erl(Meta, '=='), Meta, [Var, function_clause] };
erlang_rescue_guard_for(Meta, Var, 'Elixir.SystemLimitError') ->
erl_rescue_guard_for(Meta, Var, 'Elixir.SystemLimitError') ->
{ erl(Meta, '=='), Meta, [Var, system_limit] };
erlang_rescue_guard_for(Meta, Var, 'Elixir.ArithmeticError') ->
erl_rescue_guard_for(Meta, Var, 'Elixir.ArithmeticError') ->
{ erl(Meta, '=='), Meta, [Var, badarith] };
erlang_rescue_guard_for(Meta, Var, 'Elixir.BadArityError') ->
do_and(Meta,
{ erl(Meta, is_tuple), Meta, [Var] },
exception_compare(Meta, Var, badarity));
erl_rescue_guard_for(Meta, Var, 'Elixir.BadArityError') ->
erl_and(Meta,
erl_tuple_size(Meta, Var, 2),
erl_exception_compare(Meta, Var, badarity));
erlang_rescue_guard_for(Meta, Var, 'Elixir.BadFunctionError') ->
do_and(Meta,
{ erl(Meta, is_tuple), Meta, [Var] },
exception_compare(Meta, Var, badfun));
erl_rescue_guard_for(Meta, Var, 'Elixir.BadFunctionError') ->
erl_and(Meta,
erl_tuple_size(Meta, Var, 2),
erl_exception_compare(Meta, Var, badfun));
erlang_rescue_guard_for(Meta, Var, 'Elixir.MatchError') ->
do_and(Meta,
{ erl(Meta, is_tuple), Meta, [Var] },
exception_compare(Meta, Var, badmatch));
erl_rescue_guard_for(Meta, Var, 'Elixir.MatchError') ->
erl_and(Meta,
erl_tuple_size(Meta, Var, 2),
erl_exception_compare(Meta, Var, badmatch));
erlang_rescue_guard_for(Meta, Var, 'Elixir.CaseClauseError') ->
do_and(Meta,
{ erl(Meta, is_tuple), Meta, [Var] },
exception_compare(Meta, Var, case_clause));
erl_rescue_guard_for(Meta, Var, 'Elixir.CaseClauseError') ->
erl_and(Meta,
erl_tuple_size(Meta, Var, 2),
erl_exception_compare(Meta, Var, case_clause));
erlang_rescue_guard_for(Meta, Var, 'Elixir.TryClauseError') ->
do_and(Meta,
{ erl(Meta, is_tuple), Meta, [Var] },
exception_compare(Meta, Var, try_clause));
erl_rescue_guard_for(Meta, Var, 'Elixir.TryClauseError') ->
erl_and(Meta,
erl_tuple_size(Meta, Var, 2),
erl_exception_compare(Meta, Var, try_clause));
erlang_rescue_guard_for(Meta, Var, 'Elixir.ArgumentError') ->
do_or(Meta,
{ erl(Meta, '=='), Meta, [Var, badarg] },
do_and(Meta,
{ erl(Meta, is_tuple), Meta, [Var] },
exception_compare(Meta, Var, badarg)));
erl_rescue_guard_for(Meta, Var, 'Elixir.BadStructError') ->
erl_and(Meta,
erl_tuple_size(Meta, Var, 3),
erl_exception_compare(Meta, Var, badstruct));
erlang_rescue_guard_for(Meta, Var, 'Elixir.ErlangError') ->
erl_rescue_guard_for(Meta, Var, 'Elixir.ArgumentError') ->
erl_or(Meta,
{ erl(Meta, '=='), Meta, [Var, badarg] },
erl_and(Meta,
erl_tuple_size(Meta, Var, 2),
erl_exception_compare(Meta, Var, badarg)));
erl_rescue_guard_for(Meta, Var, 'Elixir.ErlangError') ->
IsNotTuple = { erl(Meta, 'not'), Meta, [{ erl(Meta, is_tuple), Meta, [Var] }] },
IsException = { erl(Meta, '/='), Meta, [
{ erl(Meta, element), Meta, [2, Var] }, '__exception__'
] },
do_or(Meta, IsNotTuple, IsException).
erl_or(Meta, IsNotTuple, IsException).
%% Helpers
@@ -205,7 +211,10 @@ format_error({ rescue_no_match, Var, Alias }) ->
is_var({ Name, _, Atom }) when is_atom(Name), is_atom(Atom) -> true;
is_var(_) -> false.
exception_compare(Meta, Var, Expr) ->
erl_tuple_size(Meta, Var, Size) ->
{ erl(Meta, '=='), Meta, [{ erl(Meta, tuple_size), Meta, [Var] }, Size] }.
erl_exception_compare(Meta, Var, Expr) ->
{ erl(Meta, '=='), Meta, [
{ erl(Meta, element), Meta, [1, Var] },
Expr
@@ -224,5 +233,5 @@ prepend_to_block(Meta, Expr, Args) ->
{ '__block__', Meta, [Expr, Args] }.
erl(Meta, Op) -> { '.', Meta, [erlang, Op] }.
do_or(Meta, Left, Right) -> { '__op__', Meta, ['orelse', Left, Right] }.
do_and(Meta, Left, Right) -> { '__op__', Meta, ['andalso', Left, Right] }.
erl_or(Meta, Left, Right) -> { '__op__', Meta, ['orelse', Left, Right] }.
erl_and(Meta, Left, Right) -> { '__op__', Meta, ['andalso', Left, Right] }.

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