Compare commits

...
842 Commits
Author SHA1 Message Date
José Valim e654f73762 Release v0.10.2 2013-09-03 15:02:43 -03:00
José Valim 147a452e7f Fix regression on Stream nesting 2013-09-03 13:39:43 -03:00
José Valim b39a5d8632 Deprecate %b and %B in favor of %s and %S 2013-09-03 13:14:13 -03:00
José Valim 4caa2e01f1 Move is_odd and is_even to Integer 2013-09-03 09:41:55 -03:00
José Valim 71129bca33 Merge pull request #1683 from meh/fix-version
Fix handling pre/build data when minor/patch is missing in Mix.Version
2013-09-02 12:39:04 -07:00
meh a107b2ba64 Fix handling pre/build data when minor/patch is missing in Mix.Version 2013-09-02 21:35:20 +02:00
José Valim 654988fe60 Update CHANGELOG 2013-09-02 16:05:50 -03:00
José Valim 10a795ff44 Only support iolists on IO 2013-09-02 16:01:10 -03:00
José Valim ba209268ad Merge pull request #1675 from ericmj/enum-at-fetch-neg
Support negative indicies in Enum.at/fetch/fetch!
2013-08-31 05:34:32 -07:00
Eric Meadows-Jonsson 80ae944d83 Support negative indicies in Enum.at/fetch/fetch! 2013-08-31 14:23:34 +02:00
José Valim 4d3a83262c Merge pull request #1671 from vanstee/string-split-trim-default
No longer trim by default when splitting
2013-08-31 04:27:46 -07:00
José Valim 6d02cd9076 Merge pull request #1672 from meh/regex-compile
Return error on unsupported options in Regex.compile
2013-08-31 04:21:46 -07:00
José Valim 42692c27ab Merge pull request #1669 from brunoro/ipv6-uri
URI.parse supports IPv6 addresses
2013-08-31 03:16:08 -07:00
Gustavo Brunoro 2b34ae14da parsing embedded IPv4 addresses 2013-08-30 19:04:30 -03:00
meh 6e60fa160e Return error on unsupported options in Regex.compile 2013-08-30 13:35:39 +02:00
vanstee dd4a0e96dd No longer trim by default when splitting
Further discussion here:

  https://groups.google.com/forum/#!topic/elixir-lang-core/66gb3J0bnUA
2013-08-30 00:38:41 -04:00
Gustavo Brunoro f4175a1dfa more tests for IPv6 2013-08-29 16:15:12 -03:00
José Valim bcceccd85e Consider stream_lazy to always return the result, move nesting check to after reduce 2013-08-29 07:46:24 -03:00
José Valim b1cf5d9252 Merge pull request #1667 from betawaffle/lazy-throw-fix
Fix Stream.take/2 and Stream.take_while/2 for Stream.Lazy
2013-08-29 03:16:27 -07:00
José Valim 447d2215d1 Merge pull request #1668 from elia/doc-fix
Markdown rendering fix
2013-08-29 03:07:54 -07:00
Gustavo Brunoro ffbcf67431 handling IPv6 addresses on URIs
* very naïve and permissive regex to check for IPv6 addreses.
2013-08-29 00:02:23 -03:00
Gustavo Brunoro d88cd5c92c adding tests for ipv6 addresses on URIs 2013-08-28 23:51:13 -03:00
Elia Schito fd5cbd2be6 Markdown rendering fix 2013-08-28 23:55:51 +02:00
Andrew Hodges e525fb3176 Fix Stream.take/2 and Stream.take_while/2 for Stream.Lazy 2013-08-28 16:27:45 -04:00
José Valim 49aed8cb9a Make a distinction about eager and full evaluation of the collection 2013-08-28 09:25:24 -03:00
José Valim 366d9843aa out of it -> as result 2013-08-28 09:16:19 -03:00
José Valim 0f2838c98c Improve Enum docs 2013-08-28 09:14:07 -03:00
José Valim 8ae1a3b60a Merge pull request #1662 from pminten/note-about-enum-list-return
Add note to Enum to clarify what's returned
2013-08-28 04:58:58 -07:00
Peter Minten 4a405cc8f0 Add note to Enum to clarify what's returned
In particular clarify that infinite streams don't work with many of the
functions.
2013-08-28 13:54:54 +02:00
José Valim 1aca6db15a Merge pull request #1660 from betawaffle/enum-concat
Add Stream.concat/1,2 and Enum.concat/1,2
2013-08-28 04:54:39 -07:00
Andrew Hodges f7391b48fa Add tests for Stream.concat/1,2 and Enum.concat/1,2 2013-08-27 12:53:31 -04:00
Andrew Hodges d78014e7e2 Add support for enumerables of enumerables 2013-08-27 10:22:24 -04:00
Andrew Hodges b2c2a30c83 Move the List concat tests to Enum 2013-08-27 09:37:56 -04:00
Andrew Hodges 9ef2996b78 Replace occurrences of List.concat/1,2 with Enum.concat/1,2 2013-08-27 09:37:26 -04:00
Andrew Hodges 0d13468b1f Deprecate List.concat/1,2 2013-08-27 09:36:17 -04:00
Andrew Hodges 6ef031b87d Add Enum.concat/1,2 and Stream.concat/1,2 2013-08-27 09:34:24 -04:00
José Valim 5f1e5ebc5f Update record docs style 2013-08-27 07:16:25 -03:00
José Valim 9312a7d127 Merge pull request #1658 from pminten/doc-guidelines-in-contributing
Add bit about documentation style to CONTRIBUTING
2013-08-27 03:15:04 -07:00
Peter Minten c555bf934b Add bit about documentation style to CONTRIBUTING 2013-08-27 11:27:19 +02:00
José Valim d4efc7bc5d Make it clear Dict.merge expects an enum that yields two-item tuples 2013-08-26 20:13:19 -03:00
José Valim 3c7fb0da6d Optimize split implementation 2013-08-26 15:53:49 -03:00
José Valim 89bebeaeea Merge pull request #1651 from vanstee/string-split-trim
Add support for `trim` option to `String.split` and `Regex.split`
2013-08-26 11:23:41 -07:00
José Valim c826aadc0f Merge pull request #1655 from ericmj/fix-enum-slice-oob
Enum.slice returns nil when start is out of bounds
2013-08-26 11:22:49 -07:00
vanstee e304ea9543 Accept the trim option for Regex.split/3
By default `trim` will be set to true. However, since the `global` option
was implemented with the `parts` option (`{ :parts, :infinity }`), we
have to apply a filter to the final result to support `trim`.
2013-08-26 11:41:22 -04:00
vanstee 64cdc76832 Accept the trim option when splitting strings
By default `trim` will be set to true. Because `:binary.strip` only
removes empty strings from the end of the result, we have to apply a
filter to remove empty strings from the beginning before returning.
2013-08-26 11:40:32 -04:00
Eric Meadows-Jonsson dbbfa3536f Enum.slice returns nil when start is out of bounds 2013-08-26 17:15:09 +02:00
José Valim 25ffb4b4b1 Merge pull request #1652 from vanstee/stream-flat-map
Implement `Stream.flat_map/2`
2013-08-26 05:42:10 -07:00
José Valim e80dad1925 Merge pull request #1654 from ericmj/fix-enum-find
Use ifnone in Enum.find for all collections
2013-08-26 05:26:25 -07:00
Eric Meadows-Jonsson e2599beff2 Use ifnone in Enum.find for all collections 2013-08-26 10:42:42 +02:00
vanstee 8478db168a Implement Stream.flat_map/2
Fixes #1642
2013-08-25 22:44:35 -04:00
vanstee 34dade0916 Revert "Merge pull request #1645 from vanstee/string-split-repeats"
This reverts commit fb30101632, reversing
changes made to c348eb7cc5.
2013-08-25 21:27:06 -04:00
José Valim eb96d73198 Add a count guard check for slice 2013-08-25 22:16:58 -03:00
José Valim cbb1f45ede Update CHANGELOG 2013-08-25 22:10:37 -03:00
José Valim 4723bca17e Merge pull request #1650 from ericmj/enum-slice
Add Enum.slice/3
2013-08-25 18:08:48 -07:00
Eric Meadows-Jonsson 263f4bf543 Add Enum.slice/3 2013-08-25 23:14:31 +02:00
José Valim fad339957d Remove empty Tuple module 2013-08-25 10:30:15 -03:00
José Valim 488344839a Allow any function identifier as a variable, closes #1649 2013-08-25 10:28:55 -03:00
José Valim f02f90fce0 Merge pull request #1648 from pminten/quote-improper-lists
Allow Macro.escape([1|2])
2013-08-25 03:45:37 -07:00
Peter Minten c73919993d Allow Macro.escape([1|2])
This fixes #1646
2013-08-25 09:35:08 +02:00
Alexei Sholik 3768087b47 Fix failing doctest 2013-08-25 00:29:54 +03:00
José Valim 4a0db2e974 Handle more types on bitsyntax. 2013-08-24 16:07:55 -03:00
José Valim 55aad68512 Splice nested binaries with default types and size 2013-08-24 15:55:24 -03:00
José Valim 3b4954b652 Merge pull request #1643 from pminten/elixirc-silent
Add --verbose to elixirc
2013-08-24 11:15:21 -07:00
José Valim fb30101632 Merge pull request #1645 from vanstee/string-split-repeats
Keep empty strings when splitting
2013-08-24 05:21:14 -07:00
José Valim c348eb7cc5 Merge pull request #1644 from jwarwick/string_typos
Fixed doc typos in String and Node
2013-08-24 05:02:29 -07:00
José Valim 98c47e94a1 String.to_float/1 docs updated 2013-08-24 13:56:35 +02:00
Peter Minten 4c28f325e3 Supress 'Compiled' msg by default in elixirc
Adds a --verbose option to elixirc

This fixes #1618
2013-08-24 13:30:34 +02:00
José Valim da50584060 Deprecate def/4 and friends in favor of def/2 with unquote and friends 2013-08-23 19:42:20 +02:00
vanstee 891564f114 Keep empty strings when splitting
To stay consistent with both `String.split/2` and `:binary.split` we
need to keep around empty strings that are accumulated while splitting.
In the future we could support the `:trim` option to consistently remove
these in call calls to `String.split`.

Fixes #1636
2013-08-23 12:37:02 -04:00
John Warwick 22336a94d0 Fixed doc typos in String and Node 2013-08-23 10:58:38 -04:00
José Valim cae9b05d6a Merge pull request #1638 from jwarwick/enum_typo
Fixed typo in Enum docs
2013-08-23 02:31:27 -07:00
José Valim a6cdc835a1 No need to wrap quote do: nil in a block 2013-08-23 11:22:11 +02:00
John Warwick 7ae1795c62 Fixed typo in Enum docs 2013-08-22 11:36:35 -04:00
José Valim 7b3bbe3a36 Another attempt at cwd handling 2013-08-22 16:07:19 +02:00
José Valim ade2c25e08 Handle when :file.get_cwd returns a binary 2013-08-22 15:59:14 +02:00
José Valim e2ba0bd649 Properly order CHANGELOG 2013-08-22 14:27:57 +02:00
José Valim 3de4cbbdb9 Update CHANGELOG 2013-08-22 12:20:37 +02:00
José Valim 0510512375 Deprecate to_binary in favor of to_string
We also rename Binary.Chars to String.Chars.

As Binary.Chars was the protocol used for interpolation,
it is semantically correct to have a protocol that works
on strings, instead of raw binaries.
2013-08-22 12:19:30 +02:00
José Valim a3827527a8 Update CHANGELOG 2013-08-22 11:13:18 +02:00
José Valim 820d0cb9a0 Print failures for ExUnit as soon as they come 2013-08-22 11:12:18 +02:00
José Valim 0e1aac32f5 import accept :functions and :macros as values in :only
This commit removes the previous ambiguous syntax:

    import :functions, Foo

In favor of:

    import Foo, only: :functions
2013-08-22 10:34:45 +02:00
José Valim 308d5a35eb Merge pull request #1629 from Raynes/is_it_even_odd
Add a pair of even and odd checking functions
2013-08-21 23:48:59 -07:00
José Valim 6244bdd140 Merge pull request #1634 from jwarwick/path_typo
Fixed doc typos in Code and Path
2013-08-21 13:24:23 -07:00
John Warwick 384921a905 Fixed doc typos in Code and Path 2013-08-21 15:51:57 -04:00
Anthony Grimes fcc37030e2 Add a pair of even and odd checking functions. 2013-08-21 11:08:20 -07:00
José Valim 473f3c560e Update CHANGELOG 2013-08-21 10:42:06 +02:00
José Valim fb8ad142e9 Deprecate :umbrella path opt in favor of :in_umbrella
The previous :umbrella option was ambiguous, as it was not clear
if it was a dependency inside an umbrella or an umbrella dependency.
2013-08-21 10:31:17 +02:00
José Valim 7a50baa37b Make a distinction in between overriden and converged, closes #1631 2013-08-21 10:31:17 +02:00
José Valim 2f63c745a3 Merge pull request #1630 from Raynes/chunks_by
Add chunks_by
2013-08-21 01:30:26 -07:00
José Valim fcef2d57fc Rename manifest -> manifests to support compilers with multiple manifests 2013-08-21 09:12:25 +02:00
José Valim fcabb677d5 Merge pull request #1632 from jwarwick/change_typo
Fixed typos in CHANGELOG.md
2013-08-21 00:01:22 -07:00
John Warwick de3d5cdc40 Fixed typos in CHANGELOG.md 2013-08-20 21:33:59 -04:00
José Valim 29f030bb28 Update CHANGELOG 2013-08-20 23:57:59 +02:00
José Valim 8b02a2a7a0 Convert external functions into quoted expressions 2013-08-20 23:52:00 +02:00
Anthony Grimes f7e6b29e86 Add chunks_by. 2013-08-20 14:30:45 -07:00
José Valim c6a12f5039 Improve checks of functions inside records 2013-08-20 19:31:10 +02:00
José Valim 20418a14f7 Refactor Enum.chunks to use Enumerable.reduce/3, avoiding starting lazy collections twice 2013-08-20 14:08:27 +02:00
José Valim 3e1099830a Properly handle @file that comes from warnings 2013-08-20 11:39:29 +02:00
José Valim 82aed93466 Merge pull request #1626 from ToJans/master
Refactored Mix.Tasks.Local.Install to make it more explicit
2013-08-20 02:38:44 -07:00
Tom Janssens df9098ea36 Refactored Mix.Tasks.Local.Install to make it more explicit 2013-08-20 10:15:11 +02:00
José Valim 6831c09913 Merge pull request #1624 from Raynes/chunked
Add Enum.chunks
2013-08-20 00:49:47 -07:00
Anthony Grimes 7f00aded18 Add Enum.chunks. 2013-08-20 00:48:42 -07:00
José Valim 37689946d2 Merge pull request #1625 from jwarwick/typo
Fixed doc typos in IEx.Helpers
2013-08-20 00:14:50 -07:00
John Warwick d76dba1518 Fixed doc typos in IEx.Helpers 2013-08-19 18:22:46 -04:00
José Valim baf647986f Avoid extra eval on EEx 2013-08-19 22:17:29 +02:00
José Valim 84a2b31221 Add Macro.to_string/2 2013-08-19 18:21:42 +02:00
José Valim 9385e59c29 Provide more semantic names for ExUnit.ExpectationError 2013-08-19 11:46:59 +02:00
José Valim 6d9506c012 Improve error message for h(:lists) 2013-08-19 08:41:43 +02:00
José Valim 11640d521f Merge pull request #1621 from meh/version-fix
Fix for Mix.Version comparisons with dev versions
2013-08-18 12:43:38 -07:00
meh 34e0637520 Fix for Mix.Version comparisons with dev versions 2013-08-18 17:29:32 +02:00
José Valim f84b4121ce Allow << _ :: utf8 >> <> binary in matches, closes #1619 2013-08-18 14:36:54 +02:00
José Valim ff2af55e1d Improve warning for ungrouped clauses 2013-08-18 10:05:36 +02:00
José Valim a45a2c9d93 Allow erlang options when compiling elixir regexes 2013-08-17 11:57:17 +02:00
José Valim e3dc5564e8 Merge pull request #1616 from parroty/patch-1
Add underscore for avoiding variable unused warning
2013-08-17 00:10:23 -07:00
José Valim b4a42a4f87 Remove unecessary modules from docs 2013-08-17 09:09:23 +02:00
parroty f2066e0ea9 Add underscore for avoiding variable unused warning 2013-08-17 12:02:48 +09:00
José Valim eccf6c9e6e Rely on :binary.part since we are using :binary prefix functions 2013-08-16 09:36:11 +02:00
José Valim aaebf84440 Always stop on --, even for parse 2013-08-16 09:28:58 +02:00
José Valim 9956785769 Do not show dest info when comparing deps 2013-08-16 09:22:13 +02:00
José Valim 8a69780246 Merge pull request #1610 from stratosv/patch-1
Fixed comment typo in lib/elixir/src/elixir_translator.erl
2013-08-15 23:34:34 -07:00
José Valim 7f36bf105a Merge pull request #1611 from thmzlt/patch-1
Fix typo in CHANGELOG.md
2013-08-15 23:32:18 -07:00
Thomaz Leite f832d3874b Fix typo in CHANGELOG.md 2013-08-15 17:08:38 -07:00
Stratos Voukelatos 42339679e0 Fixed comment typo in lib/elixir/src/elixir_translator.erl 2013-08-16 00:34:23 +03:00
José Valim 0edc5ac2b9 Merge pull request #1608 from jwarwick/mix_shell_process
Fixed typos in Mix.Shell.Process docs
2013-08-15 11:56:22 -07:00
John Warwick 74e194669b Fixed typos in Mix.Shell.Process docs 2013-08-15 14:38:23 -04:00
José Valim 80ce7fc787 Update LEGAL file 2013-08-15 17:04:49 +02:00
José Valim f4270a0432 Do not touch lock manifest if there is no change 2013-08-15 17:04:32 +02:00
José Valim a696b9cc11 Update Macro.to_string comments 2013-08-15 14:27:53 +02:00
José Valim ffc629a408 Merge pull request #1606 from pminten/macro-to-string-binop-parens
Macro.to_string: don't add unneeded binop parens
2013-08-15 05:24:53 -07:00
Peter Minten 081dbceeee Macro.to_string: don't add unneeded binop parens
This changes the `Macro.to_string` output of (quoted) `1+2+3+4` from
`((1 + 2) + 3) + 4` to `1 + 2 + 3 + 4`.
2013-08-15 14:05:37 +02:00
José Valim 9ec2176675 Ensure we are not allocating a new binary on each step 2013-08-15 13:51:55 +02:00
José Valim 1ec8321658 Update CHANGELOG 2013-08-15 10:40:24 +02:00
José Valim eb9753ddd1 Limit inspect by default, closes #1595 2013-08-15 10:35:04 +02:00
José Valim 5193410ba9 Rewrite IEx.Autocomplete to work on strings 2013-08-15 10:35:04 +02:00
José Valim 59013d4066 Merge pull request #1602 from jwarwick/iolist_typo
Cleaned up Kernel.iolist_to_binary/1 docs
2013-08-14 23:53:52 -07:00
José Valim a641a63f98 Merge pull request #1604 from kwijibo/master
Update file.ex
2013-08-14 23:51:56 -07:00
José Valim a86288f5e3 Merge pull request #1597 from thmzlt/doctest_indent
Improve DocTest indentation handling
2013-08-14 23:51:17 -07:00
John Warwick 4900cd84fc Cleaned up Kernel.iolist_to_binary/1 docs 2013-08-14 21:09:46 -04:00
Thomaz Leite 09b34c3aa6 Change DocTest to conform with updated specs
Updated specs are described in http://git.io/o_ccxQ
2013-08-14 17:24:05 -07:00
Keith Alexander 5c3ff52741 Update file.ex
fixed typo
2013-08-14 23:47:00 +01:00
José Valim 7ad6d8e6e2 Merge pull request #1601 from ericmj/macro-tostring-access
Macro.to_string handles access and kw-lists nicer
2013-08-14 14:04:32 -07:00
Eric Meadows-Jonsson 35af4348ae Macro.to_string handles access and kw-lists nicer 2013-08-14 22:10:48 +02:00
José Valim a60e6fab48 Merge pull request #1600 from devinus/defp-justify
do_justify should be private
2013-08-14 11:52:42 -07:00
Devin Torres d6c7e813f3 do_justify should be private 2013-08-14 13:40:33 -05:00
José Valim 5ce8f34fe6 Add missing helpers file 2013-08-14 13:32:50 +02:00
José Valim ff9aba4350 Handle unsafe vars inside or/and 2013-08-14 13:24:12 +02:00
José Valim 6ac280e17c Clean up and expose are more complete API in Exception 2013-08-14 12:55:13 +02:00
José Valim db47bd56b4 Add Path.relative_to_cwd and rely on it when generating stacktraces 2013-08-14 12:36:54 +02:00
José Valim e2ef1ae8de Improve docs for iolist_to_binary 2013-08-14 11:39:04 +02:00
José Valim d5fc147ff0 Quote arguments on elixir/iex help check 2013-08-14 11:25:28 +02:00
José Valim cea31f69dc Review and deprecate list <-> binary conversion 2013-08-14 11:16:37 +02:00
José Valim 3259599111 Update CHANGELOG 2013-08-14 08:34:32 +02:00
José Valim 8affa706b7 Merge pull request #1593 from vanstee/string-justification-functions
Implement string justification functions
2013-08-13 23:27:04 -07:00
Thomaz Leite b248822a0c Remove unused variable from DocTest 2013-08-13 23:13:45 -07:00
Thomaz Leite e4d679c039 Add "too much" and "not enough" tests to DocTest 2013-08-13 23:13:45 -07:00
Thomaz Leite b5cebbfc14 Make mismatched indentation test pass 2013-08-13 23:13:45 -07:00
Thomaz Leite 649536fc61 Add mismatched indentation test to DocTest 2013-08-13 23:13:45 -07:00
Thomaz Leite 676cab5c8f Make heredocs indentation test pass 2013-08-13 23:13:45 -07:00
Thomaz Leite bdf7bc5e6c Add heredocs indentation test to DocTest 2013-08-13 23:13:45 -07:00
José Valim d8ba3e63b3 Merge pull request #1596 from chrismccord/improve_enum_take_docs
Add example to Enum.take docs using negative count.
2013-08-13 22:50:08 -07:00
Chris McCord 8e43b4268d Add example to Enum.take docs using negative count. 2013-08-13 22:55:28 -04:00
vanstee 0850430768 Only accept a single character as padding 2013-08-13 11:07:02 -04:00
vanstee b14617ca90 Do not truncate strings when justifying 2013-08-13 10:53:27 -04:00
vanstee bc35aebd0c Include documentation for justification functions 2013-08-13 10:28:40 -04:00
vanstee 0276419f4e Implement rjust and ljust string functions
To provide string justification, the following functions were
implemented:

  * `String.rjust/2`
  * `String.rjust/3`
  * `String.ljust/2`
  * `String.ljust/3`
2013-08-13 10:28:40 -04:00
vanstee b710ebd5e6 Return an empty string from slice with valid index
To keep in line with ruby semantics of
[`String#slice`](http://ruby-doc.org/core-2.0/String.html#method-i-slice)
slice should return an empty string instead of nil when the starting
index is valid even if the length is 0.

Fixes #1591
2013-08-13 09:45:08 -04:00
José Valim ec479a6805 Do not compile behaviour docs if --docs is not set 2013-08-13 10:47:25 +02:00
José Valim 6277009d90 Update CHANGELOG 2013-08-13 08:39:16 +02:00
José Valim 52188fab89 Move from unicode to list to String.to_char_list 2013-08-13 08:39:16 +02:00
José Valim 19ac199b06 Move from unicode to binary to String.from_char_list 2013-08-13 08:39:16 +02:00
José Valim 5262cb9c57 Macro.unescape_binary -> Macro.unescape_string 2013-08-13 08:39:16 +02:00
José Valim 82d8d2ad63 Deprecate Dict.update/3 in favor of Dict.update!/3
A non-raising version that yields nil will be added in
upcoming releases.
2013-08-13 08:39:16 +02:00
Eric Meadows-Jonsson 5a620ca319 Remove sleep in recompile test 2013-08-12 22:01:20 +02:00
José Valim cbcdf411fa Merge pull request #1590 from jwarwick/record_docs
Fixed Kernel.defrecord arity in docs
2013-08-12 07:17:40 -07:00
John Warwick 40478d1014 Fixed Kernel.defrecord arity in docs 2013-08-12 10:12:08 -04:00
José Valim 7100965169 Merge pull request #1587 from meh/process-fix
Process fix
2013-08-12 00:37:03 -07:00
José Valim d8ded6ecd4 Merge pull request #1588 from vanstee/string-strip-support-unicode
Support removing unicode characters with strip
2013-08-12 00:36:50 -07:00
José Valim 7e0428f52f Merge pull request #1589 from rramsden/doc-fix/typo
Fix typo in File.rmdir/1
2013-08-11 23:07:59 -07:00
Richard Ramsden 77ddc4540d Fix typo in File.rmdir/1 2013-08-12 09:07:04 +09:00
vanstee 742219339c Support removing unicode characters with strip
Thanks for the suggested fix @josevalim
2013-08-11 16:49:15 -04:00
meh e02bf30c9d Make Process.info return nil instead of :undefined 2013-08-11 22:11:07 +02:00
meh cde006affa Fix Process.whereis documentation 2013-08-11 22:11:07 +02:00
José Valim 9daa0bc3a2 Support IO.stream/2 and IO.binstream/2 and deprecate current IO.sream/1 and IO.binstream/1
Closes #1582
2013-08-11 15:48:31 +02:00
José Valim b0317a8e18 Allow capture on anonymous function calls 2013-08-11 15:16:00 +02:00
José Valim 4eb98a83e5 Clean up option parser 2013-08-11 12:38:47 +02:00
José Valim 7e17551456 Update CHANGELOG 2013-08-11 12:31:42 +02:00
José Valim 437037cda6 Fix the build 2013-08-11 12:17:16 +02:00
José Valim d3cb41614b Merge pull request #1561 from thmzlt/option-parser-invalid-options
Improve OptionParser switch validation
2013-08-11 02:54:18 -07:00
José Valim cdef2e762f Merge pull request #1584 from vanstee/atom-system-limit-error
Return error when tokenizing oversized atom
2013-08-11 02:48:12 -07:00
José Valim a7ee38ae9e Detect windows symlinks on File.rm_rf 2013-08-11 11:43:47 +02:00
vanstee ad67e4935a Return error when tokenizing oversized atom
When "atomizing" an unsafe string, we need to make sure the atom length
is going to be less than the system limit (255). Any function calling
`unsafe_to_atom` checks for an error and returns it if present.
2013-08-10 17:10:47 -04:00
vanstee 5371101ad0 Include line number in atomization 2013-08-10 16:54:31 -04:00
vanstee d9cb9c99f0 Separate atomization from tokenization 2013-08-10 16:53:03 -04:00
José Valim 978850ec9e Do not stack rm_rf errors together 2013-08-10 20:06:46 +02:00
José Valim 1b5d81c4d4 Clean up app.start tests 2013-08-10 19:28:25 +02:00
José Valim 12d6666b54 Get rid of unused variable warning 2013-08-10 19:28:15 +02:00
José Valim e83dc3495e Pass Line and File as arguments to we don't read the table twice 2013-08-10 19:09:38 +02:00
José Valim 4ade2cbd48 Merge pull request #1577 from ericmj/mix-recompile
Force recompile of project when elixir version changes
2013-08-10 10:08:42 -07:00
Eric Meadows-Jonsson bf96705d5f Recompile project if elixir version changes 2013-08-10 17:14:41 +02:00
José Valim de72fa2012 Merge pull request #1579 from pminten/add-location-of-overridden-def
Add location of overridden def in warning message
2013-08-10 06:42:38 -07:00
José Valim 55de57ced6 Ensure proper line on interpolations inside heredocs, closes #1581 2013-08-10 15:40:02 +02:00
José Valim 70421e79b1 Fix bug on sorting algorithm on certain branches, closes #1580 2013-08-10 13:28:29 +02:00
Peter Minten a538e86c0c Put overridden def test in warning_test.exs
This removes the need for a separate fixture.
2013-08-10 13:18:34 +02:00
Peter Minten 95bffca5f0 Use relative filename in redefined def warning
The absolute filename can be a bit long, which makes the warning
slightly harder to parse for humans.
2013-08-10 13:08:26 +02:00
Peter Minten e18ae5e335 Add test for overridden def warning 2013-08-10 11:59:50 +02:00
Peter Minten 00d2ad0649 Add location of overridden def in error message
When a def is overridden, for example because there are multiple
clauses with the clauses of a different def are between them, it
is nice to know where the def that was overridden starts.

This should fix #1559.
2013-08-10 11:59:24 +02:00
José Valim 9c7e6495e9 Merge pull request #1578 from guilleiguaran/enum_map_flatten
Add Enum.flat_map
2013-08-10 01:28:43 -07:00
Guillermo Iguaran ed7965e257 Add Enum.flat_map
Returns a new collection appending the result of invoking `fun`
on each corresponding item of `collection`.
2013-08-10 02:36:16 -05:00
Thomaz Leite 0187f31520 Converge store_option and validate_option in OptionParser 2013-08-09 09:52:15 -07:00
Thomaz Leite a577051b09 Update mix tasks for OptionParser permanent invalid list 2013-08-09 09:52:15 -07:00
Thomaz Leite abbbcdf659 Update OptionParser test for permanent invalid list 2013-08-09 09:52:15 -07:00
Thomaz Leite 9fa7f0810f Change OptionParser to always permanent invalid list
The tuple returned from OptionParser now always includes the third element (invalid options list).
2013-08-09 09:52:15 -07:00
Thomaz Leite 770a7880c2 Add documentation for invalid switches 2013-08-09 09:52:15 -07:00
Thomaz Leite edfacf0c2f Use list concatenation operator instead of function 2013-08-09 09:52:15 -07:00
Thomaz Leite 06bc04a7bf Improve OptionParser switch validation
This adds a third element to the tuple returned from OptionParser.parse/2 when the value for an option doesn't match its switch type.
2013-08-09 09:52:15 -07:00
José Valim 730c0992b5 Add Dict.Behaviour to CHANGELOG 2013-08-09 08:42:09 +02:00
José Valim 8d6d4c37a4 Merge pull request #1574 from jwarwick/dict_typos
Fixed doc typos in Dict
2013-08-08 23:37:11 -07:00
José Valim bae0a46bab Merge pull request #1571 from sasa1977/dict_behaviour
added Dict.Behaviour
2013-08-08 23:37:02 -07:00
John Warwick 80caf331ec Fixed doc typos in Dict 2013-08-08 17:59:58 -04:00
sasa1977 47316bcae7 modifications according to comments from the pull request 2013-08-08 18:16:08 +02:00
José Valim b83eb85a25 Slight clean up on dict_test.exs 2013-08-08 17:28:01 +02:00
sasa1977 596f62c57e added Dict.Behaviour 2013-08-08 14:58:12 +02:00
José Valim 9d9bec0af2 Support --detached and --hidden options 2013-08-08 11:19:35 +02:00
José Valim db26fb2d18 Allow iex to --remsh into an Erlang node that has IEx 2013-08-08 10:46:17 +02:00
José Valim 23c286c4ac Merge pull request #1567 from jwarwick/doc_typos
Fixed doc typos in List and Enum
2013-08-07 23:40:01 -07:00
John Warwick 0c7995d119 Fixed doc typos in Enum 2013-08-07 17:24:25 -04:00
John Warwick 7aa28e042e Fixed doc typos in List 2013-08-07 17:09:37 -04:00
José Valim c0ad8c5539 Clean up Dict interface 2013-08-07 19:42:55 +02:00
José Valim 03d2260776 Fix warnings and update CHANGELOG 2013-08-07 12:51:31 +02:00
José Valim 8d156ba5fb Merge pull request #1562 from alexrp/master
Make System.get_env/0 behave sanely.
2013-08-07 03:49:01 -07:00
alexrp 8acdb26842 Make System.get_env/0 behave sanely. 2013-08-07 12:44:05 +02:00
José Valim bb487447ac Update CHANGELOG 2013-08-07 08:26:18 +02:00
José Valim 156905d684 Deprecate Enum.min/2 and Enum.max/2 in favor of Enum.min_by/2 and Enum.max_by/2 2013-08-07 08:26:06 +02:00
José Valim b212b036f9 Refactor Enum.min/1 and Enum.max/1 2013-08-07 08:14:23 +02:00
Guillermo Iguaran 4906f22479 Add Enum.reduce/2 that sets initial value of acc to the first element of collection
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>

Conflicts:
	lib/elixir/lib/enum.ex
2013-08-07 08:04:06 +02:00
José Valim 35282e1d35 Fix a bug in insert_at adding items to the wrong location with negative indexes 2013-08-07 07:56:03 +02:00
José Valim 2bf693292b Merge pull request #1558 from sasa1977/list_replace_at
List.replace_at
2013-08-06 22:45:58 -07:00
sasa1977 7c8b183513 List.replace_at 2013-08-06 23:34:39 +02:00
José Valim d5cfe56c67 Improve Enum.shuffle and String.replace docs 2013-08-06 21:35:04 +02:00
José Valim d74a19c887 Merge pull request #1557 from meh/string-replace
Add regex pattern support to String.replace
2013-08-06 12:15:06 -07:00
meh 09f5a84a13 Add regex pattern support to String.replace 2013-08-06 20:27:02 +02:00
José Valim 93eea5dd6d Reduce the ammount of module binaries being passing around 2013-08-06 17:21:40 +02:00
José Valim d8393f97ca Remove deprecated code 2013-08-06 17:21:40 +02:00
José Valim 905a526b18 Merge pull request #1556 from derekmcloughlin/issue-1555
Fix for #1555
2013-08-06 06:53:07 -07:00
José Valim a8cee02558 Fix regression on do/end blocks, closes #1551 2013-08-06 15:44:52 +02:00
Derek McLoughlin 08ed1f087a Fix for #1555 2013-08-06 14:28:23 +01:00
José Valim e591a0f8e8 Merge pull request #1552 from ericmj/iex-erlang-helpers
IEx helpers r and c can handle erlang sources
2013-08-06 05:34:44 -07:00
José Valim 7a7d1c54e7 Merge pull request #1554 from tristanoneil/mix-clean-all
Pass clean arguments to deps.clean
2013-08-05 14:14:09 -07:00
Tristan O'Neil 74c3ed0cfb Test mix clean with the --all flag. 2013-08-05 17:04:19 -04:00
Tristan O'Neil 5d1a6c2fc0 Original clean arguments need to be passed to deps.clean. 2013-08-05 16:15:45 -04:00
Eric Meadows-Jonsson fe65f5f1f4 IEx helpers r and c can handle erlang sources 2013-08-05 20:11:39 +02:00
José Valim b948f3b3dd Make it explicit Regex.run returns all captures in a match 2013-08-06 01:15:01 +08:00
José Valim aad42261cc Document dash to underscore in OptionParser 2013-08-04 23:17:35 +02:00
José Valim bd21251659 Merge pull request #1546 from fishcakez/supervisor-shutdown
Fix default shutdown of child supervisors to infinity
2013-08-04 11:53:06 -07:00
James Fish 5bbaeee149 Fix default shutdown of child supervisors to infinity
A supervisor should wait for a child supervisor to shutdown its
children, and not kill it. By killing a supervisor you may prevent the
correct termination of processes further down the supervision tree.
2013-08-04 19:15:34 +01:00
José Valim 206fa3f9ed Update CHANGELOG 2013-08-04 19:24:21 +02:00
José Valim 6470a36e68 Ensure watched extensions trigger full recompilation on change 2013-08-04 19:22:15 +02:00
José Valim 69754fa6b5 Raise on &0 inside capture 2013-08-04 19:04:22 +02:00
Yurii Rashkovskii 591c576307 Minor mix compile.elixir task documentation fix 2013-08-04 09:48:34 -07:00
José Valim 8eabc189b7 Merge pull request #1545 from yrashk/deps-compile-quiet
Ensure even a greater level of quiteness when --quiet is used in deps
2013-08-04 09:38:35 -07:00
Yurii Rashkovskii bb98f6a40a Ensure even a greater level of quiteness when --quiet is used in deps
Before (on deps.get --quiet):

==> project
* Compiling project_p1
==> project_p2
* Compiling project_p1
* Compiling project
* Compiling project
==> project_p3
* Compiling project_p1
* Compiling project
* Compiling project
==> project_p4
* Compiling project_p1
* Compiling project
* Compiling project
==> project_p5
* Compiling project_p1
* Compiling project
* Compiling project

Now:

(empty)
2013-08-04 09:34:56 -07:00
José Valim 9861d8c55c Only touch look if a dependency possibly changed 2013-08-04 17:51:49 +02:00
José Valim eb3d720556 Use a specific .compile.deps flag for tracking deps changes 2013-08-04 16:32:42 +02:00
José Valim 17eb7320ac Merge pull request #1542 from thmzlt/iex-h-helper-string
Allow for strings in iex h helper
2013-08-04 00:46:34 -07:00
Thomaz Leite 3d147b1f4e Use list argument in h helper for strings
This will check the function name in three different places (__MODULE__, Kernel, and Kernel.SpecialForms), allowing one to ask the docs for the new operator &.
2013-08-03 14:41:15 -07:00
Thomaz Leite fb05c7e5a7 Rephrase h helper doc for string arguments 2013-08-03 14:21:04 -07:00
Thomaz Leite f55f4d4a4e Add h helper doc for string arguments 2013-08-03 13:56:30 -07:00
Thomaz Leite 0f0efbc13c Allow for strings in iex h helper 2013-08-03 13:44:38 -07:00
José Valim 99394d2a75 Mention capture operator in CHANGELOG 2013-08-03 21:07:23 +02:00
José Valim bac39e4a8d Bump to 0.10.2-dev 2013-08-03 18:20:15 +02:00
José Valim 0523789c33 Release v0.10.1 2013-08-03 18:08:26 +02:00
José Valim 6d62983581 Capture now works with macros, closes #1534 2013-08-03 16:15:29 +02:00
José Valim 2e8cb67880 Remove weird chars from bat, closes #1539 2013-08-03 15:55:13 +02:00
José Valim 37a1a232b3 Merge pull request #1532 from derekmcloughlin/issue-1531
Issue 1531 - fix issue with slow batch files.
2013-08-03 06:43:35 -07:00
Derek McLoughlin b92099f5bd Fix issue with enableddelayedexpansion 2013-08-02 12:26:56 +01:00
Eric Meadows-Jonsson 15236c34e3 Fix String.contains? not raising on non-string arg 2013-08-02 13:08:14 +02:00
José Valim 34b6e17a1b Fix shadowing warning in case with function, closes #1524 2013-08-02 13:04:03 +02:00
Derek McLoughlin c374b357bb Issue #1531 - fix slow running of batch file in Windows 2013-08-02 11:20:58 +01:00
José Valim 31818d677e Stop checking for matches once we reach a fun 2013-08-02 10:35:52 +02:00
José Valim c6f9287ce7 Only raise on local macro conflicts if it was previously defined 2013-08-02 09:53:30 +02:00
José Valim 09b1216c54 Merge pull request #1529 from jwarwick/macro_typos
Fixed doc typos in Macro
2013-08-01 22:56:04 -07:00
Eric Meadows-Jonsson f7f486c7d1 Fix String.starts_with? and String.ends_with? non-raising edge case 2013-08-01 23:57:02 +02:00
José Valim 7df5d157af Remove space excess in compile.elixir docs 2013-08-01 22:42:08 +02:00
José Valim d7ff228b14 Update docs for Mix.project/0 2013-08-01 22:35:51 +02:00
John Warwick 934b47f2d3 Fixed doc typos in Macro 2013-08-01 14:52:53 -04:00
José Valim ec02c65af5 Fix issue with nested binaries in Macro.to_string/1, closes #1522 2013-08-01 20:48:22 +02:00
José Valim ff0304cac7 Merge pull request #1528 from jwarwick/regex_typos
Fixed doc typos in Regex
2013-08-01 11:37:01 -07:00
John Warwick 660740bee0 Fixed doc typos in Regex 2013-08-01 14:36:08 -04:00
José Valim 4068160a76 Normalize and allow captures in Regex.scan/3, closes #1526 2013-08-01 19:52:32 +02:00
José Valim 2a672faca1 Merge pull request #1521 from ericmj/stream-fixes
Fix Stream bugs and add more composing tests
2013-07-31 23:15:03 -07:00
Eric Meadows-Jonsson d454aaf155 Fix Stream bugs and add more composing tests
Fix Stream.take_while looping infinitely when given lazy stream.
Fix Stream.take and Stream.take_while calling fun one extra time.
Add more tests where Stream functions are given streams.
2013-08-01 01:20:59 +02:00
José Valim a154a9e99a Allow &1.foo syntax inside capture 2013-07-31 16:03:52 +02:00
José Valim 2ed1c373e2 Fix Macro.to_string with & 2013-07-31 15:52:53 +02:00
José Valim 46fae17b7e Allow &() as a general capture mechanism 2013-07-31 14:53:22 +02:00
José Valim fe9ae95bdc Merge pull request #1519 from jwarwick/file_typos
Fixing doc typos in File
2013-07-31 03:58:31 -07:00
José Valim 6a68ca5eef Deprecate function/1 and function/3 2013-07-31 12:58:07 +02:00
José Valim b5428adda6 Add &local/arity and &Remote.fun/arity 2013-07-31 12:58:07 +02:00
John Warwick 05a20e3bc7 Fixing doc typos in File 2013-07-30 14:08:03 -04:00
José Valim 11093b7e27 Merge pull request #1517 from ToJans/master
Fixed problem with elixir.bat removing exclamation marks from command line
2013-07-30 05:42:10 -07:00
Tom Janssens 6d57915496 Fixed problem with elixir.bat removing exclamation marks from command line 2013-07-30 12:05:51 +02:00
Kevin Liddle 55623c1dcb always remove unmatched beam files when compiling 2013-07-30 11:50:02 +02:00
José Valim 2e348288a1 Remove old mix artifacts without a need for force or changes 2013-07-30 11:47:29 +02:00
José Valim fb5c67bf99 Update CONTRIBUTING.md
Feature requests should be discussed over the core mailing list.
2013-07-30 09:03:41 +02:00
José Valim d63d2c93e5 Get rid of buggy System.halt/2
Closes #1515, thanks to @AtKaaZ for debugging and pinpointing
this function as the root cause for output issues on Windows.
2013-07-30 00:50:10 +02:00
José Valim abb2ada8b1 Make & an unary operator 2013-07-29 14:08:33 +02:00
José Valim 5dfb2c33e2 Merge pull request #1514 from jwarwick/enum_docs
Fixed typos in Enum docs
2013-07-28 23:35:39 -07:00
José Valim 1c2f82362e Merge pull request #1509 from jwarwick/issue_1506
Return values for Code.eval_string and Code.eval_quoted
2013-07-28 23:34:43 -07:00
José Valim 73645f05f0 Merge pull request #1513 from ericmj/enum-at-docs
Fix Enum.at docs
2013-07-28 23:34:29 -07:00
John Warwick cda0c6c3d6 Fixed typos in Enum docs 2013-07-28 21:03:35 -04:00
John Warwick 933d4b88ee Using 'binding' to refer to a keyword list of variable bindings 2013-07-28 20:34:51 -04:00
Eric Meadows-Jonsson 26efdaf931 Fix Enum.at docs 2013-07-29 00:18:24 +02:00
José Valim e6b075f52f Remove another invalid syntax sample 2013-07-28 23:25:05 +02:00
José Valim a453ea1afa Fix usage of char_list and fix the build 2013-07-28 22:57:46 +02:00
José Valim 61fb890d01 Merge pull request #1511 from guilleiguaran/with_index
Move definition of with_index below
2013-07-28 13:50:32 -07:00
Guillermo Iguaran d1e26eafaa Move definition of with_index below
Right now is defined in the middle of two definitions of max function.
2013-07-28 15:26:48 -05:00
José Valim 7bb592cccf Merge pull request #1507 from AtKaaZ/reborntest1
revive a non-functioning test
2013-07-28 10:55:04 -07:00
José Valim a24b9c4afc Merge pull request #1510 from ericmj/mix-cmd-win
Dont escape " in shell commands on windows
2013-07-28 10:54:33 -07:00
Eric Meadows-Jonsson 4c7ee1e65c Dont escape " in shell commands on windows 2013-07-28 18:29:17 +02:00
José Valim f2addbb7b8 Improve docs for Enum.all?/2, closes #1508 2013-07-28 17:22:36 +02:00
José Valim b5e57970fe Remove undocumented Record.new(tuple) feature 2013-07-28 17:19:24 +02:00
AtKaaZ eee3d3f757 revive a non-functioning test
tested on Windows
2013-07-28 18:13:41 +03:00
John Warwick 835403af54 Fixed additional doc typos in Code module 2013-07-27 23:12:40 -04:00
John Warwick 9200327f77 Fixed Issue #1506, added description of return values for Code.eval_string and Code.eval_quoted 2013-07-27 22:59:03 -04:00
José Valim 12f6a9aa86 Fix some invalid error messages from Mix 2013-07-27 12:21:50 +02:00
José Valim fe340feb06 test_helper files still need to be required upfront
Since they have to be required upfront, it makes no sense to have
a Code.require_file("test_helper.exs", __DIR__) in Mix projects
since the files can't run in isolation anyway.
2013-07-27 12:15:00 +02:00
José Valim 1e90076738 Make it explicit that some tasks require a project to be defined 2013-07-27 10:43:41 +02:00
José Valim 456b84616c mix run now properly sets System.argv 2013-07-27 10:17:02 +02:00
José Valim ac666a9731 Improve docs for EEx.AssignsEngine, closes #1499 2013-07-27 09:55:24 +02:00
José Valim cca4ac9052 Merge pull request #1502 from jwarwick/code_formatting
Fixed docs typos and added code markers around functions in docs
2013-07-26 13:52:58 -07:00
John Warwick a700e23d90 Fixed docs typos and added markers around functions in docs 2013-07-26 16:28:55 -04:00
José Valim eee8880930 Merge pull request #1501 from AtKaaZ/mixenvWindows1
fixing a test to also work in Windows
2013-07-26 13:11:56 -07:00
AtKaaZ 004bfc43a4 fixing a test to also work in Windows
used to do this:
'MIX_ENV' is not recognized as an internal or external command,
operable program or batch file.
Tested to work on both Windows and Linux.
However the following is seen on console:
Mix requires the compile path "ebin" to be inside a directory with the
 same name as the application name :no_mixfile,
got "s:/workspace.elixir.2013/elixir/lib/mix/tmp/Mix.CLITest/test env/ebin"

quick test with `make test_mix`
2013-07-26 22:02:29 +03:00
José Valim 318716afcd Return :ok from FileCase callbacks 2013-07-26 18:37:24 +02:00
José Valim ca6b8d356e No longer clean or unlock deps unless --all is given 2013-07-26 17:25:53 +02:00
José Valim 178b9370d0 Ensure path dependencies are compiled on mix deps.get 2013-07-26 17:18:08 +02:00
José Valim e49baddffa Update @on_definition docs 2013-07-26 08:40:12 +02:00
José Valim a1c7aa1c7a Merge pull request #1497 from pragdave/master
Trivial wording changes in the Changelog
2013-07-25 23:24:09 -07:00
Dave Thomas 973df580a1 Trivial wording changes in the Changelog 2013-07-25 20:41:38 -05:00
José Valim 91d5ee1d8f Clean up Elixir path related tests 2013-07-25 21:49:54 +02:00
José Valim 5116e28aac Avoid echo with no args on windows 2013-07-25 19:59:54 +02:00
José Valim 751ebc8c63 Revert "Update ExUnit.Assertions docs, closes #1493"
This reverts commit 655a3c7d70.
2013-07-25 15:36:20 +02:00
José Valim 7f18e1426e Only set preferred cli env if MIX_ENV is nil 2013-07-25 14:55:46 +02:00
José Valim a013b39b8f Do not double escape strings inside interpolation, closes #1494 2013-07-25 14:34:13 +02:00
José Valim 4e4c9f13a0 Properly treat :<<>>, :[] and :{} as atoms 2013-07-25 14:13:12 +02:00
José Valim 3ebd7bbdb7 Allow defmacrocallback to be specified in Behaviour 2013-07-25 13:05:26 +02:00
José Valim 7672e7740d Update CHANGELOG 2013-07-25 12:39:57 +02:00
José Valim d77d013b32 Support --color option in ExUnit
The option can also be passed via mix, closes #1364
2013-07-25 12:31:18 +02:00
José Valim 1db491eb49 Add mix cmd 2013-07-25 11:32:21 +02:00
José Valim 4d70494474 Use %B on docs 2013-07-25 11:06:23 +02:00
José Valim 954310ae2c Get rid of Mix.Tasks.IEx 2013-07-25 11:02:29 +02:00
José Valim f86c99c91a Add umbrella: true to supported deps options 2013-07-25 11:00:50 +02:00
José Valim 408ef28da2 Group ExUnit.start calls into one, closes #1489 2013-07-25 10:38:28 +02:00
José Valim 151e49ef49 Ensure environment is set before any task is executed, closes #1490 2013-07-25 10:34:53 +02:00
José Valim 655a3c7d70 Update ExUnit.Assertions docs, closes #1493 2013-07-25 10:21:58 +02:00
José Valim b52c3c7b52 Merge pull request #1492 from jwarwick/kernel_typo
Fixed Kernel.match? example. Added formatting to Kernel docs.
2013-07-24 23:30:58 -07:00
John Warwick 38ea253d02 Fixed Kernel.match? example. Added formatting to Kernel docs. 2013-07-24 23:23:22 -04:00
José Valim 182e11f059 Update new app docs 2013-07-24 23:48:39 +02:00
José Valim fe67ecd2b6 Mix retriever should use the .app set at compile_path 2013-07-24 23:38:00 +02:00
José Valim b628956310 Update deps docs 2013-07-24 21:22:59 +02:00
José Valim 0fdc15ccdf Merge pull request #1487 from jwarwick/typos
Fixed docs typos in recent commits
2013-07-24 08:52:43 -07:00
John Warwick 514be83b63 Fixed docs typos in recent commits 2013-07-24 11:44:29 -04:00
José Valim 867a3ab4d2 A test case changing compiler options should not be async 2013-07-24 17:32:09 +02:00
José Valim 888155ee08 Do not re-use stacktraces on IEx, closes #1486
This commit also cleans up error normalization throughout Elixir.
2013-07-24 16:43:54 +02:00
José Valim 540d3151b4 Update CHANGELOG 2013-07-23 23:40:10 +02:00
José Valim 089f82205f Clean up IEx helpers to not contain spurious functions 2013-07-23 23:37:37 +02:00
José Valim 9cd6b528c9 Get rid of unmatched docs warnings 2013-07-23 23:37:37 +02:00
José Valim eff2fecf5a binary_size => byte_size 2013-07-23 23:37:37 +02:00
José Valim f944694b2e Rename Protocol.UndefinedError keys 2013-07-23 23:37:37 +02:00
José Valim 36f50af6e0 Give more information on invalid versions 2013-07-23 23:37:37 +02:00
José Valim 155398c445 Warn for unmatched docs 2013-07-23 23:37:36 +02:00
José Valim 73506d2051 Merge pull request #1485 from jwarwick/test_typo
Fixed typos in Mix.Tasks.Test docs
2013-07-23 13:51:08 -07:00
John Warwick 477fdc7053 Fixed typos in Mix.Tasks.Test docs 2013-07-23 16:49:11 -04:00
José Valim 424bbbaf74 Fix the build 2013-07-23 14:07:57 +02:00
José Valim fdb8e8d137 Improve inspect for functions 2013-07-23 13:11:03 +02:00
José Valim c4b656eba6 Do not show internal compiler output in stacktraces 2013-07-23 13:11:03 +02:00
José Valim b6e0e2fdba Merge pull request #1481 from meh/protocol
Split @protocol into @protocol and @for and add __impl__(:for)
2013-07-23 04:10:47 -07:00
meh ba967a0834 Split @protocol into @protocol and @for and add __impl__(:for) 2013-07-23 11:41:08 +02:00
José Valim 587bb9a075 Merge pull request #1477 from jwarwick/genfsm_docs
Updated GenFSM
2013-07-22 23:43:51 -07:00
José Valim 7c789afcb5 Merge pull request #1478 from ericmj/mix-cmd-quotes
Wrap Mix.Shell.cmd with " instead of '
2013-07-22 23:43:36 -07:00
Eric Meadows-Jonsson e922847115 Wrap Mix.Shell.cmd with " instead of '
Single quotes can't be escaped in the shell so commands containing '
will not work. Instead wrap the command in double quotes that can be
escaped and escape " in the command.
2013-07-23 01:02:30 +02:00
John Warwick 407b0519cf Updated GenFSM example code 2013-07-22 17:48:55 -04:00
John Warwick 8f686815b3 Fixed GenFSM example code and typos. 2013-07-22 12:00:04 -04:00
José Valim 214bfbbfdf mix deps.update no longer update all dependencies unless --all is given 2013-07-22 13:22:12 +02:00
José Valim e14d689e0e Normalize windows home, closes #1462 2013-07-22 13:09:13 +02:00
José Valim c294a7f507 Print detailed information on conversion errors 2013-07-22 13:02:01 +02:00
José Valim de7cf1c093 Merge pull request #1476 from meh/string-list
Add String.to_char_list and String.from_char_list
2013-07-22 03:46:09 -07:00
José Valim 989fc155de Check ExUnit.Server is registered before adding cases, closes #1472 2013-07-22 12:45:10 +02:00
meh 69386027ac Add String.to_char_list and String.from_char_list 2013-07-22 12:41:50 +02:00
Eric Meadows-Jonsson 157173d000 Add record type Record.t 2013-07-22 12:38:15 +02:00
José Valim f1e8c05fef Allow coverage tool to be pluggable 2013-07-22 11:08:57 +02:00
José Valim bcf0c26224 Update CHANGELOG 2013-07-22 09:49:29 +02:00
José Valim 48c67c131b Include test time on trace results 2013-07-22 09:41:32 +02:00
José Valim 7f147a2b89 Clean up make clean 2013-07-22 09:11:25 +02:00
José Valim dc7490eb46 Tidy up IEx docs and tests 2013-07-22 08:40:21 +02:00
José Valim 14f8b20084 Merge pull request #1473 from pragdave/iex-cls
Add the clear helper, which cleared the screen. Also introduced the ...
2013-07-21 23:23:20 -07:00
José Valim c816380a4f Merge pull request #1474 from jwarwick/mix_typos
Fixed typos in Mix.Tasks.Compile.Erlang and Mix.Tasks.Test
2013-07-21 23:16:45 -07:00
John Warwick d4e2502c70 Fixed typos in Mix.Tasks.Compile.Erlang and Mix.Tasks.Test 2013-07-21 21:47:04 -04:00
Dave Thomas 02f85d32a9 Forgot the changelog 2013-07-21 17:19:41 -05:00
Dave Thomas fb18f51b95 Add the clear helper, which cleared the screen. Also introduced the convention that if a helper returns dont_display_result/0, then we skip the inspecting of the result. Changed the introspection helpers h, s, and t to use this. 2013-07-21 17:13:21 -05:00
José Valim 0cd1be1439 Update mix test docs 2013-07-22 00:08:24 +02:00
José Valim 37589f99b2 Improve docs for ExUnit 2013-07-21 23:58:56 +02:00
José Valim 4bc2bf3a6d Ensure sigils with no interpolation are fully expanded at compile time, closes #1471 2013-07-21 22:21:04 +02:00
José Valim 1b79a1782c Remove duplicate in erlang, leex and yecc compilers 2013-07-21 22:08:32 +02:00
José Valim 4aab8656d8 Only clean up manifest for files that were actually removed, closes #1440 2013-07-21 18:01:04 +02:00
José Valim 391276762a Merge pull request #1470 from ToJans/master
Updated windows elixir.bat to use -pa instead of -env ERL_LIBS
2013-07-21 02:53:11 -07:00
Tom Janssens ce86541135 Updated windows elixir.bat to use -pa instead of -env ERL_LIBS
Fixes #1469
2013-07-21 11:44:29 +02:00
José Valim 4fcc14041c Merge pull request #1460 from guilleiguaran/string_reverse
Add String.reverse to reverse the given string.
2013-07-21 01:04:58 -07:00
Guillermo Iguaran 0bcf0571d9 Add String.reverse to reverse the given string. 2013-07-21 02:29:15 -05:00
José Valim fffafb84bc Merge pull request #1468 from jwarwick/mix_typos
Fixed doc typos in Mix.Projects and Mix.Utils
2013-07-21 00:17:47 -07:00
John Warwick 7b1f194b8d Fixed doc typos in Mix.Projects and Mix.Utils 2013-07-20 23:46:17 -04:00
José Valim a517a1a72a Merge pull request #1467 from guilleiguaran/enum_shuffle2
Add Enum.shuffle
2013-07-20 13:32:26 -07:00
Guillermo Iguaran 6d03230aaf Add Enum.shuffle
Enum.shuffle returns a list of collection elements shuffled.
2013-07-20 14:46:34 -05:00
José Valim 83d0581370 Merge pull request #1466 from ericmj/mix-git-force-https
Add MIX_GIT_FORCE_HTTPS system environment var
2013-07-20 12:23:01 -07:00
Eric Meadows-Jonsson 89e7fe4039 Add MIX_GIT_FORCE_HTTPS system environment var 2013-07-20 21:11:59 +02:00
Eric Meadows-Jonsson e31294863e Merge pull request #1465 from ericmj/mix-rebar-script
Dont raise on erroneous rebar.config.script
2013-07-20 08:56:09 -07:00
Eric Meadows-Jonsson 3368578316 Dont raise on erroneous rebar.config.script 2013-07-20 17:55:24 +02:00
José Valim 5aab6da4f9 Merge pull request #1464 from ericmj/mix-dep-req-regex
Remove deprecation warn on regex dep requirement
2013-07-20 08:35:08 -07:00
José Valim fecace0a77 Update dyalizer task and fix some of the reports 2013-07-20 17:21:21 +02:00
Eric Meadows-Jonsson c5ca8f31ea Remove deprecation warn on regex dep requirement 2013-07-20 17:04:03 +02:00
José Valim 7ed1f2cbef EEx exprs should receive binaries and use Behaviour 2013-07-20 16:35:32 +02:00
José Valim 40ee57534a Update typespecs for Enum and Inspect.Algebra 2013-07-20 15:20:24 +02:00
José Valim 843343c7ad The manifest variable is already set 2013-07-20 14:11:21 +02:00
José Valim 0ac743e6aa Update CHANGELOG 2013-07-20 13:56:14 +02:00
José Valim 268443edb5 Reverse behaviour docs at compilation time 2013-07-20 13:56:08 +02:00
José Valim b878ec25bb Merge pull request #1458 from guilleiguaran/list_keysort
Add List.keysort
2013-07-20 04:28:13 -07:00
José Valim 90d404334e Merge pull request #1461 from madlep/fix_backwards_behaviour_docs
reverse @__behaviour_docs__
2013-07-20 04:28:03 -07:00
Julian Doherty (madlep) 7ab19565da reverse @__behaviour_docs__
These are top accumlated as module attributes, so are therefore stored
in reverse order to how they were added. When we get them back out,
reverse them again so they're back in the right order.

This is used by ex_doc, and callbacks are listed backwards otherwise
2013-07-20 21:04:20 +10:00
José Valim 7fc787bdcc Improve docs for Mix 2013-07-20 13:01:58 +02:00
José Valim 1f76fb5246 Fix a bug on OptionParser where missing options appeared on the final result 2013-07-20 13:01:44 +02:00
Guillermo Iguaran 4e20aee0d1 Add List.keysort
Receives a list of tuples and sorts the items at position `pos` of the tuples
2013-07-20 01:55:42 -05:00
José Valim 4df054c1ad Record aliases separatedly from remotes 2013-07-19 23:10:03 +02:00
José Valim e32593aa30 Merge branch 'jv-watch-manifest'
This brings smart compilation to mix compile, where
we only recompile files that changed and their dependencies.
2013-07-19 22:55:10 +02:00
José Valim 21d9690faf Simplify translate_apply 2013-07-19 22:54:23 +02:00
José Valim 5da9c83588 Ensure no duplicate keys in the compile.elixir manifest 2013-07-19 22:16:32 +02:00
José Valim 8be887bff7 Also record aliases as dependencies 2013-07-19 22:16:32 +02:00
José Valim 1ff2326755 Just preload files if something is stale 2013-07-19 22:16:32 +02:00
José Valim fa0c9dfc7c Put old beam files back if compilation fails 2013-07-19 22:16:31 +02:00
José Valim 16d3a84637 Improve ManifestCompiler to track more dependencies 2013-07-19 22:16:31 +02:00
José Valim cec35782ef Initial work on ManifestCompiler that tracks dependencies in the manifest for quick compilation 2013-07-19 22:14:04 +02:00
José Valim 2e3c4ee1c9 Support each_module callback on ParallelCompiler 2013-07-19 22:10:30 +02:00
José Valim af454d3f90 Support for multiple callbacks in parallel compiler 2013-07-19 22:10:30 +02:00
José Valim 4eeef37495 Remove deprecations pre 0.10.0 2013-07-19 22:10:30 +02:00
José Valim afbfbab6a6 Watch the target directory on compilation 2013-07-19 22:10:30 +02:00
José Valim c8f1123ee9 Merge pull request #1454 from jwarwick/issue_1448
Issue #1448, Mix.Tasks.Compiler.*.manifests returns complete path
2013-07-19 13:09:05 -07:00
John Warwick 44ddd520fb Issue #1448, Mix.Tasks.Compiler.*.manifests returns complete path 2013-07-19 15:54:50 -04:00
José Valim 3ecc226d7a Merge pull request #1453 from jwarwick/escript_docs
Fixed doc typos in Mix.Tasks.Escriptize
2013-07-19 10:51:31 -07:00
John Warwick 9a51189dbd Fixed doc typos in Mix.Tasks.Escriptize 2013-07-19 13:48:44 -04:00
José Valim 94d3f187ad Slight clean up on mix tests
We don't need to test all scenarios because guaranteeing that
a failure in one compiler is enough to abort the others is enough
for an integration test.
2013-07-19 18:52:31 +02:00
José Valim 2ebd1cf5ea Merge pull request #1449 from jwarwick/issue_1441
Possible fix for Issue #1441
2013-07-19 09:35:24 -07:00
José Valim 0c7d12f0da Document mix loadpaths and app.start options 2013-07-19 16:46:53 +02:00
José Valim 8f4bc95b2d Merge pull request #1452 from meh/mix-version
Move the Elixir version requirement check to a better place
2013-07-19 07:10:08 -07:00
meh e7354614c1 Move the Elixir version requirement check to a better place 2013-07-19 15:57:29 +02:00
José Valim 2a57338f06 Always load paths on app.start, even with --no-compile 2013-07-19 15:17:40 +02:00
John Warwick f8bf2824c8 Updated tests to verify exceptions raised 2013-07-19 08:07:30 -04:00
John Warwick 1c914621a4 Upated test cases to support compilation halting on first failed compiler. Updated existing tests to handle CompileError being thrown on failure.
Updated compile tasks to raise exceptions after compiling all files
2013-07-19 08:06:34 -04:00
José Valim c78e47a42a Merge pull request #1450 from meh/mix-elixir
Make Mix.SystemVersionError message more meaningful
2013-07-18 22:39:53 -07:00
meh 233319df89 Make Mix.SystemVersionError message more meaningful 2013-07-19 07:38:47 +02:00
José Valim 669a8867bd Merge pull request #1447 from jwarwick/compiler_typos
Fixed typos in Kernel.ParallelCompiler docs
2013-07-17 23:03:20 -07:00
John Warwick 11c2854a85 Fixed typos in Kernel.ParallelCompiler docs 2013-07-17 23:17:43 -04:00
José Valim 40b8e8b4ac Merge pull request #1444 from jwarwick/doc_typos
Fixed doc typos in Mix.deps and IO
2013-07-17 06:08:47 -07:00
John Warwick 229b1919d3 Fixed doc typos and formatting in IO 2013-07-17 07:53:06 -04:00
John Warwick 83ff73806c Fixed minor typo in Mix.deps doc 2013-07-17 07:43:37 -04:00
José Valim 01e2e974b8 Reduce cases where IEx parser can get stuck, closes #1443 2013-07-17 13:02:11 +02:00
José Valim 498e8038fc Only delete beam files with no source 2013-07-17 11:58:46 +02:00
José Valim 79222a45d1 Merge pull request #1435 from ericmj/mix-force-path-deps
Check mix path deps to see if we need recompile project
2013-07-17 02:18:21 -07:00
Eric Meadows-Jonsson 6e3a64a1d0 Check path deps to see if we should recompile
'mix compile' will check the manifest files' mtime of all mix path
dependencies - if they are newer then the local .compile.elixir file
we need to recompile.
2013-07-17 11:14:15 +02:00
José Valim 37992017ea Improve docs for Mix.Deps 2013-07-17 10:01:06 +02:00
José Valim 267a68c399 Remove test at the wrong location 2013-07-17 09:05:20 +02:00
José Valim a46b78a353 Ensure __impl__ follows the same convention as other dynamically generated info functions 2013-07-17 09:00:27 +02:00
José Valim e93dfe6de2 Merge pull request #1439 from meh/protocol
Add @protocol attribute to defimpl
2013-07-16 23:50:28 -07:00
meh 6031c27aa1 Add @protocol attribute to defimpl 2013-07-17 08:42:48 +02:00
José Valim 83ac67565b Merge pull request #1438 from jwarwick/mix_tasks
Fixed doc typos in Mix.Tasks.Compile.*
2013-07-16 23:11:55 -07:00
John Warwick 660f7d0dd7 Fixed doc typos in Mix.Tasks.Compile.* 2013-07-16 22:19:09 -04:00
John Warwick 6160fd2669 Fixed typos and formatting issues in Mix.Tasks.CompileElixir 2013-07-16 16:10:23 -04:00
José Valim 2c457d47f2 No need to define a new compilation error 2013-07-16 15:43:06 +02:00
José Valim 121d16dc87 Update CHANGELOG 2013-07-16 12:57:22 +02:00
José Valim 6034738d48 Document that on_definition can only be a function, closes #1431 2013-07-16 12:56:29 +02:00
José Valim 740b724f07 Merge pull request #1433 from ericmj/mix-deps-check
Rename mix flag --no-check -> --no-deps-check, document it and use it everywhere
2013-07-16 03:55:30 -07:00
Eric Meadows-Jonsson 9304aae28a If no deps.check is done use all available deps
Add all available deps to loadpaths even if they didn't pass the deps
check.
2013-07-16 12:41:26 +02:00
Eric Meadows-Jonsson a26a55876d Rename mix flag --no-check -> --no-deps-check
Document it and check the flag everytime another task call deps.check.
Do a deps.check after deps.update.
2013-07-16 12:39:54 +02:00
Eric Meadows-Jonsson b45620d85d Remove hard-coded project defaults 2013-07-16 12:37:38 +02:00
José Valim 9c86fb8df1 Update CHANGELOG 2013-07-16 11:56:41 +02:00
José Valim 0d5d955ada Clean up GenFSM docs 2013-07-16 11:53:52 +02:00
Torben Hoffmann cfd546b42b Created 1st version of GenFsm 2013-07-16 11:53:51 +02:00
José Valim 716890be3c Merge pull request #1422 from ericmj/mix-manifest
Add manifests for yecc, leex and erlang compilers
2013-07-16 02:45:17 -07:00
José Valim 55ca2c199e Merge pull request #1425 from tonini/mix-help-default-task-description
Mix.Tasks.Help.run([]) outputs information about current default task
2013-07-16 02:42:56 -07:00
José Valim 095ac686cc Add expansion callsite to the end of the stacktrace, closes #1426 2013-07-16 11:40:18 +02:00
José Valim 67e9cb5b05 Improve use of operator has no effect messages, closes #1431 2013-07-16 11:23:55 +02:00
José Valim 8d3c78bd40 Merge pull request #1427 from jwarwick/doc_typos
Fixed doc typos in Kernel.Special_Forms
2013-07-16 02:04:45 -07:00
José Valim 0675ecbbe4 Merge pull request #1429 from jwarwick/ex_unit_docs
Fixed doc typos in ExUnit
2013-07-16 02:04:19 -07:00
José Valim 392697d64b Remove unused test_helper project configuration from mix 2013-07-16 11:00:36 +02:00
John Warwick 3f8a8c547e Fixed docs as recommended by @alco 2013-07-15 17:15:36 -04:00
John Warwick 9f8601d5ac Fixed doc typos in ExUnit.DocTest 2013-07-15 14:58:57 -04:00
John Warwick c29dfb01ed Fixed doc typos in ExUnit.Assertions 2013-07-15 14:53:20 -04:00
John Warwick e3ea93f8de Fixed doc typos in ExUnit 2013-07-15 14:38:48 -04:00
John Warwick 888d7c285b Fixed doc typos in Kernel.Special_Forms 2013-07-15 11:54:29 -04:00
José Valim dbf1e25e15 Bump to 0.10.1-dev 2013-07-15 11:34:00 +02:00
José Valim 83591b9d76 Release v0.10.0 2013-07-15 11:29:40 +02:00
Samuel Tonini d88138a8af Mix.Tasks.Help.run([]) outputs information about current default task 2013-07-15 10:45:47 +02:00
José Valim b0bc02ef70 Do not call :io.columns if width is set 2013-07-13 22:42:31 +02:00
José Valim a7ffebc915 Tidy up CHANGELOG 2013-07-13 22:42:15 +02:00
Eric Meadows-Jonsson 7ec5ec80b8 Use all compilers manifests when cleaning
A compiler can expose a `manifest/0` function providing the filename of
its manifest filename.
A manifest file should contain a list of files that was generated by the
compiler. They should be absolute paths relative to the project root and
be separated by newlines.
2013-07-13 11:27:48 +02:00
Eric Meadows-Jonsson 071988e8b1 Check .compile.erlang mtime when compiling elixir
Consider erlang files when checking if elixir files needs to be
recompiled.
2013-07-12 17:09:25 +02:00
Eric Meadows-Jonsson e820fb27b1 Add manifest files to erlang compiler
Makes sure that the correct generated files are deleted and
cleaned even when the source files are moved or renamed.
2013-07-12 17:04:04 +02:00
Eric Meadows-Jonsson f4b329a796 Add manifest files to yecc and leex compilers
Makes sure that the correct generated files are deleted and cleaned
even when the source files are moved or renamed.
2013-07-12 17:04:04 +02:00
José Valim 40653e433b Deprecate mix run EXPR in favor of mix run -e EXPR 2013-07-12 12:53:10 +02:00
José Valim c0b007af9a Never allow a comprehension to behave like a guard 2013-07-12 12:46:17 +02:00
José Valim 2fee17e709 Deprecate Kernel.float/1 in favor of explicit conversion 2013-07-12 12:38:08 +02:00
José Valim ebb5500086 Clean up Elixir internal errors
In particular, we reserve SyntaxError for synatx errors (doh)
and for macros that expects a given format at compilation time.

For example, defmodule/2 expects a :do keyword in the second
argument, not providing one is a syntax error.

In other places we impose similar restrictions bue the values
are allowed to be any expression as long as the final result
at compilation time is a given expression, for example:

    when foo in @value

The right side of in can be anything as long it is a list or
a range at compilation time. Those kind of errors are now
CompileError.
2013-07-12 11:44:49 +02:00
José Valim 9754f056dd Improve error messages for in-erlang macros 2013-07-12 10:15:01 +02:00
José Valim e19dbf04ed Update CHANGELOG 2013-07-12 10:04:46 +02:00
José Valim c654159a2b Do not support inline types with defrecord 2013-07-12 10:02:47 +02:00
José Valim 6715c528df Improve error messages from typespecs, closes #1401 2013-07-12 09:53:05 +02:00
José Valim 1c7a37a2b7 Raise proper exception on invalid quoted expression
We also documented valid types in Macro.t

Closes #1411
2013-07-12 09:29:57 +02:00
José Valim 84a0985a39 Deprecate quote binding: in favor of quote bind_quoted: 2013-07-12 08:54:01 +02:00
José Valim 73b9ac7449 Add deprecation warning to __index__ on Records 2013-07-12 08:44:35 +02:00
José Valim 8e659930c0 Merge pull request #1417 from jwarwick/issue_1414
Fix for Issue 1414, Record.__record__(:index) syntax
2013-07-11 23:35:49 -07:00
José Valim 095a91a31f Merge pull request #1419 from brunoro/raw-kwlist
kwlist are inspected as a list of tuples with raw: true
2013-07-11 23:34:55 -07:00
José Valim 7393973d96 Merge pull request #1402 from jwarwick/retriever_typos
Fixed typos in Mix.Deps.Retriever docs
2013-07-11 23:34:21 -07:00
José Valim c3c8c3c71d Update CHANGELOG 2013-07-12 08:33:10 +02:00
José Valim 73eeec2eec Keep head|tail format on splice, closes #1393 2013-07-12 08:32:01 +02:00
Gustavo Brunoro c0bb6d73ae kwlist are inspected as a list of tuples with raw: true 2013-07-11 16:17:00 -03:00
John Warwick 82da86afcb Updated System and Mix to use __record__(:index) syntax 2013-07-11 12:56:42 -04:00
John Warwick c1a7c039fe Updated docs to use __record__(:index) instead of __index__ 2013-07-11 12:52:01 -04:00
John Warwick 5b754c8630 Implemented Record.__record__(:index) syntax, Issue 1414. 2013-07-11 12:47:16 -04:00
José Valim d195429470 Merge pull request #1415 from jwarwick/mix_help_typos
Fixed doc typos in Mix.Tasks.Test
2013-07-11 07:21:36 -07:00
John Warwick 6cc9a4b9f4 Fixed doc typos in Mix.Tasks.Test 2013-07-11 09:57:47 -04:00
José Valim 08aa22616a Merge pull request #1412 from jwarwick/optionparser_docs
Fixed doc typos in OptionParser
2013-07-10 15:24:11 -07:00
John Warwick e4ddfc2118 Fixed doc typos in OptionParser 2013-07-10 18:05:31 -04:00
José Valim 4955eb9deb Start app before perfoming requires 2013-07-10 23:40:29 +02:00
José Valim befa36ce1e Optimize stale handling 2013-07-10 19:32:00 +02:00
José Valim 5e8f9284d2 Clean up mix tests 2013-07-10 16:57:11 +02:00
José Valim c0a6dc4b47 Simplify manifest API and put tests back on green 2013-07-10 16:14:34 +02:00
José Valim 99da29b430 Ensure eval callbacks gets a fresh vars dict 2013-07-10 16:04:36 +02:00
José Valim 3c3c989ef7 Merge pull request #1408 from meh/mix-leex-yecc
Delete files in the manifest instead of removing the loadpath
2013-07-10 07:04:29 -07:00
meh 977ec32476 Delete files in the manifest instead of removing the loadpath 2013-07-10 15:49:58 +02:00
José Valim 27b4ebe34e Merge pull request #1407 from gentoo/Makefile
Makefile improvements
2013-07-10 06:12:58 -07:00
hasufell b0f9d7e9bc MAKE: respect LIBDIR variable
Some distributions do not use plain "lib", rather "lib64"
and "lib32". Allow to control this variable.
2013-07-10 14:54:53 +02:00
hasufell f9afc520ca MAKE: standardize install variables
- convert to autotools-like variables commonly used in build scripts
- respect DESTDIR (used for temporary install locations)
- fix symlink logic to work with DESTDIR
${foo##bar} is posix, see:
http://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html
under "2.6.2 Parameter Expansion"
- quote all variables that can contain whitespace
(make does not take care of that!)
2013-07-10 14:52:48 +02:00
hasufell a6589c62a4 MAKE: allow more verbose build output
people can now run:
  make Q=
to get a the maximum of build output
2013-07-10 14:27:40 +02:00
José Valim 77155c55a6 Merge pull request #1406 from jwarwick/mix_typos
Fixed typos in various Mix modules
2013-07-09 23:26:00 -07:00
John Warwick 776b07f2f0 Fixed typos in Mix.Deps. Updated error strings in test cases 2013-07-09 21:46:03 -04:00
John Warwick 1f00e01679 Fixed doc typos in Mix.Archive 2013-07-09 21:24:46 -04:00
John Warwick a7fa43338e Fixed doc typos in Mix 2013-07-09 21:18:19 -04:00
John Warwick 394ccb3f42 Fixed doc typos in Mix.Tasks.Deps.* 2013-07-09 21:09:39 -04:00
José Valim e7c0a2a3a7 Merge pull request #1403 from devinus/raise-on-unsupported-dictset
Raise an error when Dict/Set doesn't support the target
2013-07-09 14:13:02 -07:00
Devin Torres f808da8ffb Raise an error when Dict/Set doesn't support the target 2013-07-09 16:04:48 -05:00
José Valim c0c8f7f72b Allow -boot start to be overriden on releases 2013-07-09 21:29:35 +02:00
José Valim 290403f7d7 Check the key on register_attribute 2013-07-09 21:24:25 +02:00
José Valim 967ca97455 Continue checking first for a mixfile before a rebarconfig 2013-07-09 21:24:25 +02:00
Yurii Rashkovskii ba00a1ffc6 Erlang release for Elixir should use start.boot instead of elixir.boot 2013-07-09 12:05:12 -07:00
John Warwick a209dc72e3 Fixed typos in Mix.Deps.Retriever docs 2013-07-09 13:23:49 -04:00
José Valim b6ea6b63ed Merge pull request #1400 from ericmj/mix-deps-rebar
All deps of a rebar dep should be handled by rebar
2013-07-09 09:24:30 -07:00
Eric Meadows-Jonsson df4cee50d5 All deps of a rebar dep should be handled by rebar 2013-07-09 17:48:01 +02:00
José Valim 318eaebf3f Add some comments to Mix.Deps.Retriever 2013-07-09 14:30:00 +02:00
José Valim 0a182b4590 Merge pull request #1399 from ericmj/patch-1396
Dont use manager to decide where a dep was defined
2013-07-09 05:25:43 -07:00
José Valim 88ac1f6a7b Improve coverage for lexical scope on try and case 2013-07-09 14:24:57 +02:00
Eric Meadows-Jonsson 621db32ffb Dont use manager to decide where a dep was defined 2013-07-09 13:19:43 +02:00
José Valim 5b2f44d1d7 Ensure --app switch does not halt upcoming ones, closes #1398 2013-07-09 11:24:34 +02:00
José Valim ad3cf4e451 Merge pull request #1395 from devinus/split-empty-string
String.split("") should be [""], %w() should be []
2013-07-08 23:44:01 -07:00
José Valim 209c88ab0b Merge pull request #1397 from jwarwick/typos
Fixed doc typos in Kernel and Record
2013-07-08 23:38:59 -07:00
John Warwick 3c3cf143e7 Fixed legacy typos in Kernel 2013-07-08 23:24:56 -04:00
John Warwick 45454ebaf0 Fixed typos in Record docs 2013-07-08 22:59:49 -04:00
John Warwick e321550127 Fixed typos in Kernel docs 2013-07-08 22:39:14 -04:00
José Valim 2dc1e41aca Pretty printer now prints grouped elements in the same line, closes #1383
This implementation slightly changes the semantic of Lindig's algorithm
to allow elements that belong to the same group to be printed together
in the same line, even if they do not fit the line fully. This was achieved
by changing `:break` to mean a possible break and `:flat` to force a flat
structure. Then deciding if a break works as a newline is just a matter
of checking if we have enough space until the next break that is not
inside a group (which is still flat).
2013-07-09 02:24:59 +02:00
Devin Torres 74c82f8590 String.split("") should be [""], %w() should be [] 2013-07-08 17:48:18 -05:00
José Valim 63cfe89096 Improve types for Stream 2013-07-08 22:32:04 +02:00
José Valim 5e5bf1ddb1 Install rebar from elixir repo which is more up to date 2013-07-08 21:48:56 +02:00
José Valim 262a47d6ee Remove hopefully uneeded rebar hacks 2013-07-08 21:01:47 +02:00
José Valim f8dfc485ea Merge pull request #1392 from pragdave/update-rebar
Update rebar to rebar 2.1.0-pre. This (among other things) fixes a crash...
2013-07-08 11:59:46 -07:00
José Valim 52ab64d60b Ensure unquote_splicing followed by tail works, closes #1393 2013-07-08 20:57:51 +02:00
Dave Thomas 3de7f42155 Forgot the changelog 2013-07-08 13:09:17 -05:00
Dave Thomas 3cf4c21ad1 Update rebar to rebar 2.1.0-pre. This (among other things) fixes a crash if an environment variable contains non-ascii characters 2013-07-08 10:26:24 -05:00
José Valim 802b0d11a6 Update CHANGELOG 2013-07-08 11:22:17 +02:00
José Valim 67443d3691 First element of defrecordp is now the record name and longer the current module 2013-07-08 11:22:08 +02:00
José Valim 85371e4e45 Ensure record rewriter keeps tags after setelement 2013-07-08 10:35:37 +02:00
José Valim 2da5326549 Allow quoting on aliases with calls 2013-07-08 09:42:42 +02:00
José Valim a83547723e Just iterate the next value if we are going to use it 2013-07-08 09:28:09 +02:00
José Valim 879651cba9 Rename --docs tests file to docs_test.exs 2013-07-08 09:21:19 +02:00
José Valim cc3634ed37 Merge pull request #1390 from pragdave/iterate
Iterate
2013-07-08 00:15:38 -07:00
José Valim 5a5ab18a57 Merge pull request #1391 from jwarwick/iex_docs
Fixed doc typos in IEx, IEx.Options and IEx.Helpers
2013-07-08 00:13:48 -07:00
John Warwick 8fca8a965b Fixed doc typos in IEx.Helpers 2013-07-07 23:25:15 -04:00
John Warwick 0beb65298d Fixed doc typos in the IEx module 2013-07-07 23:02:57 -04:00
John Warwick 218552af3f Fixed typos in IEx.Options docs 2013-07-07 22:53:57 -04:00
Dave Thomas a2c6630793 Add Stream.repeatedly 2013-07-07 18:12:08 -05:00
Dave Thomas 8c3c0a9149 Add Stream.iterate 2013-07-07 17:50:01 -05:00
José Valim b1181320d3 Small refactoring on IEx.Options 2013-07-07 23:17:52 +02:00
José Valim 9742c1c17d Merge pull request #1389 from alco/iex-options-fix
Refactor IEx.Options and raise on bad key
2013-07-07 14:13:41 -07:00
Alexei Sholik eb4dda0b1e Refactor IEx.Options and raise on bad key 2013-07-08 00:02:22 +03:00
José Valim bde91c325c Ensure Stream.take/2 halts execution 2013-07-07 22:50:49 +02:00
Dave Thomas 7c2f25fc85 Add Stream.seq() for generating potentially infinite streams of values 2013-07-07 14:41:24 -05:00
José Valim d82f315fa7 inspect_kw no longer is 2013-07-07 21:21:06 +02:00
José Valim fc505f8a82 Replace ?is_new_atom macro by ?is_atom_start 2013-07-07 21:14:36 +02:00
José Valim 241cd1747d Merge pull request #1376 from ericmj/mix-deps-version
Use Mix.Version for dependencies
2013-07-07 12:10:10 -07:00
Yurii Rashkovskii dd6fb4acc2 Minor doc fix 2013-07-07 10:04:09 -07:00
José Valim a056f0ac40 Projects without a mix.exs can't be recursive
Otherwise, recursive_enabled? always returns true, even
when it should be disabled, affecting the output of tasks.

Closes #1374
2013-07-07 18:04:22 +02:00
José Valim b830485541 Ensure Elixir can run on directories with :, closes #1375 2013-07-07 10:00:46 +02:00
José Valim 15d347fb96 Merge pull request #1385 from jwarwick/inspect_docs
Fixed doc typos for Inspect
2013-07-06 23:55:29 -07:00
John Warwick c11d50e451 Fixed doc typos in the Inspect protocol 2013-07-06 23:33:10 -04:00
John Warwick 64e6037986 Fixed doc typos in Inspect.Algebra 2013-07-06 23:17:35 -04:00
José Valim 8a9927ea1b Optimize Inspect implementation 2013-07-06 16:17:09 +02:00
Yurii Rashkovskii 889de92187 Doc typo fix 2013-07-06 07:00:05 -07:00
José Valim 033fb6a5e9 Update CHANGELOG 2013-07-06 12:36:08 +02:00
José Valim 7ad7847596 Rearrange how ambiguous ops are handled, closes #1378 2013-07-06 12:32:22 +02:00
José Valim 9dcfc53f60 Merge pull request #1382 from jwarwick/think_different
Fixed doc typos in Path and Kernel
2013-07-06 02:50:03 -07:00
José Valim 26f9ce121a Merge pull request #1381 from jwarwick/set_docs
Fixed typos in Set module docs
2013-07-06 02:49:47 -07:00
José Valim 0c557d2766 Document the Inspect protocol and Inspect.Algebra 2013-07-06 11:43:59 +02:00
José Valim e13a516715 Get rid of Inspect.Utils 2013-07-06 11:10:12 +02:00
José Valim accf7dac82 Change inspect formatting to use spaces in between , 2013-07-06 10:07:54 +02:00
John Warwick b3a65eaca1 Fixing typos in Kernel module docs 2013-07-05 23:57:22 -04:00
John Warwick 3e29d9fa2e Fixing typos in Path module docs 2013-07-05 23:41:19 -04:00
John Warwick 0edf1882ff Fixed typos in Set module docs 2013-07-05 23:33:04 -04:00
José Valim 35d019559d Remove the need for Inspect.Utils.return and Inspect.Algebra.text 2013-07-05 23:19:39 +02:00
José Valim 7048236988 Use a Inspect.Opts record instead of passing opts around 2013-07-05 22:37:47 +02:00
José Valim b335b1663d Rename Binary.Inspect -> Inspect and Wadler -> Inspect.Algebra 2013-07-05 22:15:16 +02:00
José Valim f69ecfe8aa Make Wadler API conform with Elixir API and update doctests to actually run 2013-07-05 20:29:15 +02:00
José Valim 10a456823c Do not enable pretty by default 2013-07-05 19:27:55 +02:00
José Valim a26d4cf0cd Merge remote-tracking branch 'brunoro/master' into jv-pretty-printing 2013-07-05 19:27:37 +02:00
Eric Meadows-Jonsson 3f11eef086 Require override: true option to override a dep 2013-07-05 15:33:04 +02:00
Eric Meadows-Jonsson ad5885ba2e Use Mix.Version for dependencies 2013-07-05 15:32:20 +02:00
José Valim a42725910f Update CHANGELOG 2013-07-04 21:26:01 +02:00
José Valim a6f8e178c0 Consistently allow empty expressions on both side of operators 2013-07-04 20:29:39 +02:00
José Valim 7c2a661a5f Merge pull request #1369 from ericmj/mix-deps-errors
Improve Mix dependency error messages
2013-07-04 11:10:48 -07:00
Eric Meadows-Jonsson 15af0356a2 Improve Mix dependency error messages 2013-07-04 20:04:28 +02:00
José Valim ebe522ec50 Tighten up the grammar rules regarding parentheses omission
Prior to this commit, Elixir allowed too many ambiguous calls
by ommitting the parentheses:

    do_something 1, is_list [], 3
    [1, is_atom :foo, 3]

Both cases above would lead to a compilation error, as an attempt
to call is_list/2 and is_atom/2 would happen. Those examples will
now raise a syntax error, pointing to the ambiguous location.

We have disallowed such examples throughout the language with
the exception of one argument calls:

    assert is_list []

Examples as above are extremely frequent and were kept as is.
2013-07-04 18:48:55 +02:00
José Valim 6d491325d5 Update and remove out of date erl tests 2013-07-04 11:40:00 +02:00
José Valim f697ff4160 Ensure HashDict can be serialized into attribute, closes #1368 2013-07-04 11:35:14 +02:00
José Valim 4ae58d3bf4 Do not try to parse arguments for undefined functions 2013-07-04 11:16:14 +02:00
José Valim 650648a687 Clean up ExUnit.Case 2013-07-04 10:57:31 +02:00
José Valim b561c93db2 Clarify docs for Macro.expand_all/2 2013-07-04 10:30:13 +02:00
José Valim 15c1a9891c Implement bindings in Elixir 2013-07-04 10:18:46 +02:00
Gustavo Brunoro 10697628f9 to_binary always equals to 2. 2013-07-03 22:25:18 -03:00
Gustavo Brunoro 89da6e032d adding documentation for Binary.Inspect.Utils 2013-07-03 22:19:10 -03:00
Gustavo Brunoro e969a330d5 tests use tuples instead of recordp macros
* recordp builder/accessor macros aren't accessible on test cases.
2013-07-03 20:28:30 -03:00
Gustavo Brunoro 0f4598fca0 using defrecordp for all records on wadler.ex
* some tests still need to be refactored.
2013-07-03 20:03:04 -03:00
Gustavo Brunoro cb5bb2e548 Merge branch 'master' of https://github.com/elixir-lang/elixir 2013-07-03 18:54:33 -03:00
José Valim acf912c2c8 Clean up ExUnit.CaseTemplate to allow composition 2013-07-03 21:29:35 +02:00
José Valim 6b82967dd6 Improve error report on unhandled -> 2013-07-03 21:29:35 +02:00
José Valim 789d01ae2a Merge pull request #1366 from ericmj/iex-show-exit
Show exit messages in IEx
2013-07-03 08:30:54 -07:00
Eric Meadows-Jonsson 531434959f Show exit messages in IEx 2013-07-03 17:21:28 +02:00
Vladimir Dronnikov d9baf3c39e elixir.bat: accomodate for paths with spaces
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2013-07-03 15:05:45 +02:00
José Valim 91be13d128 Optimize HashDict to store pairs in a cons cell reducing storage per key by half 2013-07-03 15:05:08 +02:00
José Valim 62527cae9c Small clean up on parser file 2013-07-03 15:05:08 +02:00
José Valim 466c2b2e1b Merge pull request #1365 from jwarwick/mix_doc_typos
Fixing typos in Mix.Task docs
2013-07-03 05:52:23 -07:00
John Warwick 2ba6238dd1 Fixing typos in Mix.Task docs 2013-07-03 08:50:08 -04:00
José Valim 48cef0c65b Merge pull request #1361 from jwarwick/regex_examples
Doc cleanup and more examples for Regex module
2013-07-03 01:57:58 -07:00
John Warwick 3d208e219d Added doctest examples for Regex.split 2013-07-02 22:24:02 -04:00
John Warwick f8fd11bef0 Fixing doc typos in Regex module 2013-07-02 22:19:11 -04:00
John Warwick 9703c9968b More doctests for Regex.captures and fixed return type doc 2013-07-02 22:01:56 -04:00
José Valim 82f5868257 Merge pull request #1360 from AlbertMoscow/master
Fixed typos in Kernel
2013-07-02 11:02:18 -07:00
AlbertMoscow 3ab74433c0 Fixed typos in Kernel
This line of code is referenced from the blog post by the following link: https://peepcode.com/blog/2013/elixir-is-for-programmers
2013-07-02 10:55:53 -07:00
José Valim 31dec377f0 Merge pull request #1359 from justinj/kernel_typos
Fixed typos in Kernel
2013-07-02 10:00:52 -07:00
Justin Jaffray a6db102d05 Fixed typos in Kernel
Also reworded some docstrings to make more sense
2013-07-02 12:06:08 -04:00
José Valim 628d1b630e Fix surprising behaviour with Module.register_attribute/3 that caused attributes to be persisted unecessarily 2013-07-02 10:31:32 +02:00
José Valim fbd7c34211 Merge pull request #1356 from jwarwick/regex_typos
Fixed typos in Regex module docs
2013-07-02 00:07:46 -07:00
José Valim 25f5546d6f Merge pull request #1355 from jwarwick/kernel_record_typos
Fixed doc typos in Kernel.defrecord and Kernel.defrecordp
2013-07-02 00:06:59 -07:00
José Valim 288925c2fa Merge pull request #1354 from jwarwick/record_typos
Fixing Record doc typos
2013-07-02 00:06:44 -07:00
John Warwick 4a0b1aa384 Fixed typos in Regex module docs 2013-07-01 22:59:20 -04:00
John Warwick ee1b87089d Fixed doc typos for Kernel.defrecord and Kernel.defrecordp 2013-07-01 22:36:33 -04:00
John Warwick 074db53fbb Fixed typo in Record.update ArgumentError message 2013-07-01 22:11:41 -04:00
John Warwick 60693e48e5 Fixed typos in Record docs 2013-07-01 22:09:24 -04:00
José Valim e49a00d3cb Tidy up Record and access docs 2013-07-01 21:19:09 +02:00
José Valim 1fe756a2db Improve Record docs 2013-07-01 21:10:37 +02:00
José Valim 1da36c5b04 Re-organize records tests 2013-07-01 21:10:13 +02:00
José Valim 7a2aea160c Rewrite how records are introduced 2013-07-01 21:10:08 +02:00
José Valim 2837ce381b Get rid of specifics 2013-07-01 21:09:35 +02:00
José Valim 3ae417cbb2 Merge pull request #1352 from elixir-lang/jv-uri
Remove the URI lookup mechanism in favor of application env
2013-07-01 12:09:11 -07:00
José Valim 12c5cf0515 Fix typo on ExUnit.CLIFormatter 2013-07-01 20:27:01 +02:00
José Valim 26eafd1a4c Remove the URI lookup mechanism in favor of application env 2013-07-01 20:25:01 +02:00
José Valim cdd8e5862d Rename the ExUnit option from debug to trace 2013-07-01 16:41:34 +02:00
José Valim eee1993cdd Update CHANGELOG 2013-07-01 15:14:38 +02:00
José Valim 46e898e413 Add mix test --debug 2013-07-01 15:02:57 +02:00
José Valim 82e1c9337c No need to print 'test' before each test name 2013-07-01 14:50:07 +02:00
José Valim 3bac4ddec6 Support --debug on internal tests which are not run through mix 2013-07-01 14:20:33 +02:00
José Valim 0703be3863 Support :debug option on ExUnit 2013-07-01 14:18:22 +02:00
José Valim d087686f3d Remove unecessary overheard on ExUnit runner and ensure messages are in order 2013-07-01 13:18:26 +02:00
José Valim 595d343e05 Refactor and improve coverage of ExUnit formatters 2013-07-01 13:18:26 +02:00
José Valim dce2bee57e Merge pull request #1349 from jwarwick/code_typos
Fixing typos in the Code module
2013-07-01 00:46:07 -07:00
José Valim d512f545f4 Merge pull request #1348 from jwarwick/stream_typos
Fixing typos in Stream docs
2013-07-01 00:45:46 -07:00
John Warwick 1bdefbb3d1 Fixing typos in the Code module 2013-06-30 22:32:37 -04:00
John Warwick 0e4bef3ac8 Fixing typos in Stream docs 2013-06-30 21:50:05 -04:00
José Valim c5121feac5 Update CHANGELOG 2013-06-30 19:24:54 +02:00
José Valim 08f592610e Merge branch 'jv-streams' 2013-06-30 19:21:58 +02:00
José Valim 664d1ec0d3 Decide on IO.stream and unify read/readline API 2013-06-30 19:21:39 +02:00
José Valim 8ca28496e4 Add Stream.reject/2 and Stream.(drop|take)*/2 2013-06-30 19:21:39 +02:00
José Valim c5cd8c3fcd Improve the docs 2013-06-30 19:21:39 +02:00
José Valim cf98f9b975 Deprecate File.*iterator! in favor of File.*lines_stream! 2013-06-30 19:21:39 +02:00
José Valim 26e6fdbeb2 Improve Stream doctests 2013-06-30 19:21:39 +02:00
José Valim 35edf833b6 Add Stream.cycle/1 2013-06-30 19:21:39 +02:00
José Valim 1a343b01d6 Add Stream.with_index/1 and Enum.with_index/1 2013-06-30 19:21:39 +02:00
José Valim bc76627feb Add Streams 2013-06-30 19:21:39 +02:00
José Valim 0e0d93076c Update CHANGELOG 2013-06-30 16:48:57 +02:00
José Valim c04dcc1882 Avoid bootstraping issues 2013-06-30 16:33:04 +02:00
José Valim 5822cd153f Do not automatically nest defrecordp argument
This may lead people think that an alias is defined
and that is not the case.
2013-06-30 16:25:08 +02:00
José Valim 65c624ef55 Merge pull request #1339 from yrashk/defrecordp-3
defrecordp/3
2013-06-30 07:13:43 -07:00
José Valim 6c56bb8682 Put doc tests in alphabetical order 2013-06-30 15:52:10 +02:00
José Valim b666c696b4 Remove docs from functions documented on behaviours 2013-06-30 15:51:05 +02:00
José Valim 1f8c8f5292 Merge pull request #1241 from josephwilk/sets
Add Sets to Elixir
2013-06-30 06:43:36 -07:00
José Valim 03323b9bfa Macro.escape should also escape nested tuples, closes #1344 2013-06-30 15:30:44 +02:00
José Valim 8195662b98 Merge pull request #1345 from JordiPolo/feature/note_about_make_tests
when contributing it is important to run the tests
2013-06-30 05:19:23 -07:00
Joseph Wilk d6c0533395 We don't need the HashSet, its already one. 2013-06-30 13:44:19 +02:00
Jordi Carres 474d4f6532 when contributing it is important to run the tests 2013-06-30 18:59:45 +09:00
José Valim e548807605 Do not downcase host and inline common lookups 2013-06-30 10:57:11 +02:00
José Valim 25f82dcb46 Go directly through :elixir_errors.warn 2013-06-30 10:42:57 +02:00
José Valim 596670ab9c Merge pull request #1341 from jarsen/string-duplicate
Return empty string when given 0 for n in String.duplicate
2013-06-29 23:47:49 -07:00
Yurii Rashkovskii c20ee8f7e9 Flip the order of first two arguments in defrecordp/3 2013-06-30 08:36:24 +02:00
José Valim 3e13f77d1b Merge pull request #1342 from jwarwick/gen_event_typos
Fixing GenEvent.Behaviour doc typos
2013-06-29 23:23:27 -07:00
José Valim 76be461964 Merge pull request #1340 from jwarwick/gen_server_typos
Fixed GenServer.Behaviour docs typos and example bug
2013-06-29 23:23:09 -07:00
John Warwick 31431fa795 Added additional iex output to GenEvent.Behaviour example 2013-06-29 22:31:10 -04:00
John Warwick 3bf1b08af3 Fixing typos in GenEvent.Behaviour 2013-06-29 22:26:19 -04:00
Jason Larsen f1baed6434 Add test for String.duplicate with n of 0 2013-06-29 20:24:50 -06:00
John Warwick e9aa9b213b Fixed calls to super in GenServer.Behaviour example 2013-06-29 22:16:51 -04:00
John Warwick 93b3c6fc3d Fixed typos in GenServer.Behaviour docs 2013-06-29 22:10:48 -04:00
Jason Larsen 17eb2c77c8 make String.duplicate return empty string for n of 0
When passed 0 for n, return an empty string from String.duplicate.
:binary.copy already supports this, so no changes needed outside
adjusting the guard clause.
2013-06-29 19:51:42 -06:00
Yurii Rashkovskii 1b043c36bb defrecordp/3 should nest aliases under the parent module 2013-06-30 00:56:25 +02:00
Yurii Rashkovskii cda2986a63 Implement defrecordp/3 that enables specifying a custom record tag 2013-06-30 00:35:24 +02:00
Yurii Rashkovskii 9bc67bbfdf Fix defrecordp documentation with regards to the type generated 2013-06-30 00:04:16 +02:00
Yurii Rashkovskii 83c964ae46 Fix defrecordp definition of the <record>_t type
It should use the correct tag in the tuple to be correct.
2013-06-29 23:58:16 +02:00
Joseph Wilk 1b2d8bb915 Contraction trie after filtering if needed. 2013-06-29 23:21:03 +02:00
Joseph Wilk 34f0f93e8f Move HashSet examples over to Set. 2013-06-29 23:21:02 +02:00
Joseph Wilk e6cc35fbbd Doco Set functions. 2013-06-29 23:21:02 +02:00
josephwilk 6e9b8fb454 Adding Set behaviour
* Pushing some HashSet tests into the Set tests.
2013-06-29 23:21:02 +02:00
josephwilk 83173698da Remove unused var. 2013-06-29 23:21:02 +02:00
José Valim 72b7b8ebe2 Increase node size 2013-06-29 23:21:02 +02:00
José Valim 358e49378c Remove unecessary overhead on put/delete 2013-06-29 23:21:02 +02:00
José Valim e8bd90a1c8 Speed up HashSet.member? and HashSet.intersection 2013-06-29 23:21:02 +02:00
josephwilk 096711b517 Adding doco. 2013-06-29 23:21:02 +02:00
josephwilk d599e76366 Improve performance by using set_filter for difference with tries.
Beautiful.
2013-06-29 23:21:02 +02:00
josephwilk 1c16ebe2ff consistency with other bucket_... functions 2013-06-29 23:21:02 +02:00
josephwilk eb813f71d6 Support set_filter with tries.
Set intersection for tries now uses set_filter.
2013-06-29 23:21:01 +02:00
josephwilk ce683fc10e Refactor: Add filter.
Use filter for both intersection and difference.
2013-06-29 23:21:01 +02:00
Joseph Wilk 7dd8fa4c8d quieten warnings. 2013-06-29 23:21:01 +02:00
Joseph Wilk fd7e038927 Set renamed to HashSet.
Separate Set from the implementation strategy.
2013-06-29 23:21:01 +02:00
Joseph Wilk f3a0a52c10 Add examples for disjoint? and subset? 2013-06-29 23:21:01 +02:00
Joseph Wilk b7018f77f3 Use doctests. 2013-06-29 23:21:01 +02:00
Joseph Wilk 01230d68c7 Adding disjoint? for sets 2013-06-29 23:21:01 +02:00
Joseph Wilk e1bca3c65f Adding useful Enumerable functions. 2013-06-29 23:21:01 +02:00
Joseph Wilk f726b6bbdf Adding test subset? 2013-06-29 23:21:01 +02:00
Joseph Wilk 00e9098d19 Adding set equal? 2013-06-29 23:21:00 +02:00
Joseph Wilk 7b7c691196 Don't use new on set_difference, just create the ordered directly. 2013-06-29 23:21:00 +02:00
Joseph Wilk 94d714bb2f new was wasteful, just create the new ordered bucket. 2013-06-29 23:21:00 +02:00
Joseph Wilk 1be0a5329b formatting tweaks 2013-06-29 23:21:00 +02:00
Joseph Wilk 73fdf7442b Corrected definition of difference.
Set 1 minus Set 2 members.
2013-06-29 23:21:00 +02:00
Joseph Wilk 5598eb68d0 difference and intersection support for nodes.
Though not very efficient for nodes.
2013-06-29 23:21:00 +02:00
Joseph Wilk 99a03ffcf9 Refactor: removing unneeded comparisons 2013-06-29 23:21:00 +02:00
josephwilk 07e8a7318c documentation for functions. 2013-06-29 23:21:00 +02:00
josephwilk b7dac7e24a Correcting a bad merge 2013-06-29 23:21:00 +02:00
josephwilk 9b01bae463 nodes also don't contain duplicates 2013-06-29 23:20:59 +02:00
josephwilk ca4997e7f4 delete works with nodes 2013-06-29 23:20:59 +02:00
josephwilk 4c1ee4e261 union works with nodes 2013-06-29 23:20:59 +02:00
josephwilk cacb7d5997 member? for sets works with nodes 2013-06-29 23:20:59 +02:00
josephwilk 4b502985d4 Support expanding depth of nodes.
There is a lot of CRAZY resizing logic that is copied from HashDict. Once everything is working consider extracting.
2013-06-29 23:20:59 +02:00
josephwilk 48ecb7d6f0 put supports growing into a trie.
* to_list works with trie
2013-06-29 23:20:59 +02:00
josephwilk 023866b2b0 Adding set member? for buckets 2013-06-29 23:20:59 +02:00
josephwilk 72c3432042 Adding set difference for buckets 2013-06-29 23:20:59 +02:00
josephwilk d7facd11df Adding set intersection for buckets 2013-06-29 23:20:59 +02:00
josephwilk 76b8b9f3a9 Adding set union 2013-06-29 23:20:59 +02:00
Joseph Wilk 20f26909c8 I think member is a better name than value 2013-06-29 23:20:58 +02:00
Joseph Wilk 276eee072d delete from a set 2013-06-29 23:20:58 +02:00
josephwilk 37621d4a07 Adding Set with simple ops just for bucket (small sets) 2013-06-29 23:20:58 +02:00
Yurii Rashkovskii a768da2980 IEx autocompletion should support identifiers that include numbers
Closes #1337
2013-06-29 20:44:08 +02:00
José Valim 8e5f14dc74 Require Erlang 16B+ 2013-06-29 16:59:01 +02:00
José Valim c832d3a78e Import records that depends on others, closes #1334 2013-06-29 13:11:50 +02:00
José Valim c052558cbf Simplify runcmd on IEx 2013-06-29 13:11:50 +02:00
José Valim a0d225f891 Merge pull request #1335 from mururu/fix-typo
Fix typo in Module.get_attribute doc
2013-06-29 00:36:34 -07:00
Gustavo Brunoro 45862f8472 Merge branch 'master' of https://github.com/elixir-lang/elixir
Conflicts:
	lib/elixir/lib/binary/inspect.ex
	lib/mix/lib/mix/tasks/local.install.ex
2013-06-29 02:54:16 -03:00
Yuki Ito dbe2851cdb Fix typo in Module.get_attribute doc 2013-06-29 11:41:54 +09:00
José Valim 33d6780bb1 Merge pull request #1333 from ToJans/master
Some more fixes for #1280
2013-06-28 13:01:21 -07:00
Tom Janssens 8924fa4aee Fixed iex tests on windows
Elixirc was not properly invoked
2013-06-28 16:30:49 +02:00
Tom Janssens e4812ec185 Skip color tests on windows 2013-06-28 16:17:11 +02:00
Tom Janssens ea32d951ab Skip symlink tests on windows 2013-06-28 15:45:42 +02:00
Tom Janssens 598f0e0141 We no longer execute the doc_tests for elixir file helpers
Due to windows incompatibility, we removed some of the doctests for the
file helpers.
2013-06-28 15:43:51 +02:00
José Valim d484499917 Merge pull request #1332 from alco/1329-win-no-color
Disable coloring by default in IEx on Windows
2013-06-28 01:26:21 -07:00
Alexei Sholik 00a6753497 Disable coloring by default in IEx on Windows. Closes #1329 2013-06-28 11:11:35 +03:00
José Valim edb5016112 Merge pull request #1330 from jwarwick/typos
Doc cleanup for Keyword module
2013-06-27 23:23:59 -07:00
José Valim 672dc8c595 Merge pull request #1331 from jwarwick/iex_typos
Fixed doc typos in IEx module
2013-06-27 23:23:36 -07:00
John Warwick f203560832 Fixed doc typos in IEx module 2013-06-27 23:21:26 -04:00
John Warwick e3d086abb5 Added more doctests in Keyword module 2013-06-27 22:53:53 -04:00
John Warwick 74ea72246e Fixing typos and formatting in Keyword docs 2013-06-27 22:42:53 -04:00
José Valim 447a57750f Simplify binding handling 2013-06-27 17:47:45 +02:00
José Valim e1c3df3bef Remove verbose doctest 2013-06-27 14:33:19 +02:00
José Valim 4c5cb1bc7a Set a PROMPT when invoking rebar
This is a common failure when installing Elixir when
users have Unicode Characters in their PROMPT environment
variable. We have attempted to fix this issue in rebar
but it continues showing up, so we will temporarily set
the prompt ourselves
2013-06-27 09:27:38 +02:00
José Valim 81d36cd921 Merge pull request #1328 from jwarwick/typos
Fixed doc typos in Enum
2013-06-26 23:32:43 -07:00
John Warwick 88e4b5833f Fixed return type in doc of Enum.each 2013-06-26 23:55:04 -04:00
John Warwick d5323e3311 Added examples for Enum.count/2 and Enum.zip/2 2013-06-26 23:45:36 -04:00
John Warwick 1e73ea0527 Fixed doc example for Enum.each 2013-06-26 23:30:02 -04:00
John Warwick dc3ca821fb Fixed typo in Enum.min docs 2013-06-26 23:27:40 -04:00
John Warwick c9fe36c28b Fixed bad Example formatting in Enum 2013-06-26 23:23:58 -04:00
José Valim 7ba5733496 No need to wrap lists in blocks 2013-06-26 21:53:33 +02:00
José Valim e035dda2db Inline list funcs in HashDict too 2013-06-26 21:53:33 +02:00
Eric Meadows-Jonsson 8e8ebe73b9 Refer to the moduledoc from Record.defrecord 2013-06-25 15:10:27 +02:00
José Valim 30e56696c9 Merge pull request #1326 from jwarwick/mix_typo
Fixed typo in mix task local.uninstall doc
2013-06-25 05:47:18 -07:00
John Warwick c817e6a078 Fixed typo in mix task local.uninstall doc 2013-06-25 07:42:41 -04:00
José Valim c8a04ac0ad Update CHANGELOG 2013-06-25 11:57:42 +02:00
José Valim e66fe507bb Remove matchspec duplication 2013-06-25 11:17:24 +02:00
José Valim 6ea09bb2ef Merge pull request #1316 from meh/mix-system-version
Mix system version
2013-06-25 02:09:36 -07:00
José Valim 7a1065809a Erlang says that the first directory of an archive should be the same as the archive name 2013-06-25 10:43:54 +02:00
meh 8f194b7d19 Remove doctests from Mix.Version 2013-06-25 10:02:28 +02:00
meh 6a4fa93b00 Fix typo in mix project tests 2013-06-25 09:57:06 +02:00
meh cad30a73fe Fix Mix.Version being inconsistent with pre and build metadata 2013-06-25 09:57:06 +02:00
meh 0ba2162113 Validate Elixir version in projects, closes #1310 2013-06-25 09:57:06 +02:00
José Valim 42841a021e Merge pull request #1320 from jeremyjh/1284-version-archives
Add version number to mix archive files by default per #1284
2013-06-25 00:17:02 -07:00
José Valim 690687353f Don't end it with ? if it doesnt return a boolean 2013-06-25 09:11:42 +02:00
José Valim 25862cb7a3 Ensure variables from when in match? do not leak too 2013-06-25 08:58:19 +02:00
José Valim 227e7690a3 Merge pull request #1311 from vanstee/contain-match-scope
Don't leak matched variables when calling `match?`
2013-06-24 23:31:09 -07:00
vanstee a6f256fbe8 Remove variables defined in match? from scope
At @josevalim's suggestion, we loop over the ast given to the macro and
disable scope of any new variables defined, which effectively stops
variables from leaking into the outside scope.

Fixes #1304
2013-06-24 22:38:40 -04:00
José Valim 3ebca00872 quote calls them context, so let's keep it consistent 2013-06-24 22:38:41 +02:00
José Valim 7e11cea780 Ensure binding(..., false) also works 2013-06-24 16:24:08 +02:00
José Valim ad7f1c6a3b Merge pull request #1323 from ToJans/master
Skip windows test for CHMOD 777, as this does not work on windows
2013-06-24 04:23:38 -07:00
Tom Janssens a8b7663f37 Skip windows test for CHMOD 777, as this does not work on windows 2013-06-24 13:17:28 +02:00
Tom Janssens 3b6afdd7d4 Cleaned up test_helper some more 2013-06-24 12:47:30 +02:00
José Valim 943fabb548 Merge pull request #1322 from ToJans/master
Tests on windows now intercept stderr from elixir(c) invocations
2013-06-24 03:35:27 -07:00
Tom Janssens 53f5fe71bb Cleaned up the elixir test_helper.exs a bit 2013-06-24 12:33:12 +02:00
Tom Janssens c406ce4aac Grouped some windows functionality in test_helper for elixir 2013-06-24 12:16:38 +02:00
Tom Janssens 4b4a7e8d4a Tests on windows now intercept stderr from elixir(c) invocations
Previously tests on windows did not intercept stderr when they were
invoked through :os.cmd, so we now add a piper redirect from stderr
to stdout when invoking elixir(c) on windows.
2013-06-24 12:04:46 +02:00
José Valim 7d2214678e Remove useless assignment on defrecord 2013-06-24 10:30:08 +02:00
José Valim 81972106d9 Fix unused variable warning 2013-06-24 10:12:58 +02:00
José Valim 6a95d0517f Improve doctest error messages, closes #1318 2013-06-24 08:15:23 +02:00
José Valim 8abed86b1a Improve error message and optimize record macros 2013-06-24 08:14:38 +02:00
José Valim 5a882161ac Allow a record to import itself, closes #1319 2013-06-24 07:51:21 +02:00
José Valim ffb1d9be4f Merge pull request #1321 from guilleiguaran/enum_reject
Add Enum.reject
2013-06-23 22:39:35 -07:00
Guillermo Iguaran d5e2b41c37 Add Enum.reject
Enum.reject returns elements of collection for which `fun` returns false.
2013-06-24 00:26:52 -05:00
Jeremy Huffman c049c1388a mix archive version # & -o flag for #1284 2013-06-23 23:35:22 -04:00
José Valim eb93b9f7a9 Make release instructions clearer 2013-06-24 00:07:44 +02:00
José Valim 867d1eaf9b Bump to v0.9.4-dev 2013-06-23 23:58:19 +02:00
Gustavo Brunoro e8fd78b85d Mix not using pretty printing
* Added pretty: false option to all inspect calls.
2013-06-21 18:03:19 -03:00
Gustavo Brunoro 1a578f6574 fixing inspect pretty: false option
* inspect pretty: false calls Wadler.format with :infinity
  as line width.
* Wadler.fits? with w=:infinity always returns true.
* Wadler.format with w=:infinity will only use Flat mode,
  not adding any line breaks.
2013-06-21 17:51:26 -03:00
Gustavo Brunoro 001c923f43 moving over to :lists.duplicate
* List.duplicate makes exponentially more :lists.duplicate/3 calls
  than :lists.duplicate/2.
* it also yields better slightly results than String.duplicate.
2013-06-21 15:47:12 -03:00
Gustavo Brunoro 8170ff7b2c adjusting failing tests relying on inspection
* some test cases rely on result from the inspection functions.
  Those were adjusted based on the current result of the inspect
  function, but a more clever method of comparing the result may
  be needed if changes on the tests recur.
2013-06-18 02:03:33 -03:00
Gustavo Brunoro f442fc0d77 stripped spaces from record pretty printing
* brackets are not followed/preceded by spaces when inspecting.
2013-06-18 01:12:10 -03:00
Gustavo Brunoro c608f8279a adjusting nesting tests and behaviour on wadler.ex
* surround/3 function aware that nesting only adds spaces after a
  linebreak
* nesting tests should force Break format mode, as Lindig pretty
  printer defaults to Flat printing for small strings.
2013-06-16 21:17:52 -03:00
Gustavo Brunoro d99a5c5e9e adapted wadler.ex unit tests to current interface 2013-06-16 20:46:10 -03:00
Gustavo Brunoro 5999caed62 using iolist_to_binary over Enum.join on wadler.ex
* iolist_to_binary assumed to have better performance.
2013-06-16 16:55:42 -03:00
Gustavo Brunoro 163485b5ba Merge remote-tracking branch 'upstream/master' into wadler-lindig
Conflicts:
	lib/elixir/lib/binary/inspect.ex
	lib/elixir/lib/kernel.ex
2013-06-16 16:20:15 -03:00
Gustavo Brunoro f7c8ed0dd6 Optimizing string concatenation calls on wadler.ex
* Modified render method to render sdoc records to a string list
  followed by a Enum.join call.
2013-06-15 19:51:22 -03:00
Gustavo Brunoro 54097e11d7 adapted IO.inspect calls to Lindig interface
* group1 and line functions were changed
* relying on `surround` helper
* using a default identation of 2, this should be an option?
2013-06-14 18:57:50 -03:00
Gustavo Brunoro a41528d297 using Lindig's strict pretty printer
* Implemented Wadler document algebra with explicit groups, meaning
  that a break should be rendered to a linebreak or a space as
  specified by a mode: `Flat` or `Break`.
* This algorithm reduces the search space for document layouts, as
  no `best` function is used to search lazily over layouts with a
  flat or newline breaks.
2013-06-14 15:56:44 -03:00
Jonn Mostovoy 0060190580 Binary.Inspect now uses pretty printing library 2013-05-11 11:29:03 +03:00
Jonn Mostovoy 1da4844103 Put pretty printing library into core 2013-05-10 07:42:10 +03:00
Jonn Mostovoy 7f2862762a Merged (Fri May 10 06:22:13 EEST 2013) 2013-05-10 06:22:05 +03:00
Jonn Mostovoy 3e2af17a83 Tests for wadler; Guards for primitive types. 2013-05-10 06:21:42 +03:00
Jonn Mostovoy 1f7a8065fa Add pretty printing library to Elixir stdlib 2013-05-07 05:55:02 +03:00
296 changed files with 14329 additions and 7748 deletions
+162 -5
View File
@@ -1,18 +1,175 @@
# v0.10.2 (2013-09-03)
* enhancements
* [CLI] Add `--verbose` to elixirc, which now is non-verbose by default
* [Dict] Add `Dict.Behaviour` as a convenience to create your own dictionaries
* [Enum] Add `Enum.split/2`, `Enum.reduce/2`, `Enum.flat_map/2`, `Enum.chunks/2`, `Enum.chunks/4`, `Enum.chunks_by/2`, `Enum.concat/1` and `Enum.concat/2`
* [Enum] Support negative indices in `Enum.at/fetch/fetch!`
* [ExUnit] Show failures on CLIFormatter as soon as they pop up
* [IEx] Allow for strings in `h` helper
* [IEx] Helpers `r` and `c` can handle erlang sources
* [Integer] Add `odd?/1` and `even?/1`
* [IO] Added support to specifying a number of bytes to stream to `IO.stream`, `IO.binstream`, `File.stream!` and `File.binstream!`
* [Kernel] Include file and line on error report for overriding an existing function/macro
* [Kernel] Convert external functions into quoted expressions. This allows record fields to contain functions as long as they point to an `&Mod.fun/arity`
* [Kernel] Allow `foo?` and `bar!` as valid variable names
* [List] Add `List.replace_at/3`
* [Macro] Improve printing of the access protocol on `Macro.to_string/1`
* [Macro] Add `Macro.to_string/2` to support annotations on the converted string
* [Mix] Automatically recompile a project if the Elixir version changes
* [Path] Add `Path.relative_to_cwd/2`
* [Regex] Allow erlang `re` options when compiling Elixir regexes
* [Stream] Add `Stream.concat/1`, `Stream.concat/2` and `Stream.flat_map/2`
* [String] Add regex pattern support to `String.replace/3`
* [String] Add `String.ljust/2`, `String.rjust/2`, `String.ljust/3` and `String.rjust/3`
* [URI] `URI.parse/1` supports IPv6 addresses
* bug fix
* [Behaviour] Do not compile behaviour docs if docs are disabled on compilation
* [ExUnit] Doctests no longer eat too much space and provides detailed reports for poorly indented lines
* [File] Fix a bug where `File.touch(file, datetime)` was not setting the proper datetime when the file did not exist
* [Kernel] Limit `inspect` results to 50 items by default to avoid printing too much data
* [Kernel] Return a readable error on oversized atoms
* [Kernel] Allow functions ending with `?` or `!` to be captured
* [Kernel] Fix default shutdown of child supervisors to `:infinity`
* [Kernel] Fix regression when calling a function/macro ending with bang, followed by `do/end` blocks
* [List] Fix bug on `List.insert_at/3` that added the item at the wrong position for negative indexes
* [Macro] `Macro.escape/2` can now escape improper lists
* [Mix] Fix `Mix.Version` matching on pre-release info
* [Mix] Ensure `watch_exts` trigger full recompilation on change with `mix compile`
* [Mix] Fix regression on `mix clean --all`
* [String] `String.strip/2` now supports removing unicode characters
* [String] `String.slice/3` still returns the proper result when there is no length to be extracted
* [System] `System.get_env/0` now returns a list of tuples as previously advertised
* deprecations
* [Dict] `Dict.update/3` is deprecated in favor of `Dict.update!/3`
* [Enum] `Enum.min/2` and `Enum.max/2` are deprecated in favor of `Enum.min_by/2` and `Enum.max_by/2`
* [Enum] `Enum.join/2` and `Enum.map_join/3` with a char list are deprecated
* [IO] `IO.stream(device)` and `IO.binstream(device)` are deprecated in favor of `IO.stream(device, :line)` and `IO.binstream(device, :line)`
* [Kernel] `list_to_binary/1`, `binary_to_list/1` and `binary_to_list/3` are deprecated in favor of `String.from_char_list!/1` and `String.to_char_list!/1` for characters and `:binary.list_to_bin/1`, `:binary.bin_to_list/1` and `:binary.bin_to_list/3` for bytes
* [Kernel] `to_binary/1` is deprecated in favor of `to_string/1`
* [Kernel] Deprecate `def/4` and friends in favor of `def/2` with unquote and friends
* [Kernel] Deprecate `%b` and `%B` in favor of `%s` and `%S`
* [List] `List.concat/2` is deprecated in favor of `Enum.concat/2`
* [Macro] `Macro.unescape_binary/1` and `Macro.unescape_binary/2` are deprecated in favor of `Macro.unescape_string/1` and `Macro.unescape_string/2`
* [Mix] `:umbrella` option for umbrella paths has been deprecated in favor of `:in_umbrella`
* backwards incompatible changes
* [IO] IO functions now only accept iolists as arguments
* [Kernel] `Binary.Chars` was renamed to `String.Chars`
* [Kernel] The previous ambiguous import syntax `import :functions, Foo` was removed in favor of `import Foo, only: :functions`
* [OptionParser] `parse` and `parse_head` now returns a tuple with three elements instead of two
# v0.10.1 (2013-08-03)
* enhancements
* [Behaviour] Add support for `defmacrocallback/1`
* [Enum] Add `Enum.shuffle/1`
* [ExUnit] The `:trace` option now also reports run time for each test
* [ExUnit] Add support for `:color` to enable/disable ANSI coloring
* [IEx] Add the `clear` helper to clear the screen.
* [Kernel] Add the capture operator `&`
* [Kernel] Add support for `GenFSM.Behaviour`
* [Kernel] Functions now points to the module and function they were defined when inspected
* [Kernel] A documentation attached to a function that is never defined now prints warnings
* [List] Add `List.keysort/2`
* [Mix] `:test_helper` project configuration did not affect `mix test` and was therefore removed. A `test/test_helper.exs` file is still necessary albeit it doesn't need to be automatically required in each test file
* [Mix] Add manifests for yecc, leex and Erlang compilers, making it easier to detect dependencies in between compilers and providing a more useful clean behaviour
* [Mix] `mix help` now outputs information about the default mix task
* [Mix] Add `--no-deps-check` option to `mix run`, `mix compile` and friends to not check dependency status
* [Mix] Add support for `MIX_GIT_FORCE_HTTPS` system environment that forces HTTPS for known providers, useful when the regular git port is blocked. This configuration does not affect the `mix.lock` results
* [Mix] Allow coverage tool to be pluggable via the `:test_coverage` configuration
* [Mix] Add `mix cmd` as a convenience to run a command recursively in child apps in an umbrella application
* [Mix] Support `umbrella: true` in dependencies as a convenience for setting up umbrella path deps
* [Mix] `mix run` now behaves closer to the `elixir` command and properly mangles the ARGV
* [String] Add `Regex.scan/3` now supports capturing groups
* [String] Add `String.reverse/1`
* bug fix
* [Behaviour] Ensure callbacks are stored in the definition order
* [CLI] Speed up boot time on Elixir .bat files
* [IEx] Reduce cases where IEx parser can get stuck
* [Kernel] Improve error messages when the use of an operator has no effect
* [Kernel] Fix a bug where warnings were not being generated when imported macros conflicted with local functions or macros
* [Kernel] Document that `on_definition` can only be a function as it is evaluated inside the function context
* [Kernel] Ensure `%w` sigils with no interpolation are fully expanded at compile time
* [Mix] `mix deps.update`, `mix deps.clean` and `mix deps.unlock` no longer change all dependencies unless `--all` is given
* [Mix] Always run ` mix loadpaths` on `mix app.start`, even if `--no-compile` is given
* [OptionParser] Do not add boolean flags to the end result if they were not given
* [OptionParser] Do not parse non-boolean flags as booleans when true or false are given
* [OptionParser] Ensure `:keep` and `:integer`|`:float` can be given together as options
* [OptionParser] Ensure `--no-flag` sets `:flag` to false when `:flag` is a registered boolean switch
* deprecations
* [Kernel] `function(Mod.fun/arity)` and `function(fun/arity)` are deprecated in favor of `&Mod.fun/arity` and `&fun/arity`
* [Kernel] `function/3` is deprecated in favor of `Module.function/3`
* [Kernel] `Kernel.ParallelCompiler` now receives a set of callbacks instead of a single one
* [Mix] `:test_coverage` option now expect keywords arguments and the `--cover` flag is now treated as a boolean
* backwards incompatible changes
* [Regex] `Regex.scan/3` now always returns a list of lists, normalizing the result, instead of list with mixed lists and binaries
* [System] `System.halt/2` was removed since the current Erlang implementation of such function is bugged
# v0.10.0 (2013-07-15)
* enhancements
* [ExUnit] Support `trace: true` option which gives detailed reporting on test runs
* [HashDict] Optimize `HashDict` to store pairs in a cons cell reducing storage per key by half
* [Kernel] Add pretty printing support for inspect
* [Kernel] Add document algebra library used as the foundation for pretty printing
* [Kernel] Add `defrecordp/3` that enables specifying the first element of the tuple
* [Kernel] Add the `Set` API and a hash based implementation via `HashSet`
* [Kernel] Add `Stream` as composable, lazy-enumerables
* [Mix] `mix archive` now includes the version of the generated archive
* [Mix] Mix now requires explicit dependency overriding to be given with `override: true`
* [Mix] Projects can now define an `:elixir` key to outline supported Elixir versions
* [Typespec] Improve error messages to contain file, line and the typespec itself
* bug fix
* [CLI] Elixir can now run on Unix directories with `:` in its path
* [Kernel] `match?/2` does not leak variables to outer scope
* [Kernel] Keep `head|tail` format when splicing at the tail
* [Kernel] Ensure variables defined in the module body are not passed to callbacks
* [Mix] On dependencies conflict, show from where each source is coming from
* [Mix] Empty projects no longer leave empty ebin files on `mix compile`
* [Module] Calling `Module.register_attribute/3` no longer automatically changes it to persisted or accumulated
* deprecations
* [Enum] Receiving the index of iteration in `Enum.map/2` and `Enum.each/2` is deprecated in favor of `Stream.with_index/1`
* [File] `File.iterator/1` and `File.biniterator/1` are deprecated in favor of `IO.stream/1` and `IO.binstream/1`
* [File] `File.iterator!/2` and `File.biniterator!/2` are deprecated in favor of `File.stream!/2` and `File.binstream!/2`
* [Kernel] Deprecate recently added `quote binding: ...` in favor of the clearer `quote bind_quoted: ...`
* [Kernel] Deprecate `Kernel.float/1` in favor of a explicit conversion
* [Mix] Deprecate `mix run EXPR` in favor of `mix run -e EXPR`
* [Record] `Record.__index__/2` deprecated in favor of `Record.__record__(:index, key)`
* backwards incompatible changes
* [Kernel] The `Binary.Inspect` protocol has been renamed to `Inspect`
* [Kernel] Tighten up the grammar rules regarding parentheses omission, previously the examples below would compile but now they raise an error message:
do_something 1, is_list [], 3
[1, is_atom :foo, 3]
* [Module] Calling `Module.register_attribute/3` no longer automatically changes it to persisted or accumulated
* [Record] First element of a record via `defrecordp` is now the `defrecordp` name and no longer the current atom
* [URI] Remove custom URI parsers in favor of `URI.default_port/2`
# v0.9.3 (2013-06-23)
* enhancements
* [File] Add `File.chgrp`, `File.chmod` and `File.chown`
* [Kernel] Add --warnings-as-errors to Elixir's compiler options
* [Kernel] Add `--warnings-as-errors` to Elixir's compiler options
* [Kernel] Print warnings to stderr
* [Kernel] Warn on undefined module attributes
* [Kernel] Emit warning for `x in []` in guards
* [Kernel] Add `binding/0` and `binding/1` for retrieving bindings
* [Kernel] `quote` now allows a binding as an option
* [Macro] Add `Macro.expand_once/2` and `Macro.expand_all/2`
* [Mix] Implement `Mix.Version` for basic versioniong semantics
* [Mix] Implement `Mix.Version` for basic versioning semantics
* [Mix] Support creation and installation of archives (.ez files)
* [Mix] `github: ...` shortcut now uses the faster `git` schema instead of `https`
* [Record] Allow types to be given to `defrecord` and `defrecordp`
* [Record] Allow types to be given to `defrecordp`
* bug fix
* [Kernel] The elixir executable on Windows now supports the same options as the UNIX one
@@ -29,7 +186,7 @@
* [Typespec] Deprecate `(fun(...) -> ...)` in favor of `(... -> ...)`
* backwards incompatible changes
* [Bitwise] Precedence of operators used by the Bitwise module were changed. Check `elixir_parser.yrl` for more information.
* [Bitwise] Precedence of operators used by the Bitwise module were changed, check `elixir_parser.yrl` for more information
* [File] `rm_rf` and `cp_r` now returns a tuple with three elements on failures
* [Kernel] The quoted representation for `->` clauses changed from a tuple with two elements to a tuple with three elements to support metadata
* [Kernel] Sigils now dispatch to `sigil_$` instead of `__$__` where `$` is the sigil caracter
@@ -49,7 +206,7 @@
* bug fix
* [HashDict] Ensure HashDict uses exact match throughout its implementation
* [IEx] Do not interpret ANSI codes in IEx results
* [IEx] Ensure --cookie is set before accessing remote shell
* [IEx] Ensure `--cookie` is set before accessing remote shell
* [Kernel] Do not ignore nil when dispatching protocols to avoid infinite loops
* [Mix] Fix usage of shell expressions in `Mix.Shell.cmd`
* [Mix] Start the application by default on escripts
+91 -14
View File
@@ -5,13 +5,18 @@ process easy and effective for everyone involved!
## Using the issue tracker
The issue tracker is the preferred channel for [bug reports](#bugs-reports),
[features requests](#feature-requests) and [submitting pull
requests](#pull-requests), but please **do not** use the issue tracker for
personal support requests. Instead, use
[the mailing list](http://groups.google.com/group/elixir-lang-talk),
[Stack Overflow](http://stackoverflow.com/questions/ask?tags=elixir), or
[#elixir-lang](irc://chat.freenode.net/elixir-lang) on Freenode).
Use the issues tracker for:
* [bug reports](#bugs-reports)
* [submitting pull requests](#pull-requests)
Please **do not** use the issues tracker for personal support requests nor feature requests. Support requests should be send to:
* [the elixir-talk mailing list](http://groups.google.com/group/elixir-lang-talk)
* [Stack Overflow](http://stackoverflow.com/questions/ask?tags=elixir)
* [#elixir-lang](irc://chat.freenode.net/elixir-lang)
Feature requests can be discussed on [the elixir-core mailing list](http://groups.google.com/group/elixir-lang-core).
We do our best to keep the issues tracker tidy and organized, making it useful
for everyone. For example, we classify open issues per application and perceived
@@ -60,10 +65,10 @@ Example:
## Feature requests
Feature requests are welcome. But take a moment to find out whether your idea
fits with the scope and aims of the project. It's up to *you* to make a strong
case to convince the project's developers of the merits of this feature. Please
provide as much detail and context as possible.
Feature requests are welcome and should be discussed on [the elixir-core mailing list](http://groups.google.com/group/elixir-lang-core). But take a moment to find
out whether your idea fits with the scope and aims of the project. It's up to *you*
to make a strong case to convince the project's developers of the merits of this
feature. Please provide as much detail and context as possible.
## Contributing
@@ -86,9 +91,71 @@ You can run all tests in the root directory with `make test` and you can
also run tests for a specific framework `make test_#{NAME}`, for example,
`make test_ex_unit`.
From time to time, your tests may fail in an existing Elixir checkout and
may require a clean start by running `make clean compile`. You can always
check [the official build status on Travis-CI](https://travis-ci.org/elixir-lang/elixir).
With tests running and passing, you are ready to contribute to Elixir and
send your pull requests.
## Contributing Documentation
Code documentation (`@doc`, `@moduledoc`, `@typedoc`) has a special convention:
the first paragraph is considered to be a short summary.
For functions, macros and callbacks say what it will do. For example write
something like:
```elixir
@doc """
Return only those elements for which `fun` is true.
...
"""
def filter(collection, fun) ...
```
For modules, records, protocols and types say what it is. For example write
something like:
```elixir
defrecord File.Stat, [...] do
@moduledoc """
Information about a file.
...
"""
end
```
Keep in mind that the first paragraph might show up in a summary somewhere, long
texts in the first paragraph create very ugly summaries. As a rule of thumb
anything longer than 80 characters is too long.
Try to keep unneccesary details out of the first paragraph, it's only there to
give a user a quick idea of what the documented "thing" does/is. The rest of the
documentation string can contain the details, for example when a value and when
`nil` is returned.
If possible include examples, preferably in a form that works with doctests. For
example:
```elixir
@doc """
Return only those elements for which `fun` is true.
## Examples
iex> Enum.filter([1, 2, 3], fn(x) -> rem(x, 2) == 0 end)
[2]
"""
def filter(collection, fun) ...
```
This makes it easy to test the examples so that they don't go stale and examples
are often a great help in explaining what a function does.
## Pull requests
Good pull requests - patches, improvements, new features - are a fantastic
@@ -144,16 +211,26 @@ in order to craft an excellent pull request:
[interactive rebase](https://help.github.com/articles/interactive-rebase)
feature to tidy up your commits before making them public.
5. Push your topic branch up to your fork:
5. Make sure all the tests are still passing.
```bash
make test
```
This command will compile the code in your branch and use that
version of Elixir to run the tests. This is needed to ensure your changes can
pass all the tests.
6. Push your topic branch up to your fork:
```bash
git push origin <topic-branch-name>
```
6. [Open a Pull Request](https://help.github.com/articles/using-pull-requests/)
7. [Open a Pull Request](https://help.github.com/articles/using-pull-requests/)
with a clear title and description.
7. If you haven't updated your pull request for a while, you should consider
8. If you haven't updated your pull request for a while, you should consider
rebasing on master and resolving any conflicts.
**IMPORTANT**: _Never ever_ merge upstream `master` into your branches. You
-2
View File
@@ -5,6 +5,4 @@ All the files in this distribution are covered under either Elixir's
license (see the file LICENSE) except the files mentioned below that
contains sections that are under Erlang's License (EPL):
lib/elixir/src/elixir_glob.erl
lib/elixir/src/elixir_parser.erl (generated by build scripts)
+48 -42
View File
@@ -1,9 +1,15 @@
REBAR := $(shell echo `pwd`/rebar)
ELIXIRC := bin/elixirc --ignore-module-conflict $(ELIXIRC_OPTS)
REBAR := "$(shell echo `pwd`/rebar)"
ELIXIRC := bin/elixirc --verbose --ignore-module-conflict $(ELIXIRC_OPTS)
ERLC := erlc -I lib/elixir/include
ERL := erl -I lib/elixir/include -noshell -pa lib/elixir/ebin
VERSION := $(strip $(shell cat VERSION))
INSTALL_PATH := /usr/local
Q := @
PREFIX := /usr/local
LIBDIR := lib
INSTALL = install
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
.NOTPARALLEL: compile
@@ -14,15 +20,15 @@ define APP_TEMPLATE
$(1): lib/$(1)/ebin/Elixir.$(2).beam lib/$(1)/ebin/$(1).app
lib/$(1)/ebin/$(1).app:
@ cd lib/$(1) && ../../bin/elixir -e "Mix.Server.start_link(:dev)" -r mix.exs -e "Mix.Task.run('compile.app')"
$(Q) cd lib/$(1) && ../../bin/elixir -e "Mix.Server.start_link(:dev)" -r mix.exs -e "Mix.Task.run('compile.app')"
lib/$(1)/ebin/Elixir.$(2).beam: $(wildcard lib/$(1)/lib/*.ex) $(wildcard lib/$(1)/lib/*/*.ex) $(wildcard lib/$(1)/lib/*/*/*.ex)
@ echo "==> $(1) (compile)"
@ $$(ELIXIRC) "lib/$(1)/lib/**/*.ex" -o lib/$(1)/ebin
$(Q) $$(ELIXIRC) "lib/$(1)/lib/**/*.ex" -o lib/$(1)/ebin
test_$(1): $(1)
@ echo "==> $(1) (exunit)"
@ cd lib/$(1) && ../../bin/elixir -r "test/test_helper.exs" -pr "test/**/*_test.exs";
$(Q) cd lib/$(1) && ../../bin/elixir -r "test/test_helper.exs" -pr "test/**/*_test.exs";
endef
#==> Compilation tasks
@@ -35,11 +41,11 @@ default: compile
compile: lib/elixir/src/elixir.app.src erlang elixir
lib/elixir/src/elixir.app.src: src/elixir.app.src
@ rm -rf lib/elixir/src/elixir.app.src
@ cp src/elixir.app.src lib/elixir/src/elixir.app.src
$(Q) rm -rf lib/elixir/src/elixir.app.src
$(Q) cp src/elixir.app.src lib/elixir/src/elixir.app.src
erlang:
@ cd lib/elixir && $(REBAR) compile
$(Q) cd lib/elixir && $(REBAR) compile
# Since Mix depends on EEx and EEx depends on
# Mix, we first compile EEx without the .app
@@ -48,21 +54,21 @@ elixir: kernel lib/eex/ebin/Elixir.EEx.beam mix ex_unit eex iex
kernel: $(KERNEL) VERSION
$(KERNEL): lib/elixir/lib/*.ex lib/elixir/lib/*/*.ex
@ if [ ! -f $(KERNEL) ]; then \
$(Q) if [ ! -f $(KERNEL) ]; then \
echo "==> bootstrap (compile)"; \
$(ERL) -s elixir_compiler core -s erlang halt; \
fi
@ echo "==> kernel (compile)";
@ $(ELIXIRC) "lib/elixir/lib/**/*.ex" -o lib/elixir/ebin;
@ $(MAKE) unicode
@ rm -rf lib/elixir/ebin/elixir.app
@ cd lib/elixir && $(REBAR) compile
$(Q) $(ELIXIRC) "lib/elixir/lib/**/*.ex" -o lib/elixir/ebin;
$(Q) $(MAKE) unicode
$(Q) rm -rf lib/elixir/ebin/elixir.app
$(Q) cd lib/elixir && $(REBAR) compile
unicode: $(UNICODE)
$(UNICODE): lib/elixir/priv/unicode.ex lib/elixir/priv/UnicodeData.txt lib/elixir/priv/NamedSequences.txt
@ echo "==> unicode (compile)";
@ echo "This step can take up to a minute to compile in order to embed the Unicode database"
@ $(ELIXIRC) lib/elixir/priv/unicode.ex -o lib/elixir/ebin;
$(Q) $(ELIXIRC) lib/elixir/priv/unicode.ex -o lib/elixir/ebin;
$(eval $(call APP_TEMPLATE,ex_unit,ExUnit))
$(eval $(call APP_TEMPLATE,eex,EEx))
@@ -72,28 +78,28 @@ $(eval $(call APP_TEMPLATE,iex,IEx))
install: compile
@ echo "==> elixir (install)"
for dir in lib/*; do \
install -m755 -d $(INSTALL_PATH)/lib/elixir/$$dir/ebin; \
install -m644 $$dir/ebin/* $(INSTALL_PATH)/lib/elixir/$$dir/ebin; \
$(INSTALL_DIR) "$(DESTDIR)$(PREFIX)/$(LIBDIR)/elixir/$$dir/ebin"; \
$(INSTALL_DATA) $$dir/ebin/* "$(DESTDIR)$(PREFIX)/$(LIBDIR)/elixir/$$dir/ebin"; \
done
$(INSTALL_DIR) "$(DESTDIR)$(PREFIX)/$(LIBDIR)/elixir/bin"
$(INSTALL_PROGRAM) $(filter-out %.bat, $(wildcard bin/*)) "$(DESTDIR)$(PREFIX)/$(LIBDIR)/elixir/bin"
$(INSTALL_DIR) "$(DESTDIR)$(PREFIX)/bin"
for file in "$(DESTDIR)$(PREFIX)"/$(LIBDIR)/elixir/bin/* ; do \
ln -sf "../$(LIBDIR)/elixir/bin/$${file##*/}" "$(DESTDIR)$(PREFIX)/bin/" ; \
done
install -m755 -d $(INSTALL_PATH)/lib/elixir/bin
install -m755 $(filter-out %.bat, $(wildcard bin/*)) $(INSTALL_PATH)/lib/elixir/bin
install -m755 -d $(INSTALL_PATH)/bin
ln -sf $(INSTALL_PATH)/lib/elixir/bin/* $(INSTALL_PATH)/bin
clean:
@ cd lib/elixir && $(REBAR) clean
cd lib/elixir && $(REBAR) clean
rm -rf ebin
rm -rf lib/*/ebin
rm -rf lib/*/test/tmp
rm -rf lib/mix/test/fixtures/git_repo
rm -rf lib/*/tmp
rm -rf lib/mix/test/fixtures/git_repo
rm -rf lib/mix/test/fixtures/deps_on_git_repo
rm -rf lib/mix/test/fixtures/git_rebar
rm -rf lib/elixir/src/elixir.app.src
rm -rf lib/elixir/src/*_lexer.erl
rm -rf lib/elixir/src/*_parser.erl
rm -rf lib/elixir/test/ebin
clean_exbeam:
@ rm -f lib/*/ebin/Elixir.*.beam
$(Q) rm -f lib/*/ebin/Elixir.*.beam
#==> Release tasks
@@ -115,13 +121,13 @@ release_zip: compile
zip -9 -r v$(VERSION).zip bin CHANGELOG.md LEGAL lib/*/ebin LICENSE README.md rel VERSION
release_docs: docs
cd ../elixir-lang.github.com && git checkout master
cd ../elixir-lang.github.com
rm -rf ../elixir-lang.github.com/docs/master
mv docs ../elixir-lang.github.com/docs/master
release_erl: compile
@ rm -rf rel/elixir
@ cd rel && ../rebar generate
$(Q) rm -rf rel/elixir
$(Q) cd rel && ../rebar generate
#==> Tests tasks
@@ -129,29 +135,29 @@ test: test_erlang test_elixir
test_erlang: compile
@ echo "==> elixir (eunit)"
@ mkdir -p lib/elixir/test/ebin
@ $(ERLC) -pa lib/elixir/ebin -o lib/elixir/test/ebin lib/elixir/test/erlang/*.erl
@ $(ERL) -pa lib/elixir/test/ebin -s test_helper test -s erlang halt;
@ echo
$(Q) mkdir -p lib/elixir/test/ebin
$(Q) $(ERLC) -pa lib/elixir/ebin -o lib/elixir/test/ebin lib/elixir/test/erlang/*.erl
$(Q) $(ERL) -pa lib/elixir/test/ebin -s test_helper test -s erlang halt;
@ echo ""
test_elixir: test_kernel test_ex_unit test_doc_test test_mix test_eex test_iex
test_doc_test: compile
@ echo "==> doctest (exunit)"
@ cd lib/elixir && ../../bin/elixir -r "test/doc_test.exs";
$(Q) cd lib/elixir && ../../bin/elixir -r "test/doc_test.exs";
test_kernel: compile
@ echo "==> kernel (exunit)"
@ cd lib/elixir && ../../bin/elixir -r "test/elixir/test_helper.exs" -pr "test/elixir/**/*_test.exs";
$(Q) cd lib/elixir && ../../bin/elixir -r "test/elixir/test_helper.exs" -pr "test/elixir/**/*_test.exs";
.dialyzer.base_plt:
@ echo "==> Adding Erlang/OTP basic applications to a new base PLT"
@ dialyzer --output_plt .dialyzer.base_plt --build_plt --apps erts kernel stdlib compiler syntax_tools inets crypto ssl
$(Q) dialyzer --output_plt .dialyzer.base_plt --build_plt --apps erts kernel stdlib compiler tools syntax_tools parsetools
dialyze: .dialyzer.base_plt
@ rm -f .dialyzer_plt
@ cp .dialyzer.base_plt .dialyzer_plt
$(Q) rm -f .dialyzer_plt
$(Q) cp .dialyzer.base_plt .dialyzer_plt
@ echo "==> Adding Elixir to PLT..."
@ dialyzer --plt .dialyzer_plt --add_to_plt -r lib/elixir/ebin lib/ex_unit/ebin lib/mix/ebin lib/iex/ebin lib/eex/ebin
$(Q) dialyzer --plt .dialyzer_plt --add_to_plt -r lib/elixir/ebin lib/ex_unit/ebin lib/eex/ebin lib/iex/ebin lib/mix/ebin
@ echo "==> Dialyzing Elixir..."
@ dialyzer --plt .dialyzer_plt -r lib/elixir/ebin lib/ex_unit/ebin lib/mix/ebin lib/iex/ebin lib/eex/ebin
$(Q) dialyzer --plt .dialyzer_plt -r lib/elixir/ebin lib/ex_unit/ebin lib/eex/ebin lib/iex/ebin lib/mix/ebin
+10 -6
View File
@@ -2,23 +2,27 @@
This document simply outlines the release process:
1) Remove `+dev` extension from VERSION
1) Remove `-dev` extension from VERSION
2) Run `make clean test` to ensure all tests pass from scratch and the CI is green
3) Ensure CHANGELOG is updated and tag release version with timestamp in it
4) Commit changes above and update stable branch
4) Commit changes above with title "Release vVERSION"
5) Create tag from master branch
5) Push master and create tag from master branch
6) Release new docs with `make release_docs`, update elixir-lang.org
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 Elixir's elixir-lang.org/packages.html
7) Release new zip with `make release_zip`, push new zip to Github Releases
8) Push package to expm with `expm publish package.exs`
9) After release, bump versions and add `+dev` back
9) Merge master into stable branch and push it
10) After release, bump versions and add `-dev` back
11) `make release_docs` once again and push `elixir-lang.github.com`
## Places where version is mentioned
+1 -1
View File
@@ -1 +1 @@
0.9.3
0.10.2
+4 -13
View File
@@ -1,5 +1,5 @@
#!/bin/sh
if [ $# -eq 0 ] || [ $1 = "--help" ] || [ $1 = "-h" ]; then
if [ $# -eq 0 ] || [ "$1" = "--help" ] || [ "$1" = "-h" ]; then
echo "Usage: `basename $0` [options] [.exs file] [data]
-v Prints version and exit
@@ -43,7 +43,7 @@ while [ $I -le $# ]; do
case "$PEEK" in
-v|--compile|--no-halt|+iex|+compile)
;;
-e|-r|-pr|-pa|-pz|--remsh)
-e|-r|-pr|-pa|-pz|--remsh|--app)
S=2
;;
--detached|--hidden)
@@ -74,21 +74,12 @@ done
SELF=$(readlink_f "$0")
SCRIPT_PATH=$(dirname "$SELF")
if [ "$ERL_LIBS" = "" ]
then
ERL_LIBS="$SCRIPT_PATH/../lib"
else
PATHSEP=":"
if [ "$OSTYPE" = "msys" ]; then PATHSEP=";" ; fi
ERL_LIBS="$ERL_LIBS$PATHSEP$SCRIPT_PATH/../lib"
fi
if [ -f "$HOME/.elixirrc" ]; then . "$HOME/.elixirrc"; fi
if [ "$ELIXIR_NO_CLI" != "1" ]; then ERL="$ERL -s elixir start_cli"; fi
if [ -f "$SCRIPT_PATH/../releases/RELEASES" ] && [ -f "$SCRIPT_PATH/erl" ]
then
exec "$SCRIPT_PATH"/erl -env ERL_LIBS "$ERL_LIBS" -boot elixir -noshell $ELIXIR_ERL_OPTS $ERL -extra "$@"
exec "$SCRIPT_PATH"/erl -pa "$SCRIPT_PATH"/../lib/*/ebin -noshell $ELIXIR_ERL_OPTS $ERL -boot start -extra "$@"
else
exec erl -env ERL_LIBS "$ERL_LIBS" -noshell $ELIXIR_ERL_OPTS $ERL -extra "$@"
exec erl -pa "$SCRIPT_PATH"/../lib/*/ebin -noshell $ELIXIR_ERL_OPTS $ERL -extra "$@"
fi
+33 -60
View File
@@ -1,5 +1,4 @@
@echo off
SETLOCAL enabledelayedexpansion
set argc=0
for %%x in (%*) do set /A argc+=1
if %argc%== 0 (
@@ -13,7 +12,7 @@ echo.
echo -v Prints version and exit
echo -e command Evaluates the given command (*)
echo -r file Requires the given files/patterns (*)
echo -S script   Finds and executes the given script (*)
echo -S script Finds and executes the given script (*)
echo -pr file Requires the given files/patterns in parallel (*)
echo -pa path Prepends the given path to Erlang code path (*)
echo -pz path Appends the given path to Erlang code path (*)
@@ -47,69 +46,43 @@ set beforeExtra=
rem Recursive loop called for each parameter that parses the cmd line parameters
:startloop
set par=%1
set par="%1"
shift
if "%par%"=="" (
rem if no parameters defined
goto :run
goto :expand_erl_libs
)
if "%par%"=="""" (
rem if no parameters defined - special case for parameter that is already quoted
goto :expand_erl_libs
)
rem ******* ERLANG PARAMETERS **********************
for /f "usebackq" %%m in (`echo %par%^|findstr \--detached`) do (
set parsErlang=%parsErlang% -detached
goto:startloop
)
for /f "usebackq" %%m in (`echo %par%^|findstr \--hidden`) do (
set parsErlang=%parsErlang% -hidden
goto:startloop
)
for /f "usebackq" %%m in (`echo %par%^|findstr \--cookie`) do (
set parsErlang=%parsErlang% -setcookie %1
shift
goto:startloop
)
for /f "usebackq" %%m in (`echo %par%^|findstr \--sname`) do (
set parsErlang=%parsErlang% -sname %1
shift
goto:startloop
)
for /f "usebackq" %%m in (`echo %par%^|findstr \--name`) do (
set parsErlang=%parsErlang% -name %1
shift
goto:startloop
)
for /f "usebackq" %%m in (`echo %par%^|findstr \--erl`) do (
set beforeExtra=%beforeExtra% %~1
shift
goto:startloop
)
IF NOT "%par%"=="%par:--detached=%" (Set parsErlang=%parsErlang% -detached)
IF NOT "%par%"=="%par:--hidden=%" (Set parsErlang=%parsErlang% -hidden)
IF NOT "%par%"=="%par:--cookie=%" (Set parsErlang=%parsErlang% -setcookie %1 && shift)
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)
rem ******* elixir parameters **********************
for /f "usebackq" %%m in (`echo %par%^|findstr \--v`) do (
goto:startloop
)
for /f "usebackq" %%m in (`echo %par%^|findstr \--compile`) do (
goto:startloop
)
for /f "usebackq" %%m in (`echo %par%^|findstr \--no-halt`) do (
goto:startloop
)
for /f "usebackq" %%m in (`echo %par%^|findstr \+iex`) do (
goto:startloop
)
for /f "usebackq" %%m in (`echo %par%^|findstr \+compile`) do (
goto:startloop
)
for /f "usebackq" %%m in (`echo %par%^|findstr \-[er]`) do (
shift
goto:startloop
)
for /f "usebackq" %%m in (`echo %par%^|findstr \-p[raz]`) do (
shift
goto:startloop
)
for /f "usebackq" %%m in (`echo %par%^|findstr \--remsh`) do (
shift
goto:startloop
)
rem Note: we don't have to do anything with options that don't take an argument
IF NOT "%par%"=="%par:-e=%" (shift)
IF NOT "%par%"=="%par:-r=%" (shift)
IF NOT "%par%"=="%par:-pr=%" (shift)
IF NOT "%par%"=="%par:-pa=%" (shift)
IF NOT "%par%"=="%par:-pz=%" (shift)
IF NOT "%par%"=="%par:--app=%" (shift)
IF NOT "%par%"=="%par:--remsh=%" (shift)
goto:startloop
rem ******* assume all pre-params are parsed ********************
:expand_erl_libs
rem ******* expand all ebin paths as Windows does not support the ..\*\ebin wildcard ********************
SETLOCAL enabledelayedexpansion
set ext_libs=
for /d %%d in ("%originPath%..\lib\*.") do (
set ext_libs=!ext_libs! -pa "%%~fd\ebin"
)
SETLOCAL disabledelayedexpansion
:run
erl -env ERL_LIBS %ERL_LIBS%;"%originPath%\..\lib" -noshell %ELIXIR_ERL_OPTS% %parsErlang% -s elixir start_cli %beforeExtra% -extra %*
erl %ext_libs% -noshell %ELIXIR_ERL_OPTS% %parsErlang% -s elixir start_cli %beforeExtra% -extra %*
+2 -1
View File
@@ -1,5 +1,5 @@
#!/bin/sh
if [ $# -eq 0 ]; then
if [ $# -eq 0 ] || [ "$1" = "--help" ] || [ "$1" = "-h" ]; then
echo "Usage: `basename $0` [elixir switches] [compiler switches] [.ex files]
-o The directory to output compiled files
@@ -7,6 +7,7 @@ if [ $# -eq 0 ]; then
--no-debug-info Do not attach debug info to compiled modules
--ignore-module-conflict
--warnings-as-errors Treat warnings as errors and return non-zero exit code
--verbose Print informational messages.
** Options given after -- are passed down to the executed code
** Options can be passed to the erlang runtime using ELIXIR_ERL_OPTS" >&2
+1
View File
@@ -14,6 +14,7 @@ echo --no-docs Do not attach documentation to compiled modules
echo --no-debug-info Do not attach debug info to compiled modules
echo --ignore-module-conflict
echo --warnings-as-errors Treat warnings as errors and return non-zero exit code
echo --verbose Print informational messages.
echo.
echo ** Options marked with (*) can be given more than once
echo ** Options given after -- are passed down to the executed code
+2 -2
View File
@@ -1,5 +1,5 @@
#!/bin/sh
if [ $# -gt 0 ] && ([ $1 = "--help" ] || [ $1 = "-h" ]); then
if [ $# -gt 0 ] && ([ "$1" = "--help" ] || [ "$1" = "-h" ]); then
echo "Usage: `basename $0` [options] [.exs file] [data]
-v Prints version
@@ -38,4 +38,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 "$@"
ELIXIR_NO_CLI=1 exec "$SCRIPT_PATH"/elixir --no-halt --erl "-nouser -s Elixir.IEx.CLI" +iex "$@"
+19 -21
View File
@@ -1,7 +1,7 @@
defexception EEx.SyntaxError, message: nil
defmodule EEx do
@moduledoc %B"""
@moduledoc %S"""
EEx stands for Embedded Elixir. It allows you to embed
Elixir code inside a string in a robust way:
@@ -93,11 +93,15 @@ defmodule EEx do
"""
defmacro function_from_string(kind, name, source, args // [], options // []) do
info = [file: __CALLER__.file, line: __CALLER__.line + 1]
quote do
info = Keyword.merge unquote(info), unquote(options)
EEx.function_from_quoted(__MODULE__, unquote(kind), unquote(name),
unquote(args), EEx.compile_string(unquote(source), info), info)
quote bind_quoted: binding do
info = Keyword.merge [file: __ENV__.file, line: __ENV__.line], options
args = Enum.map args, fn arg -> { arg, [line: info[:line]], nil } end
compiled = EEx.compile_string(source, info)
case kind do
:def -> def(unquote(name)(unquote_splicing(args)), do: unquote(compiled))
:defp -> defp(unquote(name)(unquote_splicing(args)), do: unquote(compiled))
end
end
end
@@ -124,14 +128,17 @@ defmodule EEx do
Sample.sample(1, 2) #=> "3"
"""
defmacro function_from_file(kind, name, filename, args // [], options // []) do
quote do
file = unquote(filename)
info = Keyword.merge unquote(options), [file: file, line: 1]
defmacro function_from_file(kind, name, file, args // [], options // []) do
quote bind_quoted: binding do
info = Keyword.merge options, [file: file, line: 1]
args = Enum.map args, fn arg -> { arg, [line: 1], nil } end
compiled = EEx.compile_file(file, info)
@file file
EEx.function_from_quoted(__MODULE__, unquote(kind), unquote(name),
unquote(args), EEx.compile_file(file, info), info)
case kind do
:def -> def(unquote(name)(unquote_splicing(args)), do: unquote(compiled))
:defp -> defp(unquote(name)(unquote_splicing(args)), do: unquote(compiled))
end
end
end
@@ -187,15 +194,6 @@ defmodule EEx do
### Helpers
@doc false
def function_from_quoted(module, kind, name, args, source, info) do
args = Enum.map args, fn arg -> { arg, [], nil } end
quote = quote do
unquote(kind)(unquote(name)(unquote_splicing(args)), do: unquote(source))
end
Module.eval_quoted module, quote, [], info
end
defp do_eval(compiled, bindings, options) do
{ result, _ } = Code.eval_quoted(compiled, bindings, options)
result
+9 -8
View File
@@ -1,5 +1,5 @@
defmodule EEx.Engine do
@moduledoc %B"""
@moduledoc %S"""
This is the basic EEx engine that ships with Elixir.
An engine needs to implement two functions:
@@ -20,6 +20,11 @@ defmodule EEx.Engine do
implemented by default by this engine.
"""
use Behaviour
defcallback handle_text(Macro.t, binary) :: Macro.t
defcallback handle_expr(Macro.t, binary, Macro.t) :: Macro.t
@doc """
The default implementation simply concatenates text to the buffer.
"""
@@ -35,22 +40,18 @@ defmodule EEx.Engine do
All other markers are not implemented by this engine.
"""
def handle_expr(buffer, '=', expr) do
def handle_expr(buffer, "=", expr) do
quote do
tmp = unquote(buffer)
tmp <> to_binary(unquote(expr))
tmp <> to_string(unquote(expr))
end
end
def handle_expr(buffer, '', expr) do
def handle_expr(buffer, "", expr) do
quote do
tmp = unquote(buffer)
unquote(expr)
tmp
end
end
def behaviour_info(:callbacks) do
[handle_text: 2, handle_expr: 3]
end
end
+17 -2
View File
@@ -67,13 +67,28 @@ defmodule EEx.AssignsEngine do
a template, after compiled, may receive different assigns
and the developer don't want to recompile it for each
variable set.
Assigns can also be used when compiled to a function:
# sample.eex
<%= @a + @b %>
# sample.ex
defmodule Sample do
require EEx
EEx.function_from_file :def, :sample, "sample.eex", [:assigns]
end
# iex
Sample.sample(a: 1, b: 2) #=> "3"
"""
@doc false
defmacro __using__(_) do
quote unquote: false do
defp transform({ :@, line, [{ name, _, atom }] }) when is_atom(name) and is_atom(atom) do
quote(do: Keyword.get var!(assigns), unquote(name))
quote do: Keyword.get(var!(assigns), unquote(name))
end
defp transform(arg) do
@@ -91,7 +106,7 @@ defmodule EEx.SmartEngine do
@moduledoc """
An engine meant for end-user usage that includes
`AssignsEngine` and other conveniences. Read
`EEx.AssignsEngine` and other conveniences. Read
`EEx.AssignsEngine` for examples.
"""
end
+6 -6
View File
@@ -12,7 +12,7 @@ defmodule EEx.Tokenizer do
"""
def tokenize(bin, line) when is_binary(bin) do
tokenize(:unicode.characters_to_list(bin), line)
tokenize(String.to_char_list!(bin), line)
end
def tokenize(list, line) do
@@ -54,11 +54,11 @@ defmodule EEx.Tokenizer do
# Retrieve marker for <%
defp retrieve_marker('=' ++ t) do
{ '=', t }
{ "=", t }
end
defp retrieve_marker(t) do
{ '', t }
{ "", t }
end
# Tokenize an expression until we find %>
@@ -128,11 +128,11 @@ defmodule EEx.Tokenizer do
end
defp fn_index(tokens) do
Enum.find_index(tokens, function do
Enum.find_index tokens, fn
{ :fn_paren, _ } -> true
{ :fn, _ } -> true
_ -> false
end)
end
end
defp end_index(tokens) do
@@ -154,6 +154,6 @@ defmodule EEx.Tokenizer do
end
defp tokenize_text(line, buffer, acc) do
[{ :text, line, :unicode.characters_to_binary(Enum.reverse(buffer)) } | acc]
[{ :text, line, String.from_char_list!(Enum.reverse(buffer)) } | acc]
end
end
+14 -14
View File
@@ -13,15 +13,15 @@ defmodule EEx.TokenizerTest do
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, 1, "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, 1, "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, 1, "foo\n" }, { :expr, 2, "=", ' bar ' } ]
end
test "strings with more than one line and expression with more than one line" do
@@ -34,9 +34,9 @@ baz %>
assert T.tokenize(string, 1) == [
{:text, 1, "foo "},
{:expr, 1, '=', ' bar\n\nbaz '},
{:expr, 1, "=", ' bar\n\nbaz '},
{:text, 3, "\n"},
{:expr, 4, [], ' foo '},
{:expr, 4, "", ' foo '},
{:text, 4, "\n"}
]
end
@@ -68,32 +68,32 @@ baz %>
test "strings with embedded do end" do
assert T.tokenize('foo <% if true do %>bar<% end %>', 1) == [
{ :text, 1, "foo " },
{ :start_expr, 1, '', ' if true do ' },
{ :start_expr, 1, "", ' if true do ' },
{ :text, 1, "bar" },
{ :end_expr, 1, '', ' end ' }
{ :end_expr, 1, "", ' end ' }
]
end
test "strings with embedded -> end" do
assert T.tokenize('foo <% cond do %><% false -> %>bar<% true -> %>baz<% end %>', 1) == [
{ :text, 1, "foo " },
{ :start_expr, 1, '', ' cond do ' },
{ :middle_expr, 1, '', ' false -> ' },
{ :start_expr, 1, "", ' cond do ' },
{ :middle_expr, 1, "", ' false -> ' },
{ :text, 1, "bar" },
{ :middle_expr, 1, '', ' true -> ' },
{ :middle_expr, 1, "", ' true -> ' },
{ :text, 1, "baz" },
{ :end_expr, 1, '', ' end ' }
{ :end_expr, 1, "", ' end ' }
]
end
test "strings with embedded keywords blocks" do
assert T.tokenize('foo <% if true do %>bar<% else %>baz<% end %>', 1) == [
{ :text, 1, "foo " },
{ :start_expr, 1, '', ' if true do ' },
{ :start_expr, 1, "", ' if true do ' },
{ :text, 1, "bar" },
{ :middle_expr, 1, '', ' else ' },
{ :middle_expr, 1, "", ' else ' },
{ :text, 1, "baz" },
{ :end_expr, 1, '', ' end ' }
{ :end_expr, 1, "", ' end ' }
]
end
+2 -17
View File
@@ -25,12 +25,6 @@ defmodule EExText.Compiled do
{ __ENV__.line, hd(tl(System.stacktrace)) }
end
@file __ENV__
def other do
fill_in_stacktrace
{ __ENV__.line, hd(tl(System.stacktrace)) }
end
defp fill_in_stacktrace do
try do
:erlang.error "failed"
@@ -312,7 +306,7 @@ foo
{ EExText.Compiled,
:before_compile,
0,
[file: binary_to_list(__FILE__), line: 7]
[file: to_char_list(__FILE__), line: 7]
}
}
@@ -321,7 +315,7 @@ foo
{ EExText.Compiled,
:after_compile,
0,
[file: binary_to_list(__FILE__), line: 18]
[file: to_char_list(__FILE__), line: 18]
}
}
@@ -333,15 +327,6 @@ foo
[file: 'unknown', line: 24]
}
}
assert EExText.Compiled.other ==
{ 31,
{ EExText.Compiled,
:other,
0,
[file: binary_to_list(__FILE__), line: 30]
}
}
end
defp assert_eval(expected, actual) do
+1 -1
View File
@@ -1 +1 @@
ExUnit.start []
ExUnit.start [trace: "--trace" in System.argv]
+5 -3
View File
@@ -6,16 +6,18 @@
-define(atom_concat(Atoms), list_to_atom(lists:concat(Atoms))).
-define(elixir_macro(Macro), list_to_atom(lists:concat(['MACRO-',Macro]))).
-define(line(Opts), elixir_tree_helpers:get_line(Opts)).
-define(line(Opts), elixir_utils:get_line(Opts)).
-record(elixir_scope, {
context=nil, %% can be assign, guards or nil
extra=nil, %% extra information about the context, like fn_match for fns
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
module=nil, %% the current module
function=nil, %% the current function
vars=[], %% a dict of defined variables and their alias
list_vars=nil, %% a list of vars passed down to Macro.Env
backup_vars=nil, %% a copy of vars to be used on ^var
temp_vars=nil, %% a set of all variables defined in a particular assign
clause_vars=nil, %% a dict of all variables defined in a particular clause
@@ -41,7 +43,7 @@
imports_hygiene=true,
unquote=true,
unquoted=false,
mark=true
escape=false
}).
%% Introspection
@@ -61,7 +63,7 @@
-define(is_downcase(S), S >= $a andalso S =< $z).
%% Atoms
-define(is_new_atom(S), ?is_quote(S) orelse ?is_upcase(S) orelse ?is_downcase(S) orelse (S == $_)).
-define(is_atom_start(S), ?is_quote(S) orelse ?is_upcase(S) orelse ?is_downcase(S) orelse (S == $_)).
-define(is_atom(S), ?is_identifier(S) orelse (S == $@)).
-define(is_identifier(S), ?is_digit(S) orelse ?is_upcase(S) orelse ?is_downcase(S) orelse (S == $_)).
+34 -9
View File
@@ -53,15 +53,36 @@ defmodule Behaviour do
do_defcallback(fun, quote(do: term), __CALLER__)
end
defp do_defcallback(fun, return, caller) do
@doc """
Defines a macro callback according to the given type specification.
"""
defmacro defmacrocallback({ :::, _, [fun, return] }) do
do_defmacrocallback(fun, return, __CALLER__)
end
defmacro defmacrocallback(fun) do
do_defmacrocallback(fun, quote(do: Macro.t), __CALLER__)
end
defp do_defcallback(fun, spec, caller) do
case Macro.extract_args(fun) do
{ name, args } -> :ok
{ name, args } ->
do_callback(:def, name, args, name, length(args), spec, caller)
:error ->
raise ArgumentError, message: "invalid syntax in defcallback #{Macro.to_string(fun)}"
end
end
arity = length(args)
defp do_defmacrocallback(fun, spec, caller) do
case Macro.extract_args(fun) do
{ name, args } ->
do_callback(:defmacro, :"MACRO-#{name}", [quote(do: env :: Macro.Env.t)|args], name, length(args), spec, caller)
:error ->
raise ArgumentError, message: "invalid syntax in defmacrocallback #{Macro.to_string(fun)}"
end
end
defp do_callback(kind, name, args, docs_name, docs_arity, return, caller) do
Enum.each args, fn
{ :::, _, [left, right] } ->
ensure_not_default(left)
@@ -74,7 +95,7 @@ defmodule Behaviour do
quote do
@callback unquote(name)(unquote_splicing(args)) :: unquote(return)
Behaviour.store_docs __MODULE__, unquote(caller.line), unquote(name), unquote(arity)
Behaviour.store_docs __MODULE__, unquote(caller.line), unquote(kind), unquote(docs_name), unquote(docs_arity)
end
end
@@ -85,23 +106,27 @@ defmodule Behaviour do
defp ensure_not_default(_), do: :ok
@doc false
def store_docs(module, line, name, arity) do
def store_docs(module, line, kind, name, arity) do
doc = Module.get_attribute module, :doc
Module.delete_attribute module, :doc
Module.put_attribute module, :__behaviour_docs, { { name, arity }, line, doc }
Module.put_attribute module, :behaviour_docs, { { name, arity }, line, kind, doc }
end
@doc false
defmacro __using__(_) do
quote do
Module.register_attribute(__MODULE__, :__behaviour_docs, accumulate: true)
Module.register_attribute(__MODULE__, :behaviour_docs, accumulate: true)
@before_compile unquote(__MODULE__)
import unquote(__MODULE__)
end
end
@doc false
defmacro __before_compile__(_) do
defmacro __before_compile__(env) do
docs = if Code.compiler_options[:docs] do
Enum.reverse Module.get_attribute(env.module, :behaviour_docs)
end
quote do
@doc false
def __behaviour__(:callbacks) do
@@ -109,7 +134,7 @@ defmodule Behaviour do
end
def __behaviour__(:docs) do
@__behaviour_docs
unquote(Macro.escape(docs))
end
end
end
-92
View File
@@ -1,92 +0,0 @@
import Kernel, except: [to_binary: 1]
defprotocol Binary.Chars do
@moduledoc %B"""
The Binary.Chars protocol is responsible for
converting a structure to a Binary (only if applicable).
The only function required to be implemented is
`to_binary` which does the conversion.
The `to_binary` function automatically imported
by Kernel invokes this protocol. String
interpolation also invokes to_binary in its
arguments. For example, `"foo#{bar}"` is the same
as `"foo" <> to_binary(bar)`.
"""
@only [BitString, List, Number, Atom, Record]
def to_binary(thing)
end
defimpl Binary.Chars, for: Atom do
@doc """
Convert the atom literally to a binary, except
`nil` which is converted to an empty string.
"""
def to_binary(nil) do
""
end
def to_binary(atom) do
atom_to_binary(atom, :utf8)
end
end
defimpl Binary.Chars, for: BitString do
@doc """
Simply returns the binary itself.
"""
def to_binary(thing) when is_binary(thing) do
thing
end
end
defimpl Binary.Chars, for: List do
@doc """
Consider the list is an iolist and converts it
to a binary. This allows a list of binaries, or
a charlist, or a mix of both, to be converted
successfully.
## Examples
iex> to_binary('foo')
"foo"
iex> to_binary(["foo", 'bar'])
"foobar"
"""
def to_binary(thing) do
try do
iolist_to_binary(thing)
rescue
ArgumentError ->
raise Protocol.UndefinedError,
protocol: __MODULE__,
structure: thing,
extra: "Only iolists are supported"
end
end
end
defimpl Binary.Chars, for: Number do
@doc """
Simply converts the number (integer or a float) to a binary.
"""
@digits 20
@limit :math.pow(10, @digits)
def to_binary(thing) when is_integer(thing) do
integer_to_binary(thing)
end
def to_binary(thing) when thing > @limit do
float_to_binary(thing, scientific: @digits)
end
def to_binary(thing) do
float_to_binary(thing, compact: true, decimals: @digits)
end
end
-462
View File
@@ -1,462 +0,0 @@
import Kernel, except: [inspect: 1]
defprotocol Binary.Inspect do
@moduledoc """
The `Binary.Inspect` protocol is responsible for
converting any structure to a binary for textual
representation. All basic data structures
(tuple, list, function, pid, etc) implement the
inspect protocol. Other structures are advised to
implement the protocol in order to provide pretty
printing.
"""
def inspect(thing, opts)
end
defmodule Binary.Inspect.Utils do
@moduledoc false
## container_join
def container_join(tuple, first, last, opts) when is_tuple(tuple) do
container_join(tuple_to_list(tuple), first, last, opts)
end
def container_join(list, first, last, opts) do
first <> do_container_join(list, opts, Keyword.get(opts, :limit, :infinity)) <> last
end
defp do_container_join(_, _opts, 0) do
"..."
end
defp do_container_join([h], opts, _counter) do
Kernel.inspect(h, opts)
end
defp do_container_join([h|t], opts, counter) when is_list(t) do
Kernel.inspect(h, opts) <> "," <> do_container_join(t, opts, decrement(counter))
end
defp do_container_join([h|t], opts, _counter) do
Kernel.inspect(h, opts) <> "|" <> Kernel.inspect(t, opts)
end
defp do_container_join([], _opts, _counter) do
""
end
defp decrement(:infinity), do: :infinity
defp decrement(counter), do: counter - 1
## escape
def escape(other, char) do
b = do_escape(other, char, <<>>)
<< char, b :: binary, char >>
end
@compile {:inline, do_escape: 3}
defp do_escape(<<>>, _char, binary), do: binary
defp do_escape(<< char, t :: binary >>, char, binary) do
do_escape(t, char, << binary :: binary, ?\\, char >>)
end
defp do_escape(<<?#, ?{, t :: binary>>, char, binary) do
do_escape(t, char, << binary :: binary, ?\\, ?#, ?{ >>)
end
defp do_escape(<<?\a, t :: binary>>, char, binary) do
do_escape(t, char, << binary :: binary, ?\\, ?a >>)
end
defp do_escape(<<?\b, t :: binary>>, char, binary) do
do_escape(t, char, << binary :: binary, ?\\, ?b >>)
end
defp do_escape(<<?\d, t :: binary>>, char, binary) do
do_escape(t, char, << binary :: binary, ?\\, ?d >>)
end
defp do_escape(<<?\e, t :: binary>>, char, binary) do
do_escape(t, char, << binary :: binary, ?\\, ?e >>)
end
defp do_escape(<<?\f, t :: binary>>, char, binary) do
do_escape(t, char, << binary :: binary, ?\\, ?f >>)
end
defp do_escape(<<?\n, t :: binary>>, char, binary) do
do_escape(t, char, << binary :: binary, ?\\, ?n >>)
end
defp do_escape(<<?\r, t :: binary>>, char, binary) do
do_escape(t, char, << binary :: binary, ?\\, ?r >>)
end
defp do_escape(<<?\\, t :: binary>>, char, binary) do
do_escape(t, char, << binary :: binary, ?\\, ?\\ >>)
end
defp do_escape(<<?\t, t :: binary>>, char, binary) do
do_escape(t, char, << binary :: binary, ?\\, ?t >>)
end
defp do_escape(<<?\v, t :: binary>>, char, binary) do
do_escape(t, char, << binary :: binary, ?\\, ?v >>)
end
defp do_escape(<<h, t :: binary>>, char, binary) do
do_escape(t, char, << binary :: binary, h >>)
end
end
defimpl Binary.Inspect, for: Atom do
require Macro
import Binary.Inspect.Utils
@moduledoc """
Represents the atom as an Elixir term. The atoms false, true
and nil are simply quoted. Modules are properly represented
as modules using the dot notation.
Notice that in Elixir, all operators can be represented using
literal atoms (`:+`, `:-`, etc).
## Examples
iex> inspect(:foo)
":foo"
iex> inspect(nil)
"nil"
iex> inspect(Foo.Bar)
"Foo.Bar"
"""
def inspect(false, _), do: "false"
def inspect(true, _), do: "true"
def inspect(nil, _), do: "nil"
def inspect(:"", _), do: ":\"\""
def inspect(Elixir, _), do: "Elixir"
def inspect(atom, _) do
binary = atom_to_binary(atom)
cond do
valid_atom_identifier?(binary) ->
":" <> binary
valid_ref_identifier?(binary) ->
Module.to_binary(atom)
atom in Macro.binary_ops or atom in Macro.unary_ops ->
":" <> binary
true ->
":" <> escape(binary, ?")
end
end
# Detect if atom is an atom alias (Elixir.Foo.Bar.Baz)
defp valid_ref_identifier?("Elixir" <> rest) do
valid_ref_piece?(rest)
end
defp valid_ref_identifier?(_), do: false
defp valid_ref_piece?(<<?., h, t :: binary>>) when h in ?A..?Z do
valid_ref_piece? valid_identifier?(t)
end
defp valid_ref_piece?(<<>>), do: true
defp valid_ref_piece?(_), do: false
# Detect if atom
defp valid_atom_identifier?(<<h, t :: binary>>) when h in ?a..?z or h in ?A..?Z or h == ?_ do
case valid_identifier?(t) do
<<>> -> true
<<??>> -> true
<<?!>> -> true
_ -> false
end
end
defp valid_atom_identifier?(_), do: false
defp valid_identifier?(<<h, t :: binary>>)
when h in ?a..?z
when h in ?A..?Z
when h in ?0..?9
when h == ?_ do
valid_identifier? t
end
defp valid_identifier?(other), do: other
end
defimpl Binary.Inspect, for: BitString do
import Binary.Inspect.Utils
@moduledoc %B"""
Represents the string as itself escaping
all necessary characters.
## Examples
iex> inspect("bar")
"\"bar\""
iex> inspect("f\"oo")
"\"f\\\"oo\""
"""
def inspect(thing, opts) when is_binary(thing) do
if String.printable?(thing) do
escape(thing, ?")
else
as_bitstring(thing, opts)
end
end
def inspect(thing, opts) do
as_bitstring(thing, opts)
end
## Bitstrings
defp as_bitstring(bitstring, opts) do
"<<" <> each_bit(bitstring, Keyword.get(opts, :limit, :infinity)) <> ">>"
end
defp each_bit(_, 0) do
"..."
end
defp each_bit(<<h, t :: bitstring>>, counter) when t != <<>> do
integer_to_binary(h) <> "," <> each_bit(t, decrement(counter))
end
defp each_bit(<<h :: size(8)>>, _counter) do
integer_to_binary(h)
end
defp each_bit(<<>>, _counter) do
<<>>
end
defp each_bit(bitstring, _counter) do
size = bit_size(bitstring)
<<h :: size(size)>> = bitstring
integer_to_binary(h) <> "::size(" <> integer_to_binary(size) <> ")"
end
defp decrement(:infinity), do: :infinity
defp decrement(counter), do: counter - 1
end
defimpl Binary.Inspect, for: List do
import Binary.Inspect.Utils
@moduledoc %B"""
Represents a list checking if it can be printed or not.
If so, a single-quoted representation is returned,
otherwise the brackets syntax is used.
Inspecting a list is conservative as it does not try
to guess how the list is encoded. That said, `'josé'`
will likely be inspected as `[106,111,115,195,169]`
because we can't know if it is encoded in utf-8
or iso-5569-1, which is common in Erlang libraries.
## Examples
iex> inspect('bar')
"'bar'"
iex> inspect([0|'bar'])
"[0,98,97,114]"
iex> inspect([:foo,:bar])
"[:foo,:bar]"
"""
def inspect([], _), do: "[]"
def inspect(thing, opts) do
cond do
:io_lib.printable_list(thing) ->
escape(:unicode.characters_to_binary(thing), ?')
keyword?(thing) ->
"[" <> join_keywords(thing, opts) <> "]"
true ->
container_join(thing, "[", "]", opts)
end
end
defp join_keywords(thing, opts) do
Enum.join(lc {key, value} inlist thing do
key_to_binary(key, opts) <> ": " <> Kernel.inspect(value, opts)
end, ", ")
end
defp key_to_binary(key, opts) do
case Binary.Inspect.Atom.inspect(key, opts) do
":" <> right -> right
other -> other
end
end
defp keyword?([{ key, _value } | rest]) when is_atom(key) do
case atom_to_list(key) do
'Elixir.' ++ _ -> false
_ -> keyword?(rest)
end
end
defp keyword?([]), do: true
defp keyword?(_other), do: false
end
defimpl Binary.Inspect, for: Tuple do
import Binary.Inspect.Utils
@moduledoc """
Inspect tuples. If the tuple represents a record,
it shows it nicely formatted using the access syntax.
## Examples
iex> inspect({1, 2, 3})
"{1,2,3}"
iex> inspect(ArgumentError.new)
"ArgumentError[message: \\\"argument error\\\"]"
"""
def inspect({}, _), do: "{}"
def inspect(tuple, opts) do
unless opts[:raw] do
record_inspect(tuple, opts)
end || container_join(tuple, "{", "}", opts)
end
## Helpers
defp record_inspect(record, opts) do
list = tuple_to_list(record)
[name|tail] = list
if is_atom(name) && (fields = record_fields(name)) && (length(fields) == size(record) - 1) do
if Enum.first(tail) == :__exception__ do
record_join(name, tl(fields), tl(tail), opts)
else
record_join(name, fields, tail, opts)
end
end
end
defp record_fields(name) do
try do
name.__record__(:fields)
rescue
_ -> nil
end
end
defp record_join(name, fields, tail, opts) do
fields = lc { field, _ } inlist fields, do: field
Binary.Inspect.Atom.inspect(name, opts) <> "[" <>
record_join(fields, tail, opts) <> "]"
end
defp record_join([f], [v], opts) do
atom_to_binary(f, :utf8) <> ": " <> Kernel.inspect(v, opts)
end
defp record_join([fh|ft], [vh|vt], opts) do
atom_to_binary(fh, :utf8) <> ": " <>
Kernel.inspect(vh, opts) <> ", " <>
record_join(ft, vt, opts)
end
defp record_join([], [], _opts) do
""
end
end
defimpl Binary.Inspect, for: Number do
@moduledoc """
Represents the number as a binary.
## Examples
iex> inspect(1)
"1"
"""
@digits 20
@limit :math.pow(10, @digits)
def inspect(thing, _) when is_integer(thing) do
integer_to_binary(thing)
end
def inspect(thing, _) when thing > @limit do
float_to_binary(thing, scientific: @digits)
end
def inspect(thing, _) do
float_to_binary(thing, compact: true, decimals: @digits)
end
end
defimpl Binary.Inspect, for: Regex do
@moduledoc %B"""
Represents the Regex using the `%r""` syntax.
## Examples
iex> inspect(%r/foo/m)
"%r\"foo\"m"
"""
def inspect(regex, _opts) when size(regex) == 5 do
"%r" <> Kernel.inspect(Regex.source(regex), []) <> Regex.opts(regex)
end
def inspect(other, opts) do
Kernel.inspect other, Keyword.put(opts, :raw, true)
end
end
defimpl Binary.Inspect, for: Function do
@moduledoc """
Inspect functions, when possible, in a literal form.
"""
def inspect(function, _opts) do
fun_info = :erlang.fun_info(function)
if fun_info[:type] == :external and fun_info[:env] == [] do
"function(#{Kernel.inspect(fun_info[:module])}.#{fun_info[:name]}/#{fun_info[:arity]})"
else
'#Fun' ++ rest = :erlang.fun_to_list(function)
"#Function" <> list_to_binary(rest)
end
end
end
defimpl Binary.Inspect, for: PID do
@moduledoc "Inspect PIDs"
def inspect(pid, _) do
"#PID" <> list_to_binary pid_to_list(pid)
end
end
defimpl Binary.Inspect, for: Port do
@moduledoc "Inspect ports"
def inspect(port, _) do
list_to_binary :erlang.port_to_list(port)
end
end
defimpl Binary.Inspect, for: Reference do
@moduledoc "Inspect references"
def inspect(ref, _) do
'#Ref' ++ rest = :erlang.ref_to_list(ref)
"#Reference" <> list_to_binary(rest)
end
end
+12 -12
View File
@@ -46,83 +46,83 @@ defmodule Bitwise do
Bitwise not.
"""
defmacro bnot(expr) do
quote do: __op__ :bnot, unquote(expr)
quote do: __op__(:bnot, unquote(expr))
end
@doc """
Bitwise not as operator.
"""
defmacro ~~~expr do
quote do: __op__ :bnot, unquote(expr)
quote do: __op__(:bnot, unquote(expr))
end
@doc """
Bitwise and.
"""
defmacro band(left, right) do
quote do: __op__ :band, unquote(left), unquote(right)
quote do: __op__(:band, unquote(left), unquote(right))
end
@doc """
Bitwise and as operator.
"""
defmacro left &&& right do
quote do: __op__ :band, unquote(left), unquote(right)
quote do: __op__(:band, unquote(left), unquote(right))
end
@doc """
Bitwise or.
"""
defmacro bor(left, right) do
quote do: __op__ :bor, unquote(left), unquote(right)
quote do: __op__(:bor, unquote(left), unquote(right))
end
@doc """
Bitwise or as operator.
"""
defmacro left ||| right do
quote do: __op__ :bor, unquote(left), unquote(right)
quote do: __op__(:bor, unquote(left), unquote(right))
end
@doc """
Bitwise xor.
"""
defmacro bxor(left, right) do
quote do: __op__ :bxor, unquote(left), unquote(right)
quote do: __op__(:bxor, unquote(left), unquote(right))
end
@doc """
Bitwise xor as operator.
"""
defmacro left ^^^ right do
quote do: __op__ :bxor, unquote(left), unquote(right)
quote do: __op__(:bxor, unquote(left), unquote(right))
end
@doc """
Arithmetic bitshift left.
"""
defmacro bsl(left, right) do
quote do: __op__ :bsl, unquote(left), unquote(right)
quote do: __op__(:bsl, unquote(left), unquote(right))
end
@doc """
Arithmetic bitshift left as operator.
"""
defmacro left <<< right do
quote do: __op__ :bsl, unquote(left), unquote(right)
quote do: __op__(:bsl, unquote(left), unquote(right))
end
@doc """
Arithmetic bitshift right.
"""
defmacro bsr(left, right) do
quote do: __op__ :bsr, unquote(left), unquote(right)
quote do: __op__(:bsr, unquote(left), unquote(right))
end
@doc """
Arithmetic bitshift right as operator.
"""
defmacro left >>> right do
quote do: __op__ :bsr, unquote(left), unquote(right)
quote do: __op__(:bsr, unquote(left), unquote(right))
end
end
+85 -90
View File
@@ -6,18 +6,16 @@ defmodule Code do
end
@moduledoc """
The Code module is responsible to manage code compilation,
The Code module is responsible for managing code compilation,
code evaluation and code loading.
It complements (Erlang's code module)[1] to add behavior
which is specific to Elixir.
[1]: (www.erlang.org/doc/man/code.html)
It complements [Erlang's code module](http://www.erlang.org/doc/man/code.html)
to add behavior which is specific to Elixir.
"""
@doc """
Returns all the loaded files.
Returns all loaded files.
"""
def loaded_files do
:elixir_code_server.call :loaded
@@ -26,75 +24,86 @@ defmodule Code do
@doc """
Removes the given files from the loaded files list.
The modules defined in the file are not removed,
calling this function only removes it from the list,
allowing it to be required again.
calling this function only removes them from the list,
allowing them to be required again.
"""
def unload_files(files) do
:elixir_code_server.cast { :unload_files, files }
end
@doc """
Appends a path to Erlang VM code path.
The path is expanded with `Path.expand` before added.
Appends a path to the Erlang VM code path.
The path is expanded with `Path.expand/1` before being appended.
"""
def append_path(path) do
:code.add_pathz(Path.expand to_char_list(path))
end
@doc """
Prepends a path to Erlang VM code path.
The path is expanded with `Path.expand` before added.
Prepends a path to the Erlang VM code path.
The path is expanded with `Path.expand/1` before being prepended.
"""
def prepend_path(path) do
:code.add_patha(Path.expand to_char_list(path))
end
@doc """
Deletes a path from Erlang VM code path.
The path is expanded with `Path.expand` before deleted.
Deletes a path from the Erlang VM code path.
The path is expanded with `Path.expand/1` before being deleted.
"""
def delete_path(path) do
:code.del_path(Path.expand to_char_list(path))
end
@doc """
Evaluates the contents given by string. The second argument is the
binding (which should be a keyword) followed by a keyword list of
Evaluates the contents given by `string`. The second argument is
a keyword list of variable bindings, followed by a keyword list of
environment options. Those options can be:
* `:file` - the file to be considered in the evaluation
* `:line` - the line the script starts
* `:line` - the line on which the script starts
* `:delegate_locals_to` - delegate local calls to the given module,
the default is to not delegate
Besides, the following scope values can be configured:
Additionally, the following scope values can be configured:
* `:aliases` - a list of tuples with the alias and its target
* `:requires` - a list of modules required
* `:functions` - a list of tuples where the first element is a module
and the second a list of imported function names and arity. The list
of function names and arity must be sorted;
of function names and arity must be sorted
* `:macros` - a list of tuples where the first element is a module
and the second a list of imported macro names and arity. The list
of function names and arity must be sorted;
of function names and arity must be sorted
Notice that setting any of the values above overrides Elixir default
Notice that setting any of the values above overrides Elixir's default
values. For example, setting `:requires` to `[]`, will no longer
automatically required the `Kernel` module, in the same way setting
`:macros` will no longer auto-import `Kernel` macros as `if`, `case`,
automatically require the `Kernel` module; in the same way setting
`:macros` will no longer auto-import `Kernel` macros like `if`, `case`,
etc.
Returns a tuple of the form `{ value, binding }`,
where `value` is the the value returned from evaluating `string`; `binding`
is a keyword list with the value of all variable bindings after evaluating
`string`. If an error occurs while evaluating `string` an exception will be raised.
## Examples
iex> Code.eval_string("a + b", [a: 1, b: 2], file: __ENV__.file, line: __ENV__.line)
{ 3, [ {:a, 1}, {:b, 2} ] }
{3, [a: 1, b: 2]}
For convenience, you can my pass `__ENV__` as argument and
all imports, requires and aliases will be automatically carried
over:
iex> Code.eval_string("c = a + b", [a: 1, b: 2], __ENV__)
{3, [a: 1, b: 2, c: 3]}
iex> Code.eval_string("a = a + b", [a: 1, b: 2])
{3, [a: 3, b: 2]}
For convenience, you can pass `__ENV__` as the `opts` argument and
all imports, requires and aliases defined in the current environment
will be automatically carried over:
iex> Code.eval_string("a + b", [a: 1, b: 2], __ENV__)
{ 3, [ {:a, 1}, {:b, 2} ] }
{3, [a: 1, b: 2]}
"""
def eval_string(string, binding // [], opts // [])
@@ -109,29 +118,27 @@ defmodule Code do
end
defp do_eval_string(string, binding, opts) when is_list(binding) do
{ value, binding, _scope } =
:elixir.eval :unicode.characters_to_list(string), binding, opts
{ value, binding, _scope } = :elixir.eval to_char_list(string), binding, opts
{ value, binding }
end
@doc """
Evaluates the quoted contents.
This function accepts a list of environment options.
Check `Code.eval_string` for more information.
See `eval_string/3` for a description of arguments and return values.
## Examples
iex> contents = quote(hygiene: [vars: false], do: a + b)
...> Code.eval_quoted(contents, [a: 1, b: 2], file: __ENV__.file, line: __ENV__.line)
{ 3, [ {:a, 1}, {:b, 2} ] }
{3, [a: 1, b: 2]}
For convenience, you can my pass `__ENV__` as argument and
all options will be automatically extracted from the environment:
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__)
{ 3, [ {:a, 1}, {:b, 2} ] }
{3, [a: 1, b: 2]}
"""
def eval_quoted(quoted, binding // [], opts // [])
@@ -188,36 +195,30 @@ defmodule Code do
end
end
@doc false
def string_to_ast(string, opts // []) do
IO.write "[WARNING] Code.string_to_ast is deprecated, please use Code.string_to_quoted instead\n#{Exception.format_stacktrace}"
string_to_quoted(string, opts)
end
@doc """
Converts the given string to quoted form. It returns `{ :ok, quoted_form }`
Converts the given string to its quoted form. Returns `{ :ok, quoted_form }`
if it succeeds, `{ :error, { line, error, token } }` otherwise.
## Options
* `:file` - The filename to be used in stacktraces
and the file reported in the __ENV__ variable.
and the file reported in the `__ENV__` variable.
* `:line` - The line reported in the __ENV__ variable.
* `:line` - The line reported in the `__ENV__` variable.
* `:existing_atoms_only` - When true, raises an error
* `:existing_atoms_only` - When `true`, raises an error
when non-existing atoms are found by the tokenizer.
## Macro.to_string/1
The opposite of converting a string to its quoted form is
`Macro.to_string`, which converts a quoted form to a string/binary
`Macro.to_string/1`, which converts a quoted form to a string/binary
representation.
"""
def string_to_quoted(string, opts // []) do
def string_to_quoted(string, opts // []) when is_list(opts) do
file = Keyword.get opts, :file, "nofile"
line = Keyword.get opts, :line, 1
res = :elixir_translator.forms(:unicode.characters_to_list(string), line, file, opts)
res = :elixir_translator.forms(to_char_list(string), line, file, opts)
case res do
{ :ok, forms } -> { :ok, unpack_quote(line, forms) }
@@ -225,24 +226,18 @@ defmodule Code do
end
end
@doc false
def string_to_ast!(string, opts // []) do
IO.write "[WARNING] Code.string_to_ast! is deprecated, please use Code.string_to_quoted! instead\n#{Exception.format_stacktrace}"
string_to_quoted!(string, opts)
end
@doc """
Converts the given string to quoted form. It returns the ast if it succeeds,
raises an exception otherwise. The exception is a TokenMissingError
Converts 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),
SyntaxError otherwise.
`SyntaxError` otherwise.
Check `Code.string_to_quoted/2` for options information.
Check `string_to_quoted/2` for options information.
"""
def string_to_quoted!(string, opts // []) do
def string_to_quoted!(string, opts // []) when is_list(opts) do
file = Keyword.get opts, :file, "nofile"
line = Keyword.get opts, :line, 1
res = :elixir_translator.forms!(:unicode.characters_to_list(string), line, file, opts)
res = :elixir_translator.forms!(to_char_list(string), line, file, opts)
unpack_quote(line, res)
end
@@ -253,14 +248,14 @@ defmodule Code do
@doc """
Loads 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
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 invoked concurrently
in many times. I.e. if `load_file` is called N times with
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` if you don't want a file to be loaded concurrently.
`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)
@@ -272,16 +267,16 @@ defmodule Code do
@doc """
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`. If
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
ones will block until the file is available. I.e. if `require_file` is called
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.
call `require_file` will get the list of loaded modules, others will get `nil`.
Check `load_file` if you want a file to be loaded concurrently.
Check `load_file/2` if you want a file to be loaded concurrently.
"""
def require_file(file, relative_to // nil) when is_binary(file) do
file = find_file(file, relative_to)
@@ -307,22 +302,22 @@ defmodule Code do
end
@doc """
Sets compilation options. Those options are global
Sets compilation options. These options are global
since they are stored by Elixir's Code Server.
Available options are:
* `:docs` - when true, retain documentation in the compiled module.
True by default;
* `:docs` - when `true`, retain documentation in the compiled module,
`true` by default;
* `:debug_info` - when true, retain debug information in the compiled module.
* `:debug_info` - when `true`, retain debug information in the compiled module.
This allows a developer to reconstruct the original source
code, for such reasons, false by default;
code, for such reasons, `false` by default;
* `:ignore_module_conflict` - when true, override modules that were already defined
without raising errors, false by default;
* `:ignore_module_conflict` - when `true`, override modules that were already defined
without raising errors, `false` by default;
* `:warnings_as_errors` - cause compilation to fail when warnings are spewed;
* `:warnings_as_errors` - cause compilation to fail when warnings are generated;
"""
def compiler_options(opts) do
@@ -334,10 +329,10 @@ defmodule Code do
the first element is the module name and the second one is its
binary.
For compiling many files at once, check `Kernel.ParallelCompiler`.
For compiling many files at once, check `Kernel.ParallelCompiler.files/2`.
"""
def compile_string(string, file // "nofile") when is_binary(file) do
:elixir_compiler.string :unicode.characters_to_list(string), file
:elixir_compiler.string String.to_char_list!(string), file
end
@doc """
@@ -351,10 +346,10 @@ defmodule Code do
@doc """
Ensures the given module is loaded. If the module is already
loaded, it works as no-op. If the module was not loaded yet,
loaded, it works as no-op. If the module was not yet loaded,
it tries to load it.
If it succeeds loading the module anyhow, it returns
If it succeeds loading the module, it returns
`{ :module, module }`. If not, returns `{ :error, reason }` with
the error reason.
@@ -362,13 +357,13 @@ defmodule Code do
Erlang has two modes to load code: interactive and embedded.
By default, the Erlang VM runs on interactive mode, where modules
By default, the Erlang VM runs in interactive mode, where modules
are loaded as needed. In embedded mode the opposite happens, as all
modules need to be loaded upfront or explicitly.
Therefore, this function is useful to check if a module is loaded
before using it and react accordingly. For example, the `URI` module
uses this function to check if a specific parser exists for a given
Therefore, this function is used to check if a module is loaded
before using it and allows one to react accordingly. For example, the `URI`
module uses this function to check if a specific parser exists for a given
URI scheme.
## Code.ensure_compiled
@@ -377,15 +372,15 @@ defmodule Code do
superset of `ensure_loaded/1`.
Since Elixir's compilation happens in parallel, in some situations
you may need to use a module but it was not compiled yet, therefore
you may need to use a module but that was not yet compiled, therefore
it can't even be loaded.
`ensure_compiled/1` puts a halt in the current process until the
`ensure_compiled/1` halts the current process until the
module we are depending on is available.
In most of the cases, `ensure_loaded` is enough. `ensure_compiled`
must be used just in same rare conditions, usually involving macros
that needs to invoke a module for callback information.
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.
"""
def ensure_loaded(module) when is_atom(module) do
:code.ensure_loaded(module)
@@ -402,10 +397,10 @@ defmodule Code do
@doc """
Ensures 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 just
loaded yet, it checks if it needs to be compiled first and
then tries to load it.
If it succeeds loading the module anyhow, it returns
If it succeeds loading the module, it returns
`{ :module, module }`. If not, returns `{ :error, reason }` with
the error reason.
+86 -63
View File
@@ -1,5 +1,5 @@
defmodule Dict do
@moduledoc %B"""
@moduledoc %S"""
This module specifies the Dict API expected to be
implemented by different dictionaries. It also provides
functions that redirect to the underlying Dict, allowing
@@ -11,25 +11,21 @@ defmodule Dict do
HashDict.new #=> creates an empty HashDict
For simplicity's sake, in the examples below everytime
`new` is used, it implies one of the module-specific
calls like above. Likewise, when the result of a function
invocation is shown in the form `[a: 1, b: 2]`, it implies
that the returned value is actually of the same dict type
as the input one.
In the examples below, `dict_impl` means a specific
`Dict` implementation, for example `HashDict` or `ListDict`.
## Protocols
Besides implementing the functions in this module, all
dictionaries are also required to implement the `Access`
dictionaries are required to implement the `Access`
protocol:
iex> dict = HashDict.new
iex> dict = dict_impl.new
...> dict = Dict.put(dict, :hello, :world)
...> dict[:hello]
:world
And also the `Enumerable` protocol, allowing one to write:
And the `Enumerable` protocol, allowing one to write:
Enum.each(dict, fn ({ k, v }) ->
IO.puts "#{k}: #{v}"
@@ -44,12 +40,16 @@ defmodule Dict do
@type keys :: [ key ]
@type t :: tuple | list
defcallback new :: t
defcallback new(Keyword.t) :: t
defcallback delete(t, key) :: t
defcallback drop(t, keys) :: t
defcallback empty(t) :: t
defcallback equal?(t, t) :: boolean
defcallback get(t, key) :: value
defcallback get(t, key, value) :: value
defcallback fetch(t, key) :: { :ok, value } | :error
defcallback fetch!(t, key) :: value | no_return
defcallback has_key?(t, key) :: boolean
defcallback keys(t) :: list(key)
defcallback merge(t, t) :: t
@@ -62,9 +62,10 @@ defmodule Dict do
defcallback split(t, keys) :: {t, t}
defcallback take(t, keys) :: t
defcallback to_list(t) :: list()
defcallback update(t, key, (value -> value)) :: t | no_return
defcallback update(t, key, value, (value -> value)) :: t
defcallback update!(t, key, (value -> value)) :: t | no_return
defcallback values(t) :: list(value)
defcallback reduce(t, any, ({key, value}, any -> any)) :: any
defmacrop target(dict) do
quote do
@@ -73,18 +74,19 @@ defmodule Dict do
elem(unquote(dict), 0)
is_list(unquote(dict)) ->
ListDict
true ->
unsupported_dict(unquote(dict))
end
end
end
@doc """
Returns a list containing all dict's keys.
The keys are not guaranteed to be sorted, unless
the underlying dict implementation defines so.
Returns a list of all keys in `dict`.
The keys are not guaranteed to be in any order.
## Examples
iex> d = HashDict.new([a: 1, b: 2])
iex> d = dict_impl.new([a: 1, b: 2])
...> Enum.sort(Dict.keys(d))
[:a,:b]
@@ -95,11 +97,12 @@ defmodule Dict do
end
@doc """
Returns a list containing all dict's values.
Returns a list of all values in `dict`
The values are not guaranteed to be in any order.
## Examples
iex> d = HashDict.new([a: 1, b: 2])
iex> d = dict_impl.new([a: 1, b: 2])
...> Enum.sort(Dict.values(d))
[1,2]
@@ -114,7 +117,7 @@ defmodule Dict do
## Examples
iex> d = HashDict.new([a: 1, b: 2])
iex> d = dict_impl.new([a: 1, b: 2])
...> Dict.size(d)
2
@@ -125,11 +128,11 @@ defmodule Dict do
end
@doc """
Returns whether the given key exists in the given dict.
Returns whether the given `key` exists in the given `dict`.
## Examples
iex> d = HashDict.new([a: 1])
iex> d = dict_impl.new([a: 1])
iex> Dict.has_key?(d, :a)
true
iex> Dict.has_key?(d, :b)
@@ -143,11 +146,11 @@ defmodule Dict do
@doc """
Returns the value associated with `key` in `dict`. If `dict` does not
contain `key`, returns `default` (or nil if not provided).
contain `key`, returns `default` (or `nil` if not provided).
## Examples
iex> d = HashDict.new([a: 1])
iex> d = dict_impl.new([a: 1])
iex> Dict.get(d, :a)
1
iex> Dict.get(d, :b)
@@ -160,18 +163,13 @@ defmodule Dict do
target(dict).get(dict, key, default)
end
@doc false
def get!(dict, key) do
target(dict).get!(dict, key)
end
@doc """
Returns the `{ :ok, value }` associated with `key` in `dict`.
Returns `{ :ok, value }` associated with `key` in `dict`.
If `dict` does not contain `key`, returns `:error`.
## Examples
iex> d = HashDict.new([a: 1])
iex> d = dict_impl.new([a: 1])
iex> Dict.fetch(d, :a)
{ :ok, 1 }
iex> Dict.fetch(d, :b)
@@ -189,7 +187,7 @@ defmodule Dict do
## Examples
iex> d = HashDict.new([a: 1])
iex> d = dict_impl.new([a: 1])
iex> Dict.fetch!(d, :a)
1
iex> Dict.fetch!(d, :b)
@@ -207,7 +205,7 @@ defmodule Dict do
## Examples
iex> d = HashDict.new([a: 1, b: 2])
iex> d = dict_impl.new([a: 1, b: 2])
...> d = Dict.put(d, :a, 3)
...> Dict.get(d, :a)
3
@@ -223,7 +221,7 @@ defmodule Dict do
## Examples
iex> d = HashDict.new([a: 1, b: 2])
iex> d = dict_impl.new([a: 1, b: 2])
...> d = Dict.put_new(d, :a, 3)
...> Dict.get(d, :a)
1
@@ -235,17 +233,17 @@ defmodule Dict do
end
@doc """
Removes the entry stored under the given key from `dict`.
Removes the entry stored under the given `key` from `dict`.
If `dict` does not contain `key`, returns the dictionary unchanged.
## Examples
iex> d = HashDict.new([a: 1, b: 2])
iex> d = dict_impl.new([a: 1, b: 2])
...> d = Dict.delete(d, :a)
...> Dict.get(d, :a)
nil
iex> d = HashDict.new([b: 2])
iex> d = dict_impl.new([b: 2])
...> Dict.delete(d, :a) == d
true
@@ -256,13 +254,17 @@ defmodule Dict do
end
@doc """
Merges the given enum into the dict. In case one of the enum entries
alread exist in the dict, it is given higher preference.
Merges the given `enum` into `dict`. If one of the `enum` keys
already exists in `dict`, the `dict` value is replaced by the `enum`
value.
The `enum` must yield tuples with two elements on enumeration,
where the first element represents the key and the second the value.
## Examples
iex> d1 = HashDict.new([a: 1, b: 2])
...> d2 = HashDict.new([a: 3, d: 4])
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)]
[a: 3, b: 2, d: 4]
@@ -274,14 +276,17 @@ defmodule Dict do
end
@doc """
Merges the given enum into the dict. In case one of the enum entries
alread exist in the dict, the given function is invoked to solve
conflicts.
Merges the given `enum` into `dict`. If one of the `enum` entries
already exists in `dict`, the given function is invoked to resolve
the conflict.
The `enum` must yield tuples with two elements on enumeration,
where the first element represents the key and the second the value.
## Examples
iex> d1 = HashDict.new([a: 1, b: 2])
...> d2 = HashDict.new([a: 3, 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) ->
...> v1 + v2
...> end)
@@ -300,17 +305,17 @@ defmodule Dict do
## Examples
iex> dict = HashDict.new [a: 1]
iex> dict = dict_impl.new [a: 1]
...> {v, d} = Dict.pop dict, :a
...> {v, Enum.sort(d)}
{1,[]}
iex> dict = HashDict.new [a: 1]
iex> dict = dict_impl.new [a: 1]
...> {v, d} = Dict.pop dict, :b
...> {v, Enum.sort(d)}
{nil,[a: 1]}
iex> dict = HashDict.new [a: 1]
iex> dict = dict_impl.new [a: 1]
...> {v, d} = Dict.pop dict, :b, 3
...> {v, Enum.sort(d)}
{3,[a: 1]}
@@ -327,13 +332,18 @@ defmodule Dict do
## Examples
iex> d = HashDict.new([a: 1, b: 2])
...> d = Dict.update(d, :a, fn(val) -> -val end)
iex> d = dict_impl.new([a: 1, b: 2])
...> d = Dict.update!(d, :a, fn(val) -> -val end)
...> Dict.get(d, :a)
-1
"""
@spec update(t, key, (value -> value)) :: t
@spec update!(t, key, (value -> value)) :: t
def update!(dict, key, fun) do
target(dict).update!(dict, key, fun)
end
@doc false
def update(dict, key, fun) do
target(dict).update(dict, key, fun)
end
@@ -345,7 +355,7 @@ defmodule Dict do
## Examples
iex> d = HashDict.new([a: 1, b: 2])
iex> d = dict_impl.new([a: 1, b: 2])
...> d = Dict.update(d, :c, 3, fn(val) -> -val end)
...> Dict.get(d, :c)
3
@@ -365,17 +375,17 @@ defmodule Dict do
## Examples
iex> d = HashDict.new([a: 1, b: 2])
...> { d1, d2 } = Dict.split(d, [:a, :c])
iex> d = dict_impl.new([a: 1, b: 2, c: 3, d: 4])
...> { d1, d2 } = Dict.split(d, [:a, :c, :e])
...> { Dict.to_list(d1), Dict.to_list(d2) }
{ [a: 1], [b: 2] }
{ [a: 1, c: 3], [b: 2, d: 4] }
iex> d = HashDict.new([])
iex> d = dict_impl.new([])
...> { d1, d2 } = Dict.split(d, [:a, :c])
...> { Dict.to_list(d1), Dict.to_list(d2) }
{ [], [] }
iex> d = HashDict.new([a: 1, b: 2])
iex> d = dict_impl.new([a: 1, b: 2])
...> { d1, d2 } = Dict.split(d, [:a, :b, :c])
...> { Dict.to_list(d1), Dict.to_list(d2) }
{ [a: 1, b: 2], [] }
@@ -392,12 +402,12 @@ defmodule Dict do
## Examples
iex> d = HashDict.new([a: 1, b: 2])
iex> d = dict_impl.new([a: 1, b: 2])
...> d = Dict.drop(d, [:a, :c, :d])
...> Dict.to_list(d)
[b: 2]
iex> d = HashDict.new([a: 1, b: 2])
iex> d = dict_impl.new([a: 1, b: 2])
...> d = Dict.drop(d, [:c, :d])
...> Dict.to_list(d)
[a: 1, b: 2]
@@ -410,11 +420,12 @@ defmodule Dict do
@doc """
Returns a new dict where only the keys in `keys` from `dict` are
included. Any non-member keys are ignored.
included.
Any non-member keys are ignored.
## Examples
iex> d = HashDict.new([a: 1, b: 2])
iex> d = dict_impl.new([a: 1, b: 2])
...>
...> d = Dict.take(d, [:a, :c, :d])
...> Dict.to_list(d)
@@ -432,6 +443,14 @@ defmodule Dict do
@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)
[]
"""
@spec empty(t) :: t
def empty(dict) do
@@ -439,17 +458,17 @@ defmodule Dict do
end
@doc """
Check if two dicts are equal, if the dicts are of different types they're
Check if two dicts are equal. If the dicts are of different types, they are
first converted to lists.
## Examples
iex> a = HashDict.new(a: 2, b: 3, f: 5, c: 123)
iex> a = dict_impl.new(a: 2, b: 3, f: 5, c: 123)
...> b = ListDict.new(a: 2, b: 3, f: 5, c: 123)
...> Dict.equal?(a, b)
true
iex> a = HashDict.new(a: 2, b: 3, f: 5, c: 123)
iex> a = dict_impl.new(a: 2, b: 3, f: 5, c: 123)
...> b = []
...> Dict.equal?(a, b)
false
@@ -480,4 +499,8 @@ defmodule Dict do
def to_list(dict) do
target(dict).to_list(dict)
end
defp unsupported_dict(dict) do
raise ArgumentError, message: "unsupported dict: #{inspect dict}"
end
end
+125
View File
@@ -0,0 +1,125 @@
defmodule Dict.Behaviour do
@moduledoc """
This module makes it easier to create your own `Dict` compliant
module, by providing default implementations for some required functions.
Usage:
defmodule MyDict do
use Dict.Behaviour
# implement required functions (see below)
# override default implementations if needed
end
The client module must contain following functions: `size/1`, `fetch/2`,
`put/3`, `update/4`, `delete/2` and `reduce/3`. All of them are part of
the Dict behaviour, so no extra functions are actually required.
Based on these functions, `Dict.Behaviour` generates default implementations
for other functions such as `drop`, `take`, etc. All of the functions are
defined as overridable, so you can provide your own implementation if
needed.
If you implement `new/0` and `new/1` functions, you can also test your custom
module via `Dict` doctests:
defmodule MyDict do
def new(keywords // []) do
...
end
end
defmodule MyTests do
use ExUnit.Case
doctest Dict
defp dict_impl, do: MyDict
end
"""
defmacro __using__(_) do
quote do
@behaviour Dict
def get(dict, key, default // nil) do
case fetch(dict, key) do
{ :ok, value } -> value
:error -> default
end
end
defoverridable get: 2, get: 3
def fetch!(dict, key) do
case fetch(dict, key) do
{ :ok, value } -> value
:error -> raise(KeyError, key: key)
end
end
defoverridable fetch!: 2
def has_key?(dict, key) do
match? { :ok, _ }, fetch(dict, key)
end
defoverridable has_key?: 2
def put_new(dict, key, value) do
update(dict, key, value, fn(v) -> v end)
end
defoverridable put_new: 3
def drop(dict, []), do: dict
def drop(dict, [key|keys]) do
drop(delete(dict, key), keys)
end
defoverridable drop: 2
def take(dict, keys) do
take(dict, keys, new)
end
defoverridable take: 2
defp take(_dict, [], acc), do: acc
defp take(dict, [key|keys], acc) do
case fetch(dict, key) do
{ :ok, value } -> take(dict, keys, put(acc, key, value))
:error -> take(dict, keys, acc)
end
end
def to_list(dict), do: reduce(dict, [], &[&1|&2]) |> Enum.reverse
defoverridable to_list: 1
def keys(dict), do: reduce(dict, [], fn({k, _}, acc) -> [k | acc] end) |> Enum.reverse
defoverridable keys: 1
def values(dict), do: reduce(dict, [], fn({_, v}, acc) -> [v | acc] end) |> Enum.reverse
defoverridable values: 1
def equal?(dict1, dict2) do
case size(dict1) == size(dict2) do
false -> false
true ->
try do
reduce(dict1, nil, fn({ k, v }, _acc) ->
unless fetch(dict2, k) == { :ok, v }, do: throw(:error)
end)
true
catch
:error -> false
end
end
end
defoverridable equal?: 2
def merge(dict, enumerable, callback // fn(_k, _v1, v2) -> v2 end) do
Enum.reduce(enumerable, dict, fn({key, value}, acc) ->
update(acc, key, value, fn(v1) -> callback.(key, v1, value) end)
end)
end
defoverridable merge: 2, merge: 3
end
end
end
+506 -186
View File
File diff suppressed because it is too large Load Diff
+85 -44
View File
@@ -5,19 +5,19 @@ defexception SystemLimitError, message: "a system limit has been reached"
defexception SyntaxError, [file: nil, line: nil, description: "syntax error"] do
def message(exception) do
"#{Exception.format_file_line(exception.file, exception.line, nil)}#{exception.description}"
"#{Exception.format_file_line(exception.file, exception.line)}#{exception.description}"
end
end
defexception TokenMissingError, [file: nil, line: nil, description: "expression is incomplete"] do
def message(exception) do
"#{Exception.format_file_line(exception.file, exception.line, nil)}#{exception.description}"
"#{Exception.format_file_line(exception.file, exception.line)}#{exception.description}"
end
end
defexception CompileError, [file: nil, line: nil, description: "compile error"] do
def message(exception) do
"#{Exception.format_file_line(exception.file, exception.line, nil)}#{exception.description}"
"#{Exception.format_file_line(exception.file, exception.line)}#{exception.description}"
end
end
@@ -48,7 +48,7 @@ end
defexception UndefinedFunctionError, [module: nil, function: nil, arity: nil] do
def message(exception) do
if exception.function do
formatted = Exception.format_module_fun_arity exception.module, exception.function, exception.arity
formatted = Exception.format_mfa exception.module, exception.function, exception.arity
"undefined function: #{formatted}"
else
"undefined function"
@@ -59,7 +59,7 @@ end
defexception FunctionClauseError, [module: nil, function: nil, arity: nil] do
def message(exception) do
if exception.function do
formatted = Exception.format_module_fun_arity exception.module, exception.function, exception.arity
formatted = Exception.format_mfa exception.module, exception.function, exception.arity
"no function clause matching in #{formatted}"
else
"no function clause matches"
@@ -67,11 +67,11 @@ defexception FunctionClauseError, [module: nil, function: nil, arity: nil] do
end
end
defexception Protocol.UndefinedError, [protocol: nil, structure: nil, extra: nil] do
defexception Protocol.UndefinedError, [protocol: nil, value: nil, description: nil] do
def message(exception) do
msg = "protocol #{inspect exception.protocol} not implemented for #{inspect exception.structure}"
if exception.extra do
msg <> ". " <> exception.extra
msg = "protocol #{inspect exception.protocol} not implemented for #{inspect exception.value}"
if exception.description do
msg <> ", " <> exception.description
else
msg
end
@@ -96,12 +96,21 @@ defexception Enum.EmptyError, message: "empty error"
defmodule Exception do
@moduledoc """
Several convenience functions to work and pretty print
Several convenience functions to work with and pretty print
exceptions and stacktraces.
"""
@doc """
Normalizes an exception converting Erlang exceptions
Normalizes an exception, converting Erlang exceptions
to Elixir exceptions. It takes the kind spilled by
`catch` as an argument as a convenience for converting only
`:errors`, ignorning the others.
"""
def normalize(:error, exception), do: normalize(exception)
def normalize(_kind, other), do: other
@doc """
Normalizes an exception, converting Erlang exceptions
to Elixir exceptions. Useful when interfacing Erlang
code with Elixir code.
"""
@@ -157,39 +166,40 @@ defmodule Exception do
@doc """
Receives a tuple representing a stacktrace entry and formats it.
The current working directory may be given as argument, which
is used to prettify the stacktrace.
The current working directory may be given as an argument,
otherwise one is automatically retrieved.
"""
def format_stacktrace_entry(entry, cwd // nil)
# From Macro.Env.stacktrace
def format_stacktrace_entry({ module, :__MODULE__, 0, file_line }, cwd) do
"#{format_file_line(file_line, cwd)}#{inspect module} (module)"
"#{format_location(file_line, cwd)}#{inspect module} (module)"
end
# From :elixir_compiler
# From :elixir_compiler_*
def format_stacktrace_entry({ _module, :__MODULE__, 2, file_line }, cwd) do
"#{format_file_line(file_line, cwd)}(module)"
"#{format_location(file_line, cwd)}(module)"
end
# From :elixir_compiler
# From :elixir_compiler_*
def format_stacktrace_entry({ _module, :__FILE__, 2, file_line }, cwd) do
"#{format_file_line(file_line, cwd)}(file)"
"#{format_location(file_line, cwd)}(file)"
end
def format_stacktrace_entry({module, fun, arity, file_line}, cwd) do
"#{format_file_line(file_line, cwd)}#{format_module_fun_arity(module, fun, arity)}"
"#{format_location(file_line, cwd)}#{format_mfa(module, fun, arity)}"
end
def format_stacktrace_entry({fun, arity, file_line}, cwd) do
"#{format_file_line(file_line, cwd)}#{format_fun_arity(fun, arity)}"
"#{format_location(file_line, cwd)}#{format_fa(fun, arity)}"
end
@doc """
Formats the stacktrace.
A stacktrace must be given as argument. If not, this function
calculates the current stacktrace and formats it. As consequence,
A stacktrace must be given as an argument. If not, this function
calculates the current stacktrace and formats it. As a consequence,
the value of `System.stacktrace` is changed.
"""
def format_stacktrace(trace // nil) do
@@ -199,15 +209,17 @@ defmodule Exception do
:stacktrace -> Enum.drop(:erlang.get_stacktrace, 1)
end
cwd = System.cwd
case trace do
[] -> "\n"
s -> " " <> Enum.map_join(s, "\n ", format_stacktrace_entry(&1)) <> "\n"
s -> " " <> Enum.map_join(s, "\n ", &format_stacktrace_entry(&1, cwd)) <> "\n"
end
end
@doc """
Formats the caller, i.e. the first entry in the stacktrace.
Notice that doing to tail call optimization, the stacktrace
Notice that due to tail call optimization, the stacktrace
may not report the direct caller of the function.
"""
def format_caller(trace // nil) do
@@ -224,11 +236,17 @@ defmodule Exception do
end
end
## Helpers
@doc """
Receives an anonymous function and arity and formats it as
shown in stacktraces. The arity may also be a list of arguments.
# Format fun and arity
@doc false
def format_fun_arity(fun, arity) do
## Examples
Exception.format_fa(fn -> end, 1)
#=> "#Function<...>/1"
"""
def format_fa(fun, arity) do
if is_list(arity) do
inspected = lc x inlist arity, do: inspect(x)
"#{inspect fun}(#{Enum.join(inspected, ", ")})"
@@ -237,11 +255,20 @@ defmodule Exception do
end
end
# Receives a module, fun and arity and returns a string
# representing such invocation. Arity may also be a list
# of arguments. It follows the same syntax as in stacktraces.
@doc false
def format_module_fun_arity(module, fun, arity) do
@doc """
Receives a module, fun and arity and formats it
as shown in stacktraces. The arity may also be a list
of arguments.
## Examples
iex> Exception.format_mfa Foo, :bar, 1
"Foo.bar/1"
iex> Exception.format_mfa Foo, :bar, []
"Foo.bar()"
"""
def format_mfa(module, fun, arity) do
fun =
case inspect(fun) do
<< ?:, erl :: binary >> -> erl
@@ -256,19 +283,29 @@ defmodule Exception do
end
end
# Format file and line for exception printing.
@doc false
def format_file_line(opts, cwd) do
format_file_line Keyword.get(opts, :file), Keyword.get(opts, :line), cwd
end
@doc """
Formats the given file and line as shown in stacktraces.
If any of the values are nil, they are omitted.
def format_file_line(file, line, cwd) do
The current working directory may be given as an argument,
otherwise one is automatically retrieved.
## Examples
iex> Exception.format_file_line("foo", 1)
"foo:1: "
iex> Exception.format_file_line("foo", nil)
"foo: "
iex> Exception.format_file_line(nil, nil)
""
"""
def format_file_line(file, line, cwd // nil) do
if file do
file = to_binary(file)
if cwd do
file = Path.relative_to(file, cwd)
end
file = to_string(file)
file = if cwd, do: Path.relative_to(file, cwd), else: Path.relative_to_cwd(file)
if line && line != 0 do
"#{file}:#{line}: "
@@ -280,6 +317,10 @@ defmodule Exception do
end
end
defp format_location(opts, cwd) do
format_file_line Keyword.get(opts, :file), Keyword.get(opts, :line), cwd
end
defp from_stacktrace([{ module, function, args, _ }|_]) when is_list(args) do
{ module, function, length(args) }
end
+157 -186
View File
@@ -33,14 +33,14 @@ end
defexception File.Error, [reason: nil, action: "", path: nil] do
def message(exception) do
formatted = list_to_binary(:file.format_error(reason exception))
formatted = iolist_to_binary(:file.format_error(reason exception))
"could not #{action exception} #{path exception}: #{formatted}"
end
end
defexception File.CopyError, [reason: nil, action: "", source: nil, destination: nil, on: nil] do
def message(exception) do
formatted = list_to_binary(:file.format_error(reason exception))
formatted = iolist_to_binary(:file.format_error(reason exception))
location = if on = on(exception), do: ". #{on}", else: ""
"could not #{action exception} from #{source exception} to " <>
"#{destination exception}#{location}: #{formatted}"
@@ -49,7 +49,7 @@ end
defexception File.IteratorError, reason: nil do
def message(exception) do
formatted = list_to_binary(:file.format_error(reason exception))
formatted = iolist_to_binary(:file.format_error(reason exception))
"error during file iteration: #{formatted}"
end
end
@@ -59,20 +59,20 @@ defmodule File do
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 `File.open/2`,
`File.copy/3` and others. This module also provides higher
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
based on UNIX variants. For example, one can copy a file
via `File.cp/3` and remove files and directories recursively
via `File.rm_rf/2`
via `cp/3` and remove files and directories recursively
via `rm_rf/1`
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`, `IO.binwrite` and
`IO.binreadline` to interact with the file. A developer may
pass `:utf8` as an option when opening the file and then all
other functions from IO are available, since they work directly
with Unicode data.
in the `IO` module. By default, a file is opened in binary mode
which requires the functions `IO.binread/2` and `IO.binwrite/2`
to interact with the file. A developer may pass `:utf8` as an
option when opening the file and then all other functions
from `IO` are available, since they work directly with Unicode
data.
Most of the functions in this module return `:ok` or
`{ :ok, result }` in case of success, `{ :error, reason }`
@@ -104,7 +104,7 @@ defmodule File do
alias :filelib, as: FL
@doc """
Returns true if the path is a regular file.
Returns `true` if the path is a regular file.
## Examples
@@ -116,14 +116,14 @@ defmodule File do
end
@doc """
Returns true if the path is a directory.
Returns `true` if the path is a directory.
"""
def dir?(path) do
FL.is_dir(path)
end
@doc """
Returns true if the given path exists.
Returns `true` if the given path exists.
It can be regular file, directory, socket,
symbolic link, named pipe or device file.
@@ -161,13 +161,13 @@ defmodule File do
end
@doc """
Same as `mkdir`, but raises an exception in case of failure. Otherwise `:ok`.
Same as `mkdir/1`, but raises an exception in case of failure. Otherwise `:ok`.
"""
def mkdir!(path) do
case mkdir(path) do
:ok -> :ok
{ :error, reason } ->
raise File.Error, reason: reason, action: "make directory", path: :unicode.characters_to_binary(path)
raise File.Error, reason: reason, action: "make directory", path: to_string(path)
end
end
@@ -186,13 +186,13 @@ defmodule File do
end
@doc """
Same as `mkdir_p`, but raises an exception in case of failure. Otherwise `:ok`.
Same as `mkdir_p/1`, but raises an exception in case of failure. Otherwise `:ok`.
"""
def mkdir_p!(path) do
case mkdir_p(path) do
:ok -> :ok
{ :error, reason } ->
raise File.Error, reason: reason, action: "make directory (with -p)", path: :unicode.characters_to_binary(path)
raise File.Error, reason: reason, action: "make directory (with -p)", path: to_string(path)
end
end
@@ -210,7 +210,7 @@ defmodule File do
On some platforms, `:enoent` is returned instead.
* :enomem - There is not enough memory for the contents of the file.
You can use `:file.format_error(reason)` to get a descriptive string of the error.
You can use `:file.format_error/1` to get a descriptive string of the error.
"""
def read(path) do
F.read_file(path)
@@ -218,14 +218,14 @@ defmodule File do
@doc """
Returns binary with the contents of the given filename or raises
File.Error if an error occurs.
`File.Error` if an error occurs.
"""
def read!(path) do
case read(path) do
{ :ok, binary } ->
binary
{ :error, reason } ->
raise File.Error, reason: reason, action: "read file", path: :unicode.characters_to_binary(path)
raise File.Error, reason: reason, action: "read file", path: to_string(path)
end
end
@@ -233,34 +233,34 @@ defmodule File do
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
the same reasons as `File.read` if a failure occurs.
the same reasons as `read/1` if a failure occurs.
## Options
The accepted options are:
* `:time` if the time should be local, universal or posix.
Default is local.
* `:time` if the time should be `:local`, `:universal` or `:posix`.
Default is `:local`.
"""
def stat(path, opts // []) do
case F.read_file_info(path, opts) do
{:ok, fileinfo} ->
{:ok, File.Stat.new fileinfo}
{:ok, set_elem(fileinfo, 0, File.Stat) }
error ->
error
end
end
@doc """
Same as `stat` but returns the `File.Stat` directly and
Same as `stat/2` but returns the `File.Stat` directly and
throws `File.Error` if an error is returned.
"""
def stat!(path, opts // []) do
case stat(path, opts) do
{:ok, info} -> info
{:error, reason} ->
raise File.Error, reason: reason, action: "read file stats", path: :unicode.characters_to_binary(path)
raise File.Error, reason: reason, action: "read file stats", path: to_string(path)
end
end
@@ -280,7 +280,7 @@ defmodule File do
case write_stat(path, stat, opts) do
:ok -> :ok
{ :error, reason } ->
raise File.Error, reason: reason, action: "write file stats", path: :unicode.characters_to_binary(path)
raise File.Error, reason: reason, action: "write file stats", path: to_string(path)
end
end
@@ -290,20 +290,23 @@ defmodule File do
"""
def touch(path, time // :calendar.local_time) do
case F.change_time(path, time) do
{ :error, :enoent } -> write(path, "")
other -> other
{ :error, :enoent } ->
write(path, "")
F.change_time(path, time)
other ->
other
end
end
@doc """
Same as `touch/1` but raises an exception if it fails.
Same as `touch/2` but raises an exception if it fails.
Returns `:ok` otherwise.
"""
def touch!(path, time // :calendar.local_time) do
case touch(path, time) do
:ok -> :ok
{ :error, reason } ->
raise File.Error, reason: reason, action: "touch", path: :unicode.characters_to_binary(path)
raise File.Error, reason: reason, action: "touch", path: to_string(path)
end
end
@@ -311,7 +314,7 @@ defmodule File do
Copies the contents of `source` to `destination`.
Both parameters can be a filename or an io device opened
with `File.open/2`. `bytes_count` specifies the number of
with `open/2`. `bytes_count` specifies the number of
bytes to copy, the default being `:infinity`.
If file `destination` already exists, it is overwritten
@@ -320,14 +323,14 @@ defmodule File do
Returns `{ :ok, bytes_copied }` if successful,
`{ :error, reason }` otherwise.
Compared to the `File.cp/3`, this function is more low-level,
Compared to the `cp/3`, this function is more low-level,
allowing a copy from device to device limited by a number of
bytes. On the other hand, `File.cp/3` performs more extensive
bytes. On the other hand, `cp/3` performs more extensive
checks on both source and destination and it also preserves
the file mode after copy.
Typical error reasons are the same as in `open/2`,
`read/1` and `write/2`.
`read/1` and `write/3`.
"""
def copy(source, destination, bytes_count // :infinity) do
F.copy(source, destination, bytes_count)
@@ -342,7 +345,7 @@ defmodule File do
{ :ok, bytes_count } -> bytes_count
{ :error, reason } ->
raise File.CopyError, reason: reason, action: "copy",
source: :unicode.characters_to_binary(source), destination: :unicode.characters_to_binary(destination)
source: to_string(source), destination: to_string(destination)
end
end
@@ -355,15 +358,15 @@ defmodule File do
copies the contents of `source` to `destination/source`.
If a file already exists in the destination, it invokes a
callback which should return true if the existing file
should be overwritten, false otherwise. It defaults to return true.
callback which should return `true` if the existing file
should be overwritten, `false` otherwise. It defaults to return `true`.
It returns `:ok` in case of success, returns
`{ :error, reason }` otherwise.
If you want to copy contents from an io device to another device
or do a straight copy from a source to a destination without
preserving modes, check `File.copy/3` instead.
preserving modes, check `copy/3` instead.
"""
def cp(source, destination, callback // fn(_, _) -> true end) do
output =
@@ -381,7 +384,7 @@ defmodule File do
end
@doc """
The same as `cp/3`, but raises File.CopyError if it fails.
The same as `cp/3`, but raises `File.CopyError` if it fails.
Returns the list of copied files otherwise.
"""
def cp!(source, destination, callback // fn(_, _) -> true end) do
@@ -389,12 +392,12 @@ defmodule File do
:ok -> :ok
{ :error, reason } ->
raise File.CopyError, reason: reason, action: "copy recursively",
source: :unicode.characters_to_binary(source),
destination: :unicode.characters_to_binary(destination)
source: to_string(source),
destination: to_string(destination)
end
end
@doc %B"""
@doc %S"""
Copies the contents in source to destination.
Similar to the command `cp -r` in Unix systems,
this function behaves differently depending
@@ -407,8 +410,8 @@ defmodule File do
If a file already exists in the destination,
it invokes a callback which should return
true if the existing file should be overwritten,
false otherwise. It defaults to return true.
`true` if the existing file should be overwritten,
`false` otherwise. It defaults to return `true`.
If a directory already exists in the destination
where a file is meant to be (or otherwise), this
@@ -456,7 +459,7 @@ defmodule File do
end
@doc """
The same as `cp_r/3`, but raises File.CopyError if it fails.
The same as `cp_r/3`, but raises `File.CopyError` if it fails.
Returns the list of copied files otherwise.
"""
def cp_r!(source, destination, callback // fn(_, _) -> true end) do
@@ -464,8 +467,8 @@ defmodule File do
{ :ok, files } -> files
{ :error, reason, file } ->
raise File.CopyError, reason: reason, action: "copy recursively",
source: :unicode.characters_to_binary(source),
destination: :unicode.characters_to_binary(destination),
source: to_string(source),
destination: to_string(destination),
on: file
end
end
@@ -473,13 +476,13 @@ defmodule File do
# src may be a file or a directory, dest is definitely
# a directory. Returns nil unless an error is found.
defp do_cp_r(src, dest, callback, acc) when is_list(acc) do
case :elixir.file_type(src) do
case :elixir_utils.file_type(src) do
{ :ok, :regular } ->
do_cp_file(src, dest, callback, acc)
{ :ok, :symlink } ->
case F.read_link(src) do
{ :ok, link } -> do_cp_link(link, src, dest, callback, acc)
{ :error, reason } -> { :error, reason, :unicode.characters_to_binary(src) }
{ :error, reason } -> { :error, reason, to_string(src) }
end
{ :ok, :directory } ->
case F.list_dir(src) do
@@ -489,12 +492,12 @@ defmodule File do
Enum.reduce(files, [dest|acc], fn(x, acc) ->
do_cp_r(FN.join(src, x), FN.join(dest, x), callback, acc)
end)
{ :error, reason } -> { :error, reason, :unicode.characters_to_binary(dest) }
{ :error, reason } -> { :error, reason, to_string(dest) }
end
{ :error, reason } -> { :error, reason, :unicode.characters_to_binary(src) }
{ :error, reason } -> { :error, reason, to_string(src) }
end
{ :ok, _ } -> { :error, :eio, src }
{ :error, reason } -> { :error, reason, :unicode.characters_to_binary(src) }
{ :error, reason } -> { :error, reason, to_string(src) }
end
end
@@ -523,12 +526,12 @@ defmodule File do
{ :ok, _ } ->
copy_file_mode!(src, dest)
[dest|acc]
{ :error, reason } -> { :error, reason, :unicode.characters_to_binary(src) }
{ :error, reason } -> { :error, reason, to_string(src) }
end
else
acc
end
{ :error, reason } -> { :error, reason, :unicode.characters_to_binary(src) }
{ :error, reason } -> { :error, reason, to_string(src) }
end
end
@@ -542,12 +545,12 @@ defmodule File do
rm(dest)
case F.make_symlink(link, dest) do
:ok -> [dest|acc]
{ :error, reason } -> { :error, reason, :unicode.characters_to_binary(src) }
{ :error, reason } -> { :error, reason, to_string(src) }
end
else
acc
end
{ :error, reason } -> { :error, reason, :unicode.characters_to_binary(src) }
{ :error, reason } -> { :error, reason, to_string(src) }
end
end
@@ -576,7 +579,7 @@ defmodule File do
case F.write_file(path, content, modes) do
:ok -> :ok
{ :error, reason } ->
raise File.Error, reason: reason, action: "write to file", path: :unicode.characters_to_binary(path)
raise File.Error, reason: reason, action: "write to file", path: to_string(path)
end
end
@@ -607,13 +610,13 @@ defmodule File do
end
@doc """
Same as `rm`, but raises an exception in case of failure. Otherwise `:ok`.
Same as `rm/1`, but raises an exception in case of failure. Otherwise `:ok`.
"""
def rm!(path) do
case rm(path) do
:ok -> :ok
{ :error, reason } ->
raise File.Error, reason: reason, action: "remove file", path: :unicode.characters_to_binary(path)
raise File.Error, reason: reason, action: "remove file", path: to_string(path)
end
end
@@ -623,7 +626,7 @@ defmodule File do
## Examples
File.rddir('tmp_dir')
File.rmdir('tmp_dir')
#=> :ok
File.rmdir('file.txt')
@@ -641,7 +644,7 @@ defmodule File do
case rmdir(path) do
:ok -> :ok
{ :error, reason } ->
raise File.Error, reason: reason, action: "remove directory", path: :unicode.characters_to_binary(path)
raise File.Error, reason: reason, action: "remove directory", path: to_string(path)
end
end
@@ -651,7 +654,7 @@ defmodule File do
files are simply ignored (i.e. doesn't make this function fail).
Returns `{ :ok, files_and_directories }` with all files and
directories removed in no specific order, `{ :error, reason }`
directories removed in no specific order, `{ :error, reason, file }`
otherwise.
## Examples
@@ -669,7 +672,7 @@ defmodule File do
defp do_rm_rf(path, { :ok, acc } = entry) do
case safe_list_dir(path) do
{ :ok, files } ->
{ :ok, files } when is_list(files) ->
res =
Enum.reduce files, entry, fn(file, tuple) ->
do_rm_rf(FN.join(path, file), tuple)
@@ -680,18 +683,15 @@ defmodule File do
case rmdir(path) do
:ok -> { :ok, [path|acc] }
{ :error, :enoent } -> res
reason -> { :error, reason, :unicode.characters_to_binary(path) }
{ :error, reason } -> { :error, reason, to_string(path) }
end
reason -> { :error, reason, :unicode.characters_to_binary(path) }
reason ->
reason
end
{ :error, reason } when reason in [:enotdir, :eio] ->
case rm(path) do
:ok -> { :ok, [path|acc] }
{ :error, reason } when reason in [:enoent, :enotdir] -> entry
reason -> { :error, reason, :unicode.characters_to_binary(path) }
end
{ :error, :enoent } -> entry
{ :error, reason } -> { :error, reason, :unicode.characters_to_binary(path) }
{ :ok, :directory } -> do_rm_directory(path, entry)
{ :ok, :regular } -> do_rm_regular(path, entry)
{ :error, reason } when reason in [:enoent, :enotdir] -> entry
{ :error, reason } -> { :error, reason, to_string(path) }
end
end
@@ -699,10 +699,40 @@ defmodule File do
reason
end
defp do_rm_regular(path, { :ok, acc } = entry) do
case rm(path) do
:ok -> { :ok, [path|acc] }
{ :error, :enoent } -> entry
{ :error, reason } -> { :error, reason, to_string(path) }
end
end
# On windows, symlinks are treated as directory and must be removed
# with rmdir/1. But on Unix, we remove them via rm/1. So we first try
# to remove it as a directory and, if we get :enotdir, we fallback to
# a file removal.
defp do_rm_directory(path, { :ok, acc } = entry) do
case rmdir(path) do
:ok -> { :ok, [path|acc] }
{ :error, :enotdir } -> do_rm_regular(path, entry)
{ :error, :enoent } -> entry
{ :error, reason } -> { :error, reason, to_string(path) }
end
end
defp safe_list_dir(path) do
case F.read_link(path) do
{ :ok, _ } -> { :error, :enotdir }
_ -> F.list_dir(path)
case :elixir_utils.file_type(path) do
{ :ok, :symlink } ->
case :elixir_utils.file_type(path, :read_file_info) do
{ :ok, :directory } -> { :ok, :directory }
_ -> { :ok, :regular }
end
{ :ok, :directory } ->
F.list_dir(path)
{ :ok, _ } ->
{ :ok, :regular }
{ :error, reason } ->
{ :error, reason }
end
end
@@ -716,7 +746,7 @@ defmodule File do
{ :error, reason, _ } ->
raise File.Error, reason: reason,
action: "remove files and directories recursively from",
path: :unicode.characters_to_binary(path)
path: to_string(path)
end
end
@@ -724,12 +754,11 @@ defmodule File do
Opens the given `path` according to the given list of modes.
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`, `IO.binwrite` and
`IO.binreadline` to interact with the file. A developer may pass
`:utf8` as an option when opening the file and then all other
functions from IO are available, since they work directly with
Unicode data.
in the `IO` module. By default, a file is opened in binary mode
which requires the functions `IO.binread/2` and `IO.binwrite/2`
to interact with the file. A developer may pass `:utf8` as an
option when opening the file and then all other functions from
`IO` are available, since they work directly with Unicode data.
The allowed modes:
@@ -757,10 +786,10 @@ 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 to modes (instead of a list), it dispatches to `open/3`.
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 as `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:
@@ -776,7 +805,7 @@ defmodule File do
## Examples
{ :ok, file } = File.open("foo.tar.gz", [:read, :compressed])
IO.readline(file)
IO.read(file, :line)
File.close(file)
"""
@@ -807,7 +836,7 @@ defmodule File do
## Examples
File.open("file.txt", [:read, :write], fn(file) ->
IO.readline(file)
IO.read(file, :line)
end)
"""
@@ -831,7 +860,7 @@ defmodule File do
case open(path, modes) do
{ :ok, device } -> device
{ :error, reason } ->
raise File.Error, reason: reason, action: "open", path: :unicode.characters_to_binary(path)
raise File.Error, reason: reason, action: "open", path: to_string(path)
end
end
@@ -843,7 +872,7 @@ defmodule File do
case open(path, modes, function) do
{ :ok, device } -> device
{ :error, reason } ->
raise File.Error, reason: reason, action: "open", path: :unicode.characters_to_binary(path)
raise File.Error, reason: reason, action: "open", path: to_string(path)
end
end
@@ -855,7 +884,7 @@ defmodule File do
"""
def cwd() do
case F.get_cwd do
{ :ok, cwd } -> { :ok, :unicode.characters_to_binary(cwd) }
{ :ok, base } -> { :ok, String.from_char_list!(base) }
{ :error, _ } = error -> error
end
end
@@ -865,7 +894,7 @@ defmodule File do
"""
def cwd!() do
case F.get_cwd do
{ :ok, cwd } -> :unicode.characters_to_binary(cwd)
{ :ok, cwd } -> to_string(cwd)
{ :error, reason } ->
raise File.Error, reason: reason, action: "get current working directory"
end
@@ -880,13 +909,13 @@ defmodule File do
end
@doc """
The same as `cd/0`, but raises an exception if it fails.
The same as `cd/1`, but raises an exception if it fails.
"""
def cd!(path) do
case F.set_cwd(path) do
:ok -> :ok
{ :error, reason } ->
raise File.Error, reason: reason, action: "set current working directory to", path: :unicode.characters_to_binary(path)
raise File.Error, reason: reason, action: "set current working directory to", path: to_string(path)
end
end
@@ -916,7 +945,7 @@ defmodule File do
"""
def ls(path // ".") do
case F.list_dir(path) do
{ :ok, file_list } -> { :ok, Enum.map file_list, :unicode.characters_to_binary(&1) }
{ :ok, file_list } -> { :ok, Enum.map(file_list, to_string(&1)) }
{ :error, _ } = error -> error
end
end
@@ -929,7 +958,7 @@ defmodule File do
case ls(dir) do
{ :ok, value } -> value
{ :error, reason } ->
raise File.Error, reason: reason, action: "list directory", path: :unicode.characters_to_binary(dir)
raise File.Error, reason: reason, action: "list directory", path: to_string(dir)
end
end
@@ -946,45 +975,18 @@ defmodule File do
end
@doc """
Converts the file device into an iterator that can be
passed into `Enum`. The device is iterated line
by line, at the end of iteration the file is closed.
This reads the file as utf-8. Check out `File.biniterator`
to handle the file as a raw binary.
## Examples
An example that lazily iterates a file replacing all double
quotes per single quotes and writes each line to a target file
is shown below:
{ :ok, device } = File.open("README.md")
source = File.iterator(device)
File.open "NEWREADME.md", [:write], fn(target) ->
Enum.each source, fn(line) ->
IO.write target, Regex.replace(%r/"/, line, "'")
end
end
Opens the given `file` with the given `mode` and returns
a stream for each `:line` (default) or for a given number
of bytes given by `line_or_bytes`.
The returned stream will fail for the same reasons as `File.open!`.
Note that the file is opened only when streaming begins.
"""
def iterator(device) do
fn(fun, acc) ->
do_iterator(device, fun, acc)
end
end
@doc """
Opens the given `file` with the given `mode` and
returns its iterator. The returned iterator will
fail for the same reasons as `File.open!`. Note
that the file is opened when the iteration begins.
"""
def iterator!(file, mode // []) do
fn(fun, acc) ->
def stream!(file, mode // [], line_or_bytes // :line) do
fn(acc, fun) ->
device = open!(file, mode)
try do
do_iterator(device, fun, acc)
IO.stream(device, line_or_bytes, acc, fun)
after
F.close(device)
end
@@ -992,27 +994,18 @@ defmodule File do
end
@doc """
Converts the file device into an iterator that can
be passed into `Enum` to iterate line by line as a
binary. Check `iterator/1` for more information.
"""
def biniterator(device) do
fn(fun, acc) ->
do_biniterator(device, fun, acc)
end
end
Opens the given `file` with the given `mode` and returns
a binstream for each `:line` (default) or for a given number
of bytes given by `line_or_bytes`.
@doc """
Opens the given `file` with the given `mode` and
returns its biniterator. The returned iterator will
fail for the same reasons as `File.open!`. Note
that the file is opened when the iteration begins.
The returned stream will fail for the same reasons as `File.open!`.
Note that the file is opened only when streaming begins.
"""
def biniterator!(file, mode // []) do
def binstream!(file, mode // [], line_or_bytes // :line) do
fn(fun, acc) ->
device = open!(file, mode)
try do
do_biniterator(device, fun, acc)
IO.binstream(device, line_or_bytes, fun, acc)
after
F.close(device)
end
@@ -1020,9 +1013,9 @@ defmodule File do
end
@doc """
Changes the unix file `mode` for a given `file`.
Returns `:ok` on success, or `{:error, reason}`
on failure.
Changes the unix file `mode` for a given `file`.
Returns `:ok` on success, or `{:error, reason}`
on failure.
"""
def chmod(file, mode) do
F.change_mode(file, mode)
@@ -1035,14 +1028,14 @@ defmodule File do
case chmod(file, mode) do
:ok -> :ok
{ :error, reason } ->
raise File.Error, reason: reason, action: "change mode for", path: :unicode.characters_to_binary(file)
raise File.Error, reason: reason, action: "change mode for", path: to_string(file)
end
end
@doc """
Changes the user group given by the group id `gid`
for a given `file`. Returns `:ok` on success, or
`{:error, reason}` on failure.
Changes the user group given by the group id `gid`
for a given `file`. Returns `:ok` on success, or
`{:error, reason}` on failure.
"""
def chgrp(file, gid) do
F.change_group(file, gid)
@@ -1055,16 +1048,16 @@ defmodule File do
case chgrp(file, gid) do
:ok -> :ok
{ :error, reason } ->
raise File.Error, reason: reason, action: "change group for", path: :unicode.characters_to_binary(file)
raise File.Error, reason: reason, action: "change group for", path: to_string(file)
end
end
@doc """
Changes the owner given by the user id `gid`
for a given `file`. Returns `:ok` on success,
or `{:error, reason}` on failure.
Changes the owner given by the user id `gid`
for a given `file`. Returns `:ok` on success,
or `{:error, reason}` on failure.
"""
def chown(file, uid) do
def chown(file, uid) do
F.change_owner(file, uid)
end
@@ -1075,7 +1068,7 @@ defmodule File do
case chown(file, gid) do
:ok -> :ok
{ :error, reason } ->
raise File.Error, reason: reason, action: "change owner for", path: :unicode.characters_to_binary(file)
raise File.Error, reason: reason, action: "change owner for", path: to_string(file)
end
end
@@ -1095,26 +1088,4 @@ defmodule File do
defp open_defaults([], true), do: [:binary]
defp open_defaults([], false), do: []
defp do_iterator(device, acc, fun) do
case :io.get_line(device, '') do
:eof ->
acc
{ :error, reason } ->
raise File.IteratorError, reason: reason
data ->
do_iterator(device, fun.(data, acc), fun)
end
end
defp do_biniterator(device, acc, fun) do
case F.read_line(device) do
:eof ->
acc
{ :error, reason } ->
raise File.IteratorError, reason: reason
{ :ok, data } ->
do_iterator(device, fun.(data, acc), fun)
end
end
end
+10 -5
View File
@@ -1,12 +1,12 @@
defmodule GenEvent.Behaviour do
@moduledoc """
This module is a convenience to define GenEvent callbacks in Elixir.
This module is a convenience for defining GenEvent callbacks in Elixir.
GenEvent is an OTP behaviour that encapsulates event handling functionality.
## Example
Bellow follows an example of a GenEvent that stores notifications
Below is an example of a GenEvent that stores notifications
until they are fetched:
defmodule MyEventHandler do
@@ -29,11 +29,16 @@ defmodule GenEvent.Behaviour do
end
{ :ok, pid } = :gen_event.start_link
#=> {:ok,#PID<0.42.0>}
:gen_event.add_handler(pid, MyEventHandler, [])
#=> :ok
:gen_event.notify(pid, {:notification, 1})
#=> :ok
:gen_event.notify(pid, {:notification, 2})
#=> :ok
:gen_event.call(pid, MyEventHandler, :notifications)
#=> [1, 2]
@@ -44,12 +49,12 @@ defmodule GenEvent.Behaviour do
Notice we never call the server callbacks directly, they are called
by OTP whenever we interact with the server.
Starting and sending messages to gen_event is done
Starting and sending messages to the GenEvent is done
via Erlang's `:gen_event` module. For more information,
please refer to the following:
http://www.erlang.org/doc/man/gen_event.html
http://learnyousomeerlang.com/event-handlers
* http://www.erlang.org/doc/man/gen_event.html
* http://learnyousomeerlang.com/event-handlers
"""
@doc false
+211
View File
@@ -0,0 +1,211 @@
defmodule GenFSM.Behaviour do
@moduledoc """
This module is a convenience for defining GenFSM callbacks in Elixir.
A finite state machine (FSM) is responsible for reacting to events received;
GenFSM is an OTP behaviour that encapsulates common FSM
functionalities.
## Example
Below is an example of a GenFSM that runs a very simple minded
coffee vending machine (CVM). The CVM treats all coins the same. If
you press the request button then the CVM will brew coffee, if you
have paid enough coins; if not, it will wait until you have inserted
enough coins and then it will instantly brew the coffee, since you
had already pressed the request button. As we told you - a very
simple minded CVM! And greedy too. If you insert more coins than you
need it will gladly eat them until you press the request button.
We will leave it to the service-minded reader to improve the way the CVM
works - we hereby declare a full disclaimer for any lawsuits that the
behaviour of the CVM in its original state might encur.
defmodule MyFsm do
use GenFSM.Behaviour
# keeping track of what is going on inside the CVM.
# 3 is the target price for a cup of coffee
defrecord StateData, coins: 0, price: 3
#
# API functions
#
def start_link() do
:gen_fsm.start_link({:local, :cvm}, __MODULE__, [], [])
end
def insert_coin() do
:gen_fsm.send_event(:cvm, :coin)
end
def request_coffee() do
:gen_fsm.send_event(:cvm, :request_coffee)
end
#
# Callbacks
#
def init(_args) do
{ :ok, :short_paid, StateData.new }
end
def short_paid(:coin, state_data = StateData[coins: c, price: p])
when c + 1 < p do
{ :next_state, :short_paid, state_data.coins(c + 1) }
end
def short_paid(:coin, state_data) do
{ :next_state, :paid_in_full, state_data.update_coins(&1 + 1) }
end
def short_paid(:request_coffee, state_data) do
{ :next_state, :requested_short_paid, state_data }
end
def requested_short_paid(:request_coffee, state_data) do
{:next_state, :requested_short_paid, state_data }
end
def requested_short_paid(:coin, state_data=StateData[coins: c, price: p])
when c+1 < p do
{ :next_state, :requested_short_paid, state_data.coins(c + 1) }
end
def requested_short_paid(:coin, _state_data) do
IO.puts "Here's your coffee!"
{ :next_state, :short_paid, StateData.new }
end
def paid_in_full(:coin, state_data) do
{ :next_state, :paid_in_full, state_data.update_coins(&1 + 1) }
end
def paid_in_full(:request_coffee, _state_data) do
IO.puts "Here's your coffee!"
{ :next_state, :short_paid, StateData.new }
end
end
{ :ok, _pid } = MyFsm.start_link()
MyFsm.insert_coin
#=> :ok
MyFsm.insert_coin
#=> :ok
MyFsm.request_coffee
#=> :ok
MyFsm.insert_coin
#=> :ok
#=> Here's your coffee!
Notice we never call the GenFSM callbacks directly; they are called by
OTP whenever we interact with the server throught the API. `send_event` is
asynchronous, whereas `sync_send_event` is synchronous. For
a GenFSM, the different values a callback can return depend
on the type of callback.
State handling returns for `send_event` callbacks:
{ :next_state, next_state_name, new_state_data }
{ :next_state, next_state_name, new_state_data, timeout }
{ :next_state, next_state_name, new_state_data, :hibernate }
{ :stop, reason, new_state_data }
State handling returns for `sync_send_event` callbacks:
{ :reply, reply, next_state_name, new_state_data }
{ :reply, reply, next_state_name, new_state_data, timeout }
{ :reply, reply, next_state_name, new_state_data, :hibernate }
{ :next_state, next_state_name, new_state_data }
{ :next_state, next_state_name, new_state_data, timeout }
{ :next_state, next_state_name, new_state_data, :hibernate }
{ :stop, reason, reply, new_state_data }
{ :stop, reason, new_state_date }
There are 6 callbacks required to be implemented in a GenFsm plus 1
or 2 for each state. The `GenFSM.Behaviour` module defines
`handle_sync_event`, `handle_info`, `terminate` and `code_change`
for you. The list of callbacks are:
* `init(args)` - invoked when the FSM is started;
* `handle_sync_event(event, from, state_name, state_data)` - invoked to
handle `sync_send_all_state_event` messages;
* `handle_event(event, state_name, state_data)` - invoked to handle
`send_all_state_event` messages;
* `handle_info(msg, state_name, state_data)` - handle all other
messages which are normally received by processes;
* `terminate(reason, state_name, state_data)` - called when the FSM
is about to terminate, useful for cleaning up;
* `code_change(old_vsn, state, extra)` - called when the application
code is being upgraded live (hot code swap);
Unlike `GenServer` and `GenEvent`, the callback `init/1` is not
implemented by default, as it requires the next state to be returned.
For each state you need to define either or both of these:
* `state_name(event, state_data)` - invoked to handle
`send_event` messages;
* `state_name(event, from, state_data)`- invoked to handle
`sync_send_event` messages;
If you send asynchronous events you only need to implement the
`state_name/2` variant and vice-versa for synchronous events and
`state_name/3`. Keep in mind that if you mix `send_event` and
`sync_send_event` the best thing to do is to implement both
callbacks for all states.
Starting and sending messages to the GenFSM is done via Erlang's
`:gen_fsm` module. For more information, please refer to the
following:
* http://www.erlang.org/doc/man/gen_fsm.html
* http://www.erlang.org/doc/design_principles/fsm.html
* http://learnyousomeerlang.com/finite-state-machines
"""
@doc false
defmacro __using__(_) do
quote location: :keep do
@behavior :gen_fsm
@doc false
def handle_event(_event, state_name, state_data) do
{ :next_state, state_name, state_data }
end
@doc false
def handle_sync_event(_event, from, state_name, state_data) do
{ :reply, :default_implementation, state_name, 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
+17 -17
View File
@@ -1,15 +1,15 @@
defmodule GenServer.Behaviour do
@moduledoc """
This module is a convenience to define GenServer callbacks in Elixir.
This module is a convenience for defining GenServer callbacks in Elixir.
A server is responsible to react to messages received from client
and a GenServer is an OTP behaviour that encapsulates common server
A server is responsible for reacting to messages received from a client.
A GenServer is an OTP behaviour that encapsulates common server
functionalities.
## Example
Bellow follows an example of a GenServer that push and pop items
into a stack:
Below is an example of a GenServer that pushes and pops items
onto a stack:
defmodule MyServer do
use GenServer.Behaviour
@@ -20,17 +20,17 @@ defmodule GenServer.Behaviour do
{ :reply, h, t }
end
def handle_call(_request, _from, _config) do
def handle_call(request, from, config) do
# Call the default implementation from GenServer.Behaviour
super
super(request, from, config)
end
def handle_cast({ :push, item }, config) do
{ :noreply, [item|config] }
end
def handle_cast(_request, _config) do
super
def handle_cast(request, config) do
super(request, config)
end
end
@@ -47,8 +47,8 @@ defmodule GenServer.Behaviour do
Notice we never call the server callbacks directly, they are called
by OTP whenever we interact with the server. **cast** messages are
asynchronous while **call** ones are synchronous. In the case of
GenServer's, there are 8 different values a callback such as
asynchronous while **call** ones are synchronous. For a
GenServer, there are 8 different values a callback such as
`handle_call` or `handle_cast` can return:
{ :reply, reply, new_state }
@@ -62,7 +62,7 @@ defmodule GenServer.Behaviour do
There are 6 callbacks required to be implemented in a GenServer. The
`GenServer.Behaviour` module defines all of them automatically, but
allows us to customize the ones we need. The list of callbacks are:
allows us to customize the ones we need. The required callbacks are:
* `init(args)` - invoked when the server is started;
* `handle_call(msg, from, state)` - invoked to handle call messages;
@@ -74,13 +74,13 @@ defmodule GenServer.Behaviour do
* `code_change(old_vsn, state, extra)` - called when the application
code is being upgraded live (hot code swap);
Starting and sending messages to the gen_server is done
Starting and sending messages to the GenServer is done
via Erlang's `:gen_server` module. For more information,
please refer to the following:
http://www.erlang.org/doc/man/gen_server.html
http://www.erlang.org/doc/design_principles/gen_server_concepts.html
http://learnyousomeerlang.com/clients-and-servers
* http://www.erlang.org/doc/man/gen_server.html
* http://www.erlang.org/doc/design_principles/gen_server_concepts.html
* http://learnyousomeerlang.com/clients-and-servers
"""
@doc false
@@ -122,4 +122,4 @@ defmodule GenServer.Behaviour do
handle_cast: 2, terminate: 2, code_change: 3]
end
end
end
end
+67 -190
View File
@@ -8,7 +8,7 @@ defmodule HashDict do
their APIs, please consult the `Dict` module.
"""
@behaviour Dict
use Dict.Behaviour
# A dictionary (key-value) implementation based on dynamic hashing.
#
@@ -35,7 +35,7 @@ defmodule HashDict do
# The ordered record contains a single bucket
@ordered_threshold 8
defrecordp :ordered,
defrecordp :ordered, HashDict,
size: 0,
bucket: []
@@ -47,7 +47,7 @@ defmodule HashDict do
@node_size 8
@node_template :erlang.make_tuple(@node_size, [])
defrecordp :trie,
defrecordp :trie, HashDict,
size: 0,
depth: 0,
expand_on: @node_size * @expand_load,
@@ -57,6 +57,7 @@ defmodule HashDict do
import Bitwise
# Let's inline common instructions
@compile :inline_list_funcs
@compile { :inline, bucket_hash: 1, bucket_index: 1, bucket_nth_index: 2, bucket_next: 1 }
@doc """
@@ -101,28 +102,18 @@ defmodule HashDict do
end
end
@doc """
Puts the given key and value in the dict.
"""
def put(dict, key, value) do
{ dict, _ } = dict_put(dict, key, { :put, value })
dict
end
@doc """
Puts the given value under key in the dictionary
only if one does not exist yet.
"""
def put_new(dict, key, value) do
update(dict, key, value, fn(v) -> v end)
@doc false
def update(dict, key, fun) when is_function(fun, 1) do
IO.write "HashDict.update/3 is deprecated, please use HashDict.update!/3 instead\n#{Exception.format_stacktrace}"
update!(dict, key, fun)
end
@doc """
Updates the key in the dictionary according
to the given function. Raises if the key does
not exist in the dictionary.
"""
def update(dict, key, fun) when is_function(fun, 1) do
def update!(dict, key, fun) when is_function(fun, 1) do
case dict_put(dict, key, { :update, nil, fun }) do
{ dict, 0 } ->
dict
@@ -131,55 +122,19 @@ defmodule HashDict do
end
end
@doc """
Updates the key in the dictionary according
to the given function. Adds initial value if
the key does not exist in the dicionary.
"""
def update(dict, key, initial, fun) when is_function(fun, 1) do
{ dict, _ } = dict_put(dict, key, { :update, initial, fun })
dict
end
@doc """
Gets the value under key from the dict.
"""
def get(dict, key, default // nil) do
case dict_get(dict, key) do
{ ^key, value } -> value
false -> default
end
def fetch(ordered(bucket: bucket), key) do
bucket_get(bucket, key)
end
@doc """
Fetches the value under key from the dict
and return it in a tagged tuple.
"""
def fetch(dict, key) do
case dict_get(dict, key) do
{ ^key, value } -> { :ok, value }
false -> :error
end
def fetch(trie(root: root, depth: depth), key) do
bucket_get(node_bucket(root, depth, bucket_hash(key)), key)
end
def fetch!(dict, key) when is_tuple(dict) do
case dict_get(dict, key) do
{ ^key, value } -> value
false -> raise(KeyError, key: key)
end
end
@doc """
Checks if the dict has the given key.
"""
def has_key?(dict, key) do
match? { ^key, _ }, dict_get(dict, key)
end
@doc """
Returns the value under key from the dict as well as the dict without key.
"""
def pop(dict, key, default // nil) do
case dict_delete(dict, key) do
{ dict, _, 0 } -> { default, dict }
@@ -187,87 +142,35 @@ defmodule HashDict do
end
end
@doc """
Deletes a value from the dict.
"""
def delete(dict, key) do
{ dict, _, _ } = dict_delete(dict, key)
dict
end
@doc """
Returns the dict size.
"""
def size(dict) do
elem(dict, 1)
end
@doc """
Returns an empty dict.
"""
def empty(_) do
ordered()
end
@doc """
Checks if two dicts are equal
"""
def equal?(dict1, dict2) do
size = elem(dict1, 1)
case elem(dict2, 1) do
^size ->
dict_equal?(dict1, dict2)
_ ->
false
end
end
@doc """
Converts the dict to a list.
"""
def to_list(ordered(bucket: bucket)) do
bucket
end
def to_list(dict) do
dict_fold(dict, [], [&1|&2]) |> :lists.reverse
end
@doc """
Get all keys in the dict.
"""
def keys(dict) do
dict_fold(dict, [], fn { k, _ }, acc -> [k|acc] end)
end
@doc """
Get all values in the dict.
"""
def values(dict) do
dict_fold(dict, [], fn { _, v }, acc -> [v|acc] end)
end
@doc """
Merges two dictionaries.
"""
def merge(dict, enum, callback // fn(_k, _v1, v2) -> v2 end)
def merge(dict1, dict2, callback) when is_record(dict1, HashDict) and is_record(dict2, HashDict) and elem(dict1, 1) < elem(dict2, 1) do
dict_fold dict1, dict2, fn { k, v1 }, acc ->
dict_fold dict1, dict2, fn [k|v1], acc ->
update(acc, k, v1, callback.(k, v1, &1))
end
end
def merge(dict1, dict2, callback) when is_record(dict1, HashDict) and is_record(dict2, HashDict) do
dict_fold dict2, dict1, fn { k, v2 }, acc ->
dict_fold dict2, dict1, fn [k|v2], acc ->
update(acc, k, v2, callback.(k, &1, v2))
end
end
def merge(dict1, dict2, callback) when is_record(dict1, HashDict) do
Enum.reduce dict2, dict1, fn { k, v2 }, acc ->
update(acc, k, v2, callback.(k, &1, v2))
end
def merge(dict, enumerable, callback) when is_record(dict, HashDict) do
super(dict, enumerable, callback)
end
def split(dict, keys) do
@@ -285,42 +188,17 @@ defmodule HashDict do
end
end
def take(dict, keys) do
take(dict, keys, new)
end
defp take(_dict, [], acc), do: acc
defp take(dict, [key|keys], acc) do
case fetch(dict, key) do
{ :ok, value } -> take(dict, keys, put(acc, key, value))
:error -> take(dict, keys, acc)
end
end
def drop(dict, []), do: dict
def drop(dict, [key|keys]) do
drop(delete(dict, key), keys)
end
@doc false
def reduce(ordered(bucket: bucket), acc, fun) do
:lists.foldl(fun, acc, bucket)
bucket_reduce(bucket, acc, fun)
end
def reduce(trie() = dict, acc, fun) do
dict_fold(dict, acc, fun)
def reduce(trie(root: root, depth: depth), acc, fun) do
node_reduce(root, depth, acc, fun, @node_size)
end
## Dict-wide functions
defp dict_get(ordered(bucket: bucket), key) do
bucket_get(bucket, key)
end
defp dict_get(trie(root: root, depth: depth), key) do
bucket_get(node_bucket(root, depth, bucket_hash(key)), key)
end
defp dict_fold(ordered(bucket: bucket), acc, fun) do
bucket_fold(bucket, acc, fun)
end
@@ -381,30 +259,15 @@ defmodule HashDict do
end
end
defp dict_equal?(dict1, dict2) do
try do
reduce(dict1, true, fn({ key, value }, acc) ->
case fetch(dict2, key) do
{ _ok, ^value } ->
acc
_ ->
throw(:error)
end
end)
catch
:error -> false
end
end
## Bucket helpers
# Get value from the bucket
defp bucket_get([{k, _}|_bucket], key) when k > key do
false
defp bucket_get([[k|_]|_bucket], key) when k > key do
:error
end
defp bucket_get([{key, _}=e|_bucket], key) do
e
defp bucket_get([[key|value]|_bucket], key) do
{ :ok, value }
end
defp bucket_get([_e|bucket], key) do
@@ -412,24 +275,24 @@ defmodule HashDict do
end
defp bucket_get([], _key) do
false
:error
end
# Puts a value in the bucket
defp bucket_put([{k, _}|_]=bucket, key, { :put, value }) when k > key do
{ [{key, value}|bucket], 1 }
defp bucket_put([[k|_]|_]=bucket, key, { :put, value }) when k > key do
{ [[key|value]|bucket], 1 }
end
defp bucket_put([{k, _}|_]=bucket, key, { :update, initial, _fun }) when k > key do
{ [{key, initial}|bucket], 1 }
defp bucket_put([[k|_]|_]=bucket, key, { :update, initial, _fun }) when k > key do
{ [[key|initial]|bucket], 1 }
end
defp bucket_put([{key, _}|bucket], key, { :put, value }) do
{ [{key, value}|bucket], 0 }
defp bucket_put([[key|_]|bucket], key, { :put, value }) do
{ [[key|value]|bucket], 0 }
end
defp bucket_put([{key, value}|bucket], key, { :update, _initial, fun }) do
{ [{key, fun.(value)}|bucket], 0 }
defp bucket_put([[key|value]|bucket], key, { :update, _initial, fun }) do
{ [[key|fun.(value)]|bucket], 0 }
end
defp bucket_put([e|bucket], key, value) do
@@ -438,26 +301,26 @@ defmodule HashDict do
end
defp bucket_put([], key, { :put, value }) do
{ [{key, value}], 1 }
{ [[key|value]], 1 }
end
defp bucket_put([], key, { :update, initial, _fun }) do
{ [{key, initial}], 1 }
{ [[key|initial]], 1 }
end
# Puts a value in the bucket without returning
# the operation value
defp bucket_put!([{k, _}|_]=bucket, key, value) when k > key, do: [{key, value}|bucket]
defp bucket_put!([{key, _}|bucket], key, value), do: [{key, value}|bucket]
defp bucket_put!([{_, _}=e|bucket], key, value), do: [e|bucket_put!(bucket, key, value)]
defp bucket_put!([], key, value), do: [{key, value}]
defp bucket_put!([[k|_]|_]=bucket, key, value) when k > key, do: [[key|value]|bucket]
defp bucket_put!([[key|_]|bucket], key, value), do: [[key|value]|bucket]
defp bucket_put!([e|bucket], key, value), do: [e|bucket_put!(bucket, key, value)]
defp bucket_put!([], key, value), do: [[key|value]]
# Deletes a key from the bucket
defp bucket_delete([{k, _}|_]=bucket, key) when k > key do
defp bucket_delete([[k|_]|_]=bucket, key) when k > key do
{ bucket, nil, 0 }
end
defp bucket_delete([{key, value}|bucket], key) do
defp bucket_delete([[key|value]|bucket], key) do
{ bucket, value, -1 }
end
@@ -470,7 +333,15 @@ defmodule HashDict do
{ [], nil, 0 }
end
# Folds the bucket
# Reduces the bucket
defp bucket_reduce([[k|v]|t], acc, fun) do
bucket_reduce(t, fun.({ k, v }, acc), fun)
end
defp bucket_reduce([], acc, _fun) do
acc
end
defp bucket_fold(bucket, acc, fun) do
:lists.foldl(fun, acc, bucket)
end
@@ -533,6 +404,20 @@ defmodule HashDict do
end
end
# Reduces a node recursively
defp node_reduce(bucket, -1, acc, fun, _) do
bucket_reduce(bucket, acc, fun)
end
defp node_reduce(node, depth, acc, fun, count) when count >= 1 do
acc = node_reduce(:erlang.element(count, node), depth - 1, acc, fun, @node_size)
node_reduce(node, depth, acc, fun, count - 1)
end
defp node_reduce(_node, _, acc, _fun, 0) do
acc
end
# Folds a node recursively
defp node_fold(bucket, -1, acc, fun, _) do
bucket_fold(bucket, acc, fun)
@@ -574,13 +459,13 @@ defmodule HashDict do
|> each_contract(b8)
end
defp each_contract([{k, _v}=e|acc], [{key, _value}|_]=bucket) when k < key, do: [e|each_contract(acc, bucket)]
defp each_contract([[k|_v]=e|acc], [[key|_value]|_]=bucket) when k < key, do: [e|each_contract(acc, bucket)]
defp each_contract(acc, [e|bucket]), do: [e|each_contract(acc, bucket)]
defp each_contract([], bucket), do: bucket
defp each_contract(acc, []), do: acc
defp node_relocate(node // @node_template, bucket, n) do
:lists.foldl fn { key, value }, acc ->
:lists.foldl fn [key|value], acc ->
pos = key |> bucket_hash() |> bucket_nth_index(n)
set_elem(acc, pos, bucket_put!(elem(acc, pos), key, value))
end, node, bucket
@@ -597,11 +482,3 @@ end
defimpl Access, for: HashDict do
def access(dict, key), do: HashDict.get(dict, key, nil)
end
defimpl Binary.Inspect, for: HashDict do
import Kernel, except: [inspect: 2]
def inspect(dict, opts) do
"#HashDict<" <> Kernel.inspect(HashDict.to_list(dict), opts) <> ">"
end
end
+492
View File
@@ -0,0 +1,492 @@
defmodule HashSet do
@moduledoc """
A set store.
The `HashSet` is meant to work well with both small and
large sets. It is an implementation of the `Set` behaviour.
For more information about the functions and their APIs,
please consult the `Set` module.
"""
@behaviour Set
# The ordered record contains a single bucket.
@ordered_threshold 8
defrecordp :ordered, HashSet,
size: 0,
bucket: []
# The bucketed record contains a series of buckets.
@expand_load 5
@contract_load 2
@node_bitmap 0b1111
@node_shift 4
@node_size 16
@node_template :erlang.make_tuple(@node_size, [])
@expand_default (@node_size * @expand_load)
@contract_default @contract_load
defrecordp :trie, HashSet,
size: 0,
depth: 0,
expand_on: @expand_default,
contract_on: @contract_default,
root: @node_template
import Bitwise
@compile :inline_list_funcs
@compile { :inline, bucket_hash: 1, bucket_index: 1, bucket_nth_index: 2, bucket_next: 1 }
@doc """
Creates a new empty set.
"""
def new() do
ordered()
end
@doc """
Creates a new set from the given enumerable.
## Examples
iex> HashSet.new [1, 1, 2, 3, 3] |> HashSet.to_list
[1,2,3]
"""
def new(members) do
Enum.reduce members, ordered(), fn member, set ->
put(set, member)
end
end
def union(set1, set2) when is_record(set1, HashSet) and is_record(set2, HashSet) and elem(set1, 1) <= elem(set2, 1) do
set_fold set1, set2, fn v1, acc ->
put(acc, v1)
end
end
def union(set1, set2) when is_record(set1, HashSet) and is_record(set2, HashSet) do
set_fold set2, set1, fn v, acc ->
put(acc, v)
end
end
def intersection(set1, set2) when is_record(set1, HashSet) and is_record(set2, HashSet) and elem(set1, 1) <= elem(set2, 1) do
set_filter set1, fn e -> set_member?(set2, e) end
end
def intersection(set1, set2) when is_record(set1, HashSet) and is_record(set2, HashSet) do
set_filter set2, fn e -> set_member?(set1, e) end
end
def difference(set1, set2) when is_record(set1, HashSet) and is_record(set2, HashSet) do
set_filter set1, fn m -> not set_member?(set2, m) end
end
def member?(set, member) when is_record(set, HashSet) do
set_member?(set, member)
end
def empty(_) do
ordered()
end
def size(set) do
elem(set, 1)
end
def to_list(ordered(bucket: bucket)) do
bucket
end
def to_list(set) do
set_fold(set, [], [&1|&2]) |> :lists.reverse
end
def put(set, member) do
{ set, _ } = set_put(set, member)
set
end
def delete(set, member) do
{ set, _ } = set_delete(set, member)
set
end
def equal?(set1, set2) do
size = elem(set1, 1)
case elem(set2, 1) do
^size ->
set_equal?(set1, set2)
_ ->
false
end
end
def subset?(set1, set2) do
set_equal?(set1, set2)
end
def disjoint?(set1, set2) do
set_disjoint?(set1, set2)
end
@doc false
def reduce(ordered(bucket: bucket), acc, fun) do
:lists.foldl(fun, acc, bucket)
end
def reduce(trie() = set, acc, fun) do
set_fold(set, acc, fun)
end
## HashSet-wide functions
defp set_filter(ordered(bucket: bucket, size: size), fun) do
{ new, removed_count } = bucket_filter(bucket, fun, [], 0)
ordered(bucket: new, size: size - removed_count)
end
defp set_filter(trie(root: root, depth: depth, size: size) = set, fun) do
{ new, removed_count } = node_filter(root, depth, fun, @node_size)
if depth > 0 and trie(set, :contract_on) >= (size - removed_count) do
contract_trie(trie(root: new,
size: size - removed_count,
depth: depth,
contract_on: trie(set, :contract_on),
expand_on: trie(set, :expand_on)))
else
trie(size: size - removed_count, root: new, depth: depth)
end
end
defp set_put(ordered(size: @ordered_threshold, bucket: bucket), member) do
root = node_relocate(bucket, 0)
set_put(trie(size: @ordered_threshold, root: root), member)
end
defp set_put(ordered(size: size, bucket: bucket) = set, member) do
{ new, count } = bucket_put(bucket, member)
{ ordered(set, size: size + count, bucket: new), count }
end
defp set_put(trie(root: root, depth: depth, size: size, expand_on: size, contract_on: contract_on) = set, member) do
root = node_expand(root, depth, depth + 1)
set = trie(set, root: root, depth: depth + 1,
expand_on: size * @node_size, contract_on: contract_on * @node_size)
set_put(set, member)
end
defp set_put(trie(root: root, size: size, depth: depth) = set, member) do
pos = bucket_hash(member)
{ root, count } = node_put(root, depth, pos, member)
{ trie(set, size: size + count, root: root), count }
end
defp set_member?(ordered(bucket: bucket), member) do
:lists.member(member, bucket)
end
defp set_member?(trie(root: root, depth: depth), member) do
:lists.member(member, node_bucket(root, depth, bucket_hash(member)))
end
defp set_delete(ordered(bucket: bucket, size: size) = set, member) do
case bucket_delete(bucket, member) do
{ _, 0 } ->
{ set, 0 }
{ new_bucket, -1 } ->
{ ordered(set, size: size - 1, bucket: new_bucket), -1 }
end
end
defp set_delete(trie(root: root, size: size, depth: depth) = set, member) do
pos = bucket_hash(member)
case node_delete(root, depth, pos, member) do
{ _, 0 } ->
{ set, 0 }
{ root, -1 } ->
{ if depth > 0 and trie(set, :contract_on) == size do
root = node_contract(root, depth)
trie(set,
root: root,
size: size - 1,
depth: depth - 1,
contract_on: div(size, @node_size),
expand_on: div(trie(set, :expand_on), @node_size))
else
trie(set, size: size - 1, root: root)
end, -1 }
end
end
defp set_equal?(set1, set2) do
try do
reduce(set1, true, fn member, acc ->
case member?(set2, member) do
true -> acc
_ -> throw(:error)
end
end)
catch
:error -> false
end
end
defp set_disjoint?(set1, set2) do
try do
reduce(set1, true, fn member, acc ->
case member?(set2, member) do
false -> acc
_ -> throw(:error)
end
end)
catch
:error -> false
end
end
defp set_fold(ordered(bucket: bucket), acc, fun) do
bucket_fold(bucket, acc, fun)
end
defp set_fold(trie(root: root, depth: depth), acc, fun) do
node_fold(root, depth, acc, fun, @node_size)
end
## Bucket helpers
defp bucket_filter([e|bucket], fun, acc, count) do
case fun.(e) do
true -> bucket_filter(bucket, fun, [e|acc], count)
false -> bucket_filter(bucket, fun, acc, count + 1)
end
end
defp bucket_filter([], _fun, acc, count) do
{ :lists.reverse(acc), count }
end
defp bucket_put([m|_]=bucket, member) when m > member do
{ [member|bucket], 1 }
end
defp bucket_put([member|bucket], member) do
{ [member|bucket], 0 }
end
defp bucket_put([e|bucket], member) do
{ rest, count } = bucket_put(bucket, member)
{ [e|rest], count }
end
defp bucket_put([], member) do
{ [member], 1 }
end
defp bucket_put!([m|_]=bucket, member) when m > member, do: [member|bucket]
defp bucket_put!([member|bucket], member), do: [member|bucket]
defp bucket_put!([e|bucket], member), do: [e|bucket_put!(bucket, member)]
defp bucket_put!([], member), do: [member]
# Deletes a key from the bucket
defp bucket_delete([m,_|_]=bucket, member) when m > member do
{ bucket, 0 }
end
defp bucket_delete([member|bucket], member) do
{ bucket, -1 }
end
defp bucket_delete([e|bucket], member) do
{ rest, count } = bucket_delete(bucket, member)
{ [e|rest], count }
end
defp bucket_delete([], _member) do
{ [], 0 }
end
defp bucket_fold(bucket, acc, fun) do
:lists.foldl(fun, acc, bucket)
end
defp bucket_hash(key) do
:erlang.phash2(key)
end
defp bucket_nth_index(hash, n) do
(hash >>> (@node_shift * n)) &&& @node_bitmap
end
defp bucket_index(hash) do
hash &&& @node_bitmap
end
defp bucket_next(hash) do
hash >>> @node_shift
end
# Trie resizing
defp contract_trie(trie(depth: 0) = set) do
set
end
defp contract_trie(trie(root: root, depth: depth, size: size, contract_on: contract_on, expand_on: expand_on) = set) when size <= contract_on do
new_contract_on = div(contract_on, @node_size)
new_expand_on = div(expand_on, @node_size)
if new_contract_on == 0, do: new_contract_on = @contract_default
if new_expand_on == 0, do: new_expand_on = @expand_default
contract_trie(trie(set, root: node_contract(root, depth),
size: size,
depth: depth - 1,
contract_on: new_contract_on,
expand_on: new_expand_on))
end
defp contract_trie(set) do
set
end
# Node helpers
# Gets a bucket from the node
defp node_bucket(node, 0, hash) do
elem(node, bucket_index(hash))
end
defp node_bucket(node, depth, hash) do
child = elem(node, bucket_index(hash))
node_bucket(child, depth - 1, bucket_next(hash))
end
defp node_put(node, 0, hash, member) do
pos = bucket_index(hash)
{ new, count } = bucket_put(elem(node, pos), member)
{ set_elem(node, pos, new), count }
end
defp node_put(node, depth, hash, member) do
pos = bucket_index(hash)
{ new, count } = node_put(elem(node, pos), depth - 1, bucket_next(hash), member)
{ set_elem(node, pos, new), count }
end
# Deletes a key from the bucket
defp node_delete(node, 0, hash, member) do
pos = bucket_index(hash)
case bucket_delete(elem(node, pos), member) do
{ _, 0 } -> { node, 0 }
{ new, -1 } -> { set_elem(node, pos, new), -1 }
end
end
defp node_delete(node, depth, hash, member) do
pos = bucket_index(hash)
case node_delete(elem(node, pos), depth - 1, bucket_next(hash), member) do
{ _, 0 } -> { node, 0 }
{ new, -1 } -> { set_elem(node, pos, new), -1 }
end
end
defp node_fold(bucket, -1, acc, fun, _) do
bucket_fold(bucket, acc, fun)
end
defp node_fold(node, depth, acc, fun, count) when count >= 1 do
acc = node_fold(:erlang.element(count, node), depth - 1, acc, fun, @node_size)
node_fold(node, depth, acc, fun, count - 1)
end
defp node_fold(_node, _, acc, _fun, 0) do
acc
end
defp node_filter(bucket, -1, fun, _) do
bucket_filter(bucket, fun, [], 0)
end
defp node_filter(node, depth, fun, count) when count >= 1 do
case node_filter(:erlang.element(count, node), depth - 1, fun, @node_size) do
{ _, 0 } ->
node_filter(node, depth, fun, count - 1)
{ new_element, count1 } ->
{ new_node, count2 } = node_filter(:erlang.setelement(count, node, new_element), depth, fun, count - 1)
{ new_node, count1 + count2 }
end
end
defp node_filter(node, _, _fun, 0) do
{ node, 0 }
end
defp node_relocate(node // @node_template, bucket, n) do
:lists.foldl fn member, acc ->
pos = member |> bucket_hash() |> bucket_nth_index(n)
set_elem(acc, pos, bucket_put!(elem(acc, pos), member))
end, node, bucket
end
# Node resizing
defp node_expand({ b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12, b13, b14, b15, b16 }, 0, n) do
{ node_relocate(b1, n), node_relocate(b2, n), node_relocate(b3, n),
node_relocate(b4, n), node_relocate(b5, n), node_relocate(b6, n),
node_relocate(b7, n), node_relocate(b8, n), node_relocate(b9, n),
node_relocate(b10, n), node_relocate(b11, n), node_relocate(b12, n),
node_relocate(b13, n), node_relocate(b14, n), node_relocate(b15, n),
node_relocate(b16, n) }
end
defp node_expand({ b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12, b13, b14, b15, b16 }, depth, n) do
depth = depth - 1
{ node_expand(b1, depth, n), node_expand(b2, depth, n), node_expand(b3, depth, n),
node_expand(b4, depth, n), node_expand(b5, depth, n), node_expand(b6, depth, n),
node_expand(b7, depth, n), node_expand(b8, depth, n), node_expand(b9, depth, n),
node_expand(b10, depth, n), node_expand(b11, depth, n), node_expand(b12, depth, n),
node_expand(b13, depth, n), node_expand(b14, depth, n), node_expand(b15, depth, n),
node_expand(b16, depth, n) }
end
defp node_contract({ b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12, b13, b14, b15, b16 }, depth) when depth > 0 do
depth = depth - 1
{ node_contract(b1, depth), node_contract(b2, depth), node_contract(b3, depth),
node_contract(b4, depth), node_contract(b5, depth), node_contract(b6, depth),
node_contract(b7, depth), node_contract(b8, depth), node_contract(b9, depth),
node_contract(b10, depth), node_contract(b11, depth), node_contract(b12, depth),
node_contract(b13, depth), node_contract(b14, depth), node_contract(b15, depth),
node_contract(b16, depth) }
end
defp node_contract({ b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12, b13, b14, b15, b16 }, 0) do
b1 |> each_contract(b2) |> each_contract(b3) |> each_contract(b4)
|> each_contract(b5) |> each_contract(b6) |> each_contract(b7)
|> each_contract(b8) |> each_contract(b9) |> each_contract(b10)
|> each_contract(b11) |> each_contract(b12) |> each_contract(b13)
|> each_contract(b14) |> each_contract(b15) |> each_contract(b16)
end
defp each_contract([m1|acc], [m2|_]=bucket) when m1 < m2, do: [m1|each_contract(acc, bucket)]
defp each_contract(acc, [m|bucket]), do: [m|each_contract(acc, bucket)]
defp each_contract([], bucket), do: bucket
defp each_contract(acc, []), do: acc
@doc false
def inspect_depth(trie(depth: d)), do: d
@doc false
def inspect_contract(trie(contract_on: c)), do: c
@doc false
def inspect_expand(trie(expand_on: e)), do: e
end
defimpl Enumerable, for: HashSet do
def reduce(set, acc, fun), do: HashSet.reduce(set, acc, fun)
def member?(set, v), do: HashSet.member?(set, v)
def count(set), do: HashSet.size(set)
end
+476
View File
@@ -0,0 +1,476 @@
import Kernel, except: [inspect: 1]
import Inspect.Algebra
defrecord Inspect.Opts, raw: false, limit: 50, pretty: false, width: 80
defprotocol Inspect do
@moduledoc """
The `Inspect` protocol is responsible for converting any Elixir
data structure into an algebra document. This document is then
formatted, either in pretty printing format or a regular one.
The `inspect/2` function receives the entity to be inspected
followed by the inspecting options, represented by the record
`Inspect.Opts`.
Inspection is done using the functions available in
`Inspect.Algebra` and by calling `Kernel.inspect/2` recursively
passing the `Inspect.Opts` as an argument. When `Kernel.inspect/2`
receives an `Inspect.Opts` record as the second argument, it returns
the underlying algebra document instead of the formatted string.
Many times, inspecting a structure can be implemented in function
of existing entities. For example, here is `HashSet`'s `inspect`
implementation:
defimpl Inspect, for: HashSet do
import Inspect.Algebra
def inspect(dict, opts) do
concat ["#HashSet<", Kernel.inspect(HashSet.to_list(dict), opts), ">"]
end
end
The `concat` function comes from `Inspect.Algebra` and it
concatenates algebra documents together. In the example above,
it is concatenating the string `"HashSet<"` (all strings are
valid algebra documents that keep their formatting when pretty
printed), the document returned by `Kernel.inspect/2` and the
other string `">"`.
Since regular strings are valid entities in an algebra document,
an implementation of inspect may simply return a string,
although that will devoid it of any pretty-printing.
"""
def inspect(thing, opts)
end
defimpl Inspect, for: Atom do
require Macro
@doc """
Represents the atom as an Elixir term. The atoms `false`, `true`
and `nil` are simply quoted. Modules are properly represented
as modules using the dot notation.
Notice that in Elixir, all operators can be represented using
literal atoms (`:+`, `:-`, etc).
## Examples
iex> inspect(:foo)
":foo"
iex> inspect(nil)
"nil"
iex> inspect(Foo.Bar)
"Foo.Bar"
"""
def inspect(atom, _opts) do
inspect(atom)
end
def inspect(false), do: "false"
def inspect(true), do: "true"
def inspect(nil), do: "nil"
def inspect(:""), do: ":\"\""
def inspect(Elixir), do: "Elixir"
def inspect(atom) do
binary = atom_to_binary(atom)
cond do
valid_atom_identifier?(binary) ->
":" <> binary
valid_ref_identifier?(binary) ->
"Elixir." <> rest = binary
rest
atom in [:{}, :[], :<<>>] ->
":" <> binary
atom in Macro.binary_ops or atom in Macro.unary_ops ->
":" <> binary
true ->
<< ?:, ?", Inspect.BitString.escape(binary, ?") :: binary, ?" >>
end
end
# Detect if atom is an atom alias (Elixir.Foo.Bar.Baz)
defp valid_ref_identifier?("Elixir" <> rest) do
valid_ref_piece?(rest)
end
defp valid_ref_identifier?(_), do: false
defp valid_ref_piece?(<<?., h, t :: binary>>) when h in ?A..?Z do
valid_ref_piece? valid_identifier?(t)
end
defp valid_ref_piece?(<<>>), do: true
defp valid_ref_piece?(_), do: false
# Detect if atom
defp valid_atom_identifier?(<<h, t :: binary>>) when h in ?a..?z or h in ?A..?Z or h == ?_ do
case valid_identifier?(t) do
<<>> -> true
<<??>> -> true
<<?!>> -> true
_ -> false
end
end
defp valid_atom_identifier?(_), do: false
defp valid_identifier?(<<h, t :: binary>>)
when h in ?a..?z
when h in ?A..?Z
when h in ?0..?9
when h == ?_ do
valid_identifier? t
end
defp valid_identifier?(other), do: other
end
defimpl Inspect, for: BitString do
@doc %S"""
Represents a string as itself escaping all necessary
characters. Binaries that contain non-printable characters
are printed using the bitstring syntax.
## Examples
iex> inspect("bar")
"\"bar\""
iex> inspect("f\"oo")
"\"f\\\"oo\""
iex> inspect(<<0,1,2>>)
"<<0, 1, 2>>"
"""
def inspect(thing, opts) when is_binary(thing) do
if String.printable?(thing) do
<< ?", escape(thing, ?") :: binary, ?" >>
else
inspect_bitstring(thing, opts)
end
end
def inspect(thing, opts) do
inspect_bitstring(thing, opts)
end
## Escaping
@doc false
def escape(other, char) do
escape(other, char, <<>>)
end
defp escape(<< char, t :: binary >>, char, binary) do
escape(t, char, << binary :: binary, ?\\, char >>)
end
defp escape(<<?#, ?{, t :: binary>>, char, binary) do
escape(t, char, << binary :: binary, ?\\, ?#, ?{ >>)
end
defp escape(<<?\a, t :: binary>>, char, binary) do
escape(t, char, << binary :: binary, ?\\, ?a >>)
end
defp escape(<<?\b, t :: binary>>, char, binary) do
escape(t, char, << binary :: binary, ?\\, ?b >>)
end
defp escape(<<?\d, t :: binary>>, char, binary) do
escape(t, char, << binary :: binary, ?\\, ?d >>)
end
defp escape(<<?\e, t :: binary>>, char, binary) do
escape(t, char, << binary :: binary, ?\\, ?e >>)
end
defp escape(<<?\f, t :: binary>>, char, binary) do
escape(t, char, << binary :: binary, ?\\, ?f >>)
end
defp escape(<<?\n, t :: binary>>, char, binary) do
escape(t, char, << binary :: binary, ?\\, ?n >>)
end
defp escape(<<?\r, t :: binary>>, char, binary) do
escape(t, char, << binary :: binary, ?\\, ?r >>)
end
defp escape(<<?\\, t :: binary>>, char, binary) do
escape(t, char, << binary :: binary, ?\\, ?\\ >>)
end
defp escape(<<?\t, t :: binary>>, char, binary) do
escape(t, char, << binary :: binary, ?\\, ?t >>)
end
defp escape(<<?\v, t :: binary>>, char, binary) do
escape(t, char, << binary :: binary, ?\\, ?v >>)
end
defp escape(<<h, t :: binary>>, char, binary) do
escape(t, char, << binary :: binary, h >>)
end
defp escape(<<>>, _char, binary), do: binary
## Bitstrings
defp inspect_bitstring(bitstring, Inspect.Opts[] = opts) do
each_bit(bitstring, opts.limit, "<<") <> ">>"
end
defp each_bit(_, 0, acc) do
acc <> "..."
end
defp each_bit(<<h, t :: bitstring>>, counter, acc) when t != <<>> do
each_bit(t, decrement(counter), acc <> integer_to_binary(h) <> ", ")
end
defp each_bit(<<h :: size(8)>>, _counter, acc) do
acc <> integer_to_binary(h)
end
defp each_bit(<<>>, _counter, acc) do
acc
end
defp each_bit(bitstring, _counter, acc) do
size = bit_size(bitstring)
<<h :: size(size)>> = bitstring
acc <> integer_to_binary(h) <> "::size(" <> integer_to_binary(size) <> ")"
end
defp decrement(:infinity), do: :infinity
defp decrement(counter), do: counter - 1
end
defimpl Inspect, for: List do
@doc %S"""
Represents a list, checking if it can be printed or not.
If so, a single-quoted representation is returned,
otherwise the brackets syntax is used. Keywords are
printed in keywords syntax.
## Examples
iex> inspect('bar')
"'bar'"
iex> inspect([0|'bar'])
"[0, 98, 97, 114]"
iex> inspect([:foo,:bar])
"[:foo, :bar]"
"""
def inspect([], _opts), do: "[]"
def inspect(thing, Inspect.Opts[] = opts) do
cond do
:io_lib.printable_list(thing) ->
<< ?', Inspect.BitString.escape(String.from_char_list!(thing), ?') :: binary, ?' >>
keyword?(thing) && not opts.raw ->
surround_many("[", thing, "]", opts.limit, keyword(&1, opts))
true ->
surround_many("[", thing, "]", opts.limit, Kernel.inspect(&1, opts))
end
end
defp keyword({key, value}, opts) do
concat(
key_to_binary(key) <> ": ",
Kernel.inspect(value, opts)
)
end
defp key_to_binary(key) do
case Inspect.Atom.inspect(key) do
":" <> right -> right
other -> other
end
end
defp keyword?([{ key, _value } | rest]) when is_atom(key) do
case atom_to_list(key) do
'Elixir.' ++ _ -> false
_ -> keyword?(rest)
end
end
defp keyword?([]), do: true
defp keyword?(_other), do: false
end
defimpl Inspect, for: Tuple do
@doc """
Represents tuples. If the tuple represents a record,
it shows it nicely formatted using the access syntax.
## Examples
iex> inspect({1, 2, 3})
"{1, 2, 3}"
iex> inspect(ArgumentError.new)
"ArgumentError[message: \\\"argument error\\\"]"
"""
def inspect({}, _opts), do: "{}"
def inspect(tuple, opts) do
unless opts.raw do
record_inspect(tuple, opts)
end || surround_many("{", tuple_to_list(tuple), "}", opts.limit, Kernel.inspect(&1, opts))
end
## Helpers
defp record_inspect(record, opts) do
[name|tail] = tuple_to_list(record)
if is_atom(name) && (fields = record_fields(name)) && (length(fields) == size(record) - 1) do
if Enum.first(tail) == :__exception__ do
surround_record(name, tl(fields), tl(tail), opts)
else
surround_record(name, fields, tail, opts)
end
end || surround_many("{", [name|tail], "}", opts.limit, Kernel.inspect(&1, opts))
end
defp record_fields(name) do
case atom_to_binary(name) do
"Elixir." <> _ ->
try do
name.__record__(:fields)
rescue
_ -> nil
end
_ -> nil
end
end
defp surround_record(name, fields, tail, opts) do
fields = lc { field, _ } inlist fields, do: field
concat(
Inspect.Atom.inspect(name, opts),
surround_many("[", Enum.zip(fields, tail), "]", opts.limit, keyword(&1, opts))
)
end
defp keyword({ k, v }, opts) do
concat(
atom_to_binary(k, :utf8) <> ": ",
Kernel.inspect(v, opts)
)
end
end
defimpl Inspect, for: Number do
@doc """
Represents the number as a string.
Floats are represented using the shortened, correctly rounded string
that converts to float when read back with `binary_to_float/1`. This
is done via the Erlang implementation of _Printing Floating-Point
Numbers Quickly and Accurately_ in Proceedings of the SIGPLAN '96
Conference on Programming Language Design and Implementation.
## Examples
iex> inspect(1)
"1"
"""
def inspect(thing, _opts) when is_integer(thing) do
integer_to_binary(thing)
end
def inspect(thing, _opts) do
iolist_to_binary(:io_lib_format.fwrite_g(thing))
end
end
defimpl Inspect, for: Regex do
@doc %S"""
Represents the Regex using the `%r""` syntax.
## Examples
iex> inspect(%r/foo/m)
"%r\"foo\"m"
"""
def inspect(regex, opts) when size(regex) == 5 do
concat ["%r", Kernel.inspect(Regex.source(regex), opts), Regex.opts(regex)]
end
def inspect(other, opts) do
Kernel.inspect(other, opts.raw(true))
end
end
defimpl Inspect, for: Function do
def inspect(function, _opts) do
fun_info = :erlang.fun_info(function)
mod = fun_info[:module]
if fun_info[:type] == :external and fun_info[:env] == [] do
"&#{Inspect.Atom.inspect(mod)}.#{fun_info[:name]}/#{fun_info[:arity]}"
else
case atom_to_list(mod) do
'elixir_compiler_' ++ _ ->
if function_exported?(mod, :__RELATIVE__, 0) do
"#Function<#{uniq(fun_info)} in file:#{mod.__RELATIVE__}>"
else
default_inspect(mod, fun_info)
end
_ ->
default_inspect(mod, fun_info)
end
end
end
defp default_inspect(mod, fun_info) do
"#Function<#{uniq(fun_info)} in #{Inspect.Atom.inspect(mod)}.#{extract_name(fun_info[:name])}>"
end
defp extract_name(name) do
case :binary.split(atom_to_binary(name), "-", [:global]) do
["", name | _] -> name
_ -> name
end
end
defp uniq(fun_info) do
integer_to_binary(fun_info[:new_index]) <> "." <> integer_to_binary(fun_info[:uniq])
end
end
defimpl Inspect, for: PID do
def inspect(pid, _opts) do
"#PID" <> iolist_to_binary(pid_to_list(pid))
end
end
defimpl Inspect, for: Port do
def inspect(port, _opts) do
iolist_to_binary :erlang.port_to_list(port)
end
end
defimpl Inspect, for: Reference do
def inspect(ref, _opts) do
'#Ref' ++ rest = :erlang.ref_to_list(ref)
"#Reference" <> iolist_to_binary(rest)
end
end
defimpl Inspect, for: HashDict do
def inspect(dict, opts) do
concat ["#HashDict<", Inspect.List.inspect(HashDict.to_list(dict), opts), ">"]
end
end
defimpl Inspect, for: HashSet do
def inspect(set, opts) do
concat ["#HashSet<", Inspect.List.inspect(HashSet.to_list(set), opts), ">"]
end
end
+390
View File
@@ -0,0 +1,390 @@
defmodule Inspect.Algebra do
@moduledoc %S"""
A set of functions for creating and manipulating algebra
documents, as described in ["Strictly Pretty" (2000) by Christian Lindig][0].
An algebra document is represented by an `Inspect.Algebra` node
or a regular string.
iex> Inspect.Algebra.empty
:doc_nil
iex> "foo"
"foo"
With the functions in this module, we can concatenate different
elements together and render them:
iex> doc = Inspect.Algebra.concat(Inspect.Algebra.empty, "foo")
iex> Inspect.Algebra.pretty(doc, 80)
"foo"
The functions `nest/2`, `space/2` and `line/2` help you put the
document together into a rigid structure. However, the document
algebra gets interesting when using functions like `break/2`, which
converts the given string into a line break depending on how much space
there is to print. Let's glue two docs together with a break and then
render it:
iex> doc = Inspect.Algebra.glue("a", " ", "b")
iex> Inspect.Algebra.pretty(doc, 80)
"a b"
Notice the break was represented as is, because we haven't reached
a line limit. Once we do, it is replaced by a newline:
iex> doc = Inspect.Algebra.glue(String.duplicate("a", 20), " ", "b")
iex> Inspect.Algebra.pretty(doc, 10)
"aaaaaaaaaaaaaaaaaaaa\nb"
Finally, this module also contains Elixir related functions, a bit
tied to Elixir formatting, namely `surround/3` and `surround_many/5`.
## Implementation details
The original Haskell implementation of the algorithm by [Wadler][1]
relies on lazy evaluation to unfold document groups on two alternatives:
`:flat` (breaks as spaces) and `:break` (breaks as newlines).
Implementing the same logic in a strict language such as Elixir leads
to an exponential growth of possible documents, unless document groups
are encoded explictly as `:flat` or `:break`. Those groups are then reduced
to a simple document, where the layout is already decided, per [Lindig][0].
This implementation slightly changes the semantic of Lindig's algorithm
to allow elements that belong to the same group to be printed together
in the same line, even if they do not fit the line fully. This was achieved
by changing `:break` to mean a possible break and `:flat` to force a flat
structure. Then deciding if a break works as a newline is just a matter
of checking if we have enough space until the next break that is not
inside a group (which is still flat).
Custom pretty printers can be implemented using the documents returned
by this module and by providing their own rendering functions.
[0]: http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.34.2200
[1]: http://homepages.inf.ed.ac.uk/wadler/papers/prettier/prettier.pdf
"""
@surround_separator ","
@tail_separator " |"
@newline "\n"
@nesting 1
@break " "
defp repeat(_, 0), do: ""
defp repeat(s, i), do: :lists.duplicate(i, s)
# Functional interface to `doc` records
@type t :: :doc_nil | doc_cons_t | doc_nest_t | doc_break_t | doc_group_t | binary
defrecordp :doc_cons, left: :doc_nil :: t, right: :doc_nil :: t
defrecordp :doc_nest, indent: 1 :: non_neg_integer, doc: :doc_nil :: t
defrecordp :doc_break, str: " " :: binary
defrecordp :doc_group, doc: :doc_nil :: t
@doc """
Returns `:doc_nil` which is a document entity used to represent
nothingness. Takes no arguments.
## Examples
iex> Inspect.Algebra.empty
:doc_nil
"""
@spec empty() :: :doc_nil
def empty, do: :doc_nil
@doc """
Concatenates two document entities. Takes two arguments:
left doc and right doc. Returns a DocCons doc
## Examples
iex> doc = Inspect.Algebra.concat "Tasteless", "Artosis"
iex> Inspect.Algebra.pretty(doc, 80)
"TastelessArtosis"
"""
@spec concat(t, t) :: doc_cons_t
def concat(x, y), do: doc_cons(left: x, right: y)
@doc """
Concatenates a list of documents.
"""
@spec concat([t]) :: doc_cons_t
def concat(docs), do: folddoc(docs, concat(&1, &2))
@doc """
Nests document entity `x` positions deep. Nesting will be
appended to the line breaks.
## Examples
iex> doc = Inspect.Algebra.nest(Inspect.Algebra.concat(Inspect.Algebra.break, "6"), 5)
iex> Inspect.Algebra.pretty(doc, 80)
" 6"
"""
@spec nest(t, non_neg_integer) :: doc_nest_t
def nest(x, 0), do: x
def nest(x, i) when is_integer(i), do: doc_nest(indent: i, doc: x)
@doc %S"""
Document entity representing a break. This break can
be rendered as a linebreak or as spaces, depending on the
`mode` of the chosen layout or the provided separator.
## Examples
Let's glue two docs together with a break and then render it:
iex> doc = Inspect.Algebra.glue("a", " ", "b")
iex> Inspect.Algebra.pretty(doc, 80)
"a b"
Notice the break was represented as is, because we haven't reached
a line limit. Once we do, it is replaced by a newline:
iex> doc = Inspect.Algebra.glue(String.duplicate("a", 20), " ", "b")
iex> Inspect.Algebra.pretty(doc, 10)
"aaaaaaaaaaaaaaaaaaaa\nb"
"""
@spec break(binary) :: doc_break_t
def break(s) when is_binary(s), do: doc_break(str: s)
@spec break() :: doc_break_t
def break(), do: doc_break(str: @break)
@doc """
Inserts a break between two docs. See `break/1` for more info.
"""
@spec glue(t, t) :: doc_cons_t
def glue(x, y), do: concat(x, concat(break, y))
@doc """
Inserts a break, passed as the second argument, between two docs,
the first and the third arguments.
"""
@spec glue(t, binary, t) :: doc_cons_t
def glue(x, g, y) when is_binary(g), do: concat(x, concat(break(g), y))
@doc %S"""
Returns a group containing the specified document.
## Examples
iex> doc = Inspect.Algebra.group(
...> Inspect.Algebra.concat(
...> Inspect.Algebra.group(
...> Inspect.Algebra.concat(
...> "Hello,",
...> Inspect.Algebra.concat(
...> Inspect.Algebra.break,
...> "A"
...> )
...> )
...> ),
...> Inspect.Algebra.concat(
...> Inspect.Algebra.break,
...> "B"
...> )
...> ))
iex> Inspect.Algebra.pretty(doc, 80)
"Hello, A B"
iex> Inspect.Algebra.pretty(doc, 6)
"Hello,\nA B"
"""
@spec group(t) :: doc_group_t
def group(d), do: doc_group(doc: d)
@doc """
Inserts a mandatory single space between two document entities.
## Examples
iex> doc = Inspect.Algebra.space "Hughes", "Wadler"
iex> Inspect.Algebra.pretty(doc, 80)
"Hughes Wadler"
"""
@spec space(t, t) :: doc_cons_t
def space(x, y), do: concat(x, concat(" ", y))
@doc %S"""
Inserts a mandatory linebreak between two document entities.
## Examples
iex> doc = Inspect.Algebra.line "Hughes", "Wadler"
iex> Inspect.Algebra.pretty(doc, 80)
"Hughes\nWadler"
"""
@spec line(t, t) :: doc_cons_t
def line(x, y), do: concat(x, concat(@newline, y))
@doc """
Folds a list of document entities into a document entity
using a function that is passed as the first argument.
## Examples
iex> doc = ["A", "B"]
iex> doc = Inspect.Algebra.folddoc(doc, fn(x,y) ->
...> Inspect.Algebra.concat [x, "!", y]
...> end)
iex> Inspect.Algebra.pretty(doc, 80)
"A!B"
"""
@spec folddoc([any], ((any, [t]) -> t)) :: t
def folddoc([], _), do: empty
def folddoc([doc], _), do: doc
def folddoc([d|ds], f), do: f.(d, folddoc(ds, f))
# Elixir conveniences
@doc %S"""
Surrounds a document with characters.
Puts the document between left and right enclosing and nesting it.
The document is marked as a group, to show the maximum as possible
concisely together.
## Examples
iex> doc = Inspect.Algebra.surround "[", Inspect.Algebra.glue("a", "b"), "]"
iex> Inspect.Algebra.pretty(doc, 3)
"[a\n b]"
"""
@spec surround(binary, t, binary) :: t
def surround(left, doc, right) do
group concat [left, nest(doc, @nesting), right]
end
@doc %S"""
Maps and glues a collection of items together using the given separator
and surrounds them. A limit can be passed which, once reached, stops
gluing and outputs "..." instead.
## Examples
iex> doc = Inspect.Algebra.surround_many("[", Enum.to_list(1..5), "]", :infinity, integer_to_binary(&1))
iex> Inspect.Algebra.pretty(doc, 5)
"[1,\n 2,\n 3,\n 4,\n 5]"
iex> doc = Inspect.Algebra.surround_many("[", Enum.to_list(1..5), "]", 3, integer_to_binary(&1))
iex> Inspect.Algebra.pretty(doc, 20)
"[1, 2, 3, ...]"
"""
@spec surround_many(binary, [any], binary, integer | :infinity, (term -> t)) :: t
def surround_many(left, [], right, _, _fun) do
concat(left, right)
end
def surround_many(left, docs, right, limit, fun) do
surround(left, surround_many(docs, limit, fun), right)
end
defp surround_many(_, 0, _fun) do
"..."
end
defp surround_many([h], _limit, fun) do
fun.(h)
end
defp surround_many([h|t], limit, fun) when is_list(t) do
glue(
concat(fun.(h), @surround_separator),
surround_many(t, decrement(limit), fun)
)
end
defp surround_many([h|t], _limit, fun) do
glue(
concat(fun.(h), @tail_separator),
fun.(t)
)
end
defp decrement(:infinity), do: :infinity
defp decrement(counter), do: counter - 1
@doc """
The pretty printing function.
Takes the maximum width and a document to print as its arguments
and returns the string representation of the best layout for the
document to fit in the given width.
"""
@spec pretty(t, non_neg_integer | :infinity) :: binary
def pretty(d, w) do
sdoc = format w, 0, [{0, default_mode(w), doc_group(doc: d)}]
render(sdoc)
end
defp default_mode(:infinity), do: :flat
defp default_mode(_), do: :break
# Rendering and internal helpers
# Records representing __simple__ documents, already on a fixed layout
# Those are generalized by `sdoc` type.
@type sdoc :: :s_nil | s_text_t | s_line_t
defrecordp :s_text, str: "" :: binary, sdoc: :s_nil :: sdoc
defrecordp :s_line, indent: 1 :: non_neg_integer, sdoc: :s_nil :: sdoc
# Record representing the document mode to be rendered: flat or broken
@typep mode :: :flat | :break
@doc false
@spec fits?(integer, [{ integer, mode, t }]) :: boolean
def fits?(:infinity, _), do: true # no pretty printing
def fits?(w, _) when w < 0, do: false
def fits?(_, []), do: true
def fits?(w, [{_, _, :doc_nil} | t]), do: fits?(w, t)
def fits?(w, [{i, m, doc_cons(left: x, right: y)} | t]), do: fits?(w, [{i, m, x} | [{i, m, y} | t]])
def fits?(w, [{i, m, doc_nest(indent: j, doc: x)} | t]), do: fits?(w, [{i + j, m, x} | t])
def fits?(w, [{_, _, s} | t]) when is_binary(s), do: fits?((w - byte_size s), t)
def fits?(w, [{_, :flat, doc_break(str: s)} | t]), do: fits?((w - byte_size s), t)
def fits?(_, [{_, :break, doc_break(str: _)} | _]), do: true
def fits?(w, [{i, _, doc_group(doc: x)} | t]), do: fits?(w, [{i, :flat, x} | t])
@doc false
@spec format(integer, integer, [{ integer, mode, t }]) :: atom | tuple
def format(_, _, []), do: :s_nil
def format(w, k, [{_, _, :doc_nil} | t]), do: format(w, k, t)
def format(w, k, [{i, m, doc_cons(left: x, right: y)} | t]), do: format(w, k, [{i, m, x} | [{i, m, y} | t]])
def format(w, k, [{i, m, doc_nest(indent: j, doc: x)} | t]), do: format(w, k, [{i + j, m, x} | t])
def format(w, k, [{_, _, s} | t]) when is_binary(s), do: s_text(str: s, sdoc: format(w, (k + byte_size s), t))
def format(w, k, [{i, m, doc_group(doc: x)} | t]), do: format(w, k, [{i, m, x} | t])
def format(w, k, [{_, :flat, doc_break(str: s)} | t]), do: s_text(str: s, sdoc: format(w, (k + byte_size s), t))
def format(w, k, [{i, :break, doc_break(str: s)} | t]) do
k = k + byte_size(s)
if fits?(w - k, t) do
s_text(str: s, sdoc: format(w, k, t))
else
s_line(indent: i, sdoc: format(w, i, t))
end
end
@doc false
@spec render(sdoc) :: binary
def render(sdoc) do
iolist_to_binary do_render sdoc
end
@spec do_render(sdoc) :: [binary]
defp do_render(:s_nil), do: [""]
defp do_render(s_text(str: s, sdoc: d)), do: [s | do_render(d)]
defp do_render(s_line(indent: i, sdoc: d)) do
prefix = repeat " ", i
[@newline | [prefix | do_render d]]
end
end
+19
View File
@@ -0,0 +1,19 @@
defmodule Integer do
@moduledoc """
Functions for working with integers.
"""
@doc """
Returns true if `n` is an odd number, otherwise false.
"""
defmacro odd?(n) do
quote do: rem(unquote(n), 2) != 0
end
@doc """
Returns true if `n` is an even number, otherwise false.
"""
defmacro even?(n) do
quote do: rem(unquote(n), 2) == 0
end
end
+145 -82
View File
@@ -5,7 +5,7 @@ defmodule IO do
An IO device must be a pid or an atom representing a process.
For convenience, Elixir provides `:stdio` and `:stderr` as
shortcut to Erlang's `:standard_io` and `:standard_error`.
shortcuts to Erlang's `:standard_io` and `:standard_error`.
An io data can be:
@@ -18,67 +18,64 @@ defmodule IO do
* A list of binaries or a list of char lists (as described above);
* If none of the above, `to_binary` is invoked in the
* If none of the above, `to_string` is invoked on the
given argument;
"""
import :erlang, only: [group_leader: 0]
@doc """
Reads `count` bytes from the IO device. It returns:
* `data` - The input characters.
* :eof - End of file was encountered.
* {:error, reason} - Other (rare) error condition,
for instance {:error, :estale} if reading from an
NFS file system.
"""
def read(device // group_leader(), count) do
:io.get_chars(map_dev(device), "", count)
defmacrop is_iolist(data) do
quote do
is_list(unquote(data)) or is_binary(unquote(data))
end
end
@doc """
Reads `count` bytes from the IO device as binary,
no unicode conversion happens.
Reads `count` characters from the IO device or until
the end of the line if `:line` is given. It returns:
Check `read/2` for more information.
* `data` - The input characters.
* `:eof` - End of file was encountered.
* `{:error, reason}` - Other (rare) error condition,
for instance `{:error, :estale}` if reading from an
NFS file system.
"""
def binread(device // group_leader(), count) do
case :file.read(map_dev(device), count) do
def read(device // group_leader, chars_or_line)
def read(device, :line) do
:io.get_line(map_dev(device), '')
end
def read(device, count) when count >= 0 do
:io.get_chars(map_dev(device), '', count)
end
@doc """
Reads `count` bytes from the IO device or until
the end of the line if `:line` is given. It returns:
* `data` - The input characters.
* `:eof` - End of file was encountered.
* `{:error, reason}` - Other (rare) error condition,
for instance `{:error, :estale}` if reading from an
NFS file system.
"""
def binread(device // group_leader, chars_or_line)
def binread(device, :line) do
case :file.read_line(map_dev(device)) do
{ :ok, data } -> data
other -> other
end
end
@doc """
Reads a line from the IO device. It returns:
* `data` - The input characters.
* :eof - End of file was encountered.
* {:error, reason} - Other (rare) error condition,
for instance {:error, :estale} if reading from an
NFS file system.
This function does the same as `gets/2`,
except the prompt is not required as argument.
"""
def readline(device // group_leader()) do
:io.get_line(map_dev(device), "")
end
@doc """
Reads a line from the IO device as binary,
no unicode conversion happens.
Check `readline/1` for more information.
"""
def binreadline(device // group_leader()) do
case :file.read_line(map_dev(device)) do
def binread(device, count) when count >= 0 do
case :file.read(map_dev(device), count) do
{ :ok, data } -> data
other -> other
end
@@ -101,8 +98,8 @@ defmodule IO do
#=> "error"
"""
def write(device // group_leader(), item) do
:io.put_chars map_dev(device), to_iodata(item)
def write(device // group_leader(), item) when is_iolist(item) do
:io.put_chars map_dev(device), item
end
@doc """
@@ -111,34 +108,27 @@ defmodule IO do
Check `write/2` for more information.
"""
def binwrite(device // group_leader(), item) do
:file.write map_dev(device), to_iodata(item)
def binwrite(device // group_leader(), item) when is_iolist(item) do
:file.write map_dev(device), item
end
@doc """
Writes the argument to the device, similarly to write
but adds a new line at the end. The argument is expected
Writes the argument to the device, similar to `write/2`,
but adds a newline at the end. The argument is expected
to be a chardata.
"""
def puts(device // group_leader(), item) do
def puts(device // group_leader(), item) when is_iolist(item) do
erl_dev = map_dev(device)
:io.put_chars erl_dev, [to_iodata(item), ?\n]
end
@doc """
Writes the given arguments to stderr as a warning.
If `--warnings-as-errors` is true and this function is
called during compilation, compilation will fail.
"""
def warn(item) do
:elixir_code_server.cast(:register_warning)
:io.put_chars :standard_error, [to_iodata(item), ?\n]
:io.put_chars erl_dev, [item, ?\n]
end
@doc """
Inspects and writes the given argument to the device
followed by a new line. A set of options can be given.
followed by a newline. A set of options can be given.
It sets by default pretty printing to true and the
width to be the width of the device, with a minimum
of 80 characters.
## Examples
@@ -152,23 +142,33 @@ defmodule IO do
@doc """
Inspects the item with options using the given device.
"""
def inspect(device, item, opts) do
def inspect(device, item, opts) when is_list(opts) do
opts = Keyword.put_new(opts, :pretty, true)
unless Keyword.get(opts, :width) do
opts = case :io.columns(device) do
{ :ok, width } -> Keyword.put(opts, :width, width)
{ :error, _ } -> opts
end
end
puts device, Kernel.inspect(item, opts)
item
end
@doc """
Gets a number of bytes from the io device. If the
io device is a unicode device, count implies
io device is a unicode device, `count` implies
the number of unicode codepoints to be retrieved.
Otherwise, the number of raw bytes. It returns:
Otherwise, `count` is the number of raw bytes to be retrieved.
It returns:
* `data` - The input characters.
* :eof - End of file was encountered.
* `:eof` - End of file was encountered.
* {:error, reason} - Other (rare) error condition,
for instance {:error, :estale} if reading from an
* `{:error, reason}` - Other (rare) error condition,
for instance `{:error, :estale}` if reading from an
NFS file system.
"""
def getn(prompt, count // 1)
@@ -183,12 +183,12 @@ defmodule IO do
@doc """
Gets a number of bytes from the io device. If the
io device is a unicode device, count implies
io device is a unicode device, `count` implies
the number of unicode codepoints to be retrieved.
Otherwise, the number of raw bytes.
Otherwise, `count` is the number of raw bytes to be retrieved.
"""
def getn(device, prompt, count) do
:io.get_chars(map_dev(device), to_iodata(prompt), count)
:io.get_chars(map_dev(device), prompt, count)
end
@doc """
@@ -197,21 +197,84 @@ defmodule IO do
* `data` - The characters in the line terminated
by a LF (or end of file).
* :eof - End of file was encountered.
* `:eof` - End of file was encountered.
* {:error, reason} - Other (rare) error condition,
for instance {:error, :estale} if reading from an
* `{:error, reason}` - Other (rare) error condition,
for instance `{:error, :estale}` if reading from an
NFS file system.
"""
def gets(device // group_leader(), prompt) do
:io.get_line(map_dev(device), to_iodata(prompt))
:io.get_line(map_dev(device), prompt)
end
@doc """
Converts the io device into a Stream. The device is
iterated line by line if :line is given or by a given
number of codepoints.
This reads the io as utf-8. Check out
`IO.binstream/2` to handle the IO as a raw binary.
## Examples
Here is an example on how we mimic an echo server
from the command line:
Enum.each IO.stream(:stdio, :line), IO.write(&1)
"""
def stream(device, line_or_bytes) do
stream(map_dev(device), line_or_bytes, &1, &2)
end
@doc false
def stream(device) do
IO.write "IO.stream(device) is deprecated, please use IO.stream(device, :line) instead\n#{Exception.format_stacktrace}"
stream(device, :line)
end
@doc """
Converts the io device into a Stream. The device is
iterated line by line.
This reads the io as a raw binary.
"""
def binstream(device, line_or_bytes) do
binstream(map_dev(device), line_or_bytes, &1, &2)
end
@doc false
def binstream(device) do
IO.write "IO.binstream(device) is deprecated, please use IO.binstream(device, :line) instead\n#{Exception.format_stacktrace}"
binstream(device, :line)
end
@doc false
def stream(device, what, acc, fun) do
case read(device, what) do
:eof ->
acc
{ :error, reason } ->
raise File.IteratorError, reason: reason
data ->
stream(device, what, fun.(data, acc), fun)
end
end
@doc false
def binstream(device, what, acc, fun) do
case binread(device, what) do
:eof ->
acc
{ :error, reason } ->
raise File.IteratorError, reason: reason
data ->
binstream(device, what, fun.(data, acc), fun)
end
end
# 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 to_iodata(io) when is_list(io) or is_binary(io), do: io
defp to_iodata(other), do: to_binary(other)
end
+14 -7
View File
@@ -1,14 +1,14 @@
defmodule IO.ANSI.Sequence do
@moduledoc false
defmacro defsequence(name, code) do
quote binding: [name: name, code: code] do
defmacro defsequence(name, code // "", terminator // "m") do
quote bind_quoted: [name: name, code: code, terminator: terminator] do
def unquote(name)() do
"\e[#{unquote(code)}m"
"\e[#{unquote(code)}#{unquote(terminator)}"
end
defp escape_sequence(<< unquote(atom_to_binary(name)), rest :: binary >>) do
{ "\e[#{unquote(code)}m", rest }
{ "\e[#{unquote(code)}#{unquote(terminator)}", rest }
end
end
end
@@ -121,6 +121,13 @@ defmodule IO.ANSI do
@doc "Not overlined"
defsequence :not_overlined, 55
@doc "Send cursor home"
defsequence :home, "", "H"
@doc "Clear screen"
defsequence :clear, "2", "J"
# Catch spaces between codes
defp escape_sequence(<< ?\s, rest :: binary >>) do
escape_sequence(rest)
@@ -131,7 +138,7 @@ defmodule IO.ANSI do
raise ArgumentError, message: "invalid ANSI sequence specification: #{spec}"
end
@doc %B"""
@doc %S"""
Escapes a string by converting named ANSI sequences into actual ANSI codes.
The format for referring to sequences is `%{red}` and `%{red,bright}` (for
@@ -160,7 +167,7 @@ defmodule IO.ANSI do
end
end
@doc %B"""
@doc %S"""
Escapes a string by converting named ANSI sequences into actual ANSI codes.
The format for referring to sequences is `%{red}` and `%{red,bright}` (for
@@ -201,7 +208,7 @@ defmodule IO.ANSI do
do_escape(rest, false, emit, emitted, [x|acc])
end
defp do_escape("", false, _emit, emitted, acc) do
{list_to_binary(Enum.reverse(acc)), emitted}
{iolist_to_binary(Enum.reverse(acc)), emitted}
end
defp do_escape_sequence(rest, emit, acc) do
+550 -459
View File
File diff suppressed because it is too large Load Diff
+49 -28
View File
@@ -1,18 +1,18 @@
defmodule Kernel.CLI do
@moduledoc """
Module responsible for controlling Elixir's CLI
"""
@moduledoc false
defrecord Config, commands: [], output: ".", compile: [],
halt: true, compiler_options: [], errors: []
halt: true, compiler_options: [], errors: [],
verbose_compile: false
# This is the API invoked by Elixir boot process.
@doc false
@doc """
This is the API invoked by Elixir boot process.
"""
def main(argv) do
argv = lc arg inlist argv, do: :unicode.characters_to_binary(arg)
argv = lc arg inlist argv, do: String.from_char_list!(arg)
{ config, argv } = process_argv(argv, Kernel.CLI.Config.new)
:elixir_code_server.cast({ :argv, argv })
System.argv(argv)
run fn ->
command_results = Enum.map(Enum.reverse(config.commands), process_command(&1, config))
@@ -41,13 +41,6 @@ defmodule Kernel.CLI do
at_exit(0)
System.halt(0)
end
rescue
exception ->
at_exit(1)
trace = System.stacktrace
IO.puts :stderr, "** (#{inspect exception.__record__(:name)}) #{exception.message}"
IO.puts :stderr, Exception.format_stacktrace(trace)
System.halt(1)
catch
:exit, reason when is_integer(reason) ->
at_exit(reason)
@@ -57,9 +50,7 @@ defmodule Kernel.CLI do
System.halt(0)
kind, reason ->
at_exit(1)
trace = System.stacktrace
IO.puts :stderr, "** (#{kind}) #{inspect(reason)}"
IO.puts :stderr, Exception.format_stacktrace(trace)
print_error(kind, Exception.normalize(kind, reason), System.stacktrace)
System.halt(1)
end
end
@@ -72,16 +63,9 @@ defmodule Kernel.CLI do
lc hook inlist hooks do
try do
hook.(status)
rescue
exception ->
trace = System.stacktrace
IO.puts :stderr, "** (#{inspect exception.__record__(:name)}) #{exception.message}"
IO.puts :stderr, Exception.format_stacktrace(trace)
catch
kind, reason ->
trace = System.stacktrace
IO.puts :stderr, "** #{kind} #{inspect(reason)}"
IO.puts :stderr, Exception.format_stacktrace(trace)
print_error(kind, Exception.normalize(kind, reason), System.stacktrace)
end
end
@@ -100,6 +84,34 @@ defmodule Kernel.CLI do
end
end
defp print_error(:error, exception, trace) do
IO.puts :stderr, "** (#{inspect exception.__record__(:name)}) #{exception.message}"
IO.puts :stderr, format_stacktrace(trace)
end
defp print_error(kind, reason, trace) do
IO.puts :stderr, "** #{kind} #{inspect(reason)}"
IO.puts :stderr, format_stacktrace(trace)
end
defp format_stacktrace(stack) do
Exception.format_stacktrace(prune_stacktrace(stack))
end
@elixir_internals [:elixir_compiler, :elixir_module]
defp prune_stacktrace([{ mod, _, _, _ }|t]) when mod in @elixir_internals do
prune_stacktrace(t)
end
defp prune_stacktrace([h|t]) do
[h|prune_stacktrace(t)]
end
defp prune_stacktrace([]) do
[]
end
# Process shared options
defp process_shared([opt|_t], _config) when opt in ["-v", "--version"] do
@@ -141,6 +153,10 @@ defmodule Kernel.CLI do
process_shared t, config
end
defp process_shared([erl|t], config) when erl in ["--detached", "--hidden"] do
process_shared t, config
end
defp process_shared(list, config) do
{ list, config }
end
@@ -201,6 +217,10 @@ defmodule Kernel.CLI do
defp process_compiler(["--warnings-as-errors"|t], config) do
process_compiler t, config.update_compiler_options([{:warnings_as_errors, true}|&1])
end
defp process_compiler(["--verbose"|t], config) do
process_compiler t, config.verbose_compile(true)
end
defp process_compiler([h|t] = list, config) do
case h do
@@ -322,13 +342,14 @@ defmodule Kernel.CLI do
:filelib.ensure_dir(:filename.join(config.output, "."))
files = Enum.map patterns, Path.wildcard(&1)
files = Enum.uniq(List.concat(files))
files = Enum.uniq(Enum.concat(files))
files = Enum.filter files, :filelib.is_regular(&1)
if files != [] do
Code.compiler_options(config.compiler_options)
Kernel.ParallelCompiler.files_to_path(files, config.output,
fn file -> IO.puts "Compiled #{file}" end)
each_file: fn file -> if config.verbose_compile do IO.puts "Compiled #{file}" end end)
:ok
else
{ :error, "--compile : No files matched patterns #{Enum.join(patterns, ",")}" }
end
+66 -35
View File
@@ -1,14 +1,8 @@
defexception Kernel.CompilationError, message: "compilation failed"
defmodule Kernel.ParallelCompiler do
alias :orddict, as: OrdDict
@moduledoc """
A module responsible for compiling files in parallel.
"""
defmacrop default_callback, do: quote(do: fn x -> x end)
@doc """
Compiles the given files.
@@ -17,45 +11,63 @@ defmodule Kernel.ParallelCompiler do
the current file stops being compiled until the dependency is
resolved.
If there is any error during compilation or if warnings_as_errors
is set to true and there is a warning, this function will fail
If there is an error during compilation or if `warnings_as_errors`
is set to `true` and there is a warning, this function will fail
with an exception.
A callback that is invoked every time a file is compiled
with its name can be optionally given as argument.
This function receives a set of callbacks as options:
* `:each_file` - for each file compiled, invokes the callback passing the file
* `:each_module` - for each module compiled, invokes the callback
passing the file, module and the module bytecode
* `:each_waiting` - every time a module waits for another module to be compiled,
this callback is invoked with the module we are waiting
The compiler doesn't care about the return values of the callbacks except
by `each_waiting`, which must return nil or a file as a hint where the source
could be found.
Returns the modules generated by each compiled file.
"""
def files(files, callback // default_callback) do
spawn_compilers(files, nil, callback)
def files(files, callbacks // [])
def files(files, callbacks) when is_list(callbacks) do
spawn_compilers(files, nil, callbacks)
end
@doc """
Compiles the given files to the given path.
Read files/2 for more information.
Read `files/2` for more information.
"""
def files_to_path(files, path, callback // default_callback) when is_binary(path) do
spawn_compilers(files, path, callback)
def files_to_path(files, path, callbacks // [])
def files_to_path(files, path, callbacks) when is_binary(path) and is_list(callbacks) do
spawn_compilers(files, path, callbacks)
end
defp spawn_compilers(files, path, callback) do
defp spawn_compilers(files, path, callbacks) do
Code.ensure_loaded(Kernel.ErrorHandler)
:elixir_code_server.cast(:reset_warnings)
schedulers = max(:erlang.system_info(:schedulers_online), 2)
result = spawn_compilers(files, path, callback, [], [], schedulers, [])
result = spawn_compilers(files, files, path, callbacks, [], [], schedulers, [])
# In case --warning-as-errors is enabled and there was a warning,
# compilation status will be set to error and we fail with Kernel.CompilationError
case :elixir_code_server.call(:compilation_status) do
:ok -> result
:error -> raise Kernel.CompilationError, [], []
:error -> raise CompileError, [], []
end
end
# We already have 4 currently running, don't spawn new ones
defp spawn_compilers(files, output, callback, waiting, queued, schedulers, result) when
defp spawn_compilers(files, original, output, callbacks, waiting, queued, schedulers, result) when
length(queued) - length(waiting) >= schedulers do
wait_for_messages(files, output, callback, waiting, queued, schedulers, result)
wait_for_messages(files, original, output, callbacks, waiting, queued, schedulers, result)
end
# Spawn a compiler for each file in the list until we reach the limit
defp spawn_compilers([h|t], output, callback, waiting, queued, schedulers, result) do
defp spawn_compilers([h|t], original, output, callbacks, waiting, queued, schedulers, result) do
parent = self()
child = spawn_link fn ->
@@ -76,40 +88,59 @@ defmodule Kernel.ParallelCompiler do
end
end
spawn_compilers(t, output, callback, waiting, [{child, h}|queued], schedulers, result)
spawn_compilers(t, original, output, callbacks, waiting, [{child, h}|queued], schedulers, result)
end
# No more files, nothing waiting, queue is empty, we are done
defp spawn_compilers([], _output, _callback, [], [], _schedulers, result), do: result
defp spawn_compilers([], _original, _output, _callbacks, [], [], _schedulers, result), do: result
# Queued x, waiting for x: POSSIBLE ERROR! Release processes so we get the failures
defp spawn_compilers([], output, callback, waiting, queued, schedulers, result) when length(waiting) == length(queued) do
defp spawn_compilers([], original, output, callbacks, waiting, queued, schedulers, result) when length(waiting) == length(queued) do
Enum.each queued, fn { child, _ } -> child <- { :release, self() } end
wait_for_messages([], output, callback, waiting, queued, schedulers, result)
wait_for_messages([], original, output, callbacks, waiting, queued, schedulers, result)
end
# No more files, but queue and waiting are not full or do not match
defp spawn_compilers([], output, callback, waiting, queued, schedulers, result) do
wait_for_messages([], output, callback, waiting, queued, schedulers, result)
defp spawn_compilers([], original, output, callbacks, waiting, queued, schedulers, result) do
wait_for_messages([], original, output, callbacks, waiting, queued, schedulers, result)
end
# Wait for messages from child processes
defp wait_for_messages(files, output, callback, waiting, queued, schedulers, result) do
defp wait_for_messages(files, original, output, callbacks, waiting, queued, schedulers, result) do
receive do
{ :compiled, child, file } ->
callback.(file)
if callback = Keyword.get(callbacks, :each_file) do
callback.(file)
end
new_queued = List.keydelete(queued, child, 0)
# Sometimes we may have spurious entries in the waiting
# list because someone invoked try/rescue UndefinedFunctionError
new_waiting = List.keydelete(waiting, child, 0)
spawn_compilers(files, output, callback, new_waiting, new_queued, schedulers, result)
{ :module_available, _child, module, binary } ->
spawn_compilers(files, original, output, callbacks, new_waiting, new_queued, schedulers, result)
{ :module_available, child, file, module, binary } ->
if callback = Keyword.get(callbacks, :each_module) do
callback.(file, module, binary)
end
# Release the module loader which is waiting for an ack
child <- { self, :ack }
new_waiting = release_waiting_processes(module, waiting)
new_result = [{module, binary}|result]
wait_for_messages(files, output, callback, new_waiting, queued, schedulers, new_result)
new_result = [module|result]
wait_for_messages(files, original, output, callbacks, new_waiting, queued, schedulers, new_result)
{ :waiting, child, on } ->
new_waiting = OrdDict.store(child, on, waiting)
spawn_compilers(files, output, callback, new_waiting, queued, schedulers, result)
# If one of the callbacks is each_waiting and we haven't seen
# the hinted file before, add it to the list to be processed.
if (callback = Keyword.get(callbacks, :each_waiting)) &&
(hint = callback.(on)) &&
not(hint in original) do
files = [hint|files]
original = [hint|original]
end
new_waiting = :orddict.store(child, on, waiting)
spawn_compilers(files, original, output, callbacks, new_waiting, queued, schedulers, result)
{ :failure, child, kind, reason, stacktrace } ->
if many_missing?(child, files, waiting, queued) do
IO.puts "== Compilation failed =="
+6 -4
View File
@@ -10,6 +10,8 @@ defmodule Kernel.ParallelRequire do
A callback that is invoked every time a file is required
can be optionally given as argument.
Returns the modules generated by each required file.
"""
def files(files, callback // default_callback) do
schedulers = max(:erlang.system_info(:schedulers_online), 2)
@@ -45,8 +47,8 @@ defmodule Kernel.ParallelRequire do
:erlang.process_flag(:error_handler, handler)
try do
new = Code.require_file(h)
parent <- { :required, self, new, h }
new = Code.require_file(h) || []
parent <- { :required, self, Enum.map(new, &elem(&1, 0)), h }
catch
kind, reason ->
parent <- { :failure, self, kind, reason, System.stacktrace }
@@ -58,9 +60,9 @@ defmodule Kernel.ParallelRequire do
defp wait_for_messages(files, waiting, callback, schedulers, result) do
receive do
{ :required, child, new, file } ->
{ :required, child, mods, file } ->
callback.(file)
spawn_requires(files, List.delete(waiting, child), callback, schedulers, (new || []) ++ result)
spawn_requires(files, List.delete(waiting, child), callback, schedulers, mods ++ result)
{ :failure, _child, kind, reason, stacktrace } ->
:erlang.raise(kind, reason, stacktrace)
end
+10 -1
View File
@@ -119,7 +119,16 @@ defmodule Kernel.RecordRewriter do
## Expr
defp optimize_expr({ :call, call_line, { :remote, line, left, right }, args }, module, dict) do
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 } = call, 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)
+162 -64
View File
@@ -16,7 +16,7 @@ defmodule Kernel.SpecialForms do
Finally, it also documents 3 special forms (`__block__`,
`__scope__` and `__aliases__`), which are not intended to be
called directly by the developer but they appear in quoted
contents since they are essential in Elixir's constructions.
contents since they are essential in Elixir's constructs.
"""
@doc """
@@ -51,27 +51,32 @@ defmodule Kernel.SpecialForms do
## Bitstring types
A bitstring may contain many parts and those may have
specific types. Most of the time, Elixir will figure out
the part's type and won't require any work from you:
A bitstring is made of many segments. Each segment has a
type, which defaults to integer:
iex> <<102, "oo">>
"foo"
iex> <<1, 2, 3>>
<<1, 2, 3>>
Above we have two parts: the first is an integer and the
second is a binary. If we use any other Elixir expression,
Elixir can no longer guess the type:
Elixir also accepts by default the segment to be a literal
string or a literal char list, which are by expanded to integers:
iex> <<0, "foo">>
<<0, 102, 111, 111>>
Any other type needs to be explicitly tagged. For example,
in order to store a float type in the binary, one has to do:
iex> <<3.14 :: float>>
<<64, 9, 30, 184, 81, 235, 133, 31>>
This also means that variables need to be explicitly tagged,
otherwise Elixir defaults to integer:
iex> rest = "oo"
...> <<102, rest>>
iex> <<102, rest>>
** (ArgumentError) argument error
When a variable or expression is given as a binary part,
Elixir defaults the type of that part to an unsigned
little-endian integer. In the example above, since we haven't
specified a type, Elixir expected an integer but we passed a
binary, resulting in `ArgumentError`. We can solve this by
explicitly tagging it as a binary:
We can solve this by explicitly tagging it as a binary:
<<102, rest :: binary>>
@@ -85,7 +90,11 @@ defmodule Kernel.SpecialForms do
is an arbitrary series of bits. A binary is a special case of
bitstring that has a total size divisible by 8.
The utf8, utf16, and utf32 types are for UTF code points.
The utf8, utf16, and utf32 types are for UTF code points. They
can also be applied to literal strings and char lists:
iex> <<"foo" :: utf16>>
<<0,102,0,111,0,111>>
The bits type is an alias for bitstring. The bytes type is an
alias for binary.
@@ -111,7 +120,7 @@ defmodule Kernel.SpecialForms do
Signedness is only relevant on integers.
The endianness of a part can be big, little or native (the
The endianness of a segment can be big, little or native (the
latter meaning it will be resolved at VM load time). Passing
many options can be done by giving a list:
@@ -126,9 +135,9 @@ defmodule Kernel.SpecialForms do
Endianness only makes sense for integers and some UTF code
point types (utf16 and utf32).
Finally, we can also specify size and unit for each part. The
Finally, we can also specify size and unit for each segment. The
unit is multiplied by the size to give the effective size of
the part:
the segment:
iex> <<102, _rest :: [size(2), unit(8)]>> = "foo"
"foo"
@@ -141,7 +150,7 @@ defmodule Kernel.SpecialForms do
In the example above, the first two expressions matches
because the string "foo" takes 24 bits and we are matching
against a part of 24 bits as well, 8 of which are taken by
against a segment of 24 bits as well, 8 of which are taken by
the integer 102 and the remaining 16 bits are specified on
the rest. On the last example, we expect a rest with size 32,
which won't match.
@@ -180,12 +189,12 @@ defmodule Kernel.SpecialForms do
alias MyKeyword, as: Keyword
end
In the example above, we have set up `MyKeyword` to be alias
In the example above, we have set up `MyKeyword` to be aliased
as `Keyword`. So now, any reference to `Keyword` will be
automatically replaced by `MyKeyword`.
In case one wants to access the original `Keyword`, it can be done
by accessing Elixir:
by accessing `Elixir`:
Keyword.values #=> uses MyKeyword.values
Elixir.Keyword.values #=> uses Keyword.values
@@ -258,23 +267,27 @@ defmodule Kernel.SpecialForms do
import List
A developer can change this behavior to include all macros and
functions, regardless if it starts with underscore, by passing
`:all` as first argument:
A developer can filter to import only macros or functions via
the only option:
import :all, List
import List, only: :functions
import List, only: :macros
It can also be customized to import only all functions or
all macros:
import :functions, List
import :macros, List
Alternatively, Elixir allows a developer to specify `:only`
or `:except` as a fine grained control on what to import (or
not):
Alternatively, Elixir allows a developer to pass pairs of
name/arities to `:only` or `:except` as a fine grained control
on what to import (or not):
import List, only: [flatten: 1]
import String, except: [split: 2]
Notice that calling `except` for a previously declared `import`
simply filters the previously imported elements. For example:
import List, only: [flatten: 1, keyfind: 3]
import List, except: [flatten: 1]
After the two import calls above, only `List.keyfind/3` will be
imported.
## Lexical scope
@@ -296,15 +309,9 @@ defmodule Kernel.SpecialForms do
In the example above, we imported macros from `MyMacros`,
replacing the original `if/2` implementation by our own
during that specific function. All other functions in that
within that specific function. All other functions in that
module will still be able to use the original one.
## Alias/Require shortcut
All imported modules are also required by default. `import`
also accepts `as:` as an option so it automatically sets up
an alias. Please check `alias` for more information.
## Warnings
If you import a module and you don't use any of the imported
@@ -335,15 +342,15 @@ defmodule Kernel.SpecialForms do
defmacro __ENV__
@doc """
Returns the current module name as an atom or nil otherwise.
Although the module can be accessed in the __ENV__, this macro
Returns the current module name as an atom or `nil` otherwise.
Although the module can be accessed in the `__ENV__`, this macro
is a convenient shortcut.
"""
defmacro __MODULE__
@doc """
Returns the current file name as a binary.
Although the file can be accessed in the __ENV__, this macro
Although the file can be accessed in the `__ENV__`, this macro
is a convenient shortcut.
"""
defmacro __FILE__
@@ -369,7 +376,7 @@ defmodule Kernel.SpecialForms do
{ :sum, [], [1, 2, 3] }
The tuple above represents a function call to sum passing 1, 2 and
The tuple above represents a function call to `sum` passing 1, 2 and
3 as arguments. The tuple elements are:
* The first element of the tuple is always an atom or
@@ -382,14 +389,14 @@ defmodule Kernel.SpecialForms do
## Options
* `:unquote` - When false, disables unquoting. Useful when you have a quote
inside another quote and want to control which quote is
inside another quote and want to control what quote is
able to unquote;
* `:location` - When set to `:keep`, keeps the current line and file on quotes.
Read the Stacktrace information section below for more information;
* `:hygiene` - Allows a developer to disable hygiene selectively;
* `:context` - Sets the context resolution happens at;
* `:context` - Sets the resolution context;
* `:binding` - Passes a binding to the macro. Whenever a binding is given,
unquote is automatically disabled;
`unquote` is automatically disabled;
## Macro literals
@@ -424,14 +431,14 @@ defmodule Kernel.SpecialForms do
Notice how the third element of the returned tuple is the
module name. This means that the variable is associated to the
ContextSample module and only code generated by this module
`ContextSample` module and only code generated by this module
will be able to access that particular `world` variable.
While this means macros from the same module could have
conflicting variables, it also allows different quotes from
the same module to access them.
The context can be disabled or changed by explicitly setting
the context option. All hygiene mechanisms are based on such
the `context` option. All hygiene mechanisms are based on such
context and we are going to explore each of them in the following
subsections.
@@ -553,7 +560,7 @@ defmodule Kernel.SpecialForms do
## Hygiene in imports
Similar to aliases, imports in Elixir hygienic. Consider the
Similar to aliases, imports in Elixir are hygienic. Consider the
following code:
defmodule Hygiene do
@@ -576,7 +583,7 @@ defmodule Kernel.SpecialForms do
Elixir is smart enough to delay the resolution to the latest
moment possible. So, if you call `size("hello")` inside quote,
but no `size/1` function is available, it is then expanded on
but no `size/1` function is available, it is then expanded in
the caller:
defmodule Lazy do
@@ -597,12 +604,12 @@ defmodule Kernel.SpecialForms do
Lazy.return_size #=> 2
As in aliases, imports expansion can be explicitly disabled
As in aliases, import expansion can be explicitly disabled
via the `hygiene: [imports: false]` option.
## Stacktrace information
One of Elixir goals is to provide proper stacktrace whenever there is an
One of Elixir's goals is to provide a proper stacktrace whenever there is an
exception. In order to work properly with macros, the default behavior
in quote is to not set a line. When a macro is invoked and the quoted
expressions is expanded, the call site line is inserted.
@@ -635,7 +642,7 @@ defmodule Kernel.SpecialForms do
## Binding and unquote fragments
Elixir quote/unquote mechanisms provides a functionality called
unquote fragments. Unquote fragments provide an easy to generate
unquote fragments. Unquote fragments provide an easy way to generate
functions on the fly. Consider this example:
kv = [foo: 1, bar: 2]
@@ -670,7 +677,7 @@ defmodule Kernel.SpecialForms do
code fails.
This is actually a common pitfall when developing macros. In
practive, we want to avoid doing work at compilation time as
practice, we want to avoid doing work at compilation time as
much as we can. That said, let's attempt to improve our macro:
defmacro defkv(kv) do
@@ -689,13 +696,13 @@ defmodule Kernel.SpecialForms do
One solution for this problem is to disable unquoting in the
macro, however, doing that would make it impossible to inject
`kv` representation into the tree. That's when the `:binding`
option comes to the rescue. By using `:binding`, we can
`kv` representation into the tree. That's when the `:bind_quoted`
option comes to the rescue. By using `:bind_quoted`, we can
automatically disable unquoting while still injecting the
desired variables into the tree:
defmacro defkv(kv) do
quote binding: [kv: kv] do
quote bind_quoted: [kv: kv] do
Enum.each kv, fn { k, v } ->
def unquote(k)(), do: unquote(v)
end
@@ -719,7 +726,7 @@ defmodule Kernel.SpecialForms do
@doc """
Defines a variable in the given context.
If the context is false, it is not stored in any particular
If the context is `false`, it is not stored in any particular
context and the variable is not shared in between clauses.
Check `quote/2` for more information.
@@ -813,6 +820,19 @@ defmodule Kernel.SpecialForms do
iex> lc <<r::8, g::8, b::8>> inbits pixels, do: {r, g, b}
[{213,45,132},{64,76,32},{76,0,0},{234,32,15}]
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:
erl> [I || I <- [1,2,3], hd(I)].
[]
In Elixir, it will raise:
iex> lc i inlist [1,2,3], hd(i), do: i
** (ArgumentError) argument error
"""
defmacro lc(args)
@@ -839,6 +859,84 @@ defmodule Kernel.SpecialForms do
"""
defmacro __block__(args)
@doc """
Captures a call as an anonymous function.
The most common format to capture a function is via module,
name and arity:
iex> fun = &Kernel.is_atom/1
iex> fun.(:atom)
true
iex> fun.("string")
false
Local functions, including private ones, and imported ones
can also be captured by omitting the module name:
&local_function/1
A capture also allows the captured functions to be partially
applied, for example:
iex> fun = &atom_to_binary(&1, :utf8)
iex> fun.(:hello)
"hello"
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 = &atom_to_binary(&1, &2)
iex> fun.(:hello, :utf8)
"hello"
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:
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:
iex> fun = &{&1, &2}
iex> fun.(1, 2)
{ 1, 2 }
iex> fun = &[&1|&2]
iex> fun.(1, 2)
[1|2]
Anything that is not a call is not allowed though, examples:
# An atom is not a call
&:foo
# A var is not a call
var = 1
&var
# A block expression is not a call
&(foo(&1, &2); &3 + &4)
"""
name = :&
defmacro unquote(name)(expr)
@doc """
This is the special form used whenever we have to temporarily
change the scope information of a block. Used when `quote` is
@@ -867,7 +965,7 @@ defmodule Kernel.SpecialForms do
Whenever an expression iterator sees a `:.` as the tuple key,
it can be sure that it represents a call and the second argument
is the list is an atom.
in the list is an atom.
On the other hand, aliases holds some properties:
@@ -875,7 +973,7 @@ defmodule Kernel.SpecialForms do
2) The tail elements of aliases are guaranteed to always be atoms;
3) When the head element of aliases is the atom :Elixir, no expansion happen;
3) When the head element of aliases is the atom `:Elixir`, no expansion happen;
4) When the head element of aliases is not an atom, it is expanded at runtime:
+18 -14
View File
@@ -169,7 +169,7 @@ defmodule Kernel.Typespec do
Module.compile_typespec module, kind, type
if export, do:
Module.compile_typespec module, :export_type, [{ name, length(vars) }]
Module.compile_typespec(module, :export_type, [{ name, length(vars) }])
type
end
@@ -349,9 +349,9 @@ defmodule Kernel.Typespec do
do_deftype(kind, type, { :term, [line: caller.line], nil }, caller)
end
def deftype(_kind, other, _caller) do
def deftype(_kind, other, caller) do
type_spec = Macro.to_string(other)
raise ArgumentError, message: "invalid type specification #{type_spec}"
compile_error caller, "invalid type specification #{type_spec}"
end
defp do_deftype(kind, { name, _, args }, definition, caller) do
@@ -390,9 +390,9 @@ defmodule Kernel.Typespec do
code
end
def defspec(_type, other, _caller) do
def defspec(_type, other, caller) do
spec = Macro.to_string(other)
raise ArgumentError, message: "invalid function type specification #{spec}"
compile_error caller, "invalid function type specification #{spec}"
end
defp guard_to_constraints({ :is_subtype, meta, [{ name, _, _ }, type] }, caller) do
@@ -563,8 +563,6 @@ defmodule Kernel.Typespec do
# Handle funs
defp typespec({:->, meta, [{[{:fun, _, arguments}], cmeta, return}]}, vars, caller) when is_list(arguments) do
IO.warn "(fun(...) -> ...) in typespecs is deprecated, please drop fun() and " <>
"write (... -> ...) instead. #{Exception.format_stacktrace(caller.stacktrace)}"
typespec({:->, meta, [{arguments, cmeta, return}]}, vars, caller)
end
@@ -593,9 +591,11 @@ defmodule Kernel.Typespec do
end
# Handle remote calls
defp typespec({{:., meta, [remote, name]}, _, args}, vars, caller) do
defp typespec({{:., meta, [remote, name]}, _, args} = orig, vars, caller) do
remote = Macro.expand remote, caller
unless is_atom(remote), do: raise ArgumentError, message: "invalid remote in typespec"
unless is_atom(remote) do
compile_error(caller, "invalid remote in typespec: #{Macro.to_string(orig)}")
end
remote_type({typespec(remote, vars, caller), meta, typespec(name, vars, caller), args}, vars, caller)
end
@@ -670,8 +670,8 @@ defmodule Kernel.Typespec do
end
defp typespec([h|t] = l, vars, caller) do
union = Enum.reduce(t, validate_kw(h, l), fn(x, acc) ->
{ :|, [], [acc, validate_kw(x, l)] }
union = Enum.reduce(t, validate_kw(h, l, caller), fn(x, acc) ->
{ :|, [], [acc, validate_kw(x, l, caller)] }
end)
typespec({ :list, [], [union] }, vars, caller)
end
@@ -683,6 +683,10 @@ defmodule Kernel.Typespec do
## Helpers
defp compile_error(caller, desc) do
raise CompileError, file: caller.file, line: caller.line, description: desc
end
defp remote_type({remote, meta, name, arguments}, vars, caller) do
arguments = lc arg inlist arguments, do: typespec(arg, vars, caller)
{ :remote_type, line(meta), [ remote, name, arguments ] }
@@ -691,9 +695,9 @@ defmodule Kernel.Typespec do
defp collect_union({ :|, _, [a, b] }), do: [b|collect_union(a)]
defp collect_union(v), do: [v]
defp validate_kw({ key, _ } = t, _) when is_atom(key), do: t
defp validate_kw(_, original) do
raise ArgumentError, message: "unexpected list #{inspect original} in typespec"
defp validate_kw({ key, _ } = t, _, _caller) when is_atom(key), do: t
defp validate_kw(_, original, caller) do
compile_error(caller, "unexpected list #{Macro.to_string original} in typespec")
end
defp fn_args(meta, args, return, vars, caller) do
+49 -27
View File
@@ -18,8 +18,8 @@ defmodule Keyword do
@type t :: [{key, value}]
@doc """
Creates a Keyword from enum. Differently from `Keyword.new`
which behaves as a dict, `Keyword.from_enum` do not remove
Creates a Keyword from an enum. Unlike `Keyword.new`
which behaves as a dict, `Keyword.from_enum` does not remove
duplicated entries.
"""
@spec from_enum(Enum.t) :: t
@@ -51,7 +51,7 @@ defmodule Keyword do
end
@doc """
Creates a Keyword from an enumerable. Similarly to dicts,
Creates a Keyword from an enumerable. Similar to dicts,
duplicated entries are removed, the latest one prevails.
## Examples
@@ -87,20 +87,21 @@ defmodule Keyword do
end
@doc """
Gets the value for specific key.
Gets the value for a specific `key`.
If key not exist return default value (nil if no default value)
exists.
If `key` does not exist, return default value (`nil` if no default value).
If duplicated entries exist, the first one is returned.
Use get_values/2 to retrieve all entries.
Use `get_values/2` to retrieve all entries.
## Examples
iex> Keyword.get([a: 1], :a)
1
iex> Keyword.get([a: 1], :b)
nil
iex> Keyword.get([a: 1], :b, 3)
3
@@ -115,8 +116,8 @@ defmodule Keyword do
end
@doc """
Fetchs the value for specific key and return it in a tuple.
If the key does not exist, returns `:error`.
Fetches the value for a specific `key` and returns it in a tuple.
If the `key` does not exist, returns `:error`.
## Examples
@@ -136,8 +137,8 @@ defmodule Keyword do
end
@doc """
Fetches the value for specific key. If key does not exist,
an error is raised.
Fetches the value for specific `key`. If `key` does not exist,
a `KeyError` is raised.
## Examples
@@ -157,7 +158,7 @@ defmodule Keyword do
end
@doc """
Gets all values for a specific key.
Gets all values for a specific `key`.
## Examples
@@ -179,6 +180,9 @@ defmodule Keyword do
iex> Keyword.keys([a: 1, b: 2])
[:a,:b]
iex> Keyword.keys([a: 1, b: 2, a: 3])
[:a,:b,:a]
"""
@spec keys(t) :: [key]
def keys(keywords) do
@@ -186,7 +190,7 @@ defmodule Keyword do
end
@doc """
Returns all values.
Returns all values from the keyword list.
## Examples
@@ -200,8 +204,8 @@ defmodule Keyword do
end
@doc """
Deletes all entries in the keyword list for a specific key.
If the key does not exist, returns the keyword list unchanged.
Deletes all entries in the keyword list for a specific `key`.
If the `key` does not exist, returns the keyword list unchanged.
Use `delete_first` to delete just the first entry in case of
duplicated keys.
@@ -209,6 +213,10 @@ defmodule Keyword do
iex> Keyword.delete([a: 1, b: 2], :a)
[b: 2]
iex> Keyword.delete([a: 1, b: 2, a: 3], :a)
[b: 2]
iex> Keyword.delete([b: 2], :a)
[b: 2]
@@ -219,13 +227,14 @@ defmodule Keyword do
end
@doc """
Deletes the first entry in the keyword list for a specific key.
If the key does not exist, returns the keyword list unchanged.
Deletes the first entry in the keyword list for a specific `key`.
If the `key` does not exist, returns the keyword list unchanged.
## Examples
iex> Keyword.delete_first([a: 1, b: 2, a: 3], :a)
[b: 2, a: 3]
iex> Keyword.delete_first([b: 2], :a)
[b: 2]
@@ -246,6 +255,9 @@ defmodule Keyword do
iex> Keyword.put([a: 1, b: 2], :a, 3)
[a: 3, b: 2]
iex> Keyword.put([a: 1, b: 2, a: 4], :a, 3)
[a: 3, b: 2]
"""
@spec put(t, key, value) :: t
def put(keywords, key, value) when is_atom(key) do
@@ -257,6 +269,8 @@ defmodule Keyword do
already exists.
## Examples
iex> Keyword.put_new([a: 1], :b, 2)
[b: 2, a: 1]
iex> Keyword.put_new([a: 1, b: 2], :a, 3)
[a: 1, b: 2]
@@ -326,12 +340,13 @@ defmodule Keyword do
end
@doc """
Returns whether a given key exists in the given keywords.
Returns whether a given `key` exists in the given `keywords`.
## Examples
iex> Keyword.has_key?([a: 1], :a)
true
iex> Keyword.has_key?([a: 1], :b)
false
@@ -341,40 +356,47 @@ defmodule Keyword do
:lists.keymember(key, 1, keywords)
end
@doc false
def update(dict, key, fun) when is_function(fun, 1) do
IO.write "Keyword.update/3 is deprecated, please use Keyword.update!/3 instead\n#{Exception.format_stacktrace}"
update!(dict, key, fun)
end
@doc """
Updates the key with the given function. If the key does
Updates the `key` with the given function. If the `key` does
not exist, raises `KeyError`.
## Examples
iex> Keyword.update([a: 1], :a, &1 * 2)
iex> Keyword.update!([a: 1], :a, &1 * 2)
[a: 2]
iex> Keyword.update([a: 1], :b, &1 * 2)
iex> Keyword.update!([a: 1], :b, &1 * 2)
** (KeyError) key not found: :b
"""
@spec update(t, key, (value -> value)) :: t | no_return
def update([{key, value}|keywords], key, fun) do
@spec update!(t, key, (value -> value)) :: t | no_return
def update!([{key, value}|keywords], key, fun) do
[{key, fun.(value)}|delete(keywords, key)]
end
def update([{_, _} = e|keywords], key, fun) do
[e|update(keywords, key, fun)]
def update!([{_, _} = e|keywords], key, fun) do
[e|update!(keywords, key, fun)]
end
def update([], key, _fun) when is_atom(key) do
def update!([], key, _fun) when is_atom(key) do
raise(KeyError, key: key)
end
@doc """
Updates the key with the given function. If the key does
Updates the `key` with the given function. If the `key` does
not exist, inserts the given `initial` value.
## Examples
iex> Keyword.update([a: 1], :a, 13, &1 * 2)
[a: 2]
iex> Keyword.update([a: 1], :b, 11, &1 * 2)
[a: 1, b: 11]
+110 -44
View File
@@ -5,40 +5,22 @@ defmodule List do
favor using the Enum API instead of List.
A decision was taken to delegate most functions to
Erlang's standard lib but following Elixir's convention
Erlang's standard library but follow Elixir's convention
of receiving the target (in this case, a list) as the
first argument.
"""
@compile :inline_list_funcs
@doc """
Given a list of lists, concatenates the sublists into a single list.
## Examples
iex> List.concat([[1, [2], 3], [4], [5, 6]])
[1,[2],3,4,5,6]
"""
@doc false
def concat(list) when is_list(list) do
IO.write "List.concat/1 is deprecated, please use Enum.concat/1 instead\n#{Exception.format_stacktrace}"
:lists.append(list)
end
@doc """
Concatenates the list on the right with the list on the left.
This function produces the same result the `++` operator. The only difference
is a minor optimization: when the first list contains only one element, we
simply add it as a head to the second list.
## Examples
iex> List.concat([1, 2, 3], [4, 5, 6])
[1,2,3,4,5,6]
"""
@doc false
def concat(list, elements) when is_list(list) and is_list(elements) do
IO.write "List.concat/2 is deprecated, please use Enum.concat/2 instead\n#{Exception.format_stacktrace}"
list ++ elements
end
@@ -52,13 +34,16 @@ defmodule List do
iex> List.delete([1, 2, 3], 1)
[2,3]
iex> List.delete([1, 2, 2, 3], 2)
[1, 2, 3]
"""
def delete(list, item) do
:lists.delete(item, list)
end
@doc """
Duplicates the given element n times in a list.
Duplicates the given element `n` times in a list.
## Examples
@@ -73,23 +58,29 @@ defmodule List do
end
@doc """
Flattens the given `list` of nested lists. An optional
tail can be given that will be added at the end of
the flattened list.
Flattens the given `list` of nested lists.
## Examples
iex> List.flatten([1, [[2], 3]])
[1,2,3]
iex> List.flatten([1, [[2], 3]], [4, 5])
[1,2,3,4,5]
"""
def flatten(list) do
:lists.flatten(list)
end
@doc """
Flattens the given `list` of nested lists.
The list `tail` will be added at the end of
the flattened list.
## Examples
iex> List.flatten([1, [[2], 3]], [4, 5])
[1,2,3,4,5]
"""
def flatten(list, tail) do
:lists.flatten(list, tail)
end
@@ -126,7 +117,7 @@ defmodule List do
end
@doc """
Returns the last element in `list` or nil if the `list` is empty.
Returns the last element in `list` or `nil` if `list` is empty.
## Examples
@@ -146,7 +137,7 @@ defmodule List do
@doc """
Receives a list of tuples and returns the first tuple
where the item at position `position` matches with the
where the item at `position` in the tuple matches the
given `item`.
## Examples
@@ -166,9 +157,9 @@ defmodule List do
end
@doc """
Receives a list of tuples and returns true if there is
a tuple where the item at position `position` matches
with the given `item`.
Receives a list of tuples and returns `true` if there is
a tuple where the item at `position` in the tuple matches
the given `item`.
## Examples
@@ -188,7 +179,7 @@ defmodule List do
@doc """
Receives a list of tuples and replaces the item
identified by `key` at position `pos` if it exists.
identified by `key` at `position` if it exists.
## Examples
@@ -200,9 +191,26 @@ defmodule List do
:lists.keyreplace(key, position + 1, list, new_tuple)
end
@doc """
Receives a list of tuples and sorts the items
at `position` of the tuples. The sort is stable.
## Examples
iex> List.keysort([a: 5, b: 1, c: 3], 1)
[b: 1, c: 3, a: 5]
iex> List.keysort([a: 5, c: 1, b: 3], 0)
[a: 5, b: 3, c: 1]
"""
def keysort(list, position) do
:lists.keysort(position + 1, list)
end
@doc """
Receives a list of tuples and replaces the item
identified by `key` at position `pos`. If the item
identified by `key` at `position`. If the item
does not exist, it is added to the end of the list.
## Examples
@@ -210,6 +218,9 @@ defmodule List do
iex> List.keystore([a: 1, b: 2], :a, 0, { :a, 3 })
[a: 3, b: 2]
iex> List.keystore([a: 1, b: 2], :c, 0, { :c, 3 })
[a: 1, b: 2, c: 3]
"""
def keystore(list, key, position, new_tuple) do
:lists.keystore(key, position + 1, list, new_tuple)
@@ -217,8 +228,8 @@ defmodule List do
@doc """
Receives a list of tuples and deletes the first tuple
where the item at position `position` matches with the
given `item`. Returns the new tuple.
where the item at `position` matches the
given `item`. Returns the new list.
## Examples
@@ -239,13 +250,19 @@ defmodule List do
@doc """
Wraps the argument in a list.
If the argument is already a list, returns the list.
If the argument is nil, returns an empty list.
If the argument is `nil`, returns an empty list.
## Examples
iex> List.wrap("hello")
["hello"]
iex> List.wrap([1, 2, 3])
[1,2,3]
iex> List.wrap(nil)
[]
"""
def wrap(list) when is_list(list) do
list
@@ -294,8 +311,8 @@ defmodule List do
end
@doc """
Returns a list with an inserted value at specified index. Note that the index
is capped at the list length and that negative indicies wraps around at the
Returns a list with `value` inserted at the specified `index`. Note that `index`
is capped at the list length. Negative indices indicate an offset from the
end of the list.
## Examples
@@ -307,19 +324,68 @@ defmodule List do
[1, 2, 3, 0]
iex> List.insert_at([1, 2, 3], -1, 0)
[1, 2, 0, 3]
[1, 2, 3, 0]
iex> List.insert_at([1, 2, 3], -10, 0)
[0, 1, 2, 3]
"""
def insert_at(list, index, value) do
if index < 0 do
do_insert_at(list, length(list) + index, value)
do_insert_at(list, length(list) + index + 1, value)
else
do_insert_at(list, index, value)
end
end
@doc """
Returns a list with a replaced value at the specified `index`. Negative indices
indicate an offset from the end of the list. If `index` is out of bounds, the
original `list` is returned.
## Examples
iex> List.replace_at([1, 2, 3], 0, 0)
[0, 2, 3]
iex> List.replace_at([1, 2, 3], 10, 0)
[1, 2, 3]
iex> List.replace_at([1, 2, 3], -1, 0)
[1, 2, 0]
iex> List.replace_at([1, 2, 3], -10, 0)
[1, 2, 3]
"""
def replace_at(list, index, value) do
if index < 0 do
do_replace_at(list, length(list) + index, value)
else
do_replace_at(list, index, value)
end
end
## Helpers
# replace_at
defp do_replace_at([], _index, _value) do
[]
end
defp do_replace_at(list, index, _value) when index < 0 do
list
end
defp do_replace_at([_old|rest], 0, value) do
[ value | rest ]
end
defp do_replace_at([h|t], index, value) do
[ h | do_replace_at(t, index - 1, value) ]
end
# insert_at
defp do_insert_at([], _index, value) do
+14 -2
View File
@@ -1,5 +1,5 @@
defprotocol List.Chars do
@moduledoc %B"""
@moduledoc %S"""
The List.Chars protocol is responsible for
converting a structure to a list (only if applicable).
The only function required to be implemented is
@@ -19,7 +19,19 @@ defimpl List.Chars, for: Atom do
end
defimpl List.Chars, for: BitString do
def to_char_list(bitstring), do: bitstring_to_list(bitstring)
@doc """
Returns the given binary converted to a char list.
"""
def to_char_list(thing) when is_binary(thing) do
String.to_char_list!(thing)
end
def to_char_list(thing) do
raise Protocol.UndefinedError,
protocol: @protocol,
value: thing,
description: "cannot convert a bitstring to a char list"
end
end
defimpl List.Chars, for: List do
+14 -63
View File
@@ -15,7 +15,7 @@ defmodule ListDict do
Creates a new `ListDict` from the given pairs.
"""
def new(pairs) do
Enum.map pairs, fn({ k, v }) -> { k, v } end
Enum.to_list pairs
end
@doc """
@@ -26,38 +26,22 @@ defmodule ListDict do
Enum.map list, transform
end
@doc """
Returns all keys in the dict.
"""
def keys(dict) do
lc { key, _ } inlist dict, do: key
end
@doc """
Returns all values in the dict.
"""
def values(dict) do
lc { _, value } inlist dict, do: value
end
@doc """
Returns the dict size.
"""
def size(dict) do
length(dict)
end
@doc """
Returns true if the dict has the given key.
"""
def has_key?(dict, key) do
:lists.keymember(key, 1, dict)
end
@doc """
Returns the value under key from the given
dict or default if no key is set.
"""
def get(dict, key, default // nil) do
case :lists.keyfind(key, 1, dict) do
{ ^key, value } -> value
@@ -65,10 +49,6 @@ defmodule ListDict do
end
end
@doc """
Returns the value under key from the given
dict in a tagged tuple, otherwise `:error`.
"""
def fetch(dict, key) do
case :lists.keyfind(key, 1, dict) do
{ ^key, value } -> { :ok, value }
@@ -76,10 +56,6 @@ defmodule ListDict do
end
end
@doc """
Returns the value under the given key
raises `KeyError` if the key does not exist.
"""
def fetch!(dict, key) do
case :lists.keyfind(key, 1, dict) do
{ ^key, value } -> value
@@ -87,25 +63,14 @@ defmodule ListDict do
end
end
@doc """
Returns the value under the given key
from the dict as well as the dict without that key.
"""
def pop(dict, key, default // nil) do
{ get(dict, key, default), delete(dict, key) }
end
@doc """
Puts the given key-value pair in the dict.
"""
def put(dict, key, val) do
[{key, val}|delete(dict, key)]
end
@doc """
Puts the given key-value pair in the dict
if no entry exists yet.
"""
def put_new(dict, key, val) do
case :lists.keyfind(key, 1, dict) do
{ ^key, _ } -> dict
@@ -113,16 +78,10 @@ defmodule ListDict do
end
end
@doc """
Deletes the entry under the given key from the dict.
"""
def delete(dict, key) do
lc { k, _ } = tuple inlist dict, key != k, do: tuple
end
@doc """
Merges the given Enumerable into the dict.
"""
def merge(dict, enum, callback // fn(_k, _v1, v2) -> v2 end)
def merge(dict1, dict2, fun) do
@@ -133,13 +92,15 @@ defmodule ListDict do
def split(dict, keys) do
acc = { new(), new() }
Enum.reduce dict, acc, fn({ k, v }, { take, drop }) ->
{take, drop} = Enum.reduce dict, acc, fn({ k, v }, { take, drop }) ->
if :lists.member(k, keys) do
{ [{k, v}|take], drop }
else
{ take, [{k, v}|drop] }
end
end
{Enum.reverse(take), Enum.reverse(drop)}
end
def take(dict, keys) do
@@ -150,25 +111,24 @@ defmodule ListDict do
lc { k, _ } = tuple inlist dict, not :lists.member(k, keys), do: tuple
end
@doc """
Updates the key in the dict according to the given function.
"""
def update([{key, value}|dict], key, fun) do
@doc false
def update(dict, key, fun) when is_function(fun, 1) do
IO.write "ListDict.update/3 is deprecated, please use ListDict.update!/3 instead\n#{Exception.format_stacktrace}"
update!(dict, key, fun)
end
def update!([{key, value}|dict], key, fun) do
[{key, fun.(value)}|delete(dict, key)]
end
def update([{_, _} = e|dict], key, fun) do
[e|update(dict, key, fun)]
def update!([{_, _} = e|dict], key, fun) do
[e|update!(dict, key, fun)]
end
def update([], key, _fun) do
def update!([], key, _fun) do
raise(KeyError, key: key)
end
@doc """
Updates the key in the dict according to the given function
or uses the given initial value if no entry exists.
"""
def update([{key, value}|dict], key, _initial, fun) do
[{key, fun.(value)}|delete(dict, key)]
end
@@ -181,20 +141,11 @@ defmodule ListDict do
[{key, initial}]
end
@doc """
Returns an empty `ListDict`.
"""
def empty(_dict), do: []
@doc """
Check if the ListDict is equal to another ListDict.
"""
def equal?(dict, other) do
:lists.keysort(1, dict) == :lists.keysort(1, other)
end
@doc """
Converts the dict to a list.
"""
def to_list(dict), do: dict
end
+232 -128
View File
@@ -5,30 +5,48 @@ defmodule Macro do
This module provides conveniences for working with macros.
"""
@doc """
Returns a list of binary operators. This is available
as a macro so it can be used in guard clauses.
"""
@type t :: { t, t } | { t, Keyword.t, t } | atom | number | binary | list
@doc false
defmacro binary_ops do
[
:===, :!==,
[ :===, :!==,
:==, :!=, :<=, :>=,
:&&, :||, :<>, :++, :--, :**, ://, :::, :<-, :.., :|>, :=~,
:<, :>, :->,
:+, :-, :*, :/, :=, :|, :.,
:and, :or, :xor, :when, :in, :inlist, :inbits,
:<<<, :>>>, :|||, :&&&, :^^^
]
:<<<, :>>>, :|||, :&&&, :^^^, :~~~ ]
end
@doc """
Returns a list of unary operators. This is available
as a macro so it can be used in guard clauses.
"""
@doc false
defmacro unary_ops do
[:!, :@, :^, :not, :+, :-, :~~~, :&]
end
@typep precedence :: integer # Higher means binds stronger
@spec binary_op_props(atom) :: { :left|:right, precedence }
defp binary_op_props(o) do
case o do
::: -> {:right, 30}
:when -> {:right, 40}
o when o in [:inlist, :inbits] -> {:left, 50}
:// -> {:right, 60}
:| -> {:left, 70}
:= -> {:right, 80}
o when o in [:||, :|||, :or, :xor] -> {:left, 130}
o when o in [:&&, :&&&, :and] -> {:left, 140}
o when o in [:==, :!=, :<, :<=, :>=, :>, :=~, :===, :!==] -> {:left, 150}
o when o in [:<-, :|>, :<<<, :>>>] -> {:right, 160}
:in -> {:left, 170}
:.. -> {:left, 200}
o when o in [:+, :-] -> {:left, 210}
o when o in [:*, :/] -> {:left, 220}
o when o in [:<>] -> {:right, 230}
:^^^ -> {:left, 250}
:. -> {:left, 310}
end
end
@doc """
Receives an expresion representing a possible definition
and extracts its arguments. It returns a tuple with the
@@ -36,7 +54,7 @@ defmodule Macro do
a valid call syntax.
This is useful for macros that want to provide the same
arguments syntax available in def/defp/defmacro and friends.
argument syntax available in def/defp/defmacro and friends.
## Examples
@@ -46,6 +64,7 @@ defmodule Macro do
extract_args(quote do: 1.(1, 2, 3)) == :error
"""
@spec extract_args(Macro.t) :: { atom, [Macro.t] } | :error
def extract_args(expr) do
:elixir_clauses.extract_args(expr)
end
@@ -54,7 +73,7 @@ defmodule Macro do
Recursively escapes a value so it can be inserted
into a syntax tree.
One may pass `unquote: true` to `Macro.escape/2`
One may pass `unquote: true` to `escape/2`
which leaves unquote statements unescaped, effectively
unquoting the contents on escape.
@@ -70,14 +89,28 @@ defmodule Macro do
1
"""
@spec escape(Macro.t) :: Macro.t
@spec escape(Macro.t, Keyword.t) :: Macro.t
def escape(expr, opts // []) do
:elixir_quote.escape(expr, Keyword.get(opts, :unquote, false)) |> elem(0)
end
@doc %B"""
@doc false
def unescape_binary(chars) do
IO.write "Macro.unescape_binary/1 is deprecated, please use Macro.unescape_string/1 instead\n#{Exception.format_stacktrace}"
unescape_string(chars)
end
@doc false
def unescape_binary(chars, map) do
IO.write "Macro.unescape_binary/2 is deprecated, please use Macro.unescape_string/2 instead\n#{Exception.format_stacktrace}"
unescape_string(chars, map)
end
@doc %S"""
Unescape the given chars. This is the unescaping behavior
used by default in Elixir single- and double-quoted strings.
Check `unescape_binary/2` for information on how to customize
Check `unescape_string/2` for information on how to customize
the escaping map.
In this setup, Elixir will escape the following: `\a`, `\b`,
@@ -85,23 +118,25 @@ defmodule Macro do
also escaped according to the latin1 set they represent.
This function is commonly used on sigil implementations
(like `%r`, `%b` and others).
(like `%r`, `%b` and others) which receive a raw, unescaped
string.
## Examples
iex> Macro.unescape_binary("example\\n")
iex> Macro.unescape_string("example\\n")
"example\n"
In the example above, we pass a string with `\n` escaped
and we return a version with it unescaped.
"""
def unescape_binary(chars) do
@spec unescape_string(String.t) :: String.t
def unescape_string(chars) do
:elixir_interpolation.unescape_chars(chars)
end
@doc %B"""
@doc %S"""
Unescape the given chars according to the map given.
Check `unescape_binary/1` if you want to use the same map
Check `unescape_string/1` if you want to use the same map
as Elixir single- and double-quoted strings.
## Map
@@ -122,7 +157,7 @@ defmodule Macro do
def unescape_map(?v), do: ?\v
def unescape_map(e), do: e
If the `unescape_map` function returns false. The char is
If the `unescape_map` function returns `false`. The char is
not escaped and `\` is kept in the char list.
## Octals
@@ -132,23 +167,24 @@ defmodule Macro do
## Hex
Octals will by default be escaped unless the map function
Hexadecimals will by default be escaped unless the map function
returns false for ?x.
## Examples
Using the unescape_map defined above is easy:
Macro.unescape_binary "example\\n", unescape_map(&1)
Macro.unescape_string "example\\n", unescape_map(&1)
"""
def unescape_binary(chars, map) do
@spec unescape_string(String.t, (non_neg_integer -> non_neg_integer | false)) :: String.t
def unescape_string(chars, map) do
:elixir_interpolation.unescape_chars(chars, map)
end
@doc """
Unescape the given tokens according to the default map.
Check `unescape_binary/1` and `unescape_binary/2` for more
Check `unescape_string/1` and `unescape_string/2` for more
information about unescaping.
Only tokens that are binaries are unescaped, all others are
@@ -156,24 +192,20 @@ defmodule Macro do
sigils. Check the implementation of `Kernel.sigil_b`
for examples.
"""
@spec unescape_tokens([Macro.t]) :: [Macro.t]
def unescape_tokens(tokens) do
:elixir_interpolation.unescape_tokens(tokens)
end
@doc """
Unescape the given tokens according to the given map.
Check `unescape_tokens/1` and `unescape_binary/2` for more information.
Check `unescape_tokens/1` and `unescape_string/2` for more information.
"""
@spec unescape_tokens([Macro.t], (non_neg_integer -> non_neg_integer | false)) :: [Macro.t]
def unescape_tokens(tokens, map) do
:elixir_interpolation.unescape_tokens(tokens, map)
end
@doc false
def to_binary(tree) do
IO.write "[WARNING] Macro.to_binary is deprecated, please use Macro.to_string instead\n#{Exception.format_stacktrace}"
to_string(tree)
end
@doc """
Converts the given expression to a binary.
@@ -183,109 +215,122 @@ defmodule Macro do
"foo.bar(1, 2, 3)"
"""
def to_string(tree)
@spec to_string(Macro.t) :: String.t
@spec to_string(Macro.t, (Macro.t, String.t -> String.t)) :: String.t
def to_string(tree, fun // fn(_ast, string) -> string end)
# Variables
def to_string({ var, _, atom }) when is_atom(atom) do
atom_to_binary(var, :utf8)
def to_string({ var, _, atom } = ast, fun) when is_atom(atom) do
fun.(ast, atom_to_binary(var, :utf8))
end
# Aliases
def to_string({ :__aliases__, _, refs }) do
Enum.map_join(refs, ".", call_to_string(&1))
def to_string({ :__aliases__, _, refs } = ast, fun) do
fun.(ast, Enum.map_join(refs, ".", &call_to_string(&1, fun)))
end
# Blocks
def to_string({ :__block__, _, [expr] }) do
to_string(expr)
def to_string({ :__block__, _, [expr] } = ast, fun) do
fun.(ast, to_string(expr, fun))
end
def to_string({ :__block__, _, _ } = expr) do
block = adjust_new_lines block_to_string(expr), "\n "
"(\n " <> block <> "\n)"
def to_string({ :__block__, _, _ } = ast, fun) do
block = adjust_new_lines block_to_string(ast, fun), "\n "
fun.(ast, "(\n " <> block <> "\n)")
end
# Bits containers
def to_string({ :<<>>, _, args }) do
"<<" <> Enum.map_join(args, ", ", to_string(&1)) <> ">>"
def to_string({ :<<>>, _, args } = ast, fun) do
fun.(ast, case Enum.map_join(args, ", ", &to_string(&1, fun)) do
"<" <> rest -> "<< <" <> rest <> " >>"
rest -> "<<" <> rest <> ">>"
end)
end
# Tuple containers
def to_string({ :{}, _, args }) do
"{" <> Enum.map_join(args, ", ", to_string(&1)) <> "}"
def to_string({ :{}, _, args } = ast, fun) do
fun.(ast, "{" <> Enum.map_join(args, ", ", &to_string(&1, fun)) <> "}")
end
# List containers
def to_string({ :[], _, args }) do
"[" <> Enum.map_join(args, ", ", to_string(&1)) <> "]"
def to_string({ :[], _, args } = ast, fun) do
fun.(ast, "[" <> Enum.map_join(args, ", ", &to_string(&1, fun)) <> "]")
end
# Fn keyword
def to_string({ :fn, _, [[do: { :->, _, [{_, _, tuple}] } = arrow]] })
def to_string({ :fn, _, [[do: { :->, _, [{_, _, tuple}] } = arrow]] } = ast, fun)
when not is_tuple(tuple) or elem(tuple, 0) != :__block__ do
"fn " <> arrow_to_string(arrow) <> " end"
fun.(ast, "fn " <> arrow_to_string(arrow, fun) <> " end")
end
def to_string({ :fn, _, [[do: { :->, _, [_] } = block]] }) do
"fn " <> block_to_string(block) <> "\nend"
def to_string({ :fn, _, [[do: { :->, _, [_] } = block]] } = ast, fun) do
fun.(ast, "fn " <> block_to_string(block, fun) <> "\nend")
end
def to_string({ :fn, _, [[do: block]] }) do
block = adjust_new_lines block_to_string(block), "\n "
"fn\n " <> block <> "\nend"
end
# Partial call
def to_string({ :&, _, [num] }) do
"&#{num}"
def to_string({ :fn, _, [[do: block]] } = ast, fun) do
block = adjust_new_lines block_to_string(block, fun), "\n "
fun.(ast, "fn\n " <> block <> "\nend")
end
# left -> right
def to_string({ :->, _, _ } = arrow) do
"(" <> arrow_to_string(arrow, true) <> ")"
def to_string({ :->, _, _ } = ast, fun) do
fun.(ast, "(" <> arrow_to_string(ast, fun, true) <> ")")
end
# Binary ops
def to_string({ op, _, [left, right] }) when op in binary_ops do
op_to_string(left) <> " #{op} " <> op_to_string(right)
def to_string({ op, _, [left, right] } = ast, fun) when op in binary_ops do
fun.(ast, op_to_string(left, fun, op, :left) <> " #{op} " <> op_to_string(right, fun, op, :right))
end
# Splat when
def to_string({ :when, _, args }) do
{ left, right } = :elixir_tree_helpers.split_last(args)
"(" <> Enum.map_join(left, ", ", to_string(&1)) <> ") when " <> to_string(right)
def to_string({ :when, _, args } = ast, fun) do
{ left, right } = :elixir_utils.split_last(args)
fun.(ast, "(" <> Enum.map_join(left, ", ", &to_string(&1, fun)) <> ") when " <> to_string(right, fun))
end
# Unary ops
def to_string({ :not, _, [arg] }) do
"not " <> to_string(arg)
def to_string({ :not, _, [arg] } = ast, fun) do
fun.(ast, "not " <> to_string(arg, fun))
end
def to_string({ op, _, [arg] }) when op in unary_ops do
atom_to_binary(op, :utf8) <> to_string(arg)
def to_string({ op, _, [arg] } = ast, fun) when op in unary_ops do
fun.(ast, atom_to_binary(op, :utf8) <> to_string(arg, fun))
end
# Access
def to_string({ { :., _, [Kernel, :access] }, _, [left, right] } = ast, fun) do
fun.(ast, if right != [] and Keyword.keyword?(right) do
to_string(left, fun) <> to_string(right, fun)
else
to_string(left, fun) <> "[" <> to_string(right, fun) <> "]"
end)
end
# All other calls
def to_string({ target, _, args }) when is_list(args) do
{ list, last } = :elixir_tree_helpers.split_last(args)
case is_kw_blocks?(last) do
true -> call_to_string_with_args(target, list) <> kw_blocks_to_string(last)
false -> call_to_string_with_args(target, args)
end
def to_string({ target, _, args } = ast, fun) when is_list(args) do
{ list, last } = :elixir_utils.split_last(args)
fun.(ast, case is_kw_blocks?(last) do
true -> call_to_string_with_args(target, list, fun) <> kw_blocks_to_string(last, fun)
false -> call_to_string_with_args(target, args, fun)
end)
end
# Two-item tuples
def to_string({ left, right }) do
to_string({ :{}, [], [left, right] })
def to_string({ left, right }, fun) do
to_string({ :{}, [], [left, right] }, fun)
end
# Lists
def to_string(list) when is_list(list) do
to_string({ :[], [], list })
def to_string(list = ast, fun) when is_list(list) do
if Keyword.keyword?(list) do
fun.(ast, "[" <> kw_list_to_string(list, fun) <> "]")
else
to_string({ :[], [], list }, fun)
end
end
# All other structures
def to_string(other), do: inspect(other, raw: true)
def to_string(other, fun), do: fun.(other, inspect(other, raw: true))
# Block keywords
defmacrop kw_keywords, do: [:do, :catch, :rescue, :after, :else]
@@ -295,64 +340,97 @@ defmodule Macro do
end
defp is_kw_blocks?(_), do: false
defp module_to_string(atom) when is_atom(atom), do: inspect(atom, raw: true)
defp module_to_string(other), do: call_to_string(other)
defp module_to_string(atom, _fun) when is_atom(atom), do: inspect(atom, raw: true)
defp module_to_string(other, fun), do: call_to_string(other, fun)
defp call_to_string(atom) when is_atom(atom), do: atom_to_binary(atom, :utf8)
defp call_to_string({ :., _, [arg] }), do: module_to_string(arg) <> "."
defp call_to_string({ :., _, [left, right] }), do: module_to_string(left) <> "." <> call_to_string(right)
defp call_to_string(other), do: to_string(other)
defp call_to_string(atom, _fun) when is_atom(atom), do: atom_to_binary(atom, :utf8)
defp call_to_string({ :., _, [arg] }, fun), do: module_to_string(arg, fun) <> "."
defp call_to_string({ :., _, [left, right] }, fun), do: module_to_string(left, fun) <> "." <> call_to_string(right, fun)
defp call_to_string(other, fun), do: to_string(other, fun)
defp call_to_string_with_args(target, args) do
args = Enum.map_join(args, ", ", to_string(&1))
call_to_string(target) <> "(" <> args <> ")"
defp call_to_string_with_args(target, args, fun) do
{ list, last } = :elixir_utils.split_last(args)
target = call_to_string(target, fun)
case last != [] and Keyword.keyword?(last) do
true ->
args = Enum.map_join(list, ", ", &to_string(&1, fun))
if list != [], do: args = args <> ", "
args = args <> kw_list_to_string(last, fun)
target <> "(" <> args <> ")"
false ->
args = Enum.map_join(args, ", ", &to_string(&1, fun))
target <> "(" <> args <> ")"
end
end
defp kw_blocks_to_string(kw) do
defp kw_blocks_to_string(kw, fun) do
Enum.reduce(kw_keywords, " ", fn(x, acc) ->
case Keyword.has_key?(kw, x) do
true -> acc <> kw_block_to_string(x, Keyword.get(kw, x))
true -> acc <> kw_block_to_string(x, Keyword.get(kw, x), fun)
false -> acc
end
end) <> "end"
end
defp kw_block_to_string(key, value) do
block = adjust_new_lines block_to_string(value), "\n "
defp kw_block_to_string(key, value, fun) do
block = adjust_new_lines block_to_string(value, fun), "\n "
atom_to_binary(key, :utf8) <> "\n " <> block <> "\n"
end
defp block_to_string({ :->, _, exprs }) do
defp block_to_string({ :->, _, exprs }, fun) do
Enum.map_join(exprs, "\n", fn({ left, _, right }) ->
left = comma_join_or_empty_paren(left, false)
left <> "->\n " <> adjust_new_lines block_to_string(right), "\n "
left = comma_join_or_empty_paren(left, fun, false)
left <> "->\n " <> adjust_new_lines block_to_string(right, fun), "\n "
end)
end
defp block_to_string({ :__block__, _, exprs }) do
Enum.map_join(exprs, "\n", to_string(&1))
defp block_to_string({ :__block__, _, exprs }, fun) do
Enum.map_join(exprs, "\n", &to_string(&1, fun))
end
defp block_to_string(other), do: to_string(other)
defp block_to_string(other, fun), do: to_string(other, fun)
defp op_to_string({ op, _, [_, _] } = expr) when op in binary_ops do
"(" <> to_string(expr) <> ")"
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 op_to_string(expr), do: to_string(expr)
defp parenthise(expr, fun) do
"(" <> to_string(expr, fun) <> ")"
end
defp arrow_to_string({ :->, _, pairs }, paren // false) do
defp op_to_string({ op, _, [_, _] } = expr, fun, parent_op, side) when op in binary_ops do
{ parent_assoc, parent_prec } = binary_op_props(parent_op)
{ _, prec } = binary_op_props(op)
cond do
parent_prec < prec -> to_string(expr, fun)
parent_prec > prec -> parenthise(expr, fun)
true ->
# parent_prec == prec, so look at associativity.
if parent_assoc == side do
to_string(expr, fun)
else
parenthise(expr, fun)
end
end
end
defp op_to_string(expr, fun, _, _), do: to_string(expr, fun)
defp arrow_to_string({ :->, _, pairs }, fun, paren // false) do
Enum.map_join(pairs, "; ", fn({ left, _, right }) ->
left = comma_join_or_empty_paren(left, paren)
left <> "-> " <> to_string(right)
left = comma_join_or_empty_paren(left, fun, paren)
left <> "-> " <> to_string(right, fun)
end)
end
defp comma_join_or_empty_paren([], true), do: "() "
defp comma_join_or_empty_paren([], false), do: ""
defp comma_join_or_empty_paren([], _fun, true), do: "() "
defp comma_join_or_empty_paren([], _fun, false), do: ""
defp comma_join_or_empty_paren(left, _) do
Enum.map_join(left, ", ", to_string(&1)) <> " "
defp comma_join_or_empty_paren(left, fun, _) do
Enum.map_join(left, ", ", &to_string(&1, fun)) <> " "
end
defp adjust_new_lines(block, replacement) do
@@ -369,13 +447,13 @@ defmodule Macro do
* Macros (local or remote);
* Aliases are expanded (if possible) and return atoms;
* All pseudo-variables (__FILE__, __MODULE__, etc);
* Module attributes reader (@foo);
* All pseudo-variables (`__FILE__`, `__MODULE__`, etc);
* Module attributes reader (`@foo`);
In case the expression cannot be expanded, it returns the expression
itself. Notice that `Macro.expand_once/2` performs the expansion just
once and it is not recursive. Check `Macro.expand/2` for expansion
until the node no longer represents a macro and `Macro.expand_all/2`
itself. Notice that `expand_once/2` performs the expansion just
once and it is not recursive. Check `expand/2` for expansion
until the node no longer represents a macro and `expand_all/2`
for recursive expansion.
## Examples
@@ -439,18 +517,23 @@ defmodule Macro do
end
"""
def expand_once(aliases, env) do
expand_once(aliases, env, nil) |> elem(0)
def expand_once(ast, env) do
expand_once(ast, env, nil) |> elem(0)
end
defp expand_once({ :__aliases__, _, _ } = original, env, cache) do
case :elixir_aliases.expand(original, env.aliases, env.macro_aliases) do
atom when is_atom(atom) -> { atom, true, cache }
receiver when is_atom(receiver) ->
:elixir_tracker.record_alias(receiver, env.module)
{ receiver, true, cache }
aliases ->
aliases = lc alias inlist aliases, do: (expand_once(alias, env, cache) |> elem(0))
case :lists.all(is_atom(&1), aliases) do
true -> { :elixir_aliases.concat(aliases), true, cache }
true ->
receiver = :elixir_aliases.concat(aliases)
:elixir_tracker.record_alias(receiver, env.module)
{ receiver, true, cache }
false -> { original, false, cache }
end
end
@@ -533,8 +616,8 @@ defmodule Macro do
@doc """
Receives a AST node and expands it until it no longer represents
a macro. Check `Macro.expand_once/2` for more information on how
expansion works and `Macro.expand_all/2` for recursive expansion.
a macro. Check `expand_once/2` for more information on how
expansion works and `expand_all/2` for recursive expansion.
"""
def expand(tree, env) do
expand(tree, env, nil) |> elem(0)
@@ -553,12 +636,33 @@ defmodule Macro do
{ tree, cache }
end
@doc """
@doc %S"""
Receives a AST node and expands it until it no longer represents
a macro. Then it expands all of its children recursively.
Check `Macro.expand_once/2` for more information on how expansion
works.
The documentation for `expand_once/2` goes into more detail
on how expansion works. Keep in mind that `expand_all/2` is
naive as it doesn't mutate the environment. Take this example:
quoted = quote do: __MODULE__
Macro.to_string Macro.expand_all(quoted, __ENV__)
#=> "nil"
The example above, works as expected. Outside of a module,
`__MODULE__` returns `nil`. Now consider this variation:
quoted = quote do
defmodule Foo, do: __MODULE__
end
Macro.to_string Macro.expand_all(quoted, __ENV__)
#=> "defmodule(Foo) do\n nil\nend"
One would expect `__MODULE__` to be expanded to `Foo`
but that is not the case since all the expansion is done
based on the environment `__ENV__`. This behaviour is on
purpose, as the proper expansion would require passing through
the whole Elixir compiler by actually executing the code, which
is beyond the scope of this function.
"""
def expand_all(tree, env) do
expand_all(tree, env, nil) |> elem(0)
@@ -590,8 +694,8 @@ defmodule Macro do
end
@doc """
Recurs the quoted expression checking if all sub terms are
safe (i.e. they represented data structured and don't actually
Recurs the quoted expression checking if all sub-terms are
safe (i.e. they represent data structures and don't actually
evaluate code) and returns `:ok` unless a given term is unsafe,
which is returned as `{ :unsafe, term }`.
"""
+4 -2
View File
@@ -21,6 +21,7 @@ defmodule Macro.Env do
* `macros` - a list of macros imported from each module
* `context_modules` - a list of modules defined in the current context
* `macro_aliases` - a list of aliases defined inside the current macro
* `vars` - a list keeping all defined varaibles as { var, context }
"""
@type name_arity :: { atom, non_neg_integer }
@@ -32,14 +33,15 @@ defmodule Macro.Env do
@type functions :: [{ module, [name_arity] }]
@type macros :: [{ module, [name_arity] }]
@type context_modules :: [module]
@type vars :: [{ atom, atom }]
fields = [:module, :file, :line, :function, :aliases, :context, :requires,
:functions, :macros, :context_modules, :macro_aliases]
:functions, :macros, :context_modules, :macro_aliases, :vars]
types = quote do: [module: module, file: file, line: line,
function: name_arity, aliases: aliases, requires: requires,
functions: functions, macros: macros, context_modules: context_modules,
macro_aliases: aliases]
macro_aliases: aliases, vars: vars]
Record.deffunctions(fields, __MODULE__)
Record.deftypes(fields, types, __MODULE__)
+36 -34
View File
@@ -5,7 +5,7 @@ defmodule Module do
end
end
@moduledoc %B'''
@moduledoc %S'''
This module provides many functions to deal with modules during
compilation time. It allows a developer to dynamically attach
documentation, add, delete and register attributes and so forth.
@@ -44,7 +44,7 @@ defmodule Module do
A hook that will be invoked before the module is compiled.
Accepts a module or a tuple `{ <module>, <function/macro atom> }`. The
function/macro must take one argument: the module environment. If it's a
function/macro must take one argument: the module environment. If it\'s a
macro, its returned value will be injected at the end of the module definition
before the compilation starts.
@@ -91,7 +91,7 @@ defmodule Module do
@compile { :inline, myfun: 1 }
def myfun(arg) do
to_binary(arg)
to_string(arg)
end
end
@@ -159,8 +159,7 @@ defmodule Module do
* `@on_definition`
A hook that will be invoked after each function or macro in the current
module is defined. This makes it easy to annotate and customize
functions.
module is defined. Useful when annotating functions.
Accepts a module or a tuple `{ <module>, <function atom> }`. The function
must take 6 arguments:
@@ -178,8 +177,11 @@ defmodule Module do
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.
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**
@@ -198,7 +200,7 @@ defmodule Module do
@on_definition { H, :on_def }
def hello(arg) when is_binary(arg) or is_list(arg) do
"Hello" <> to_binary(arg)
"Hello" <> to_string(arg)
end
def hello(_) do
@@ -266,7 +268,6 @@ defmodule Module do
It is possible to query a module at runtime to find out which functions and
macros it defines, extract its docstrings, etc. See `__info__/1`.
'''
@doc """
@@ -462,6 +463,19 @@ defmodule Module do
:elixir_aliases.safe_concat([left, right])
end
@doc """
Gets an anonymous function from the given module, function
and arity. The module and function are not verified to exist.
iex> fun = Module.function(Kernel, :is_atom, 1)
iex> fun.(:hello)
true
"""
def function(mod, fun, arity) do
:erlang.make_fun(mod, fun, arity)
end
@doc """
Attaches documentation to a given function. It expects
the module the function belongs to, the line (a non negative
@@ -715,7 +729,7 @@ defmodule Module do
Expands to:
Module.get_attribute(__MODULE__, @foo, true)
Module.get_attribute(__MODULE__, :foo, true)
Notice the third argument is used to indicate if a warning
should be emitted when the attribute was not previously defined.
@@ -745,8 +759,8 @@ defmodule Module do
if :lists.member(key, acc) do
[]
else
warn && IO.warn "#{Exception.format_caller} undefined module attribute @#{key}, " <>
"please remove access to @#{key} or explicitly set it to nil before access"
warn && :elixir_errors.warn "#{Exception.format_caller} undefined module attribute @#{key}, " <>
"please remove access to @#{key} or explicitly set it to nil before access\n"
nil
end
end
@@ -785,9 +799,7 @@ defmodule Module do
* `:persist` - The attribute will be persisted in the Erlang
Abstract Format. Useful when interfacing with Erlang libraries.
By default, both options are true. Which means that registering
an attribute without passing any options will revert the attribute
behavior to exactly the same expected in :
By default, both options are false.
## Examples
@@ -802,16 +814,16 @@ defmodule Module do
end
"""
def register_attribute(module, new, opts // []) do
def register_attribute(module, new, opts) when is_atom(new) do
assert_not_compiled!(:register_attribute, module)
table = data_table_for(module)
if Keyword.get(opts, :persist, true) do
if Keyword.get(opts, :persist) do
old = :ets.lookup_element(table, :__persisted_attributes, 2)
:ets.insert(table, { :__persisted_attributes, [new|old] })
end
if Keyword.get(opts, :accumulate, true) do
if Keyword.get(opts, :accumulate) do
old = :ets.lookup_element(table, :__acc_attributes, 2)
:ets.insert(table, { :__acc_attributes, [new|old] })
end
@@ -827,17 +839,7 @@ defmodule Module do
"""
def split(module) do
tl(String.split(Binary.Chars.to_binary(module), "."))
end
@doc """
Convert a module name to binary without the Elixir prefix.
"""
def to_binary(Elixir), do: "Elixir"
def to_binary(module) do
"Elixir." <> rest = Binary.Chars.to_binary(module)
rest
tl(String.split(String.Chars.to_string(module), "."))
end
@doc false
@@ -854,7 +856,7 @@ defmodule Module do
:ok ->
:ok
{ :error, :private_doc } ->
IO.warn "#{env.file}:#{line} function #{name}/#{arity} is private, @doc's are always discarded for private functions"
:elixir_errors.warn "#{env.file}:#{line} function #{name}/#{arity} is private, @doc's are always discarded for private functions\n"
end
delete_attribute(module, :doc)
@@ -887,9 +889,9 @@ defmodule Module do
atom
end
defp normalize_attribute(:file, env) when is_env(env), do: { binary_to_list(env.file), env.line }
defp normalize_attribute(:file, { binary, line }) when is_binary(binary), do: { binary_to_list(binary), line }
defp normalize_attribute(:file, other) when not is_tuple(other), do: normalize_attribute(:file, { other, 1 })
defp normalize_attribute(:file, file) when is_binary(file) do
file
end
defp normalize_attribute(key, atom) when is_atom(atom) and
key in [:before_compile, :after_compile, :on_definition] do
@@ -897,7 +899,7 @@ defmodule Module do
end
defp normalize_attribute(key, _value) when key in [:type, :typep, :export_type, :opaque, :callback] do
raise ArgumentError, message: "Attributes type, typep, export_type, opaque and callback " <>
raise ArgumentError, message: "attributes type, typep, export_type, opaque and callback " <>
"must be set via Kernel.Typespec"
end
+29 -12
View File
@@ -94,8 +94,6 @@ defmodule Module.DispatchTracker do
@doc """
Returns all the modules which were imported.
All external dependencies to a module is the sum
of imports and remotes.
"""
@spec imports(ref) :: [module]
def imports(ref) do
@@ -114,9 +112,16 @@ defmodule Module.DispatchTracker do
end
@doc """
Returns all the modules which were remotely dispatched
to. All external dependencies to a module is the sum
of imports and remotes.
Returns all the aliases defined in the given module.
"""
@spec aliases(ref) :: [module]
def aliases(ref) do
d = :gen_server.call(to_pid(ref), :digraph, @timeout)
:digraph.out_neighbours(d, :alias)
end
@doc """
Returns all the modules which were remotely dispatched to.
"""
@spec remotes(ref) :: [module]
def remotes(ref) do
@@ -156,7 +161,11 @@ defmodule Module.DispatchTracker do
end
defp to_pid(pid) when is_pid(pid), do: pid
defp to_pid(mod) when is_atom(mod), do: Module.get_attribute(mod, :__dispatch_tracker)
defp to_pid(mod) when is_atom(mod) do
table = :elixir_module.data_table(mod)
[{ _, val }] = :ets.lookup(table, :__dispatch_tracker)
val
end
defp only_tuples(list) do
lc x inlist list, is_tuple(x), do: x
@@ -197,16 +206,22 @@ defmodule Module.DispatchTracker do
:gen_server.cast(pid, { :add_local, from, to })
end
# Adds an alias.
@doc false
def add_alias(pid, module) when is_atom(module) do
:gen_server.cast(pid, { :add_alias, module })
end
# Adds a remote dispatch to the given target.
@doc false
def add_remote(pid, function, module, target) when is_atom(module) and is_tuple(target) do
:gen_server.cast(pid, { :add_remote, function, module, target })
:gen_server.cast(pid, { :add_external, :remote, function, module, target })
end
# Adds a import dispatch to the given target.
@doc false
def add_import(pid, function, module, target) when is_atom(module) and is_tuple(target) do
:gen_server.cast(pid, { :add_import, function, module, target })
:gen_server.cast(pid, { :add_external, :import, function, module, target })
end
# Associates a module with a warn. This adds the given
@@ -307,6 +322,7 @@ defmodule Module.DispatchTracker do
def init([]) do
d = :digraph.new([:protected])
:digraph.add_vertex(d, :local)
:digraph.add_vertex(d, :alias)
:digraph.add_vertex(d, :import)
:digraph.add_vertex(d, :remote)
:digraph.add_vertex(d, :warn)
@@ -337,13 +353,14 @@ defmodule Module.DispatchTracker do
{ :noreply, d }
end
def handle_cast({ :add_remote, function, module, { name, arity } }, d) do
handle_import_or_remote(d, :remote, function, module, name, arity)
def handle_cast({ :add_alias, module }, d) do
:digraph.add_vertex(d, module)
replace_edge!(d, :alias, module)
{ :noreply, d }
end
def handle_cast({ :add_import, function, module, { name, arity } }, d) do
handle_import_or_remote(d, :import, function, module, name, arity)
def handle_cast({ :add_external, kind, function, module, { name, arity } }, d) do
handle_import_or_remote(d, kind, function, module, name, arity)
{ :noreply, d }
end
+17 -17
View File
@@ -6,7 +6,7 @@ defmodule Node do
@type t :: atom
@doc """
Returns the current node. It returns the same as the built-in node().
Returns the current node. It returns the same as the built-in `node()`.
"""
@spec self :: t
def self do
@@ -14,8 +14,8 @@ 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. Otherwise, it returns `false`.
"""
@spec alive? :: boolean
def alive? do
@@ -24,7 +24,7 @@ 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
@@ -45,8 +45,8 @@ 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.
"""
@@ -56,8 +56,8 @@ defmodule Node do
end
@doc """
Behaves as monitor_node/2 except that it allows an extra
option to be given, namely :allow_passive_connect.
Behaves as `monitor/2` except that it allows an extra
option to be given, namely `:allow_passive_connect`.
See http://www.erlang.org/doc/man/erlang.html#monitor_node-3 for more info.
"""
@@ -69,8 +69,8 @@ defmodule Node do
@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.
authentication protocols. Returns `true` if disconnection succeeds, otherwise
`false`. If the local node is not alive, the function returns `:ignored`.
See http://www.erlang.org/doc/man/erlang.html#disconnect_node-1 for more info.
"""
@@ -80,8 +80,8 @@ 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
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.
@@ -145,7 +145,7 @@ defmodule Node do
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
(and due to the link, an exit signal with exit reason `:noconnection` will be
received).
"""
@spec spawn_link(t, (() -> any)) :: pid
@@ -158,7 +158,7 @@ defmodule Node do
`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).
`:noconnection` will be received).
"""
@spec spawn_link(t, module, atom, [any]) :: pid
def spawn_link(node, module, fun, args) do
@@ -167,10 +167,10 @@ defmodule Node do
@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
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.
This function will raise `FunctionClauseError` if the given `node` is not alive.
"""
def set_cookie(node // Node.self, cookie) when is_atom(cookie) do
:erlang.set_cookie(node, cookie)
@@ -183,4 +183,4 @@ defmodule Node do
def get_cookie() do
:erlang.get_cookie()
end
end
end
+121 -66
View File
@@ -1,49 +1,69 @@
defmodule OptionParser do
@moduledoc """
This module contains functions to parse command line arguments.
"""
@doc """
Parses the argv and returns one tuple with parsed options
and the arguments.
Parses `argv` and returns a tuple with the parsed options, its
arguments, and a list options that couldn't be parsed.
## Examples
iex> OptionParser.parse(["--debug"])
{ [debug: true], [] }
{ [debug: true], [], [] }
iex> OptionParser.parse(["--source", "lib"])
{ [source: "lib"], [] }
{ [source: "lib"], [], [] }
iex> OptionParser.parse(["--source", "lib", "test/enum_test.exs", "--verbose"])
{ [source: "lib", verbose: true], ["test/enum_test.exs"] }
iex> OptionParser.parse(["--source-path", "lib", "test/enum_test.exs", "--verbose"])
{ [source_path: "lib", verbose: true], ["test/enum_test.exs"], [] }
Notice how Elixir automatically translates the "--source-path"
switch to the underscored atom `:source_path`, which better follows
Elixir conventions.
## Aliases
A set of aliases can be given as second argument:
A set of aliases can be given as the second argument:
iex> OptionParser.parse(["-d"], aliases: [d: :debug])
{ [debug: true], [] }
{ [debug: true], [], [] }
## Switches
Extra information about switches can be given as argument too.
This is useful in order to say a switch must behave as a boolean
Extra information about switches can be given as arguments, too.
This is useful when a switch must behave as a boolean
or if duplicated switches should be kept, overriden or accumulated.
The following types are supported:
* `:boolean` - Mark the given switch as boolean. Boolean switches
never consumes the following value unless it is
true or false;
* `:boolean` - Marks the given switch as a boolean. Boolean switches
never consume the following value unless it is
`true` or `false`;
* `:integer` - Parses the switch as an integer;
* `:float` - Parses the switch as a float;
If a switch can't be parsed, the option is returned in the invalid
options list (third element of the returned tuple).
The following extra options are supported:
* `:keep` - Keep duplicated items in the list instead of overriding;
* `:keep` - Keeps duplicated items in the list instead of overriding;
Examples:
iex> OptionParser.parse(["--unlock", "path/to/file"], switches: [unlock: :boolean])
{ [unlock: true], ["path/to/file"] }
{ [unlock: true], ["path/to/file"], [] }
iex> OptionParser.parse(["--unlock", "false", "path/to/file"], switches: [unlock: :boolean])
{ [unlock: false], ["path/to/file"] }
iex> OptionParser.parse(["--unlock", "--limit", "0", "path/to/file"],
...> switches: [unlock: :boolean, limit: :integer])
{ [unlock: true, limit: 0], ["path/to/file"], [] }
iex> OptionParser.parse(["-limit", "3"], switches: [limit: :integer])
{ [limit: 3], [], [] }
iex> OptionParser.parse(["-limit", "yyz"], switches: [limit: :integer])
{ [], [], [limit: "yyz"] }
## Negation switches
@@ -51,7 +71,12 @@ defmodule OptionParser do
booleans and never parse the next value:
iex> OptionParser.parse(["--no-op", "path/to/file"])
{ [no_op: true], ["path/to/file"] }
{ [no_op: true], ["path/to/file"], [] }
In case the negated switch exists as a boolean, it sets the boolean to false:
iex> OptionParser.parse(["--no-op", "path/to/file"], switches: [op: :boolean])
{ [op: false], ["path/to/file"], [] }
"""
def parse(argv, opts // []) when is_list(argv) and is_list(opts) do
@@ -59,16 +84,18 @@ defmodule OptionParser do
end
@doc """
Similar to parse but only parses the head of the argv.
I.e. as soon as it finds a non switch, it stops parsing.
Similar to `parse/2` but only parses the head of `argv`;
as soon as it finds a non-switch, it stops parsing.
Check `parse/2` for more info.
See `parse/2` for more information.
## Example
iex> OptionParser.parse_head(["--source", "lib", "test/enum_test.exs", "--verbose"])
{ [source: "lib"], ["test/enum_test.exs", "--verbose"] }
{ [source: "lib"], ["test/enum_test.exs", "--verbose"], [] }
iex> OptionParser.parse_head(["--verbose", "--source", "lib", "test/enum_test.exs", "--unlock"])
{ [verbose: true, source: "lib"], ["test/enum_test.exs", "--unlock"], [] }
"""
def parse_head(argv, opts // []) when is_list(argv) and is_list(opts) do
parse(argv, opts, false)
@@ -83,90 +110,118 @@ defmodule OptionParser do
end
defp parse(argv, aliases, switches, all) do
parse(argv, aliases, switches, [], [], all)
parse(argv, aliases, switches, [], [], [], all)
end
defp parse(["-" <> option|t], aliases, switches, dict, args, all) do
{ option, value } = normalize_option(option, aliases)
kind = switches[option]
defp parse(["--"|_] = value, _aliases, _switches, dict, _args, invalid, _all) do
{ Enum.reverse(dict), value, Enum.reverse(invalid) }
end
if value == nil do
defp parse(["-" <> option|t], aliases, switches, dict, args, invalid, all) do
{ option, kinds, value } = normalize_option(option, switches, aliases)
if nil?(value) do
{ value, t } =
if is_switch_a? :boolean, kind do
boolean_from_tail(t)
if :boolean in kinds do
{ true, t }
else
value_from_tail(t)
end
end
dict = store_option dict, option, value, kind
parse(t, aliases, switches, dict, args, all)
{ dict, invalid } = store_option(dict, invalid, option, value, kinds)
parse(t, aliases, switches, dict, args, invalid, all)
end
defp parse([h|t], aliases, switches, dict, args, true) do
parse(t, aliases, switches, dict, [h|args], true)
defp parse([h|t], aliases, switches, dict, args, invalid, true) do
parse(t, aliases, switches, dict, [h|args], invalid, true)
end
defp parse([], _, switches, dict, args, true) do
{ reverse_dict(dict, switches), Enum.reverse(args) }
defp parse([], _, _switches, dict, args, invalid, true) do
{ Enum.reverse(dict), Enum.reverse(args), Enum.reverse(invalid) }
end
defp parse(value, _, switches, dict, _args, false) do
{ reverse_dict(dict, switches), value }
defp parse(value, _, _switches, dict, _args, invalid, false) do
{ Enum.reverse(dict), value, Enum.reverse(invalid) }
end
defp boolean_from_tail([h|t]) when h in ["false", "true"], do: { h, t }
defp boolean_from_tail(t) , do: { true, t }
defp value_from_tail(["-" <> _|_] = t), do: { true, t }
defp value_from_tail([h|t]), do: { h, t }
defp value_from_tail([]), do: { true, [] }
defp store_option(dict, option, value, switches) when value in ["true", "false"] do
store_option dict, option, binary_to_atom(value), switches
end
defp store_option(dict, invalid, option, value, kinds) do
{ invalid_option, value } =
cond do
:boolean in kinds ->
{ nil, value in [true, "true"] }
:integer in kinds ->
case String.to_integer(value) do
{ value, "" } -> { nil, value }
_ -> { option, value }
end
:float in kinds ->
case String.to_float(value) do
{ value, "" } -> { nil, value }
_ -> { option, value }
end
true ->
{ nil, value }
end
defp store_option(dict, option, value, kind) do
if is_switch_a? :keep, kind do
[{ option, value }|dict]
if invalid_option do
{ dict, [{ option, value }|invalid] }
else
[{ option, value }|Keyword.delete(dict, option)]
{ do_store_option(dict, option, value, kinds), invalid }
end
end
defp reverse_dict(dict, switches) do
switches = lc { k, v } inlist switches,
is_switch_a?(:boolean, v),
not Keyword.has_key?(dict, k), do: { k, false }
Enum.reverse switches ++ dict
defp do_store_option(dict, option, value, kinds) do
cond do
:keep in kinds ->
[{ option, value }|dict]
true ->
[{ option, value }|Keyword.delete(dict, option)]
end
end
defp normalize_option(<<?-, option :: binary>>, aliases) do
normalize_option(option, aliases)
defp normalize_option(<<?-, option :: binary>>, switches, aliases) do
normalize_option(option, switches, aliases)
end
defp normalize_option(option, aliases) do
defp normalize_option(option, switches, aliases) do
{ option, value } = split_option(option)
if is_no?(option), do: value = true
atom = option |> to_underscore |> binary_to_atom
{ aliases[atom] || atom, value }
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
else
atom = get_aliased(option, aliases)
{ atom, List.wrap(switches[atom]), value }
end
end
defp split_option(option) do
case :binary.split(option, "=") do
[h] -> { h, nil }
[h|t] -> { h, Enum.join(t, "=") }
[h] -> { h, nil }
[h, t] -> { h, t }
end
end
defp to_underscore(option) do
bc <<c>> inbits option, do: << if c == ?-, do: ?_, else: c >>
bc <<c>> inbits option, do: << if(c == ?-, do: ?_, else: c) >>
end
defp is_no?("no-" <> _), do: true
defp is_no?(_), do: false
defp get_aliased(option, aliases) do
atom = option |> to_underscore |> binary_to_atom
aliases[atom] || atom
end
defp is_switch_a?(kind, list) when is_list(list), do: kind in list
defp is_switch_a?(kind, kind), do: true
defp is_switch_a?(_, _), do: false
defp get_non_negated("no-" <> rest, aliases), do: get_aliased(rest, aliases)
defp get_non_negated(_, _), do: nil
end
+32 -15
View File
@@ -4,12 +4,12 @@ defmodule Path do
retrieving file system paths.
The functions in this module may receive a char list or
a binary as argument and will return a value of the same
a binary as an argument and will return a value of the same
type.
The majority of the functions in this module do not
interact with the file system, except for a few functions
that require it (like `Path.wildcard` and `Path.expand`).
that require it (like `wildcard/1` and `expand/1`).
"""
alias :filename, as: FN
@@ -18,8 +18,8 @@ defmodule Path do
@type r :: char_list | binary
@doc """
Converts the given path to an absolute one. Differently from
`Path.expand/1`, no attempt is made to resolve `..`, `.` or `~`.
Converts the given path to an absolute one. Unlike
`expand/1`, no attempt is made to resolve `..`, `.` or `~`.
## Unix examples
@@ -43,9 +43,9 @@ defmodule Path do
@doc """
Builds a path from `relative_to` to `path`. If `path` is already
an absolute path, `relative_to` is ignored. See also `Path.relative/2`.
an absolute path, `relative_to` is ignored. See also `relative_to/2`.
Differently from `Path.expand/2`, no attempt is made to
Unlike `expand/2`, no attempt is made to
resolve `..`, `.` or `~`.
## Examples
@@ -67,7 +67,7 @@ defmodule Path do
## Examples
iex> Path.expand("/foo/bar/../bar")
Path.expand("/foo/bar/../bar")
"/foo/bar"
"""
@@ -80,7 +80,7 @@ defmodule Path do
expanding any `.` and `..` characters. If the path is already an
absolute path, `relative_to` is ignored.
Note, that this function treats `path` with leading `~` as
Note, that this function treats `path` with a leading `~` as
an absolute one.
The second argument is first expanded to an absolute path.
@@ -91,9 +91,9 @@ defmodule Path do
Path.expand("foo/bar/../bar", "baz")
#=> "/quux/baz/foo/bar"
iex> Path.expand("foo/bar/../bar", "/baz")
Path.expand("foo/bar/../bar", "/baz")
"/baz/foo/bar"
iex> Path.expand("/foo/bar/../bar", "/baz")
Path.expand("/foo/bar/../bar", "/baz")
"/foo/bar"
"""
@@ -236,6 +236,18 @@ defmodule Path do
original
end
@doc """
Convenience to get the path relative to the current working
directory. If, for some reason, the current working directory
cannot be retrieved, returns the full path.
"""
def relative_to_cwd(path) do
case :file.get_cwd do
{ :ok, base } -> relative_to(path, base)
_ -> path
end
end
@doc """
Returns the last component of the path or the path
itself if it does not contain any directory separators.
@@ -397,7 +409,7 @@ defmodule Path do
defp do_join(<<?/, rest :: binary>>, relativename, [?/|result], os_type), do:
do_join(rest, relativename, [?/|result], os_type)
defp do_join(<<>>, <<>>, result, os_type), do:
list_to_binary(maybe_remove_dirsep(result, os_type))
iolist_to_binary(maybe_remove_dirsep(result, os_type))
defp do_join(<<>>, relativename, [?:|rest], :win32), do:
do_join(relativename, <<>>, [?:|rest], :win32)
defp do_join(<<>>, relativename, [?/|result], os_type), do:
@@ -461,7 +473,7 @@ defmodule Path do
Imagine you have a directory called `projects` with three Elixir projects
inside of it: `elixir`, `ex_doc` and `dynamo`. You can find all `.beam` files
inside the ebin directory of each project as follows:
inside the `ebin` directory of each project as follows:
Path.wildcard("projects/*/ebin/**/*.beam")
@@ -471,8 +483,9 @@ defmodule Path do
"""
def wildcard(glob) when is_binary(glob) do
paths = :filelib.wildcard :unicode.characters_to_list(glob)
Enum.map paths, :unicode.characters_to_binary(&1)
paths = :filelib.wildcard binary_to_filename_string(glob)
encoding = :file.native_name_encoding()
Enum.map paths, &flatten_filename_to_binary(&1, encoding)
end
def wildcard(glob) when is_list(glob) do
@@ -494,7 +507,11 @@ defmodule Path do
end
defp filename_string_to_binary(list) do
case :unicode.characters_to_binary(:filename.flatten(list), :unicode, :file.native_name_encoding()) do
flatten_filename_to_binary(:filename.flatten(list), :file.native_name_encoding())
end
defp flatten_filename_to_binary(list, encoding) do
case :unicode.characters_to_binary(list, :unicode, encoding) do
{ :error, _, _ } ->
:erlang.error(:badarg)
bin when is_binary(bin) ->
+6 -5
View File
@@ -286,7 +286,7 @@ defmodule Process do
@doc """
Returns the pid or port identifier with the registered name.
Returns undefined if the name is not registered.
Returns nil if the name is not registered.
See http://www.erlang.org/doc/man/erlang.html#whereis-1 for more info.
"""
@@ -347,25 +347,26 @@ defmodule Process do
end
@doc """
Returns information about the process identified by pid.
Returns information about the process identified by pid or nil if the process
is not alive.
Use this only for debugging information.
See http://www.erlang.org/doc/man/erlang.html#process_info-1 for more info.
"""
@spec info(pid) :: Keyword.t
def info(pid) do
:erlang.process_info(pid)
nillify :erlang.process_info(pid)
end
@doc """
Returns information about the process identified by pid
or undefined if the process is not alive.
or nil if the process is not alive.
See http://www.erlang.org/doc/man/erlang.html#process_info-2 for more info.
"""
@spec info(pid, atom) :: {atom, term}
def info(pid, spec) do
:erlang.process_info(pid, spec)
nillify :erlang.process_info(pid, spec)
end
@compile { :inline, nillify: 1 }
+18 -8
View File
@@ -30,7 +30,7 @@ defmodule Protocol do
]
# Import the new dsl that holds the new def
import :macros, Protocol.DSL
import Protocol.DSL, only: :macros
# Set up a clear slate to store defined functions
@functions []
@@ -71,8 +71,14 @@ defmodule Protocol do
defmodule name do
@behaviour unquote(protocol)
@protocol unquote(protocol)
@for unquote(for)
unquote(block)
def __impl__, do: unquote(protocol)
def __impl__(:name), do: __MODULE__
def __impl__(:protocol), do: @protocol
def __impl__(:for), do: @for
end
end
end
@@ -169,7 +175,7 @@ defmodule Protocol do
impl_bang = if returns_nil do
quote do
def __impl_for__!(arg) do
__impl_for__(arg) || raise(Protocol.UndefinedError, protocol: __MODULE__, structure: arg)
__impl_for__(arg) || raise(Protocol.UndefinedError, protocol: __MODULE__, value: arg)
end
end
else
@@ -359,7 +365,7 @@ defmodule Protocol.DSL do
defp nil_clause() do
{ [nil],
[],
quote(do: raise(Protocol.UndefinedError, protocol: __MODULE__, structure: xA)) }
quote(do: raise(Protocol.UndefinedError, protocol: __MODULE__, value: xA)) }
end
defp record_clause(name, args, nil) do
@@ -400,7 +406,7 @@ defmodule Protocol.DSL do
type_args = lc _ inlist :lists.seq(2, arity), do: quote(do: term)
type_args = [quote(do: t) | type_args]
quote do
meta = quote do
name = unquote(name)
arity = unquote(arity)
@@ -410,13 +416,17 @@ defmodule Protocol.DSL do
# signature that will be used by docs
Kernel.def unquote(name)(unquote_splicing(args))
{ args, body } = Protocol.DSL.args_and_body(__MODULE__, name, arity)
Kernel.def unquote(name), args, [], do: body
# Convert the spec to callback if possible,
# otherwise generate a dummy callback
Protocol.DSL.callback_from_spec(__MODULE__, name, arity) ||
@callback unquote(name)(unquote_splicing(type_args)) :: term
end
impl = quote bind_quoted: [name: name, arity: arity] do
{ args, body } = Protocol.DSL.args_and_body(__MODULE__, name, arity)
Kernel.def unquote(name)(unquote_splicing(args)), do: unquote(body)
end
[meta, impl]
end
end
+6 -4
View File
@@ -5,6 +5,9 @@ defrecord Range, [:first, :last] do
end
defprotocol Range.Iterator do
@doc """
Reduces the range based on the type of the first argument.
"""
def reduce(first, range, acc, fun)
@doc """
@@ -17,6 +20,7 @@ defimpl Enumerable, for: Range do
def reduce(Range[first: first] = range, acc, fun) do
Range.Iterator.reduce(first, range, acc, fun)
end
def member?(Range[first: first, last: last], value) do
value in first..last
end
@@ -61,10 +65,8 @@ defimpl Range.Iterator, for: Number do
end
end
defimpl Binary.Inspect, for: Range do
import Kernel, except: [inspect: 2]
defimpl Inspect, for: Range do
def inspect(Range[first: first, last: last], opts) do
Kernel.inspect(first, opts) <> ".." <> Kernel.inspect(last, opts)
Inspect.Algebra.concat [Kernel.inspect(first, opts), "..", Kernel.inspect(last, opts)]
end
end
+328 -299
View File
@@ -1,164 +1,185 @@
defmodule Record do
@moduledoc %B"""
Functions to define Elixir records
@moduledoc %S"""
Convenience functions for working with Records.
A record is a tagged tuple which contains one or more elements and the first
element is a module. One creates a record by calling `defrecord` or
`defrecordp` which are documented in `Kernel`.
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:
## Examples
iex> record = { User, "josé", 25 }
iex> is_record(record, User)
true
defrecord FileInfo, atime: nil, accesses: 0
However, manually constructing tuples can be quite error prone.
In case 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 and more items are added to the tuple, they
lose semantic value.
The line above will define a module named `FileInfo` which
contains a function named `new` that returns a new record
and other functions to read and set the values in the
record:
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.
file_info = FileInfo.new(atime: now())
file_info.atime #=> Returns the value of atime
file_info.atime(now()) #=> Updates the value of atime
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.
# Update multiple attributes at once:
file_info.update(atime: now(), accesses: 1)
## defrecordp
# Obtain the keywords representation of a record:
file_info.to_keywords #=> [accesses: 1, atime: {1370,7171,911705}]
The simplest way of working with records is via `defrecordp`:
A record is simply a tuple where the first element is the record
module name. We can get the record raw representation as follow:
inspect FileInfo.new, raw: true
#=> { FileInfo, nil, nil }
Besides defining readers and writers for each attribute, Elixir also
defines an `update_#{attribute}` function to update the value. Such
functions expect a function as argument that receives the current
value and must return the new one. For example, every time the file
is accessed, the accesses counter can be incremented with:
file_info.update_accesses(fn(old) -> old + 1 end)
Which can be also written as:
file_info.update_accesses(&1 + 1)
## Access syntax
Records in Elixir can be expanded at compilation time to provide
pattern matching and faster operations. For example, the clause
below will only match if a `FileInfo` is given and the number of
accesses is zero:
def enforce_no_access(FileInfo[accesses: 0]), do: :ok
The clause above will expand to:
def enforce_no_access({ FileInfo, _, 0 }), do: :ok
The downside of using such syntax is that, every time the record
changes, your code now needs to be recompiled (which is usually
not a concern since Elixir build tools by default recompiles the
whole project whenever there is a change).
Finally, keep in mind that Elixir triggers some optimizations whenever
the access syntax is used. For example:
def no_access?(FileInfo[] = file_info) do
file_info.accesses == 0
defmodule User do
defrecordp :user, name: "José", age: 25
end
Is translated to:
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` of 25.
def no_access?({ FileInfo, _, _ } = file_info) do
elem(file_info, 1) == 0
Let's see some examples:
# 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:
# Create a new record
sample_user = user()
# And now change its age to 26
user(sample_user, age: 26)
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/2` docs.
In general, though, records are used as part of a module's public
interface. For such use cases, Elixir provides record modules.
## 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
Notice that, unlike `defrecordp`, `defrecord` expects an
alias as the first argument. This is because `defrecord` is going
to create a module named `User` with all the relevant metadata.
This module can then be imported and we can manipulate the user
as with `defrecordp`:
Record.import User, as: :user
user() #=> User[name: "José", age: 25]
user(age: 26) #=> User[name: "José", age: 26]
Notice that now, since the record definition is accessible, Elixir
shows the record nicely formatted, no longer as a simple tuple. We
can get the raw formatting by passing `raw: true` to `inspect`:
inspect user(), raw: true
{ User, "José", 25 }
Since working with external records is common, Elixir allows
developers to skip importing the record altogether in favor
of a `Module[args]` syntax:
# Skipping a field uses its default value
User[] #=> User[name: "José", age: 25]
User[age: 26] #=> User[name: "José", age: 26]
The macro name is replaced by the module name and the parenthesis
are replaced by brackets. When the shortcut syntax is used, there
is no need to import the record.
Before we sum up the differences between `defrecord` and
`defrecordp`, there is one last functionality introduced by
`defrecord` that we need to discuss.
## Runtime access
All the functionality discussed above happens at compilation time.
This means that both `user(user, age: 26)` and `User[user, age: 26]`
will be transformed into a simple tuple operation at compile time.
However, there are some situations where we want to set or update
fields dynamically. `defrecord` (and `defrecord` only) supports
it 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.
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.
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
end
Which provides faster get and set times for record operations.
Now we can explicitly convert our WeekDate:
## Runtime introspection
to_string WeekDate[year: 2013, week: 26, week_day: 4]
"2013-W26-4"
At runtime, developers can use `__record__` to get information
about the given record:
FileInfo.__record__(:name)
#=> FileInfo
FileInfo.__record__(:fields)
#=> [atime: nil, accesses: 0]
In order to quickly access the index of a field, one can use
the `__index__` function:
FileInfo.__index__(:atime)
#=> 0
FileInfo.__index__(:unknown)
#=> nil
## Compile-time introspection
At the compile time, one can access 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
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]
## Documentation
By default records are not documented and have `@moduledoc` set to false.
## Types
Every record defines a type named `t` that can be accessed in typespecs.
Those types can be passed at the moment the record is defined:
defrecord User,
name: "" :: string,
age: 0 :: integer
All the 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()
If the developer wants to define their own types to be used with the
record, Elixir allows a more lengthy definition with the help of the
`record_type` macro:
defrecord Config, counter: 0, failures: [] do
@type kind :: term
record_type counter: integer, failures: [kind]
end
## Importing records
It is also possible to import a public record (a record, defined using
`defrecord`) as a set of private macros (as if it was defined using `defrecordp`):
Record.import Config, as: :config
See `Record.import/2` and `defrecordp/2` documentation for more information
A protocol can be implemented for any record defined via `defrecord`.
"""
@type t :: tuple
@doc """
Extract record information from an Erlang file and
return the fields as a list of tuples.
@@ -173,94 +194,75 @@ defmodule Record do
end
@doc """
Main entry point for records definition. It defines a module
with the given `name` and the fields specified in `values`.
This is invoked directly by `Kernel.defrecord`, so check it
for more information and documentation.
"""
def defrecord(name, values, opts) do
block = Keyword.get(opts, :do, nil)
{ fields, types } = record_split(values)
quote do
unquoted_fields = unquote(fields)
defmodule unquote(name) do
@moduledoc false
import Elixir.Record.DSL
@record_fields []
@record_types unquote(types)
# Reassign fields to inner scope to
# avoid conflicts in nested records
fields = unquoted_fields
Elixir.Record.deffunctions(fields, __ENV__)
value = unquote(block)
Elixir.Record.deftypes(fields, @record_types, __ENV__)
value
end
end
end
defp record_split(fields) when is_list(fields) do
record_split(fields, [], [])
end
defp record_split(other) do
{ other, [] }
end
defp record_split([{ field, { :::, _, [default, type] }}|t], defaults, types) do
record_split t, [{ field, default }|defaults], [{ field, Macro.escape(type) }|types]
end
defp record_split([other|t], defaults, types) do
record_split t, [other|defaults], types
end
defp record_split([], defaults, types) do
{ :lists.reverse(defaults), types }
end
@doc """
Import public record definition as a set of private macros,
Imports a public record definition as a set of private macros,
as defined by `Kernel.defrecordp/2`. This is useful when one
desires to manipulate a record via a set of macros instead
of the regular access syntax.
## Example
defmodule Test do
Record.import File.Stat, as: :file_stat
defmodule Test do
Record.import File.Stat, as: :file_stat
def size(file_stat(size: size)), do: size
end
def size(file_stat(size: size)), do: size
end
"""
defmacro import(module, as: name) do
quote do
Record.defmacros(unquote(name),
unquote(module).__record__(:fields), __ENV__, unquote(module))
module = unquote(module)
fields = if module == __MODULE__ do
@record_fields
else
module.__record__(:fields)
end
Record.defmacros(unquote(name), fields, __ENV__, module)
end
end
@doc """
Main entry point for private records definition. It defines
a set of macros with the given `name` and the fields specified
in `values`. This is invoked directly by `Kernel.defrecordp`,
so check it for more information and documentation.
"""
def defrecordp(name, fields) when is_atom(name) and is_list(fields) do
{ fields, types, def_type } = recordp_split(fields, [], [], false)
type = :"#{name}_t"
@doc false
def defrecord(name, fields, opts) do
block = Keyword.get(opts, :do, nil)
record_check!(fields)
quote do
Record.defmacros(unquote(name), unquote(fields), __ENV__)
unquoted_fields = unquote(fields)
defmodule unquote(name) do
import Elixir.Record.DSL
@record_fields []
@record_types []
Elixir.Record.deffunctions(unquoted_fields, __ENV__)
value = unquote(block)
Elixir.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) when is_atom(name) and is_atom(tag) and is_list(fields) do
{ fields, types, def_type } = recordp_split(fields, [], [], false)
type = binary_to_atom(atom_to_binary(name) <> "_t")
tag = tag || name
quote do
Record.defmacros(unquote(name), unquote(fields), __ENV__, unquote(tag))
if unquote(def_type) do
@typep unquote(type)() :: { unquote(name), unquote_splicing(types) }
@typep unquote(type)() :: { unquote(tag), unquote_splicing(types) }
end
end
end
@@ -297,7 +299,6 @@ defmodule Record do
contents = [
reflection(escaped),
initializer(escaped),
indexes(escaped),
conversions(values),
record_optimizable(),
updater(values),
@@ -355,25 +356,23 @@ defmodule Record do
{ key, Macro.escape(value) }
end
tag = tag || name
contents = quote do
defmacrop unquote(name)() do
Record.access(unquote(tag) || __MODULE__, unquote(escaped), [], __CALLER__)
Record.access(unquote(tag), unquote(escaped), [], __CALLER__)
end
defmacrop unquote(name)(record) when is_tuple(record) do
Record.to_keywords(unquote(tag) || __MODULE__, unquote(escaped), record)
Record.to_keywords(unquote(tag), unquote(escaped), record)
end
defmacrop unquote(name)(args) do
Record.access(unquote(tag) || __MODULE__, unquote(escaped), args, __CALLER__)
end
defmacrop unquote(name)(record, key) when is_atom(key) do
Record.get(unquote(tag) || __MODULE__, unquote(escaped), record, key)
Record.access(unquote(tag), unquote(escaped), args, __CALLER__)
end
defmacrop unquote(name)(record, args) do
Record.dispatch(unquote(tag) || __MODULE__, unquote(escaped), record, args, __CALLER__)
Record.dispatch(unquote(tag), unquote(escaped), record, args, __CALLER__)
end
end
@@ -449,23 +448,48 @@ defmodule Record do
end
end
# Dispatch the call to either update or to_list depending on the args given.
# Implements to_keywords macro defined by defmacros.
# It returns a quoted expression that represents
# converting record to keywords list.
@doc false
def to_keywords(_atom, fields, record) do
{ var, extra } = cache_var(record)
keywords = Enum.map fields,
fn { key, _default } ->
index = find_index(fields, key, 0)
quote do
{ unquote(key), :erlang.element(unquote(index + 2), unquote(var)) }
end
end
quote do
unquote_splicing(extra)
unquote(keywords)
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
if is_keyword(args) do
update(atom, fields, record, args, caller)
else
to_list(atom, fields, record, args)
cond do
is_atom(args) ->
get(atom, fields, record, args)
is_keyword(args) ->
update(atom, fields, record, args, caller)
is_list(args) ->
to_list(atom, fields, record, args)
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.
@doc false
defp update(atom, fields, var, keyword, caller) do
unless is_keyword(keyword) do
raise ArgumentError, message: "expected contents inside brackets to be a Keyword"
raise ArgumentError, message: "expected arguments to be compile time keywords"
end
if caller.in_match? do
@@ -487,8 +511,7 @@ defmodule Record do
# Implements the get macro defined by defmacros.
# It returns a quoted expression that represents
# getting the value of a given field.
@doc false
def get(atom, fields, var, key) do
defp get(atom, fields, var, key) do
index = find_index(fields, key, 0)
if index do
quote do
@@ -499,40 +522,47 @@ defmodule Record do
end
end
# Implements to_keywords macro defined by defmacros.
# It returns a quoted expression that represents
# converting record to keywords list.
@doc false
def to_keywords(_atom, fields, record) do
Enum.map fields,
fn { key, _default } ->
index = find_index(fields, key, 0)
quote do
{ unquote(key), :erlang.element(unquote(index + 2), unquote(record)) }
end
end
end
# Implements to_list macro defined by defmacros.
# It returns a quoted expression that represents
# extracting given fields from record.
@doc false
defp to_list(atom, fields, record, keys) do
Enum.map keys,
unless is_list(fields) do
raise ArgumentError, message: "expected arguments to be a compile time list"
end
{ var, extra } = cache_var(record)
list = Enum.map keys,
fn(key) ->
index = find_index(fields, key, 0)
if index do
quote do: :erlang.element(unquote(index + 2), unquote(record))
quote do: :erlang.element(unquote(index + 2), unquote(var))
else
raise ArgumentError, message: "record #{inspect atom} does not have the key: #{inspect key}"
end
end
quote do
unquote_splicing(extra)
unquote(list)
end
end
defp cache_var({ var, _, kind } = tuple) when is_atom(var) and is_atom(kind) do
{ tuple, [] }
end
defp cache_var(other) do
quote do
{ x, [x = unquote(other)] }
end
end
## Function generation
# Define __record__/1 and __record__/2 as reflection functions
# that returns the record names and fields.
# 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
@@ -544,15 +574,31 @@ defmodule Record do
#
# 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
@doc false
def __record__(kind, _), do: __record__(kind)
unquote(quoted)
@doc false
def __record__(:name), do: __MODULE__
def __record__(:fields), do: unquote(values)
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
@@ -592,42 +638,6 @@ defmodule Record do
@doc false
def new([]), do: { __MODULE__, unquote_splicing(defaults) }
def new(opts) when is_list(opts), do: { __MODULE__, unquote_splicing(selective) }
def new(tuple) when is_tuple(tuple), do: :erlang.setelement(1, tuple, __MODULE__)
end
end
# Define method to get index of a given key.
#
# Useful if you need to know position of the key for such applications as:
# - ets
# - mnesia
#
# For a declaration like:
#
# defrecord FileInfo, atime: nil, mtime: nil
#
# It will define following method:
#
# def __index__(:atime), do: 2
# def __index__(:mtime), do: 3
# def __index__(_), do: nil
#
defp indexes(values) do
quoted = lc { k, _ } inlist values do
index = find_index(values, k, 0)
quote do
@doc false
def __index__(unquote(k)), do: unquote(index + 1)
end
end
quote do
unquote(quoted)
@doc false
def __index__(_), do: nil
@doc false
def __index__(key, _), do: __index__(key)
end
end
@@ -769,12 +779,12 @@ defmodule Record do
end
@spec new :: t
@spec new(options | tuple) :: 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 __index__(atom) :: non_neg_integer | nil
@spec __record__(:index, atom) :: non_neg_integer | nil
end
end
@@ -812,16 +822,35 @@ defmodule Record do
defp convert_value(atom) when is_atom(atom), do: { atom, nil }
defp convert_value({ atom, other }) when is_atom(atom) and is_function(other), do:
raise ArgumentError, message: "record field default value #{inspect atom} cannot be a function"
defp convert_value({ atom, other }) when is_atom(atom) and (is_reference(other) or is_pid(other) or is_port(other)), do:
raise ArgumentError, message: "record field default value #{inspect atom} cannot be a reference, pid or port"
defp convert_value({ atom, _ } = tuple) when is_atom(atom), do: tuple
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}"
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
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)
+28 -16
View File
@@ -3,12 +3,12 @@ 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: string) do
file = to_char_list(string)
def retrieve(name, from: file) when is_binary(file) do
file = String.to_char_list!(file)
case :code.where_is_file(file) do
:non_existing -> realfile = file
realfile -> nil
realfile -> :ok
end
retrieve_record(name, realfile)
@@ -16,12 +16,12 @@ defmodule Record.Extractor do
# Retrieve a record definition from an Erlang file using
# the same lookup as the *include_lib* attribute from Erlang modules.
def retrieve(name, from_lib: file) do
[app|path] = :filename.split(to_char_list(file))
def retrieve(name, from_lib: file) when is_binary(file) do
[app|path] = :filename.split(String.to_char_list!(file))
case :code.lib_dir(to_char_list(app)) do
case :code.lib_dir(app) do
{ :error, _ } ->
raise ArgumentError, "lib file #{to_binary(file)} could not be found"
raise ArgumentError, message: "lib file #{file} could not be found"
libpath ->
retrieve_record name, :filename.join([libpath|path])
end
@@ -29,17 +29,18 @@ defmodule Record.Extractor do
# Retrieve the record with the given name from the given file
defp retrieve_record(name, file) do
records = retrieve_from_file(file)
form = read_file(file)
records = retrieve_records(form)
if record = List.keyfind(records, name, 0) do
parse_record(record)
parse_record(record, form)
else
raise ArgumentError, "no record #{name} found at #{to_binary(file)}"
raise ArgumentError, message: "no record #{name} found at #{file}"
end
end
# Parse the given file and retrieve all existent records.
defp retrieve_from_file(file) do
lc { :attribute, _, :record, record } inlist read_file(file), do: record
defp retrieve_records(form) do
lc { :attribute, _, :record, record } inlist form, do: record
end
# Read a file and return its abstract syntax form that also
@@ -50,19 +51,18 @@ defmodule Record.Extractor do
{ :ok, form } ->
form
other ->
raise "error parsing file #{to_binary(file)}, got: #{inspect(other)}"
raise "error parsing file #{file}, got: #{inspect(other)}"
end
end
# Parse a tuple with name and fields and returns a
# list of tuples where the first element is the field
# and the second is its default value.
defp parse_record({ _name, fields }) do
defp parse_record({ _name, fields }, form) do
cons = List.foldr fields, { nil, 0 }, fn f, acc ->
{ :cons, 0, parse_field(f), acc }
end
{ :value, list, _ } = :erl_eval.expr(cons, [])
list
eval_record(cons, form)
end
defp parse_field({ :typed_record_field, record_field, _type }) do
@@ -76,4 +76,16 @@ defmodule Record.Extractor do
defp parse_field({ :record_field, _, key, value }) do
{ :tuple, 0, [key, value] }
end
defp eval_record(cons, form) do
form = form ++
[ { :function, 0, :hello, 0, [
{ :clause, 0, [], [], [ cons ] } ] } ]
{ :function, 0, :hello, 0, [
{ :clause, 0, [], [], [ record_ast ] } ] } = :erl_expand_records.module(form, []) |> List.last
{ :value, record, _ } = :erl_eval.expr(record_ast, [])
record
end
end
+110 -73
View File
@@ -1,8 +1,8 @@
defmodule Regex do
@moduledoc %B"""
@moduledoc %S"""
Regular expressions for Elixir built on top of the re module
in the Erlang Standard Library. More information can be found
on re documentation: http://www.erlang.org/doc/man/re.html
in the re documentation: http://www.erlang.org/doc/man/re.html
Regular expressions in Elixir can be created using Regex.compile!
or using the special form with `%r`:
@@ -10,32 +10,32 @@ defmodule Regex do
# A simple regular expressions that matches foo anywhere in the string
%r/foo/
# A regular expression with case insensitive options and handle unicode chars
# A regular expression with case insensitive options and handling for unicode chars
%r/foo/iu
The re module provides several options, the one available in Elixir, followed by
The re module provides several options, the ones available in Elixir, followed by
their shortcut in parenthesis, are:
* unicode (u) - enable unicode specific patterns like \p
* caseless (i) - add case insensitivity
* dotall (s) - causes dot to match newlines and also set newline to anycrlf.
The new line setting can be overwritten by setting `(*CR)` or `(*LF)` or
* `unicode` (u) - enable unicode specific patterns like \p
* `caseless` (i) - add case insensitivity
* `dotall` (s) - causes dot to match newlines and also set newline to anycrlf.
The new line setting can be overridden by setting `(*CR)` or `(*LF)` or
`(*CRLF)` or `(*ANY)` according to re documentation
* multiline (m) - causes `^` and `$` to mark the beginning and end of each line.
You need to use `\A` and `\z` to match the end or beginning of the string
* extended (x) - whitespace characters are ignored except when escaped and
* `multiline` (m) - causes `^` and `$` to mark the beginning and end of each line.
Use `\A` and `\z` to match the end or beginning of the string
* `extended` (x) - whitespace characters are ignored except when escaped and
allow `#` to delimit comments
* firstline (f) - forces the unanchored pattern to match before or at the first
* `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) - invert the "greediness" of the regexp
* groups (g) - compile with info about groups available
* `ungreedy` (r) - invert the "greediness" of the regexp
* `groups` (g) - compile with info about groups available
The options not available are:
* anchored - not available, use `^` or `\A` instead
* dollar_endonly - not available, use `\z` instead
* no_auto_capture - not available, use `?:` instead
* newline - not available, use `(*CR)` or `(*LF)` or `(*CRLF)` or `(*ANYCRLF)`
* `anchored` - not available, use `^` or `\A` instead
* `dollar_endonly` - not available, use `\z` instead
* `no_auto_capture` - not available, use `?:` instead
* `newline` - not available, use `(*CR)` or `(*LF)` or `(*CRLF)` or `(*ANYCRLF)`
or `(*ANY)` at the beginning of the regexp according to the re documentation
Most of the functions in this module accept either a binary or a char list
@@ -43,26 +43,45 @@ defmodule Regex do
a binary, a char list will return a char list).
"""
defrecordp :regex, [:re_pattern, :source, :options, :groups]
@type t :: { Regex, term, term, term, term }
defrecordp :regex, Regex, [:re_pattern, :source, :options, :groups]
@type t :: { Regex, term, binary, binary, [atom] }
defexception CompileError, message: "regex could not be compiled"
@doc """
Compiles the regular expression according to the given options.
Compiles the regular expression.
The given options can either be a binary with the characters
representing the same regex options given to the `%r` sigil,
or a list of options, as expected by the Erlang `re` docs.
It returns `{ :ok, regex }` in case of success,
`{ :error, reason }` otherwise.
"""
def compile(source, options // "") when is_binary(source) do
options = to_binary(options)
opts = translate_options(options)
@spec compile(binary, binary | [term]) :: t
def compile(source, options // "")
def compile(source, options) when is_binary(options) do
case translate_options(options) do
{ :error, rest } ->
{ :error, { :invalid_option, rest } }
translated_options ->
compile(source, translated_options, options)
end
end
def compile(source, options) when is_list(options) do
compile(source, options, "")
end
defp compile(source, opts, doc_opts) when is_binary(source) do
re_opts = opts -- [:groups]
groups = if opts != re_opts, do: parse_groups(source)
case :re.compile(source, re_opts) do
{ :ok, re_pattern } ->
{ :ok, regex(re_pattern: re_pattern, source: source, options: options, groups: groups) }
{ :ok, regex(re_pattern: re_pattern, source: source, options: doc_opts, groups: groups) }
error ->
error
end
@@ -80,7 +99,7 @@ defmodule Regex do
end
@doc """
Returns a boolean if there was a match or not.
Returns a boolean indicating whether there was a match or not.
## Examples
@@ -95,9 +114,11 @@ defmodule Regex do
end
@doc """
Runs the regular expression against the given string.
It returns a list with all matches, nil if no match ocurred, or []
if it matched, /g was specified, but nothing was captured.
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.
## Examples
@@ -128,26 +149,23 @@ defmodule Regex do
end
@doc """
Returns the given captures as a list of tuples.
Requires the regex to be compiled with the groups option.
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.
## Examples
iex> Regex.captures(%r/c(?<foo>d)/g, "abcd")
[foo: "d"]
iex> Regex.captures(%r/a(?<foo>b)c(?<bar>d)/g, "abcd")
[foo: "b", bar: "d"]
iex> Regex.captures(%r/a(?<foo>b)c(?<bar>d)/g, "efgh")
nil
"""
def captures(regex(groups: groups) = regex, string, options // []) do
unless captures = Keyword.get(options, :capture) do
captures = if groups do
Enum.sort(groups)
else
raise ArgumentError, message: "regex was not compiled with g"
end
options = Keyword.put(options, :capture, captures)
end
options = Keyword.put_new(options, :capture, :groups)
results = run(regex, string, options)
if results, do: Enum.zip captures, results
if results, do: Enum.zip(groups, results)
end
@doc """
@@ -158,7 +176,7 @@ defmodule Regex do
end
@doc """
Returns the regex source as binary.
Returns the regex source as a binary.
## Examples
@@ -184,11 +202,11 @@ defmodule Regex do
end
@doc """
Returns list of named groups in regex.
Returns a list of named groups in the regex.
## Examples
iex> Regex.groups(%r/(?<foo>foo)/g)
iex> Regex.groups(%r/(?<foo>bar)/g)
[:foo]
"""
@@ -197,35 +215,56 @@ defmodule Regex do
end
@doc """
Same as run, but scans the target several times collecting all matches of
the regular expression. A list is returned with each match. If the item in
the list is a binary, it means there were no captures. If the item is another
list, each element in this secondary list is a capture.
Same as `run/3`, but scans the target several times collecting all
matches of the regular expression. A list of lists is returned,
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.
When the option `:capture` is set to `:groups`, it will capture all
the groups in the regex.
## Examples
iex> Regex.scan(%r/c(d|e)/, "abcd abce")
[["d"], ["e"]]
[["cd", "d"], ["ce", "e"]]
iex> Regex.scan(%r/c(?:d|e)/, "abcd abce")
["cd", "ce"]
[["cd"], ["ce"]]
iex> Regex.scan(%r/e/, "abcd")
[]
"""
def scan(regex, string, options // [])
def scan(regex(re_pattern: compiled), string, options) do
def scan(regex(re_pattern: compiled, groups: groups), string, options) do
return = Keyword.get(options, :return, return_for(string))
options = [{ :capture, :all, return }, :global]
captures =
case Keyword.get(options, :capture, :all) do
:groups -> groups || raise ArgumentError, message: "regex was not compiled with g"
others -> others
end
options = [{ :capture, captures, return }, :global]
case :re.run(string, compiled, options) do
:nomatch -> []
{ :match, results } -> flatten_result(results)
{ :match, results } -> results
end
end
@doc """
Split the given target in the number of parts specified.
If no ammount of parts is given, it defaults to :infinity.
Split the given target into the number of parts specified.
If no number of parts is given, it defaults to `:infinity`.
## 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"]
"""
def split(regex, string, options // [])
@@ -240,16 +279,22 @@ defmodule Regex do
return = Keyword.get(options, :return, return_for(string))
opts = [return: return, parts: parts]
:re.split(string, compiled, opts)
splits = :re.split(string, compiled, opts)
if Keyword.get(options, :trim, false) do
lc split inlist splits, split != "", do: split
else
splits
end
end
@doc %B"""
Receives a regex, a binary and a replacement and returns a new
@doc %S"""
Receives a regex, a binary and a replacement, returns a new
binary where the all matches are replaced by replacement.
Inside the replacement, you can either give "&" to access the
whole regular expression or \N, where N is in integer to access
a specific matching parens. You can also set global to false
Inside the replacement, you can either give `&` to access the
whole regular expression or `\N`, where `N` is in integer to access
a specific matching parens. You can also set `:global` to `false`
if you want to replace just the first occurrence.
## Examples
@@ -273,10 +318,10 @@ defmodule Regex do
:re.replace(string, compiled, replacement, opts)
end
{ :ok, pattern } = :re.compile(%B"[.^$*+?()[{\\\|\s#]", [:unicode])
{ :ok, pattern } = :re.compile(%S"[.^$*+?()[{\\\|\s#]", [:unicode])
@escape_pattern pattern
@doc %B"""
@doc %S"""
Escapes a string to be literally matched in a regex.
## Examples
@@ -295,7 +340,7 @@ defmodule Regex do
# Helpers
@doc false
# Unescape map function used by Macro.unescape_binary.
# Unescape map function used by Macro.unescape_string.
def unescape_map(?f), do: ?\f
def unescape_map(?n), do: ?\n
def unescape_map(?r), do: ?\r
@@ -318,17 +363,9 @@ defmodule Regex do
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 }
defp flatten_result(results) do
lc result inlist results do
case result do
[t] -> t
[_|t] -> t
end
end
end
{ :ok, pattern } = :re.compile(%B"\(\?<(?<G>[^>]*)>")
{ :ok, pattern } = :re.compile(%S"\(\?<(?<G>[^>]*)>")
@groups_pattern pattern
defp parse_groups(source) do
+233
View File
@@ -0,0 +1,233 @@
defmodule Set do
@moduledoc %S"""
This module specifies the Set API expected to be
implemented by different representations.
It also provides functions that redirect to the
underlying Set, allowing a developer to work with
different Set implementations using one API.
To create a new set, use the `new` functions defined
by each set type:
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.
"""
use Behaviour
@type value :: any
@type values :: [ value ]
@type t :: tuple
defcallback delete(t, value) :: t
defcallback difference(t, t) :: t
defcallback disjoint?(t, t) :: boolean
defcallback empty(t) :: t
defcallback equal?(t, t) :: boolean
defcallback intersection(t, t) :: t
defcallback member?(t, value) :: boolean
defcallback put(t, value) :: t
defcallback size(t) :: non_neg_integer
defcallback subset?(t, t) :: boolean
defcallback to_list(t) :: list()
defcallback union(t, t) :: t
defmacrop target(set) do
quote do
if is_tuple(unquote(set)) do
elem(unquote(set), 0)
else
unsupported_set(unquote(set))
end
end
end
@doc """
Deletes `value` from `set`.
## Examples
iex> s = HashSet.new([1, 2, 3])
...> Set.delete(s, 4) |> HashSet.to_list
[1, 2, 3]
iex> s = HashSet.new([1, 2, 3])
...> Set.delete(s, 2) |> HashSet.to_list
[1, 3]
"""
@spec delete(t, value) :: t
def delete(set, value) do
target(set).delete(set, value)
end
@doc """
Returns a set that is `set1` without the members of `set2`.
## Examples
iex> Set.difference(HashSet.new([1,2]), HashSet.new([2,3,4])) |> HashSet.to_list
[1]
"""
@spec difference(t, t) :: t
def difference(set1, set2) do
target(set1).difference(set1, set2)
end
@doc """
Checks if `set1` and `set2` have no members in common.
## Examples
iex> Set.disjoint?(HashSet.new([1, 2]), HashSet.new([3, 4]))
true
iex> Set.disjoint?(HashSet.new([1, 2]), HashSet.new([2, 3]))
false
"""
@spec disjoint?(t, t) :: boolean
def disjoint?(set1, set2) do
target(set1).disjoint?(set1, set2)
end
@doc """
Returns an empty set of the same type as `set`.
"""
@spec empty(t) :: t
def empty(set) do
target(set).empty(set)
end
@doc """
Checks if `set1` and `set2` are equal.
## Examples
iex> Set.equal?(HashSet.new([1, 2]), HashSet.new([2, 1, 1]))
true
iex> Set.equal?(HashSet.new([1, 2]), HashSet.new([3, 4]))
false
"""
@spec equal?(t, t) :: boolean
def equal?(set1, set2) do
target(set1).equal?(set1, set2)
end
@doc """
Returns a set containing only members in common between `set1` and `set2`.
## Examples
iex> Set.intersection(HashSet.new([1,2]), HashSet.new([2,3,4])) |> HashSet.to_list
[2]
iex> Set.intersection(HashSet.new([1,2]), HashSet.new([3,4])) |> HashSet.to_list
[]
"""
@spec intersection(t, t) :: t
def intersection(set1, set2) do
target(set1).intersection(set1, set2)
end
@doc """
Checks if `set` contains `value`.
## Examples
iex> Set.member?(HashSet.new([1, 2, 3]), 2)
true
iex> Set.member?(HashSet.new([1, 2, 3]), 4)
false
"""
@spec member?(t, value) :: boolean
def member?(set, value) do
target(set).member?(set, value)
end
@doc """
Inserts `value` into `set` if it does not already contain it.
## Examples
iex> Set.put(HashSet.new([1, 2, 3]), 3) |> Set.to_list
[1, 2, 3]
iex> Set.put(HashSet.new([1, 2, 3]), 4) |> Set.to_list
[1, 2, 3, 4]
"""
@spec put(t, value) :: t
def put(set, value) do
target(set).put(set, value)
end
@doc """
Returns the number of elements in `set`.
## Examples
iex> Set.size(HashSet.new([1, 2, 3]))
3
"""
@spec size(t) :: non_neg_integer
def size(set) do
target(set).size(set)
end
@doc """
Checks if `set1`'s members are all contained in `set2`.
## Examples
iex> Set.subset?(HashSet.new([1, 2]), HashSet.new([1, 2, 3]))
true
iex> Set.subset?(HashSet.new([1, 2, 3]), HashSet.new([1, 2]))
false
"""
@spec subset?(t, t) :: boolean
def subset?(set1, set2) do
target(set1).subset?(set1, set2)
end
@doc """
Converts `set` to a list.
## Examples
iex> HashSet.to_list(HashSet.new([1, 2, 3]))
[1,2,3]
"""
@spec to_list(t) :: list
def to_list(set) do
target(set).to_list(set)
end
@doc """
Returns a set containing all members of `set1` and `set2`.
## Examples
iex> Set.union(HashSet.new([1,2]), HashSet.new([2,3,4])) |> HashSet.to_list
[1,2,3,4]
"""
@spec union(t, t) :: t
def union(set1, set2) do
target(set1).union(set1, set2)
end
defp unsupported_set(set) do
raise ArgumentError, message: "unsupported set: #{inspect set}"
end
end
+452
View File
@@ -0,0 +1,452 @@
defmodule Stream do
@moduledoc """
Module for creating and composing streams.
Streams are composable, lazy enumerables. Any enumerable that generates
items one by one during enumeration is called a stream. For example,
Elixir's `Range` is a stream:
iex> range = 1..5
1..5
iex> Enum.map range, &1 * 2
[2,4,6,8,10]
In the example above, as we mapped over the range, the elements being
enumerated were created one by one, during enumeration. The `Stream`
module allows us to map the range, without triggering its enumeration:
iex> range = 1..3
iex> stream = Stream.map(range, &1 * 2)
iex> Enum.map(stream, &1 + 1)
[3,5,7]
Notice we started with a range and then we created a stream that is
meant to multiply each item in the range by 2. At this point, no
computation was done yet. Just when `Enum.map/2` is called we
enumerate over each item in the range, multiplying it by 2 and adding 1.
We say the functions in `Stream` are *lazy* and the functions in `Enum`
are *eager*.
Due to their laziness, streams are useful when working with large
(or even infinite) collections. When chaining many operations with `Enum`,
intermediate lists are created, while `Stream` creates a recipe of
computations that are executed at a later moment. Let's see another
example:
1..3 |>
Enum.map(IO.inspect(&1)) |>
Enum.map(&1 * 2) |>
Enum.map(IO.inspect(&1))
1
2
3
2
4
6
#=> [2,4,6]
Notice that we first printed each item in the list, then multiplied each
element by 2 and finally printed each new value. In this example, the list
was iterated three times. Let's see an example with streams:
stream = 1..3 |>
Stream.map(IO.inspect(&1)) |>
Stream.map(&1 * 2) |>
Stream.map(IO.inspect(&1))
Enum.to_list(stream)
1
2
2
4
3
6
#=> [2,4,6]
Although the end result is the same, the order in which the items were
printed changed! With streams, we print the first item and then print
its double. In this example, the list was iterated just once!
That's what we meant when we first said that streams are composable,
lazy enumerables. Notice we could call `Stream.map/2` multiple times,
effectively composing the streams and they are lazy. The computations
are performed only when you call a function from the `Enum` module.
## Creating Streams
There are many functions in Elixir's standard library that return
streams, some examples are:
* `IO.stream/1` - Streams input lines, one by one;
* `URI.query_decoder/1` - Decodes a query string, pair by pair;
This module also allows us to create streams from any enumerable:
iex> stream = Stream.map([1,2,3], &1 * 2)
iex> Enum.map(stream, &1 + 1)
[3,5,7]
By simply passing a list (which is an enumerable) as the first argument
to `Stream.map/2`, we have automatically created a stream that will
multiply the items in the list by 2 on enumeration.
This module also provides other functions for creating streams, such as
`Stream.cycle/1`.
"""
defrecord Lazy, [:enumerable, :fun, :acc]
defimpl Enumerable, for: Lazy do
def reduce(Lazy[] = lazy, acc, fun) do
do_reduce(lazy, acc, fun, 0)
end
def count(Lazy[] = lazy) do
do_reduce(lazy, 0, fn _, acc -> acc + 1 end, 0)
end
def member?(Lazy[] = lazy, value) do
do_reduce(lazy, false, fn(entry, _) ->
if entry === value, do: throw({ :stream_lazy, 0, true }), else: false
end, 0)
end
defp do_reduce(Lazy[enumerable: enumerable, fun: f1, acc: nil], acc, fun, nesting) do
do_reduce(enumerable, acc, f1.(fun), nesting)
end
defp do_reduce(Lazy[enumerable: enumerable, fun: f1, acc: side], acc, fun, nesting) do
do_reduce(enumerable, { acc, side }, f1.(fun, nesting), nesting + 1)
end
defp do_reduce(enumerable, acc, fun, nesting) do
Enumerable.reduce(enumerable, acc, fun) |> remove_nesting(nesting)
catch
{ :stream_lazy, nesting, res } -> remove_nesting(res, nesting)
end
defp remove_nesting(acc, 0), do: acc
defp remove_nesting(acc, nesting), do: remove_nesting(elem(acc, 0), nesting - 1)
end
@type t :: Lazy.t | (acc, (element, acc -> acc) -> acc)
@type acc :: any
@type element :: any
@type index :: non_neg_integer
@type default :: any
@doc """
Creates a stream that enumerates each enumerable in an enumerable.
## Examples
iex> stream = Stream.concat([1..3, 4..6, 7..9])
iex> Enum.to_list(stream)
[1,2,3,4,5,6,7,8,9]
"""
@spec concat(Enumerable.t) :: t
def concat(enumerables) do
&do_concat(enumerables, &1, &2)
end
@doc """
Creates a stream that enumerates the first argument, followed by the second.
## Examples
iex> stream = Stream.concat(1..3, 4..6)
iex> Enum.to_list(stream)
[1,2,3,4,5,6]
iex> stream1 = Stream.cycle([1, 2, 3])
iex> stream2 = Stream.cycle([4, 5, 6])
iex> stream = Stream.concat(stream1, stream2)
iex> Enum.take(stream, 6)
[1,2,3,1,2,3]
"""
@spec concat(Enumerable.t, Enumerable.t) :: t
def concat(first, second) do
&do_concat([first, second], &1, &2)
end
defp do_concat(enumerables, acc, fun) do
Enumerable.reduce(enumerables, acc, &Enumerable.reduce(&1, &2, fun))
end
@doc """
Creates a stream that cycles through the given enumerable,
infinitely.
## Examples
iex> stream = Stream.cycle([1,2,3])
iex> Enum.take(stream, 5)
[1,2,3,1,2]
"""
@spec cycle(Enumerable.t) :: t
def cycle(enumerable) do
do_cycle(enumerable, &1, &2)
end
defp do_cycle(enumerable, acc, fun) do
acc = Enumerable.reduce(enumerable, acc, fun)
do_cycle(enumerable, acc, fun)
end
@doc """
Lazily drops the next `n` items from the enumerable.
## Examples
iex> stream = Stream.drop(1..10, 5)
iex> Enum.to_list(stream)
[6,7,8,9,10]
"""
@spec drop(Enumerable.t, non_neg_integer) :: t
def drop(enumerable, n) when n >= 0 do
Lazy[enumerable: enumerable,
fun: fn(f1, _) ->
fn
_entry, { acc, n } when n > 0 ->
{ acc, n - 1 }
entry, { acc, n } ->
{ f1.(entry, acc), n }
end
end,
acc: n]
end
@doc """
Lazily drops elements of the enumerable while the given
function returns true.
## Examples
iex> stream = Stream.drop_while(1..10, &1 <= 5)
iex> Enum.to_list(stream)
[6,7,8,9,10]
"""
@spec drop_while(Enumerable.t, (element -> as_boolean(term))) :: t
def drop_while(enumerable, f) do
Lazy[enumerable: enumerable,
fun: fn(f1, _) ->
fn
entry, { acc, true } ->
if f.(entry), do: { acc, true }, else: { f1.(entry, acc), false }
entry, { acc, false } ->
{ f1.(entry, acc), false }
end
end,
acc: true]
end
@doc """
Creates a stream that will filter elements according to
the given function on enumeration.
## Examples
iex> stream = Stream.filter([1, 2, 3], fn(x) -> rem(x, 2) == 0 end)
iex> Enum.to_list(stream)
[2]
"""
@spec filter(Enumerable.t, (element -> as_boolean(term))) :: t
def filter(enumerable, f) do
Lazy[enumerable: enumerable,
fun: fn(f1) ->
fn(entry, acc) ->
if f.(entry), do: f1.(entry, acc), else: acc
end
end]
end
@doc """
Emit a sequence of values, starting with `start_value`. Successive
values are generated by calling `next_fun` on the previous value.
## Examples
iex> Stream.iterate(0, &1+1) |> Enum.take(5)
[0,1,2,3,4]
"""
@spec iterate(element, (element -> element)) :: t
def iterate(start_value, next_fun) do
fn acc, fun ->
do_iterate(start_value, next_fun, fun.(start_value, acc), fun)
end
end
defp do_iterate(value, next_fun, acc, fun) do
next = next_fun.(value)
do_iterate(next, next_fun, fun.(next, acc), fun)
end
@doc """
Creates a stream that will apply the given function on
enumeration.
## Examples
iex> stream = Stream.map([1, 2, 3], fn(x) -> x * 2 end)
iex> Enum.to_list(stream)
[2,4,6]
"""
@spec map(Enumerable.t, (element -> any)) :: t
def map(enumerable, f) do
Lazy[enumerable: enumerable,
fun: fn(f1) ->
fn(entry, acc) ->
f1.(f.(entry), acc)
end
end]
end
@doc """
Creates a stream that will apply the given function on enumeration and
flatten the result.
## Examples
iex> stream = Stream.flat_map([1, 2, 3], fn(x) -> [x, x * 2] end)
iex> Enum.to_list(stream)
[1, 2, 2, 4, 3, 6]
"""
@spec flat_map(Enumerable.t, (element -> any)) :: t
def flat_map(enumerable, f) do
Lazy[enumerable: enumerable,
fun: fn(f1) ->
fn(entry, acc) ->
Enumerable.reduce(f.(entry), acc, f1)
end
end]
end
@doc """
Creates a stream that will reject elements according to
the given function on enumeration.
## Examples
iex> stream = Stream.reject([1, 2, 3], fn(x) -> rem(x, 2) == 0 end)
iex> Enum.to_list(stream)
[1,3]
"""
@spec reject(Enumerable.t, (element -> as_boolean(term))) :: t
def reject(enumerable, f) do
Lazy[enumerable: enumerable,
fun: fn(f1) ->
fn(entry, acc) ->
unless f.(entry), do: f1.(entry, acc), else: acc
end
end]
end
@doc """
Returns a stream generated by calling `generator_fun` repeatedly.
## Examples
iex> Stream.repeatedly(&:random.uniform/0) |> Enum.take(3)
[0.4435846174457203, 0.7230402056221108, 0.94581636451987]
"""
@spec repeatedly((() -> element)) :: t
def repeatedly(generator_fun)
when is_function(generator_fun, 0) do
do_repeatedly(generator_fun, &1, &2)
end
defp do_repeatedly(generator_fun, acc, fun) do
do_repeatedly(generator_fun, fun.(generator_fun.(), acc), fun)
end
@doc """
Lazily takes the next `n` items from the enumerable and stops
enumeration.
## Examples
iex> stream = Stream.take(1..100, 5)
iex> Enum.to_list(stream)
[1,2,3,4,5]
iex> stream = Stream.cycle([1, 2, 3]) |> Stream.take(5)
iex> Enum.to_list(stream)
[1,2,3,1,2]
"""
@spec take(Enumerable.t, non_neg_integer) :: t
def take(_enumerable, 0), do: Lazy[enumerable: [], fun: & &1]
def take(enumerable, n) when n > 0 do
Lazy[enumerable: enumerable,
fun: fn(f1, nesting) ->
fn(entry, { acc, n }) ->
res = f1.(entry, acc)
if n > 1, do: { res, n-1 }, else: throw { :stream_lazy, nesting, res }
end
end,
acc: n]
end
@doc """
Lazily takes elements of the enumerable while the given
function returns true.
## Examples
iex> stream = Stream.take_while(1..100, &1 <= 5)
iex> Enum.to_list(stream)
[1,2,3,4,5]
"""
@spec take_while(Enumerable.t, (element -> as_boolean(term))) :: t
def take_while(enumerable, f) do
Lazy[enumerable: enumerable,
fun: fn(f1, nesting) ->
fn(entry, { acc, true }) ->
if f.(entry) do
{ f1.(entry, acc), true }
else
throw { :stream_lazy, nesting, acc }
end
end
end,
acc: true]
end
@doc """
Creates a stream where each item in the enumerable will
be accompanied by its index.
## Examples
iex> stream = Stream.with_index([1, 2, 3])
iex> Enum.to_list(stream)
[{1,0},{2,1},{3,2}]
"""
@spec with_index(Enumerable.t) :: t
def with_index(enumerable) do
Lazy[enumerable: enumerable,
fun: fn(f1, _) ->
fn(entry, { acc, counter }) ->
acc = f1.({ entry, counter }, acc)
{ acc, counter + 1 }
end
end,
acc: 0]
end
end
+321 -65
View File
@@ -1,27 +1,27 @@
defmodule String do
@moduledoc %B"""
@moduledoc %S"""
A String in Elixir is a UTF-8 encoded binary.
## String and binary operations
The functions in this module act according to the
Unicode Standard, version 6.2.0. For example,
`titlecase`, `downcase`, `strip` are provided by this
`capitalize/1`, `downcase/1`, `strip/1` are provided by this
module.
Besides this module, Elixir provides more low-level
operations that works directly with binaries. Some
In addition to this module, Elixir provides more low-level
operations that work directly with binaries. Some
of those can be found in the `Kernel` module, as:
* `binary_part/2` and `binary_part/3` - retrieves part of the binary
* `bit_size/1` and `byte_size/1` - size related functions
* `is_bitstring/1` and `is_binary/1` - type checking function
* Plus a bunch of conversion functions, like `binary_to_atom/2`,
`binary_to_integer/2`, `binary_to_term/1` and their opposite
like `integer_to_binary/2`
* `Kernel.binary_part/3` - retrieves part of the binary
* `Kernel.bit_size/1` and `Kernel.byte_size/1` - size related functions
* `Kernel.is_bitstring/1` and `Kernel.is_binary/1` - type checking function
* Plus a number of conversion functions, like `Kernel.binary_to_atom/2`,
`Kernel.binary_to_integer/2`, `Kernel.binary_to_term/1` and their inverses,
like `Kernel.integer_to_binary/2`
Finally, [the `:binary` module](http://erlang.org/doc/man/binary.html)
provides a couple other functions that works on the byte level.
Finally, the [`:binary` module](http://erlang.org/doc/man/binary.html)
provides a few other functions that work on the byte level.
## Codepoints and graphemes
@@ -93,7 +93,7 @@ defmodule String do
codepoint needs to be rejected.
This module relies on this behaviour to ignore such invalid
characters. For example, `String.length` is going to return
characters. For example, `length/1` is going to return
a correct result even if an invalid codepoint is fed into it.
In other words, this module expects invalid data to be detected
@@ -108,7 +108,7 @@ defmodule String do
@doc """
Checks if a string is printable considering it is encoded
as UTF-8. Returns true if so, false otherwise.
as UTF-8. Returns `true` if so, `false` otherwise.
## Examples
@@ -139,7 +139,7 @@ defmodule String do
def printable?(_), do: false
@doc """
Splits a string on sub strings at each Unicode whitespace
Splits a string on substrings at each Unicode whitespace
occurrence with leading and trailing whitespace ignored.
## Examples
@@ -156,12 +156,15 @@ defmodule String do
defdelegate split(binary), to: String.Unicode
@doc """
Divides a string into sub strings based on a pattern,
returning a list of these sub string. The pattern can
Divides a string into substrings based on a pattern,
returning a list of these substrings. The pattern can
be a string, a list of strings or a regular expression.
The string is split into as many parts as possible by
default, unless the `global` option is set to false.
default, unless the `global` option is set to `false`.
Empty strings are only removed from the result if the
`trim` option is set to `true`.
## Examples
@@ -169,6 +172,8 @@ defmodule String do
["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"]
iex> String.split("1,2 3,4", [" ", ","])
["1", "2", "3", "4"]
@@ -185,17 +190,25 @@ defmodule String do
@spec split(t, t | [t] | Regex.t, Keyword.t) :: [t]
def split(binary, pattern, options // [])
def split("", _pattern, _options), do: [""]
def split(binary, pattern, options) when is_regex(pattern) do
Regex.split(pattern, binary, global: options[:global])
Regex.split(pattern, binary, options)
end
def split(binary, pattern, options) do
opts = if options[:global] != false, do: [:global], else: []
:binary.split(binary, pattern, opts)
splits = :binary.split(binary, pattern, opts)
if Keyword.get(options, :trim, false) do
lc split inlist splits, split != "", do: split
else
splits
end
end
@doc """
Convert all characters on the given string to upcase.
Convert all characters on the given string to uppercase.
## Examples
@@ -211,7 +224,7 @@ defmodule String do
defdelegate upcase(binary), to: String.Unicode
@doc """
Convert all characters on the given string to downcase.
Convert all characters on the given string to lowercase.
## Examples
@@ -228,11 +241,11 @@ defmodule String do
@doc """
Converts the first character in the given string to
titlecase and the remaining to downcase.
uppercase and the remaining to lowercase.
This relies on the titlecase information provided
by the Unicode Standard. Note this function makes
no attempt in capitalizing all words in the string
no attempt to capitalize all words in the string
(usually known as titlecase).
## Examples
@@ -280,7 +293,7 @@ defmodule String do
# Do a quick check before we traverse the whole
# binary. :binary.last is a fast operation (it
# does not traverse the whole binary).
def rstrip(string, char) do
def rstrip(string, char) when char in 0..127 do
if :binary.last(string) == char do
do_rstrip(string, "", char)
else
@@ -288,12 +301,16 @@ defmodule String do
end
end
defp do_rstrip(<<char, string :: binary>>, buffer, char) do
do_rstrip(string, <<char, buffer :: binary>>, char)
def rstrip(string, char) do
do_rstrip(string, "", char)
end
defp do_rstrip(<<char, string :: binary>>, buffer, another_char) do
<<buffer :: binary, char, do_rstrip(string, "", another_char) :: binary>>
defp do_rstrip(<<char :: utf8, string :: binary>>, buffer, char) do
<<do_rstrip(string, <<char :: utf8, buffer :: binary>>, char) :: binary>>
end
defp do_rstrip(<<char :: utf8, string :: binary>>, buffer, another_char) do
<<buffer :: binary, char :: utf8, do_rstrip(string, "", another_char) :: binary>>
end
defp do_rstrip(<<>>, _, _) do
@@ -324,7 +341,7 @@ defmodule String do
@spec lstrip(t, char) :: t
def lstrip(<<char, rest :: binary>>, char) do
def lstrip(<<char :: utf8, rest :: binary>>, char) do
<<lstrip(rest, char) :: binary>>
end
@@ -364,14 +381,81 @@ defmodule String do
rstrip(lstrip(string, char), char)
end
@doc """
Returns a new binary based on `subject` by replacing the parts
matching `pattern` for `replacement`. By default, it replaces
all entries, except if the `global` option is set to false.
@doc %S"""
Returns a new string of length `len` with `subject` right justified and
padded with `padding`. If `padding` is not present, it defaults to
whitespace. When `len` is less than the length of `subject`, `subject` is
returned.
If the replaced part must be used in `replacement`, then the
position or the positions where it is to be inserted must be
specified by using the option `insert_replaced`.
## Examples
iex> String.rjust("abc", 5)
" abc"
iex> String.rjust("abc", 5, ?-)
"--abc"
"""
@spec rjust(t, pos_integer) :: t
@spec rjust(t, pos_integer, char) :: t
def rjust(subject, len) do
rjust(subject, len, ?\s)
end
def rjust(subject, len, padding) when is_integer(padding) do
do_justify(subject, len, padding, :right)
end
@doc %S"""
Returns a new string of length `len` with `subject` left justified and padded
with `padding`. If `padding` is not present, it defaults to whitespace. When
`len` is less than the length of `subject`, `subject` is returned.
## Examples
iex> String.ljust("abc", 5)
"abc "
iex> String.ljust("abc", 5, ?-)
"abc--"
"""
@spec ljust(t, pos_integer) :: t
@spec ljust(t, pos_integer, char) :: t
def ljust(subject, len) do
ljust(subject, len, ?\s)
end
def ljust(subject, len, padding) when is_integer(padding) do
do_justify(subject, len, padding, :left)
end
defp do_justify(subject, 0, _padding, _type) do
subject
end
defp do_justify(subject, len, padding, type) when is_integer(padding) do
subject_len = String.length(subject)
cond do
subject_len >= len ->
subject
subject_len < len ->
fill = String.duplicate(<<padding :: utf8>>, len - subject_len)
case type do
:left -> subject <> fill
:right -> fill <> subject
end
end
end
@doc %S"""
Returns a new binary based on `subject` by replacing the parts
matching `pattern` by `replacement`. By default, it replaces
all entries, except if the `global` option is set to `false`.
A `pattern` may be a string or a regex.
## Examples
@@ -379,6 +463,19 @@ defmodule String do
"a-b-c"
iex> String.replace("a,b,c", ",", "-", global: false)
"a-b,c"
The pattern can also be a regex. In those cases, one can give `\N`
in the `replacement` string to access a specific catpure in the regex:
iex> String.replace("a,b,c", %r/,(.)/, ",\\1\\1")
"a,bb,cc"
Notice we had to escape the escape character `\`. By giving `&`,
one can inject the whole matched pattern in the replacement string.
When strings are used as a pattern, a developer can also use the
replaced part inside the `replacement` via the `:insert_replaced` option:
iex> String.replace("a,b,c", "b", "[]", insert_replaced: 1)
"a,[b],c"
iex> String.replace("a,b,c", ",", "[]", insert_replaced: 2)
@@ -390,7 +487,13 @@ defmodule String do
@spec replace(t, t, t) :: t
@spec replace(t, t, t, Keyword.t) :: t
def replace(subject, pattern, replacement, options // []) do
def replace(subject, pattern, replacement, options // [])
def replace(subject, pattern, replacement, options) when is_regex(pattern) do
Regex.replace(pattern, subject, replacement, global: options[:global])
end
def replace(subject, pattern, replacement, options) do
opts = translate_replace_options(options)
:binary.replace(subject, pattern, replacement, opts)
end
@@ -405,11 +508,37 @@ defmodule String do
opts
end
@doc """
Reverses the given string. Works on graphemes.
## Examples
iex> String.reverse("abcd")
"dcba"
iex> String.reverse("hello world")
"dlrow olleh"
iex> String.reverse("hello ∂og")
"go∂ olleh"
"""
@spec reverse(t) :: t
def reverse(string) do
do_reverse(String.Unicode.next_grapheme(string), [])
end
defp do_reverse({grapheme, rest}, acc) do
do_reverse(String.Unicode.next_grapheme(rest), [grapheme|acc])
end
defp do_reverse(:no_grapheme, acc), do: iolist_to_binary(acc)
@doc """
Returns a binary `subject` duplicated `n` times.
## Examples
iex> String.duplicate("abc", 0)
""
iex> String.duplicate("abc", 1)
"abc"
iex> String.duplicate("abc", 2)
@@ -417,7 +546,7 @@ defmodule String do
"""
@spec duplicate(t, pos_integer) :: t
def duplicate(subject, n) when is_integer(n) and n > 0 do
def duplicate(subject, n) when is_integer(n) and n >= 0 do
:binary.copy(subject, n)
end
@@ -444,7 +573,7 @@ defmodule String do
remaining of the string or `:no_codepoint` in case
the string reached its end.
As the other functions in the String module, this
As with other functions in the String module, this
function does not check for the validity of the codepoint.
That said, if an invalid codepoint is found, it will
be returned by this function.
@@ -458,7 +587,7 @@ defmodule String do
@spec next_codepoint(t) :: {codepoint, t} | :no_codepoint
defdelegate next_codepoint(string), to: String.Unicode
@doc %B"""
@doc %S"""
Checks whether `str` contains only valid characters.
## Examples
@@ -489,7 +618,7 @@ defmodule String do
def valid?(<<>>), do: true
def valid?(_), do: false
@doc %B"""
@doc %S"""
Checks whether `str` is a valid character.
All characters are codepoints, but some codepoints
@@ -513,7 +642,7 @@ defmodule String do
def valid_character?(<<_ :: utf8>> = codepoint), do: valid?(codepoint)
def valid_character?(_), do: false
@doc %B"""
@doc %S"""
Checks whether `str` is a valid codepoint.
Note that the empty string is considered invalid, as are
@@ -585,7 +714,7 @@ defmodule String do
@doc """
Returns the last grapheme from an utf8 string,
nil if the string is empty.
`nil` if the string is empty.
## Examples
@@ -673,7 +802,7 @@ defmodule String do
@doc """
Returns a substring starting at the offset given by the first, and
a length given by the second.
If the offset is greater than string length, than it returns nil.
If the offset is greater than string length, than it returns `nil`.
## Examples
@@ -697,11 +826,18 @@ defmodule String do
"""
@spec slice(t, integer, integer) :: grapheme | nil
def slice(string, start, len) when start >= 0 do
def slice(string, start, 0) do
case abs(start) <= String.length(string) do
true -> ""
false -> nil
end
end
def slice(string, start, len) when start >= 0 and len >= 0 do
do_slice(next_grapheme(string), start, start + len - 1, 0, "")
end
def slice(string, start, len) when start < 0 do
def slice(string, start, len) when start < 0 and len >= 0 do
real_start_pos = do_length(next_grapheme(string)) - abs(start)
case real_start_pos >= 0 do
true -> do_slice(next_grapheme(string), real_start_pos, real_start_pos + len - 1, 0, "")
@@ -738,8 +874,8 @@ defmodule String do
@doc """
Converts a string to an integer. If successful, returns a
tuple of form {integer, remainder of string}. If unsuccessful,
returns :error.
tuple of the form `{integer, remainder of string}`. If unsuccessful,
returns `:error`.
## Examples
@@ -754,18 +890,17 @@ defmodule String do
@spec to_integer(t) :: {integer, t} | :error
def to_integer(string) do
{result, remainder} = :string.to_integer(binary_to_list(string))
{result, remainder} = :string.to_integer(:binary.bin_to_list(string))
case result do
:error -> :error
_ -> {result, list_to_binary(remainder)}
_ -> {result, :binary.list_to_bin(remainder)}
end
end
@doc """
Converts a string to a float. If successful, returns a
tuple of form {float, remainder of string}. If unsuccessful,
returns :error. If given an integer value, will return
same as to_integer/1.
tuple of the form `{float, remainder of string}`.
If unsuccessful, returns `:error`.
## Examples
@@ -782,22 +917,22 @@ defmodule String do
@spec to_float(t) :: {integer, t} | :error
def to_float(string) do
charlist = binary_to_list(string)
charlist = :binary.bin_to_list(string)
{result, remainder} = :string.to_float(charlist)
case result do
:error ->
{int_result, int_remainder} = :string.to_integer(charlist)
case int_result do
:error -> :error
_ -> {float(int_result), list_to_binary(int_remainder)}
_ -> {:erlang.float(int_result), :binary.list_to_bin(int_remainder)}
end
_ -> {result, list_to_binary(remainder)}
_ -> {result, :binary.list_to_bin(remainder)}
end
end
@doc """
Returns true if `string` starts with any of the prefixes given, otherwise
false. `prefixes` can be either a single prefix or a list of prefixes.
Returns `true` if `string` starts with any of the prefixes given, otherwise
`false`. `prefixes` can be either a single prefix or a list of prefixes.
## Examples
@@ -819,7 +954,7 @@ defmodule String do
do_starts_with(string, prefix)
end
defp do_starts_with(_, "") do
defp do_starts_with(string, "") when is_binary(string) do
true
end
@@ -832,8 +967,8 @@ defmodule String do
end
@doc """
Returns true if `string` ends with any of the suffixes given, otherwise
false. `suffixes` can be either a single suffix or a list of suffixes.
Returns `true` if `string` ends with any of the suffixes given, otherwise
`false`. `suffixes` can be either a single suffix or a list of suffixes.
## Examples
@@ -855,7 +990,7 @@ defmodule String do
do_ends_with(string, suffix)
end
defp do_ends_with(_, "") do
defp do_ends_with(string, "") when is_binary(string) do
true
end
@@ -871,7 +1006,7 @@ defmodule String do
end
@doc """
Returns true if `string` contains match, otherwise false.
Returns `true` if `string` contains match, otherwise `false`.
`matches` can be either a single string or a list of strings.
## Examples
@@ -894,7 +1029,7 @@ defmodule String do
do_contains(string, match)
end
defp do_contains(_, "") do
defp do_contains(string, "") when is_binary(string) do
true
end
@@ -905,4 +1040,125 @@ defmodule String do
defp do_contains(_, _) do
raise ArgumentError
end
defexception UnicodeConversionError, encoded: nil, rest: nil, kind: nil do
def message(exception) do
"#{exception.kind} #{detail(exception.rest)}"
end
defp detail(rest) when is_binary(rest) do
"encoding starting at #{inspect rest}"
end
defp detail([h|_]) do
"code point #{h}"
end
end
@doc """
Converts a string into a char list converting each codepoint to its
respective integer value.
## Examples
iex> String.to_char_list("æß")
{ :ok, 'æß' }
iex> String.to_char_list("abc")
{ :ok, 'abc' }
"""
@spec to_char_list(String.t) :: { :ok, char_list } | { :error, list, binary } | { :incomplete, list, binary }
def to_char_list(string) when is_binary(string) do
case :unicode.characters_to_list(string) do
result when is_list(result) ->
{ :ok, result }
{ :error, _, _ } = error ->
error
{ :incomplete, _, _ } = incomplete ->
incomplete
end
end
@doc """
Converts a string into a char list converting each codepoint to its
respective integer value.
In case the conversion fails or is incomplete,
it raises a `String.UnicodeConversionError`.
## Examples
iex> String.to_char_list!("æß")
'æß'
iex> String.to_char_list!("abc")
'abc'
"""
@spec to_char_list!(String.t) :: char_list | no_return
def to_char_list!(string) when is_binary(string) do
case :unicode.characters_to_list(string) do
result when is_list(result) ->
result
{ :error, encoded, rest } ->
raise UnicodeConversionError, encoded: encoded, rest: rest, kind: :invalid
{ :incomplete, encoded, rest } ->
raise UnicodeConversionError, encoded: encoded, rest: rest, kind: :incomplete
end
end
@doc """
Converts a list of integer codepoints to a string.
## Examples
iex> String.from_char_list([0x00E6, 0x00DF])
{ :ok, "æß" }
iex> String.from_char_list([0x0061, 0x0062, 0x0063])
{ :ok, "abc" }
"""
@spec from_char_list(char_list) :: { :ok, String.t } | { :error, binary, binary } | { :incomplete, binary, binary }
def from_char_list(list) when is_list(list) do
case :unicode.characters_to_binary(list) do
result when is_binary(result) ->
{ :ok, result }
{ :error, _, _ } = error ->
error
{ :incomplete, _, _ } = incomplete ->
incomplete
end
end
@doc """
Converts a list of integer codepoints to a string.
In case the conversion fails, it raises a `String.UnicodeConversionError`.
## Examples
iex> String.from_char_list!([0x00E6, 0x00DF])
"æß"
iex> String.from_char_list!([0x0061, 0x0062, 0x0063])
"abc"
"""
@spec from_char_list!(char_list) :: String.t | no_return
def from_char_list!(list) when is_list(list) do
case :unicode.characters_to_binary(list) do
result when is_binary(result) ->
result
{ :error, encoded, rest } ->
raise UnicodeConversionError, encoded: encoded, rest: rest, kind: :invalid
{ :incomplete, encoded, rest } ->
raise UnicodeConversionError, encoded: encoded, rest: rest, kind: :incomplete
end
end
end
+81
View File
@@ -0,0 +1,81 @@
import Kernel, except: [to_string: 1]
defprotocol String.Chars do
@moduledoc %S"""
The String.Chars protocol is responsible for
converting a structure to a Binary (only if applicable).
The only function required to be implemented is
`to_string` which does the conversion.
The `to_string` function automatically imported
by Kernel invokes this protocol. String
interpolation also invokes to_string in its
arguments. For example, `"foo#{bar}"` is the same
as `"foo" <> to_string(bar)`.
"""
@only [BitString, List, Number, Atom, Record]
def to_string(thing)
end
defimpl String.Chars, for: Atom do
@doc """
Convert the atom literally to a binary, except
`nil` which is converted to an empty string.
"""
def to_string(nil) do
""
end
def to_string(atom) do
atom_to_binary(atom, :utf8)
end
end
defimpl String.Chars, for: BitString do
@doc """
Returns the given binary or raises an error for bitstrings.
"""
def to_string(thing) when is_binary(thing) do
thing
end
def to_string(thing) do
raise Protocol.UndefinedError,
protocol: @protocol,
value: thing,
description: "cannot convert a bitstring to a string"
end
end
defimpl String.Chars, for: List do
@doc """
Consider the list is an iolist and converts it
to a binary. This allows a list of binaries, or
a charlist, or a mix of both, to be converted
successfully.
## Examples
iex> to_string('foo')
"foo"
iex> to_string(["foo", 'bar'])
"foobar"
"""
def to_string(char_list), do: String.from_char_list!(char_list)
end
defimpl String.Chars, for: Number do
@doc """
Simply converts the number (integer or a float) to a binary.
"""
def to_string(thing) when is_integer(thing) do
integer_to_binary(thing)
end
def to_string(thing) do
iolist_to_binary(:io_lib_format.fwrite_g(thing))
end
end
+4 -3
View File
@@ -103,11 +103,11 @@ defmodule Supervisor.Behaviour do
Defaults to `:permanent`;
* `:shutdown` - defines how a child process should be terminated.
Defaults to `5000`;
Defaults to `5000` for a worker and `:infinity` for a supervisor;
* `:modules` - it should be a list with one element `[module]`,
where module is the name of the callback module only if the
child process is a supervisor, gen_server or gen_fsm. If the
child process is a supervisor, `gen_server` or `gen_fsm`. If the
child process is a gen_event, modules should be `:dynamic`.
Defaults to a list with the given module;
@@ -167,6 +167,7 @@ defmodule Supervisor.Behaviour do
#{@child_doc}
"""
def supervisor(module, args, options // []) do
options = Keyword.update(options, :shutdown, :infinity, fn(x) -> x end)
child(:supervisor, module, args, options)
end
@@ -180,4 +181,4 @@ defmodule Supervisor.Behaviour do
{ id, { module, function, args },
restart, shutdown, type, modules }
end
end
end
+78 -60
View File
@@ -53,7 +53,7 @@ defmodule System do
# Get the date at compilation time.
defmacrop get_date do
list_to_binary :httpd_util.rfc1123_date
iolist_to_binary :httpd_util.rfc1123_date
end
@doc """
@@ -79,12 +79,21 @@ defmodule System do
end
@doc """
Returns the current working directory or nil if one
Changes the list of command-line arguments. Use it with caution,
as it destory any previous argv information.
"""
@spec argv([String.t]) :: :ok
def argv(argv) do
:elixir_code_server.cast({ :argv, argv })
end
@doc """
Returns the current working directory or `nil` if one
is not available.
"""
def cwd do
case :file.get_cwd do
{ :ok, list } -> :unicode.characters_to_binary(list)
{ :ok, base } -> String.from_char_list!(base)
_ -> nil
end
end
@@ -98,15 +107,18 @@ defmodule System do
@doc """
Returns the user home (platform independent).
It returns nil if no user home is set.
It returns `nil` if no user home is set.
"""
def user_home do
get_unix_home || get_windows_home
case :os.type() do
{ :win32, _ } -> get_windows_home
_ -> get_unix_home
end
end
@doc """
Same as `user_home` but raises `System.NoHomeError`
instead of returning nil if no user home is set.
Same as `user_home/0` but raises `System.NoHomeError`
instead of returning `nil` if no user home is set.
"""
def user_home! do
user_home || raise NoHomeError
@@ -117,14 +129,16 @@ defmodule System do
end
defp get_windows_home do
get_env("USERPROFILE") || (
hd = get_env("HOMEDRIVE")
hp = get_env("HOMEPATH")
hd && hp && hd <> hp
:filename.absname(
get_env("USERPROFILE") || (
hd = get_env("HOMEDRIVE")
hp = get_env("HOMEPATH")
hd && hp && hd <> hp
)
)
end
@doc %B"""
@doc %S"""
Returns a writable temporary directory.
It searches for directories in the following order:
@@ -134,40 +148,41 @@ defmodule System do
4. `C:\TMP` on Windows or `/tmp` on Unix
5. As a last resort, the current working directory
Returns nil if none of the above are writable.
Returns `nil` if none of the above are writable.
"""
def tmp_dir do
write_env_tmp_dir('TMPDIR') ||
write_env_tmp_dir('TEMP') ||
write_env_tmp_dir('TMP') ||
write_tmp_dir("/tmp") ||
write_env_tmp_dir('TEMP') ||
write_env_tmp_dir('TMP') ||
write_tmp_dir('/tmp') ||
((cwd = cwd()) && write_tmp_dir(cwd))
end
@doc """
Same as `tmp_dir` but raises `System.NoTmpDirError`
instead of returning nil if no temp dir is set.
instead of returning `nil` if no temp dir is set.
"""
def tmp_dir! do
tmp_dir || raise NoTmpDirError
end
defp write_env_tmp_dir(env) do
case get_env(env) do
nil -> nil
tmp -> write_tmp_dir tmp
case :os.getenv(env) do
false -> nil
tmp -> write_tmp_dir(tmp)
end
end
defp write_tmp_dir(dir) do
case :file.read_file_info(dir) do
{:ok, info} ->
type_index = File.Stat.__index__ :type
access_index = File.Stat.__index__ :access
type_index = File.Stat.__record__(:index, :type)
access_index = File.Stat.__record__(:index, :access)
case { elem(info, type_index), elem(info, access_index) } do
{ :directory, access } when access in [:read_write, :write] ->
dir
_ -> nil
String.from_char_list!(dir)
_ ->
nil
end
{ :error, _ } -> nil
end
@@ -193,14 +208,17 @@ defmodule System do
If `command` is a char list, a char list is returned.
Returns a binary otherwise.
"""
@spec cmd(binary) :: binary
@spec cmd(char_list) :: char_list
@spec cmd(String.t) :: String.t
def cmd(command) when is_list(command) do
:os.cmd(command)
end
def cmd(command) do
list_to_binary :os.cmd(to_char_list(command))
def cmd(command) when is_binary(command) do
# Notice we don't use unicode for conversion
# because the OS is expecting and returning raw bytes
:binary.list_to_bin :os.cmd(:binary.bin_to_list(command))
end
@doc """
@@ -213,39 +231,45 @@ defmodule System do
If `program` is a char list, a char list is returned.
Returns a binary otherwise.
"""
@spec find_executable(binary) :: binary | nil
@spec find_executable(char_list) :: char_list | nil
@spec find_executable(String.t) :: String.t | nil
def find_executable(program) when is_list(program) do
:os.find_executable(program) || nil
end
def find_executable(program) do
case :os.find_executable(to_char_list(program)) do
def find_executable(program) when is_binary(program) do
# Notice we don't use unicode for conversion
# because the OS is expecting and returning raw bytes
case :os.find_executable(:binary.bin_to_list(program)) do
false -> nil
other -> list_to_binary(other)
other -> :binary.list_to_bin(other)
end
end
@doc """
Returns a list of all environment variables. Each environment variable is
given as a single string of the format "VarName=Value", where VarName is the
name of the variable and Value its value.
Returns a list of all environment variables. Each variable is given as a
`{name, value}` tuple where both `name` and `value` are strings.
"""
@spec get_env() :: [{String.t, String.t}]
def get_env do
Enum.map :os.getenv, :unicode.characters_to_binary &1
Enum.map(:os.getenv, fn var ->
var = String.from_char_list! var
[k, v] = String.split var, "=", global: false
{k, v}
end)
end
@doc """
Returns the value of the environment variable
`varname` as a binary, or nil if the environment
`varname` as a binary, or `nil` if the environment
variable is undefined.
"""
@spec get_env(String.t) :: String.t | nil
def get_env(varname) do
case :os.getenv(to_char_list(varname)) do
@spec get_env(binary) :: binary | nil
def get_env(varname) when is_binary(varname) do
case :os.getenv(String.to_char_list!(varname)) do
false -> nil
other -> :unicode.characters_to_binary(other)
other -> String.from_char_list!(other)
end
end
@@ -255,15 +279,15 @@ defmodule System do
See http://www.erlang.org/doc/man/os.html#getpid-0 for more info.
"""
@spec get_pid() :: String.t
def get_pid, do: list_to_binary(:os.getpid)
@spec get_pid() :: binary
def get_pid, do: iolist_to_binary(:os.getpid)
@doc """
Sets a new `value` for the environment variable `varname`.
"""
@spec put_env(String.t, String.t | char_list) :: :ok
def put_env(varname, value) when is_binary(value) or is_list(value) do
:os.putenv to_char_list(varname), :unicode.characters_to_list(value)
@spec put_env(binary, binary) :: :ok
def put_env(varname, value) when is_binary(varname) and is_binary(value) do
:os.putenv :binary.bin_to_list(varname), String.to_char_list!(value)
:ok
end
@@ -289,8 +313,7 @@ defmodule System do
@doc """
Halts the Erlang runtime system where the first argument status must be a
non-negative integer, the atom `:abort` or any type that can be converted
to a char list.
non-negative integer, the atom `:abort` or a binary.
* If an integer, the runtime system exits with the integer value which
is returned to the Operating System;
@@ -304,30 +327,25 @@ defmodule System do
Note that on many platforms, only the status codes 0-255 are supported
by the operating system.
For integer status, Erlang runtime system closes all ports and allows async
threads to finish their operations before exiting. To exit without such
flushing, pass options [flush: false] instead.
For more information, check: http://www.erlang.org/doc/man/erlang.html#halt-2
For more information, check: http://www.erlang.org/doc/man/erlang.html#halt-1
## Examples
System.halt(0)
System.halt(1, flush: false)
System.halt(1)
System.halt(:abort)
"""
@spec halt() :: no_return
@spec halt(non_neg_integer | List.Chars.t | :abort) :: no_return
@spec halt(non_neg_integer | List.Chars.t | :abort, [] | [flush: false]) :: no_return
def halt(status // 0, options // [])
@spec halt(non_neg_integer | binary | :abort) :: no_return
def halt(status // 0)
def halt(status, options) when is_integer(status) or status == :abort do
:erlang.halt(status, options)
def halt(status) when is_integer(status) or status == :abort do
:erlang.halt(status)
end
def halt(status, options) do
:erlang.halt(to_char_list(status), options)
def halt(status) when is_binary(status) do
:erlang.halt(:binary.bin_to_list(status))
end
## Helpers
-3
View File
@@ -1,3 +0,0 @@
defmodule Tuple do
@moduledoc false
end
+77 -73
View File
@@ -1,23 +1,58 @@
defmodule URI do
@on_load :preload_parsers
defrecord Info, [scheme: nil, path: nil, query: nil,
fragment: nil, authority: nil,
userinfo: nil, host: nil, port: nil,
specifics: nil]
import Bitwise
@moduledoc """
Utilities for working with and creating URIs.
"""
defrecord Info, [scheme: nil, path: nil, query: nil,
fragment: nil, authority: nil,
userinfo: nil, host: nil, port: nil]
import Bitwise
@ports [
{ "ftp", 21 },
{ "http", 80 },
{ "https", 443 },
{ "ldap", 389 },
{ "sftp", 22 },
{ "tftp", 69 },
]
Enum.each @ports, fn { scheme, port } ->
def normalize_scheme(unquote(scheme)), do: unquote(scheme)
def default_port(unquote(scheme)), do: unquote(port)
end
@doc """
Normalizes the scheme according to the spec by downcasing it.
"""
def normalize_scheme(nil), do: nil
def normalize_scheme(scheme), do: String.downcase(scheme)
@doc """
Returns the default port for a given scheme.
If the scheme is unknown to URI, returns `nil`.
Any scheme may be registered via `default_port/2`.
"""
def default_port(scheme) when is_binary(scheme) do
{ :ok, dict } = :application.get_env(:elixir, :uri)
Dict.get(dict, scheme)
end
@doc """
Registers a scheme with a default port.
"""
def default_port(scheme, port) when is_binary(scheme) and port > 0 do
{ :ok, dict } = :application.get_env(:elixir, :uri)
:application.set_env(:elixir, :uri, Dict.put(dict, scheme, port))
end
@doc """
Takes an enumerable (containing a sequence of two-item tuples)
and returns a string of k=v&k2=v2... where keys and values are
URL encoded as per encode. Keys and values can be any term
that implements the Binary.Chars protocol (i.e. can be converted
to binary).
and returns a string of the form "k=v&k2=v2..." 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).
"""
def encode_query(l), do: Enum.map_join(l, "&", pair(&1))
@@ -26,7 +61,7 @@ defmodule URI do
orddict with one entry for each key-value pair. Each key and value will be a
binary. It also does percent-unescaping of both keys and values.
Use decoder/1 if you want to customize or iterate each value manually.
Use `query_decoder/1` if you want to iterate over each value manually.
"""
def decode_query(q, dict // HashDict.new) when is_binary(q) do
Enum.reduce query_decoder(q), dict, fn({ k, v }, acc) -> Dict.put(acc, k, v) end
@@ -63,13 +98,13 @@ defmodule URI do
end
defp pair({k, v}) do
encode(to_binary(k)) <> "=" <> encode(to_binary(v))
encode(to_string(k)) <> "=" <> encode(to_string(v))
end
@doc """
Percent (URL) encodes a URI.
"""
def encode(s), do: bc <<c>> inbits s, do: <<percent(c) :: binary>>
def encode(s), do: bc(<<c>> inbits s, do: <<percent(c) :: binary>>)
defp percent(32), do: <<?+>>
defp percent(?-), do: <<?->>
@@ -115,14 +150,14 @@ defmodule URI do
have different default ports. Sometimes the parsing
of portions themselves are different. This parser
is extensible via behavior modules. If you have a
module named URI.MYSCHEME with a function called
'parse' that takes a single argument, the generically
module named `URI.MYSCHEME` with a function called
`parse` that takes a single argument, the generically
parsed URI, that function will be called when this
parse function is passed a URI of that scheme. This
allows you to build on top of what the URI library
currently offers. You also need to define default_port
which takes 0 arguments and returns the default port
for that particular scheme. Take a look at URI.HTTPS for an
currently offers. You also need to define `default_port`
which takes no arguments and returns the default port
for that particular scheme. Take a look at `URI.HTTPS` for an
example of one of these extension modules.
"""
def parse(s) when is_binary(s) do
@@ -141,81 +176,50 @@ defmodule URI do
if port, do: authority = authority <> ":" <> integer_to_binary(port)
end
info = URI.Info[
scheme: scheme && String.downcase(scheme), path: path, query: query,
scheme = normalize_scheme(scheme)
if nil?(port) and not nil?(scheme) do
port = default_port(scheme)
end
URI.Info[
scheme: scheme, path: path, query: query,
fragment: fragment, authority: authority,
userinfo: userinfo, host: host, port: port
]
scheme_specific(scheme, info)
end
@doc false
def scheme_module(scheme) do
if scheme do
module =
try do
Module.safe_concat(URI, String.upcase(scheme))
rescue
ArgumentError -> nil
end
if module && Code.ensure_loaded?(module) do
module
end
end
end
defp scheme_specific(scheme, info) do
if module = scheme_module(scheme) do
module.parse(default_port(info, module))
else
info
end
end
defp default_port(info, module) do
if info.port, do: info, else: info.port(module.default_port)
end
# Split an authority into its userinfo, host and port parts.
defp split_authority(s) do
s = s || ""
components = Regex.run %r/(^(.*)@)?([^:]*)(:(\d*))?/, s
components = Regex.run %r/(^(.*)@)?(\[[a-zA-Z0-9:.]*\]|[^:]*)(:(\d*))?/, s
destructure [_, _, userinfo, host, _, port], nillify(components)
port = if port, do: binary_to_integer(port)
{ userinfo, host && String.downcase(host), port }
host = if host, do: host |> String.lstrip(?[) |> String.rstrip(?])
{ userinfo, host, port }
end
# Regex.run returns empty strings sometimes. We want
# to replace those with nil for consistency.
defp nillify(l) do
lc s inlist l do
if size(s) > 0 do
s
else
nil
end
if size(s) > 0, do: s, else: nil
end
end
# Reference parsers so the parse/1 doesn't fail
# on safe_concat.
defp preload_parsers do
parsers = [URI.FTP, URI.HTTP, URI.HTTPS, URI.LDAP, URI.SFTP, URI.TFTP]
Enum.each parsers, Code.ensure_loaded(&1)
:ok
end
end
defimpl Binary.Chars, for: URI.Info do
def to_binary(uri) do
result = ""
defimpl String.Chars, for: URI.Info do
def to_string(URI.Info[] = uri) do
scheme = uri.scheme
if module = URI.scheme_module(uri.scheme) do
if module.default_port == uri.port, do: uri = uri.port(nil)
if scheme && (port = URI.default_port(scheme)) do
if uri.port == port, do: uri = uri.port(nil)
end
result = ""
if uri.scheme, do: result = result <> uri.scheme <> "://"
if uri.userinfo, do: result = result <> uri.userinfo <> "@"
if uri.host, do: result = result <> uri.host
-5
View File
@@ -1,5 +0,0 @@
defmodule URI.FTP do
@behavior URI.Parser
def default_port(), do: 21
def parse(info), do: info
end
-5
View File
@@ -1,5 +0,0 @@
defmodule URI.HTTP do
@behavior URI.Parser
def default_port(), do: 80
def parse(info), do: info
end
-5
View File
@@ -1,5 +0,0 @@
defmodule URI.HTTPS do
@behavior URI.Parser
def default_port(), do: 443
def parse(info), do: info
end
-7
View File
@@ -1,7 +0,0 @@
defmodule URI.LDAP do
@behavior URI.Parser
def default_port(), do: 389
# TODO: LDAP specific parsing.
def parse(info), do: info
end
-18
View File
@@ -1,18 +0,0 @@
defmodule URI.Parser do
@moduledoc """
Defines the behavior for each URI.Parser.
Check URI.HTTP for a possible implementation.
"""
use Behaviour
@doc """
Responsible for parsing extra URL information.
"""
defcallback parse(uri_info :: URI.Info.t) :: URI.Info.t
@doc """
Responsible for returning the default port.
"""
defcallback default_port() :: integer
end
-5
View File
@@ -1,5 +0,0 @@
defmodule URI.SFTP do
@behavior URI.Parser
def default_port(), do: 22
def parse(info), do: info
end
-5
View File
@@ -1,5 +0,0 @@
defmodule URI.TFTP do
@behavior URI.Parser
def default_port(), do: 69
def parse(info), do: info
end
+17 -20
View File
@@ -18,7 +18,7 @@ defmodule String.Unicode do
data_path = Path.join(__DIR__, "UnicodeData.txt")
{ codes, whitespace } = Enum.reduce File.iterator!(data_path), { [], [] }, fn(line, { cacc, wacc }) ->
{ codes, whitespace } = Enum.reduce File.stream!(data_path), { [], [] }, fn(line, { cacc, wacc }) ->
[ codepoint, _name, _category,
_class, bidi, _decomposition,
_numeric_1, _numeric_2, _numeric_3,
@@ -39,7 +39,7 @@ defmodule String.Unicode do
special_path = Path.join(__DIR__, "SpecialCasing.txt")
codes = Enum.reduce File.iterator!(special_path), codes, fn(line, acc) ->
codes = Enum.reduce File.stream!(special_path), codes, fn(line, acc) ->
[ codepoint, lower, title, upper, _comment ] = :binary.split(line, "; ", [:global])
key = to_binary.(codepoint)
:lists.keystore(key, 1, acc, { key, to_binary.(upper), to_binary.(lower), to_binary.(title) })
@@ -47,7 +47,7 @@ defmodule String.Unicode do
seqs_path = Path.join(__DIR__, "NamedSequences.txt")
seqs = Enum.map File.iterator!(seqs_path), fn(line) ->
seqs = Enum.map File.stream!(seqs_path), fn(line) ->
[ _name, codepoints ] = :binary.split(line, ";", [:global])
codepoints = :binary.split(codepoints, " ", [:global])
codepoints = Enum.map codepoints, Regex.replace(%r/\s+/, &1, "")
@@ -57,11 +57,11 @@ defmodule String.Unicode do
# Downcase
def downcase(string), do: do_downcase(string) |> list_to_binary
def downcase(string), do: do_downcase(string) |> iolist_to_binary
lc { codepoint, _upper, lower, _title } inlist codes, lower && lower != codepoint do
defp do_downcase(unquote(codepoint) <> rest) do
unquote(binary_to_list(lower)) ++ downcase(rest)
unquote(:binary.bin_to_list(lower)) ++ downcase(rest)
end
end
@@ -73,11 +73,11 @@ defmodule String.Unicode do
# Upcase
def upcase(string), do: do_upcase(string) |> list_to_binary
def upcase(string), do: do_upcase(string) |> iolist_to_binary
lc { codepoint, upper, _lower, _title } inlist codes, upper && upper != codepoint do
defp do_upcase(unquote(codepoint) <> rest) do
unquote(binary_to_list(upper)) ++ do_upcase(rest)
unquote(:binary.bin_to_list(upper)) ++ do_upcase(rest)
end
end
@@ -118,7 +118,7 @@ defmodule String.Unicode do
end
lc codepoint inlist whitespace do
c = binary_to_list(codepoint) |> :lists.reverse
c = :binary.bin_to_list(codepoint) |> :lists.reverse
defp do_rstrip(unquote(codepoint) <> rest, acc1, acc2) do
do_rstrip(rest, unquote(c) ++ (acc1 || acc2), acc2)
@@ -133,11 +133,11 @@ defmodule String.Unicode do
do_rstrip(rest, nil, [char|acc1])
end
defp do_rstrip(<<>>, _acc1, acc2), do: acc2 |> :lists.reverse |> list_to_binary
defp do_rstrip(<<>>, _acc1, acc2), do: acc2 |> :lists.reverse |> iolist_to_binary
# Split
def split(""), do: ""
def split(""), do: [""]
def split(string) when is_binary(string) do
:lists.reverse do_split(string, "", [])
@@ -145,11 +145,7 @@ defmodule String.Unicode do
lc codepoint inlist whitespace do
defp do_split(unquote(codepoint) <> rest, buffer, acc) do
if buffer != "" do
do_split(rest, "", [buffer | acc])
else
do_split(rest, buffer, acc)
end
do_split(rest, "", add_buffer_to_acc(buffer, acc))
end
end
@@ -158,13 +154,14 @@ defmodule String.Unicode do
end
defp do_split(<<>>, buffer, acc) do
if buffer != "" do
[buffer | acc]
else
acc
end
add_buffer_to_acc(buffer, acc)
end
@compile { :inline, add_buffer_to_acc: 2 }
defp add_buffer_to_acc("", acc), do: acc
defp add_buffer_to_acc(buffer, acc), do: [buffer|acc]
# Graphemes
lc codepoints inlist seqs do
+1 -1
View File
@@ -24,4 +24,4 @@
{verbose, false}
]}.
{require_otp_vsn,"(R16).*"}.
{require_otp_vsn,"(R16B).*"}.
+5 -49
View File
@@ -2,11 +2,10 @@
-behaviour(application).
-export([main/1, start_cli/0,
scope_for_eval/1, scope_for_eval/2,
eval/2, eval/3, eval/4, file_type/1,
eval/2, eval/3, eval/4,
eval_quoted/2, eval_quoted/3, eval_quoted/4,
eval_forms/3, translate_forms/3]).
-include("elixir.hrl").
-include_lib("kernel/include/file.hrl").
%% Top level types
-export_type([char_list/0, as_boolean/1]).
@@ -44,14 +43,6 @@ start_cli() ->
application:start(?MODULE),
'Elixir.Kernel.CLI':main(init:get_plain_arguments()).
%% Helpers
file_type(File) ->
case file:read_file_info(File) of
{ ok, #file_info{type=Type} } -> { ok, Type };
{ error, _ } = Error -> Error
end.
%% EVAL HOOKS
scope_for_eval(Opts) ->
@@ -66,7 +57,7 @@ scope_for_eval(Opts) ->
scope_for_eval(Scope, Opts) ->
File = case lists:keyfind(file, 1, Opts) of
{ file, RawFile } -> to_binary(RawFile);
{ file, RawFile } when is_binary(RawFile) -> RawFile;
false -> Scope#elixir_scope.file
end,
@@ -137,48 +128,13 @@ translate_forms(Tree, Binding, Opts) when is_list(Opts) ->
translate_forms(Tree, Binding, scope_for_eval(Opts));
translate_forms(Tree, Binding, #elixir_scope{} = Scope) ->
elixir_translator:translate(Tree, Scope#elixir_scope{
vars=binding_dict(Binding),
temp_vars=[],
clause_vars=nil,
counter=[]
}).
elixir_translator:translate(Tree, elixir_scope:vars_from_binding(Scope, Binding)).
eval_forms(Tree, Binding, Scope) ->
{ ParseTree, NewScope } = translate_forms(Tree, Binding, Scope),
case ParseTree of
[] -> { nil, Binding, NewScope };
_ ->
{value, Value, NewBinding} = erl_eval:exprs(ParseTree, normalize_binding(Binding)),
{Value, final_binding(NewBinding, NewScope#elixir_scope.vars), NewScope }
end.
%% INTERNAL HELPERS
to_binary(Bin) when is_binary(Bin) -> Bin;
to_binary(List) when is_list(List) -> list_to_binary(List).
binding_dict(List) -> binding_dict(List, orddict:new()).
binding_dict([{{H,Kind},_}|T], Dict) -> binding_dict(T, orddict:store({ H, Kind }, H, Dict));
binding_dict([{H,_}|T], Dict) -> binding_dict(T, orddict:store({ H, nil }, H, Dict));
binding_dict([], Dict) -> Dict.
final_binding(Binding, Vars) -> final_binding(Binding, [], Binding, Vars).
final_binding([{Var,_}|T], Acc, Binding, Vars) ->
case lists:member($@, atom_to_list(Var)) of
true ->
final_binding(T, Acc, Binding, Vars);
false ->
RealName = orddict:fetch({ Var, nil }, Vars),
RealValue = proplists:get_value(RealName, Binding, nil),
final_binding(T, [{Var, RealValue}|Acc], Binding, Vars)
end;
final_binding([], Acc, _Binding, _Vars) -> lists:reverse(Acc).
normalize_binding(Binding) ->
Keyword = orddict:from_list(Binding),
case orddict:find('_@MODULE', Keyword) of
{ ok, _ } -> Keyword;
_ -> orddict:store('_@MODULE', nil, Keyword)
{value, Value, NewBinding} = erl_eval:exprs(ParseTree, elixir_scope:binding_for_eval(Binding, nil)),
{Value, elixir_scope:binding_from_vars(NewScope, NewBinding), NewScope }
end.
+4 -1
View File
@@ -83,7 +83,7 @@ extract_args(_) -> error.
% Extract guards when multiple left side args are allowed.
extract_splat_guards([{ 'when', _, [_,_|_] = Args }]) ->
{ Left, Right } = elixir_tree_helpers:split_last(Args),
{ Left, Right } = elixir_utils:split_last(Args),
{ Left, extract_or_clauses(Right, []) };
extract_splat_guards(Else) ->
{ Else, [] }.
@@ -199,6 +199,9 @@ has_match_tuple({'case', _, _, _}) ->
has_match_tuple({match, _, _, _}) ->
true;
has_match_tuple({'fun', _, { clauses, _ }}) ->
false;
has_match_tuple(H) when is_tuple(H) ->
has_match_tuple(tuple_to_list(H));
+44 -37
View File
@@ -40,7 +40,7 @@ quoted(Forms, File) when is_binary(File) ->
file(Relative) when is_binary(Relative) ->
File = filename:absname(Relative),
{ ok, Bin } = file:read_file(File),
string(unicode:characters_to_list(Bin), File).
string(elixir_utils:characters_to_list(Bin), File).
%% Compiles a file to the given path (directory).
@@ -84,17 +84,17 @@ eval_forms(Forms, Line, Vars, S) ->
%% Compile the module by forms based on the scope information
%% executes the callback in case of success. This automatically
%% handles errors and warnings. Used by this module and elixir_module.
module(Forms, S, Callback) ->
module(Forms, File, Callback) ->
Options = case get_opt(debug_info) of
true -> [debug_info];
_ -> []
end,
module(Forms, S#elixir_scope.file, Options, false, Callback).
module(Forms, File, Options, false, Callback).
module(Forms, File, RawOptions, Bootstrap, Callback) when
is_binary(File), is_list(Forms), is_list(RawOptions), is_boolean(Bootstrap), is_function(Callback) ->
{ Options, SkipNative } = compile_opts(Forms, RawOptions),
Listname = binary_to_list(File),
Listname = elixir_utils:characters_to_list(File),
case compile:noenv_forms([no_auto_import()|Forms], [return,{source,Listname}|Options]) of
{ok, ModuleName, Binary, RawWarnings} ->
@@ -102,12 +102,12 @@ module(Forms, File, RawOptions, Bootstrap, Callback) when
true -> [{?MODULE,[{0,?MODULE,{skip_native,ModuleName}}]}|RawWarnings];
false -> RawWarnings
end,
format_warnings(Bootstrap, File, Warnings),
format_warnings(Bootstrap, Warnings),
code:load_binary(ModuleName, Listname, Binary),
Callback(ModuleName, Binary);
{error, Errors, Warnings} ->
format_warnings(Bootstrap, File, Warnings),
format_errors(File, Errors)
format_warnings(Bootstrap, Warnings),
format_errors(Errors)
end.
%% Compile core files for bootstrap.
@@ -166,13 +166,17 @@ module_form(Fun, Exprs, Line, File, Module, Vars) when
end, { nil, Line }, Vars),
Args = [{ var, Line, '_@MODULE'}, Cons],
Relative = elixir_utils:relative_to_cwd(File),
[
{ attribute, Line, file, { binary_to_list(File), 1 } },
{ attribute, Line, file, { elixir_utils:characters_to_list(File), 1 } },
{ attribute, Line, module, Module },
{ attribute, Line, export, [{ Fun, 2 }] },
{ attribute, Line, export, [{ Fun, 2 }, { '__RELATIVE__', 0 }] },
{ function, Line, Fun, length(Args), [
{ clause, Line, Args, [], Exprs }
] },
{ function, Line, '__RELATIVE__', 0, [
{ clause, Line, [], [], [elixir_utils:elixir_to_erl(Relative)] }
] }
].
@@ -195,7 +199,7 @@ binary_to_path({ModuleName, Binary}, CompilePath) ->
core_file(File) ->
try
Lists = file(list_to_binary(File)),
Lists = file(File),
[binary_to_path(X, "lib/elixir/ebin") || X <- Lists],
io:format("Compiled ~ts~n", [File])
catch
@@ -206,28 +210,31 @@ core_file(File) ->
core_main() ->
[
"lib/elixir/lib/kernel.ex",
"lib/elixir/lib/keyword.ex",
"lib/elixir/lib/module.ex",
"lib/elixir/lib/list.ex",
"lib/elixir/lib/kernel/typespec.ex",
"lib/elixir/lib/record.ex",
"lib/elixir/lib/macro.ex",
"lib/elixir/lib/macro/env.ex",
"lib/elixir/lib/exception.ex",
"lib/elixir/lib/code.ex",
"lib/elixir/lib/protocol.ex",
"lib/elixir/lib/enum.ex",
"lib/elixir/lib/binary/inspect.ex",
"lib/elixir/lib/binary/chars.ex",
"lib/elixir/lib/io.ex",
"lib/elixir/lib/path.ex",
"lib/elixir/lib/system.ex",
"lib/elixir/lib/kernel/cli.ex",
"lib/elixir/lib/kernel/error_handler.ex",
"lib/elixir/lib/kernel/parallel_compiler.ex",
"lib/elixir/lib/kernel/record_rewriter.ex",
"lib/elixir/lib/module/dispatch_tracker.ex"
<<"lib/elixir/lib/kernel.ex">>,
<<"lib/elixir/lib/keyword.ex">>,
<<"lib/elixir/lib/module.ex">>,
<<"lib/elixir/lib/list.ex">>,
<<"lib/elixir/lib/kernel/typespec.ex">>,
<<"lib/elixir/lib/record.ex">>,
<<"lib/elixir/lib/macro.ex">>,
<<"lib/elixir/lib/macro/env.ex">>,
<<"lib/elixir/lib/exception.ex">>,
<<"lib/elixir/lib/code.ex">>,
<<"lib/elixir/lib/protocol.ex">>,
<<"lib/elixir/lib/enum.ex">>,
<<"lib/elixir/lib/inspect/algebra.ex">>,
<<"lib/elixir/lib/inspect.ex">>,
<<"lib/elixir/lib/range.ex">>,
<<"lib/elixir/lib/string.ex">>,
<<"lib/elixir/lib/string/chars.ex">>,
<<"lib/elixir/lib/io.ex">>,
<<"lib/elixir/lib/path.ex">>,
<<"lib/elixir/lib/system.ex">>,
<<"lib/elixir/lib/kernel/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/module/dispatch_tracker.ex">>
].
%% ERROR HANDLING
@@ -236,15 +243,15 @@ format_error({ skip_native, Module }) ->
io_lib:format("skipping native compilation for ~ts because it contains on_load attribute",
[elixir_errors:inspect(Module)]).
format_errors(_File, []) ->
format_errors([]) ->
exit({ nocompile, "compilation failed but no error was raised" });
format_errors(File, Errors) ->
lists:foreach(fun ({_, Each}) ->
format_errors(Errors) ->
lists:foreach(fun ({File, Each}) ->
lists:foreach(fun (Error) -> elixir_errors:handle_file_error(File, Error) end, Each)
end, Errors).
format_warnings(Bootstrap, File, Warnings) ->
lists:foreach(fun ({_, Each}) ->
format_warnings(Bootstrap, Warnings) ->
lists:foreach(fun ({File, Each}) ->
lists:foreach(fun (Warning) -> elixir_errors:handle_file_warning(Bootstrap, File, Warning) end, Each)
end, Warnings).
+26 -19
View File
@@ -152,21 +152,21 @@ run_on_definition_callbacks(Kind, Line, Module, Name, Args, Guards, Expr, S, CO)
ok;
_ ->
Env = elixir_scope:to_ex_env({ Line, S }),
elixir_module:eval_callbacks(Line, Module, on_definition,
[Env, Kind, Name, Args, Guards, Expr], S)
Callbacks = 'Elixir.Module':get_attribute(Module, on_definition),
[Mod:Fun(Env, Kind, Name, Args, Guards, Expr) || { Mod, Fun } <- Callbacks]
end.
%% Retrieve @file or fallback to default
retrieve_file(Line, Module, S, CO) ->
case elixir_compiler:get_opt(internal, CO) of
true -> { binary_to_list(S#elixir_scope.file), Line };
true -> { elixir_utils:characters_to_list(S#elixir_scope.file), Line };
_ ->
case 'Elixir.Module':get_attribute(Module, file) of
nil -> { binary_to_list(S#elixir_scope.file), Line };
nil -> { elixir_utils:characters_to_list(S#elixir_scope.file), Line };
Else ->
'Elixir.Module':delete_attribute(Module, file),
Else
{ Else, 0 }
end
end.
@@ -204,7 +204,7 @@ translate_clause(_Line, _Kind, _Unpacked, [], nil, S) ->
{ [], S };
translate_clause(Line, Kind, _Unpacked, _Guards, nil, #elixir_scope{file=File}) ->
elixir_errors:syntax_error(Line, File, "missing keyword do in ~ts", [Kind]);
elixir_errors:syntax_error(Line, File, "missing do keyword in ~ts", [Kind]);
translate_clause(Line, Kind, Unpacked, Guards, Body, S) ->
Expr = expr_from_body(Line, Body),
@@ -239,9 +239,9 @@ unwrap_stored_definitions(File, Module) ->
Table = table(Module),
CTable = clauses_table(Module),
ets:delete(Table, last),
unwrap_stored_definition(ets:tab2list(Table), CTable, File, [], [], [], [], [], []).
unwrap_stored_definition(ets:tab2list(Table), CTable, File, [], [], [], [], [], [], []).
unwrap_stored_definition([Fun|T], CTable, File, Exports, Private, Def, Defmacro, Functions, Tail) ->
unwrap_stored_definition([Fun|T], CTable, File, All, Exports, Private, Def, Defmacro, Functions, Tail) ->
Tuple = element(1, Fun),
Clauses = [Clause || { _, Clause } <- ets:lookup(CTable, Tuple)],
@@ -252,15 +252,19 @@ unwrap_stored_definition([Fun|T], CTable, File, Exports, Private, Def, Defmacro,
end,
{ NewFunctions, NewTail } = case NewFun of
false -> { Functions, Tail };
_ -> function_for_stored_definition(NewFun, Clauses, File, Functions, Tail)
false ->
NewAll = All,
{ Functions, Tail };
_ ->
NewAll = [Tuple|All],
function_for_stored_definition(NewFun, Clauses, File, Functions, Tail)
end,
unwrap_stored_definition(T, CTable, File, NewExports, NewPrivate,
unwrap_stored_definition(T, CTable, File, NewAll, NewExports, NewPrivate,
NewDef, NewDefmacro, NewFunctions, NewTail);
unwrap_stored_definition([], _CTable, _File, Exports, Private, Def, Defmacro, Functions, Tail) ->
{ Exports, Private, ordsets:from_list(Def),
unwrap_stored_definition([], _CTable, _File, All, Exports, Private, Def, Defmacro, Functions, Tail) ->
{ All, Exports, Private, ordsets:from_list(Def),
ordsets:from_list(Defmacro), lists:reverse(Tail ++ Functions) }.
unwrap_stored_definition(def, Tuple, Fun, Exports, Private, Def, Defmacro) ->
@@ -305,12 +309,13 @@ default_function_for(_, Name, { clause, Line, Args, _Guards, _Exprs } = Clause)
store_each(Check, Kind, File, Location, Table, CTable, Defaults, {function, Line, Name, Arity, Clauses}) ->
Tuple = { Name, Arity },
case ets:lookup(Table, Tuple) of
[{ Tuple, StoredKind, StoredLine, _, StoredCheck, StoredLocation, StoredDefaults }] ->
[{ Tuple, StoredKind, StoredLine, StoredFile, StoredCheck, StoredLocation, StoredDefaults }] ->
FinalLine = StoredLine,
FinalLocation = StoredLocation,
FinalDefaults = max(Defaults, StoredDefaults),
check_valid_kind(Line, File, Name, Arity, Kind, StoredKind),
(Check and StoredCheck) andalso check_valid_clause(Line, File, Name, Arity, Kind, Table),
(Check and StoredCheck) andalso
check_valid_clause(Line, File, Name, Arity, Kind, Table, StoredLine, StoredFile),
check_valid_defaults(Line, File, Name, Arity, Kind, Defaults, StoredDefaults);
[] ->
FinalLine = Line,
@@ -328,13 +333,14 @@ check_valid_kind(Line, File, Name, Arity, Kind, StoredKind) ->
elixir_errors:form_error(Line, File, ?MODULE,
{ changed_kind, { Name, Arity, StoredKind, Kind } }).
check_valid_clause(Line, File, Name, Arity, Kind, Table) ->
check_valid_clause(Line, File, Name, Arity, Kind, Table, StoredLine, StoredFile) ->
case ets:lookup_element(Table, last, 2) of
{Name,Arity} -> [];
[] -> [];
_ ->
Relative = elixir_utils:relative_to_cwd(StoredFile),
elixir_errors:handle_file_warning(File, { Line, ?MODULE,
{ override_function, { Kind, Name, Arity } } })
{ ungrouped_clause, { Kind, Name, Arity, StoredLine, Relative } } })
end.
check_valid_defaults(_Line, _File, _Name, _Arity, _Kind, 0, _) -> [];
@@ -381,8 +387,9 @@ format_error({clauses_with_defaults,{Kind,Name,Arity}}) ->
format_error({out_of_order_defaults,{Kind,Name,Arity}}) ->
io_lib:format("clause with defaults should be the first clause in ~ts ~ts/~B", [Kind, Name, Arity]);
format_error({override_function,{Kind,Name,Arity}}) ->
io_lib:format("trying to override previously defined ~ts ~ts/~B", [Kind, Name, Arity]);
format_error({ungrouped_clause,{Kind,Name,Arity,OrigLine,OrigFile}}) ->
io_lib:format("clauses for the same ~ts should be grouped together, ~ts ~ts/~B was previously defined (~ts:~B)",
[Kind, Kind, Name, Arity, OrigFile, OrigLine]);
format_error({changed_kind,{Name,Arity,Previous,Current}}) ->
io_lib:format("~ts ~ts/~B already defined as ~ts", [Current, Name, Arity, Previous]).
+5 -5
View File
@@ -5,11 +5,11 @@
macro_for(_Tuple, _All, #elixir_scope{module=nil}) -> false;
macro_for(Tuple, All, #elixir_scope{module=Module,function=Function}) ->
macro_for(Tuple, All, #elixir_scope{module=Module}) ->
try elixir_def:lookup_definition(Module, Tuple) of
{ { Tuple, Kind, Line, _, _, _, _ }, Clauses } when Kind == defmacro; All, Kind == defmacrop ->
elixir_tracker:record_local(Tuple, Module, Function),
get_function(Line, Module, Clauses);
{ { Tuple, Kind, Line, _, _, _, _ }, Clauses } when Kind == defmacro, Clauses /= [];
All, Kind == defmacrop, Clauses /= [] ->
fun() -> get_function(Line, Module, Clauses) end;
_ ->
false
catch
@@ -19,7 +19,7 @@ macro_for(Tuple, All, #elixir_scope{module=Module,function=Function}) ->
function_for(Module, Name, Arity) ->
Tuple = { Name, Arity },
case elixir_def:lookup_definition(Module, Tuple) of
{ { Tuple, _, Line, _, _, _, _ }, Clauses } ->
{ { 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.
+1 -1
View File
@@ -85,5 +85,5 @@ format_error({ no_super, Module, { Name, Arity } }) ->
format_fa({ Name, Arity }) ->
A = atom_to_binary(Name, utf8),
B = list_to_binary(integer_to_list(Arity)),
B = integer_to_binary(Arity),
<< A/binary, $/, B/binary >>.
+74 -34
View File
@@ -17,7 +17,7 @@ default_functions() ->
default_macros() ->
[ { ?builtin, elixir_imported_macros() } ].
default_requires() ->
[ ?builtin, 'Elixir.Kernel.Typespec', 'Elixir.Record' ].
[ 'Elixir.Integer', 'Elixir.Kernel', 'Elixir.Kernel.Typespec', 'Elixir.Record' ].
find_import(Meta, Name, Arity, S) ->
Tuple = { Name, Arity },
@@ -45,9 +45,13 @@ import_function(Meta, Name, Arity, S) ->
false;
{ import, Receiver } ->
require_function(Meta, Receiver, Name, Arity, S);
nomatch ->
elixir_tracker:record_local(Tuple, S#elixir_scope.module, S#elixir_scope.function),
{ { 'fun', ?line(Meta), { function, Name, Arity } }, S }
false ->
case elixir_import:special_form(Name, Arity) of
true -> false;
false ->
elixir_tracker:record_local(Tuple, S#elixir_scope.module, S#elixir_scope.function),
{ { 'fun', ?line(Meta), { function, Name, Arity } }, S }
end
end.
require_function(Meta, Receiver, Name, Arity, S) ->
@@ -119,6 +123,28 @@ expand_import(Meta, Tuple, Args, Module, Extra, S) ->
do_expand_import(Meta, Tuple, Args, Module, S, Result).
do_expand_import(Meta, { Name, Arity } = Tuple, Args, Module, S, Result) ->
Function = S#elixir_scope.function,
Fun = (Function /= nil) andalso (Function /= Tuple) andalso
elixir_def_local:macro_for(Tuple, true, S),
case Fun of
false ->
do_expand_import_no_local(Meta, Tuple, Args, Module, S, Result);
_ ->
case Result of
{ Kind, Receiver } when Kind == import; Receiver == Module ->
do_expand_import_no_local(Meta, Tuple, Args, Module, S, Result);
{ _, Receiver } ->
Error = { macro_conflict, { Receiver, Name, Arity } },
elixir_errors:form_error(Meta, S#elixir_scope.file, ?MODULE, Error);
false ->
elixir_tracker:record_local(Tuple, Module, S#elixir_scope.function),
{ ok, Module, expand_macro_fun(Meta, Fun(), Module, Name, Args, Module, S) }
end
end.
do_expand_import_no_local(Meta, { Name, Arity } = Tuple, Args, Module, S, Result) ->
case Result of
{ macro, ?builtin } ->
case is_element(Tuple, in_erlang_macros()) of
@@ -133,13 +159,7 @@ do_expand_import(Meta, { Name, Arity } = Tuple, Args, Module, S, Result) ->
{ import, Receiver } ->
expand_require(Meta, Receiver, Tuple, Args, Module, S);
_ ->
Fun = (S#elixir_scope.function /= Tuple) andalso
elixir_def_local:macro_for(Tuple, true, S),
case Fun of
false -> { error, noexpansion };
_ ->
{ ok, Module, expand_macro_fun(Meta, Fun, Module, Name, Args, Module, S) }
end
{ error, noexpansion }
end.
expand_require(Meta, ?builtin, { Name, Arity } = Tuple, Args, Module, S) ->
@@ -156,19 +176,22 @@ expand_require(Meta, ?builtin, { Name, Arity } = Tuple, Args, Module, S) ->
end;
expand_require(Meta, Receiver, { Name, Arity } = Tuple, Args, Module, S) ->
Fun = (Module == Receiver) andalso (S#elixir_scope.function /= Tuple) andalso
elixir_def_local:macro_for(Tuple, false, S),
Function = S#elixir_scope.function,
Fun = (Module == Receiver) andalso (Function /= nil) andalso
(Function /= Tuple) andalso elixir_def_local:macro_for(Tuple, false, S),
case Fun of
false ->
case is_element(Tuple, get_optional_macros(Receiver)) of
true ->
elixir_tracker:record_remote(Tuple, Receiver, S#elixir_scope.module, S#elixir_scope.function),
elixir_tracker:record_remote(Tuple, Receiver, Module, Function),
{ ok, Receiver, expand_macro_named(Meta, Receiver, Name, Arity, Args, Module, S) };
false -> { error, noexpansion }
end;
_ ->
{ ok, Receiver, expand_macro_fun(Meta, Fun, Receiver, Name, Args, Module, S) }
elixir_tracker:record_local(Tuple, Module, Function),
{ ok, Receiver, expand_macro_fun(Meta, Fun(), Receiver, Name, Args, Module, S) }
end.
%% Expansion helpers
@@ -189,7 +212,7 @@ expand_macro_fun(Meta, Fun, Receiver, Name, Args, Module, S) ->
apply(Fun, [SArg|Args])
catch
Kind:Reason ->
Info = { Receiver, Name, length(Args), [{ file, S#elixir_scope.file }, { line, Line }] },
Info = { Receiver, Name, length(Args), location(Line, S) },
erlang:raise(Kind, Reason, munge_stacktrace(Info, erlang:get_stacktrace(), SArg))
end.
@@ -200,10 +223,29 @@ expand_macro_named(Meta, Receiver, Name, Arity, Args, Module, S) ->
expand_macro_fun(Meta, Fun, Receiver, Name, Args, Module, S).
translate_expansion(Meta, Tree, S) ->
elixir_translator:translate_each(
elixir_quote:linify(?line(Meta), Tree),
S
).
Line = ?line(Meta),
try
elixir_translator:translate_each(
elixir_quote:linify(Line, Tree),
S
)
catch
Kind:Reason ->
erlang:raise(Kind, Reason, munge_stacktrace(mfa(Line, S), erlang:get_stacktrace(), nil))
end.
mfa(Line, #elixir_scope{module=nil} = S) ->
{ elixir_compiler, '__FILE__', 2, location(Line, S) };
mfa(Line, #elixir_scope{module=Module,function=nil} = S) ->
{ Module, '__MODULE__', 0, location(Line, S) };
mfa(Line, #elixir_scope{module=Module,function={ Name, Arity }} = S) ->
{ Module, Name, Arity, location(Line, S) }.
location(Line, S) ->
[{ file, S#elixir_scope.file }, { line, Line }].
%% Helpers
@@ -227,7 +269,7 @@ find_dispatch(Meta, Tuple, Extra, S) ->
case { FunMatch, MacMatch } of
{ [], [Receiver] } -> { macro, Receiver };
{ [Receiver], [] } -> { function, Receiver };
{ [], [] } -> nomatch;
{ [], [] } -> false;
_ ->
{ Name, Arity } = Tuple,
[First, Second|_] = FunMatch ++ MacMatch,
@@ -262,17 +304,19 @@ not_an_import(Tuple, Context, Dict) ->
true
end.
%% We've reached the invoked macro, skip it with the rest
munge_stacktrace(Info, [{ _, _, [S|_], _ }|_], S) ->
[Info];
munge_stacktrace(Info, [{ elixir_dispatch, expand_macro_fun, _, _ }|_], _) ->
%% We've reached the elixir_dispatch internals, skip it with the rest
munge_stacktrace(Info, [{ elixir_dispatch, _, _, _ }|_], _) ->
[Info];
munge_stacktrace(Info, [H|T], S) ->
[H|munge_stacktrace(Info, T, S)];
munge_stacktrace(_, [], _) ->
[].
munge_stacktrace(Info, [], _) ->
[Info].
remote_function(Meta, Receiver, Name, Arity, S) ->
Line = ?line(Meta),
@@ -293,10 +337,14 @@ remote_function(Meta, Receiver, Name, Arity, S) ->
%% ERROR HANDLING
format_error({ unrequired_module, { Receiver, Name, Arity, Required }}) ->
String = 'Elixir.Enum':join([elixir_errors:inspect(R) || R <- Required], ", "),
String = 'Elixir.Enum':join([elixir_errors:inspect(R) || R <- Required], <<", ">>),
io_lib:format("tried to invoke macro ~ts.~ts/~B but module was not required. Required: ~ts",
[elixir_errors:inspect(Receiver), Name, Arity, String]);
format_error({ macro_conflict, { Receiver, Name, Arity } }) ->
io_lib:format("call to local macro ~ts/~B conflicts with imported ~ts.~ts/~B",
[Name, Arity, elixir_errors:inspect(Receiver), Name, Arity]);
format_error({ ambiguous_call, { Mod1, Mod2, Name, Arity }}) ->
io_lib:format("function ~ts/~B imported from both ~ts and ~ts, call is ambiguous",
[Name, Arity, elixir_errors:inspect(Mod1), elixir_errors:inspect(Mod2)]).
@@ -353,8 +401,6 @@ in_erlang_functions() ->
{ binary_to_float, 2 },
{ binary_to_integer, 1 },
{ binary_to_integer, 2 },
{ binary_to_list, 1 },
{ binary_to_list, 3 },
{ binary_to_term, 1 },
{ binary_to_term, 2 },
{ bit_size, 1 },
@@ -362,7 +408,7 @@ in_erlang_functions() ->
{ byte_size, 1 },
% { date, 0 },
{ exit, 1 },
{ float, 1 },
% { float, 1 },
{ float_to_binary, 1 },
{ float_to_list, 1 },
{ hd, 1 },
@@ -389,7 +435,6 @@ in_erlang_functions() ->
{ is_tuple, 1 },
{ length, 1 },
{ list_to_atom, 1 },
{ list_to_binary, 1 },
{ list_to_bitstring, 1 },
{ list_to_existing_atom, 1 },
{ list_to_float, 1 },
@@ -447,9 +492,6 @@ in_erlang_macros() ->
{'and',2},
{apply,2},
{apply,3},
{binding,0},
{binding,1},
{binding,2},
{'case',2},
{def,1},
{def,2},
@@ -464,8 +506,6 @@ in_erlang_macros() ->
{defp,1},
{defp,2},
{defp,4},
{function,1},
{function,3},
{in,2},
{'not',1},
{'or',2},
+79 -27
View File
@@ -1,19 +1,24 @@
% A bunch of helpers to help to deal with errors in Elixir source code.
% This is not exposed in the Elixir language.
-module(elixir_errors).
-export([syntax_error/3, syntax_error/4, inspect/1,
-export([syntax_error/3, syntax_error/4,
compile_error/3, compile_error/4, inspect/1,
form_error/4, parse_error/4, assert_module_scope/3,
assert_no_function_scope/3, assert_function_scope/3,
assert_no_match_scope/3, assert_no_guard_scope/3,
assert_no_match_or_guard_scope/3,
assert_no_match_or_guard_scope/3, warn/1,
handle_file_warning/2, handle_file_warning/3, handle_file_error/2,
deprecation/3, deprecation/4, file_format/3]).
deprecation/3, deprecation/4]).
-include("elixir.hrl").
warn(Warning) ->
elixir_code_server:cast(register_warning),
io:put_chars(standard_error, Warning).
%% Handle inspecting for exceptions
inspect(Atom) when is_atom(Atom) ->
'Elixir.Binary.Inspect.Atom':inspect(Atom, []).
'Elixir.Inspect.Atom':inspect(Atom).
%% Raised during macros translation.
@@ -28,7 +33,17 @@ syntax_error(Meta, File, Message) when is_binary(Message) ->
syntax_error(Meta, File, Format, Args) ->
Message = io_lib:format(Format, Args),
raise(Meta, File, 'Elixir.SyntaxError', unicode:characters_to_binary(Message)).
raise(Meta, File, 'Elixir.SyntaxError', elixir_utils:characters_to_binary(Message)).
compile_error(Meta, File, Message) when is_list(Message) ->
compile_error(Meta, File, iolist_to_binary(Message));
compile_error(Meta, File, Message) when is_binary(Message) ->
raise(Meta, File, 'Elixir.CompileError', Message).
compile_error(Meta, File, Format, Args) ->
Message = io_lib:format(Format, Args),
raise(Meta, File, 'Elixir.CompileError', elixir_utils:characters_to_binary(Message)).
%% Raised on tokenizing/parsing
@@ -52,7 +67,7 @@ parse_error(Meta, File, Error, Token) ->
BinToken = if
Token == [] -> <<>>;
true -> unicode:characters_to_binary(Token)
true -> elixir_utils:characters_to_binary(Token)
end,
Message = <<BinError / binary, BinToken / binary >>,
@@ -60,7 +75,7 @@ parse_error(Meta, File, Error, Token) ->
%% Raised during compilation
-spec form_error(non_neg_integer(), file:filename(), module(), any()) -> no_return().
-spec form_error(non_neg_integer() | list(), file:filename(), module(), any()) -> no_return().
form_error(Meta, File, Module, Desc) ->
Message = iolist_to_binary(format_error(Module, Desc)),
@@ -75,11 +90,6 @@ deprecation(Meta, File, Message, Args) ->
%% Handle warnings and errors (called during module compilation)
%% output warning based on warnings going to stdout or stderr.
output_warning(Warning) ->
elixir_code_server:cast(register_warning),
io:format(standard_error, Warning, []).
%% Ignore on bootstrap
handle_file_warning(true, _File, { _Line, sys_core_fold, nomatch_guard }) -> [];
handle_file_warning(true, _File, { _Line, sys_core_fold, { nomatch_shadow, _ } }) -> [];
@@ -93,42 +103,72 @@ handle_file_warning(_, _File, { _Line, v3_kernel, bad_call }) -> [];
%% We handle unused local warnings ourselves
handle_file_warning(_, _File, { _Line, erl_lint, { unused_function, _ } }) -> [];
%% Rewrite
%% Make no_effect clauses pretty
handle_file_warning(_, File, { Line, sys_core_fold, { no_effect, { erlang, F, A } } }) ->
{ Fmt, Args } = case erl_internal:comp_op(F, A) of
true -> { "use of operator ~ts has no effect", [translate_comp_op(F)] };
false ->
case erl_internal:bif(F, A) of
false -> { "the call to :erlang.~ts/~B has no effect", [F,A] };
true -> { "the call to ~ts/~B has no effect", [F,A] }
end
end,
Message = io_lib:format(Fmt, Args),
warn(file_format(Line, File, Message));
%% Rewrite undefined behaviour to check for protocols
handle_file_warning(_, File, {Line,erl_lint,{undefined_behaviour_func,{Fun,Arity},Module}}) ->
{ DefKind, Def, DefArity } =
case atom_to_list(Fun) of
"MACRO-" ++ Rest -> { macro, list_to_atom(Rest), Arity - 1 };
_ -> { function, Fun, Arity }
end,
Kind = protocol_or_behaviour(Module),
Raw = "undefined ~ts function ~ts/~B (for ~ts ~ts)",
Message = io_lib:format(Raw, [Kind, Fun, Arity, Kind, inspect(Module)]),
output_warning(file_format(Line, File, Message));
Raw = "undefined ~ts ~ts ~ts/~B (for ~ts ~ts)",
Message = io_lib:format(Raw, [Kind, DefKind, Def, DefArity, Kind, inspect(Module)]),
warn(file_format(Line, File, Message));
handle_file_warning(_, File, {Line,erl_lint,{undefined_behaviour,Module}}) ->
case elixir_compiler:get_opt(internal) of
true -> [];
false ->
Message = io_lib:format("behaviour ~ts undefined", [inspect(Module)]),
output_warning(file_format(Line, File, Message))
warn(file_format(Line, File, Message))
end;
%% Ignore unused vars at "weird" lines (<= 0)
handle_file_warning(_, _File, {Line,erl_lint,{unused_var,_Var}}) when Line =< 0 ->
[];
%% Ignore shadowed vars as we guarantee no conflicts ourselves
handle_file_warning(_, _File, {_Line,erl_lint,{shadowed_var,_Var,_Where}}) ->
[];
%% Properly format other unused vars
handle_file_warning(_, File, {Line,erl_lint,{unused_var,Var}}) ->
Message = format_error(erl_lint, { unused_var, format_var(Var) }),
output_warning(file_format(Line, File, Message));
handle_file_warning(_, File, {Line,erl_lint,{shadowed_var,Var,Where}}) ->
Message = format_error(erl_lint, { shadowed_var, format_var(Var), Where }),
output_warning(file_format(Line, File, Message));
warn(file_format(Line, File, Message));
%% Default behavior
handle_file_warning(_, File, {Line,Module,Desc}) ->
Message = format_error(Module, Desc),
output_warning(file_format(Line, File, Message)).
warn(file_format(Line, File, Message)).
handle_file_warning(File, Desc) ->
handle_file_warning(false, File, Desc).
-spec handle_file_error(file:filename(), {non_neg_integer(), module(), any()}) -> no_return().
handle_file_error(File, {Line,erl_lint,{unsafe_var,Var,{In,_Where}}}) ->
Translated = case In of
'orelse' -> 'or';
'andalso' -> 'and';
_ -> In
end,
Message = io_lib:format("cannot define variable ~ts inside ~ts", [format_var(Var), Translated]),
raise(Line, File, 'Elixir.CompileError', iolist_to_binary(Message));
handle_file_error(File, {Line,Module,Desc}) ->
form_error(Line, File, Module, Desc).
@@ -167,15 +207,21 @@ raise(none, File, Kind, Message) ->
raise(0, File, Kind, Message);
raise(Line, File, Kind, Message) when is_integer(Line) ->
%% Populate the stacktrace so we can raise it
try
throw(ok)
catch
ok -> ok
end,
Stacktrace = erlang:get_stacktrace(),
Exception = Kind:new([{description, Message}, {file, iolist_to_binary(File)}, {line, Line}]),
erlang:raise(error, Exception, Stacktrace).
erlang:raise(error, Exception, tl(Stacktrace)).
file_format(0, File, Message) ->
io_lib:format("~ts: ~ts~n", [File, Message]);
io_lib:format("~ts: ~ts~n", [elixir_utils:relative_to_cwd(File), Message]);
file_format(Line, File, Message) ->
io_lib:format("~ts:~w: ~ts~n", [File, Line, Message]).
io_lib:format("~ts:~w: ~ts~n", [elixir_utils:relative_to_cwd(File), Line, Message]).
format_var(Var) ->
list_to_atom(lists:takewhile(fun(X) -> X /= $@ end, atom_to_list(Var))).
@@ -201,4 +247,10 @@ is_protocol(Module) ->
end;
{ error, _ } ->
false
end.
end.
translate_comp_op('/=') -> '!=';
translate_comp_op('=<') -> '<=';
translate_comp_op('=:=') -> '===';
translate_comp_op('=/=') -> '!==';
translate_comp_op(Other) -> Other.
+156
View File
@@ -0,0 +1,156 @@
-module(elixir_fn).
-export([fn/3, capture/3]).
-import(elixir_scope, [umergec/2]).
-import(elixir_errors, [syntax_error/3, syntax_error/4, compile_error/4]).
-include("elixir.hrl").
fn(Meta, Clauses, S) ->
Transformer = fun({ ArgsWithGuards, CMeta, Expr }, Acc) ->
{ Args, Guards } = elixir_clauses:extract_splat_guards(ArgsWithGuards),
elixir_clauses:assigns_block(?line(CMeta), fun translate_fn_match/2, Args, [Expr], Guards, umergec(S, Acc))
end,
{ TClauses, NS } = lists:mapfoldl(Transformer, S, Clauses),
Arities = [length(Args) || { clause, _Line, Args, _Guards, _Exprs } <- TClauses],
case length(lists:usort(Arities)) of
1 ->
{ { 'fun', ?line(Meta), { clauses, TClauses } }, umergec(S, NS) };
_ ->
syntax_error(Meta, S#elixir_scope.file,
"cannot mix clauses with different arities in function definition")
end.
translate_fn_match(Arg, S) ->
{ TArg, TS } = elixir_translator:translate(Arg, S#elixir_scope{extra=fn_match}),
{ TArg, TS#elixir_scope{extra=S#elixir_scope.extra} }.
capture(Meta, { '/', _, [{ { '.', _, [_, F] } = Dot, RequireMeta , [] }, A] }, S) when is_atom(F), is_integer(A) ->
Args = [{ '&', [], [X] } || X <- lists:seq(1, A)],
capture_require(Meta, { Dot, RequireMeta, Args }, S, true);
capture(Meta, { '/', _, [{ F, _, C }, A] }, S) when is_atom(F), is_integer(A), is_atom(C) ->
ImportMeta =
case lists:keyfind(import_fa, 1, Meta) of
{ import_fa, { Receiver, Context } } ->
lists:keystore(context, 1,
lists:keystore(import, 1, Meta, { import, Receiver }),
{ context, Context }
);
false -> Meta
end,
Args = [{ '&', [], [X] } || X <- lists:seq(1, A)],
capture_import(Meta, { F, ImportMeta, Args }, S, true);
capture(Meta, { { '.', _, [_, Fun] }, _, Args } = Expr, S) when is_atom(Fun), is_list(Args) ->
capture_require(Meta, Expr, S, is_sequential_and_not_empty(Args));
capture(Meta, { { '.', _, [_] }, _, Args } = Expr, S) when is_list(Args) ->
do_capture(Meta, Expr, S, false);
capture(Meta, { '__block__', _, _ } = Expr, S) ->
Message = "invalid args for &, block expressions are not allowed, got: ~ts",
syntax_error(Meta, S#elixir_scope.file, Message, ['Elixir.Macro':to_string(Expr)]);
capture(Meta, { Atom, _, Args } = Expr, S) when is_atom(Atom), is_list(Args) ->
capture_import(Meta, Expr, S, is_sequential_and_not_empty(Args));
capture(Meta, { Left, Right }, S) ->
capture(Meta, { '{}', Meta, [Left, Right] }, S);
capture(Meta, List, S) when is_list(List) ->
capture(Meta, { '[]', Meta, List }, S);
capture(Meta, Arg, S) when is_integer(Arg) ->
compile_error(Meta, S#elixir_scope.file, "unhandled &~B outside of a capture", [Arg]);
capture(Meta, Arg, S) ->
invalid_capture(Meta, Arg, S).
%% Helpers
capture_import(Meta, { Atom, ImportMeta, Args } = Expr, S, Sequential) ->
case Sequential andalso
elixir_dispatch:import_function(ImportMeta, Atom, length(Args), S) of
false -> do_capture(Meta, Expr, S, Sequential);
Else -> Else
end.
capture_require(Meta, { { '.', _, [Left, Right] }, RequireMeta, Args } = Expr, S, Sequential) ->
{ Mod, SE } = 'Elixir.Macro':expand_all(Left, elixir_scope:to_ex_env({ ?line(Meta), S }), S),
case Sequential andalso is_atom(Mod) andalso
elixir_dispatch:require_function(RequireMeta, Mod, Right, length(Args), SE) of
false -> do_capture(Meta, Expr, S, Sequential);
Else -> Else
end.
do_capture(Meta, Expr, S, Sequential) ->
case do_escape(Expr, S, []) of
{ _, _, [] } when not Sequential ->
invalid_capture(Meta, Expr, S);
{ TExpr, TS, TDict } ->
TVars = validate(Meta, TDict, 1, S),
fn(Meta, [{ TVars, Meta, TExpr }], TS)
end.
invalid_capture(Meta, Arg, S) ->
Message = "invalid args for &, expected an expression in the format of &Mod.fun/arity, "
"&local/arity or a capture containing at least one argument as &1, got: ~ts",
syntax_error(Meta, S#elixir_scope.file, Message, ['Elixir.Macro':to_string(Arg)]).
validate(Meta, [{ Pos, Var }|T], Pos, S) ->
[Var|validate(Meta, T, Pos + 1, S)];
validate(Meta, [{ Pos, _ }|_], Expected, S) ->
compile_error(Meta, S#elixir_scope.file, "capture &~B cannot be defined without &~B", [Pos, Expected]);
validate(_Meta, [], _Pos, _S) ->
[].
do_escape({ '&', Meta, [Pos] }, S, Dict) when is_integer(Pos), Pos > 0 ->
case orddict:find(Pos, Dict) of
{ ok, Var } ->
{ Var, S, Dict };
error ->
{ Var, SC } = elixir_scope:build_ex_var(?line(Meta), S),
{ Var, SC, orddict:store(Pos, Var, Dict) }
end;
do_escape({ '&', Meta, [Pos] }, S, _Dict) when is_integer(Pos) ->
compile_error(Meta, S#elixir_scope.file, "capture &~B is not allowed", [Pos]);
do_escape({ '&', Meta, _ } = Arg, S, _Dict) ->
Message = "nested captures via & are not allowed: ~ts",
compile_error(Meta, S#elixir_scope.file, Message, ['Elixir.Macro':to_string(Arg)]);
do_escape({ Left, Meta, Right }, S0, Dict0) ->
{ TLeft, S1, Dict1 } = do_escape(Left, S0, Dict0),
{ TRight, S2, Dict2 } = do_escape(Right, S1, Dict1),
{ { TLeft, Meta, TRight }, S2, Dict2 };
do_escape({ Left, Right }, S0, Dict0) ->
{ TLeft, S1, Dict1 } = do_escape(Left, S0, Dict0),
{ TRight, S2, Dict2 } = do_escape(Right, S1, Dict1),
{ { TLeft, TRight }, S2, Dict2 };
do_escape(List, S, Dict) when is_list(List) ->
do_escape_list(List, S, Dict, []);
do_escape(Other, S, Dict) ->
{ Other, S, Dict }.
do_escape_list([H|T], S, Dict, Acc) ->
{ TH, TS, TDict } = do_escape(H, S, Dict),
do_escape_list(T, TS, TDict, [TH|Acc]);
do_escape_list([], S, Dict, Acc) ->
{ lists:reverse(Acc), S, Dict }.
is_sequential_and_not_empty([]) -> false;
is_sequential_and_not_empty(List) -> is_sequential(List, 1).
is_sequential([{ '&', _, [Int] }|T], Int) ->
is_sequential(T, Int + 1);
is_sequential([], _Int) -> true;
is_sequential(_, _Int) -> false.
+88 -111
View File
@@ -2,43 +2,41 @@
%% in between local functions and imports.
%% For imports dispatch, please check elixir_dispatch.
-module(elixir_import).
-export([import/5, format_error/1,
ensure_no_local_conflict/4]).
-export([import/4, special_form/2, format_error/1]).
-include("elixir.hrl").
%% IMPORT HELPERS
%% IMPORT
%% Update the scope to consider the imports for aliases
%% based on the given options and selector.
import(Meta, Ref, Opts, S) ->
SI =
case keyfind(only, Opts) of
{ only, functions } ->
import_functions(Meta, Ref, Opts, S);
{ only, macros } ->
import_macros(true, Meta, Ref, Opts, S);
{ only, List } when is_list(List) ->
import_macros(false, Meta, Ref, Opts, import_functions(Meta, Ref, Opts, S));
false ->
import_macros(false, Meta, Ref, Opts, import_functions(Meta, Ref, Opts, S))
end,
record_warn(Meta, Ref, Opts, SI),
SI.
import(Meta, Ref, Opts, Selector, S) ->
IncludeAll = (Selector == all) or (Selector == default),
import_functions(Meta, Ref, Opts, S) ->
{ Functions, Temp } = calculate(Meta, Ref, Opts, S#elixir_scope.functions,
S#elixir_scope.macro_functions, fun() -> get_functions(Ref) end, S),
S#elixir_scope{functions=Functions, macro_functions=Temp}.
SF = case IncludeAll or (Selector == functions) of
false -> S;
true ->
FunctionsFun = fun(K) -> remove_underscored(K andalso Selector, get_functions(Ref)) end,
{ Functions, TempF } = calculate(Meta, Ref, Opts,
S#elixir_scope.functions, S#elixir_scope.macro_functions, FunctionsFun, S),
S#elixir_scope{functions=Functions, macro_functions=TempF}
import_macros(Force, Meta, Ref, Opts, S) ->
Existing = fun() ->
case Force of
true -> get_macros(Meta, Ref, S);
false -> get_optional_macros(Ref)
end
end,
SM = case IncludeAll or (Selector == macros) of
false -> SF;
true ->
MacrosFun = fun(K) ->
case IncludeAll of
true -> remove_underscored(K andalso Selector, get_optional_macros(Ref));
false -> get_macros(Meta, Ref, SF)
end
end,
{ Macros, TempM } = calculate(Meta, Ref, Opts,
SF#elixir_scope.macros, SF#elixir_scope.macro_macros, MacrosFun, SF),
SF#elixir_scope{macros=Macros, macro_macros=TempM}
end,
record_warn(Meta, Ref, Opts, S),
SM.
{ Macros, Temp } = calculate(Meta, Ref, Opts, S#elixir_scope.macros,
S#elixir_scope.macro_macros, Existing, S),
S#elixir_scope{macros=Macros, macro_macros=Temp}.
record_warn(_Meta, _Ref, _Opts, #elixir_scope{module=nil}) -> false;
record_warn(Meta, Ref, Opts, #elixir_scope{module=Module}) ->
@@ -48,30 +46,27 @@ record_warn(Meta, Ref, Opts, #elixir_scope{module=Module}) ->
{ warn, true } -> true;
false -> not lists:keymember(context, 1, Meta)
end,
elixir_tracker:record_warn(Ref, Warn, ?line(Meta), Module).
%% Calculates the imports based on only and except
calculate(Meta, Key, Opts, Old, Temp, AvailableFun, S) ->
File = S#elixir_scope.file,
calculate(Meta, Key, Opts, Old, Temp, Existing, S) ->
New = case keyfind(only, Opts) of
{ only, Only } ->
{ only, Only } when is_list(Only) ->
case Only -- get_exports(Key) of
[{Name,Arity}|_] ->
Tuple = { invalid_import, { Key, Name, Arity } },
elixir_errors:form_error(Meta, File, ?MODULE, Tuple);
elixir_errors:form_error(Meta, S#elixir_scope.file, ?MODULE, Tuple);
_ ->
intersection(Only, AvailableFun(false))
intersection(Only, Existing())
end;
false ->
_ ->
case keyfind(except, Opts) of
false -> AvailableFun(true);
{ except, [] } -> AvailableFun(true);
{ except, Except } ->
false -> remove_underscored(Existing());
{ except, [] } -> remove_underscored(Existing());
{ except, Except } when is_list(Except) ->
case keyfind(Key, Old) of
false -> AvailableFun(true) -- Except;
false -> remove_underscored(Existing()) -- Except;
{Key,OldImports} -> OldImports -- Except
end
end
@@ -82,9 +77,11 @@ calculate(Meta, Key, Opts, Old, Temp, AvailableFun, S) ->
Final = remove_internals(Set),
case Final of
[] -> { keydelete(Key, Old), if_quoted(Meta, Temp, fun(Value) -> keydelete(Key, Value) end) };
[] ->
{ keydelete(Key, Old),
if_quoted(Meta, Temp, fun(Value) -> keydelete(Key, Value) end) };
_ ->
ensure_no_special_form_conflict(Meta, File, Key, Final, internal_conflict),
ensure_no_special_form_conflict(Meta, S#elixir_scope.file, Key, Final),
{ [{ Key, Final }|keydelete(Key, Old)],
if_quoted(Meta, Temp, fun(Value) -> [{ Key, Final }|keydelete(Key, Value)] end) }
end.
@@ -139,28 +136,16 @@ get_optional_macros(Module) ->
%% VALIDATION HELPERS
%% Check if any of the locals defined conflicts with an invoked
%% Elixir "implemented in Erlang" macro.
ensure_no_local_conflict(Meta, File, Module, AllDefined) ->
ensure_no_special_form_conflict(Meta, File, Module, AllDefined, local_conflict).
%% Ensure the given functions don't clash with any
%% of Elixir non overridable macros.
ensure_no_special_form_conflict(Meta, File, Key, [{Name,Arity}|T], Reason) ->
Values = lists:filter(fun({X,Y}) ->
(Name == X) andalso ((Y == '*') orelse (Y == Arity))
end, special_form()),
case Values /= [] of
ensure_no_special_form_conflict(Meta, File, Key, [{Name,Arity}|T]) ->
case special_form(Name, Arity) of
true ->
Tuple = { Reason, { Key, Name, Arity } },
Tuple = { special_form_conflict, { Key, Name, Arity } },
elixir_errors:form_error(Meta, File, ?MODULE, Tuple);
false -> ensure_no_special_form_conflict(Meta, File, Key, T, Reason)
false ->
ensure_no_special_form_conflict(Meta, File, Key, T)
end;
ensure_no_special_form_conflict(_Meta, _File, _Key, [], _) -> ok.
ensure_no_special_form_conflict(_Meta, _File, _Key, []) -> ok.
%% ERROR HANDLING
@@ -168,10 +153,7 @@ format_error({invalid_import,{Receiver, Name, Arity}}) ->
io_lib:format("cannot import ~ts.~ts/~B because it doesn't exist",
[elixir_errors:inspect(Receiver), Name, Arity]);
format_error({local_conflict,{_, Name, Arity}}) ->
io_lib:format("cannot define local ~ts/~B because it conflicts with Elixir special forms", [Name, Arity]);
format_error({internal_conflict,{Receiver, Name, Arity}}) ->
format_error({special_form_conflict,{Receiver, Name, Arity}}) ->
io_lib:format("cannot import ~ts.~ts/~B because it conflicts with Elixir special forms",
[elixir_errors:inspect(Receiver), Name, Arity]);
@@ -196,58 +178,53 @@ intersection([], _All) -> [].
%% Internal funs that are never imported etc.
remove_underscored(default, List) ->
remove_underscored(List) ->
lists:filter(fun({ Name, _ }) ->
case atom_to_list(Name) of
"_" ++ _ -> false;
_ -> true
end
end, List);
remove_underscored(_, List) -> List.
end, List).
remove_internals(Set) ->
ordsets:del_element({ module_info, 1 },
ordsets:del_element({ module_info, 0 }, Set)).
%% Macros implemented in Erlang that are not importable.
%% Special forms
special_form() ->
[
{'^',1},
{'=',2},
{'__op__',2},
{'__op__',3},
{'__ambiguousop__','*'},
{'__scope__',2},
{'__block__','*'},
{'->','*'},
{'<<>>','*'},
{'{}','*'},
{'[]','*'},
{'alias',1},
{'alias',2},
{'require',1},
{'require',2},
{'import',1},
{'import',2},
{'import',3},
{'__ENV__',0},
{'__CALLER__',0},
{'__MODULE__',0},
{'__FILE__',0},
{'__DIR__',0},
{'__aliases__','*'},
{'quote',1},
{'quote',2},
{'unquote',1},
{'unquote_splicing',1},
{'fn','*'},
{'super','*'},
{'super?',0},
{'bc','*'},
{'lc','*'},
{'var!',1},
{'var!',2},
{'alias!',1}
].
special_form('&',1) -> true;
special_form('^',1) -> true;
special_form('=',2) -> true;
special_form('__op__',2) -> true;
special_form('__op__',3) -> true;
special_form('__scope__',2) -> 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('__FILE__',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('super?',0) -> true;
special_form('bc',_) -> true;
special_form('lc',_) -> true;
special_form('var!',1) -> true;
special_form('var!',2) -> true;
special_form('alias!',1) -> true;
special_form(_, _) -> false.
+2 -5
View File
@@ -159,17 +159,14 @@ build_interpol(_Kind, _Line, _File, [], Output) ->
Output;
build_interpol(s, _Line, _File, Buffer, Output) ->
[unicode:characters_to_binary(lists:reverse(Buffer))|Output];
[elixir_utils:characters_to_binary(lists:reverse(Buffer))|Output];
build_interpol(i, Line, File, Buffer, Output) ->
[wrap_interpol(Line, forms(lists:reverse(Buffer), Line, File))| Output].
wrap_interpol(_Line, Form) when is_binary(Form) ->
Form;
wrap_interpol(Line, Form) ->
Meta = [{line,Line}],
{ '::', Meta, [{ { '.', Meta, ['Elixir.Binary.Chars', to_binary] }, Meta, [Form]}, { binary, Meta, nil }]}.
{ '::', Meta, [{ { '.', Meta, ['Elixir.Kernel', to_string] }, Meta, [Form]}, { binary, Meta, nil }]}.
forms(String, StartLine, File) ->
case elixir_translator:forms(String, StartLine, File, []) of
+27 -23
View File
@@ -45,7 +45,7 @@ translate({ '[]', Meta, Args } = Original, S) when is_list(Args) ->
{ Exprs, SE } = translate_args(lists:reverse(RExprs), S),
{ Tail, ST } = TailFun(SE),
{ elixir_tree_helpers:list_to_cons(?line(Meta), Exprs, Tail), ST };
{ elixir_utils:list_to_cons(?line(Meta), Exprs, Tail), ST };
Else -> Else
end;
@@ -55,17 +55,13 @@ translate({ Left, Right }, S) ->
translate(Args, S) when is_list(Args) ->
translate({ '[]', [], Args }, S);
translate(Number, S) when is_integer(Number) ->
{ { integer, 0, Number }, S };
translate(Tuple, S) when is_tuple(Tuple) ->
elixir_errors:compile_error(0, S#elixir_scope.file,
"tuples in quoted expressions must have 2 or 3 items, invalid quoted expression: ~ts",
['Elixir.Kernel':inspect(Tuple)]);
translate(Number, S) when is_float(Number) ->
{ { float, 0, Number }, S };
translate(Atom, S) when is_atom(Atom) ->
{ { atom, 0, Atom }, S };
translate(Bitstring, S) when is_bitstring(Bitstring) ->
{ elixir_tree_helpers:elixir_to_erl(Bitstring), S }.
translate(Other, S) ->
{ elixir_utils:elixir_to_erl(0, Other, S), S }.
%% Helpers
@@ -98,9 +94,16 @@ build_bitstr_each(Fun, [H|T], Meta, S, Acc) ->
build_bitstr_each(Fun, T, Meta, S, Acc, H, Size, Types) ->
{ Expr, NS } = Fun(H, S),
case (is_default_or_utf(Types) andalso Expr) of
{ bin, _, BinElements } ->
build_bitstr_each(Fun, T, Meta, NS, rehash_bin_elements(BinElements, Size, Types, []) ++ Acc);
AllowString = types_allow_string(Types),
AllowSplice = types_allow_splice(Types),
AllowAny = (AllowString orelse AllowSplice) andalso (Size == default),
case AllowAny andalso Expr of
{ bin, _, [{ bin_element, 0, { string, 0, String }, default, default }] } when AllowString ->
build_bitstr_each(Fun, T, Meta, NS, [{ bin_element, ?line(Meta), { string, 0, String }, Size, Types }|Acc]);
{ bin, _, Elements } when AllowSplice ->
build_bitstr_each(Fun, T, Meta, NS, lists:reverse(Elements) ++ Acc);
{ cons, _, _, _ } = Cons ->
build_bitstr_each(Fun, T, Meta, NS, rehash_cons(Cons, Size, Types, []) ++ Acc);
{ nil, _ } ->
@@ -113,16 +116,17 @@ rehash_cons({ nil, _ }, _Size, _Types, Acc) -> Acc;
rehash_cons({ cons, Line, Left, Right }, Size, Types, Acc) ->
rehash_cons(Right, Size, Types, [{ bin_element, Line, Left, Size, Types }|Acc]).
rehash_bin_elements([{ bin_element, Line, Expr, _S, _T }|T], Size, Types, Acc) ->
rehash_bin_elements(T, Size, Types, [{ bin_element, Line, Expr, Size, Types }|Acc]);
types_allow_string([End|T]) when End == little; End == big -> types_allow_string(T);
types_allow_string([UTF|T]) when UTF == utf8; UTF == utf16; UTF == utf32 -> types_allow_string(T);
types_allow_string([]) -> true;
types_allow_string(_) -> false.
rehash_bin_elements([], _Size, _Types, Acc) ->
Acc.
is_default_or_utf(default) -> true;
is_default_or_utf([UTF|_]) when UTF == utf8; UTF == utf16; UTF == utf32 -> true;
is_default_or_utf([_|T]) -> is_default_or_utf(T);
is_default_or_utf([]) -> false.
types_allow_splice(default) -> true;
types_allow_splice([bytes]) -> true;
types_allow_splice([binary]) -> true;
types_allow_splice([bits]) -> true;
types_allow_splice([bitstring]) -> true;
types_allow_splice(_) -> false.
%% Extra bitstring specifiers
+26 -118
View File
@@ -3,8 +3,8 @@
-module(elixir_macros).
-export([translate/2]).
-import(elixir_translator, [translate_each/2, translate_args/2, translate_apply/7]).
-import(elixir_scope, [umergec/2]).
-import(elixir_errors, [syntax_error/3, syntax_error/4,
-import(elixir_scope, [umergec/2, umergea/2]).
-import(elixir_errors, [compile_error/3, syntax_error/3, syntax_error/4,
assert_no_function_scope/3, assert_module_scope/3, assert_no_match_or_guard_scope/3]).
-include("elixir.hrl").
@@ -33,11 +33,11 @@ translate({ Op, Meta, Exprs }, S) when is_list(Exprs),
translate({ '!', Meta, [{ '!', _, [Expr] }] }, S) ->
{ TExpr, SE } = translate_each(Expr, S),
elixir_tree_helpers:convert_to_boolean(?line(Meta), TExpr, true, S#elixir_scope.context == guard, SE);
elixir_utils:convert_to_boolean(?line(Meta), TExpr, true, S#elixir_scope.context == guard, SE);
translate({ '!', Meta, [Expr] }, S) ->
{ TExpr, SE } = translate_each(Expr, S),
elixir_tree_helpers:convert_to_boolean(?line(Meta), TExpr, false, S#elixir_scope.context == guard, SE);
elixir_utils:convert_to_boolean(?line(Meta), TExpr, false, S#elixir_scope.context == guard, SE);
translate({ in, Meta, [Left, Right] }, #elixir_scope{extra_guards=nil} = S) ->
{ _, TExpr, TS } = translate_in(Meta, Left, Right, S),
@@ -47,42 +47,6 @@ translate({ in, Meta, [Left, Right] }, #elixir_scope{extra_guards=Extra} = S) ->
{ TVar, TExpr, TS } = translate_in(Meta, Left, Right, S),
{ TVar, TS#elixir_scope{extra_guards=[TExpr|Extra]} };
%% Functions
translate({ function, Meta, [[{do,{ '->',_,Pairs}}]] }, S) ->
assert_no_match_or_guard_scope(Meta, 'function', S),
elixir_translator:translate_fn(Meta, Pairs, S);
translate({ function, _, [{ '/', _, [{{ '.', Meta, [M, F] }, _ , []}, A]}] }, S) when is_atom(F), is_integer(A) ->
translate({ function, Meta, [M, F, A] }, S);
translate({ function, MetaFA, [{ '/', _, [{F, Meta, C}, A]}] }, S) when is_atom(F), is_integer(A), is_atom(C) ->
assert_no_match_or_guard_scope(Meta, 'function', S),
WrappedMeta =
case lists:keyfind(import_fa, 1, MetaFA) of
{ import_fa, { Receiver, Context } } ->
lists:keystore(context, 1,
lists:keystore(import, 1, Meta, { import, Receiver }),
{ context, Context }
);
false -> Meta
end,
case elixir_dispatch:import_function(WrappedMeta, F, A, S) of
false -> syntax_error(WrappedMeta, S#elixir_scope.file, "expected ~ts/~B to be a function, but it is a macro", [F, A]);
Else -> Else
end;
translate({ function, Meta, [_] }, S) ->
assert_no_match_or_guard_scope(Meta, 'function', S),
syntax_error(Meta, S#elixir_scope.file, "invalid args for function");
translate({ function, Meta, [_,_,_] = Args }, S) when is_list(Args) ->
assert_no_match_or_guard_scope(Meta, 'function', S),
{ [A,B,C], SA } = translate_args(Args, S),
{ { 'fun', ?line(Meta), { function, A, B, C } }, SA };
%% @
translate({'@', Meta, [{ Name, _, [Arg] }]}, S) when Name == typep; Name == type; Name == spec; Name == callback; Name == opaque ->
@@ -123,70 +87,21 @@ translate({'@', Meta, [{ Name, _, Args }]}, S) ->
}, S);
_ ->
Contents = 'Elixir.Module':get_attribute(S#elixir_scope.module, Name),
{ elixir_tree_helpers:elixir_to_erl(Contents), S }
{ elixir_utils:elixir_to_erl(?line(Meta), Contents, S), S }
end;
_ ->
syntax_error(Meta, S#elixir_scope.file, "expected 0 or 1 argument for @~ts, got: ~p", [Name, length(Args)])
end
end;
%% Binding
translate({ 'binding', Meta, [] }, S) ->
translate({ 'binding', Meta, [false] }, S);
translate({ 'binding', Meta, [false] }, S) ->
Line = ?line(Meta),
{ elixir_tree_helpers:list_to_cons(Line,
[ to_var_value_tuple(Line, Name, Var) || { { Name, nil }, Var } <- S#elixir_scope.vars]), S };
translate({ 'binding', Meta, [true] }, S) ->
Line = ?line(Meta),
{ elixir_tree_helpers:list_to_cons(Line,
[ to_var_value_tuple(Line, Name, Kind, Var) || { { Name, Kind }, Var } <- S#elixir_scope.vars]), S };
translate({ 'binding', Meta, [List] }, #elixir_scope{vars=Vars} = S) when is_list(List) ->
Line = ?line(Meta),
Dict = lists:foldl(fun
(Name, Acc) when is_atom(Name) ->
case orddict:find({ Name, nil }, Vars) of
{ ok, Var } ->
orddict:store(Name, Var, Acc);
error ->
Acc
end;
(Name, _Acc) ->
elixir_errors:syntax_error(Line, S#elixir_scope.file, "binding/1 expects a list of atoms "
"at compilation time, got: ~ts", ['Elixir.Macro':to_string(Name)])
end, [], List),
{ elixir_tree_helpers:list_to_cons(Line,
[ to_var_value_tuple(Line, Name, Var) || { Name, Var } <- Dict]), S };
translate({ 'binding', Meta, [List, true] }, #elixir_scope{vars=Vars} = S) when is_list(List) ->
Line = ?line(Meta),
Dict = lists:foldl(fun
(Tuple, Acc) when is_tuple(Tuple) ->
case orddict:find(Tuple, Vars) of
{ ok, Var } ->
orddict:store(Tuple, Var, Acc);
error ->
Acc
end;
(Tuple, _Acc) ->
elixir_errors:syntax_error(Line, S#elixir_scope.file, "binding/2 expects a list of tuples "
"at compilation time, got: ~ts", ['Elixir.Macro':to_string(Tuple)])
end, [], List),
{ elixir_tree_helpers:list_to_cons(Line,
[ to_var_value_tuple(Line, Name, Kind, Var) || { { Name, Kind }, Var } <- Dict]), S };
%% Case
translate({'case', Meta, [Expr, KV]}, S) ->
translate({'case', Meta, [Expr, KV]}, S) when is_list(KV) ->
assert_no_match_or_guard_scope(Meta, 'case', S),
Clauses = elixir_clauses:get_pairs(Meta, do, KV, S),
{ TExpr, NS } = translate_each(Expr, S),
RClauses = case elixir_tree_helpers:returns_boolean(TExpr) of
RClauses = case elixir_utils:returns_boolean(TExpr) of
true -> rewrite_case_clauses(Clauses);
false -> Clauses
end,
@@ -196,27 +111,27 @@ translate({'case', Meta, [Expr, KV]}, S) ->
%% Try
translate({'try', Meta, [Clauses]}, S) ->
translate({'try', Meta, [Clauses]}, S) when is_list(Clauses) ->
assert_no_match_or_guard_scope(Meta, 'try', S),
Do = proplists:get_value('do', Clauses, nil),
{ TDo, SB } = elixir_translator:translate_each(Do, S#elixir_scope{noname=true}),
Catch = [Tuple || { X, _ } = Tuple <- Clauses, X == 'rescue' orelse X == 'catch'],
{ TCatch, SC } = elixir_try:clauses(Meta, Catch, umergec(S, SB)),
{ TCatch, SC } = elixir_try:clauses(Meta, Catch, umergea(S, SB)),
After = proplists:get_value('after', Clauses, nil),
{ TAfter, SA } = elixir_translator:translate_each(After, umergec(S, SC)),
{ TAfter, SA } = elixir_translator:translate_each(After, umergea(S, SC)),
Else = elixir_clauses:get_pairs(Meta, else, Clauses, S),
{ TElse, SE } = elixir_clauses:match(Meta, Else, umergec(S, SA)),
{ TElse, SE } = elixir_clauses:match(Meta, Else, umergea(S, SA)),
SF = (umergec(S, SE))#elixir_scope{noname=S#elixir_scope.noname},
{ { 'try', ?line(Meta), pack(TDo), TElse, TCatch, pack(TAfter) }, SF };
%% Receive
translate({'receive', Meta, [KV] }, S) ->
translate({'receive', Meta, [KV] }, S) when is_list(KV) ->
assert_no_match_or_guard_scope(Meta, 'receive', S),
Do = elixir_clauses:get_pairs(Meta, do, KV, S, true),
@@ -227,19 +142,19 @@ translate({'receive', Meta, [KV] }, S) ->
_ ->
After = elixir_clauses:get_pairs(Meta, 'after', KV, S),
{ TClauses, SC } = elixir_clauses:match(Meta, Do ++ After, S),
{ FClauses, TAfter } = elixir_tree_helpers:split_last(TClauses),
{ FClauses, TAfter } = elixir_utils:split_last(TClauses),
{ _, _, [FExpr], _, FAfter } = TAfter,
{ { 'receive', ?line(Meta), FClauses, FExpr, FAfter }, SC }
end;
%% Definitions
translate({defmodule, Meta, [Ref, KV]}, S) ->
translate({defmodule, Meta, [Ref, KV]}, S) when is_list(KV) ->
{ TRef, _ } = translate_each(Ref, S),
Block = case lists:keyfind(do, 1, KV) of
{ do, DoValue } -> DoValue;
false -> syntax_error(Meta, S#elixir_scope.file, "expected do: argument in defmodule")
false -> syntax_error(Meta, S#elixir_scope.file, "missing do keyword in defmodule")
end,
{ FRef, FS } = case TRef of
@@ -275,6 +190,7 @@ translate({Kind, Meta, [Call, Expr]}, S) when ?defs(Kind) ->
{ elixir_def:wrap_definition(Kind, Meta, TCall, TExpr, CheckClauses, SE), SE };
translate({Kind, Meta, [Name, Args, Guards, Expr]}, S) when ?defs(Kind) ->
elixir_errors:deprecation(Meta, S#elixir_scope.file, "~ts/4 is deprecated, please use ~ts/2 instead", [Kind, Kind]),
assert_module_scope(Meta, Kind, S),
assert_no_function_scope(Meta, Kind, S),
{ TName, SN } = translate_each(Name, S),
@@ -292,7 +208,11 @@ translate({ apply, Meta, [Left, Right, Args] }, S) when is_list(Args) ->
translate({ apply, Meta, Args }, S) ->
{ TArgs, NS } = translate_args(Args, S),
{ ?wrap_call(?line(Meta), erlang, apply, TArgs), NS }.
{ ?wrap_call(?line(Meta), erlang, apply, TArgs), NS };
translate({ Name, Meta, Args }, S) ->
syntax_error(Meta, S#elixir_scope.file,
"invalid arguments for macro ~ts/~B", [Name, length(Args)]).
%% Helpers
@@ -315,12 +235,12 @@ translate_in(Meta, Left, Right, S) ->
false -> { TLeft, SR }
end,
{ InGuard, TExpr } = case TRight of
{ TCache, TExpr } = case TRight of
{ nil, _ } ->
Expr = { atom, Line, false },
{ Cache, Expr };
{ cons, _, _, _ } ->
[H|T] = elixir_tree_helpers:cons_to_list(TRight),
[H|T] = elixir_utils:cons_to_list(TRight),
Expr = lists:foldr(fun(X, Acc) ->
{ op, Line, 'orelse', { op, Line, '==', Var, X }, Acc }
end, { op, Line, '==', Var, H }, T),
@@ -351,11 +271,12 @@ translate_in(Meta, Left, Right, S) ->
true ->
{ false, ?wrap_call(Line, 'Elixir.Enum', 'member?', [TRight, TLeft]) };
false ->
syntax_error(Meta, S#elixir_scope.file, "invalid args for operator in, it expects an explicit array or an explicit range on the right side when used in guard expressions")
compile_error(Meta, S#elixir_scope.file, "invalid args for operator in, it expects a "
"compile time list or range on the right side when used in guard expressions")
end
end,
case InGuard of
case TCache of
true -> { Var, { block, Line, [ { match, Line, Var, TLeft }, TExpr ] }, SV };
false -> { Var, TExpr, SV }
end.
@@ -401,19 +322,6 @@ is_reserved_data(moduledoc) -> true;
is_reserved_data(doc) -> true;
is_reserved_data(_) -> false.
to_var_value_tuple(Line, Name, Var) ->
{ tuple, Line,
[ { atom, Line, Name },
{ var, Line, Var } ] }.
to_var_value_tuple(Line, Name, Kind, Var) ->
{ tuple, Line,
[ { tuple, Line, [
{ atom, Line, Name },
{ atom, Line, Kind }
] },
{ var, Line, Var } ] }.
spec_to_macro(type) -> deftype;
spec_to_macro(typep) -> deftypep;
spec_to_macro(opaque) -> defopaque;
+65 -35
View File
@@ -8,7 +8,7 @@
-define(persisted_attr, '__persisted_attributes').
eval_quoted(Module, Quoted, RawBinding, Opts) ->
Binding = binding_for_eval(Module, RawBinding),
Binding = elixir_scope:binding_for_eval(RawBinding, Module),
Scope = scope_for_eval(Module, Opts),
elixir_def:reset_last(Module),
@@ -27,8 +27,6 @@ scope_for_eval(Module, #elixir_scope{} = S) ->
scope_for_eval(Module, Opts) ->
scope_for_eval(Module, elixir:scope_for_eval(Opts)).
binding_for_eval(Module, Binding) -> [{'_@MODULE',Module}|Binding].
%% TABLE METHODS
data_table(Module) ->
@@ -45,7 +43,7 @@ docs_table(Module) ->
translate(Meta, Ref, Block, S) ->
Line = ?line(Meta),
MetaBlock = elixir_tree_helpers:elixir_to_erl(Block),
MetaBlock = elixir_utils:elixir_to_erl(Line, Block, S),
{ MetaS, Vars } = elixir_scope:serialize_with_vars(Line, S),
Args = [{integer, Line, Line}, Ref, MetaBlock, Vars, MetaS],
@@ -61,16 +59,16 @@ compile(Line, Module, Block, Vars, #elixir_scope{context_modules=FileModules} =
end,
File = S#elixir_scope.file,
FileList = binary_to_list(File),
FileList = elixir_utils:characters_to_list(File),
check_module_availability(Line, File, Module, C),
build(Line, File, Module),
try
Result = eval_form(Line, Module, Block, Vars, S),
{ Export, Private, Def, Defmacro, Functions } = elixir_def:unwrap_stored_definitions(FileList, Module),
{ Base, Export, Private, Def, Defmacro, Functions } = elixir_def:unwrap_stored_definitions(FileList, Module),
{ All, Forms0 } = functions_form(Line, File, Module, Export, Private, Def, Defmacro, Functions, C),
{ All, Forms0 } = functions_form(Line, File, Module, Base, Export, Def, Defmacro, Functions, C),
Forms1 = specs_form(Line, Module, Private, Defmacro, Forms0, C),
Forms2 = attributes_form(Line, File, Module, Forms1),
@@ -80,11 +78,11 @@ compile(Line, Module, Block, Vars, #elixir_scope{context_modules=FileModules} =
[elixir_tracker:record_local(Tuple, Module) || Tuple <- OnLoad]
end,
elixir_tracker:warn_unused_local(File, Module, Private),
elixir_tracker:ensure_no_import_conflict(Line, File, Module, All),
AllFunctions = Def ++ [T || { T, defp, _, _, _ } <- Private],
elixir_tracker:ensure_no_function_conflict(Line, File, Module, AllFunctions),
elixir_tracker:ensure_all_imports_used(Line, File, Module),
elixir_import:ensure_no_local_conflict(Line, File, Module, All),
elixir_tracker:warn_unused_local(File, Module, Private),
warn_unused_docs(Line, File, Module, All),
Final = [
{ attribute, Line, file, { FileList, Line } },
@@ -151,18 +149,20 @@ eval_form(Line, Module, Block, Vars, RawS) ->
%% Return the form with exports and function declarations.
functions_form(Line, File, Module, Export, Private, Def, Defmacro, RawFunctions, C) ->
Functions = case elixir_compiler:get_opt(internal, C) of
functions_form(Line, File, Module, BaseAll, BaseExport, Def, Defmacro, RawFunctions, C) ->
BaseFunctions = case elixir_compiler:get_opt(internal, C) of
true -> RawFunctions;
false -> record_rewrite_functions(Module, RawFunctions)
end,
{ FinalExport, FinalFunctions } =
add_info_function(Line, File, Module, Export, Functions, Def, Defmacro, C),
Info = add_info_function(Line, File, Module, BaseExport, Def, Defmacro, C),
PrivateTuple = [Tuple || { Tuple, _, _, _, _ } <- Private],
{ FinalExport ++ PrivateTuple, [
{attribute, Line, export, lists:sort(FinalExport)} | FinalFunctions
All = [{ '__info__', 1 }|BaseAll],
Export = [{ '__info__', 1 }|BaseExport],
Functions = [Info|BaseFunctions],
{ All, [
{ attribute, Line, export, lists:sort(Export) } | Functions
] }.
record_rewrite_functions(Module, Functions) ->
@@ -246,8 +246,8 @@ spec_for_macro(Else) -> Else.
%% Loads the form into the code server.
load_form(Line, Forms, S) ->
elixir_compiler:module(Forms, S, fun(Module, Binary) ->
load_form(Line, Forms, #elixir_scope{file=File} = S) ->
elixir_compiler:module(Forms, File, fun(Module, Binary) ->
EvalS = scope_for_eval(Module, S),
Env = elixir_scope:to_ex_env({ Line, EvalS }),
eval_callbacks(Line, Module, after_compile, [Env, Binary], EvalS),
@@ -255,11 +255,15 @@ load_form(Line, Forms, S) ->
case get(elixir_compiled) of
Current when is_list(Current) ->
put(elixir_compiled, [{Module,Binary}|Current]),
case get(elixir_compiler_pid) of
undefined -> [];
PID -> PID ! { module_available, self(), Module, Binary }
PID ->
PID ! { module_available, self(), File, Module, Binary },
receive { PID, ack } -> ok end
end;
_ -> []
_ ->
[]
end,
Binary
@@ -278,31 +282,43 @@ check_module_availability(Line, File, Module, Compiler) ->
[]
end.
warn_unused_docs(_Line, _File, 'Elixir.Kernel', _All) -> ok;
warn_unused_docs(_Line, _File, 'Elixir.Kernel.SpecialForms', _All) -> ok;
warn_unused_docs(_Line, File, Module, All) ->
ets:foldl(fun
({ Tuple, Line, _, _, _ }, Acc) ->
case lists:member(Tuple, All) of
true -> Acc;
false ->
elixir_errors:handle_file_warning(File, { Line, ?MODULE, { invalid_doc, Tuple } })
end
end, ok, docs_table(Module)).
% EXTRA FUNCTIONS
add_info_function(Line, File, Module, Export, Functions, Def, Defmacro, C) ->
add_info_function(Line, File, Module, Export, Def, Defmacro, C) ->
Pair = { '__info__', 1 },
case lists:member(Pair, Export) of
true -> elixir_errors:form_error(Line, File, ?MODULE, {internal_function_overridden, Pair});
true ->
elixir_errors:form_error(Line, File, ?MODULE, {internal_function_overridden, Pair});
false ->
Docs = elixir_compiler:get_opt(docs, C),
Contents = { function, 0, '__info__', 1, [
{ function, 0, '__info__', 1, [
functions_clause(Def),
macros_clause(Module, Def, Defmacro),
docs_clause(Module, Docs),
moduledoc_clause(Line, Module, Docs),
module_clause(Module),
else_clause()
] },
{ [Pair|Export], [Contents|Functions] }
] }
end.
functions_clause(Def) ->
{ clause, 0, [{ atom, 0, functions }], [], [elixir_tree_helpers:elixir_to_erl(Def)] }.
{ clause, 0, [{ atom, 0, functions }], [], [elixir_utils:elixir_to_erl(Def)] }.
macros_clause(Module, Def, Defmacro) ->
All = handle_builtin_macros(Module, Def, Defmacro),
{ clause, 0, [{ atom, 0, macros }], [], [elixir_tree_helpers:elixir_to_erl(All)] }.
{ clause, 0, [{ atom, 0, macros }], [], [elixir_utils:elixir_to_erl(All)] }.
handle_builtin_macros('Elixir.Kernel', Def, Defmacro) ->
ordsets:subtract(ordsets:union(Defmacro,
@@ -314,14 +330,14 @@ module_clause(Module) ->
docs_clause(Module, true) ->
Docs = ordsets:from_list(ets:tab2list(docs_table(Module))),
{ clause, 0, [{ atom, 0, docs }], [], [elixir_tree_helpers:elixir_to_erl(Docs)] };
{ clause, 0, [{ atom, 0, docs }], [], [elixir_utils:elixir_to_erl(Docs)] };
docs_clause(_Module, _) ->
{ clause, 0, [{ atom, 0, docs }], [], [{ atom, 0, nil }] }.
moduledoc_clause(Line, Module, true) ->
Docs = 'Elixir.Module':get_attribute(Module, moduledoc),
{ clause, 0, [{ atom, 0, moduledoc }], [], [elixir_tree_helpers:elixir_to_erl({ Line, Docs })] };
{ clause, 0, [{ atom, 0, moduledoc }], [], [elixir_utils:elixir_to_erl({ Line, Docs })] };
moduledoc_clause(_Line, _Module, _) ->
{ clause, 0, [{ atom, 0, moduledoc }], [], [{ atom, 0, nil }] }.
@@ -332,8 +348,9 @@ else_clause() ->
% HELPERS
eval_callbacks(Line, Module, Name, Args, S) ->
Binding = binding_for_eval(Module, []),
eval_callbacks(Line, Module, Name, Args, RawS) ->
Binding = elixir_scope:binding_for_eval([], Module),
S = elixir_scope:vars_from_binding(RawS, Binding),
Callbacks = lists:reverse(ets:lookup_element(data_table(Module), Name, 2)),
Meta = [{line,Line},{require,false}],
@@ -351,14 +368,27 @@ eval_callbacks(Line, Module, Name, Args, S) ->
erl_eval:exprs([Tree], Binding)
catch
Kind:Reason ->
Info = { M, F, Args, [{ file, binary_to_list(S#elixir_scope.file) }, { line, Line }] },
erlang:raise(Kind, Reason, [Info|erlang:get_stacktrace()])
Info = { M, F, length(Args), [{ file, elixir_utils:characters_to_list(S#elixir_scope.file) }, { line, Line }] },
erlang:raise(Kind, Reason, munge_stacktrace(Info, erlang:get_stacktrace()))
end
end
end, Callbacks).
%% We've reached the elixir_dispatch internals, skip it with the rest
munge_stacktrace(Info, [{ elixir_module, _, _, _ }|_]) ->
[Info];
munge_stacktrace(Info, [H|T]) ->
[H|munge_stacktrace(Info, T)];
munge_stacktrace(Info, []) ->
[Info].
% ERROR HANDLING
format_error({ invalid_doc, { Name, Arity } }) ->
io_lib:format("docs provided for nonexistent function or macro ~ts/~B", [Name, Arity]);
format_error({ internal_function_overridden, { Name, Arity } }) ->
io_lib:format("function ~ts/~B is internal and should not be overridden", [Name, Arity]);
+158 -102
View File
@@ -1,7 +1,8 @@
Nonterminals
grammar expr_list
expr paren_expr block_expr fn_expr bracket_expr call_expr bracket_at_expr max_expr
base_expr matched_expr matched_op_expr unmatched_expr op_expr
expr container_expr block_expr no_parens_expr identifier_expr max_expr
bracket_expr bracket_at_expr base_expr matched_expr unmatched_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 tail_op_eol
add_op_eol mult_op_eol exp_op_eol two_op_eol type_op_eol stab_op_eol
arrow_op_eol range_op_eol than_op_eol default_op_eol match_op_eol
@@ -10,33 +11,38 @@ Nonterminals
open_bracket close_bracket
open_curly close_curly
open_bit close_bit
base_comma_expr comma_expr optional_comma_expr matched_comma_expr
call_args call_args_parens parens_call
call_args_no_parens call_args_no_parens_strict call_args_parens_not_one
stab stab_eol stab_expr stab_maybe_expr
kw_eol kw_expr kw_comma kw_base
matched_kw_expr matched_kw_comma matched_kw_base
dot_op dot_ref dot_identifier dot_op_identifier dot_do_identifier
dot_paren_identifier dot_punctuated_identifier dot_bracket_identifier
container_comma_expr_base container_comma_expr container_arg container_args
call_args_parens_comma_expr 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
dot_op dot_alias dot_identifier dot_op_identifier dot_do_identifier
dot_paren_identifier dot_bracket_identifier
var list bracket_access bit_string tuple
do_block fn_eol do_eol end_eol block_eol block_item block_list
.
Terminals
identifier kw_identifier punctuated_identifier
bracket_identifier paren_identifier do_identifier block_identifier
identifier kw_identifier bracket_identifier
paren_identifier do_identifier block_identifier
fn 'end' aliases
number signed_number atom bin_string list_string sigil
dot_call_op op_identifier
comp_op at_op unary_op and_op or_op arrow_op match_op
range_op in_op inc_op when_op than_op default_op tail_op
dual_op add_op mult_op exp_op two_op type_op stab_op
'true' 'false' 'nil' 'do' eol ',' '.' '&' '...'
'true' 'false' 'nil' 'do' eol ',' '.' '&'
'(' ')' '[' ']' '{' '}' '<<' '>>'
.
Rootsymbol grammar.
%% There are two shift/reduce conflicts coming from call_args_parens.
Expect 2.
%% Changes in ops and precedence should be reflected on lib/elixir/lib/macro.ex
Left 5 do.
Right 10 stab_op_eol. %% ->
Left 20 ','.
@@ -57,7 +63,7 @@ Left 220 mult_op_eol. %% * (op), / (op)
Right 230 than_op_eol. %% < (op) > (e.g <>)
Right 240 two_op_eol. %% ++, --, **
Left 250 exp_op_eol. %% ^ (op) (e.g ^^^)
Nonassoc 300 unary_op_eol. %% +, -, !, ^, not, ~~~
Nonassoc 300 unary_op_eol. %% +, -, !, ^, not, &, ~~~
Left 310 dot_call_op.
Left 310 dot_op. %% .
Nonassoc 320 at_op_eol. %% @ (op)
@@ -74,27 +80,37 @@ grammar -> '$empty' : [nil].
% Note expressions are on reverse order
expr_list -> expr : ['$1'].
expr_list -> empty_paren : [nil].
expr_list -> expr_list eol expr : ['$3'|'$1'].
paren_expr -> empty_paren : nil.
paren_expr -> expr : '$1'.
expr -> empty_paren : nil.
expr -> matched_expr : '$1'.
expr -> no_parens_expr : '$1'.
expr -> unmatched_expr : '$1'.
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 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 -> fn_expr : '$1'.
matched_expr -> identifier_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').
unmatched_expr -> empty_paren op_expr : build_op(element(1, '$2'), nil, element(2, '$2')).
unmatched_expr -> matched_expr op_expr : build_op(element(1, '$2'), '$1', element(2, '$2')).
unmatched_expr -> unmatched_expr op_expr : build_op(element(1, '$2'), '$1', element(2, '$2')).
unmatched_expr -> unary_op_eol expr : build_unary_op('$1', '$2').
unmatched_expr -> at_op_eol expr : build_unary_op('$1', '$2').
unmatched_expr -> block_expr : '$1'.
block_expr -> parens_call call_args_parens do_block : build_identifier('$1', '$2' ++ '$3').
block_expr -> parens_call call_args_parens call_args_parens do_block : build_nested_parens('$1', '$2', '$3' ++ '$4').
block_expr -> dot_do_identifier do_block : build_identifier('$1', '$2').
block_expr -> dot_identifier call_args_no_parens_all do_block : build_identifier('$1', '$2' ++ '$3').
op_expr -> match_op_eol expr : { '$1', '$2' }.
op_expr -> add_op_eol expr : { '$1', '$2' }.
op_expr -> mult_op_eol expr : { '$1', '$2' }.
@@ -113,6 +129,24 @@ op_expr -> type_op_eol expr : { '$1', '$2' }.
op_expr -> comp_op_eol expr : { '$1', '$2' }.
op_expr -> arrow_op_eol expr : { '$1', '$2' }.
no_parens_op_expr -> match_op_eol no_parens_expr : { '$1', '$2' }.
no_parens_op_expr -> add_op_eol no_parens_expr : { '$1', '$2' }.
no_parens_op_expr -> mult_op_eol no_parens_expr : { '$1', '$2' }.
no_parens_op_expr -> exp_op_eol no_parens_expr : { '$1', '$2' }.
no_parens_op_expr -> two_op_eol no_parens_expr : { '$1', '$2' }.
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 -> tail_op_eol no_parens_expr : { '$1', '$2' }.
no_parens_op_expr -> than_op_eol no_parens_expr : { '$1', '$2' }.
no_parens_op_expr -> in_op_eol no_parens_expr : { '$1', '$2' }.
no_parens_op_expr -> inc_op_eol no_parens_expr : { '$1', '$2' }.
no_parens_op_expr -> when_op_eol no_parens_expr : { '$1', '$2' }.
no_parens_op_expr -> range_op_eol no_parens_expr : { '$1', '$2' }.
no_parens_op_expr -> default_op_eol no_parens_expr : { '$1', '$2' }.
no_parens_op_expr -> type_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' }.
matched_op_expr -> match_op_eol matched_expr : { '$1', '$2' }.
matched_op_expr -> add_op_eol matched_expr : { '$1', '$2' }.
matched_op_expr -> mult_op_eol matched_expr : { '$1', '$2' }.
@@ -131,29 +165,19 @@ matched_op_expr -> type_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' }.
block_expr -> parens_call call_args_parens do_block : build_identifier('$1', '$2' ++ '$3').
block_expr -> parens_call call_args_parens call_args_parens do_block : build_nested_parens('$1', '$2', '$3' ++ '$4').
block_expr -> dot_punctuated_identifier call_args_no_parens_strict do_block : build_identifier('$1', '$2' ++ '$3').
block_expr -> dot_do_identifier do_block : build_identifier('$1', '$2').
block_expr -> dot_identifier call_args_no_parens_strict do_block : build_identifier('$1', '$2' ++ '$3').
fn_expr -> fn_eol stab end_eol : build_fn('$1', build_stab(lists:reverse('$2'))).
fn_expr -> call_expr : '$1'.
call_expr -> dot_punctuated_identifier call_args_no_parens_strict : build_identifier('$1', '$2').
call_expr -> dot_op_identifier call_args_no_parens_strict : build_identifier('$1', '$2').
call_expr -> dot_identifier call_args_no_parens_strict : build_identifier('$1', '$2').
call_expr -> dot_punctuated_identifier : build_identifier('$1', []).
call_expr -> dot_do_identifier : build_identifier('$1', nil).
call_expr -> var : build_identifier('$1', nil).
call_expr -> max_expr : '$1'.
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'.
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'.
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_ref : '$1'.
max_expr -> dot_alias : '$1'.
max_expr -> base_expr : '$1'.
max_expr -> open_paren stab close_paren : build_stab(lists:reverse('$2')).
bracket_expr -> dot_bracket_identifier bracket_access : build_access(build_identifier('$1', nil), '$2').
bracket_expr -> max_expr bracket_access : build_access('$1', '$2').
@@ -177,9 +201,8 @@ base_expr -> aliases : { '__aliases__', [{line,?line('$1')}], ?exprs('$1') }.
base_expr -> bin_string : build_bin_string('$1').
base_expr -> list_string : build_list_string('$1').
base_expr -> bit_string : '$1'.
base_expr -> '&' : { '&', [{line,?line('$1')}], ?exprs('$1') }.
base_expr -> '...' : { ?id('$1'), [{line,?line('$1')}], [] }.
base_expr -> sigil : build_sigil('$1').
base_expr -> '&' number : { '&', [{line,?line('$1')}], [?exprs('$2')] }.
%% Blocks
@@ -208,12 +231,12 @@ stab_eol -> stab eol : '$1'.
stab_expr -> expr : '$1'.
stab_expr -> stab_op_eol stab_maybe_expr : build_op('$1', [], '$2').
stab_expr -> call_args_no_parens stab_op_eol stab_maybe_expr :
stab_expr -> call_args_no_parens_all stab_op_eol stab_maybe_expr :
build_op('$2', unwrap_when(unwrap_splice('$1')), '$3').
stab_expr -> call_args_parens_not_one stab_op_eol stab_maybe_expr :
stab_expr -> stab_parens_many stab_op_eol stab_maybe_expr :
build_op('$2', unwrap_splice('$1'), '$3').
stab_expr -> call_args_parens_not_one when_op matched_expr stab_op_eol stab_maybe_expr :
build_op('$4', [{ 'when', [{line,?line('$2')}], unwrap_splice('$1') ++ ['$3'] }], '$5').
stab_expr -> stab_parens_many when_op expr stab_op_eol stab_maybe_expr :
build_op('$4', [{ 'when', [{line,?line('$2')}], unwrap_splice('$1') ++ ['$3'] }], '$5').
stab_maybe_expr -> 'expr' : '$1'.
stab_maybe_expr -> '$empty' : nil.
@@ -324,7 +347,7 @@ dot_op -> '.' eol : '$1'.
dot_identifier -> identifier : '$1'.
dot_identifier -> matched_expr dot_op identifier : build_dot('$2', '$1', '$3').
dot_ref -> matched_expr dot_op aliases : build_dot_ref('$2', '$1', '$3').
dot_alias -> matched_expr dot_op aliases : build_dot_alias('$2', '$1', '$3').
dot_op_identifier -> op_identifier : '$1'.
dot_op_identifier -> matched_expr dot_op op_identifier : build_dot('$2', '$1', '$3').
@@ -338,87 +361,105 @@ dot_bracket_identifier -> matched_expr dot_op bracket_identifier : build_dot('$2
dot_paren_identifier -> paren_identifier : '$1'.
dot_paren_identifier -> matched_expr dot_op paren_identifier : build_dot('$2', '$1', '$3').
dot_punctuated_identifier -> punctuated_identifier : '$1'.
dot_punctuated_identifier -> matched_expr dot_op punctuated_identifier : build_dot('$2', '$1', '$3').
parens_call -> dot_paren_identifier : '$1'.
parens_call -> matched_expr dot_call_op : { '.', [{line,?line('$2')}], ['$1'] }. % Fun/local calls
% Function calls
% Function calls with no parentheses
matched_comma_expr -> matched_expr : ['$1'].
matched_comma_expr -> matched_comma_expr ',' matched_expr : ['$3'|'$1'].
call_args_no_parens_expr -> matched_expr : '$1'.
call_args_no_parens_expr -> empty_paren : nil.
call_args_no_parens_expr -> no_parens_expr : throw_no_parens_many_strict('$1').
call_args_no_parens_strict -> call_args_no_parens : '$1'.
call_args_no_parens_strict -> empty_paren : throw_no_parens_strict('$1').
call_args_no_parens_strict -> open_paren matched_kw_base close_paren : throw_no_parens_strict('$1').
call_args_no_parens_strict -> open_paren matched_expr ',' call_args_no_parens close_paren : throw_no_parens_strict('$1').
call_args_no_parens_comma_expr -> matched_expr ',' call_args_no_parens_expr : ['$3', '$1'].
call_args_no_parens_comma_expr -> call_args_no_parens_comma_expr ',' call_args_no_parens_expr : ['$3'|'$1'].
call_args_no_parens -> matched_comma_expr : lists:reverse('$1').
call_args_no_parens -> matched_kw_base : ['$1'].
call_args_no_parens -> matched_comma_expr ',' matched_kw_base : lists:reverse(['$3'|'$1']).
call_args_no_parens_all -> call_args_no_parens_one : '$1'.
call_args_no_parens_all -> call_args_no_parens_many : '$1'.
call_args_parens_not_one -> empty_paren : [].
call_args_parens_not_one -> open_paren matched_kw_base close_paren : ['$2'].
call_args_parens_not_one -> open_paren matched_expr ',' call_args_no_parens close_paren : ['$2'|'$4'].
call_args_no_parens_one -> call_args_no_parens_kw : ['$1'].
call_args_no_parens_one -> matched_expr : ['$1'].
call_args_no_parens_one -> no_parens_expr : ['$1'].
base_comma_expr -> paren_expr ',' : ['$1'].
base_comma_expr -> base_comma_expr paren_expr ',' : ['$2'|'$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']).
comma_expr -> paren_expr : ['$1'].
comma_expr -> kw_base : ['$1'].
comma_expr -> base_comma_expr : '$1'.
comma_expr -> base_comma_expr paren_expr : ['$2'|'$1'].
comma_expr -> base_comma_expr kw_base : ['$2'|'$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').
call_args_no_parens_many_strict -> open_paren call_args_no_parens_kw close_paren : throw_no_parens_strict('$1').
call_args_no_parens_many_strict -> open_paren call_args_no_parens_many close_paren : throw_no_parens_strict('$1').
optional_comma_expr -> paren_expr : '$1'.
optional_comma_expr -> paren_expr ',' : '$1'.
stab_parens_many -> empty_paren : [].
stab_parens_many -> open_paren call_args_no_parens_kw close_paren : ['$2'].
stab_parens_many -> open_paren call_args_no_parens_many close_paren : '$2'.
call_args -> comma_expr : lists:reverse('$1').
% Containers and function calls with parentheses
container_expr -> empty_paren : nil.
container_expr -> matched_expr : '$1'.
container_expr -> unmatched_expr : '$1'.
container_expr -> no_parens_expr : throw_no_parens_many_strict('$1').
container_comma_expr_base -> container_expr ',' : ['$1'].
container_comma_expr_base -> container_comma_expr_base container_expr ',' : ['$2'|'$1'].
container_comma_expr -> container_expr : ['$1'].
container_comma_expr -> kw : ['$1'].
container_comma_expr -> container_comma_expr_base : '$1'.
container_comma_expr -> container_comma_expr_base container_expr : ['$2'|'$1'].
container_comma_expr -> container_comma_expr_base kw : ['$2'|'$1'].
container_arg -> container_expr : '$1'.
container_arg -> container_expr ',' : '$1'.
container_args -> container_comma_expr : lists:reverse('$1').
call_args_parens_comma_expr -> container_expr : ['$1'].
call_args_parens_comma_expr -> call_args_parens_comma_expr ',' container_expr : ['$3'|'$1'].
call_args_parens -> empty_paren : [].
call_args_parens -> open_paren call_args close_paren : '$2'.
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_comma_expr close_paren : lists:reverse('$2').
call_args_parens -> open_paren call_args_parens_comma_expr ',' kw close_paren : lists:reverse(['$4'|'$2']).
% KV
kw_eol -> kw_identifier : '$1'.
kw_eol -> kw_identifier eol : '$1'.
kw_expr -> kw_eol paren_expr : { ?exprs('$1'),'$2' }.
kw_expr -> kw_eol container_expr : { ?exprs('$1'),'$2' }.
kw_comma -> kw_expr ',' : ['$1'].
kw_comma -> kw_comma kw_expr ',' : ['$2'|'$1'].
kw -> kw_expr : ['$1'].
kw -> kw_comma : lists:reverse('$1').
kw -> kw_comma kw_expr : lists:reverse(['$2'|'$1']).
kw_base -> kw_expr : ['$1'].
kw_base -> kw_comma : lists:reverse('$1').
kw_base -> kw_comma kw_expr : lists:reverse(['$2'|'$1']).
matched_kw_expr -> kw_eol matched_expr : {?exprs('$1'),'$2'}.
matched_kw_expr -> kw_eol empty_paren : {?exprs('$1'),nil}.
matched_kw_comma -> matched_kw_expr : ['$1'].
matched_kw_comma -> matched_kw_expr ',' matched_kw_comma : ['$1'|'$3'].
matched_kw_base -> matched_kw_comma : '$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'.
% Lists
bracket_access -> open_bracket ']' : { [], ?line('$1') }.
bracket_access -> open_bracket optional_comma_expr close_bracket : { '$2', ?line('$1') }.
bracket_access -> open_bracket kw_base close_bracket : { '$2', ?line('$1') }.
bracket_access -> open_bracket container_arg close_bracket : { '$2', ?line('$1') }.
bracket_access -> open_bracket kw close_bracket : { '$2', ?line('$1') }.
list -> open_bracket ']' : [].
list -> open_bracket kw_base close_bracket : '$2'.
list -> open_bracket optional_comma_expr close_bracket : ['$2'].
list -> open_bracket paren_expr ',' call_args close_bracket : ['$2'|'$4'].
list -> open_bracket kw close_bracket : '$2'.
list -> open_bracket container_arg close_bracket : ['$2'].
list -> open_bracket container_expr ',' container_args close_bracket : ['$2'|'$4'].
% Tuple
tuple -> open_curly '}' : build_tuple('$1', []).
tuple -> open_curly optional_comma_expr close_curly : build_tuple('$1', ['$2']).
tuple -> open_curly paren_expr ',' call_args close_curly : build_tuple('$1', ['$2'|'$4']).
tuple -> open_curly container_arg close_curly : build_tuple('$1', ['$2']).
tuple -> open_curly container_expr ',' container_args close_curly : build_tuple('$1', ['$2'|'$4']).
% Bitstrings
bit_string -> open_bit '>>' : { '<<>>', [{line,?line('$1')}], [] }.
bit_string -> open_bit call_args close_bit : { '<<>>', [{line,?line('$1')}], '$2' }.
bit_string -> open_bit container_args close_bit : { '<<>>', [{line,?line('$1')}], '$2' }.
Erlang code.
@@ -435,6 +476,12 @@ Erlang code.
%% Operators
build_op({ _Kind, Line, '/' }, { '&', _, [{ Kind, _, Atom } = Left] }, Right) when is_number(Right), is_atom(Atom), is_atom(Kind) ->
{ '&', [{line,Line}], [{ '/', [{line,Line}], [Left, Right] }] };
build_op({ _Kind, Line, '/' }, { '&', _, [{ { '.', _, [_, _] }, _, [] } = Left] }, Right) when is_number(Right) ->
{ '&', [{line,Line}], [{ '/', [{line,Line}], [Left, Right] }] };
build_op({ _Kind, Line, BOp }, { UOp, _, [Left] }, Right) when ?rearrange_bop(BOp), ?rearrange_uop(UOp) ->
{ UOp, [{line,Line}], [{ BOp, [{line,Line}], [Left, Right] }] };
@@ -452,19 +499,18 @@ build_tuple(Marker, Args) ->
%% Blocks
build_block([nil]) -> { '__block__', [], [nil] };
build_block([{Op,_,[_]}]=Exprs) when ?rearrange_uop(Op) -> { '__block__', [], Exprs };
build_block([{unquote_splicing,_,Args}]=Exprs) when
length(Args) =< 2 -> { '__block__', [], Exprs };
build_block([Expr]) when not is_list(Expr) -> Expr;
build_block([Expr]) -> Expr;
build_block(Exprs) -> { '__block__', [], Exprs }.
%% Dots
build_dot_ref(Dot, { '__aliases__', _, Left }, { 'aliases', _, Right }) ->
build_dot_alias(Dot, { '__aliases__', _, Left }, { 'aliases', _, Right }) ->
{ '__aliases__', [{line,?line(Dot)}], Left ++ Right };
build_dot_ref(Dot, Other, { 'aliases', _, Right }) ->
build_dot_alias(Dot, Other, { 'aliases', _, Right }) ->
{ '__aliases__', [{line,?line(Dot)}], [Other|Right] }.
build_dot(Dot, Left, Right) ->
@@ -486,15 +532,15 @@ build_identifier({ '.', Meta, _ } = Dot, Args) ->
build_identifier({ Keyword, Line }, Args) when Keyword == fn ->
{ fn, [{line,Line}], Args };
build_identifier({ op_identifier, Line, Identifier }, Args) ->
{ '__ambiguousop__', [{line,Line}], [{ Identifier, [{line,Line}], nil }|Args] };
build_identifier({ op_identifier, Line, Identifier }, [Arg]) ->
{ Identifier, [{ambiguous_op,nil},{line,Line}], [Arg] };
build_identifier({ _, Line, Identifier }, Args) ->
{ Identifier, [{line,Line}], Args }.
extract_identifier({ Kind, _, Identifier }) when
Kind == identifier; Kind == punctuated_identifier; Kind == bracket_identifier;
Kind == paren_identifier; Kind == do_identifier; Kind == op_identifier ->
Kind == identifier; Kind == bracket_identifier; Kind == paren_identifier;
Kind == do_identifier; Kind == op_identifier ->
Identifier;
extract_identifier(Other) -> Other.
@@ -519,10 +565,10 @@ build_sigil({ sigil, Line, Sigil, Parts, Modifiers }) ->
build_bin_string({ bin_string, _Line, [H] }) when is_binary(H) -> H;
build_bin_string({ bin_string, Line, Args }) -> { '<<>>', [{line,Line}], Args }.
build_list_string({ list_string, _Line, [H] }) when is_binary(H) -> unicode:characters_to_list(H);
build_list_string({ list_string, _Line, [H] }) when is_binary(H) -> elixir_utils:characters_to_list(H);
build_list_string({ list_string, Line, Args }) ->
Meta = [{line,Line}],
{ { '.', Meta, [unicode, characters_to_list] }, Meta, [{ '<<>>', Meta, Args}] }.
{ { '.', Meta, ['Elixir.String', 'to_char_list!'] }, Meta, [{ '<<>>', Meta, Args}] }.
build_atom({ atom, _Line, Atom }) when is_atom(Atom) -> Atom;
build_atom({ atom, Line, Args }) ->
@@ -561,7 +607,7 @@ unwrap_splice([{ '__block__', [], [{ unquote_splicing, _, _ }] = Splice }]) ->
unwrap_splice(Other) -> Other.
unwrap_when(Args) ->
case elixir_tree_helpers:split_last(Args) of
case elixir_utils:split_last(Args) of
{ Start, { 'when', Meta, [_, _] = End } } ->
[{ 'when', Meta, Start ++ End }];
{ _, _ } ->
@@ -574,6 +620,16 @@ throw(Line, Error, Token) ->
throw({ error, { Line, ?MODULE, [Error, Token] }}).
throw_no_parens_strict(Token) ->
throw(?line(Token), "invalid comma inside parenthesis. If you are making a "
throw(?line(Token), "unexpected parenthesis. If you are making a "
"function call, do not insert spaces in between the function name and the "
"opening parentheses. Syntax error before: ", ")").
"opening parentheses. Syntax error before: ", "'('").
throw_no_parens_many_strict(Token) ->
Line =
case lists:keyfind(line, 1, element(2, Token)) of
{ line, L } -> L;
false -> 0
end,
throw(Line, "unexpected comma. Parentheses are required to solve ambiguity "
"in nested calls. Syntax error before: ", "','").
+2 -2
View File
@@ -11,7 +11,7 @@ handle({ _, Meta, Args } = Original, S, Opt) when is_list(Args), S#elixir_scope.
{ Call, Def, SC } when Def /= [] ->
Final = validate(Meta, Def, SC),
Block = setelement(3, Original, Call),
elixir_translator:translate_fn(Meta, [{ Final, Meta, Block }], SC);
elixir_fn:fn(Meta, [{ Final, Meta, Block }], SC);
_ -> error
end;
@@ -41,7 +41,7 @@ convert([{'|', Meta, [_, _] = Args}|T], S, allow_tail, CallAcc, DefAcc) ->
{ NewArgs, NewDef, NewS } = convert(Args, S, allow_tail, [], DefAcc),
convert(T, NewS, allow_tail, [{ '|', Meta, NewArgs}|CallAcc], NewDef);
convert([{'&', Meta, [Pos]}|T], S, Opt, CallAcc, DefAcc) ->
convert([{'&', Meta, [Pos]}|T], S, Opt, CallAcc, DefAcc) when is_integer(Pos) ->
case lists:keyfind(Pos, 1, DefAcc) of
false ->
{ Var, SC } = elixir_scope:build_ex_var(?line(Meta), S),
+93 -36
View File
@@ -1,6 +1,6 @@
%% Implements Elixir quote.
-module(elixir_quote).
-export([escape/2, erl_escape/3, erl_quote/4, linify/2, unquote/5]).
-export([escape/2, erl_escape/3, erl_quote/4, linify/2, unquote/6, join/5]).
-include("elixir.hrl").
%% Apply the line from site call on quoted contents.
@@ -9,7 +9,7 @@ linify(Line, Exprs) when is_integer(Line) ->
do_linify(Line, { Left, Meta, Right }) ->
NewMeta = case ?line(Meta) of
0 -> lists:keystore(line, 1, Meta, { line, Line });
0 -> keystore(line, Meta, Line);
_ -> Meta
end,
{ do_linify(Line, Left), NewMeta, do_linify(Line, Right) };
@@ -24,10 +24,10 @@ do_linify(_, Else) -> Else.
%% Some expressions cannot be unquoted at compilation time.
%% This function is responsible for doing runtime unquoting.
unquote(_File, Meta, Left, { '__aliases__', _, Args }, nil) ->
unquote(_File, _Line, Meta, Left, { '__aliases__', _, Args }, nil) ->
{ '__aliases__', Meta, [Left|Args] };
unquote(_File, Meta, Left, Right, nil) when is_atom(Right) ->
unquote(_File, _Line, Meta, Left, Right, nil) when is_atom(Right) ->
case atom_to_list(Right) of
"Elixir." ++ _ ->
{ '__aliases__', Meta, [Left, Right] };
@@ -35,11 +35,32 @@ unquote(_File, Meta, Left, Right, nil) when is_atom(Right) ->
{ { '.', Meta, [Left, Right] }, Meta, [] }
end;
unquote(_File, Meta, Left, Right, Args) when is_atom(Right) ->
unquote(_File, _Line, Meta, Left, { Right, _, Context }, nil) when is_atom(Right), is_atom(Context) ->
{ { '.', Meta, [Left, Right] }, Meta, [] };
unquote(_File, _Line, Meta, Left, { Right, _, Args }, nil) when is_atom(Right) ->
{ { '.', Meta, [Left, Right] }, Meta, Args };
unquote(File, Meta, _Left, _Right, _Args) ->
elixir_errors:syntax_error(Meta, File, "expected unquote after dot to return an atom or an alias").
unquote(File, Line, _Meta, _Left, Right, nil) ->
elixir_errors:syntax_error(Line, File, "expected unquote after dot to return an atom, "
"an alias or a quoted call, got: ~ts", ['Elixir.Macro':to_string(Right)]);
unquote(_File, _Line, Meta, Left, Right, Args) when is_atom(Right) ->
{ { '.', Meta, [Left, Right] }, Meta, Args };
unquote(_File, _Line, Meta, Left, { Right, _, Context }, Args) when is_atom(Right), is_atom(Context) ->
{ { '.', Meta, [Left, Right] }, Meta, Args };
unquote(File, Line, _Meta, _Left, Right, _Args) ->
elixir_errors:syntax_error(Line, File, "expected unquote after dot with args to return an atom "
"or a quoted call, got: ~ts", ['Elixir.Macro':to_string(Right)]).
join(_File, _Line, Left, Right, Rest) when is_list(Left), is_list(Right), is_list(Rest) ->
Rest ++ Left ++ Right;
join(_File, _Line, Left, Right, Rest) ->
[H|T] = lists:reverse(Rest ++ Left),
lists:reverse([{ '|', [], [H, Right] }|T]).
%% Escapes the given expression. It is similar to quote, but
%% lines are kept and hygiene mechanisms are disabled.
@@ -50,7 +71,7 @@ escape(Expr, Unquote) ->
aliases_hygiene=false,
imports_hygiene=false,
unquote=Unquote,
mark=false
escape=true
}, nil).
erl_escape(Expr, Unquote, S) ->
@@ -92,12 +113,12 @@ do_quote({ unquote, _Meta, [Expr] }, #elixir_quote{unquote=true} = Q, _) ->
%% Context mark
do_quote({ Def, Meta, Args }, #elixir_quote{mark=true} = Q, S) when ?defs(Def); Def == defmodule; Def == alias; Def == import ->
NewMeta = lists:keystore(context, 1, Meta, { context, Q#elixir_quote.context }),
do_quote({ Def, Meta, Args }, #elixir_quote{escape=false} = Q, S) when ?defs(Def); Def == defmodule; Def == alias; Def == import ->
NewMeta = keystore(context, Meta, Q#elixir_quote.context),
do_quote_tuple({ Def, NewMeta, Args }, Q, S);
do_quote({ { '.', _, [_, Def] } = Target, Meta, Args }, #elixir_quote{mark=true} = Q, S) when ?defs(Def); Def == defmodule; Def == alias; Def == import ->
NewMeta = lists:keystore(context, 1, Meta, { context, Q#elixir_quote.context }),
do_quote({ { '.', _, [_, Def] } = Target, Meta, Args }, #elixir_quote{escape=false} = Q, S) when ?defs(Def); Def == defmodule; Def == alias; Def == import ->
NewMeta = keystore(context, Meta, Q#elixir_quote.context),
do_quote_tuple({ Target, NewMeta, Args }, Q, S);
%% Aliases
@@ -111,7 +132,7 @@ do_quote({ '__aliases__', Meta, [H|T] } = Alias, #elixir_quote{aliases_hygiene=t
Atom when is_atom(Atom) -> Atom;
Aliases when is_list(Aliases) -> false
end,
AliasMeta = lists:keystore(alias, 1, Meta, { alias, Annotation }),
AliasMeta = keystore(alias, Meta, Annotation),
do_quote_tuple({ '__aliases__', AliasMeta, [H|T] }, Q, S);
%% Vars
@@ -129,31 +150,33 @@ do_quote({ { '.', Meta, [Left, unquote] }, _, [Expr] }, #elixir_quote{unquote=tr
%% Imports
do_quote({ function, Meta, [{ '/', _, [{F, _, C}, A]}] = Args },
do_quote({ '&', Meta, [{ '/', _, [{F, _, C}, A]}] = Args },
#elixir_quote{imports_hygiene=true} = Q, S) when is_atom(F), is_integer(A), is_atom(C) ->
do_quote_fa(function, Meta, Args, F, A, Q, S);
do_quote_fa('&', Meta, Args, F, A, Q, S);
do_quote({ { '.', _, [_, function] } = Target, Meta, [{ '/', _, [{F, _, C}, A]}] = Args },
#elixir_quote{imports_hygiene=true} = Q, S) when is_atom(F), is_integer(A), is_atom(C) ->
do_quote_fa(Target, Meta, Args, F, A, Q, S);
do_quote({ Name, Meta, ArgsOrAtom } = Tuple, #elixir_quote{imports_hygiene=true} = Q, S) when is_atom(Name) ->
do_quote({ Name, Meta, ArgsOrAtom }, #elixir_quote{imports_hygiene=true} = Q, S) when is_atom(Name) ->
Arity = case is_atom(ArgsOrAtom) of
true -> 0;
false -> length(ArgsOrAtom)
end,
case (lists:keyfind(import, 1, Meta) == false) andalso
case (keyfind(import, Meta) == false) andalso
elixir_dispatch:find_import(Meta, Name, Arity, S) of
false -> do_quote_tuple(Tuple, Q, S);
false ->
AmbMeta =
case (Arity == 1) andalso keyfind(ambiguous_op, Meta) of
{ ambiguous_op, nil } -> keystore(ambiguous_op, Meta, Q#elixir_quote.context);
_ -> Meta
end,
do_quote_tuple({ Name, AmbMeta, ArgsOrAtom }, Q, S);
Receiver ->
ImportMeta = lists:keystore(import, 1,
lists:keystore(context, 1, Meta, { context, Q#elixir_quote.context }),
{ import, Receiver }),
ImportMeta = keystore(import,
keystore(context, Meta, Q#elixir_quote.context),
Receiver),
do_quote_tuple({ Name, ImportMeta, ArgsOrAtom }, Q, S)
end;
do_quote({ _, _, _ } = Tuple, Q, S) ->
do_quote({ _, _, _ } = Tuple, #elixir_quote{escape=false} = Q, S) ->
do_quote_tuple(Tuple, Q, S);
%% Literals
@@ -168,12 +191,22 @@ do_quote({ Left, Right }, Q, S) ->
{ TRight, RQ } = do_quote(Right, LQ, S),
{ { TLeft, TRight }, RQ };
do_quote(Tuple, Q, S) when is_tuple(Tuple) ->
do_quote(Tuple, #elixir_quote{escape=true} = Q, S) when is_tuple(Tuple) ->
{ TT, TQ } = do_quote(tuple_to_list(Tuple), Q, S),
{ { '{}', [], TT }, TQ };
do_quote(List, #elixir_quote{escape=true} = Q, S) when is_list(List) ->
% The improper case is pretty inefficient, but improper lists are hopefully
% rare.
case reverse_improper(List) of
{ L } -> do_splice(L, Q, S);
{ L, R } ->
{ TL, QL } = do_splice(L, Q, S, [], []),
{ TR, QR } = do_quote(R, QL, S),
{ update_last(TL, fun(X) -> { '|', [], [X, TR] } end), QR }
end;
do_quote(List, Q, S) when is_list(List) ->
do_splice(lists:reverse(List), Q, S);
do_splice(lists:reverse(List), Q, S);
do_quote(Other, Q, _) ->
{ Other, Q }.
@@ -181,16 +214,16 @@ do_quote(Other, Q, _) ->
%% Quote helpers
do_quote_call(Left, Meta, Expr, Args, Q, S) ->
All = [meta(Meta, Q), Left, { unquote, Meta, [Expr] }, Args],
All = [?line(Meta), meta(Meta, Q), Left, { unquote, Meta, [Expr] }, Args],
{ TAll, TQ } = lists:mapfoldl(fun(X, Acc) -> do_quote(X, Acc, S) end, Q, All),
{ { { '.', Meta, [elixir_quote, unquote] }, Meta, [{ '__FILE__', [], nil }|TAll] }, TQ }.
do_quote_fa(Target, Meta, Args, F, A, Q, S) ->
NewMeta =
case (lists:keyfind(import_fa, 1, Meta) == false) andalso
case (keyfind(import_fa, Meta) == false) andalso
elixir_dispatch:find_import(Meta, F, A, S) of
false -> Meta;
Receiver -> lists:keystore(import_fa, 1, Meta, { import_fa, { Receiver, Q#elixir_quote.context } })
Receiver -> keystore(import_fa, Meta, { Receiver, Q#elixir_quote.context })
end,
do_quote_tuple({ Target, NewMeta, Args }, Q, S).
@@ -202,15 +235,39 @@ do_quote_tuple({ Left, Meta, Right }, Q, S) ->
meta(Meta, #elixir_quote{line=keep}) ->
Meta;
meta(Meta, #elixir_quote{line=nil}) ->
lists:keydelete(line, 1, Meta);
keydelete(line, Meta);
meta(Meta, #elixir_quote{line=Line}) ->
lists:keystore(line, 1, Meta, { line, Line }).
keystore(line, Meta, Line).
reverse_improper(L) -> reverse_improper(L, []).
reverse_improper([], Acc) -> { Acc };
reverse_improper([H|T], Acc) when is_list(T) -> reverse_improper(T, [H|Acc]);
reverse_improper([H|T], Acc) -> { [H|Acc], T }.
update_last([], _) -> [];
update_last([H], F) -> [F(H)];
update_last([H|T], F) -> [H|update_last(T,F)].
keyfind(Key, Meta) ->
lists:keyfind(Key, 1, Meta).
keydelete(Key, Meta) ->
lists:keydelete(Key, 1, Meta).
keystore(Key, Meta, Value) ->
lists:keystore(Key, 1, Meta, { Key, Value }).
%% Quote splicing
do_splice([{ '|', _, [{ unquote_splicing, _, [Left] }, Right] }|T], #elixir_quote{unquote=true} = Q, S) ->
{ TT, TQ } = do_splice(T, Q, S, [], []),
{ do_splice_join(do_splice_join(TT, Left), Right), TQ#elixir_quote{unquoted=true} };
do_splice([{ '|', Meta, [{ unquote_splicing, _, [Left] }, Right] }|T], #elixir_quote{unquote=true} = Q, S) ->
%% Process the remaining entries on the list.
%% For [1, 2, 3, unquote_splicing(arg)|tail], this will quote
%% 1, 2 and 3, which could even be unquotes.
{ TT, QT } = do_splice(T, Q, S, [], []),
{ TR, QR } = do_quote(Right, QT, S),
%% Do the joining at runtime when we are aware of the values.
Args = [{ '__FILE__', [], nil }, ?line(Meta), Left, TR, TT],
{ { { '.', Meta, [elixir_quote, join] }, Meta, Args }, QR#elixir_quote{unquoted=true} };
do_splice(List, Q, S) ->
do_splice(List, Q, S, [], []).
+66 -11
View File
@@ -5,7 +5,8 @@
serialize/1, deserialize/1,
serialize_with_vars/2, deserialize_with_vars/2,
to_erl_env/1, to_ex_env/1,
umergev/2, umergec/2, merge_clause_vars/2
vars_from_binding/2, binding_for_eval/2, binding_from_vars/2,
umergev/2, umergec/2, umergea/2, merge_clause_vars/2
]).
-include("elixir.hrl").
@@ -80,23 +81,29 @@ build_ex_var(Line, Key, Name, S) when is_integer(Line) ->
% Handle Macro.Env conversion
to_erl_env({ 'Elixir.Macro.Env', Module, File, _Line, Function, Aliases, Context,
Requires, Functions, Macros, ContextModules, MacroAliases }) ->
Requires, Functions, Macros, ContextModules, MacroAliases, Vars }) ->
#elixir_scope{module=Module,file=File,
function=Function,aliases=Aliases,context=Context,
requires=Requires,macros=Macros,functions=Functions,
context_modules=ContextModules,macro_aliases=MacroAliases}.
context_modules=ContextModules,macro_aliases=MacroAliases,
list_vars=Vars}.
to_ex_env({ Line, #elixir_scope{module=Module,file=File,
function=Function,aliases=Aliases,context=Context,
requires=Requires,macros=Macros,functions=Functions,
context_modules=ContextModules,macro_aliases=MacroAliases} }) when is_integer(Line) ->
context_modules=ContextModules,macro_aliases=MacroAliases,
vars=Vars,list_vars=ListVars} }) when is_integer(Line) ->
{ 'Elixir.Macro.Env', Module, File, Line, Function, Aliases,
Context, Requires, Functions, Macros, ContextModules, MacroAliases }.
Context, Requires, Functions, Macros, ContextModules, MacroAliases,
list_vars(ListVars, Vars) }.
list_vars(nil, Vars) -> [K || { K, _ } <- Vars];
list_vars(Other, _) -> Other.
% Provides a tuple with only the scope information we want to serialize.
serialize(S) ->
elixir_tree_helpers:elixir_to_erl(
elixir_utils:elixir_to_erl(
{ S#elixir_scope.file, S#elixir_scope.functions,
S#elixir_scope.requires, S#elixir_scope.macros, S#elixir_scope.aliases,
S#elixir_scope.macro_functions, S#elixir_scope.macro_macros, S#elixir_scope.macro_aliases,
@@ -134,8 +141,8 @@ deserialize_with_vars({ File, Functions, Requires, Macros,
counter=[{'',length(Vars)}]
}.
% Receives two scopes and return a new scope based on the second
% with their variables merged.
%% Receives two scopes and return a new scope based on the second
%% with their variables merged.
umergev(S1, S2) ->
V1 = S1#elixir_scope.vars,
@@ -147,11 +154,22 @@ umergev(S1, S2) ->
clause_vars=merge_clause_vars(C1, C2)
}.
% Receives two scopes and return the later scope
% keeping the variables from the first (counters,
% imports and everything else are passed forward).
%% Receives two scopes and return the first scope with
%% counters and flags from the later.
umergec(S1, S2) ->
S1#elixir_scope{
counter=S2#elixir_scope.counter,
extra_guards=S2#elixir_scope.extra_guards,
super=S2#elixir_scope.super,
caller=S2#elixir_scope.caller
}.
%% Receives two scopes and return the later scope
%% keeping the variables from the first (counters,
%% imports and everything else are passed forward).
umergea(S1, S2) ->
S2#elixir_scope{
vars=S1#elixir_scope.vars,
temp_vars=S1#elixir_scope.temp_vars,
@@ -183,3 +201,40 @@ var_merger(_Var, K1, K2) ->
var_number([$@|T], _Acc) -> var_number(T, []);
var_number([H|T], Acc) -> var_number(T, [H|Acc]);
var_number([], Acc) -> list_to_integer(lists:reverse(Acc)).
%% Setup the vars in scope from binding
vars_from_binding(Scope, Binding) ->
Scope#elixir_scope{
vars=binding_dict(Binding),
temp_vars=[],
clause_vars=nil,
counter=[]
}.
binding_dict(List) -> binding_dict(List, orddict:new()).
binding_dict([{{H,Kind},_}|T], Dict) -> binding_dict(T, orddict:store({ H, Kind }, H, Dict));
binding_dict([{H,_}|T], Dict) -> binding_dict(T, orddict:store({ H, nil }, H, Dict));
binding_dict([], Dict) -> Dict.
binding_for_eval(Binding, Module) ->
Keyword = orddict:from_list(Binding),
case orddict:find('_@MODULE', Keyword) of
{ ok, _ } -> Keyword;
_ -> orddict:store('_@MODULE', Module, Keyword)
end.
binding_from_vars(#elixir_scope{vars=Vars}, Binding) ->
binding_from_vars(Binding, [], Binding, Vars).
binding_from_vars([{Var,_}|T], Acc, Binding, Vars) ->
case lists:member($@, atom_to_list(Var)) of
true ->
binding_from_vars(T, Acc, Binding, Vars);
false ->
RealName = orddict:fetch({ Var, nil }, Vars),
RealValue = proplists:get_value(RealName, Binding, nil),
binding_from_vars(T, [{Var, RealValue}|Acc], Binding, Vars)
end;
binding_from_vars([], Acc, _Binding, _Vars) -> lists:reverse(Acc).
+133 -111
View File
@@ -15,13 +15,11 @@
T1 == $[, T2 == $]
).
%% New ops table
-define(at_op(T),
T == $@).
-define(unary_op(T),
% T == $&;
T == $&;
T == $!;
T == $^).
@@ -259,7 +257,7 @@ tokenize([$.,T1,T2|Rest], Line, Scope, Tokens) when
% ## Single Token Operators
tokenize([$.,T|Rest], Line, Scope, Tokens) when
?at_op(T); ?unary_op(T); ?dual_op(T); ?mult_op(T); ?comp_op(T);
?match_op(T); ?tail_op(T); T == $& ->
?match_op(T); ?tail_op(T) ->
handle_call_identifier(Rest, Line, list_to_atom([T]), Scope, Tokens);
% Dot call
@@ -271,9 +269,13 @@ tokenize([$.,$(|Rest], Line, Scope, Tokens) ->
tokenize([$.,H|T], Line, #scope{file=File} = Scope, Tokens) when ?is_quote(H) ->
case elixir_interpolation:extract(Line, File, true, T, H) of
{ NewLine, [Part], Rest } when is_binary(Part) ->
Atom = unsafe_to_atom(Part, Scope),
Token = check_call_identifier(identifier, Line, Atom, Rest),
tokenize(Rest, NewLine, Scope, [Token|add_token_with_nl({ '.', Line }, Tokens)]);
case unsafe_to_atom(Part, Line, Scope) of
{ error, _ } = Error ->
Error;
Atom ->
Token = check_call_identifier(identifier, Line, Atom, Rest),
tokenize(Rest, NewLine, Scope, [Token|add_token_with_nl({ '.', Line }, Tokens)])
end;
Error ->
interpolation_error(Error, " (for function name starting at line ~B)", [Line])
end;
@@ -298,18 +300,28 @@ tokenize([$'|T], Line, Scope, Tokens) ->
tokenize([$:,H|T], Line, #scope{file=File} = Scope, Tokens) when ?is_quote(H) ->
case elixir_interpolation:extract(Line, File, true, T, H) of
{ NewLine, Parts, Rest } ->
Token = case unescape_tokens(Parts) of
[Part] when is_list(Part) or is_binary(Part) -> { atom, Line, unsafe_to_atom(Part, Scope) };
Unescaped -> { atom, Line, Unescaped }
SafeAtom = case unescape_tokens(Parts) of
[Part] when is_list(Part) or is_binary(Part) -> unsafe_to_atom(Part, Line, Scope);
Unescaped -> Unescaped
end,
tokenize(Rest, NewLine, Scope, [Token|Tokens]);
case SafeAtom of
{ error, _ } = Error ->
Error;
Atom ->
tokenize(Rest, NewLine, Scope, [{ atom, Line, Atom }|Tokens])
end;
Error ->
interpolation_error(Error, " (for atom starting at line ~B)", [Line])
end;
tokenize([$:,T|String], Line, Scope, Tokens) when ?is_new_atom(T) ->
{ Rest, Atom } = tokenize_atom([T|String], [], Scope),
tokenize(Rest, Line, Scope, [{ atom, Line, Atom }|Tokens]);
tokenize([$:,T|String], Line, Scope, Tokens) when ?is_atom_start(T) ->
{ Rest, Part } = tokenize_atom([T|String], []),
case unsafe_to_atom(Part, Line, Scope) of
{ error, _ } = Error ->
Error;
Atom ->
tokenize(Rest, Line, Scope, [{ atom, Line, Atom }|Tokens])
end;
% Atom identifiers/operators
@@ -339,7 +351,7 @@ tokenize([$:,T1,T2|Rest], Line, Scope, Tokens) when
% ## Single Token Operators
tokenize([$:,T|Rest], Line, Scope, Tokens) when
?at_op(T); ?unary_op(T); ?dual_op(T); ?mult_op(T); ?comp_op(T);
?match_op(T); ?tail_op(T); T == $&; T == $. ->
?match_op(T); ?tail_op(T); T == $. ->
tokenize(Rest, Line, Scope, [{ atom, Line, list_to_atom([T]) }|Tokens]);
% End of line
@@ -364,10 +376,6 @@ tokenize("\r\n" ++ Rest, Line, Scope, Tokens) ->
% Stand-alone tokens
% ## &
tokenize([$&,H|Rest], Line, Scope, Tokens) when ?is_digit(H) ->
tokenize(Rest, Line, Scope, [{ '&', Line, [list_to_integer([H])] }|Tokens]);
% ## Three token operators
tokenize([T1,T2,T3|Rest], Line, Scope, Tokens) when ?unary_op3(T1, T2, T3) ->
handle_nonl_op(Rest, Line, unary_op, list_to_atom([T1,T2,T3]), Scope, Tokens);
@@ -429,6 +437,11 @@ 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);
% ## Single Token Operators
%% Handle &1 and friends with special precedence.
tokenize([$&,D|Rest], Line, Scope, Tokens) when ?is_digit(D) ->
tokenize([D|Rest], Line, Scope, [{ '&', Line }|Tokens]);
tokenize([T|Rest], Line, Scope, Tokens) when ?at_op(T) ->
handle_nonl_op(Rest, Line, at_op, list_to_atom([T]), Scope, Tokens);
@@ -463,28 +476,28 @@ tokenize([H|_] = String, Line, Scope, Tokens) when ?is_digit(H) ->
tokenize([H|_] = String, Line, Scope, Tokens) when ?is_upcase(H) ->
{ Rest, Alias } = tokenize_identifier(String, []),
Atom = unsafe_to_atom(Alias, Scope),
case Rest of
[$:|T] when ?is_space(hd(T)) ->
tokenize(T, Line, Scope, [{ kw_identifier, Line, Atom }|Tokens]);
_ ->
tokenize(Rest, Line, Scope, [{ aliases, Line, [Atom] }|Tokens])
case unsafe_to_atom(Alias, Line, Scope) of
{ error, _ } = Error ->
Error;
Atom ->
case Rest of
[$:|T] when ?is_space(hd(T)) ->
tokenize(T, Line, Scope, [{ kw_identifier, Line, Atom }|Tokens]);
_ ->
tokenize(Rest, Line, Scope, [{ aliases, Line, [Atom] }|Tokens])
end
end;
% Identifier
tokenize([H|_] = String, Line, Scope, Tokens) when ?is_downcase(H); H == $_ ->
case tokenize_any_identifier(String, Line, [], Scope) of
{ error, _ } = Error -> Error;
{ Rest, { Kind, _, Identifier } } ->
case check_keyword(Line, Kind, Identifier, Tokens) of
nomatch ->
tokenize(Rest, Line, Scope, [{ Kind, Line, Identifier }|Tokens]);
{ ok, [Check|T] } ->
handle_terminator(Rest, Line, Scope, Check, T);
{ error, Token } ->
{ error, { Line, "syntax error before: ", Token } }
end
case tokenize_any_identifier(String, Line, Scope, Tokens) of
{ keyword, Rest, Check, T } ->
handle_terminator(Rest, Line, Scope, Check, T);
{ identifier, Rest, Token } ->
tokenize(Rest, Line, Scope, [Token|Tokens]);
{ error, _ } = Error ->
Error
end;
% Ambiguous unary/binary operators tokens
@@ -494,7 +507,7 @@ tokenize([Space, Sign, NotMarker|T], Line, Scope, [{ Identifier, _, _ } = H|Toke
?is_horizontal_space(Space),
not(?is_space(NotMarker)),
NotMarker /= $(, NotMarker /= $+, NotMarker /= $-, NotMarker /= $>,
Identifier == identifier orelse Identifier == punctuated_identifier ->
Identifier == identifier ->
Rest = [NotMarker|T],
tokenize(Rest, Line, Scope, [{ dual_op, Line, list_to_atom([Sign]) }, setelement(1, H, op_identifier)|Tokens]);
@@ -530,13 +543,17 @@ handle_strings(T, Line, H, #scope{file=File} = Scope, Tokens) ->
{ NewLine, Parts, [$:|Rest] } when ?is_space(hd(Rest)) ->
case Parts of
[Bin] when is_binary(Bin) ->
Atom = unsafe_to_atom(unescape_chars(Bin), Scope),
tokenize(Rest, NewLine, Scope, [{ kw_identifier, Line, Atom }|Tokens]);
case unsafe_to_atom(unescape_chars(Bin), Line, Scope) of
{ error, _ } = Error ->
Error;
Atom ->
tokenize(Rest, NewLine, Scope, [{ kw_identifier, Line, Atom }|Tokens])
end;
_ ->
{ error, { Line, "invalid interpolation in key ", [$"|T] } }
end;
{ NewLine, Parts, Rest } ->
Token = { string_type(H),Line,unescape_tokens(Parts) },
Token = { string_type(H), Line, unescape_tokens(Parts) },
tokenize(Rest, NewLine, Scope, [Token|Tokens]);
Error ->
interpolation_error(Error, " (for string starting at line ~B)", [Line])
@@ -564,13 +581,17 @@ eol(_Line, _Mod, [{',',_}|_] = Tokens) -> Tokens;
eol(_Line, _Mod, [{eol,_,_}|_] = Tokens) -> Tokens;
eol(Line, Mod, Tokens) -> [{eol,Line,Mod}|Tokens].
unsafe_to_atom(Binary, #scope{existing_atoms_only=true}) when is_binary(Binary) ->
unsafe_to_atom(Part, Line, #scope{}) when
is_binary(Part) andalso size(Part) > 255;
is_list(Part) andalso length(Part) > 255 ->
{ error, { Line, "atom length must be less than system limit", ":" } };
unsafe_to_atom(Binary, _Line, #scope{existing_atoms_only=true}) when is_binary(Binary) ->
binary_to_existing_atom(Binary, utf8);
unsafe_to_atom(Binary, #scope{}) when is_binary(Binary) ->
unsafe_to_atom(Binary, _Line, #scope{}) when is_binary(Binary) ->
binary_to_atom(Binary, utf8);
unsafe_to_atom(List, #scope{existing_atoms_only=true}) when is_list(List) ->
unsafe_to_atom(List, _Line, #scope{existing_atoms_only=true}) when is_list(List) ->
list_to_existing_atom(List);
unsafe_to_atom(List, #scope{}) when is_list(List) ->
unsafe_to_atom(List, _Line, #scope{}) when is_list(List) ->
list_to_atom(List).
collect_modifiers([H|T], Buffer) when ?is_downcase(H) ->
@@ -586,7 +607,7 @@ extract_heredoc_with_interpolation(Line, File, Interpol, T, H) ->
{ error, _ } = Error ->
Error;
{ Body, Rest } ->
case elixir_interpolation:extract(Line, File, Interpol, Body, 0) of
case elixir_interpolation:extract(Line + 1, File, Interpol, Body, 0) of
{ _, Parts, [] } -> { Parts, Rest };
Error -> interpolation_error(Error, " (for heredoc starting at line ~B)", [Line])
end
@@ -743,14 +764,14 @@ tokenize_comment([]) -> [].
%% Atoms
%% Handle atoms specially since they support @
tokenize_atom([H|T], Acc, Scope) when ?is_atom(H) ->
tokenize_atom(T, [H|Acc], Scope);
tokenize_atom([H|T], Acc) when ?is_atom(H) ->
tokenize_atom(T, [H|Acc]);
tokenize_atom([H|T], Acc, Scope) when H == $?; H == $! ->
{ T, unsafe_to_atom(lists:reverse([H|Acc]), Scope) };
tokenize_atom([H|T], Acc) when H == $?; H == $! ->
{ T, lists:reverse([H|Acc]) };
tokenize_atom(Rest, Acc, Scope) ->
{ Rest, unsafe_to_atom(lists:reverse(Acc), Scope) }.
tokenize_atom(Rest, Acc) ->
{ Rest, lists:reverse(Acc) }.
%% Identifiers
%% At this point, the validity of the first character was already verified.
@@ -763,50 +784,43 @@ tokenize_identifier(Rest, Acc) ->
%% Tokenize any identifier, handling kv, punctuated, paren, bracket and do identifiers.
tokenize_any_identifier(String, Line, Acc, Scope) ->
{ Rest, Identifier } = tokenize_identifier(String, Acc),
tokenize_any_identifier(String, Line, Scope, Tokens) ->
{ Rest, Identifier } = tokenize_identifier(String, []),
case Rest of
[H|T] when H == $?; H == $! ->
Atom = unsafe_to_atom(Identifier ++ [H], Scope),
tokenize_kw_or_call_identifier(punctuated_identifier, Line, Atom, T);
_ ->
Atom = unsafe_to_atom(Identifier, Scope),
tokenize_kw_or_call_identifier(identifier, Line, Atom, Rest)
{ AllIdentifier, AllRest } =
case Rest of
[H|T] when H == $?; H == $! -> { Identifier ++ [H], T };
_ -> { Identifier, Rest }
end,
case unsafe_to_atom(AllIdentifier, Line, Scope) of
{ error, _ } = Error ->
Error;
Atom ->
tokenize_kw_or_other(AllRest, identifier, Line, Atom, Tokens)
end.
%% Tokenize kw or call identifier (paren | bracket | do)
tokenize_kw_or_other([$:,H|T], _Kind, Line, Atom, _Tokens) when ?is_space(H) ->
{ identifier, [H|T], { kw_identifier, Line, Atom } };
tokenize_kw_or_call_identifier(_Kind, Line, Atom, [$:,H|T]) when ?is_space(H) ->
{ [H|T], { kw_identifier, Line, Atom } };
tokenize_kw_or_call_identifier(_Kind, Line, Atom, [$:,H|_]) when ?is_new_atom(H) ->
tokenize_kw_or_other([$:,H|_], _Kind, Line, Atom, _Tokens) when ?is_atom_start(H) ->
{ error, { Line, "keyword argument must be followed by space after: ", atom_to_list(Atom) ++ [$:] } };
tokenize_kw_or_call_identifier(Kind, Line, Atom, Rest) ->
{ Rest, check_call_identifier(Kind, Line, Atom, Rest) }.
tokenize_kw_or_other(Rest, Kind, Line, Atom, Tokens) ->
case check_keyword(Line, Atom, Tokens) of
nomatch ->
{ identifier, Rest, check_call_identifier(Kind, Line, Atom, Rest) };
{ ok, [Check|T] } ->
{ keyword, Rest, Check, T };
{ error, Token } ->
{ error, { Line, "syntax error before: ", Token } }
end.
%% Check if it is a call identifier (paren | bracket | do)
check_call_identifier(_Kind, Line, Atom, [$(|_]) -> { paren_identifier, Line, Atom };
check_call_identifier(_Kind, Line, Atom, [$[|_]) -> { bracket_identifier, Line, Atom };
check_call_identifier(Kind, Line, Atom, Rest) ->
case next_is_block(Rest) of
false -> { Kind, Line, Atom };
BlockIdentifier -> { BlockIdentifier, Line, Atom }
end.
next_is_block([Space|Tokens]) when Space == $\t; Space == $\s ->
next_is_block(Tokens);
next_is_block([$d,$o,H|_]) when ?is_identifier(H); ?is_terminator(H) ->
false;
next_is_block([$d,$o|_]) ->
do_identifier;
next_is_block(_) ->
false.
check_call_identifier(Kind, Line, Atom, _Rest) -> { Kind, Line, Atom }.
add_token_with_nl(Left, [{eol,_,newline}|T]) -> [Left|T];
add_token_with_nl(Left, T) -> [Left|T].
@@ -854,9 +868,10 @@ check_terminator({ E, _ }, [{ S, _ }|Terminators]) when
check_terminator({ E, Line }, [{ Start, StartLine }|_]) when
E == 'end'; E == ')'; E == ']'; E == '}'; E == '>>' ->
End = terminator(Start),
Message = io_lib:format("missing terminator: ~ts (for \"~ts\" starting at line ~B)", [End, Start, StartLine]),
{ error, { Line, Message, [] } };
End = terminator(Start),
Message = io_lib:format("\"~ts\" starting at line ~B is missing terminator \"~ts\". "
"Unexpected token: ", [Start, StartLine, End]),
{ error, { Line, Message, atom_to_list(E) } };
check_terminator({ E, Line }, []) when
E == 'end'; E == ')'; E == ']'; E == '}'; E == '>>' ->
@@ -883,40 +898,47 @@ terminator('<<') -> '>>'.
%% Keywords checking
check_keyword(Line, Identifier, Atom, [{ '.', _ }|_] = Tokens) ->
{ ok, [{ Identifier, Line, Atom }|Tokens] };
check_keyword(_Line, _Atom, [{ '.', _ }|_]) ->
nomatch;
check_keyword(Line, Identifier, Atom, Tokens) when
Identifier == identifier; Identifier == do_identifier;
Identifier == bracket_identifier; Identifier == paren_identifier ->
check_keyword(Line, do, [{ Identifier, Line, Atom }|T]) when Identifier == identifier ->
{ ok, [{ do, Line }, { do_identifier, Line, Atom }|T] };
check_keyword(Line, do, Tokens) ->
case do_keyword_valid(Tokens) of
true -> { ok, [{ do, Line }|Tokens] };
false -> { error, "do" }
end;
check_keyword(Line, Atom, Tokens) ->
case keyword(Atom) of
do ->
case do_keyword_valid(Tokens) of
true -> { ok, [{ Atom, Line }|Tokens] };
false -> { error, "do" }
end;
false -> nomatch;
true -> { ok, [{ Atom, Line }|Tokens] };
token -> { ok, [{ Atom, Line }|Tokens] };
block -> { ok, [{ block_identifier, Line, Atom }|Tokens] };
unary_op -> { ok, [{ unary_op, Line, Atom }|Tokens] };
Kind -> { ok, add_token_with_nl({ Kind, Line, Atom }, Tokens) }
end.
%% do is only valid after the end, true, false and nil keywords
do_keyword_valid([{ Atom, _ }|_]) ->
case Atom of
'end' -> true;
nil -> true;
true -> true;
false -> true;
_ -> keyword(Atom) == false
end;
check_keyword(_, _, _, _) -> nomatch.
do_keyword_valid([{ Atom, _ }|_]) ->
is_boolean(keyword(Atom));
do_keyword_valid(_) -> true.
do_keyword_valid(_) ->
true.
% Regular keywords
keyword('fn') -> true;
keyword('end') -> true;
keyword('true') -> true;
keyword('false') -> true;
keyword('nil') -> true;
% Special handling for do
keyword('do') -> do;
keyword('fn') -> token;
keyword('do') -> token;
keyword('end') -> token;
keyword('true') -> token;
keyword('false') -> token;
keyword('nil') -> token;
% Operators keywords
keyword('not') -> unary_op;
+15 -6
View File
@@ -6,8 +6,8 @@
record_local/2, record_local/3,
record_import/4, record_remote/4,
record_warn/4, record_definition/3,
record_defaults/4,
ensure_no_import_conflict/4, ensure_all_imports_used/3,
record_defaults/4, record_alias/2,
ensure_no_function_conflict/4, ensure_all_imports_used/3,
warn_unused_local/3, format_error/1
]).
-include("elixir.hrl").
@@ -65,6 +65,15 @@ record_remote(Tuple, Receiver, Module, Function) ->
error:badarg -> false
end.
record_alias(_Receiver, nil) -> false;
record_alias(Receiver, Module) ->
try
Pid = ets:lookup_element(Module, ?attr, 2),
?tracker:add_alias(Pid, Receiver)
catch
error:badarg -> false
end.
record_definition(Tuple, Kind, Module) ->
if_tracker(Module, fun(Pid) ->
?tracker:add_definition(Pid, Kind, Tuple),
@@ -89,10 +98,10 @@ if_tracker(Module, Callback) ->
%% ERROR HANDLING
ensure_no_import_conflict(Meta, File, Module, AllDefined) ->
ensure_no_function_conflict(Meta, File, Module, AllDefined) ->
if_tracker(Module, fun(Pid) ->
[ begin
elixir_errors:form_error(Meta, File, ?MODULE, { import_conflict, Error })
elixir_errors:form_error(Meta, File, ?MODULE, { function_conflict, Error })
end || Error <- ?tracker:collect_imports_conflicts(Pid, AllDefined) ]
end),
ok.
@@ -118,11 +127,11 @@ warn_unused_local(File, Module, Private) ->
end || Error <- Unused ]
end).
format_error({import_conflict,{Receivers, Name, Arity}}) ->
format_error({function_conflict,{Receivers, Name, Arity}}) ->
io_lib:format("imported ~ts.~ts/~B conflicts with local function",
[elixir_errors:inspect(hd(Receivers)), Name, Arity]);
format_error({ unused_import, Module }) ->
format_error({unused_import, Module}) ->
io_lib:format("unused import ~ts", [elixir_errors:inspect(Module)]);
format_error({unused_args,{Name, Arity}}) ->

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