Compare commits

...
163 Commits
Author SHA1 Message Date
José Valim b07fbcf8b7 Release v0.12.5 2014-03-09 17:02:41 -07:00
Eric Meadows-Jönsson 4ab8225cfb Properly clean spurious entries in waiting list 2014-03-09 16:05:09 +01:00
José Valim 455eb4c4ac Deprecate GenFSM
We plan to provide better abstractions around GenServer and GenEvent
in upcoming releases but we won't do so for GenFSM. There are other
interesting approaches out there, like `plain_fsm`, which are easier
to understand and use than OTP's FSM.
2014-03-08 07:53:53 -08:00
José Valim a6f048b3de Merge pull request #2104 from rcillo/rd-remove-git-force-https-option
Remove support to MIX_GIT_FORCE_HTTPS environment option
2014-03-09 00:43:31 +09:00
Ricardo de Cillo aa87d44cba Remove support to MIX_GIT_FORCE_HTTPS environment option
This used to be useful in case the git port was blocked.
2014-03-08 11:52:35 -03:00
José Valim d161986e93 Update CHANGELOG.md 2014-03-06 23:35:27 +08:00
José Valim 28367d9073 Ensure an after clause is generated only if required 2014-03-06 07:05:18 -08:00
José Valim 126a5a61df Deprecate more functionality to be changed or removed in v0.13 2014-03-05 08:18:09 -08:00
Eric Meadows-Jönsson 293711ff46 Merge pull request #2090 from bitwalker/gen-fsm-docs
Update Gen_FSM example to be more explicit, clear.
2014-03-03 19:33:52 +01:00
Paul Schoenfelder 1a17634de9 Update Gen_FSM example to be more explicit, clear.
By using a locally registered process name, this example has caused some
initial confusion to new users of Elixir. Using explicit PIDs will more
closely align with other examples, and reduce the number of things one
must understand in order to understand this example.
2014-03-03 12:21:33 -06:00
José Valim 2121ce1875 Guarantee HashDict.equal?/1 returns proper value when comparing subsets
Conflicts:
	lib/elixir/test/elixir/hash_dict_test.exs
2014-02-23 13:48:06 +01:00
jsvisa fd8d1f1e4c Add test case for bug 2014-02-21 19:38:47 +01:00
jsvisa 9ebe851b38 Fix String.slice error
iex(76)> String.slice("string", 0..-2)
    nil # you see here is the issue, it shouldn't return `nil`
    iex(77)> String.slice("string", 1..-2)
    "trin"
    iex(78)> String.slice("string", -5..-2)
    "trin"
2014-02-21 19:38:47 +01:00
Eric Meadows-Jönsson cb81c6b5c7 Start app after changing System.argv on 'mix run' 2014-02-21 19:16:45 +01:00
José Valim 2cc4fd2eb5 Deprecate is_regex/1 and is_range/1
Conflicts:
	lib/elixir/lib/string.ex
2014-02-19 18:22:58 +01:00
Eric Meadows-Jönsson 4637b57977 Fix handling of raw rebar deps 2014-02-15 14:17:16 +01:00
José Valim 207c626a6c Merge pull request #2057 from jwarwick/mix_underscore
Mix.Utils handling empty strings
2014-02-14 19:38:54 +01:00
John Warwick 7308d81729 Made Mix.Utils.camelize handle empty string 2014-02-14 13:05:05 -05:00
John Warwick 177a900be3 Make Mix.Utils.Underscore accept .'s and empty args 2014-02-14 12:26:26 -05:00
Eric Meadows-Jönsson c8813445c2 Revert "mix deps.get does not use MIX_ENV"
This reverts commit 1e9cd551ce.
2014-02-12 14:49:39 +01:00
Eric Meadows-Jönsson 1e9cd551ce mix deps.get does not use MIX_ENV 2014-02-12 14:33:33 +01:00
José Valim 969d48ccaf Automatically recompile on outdated elixir version 2014-02-12 14:14:52 +01:00
José Valim f2c914926d Emit warnings for using % as sigils 2014-02-12 11:16:37 +01:00
José Valim 11c4c2791d Add guards to lstrip and rstrip 2014-02-12 11:15:08 +01:00
José Valim 4051cf53dc Start v0.12.5-dev 2014-02-12 09:25:59 +01:00
José Valim 543dfdeac8 Release v0.12.4 2014-02-12 09:22:25 +01:00
John Warwick b1b1cd3263 Fixed spec for Enum.flat_map 2014-02-11 07:24:52 +01:00
John Warwick 55563dac1c Fixing result handling of :file.consult 2014-02-11 07:23:52 +01:00
Eric Meadows-Jönsson ec350645a4 Merge pull request #2050 from jwarwick/version_spec
Updated specs in Version
2014-02-10 10:24:44 +01:00
John Warwick e4aa6e1bdf Fixing doc typos in Version 2014-02-09 22:47:05 -05:00
John Warwick f7f06010ae Updating Version specs, dialyzer warnings 2014-02-09 22:44:23 -05:00
José Valim 73dc4536b0 Only raise on conflicts coming from functions 2014-02-09 21:20:16 +01:00
José Valim 2b298ab9fc CaptureIO now returns an empty string instead of nil when there is no capture 2014-02-09 16:44:15 +01:00
Eric Meadows-Jönsson 3a36c2e6ae assert_receive(d) should leak
Closes #2030.
2014-02-08 21:36:06 +01:00
José Valim 6448de834c Ensure deps can run after mix deps.get 2014-02-08 20:02:01 +01:00
Eric Meadows-Jönsson f4d145c3a0 Fix dependency compilation order 2014-02-08 13:56:39 +01:00
José Valim 0bacbaeff7 Emit warnings for Regex.match? 2014-02-08 11:32:36 +01:00
José Valim 79d8dd0020 Merge pull request #2042 from kiela/master
Add R16B03, R16B03-1 and 17.0-rc1 to .travis.yml
2014-02-08 10:49:25 +01:00
Kamil Kieliszczyk 9bce9e4fde Add R16B03, R16B03-1 and 17.0-rc1 to .travis.yml 2014-02-08 10:18:14 +01:00
José Valim 232a545d19 Avoid warnings on iex suite 2014-02-08 10:08:29 +01:00
José Valim c1deb99b70 Deprecate char lists in Regex 2014-02-08 08:46:17 +01:00
José Valim 1625bdb454 Update CHANGELOG 2014-02-07 20:50:19 +01:00
José Valim b982822fdc Do not compile after deps.get and deps.update 2014-02-07 20:47:06 +01:00
José Valim 50820dfc9c Mark dependencies as requiring compilation every time they are fetched 2014-02-07 19:26:38 +01:00
José Valim ee0fb39822 Compile dependencies when we can in deps.check 2014-02-07 17:18:52 +01:00
José Valim 9760baff9c Take deps.loadpaths out of loadpaths to simplify call graph 2014-02-07 15:08:11 +01:00
pragdave f7cd14d0f6 print_doc_arg attempted to handle argments with defaults. However, it added " \\ " between them, which is actually the string " \ ". This meant that (for example) "h IO.puts" dislayed "def puts(device \ group_leader(), item)". Changed it to escape both backslashes. 2014-02-07 12:54:25 +01:00
José Valim b29603f1b7 Remove unecessary exceptions 2014-02-06 20:36:43 +01:00
José Valim afb04a19f9 Do not defined a URI.Error, simply raise ArgumentError 2014-02-06 20:36:39 +01:00
José Valim 50e9ae6ca7 Raise URI.Error on malformed URIs, closes #2034 2014-02-06 19:40:44 +01:00
José Valim 05a85a8e9c Effectively deprecate // and deprecate ^ in anonymous function clause heads 2014-02-05 14:50:45 +01:00
José Valim 2af50f7b78 Move to new sigils syntax 2014-02-05 14:38:54 +01:00
José Valim 15dd4d3c07 Move to_matchable back to private 2014-02-03 15:18:34 +01:00
José Valim 058d55b68c Fix test suite warnings on 17.0-rc1
Those warnings happen because we need to test basic functionality,
like `nil?(nil) == true` and the Erlang compiler can see this
expression will always work.

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

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

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

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

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

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

The end result is a faster HashDict implementation (about 10% faster
on access, 30% on updates), which is still small for small dicts
but about 30% bigger for large dicts (in terms of memory) and with
a much simpler implementation.
2014-01-19 13:58:45 +01:00
José Valim adf7834529 Ensure ExUnit doesn't hang on linked failures
Thanks to @pminten for reporting this issue.
2014-01-18 19:41:19 +01:00
José Valim c837d8e1d3 Merge pull request #2004 from rcoppolo/deprecate-term-binary-in-kernel
Deprecates binary_to_term and term_to_binary in Kernel.
2014-01-18 00:56:18 -08:00
Ryan Coppolo ce61c60af5 Deprecates binary_to_term and term_to_binary in Kernel. 2014-01-17 16:08:15 -08:00
José Valim dc0dd43406 Simplify diverge/override mechanism in Mix.Deps 2014-01-17 17:47:13 +01:00
Eric Meadows-Jönsson 6c2744d373 Add index reflection to private records
Example:
defrecordp :user, [:name, age]
user(:age) #=> 2
2014-01-17 01:24:43 +01:00
José Valim f671e1204c Change precedence of <- to be the same as other generators 2014-01-16 20:08:55 +01:00
José Valim c0af2622af List iex -S mix at the bottom of mix help 2014-01-16 13:20:30 +01:00
José Valim 6b511cc844 Remove @hidden in mix tasks
Instead, we can use the presence or abscence of @shortdoc
to specify if the task should be shown or not.
2014-01-16 12:58:48 +01:00
José Valim 4ade0f98aa Remove deprecated code 2014-01-16 10:26:48 +01:00
José Valim 67a672e6ed Guarantee nullary funs/macros are allowed in guards, closes #1997 2014-01-16 10:00:39 +01:00
José Valim 54e8ea13f4 Ensure heredocs are allowed in calls, closes #1998 2014-01-16 09:49:13 +01:00
José Valim 3110be1ca5 Bump to 0.12.3-dev 2014-01-15 16:05:04 +01:00
228 changed files with 4746 additions and 5386 deletions
+3
View File
@@ -6,6 +6,9 @@ notifications:
- jose.valim@plataformatec.com.br
- eric.meadows.jonsson@gmail.com
otp_release:
- 17.0-rc1
- R16B03-1
- R16B03
- R16B02
- R16B01
- R16B
+68 -3
View File
@@ -1,9 +1,74 @@
# v0.12.5 (2014-03-09)
* Bug fixes
* [Kernel] Ensure `try` does not generate an after clause. Generating an after clause forbade clauses in the `else` part from being tail recursive. This should improve performance and memory consumption of `Stream` functions
* [Mix] Automatically recompile on outdated Elixir version and show proper error messages
* Deprecations
* [File] `File.stream_to!/3` is deprecated
* [GenFSM] `GenFSM` is deprecated
* [Kernel] `%` for sigils is deprecated in favor of `~`
* [Kernel] `is_range/1` and `is_regex/1` are deprecated in favor of `Range.range?/1` and `Regex.regex?/1`
* [Stream] `Stream.after/1` is deprecated
* [URI] `URI.decode_query/1` is deprecated in favor of `URI.decode_query/2` with explicit dict argument
* [URI] Passing lists as key or values in `URI.encode_query/1` is deprecated
* Backwards incompatible changes
* [Mix] Remove `MIX_GIT_FORCE_HTTPS` as Git itself already provides mechanisms for doing so
# v0.12.4 (2014-02-12)
* Enhancements
* [Mix] `mix deps.get` and `mix deps.update` no longer compile dependencies afterwards. Instead, they mark the dependencies which are going to be automatically compiled next time `deps.check` is invoked (which is done automatically by most mix tasks). This means users should have a better workflow when migrating in between environments
* Deprecations
* [Kernel] `//` for default arguments is deprecated in favor of `\\`
* [Kernel] Using `%` for sigils is deprecated in favor of `~`. This is a soft deprecation, no warnings will be emitted for it in this release
* [Kernel] Using `^` inside function clause heads is deprecated, please use a guard instead
* Backwards incompatible changes
* [ExUnit] `CaptureIO` returns an empty string instead of nil when there is no capture
* [Version] The `Version` module now only works with SemVer. The functions `Version.parse/1` and `Version.parse_requirement/1` now return `{:ok,res} | :error` for the cases you want to handle non SemVer cases manually. All other functions will trigger errors on non semantics versions
# v0.12.3 (2014-02-02)
* Enhancements
* [Kernel] Warnings now are explicitly tagged with "warning:" in messages
* [Kernel] Explicit functions inlined by the compiler, including operators. This means that `Kernel.+/2` will now expand to `:erlang.+/2` and so on
* [Mix] Do not fail if a Mix dependency relies on an outdated Elixir version
* [Process] Add `Process.send/2` and `Process.send_after/3`
* [Version] Add `Version.compare/2`
* Bug fixes
* [Atom] Inspect `:...` and `:foo@bar` without quoting
* [Keyword] The list `[1, 2, three: :four]` now correctly expands to `[1, 2, {:three, :four}]`
* [Kernel] Ensure undefined `@attributes` shows proper stacktrace in warnings
* [Kernel] Guarantee nullary funs/macros are allowed in guards
* [Process] Ensure monitoring functions are inlined by the compiler
* Deprecations
* [IEx] The helper `m/0` has been deprecated. The goal is to group all runtime statistic related helpers into a single module
* [Kernel] `binary_to_term/1`, `binary_to_term/2`, `term_to_binary/1` and `term_to_binary/2` are deprecated in favor of their counterparts in the `:erlang` module
* [Kernel] `//` for default arguments is deprecated in favor of `\\`. This is a soft deprecation, no warnings will be emitted for it in this release
* [Kernel] Deprecated `@behavior` in favor of `@behaviour`
* [Record] `to_keywords`, `getter` and `list getter` functionalities in `defrecordp` are deprecated
* [Record] `Record.import/2` is deprecated
* Backwards incompatible changes
* [Dict] Implementations of `equal?/2` and `merge/2` in `HashDict` and `ListDict` are no longer polymorphic. To get polymorphism, use the functions in `Dict` instead
* [File] `File.cp/3` and `File.cp_r/3` no longer carry Unix semantics where the function behaves differently if the destination is an existing previous directory or not. It now always copies source to destination, doing it recursively in the latter
* [IEx] IEx now loads the `.iex.exs` file instead of `.iex`
* [Kernel] Remove `**` from the list of allowed operators
* [Kernel] Limit sigils delimiters to one of the following: `<>`, `{}`, `[]`, `()`, `||`, `//`, `"` and `'`
* [Range] `Range` is no longer a record, instead use `first .. last` if you need pattern matching
* [Set] Implementations of `difference/2`, `disjoint?/2`, `equal?/2`, `intersection/2`, `subset?/2` and `union/2` in `HashSet` are no longer polymorphic. To get polymorphism, use the functions in `Set` instead
# v0.12.2 (2014-01-15)
* Enhancements
* [EEx] Allow `EEx.AssignsEngine` to accept any Dict
* [Enum] Add `Enum.flat_map_reduce/3`
* [ExUnit] Support @moduletag in ExUnit cases
* [ExUnit] Support `@moduletag` in ExUnit cases
* [Kernel] Improve stacktraces to be relative to the compilation path and include the related application
* [Stream] Add `Stream.transform/3`
@@ -18,7 +83,7 @@
* Deprecations
* [Enum] Deprecate `Enum.first/1` in favor of `Enum.at/2` and `List.first/1`
* [Kernel] Deprecate continuable heredocs. In previous versions, Elixir would continue parsing on the same line the heredoc started, this behavior has been deprecated
* [Kernel] Deprecate continuable heredocs. In previous versions, Elixir would continue parsing on the same line the heredoc started, this behaviour has been deprecated
* [Kernel] `is_alive/0` is deprecated in favor of `Node.alive?`
* [Kernel] `Kernel.inspect/2` with `Inspect.Opts[]` is deprecated in favor of `Inspect.Algebra.to_doc/2`
* [Kernel] `Kernel.inspect/2` with `:raw` option is deprecated, use `:records` option instead
@@ -718,7 +783,7 @@
* Backwards incompatible changes
* [Kernel] Compile files now follow `Elixir-ModuleName` convention to solve issues with Erlang embedded mode. This removes the `__MAIN__` pseudo-variable as modules are now located inside `Elixir` namespace
* [Kernel] `__using__` callback triggered by `use` now receives just one argument. Caller information can be accessed via macros using `__CALLER__`
* [Kernel] Comprehensions syntax changed to be more compatible with Erlang behavior
* [Kernel] Comprehensions syntax changed to be more compatible with Erlang behaviour
* [Kernel] loop and recur are removed in favor of recursion with named functions
* [Module] Removed data functions in favor of unifying the attributes API
+1 -1
View File
@@ -76,7 +76,7 @@ unicode: $(UNICODE)
$(UNICODE): lib/elixir/unicode/*
@ echo "==> unicode (compile)";
@ echo "This step can take up to a minute to compile in order to embed the Unicode database"
$(Q) cd lib/elixir && ../../$(ELIXIRC) unicode/graphemes.ex unicode/unicode.ex -o ebin;
$(Q) cd lib/elixir && ../../$(ELIXIRC) unicode/unicode.ex -o ebin;
$(eval $(call APP_TEMPLATE,ex_unit,ExUnit))
$(eval $(call APP_TEMPLATE,eex,EEx))
+1 -1
View File
@@ -1 +1 @@
0.12.2
0.12.5
+1 -1
View File
@@ -17,7 +17,7 @@ if [ $# -gt 0 ] && ([ "$1" = "--help" ] || [ "$1" = "-h" ]); then
--hidden Makes a hidden node
--detached Starts the Erlang VM detached from console
--remsh \"name\" Connects to a node using a remote shell
--dot-iex \"path\" Overrides default .iex file and uses path instead;
--dot-iex \"path\" Overrides default .iex.exs file and uses path instead;
path can be empty, then no file will be loaded
** Options marked with (*) can be given more than once
+7 -7
View File
@@ -1,7 +1,7 @@
defexception EEx.SyntaxError, message: nil
defmodule EEx do
@moduledoc %S"""
@moduledoc ~S"""
EEx stands for Embedded Elixir. It allows you to embed
Elixir code inside a string in a robust way:
@@ -105,7 +105,7 @@ defmodule EEx do
Sample.sample(1, 2) #=> "3"
"""
defmacro function_from_string(kind, name, source, args // [], options // []) do
defmacro function_from_string(kind, name, source, args \\ [], options \\ []) do
quote bind_quoted: binding do
info = Keyword.merge [file: __ENV__.file, line: __ENV__.line], options
args = Enum.map args, fn arg -> { arg, [line: info[:line]], nil } end
@@ -141,7 +141,7 @@ defmodule EEx do
Sample.sample(1, 2) #=> "3"
"""
defmacro function_from_file(kind, name, file, args // [], options // []) do
defmacro function_from_file(kind, name, file, args \\ [], options \\ []) do
quote bind_quoted: binding do
info = Keyword.merge options, [file: file, line: 1]
args = Enum.map args, fn arg -> { arg, [line: 1], nil } end
@@ -159,7 +159,7 @@ defmodule EEx do
Get a string `source` and generate a quoted expression
that can be evaluated by Elixir or compiled to a function.
"""
def compile_string(source, options // []) do
def compile_string(source, options \\ []) do
EEx.Compiler.compile(source, options)
end
@@ -167,7 +167,7 @@ defmodule EEx do
Get a `filename` and generate a quoted expression
that can be evaluated by Elixir or compiled to a function.
"""
def compile_file(filename, options // []) do
def compile_file(filename, options \\ []) do
options = Keyword.merge options, [file: filename, line: 1]
compile_string(File.read!(filename), options)
end
@@ -181,7 +181,7 @@ defmodule EEx do
#=> "foo baz"
"""
def eval_string(source, bindings // [], options // []) do
def eval_string(source, bindings \\ [], options \\ []) do
compiled = compile_string(source, options)
do_eval(compiled, bindings, options)
end
@@ -199,7 +199,7 @@ defmodule EEx do
#=> "foo baz"
"""
def eval_file(filename, bindings // [], options // []) do
def eval_file(filename, bindings \\ [], options \\ []) do
options = Keyword.put options, :file, filename
compiled = compile_file(filename, options)
do_eval(compiled, bindings, options)
+1 -1
View File
@@ -1,5 +1,5 @@
defmodule EEx.Engine do
@moduledoc %S"""
@moduledoc ~S"""
This is the basic EEx engine that ships with Elixir.
An engine needs to implement two functions:
+2 -2
View File
@@ -2,7 +2,7 @@ defmodule EEx.TransformerEngine do
@moduledoc """
An abstract engine that is meant to be used and
built upon in other modules. This engine implements
the `EEx.Engine` behavior and provides a `transform`
the `EEx.Engine` behaviour and provides a `transform`
overridable directive that allows a developer to
customize the expression returned by the engine.
@@ -13,7 +13,7 @@ defmodule EEx.TransformerEngine do
@doc false
defmacro __using__(_) do
quote do
@behavior EEx.Engine
@behaviour EEx.Engine
def handle_text(buffer, text) do
EEx.Engine.handle_text(buffer, text)
+2 -2
View File
@@ -2,6 +2,6 @@ defmodule EEx.Mixfile do
use Mix.Project
def project do
[app: :eex, version: System.version]
[app: :eex, version: System.version, build_per_environment: false]
end
end
end
+1 -1
View File
@@ -20,7 +20,7 @@ defmodule EEx.SmartEngineTest do
assert_eval "1\n2\n3\n", "<%= lc x inlist [1, 2, 3] do %><%= x %>\n<% end %>"
end
defp assert_eval(expected, actual, binding // []) do
defp assert_eval(expected, actual, binding \\ []) do
result = EEx.eval_string(actual, binding, file: __ENV__.file)
assert result == expected
end
+2 -1
View File
@@ -80,7 +80,8 @@
-define(is_atom(S), ?is_identifier(S) orelse (S == $@)).
-define(is_identifier(S), ?is_digit(S) orelse ?is_upcase(S) orelse ?is_downcase(S) orelse (S == $_)).
-define(is_terminator(S), (S == $?) orelse (S == $!) orelse (S == $:)).
-define(is_sigil(S), (S == $/) orelse (S == $<) orelse (S == $") orelse (S == $') orelse
(S == $[) orelse (S == $() orelse (S == ${) orelse (S == $|)).
%% Quotes
-define(is_quote(S), S == $" orelse S == $').
+2 -2
View File
@@ -75,7 +75,7 @@ defmodule Application.Behaviour do
# Application.Behaviour.start(:my_app)
#
@doc false
def start(app, type // :temporary) do
def start(app, type \\ :temporary) do
case :application.start(app, type) do
{ :error, { :not_started, dep } } ->
case start(dep) do
@@ -92,7 +92,7 @@ defmodule Application.Behaviour do
@doc false
defmacro __using__(_) do
quote location: :keep do
@behavior :application
@behaviour :application
@doc false
def stop(_state) do
+4
View File
@@ -122,6 +122,10 @@ defmodule Behaviour do
raise ArgumentError, message: "default arguments // not supported in defcallback/defmacrocallback"
end
defp ensure_not_default({ :\\, _, [_, _] }) do
raise ArgumentError, message: "default arguments \\\\ not supported in defcallback/defmacrocallback"
end
defp ensure_not_default(_), do: :ok
@doc false
+9 -9
View File
@@ -10,7 +10,7 @@ defmodule Code do
Utilities for managing code compilation, code evaluation and code loading.
This module complements [Erlang's code module](http://www.erlang.org/doc/man/code.html)
to add behavior which is specific to Elixir.
to add behaviour which is specific to Elixir.
"""
@doc """
@@ -115,7 +115,7 @@ defmodule Code do
{3, [a: 1, b: 2]}
"""
def eval_string(string, binding // [], opts // [])
def eval_string(string, binding \\ [], opts \\ [])
def eval_string(string, binding, Macro.Env[] = env) do
do_eval_string(string, binding, env.to_keywords)
@@ -150,7 +150,7 @@ defmodule Code do
{3, [a: 1, b: 2]}
"""
def eval_quoted(quoted, binding // [], opts // [])
def eval_quoted(quoted, binding \\ [], opts \\ [])
def eval_quoted(quoted, binding, Macro.Env[] = env) do
do_eval_quoted(quoted, binding, env.to_keywords)
@@ -225,7 +225,7 @@ defmodule Code do
`Macro.to_string/2`, which converts a quoted form to a string/binary
representation.
"""
def string_to_quoted(string, opts // []) when is_list(opts) do
def string_to_quoted(string, opts \\ []) when is_list(opts) do
file = Keyword.get opts, :file, "nofile"
line = Keyword.get opts, :line, 1
res = :elixir.string_to_quoted(to_char_list(string), line, file, opts)
@@ -246,7 +246,7 @@ defmodule Code do
Check `string_to_quoted/2` for options information.
"""
def string_to_quoted!(string, opts // []) when is_list(opts) do
def string_to_quoted!(string, opts \\ []) when is_list(opts) do
file = Keyword.get opts, :file, "nofile"
line = Keyword.get opts, :line, 1
:elixir.string_to_quoted!(to_char_list(string), line, file, opts)
@@ -266,7 +266,7 @@ defmodule Code do
a given file, the given file will be loaded N times. Check
`require_file/2` if you don't want a file to be loaded concurrently.
"""
def load_file(file, relative_to // nil) when is_binary(file) do
def load_file(file, relative_to \\ nil) when is_binary(file) do
file = find_file(file, relative_to)
:elixir_code_server.call { :acquire, file }
loaded = :elixir_compiler.file file
@@ -289,7 +289,7 @@ defmodule Code do
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
def require_file(file, relative_to \\ nil) when is_binary(file) do
file = find_file(file, relative_to)
case :elixir_code_server.call({ :acquire, file }) do
@@ -346,7 +346,7 @@ defmodule Code do
For compiling many files at once, check `Kernel.ParallelCompiler.files/2`.
"""
def compile_string(string, file // "nofile") when is_binary(file) do
def compile_string(string, file \\ "nofile") when is_binary(file) do
:elixir_compiler.string to_char_list(string), file
end
@@ -357,7 +357,7 @@ defmodule Code do
the first element is the module name and the second one is its
binary.
"""
def compile_quoted(quoted, file // "nofile") when is_binary(file) do
def compile_quoted(quoted, file \\ "nofile") when is_binary(file) do
:elixir_compiler.quoted quoted, file
end
+56 -57
View File
@@ -1,5 +1,5 @@
defmodule Dict do
@moduledoc %S"""
@moduledoc ~S"""
This module specifies the Dict API expected to be
implemented by different dictionaries. It also provides
functions that redirect to the underlying Dict, allowing
@@ -34,21 +34,20 @@ defmodule Dict do
## Match
Dictionaries are required to implement all operations
using the match (`===`) operator. Any deviation from
this behaviour should be avoided and explicitly documented.
using the match (`===`) operator.
"""
use Behaviour
@type key :: any
@type value :: any
@type keys :: [ key ]
@type t :: tuple | list
defcallback new :: t
defcallback new(Keyword.t) :: t
defcallback new(Enum.t) :: t
defcallback new(Enum.t, (any -> { key, value })) :: t
defcallback delete(t, key) :: t
defcallback drop(t, keys) :: t
defcallback drop(t, Enum.t) :: t
defcallback empty(t) :: t
defcallback equal?(t, t) :: boolean
defcallback get(t, key) :: value
@@ -56,16 +55,17 @@ defmodule Dict do
defcallback fetch(t, key) :: { :ok, value } | :error
defcallback fetch!(t, key) :: value | no_return
defcallback has_key?(t, key) :: boolean
defcallback keys(t) :: list(key)
defcallback keys(t) :: [key]
defcallback merge(t, t) :: t
defcallback merge(t, t, (key, value, value -> value)) :: t
defcallback pop(t, key) :: {value, t}
defcallback pop(t, key, value) :: {value, t}
defcallback put(t, key, value) :: t
defcallback put_new(t, key, value) :: t
defcallback reduce(t, Enumerable.acc, Enumerable.reducer) :: Enumerable.result
defcallback size(t) :: non_neg_integer()
defcallback split(t, keys) :: {t, t}
defcallback take(t, keys) :: t
defcallback split(t, Enum.t) :: {t, t}
defcallback take(t, Enum.t) :: t
defcallback to_list(t) :: list()
defcallback update(t, key, value, (value -> value)) :: t
defcallback update!(t, key, (value -> value)) :: t | no_return
@@ -163,7 +163,7 @@ defmodule Dict do
3
"""
@spec get(t, key, value) :: value
def get(dict, key, default // nil) do
def get(dict, key, default \\ nil) do
target(dict).get(dict, key, default)
end
@@ -258,12 +258,15 @@ defmodule Dict do
end
@doc """
Merges the given `enum` into `dict`. If one of the `enum` keys
already exists in `dict`, the `dict` value is replaced by the `enum`
value.
Merges the dict `b` into dict `a`.
The `enum` must yield tuples with two elements on enumeration,
where the first element represents the key and the second the value.
If one of the dict `b` entries already exists in the `dict`,
the functions in entries in `b` have higher precedence unless a
function is given to resolve conflicts.
Notice this function is polymorphic as it merges dicts of any
type. Each dict implementation also provides a `merge` function,
but they can only merge dicts of the same type.
## Examples
@@ -273,22 +276,6 @@ defmodule Dict do
...> [a: Dict.get(d, :a), b: Dict.get(d, :b), d: Dict.get(d, :d)]
[a: 3, b: 2, d: 4]
"""
@spec merge(t, t) :: t
def merge(dict, enum) do
merge(dict, enum, fn(_k, _v1, v2) -> v2 end)
end
@doc """
Merges the given `enum` into `dict`. If one of the `enum` entries
already exists in `dict`, the given function is invoked to resolve
the conflict.
The `enum` must yield tuples with two elements on enumeration,
where the first element represents the key and the second the value.
## Examples
iex> d1 = dict_impl.new([a: 1, b: 2])
...> d2 = dict_impl.new([a: 3, d: 4])
...> d = Dict.merge(d1, d2, fn(_k, v1, v2) ->
@@ -299,8 +286,17 @@ defmodule Dict do
"""
@spec merge(t, t, (key, value, value -> value)) :: t
def merge(dict, enum, fun) do
target(dict).merge(dict, enum, fun)
def merge(dict1, dict2, fun \\ fn(_k, _v1, v2) -> v2 end) do
target1 = target(dict1)
target2 = target(dict2)
if target1 == target2 do
target1.merge(dict1, dict2, fun)
else
target2.reduce(dict2, { :cont, dict1 }, fn({ k, v }, acc) ->
{ :cont, target1.update(acc, k, v, fn(other) -> fun.(k, other, v) end) }
end) |> elem(1)
end
end
@doc """
@@ -326,7 +322,7 @@ defmodule Dict do
"""
@spec pop(t, key, value) :: {value, t}
def pop(dict, key, default // nil) do
def pop(dict, key, default \\ nil) do
target(dict).pop(dict, key, default)
end
@@ -347,11 +343,6 @@ defmodule Dict do
target(dict).update!(dict, key, fun)
end
@doc false
def update(dict, key, fun) do
target(dict).update(dict, key, fun)
end
@doc """
Update a value in `dict` by calling `fun` on the value to get a new value. If
`key` is not present in `dict` then `initial` will be stored as the first
@@ -381,7 +372,7 @@ defmodule Dict do
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) }
...> { Dict.to_list(d1) |> Enum.sort, Dict.to_list(d2) |> Enum.sort }
{ [a: 1, c: 3], [b: 2, d: 4] }
iex> d = dict_impl.new([])
@@ -391,11 +382,11 @@ defmodule Dict do
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) }
...> { Dict.to_list(d1) |> Enum.sort, Dict.to_list(d2) }
{ [a: 1, b: 2], [] }
"""
@spec split(t, keys) :: {t, t}
@spec split(t, [key]) :: {t, t}
def split(dict, keys) do
target(dict).split(dict, keys)
end
@@ -413,18 +404,18 @@ defmodule Dict do
iex> d = dict_impl.new([a: 1, b: 2])
...> d = Dict.drop(d, [:c, :d])
...> Dict.to_list(d)
...> Dict.to_list(d) |> Enum.sort
[a: 1, b: 2]
"""
@spec drop(t, keys) :: t
@spec drop(t, [key]) :: t
def drop(dict, keys) do
target(dict).drop(dict, keys)
end
@doc """
Returns a new dict where only the keys in `keys` from `dict` are
included.
Returns a new dict where only the keys in `keys` from `dict` are included.
Any non-member keys are ignored.
## Examples
@@ -440,7 +431,7 @@ defmodule Dict do
[]
"""
@spec take(t, keys) :: t
@spec take(t, [key]) :: t
def take(dict, keys) do
target(dict).take(dict, keys)
end
@@ -462,13 +453,16 @@ defmodule Dict do
end
@doc """
Check if two dicts are equal using `===`. If the dicts are
of different types, they are first converted to lists.
Check if two dicts are equal using `===`.
Notice this function is polymorphic as it compares dicts of any
type. Each dict implementation also provides an `equal?` function,
but they can only compare dicts of the same type.
## Examples
iex> a = dict_impl.new(a: 2, b: 3, f: 5, c: 123)
...> b = ListDict.new(a: 2, b: 3, f: 5, c: 123)
...> b = [a: 2, b: 3, f: 5, c: 123]
...> Dict.equal?(a, b)
true
@@ -479,16 +473,21 @@ defmodule Dict do
"""
@spec equal?(t, t) :: boolean
def equal?(a, b) do
a_target = target(a)
b_target = target(b)
def equal?(dict1, dict2) do
target1 = target(dict1)
target2 = target(dict2)
cond do
a_target == b_target ->
a_target.equal?(a, b)
target1 == target2 ->
target1.equal?(dict1, dict2)
a_target.size(a) == b_target.size(b) ->
ListDict.equal?(a_target.to_list(a), b_target.to_list(b))
target1.size(dict1) == target2.size(dict2) ->
target2.reduce(dict2, { :cont, true }, fn({ k, v }, _acc) ->
case target1.fetch(dict1, k) do
{ :ok, ^v } -> { :cont, true }
_ -> { :halt, false }
end
end) |> elem(1)
true ->
false
+21 -22
View File
@@ -50,7 +50,7 @@ defmodule Dict.Behaviour do
Note you can also test your custom module via `Dict`'s doctests:
defmodule MyDict do
def new(keywords // []) do
def new(keywords \\ []) do
...
end
end
@@ -63,10 +63,13 @@ defmodule Dict.Behaviour do
"""
defmacro __using__(_) do
# Use this import to guarantee proper code expansion
import Kernel, except: [size: 1]
quote do
@behaviour Dict
def get(dict, key, default // nil) do
def get(dict, key, default \\ nil) do
case fetch(dict, key) do
{ :ok, value } -> value
:error -> default
@@ -88,21 +91,16 @@ defmodule Dict.Behaviour do
update(dict, key, value, fn(v) -> v end)
end
def drop(dict, []), do: dict
def drop(dict, [key|keys]) do
drop(delete(dict, key), keys)
def drop(dict, keys) do
Enum.reduce keys, dict, &delete(&2, &1)
end
def take(dict, keys) do
take(dict, keys, new)
end
defp take(_dict, [], acc), do: acc
defp take(dict, [key|keys], acc) do
case fetch(dict, key) do
{ :ok, value } -> take(dict, keys, put(acc, key, value))
:error -> take(dict, keys, acc)
Enum.reduce keys, new, fn key, acc ->
case fetch(dict, key) do
{ :ok, value } -> put(acc, key, value)
:error -> acc
end
end
end
@@ -125,24 +123,25 @@ defmodule Dict.Behaviour do
end
def equal?(dict1, dict2) do
# Use this import to avoid conflicts in the user code
import Kernel, except: [size: 1]
case size(dict1) == size(dict2) do
false -> false
true ->
reduce(dict1, { :cont, true }, fn({ k, v }, _acc) ->
unless fetch(dict2, k) == { :ok, v } do
{ :halt, false }
else
{ :cont, true }
case fetch(dict2, k) do
{ :ok, ^v } -> { :cont, true }
_ -> { :halt, false }
end
end) |> elem(1)
end
end
def merge(dict, enumerable, callback // fn(_k, _v1, v2) -> v2 end) do
Enum.reduce(enumerable, dict, fn({key, value}, acc) ->
update(acc, key, value, fn(v1) -> callback.(key, v1, value) end)
end)
def merge(dict1, dict2, fun \\ fn(_k, _v1, v2) -> v2 end) do
reduce(dict1, { :cont, dict2 }, fn { k, v1 }, acc ->
{ :cont, update(acc, k, v1, &fun.(k, v1, &1)) }
end) |> elem(1)
end
defoverridable merge: 2, merge: 3, equal?: 2, to_list: 1, keys: 1,
+13 -27
View File
@@ -145,7 +145,7 @@ defmodule Enum do
enumeration. For dicts, the argument is always a `{ key, value }` tuple:
iex> dict = HashDict.new [a: 1, b: 2]
iex> Enum.map(dict, fn { k, v } -> { k, v * 2 } end)
iex> Enum.map(dict, fn { k, v } -> { k, v * 2 } end) |> Enum.sort
[a: 2, b: 4]
Note that the functions in the `Enum` module are eager: they always start
@@ -209,7 +209,7 @@ defmodule Enum do
@spec all?(t) :: boolean
@spec all?(t, (element -> as_boolean(term))) :: boolean
def all?(collection, fun // fn(x) -> x end)
def all?(collection, fun \\ fn(x) -> x end)
def all?(collection, fun) when is_list(collection) do
do_all?(collection, fun)
@@ -245,7 +245,7 @@ defmodule Enum do
@spec any?(t) :: boolean
@spec any?(t, (element -> as_boolean(term))) :: boolean
def any?(collection, fun // fn(x) -> x end)
def any?(collection, fun \\ fn(x) -> x end)
def any?(collection, fun) when is_list(collection) do
do_any?(collection, fun)
@@ -275,7 +275,7 @@ defmodule Enum do
"""
@spec at(t, integer) :: element | nil
@spec at(t, integer, default) :: element | default
def at(collection, n, default // nil) do
def at(collection, n, default \\ nil) do
case fetch(collection, n) do
{ :ok, h } -> h
:error -> default
@@ -315,7 +315,7 @@ defmodule Enum do
"""
@spec chunk(t, non_neg_integer, non_neg_integer) :: [list]
@spec chunk(t, non_neg_integer, non_neg_integer, t | nil) :: [list]
def chunk(coll, n, step, pad // nil) when n > 0 and step > 0 do
def chunk(coll, n, step, pad \\ nil) when n > 0 and step > 0 do
limit = :erlang.max(n, step)
{ _, { acc, { buffer, i } } } =
@@ -671,7 +671,7 @@ defmodule Enum do
"""
@spec find(t, (element -> any)) :: element | nil
@spec find(t, default, (element -> any)) :: element | default
def find(collection, ifnone // nil, fun)
def find(collection, ifnone \\ nil, fun)
def find(collection, ifnone, fun) when is_list(collection) do
do_find(collection, ifnone, fun)
@@ -698,7 +698,7 @@ defmodule Enum do
"""
@spec find_value(t, (element -> any)) :: any | :nil
@spec find_value(t, any, (element -> any)) :: any | :nil
def find_value(collection, ifnone // nil, fun)
def find_value(collection, ifnone \\ nil, fun)
def find_value(collection, ifnone, fun) when is_list(collection) do
do_find_value(collection, ifnone, fun)
@@ -741,20 +741,6 @@ defmodule Enum do
end
end
@doc false
@spec first(t) :: :nil | element
def first(list) when is_list(list) do
IO.write "Enum.first/1 is deprecated, please use Enum.at/2 or List.first/1 instead\n#{Exception.format_stacktrace}"
List.first(list)
end
def first(collection) do
IO.write "Enum.first/1 is deprecated, please use Enum.at/2 instead\n#{Exception.format_stacktrace}"
Enumerable.reduce(collection, { :cont, nil }, fn(entry, _) ->
{ :halt, entry }
end) |> elem(1)
end
@doc """
Returns a new collection appending the result of invoking `fun`
on each corresponding item of `collection`.
@@ -795,7 +781,7 @@ defmodule Enum do
{ [1,2,3], 3 }
"""
@spec flat_map_reduce(t, acc, fun) :: list when
@spec flat_map_reduce(t, acc, fun) :: { [any], any } when
fun: (element, acc -> { t, acc } | { :halt, acc }),
acc: any
def flat_map_reduce(collection, acc, fun) do
@@ -860,7 +846,7 @@ defmodule Enum do
"""
@spec join(t) :: String.t
@spec join(t, String.t) :: String.t
def join(collection, joiner // "")
def join(collection, joiner \\ "")
def join(collection, joiner) when is_binary(joiner) do
reduce(collection, "", fn
@@ -913,7 +899,7 @@ defmodule Enum do
"""
@spec map_join(t, (element -> any)) :: String.t
@spec map_join(t, String.t, (element -> any)) :: String.t
def map_join(collection, joiner // "", mapper)
def map_join(collection, joiner \\ "", mapper)
def map_join(collection, joiner, mapper) when is_binary(joiner) do
reduce(collection, "", fn
@@ -1034,8 +1020,8 @@ defmodule Enum do
value
{ :error, module } ->
module.reduce(collection, { :cont, false }, fn
^value, _ -> { :halt, true }
_, _ -> { :cont, false }
v, _ when v === value -> { :halt, true }
_, _ -> { :cont, false }
end) |> elem(1)
end
end
@@ -1599,7 +1585,7 @@ defmodule Enum do
"""
@spec uniq(t) :: list
@spec uniq(t, (element -> term)) :: list
def uniq(collection, fun // fn x -> x end)
def uniq(collection, fun \\ fn x -> x end)
def uniq(collection, fun) when is_list(collection) do
do_uniq(collection, [], fun)
+52 -51
View File
@@ -10,21 +10,21 @@ defexception SystemLimitError, message: "a system limit has been reached"
defexception SyntaxError, [file: nil, line: nil, description: "syntax error"] do
def message(exception) do
Exception.format_file_line(Path.relative_to_cwd(exception.file), exception.line) <>
exception.description
" " <> exception.description
end
end
defexception TokenMissingError, [file: nil, line: nil, description: "expression is incomplete"] do
def message(exception) do
Exception.format_file_line(Path.relative_to_cwd(exception.file), exception.line) <>
exception.description
" " <> exception.description
end
end
defexception CompileError, [file: nil, line: nil, description: "compile error"] do
def message(exception) do
Exception.format_file_line(Path.relative_to_cwd(exception.file), exception.line) <>
exception.description
" " <> exception.description
end
end
@@ -54,9 +54,16 @@ end
defexception BadArityError, [function: nil, args: nil] do
def message(exception) do
args = Enum.map_join(exception.args, ", ", &inspect/1)
"bad arity error: #{inspect(exception.function)} called with (#{args})"
fun = exception.function
args = exception.args
insp = Enum.map_join(args, ", ", &inspect/1)
{ :arity, arity } = :erlang.fun_info(fun, :arity)
"#{inspect(fun)} with arity #{arity} called with #{count(length(args), insp)}"
end
defp count(0, _insp), do: "no arguments"
defp count(1, insp), do: "1 argument (#{insp})"
defp count(x, insp), do: "#{x} arguments (#{insp})"
end
defexception UndefinedFunctionError, [module: nil, function: nil, arity: nil] do
@@ -142,6 +149,15 @@ defmodule Exception do
pass the `System.stacktrace` as argument.
"""
@type stacktrace :: [stacktrace_entry]
@type stacktrace_entry ::
{ module, function, arity_or_args, location } |
{ function, arity_or_args, location }
@typep arity_or_args :: non_neg_integer | list
@typep location :: Keyword.t
@doc """
Normalizes an exception, converting Erlang exceptions
to Elixir exceptions.
@@ -214,8 +230,9 @@ defmodule Exception do
end
@doc """
Receives a tuple representing a stacktrace entry and formats it.
Receives an stacktrace entry and formats it into a string.
"""
@spec format_stacktrace_entry(stacktrace_entry) :: String.t
def format_stacktrace_entry(entry)
# From Macro.Env.stacktrace
@@ -255,43 +272,18 @@ defmodule Exception do
calculates a new stacktrace based on the caller and formats it. As
a consequence, the value of `System.stacktrace` is changed.
"""
def format_stacktrace(trace // nil) do
def format_stacktrace(trace \\ nil) do
trace = trace || try do
throw(:stacktrace)
catch
:stacktrace -> Enum.drop(:erlang.get_stacktrace, 1)
end
case trace do
[] -> "\n"
s -> " " <> Enum.map_join(s, "\n ", &format_stacktrace_entry(&1)) <> "\n"
end
end
@doc """
Formats the caller, i.e. the first entry in the stacktrace.
A stacktrace must be given as an argument. If not, this function
calculates a new stacktrace based on the caller and formats it. As
a consequence, the value of `System.stacktrace` is changed.
Notice that due to tail call optimization, the stacktrace
may not report the direct caller of the function.
"""
def format_caller(trace // nil) do
trace = trace || try do
throw(:stacktrace)
catch
:stacktrace -> Enum.drop(:erlang.get_stacktrace, 1)
end
if entry = Enum.at(trace, 1) do
format_stacktrace_entry(entry)
else
"nofile:0: "
end
end
@doc """
Receives an anonymous function and arity and formats it as
shown in stacktraces. The arity may also be a list of arguments.
@@ -303,12 +295,7 @@ defmodule Exception do
"""
def format_fa(fun, arity) do
if is_list(arity) do
inspected = lc x inlist arity, do: inspect(x)
"#{inspect fun}(#{Enum.join(inspected, ", ")})"
else
"#{inspect fun}/#{arity}"
end
"#{inspect fun}#{format_arity(arity)}"
end
@doc """
@@ -325,22 +312,32 @@ defmodule Exception do
iex> Exception.format_mfa nil, :bar, []
"nil.bar()"
Anonymous functions are reported as -func/arity-anonfn-count-,
where func is the name of the enclosing function. Convert to
"anonymous fn in func/arity"
"""
def format_mfa(module, fun, arity) do
def format_mfa(module, fun, arity) when is_atom(fun) do
fun =
case inspect(fun) do
<< ?:, erl :: binary >> -> erl
elixir -> elixir
":" <> fun -> fun
fun -> fun
end
if is_list(arity) do
inspected = lc x inlist arity, do: inspect(x)
"#{inspect module}.#{fun}(#{Enum.join(inspected, ", ")})"
else
"#{inspect module}.#{fun}/#{arity}"
case match?("\"-" <> _, fun) and String.split(fun, "-") do
[ "\"", outer_fun, "fun", _count, "\"" ] ->
"anonymous fn#{format_arity(arity)} in #{inspect module}.#{outer_fun}"
_ ->
"#{inspect module}.#{fun}#{format_arity(arity)}"
end
end
defp format_arity(arity) when is_list(arity) do
inspected = lc x inlist arity, do: inspect(x)
"(#{Enum.join(inspected, ", ")})"
end
defp format_arity(arity), do: "/#{arity}"
@doc """
Formats the given file and line as shown in stacktraces.
If any of the values are nil, they are omitted.
@@ -348,21 +345,25 @@ defmodule Exception do
## Examples
iex> Exception.format_file_line("foo", 1)
"foo:1: "
"foo:1:"
iex> Exception.format_file_line("foo", nil)
"foo: "
"foo:"
iex> Exception.format_file_line(nil, nil)
""
"""
def format_file_line(file, line) do
format_file_line(file, line, "")
end
defp format_file_line(file, line, suffix) do
if file do
if line && line != 0 do
"#{file}:#{line}: "
"#{file}:#{line}:#{suffix}"
else
"#{file}: "
"#{file}:#{suffix}"
end
else
""
@@ -370,7 +371,7 @@ defmodule Exception do
end
defp format_location(opts) do
format_file_line Keyword.get(opts, :file), Keyword.get(opts, :line)
format_file_line Keyword.get(opts, :file), Keyword.get(opts, :line), " "
end
defp from_stacktrace([{ module, function, args, _ }|_]) when is_list(args) do
+42 -72
View File
@@ -220,7 +220,7 @@ defmodule File do
Default is `:local`.
"""
def stat(path, opts // []) do
def stat(path, opts \\ []) do
case F.read_file_info(path, opts) do
{:ok, fileinfo} ->
{:ok, set_elem(fileinfo, 0, File.Stat) }
@@ -233,7 +233,7 @@ defmodule File do
Same as `stat/2` but returns the `File.Stat` directly and
throws `File.Error` if an error is returned.
"""
def stat!(path, opts // []) do
def stat!(path, opts \\ []) do
case stat(path, opts) do
{:ok, info} -> info
{:error, reason} ->
@@ -245,7 +245,7 @@ defmodule File do
Writes the given `File.Stat` back to the filesystem at the given
path. Returns `:ok` or `{ :error, reason }`.
"""
def write_stat(path, File.Stat[] = stat, opts // []) do
def write_stat(path, File.Stat[] = stat, opts \\ []) do
F.write_file_info(path, set_elem(stat, 0, :file_info), opts)
end
@@ -253,7 +253,7 @@ defmodule File do
Same as `write_stat/3` but raises an exception if it fails.
Returns `:ok` otherwise.
"""
def write_stat!(path, File.Stat[] = stat, opts // []) do
def write_stat!(path, File.Stat[] = stat, opts \\ []) do
case write_stat(path, stat, opts) do
:ok -> :ok
{ :error, reason } ->
@@ -265,7 +265,7 @@ defmodule File do
Updates modification time (mtime) and access time (atime) of
the given file. File is created if it doesn’t exist.
"""
def touch(path, time // :calendar.local_time) do
def touch(path, time \\ :calendar.local_time) do
case F.change_time(path, time) do
{ :error, :enoent } ->
write(path, "")
@@ -279,7 +279,7 @@ defmodule File do
Same as `touch/2` but raises an exception if it fails.
Returns `:ok` otherwise.
"""
def touch!(path, time // :calendar.local_time) do
def touch!(path, time \\ :calendar.local_time) do
case touch(path, time) do
:ok -> :ok
{ :error, reason } ->
@@ -309,7 +309,7 @@ defmodule File do
Typical error reasons are the same as in `open/2`,
`read/1` and `write/3`.
"""
def copy(source, destination, bytes_count // :infinity) do
def copy(source, destination, bytes_count \\ :infinity) do
F.copy(source, destination, bytes_count)
end
@@ -317,7 +317,7 @@ defmodule File do
The same as `copy/3` but raises an `File.CopyError` if it fails.
Returns the `bytes_copied` otherwise.
"""
def copy!(source, destination, bytes_count // :infinity) do
def copy!(source, destination, bytes_count \\ :infinity) do
case copy(source, destination, bytes_count) do
{ :ok, bytes_count } -> bytes_count
{ :error, reason } ->
@@ -329,11 +329,6 @@ defmodule File do
@doc """
Copies the contents in `source` to `destination` preserving its mode.
Similar to the command `cp` in Unix systems, this function
behaves differently depending if `destination` is a directory
or not. In particular, if `destination` is a directory, it
copies the contents of `source` to `destination/source`.
If a file already exists in the destination, it invokes a
callback which should return `true` if the existing file
should be overwritten, `false` otherwise. It defaults to return `true`.
@@ -344,17 +339,14 @@ defmodule File do
If you want to copy contents from an io device to another device
or do a straight copy from a source to a destination without
preserving modes, check `copy/3` instead.
"""
def cp(source, destination, callback // fn(_, _) -> true end) do
output =
if dir?(destination) do
mkdir(destination)
FN.join(destination, FN.basename(source))
else
destination
end
case do_cp_file(source, output, callback, []) do
Note: The command `cp` in Unix systems behaves differently depending
if `destination` is an existing directory or not. We have chosen to
explicitly disallow this behaviour. If destination is a directory, an
error will be returned.
"""
def cp(source, destination, callback \\ fn(_, _) -> true end) do
case do_cp_file(source, destination, callback, []) do
{ :error, reason, _ } -> { :error, reason }
_ -> :ok
end
@@ -364,7 +356,7 @@ defmodule File do
The same as `cp/3`, but raises `File.CopyError` if it fails.
Returns the list of copied files otherwise.
"""
def cp!(source, destination, callback // fn(_, _) -> true end) do
def cp!(source, destination, callback \\ fn(_, _) -> true end) do
case cp(source, destination, callback) do
:ok -> :ok
{ :error, reason } ->
@@ -374,16 +366,12 @@ defmodule File do
end
end
@doc %S"""
@doc ~S"""
Copies the contents in source to destination.
Similar to the command `cp -r` in Unix systems,
this function behaves differently depending
if `source` and `destination` are a file or a directory.
If both are files, it simply copies `source` to
`destination`. However, if `destination` is a directory,
it copies the contents of `source` to `destination/source`
recursively.
If the source is a file, it copies `source` to
`destination`. If the source is a directory, it copies
the contents inside source into the destination.
If a file already exists in the destination,
it invokes a callback which should return
@@ -403,33 +391,26 @@ defmodule File do
success with all files and directories copied in no
specific order, `{ :error, reason }` otherwise.
Note: The command `cp` in Unix systems behaves differently
depending if `destination` is an existing directory or not.
We have chosen to explicitly disallow this behaviour.
## Examples
# Copies "a.txt" to "tmp/a.txt"
File.cp_r "a.txt", "tmp"
# Copies all files in "samples" to "tmp/samples"
File.cp_r "samples", "tmp"
# Copies "a.txt" to "tmp"
File.cp_r "a.txt", "tmp.txt"
# Copies all files in "samples" to "tmp"
File.cp_r "samples/.", "tmp"
File.cp_r "samples", "tmp"
# Same as before, but asks the user how to proceed in case of conflicts
File.cp_r "samples/.", "tmp", fn(source, destination) ->
File.cp_r "samples", "tmp", fn(source, destination) ->
IO.gets("Overwriting #{destination} by #{source}. Type y to confirm.") == "y"
end
"""
def cp_r(source, destination, callback // fn(_, _) -> true end) when is_function(callback) do
output =
if dir?(destination) || dir?(source) do
mkdir(destination)
FN.join(destination, FN.basename(source))
else
destination
end
case do_cp_r(source, output, callback, []) do
def cp_r(source, destination, callback \\ fn(_, _) -> true end) when is_function(callback) do
case do_cp_r(source, destination, callback, []) do
{ :error, _, _ } = error -> error
res -> { :ok, res }
end
@@ -439,7 +420,7 @@ defmodule File do
The same as `cp_r/3`, but raises `File.CopyError` if it fails.
Returns the list of copied files otherwise.
"""
def cp_r!(source, destination, callback // fn(_, _) -> true end) do
def cp_r!(source, destination, callback \\ fn(_, _) -> true end) do
case cp_r(source, destination, callback) do
{ :ok, files } -> files
{ :error, reason, file } ->
@@ -547,14 +528,14 @@ defmodule File do
Check `File.open/2` for existing modes.
"""
def write(path, content, modes // []) do
def write(path, content, modes \\ []) do
F.write_file(path, content, modes)
end
@doc """
Same as `write/3` but raises an exception if it fails, returns `:ok` otherwise.
"""
def write!(path, content, modes // []) do
def write!(path, content, modes \\ []) do
case F.write_file(path, content, modes) do
:ok -> :ok
{ :error, reason } ->
@@ -767,7 +748,7 @@ defmodule File do
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
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:
@@ -788,7 +769,7 @@ defmodule File do
File.close(file)
"""
def open(path, modes // [])
def open(path, modes \\ [])
def open(path, modes) when is_list(modes) do
F.open(path, open_defaults(modes, true))
@@ -835,7 +816,7 @@ defmodule File do
Same as `open/2` but raises an error if file could not be opened.
Returns the `io_device` otherwise.
"""
def open!(path, modes // []) do
def open!(path, modes \\ []) do
case open(path, modes) do
{ :ok, device } -> device
{ :error, reason } ->
@@ -922,7 +903,7 @@ defmodule File do
It returns `{ :ok, [files] }` in case of success,
`{ :error, reason }` otherwise.
"""
def ls(path // ".") do
def ls(path \\ ".") do
case F.list_dir(path) do
{ :ok, file_list } -> { :ok, Enum.map(file_list, &to_string(&1)) }
{ :error, _ } = error -> error
@@ -933,7 +914,7 @@ defmodule File do
The same as `ls/1` but raises `File.Error`
in case of an error.
"""
def ls!(dir // ".") do
def ls!(dir \\ ".") do
case ls(dir) do
{ :ok, value } -> value
{ :error, reason } ->
@@ -966,7 +947,7 @@ defmodule File do
the file is opened with an encoding, then the slower `IO.read/2`
is used to do the proper data conversion and guarantees.
"""
def stream!(path, modes // [], line_or_bytes // :line) do
def stream!(path, modes \\ [], line_or_bytes \\ :line) do
modes = open_defaults(modes, true)
bin = nil? List.keyfind(modes, :encoding, 0)
@@ -988,20 +969,9 @@ defmodule File do
Stream.resource(start_fun, next_fun, &F.close/1)
end
@doc """
Receives a stream and returns a new stream that will open the file
at the given `path` for write with the extra `modes` and write
each value to the file.
The returned stream will fail for the same reasons as
`File.open!/2`. Note that the file is opened only and every time
streaming begins.
Note that stream by default uses `IO.binwrite/2` unless
the file is opened with an encoding, then the slower `IO.write/2`
is used to do the proper data conversion and guarantees.
"""
def stream_to!(stream, path, modes // []) do
@doc false
def stream_to!(stream, path, modes \\ []) do
IO.write :stderr, "File.stream_to!/3 is deprecated and will be removed/1\n#{Exception.format_stacktrace}"
modes = open_defaults([:write|List.delete(modes, :write)], true)
bin = nil? List.keyfind(modes, :encoding, 0)
+2 -2
View File
@@ -72,7 +72,7 @@ defmodule Float do
{ floatify(int, float, decimal), bitstring }
end
defp floatify(int, float, decimal, exponential // 0) do
defp floatify(int, float, decimal, exponential \\ 0) do
multiplier = if int < 0, do: -1.0, else: 1.0
# Try to ensure the minimum amount of rounding errors
@@ -82,7 +82,7 @@ defmodule Float do
# iex(1)> 0.0001 * 75
# 0.007500000000000001
# Due to IEEE 754 floating point standard
# http://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html
# http:\\docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html
final_decimal_places = decimal - exponential
if final_decimal_places > 0 do
+13 -11
View File
@@ -31,15 +31,15 @@ defmodule GenFSM.Behaviour do
# API functions
def start_link() do
:gen_fsm.start_link({:local, :cvm}, __MODULE__, [], [])
:gen_fsm.start_link(__MODULE__, [], [])
end
def insert_coin() do
:gen_fsm.send_event(:cvm, :coin)
def insert_coin(pid) do
:gen_fsm.send_event(pid, :coin)
end
def request_coffee() do
:gen_fsm.send_event(:cvm, :request_coffee)
def request_coffee(pid) do
:gen_fsm.send_event(pid, :request_coffee)
end
# Callbacks
@@ -83,17 +83,17 @@ defmodule GenFSM.Behaviour do
end
end
{ :ok, _pid } = MyFsm.start_link()
{ :ok, pid } = MyFsm.start_link()
MyFsm.insert_coin
MyFsm.insert_coin(pid)
#=> :ok
MyFsm.insert_coin
MyFsm.insert_coin(pid)
#=> :ok
MyFsm.request_coffee
MyFsm.request_coffee(pid)
#=> :ok
MyFsm.insert_coin
MyFsm.insert_coin(pid)
#=> :ok
#=> Here's your coffee!
@@ -166,7 +166,9 @@ defmodule GenFSM.Behaviour do
@doc false
defmacro __using__(_) do
quote location: :keep do
@behavior :gen_fsm
IO.puts :stderr, "warning: GenFSM is deprecated and it will be removed in upcoming releases\n#{Exception.format_stacktrace}"
@behaviour :gen_fsm
@doc false
def handle_event(event, state_name, state_data) do
+1 -1
View File
@@ -86,7 +86,7 @@ defmodule GenServer.Behaviour do
@doc false
defmacro __using__(_) do
quote location: :keep do
@behavior :gen_server
@behaviour :gen_server
@doc false
def init(args) do
+156 -356
View File
@@ -2,46 +2,14 @@ defmodule HashDict do
@moduledoc """
A key-value store.
The `HashDict` is meant to work well with both small and
large set of keys and it is an implementation of the `Dict`
behaviour. For more information about the functions and
their APIs, please consult the `Dict` module.
The `HashDict` is implemented using tries, which grows in
space as the number of keys grows, working well with both
small and large set of keys. For more information about the
functions and their APIs, please consult the `Dict` module.
"""
use Dict.Behaviour
# A dictionary (key-value) implementation based on dynamic hashing.
#
# This implementation is based on hash tries. We first start with
# a set of 8 buckets and expand when the density is about 5 entries
# per bucket. We use bit shifting to make rehashing faster on
# expansion.
#
# Compared to dict, it provides many enhancements:
#
# 1. HashDict buckets are ordered sets, this gives us faster access
# and modification times
#
# 2. It uses phash2 to calculate the hash (instead of phash)
#
# 3. The dictionary first starts with a single bucket, instead of
# a set of 8 buckets. This allow us to skip hashing altogher
# for small dictionaries, providing faster operations and
# reducing memory consumption
#
# 4. Once we reach 8 elements, the dictionary is promoted to a
# set of buckets
# The ordered record contains a single bucket
@ordered_threshold 8
defrecordp :ordered, HashDict,
size: 0,
bucket: []
# The bucketed record contains a series of buckets.
@expand_load 5
@contract_load 2
@node_bitmap 0b111
@node_shift 3
@node_size 8
@@ -49,23 +17,18 @@ defmodule HashDict do
defrecordp :trie, HashDict,
size: 0,
depth: 0,
expand_on: @node_size * @expand_load,
contract_on: @contract_load,
root: @node_template
import Bitwise
# Let's inline common instructions
# Inline common instructions
@compile :inline_list_funcs
@compile { :inline, bucket_hash: 1, bucket_index: 1, bucket_nth_index: 2, bucket_next: 1 }
@compile { :inline, key_hash: 1, key_mask: 1, key_shift: 1 }
@doc """
Creates a new empty dict.
"""
@spec new :: Dict.t
def new do
ordered()
trie()
end
@doc """
@@ -77,9 +40,9 @@ defmodule HashDict do
#=> #HashDict<[a: 2, b: 1]>
"""
@spec new(list({key :: term, value :: term})) :: Dict.t
def new(pairs) do
Enum.reduce pairs, ordered(), fn { k, v }, dict ->
@spec new(Enum.t) :: Dict.t
def new(enum) do
Enum.reduce enum, trie(), fn { k, v }, dict ->
put(dict, k, v)
end
end
@@ -94,386 +57,223 @@ defmodule HashDict do
#=> #HashDict<[{"a","a"},{"b","b"}]>
"""
@spec new(list, (term -> {key :: term, value ::term})) :: Dict.t
def new(list, transform) when is_function(transform) do
Enum.reduce list, new(), fn i, dict ->
@spec new(Enum.t, (term -> {key :: term, value ::term})) :: Dict.t
def new(enum, transform) when is_function(transform) do
Enum.reduce enum, trie(), fn i, dict ->
{ k, v } = transform.(i)
put(dict, k, v)
end
end
def put(dict, key, value) do
{ dict, _ } = dict_put(dict, key, { :put, value })
dict
def empty(trie()) do
trie()
end
def update!(dict, key, fun) when is_function(fun, 1) do
case dict_put(dict, key, { :update, nil, fun }) do
{ dict, 0 } ->
dict
{ _dict, 1 } ->
raise KeyError, key: key
end
def put(trie(root: root, size: size), key, value) do
{ root, counter } = do_put(root, key, value, key_hash(key))
trie(root: root, size: size + counter)
end
def update(dict, key, initial, fun) when is_function(fun, 1) do
{ dict, _ } = dict_put(dict, key, { :update, initial, fun })
dict
def update!(trie(root: root, size: size), key, fun) when is_function(fun, 1) do
{ root, counter } = do_update(root, key, fn -> raise KeyError, key: key end, fun, key_hash(key))
trie(root: root, size: size + counter)
end
def fetch(ordered(bucket: bucket), key) do
bucket_get(bucket, key)
def update(trie(root: root, size: size), key, initial, fun) when is_function(fun, 1) do
{ root, counter } = do_update(root, key, fn -> initial end, fun, key_hash(key))
trie(root: root, size: size + counter)
end
def fetch(trie(root: root, depth: depth), key) do
bucket_get(node_bucket(root, depth, bucket_hash(key)), key)
end
def pop(dict, key, default // nil) do
case dict_delete(dict, key) do
{ dict, _, 0 } -> { default, dict }
{ dict, value, _ } -> { value, dict }
end
def fetch(trie(root: root), key) do
do_fetch(root, key, key_hash(key))
end
def delete(dict, key) do
{ dict, _, _ } = dict_delete(dict, key)
dict
end
def size(dict) do
elem(dict, 1)
end
def empty(_) do
ordered()
end
def merge(dict, enum, callback // fn(_k, _v1, v2) -> v2 end)
def merge(dict1, dict2, callback) when is_record(dict1, HashDict) and is_record(dict2, HashDict) and elem(dict1, 1) < elem(dict2, 1) do
dict_fold dict1, dict2, fn [k|v1], acc ->
update(acc, k, v1, &callback.(k, v1, &1))
case dict_delete(dict, key) do
{ dict, _value } -> dict
:error -> dict
end
end
def merge(dict1, dict2, callback) when is_record(dict1, HashDict) and is_record(dict2, HashDict) do
dict_fold dict2, dict1, fn [k|v2], acc ->
update(acc, k, v2, &callback.(k, &1, v2))
def pop(dict, key, default \\ nil) do
case dict_delete(dict, key) do
{ dict, value } -> { value, dict }
:error -> { default, dict }
end
end
def merge(dict, enumerable, callback) when is_record(dict, HashDict) do
super(dict, enumerable, callback)
def size(trie(size: size)) do
size
end
def reduce(trie(root: root), acc, fun) do
do_reduce(root, acc, fun, @node_size, fn
{:suspend, acc} -> {:suspended, acc, &{ :done, elem(&1, 1) }}
{:halt, acc} -> {:halted, acc}
{:cont, acc} -> {:done, acc}
end)
end
def split(dict, keys) do
split(keys, new, dict)
end
defp split([], including, excluding) do
{ including, excluding }
end
defp split([key|keys], including, excluding) do
case dict_delete(excluding, key) do
{ excluding, _, 0 } -> split(keys, including, excluding)
{ excluding, value, _ } -> split(keys, put(including, key, value), excluding)
Enum.reduce keys, { new, dict }, fn key, { inc, exc } = acc ->
case dict_delete(exc, key) do
{ exc, value } -> { put(inc, key, value), exc }
:error -> acc
end
end
end
@doc false
def reduce(ordered(bucket: bucket), acc, fun) do
bucket_reduce(bucket, acc, fun)
def merge(trie(size: size1) = dict1, trie(size: size2) = dict2, callback) when size1 < size2 do
reduce(dict1, { :cont, dict2 }, fn { k, v1 }, acc ->
{ :cont, update(acc, k, v1, &callback.(k, v1, &1)) }
end) |> elem(1)
end
def reduce(trie(root: root, depth: depth), acc, fun) do
node_reduce(root, depth, acc, fun, @node_size,
fn { :cont, acc }, _f -> { :done, acc } end)
def merge(trie() = dict1, trie() = dict2, callback) do
reduce(dict2, { :cont, dict1 }, fn { k, v2 }, acc ->
{ :cont, update(acc, k, v2, &callback.(k, &1, v2)) }
end) |> elem(1)
end
## Dict-wide functions
## General helpers
defp dict_fold(ordered(bucket: bucket), acc, fun) do
bucket_fold(bucket, acc, fun)
end
defp dict_fold(trie(root: root, depth: depth), acc, fun) do
node_fold(root, depth, acc, fun, @node_size)
end
defp dict_put(ordered(size: @ordered_threshold, bucket: bucket), key, value) do
root = node_relocate(bucket, 0)
dict_put(trie(size: @ordered_threshold, root: root), key, value)
end
defp dict_put(ordered(size: size, bucket: bucket) = dict, key, value) do
{ new, count } = bucket_put(bucket, key, value)
{ ordered(dict, size: size + count, bucket: new), count }
end
defp dict_put(trie(root: root, depth: depth, size: size, expand_on: size, contract_on: contract_on) = dict, key, value) do
root = node_expand(root, depth, depth + 1)
dict = trie(dict, root: root, depth: depth + 1,
expand_on: size * @node_size, contract_on: contract_on * @node_size)
dict_put(dict, key, value)
end
defp dict_put(trie(root: root, size: size, depth: depth) = dict, key, value) do
pos = bucket_hash(key)
{ root, count } = node_put(root, depth, pos, key, value)
{ trie(dict, size: size + count, root: root), count }
end
defp dict_delete(ordered(bucket: bucket, size: size) = dict, key) do
case bucket_delete(bucket, key) do
{ _, value, 0 } ->
{ dict, value, 0 }
{ new_bucket, value, -1 } ->
{ ordered(dict, size: size - 1, bucket: new_bucket), value, -1 }
defp dict_delete(trie(root: root, size: size), key) do
case do_delete(root, key, key_hash(key)) do
{ root, value } -> { trie(root: root, size: size - 1), value }
:error -> :error
end
end
defp dict_delete(trie(root: root, size: size, depth: depth) = dict, key) do
pos = bucket_hash(key)
case node_delete(root, depth, pos, key) do
{ _, value, 0 } ->
{ dict, value, 0 }
{ root, value, -1 } ->
{ if depth > 0 and trie(dict, :contract_on) == size do
root = node_contract(root, depth)
trie(dict,
root: root,
size: size - 1,
depth: depth - 1,
contract_on: div(size, @node_size),
expand_on: div(trie(dict, :expand_on), @node_size))
else
trie(dict, size: size - 1, root: root)
end, value, -1 }
## Dict manipulation
defp do_fetch(node, key, hash) do
index = key_mask(hash)
case elem(node, index) do
[^key|v] -> {:ok, v}
{^key, v, _} -> {:ok, v}
{_, _, n} -> do_fetch(n, key, key_shift(hash))
_ -> :error
end
end
## Bucket helpers
# Get value from the bucket
defp bucket_get([[k|_]|_bucket], key) when k > key do
:error
defp do_put(node, key, value, hash) do
index = key_mask(hash)
case elem(node, index) do
[] ->
{set_elem(node, index, [key|value]), 1}
[^key|_] ->
{set_elem(node, index, [key|value]), 0}
[k|v] ->
n = set_elem(@node_template, key_mask(key_shift(hash)), [key|value])
{set_elem(node, index, {k, v, n}), 1}
{^key, _, n} ->
{set_elem(node, index, {key, value, n}), 0}
{k, v, n} ->
{n, counter} = do_put(n, key, value, key_shift(hash))
{set_elem(node, index, {k, v, n}), counter}
end
end
defp bucket_get([[key|value]|_bucket], key) do
{ :ok, value }
defp do_update(node, key, initial, fun, hash) do
index = key_mask(hash)
case elem(node, index) do
[] ->
{set_elem(node, index, [key|initial.()]), 1}
[^key|value] ->
{set_elem(node, index, [key|fun.(value)]), 0}
[k|v] ->
n = set_elem(@node_template, key_mask(key_shift(hash)), [key|initial.()])
{set_elem(node, index, {k, v, n}), 1}
{^key, value, n} ->
{set_elem(node, index, {key, fun.(value), n}), 0}
{k, v, n} ->
{n, counter} = do_update(n, key, initial, fun, key_shift(hash))
{set_elem(node, index, {k, v, n}), counter}
end
end
defp bucket_get([_e|bucket], key) do
bucket_get(bucket, key)
defp do_delete(node, key, hash) do
index = key_mask(hash)
case elem(node, index) do
[] ->
:error
[^key|value] ->
{set_elem(node, index, []), value}
[_|_] ->
:error
{^key, value, n} ->
{set_elem(node, index, do_compact_node(n)), value}
{k, v, n} ->
case do_delete(n, key, key_shift(hash)) do
{@node_template, value} ->
{set_elem(node, index, [k|v]), value}
{n, value} ->
{set_elem(node, index, {k, v, n}), value}
:error ->
:error
end
end
end
defp bucket_get([], _key) do
:error
Enum.each 0..(@node_size - 1), fn index ->
defp do_compact_node(node) when elem(node, unquote(index)) != [] do
case elem(node, unquote(index)) do
[k|v] ->
case set_elem(node, unquote(index), []) do
@node_template -> [k|v]
n -> {k, v, n}
end
{k, v, n} ->
{k, v, set_elem(node, unquote(index), do_compact_node(n))}
end
end
end
# Puts a value in the bucket
defp bucket_put([[k|_]|_]=bucket, key, { :put, value }) when k > key do
{ [[key|value]|bucket], 1 }
## Dict reduce
defp do_reduce_each(_node, {:halt, acc}, _fun, _next) do
{:halted, acc}
end
defp bucket_put([[k|_]|_]=bucket, key, { :update, initial, _fun }) when k > key do
{ [[key|initial]|bucket], 1 }
defp do_reduce_each(node, {:suspend, acc}, fun, next) do
{:suspended, acc, &do_reduce_each(node, &1, fun, next)}
end
defp bucket_put([[key|_]|bucket], key, { :put, value }) do
{ [[key|value]|bucket], 0 }
defp do_reduce_each([], acc, _fun, next) do
next.(acc)
end
defp bucket_put([[key|value]|bucket], key, { :update, _initial, fun }) do
{ [[key|fun.(value)]|bucket], 0 }
defp do_reduce_each([k|v], {:cont, acc}, fun, next) do
next.(fun.({k,v}, acc))
end
defp bucket_put([e|bucket], key, value) do
{ rest, count } = bucket_put(bucket, key, value)
{ [e|rest], count }
defp do_reduce_each({k, v, n}, {:cont, acc}, fun, next) do
do_reduce(n, fun.({k, v}, acc), fun, @node_size, next)
end
defp bucket_put([], key, { :put, value }) do
{ [[key|value]], 1 }
defp do_reduce(node, acc, fun, count, next) when count > 0 do
do_reduce_each(:erlang.element(count, node), acc, fun, &do_reduce(node, &1, fun, count - 1, next))
end
defp bucket_put([], key, { :update, initial, _fun }) do
{ [[key|initial]], 1 }
defp do_reduce(_node, acc, _fun, 0, next) do
next.(acc)
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]]
## Key operations
# Deletes a key from the bucket
defp bucket_delete([[k|_]|_]=bucket, key) when k > key do
{ bucket, nil, 0 }
end
import Bitwise
defp bucket_delete([[key|value]|bucket], key) do
{ bucket, value, -1 }
end
defp bucket_delete([e|bucket], key) do
{ rest, value, count } = bucket_delete(bucket, key)
{ [e|rest], value, count }
end
defp bucket_delete([], _key) do
{ [], nil, 0 }
end
# Reduces the bucket
defp bucket_reduce(_, { :halt, acc }, _fun), do: { :halted, acc }
defp bucket_reduce(list, { :suspend, acc }, fun), do: { :suspended, acc, &bucket_reduce(list, &1, fun) }
defp bucket_reduce([[k|v]|t], { :cont, acc }, fun), do: bucket_reduce(t, fun.({ k, v }, acc), fun)
defp bucket_reduce([], { :cont, acc }, _fun), do: { :done, acc }
defp bucket_fold(bucket, acc, fun) do
:lists.foldl(fun, acc, bucket)
end
defp bucket_hash(key) do
defp key_hash(key) do
:erlang.phash2(key)
end
defp bucket_index(hash) do
defp key_mask(hash) do
hash &&& @node_bitmap
end
defp bucket_nth_index(hash, n) do
(hash >>> (@node_shift * n)) &&& @node_bitmap
end
defp bucket_next(hash) do
defp key_shift(hash) do
hash >>> @node_shift
end
## Node helpers
# Gets a bucket from the node
defp node_bucket(node, 0, hash) do
elem(node, bucket_index(hash))
end
defp node_bucket(node, depth, hash) do
child = elem(node, bucket_index(hash))
node_bucket(child, depth - 1, bucket_next(hash))
end
# Puts a key-value into a node
defp node_put(node, 0, hash, key, value) do
pos = bucket_index(hash)
{ new, count } = bucket_put(elem(node, pos), key, value)
{ set_elem(node, pos, new), count }
end
defp node_put(node, depth, hash, key, value) do
pos = bucket_index(hash)
{ new, count } = node_put(elem(node, pos), depth - 1, bucket_next(hash), key, value)
{ set_elem(node, pos, new), count }
end
# Deletes a key from the bucket
defp node_delete(node, 0, hash, key) do
pos = bucket_index(hash)
case bucket_delete(elem(node, pos), key) do
{ _, value, 0 } -> { node, value, 0 }
{ new, value, -1 } -> { set_elem(node, pos, new), value, -1 }
end
end
defp node_delete(node, depth, hash, key) do
pos = bucket_index(hash)
case node_delete(elem(node, pos), depth - 1, bucket_next(hash), key) do
{ _, value, 0 } -> { node, value, 0 }
{ new, value, -1 } -> { set_elem(node, pos, new), value, -1 }
end
end
# Reduces a node recursively
defp node_reduce(_, -1, { :halt, acc }, _fun, _count, _next) do
{ :halted, acc }
end
defp node_reduce(list, -1, { :suspend, acc }, fun, count, next) do
{ :suspended, acc, &node_reduce(list, -1, &1, fun, count, next) }
end
defp node_reduce([[k|v]|t], -1, { :cont, acc }, fun, _count, next) do
node_reduce(t, -1, fun.({ k, v }, acc), fun, _count, next)
end
defp node_reduce([], -1, { :cont, _ } = acc, fun, _count, next) do
next.(acc, fun)
end
defp node_reduce(node, depth, acc, fun, count, next) when count >= 1 do
node_reduce(:erlang.element(count, node), depth - 1, acc, fun, @node_size,
&node_reduce(node, depth, &1, &2, count - 1, next))
end
defp node_reduce(_node, _, acc, fun, 0, next) do
next.(acc, fun)
end
# Folds a node recursively
defp node_fold(bucket, -1, acc, fun, _) do
bucket_fold(bucket, acc, fun)
end
defp node_fold(node, depth, acc, fun, count) when count >= 1 do
acc = node_fold(:erlang.element(count, node), depth - 1, acc, fun, @node_size)
node_fold(node, depth, acc, fun, count - 1)
end
defp node_fold(_node, _, acc, _fun, 0) do
acc
end
# Node resizing
defp node_expand({ b1, b2, b3, b4, b5, b6, b7, b8 }, 0, n) do
{ node_relocate(b1, n), node_relocate(b2, n), node_relocate(b3, n),
node_relocate(b4, n), node_relocate(b5, n), node_relocate(b6, n),
node_relocate(b7, n), node_relocate(b8, n) }
end
defp node_expand({ b1, b2, b3, b4, b5, b6, b7, b8 }, depth, n) do
depth = depth - 1
{ node_expand(b1, depth, n), node_expand(b2, depth, n), node_expand(b3, depth, n),
node_expand(b4, depth, n), node_expand(b5, depth, n), node_expand(b6, depth, n),
node_expand(b7, depth, n), node_expand(b8, depth, n) }
end
defp node_contract({ b1, b2, b3, b4, b5, b6, b7, b8 }, depth) when depth > 0 do
depth = depth - 1
{ node_contract(b1, depth), node_contract(b2, depth), node_contract(b3, depth),
node_contract(b4, depth), node_contract(b5, depth), node_contract(b6, depth),
node_contract(b7, depth), node_contract(b8, depth) }
end
defp node_contract({ b1, b2, b3, b4, b5, b6, b7, b8 }, 0) do
b1 |> each_contract(b2) |> each_contract(b3) |> each_contract(b4)
|> each_contract(b5) |> each_contract(b6) |> each_contract(b7)
|> each_contract(b8)
end
defp each_contract([[k|_v]=e|acc], [[key|_value]|_]=bucket) when k < key, do: [e|each_contract(acc, bucket)]
defp each_contract(acc, [e|bucket]), do: [e|each_contract(acc, bucket)]
defp each_contract([], bucket), do: bucket
defp each_contract(acc, []), do: acc
defp node_relocate(node // @node_template, bucket, n) do
:lists.foldl fn [key|value], acc ->
pos = key |> bucket_hash() |> bucket_nth_index(n)
set_elem(acc, pos, bucket_put!(elem(acc, pos), key, value))
end, node, bucket
end
end
defimpl Enumerable, for: HashDict do
+170 -397
View File
@@ -2,49 +2,33 @@ defmodule HashSet do
@moduledoc """
A set store.
The `HashSet` is meant to work well with both small and
large sets. It is an implementation of the `Set` behaviour.
For more information about the functions and their APIs,
please consult the `Set` module.
The `HashSet` is implemented using tries, which grows in
space as the number of keys grows, working well with both
small and large set of keys. For more information about the
functions and their APIs, please consult the `Set` module.
"""
@behaviour Set
# The ordered record contains a single bucket.
@ordered_threshold 8
defrecordp :ordered, HashSet,
size: 0,
bucket: []
# The bucketed record contains a series of buckets.
@expand_load 5
@contract_load 2
@node_bitmap 0b1111
@node_shift 4
@node_size 16
@node_bitmap 0b111
@node_shift 3
@node_size 8
@node_template :erlang.make_tuple(@node_size, [])
@expand_default (@node_size * @expand_load)
@contract_default @contract_load
defrecordp :trie, HashSet,
size: 0,
depth: 0,
expand_on: @expand_default,
contract_on: @contract_default,
root: @node_template
import Bitwise
# Inline common instructions
@compile :inline_list_funcs
@compile { :inline, bucket_hash: 1, bucket_index: 1, bucket_nth_index: 2, bucket_next: 1 }
@compile { :inline, key_hash: 1, key_mask: 1, key_shift: 1 }
@doc """
Creates a new empty set.
"""
def new() do
ordered()
@spec new :: Set.t
def new do
trie()
end
@doc """
@@ -52,180 +36,64 @@ defmodule HashSet do
## Examples
iex> HashSet.new [1, 1, 2, 3, 3] |> HashSet.to_list
[1,2,3]
HashSet.new [1, 2]
#=> #HashSet<[1, 2]>
"""
def new(members) do
Enum.reduce members, ordered(), fn member, set ->
put(set, member)
@spec new(Enum.t) :: Set.t
def new(enum) do
Enum.reduce enum, trie(), fn i, set ->
put(set, i)
end
end
def union(set1, set2) when is_record(set1, HashSet) and is_record(set2, HashSet) and elem(set1, 1) <= elem(set2, 1) do
set_fold set1, set2, fn v1, acc ->
put(acc, v1)
@doc """
Creates a new set from the enumerable with the
help of the transformation function.
## Examples
HashSet.new [1, 2], &integer_to_binary/1
#=> #HashSet<[1, 2]>
"""
@spec new(Enum.t, (term -> term)) :: Set.t
def new(enum, transform) when is_function(transform) do
Enum.reduce enum, trie(), fn i, set ->
put(set, transform.(i))
end
end
def union(set1, set2) when is_record(set1, HashSet) and is_record(set2, HashSet) do
set_fold set2, set1, fn v, acc ->
put(acc, v)
def union(trie(size: size1) = set1, trie(size: size2) = set2) when size1 <= size2 do
set_fold set1, set2, fn v, acc -> put(acc, v) end
end
def union(trie() = set1, trie() = set2) do
set_fold set2, set1, fn v, acc -> put(acc, v) end
end
def intersection(trie() = set1, trie() = set2) do
set_fold set1, trie(), fn v, acc ->
if member?(set2, v), do: put(acc, v), else: acc
end
end
def intersection(set1, set2) when is_record(set1, HashSet) and is_record(set2, HashSet) and elem(set1, 1) <= elem(set2, 1) do
set_filter set1, fn e -> set_member?(set2, e) end
end
def intersection(set1, set2) when is_record(set1, HashSet) and is_record(set2, HashSet) do
set_filter set2, fn e -> set_member?(set1, e) end
end
def difference(set1, set2) when is_record(set1, HashSet) and is_record(set2, HashSet) do
set_filter set1, fn m -> not set_member?(set2, m) end
end
def member?(set, member) when is_record(set, HashSet) do
set_member?(set, member)
end
def empty(_) do
ordered()
end
def size(set) do
elem(set, 1)
end
def to_list(ordered(bucket: bucket)) do
bucket
def difference(trie() = set1, trie() = set2) do
set_fold set2, set1, fn v, acc -> delete(acc, v) end
end
def to_list(set) do
set_fold(set, [], &[&1|&2]) |> :lists.reverse
end
def put(set, member) do
{ set, _ } = set_put(set, member)
set
end
def delete(set, member) do
{ set, _ } = set_delete(set, member)
set
end
def equal?(set1, set2) do
size = elem(set1, 1)
case elem(set2, 1) do
^size ->
set_equal?(set1, set2)
_ ->
false
def equal?(trie(size: size1) = set1, trie(size: size2) = set2) do
case size1 do
^size2 -> subset?(set1, set2)
_ -> false
end
end
def subset?(set1, set2) do
set_equal?(set1, set2)
end
def disjoint?(set1, set2) do
set_disjoint?(set1, set2)
end
@doc false
def reduce(ordered(bucket: bucket), acc, fun) do
bucket_reduce(bucket, acc, fun)
end
def reduce(trie(root: root, depth: depth), acc, fun) do
node_reduce(root, depth, acc, fun, @node_size,
fn { :cont, acc }, _f -> { :done, acc } end)
end
## HashSet-wide functions
defp set_filter(ordered(bucket: bucket, size: size), fun) do
{ new, removed_count } = bucket_filter(bucket, fun, [], 0)
ordered(bucket: new, size: size - removed_count)
end
defp set_filter(trie(root: root, depth: depth, size: size) = set, fun) do
{ new, removed_count } = node_filter(root, depth, fun, @node_size)
if depth > 0 and trie(set, :contract_on) >= (size - removed_count) do
contract_trie(trie(root: new,
size: size - removed_count,
depth: depth,
contract_on: trie(set, :contract_on),
expand_on: trie(set, :expand_on)))
else
trie(size: size - removed_count, root: new, depth: depth)
end
end
defp set_put(ordered(size: @ordered_threshold, bucket: bucket), member) do
root = node_relocate(bucket, 0)
set_put(trie(size: @ordered_threshold, root: root), member)
end
defp set_put(ordered(size: size, bucket: bucket) = set, member) do
{ new, count } = bucket_put(bucket, member)
{ ordered(set, size: size + count, bucket: new), count }
end
defp set_put(trie(root: root, depth: depth, size: size, expand_on: size, contract_on: contract_on) = set, member) do
root = node_expand(root, depth, depth + 1)
set = trie(set, root: root, depth: depth + 1,
expand_on: size * @node_size, contract_on: contract_on * @node_size)
set_put(set, member)
end
defp set_put(trie(root: root, size: size, depth: depth) = set, member) do
pos = bucket_hash(member)
{ root, count } = node_put(root, depth, pos, member)
{ trie(set, size: size + count, root: root), count }
end
defp set_member?(ordered(bucket: bucket), member) do
:lists.member(member, bucket)
end
defp set_member?(trie(root: root, depth: depth), member) do
:lists.member(member, node_bucket(root, depth, bucket_hash(member)))
end
defp set_delete(ordered(bucket: bucket, size: size) = set, member) do
case bucket_delete(bucket, member) do
{ _, 0 } ->
{ set, 0 }
{ new_bucket, -1 } ->
{ ordered(set, size: size - 1, bucket: new_bucket), -1 }
end
end
defp set_delete(trie(root: root, size: size, depth: depth) = set, member) do
pos = bucket_hash(member)
case node_delete(root, depth, pos, member) do
{ _, 0 } ->
{ set, 0 }
{ root, -1 } ->
{ if depth > 0 and trie(set, :contract_on) == size do
root = node_contract(root, depth)
trie(set,
root: root,
size: size - 1,
depth: depth - 1,
contract_on: div(size, @node_size),
expand_on: div(trie(set, :expand_on), @node_size))
else
trie(set, size: size - 1, root: root)
end, -1 }
end
end
defp set_equal?(set1, set2) do
def subset?(trie() = set1, trie() = set2) do
reduce(set1, { :cont, true }, fn member, acc ->
case member?(set2, member) do
true -> { :cont, acc }
@@ -234,273 +102,178 @@ defmodule HashSet do
end) |> elem(1)
end
defp set_disjoint?(set1, set2) do
reduce(set1, { :cont, true }, fn member, acc ->
case member?(set2, member) do
def disjoint?(trie() = set1, trie() = set2) do
reduce(set2, { :cont, true }, fn member, acc ->
case member?(set1, member) do
false -> { :cont, acc }
_ -> { :halt, false }
end
end) |> elem(1)
end
defp set_fold(ordered(bucket: bucket), acc, fun) do
bucket_fold(bucket, acc, fun)
def empty(trie()) do
trie()
end
defp set_fold(trie(root: root, depth: depth), acc, fun) do
node_fold(root, depth, acc, fun, @node_size)
def member?(trie(root: root), term) do
do_member?(root, term, key_hash(term))
end
## Bucket helpers
def put(trie(root: root, size: size), term) do
{root, counter} = do_put(root, term, key_hash(term))
trie(root: root, size: size + counter)
end
defp bucket_filter([e|bucket], fun, acc, count) do
case fun.(e) do
true -> bucket_filter(bucket, fun, [e|acc], count)
false -> bucket_filter(bucket, fun, acc, count + 1)
def delete(trie(root: root, size: size) = set, term) do
case do_delete(root, term, key_hash(term)) do
{:ok, root} -> trie(root: root, size: size - 1)
:error -> set
end
end
defp bucket_filter([], _fun, acc, count) do
{ :lists.reverse(acc), count }
def reduce(trie(root: root), acc, fun) do
do_reduce(root, acc, fun, @node_size, fn
{:suspend, acc} -> {:suspended, acc, &{ :done, elem(&1, 1) }}
{:halt, acc} -> {:halted, acc}
{:cont, acc} -> {:done, acc}
end)
end
defp bucket_put([m|_]=bucket, member) when m > member do
{ [member|bucket], 1 }
def size(trie(size: size)) do
size
end
defp bucket_put([member|bucket], member) do
{ [member|bucket], 0 }
## Set helpers
defp set_fold(trie(root: root), acc, fun) do
do_fold(root, acc, fun, @node_size)
end
defp bucket_put([e|bucket], member) do
{ rest, count } = bucket_put(bucket, member)
{ [e|rest], count }
end
## Set manipulation
defp bucket_put([], member) do
{ [member], 1 }
end
defp bucket_put!([m|_]=bucket, member) when m > member, do: [member|bucket]
defp bucket_put!([member|bucket], member), do: [member|bucket]
defp bucket_put!([e|bucket], member), do: [e|bucket_put!(bucket, member)]
defp bucket_put!([], member), do: [member]
# Deletes a key from the bucket
defp bucket_delete([m,_|_]=bucket, member) when m > member do
{ bucket, 0 }
end
defp bucket_delete([member|bucket], member) do
{ bucket, -1 }
end
defp bucket_delete([e|bucket], member) do
{ rest, count } = bucket_delete(bucket, member)
{ [e|rest], count }
end
defp bucket_delete([], _member) do
{ [], 0 }
end
defp bucket_reduce(_, { :halt, acc }, _fun), do: { :halted, acc }
defp bucket_reduce(list, { :suspend, acc }, fun), do: { :suspended, acc, &bucket_reduce(list, &1, fun) }
defp bucket_reduce([h|t], { :cont, acc }, fun), do: bucket_reduce(t, fun.(h, acc), fun)
defp bucket_reduce([], { :cont, acc }, _fun), do: { :done, acc }
defp bucket_fold(bucket, acc, fun) do
:lists.foldl(fun, acc, bucket)
end
defp bucket_hash(key) do
:erlang.phash2(key)
end
defp bucket_nth_index(hash, n) do
(hash >>> (@node_shift * n)) &&& @node_bitmap
end
defp bucket_index(hash) do
hash &&& @node_bitmap
end
defp bucket_next(hash) do
hash >>> @node_shift
end
# Trie resizing
defp contract_trie(trie(depth: 0) = set) do
set
end
defp contract_trie(trie(root: root, depth: depth, size: size, contract_on: contract_on, expand_on: expand_on) = set) when size <= contract_on do
new_contract_on = div(contract_on, @node_size)
new_expand_on = div(expand_on, @node_size)
if new_contract_on == 0, do: new_contract_on = @contract_default
if new_expand_on == 0, do: new_expand_on = @expand_default
contract_trie(trie(set, root: node_contract(root, depth),
size: size,
depth: depth - 1,
contract_on: new_contract_on,
expand_on: new_expand_on))
end
defp contract_trie(set) do
set
end
# Node helpers
# Gets a bucket from the node
defp node_bucket(node, 0, hash) do
elem(node, bucket_index(hash))
end
defp node_bucket(node, depth, hash) do
child = elem(node, bucket_index(hash))
node_bucket(child, depth - 1, bucket_next(hash))
end
defp node_put(node, 0, hash, member) do
pos = bucket_index(hash)
{ new, count } = bucket_put(elem(node, pos), member)
{ set_elem(node, pos, new), count }
end
defp node_put(node, depth, hash, member) do
pos = bucket_index(hash)
{ new, count } = node_put(elem(node, pos), depth - 1, bucket_next(hash), member)
{ set_elem(node, pos, new), count }
end
# Deletes a key from the bucket
defp node_delete(node, 0, hash, member) do
pos = bucket_index(hash)
case bucket_delete(elem(node, pos), member) do
{ _, 0 } -> { node, 0 }
{ new, -1 } -> { set_elem(node, pos, new), -1 }
defp do_member?(node, term, hash) do
index = key_mask(hash)
case elem(node, index) do
[] -> false
[^term|_] -> true
[_] -> false
[_|n] -> do_member?(n, term, key_shift(hash))
end
end
defp node_delete(node, depth, hash, member) do
pos = bucket_index(hash)
case node_delete(elem(node, pos), depth - 1, bucket_next(hash), member) do
{ _, 0 } -> { node, 0 }
{ new, -1 } -> { set_elem(node, pos, new), -1 }
defp do_put(node, term, hash) do
index = key_mask(hash)
case elem(node, index) do
[] ->
{set_elem(node, index, [term]), 1}
[^term|_] ->
{node, 0}
[t] ->
n = set_elem(@node_template, key_mask(key_shift(hash)), [term])
{set_elem(node, index, [t|n]), 1}
[t|n] ->
{n, counter} = do_put(n, term, key_shift(hash))
{set_elem(node, index, [t|n]), counter}
end
end
# Reduces a node recursively
defp node_reduce(_, -1, { :halt, acc }, _fun, _count, _next) do
{ :halted, acc }
defp do_delete(node, term, hash) do
index = key_mask(hash)
case elem(node, index) do
[] ->
:error
[^term] ->
{:ok, set_elem(node, index, [])}
[_] ->
:error
[^term|n] ->
{:ok, set_elem(node, index, do_compact_node(n))}
[t|n] ->
case do_delete(n, term, key_shift(hash)) do
{:ok, @node_template} ->
{:ok, set_elem(node, index, [t])}
{:ok, n} ->
{:ok, set_elem(node, index, [t|n])}
:error ->
:error
end
end
end
defp node_reduce(list, -1, { :suspend, acc }, fun, count, next) do
{ :suspended, acc, &node_reduce(list, -1, &1, fun, count, next) }
Enum.each 0..(@node_size - 1), fn index ->
defp do_compact_node(node) when elem(node, unquote(index)) != [] do
case elem(node, unquote(index)) do
[t] ->
case set_elem(node, unquote(index), []) do
@node_template -> [t]
n -> [t|n]
end
[t|n] ->
[t|set_elem(node, unquote(index), do_compact_node(n))]
end
end
end
defp node_reduce([h|t], -1, { :cont, acc }, fun, _count, next) do
node_reduce(t, -1, fun.(h, acc), fun, _count, next)
## Set fold
defp do_fold_each([], acc, _fun), do: acc
defp do_fold_each([t], acc, fun), do: fun.(t, acc)
defp do_fold_each([t|n], acc, fun), do: do_fold(n, fun.(t, acc), fun, @node_size)
defp do_fold(node, acc, fun, count) when count > 0 do
acc = do_fold_each(:erlang.element(count, node), acc, fun)
do_fold(node, acc, fun, count - 1)
end
defp node_reduce([], -1, { :cont, _ } = acc, fun, _count, next) do
next.(acc, fun)
end
defp node_reduce(node, depth, acc, fun, count, next) when count >= 1 do
node_reduce(:erlang.element(count, node), depth - 1, acc, fun, @node_size,
&node_reduce(node, depth, &1, &2, count - 1, next))
end
defp node_reduce(_node, _, acc, fun, 0, next) do
next.(acc, fun)
end
# Folds a node recursively
defp node_fold(bucket, -1, acc, fun, _) do
bucket_fold(bucket, acc, fun)
end
defp node_fold(node, depth, acc, fun, count) when count >= 1 do
acc = node_fold(:erlang.element(count, node), depth - 1, acc, fun, @node_size)
node_fold(node, depth, acc, fun, count - 1)
end
defp node_fold(_node, _, acc, _fun, 0) do
defp do_fold(_node, acc, _fun, 0) do
acc
end
defp node_filter(bucket, -1, fun, _) do
bucket_filter(bucket, fun, [], 0)
## Set reduce
defp do_reduce_each(_node, {:halt, acc}, _fun, _next) do
{:halted, acc}
end
defp node_filter(node, depth, fun, count) when count >= 1 do
case node_filter(:erlang.element(count, node), depth - 1, fun, @node_size) do
{ _, 0 } ->
node_filter(node, depth, fun, count - 1)
{ new_element, count1 } ->
{ new_node, count2 } = node_filter(:erlang.setelement(count, node, new_element), depth, fun, count - 1)
{ new_node, count1 + count2 }
end
defp do_reduce_each(node, {:suspend, acc}, fun, next) do
{:suspended, acc, &do_reduce_each(node, &1, fun, next)}
end
defp node_filter(node, _, _fun, 0) do
{ node, 0 }
defp do_reduce_each([], acc, _fun, next) do
next.(acc)
end
defp node_relocate(node // @node_template, bucket, n) do
:lists.foldl fn member, acc ->
pos = member |> bucket_hash() |> bucket_nth_index(n)
set_elem(acc, pos, bucket_put!(elem(acc, pos), member))
end, node, bucket
defp do_reduce_each([t], {:cont, acc}, fun, next) do
next.(fun.(t, acc))
end
# Node resizing
defp node_expand({ b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12, b13, b14, b15, b16 }, 0, n) do
{ node_relocate(b1, n), node_relocate(b2, n), node_relocate(b3, n),
node_relocate(b4, n), node_relocate(b5, n), node_relocate(b6, n),
node_relocate(b7, n), node_relocate(b8, n), node_relocate(b9, n),
node_relocate(b10, n), node_relocate(b11, n), node_relocate(b12, n),
node_relocate(b13, n), node_relocate(b14, n), node_relocate(b15, n),
node_relocate(b16, n) }
defp do_reduce_each([t|n], {:cont, acc}, fun, next) do
do_reduce(n, fun.(t, acc), fun, @node_size, next)
end
defp node_expand({ b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12, b13, b14, b15, b16 }, depth, n) do
depth = depth - 1
{ node_expand(b1, depth, n), node_expand(b2, depth, n), node_expand(b3, depth, n),
node_expand(b4, depth, n), node_expand(b5, depth, n), node_expand(b6, depth, n),
node_expand(b7, depth, n), node_expand(b8, depth, n), node_expand(b9, depth, n),
node_expand(b10, depth, n), node_expand(b11, depth, n), node_expand(b12, depth, n),
node_expand(b13, depth, n), node_expand(b14, depth, n), node_expand(b15, depth, n),
node_expand(b16, depth, n) }
defp do_reduce(node, acc, fun, count, next) when count > 0 do
do_reduce_each(:erlang.element(count, node), acc, fun, &do_reduce(node, &1, fun, count - 1, next))
end
defp node_contract({ b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12, b13, b14, b15, b16 }, depth) when depth > 0 do
depth = depth - 1
{ node_contract(b1, depth), node_contract(b2, depth), node_contract(b3, depth),
node_contract(b4, depth), node_contract(b5, depth), node_contract(b6, depth),
node_contract(b7, depth), node_contract(b8, depth), node_contract(b9, depth),
node_contract(b10, depth), node_contract(b11, depth), node_contract(b12, depth),
node_contract(b13, depth), node_contract(b14, depth), node_contract(b15, depth),
node_contract(b16, depth) }
defp do_reduce(_node, acc, _fun, 0, next) do
next.(acc)
end
defp node_contract({ b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12, b13, b14, b15, b16 }, 0) do
b1 |> each_contract(b2) |> each_contract(b3) |> each_contract(b4)
|> each_contract(b5) |> each_contract(b6) |> each_contract(b7)
|> each_contract(b8) |> each_contract(b9) |> each_contract(b10)
|> each_contract(b11) |> each_contract(b12) |> each_contract(b13)
|> each_contract(b14) |> each_contract(b15) |> each_contract(b16)
## Key operations
import Bitwise
defp key_hash(key) do
:erlang.phash2(key)
end
defp each_contract([m1|acc], [m2|_]=bucket) when m1 < m2, do: [m1|each_contract(acc, bucket)]
defp each_contract(acc, [m|bucket]), do: [m|each_contract(acc, bucket)]
defp each_contract([], bucket), do: bucket
defp each_contract(acc, []), do: acc
defp key_mask(hash) do
hash &&& @node_bitmap
end
defp key_shift(hash) do
hash >>> @node_shift
end
end
defimpl Enumerable, for: HashSet do
+20 -15
View File
@@ -100,7 +100,7 @@ defimpl Inspect, for: Atom do
valid_ref_identifier?(binary) ->
"Elixir." <> rest = binary
rest
atom in [:{}, :<<>>] ->
atom in [:{}, :<<>>, :...] ->
":" <> binary
atom in Macro.binary_ops or atom in Macro.unary_ops ->
":" <> binary
@@ -127,16 +127,21 @@ defimpl Inspect, for: Atom do
# Detect if atom
defp valid_atom_identifier?(<<h, t :: binary>>) when h in ?a..?z or h in ?A..?Z or h == ?_ do
case valid_identifier?(t) do
<<>> -> true
<<??>> -> true
<<?!>> -> true
_ -> false
end
valid_atom_piece?(t)
end
defp valid_atom_identifier?(_), do: false
defp valid_atom_piece?(t) do
case valid_identifier?(t) do
<<>> -> true
<<??>> -> true
<<?!>> -> true
<<?@, t::binary>> -> valid_atom_piece?(t)
_ -> false
end
end
defp valid_identifier?(<<h, t :: binary>>)
when h in ?a..?z
when h in ?A..?Z
@@ -149,7 +154,7 @@ defimpl Inspect, for: Atom do
end
defimpl Inspect, for: BitString do
@doc %S"""
@doc ~S"""
Represents a string as itself escaping all necessary
characters. Binaries that contain non-printable characters
are printed using the bitstring syntax.
@@ -275,7 +280,7 @@ defimpl Inspect, for: BitString do
end
defimpl Inspect, for: List do
@doc %S"""
@doc ~S"""
Represents a list, checking if it can be printed or not.
If so, a single-quoted representation is returned,
otherwise the brackets syntax is used. Keywords are
@@ -436,17 +441,17 @@ defimpl Inspect, for: Float do
end
defimpl Inspect, for: Regex do
@doc %S"""
Represents the Regex using the `%r""` syntax.
@doc ~S"""
Represents the Regex using the `~r""` syntax.
## Examples
iex> inspect(%r/foo/m)
"%r\"foo\"m"
iex> inspect(~r/foo/m)
"~r\"foo\"m"
"""
def inspect(regex, opts) when size(regex) == 5 do
concat ["%r", to_doc(Regex.source(regex), opts), Regex.opts(regex)]
concat ["~r", to_doc(Regex.source(regex), opts), Regex.opts(regex)]
end
def inspect(other, Inspect.Opts[] = opts) do
@@ -476,7 +481,7 @@ defimpl Inspect, for: Function do
end
defp default_inspect(mod, fun_info) do
"#Function<#{uniq(fun_info)} in #{Inspect.Atom.inspect(mod)}.#{extract_name(fun_info[:name])}>"
"#Function<#{uniq(fun_info)}/#{fun_info[:arity]} in #{Inspect.Atom.inspect(mod)}.#{extract_name(fun_info[:name])}>"
end
defp extract_name(name) do
+22 -14
View File
@@ -1,5 +1,5 @@
defmodule Inspect.Algebra do
@moduledoc %S"""
@moduledoc ~S"""
A set of functions for creating and manipulating algebra
documents, as described in ["Strictly Pretty" (2000) by Christian Lindig][0].
@@ -73,7 +73,7 @@ defmodule Inspect.Algebra do
# Functional interface to `doc` records
@type t :: :doc_nil | doc_cons_t | doc_nest_t | doc_break_t | doc_group_t | binary
@type t :: :doc_nil | :doc_line | doc_cons_t | doc_nest_t | doc_break_t | doc_group_t | binary
defrecordp :doc_cons, left: :doc_nil :: t, right: :doc_nil :: t
defrecordp :doc_nest, indent: 1 :: non_neg_integer, doc: :doc_nil :: t
defrecordp :doc_break, str: " " :: binary
@@ -94,7 +94,7 @@ defmodule Inspect.Algebra do
defp do_is_doc(doc) do
quote do
is_binary(unquote(doc)) or
unquote(doc) == :doc_nil or
unquote(doc) in [:doc_nil, :doc_line] or
(is_tuple(unquote(doc)) and
elem(unquote(doc), 0) in [:doc_cons, :doc_nest, :doc_break, :doc_group])
end
@@ -110,8 +110,12 @@ defmodule Inspect.Algebra do
if elem(opts, Inspect.Opts.__record__(:index, :records)) do
try do
Inspect.inspect(arg, opts)
catch
_, _ -> Inspect.Tuple.inspect(arg, opts)
rescue
e ->
res = Inspect.Tuple.inspect(arg, opts)
IO.puts :stderr, "** (Inspect.Error) Got #{inspect e.__record__(:name)} with message " <>
"#{e.message} while inspecting #{pretty(res, opts.width)}"
res
end
else
Inspect.Tuple.inspect(arg, opts)
@@ -178,7 +182,7 @@ defmodule Inspect.Algebra do
doc_nest(indent: i, doc: x)
end
@doc %S"""
@doc ~S"""
Document entity representing a break. This break can
be rendered as a linebreak or as spaces, depending on the
`mode` of the chosen layout or the provided separator.
@@ -218,7 +222,7 @@ defmodule Inspect.Algebra do
@spec glue(t, binary, t) :: doc_cons_t
def glue(x, g, y) when is_binary(g), do: concat(x, concat(break(g), y))
@doc %S"""
@doc ~S"""
Returns a group containing the specified document.
## Examples
@@ -263,7 +267,7 @@ defmodule Inspect.Algebra do
@spec space(t, t) :: doc_cons_t
def space(x, y), do: concat(x, concat(" ", y))
@doc %S"""
@doc ~S"""
Inserts a mandatory linebreak between two document entities.
## Examples
@@ -274,7 +278,7 @@ defmodule Inspect.Algebra do
"""
@spec line(t, t) :: doc_cons_t
def line(x, y), do: concat(x, concat(@newline, y))
def line(x, y), do: concat(x, concat(:doc_line, y))
@doc """
Folds a list of document entities into a document entity
@@ -297,7 +301,7 @@ defmodule Inspect.Algebra do
# Elixir conveniences
@doc %S"""
@doc ~S"""
Surrounds a document with characters.
Puts the document between left and right enclosing and nesting it.
@@ -316,7 +320,7 @@ defmodule Inspect.Algebra do
group concat left, concat(nest(doc, @nesting), right)
end
@doc %S"""
@doc ~S"""
Maps and glues a collection of items together using the given separator
and surrounds them. A limit can be passed which, once reached, stops
gluing and outputs "..." instead.
@@ -337,7 +341,7 @@ defmodule Inspect.Algebra do
"""
@spec surround_many(binary, [any], binary, integer | :infinity, (term -> t), binary) :: t
def surround_many(left, docs, right, limit, fun, separator // @surround_separator)
def surround_many(left, docs, right, limit, fun, separator \\ @surround_separator)
def surround_many(left, [], right, _, _fun, _) do
concat(left, right)
@@ -397,6 +401,7 @@ defmodule Inspect.Algebra do
@spec fits?(integer, [{ integer, mode, t }]) :: boolean
def fits?(w, _) when w < 0, do: false
def fits?(_, []), do: true
def fits?(_, [{_, _, :doc_line} | _]), do: true
def fits?(w, [{_, _, :doc_nil} | t]), do: fits?(w, t)
def fits?(w, [{i, m, doc_cons(left: x, right: y)} | t]), do: fits?(w, [{i, m, x} | [{i, m, y} | t]])
def fits?(w, [{i, m, doc_nest(indent: j, doc: x)} | t]), do: fits?(w, [{i + j, m, x} | t])
@@ -408,6 +413,7 @@ defmodule Inspect.Algebra do
@doc false
@spec format(integer | :infinity, integer, [{ integer, mode, t }]) :: [binary]
def format(_, _, []), do: []
def format(w, _, [{i, _, :doc_line} | t]), do: [indent(i) | format(w, i, t)]
def format(w, k, [{_, _, :doc_nil} | t]), do: format(w, k, t)
def format(w, k, [{i, m, doc_cons(left: x, right: y)} | t]), do: format(w, k, [{i, m, x} | [{i, m, y} | t]])
def format(w, k, [{i, m, doc_nest(indent: j, doc: x)} | t]), do: format(w, k, [{i + j, m, x} | t])
@@ -420,11 +426,13 @@ defmodule Inspect.Algebra do
if w == :infinity or fits?(w - k, t) do
[s | format(w, k, t)]
else
prefix = :binary.copy(" ", i)
[@newline, prefix | format(w, i, t)]
[indent(i) | format(w, i, t)]
end
end
defp indent(0), do: @newline
defp indent(i), do: @newline <> :binary.copy(" ", i)
@doc false
@spec render([binary]) :: binary
def render(sdoc) do
+8 -8
View File
@@ -49,7 +49,7 @@ defmodule IO do
NFS file system.
"""
@spec read(device, :line | non_neg_integer) :: chardata | nodata
def read(device // group_leader, chars_or_line)
def read(device \\ group_leader, chars_or_line)
def read(device, :line) do
:io.get_line(map_dev(device), '')
@@ -72,7 +72,7 @@ defmodule IO do
NFS file system.
"""
@spec binread(device, :line | non_neg_integer) :: iodata | nodata
def binread(device // group_leader, chars_or_line)
def binread(device \\ group_leader, chars_or_line)
def binread(device, :line) do
case :file.read_line(map_dev(device)) do
@@ -106,7 +106,7 @@ defmodule IO do
"""
@spec write(device, chardata) :: :ok
def write(device // group_leader(), item) do
def write(device \\ group_leader(), item) do
:io.put_chars map_dev(device), to_chardata(item)
end
@@ -117,7 +117,7 @@ defmodule IO do
Check `write/2` for more information.
"""
@spec binwrite(device, iodata) :: :ok | { :error, term }
def binwrite(device // group_leader(), item) when is_iolist(item) do
def binwrite(device \\ group_leader(), item) when is_iolist(item) do
:file.write map_dev(device), item
end
@@ -127,7 +127,7 @@ defmodule IO do
to be a chardata.
"""
@spec puts(device, chardata) :: :ok
def puts(device // group_leader(), item) do
def puts(device \\ group_leader(), item) do
erl_dev = map_dev(device)
:io.put_chars erl_dev, [to_chardata(item), ?\n]
end
@@ -146,7 +146,7 @@ defmodule IO do
"""
@spec inspect(term, Keyword.t) :: term
def inspect(item, opts // []) do
def inspect(item, opts \\ []) do
inspect group_leader(), item, opts
end
@@ -185,7 +185,7 @@ defmodule IO do
"""
@spec getn(chardata, pos_integer) :: chardata | nodata
@spec getn(device, chardata) :: chardata | nodata
def getn(prompt, count // 1)
def getn(prompt, count \\ 1)
def getn(prompt, count) when is_integer(count) do
getn(group_leader, prompt, count)
@@ -219,7 +219,7 @@ defmodule IO do
NFS file system.
"""
@spec gets(device, chardata) :: chardata | nodata
def gets(device // group_leader(), prompt) do
def gets(device \\ group_leader(), prompt) do
:io.get_line(map_dev(device), to_chardata(prompt))
end
+8 -8
View File
@@ -1,7 +1,7 @@
defmodule IO.ANSI.Sequence do
@moduledoc false
defmacro defsequence(name, code // "", terminator // "m") do
defmacro defsequence(name, code \\ "", terminator \\ "m") do
quote bind_quoted: [name: name, code: code, terminator: terminator] do
def unquote(name)() do
"\e[#{unquote(code)}#{unquote(terminator)}"
@@ -17,7 +17,7 @@ end
defmodule IO.ANSI do
@moduledoc """
Functionality to render ANSI escape sequences
(http://en.wikipedia.org/wiki/ANSI_escape_code) — characters embedded
(http:\\en.wikipedia.org/wiki/ANSI_escape_code) — characters embedded
in text used to control formatting, color, and other output options
on video text terminals.
"""
@@ -32,7 +32,7 @@ defmodule IO.ANSI do
"""
@spec terminal? :: boolean
@spec terminal?(:io.device) :: boolean
def terminal?(device // :erlang.group_leader) do
def terminal?(device \\ :erlang.group_leader) do
match?({:ok, _}, :io.columns(device))
end
@@ -134,11 +134,11 @@ defmodule IO.ANSI do
end
defp escape_sequence(other) do
[spec|_] = String.split(other, %r/(,|\})/)
[spec|_] = String.split(other, ~r/(,|\})/)
raise ArgumentError, message: "invalid ANSI sequence specification: #{spec}"
end
@doc %S"""
@doc ~S"""
Escapes a string by converting named ANSI sequences into actual ANSI codes.
The format for referring to sequences is `%{red}` and `%{red,bright}` (for
@@ -159,7 +159,7 @@ defmodule IO.ANSI do
"""
@spec escape(String.t, emit :: boolean) :: String.t
def escape(string, emit // terminal?) do
def escape(string, emit \\ terminal?) do
{rendered, emitted} = do_escape(string, false, emit, false, [])
if emitted and emit do
rendered <> reset
@@ -168,7 +168,7 @@ defmodule IO.ANSI do
end
end
@doc %S"""
@doc ~S"""
Escapes a string by converting named ANSI sequences into actual ANSI codes.
The format for referring to sequences is `%{red}` and `%{red,bright}` (for
@@ -188,7 +188,7 @@ defmodule IO.ANSI do
"""
@spec escape_fragment(String.t, emit :: boolean) :: String.t
def escape_fragment(string, emit // terminal?) do
def escape_fragment(string, emit \\ terminal?) do
{rendered, _emitted} = do_escape(string, false, emit, false, [])
rendered
end
+6 -6
View File
@@ -16,7 +16,7 @@ defmodule IO.ANSI.Docs do
See `print/3` for docs on the supported options.
"""
def print_heading(heading, colors // @default_colors) do
def print_heading(heading, colors \\ @default_colors) do
IO.puts IO.ANSI.reset
width = column_width()
padding = div(width + String.length(heading), 2)
@@ -46,7 +46,7 @@ defmodule IO.ANSI.Docs do
* Intensity: `normal bright`
* Decoration: `underline reverse`
"""
def print(doc, colors // @default_colors) do
def print(doc, colors \\ @default_colors) do
doc
|> String.split(["\r\n","\n"], trim: false)
|> Enum.map(&String.rstrip/1)
@@ -185,7 +185,7 @@ defmodule IO.ANSI.Docs do
|> Enum.join(" ")
|> handle_links
|> handle_inline(nil, [], [], colors)
|> String.split(%r{\s})
|> String.split(~r{\s})
|> write_with_wrap(column_width() - size(indent), indent, from_list)
unless from_list, do: IO.puts(IO.ANSI.reset)
@@ -276,16 +276,16 @@ defmodule IO.ANSI.Docs do
end
defp escape_underlines_in_link(text) do
case Regex.match?(%r{.*(https?\S*)}, text) do
case Regex.match?(~r{.*(https?\S*)}, text) do
true ->
Regex.replace(%r{_}, text, "\\\\_")
Regex.replace(~r{_}, text, "\\\\_")
_ ->
text
end
end
defp remove_square_brackets_in_link(text) do
Regex.replace(%r{\[(.*?)\]\((.*?)\)}, text, "\\1 (\\2)")
Regex.replace(~r{\[(.*?)\]\((.*?)\)}, text, "\\1 (\\2)")
end
# Single inline quotes.
+569 -493
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -34,7 +34,7 @@ defmodule Kernel.CLI do
This function is used by Elixir's CLI and also
by escripts generated by Elixir.
"""
def run(fun, halt // true) do
def run(fun, halt \\ true) do
try do
fun.()
if halt do
+4 -4
View File
@@ -25,10 +25,10 @@ defmodule Kernel.ErrorHandler do
{ ^ref, :ready } ->
:ok
{ ^ref, :release } ->
# On release, get rid of the compiler pid,
# no further allow elixir_ensure_compiled directives
# and revert to the original error handler.
:erlang.erase(:elixir_compiler_pid)
# On release, no further allow elixir_ensure_compiled
# directives and revert to the original error handler.
# Note we should not delete the elixir_compiler_pid though,
# as we still want to send notifications to the compiler.
:erlang.erase(:elixir_ensure_compiled)
:erlang.process_flag(:error_handler, :error_handler)
end
+4 -3
View File
@@ -24,7 +24,7 @@ defmodule Kernel.ParallelCompiler do
The compiler doesn't care about the return values of the callbacks.
Returns the modules generated by each compiled file.
"""
def files(files, callbacks // [])
def files(files, callbacks \\ [])
def files(files, callbacks) when is_list(callbacks) do
spawn_compilers(files, nil, callbacks)
@@ -34,7 +34,7 @@ defmodule Kernel.ParallelCompiler do
Compiles the given files to the given path.
Read `files/2` for more information.
"""
def files_to_path(files, path, callbacks // [])
def files_to_path(files, path, callbacks \\ [])
def files_to_path(files, path, callbacks) when is_binary(path) and is_list(callbacks) do
spawn_compilers(files, path, callbacks)
@@ -151,9 +151,10 @@ defmodule Kernel.ParallelCompiler do
# Sometimes we may have spurious entries in the waiting
# list because someone invoked try/rescue UndefinedFunctionError
new_files = List.delete(files, down_pid)
new_waiting = List.keydelete(waiting, down_pid, 0)
new_queued = List.keydelete(queued, down_pid, 0)
spawn_compilers(files, original, output, callbacks, new_waiting, new_queued, schedulers, result)
spawn_compilers(new_files, original, output, callbacks, new_waiting, new_queued, schedulers, result)
{ :DOWN, down_ref, :process, _down_pid, { :failure, kind, reason, stacktrace } } ->
handle_failure(down_ref, kind, reason, stacktrace, files, waiting, queued)
+1 -1
View File
@@ -13,7 +13,7 @@ defmodule Kernel.ParallelRequire do
Returns the modules generated by each required file.
"""
def files(files, callback // default_callback) do
def files(files, callback \\ default_callback) do
schedulers = max(:erlang.system_info(:schedulers_online), 2)
spawn_requires(files, [], callback, schedulers, [])
end
+1 -1
View File
@@ -72,7 +72,7 @@ defmodule Kernel.RecordRewriter do
case record_fields(module, record) do
{ fields, optimizable } ->
opt_call =
if :lists.member({ function, length(args) + 1 }, optimizable) do
if { function, length(args) + 1 } in optimizable do
case record_field_info(function) do
{ kind, field } ->
if index = Enum.find_index(fields, &(field == &1)) do
+7 -7
View File
@@ -331,7 +331,7 @@ defmodule Kernel.SpecialForms do
Elixir won't emit any warnings though, since the alias
was not explicitly defined.
Both warning behaviors could be changed by explicitily
Both warning behaviours could be changed by explicitily
setting the `:warn` option to true or false.
"""
defmacro alias(module, opts)
@@ -443,7 +443,7 @@ defmodule Kernel.SpecialForms do
Elixir won't emit any warnings though, since the import
was not explicitly defined.
Both warning behaviors could be changed by explicitily
Both warning behaviours could be changed by explicitily
setting the `:warn` option to true or false.
## Ambiguous function/macro names
@@ -495,8 +495,8 @@ defmodule Kernel.SpecialForms do
value, instead of rebinding. This can be done with the `^` special form:
iex> x = 1
iex> ^x = 1
iex> ^x = 2
iex> ^x = List.first([1])
iex> ^x = List.first([2])
** (MatchError) no match of right hand side value: 2
Note that `^` always refers to the value of x prior to the match. The
@@ -510,13 +510,13 @@ defmodule Kernel.SpecialForms do
"""
defmacro ^(var)
@doc %S"""
@doc ~S"""
Gets the representation of any expression.
## Examples
iex> quote do: sum(1, 2, 3)
{ :sum, [], [1, 2, 3] }
quote do: sum(1, 2, 3)
#=> { :sum, [], [1, 2, 3] }
## Explanation
+16 -25
View File
@@ -298,7 +298,7 @@ defmodule Kernel.Typespec do
if export do
Module.add_doc(module, caller.line, kind, { name, arity }, doc)
else
:elixir_errors.warn "#{caller.file}:#{caller.line}: type #{name}/#{arity} is private, " <>
:elixir_errors.warn caller.line, caller.file, "type #{name}/#{arity} is private, " <>
"@typedoc's are always discarded for private types\n"
end
end
@@ -519,21 +519,7 @@ defmodule Kernel.Typespec do
## Macro callbacks
@doc false
def deftype(kind, { :::, _, [type, definition] }, caller) do
do_deftype(kind, type, definition, caller)
end
def deftype(kind, {name, _meta, args} = type, caller)
when is_atom(name) and not is_list(args) do
do_deftype(kind, type, { :term, [line: caller.line], nil }, caller)
end
def deftype(_kind, other, caller) do
type_spec = Macro.to_string(other)
compile_error caller, "invalid type specification: #{type_spec}"
end
defp do_deftype(kind, { name, _, args }, definition, caller) do
def deftype(kind, { :::, _, [{ name, _, args }, definition] }, caller) when is_atom(name) and name != ::: do
args =
if is_atom(args) do
[]
@@ -550,8 +536,13 @@ defmodule Kernel.Typespec do
define_type(caller, kind, type)
end
def deftype(_kind, other, caller) do
type_spec = Macro.to_string(other)
compile_error caller, "invalid type specification: #{type_spec}"
end
@doc false
def defspec(type, { :::, meta, [{ name, _, args }, return_and_guard] }, caller) do
def defspec(type, { :::, meta, [{ name, _, args }, return_and_guard] }, caller) when is_atom(name) and name != ::: do
if is_atom(args), do: args = []
{ return, guard } = split_return_and_guard(return_and_guard)
@@ -622,11 +613,11 @@ defmodule Kernel.Typespec do
collect_vars(type)
end
defp collect_vars({:paren_type, _line, [type]}) do
defp collect_vars({ :paren_type, _line, [type] }) do
collect_vars(type)
end
defp collect_vars({:var, _line, var}) do
defp collect_vars({ :var, _line, var }) do
[erl_to_ex_var(var)]
end
@@ -721,16 +712,16 @@ defmodule Kernel.Typespec do
end
defp typespec_to_ast({ :typed_record_field,
{ :record_field, line, { :atom, line1, name }},
{ :record_field, line, { :atom, line1, name } },
type }) do
typespec_to_ast({ :ann_type, line, [{ :var, line1, name }, type] })
end
defp typespec_to_ast({:type, _, :any}) do
defp typespec_to_ast({ :type, _, :any }) do
quote do: ...
end
defp typespec_to_ast({:paren_type, _, [type]}) do
defp typespec_to_ast({ :paren_type, _, [type] }) do
typespec_to_ast(type)
end
@@ -779,7 +770,7 @@ defmodule Kernel.Typespec do
end
# Handle ranges
defp typespec({:"..", meta, args}, vars, caller) do
defp typespec({:.., meta, args}, vars, caller) do
typespec({:range, meta, args}, vars, caller)
end
@@ -800,7 +791,7 @@ defmodule Kernel.Typespec do
end
# Handle type operator
defp typespec({:"::", meta, [var, expr] }, vars, caller) do
defp typespec({:::, meta, [var, expr] }, vars, caller) do
left = typespec(var, [elem(var, 0)|vars], caller)
right = typespec(expr, vars, caller)
{ :ann_type, line(meta), [left, right] }
@@ -857,7 +848,7 @@ defmodule Kernel.Typespec do
# Handle local calls
defp typespec({:string, meta, arguments}, vars, caller) do
:elixir_errors.warn "warning: string() type use is discouraged. For character lists, use " <>
:elixir_errors.warn caller.line, caller.file, "string() type use is discouraged. For character lists, use " <>
"char_list() type, for strings, String.t()\n#{Exception.format_stacktrace(caller.stacktrace)}"
arguments = lc arg inlist arguments, do: typespec(arg, vars, caller)
{ :type, line(meta), :string, arguments }
+9 -9
View File
@@ -116,7 +116,7 @@ defmodule Keyword do
"""
@spec get(t, key) :: value
@spec get(t, key, value) :: value
def get(keywords, key, default // nil) when is_atom(key) do
def get(keywords, key, default \\ nil) when is_atom(key) do
case :lists.keyfind(key, 1, keywords) do
{ ^key, value } -> value
false -> default
@@ -324,7 +324,7 @@ defmodule Keyword do
"""
@spec equal?(t, t) :: boolean
def equal?(left, right) do
def equal?(left, right) when is_list(left) and is_list(right) do
:lists.sort(left) == :lists.sort(right)
end
@@ -339,7 +339,7 @@ defmodule Keyword do
"""
@spec merge(t, t) :: t
def merge(d1, d2) do
def merge(d1, d2) when is_list(d1) and is_list(d2) do
d2 ++ lc({ k, _ } = tuple inlist d1, not has_key?(d2, k), do: tuple)
end
@@ -356,7 +356,7 @@ defmodule Keyword do
"""
@spec merge(t, t, (key, value, value -> value)) :: t
def merge(d1, d2, fun) do
def merge(d1, d2, fun) when is_list(d1) and is_list(d2) do
do_merge(d2, d1, fun)
end
@@ -456,7 +456,7 @@ defmodule Keyword do
acc = { [], [] }
{ take, drop } = Enum.reduce dict, acc, fn({ k, v }, { take, drop }) ->
case :lists.member(k, keys) do
case k in keys do
true -> { [{k, v}|take], drop }
false -> { take, [{k, v}|drop] }
end
@@ -481,7 +481,7 @@ defmodule Keyword do
"""
def take(dict, keys) do
lc { k, _ } = tuple inlist dict, :lists.member(k, keys), do: tuple
lc { k, _ } = tuple inlist dict, k in keys, do: tuple
end
@doc """
@@ -500,7 +500,7 @@ defmodule Keyword do
"""
def drop(dict, keys) do
lc { k, _ } = tuple inlist dict, not :lists.member(k, keys), do: tuple
lc { k, _ } = tuple inlist dict, not k in keys, do: tuple
end
@doc """
@@ -528,7 +528,7 @@ defmodule Keyword do
{1,[]}
"""
def pop(dict, key, default // nil) do
def pop(dict, key, default \\ nil) do
{ get(dict, key, default), delete(dict, key) }
end
@@ -557,7 +557,7 @@ defmodule Keyword do
{1,[a: 2]}
"""
def pop_first(dict, key, default // nil) do
def pop_first(dict, key, default \\ nil) do
{ get(dict, key, default), delete_first(dict, key) }
end
end
+1 -1
View File
@@ -169,7 +169,7 @@ defmodule List do
"""
@spec keyfind([tuple], any, non_neg_integer, any) :: any
def keyfind(list, key, position, default // nil) do
def keyfind(list, key, position, default \\ nil) do
:lists.keyfind(key, position + 1, list) || default
end
+1 -1
View File
@@ -1,5 +1,5 @@
defprotocol List.Chars do
@moduledoc %S"""
@moduledoc ~S"""
The List.Chars protocol is responsible for
converting a structure to a list (only if applicable).
The only function required to be implemented is
+11 -6
View File
@@ -47,7 +47,7 @@ defmodule ListDict do
def has_key?([{ _, _ }|t], key), do: has_key?(t, key)
def has_key?([], _key), do: false
def get(dict, key, default // nil)
def get(dict, key, default \\ nil)
def get([{ key, value }|_], key, _default), do: value
def get([{ _, _ }|t], key, default), do: get(t, key, default)
def get([], _key, default), do: default
@@ -64,7 +64,7 @@ defmodule ListDict do
end
end
def pop(dict, key, default // nil) do
def pop(dict, key, default \\ nil) do
{ get(dict, key, default), delete(dict, key) }
end
@@ -84,7 +84,7 @@ defmodule ListDict do
def delete([{ _, _ } = h|t], key), do: [h|delete(t, key)]
def delete([], _key), do: []
def merge(dict, enum, callback // fn(_k, _v1, v2) -> v2 end) do
def merge(dict, enum, callback \\ fn(_k, _v1, v2) -> v2 end) do
Enum.reduce enum, dict, fn { k, v2 }, acc ->
update(acc, k, v2, fn(v1) -> callback.(k, v1, v2) end)
end
@@ -94,7 +94,7 @@ defmodule ListDict do
acc = { [], [] }
{take, drop} = Enum.reduce dict, acc, fn({ k, v }, { take, drop }) ->
if :lists.member(k, keys) do
if k in keys do
{ [{k, v}|take], drop }
else
{ take, [{k, v}|drop] }
@@ -105,11 +105,11 @@ defmodule ListDict do
end
def take(dict, keys) do
lc { k, _ } = tuple inlist dict, :lists.member(k, keys), do: tuple
lc { k, _ } = tuple inlist dict, k in keys, do: tuple
end
def drop(dict, keys) do
lc { k, _ } = tuple inlist dict, not :lists.member(k, keys), do: tuple
lc { k, _ } = tuple inlist dict, not k in keys, do: tuple
end
def update!([{key, value}|dict], key, fun) do
@@ -142,5 +142,10 @@ defmodule ListDict do
:lists.keysort(1, dict) === :lists.keysort(1, other)
end
def reduce(_, { :halt, acc }, _fun), do: { :halted, acc }
def reduce(list, { :suspend, acc }, fun), do: { :suspended, acc, &reduce(list, &1, fun) }
def reduce([], { :cont, acc }, _fun), do: { :done, acc }
def reduce([{_,_}=h|t], { :cont, acc }, fun), do: reduce(t, fun.(h, acc), fun)
def to_list(dict), do: dict
end
+30 -34
View File
@@ -13,7 +13,7 @@ defmodule Macro do
@binary_ops [:===, :!==,
:==, :!=, :<=, :>=,
:&&, :||, :<>, :++, :--, :**, ://, :::, :<-, :.., :|>, :=~,
:&&, :||, :<>, :++, :--, ://, :\\, :::, :<-, :.., :|>, :=~,
:<, :>, :->,
:+, :-, :*, :/, :=, :|, :.,
:and, :or, :xor, :when, :in, :inlist, :inbits,
@@ -30,16 +30,16 @@ defmodule Macro do
@spec binary_op_props(atom) :: { :left | :right, precedence :: integer }
defp binary_op_props(o) do
case o do
::: -> {:right, 40}
o when o in [:inlist, :inbits, ://, :|] -> {:right, 50}
o when o in [:<-, :inlist, :inbits, ://, :\\, :::] -> {:left, 40}
:| -> {:right, 50}
:when -> {:right, 70}
:= -> {:right, 80}
o when o in [:||, :|||, :or, :xor] -> {:left, 130}
o when o in [:&&, :&&&, :and] -> {:left, 140}
o when o in [:==, :!=, :<, :<=, :>=, :>, :=~, :===, :!==] -> {:left, 150}
o when o in [:<-, :|>, :<<<, :>>>] -> {:right, 160}
o when o in [:|>, :<<<, :>>>] -> {:right, 160}
:in -> {:left, 170}
o when o in [:++, :--, :**, :.., :<>] -> {:right, 200}
o when o in [:++, :--, :.., :<>] -> {:right, 200}
o when o in [:+, :-] -> {:left, 210}
o when o in [:*, :/] -> {:left, 220}
:^^^ -> {:left, 250}
@@ -66,7 +66,11 @@ defmodule Macro do
argument in the given `position`.
"""
@spec pipe(Macro.t, Macro.t, integer) :: Macro.t | no_return
def pipe(expr, call_args, integer // 0)
def pipe(expr, call_args, integer \\ 0)
def pipe(expr, { :&, _, _ } = call_args, _integer) do
raise ArgumentError, message: "cannot pipe #{to_string expr} into #{to_string call_args}"
end
def pipe(expr, { call, line, atom }, integer) when is_atom(atom) do
{ call, line, List.insert_at([], integer, expr) }
@@ -175,24 +179,23 @@ defmodule Macro do
"""
@spec escape(term) :: Macro.t
@spec escape(term, Keyword.t) :: Macro.t
def escape(expr, opts // []) do
def escape(expr, opts \\ []) do
elem(:elixir_quote.escape(expr, Keyword.get(opts, :unquote, false)), 0)
end
@doc %S"""
@doc ~S"""
Unescape the given chars.
This is the unescaping behavior
used by default in Elixir single- and double-quoted strings.
Check `unescape_string/2` for information on how to customize
the escaping map.
This is the unescaping behaviour used by default in Elixir
single- and double-quoted strings. Check `unescape_string/2`
for information on how to customize the escaping map.
In this setup, Elixir will escape the following: `\a`, `\b`,
`\d`, `\e`, `\f`, `\n`, `\r`, `\s`, `\t` and `\v`. Octals are
also escaped according to the latin1 set they represent.
This function is commonly used on sigil implementations
(like `%r`, `%s` and others) which receive a raw, unescaped
(like `~r`, `~s` and others) which receive a raw, unescaped
string.
## Examples
@@ -208,7 +211,7 @@ defmodule Macro do
:elixir_interpolation.unescape_chars(chars)
end
@doc %S"""
@doc ~S"""
Unescape the given chars according to the map given.
Check `unescape_string/1` if you want to use the same map
@@ -294,7 +297,7 @@ defmodule Macro do
"""
@spec to_string(Macro.t) :: String.t
@spec to_string(Macro.t, (Macro.t, String.t -> String.t)) :: String.t
def to_string(tree, fun // fn(_ast, string) -> string end)
def to_string(tree, fun \\ fn(_ast, string) -> string end)
# Variables
def to_string({ var, _, atom } = ast, fun) when is_atom(atom) do
@@ -362,7 +365,7 @@ defmodule Macro do
end
# Binary ops
def to_string({ op, _, [left, right] } = ast, fun) when op in @binary_ops do
def to_string({ op, _, [left, right] } = ast, fun) when op in unquote(@binary_ops) do
fun.(ast, op_to_string(left, fun, op, :left) <> " #{op} " <> op_to_string(right, fun, op, :right))
end
@@ -377,7 +380,7 @@ defmodule Macro do
fun.(ast, "not " <> to_string(arg, fun))
end
def to_string({ op, _, [arg] } = ast, fun) when op in @unary_ops do
def to_string({ op, _, [arg] } = ast, fun) when op in unquote(@unary_ops) do
fun.(ast, atom_to_binary(op) <> to_string(arg, fun))
end
@@ -421,7 +424,7 @@ defmodule Macro do
@kw_keywords [:do, :catch, :rescue, :after, :else]
defp kw_blocks?([_|_] = kw) do
Enum.all?(kw, &match?({x, _} when x in @kw_keywords, &1))
Enum.all?(kw, &match?({x, _} when x in unquote(@kw_keywords), &1))
end
defp kw_blocks?(_), do: false
@@ -488,7 +491,7 @@ defmodule Macro do
"(" <> to_string(expr, fun) <> ")"
end
defp op_to_string({ op, _, [_, _] } = expr, fun, parent_op, side) when op in @binary_ops do
defp op_to_string({ op, _, [_, _] } = expr, fun, parent_op, side) when op in unquote(@binary_ops) do
{ parent_assoc, parent_prec } = binary_op_props(parent_op)
{ _, prec } = binary_op_props(op)
cond do
@@ -506,7 +509,7 @@ defmodule Macro do
defp op_to_string(expr, fun, _, _), do: to_string(expr, fun)
defp arrow_to_string(pairs, fun, paren // false) do
defp arrow_to_string(pairs, fun, paren \\ false) do
Enum.map_join(pairs, "; ", fn({ :->, _, [left, right] }) ->
left = comma_join_or_empty_paren(left, fun, paren)
left <> "-> " <> to_string(right, fun)
@@ -641,12 +644,6 @@ defmodule Macro do
do: { env.module, true }
defp do_expand_once({ :__DIR__, _, atom }, env) when is_atom(atom),
do: { :filename.dirname(env.file), true }
defp do_expand_once({ :__FILE__, _, atom }, env) when is_atom(atom) do
IO.write "__FILE__ is deprecated, please use __DIR__ or __ENV__.file instead\n#{Exception.format_stacktrace}"
{ env.file, true }
end
defp do_expand_once({ :__ENV__, _, atom }, env) when is_atom(atom),
do: { { :{}, [], tuple_to_list(env) }, true }
defp do_expand_once({ { :., _, [{ :__ENV__, _, atom }, field] }, _, [] } = original, env) when
@@ -672,19 +669,18 @@ defmodule Macro do
defp do_expand_once({ atom, meta, args } = original, env)
when is_atom(atom) and is_list(args) and is_list(meta) do
module = env.module
extra = if function_exported?(module, :__info__, 1) do
[{ module, module.__info__(:macros) }]
else
[]
end
arity = length(args)
if :elixir_import.special_form(atom, arity) do
{ original, false }
else
module = env.module
extra = if function_exported?(module, :__info__, 1) do
[{ module, module.__info__(:macros) }]
else
[]
end
expand = :elixir_dispatch.expand_import(meta, { atom, length(args) }, args,
:elixir_env.ex_to_env(env), extra)
+7 -3
View File
@@ -81,12 +81,16 @@ defmodule Macro.Env do
def stacktrace(record) do
cond do
nil?(record.module) ->
[{ :elixir_compiler, :__FILE__, 1, location(record) }]
[{ :elixir_compiler, :__FILE__, 1, relative_location(record) }]
nil?(record.function) ->
[{ module(record), :__MODULE__, 0, location(record) }]
[{ module(record), :__MODULE__, 0, relative_location(record) }]
true ->
{ name, arity } = record.function
[{ module(record), name, arity, location(record) }]
[{ module(record), name, arity, relative_location(record) }]
end
end
defp relative_location(record) do
[file: Path.relative_to_cwd(file(record)), line: line(record)]
end
end
+33 -25
View File
@@ -5,7 +5,7 @@ defmodule Module do
end
end
@moduledoc %S'''
@moduledoc ~S'''
This module provides many functions to deal with modules during
compilation time. It allows a developer to dynamically attach
documentation, add, delete and register attributes and so forth.
@@ -257,7 +257,7 @@ defmodule Module do
* `@typep` - defines a private type to be used in `@spec`
* `@opaque` - defines an opaque type to be used in `@spec`
* `@spec` - provides a specification for a function
* `@callback` - provides a specification for the behavior callback
* `@callback` - provides a specification for the behaviour callback
In addition to the built-in attributes outlined above, custom attributes may
also be added. A custom attribute is any valid identifier prefixed with an
@@ -340,7 +340,7 @@ defmodule Module do
Foo.sum(1, 2) #=> 3
"""
def eval_quoted(module, quoted, binding // [], opts // [])
def eval_quoted(module, quoted, binding \\ [], opts \\ [])
def eval_quoted(env, quoted, binding, opts) when is_env(env) do
eval_quoted(env.module, quoted, binding, Keyword.merge(env.to_keywords, opts))
@@ -387,7 +387,7 @@ defmodule Module do
when defining the module, while `defmodule` automatically
shares the same environment.
"""
def create(module, quoted, opts // [])
def create(module, quoted, opts \\ [])
def create(module, quoted, env) when is_env(env) do
create(module, quoted, env.to_keywords)
@@ -498,7 +498,7 @@ defmodule Module do
end
"""
def add_doc(module, line, kind, tuple, signature // [], doc)
def add_doc(module, line, kind, tuple, signature \\ [], doc)
def add_doc(_module, _line, kind, _tuple, _signature, doc) when kind in [:defp, :defmacrop, :typep] do
if doc, do: { :error, :private_doc }, else: :ok
@@ -531,8 +531,8 @@ defmodule Module do
# Simplify signatures to be stored in docs
defp simplify_signature({ ://, defline, [left, right ] }, line, i) do
{ ://, defline, [simplify_signature(left, line, i), right] }
defp simplify_signature({ :\\, defline, [left, right ] }, line, i) do
{ :\\, defline, [simplify_signature(left, line, i), right] }
end
defp simplify_signature({ var, line, atom }, _, _i) when is_atom(atom) do
@@ -564,11 +564,11 @@ defmodule Module do
[]
end
defp merge_signature({ ://, line, [left, right] }, newer, i) do
{ ://, line, [merge_signature(left, newer, i), right] }
defp merge_signature({ :\\, line, [left, right] }, newer, i) do
{ :\\, line, [merge_signature(left, newer, i), right] }
end
defp merge_signature(older, { ://, _, [left, _] }, i) do
defp merge_signature(older, { :\\, _, [left, _] }, i) do
merge_signature(older, left, i)
end
@@ -631,7 +631,7 @@ defmodule Module do
defmodule Example do
def version, do: 1
Module.definitions_in __MODULE__ #=> [{:version,1}]
Module.definitions_in __MODULE__ #=> [{:version,0}]
end
"""
@@ -649,7 +649,7 @@ defmodule Module do
defmodule Example do
def version, do: 1
Module.definitions_in __MODULE__, :def #=> [{:version,1}]
Module.definitions_in __MODULE__, :def #=> [{:version,0}]
Module.definitions_in __MODULE__, :defp #=> []
end
@@ -745,12 +745,11 @@ defmodule Module do
Module.get_attribute(__MODULE__, :foo, true)
Notice the third argument is used to indicate if a warning
should be emitted when the attribute was not previously defined.
`warn` may also be a stacktrace, which will be used in the
warning. The default value for `warn` is false for direct calls
but the `@foo` macro sets it to the proper stacktrace automatically,
warning every time `@foo` is used but not set previously.
Notice the third argument may be given to indicate a stacktrace
to be emitted when the attribute was not previously defined.
The default value for `warn` is nil for direct calls but the `@foo`
macro sets it to the proper stacktrace automatically, warning
every time `@foo` is used but not set previously.
## Examples
@@ -764,8 +763,9 @@ defmodule Module do
end
"""
@spec get_attribute(module, atom, warn :: boolean | [tuple]) :: term
def get_attribute(module, key, warn // false) when is_atom(key) do
@spec get_attribute(module, atom, warn :: nil | [tuple]) :: term
def get_attribute(module, key, warn \\ nil) when
is_atom(key) and (is_list(warn) or nil?(warn)) do
assert_not_compiled!(:get_attribute, module)
table = data_table_for(module)
@@ -777,9 +777,8 @@ defmodule Module do
cond do
:lists.member(key, acc) ->
[]
warn ->
stack = is_list(warn) and warn
:elixir_errors.warn "#{Exception.format_caller(stack)} undefined module attribute @#{key}, " <>
is_list(warn) ->
:elixir_errors.warn warn_info(warn), "undefined module attribute @#{key}, " <>
"please remove access to @#{key} or explicitly set it to nil before access\n"
nil
true ->
@@ -788,6 +787,15 @@ defmodule Module do
end
end
defp warn_info([entry|_]) do
opts = elem(entry, size(entry) - 1)
Exception.format_file_line(Keyword.get(opts, :file), Keyword.get(opts, :line)) <> " "
end
defp warn_info([]) do
""
end
@doc """
Deletes all attributes that match the given key.
@@ -878,7 +886,7 @@ defmodule Module do
:ok ->
:ok
{ :error, :private_doc } ->
:elixir_errors.warn "#{env.file}:#{line} function #{name}/#{arity} is private, @doc's are always discarded for private functions\n"
:elixir_errors.warn line, env.file, "function #{name}/#{arity} is private, @doc's are always discarded for private functions\n"
end
delete_attribute(module, :doc)
@@ -906,7 +914,7 @@ defmodule Module do
{ atom, 0 }
end
defp normalize_attribute(kind, atom) when kind in [:behavior, :behaviour] and is_atom(atom) do
defp normalize_attribute(:behaviour, atom) when is_atom(atom) do
Code.ensure_compiled(atom)
atom
end
+3 -2
View File
@@ -34,8 +34,8 @@
defmodule Module.LocalsTracker do
@moduledoc false
@timeout 30_000
@behavior :gen_server
@timeout 30_000
@behaviour :gen_server
@type ref :: pid | module
@type name :: atom
@@ -145,6 +145,7 @@ defmodule Module.LocalsTracker do
d = :gen_server.call(pid, :digraph, @timeout)
lc { name, arity } inlist all_defined,
:digraph.in_neighbours(d, { :import, name, arity }) != [],
n = :digraph.out_neighbours(d, { :import, name, arity }),
n != [] do
{ n, name, arity }
+19 -2
View File
@@ -1,6 +1,11 @@
defmodule Node do
@moduledoc """
Functions related to Erlang nodes.
Functions related to VM nodes.
Some of the functions in this module are inlined by the compiler,
similar to functions in the `Kernel` module and they are explicitly
marked in their docs as "inlined by the compiler". For more information
about inlined functions, check out the `Kernel` module.
"""
@type t :: atom
@@ -97,6 +102,8 @@ defmodule Node do
Check http://www.erlang.org/doc/man/erlang.html#spawn-2 for
the list of available options.
Inlined by the compiler.
"""
@spec spawn(t, (() -> any)) :: pid
def spawn(node, fun) do
@@ -109,6 +116,8 @@ defmodule Node do
Check http://www.erlang.org/doc/man/erlang.html#spawn_opt-3 for
the list of available options.
Inlined by the compiler.
"""
@spec spawn(t, (() -> any), Process.spawn_opts) :: pid | {pid, reference}
def spawn(node, fun, opts) do
@@ -122,6 +131,8 @@ defmodule Node do
Check http://www.erlang.org/doc/man/erlang.html#spawn-4 for
the list of available options.
Inlined by the compiler.
"""
@spec spawn(t, module, atom, [any]) :: pid
def spawn(node, module, fun, args) do
@@ -135,6 +146,8 @@ defmodule Node do
Check http://www.erlang.org/doc/man/erlang.html#spawn_opt-5 for
the list of available options.
Inlined by the compiler.
"""
@spec spawn(t, module, atom, [any], Process.spawn_opts) :: pid | {pid, reference}
def spawn(node, module, fun, args, opts) do
@@ -147,6 +160,8 @@ defmodule Node do
new process, atomically. If `node` does not exist, a useless pid is returned
(and due to the link, an exit signal with exit reason `:noconnection` will be
received).
Inlined by the compiler.
"""
@spec spawn_link(t, (() -> any)) :: pid
def spawn_link(node, fun) do
@@ -159,6 +174,8 @@ defmodule Node do
process and the new process, atomically. If `node` does not exist, a useless
pid is returned (and due to the link, an exit signal with exit reason
`:noconnection` will be received).
Inlined by the compiler.
"""
@spec spawn_link(t, module, atom, [any]) :: pid
def spawn_link(node, module, fun, args) do
@@ -172,7 +189,7 @@ defmodule Node do
This function will raise `FunctionClauseError` if the given `node` is not alive.
"""
def set_cookie(node // Node.self, cookie) when is_atom(cookie) do
def set_cookie(node \\ Node.self, cookie) when is_atom(cookie) do
:erlang.set_cookie(node, cookie)
end
+2 -2
View File
@@ -79,7 +79,7 @@ defmodule OptionParser do
{ [op: false], ["path/to/file"], [] }
"""
def parse(argv, opts // []) when is_list(argv) and is_list(opts) do
def parse(argv, opts \\ []) when is_list(argv) and is_list(opts) do
parse(argv, opts, true)
end
@@ -97,7 +97,7 @@ defmodule OptionParser do
iex> OptionParser.parse_head(["--verbose", "--source", "lib", "test/enum_test.exs", "--unlock"])
{ [verbose: true, source: "lib"], ["test/enum_test.exs", "--unlock"], [] }
"""
def parse_head(argv, opts // []) when is_list(argv) and is_list(opts) do
def parse_head(argv, opts \\ []) when is_list(argv) and is_list(opts) do
parse(argv, opts, false)
end
+2 -11
View File
@@ -408,8 +408,8 @@ defmodule Path do
do_join(rest, relativename, [?/|result], os_type)
defp do_join(<<?/, rest :: binary>>, relativename, [?/|result], os_type), do:
do_join(rest, relativename, [?/|result], os_type)
defp do_join(<<>>, <<>>, result, os_type), do:
iolist_to_binary(maybe_remove_dirsep(result, os_type))
defp do_join(<<>>, <<>>, result, _os_type), do:
iolist_to_binary(:lists.reverse(result))
defp do_join(<<>>, relativename, [?:|rest], :win32), do:
do_join(relativename, <<>>, [?:|rest], :win32)
defp do_join(<<>>, relativename, [?/|result], os_type), do:
@@ -419,15 +419,6 @@ defmodule Path do
defp do_join(<<char, rest :: binary>>, relativename, result, os_type) when is_integer(char), do:
do_join(rest, relativename, [char|result], os_type)
defp maybe_remove_dirsep([?/, ?:, letter], :win32), do:
[letter, ?:, ?/]
defp maybe_remove_dirsep([?/], _), do:
[?/]
defp maybe_remove_dirsep([?/|name], _), do:
:lists.reverse(name)
defp maybe_remove_dirsep(name, _), do:
:lists.reverse(name)
@doc """
Returns a list with the path split by the path separator.
If an empty string is given, returns the root path.
+1 -1
View File
@@ -20,7 +20,7 @@ defmodule Port do
@doc """
See http://www.erlang.org/doc/man/erlang.html#port_command-2.
"""
def command(port, data, options // []) do
def command(port, data, options \\ []) do
:erlang.port_command(port, data, options)
end
+75 -16
View File
@@ -1,11 +1,11 @@
defmodule Process do
@moduledoc """
This module provides convenience functions around processes and
the process dictionary. In Erlang, most of these functions are
auto-imported, but in Elixir they are grouped in a module for
convenience. Notice that these functions, different from Erlang's,
always return nil instead of undefined. You can use their Erlang
version if you want the undefined value.
Conveniences for working with processes and the process dictionary.
Some of the functions in this module are inlined by the compiler,
similar to functions in the `Kernel` module and they are explicitly
marked in their docs as "inlined by the compiler". For more information
about inlined functions, check out the `Kernel` module.
"""
@doc """
@@ -32,7 +32,7 @@ defmodule Process do
"""
@spec get(term) :: term
@spec get(term, default :: term) :: term
def get(key, default // nil) do
def get(key, default \\ nil) do
case :erlang.get(key) do
:undefined ->
default
@@ -76,21 +76,23 @@ defmodule Process do
@doc """
Sends an exit signal with the given reason to the pid.
The following behavior applies if reason is any term except `:normal` or `:kill`:
The following behaviour applies if reason is any term except `:normal` or `:kill`:
1) If pid is not trapping exits, pid will exit with the given reason;
2) If pid is trapping exits, the exit signal is transformed into a message
{'EXIT', from, reason} and delivered to the message queue of pid;
{:EXIT, from, reason} and delivered to the message queue of pid;
3) If reason is the atom `:normal`, pid will not exit. If it is trapping exits,
the exit signal is transformed into a message {'EXIT', from, :normal} and
the exit signal is transformed into a message {:EXIT, from, :normal} and
delivered to its message queue;
4) If reason is the atom `:kill`, that is if `exit(pid, :kill)` is called, an
untrappable exit signal is sent to pid which will unconditionally exit with
exit reason `:killed`.
Inlined by the compiler.
## Examples
Process.exit(pid, :kill)
@@ -110,12 +112,37 @@ defmodule Process do
end
@doc """
Returns the pid of a new process started by the application of `fun`.
It behaves exactly the same as `Kernel.spawn/1`.
Sends a message to the given process.
It does the same as `Kernel.send/2`.
"""
@spec spawn((() -> any)) :: pid
def spawn(fun) do
:erlang.spawn(fun)
@spec send(dest, msg) :: msg when
dest: pid | port | atom | { atom, node },
msg: any
def send(dest, msg) do
:erlang.send(dest, msg)
end
@doc """
Sends `msg` to `dest` after `time` millisecons.
If `dest` is a pid, it has to be a pid of a local process, dead or alive.
If `dest` is an atom, it is supposed to be the name of a registered process
which is looked up at the time of delivery. No error is given if the name does
not refer to a process.
This function returns a timer reference, which can be read or canceled with
`:erlang.read_timer/1`, `:erlang.start_timer/3` and `:erlang.cancel_timer/1`.
Note `time` cannot be greater than `4294967295`.
Finally, the timer will be automatically canceled if the given `dest` is a pid
which is not alive or when the given pid exits. Note that timers will not be
automatically canceled when `dest` is an atom (as the atom resolution is done
on delivery).
"""
@spec send_after(pid | atom, term, non_neg_integer) :: reference
def send_after(dest, msg, time) do
:erlang.send_after(time, dest, msg)
end
@type spawn_opt :: :link | :monitor | {:priority, :low | :normal | :high} |
@@ -124,11 +151,24 @@ defmodule Process do
{:min_bin_vheap_size, non_neg_integer}
@type spawn_opts :: [spawn_opt]
@doc """
Returns the pid of a new process started by the application of `fun`.
It behaves exactly the same as `Kernel.spawn/1`.
Inlined by the compiler.
"""
@spec spawn((() -> any)) :: pid
def spawn(fun) do
:erlang.spawn(fun)
end
@doc """
Returns the pid of a new process started by the application of `fun`.
It also accepts extra options, for the list of available options
check http://www.erlang.org/doc/man/erlang.html#spawn_opt-2
Inlined by the compiler.
"""
@spec spawn((() -> any), spawn_opts) :: pid | {pid, reference}
def spawn(fun, opts) do
@@ -141,6 +181,8 @@ defmodule Process do
scheduler queue and be run some time later.
It behaves exactly the same as the `Kernel.spawn/3` function.
Inlined by the compiler.
"""
@spec spawn(module, atom, [any]) :: pid
def spawn(mod, fun, args) do
@@ -155,6 +197,7 @@ defmodule Process do
It also accepts extra options, for the list of available options
check http://www.erlang.org/doc/man/erlang.html#spawn_opt-4
Inlined by the compiler.
"""
@spec spawn(module, atom, [any], spawn_opts) :: pid | {pid, reference}
def spawn(mod, fun, args, opts) do
@@ -165,6 +208,8 @@ defmodule Process do
Returns the pid of a new process started by the application of `fun`.
A link is created between the calling process and the new
process, atomically.
Inlined by the compiler.
"""
@spec spawn_link((() -> any)) :: pid
def spawn_link(fun) do
@@ -175,6 +220,8 @@ defmodule Process do
Returns the pid of a new process started by the application of
`module.function(args)`. A link is created between the calling process
and the new process, atomically. Otherwise works like spawn/3.
Inlined by the compiler.
"""
@spec spawn_link(module, atom, [any]) :: pid
def spawn_link(mod, fun, args) do
@@ -184,6 +231,8 @@ defmodule Process do
@doc """
Returns the pid of a new process started by the application of `fun`
and reference for a monitor created to the new process.
Inlined by the compiler.
"""
@spec spawn_monitor((() -> any)) :: {pid, reference}
def spawn_monitor(fun) do
@@ -194,6 +243,8 @@ defmodule Process do
A new process is started by the application of `module.function(args)`
and the process is monitored at the same time. Returns the pid and a
reference for the monitor. Otherwise works like spawn/3.
Inlined by the compiler.
"""
@spec spawn_monitor(module, atom, [any]) :: {pid, reference}
def spawn_monitor(mod, fun, args) do
@@ -205,6 +256,8 @@ defmodule Process do
It returns the monitor reference.
See http://www.erlang.org/doc/man/erlang.html#monitor-2 for more info.
Inlined by the compiler.
"""
@spec monitor(pid | {reg_name :: atom, node :: atom} | reg_name :: atom) :: reference
def monitor(item) do
@@ -217,10 +270,12 @@ defmodule Process do
If the monitoring is already turned off, nothing happens.
See http://www.erlang.org/doc/man/erlang.html#demonitor-2 for more info.
Inlined by the compiler.
"""
@spec demonitor(reference) :: true
@spec demonitor(reference, options :: [:flush | :info]) :: boolean
def demonitor(monitor_ref, options // []) do
def demonitor(monitor_ref, options \\ []) do
:erlang.demonitor(monitor_ref, options)
end
@@ -244,6 +299,8 @@ defmodule Process do
(or port) `pid`, if there is not such a link already.
See http://www.erlang.org/doc/man/erlang.html#link-1 for more info.
Inlined by the compiler.
"""
@spec link(pid | port) :: true
def link(pid) do
@@ -256,6 +313,8 @@ defmodule Process do
fail, even if there is no link or `id` does not exist
See http://www.erlang.org/doc/man/erlang.html#unlink-1 for more info.
Inlined by the compiler.
"""
@spec unlink(pid | port) :: true
def unlink(pid) do
+42 -4
View File
@@ -1,7 +1,45 @@
defrecord Range, [:first, :last] do
defmodule Range do
@moduledoc """
Defines a Range.
"""
@type t :: { Range, any, any }
@type t(first, last) :: { Range, first, last }
@doc """
Creates a new range.
"""
def new(first, last) do
{ Range, first, last }
end
@doc """
Returns the first item of the range.
"""
def first({ Range, first, _ }) do
first
end
@doc """
Returns the last item of the range.
"""
def last({ Range, _, last }) do
last
end
@doc """
Returns true if the given argument is a range.
## Examples
iex> Range.range?(1..3)
true
iex> Range.range?(0)
false
"""
def range?({ Range, _, _ }), do: true
def range?(_), do: false
end
defprotocol Range.Iterator do
@@ -55,7 +93,7 @@ defimpl Enumerable, for: Range do
end
defimpl Range.Iterator, for: Integer do
def next(first, Range[last: last]) when is_integer(last) do
def next(first, _ .. last) when is_integer(last) do
if last >= first do
&(&1 + 1)
else
@@ -63,7 +101,7 @@ defimpl Range.Iterator, for: Integer do
end
end
def count(first, Range[last: last]) when is_integer(last) do
def count(first, _ .. last) when is_integer(last) do
if last >= first do
last - first + 1
else
@@ -75,7 +113,7 @@ end
defimpl Inspect, for: Range do
import Inspect.Algebra
def inspect(Range[first: first, last: last], opts) do
def inspect(first .. last, opts) do
concat [to_doc(first, opts), "..", to_doc(last, opts)]
end
end
+15 -127
View File
@@ -1,5 +1,5 @@
defmodule Record do
@moduledoc %S"""
@moduledoc ~S"""
Functions for working with Records.
A record is a tagged tuple which contains one or more elements
@@ -84,41 +84,9 @@ defmodule Record do
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 `records: false` to `inspect`:
inspect user(), records: false
{ 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 parentheses
are replaced by brackets. When this 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(age: 26)` and `User[age: 26]` are expanded
into a tuple at compile time.
@@ -207,36 +175,6 @@ defmodule Record do
Record.Extractor.retrieve(name, opts)
end
@doc """
Import a public record definition as a set of private macros.
Macros defined as in `Kernel.defrecordp/3`. 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
def size(file_stat(size: size)), do: size
end
"""
defmacro import(module, as: name) do
quote do
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 false
def defrecord(name, fields, opts) do
block = Keyword.get(opts, :do, nil)
@@ -394,7 +332,7 @@ defmodule Record do
end
"""
def defmacros(name, values, env, tag // nil)
def defmacros(name, values, env, tag \\ nil)
when is_atom(name) and is_list(values) and is_atom(tag) do
escaped = lc value inlist values do
@@ -409,10 +347,6 @@ defmodule Record do
Record.access(unquote(tag), unquote(escaped), [], __CALLER__)
end
defmacrop unquote(name)(record) when is_tuple(record) do
Record.to_keywords(unquote(tag), unquote(escaped), record)
end
defmacrop unquote(name)(args) do
Record.access(unquote(tag), unquote(escaped), args, __CALLER__)
end
@@ -453,14 +387,24 @@ defmodule Record do
Module.put_attribute(module, :record_optimizable, functions)
end
@doc false
# Implements the access macro used by records.
# It returns the index of the given field.
def access(atom, fields, arg, _caller) when is_atom(arg) do
if index = find_index(fields, arg, 0) do
index + 1
else
raise ArgumentError, message: "record #{inspect atom} does not have the key: #{inspect arg}"
end
end
# Implements the access macro used by records.
# It returns a quoted expression that defines
# a record or a match in case the record is
# inside a match.
@doc false
def access(atom, fields, keyword, caller) do
unless is_keyword(keyword) do
raise ArgumentError, message: "expected contents inside brackets to be a keyword list, got: #{inspect keyword}"
raise ArgumentError, message: "expected contents inside brackets to be a keyword list or an atom, got: #{inspect keyword}"
end
in_match = caller.in_match?
@@ -494,27 +438,6 @@ 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
{ 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
@@ -523,8 +446,6 @@ defmodule Record do
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
@@ -568,42 +489,8 @@ defmodule Record do
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
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(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
@@ -906,6 +793,7 @@ defmodule Record do
raise ArgumentError, message: "record field default value #{inspect atom} can only contain " <>
"functions that point to an existing &Mod.fun/arity"
end
other
end
defp check_value(atom, other) when is_reference(other) or is_pid(other) or is_port(other) do
+61 -46
View File
@@ -1,17 +1,17 @@
defmodule Regex do
@moduledoc %S"""
@moduledoc ~S"""
Regular expressions for Elixir built on top of the `re` module
in the Erlang Standard Library. More information can be found
in the [`re` documentation](http://www.erlang.org/doc/man/re.html).
Regular expressions in Elixir can be created using `Regex.compile!/2`
or using the special form with [`%r`](Kernel.html#sigil_r/2):
or using the special form with [`~r`](Kernel.html#sigil_r/2):
# A simple regular expressions that matches foo anywhere in the string
%r/foo/
~r/foo/
# A regular expression with case insensitive options
%r/foo/i
~r/foo/i
The `re` module provides several options, the ones available in Elixir, followed by
their shortcut in parenthesis, are:
@@ -37,9 +37,6 @@ defmodule Regex do
* `newline` - not available, use `(*CR)` or `(*LF)` or `(*CRLF)` or `(*ANYCRLF)`
or `(*ANY)` at the beginning of the regexp according to the re documentation
Most of the functions in this module accept either a binary or a char list
as subject. The result is based on the argument (a binary will return
a binary, a char list will return a char list).
"""
defrecordp :regex, Regex, [:re_pattern, :source, :options, :groups]
@@ -51,7 +48,7 @@ defmodule Regex do
Compiles the regular expression.
The given options can either be a binary with the characters
representing the same regex options given to the `%r` sigil,
representing the same regex options given to the `~r` sigil,
or a list of options, as expected by the [Erlang `re` docs](http://www.erlang.org/doc/man/re.html).
It returns `{ :ok, regex }` in case of success,
@@ -60,13 +57,13 @@ defmodule Regex do
## Examples
iex> Regex.compile("foo")
{:ok, %r"foo"}
{:ok, ~r"foo"}
iex> Regex.compile("*foo")
{:error, {'nothing to repeat', 0}}
"""
@spec compile(binary, binary | [term]) :: { :ok, t } | { :error, any }
def compile(source, options // "")
def compile(source, options \\ "")
def compile(source, options) when is_binary(options) do
case translate_options(options) do
@@ -100,7 +97,7 @@ defmodule Regex do
Compiles the regular expression according to the given options.
Fails with `Regex.CompileError` if the regex cannot be compiled.
"""
def compile!(source, options // "") do
def compile!(source, options \\ "") do
case compile(source, options) do
{ :ok, regex } -> regex
{ :error, { reason, at } } -> raise Regex.CompileError, message: "#{reason} at position #{at}"
@@ -112,16 +109,31 @@ defmodule Regex do
## Examples
iex> Regex.match?(%r/foo/, "foo")
iex> Regex.match?(~r/foo/, "foo")
true
iex> Regex.match?(%r/foo/, "bar")
iex> Regex.match?(~r/foo/, "bar")
false
"""
def match?(regex(re_pattern: compiled), string) do
_ = return_for(string)
:re.run(string, compiled, [{ :capture, :none }]) == :match
end
@doc """
Returns true if the given argument is a regex.
## Examples
iex> Regex.regex?(~r/foo/)
true
iex> Regex.regex?(0)
false
"""
def regex?(regex()), do: true
def regex?(_), do: false
@doc """
Runs the regular expression against the given string until the first match.
It returns a list with all captures or `nil` if no match occurred.
@@ -131,15 +143,15 @@ defmodule Regex do
## Examples
iex> Regex.run(%r/c(d)/, "abcd")
iex> Regex.run(~r/c(d)/, "abcd")
["cd", "d"]
iex> Regex.run(%r/e/, "abcd")
iex> Regex.run(~r/e/, "abcd")
nil
iex> Regex.run(%r/c(d)/, "abcd", return: :index)
iex> Regex.run(~r/c(d)/, "abcd", return: :index)
[{2,2},{3,1}]
"""
def run(regex, string, options // [])
def run(regex, string, options \\ [])
def run(regex(re_pattern: compiled, groups: groups), string, options) do
return = Keyword.get(options, :return, return_for(string))
@@ -163,15 +175,15 @@ defmodule Regex do
## Examples
iex> Regex.named_captures(%r/c(?<foo>d)/g, "abcd")
iex> Regex.named_captures(~r/c(?<foo>d)/g, "abcd")
[foo: "d"]
iex> Regex.named_captures(%r/a(?<foo>b)c(?<bar>d)/g, "abcd")
iex> Regex.named_captures(~r/a(?<foo>b)c(?<bar>d)/g, "abcd")
[foo: "b", bar: "d"]
iex> Regex.named_captures(%r/a(?<foo>b)c(?<bar>d)/g, "efgh")
iex> Regex.named_captures(~r/a(?<foo>b)c(?<bar>d)/g, "efgh")
nil
"""
def named_captures(regex(groups: groups) = regex, string, options // []) do
def named_captures(regex(groups: groups) = regex, string, options \\ []) do
options = Keyword.put_new(options, :capture, :groups)
results = run(regex, string, options)
if results, do: Enum.zip(groups, results)
@@ -189,7 +201,7 @@ defmodule Regex do
## Examples
iex> Regex.source(%r(foo))
iex> Regex.source(~r(foo))
"foo"
"""
@@ -202,7 +214,7 @@ defmodule Regex do
## Examples
iex> Regex.opts(%r(foo)m)
iex> Regex.opts(~r(foo)m)
"m"
"""
@@ -215,7 +227,7 @@ defmodule Regex do
## Examples
iex> Regex.groups(%r/(?<foo>bar)/g)
iex> Regex.groups(~r/(?<foo>bar)/g)
[:foo]
"""
@@ -237,15 +249,15 @@ defmodule Regex do
## Examples
iex> Regex.scan(%r/c(d|e)/, "abcd abce")
iex> Regex.scan(~r/c(d|e)/, "abcd abce")
[["cd", "d"], ["ce", "e"]]
iex> Regex.scan(%r/c(?:d|e)/, "abcd abce")
iex> Regex.scan(~r/c(?:d|e)/, "abcd abce")
[["cd"], ["ce"]]
iex> Regex.scan(%r/e/, "abcd")
iex> Regex.scan(~r/e/, "abcd")
[]
"""
def scan(regex, string, options // [])
def scan(regex, string, options \\ [])
def scan(regex(re_pattern: compiled, groups: groups), string, options) do
return = Keyword.get(options, :return, return_for(string))
@@ -269,19 +281,19 @@ defmodule Regex do
If no number of parts is given, it defaults to `:infinity`.
## Examples
iex> Regex.split(%r/-/, "a-b-c")
iex> Regex.split(~r/-/, "a-b-c")
["a","b","c"]
iex> Regex.split(%r/-/, "a-b-c", [parts: 2])
iex> Regex.split(~r/-/, "a-b-c", [parts: 2])
["a","b-c"]
iex> Regex.split(%r/-/, "abc")
iex> Regex.split(~r/-/, "abc")
["abc"]
iex> Regex.split(%r//, "abc")
iex> Regex.split(~r//, "abc")
["a", "b", "c", ""]
iex> Regex.split(%r//, "abc", trim: true)
iex> Regex.split(~r//, "abc", trim: true)
["a", "b", "c"]
"""
def split(regex, string, options // [])
def split(regex, string, options \\ [])
def split(regex(re_pattern: compiled), string, options) do
parts =
@@ -302,7 +314,7 @@ defmodule Regex do
end
end
@doc %S"""
@doc ~S"""
Receives a regex, a binary and a replacement, returns a new
binary where the all matches are replaced by replacement.
@@ -313,29 +325,29 @@ defmodule Regex do
## Examples
iex> Regex.replace(%r/d/, "abc", "d")
iex> Regex.replace(~r/d/, "abc", "d")
"abc"
iex> Regex.replace(%r/b/, "abc", "d")
iex> Regex.replace(~r/b/, "abc", "d")
"adc"
iex> Regex.replace(%r/b/, "abc", "[&]")
iex> Regex.replace(~r/b/, "abc", "[&]")
"a[b]c"
iex> Regex.replace(%r/b/, "abc", "[\\&]")
iex> Regex.replace(~r/b/, "abc", "[\\&]")
"a[&]c"
iex> Regex.replace(%r/(b)/, "abc", "[\\1]")
iex> Regex.replace(~r/(b)/, "abc", "[\\1]")
"a[b]c"
"""
def replace(regex(re_pattern: compiled), string, replacement, options // []) do
def replace(regex(re_pattern: compiled), string, replacement, options \\ []) do
opts = if Keyword.get(options, :global) != false, do: [:global], else: []
return = Keyword.get(options, :return, return_for(string))
opts = [{ :return, return }|opts]
:re.replace(string, compiled, replacement, opts)
end
{ :ok, pattern } = :re.compile(%S"[.^$*+?()[{\\\|\s#]", [:unicode])
{ :ok, pattern } = :re.compile(~S"[.^$*+?()[{\\\|\s#]", [:unicode])
@escape_pattern pattern
@doc %S"""
@doc ~S"""
Escapes a string to be literally matched in a regex.
## Examples
@@ -346,7 +358,7 @@ defmodule Regex do
"\\\\what\\ if"
"""
@spec escape(String.t | char_list) :: String.t | char_list
@spec escape(String.t) :: String.t
def escape(string) do
:re.replace(string, @escape_pattern, "\\\\&", [:global, { :return, return_for(string) }])
end
@@ -366,7 +378,10 @@ defmodule Regex do
# Private Helpers
defp return_for(element) when is_binary(element), do: :binary
defp return_for(element) when is_list(element), do: :list
defp return_for(element) when is_list(element) do
IO.write :stderr, "Passing char lists to Regex is deprecated, please use binaries instead\n#{Exception.format_stacktrace}"
:list
end
defp translate_options(<<?u, t :: binary>>) do
IO.write "The /u flag for regular expressions is no longer needed\n#{Exception.format_stacktrace}"
@@ -382,7 +397,7 @@ defmodule Regex do
defp translate_options(<<>>), do: []
defp translate_options(rest), do: { :error, rest }
{ :ok, pattern } = :re.compile(%S"\(\?<(?<G>[^>]*)>")
{ :ok, pattern } = :re.compile(~S"\(\?<(?<G>[^>]*)>")
@groups_pattern pattern
defp parse_groups(source) do
+112 -17
View File
@@ -1,5 +1,5 @@
defmodule Set do
@moduledoc %S"""
@moduledoc ~S"""
This module specifies the Set API expected to be
implemented by different representations.
@@ -38,6 +38,9 @@ defmodule Set do
@type values :: [ value ]
@type t :: tuple
defcallback new :: t
defcallback new(Enum.t) :: t
defcallback new(Enum.t, (any -> any)) :: t
defcallback delete(t, value) :: t
defcallback difference(t, t) :: t
defcallback disjoint?(t, t) :: boolean
@@ -46,6 +49,7 @@ defmodule Set do
defcallback intersection(t, t) :: t
defcallback member?(t, value) :: boolean
defcallback put(t, value) :: t
defcallback reduce(t, Enumerable.acc, Enumerable.reducer) :: Enumerable.result
defcallback size(t) :: non_neg_integer
defcallback subset?(t, t) :: boolean
defcallback to_list(t) :: list()
@@ -67,11 +71,11 @@ defmodule Set do
## Examples
iex> s = HashSet.new([1, 2, 3])
...> Set.delete(s, 4) |> HashSet.to_list
...> Set.delete(s, 4) |> Enum.sort
[1, 2, 3]
iex> s = HashSet.new([1, 2, 3])
...> Set.delete(s, 2) |> HashSet.to_list
...> Set.delete(s, 2) |> Enum.sort
[1, 3]
"""
@spec delete(t, value) :: t
@@ -82,20 +86,37 @@ defmodule Set do
@doc """
Returns a set that is `set1` without the members of `set2`.
Notice this function is polymorphic as it calculates the difference
for of any type. Each set implementation also provides a `difference`
function, but they can only work with sets of the same type.
## Examples
iex> Set.difference(HashSet.new([1,2]), HashSet.new([2,3,4])) |> HashSet.to_list
iex> Set.difference(HashSet.new([1,2]), HashSet.new([2,3,4])) |> Enum.sort
[1]
"""
@spec difference(t, t) :: t
def difference(set1, set2) do
target(set1).difference(set1, set2)
target1 = target(set1)
target2 = target(set2)
if target1 == target2 do
target1.difference(set1, set2)
else
target2.reduce(set2, { :cont, set1 }, fn v, acc ->
{ :cont, target1.delete(acc, v) }
end) |> elem(1)
end
end
@doc """
Checks if `set1` and `set2` have no members in common.
Notice this function is polymorphic as it checks for disjoint sets of
any type. Each set implementation also provides a `disjoint?` function,
but they can only work with sets of the same type.
## Examples
iex> Set.disjoint?(HashSet.new([1, 2]), HashSet.new([3, 4]))
@@ -106,7 +127,19 @@ defmodule Set do
"""
@spec disjoint?(t, t) :: boolean
def disjoint?(set1, set2) do
target(set1).disjoint?(set1, set2)
target1 = target(set1)
target2 = target(set2)
if target1 == target2 do
target1.disjoint?(set1, set2)
else
target2.reduce(set2, { :cont, true }, fn member, acc ->
case target1.member?(set1, member) do
false -> { :cont, acc }
_ -> { :halt, false }
end
end) |> elem(1)
end
end
@doc """
@@ -118,7 +151,11 @@ defmodule Set do
end
@doc """
Checks if `set1` and `set2` are equal.
Check if two sets are equal using `===`.
Notice this function is polymorphic as it compares sets of
any type. Each set implementation also provides an `equal?`
function, but they can only work with sets of the same type.
## Examples
@@ -131,24 +168,49 @@ defmodule Set do
"""
@spec equal?(t, t) :: boolean
def equal?(set1, set2) do
target(set1).equal?(set1, set2)
target1 = target(set1)
target2 = target(set2)
cond do
target1 == target2 ->
target1.equal?(set1, set2)
target1.size(set1) == target2.size(set2) ->
do_subset?(target1, target2, set1, set2)
true ->
false
end
end
@doc """
Returns a set containing only members in common between `set1` and `set2`.
Notice this function is polymorphic as it calculates the intersection of
any type. Each set implementation also provides a `intersection` function,
but they can only work with sets of the same type.
## Examples
iex> Set.intersection(HashSet.new([1,2]), HashSet.new([2,3,4])) |> HashSet.to_list
iex> Set.intersection(HashSet.new([1,2]), HashSet.new([2,3,4])) |> Enum.sort
[2]
iex> Set.intersection(HashSet.new([1,2]), HashSet.new([3,4])) |> HashSet.to_list
iex> Set.intersection(HashSet.new([1,2]), HashSet.new([3,4])) |> Enum.sort
[]
"""
@spec intersection(t, t) :: t
def intersection(set1, set2) do
target(set1).intersection(set1, set2)
target1 = target(set1)
target2 = target(set2)
if target1 == target2 do
target1.intersection(set1, set2)
else
target1.reduce(set1, { :cont, target1.empty(set1) }, fn v, acc ->
{ :cont, if(target2.member?(set2, v), do: target1.put(acc, v), else: acc) }
end) |> elem(1)
end
end
@doc """
@@ -173,10 +235,10 @@ defmodule Set do
## Examples
iex> Set.put(HashSet.new([1, 2, 3]), 3) |> Set.to_list
iex> Set.put(HashSet.new([1, 2, 3]), 3) |> Enum.sort
[1, 2, 3]
iex> Set.put(HashSet.new([1, 2, 3]), 4) |> Set.to_list
iex> Set.put(HashSet.new([1, 2, 3]), 4) |> Enum.sort
[1, 2, 3, 4]
"""
@@ -202,6 +264,10 @@ defmodule Set do
@doc """
Checks if `set1`'s members are all contained in `set2`.
Notice this function is polymorphic as it checks the subset for
any type. Each set implementation also provides a `subset?` function,
but they can only work with sets of the same type.
## Examples
iex> Set.subset?(HashSet.new([1, 2]), HashSet.new([1, 2, 3]))
@@ -211,7 +277,14 @@ defmodule Set do
"""
@spec subset?(t, t) :: boolean
def subset?(set1, set2) do
target(set1).subset?(set1, set2)
target1 = target(set1)
target2 = target(set2)
if target1 == target2 do
target1.subset?(set1, set2)
else
do_subset?(target1, target2, set1, set2)
end
end
@doc """
@@ -219,7 +292,7 @@ defmodule Set do
## Examples
iex> HashSet.to_list(HashSet.new([1, 2, 3]))
iex> HashSet.to_list(HashSet.new([1, 2, 3])) |> Enum.sort
[1,2,3]
"""
@@ -231,15 +304,37 @@ defmodule Set do
@doc """
Returns a set containing all members of `set1` and `set2`.
Notice this function is polymorphic as it calculates the union of
any type. Each set implementation also provides a `union` function,
but they can only work with sets of the same type.
## Examples
iex> Set.union(HashSet.new([1,2]), HashSet.new([2,3,4])) |> HashSet.to_list
iex> Set.union(HashSet.new([1,2]), HashSet.new([2,3,4])) |> Enum.sort
[1,2,3,4]
"""
@spec union(t, t) :: t
def union(set1, set2) do
target(set1).union(set1, set2)
target1 = target(set1)
target2 = target(set2)
if target1 == target2 do
target1.union(set1, set2)
else
target2.reduce(set2, { :cont, set1 }, fn v, acc ->
{ :cont, target1.put(acc, v) }
end) |> elem(1)
end
end
defp do_subset?(target1, target2, set1, set2) do
target1.reduce(set1, { :cont, true }, fn member, acc ->
case target2.member?(set2, member) do
true -> { :cont, acc }
_ -> { :halt, false }
end
end) |> elem(1)
end
defp unsupported_set(set) do
+12 -32
View File
@@ -173,25 +173,17 @@ defmodule Stream do
## Transformers
@doc """
Executes the given function when the stream is done, halted
or an error happened during streaming. Useful for resource
clean up.
Callbacks registered later will be executed earlier.
## Examples
iex> stream = Stream.after [1,2,3], fn -> Process.put(:done, true) end
iex> Enum.to_list(stream)
[1,2,3]
iex> Process.get(:done)
true
"""
@doc false
@spec unquote(:after)(Enumerable.t, (() -> term)) :: Enumerable.t
def unquote(:after)(Lazy[after: funs] = lazy, fun), do: lazy.after([fun|funs])
def unquote(:after)(enum, fun), do: Lazy[enum: enum, after: [fun]]
def unquote(:after)(Lazy[after: funs] = lazy, fun) do
IO.write :stderr, "Stream.after/1 is deprecated and will be removed/1\n#{Exception.format_stacktrace}"
lazy.after([fun|funs])
end
def unquote(:after)(enum, fun) do
IO.write :stderr, "Stream.after/1 is deprecated and will be removed/1\n#{Exception.format_stacktrace}"
Lazy[enum: enum, after: [fun]]
end
@doc """
Shortcut to `chunk(enum, n, n)`.
@@ -226,7 +218,7 @@ defmodule Stream do
"""
@spec chunk(Enumerable.t, non_neg_integer, non_neg_integer) :: Enumerable.t
@spec chunk(Enumerable.t, non_neg_integer, non_neg_integer, Enumerable.t | nil) :: Enumerable.t
def chunk(enum, n, step, pad // nil) when n > 0 and step > 0 do
def chunk(enum, n, step, pad \\ nil) when n > 0 and step > 0 do
limit = :erlang.max(n, step)
lazy enum, { [], 0 },
fn(f1) -> R.chunk(n, step, limit, f1) end,
@@ -447,18 +439,6 @@ defmodule Stream do
This is useful when a stream needs to be run, for side effects,
and there is no interest in its return result.
## Examples
Open up a file, replace all `#` by `%` and stream to another file
without loading the whole file in memory:
stream = File.stream!("code")
|> Stream.map(&String.replace(&1, "#", "%"))
|> File.stream_to!("new")
No computation will be done until we call one of the Enum functions
or `Stream.run/1`.
"""
@spec run(Enumerable.t) :: :ok
def run(stream) do
@@ -729,7 +709,7 @@ defmodule Stream do
"""
@spec uniq(Enumerable.t) :: Enumerable.t
@spec uniq(Enumerable.t, (element -> term)) :: Enumerable.t
def uniq(enum, fun // fn x -> x end) do
def uniq(enum, fun \\ fn x -> x end) do
lazy enum, [], fn f1 -> R.uniq(fun, f1) end
end
+15 -15
View File
@@ -2,7 +2,7 @@ defmodule Stream.Reducers do
# Collection of reducers shared by Enum and Stream.
@moduledoc false
defmacro chunk(n, step, limit, f // nil) do
defmacro chunk(n, step, limit, f \\ nil) do
quote do
fn entry, acc(h, { buffer, count }, t) ->
buffer = [entry|buffer]
@@ -25,7 +25,7 @@ defmodule Stream.Reducers do
end
end
defmacro chunk_by(callback, f // nil) do
defmacro chunk_by(callback, f \\ nil) do
quote do
fn
entry, acc(h, { buffer, value }, t) ->
@@ -41,7 +41,7 @@ defmodule Stream.Reducers do
end
end
defmacro drop(f // nil) do
defmacro drop(f \\ nil) do
quote do
fn
_entry, acc(h, n, t) when n > 0 ->
@@ -52,7 +52,7 @@ defmodule Stream.Reducers do
end
end
defmacro drop_while(callback, f // nil) do
defmacro drop_while(callback, f \\ nil) do
quote do
fn entry, acc(h, bool, t) = orig ->
if bool and unquote(callback).(entry) do
@@ -64,7 +64,7 @@ defmodule Stream.Reducers do
end
end
defmacro filter(callback, f // nil) do
defmacro filter(callback, f \\ nil) do
quote do
fn(entry, acc) ->
if unquote(callback).(entry) do
@@ -76,7 +76,7 @@ defmodule Stream.Reducers do
end
end
defmacro filter_map(filter, mapper, f // nil) do
defmacro filter_map(filter, mapper, f \\ nil) do
quote do
fn(entry, acc) ->
if unquote(filter).(entry) do
@@ -88,7 +88,7 @@ defmodule Stream.Reducers do
end
end
defmacro map(callback, f // nil) do
defmacro map(callback, f \\ nil) do
quote do
fn(entry, acc) ->
cont(unquote(f), unquote(callback).(entry), acc)
@@ -96,7 +96,7 @@ defmodule Stream.Reducers do
end
end
defmacro reject(callback, f // nil) do
defmacro reject(callback, f \\ nil) do
quote do
fn(entry, acc) ->
unless unquote(callback).(entry) do
@@ -108,7 +108,7 @@ defmodule Stream.Reducers do
end
end
defmacro scan_2(callback, f // nil) do
defmacro scan_2(callback, f \\ nil) do
quote do
fn
entry, acc(h, :first, t) ->
@@ -120,7 +120,7 @@ defmodule Stream.Reducers do
end
end
defmacro scan_3(callback, f // nil) do
defmacro scan_3(callback, f \\ nil) do
quote do
fn(entry, acc(h, acc, t)) ->
value = unquote(callback).(entry, acc)
@@ -129,7 +129,7 @@ defmodule Stream.Reducers do
end
end
defmacro take(f // nil) do
defmacro take(f \\ nil) do
quote do
fn(entry, acc(h, n, t) = orig) ->
if n >= 1 do
@@ -141,7 +141,7 @@ defmodule Stream.Reducers do
end
end
defmacro take_every(nth, f // nil) do
defmacro take_every(nth, f \\ nil) do
quote do
fn
entry, acc(h, n, t) when n === :first
@@ -153,7 +153,7 @@ defmodule Stream.Reducers do
end
end
defmacro take_while(callback, f // nil) do
defmacro take_while(callback, f \\ nil) do
quote do
fn(entry, acc) ->
if unquote(callback).(entry) do
@@ -165,7 +165,7 @@ defmodule Stream.Reducers do
end
end
defmacro uniq(callback, f // nil) do
defmacro uniq(callback, f \\ nil) do
quote do
fn(entry, acc(h, prev, t) = acc) ->
value = unquote(callback).(entry)
@@ -178,7 +178,7 @@ defmodule Stream.Reducers do
end
end
defmacro with_index(f // nil) do
defmacro with_index(f \\ nil) do
quote do
fn(entry, acc(h, counter, t)) ->
cont_with_acc(unquote(f), { entry, counter }, h, counter + 1, t)
+58 -41
View File
@@ -1,7 +1,7 @@
import Kernel, except: [length: 1]
defmodule String do
@moduledoc %S"""
@moduledoc ~S"""
A String in Elixir is a UTF-8 encoded binary.
## String and binary operations
@@ -168,7 +168,7 @@ defmodule String do
@spec split(t) :: [t]
defdelegate split(binary), to: String.Unicode
@doc %S"""
@doc ~S"""
Divides a string into substrings based on a pattern,
returning a list of these substrings. The pattern can
be a string, a list of strings or a regular expression.
@@ -197,16 +197,16 @@ defmodule String do
A regular expression:
iex> String.split("a,b,c", %r{,})
iex> String.split("a,b,c", ~r{,})
["a", "b", "c"]
iex> String.split("a,b,c", %r{,}, global: false)
iex> String.split("a,b,c", ~r{,}, global: false)
["a", "b,c"]
iex> String.split(" a b c ", %r{\s}, trim: true)
iex> String.split(" a b c ", ~r{\s}, trim: true)
["a", "b", "c"]
Splitting on empty patterns returns codepoints:
iex> String.split("abc", %r{})
iex> String.split("abc", ~r{})
["a", "b", "c", ""]
iex> String.split("abc", "")
["a", "b", "c", ""]
@@ -218,24 +218,24 @@ defmodule String do
"""
@spec split(t, t | [t] | Regex.t) :: [t]
@spec split(t, t | [t] | Regex.t, Keyword.t) :: [t]
def split(binary, pattern, options // [])
def split(binary, pattern, options \\ [])
def split("", _pattern, _options), do: [""]
def split(binary, "", options), do: split(binary, %r"", options)
def split(binary, pattern, options) when is_regex(pattern) do
Regex.split(pattern, binary, options)
end
def split(binary, "", options), do: split(binary, ~r"", options)
def split(binary, pattern, options) do
opts = if options[:global] != false, do: [:global], else: []
splits = :binary.split(binary, pattern, opts)
if Keyword.get(options, :trim, false) do
lc split inlist splits, split != "", do: split
if Regex.regex?(pattern) do
Regex.split(pattern, binary, options)
else
splits
opts = if options[:global] != false, do: [:global], else: []
splits = :binary.split(binary, pattern, opts)
if Keyword.get(options, :trim, false) do
lc split inlist splits, split != "", do: split
else
splits
end
end
end
@@ -333,7 +333,7 @@ defmodule String do
end
end
def rstrip(string, char) do
def rstrip(string, char) when is_integer(char) do
do_rstrip(string, "", char)
end
@@ -373,11 +373,11 @@ defmodule String do
@spec lstrip(t, char) :: t
def lstrip(<<char :: utf8, rest :: binary>>, char) do
def lstrip(<<char :: utf8, rest :: binary>>, char) when is_integer(char) do
<<lstrip(rest, char) :: binary>>
end
def lstrip(other, _char) do
def lstrip(other, char) when is_integer(char) do
other
end
@@ -413,7 +413,7 @@ defmodule String do
rstrip(lstrip(string, char), char)
end
@doc %S"""
@doc ~S"""
Returns a new string of length `len` with `subject` right justified and
padded with `padding`. If `padding` is not present, it defaults to
whitespace. When `len` is less than the length of `subject`, `subject` is
@@ -438,7 +438,7 @@ defmodule String do
do_justify(subject, len, padding, :right)
end
@doc %S"""
@doc ~S"""
Returns a new string of length `len` with `subject` left justified and padded
with `padding`. If `padding` is not present, it defaults to whitespace. When
`len` is less than the length of `subject`, `subject` is returned.
@@ -482,7 +482,7 @@ defmodule String do
end
end
@doc %S"""
@doc ~S"""
Returns a new binary based on `subject` by replacing the parts
matching `pattern` by `replacement`. By default, it replaces
all entries, except if the `global` option is set to `false`.
@@ -499,7 +499,7 @@ defmodule String do
The pattern can also be a regex. In those cases, one can give `\N`
in the `replacement` string to access a specific capture in the regex:
iex> String.replace("a,b,c", %r/,(.)/, ",\\1\\1")
iex> String.replace("a,b,c", ~r/,(.)/, ",\\1\\1")
"a,bb,cc"
Notice we had to escape the escape character `\`. By giving `&`,
@@ -519,15 +519,15 @@ defmodule String do
@spec replace(t, t, t) :: t
@spec replace(t, t, t, Keyword.t) :: t
def replace(subject, pattern, replacement, options // [])
def replace(subject, pattern, replacement, options) when is_regex(pattern) do
Regex.replace(pattern, subject, replacement, global: options[:global])
end
def replace(subject, pattern, replacement, options \\ [])
def replace(subject, pattern, replacement, options) do
opts = translate_replace_options(options)
:binary.replace(subject, pattern, replacement, opts)
if Regex.regex?(pattern) do
Regex.replace(pattern, subject, replacement, global: options[:global])
else
opts = translate_replace_options(options)
:binary.replace(subject, pattern, replacement, opts)
end
end
defp translate_replace_options(options) do
@@ -620,7 +620,7 @@ defmodule String do
@spec next_codepoint(t) :: {codepoint, t} | nil
defdelegate next_codepoint(string), to: String.Unicode
@doc %S"""
@doc ~S"""
Checks whether `str` contains only valid characters.
## Examples
@@ -651,7 +651,7 @@ defmodule String do
def valid?(<<>>), do: true
def valid?(_), do: false
@doc %S"""
@doc ~S"""
Checks whether `str` is a valid character.
All characters are codepoints, but some codepoints
@@ -913,7 +913,7 @@ defmodule String do
last = total + last
end
if first > 0 do
if first >= 0 do
do_slice(next_grapheme(string), first, last, 0, "")
end
end
@@ -974,7 +974,7 @@ defmodule String do
end
defp do_starts_with(string, prefix) when is_binary(prefix) do
match?({0, _}, :binary.match(string, prefix))
Kernel.match?({0, _}, :binary.match(string, prefix))
end
defp do_starts_with(_, _) do
@@ -1021,7 +1021,24 @@ defmodule String do
end
@doc """
Returns `true` if `string` contains match, otherwise `false`.
Check if `string` matches the given regular expression.
## Examples
iex> String.match?("foo", ~r/foo/)
true
iex> String.match?("bar", ~r/foo/)
false
"""
@spec match?(t, Regex.t) :: boolean
def match?(string, regex) do
Regex.match?(regex, string)
end
@doc """
Check if `string` contains any of the given `contents`.
`matches` can be either a single string or a list of strings.
## Examples
@@ -1036,12 +1053,12 @@ defmodule String do
"""
@spec contains?(t, t | [t]) :: boolean
def contains?(string, matches) when is_list(matches) do
Enum.any?(matches, &do_contains(string, &1))
def contains?(string, contents) when is_list(contents) do
Enum.any?(contents, &do_contains(string, &1))
end
def contains?(string, match) do
do_contains(string, match)
def contains?(string, content) do
do_contains(string, content)
end
defp do_contains(string, "") when is_binary(string) do
+1 -1
View File
@@ -1,7 +1,7 @@
import Kernel, except: [to_string: 1]
defprotocol String.Chars do
@moduledoc %S"""
@moduledoc ~S"""
The String.Chars protocol is responsible for
converting a structure to a Binary (only if applicable).
The only function required to be implemented is
+2 -2
View File
@@ -152,7 +152,7 @@ defmodule Supervisor.Behaviour do
#{@child_doc}
"""
def worker(module, args, options // []) do
def worker(module, args, options \\ []) do
child(:worker, module, args, options)
end
@@ -166,7 +166,7 @@ defmodule Supervisor.Behaviour do
#{@child_doc}
"""
def supervisor(module, args, options // []) do
def supervisor(module, args, options \\ []) do
options = Keyword.update(options, :shutdown, :infinity, fn(x) -> x end)
child(:supervisor, module, args, options)
end
+13 -18
View File
@@ -1,13 +1,4 @@
defmodule System do
defexception NoHomeError,
message: "could not find the user home, please set the HOME environment variable"
defexception NoTmpDirError,
message: "could not get a writable temporary directory, please set the TMPDIR environment variable"
defexception NoAccessCwdError,
message: "could not get a current working directory, the current location is not accessible"
@moduledoc """
The System module provides access to variables used or
maintained by the VM and to functions that interact directly
@@ -29,7 +20,7 @@ defmodule System do
# Read and strip the version from the `VERSION` file.
defmacrop get_version do
case read_stripped(:filename.join(__DIR__, "../../../VERSION")) do
"" -> raise ArgumentError, message: "could not read the version number from VERSION"
"" -> raise RuntimeError, message: "could not read the version number from VERSION"
data -> data
end
end
@@ -111,10 +102,11 @@ defmodule System do
@doc """
Current working directory, exception on error.
Returns the current working directory or raises `System.NoAccessCwdError`.
Returns the current working directory or raises `RuntimeError`.
"""
def cwd! do
cwd || raise NoAccessCwdError
cwd ||
raise RuntimeError, message: "could not get a current working directory, the current location is not accessible"
end
@doc """
@@ -133,11 +125,12 @@ defmodule System do
@doc """
User home directory, exception on error.
Same as `user_home/0` but raises `System.NoHomeError`
Same as `user_home/0` but raises `RuntimeError`
instead of returning `nil` if no user home is set.
"""
def user_home! do
user_home || raise NoHomeError
user_home ||
raise RuntimeError, message: "could not find the user home, please set the HOME environment variable"
end
defp get_unix_home do
@@ -154,7 +147,7 @@ defmodule System do
)
end
@doc %S"""
@doc ~S"""
Writable temporary directory.
Returns a writable temporary directory.
@@ -179,11 +172,13 @@ defmodule System do
@doc """
Writable temporary directory, exception on error.
Same as `tmp_dir/0` but raises `System.NoTmpDirError`
Same as `tmp_dir/0` but raises `RuntimeError`
instead of returning `nil` if no temp dir is set.
"""
def tmp_dir! do
tmp_dir || raise NoTmpDirError
tmp_dir ||
raise RuntimeError, message: "could not get a writable temporary directory, " <>
"please set the TMPDIR environment variable"
end
defp write_env_tmp_dir(env) do
@@ -376,7 +371,7 @@ defmodule System do
"""
@spec halt() :: no_return
@spec halt(non_neg_integer | binary | :abort) :: no_return
def halt(status // 0)
def halt(status \\ 0)
def halt(status) when is_integer(status) or status == :abort do
:erlang.halt(status)
+35 -15
View File
@@ -75,8 +75,14 @@ defmodule URI do
"""
def encode_query(l), do: Enum.map_join(l, "&", &pair/1)
@doc false
def decode_query(q) do
IO.write :stderr, "URI.decode_query/1 is deprecated, please use URI.decode_query/2 with an explicit argument\n#{Exception.format_stacktrace}"
decode_query(q, HashDict.new)
end
@doc """
Decodes a query string into a `HashDict`.
Decodes a query string into a dictionary.
Given a query string of the form "key1=value1&key2=value2...", produces a
`HashDict` with one entry for each key-value pair. Each key and value will be a
@@ -86,9 +92,6 @@ defmodule URI do
## Examples
iex> URI.decode_query("foo=1&bar=2") |> Dict.to_list
[{"bar", "2"}, {"foo", "1"}]
iex> hd = HashDict.new()
iex> URI.decode_query("foo=1&bar=2", hd) |> HashDict.keys
["bar", "foo"]
@@ -96,7 +99,7 @@ defmodule URI do
["2", "1"]
"""
def decode_query(q, dict // HashDict.new) when is_binary(q) do
def decode_query(q, dict) when is_binary(q) do
Enum.reduce query_decoder(q), dict, fn({ k, v }, acc) -> Dict.put(acc, k, v) end
end
@@ -134,6 +137,16 @@ defmodule URI do
{ current, next }
end
defp pair({k, v}) when is_list(k) do
IO.write :stderr, "Passing list keys to URI.encode_query/1 is deprecated\n#{Exception.format_stacktrace}"
encode(to_string(k)) <> "=" <> encode(to_string(v))
end
defp pair({k, v}) when is_list(v) do
IO.write :stderr, "Passing list values to URI.encode_query/1 is deprecated\n#{Exception.format_stacktrace}"
encode(to_string(k)) <> "=" <> encode(to_string(v))
end
defp pair({k, v}) do
encode(to_string(k)) <> "=" <> encode(to_string(v))
end
@@ -175,19 +188,26 @@ defmodule URI do
"http://elixir-lang.com"
"""
def decode(<<?%, hex1, hex2, tail :: binary >>) do
<< bsl(hex2dec(hex1), 4) + hex2dec(hex2) >> <> decode(tail)
def decode(uri) do
decode(uri, uri)
end
def decode(<<head, tail :: binary >>) do
<<check_plus(head)>> <> decode(tail)
def decode(<<?%, hex1, hex2, tail :: binary >>, uri) do
<< bsl(hex2dec(hex1, uri), 4) + hex2dec(hex2, uri) >> <> decode(tail, uri)
end
def decode(<<>>), do: <<>>
def decode(<<head, tail :: binary >>, uri) do
<<check_plus(head)>> <> decode(tail, uri)
end
defp hex2dec(n) when n in ?A..?F, do: n - ?A + 10
defp hex2dec(n) when n in ?a..?f, do: n - ?a + 10
defp hex2dec(n) when n in ?0..?9, do: n - ?0
def decode(<<>>, _uri), do: <<>>
defp hex2dec(n, _uri) when n in ?A..?F, do: n - ?A + 10
defp hex2dec(n, _uri) when n in ?a..?f, do: n - ?a + 10
defp hex2dec(n, _uri) when n in ?0..?9, do: n - ?0
defp hex2dec(_n, uri) do
raise ArgumentError, message: "malformed URI #{inspect uri}"
end
defp check_plus(?+), do: 32
defp check_plus(c), do: c
@@ -210,7 +230,7 @@ defmodule URI do
"""
def parse(s) when is_binary(s) do
# From http://tools.ietf.org/html/rfc3986#appendix-B
regex = %r/^(([^:\/?#]+):)?(\/\/([^\/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?/
regex = ~r/^(([^:\/?#]+):)?(\/\/([^\/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?/
parts = nillify(Regex.run(regex, s))
destructure [_, _, scheme, _, authority, path, _, query, _, fragment], parts
@@ -240,7 +260,7 @@ defmodule URI do
# Split an authority into its userinfo, host and port parts.
defp split_authority(s) do
s = s || ""
components = Regex.run %r/(^(.*)@)?(\[[a-zA-Z0-9:.]*\]|[^:]*)(:(\d*))?/, s
components = Regex.run ~r/(^(.*)@)?(\[[a-zA-Z0-9:.]*\]|[^:]*)(:(\d*))?/, s
destructure [_, _, userinfo, host, _, port], nillify(components)
port = if port, do: binary_to_integer(port)
+189 -215
View File
@@ -1,17 +1,12 @@
defmodule Version do
@moduledoc %S"""
@moduledoc ~S"""
Functions for parsing and matching versions against requirements.
A version is a string in a specific format or a `Version.Schema`
generated after parsing via `Version.parse/1`.
`Version` parsing and requirements follow
[SemVer 2.0 schema](http://semver.org/) and you will get
the most of Mix's version system by following it. In order
to support integration with projects that may
follow different versioning schemas, Elixir won't choke
on unknown versions, however you won't be able to use
Mix requirements with such unformatted versions.
[SemVer 2.0 schema](http://semver.org/).
## Versions
@@ -54,7 +49,6 @@ defmodule Version do
@type t :: String.t | Version.Schema.t
@type requirement :: String.t | Version.Requirement.t
@type matchable :: { major :: String.t | non_neg_integer,
minor :: non_neg_integer | nil,
patch :: non_neg_integer | nil,
@@ -62,98 +56,147 @@ defmodule Version do
import Kernel, except: [match?: 2]
defrecord Schema, major: 0, minor: 0, patch: 0, pre: nil, build: nil, source: nil
defrecord Requirement, source: nil, matchspec: nil
defrecord Schema, [:major, :minor, :patch, :pre, :build, :source]
defrecord Requirement, [:source, :matchspec]
defexception InvalidRequirement, [:message] do
def exception(opts) do
message =
if is_binary opts[:reason] do
{ first, rest } = String.next_grapheme(opts[:reason])
String.downcase(first) <> rest
else
"invalid version specification"
end
InvalidRequirement[message: message]
end
end
defexception InvalidRequirement, [:message]
defexception InvalidVersion, [:message]
@doc """
Check if the given version matches the specification.
Returns `true` if `version` satisfies `requirement`, `false` otherwise.
Raises a `Version.InvalidRequirement` exception if `requirement` is not parseable.
Raises a `Version.InvalidRequirement` exception if `requirement` is not
parseable, or `Version.InvalidVersion` if `version` is not parseable.
If given an already parsed version and requirement this function won't
raise.
## Examples
iex> Version.match?("2.0", ">1.0")
iex> Version.match?("2.0.0", ">1.0.0")
true
iex> Version.match?("2.0", "==1.0")
iex> Version.match?("2.0.0", "==1.0.0")
false
iex> Version.match?("foo", "==1.0.0")
** (Version.InvalidVersion) foo
iex> Version.match?("2.0.0", "== ==1.0.0")
** (Version.InvalidRequirement) == ==1.0.0
"""
@spec match?(t, requirement) :: boolean
def match?(version, requirement) when is_binary(requirement) do
case Version.Parser.parse_requirement(requirement) do
def match?(vsn, req) when is_binary(req) do
case parse_requirement(req) do
{ :ok, req } ->
match?(version, req)
{ :error, reason } ->
raise InvalidRequirement, reason: reason
match?(vsn, req)
:error ->
raise InvalidRequirement, message: req
end
end
def match?(version, requirement) when is_binary(version) do
match?(parse(version), requirement)
end
def match?(Schema[] = version, Requirement[matchspec: spec]) do
case :ets.test_ms(to_matchable(version), spec) do
{ :ok, result } ->
result != false
{ :error, reason } ->
raise InvalidRequirement, reason: reason
end
def match?(version, Requirement[matchspec: spec]) do
{ :ok, result } = :ets.test_ms(to_matchable(version), spec)
result != false
end
@doc """
Check if a version string is compatible with [semver](http://semver.org/).
Compares two versions. Returns `:gt` if first version is greater than
the second and `:lt` for vice versa. If the two versions are equal `:eq`
is returned
Raises a `Version.InvalidVersion` exception if `version` is not parseable.
If given an already parsed version this function won't raise.
## Examples
iex> Version.valid?("2.0")
true
iex> Version.valid?("invalid")
false
iex> Version.compare("2.0.1-alpha1", "2.0.0")
:gt
iex> Version.compare("2.0.1+build0", "2.0.1")
:eq
iex> Version.compare("invalid", "2.0.1")
** (Version.InvalidVersion) invalid
"""
@spec valid?(String.t | Schema.t) :: boolean
def valid?(string) when is_binary(string) do
Version.Parser.valid_version?(string)
@spec compare(String.t | Schema.t, String.t | Schema.t) :: :gt | :eq | :lt
def compare(vsn1, vsn2) do
do_compare(to_matchable(vsn1), to_matchable(vsn2))
end
def valid?(Version.Schema[major: nil]), do: false
def valid?(Version.Schema[]), do: true
defp do_compare({ major1, minor1, patch1, pre1 }, { major2, minor2, patch2, pre2 }) do
cond do
{ major1, minor1, patch1 } > { major2, minor2, patch2 } -> :gt
{ major1, minor1, patch1 } < { major2, minor2, patch2 } -> :lt
pre1 == [] and pre2 != [] -> :gt
pre1 != [] and pre2 == [] -> :lt
pre1 > pre2 -> :gt
pre1 < pre2 -> :lt
true -> :eq
end
end
@doc """
Parse a version string into a `Version.Schema`.
## Examples
iex> Version.parse("2.0.1-alpha1")
iex> Version.parse("2.0.1-alpha1") |> elem(1)
#Version.Schema<2.0.1-alpha1>
iex> Version.parse("2.0-alpha1")
:error
"""
@spec parse(String.t) :: Schema.t
@spec parse(String.t) :: { :ok, Schema.t } | :error
def parse(string) when is_binary(string) do
case Version.Parser.parse_version(string) do
{ :ok, matchable } -> from_matchable(matchable).source(string).build(get_build(string))
{ :error, _ } -> Version.Schema[source: string]
{ :ok, { major, minor, patch, pre } } ->
vsn = Version.Schema[major: major, minor: minor, patch: patch,
pre: pre, source: string, build: get_build(string)]
{ :ok, vsn }
:error ->
:error
end
end
@doc """
Parse a version requirement string into a `Version.Schema`.
## Examples
iex> Version.parse_requirement("== 2.0.1") |> elem(1)
#Version.Requirement<== 2.0.1>
iex> Version.parse_requirement("== == 2.0.1")
:error
"""
@spec parse_requirement(String.t) :: { :ok, Requirement.t } | :error
def parse_requirement(string) when is_binary(string) do
case Version.Parser.parse_requirement(string) do
{ :ok, spec } ->
{ :ok, Requirement[source: string, matchspec: spec] }
:error ->
:error
end
end
defp to_matchable(Schema[major: major, minor: minor, patch: patch, pre: pre]) do
{ major, minor, patch, pre }
end
defp to_matchable(string) do
case Version.Parser.parse_version(string) do
{ :ok, vsn } -> vsn
:error -> raise InvalidVersion, message: string
end
end
defp get_build(string) do
case Regex.run(%r/\+([^\s]+)$/, string) do
case Regex.run(~r/\+([^\s]+)$/, string) do
nil ->
nil
@@ -162,69 +205,6 @@ defmodule Version do
end
end
@doc """
Convert a version to a `Version.matchable`
## Examples
iex> Version.to_matchable("2.0.1-alpha.1")
{2, 0, 1, ["alpha", 1]}
"""
@spec to_matchable(String.t | Schema.t) :: Version.matchable
def to_matchable(Schema[major: nil, source: source]) do
{ source, nil, nil, [] }
end
def to_matchable(Version.Schema[major: major, minor: minor, patch: patch, pre: nil]) do
{ major, minor, patch, [] }
end
def to_matchable(Version.Schema[major: major, minor: minor, patch: patch, pre: pre]) do
{ major, minor, patch, Version.Parser.parse_pre(pre) }
end
def to_matchable(string) do
to_matchable(parse(string))
end
@doc """
Convert a matchable to a `Version.Schema`.
## Examples
iex> Version.from_matchable({2, 0, 1, ["alpha", 1]})
#Version.Schema<2.0.1-alpha.1>
"""
@spec from_matchable(Version.matchable) :: Schema.t
def from_matchable({ source, nil, nil, nil }) when is_binary(source) do
Version.Schema[source: source]
end
def from_matchable({ major, minor, patch, pre }) do
source = "#{major}"
if minor do
source = "#{source}.#{minor}"
if patch do
source = "#{source}.#{patch}"
case pre do
[] ->
pre = nil
list ->
pre = Enum.join(list, ".")
source = "#{source}-#{pre}"
end
end
end
Version.Schema[major: major, minor: minor, patch: patch, pre: pre, source: source]
end
defmodule Parser.DSL do
@moduledoc false
@@ -292,78 +272,75 @@ defmodule Version do
Enum.filter(Enum.reverse(acc), &(&1 != :' '))
end
@version_regex %r/^(\d+)(?:\.(\d+)(?:\.(\d+))?)?(?:\-([^\s]+))?(?:\+[^\d]+)?$/
@version_regex ~r/^
(\d+) # major
(?:\.(\d+))? # minor
(?:\.(\d+))? # patch
(?:\-([\d\w\.\-]+))? # pre
(?:\+([\d\w\-]+))? # build
$/x
@spec parse_requirement(String.t) :: { :ok, Version.Requirement.t } | { :error, binary | atom }
@spec parse_requirement(String.t) :: {:ok, Version.Requirement.t} | :error
def parse_requirement(source) do
lexed = lexer(source, [])
if valid_requirement?(lexed) do
spec = to_matchspec(lexed)
case :ets.test_ms({}, spec) do
{ :ok, _ } ->
{ :ok, Requirement[source: source, matchspec: spec] }
{ :error, errors } ->
{ :error, Enum.map(errors, fn { :error, reason } ->
to_string(reason)
end) }
end
else
{ :error, :invalid_requirement }
end
to_matchspec(lexed)
end
defp nillify(""), do: nil
defp nillify(o), do: o
@spec parse_version(String.t) :: { :ok, Version.matchable } | { :error, :invalid_version }
def parse_version(string) when is_binary(string) do
if valid_version?(string) do
destructure [_, major, minor, patch, pre], Regex.run(@version_regex, string)
@spec parse_version(String.t) :: {:ok, Version.matchable} | :error
def parse_version(string, approximate? \\ false) when is_binary(string) do
if parsed = Regex.run(@version_regex, string) do
destructure [_, major, minor, patch, pre], parsed
patch = nillify(patch)
pre = nillify(pre)
major = binary_to_integer(major)
minor = binary_to_integer(minor |> nillify || "0")
patch = binary_to_integer(patch |> nillify || "0")
pre = pre && parse_pre(pre) || []
{ :ok, { major, minor, patch, pre } }
else
{ :error, :invalid_version }
end
end
@doc false
def parse_pre(pre) do
String.split(pre, ".") |> Enum.map fn piece ->
if piece =~ %r/^(0|[1-9][0-9]*)$/ do
binary_to_integer(piece)
if nil?(minor) or (nil?(patch) and not approximate?) do
:error
else
piece
major = binary_to_integer(major)
minor = binary_to_integer(minor)
patch = patch && binary_to_integer(patch)
case parse_pre(pre) do
{ :ok, pre } ->
{ :ok, { major, minor, patch, pre } }
:error ->
:error
end
end
else
:error
end
end
@spec valid_requirement?(list) :: boolean
def valid_requirement?([]) do
false
defp parse_pre(nil), do: { :ok, [] }
defp parse_pre(pre), do: parse_pre(String.split(pre, "."), [])
defp parse_pre([piece|t], acc) do
cond do
piece =~ ~r/^(0|[1-9][0-9]*)$/ ->
parse_pre(t, [binary_to_integer(piece)|acc])
piece =~ ~r/^[0-9]*$/ ->
:error
true ->
parse_pre(t, [piece|acc])
end
end
def valid_requirement?([a | next]) do
valid_requirement?(a, next)
defp parse_pre([], acc) do
{ :ok, Enum.reverse(acc) }
end
defp valid_requirement?([]), do: false
defp valid_requirement?([a | next]), do: valid_requirement?(a, next)
# it must finish with a version
defp valid_requirement?(a, []) when is_binary(a) do
true
end
# version version
defp valid_requirement?(a, [b | _]) when is_binary(a) and is_binary(b) do
false
end
# or <op> | and <op>
defp valid_requirement?(a, [b | next]) when is_atom(a) and is_atom(b) and a in [:'||', :'&&'] do
valid_requirement?(b, next)
@@ -379,73 +356,57 @@ defmodule Version do
valid_requirement?(b, next)
end
# <op> <version>; also checks operators work on valid versions
# <op> <version>
defp valid_requirement?(a, [b | next]) when is_atom(a) and is_binary(b) do
if valid_version?(b) do
valid_requirement?(b, next)
else
if a in [:'==', :'!='] and Regex.match? %r/^\w/, b do
valid_requirement?(b, next)
else
false
end
end
valid_requirement?(b, next)
end
defp valid_requirement?(_, _) do
false
end
@spec valid_version?(String.t) :: boolean
def valid_version?(string) do
Regex.match? %r/^\d+(\.\d+(\.\d+)?)?(\-[^\s]+)?(?:\+[^\s]+)?$/, string
end
defp approximate_upper(version) do
case version do
{ major, _minor, nil, _ } ->
{ major + 1, 0, 0, [0] }
defp approximate(version) do
Version.from_matchable(case Regex.run(@version_regex, version) do
[_, major] ->
{ binary_to_integer(major) + 1, 0, 0, [0] }
[_, major, _] ->
{ binary_to_integer(major) + 1, 0, 0, [0] }
[_, major, minor, _] ->
{ binary_to_integer(major), binary_to_integer(minor) + 1, 0, [0] }
[_, major, minor, _, _] ->
{ binary_to_integer(major), binary_to_integer(minor) + 1, 0, [0] }
end)
{ major, minor, _patch, _ } ->
{ major, minor + 1, 0, [0] }
end
end
defp to_matchspec(lexed) do
first = to_condition(lexed)
rest = Enum.drop(lexed, 2)
[{{ :'$1', :'$2', :'$3', :'$4' }, [to_condition(first, rest)], [:'$_'] }]
if valid_requirement?(lexed) do
first = to_condition(lexed)
rest = Enum.drop(lexed, 2)
{ :ok, [{{ :'$1', :'$2', :'$3', :'$4' }, [to_condition(first, rest)], [:'$_'] }] }
else
:error
end
catch
:invalid_matchspec -> :error
end
defp to_condition([:'==', version | _]) do
version = Version.to_matchable(version)
version = parse_condition(version)
{ :'==', :'$_', { :const, version } }
end
defp to_condition([:'!=', version | _]) do
version = Version.to_matchable(version)
version = parse_condition(version)
{ :'/=', :'$_', { :const, version } }
end
defp to_condition([:'~>', version | _]) do
from = Version.parse(version)
to = approximate(version)
from = parse_condition(version, true)
to = approximate_upper(from)
{ :andalso, to_condition([:'>=', to_string(from)]),
to_condition([:'<', to_string(to)]) }
{ :andalso, to_condition([:'>=', matchable_to_string(from)]),
to_condition([:'<', matchable_to_string(to)]) }
end
defp to_condition([:'>', version | _]) do
{ major, minor, patch, pre } = Version.to_matchable(version)
{ major, minor, patch, pre } = parse_condition(version)
{ :andalso, { :not, { :is_binary, :'$1' } },
{ :orelse, { :'>', {{ :'$1', :'$2', :'$3' }},
@@ -461,7 +422,7 @@ defmodule Version do
end
defp to_condition([:'>=', version | _]) do
matchable = Version.to_matchable(version)
matchable = parse_condition(version)
{ :orelse, { :andalso, { :not, { :is_binary, :'$1' } },
{ :'==', :'$_', { :const, matchable } } },
@@ -469,7 +430,7 @@ defmodule Version do
end
defp to_condition([:'<', version | _]) do
{ major, minor, patch, pre } = Version.to_matchable(version)
{ major, minor, patch, pre } = parse_condition(version)
{ :andalso, { :not, { :is_binary, :'$1' } },
{ :orelse, { :'<', {{ :'$1', :'$2', :'$3' }},
@@ -485,7 +446,7 @@ defmodule Version do
end
defp to_condition([:'<=', version | _]) do
matchable = Version.to_matchable(version)
matchable = parse_condition(version)
{ :orelse, { :andalso, { :not, { :is_binary, :'$1' } },
{ :'==', :'$_', { :const, matchable } } },
@@ -503,6 +464,19 @@ defmodule Version do
defp to_condition(current, [:'||', operator, version | rest]) do
to_condition({ :orelse, current, to_condition([operator, version]) }, rest)
end
defp parse_condition(version, approximate? \\ false) do
case parse_version(version, approximate?) do
{ :ok, version } -> version
:error -> throw :invalid_matchspec
end
end
defp matchable_to_string({ major, minor, patch, pre }) do
patch = if patch, do: "#{patch}", else: "0"
pre = if pre != [], do: "-#{Enum.join(pre, ".")}"
"#{major}.#{minor}.#{patch}#{pre}"
end
end
end
@@ -519,13 +493,13 @@ defimpl Inspect, for: Version.Schema do
end
defimpl String.Chars, for: Version.Requirement do
def to_string({ _, source, _ }) do
def to_string(Version.Requirement[source: source]) do
source
end
end
defimpl Inspect, for: Version.Requirement do
def inspect({ _, source, _ }, _opts) do
def inspect(Version.Requirement[source: source], _opts) do
"#Version.Requirement<" <> source <> ">"
end
end
+1
View File
@@ -4,6 +4,7 @@ defmodule Elixir.Mixfile do
def project do
[ app: :elixir,
version: System.version,
build_per_environment: false,
escript_embed_elixir: false,
escript_main_module: :elixir,
escript_emu_args: "%%! -noshell\n" ]
+1 -1
View File
@@ -24,4 +24,4 @@
{verbose, false}
]}.
{require_otp_vsn,"(R16B|R17A).*"}.
{require_otp_vsn,"(R16B|17).*"}.
+1 -1
View File
@@ -2,7 +2,7 @@
-export([call/1, cast/1]).
-export([start_link/0, init/1, handle_call/3, handle_cast/2,
handle_info/2, terminate/2, code_change/3]).
-behavior(gen_server).
-behaviour(gen_server).
-define(timeout, 30000).
-record(elixir_code_server, {
+1 -1
View File
@@ -186,7 +186,7 @@ core_file(File) ->
catch
Kind:Reason ->
io:format("~p: ~p~nstacktrace: ~p~n", [Kind, Reason, erlang:get_stacktrace()]),
exit(1)
erlang:halt(1)
end.
core_main() ->
+1 -1
View File
@@ -1,7 +1,7 @@
-module(elixir_counter).
-export([start_link/0, init/1, handle_call/3, handle_cast/2,
handle_info/2, terminate/2, code_change/3, next/0]).
-behavior(gen_server).
-behaviour(gen_server).
-define(timeout, 1000). %% 1 second
-define(limit, 4294967296). %% 2^32
+9 -3
View File
@@ -6,9 +6,15 @@
expand(Args, E) ->
lists:mapfoldl(fun
({ '//', Meta, [Left, Right] }, Acc) ->
elixir_errors:deprecation(Meta, E#elixir_env.file, "using // for default arguments is deprecated, "
"please use \\\\ instead"),
{ ELeft, EL } = elixir_exp:expand(Left, Acc),
{ ERight, _ } = elixir_exp:expand(Right, Acc#elixir_env{context=nil}),
{ { '//', Meta, [ELeft, ERight] }, EL };
{ { '\\\\', Meta, [ELeft, ERight] }, EL };
({ '\\\\', Meta, [Left, Right] }, Acc) ->
{ ELeft, EL } = elixir_exp:expand(Left, Acc),
{ ERight, _ } = elixir_exp:expand(Right, Acc#elixir_env{context=nil}),
{ { '\\\\', Meta, [ELeft, ERight] }, EL };
(Left, Acc) ->
elixir_exp:expand(Left, Acc)
end, E, Args).
@@ -21,7 +27,7 @@ unpack(Kind, Name, Args, S) ->
%% Unpack default from given args.
%% Returns the given arguments without their default
%% clauses and a list of clauses for the default calls.
unpack_each(Kind, Name, [{'//', Line, [Expr, _]}|T] = List, Acc, Clauses, S) ->
unpack_each(Kind, Name, [{'\\\\', Line, [Expr, _]}|T] = List, Acc, Clauses, S) ->
Base = build_match(Acc, Line, []),
{ Args, Invoke } = extract_defaults(List, Line, length(Base), [], []),
@@ -44,7 +50,7 @@ unpack_each(_Kind, _Name, [], Acc, Clauses, _S) ->
% Extract default values from args following the current default clause.
extract_defaults([{'//', _, [_Expr, Default]}|T], Line, Counter, NewArgs, NewInvoke) ->
extract_defaults([{'\\\\', _, [_Expr, Default]}|T], Line, Counter, NewArgs, NewInvoke) ->
extract_defaults(T, Line, Counter, NewArgs, [Default|NewInvoke]);
extract_defaults([_|T], Line, Counter, NewArgs, NewInvoke) ->
+165 -104
View File
@@ -9,14 +9,18 @@
find_import/4, format_error/1]).
-include("elixir.hrl").
-import(ordsets, [is_element/2]).
%% Module macros
-define(kernel, 'Elixir.Kernel').
-define(node, 'Elixir.Node').
-define(process, 'Elixir.Process').
default_functions() ->
[ { ?kernel, elixir_imported_functions() } ].
default_macros() ->
[ { ?kernel, elixir_imported_macros() } ].
default_requires() ->
[ 'Elixir.Integer', 'Elixir.Kernel', 'Elixir.Kernel.Typespec', 'Elixir.Record' ].
[ 'Elixir.Kernel', 'Elixir.Kernel.Typespec' ].
find_import(Meta, Name, Arity, E) ->
Tuple = { Name, Arity },
@@ -64,13 +68,10 @@ require_function(_Meta, Receiver, Name, Arity, E) ->
remote_function(Receiver, Name, Arity, E) ->
elixir_lexical:record_remote(Receiver, E#elixir_env.lexical_tracker),
Tuple = { Name, Arity },
Final =
case (Receiver == ?kernel) andalso is_element(Tuple, in_erlang_functions()) of
true -> erlang;
false -> Receiver
end,
{ remote, Final, Name, Arity }.
case inline(Receiver, Name, Arity) of
{ AR, AN } -> { remote, AR, AN, Arity };
false -> { remote, Receiver, Name, Arity }
end.
%% Dispatches
@@ -87,19 +88,19 @@ dispatch_import(Meta, Name, Args, E, Callback) ->
dispatch_require(Meta, Receiver, Name, Args, E, Callback) when is_atom(Receiver) ->
Arity = length(Args),
Tuple = { Name, Arity },
case (Receiver == ?kernel) andalso is_element(Tuple, in_erlang_functions()) of
true -> Callback(erlang);
case rewrite(Receiver, Name, Args, Arity) of
{ ok, AR, AN, AA } ->
Callback(AR, AN, AA);
false ->
case expand_require(Meta, Receiver, Tuple, Args, E) of
case expand_require(Meta, Receiver, { Name, Arity }, Args, E) of
{ ok, Receiver, Quoted } -> expand_quoted(Meta, Receiver, Name, Arity, Quoted, E);
error -> Callback(Receiver)
error -> Callback(Receiver, Name, Args)
end
end;
dispatch_require(_Meta, Receiver, _Name, _Args, _E, Callback) ->
Callback(Receiver).
dispatch_require(_Meta, Receiver, Name, Args, _E, Callback) ->
Callback(Receiver, Name, Args).
%% Macros expansion
@@ -111,13 +112,13 @@ expand_import(Meta, { Name, Arity } = Tuple, Args, E, Extra) ->
elixir_locals:macro_for(Module, Name, Arity),
case Dispatch of
%% In case it is an import, or the receive is the same as the
%% current module (happens in bootstraping), we dispatch the import.
{ Kind, Receiver } when Kind == import; Receiver == Module ->
%% In case it is an import, we dispatch the import.
{ Kind, _ } when Kind == import ->
do_expand_import(Meta, Tuple, Args, Module, E, Dispatch);
%% There is a local and an import. This is a conflict.
{ _, Receiver } when Local /= false ->
%% There is a local and an import. This is a conflict unless
%% the receiver is the same as module (happens on bootstrap).
{ _, Receiver } when Local /= false, Receiver /= Module ->
Error = { macro_conflict, { Receiver, Name, Arity } },
elixir_errors:form_error(Meta, E#elixir_env.file, ?MODULE, Error);
@@ -136,11 +137,12 @@ do_expand_import(Meta, { Name, Arity } = Tuple, Args, Module, E, Result) ->
{ function, Receiver } ->
elixir_lexical:record_import(Receiver, E#elixir_env.lexical_tracker),
elixir_locals:record_import(Tuple, Receiver, Module, E#elixir_env.function),
Endpoint = case (Receiver == ?kernel) andalso is_element(Tuple, in_erlang_functions()) of
true -> erlang;
false -> Receiver
end,
{ ok, Endpoint };
case rewrite(Receiver, Name, Args, Arity) of
{ ok, AR, AN, AA } ->
{ ok, AR, { { '.', [], [AR, AN] }, [], AA } };
false ->
{ ok, Receiver }
end;
{ macro, Receiver } ->
elixir_lexical:record_import(Receiver, E#elixir_env.lexical_tracker),
elixir_locals:record_import(Tuple, Receiver, Module, E#elixir_env.function),
@@ -150,6 +152,13 @@ do_expand_import(Meta, { Name, Arity } = Tuple, Args, Module, E, Result) ->
{ ok, _, _ } = Response -> Response;
error -> { ok, Receiver }
end;
false when Module == ?kernel ->
case rewrite(Module, Name, Args, Arity) of
{ ok, AR, AN, AA } ->
{ ok, AR, { { '.', [], [AR, AN] }, [], AA } };
false ->
error
end;
false ->
error
end.
@@ -303,7 +312,7 @@ elixir_imported_functions() ->
try
?kernel:'__info__'(functions)
catch
error:undef -> in_erlang_functions()
error:undef -> []
end.
elixir_imported_macros() ->
@@ -313,81 +322,133 @@ elixir_imported_macros() ->
error:undef -> []
end.
%% Functions imported from Erlang module. MUST BE SORTED.
%% TODO: Inline operators here as well once bug in erl_eval is fixed.
in_erlang_functions() ->
[
{ abs, 1 },
{ apply, 2 },
{ apply, 3 },
{ atom_to_list, 1 },
{ binary_part, 3 },
{ binary_to_float, 1 },
{ binary_to_float, 2 },
{ binary_to_integer, 1 },
{ binary_to_integer, 2 },
{ binary_to_term, 1 },
{ binary_to_term, 2 },
{ bit_size, 1 },
{ bitstring_to_list, 1 },
{ byte_size, 1 },
% { date, 0 },
{ exit, 1 },
% { float, 1 },
{ float_to_binary, 1 },
% { float_to_binary, 2 },
{ float_to_list, 1 },
% { float_to_list, 2 },
{ hd, 1 },
{ integer_to_binary, 1 },
{ integer_to_binary, 2 },
{ integer_to_list, 1 },
{ integer_to_list, 2 },
{ iolist_size, 1 },
{ iolist_to_binary, 1 },
{ is_atom, 1 },
{ is_binary, 1 },
{ is_bitstring, 1 },
{ is_boolean, 1 },
{ is_float, 1 },
{ is_function, 1 },
{ is_function, 2 },
{ is_integer, 1 },
{ is_list, 1 },
{ is_number, 1 },
{ is_pid, 1 },
{ is_port, 1 },
{ is_reference, 1 },
{ is_tuple, 1 },
{ length, 1 },
{ list_to_atom, 1 },
{ list_to_bitstring, 1 },
{ list_to_existing_atom, 1 },
{ list_to_float, 1 },
{ list_to_integer, 1 },
{ list_to_integer, 2 },
{ list_to_tuple, 1 },
{ make_ref, 0 },
{ max, 2 },
{ min, 2 },
{ node, 0 },
{ node, 1 },
% { now, 0 },
{ round, 1 },
{ self, 0 },
{ send, 2 },
{ size, 1 },
{ spawn, 1 },
{ spawn, 3 },
{ spawn_link, 1 },
{ spawn_link, 3 },
% { split_binary, 2 },
{ term_to_binary, 1 },
{ term_to_binary, 2 },
{ throw, 1 },
% { time, 0 },
{ tl, 1 },
{ trunc, 1 },
{ tuple_size, 1 },
{ tuple_to_list, 1 }
].
rewrite(?kernel, atom_to_binary, [Arg], 1) ->
{ ok, erlang, atom_to_binary, [Arg, utf8] };
rewrite(?kernel, binary_to_atom, [Arg], 1) ->
{ ok, erlang, binary_to_atom, [Arg, utf8] };
rewrite(?kernel, binary_to_existing_atom, [Arg], 1) ->
{ ok, erlang, binary_to_existing_atom, [Arg, utf8] };
rewrite(?kernel, elem, [Tuple, Index], 2) ->
{ ok, erlang, element, [increment(Index), Tuple] };
rewrite(?kernel, set_elem, [Tuple, Index, Value], 2) ->
{ ok, erlang, setelement, [increment(Index), Tuple, Value] };
rewrite(?process, monitor, [Arg], 1) ->
{ ok, erlang, monitor, [process, Arg] };
rewrite(Receiver, Name, Args, Arity) ->
case inline(Receiver, Name, Arity) of
{ AR, AN } -> { ok, AR, AN, Args };
false -> false
end.
increment(Number) when is_number(Number) ->
Number + 1;
increment(Other) ->
{ { '.', [], [erlang, '+'] }, [], [Other, 1] }.
inline(?kernel, '+', 2) -> { erlang, '+' };
inline(?kernel, '-', 2) -> { erlang, '-' };
inline(?kernel, '+', 1) -> { erlang, '+' };
inline(?kernel, '-', 1) -> { erlang, '-' };
inline(?kernel, '*', 2) -> { erlang, '*' };
inline(?kernel, '/', 2) -> { erlang, '/' };
inline(?kernel, '++', 2) -> { erlang, '++' };
inline(?kernel, '--', 2) -> { erlang, '--' };
inline(?kernel, 'xor', 2) -> { erlang, 'xor' };
inline(?kernel, 'not', 1) -> { erlang, 'not' };
inline(?kernel, '<', 2) -> { erlang, '<' };
inline(?kernel, '>', 2) -> { erlang, '>' };
inline(?kernel, '<=', 2) -> { erlang, '=<' };
inline(?kernel, '>=', 2) -> { erlang, '>=' };
inline(?kernel, '==', 2) -> { erlang, '==' };
inline(?kernel, '!=', 2) -> { erlang, '/=' };
inline(?kernel, '===', 2) -> { erlang, '=:=' };
inline(?kernel, '!==', 2) -> { erlang, '=/=' };
inline(?kernel, abs, 1) -> { erlang, abs };
inline(?kernel, apply, 2) -> { erlang, apply };
inline(?kernel, apply, 3) -> { erlang, apply };
inline(?kernel, atom_to_list, 1) -> { erlang, atom_to_list };
inline(?kernel, binary_part, 3) -> { erlang, binary_part };
inline(?kernel, binary_to_float, 1) -> { erlang, binary_to_float };
inline(?kernel, binary_to_float, 2) -> { erlang, binary_to_float };
inline(?kernel, binary_to_integer, 1) -> { erlang, binary_to_integer };
inline(?kernel, binary_to_integer, 2) -> { erlang, binary_to_integer };
inline(?kernel, bit_size, 1) -> { erlang, bit_size };
inline(?kernel, bitstring_to_list, 1) -> { erlang, bitstring_to_list };
inline(?kernel, byte_size, 1) -> { erlang, byte_size };
inline(?kernel, 'div', 2) -> { erlang, 'div' };
inline(?kernel, exit, 1) -> { erlang, exit };
inline(?kernel, float_to_binary, 1) -> { erlang, float_to_binary };
inline(?kernel, float_to_list, 1) -> { erlang, float_to_list };
inline(?kernel, hd, 1) -> { erlang, hd };
inline(?kernel, integer_to_binary, 1) -> { erlang, integer_to_binary };
inline(?kernel, integer_to_binary, 2) -> { erlang, integer_to_binary };
inline(?kernel, integer_to_list, 1) -> { erlang, integer_to_list };
inline(?kernel, integer_to_list, 2) -> { erlang, integer_to_list };
inline(?kernel, iolist_size, 1) -> { erlang, iolist_size };
inline(?kernel, iolist_to_binary, 1) -> { erlang, iolist_to_binary };
inline(?kernel, is_atom, 1) -> { erlang, is_atom };
inline(?kernel, is_binary, 1) -> { erlang, is_binary };
inline(?kernel, is_bitstring, 1) -> { erlang, is_bitstring };
inline(?kernel, is_boolean, 1) -> { erlang, is_boolean };
inline(?kernel, is_float, 1) -> { erlang, is_float };
inline(?kernel, is_function, 1) -> { erlang, is_function };
inline(?kernel, is_function, 2) -> { erlang, is_function };
inline(?kernel, is_integer, 1) -> { erlang, is_integer };
inline(?kernel, is_list, 1) -> { erlang, is_list };
inline(?kernel, is_number, 1) -> { erlang, is_number };
inline(?kernel, is_pid, 1) -> { erlang, is_pid };
inline(?kernel, is_port, 1) -> { erlang, is_port };
inline(?kernel, is_reference, 1) -> { erlang, is_reference };
inline(?kernel, is_tuple, 1) -> { erlang, is_tuple };
inline(?kernel, length, 1) -> { erlang, length };
inline(?kernel, list_to_atom, 1) -> { erlang, list_to_atom };
inline(?kernel, list_to_bitstring, 1) -> { erlang, list_to_bitstring };
inline(?kernel, list_to_existing_atom, 1) -> { erlang, list_to_existing_atom };
inline(?kernel, list_to_float, 1) -> { erlang, list_to_float };
inline(?kernel, list_to_integer, 1) -> { erlang, list_to_integer };
inline(?kernel, list_to_integer, 2) -> { erlang, list_to_integer };
inline(?kernel, list_to_tuple, 1) -> { erlang, list_to_tuple };
inline(?kernel, make_ref, 0) -> { erlang, make_ref };
inline(?kernel, max, 2) -> { erlang, max };
inline(?kernel, min, 2) -> { erlang, min };
inline(?kernel, node, 0) -> { erlang, node };
inline(?kernel, node, 1) -> { erlang, node };
inline(?kernel, 'rem', 2) -> { erlang, 'rem' };
inline(?kernel, round, 1) -> { erlang, round };
inline(?kernel, self, 0) -> { erlang, self };
inline(?kernel, send, 2) -> { erlang, send };
inline(?kernel, size, 1) -> { erlang, size };
inline(?kernel, spawn, 1) -> { erlang, spawn };
inline(?kernel, spawn, 3) -> { erlang, spawn };
inline(?kernel, spawn_link, 1) -> { erlang, spawn_link };
inline(?kernel, spawn_link, 3) -> { erlang, spawn_link };
inline(?kernel, throw, 1) -> { erlang, throw };
inline(?kernel, tl, 1) -> { erlang, tl };
inline(?kernel, trunc, 1) -> { erlang, trunc };
inline(?kernel, tuple_size, 1) -> { erlang, tuple_size };
inline(?kernel, tuple_to_list, 1) -> { erlang, tuple_to_list };
inline(?process, exit, 2) -> { erlang, exit };
inline(?process, spawn, 1) -> { erlang, spawn };
inline(?process, spawn, 2) -> { erlang, spawn_opt };
inline(?process, spawn, 3) -> { erlang, spawn };
inline(?process, spawn, 4) -> { erlang, spawn_opt };
inline(?process, spawn_link, 1) -> { erlang, spawn_link };
inline(?process, spawn_link, 3) -> { erlang, spawn_link };
inline(?process, spawn_monitor, 1) -> { erlang, spawn_monitor };
inline(?process, spawn_monitor, 3) -> { erlang, spawn_monitor };
inline(?process, demonitor, 1) -> { erlang, demonitor };
inline(?process, demonitor, 2) -> { erlang, demonitor };
inline(?process, link, 1) -> { erlang, link };
inline(?process, unlink, 1) -> { erlang, unlink };
inline(?node, spawn, 2) -> { erlang, spawn };
inline(?node, spawn, 3) -> { erlang, spawn_opt };
inline(?node, spawn, 4) -> { erlang, spawn };
inline(?node, spawn, 5) -> { erlang, spawn_opt };
inline(?node, spawn_link, 2) -> { erlang, spawn_link };
inline(?node, spawn_link, 4) -> { erlang, spawn_link };
inline(?node, spawn_monitor, 2) -> { erlang, spawn_monitor };
inline(?node, spawn_monitor, 4) -> { erlang, spawn_monitor };
inline(_, _, _) -> false.
+13 -7
View File
@@ -2,7 +2,7 @@
% This is not exposed in the Elixir language.
-module(elixir_errors).
-export([compile_error/3, compile_error/4,
form_error/4, parse_error/4, warn/1,
form_error/4, parse_error/4, warn/2, warn/3,
handle_file_warning/2, handle_file_warning/3, handle_file_error/2,
deprecation/3, deprecation/4]).
-include("elixir.hrl").
@@ -16,6 +16,12 @@ warn(Warning) ->
end,
io:put_chars(standard_error, Warning).
warn(Caller, Warning) ->
warn([Caller, "warning: ", Warning]).
warn(Line, File, Warning) ->
warn(file_format(Line, File, "warning: " ++ Warning)).
%% Raised during expansion/translation/compilation.
-spec form_error(list(), binary(), module(), any()) -> no_return().
@@ -93,7 +99,7 @@ handle_file_warning(_, File, { Line, sys_core_fold, { no_effect, { erlang, F, A
end
end,
Message = io_lib:format(Fmt, Args),
warn(file_format(Line, File, Message));
warn(Line, File, Message);
%% Rewrite undefined behaviour to check for protocols
handle_file_warning(_, File, {Line,erl_lint,{undefined_behaviour_func,{Fun,Arity},Module}}) ->
@@ -106,14 +112,14 @@ handle_file_warning(_, File, {Line,erl_lint,{undefined_behaviour_func,{Fun,Arity
Kind = protocol_or_behaviour(Module),
Raw = "undefined ~ts ~ts ~ts/~B (for ~ts ~ts)",
Message = io_lib:format(Raw, [Kind, DefKind, Def, DefArity, Kind, elixir_aliases:inspect(Module)]),
warn(file_format(Line, File, Message));
warn(Line, File, Message);
handle_file_warning(_, File, {Line,erl_lint,{undefined_behaviour,Module}}) ->
case elixir_compiler:get_opt(internal) of
true -> [];
false ->
Message = io_lib:format("behaviour ~ts undefined", [elixir_aliases:inspect(Module)]),
warn(file_format(Line, File, Message))
warn(Line, File, Message)
end;
%% Ignore unused vars at "weird" lines (<= 0)
@@ -127,12 +133,12 @@ handle_file_warning(_, _File, {_Line,erl_lint,{shadowed_var,_Var,_Where}}) ->
%% Properly format other unused vars
handle_file_warning(_, File, {Line,erl_lint,{unused_var,Var}}) ->
Message = format_error(erl_lint, { unused_var, format_var(Var) }),
warn(file_format(Line, File, Message));
warn(Line, File, Message);
%% Default behavior
%% Default behaviour
handle_file_warning(_, File, {Line,Module,Desc}) ->
Message = format_error(Module, Desc),
warn(file_format(Line, File, Message)).
warn(Line, File, Message).
handle_file_warning(File, Desc) ->
handle_file_warning(false, File, Desc).
+3 -9
View File
@@ -122,9 +122,6 @@ expand({ import, Meta, [Ref, KV] }, E) ->
expand({ '__MODULE__', _, Atom }, E) when is_atom(Atom) ->
{ E#elixir_env.module, E };
expand({ '__FILE__', Meta, Atom }, E) when is_atom(Atom) ->
elixir_errors:deprecation(Meta, E#elixir_env.file, "__FILE__ is deprecated, please use __DIR__ or __ENV__.file instead"),
{ E#elixir_env.file, E };
expand({ '__DIR__', _, Atom }, E) when is_atom(Atom) ->
{ filename:dirname(E#elixir_env.file), E };
expand({ '__CALLER__', _, Atom } = Caller, E) when is_atom(Atom) ->
@@ -296,9 +293,6 @@ expand({ Name, Meta, Kind } = Var, #elixir_env{vars=Vars} = E) when is_atom(Name
compile_error(Meta, E#elixir_env.file, "expected var ~ts~ts to expand to an existing "
"variable or be a part of a match", [Name, Extra]);
E#elixir_env.context == guard ->
compile_error(Meta, E#elixir_env.file, "unknown variable ~ts or cannot invoke "
"function ~ts/0 inside guard", [Name, Name]);
true ->
expand({ Name, Meta, [] }, E)
end
@@ -319,8 +313,8 @@ expand({ { '.', DotMeta, [Left, Right] }, Meta, Args }, E)
when (is_tuple(Left) orelse is_atom(Left)), is_atom(Right), is_list(Meta), is_list(Args) ->
{ ELeft, EL } = expand(Left, E),
elixir_dispatch:dispatch_require(Meta, ELeft, Right, Args, EL, fun(Receiver) ->
expand_remote(Receiver, DotMeta, Right, Meta, Args, E, EL)
elixir_dispatch:dispatch_require(Meta, ELeft, Right, Args, EL, fun(AR, AF, AA) ->
expand_remote(AR, DotMeta, AF, Meta, AA, E, EL)
end);
%% Anonymous calls
@@ -402,7 +396,7 @@ expand_many(Args, E) ->
%% 3
%%
%% However, lexical information is.
expand_arg(Arg, Acc) when is_number(Arg); is_atom(Arg); is_binary(Arg); is_pid(Arg); is_function(Arg) ->
expand_arg(Arg, Acc) when is_number(Arg); is_atom(Arg); is_binary(Arg); is_pid(Arg) ->
{ Arg, Acc };
expand_arg(Arg, { Acc1, Acc2 }) ->
{ EArg, EAcc } = expand(Arg, Acc1),
+3 -3
View File
@@ -105,7 +105,7 @@ build(Line, File, Module, Lexical) ->
_ -> ets:insert(DataTable, { on_definition, [] })
end,
Attributes = [behavior, behaviour, on_load, spec, type, export_type, opaque, callback, compile],
Attributes = [behaviour, on_load, spec, type, export_type, opaque, callback, compile],
ets:insert(DataTable, { ?acc_attr, [before_compile, after_compile, on_definition|Attributes] }),
ets:insert(DataTable, { ?persisted_attr, [vsn|Attributes] }),
ets:insert(DataTable, { ?docs_attr, ets:new(DataTable, [ordered_set, public]) }),
@@ -360,8 +360,8 @@ eval_callbacks(Line, Module, Name, Args, E) ->
Meta = [{line,Line},{require,false}],
lists:foldl(fun({M,F}, Acc) ->
{ Expr, ET } = elixir_dispatch:dispatch_require(Meta, M, F, Args, Acc, fun(_) ->
apply(M, F, Args),
{ Expr, ET } = elixir_dispatch:dispatch_require(Meta, M, F, Args, Acc, fun(AM, AF, AA) ->
apply(AM, AF, AA),
{ nil, Acc }
end),
+44 -53
View File
@@ -4,14 +4,14 @@ Nonterminals
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
add_op_eol mult_op_eol exp_op_eol two_op_eol type_op_eol stab_op_eol
add_op_eol mult_op_eol exp_op_eol two_op_eol pipe_op_eol stab_op_eol
arrow_op_eol match_op_eol when_op_eol in_op_eol in_match_op_eol
open_paren close_paren empty_paren
open_bracket close_bracket
open_curly close_curly
open_bit close_bit
container_comma_expr_base container_comma_expr container_arg container_args
call_args_parens_comma_expr call_args_parens parens_call
container_args_base container_args
call_args_parens_base call_args_parens parens_call
call_args_no_parens_one call_args_no_parens_expr call_args_no_parens_comma_expr
call_args_no_parens_all call_args_no_parens_many call_args_no_parens_many_strict
stab stab_eol stab_expr stab_maybe_expr stab_parens_many
@@ -19,7 +19,7 @@ Nonterminals
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 bit_string tuple
var list list_args bit_string tuple
do_block fn_eol do_eol end_eol block_eol block_item block_list
.
@@ -30,8 +30,8 @@ Terminals
number signed_number atom atom_string bin_string list_string sigil
dot_call_op op_identifier
comp_op at_op unary_op and_op or_op arrow_op match_op in_op in_match_op
dual_op add_op mult_op exp_op two_op type_op stab_op when_op
'true' 'false' 'nil' 'do' eol ',' '.' '&'
dual_op add_op mult_op exp_op two_op pipe_op stab_op when_op
'true' 'false' 'nil' 'do' eol ',' '.'
'(' ')' '[' ']' '{' '}' '<<' '>>'
.
@@ -44,16 +44,16 @@ Expect 2.
Left 5 do.
Right 10 stab_op_eol. %% ->
Left 20 ','.
Right 40 type_op_eol. %% ::
Right 50 in_match_op_eol. %% inlist, inbits, //, | (allowed in matches, lower precedence than =)
Left 40 in_match_op_eol. %% <-, inlist, inbits, //, \\, :: (allowed in matches along =)
Right 50 pipe_op_eol. %% |
Right 70 when_op_eol. %% when
Right 80 match_op_eol. %% =
Left 130 or_op_eol. %% ||, |||, or, xor
Left 140 and_op_eol. %% &&, &&&, and
Left 150 comp_op_eol. %% <, >, <=, >=, ==, !=, =~, ===, !==
Right 160 arrow_op_eol. %% < (op), (op) > (e.g <-, |>, <<<, >>>)
Right 160 arrow_op_eol. %% < (op), (op) > (e.g |>, <<<, >>>)
Left 170 in_op_eol. %% in
Right 200 two_op_eol. %% ++, --, **, .., <>
Right 200 two_op_eol. %% ++, --, .., <>
Left 210 add_op_eol. %% + (op), - (op)
Left 220 mult_op_eol. %% * (op), / (op)
Left 250 exp_op_eol. %% ^ (op) (e.g ^^^)
@@ -105,6 +105,7 @@ expr -> unmatched_expr : '$1'.
%% segments and act accordingly.
matched_expr -> matched_expr matched_op_expr : build_op(element(1, '$2'), '$1', element(2, '$2')).
matched_expr -> matched_expr no_parens_op_expr : build_op(element(1, '$2'), '$1', element(2, '$2')).
matched_expr -> unary_op_eol number : build_unary_op('$1', ?exprs('$2')).
matched_expr -> unary_op_eol matched_expr : build_unary_op('$1', '$2').
matched_expr -> unary_op_eol no_parens_expr : build_unary_op('$1', '$2').
matched_expr -> at_op_eol matched_expr : build_unary_op('$1', '$2').
@@ -137,7 +138,7 @@ op_expr -> or_op_eol expr : { '$1', '$2' }.
op_expr -> in_op_eol expr : { '$1', '$2' }.
op_expr -> in_match_op_eol expr : { '$1', '$2' }.
op_expr -> when_op_eol expr : { '$1', '$2' }.
op_expr -> type_op_eol expr : { '$1', '$2' }.
op_expr -> pipe_op_eol expr : { '$1', '$2' }.
op_expr -> comp_op_eol expr : { '$1', '$2' }.
op_expr -> arrow_op_eol expr : { '$1', '$2' }.
@@ -150,7 +151,7 @@ no_parens_op_expr -> and_op_eol no_parens_expr : { '$1', '$2' }.
no_parens_op_expr -> or_op_eol no_parens_expr : { '$1', '$2' }.
no_parens_op_expr -> in_op_eol no_parens_expr : { '$1', '$2' }.
no_parens_op_expr -> in_match_op_eol no_parens_expr : { '$1', '$2' }.
no_parens_op_expr -> type_op_eol no_parens_expr : { '$1', '$2' }.
no_parens_op_expr -> pipe_op_eol no_parens_expr : { '$1', '$2' }.
no_parens_op_expr -> comp_op_eol no_parens_expr : { '$1', '$2' }.
no_parens_op_expr -> arrow_op_eol no_parens_expr : { '$1', '$2' }.
@@ -168,7 +169,7 @@ matched_op_expr -> or_op_eol matched_expr : { '$1', '$2' }.
matched_op_expr -> in_op_eol matched_expr : { '$1', '$2' }.
matched_op_expr -> in_match_op_eol matched_expr : { '$1', '$2' }.
matched_op_expr -> when_op_eol matched_expr : { '$1', '$2' }.
matched_op_expr -> type_op_eol matched_expr : { '$1', '$2' }.
matched_op_expr -> pipe_op_eol matched_expr : { '$1', '$2' }.
matched_op_expr -> comp_op_eol matched_expr : { '$1', '$2' }.
matched_op_expr -> arrow_op_eol matched_expr : { '$1', '$2' }.
@@ -210,7 +211,6 @@ base_expr -> list_string : build_list_string('$1').
base_expr -> atom_string : build_atom_string('$1').
base_expr -> bit_string : '$1'.
base_expr -> sigil : build_sigil('$1').
base_expr -> '&' number : { '&', meta('$1'), [?exprs('$2')] }.
%% Blocks
@@ -244,7 +244,7 @@ stab_expr -> call_args_no_parens_all 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 -> stab_parens_many when_op expr stab_op_eol stab_maybe_expr :
build_op('$4', [{ 'when', meta('$2'), unwrap_splice('$1') ++ ['$3'] }], '$5').
build_op('$4', [{ 'when', meta('$2'), unwrap_splice('$1') ++ ['$3'] }], '$5').
stab_maybe_expr -> 'expr' : '$1'.
stab_maybe_expr -> '$empty' : nil.
@@ -297,8 +297,8 @@ exp_op_eol -> exp_op eol : '$1'.
two_op_eol -> two_op : '$1'.
two_op_eol -> two_op eol : '$1'.
type_op_eol -> type_op : '$1'.
type_op_eol -> type_op eol : '$1'.
pipe_op_eol -> pipe_op : '$1'.
pipe_op_eol -> pipe_op eol : '$1'.
unary_op_eol -> unary_op : '$1'.
unary_op_eol -> unary_op eol : '$1'.
@@ -396,27 +396,22 @@ 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_args_base -> container_expr : ['$1'].
container_args_base -> container_args_base ',' container_expr : ['$3'|'$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_args -> kw : ['$1'].
container_args -> container_args_base : lists:reverse('$1').
container_args -> container_args_base ',' : lists:reverse('$1').
container_args -> container_args_base ',' kw : lists:reverse(['$3'|'$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_base -> container_expr : ['$1'].
call_args_parens_base -> call_args_parens_base ',' container_expr : ['$3'|'$1'].
call_args_parens -> empty_paren : [].
call_args_parens -> open_paren no_parens_expr close_paren : ['$2'].
call_args_parens -> open_paren kw close_paren : ['$2'].
call_args_parens -> open_paren call_args_parens_comma_expr close_paren : lists:reverse('$2').
call_args_parens -> open_paren call_args_parens_comma_expr ',' kw close_paren : lists:reverse(['$4'|'$2']).
call_args_parens -> open_paren call_args_parens_base close_paren : lists:reverse('$2').
call_args_parens -> open_paren call_args_parens_base ',' kw close_paren : lists:reverse(['$4'|'$2']).
% KV
@@ -426,6 +421,7 @@ kw_eol -> kw_identifier eol : '$1'.
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']).
@@ -437,16 +433,18 @@ call_args_no_parens_kw -> call_args_no_parens_kw_comma : '$1'.
% Lists
list_args -> kw : '$1'.
list_args -> container_args_base : lists:reverse('$1').
list_args -> container_args_base ',' : lists:reverse('$1').
list_args -> container_args_base ',' kw : lists:reverse('$1') ++ '$3'.
list -> open_bracket ']' : { [], ?line('$1') }.
list -> open_bracket kw close_bracket : { '$2', ?line('$1') }.
list -> open_bracket container_arg close_bracket : { ['$2'], ?line('$1') }.
list -> open_bracket container_expr ',' container_args close_bracket : { ['$2'|'$4'], ?line('$1') }.
list -> open_bracket list_args close_bracket : { '$2', ?line('$1') }.
% Tuple
tuple -> open_curly '}' : build_tuple('$1', []).
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']).
tuple -> open_curly container_args close_curly : build_tuple('$1', '$2').
% Bitstrings
@@ -458,10 +456,7 @@ Erlang code.
-define(id(Node), element(1, Node)).
-define(line(Node), element(2, Node)).
-define(exprs(Node), element(3, Node)).
-define(lexical(Kind), Kind == import; Kind == alias; Kind == '__aliases__').
-define(rearrange_uop(Op), Op == 'not' orelse Op == '!').
-define(rearrange_bop(Op), Op == 'in' orelse Op == 'inlist' orelse Op == 'inbits').
%% The following directive is needed for (significantly) faster
%% compilation of the generated .erl file by the HiPE compiler
@@ -479,8 +474,8 @@ build_op({ _Kind, Line, '/' }, { '&', _, [{ Kind, _, Atom } = Left] }, Right) wh
build_op({ _Kind, Line, '/' }, { '&', _, [{ { '.', _, [_, _] }, _, [] } = Left] }, Right) when is_number(Right) ->
{ '&', meta(Line), [{ '/', meta(Line), [Left, Right] }] };
build_op({ _Kind, Line, BOp }, { UOp, _, [Left] }, Right) when ?rearrange_bop(BOp), ?rearrange_uop(UOp) ->
{ UOp, meta(Line), [{ BOp, meta(Line), [Left, Right] }] };
build_op({ _Kind, Line, 'in' }, { UOp, _, [Left] }, Right) when ?rearrange_uop(UOp) ->
{ UOp, meta(Line), [{ 'in', meta(Line), [Left, Right] }] };
build_op({ _Kind, Line, Op }, Left, Right) ->
{ Op, meta(Line), [Left, Right] }.
@@ -513,11 +508,17 @@ build_dot_alias(Dot, Other, { 'aliases', _, Right }) ->
build_dot(Dot, Left, Right) ->
{ '.', meta(Dot), [Left, extract_identifier(Right)] }.
extract_identifier({ Kind, _, Identifier }) when
Kind == identifier; Kind == bracket_identifier; Kind == paren_identifier;
Kind == do_identifier; Kind == op_identifier ->
Identifier.
%% Identifiers
build_nested_parens(Dot, Args1, Args2) ->
Identifier = build_identifier(Dot, Args1),
{ Identifier, ?line(Identifier), Args2 }.
Meta = element(2, Identifier),
{ Identifier, Meta, Args2 }.
build_identifier({ '.', Meta, _ } = Dot, Args) ->
FArgs = case Args of
@@ -532,19 +533,9 @@ build_identifier({ Keyword, Line }, Args) when Keyword == fn ->
build_identifier({ op_identifier, Line, Identifier }, [Arg]) ->
{ Identifier, [{ambiguous_op,nil}|meta(Line)], [Arg] };
build_identifier({ _, Line, Identifier }, Args) when ?lexical(Identifier) ->
{ Identifier, meta(Line, 0), Args };
build_identifier({ _, Line, Identifier }, Args) ->
{ Identifier, meta(Line), Args }.
extract_identifier({ Kind, _, Identifier }) when
Kind == identifier; Kind == bracket_identifier; Kind == paren_identifier;
Kind == do_identifier; Kind == op_identifier ->
Identifier;
extract_identifier(Other) -> Other.
%% Fn
build_fn(Op, Stab) ->
+140 -134
View File
@@ -24,7 +24,6 @@
-define(two_op(T1, T2),
T1 == $+, T2 == $+;
T1 == $-, T2 == $-;
T1 == $*, T2 == $*;
T1 == $<, T2 == $>;
T1 == $., T2 == $.).
@@ -39,7 +38,6 @@
T1 == $>, T2 == $>, T3 == $>).
-define(arrow_op(T1, T2),
T1 == $<, T2 == $-;
T1 == $|, T2 == $>).
-define(comp_op(T),
@@ -72,17 +70,17 @@
-define(match_op(T),
T == $=).
-define(in_match_op(T),
T == $|).
-define(in_match_op2(T1, T2),
T1 == $/, T2 == $/).
-define(in_match_op(T1, T2),
T1 == $<, T2 == $-;
T1 == $/, T2 == $/;
T1 == $\\, T2 == $\\;
T1 == $:, T2 == $:).
-define(stab_op(T1, T2),
T1 == $-, T2 == $>).
-define(type_op(T1, T2),
T1 == $:, T2 == $:).
-define(pipe_op(T1),
T == $|).
tokenize(String, Line, #elixir_tokenizer{} = Scope) ->
tokenize(String, Line, Scope, []);
@@ -140,15 +138,21 @@ tokenize([$#|String], Line, Scope, Tokens) ->
% Sigils
tokenize([$%,S,H,H,H|T] = Original, Line, Scope, Tokens) when ?is_quote(H), ?is_upcase(S) orelse ?is_downcase(S) ->
io:format(standard_error, "~ts:~p: warning: using % for sigils is deprecated, "
"please use ~~ instead~n", [Scope#elixir_tokenizer.file, Line]),
case extract_heredoc_with_interpolation(Line, Scope, ?is_downcase(S), T, H) of
{ error, Reason } ->
{ error, Reason, Original, Tokens };
{ Parts, Rest } ->
{ ok, NewLine, Parts, Rest } ->
{ Final, Modifiers } = collect_modifiers(Rest, []),
tokenize(Final, Line, Scope, [{ sigil, Line, S, Parts, Modifiers }|Tokens])
tokenize(Final, NewLine, Scope, [{ sigil, Line, S, Parts, Modifiers }|Tokens]);
{ error, Reason } ->
{ error, Reason, Original, Tokens }
end;
tokenize([$%,S,H|T] = Original, Line, Scope, Tokens) when not(?is_identifier(H)), not(?is_terminator(H)), ?is_upcase(S) orelse ?is_downcase(S) ->
tokenize([$%,S,H|T] = Original, Line, Scope, Tokens) when ?is_sigil(H), ?is_upcase(S) orelse ?is_downcase(S) ->
io:format(standard_error, "~ts:~p: warning: using % for sigils is deprecated, "
"please use ~~ instead~n", [Scope#elixir_tokenizer.file, Line]),
case elixir_interpolation:extract(Line, Scope, ?is_downcase(S), T, sigil_terminator(H)) of
{ NewLine, Parts, Rest } ->
{ Final, Modifiers } = collect_modifiers(Rest, []),
@@ -158,6 +162,25 @@ tokenize([$%,S,H|T] = Original, Line, Scope, Tokens) when not(?is_identifier(H))
interpolation_error(Reason, Original, Tokens, " (for sigil ~ts starting at line ~B)", [Sigil, Line])
end;
tokenize([$~,S,H,H,H|T] = Original, Line, Scope, Tokens) when ?is_quote(H), ?is_upcase(S) orelse ?is_downcase(S) ->
case extract_heredoc_with_interpolation(Line, Scope, ?is_downcase(S), T, H) of
{ ok, NewLine, Parts, Rest } ->
{ Final, Modifiers } = collect_modifiers(Rest, []),
tokenize(Final, NewLine, Scope, [{ sigil, Line, S, Parts, Modifiers }|Tokens]);
{ error, Reason } ->
{ error, Reason, Original, Tokens }
end;
tokenize([$~,S,H|T] = Original, Line, Scope, Tokens) when ?is_sigil(H), ?is_upcase(S) orelse ?is_downcase(S) ->
case elixir_interpolation:extract(Line, Scope, ?is_downcase(S), T, sigil_terminator(H)) of
{ NewLine, Parts, Rest } ->
{ Final, Modifiers } = collect_modifiers(Rest, []),
tokenize(Final, NewLine, Scope, [{ sigil, Line, S, Parts, Modifiers }|Tokens]);
{ error, Reason } ->
Sigil = [$~,S,H],
interpolation_error(Reason, Original, Tokens, " (for sigil ~ts starting at line ~B)", [Sigil, Line])
end;
% Char tokens
tokenize([$?,$\\,P,${,A,B,C,D,E,F,$}|T], Line, Scope, Tokens) when (P == $x orelse P == $X), ?is_hex(A), ?is_hex(B), ?is_hex(C), ?is_hex(D), ?is_hex(E), ?is_hex(F) ->
@@ -211,56 +234,6 @@ tokenize([$?,$\\,H|T], Line, Scope, Tokens) ->
tokenize([$?,Char|T], Line, Scope, Tokens) ->
tokenize(T, Line, Scope, [{ number, Line, Char }|Tokens]);
% Dot identifier/operators
tokenize("..." ++ Rest, Line, Scope, Tokens) ->
Token = check_call_identifier(identifier, Line, '...', Rest),
tokenize(Rest, Line, Scope, [Token|Tokens]);
tokenize([$.,T|Tail], Line, Scope, Tokens) when ?is_space(T) ->
case [T|Tail] of
[$\r,$\n|Rest] -> tokenize([$.|Rest], Line + 1, Scope, Tokens);
[$\n|Rest] -> tokenize([$.|Rest], Line + 1, Scope, Tokens);
[_|Rest] -> tokenize([$.|Rest], Line, Scope, Tokens)
end;
% ## Three Token Operators
tokenize([$.,T1,T2,T3|Rest], Line, Scope, Tokens) when
?unary_op3(T1, T2, T3); ?comp_op3(T1, T2, T3); ?and_op3(T1, T2, T3); ?or_op3(T1, T2, T3);
?arrow_op3(T1, T2, T3); ?exp_op3(T1, T2, T3) ->
handle_call_identifier(Rest, Line, list_to_atom([T1, T2, T3]), Scope, Tokens);
% ## Two Token Operators
tokenize([$.,T1,T2|Rest], Line, Scope, Tokens) when
?comp_op2(T1, T2); ?and_op(T1, T2); ?or_op(T1, T2); ?arrow_op(T1, T2);
?in_match_op2(T1, T2); ?two_op(T1, T2); ?stab_op(T1, T2); ?type_op(T1, T2) ->
handle_call_identifier(Rest, Line, list_to_atom([T1, T2]), Scope, Tokens);
% ## Single Token Operators
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); ?in_match_op(T) ->
handle_call_identifier(Rest, Line, list_to_atom([T]), Scope, Tokens);
% Dot call
% ## Exception for .( as it needs to be treated specially in the parser
tokenize([$.,$(|Rest], Line, Scope, Tokens) ->
tokenize([$(|Rest], Line, Scope, add_token_with_nl({ dot_call_op, Line, '.' }, Tokens));
tokenize([$.,H|T] = Original, Line, Scope, Tokens) when ?is_quote(H) ->
case elixir_interpolation:extract(Line, Scope, true, T, H) of
{ NewLine, [Part], Rest } when is_binary(Part) ->
case unsafe_to_atom(Part, Line, Scope) of
{ ok, Atom } ->
Token = check_call_identifier(identifier, Line, Atom, Rest),
tokenize(Rest, NewLine, Scope, [Token|add_token_with_nl({ '.', Line }, Tokens)]);
{ error, Reason } ->
{ error, Reason, Original, Tokens }
end;
{ error, Reason } ->
interpolation_error(Reason, Original, Tokens, " (for function name starting at line ~B)", [Line])
end;
% Heredocs
tokenize("\"\"\"" ++ T, Line, Scope, Tokens) ->
@@ -318,13 +291,13 @@ tokenize([$:,T1,T2,T3|Rest], Line, Scope, Tokens) when
% ## Two Token Operators
tokenize([$:,T1,T2|Rest], Line, Scope, Tokens) when
?comp_op2(T1, T2); ?and_op(T1, T2); ?or_op(T1, T2); ?arrow_op(T1, T2);
?in_match_op2(T1, T2); ?two_op(T1, T2); ?stab_op(T1, T2); ?type_op(T1, T2) ->
?in_match_op(T1, T2); ?two_op(T1, T2); ?stab_op(T1, T2) ->
tokenize(Rest, Line, Scope, [{ atom, Line, list_to_atom([T1,T2]) }|Tokens]);
% ## 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); ?in_match_op(T); T == $. ->
?match_op(T); ?pipe_op(T); T == $. ->
tokenize(Rest, Line, Scope, [{ atom, Line, list_to_atom([T]) }|Tokens]);
% End of line
@@ -349,9 +322,13 @@ tokenize("\r\n" ++ Rest, Line, Scope, Tokens) ->
% Stand-alone tokens
tokenize("..." ++ Rest, Line, Scope, Tokens) ->
Token = check_call_identifier(identifier, Line, '...', Rest),
tokenize(Rest, Line, Scope, [Token|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);
handle_unary_op(Rest, Line, unary_op, list_to_atom([T1,T2,T3]), Scope, Tokens);
tokenize([T1,T2,T3|Rest], Line, Scope, Tokens) when ?comp_op3(T1, T2, T3) ->
handle_op(Rest, Line, comp_op, list_to_atom([T1,T2,T3]), Scope, Tokens);
@@ -394,32 +371,25 @@ tokenize([T1,T2|Rest], Line, Scope, Tokens) when ?and_op(T1, T2) ->
tokenize([T1,T2|Rest], Line, Scope, Tokens) when ?or_op(T1, T2) ->
handle_op(Rest, Line, or_op, list_to_atom([T1, T2]), Scope, Tokens);
tokenize([T1,T2|Rest], Line, Scope, Tokens) when ?in_match_op2(T1, T2) ->
tokenize([T1,T2|Rest], Line, Scope, Tokens) when ?in_match_op(T1, T2) ->
handle_op(Rest, Line, in_match_op, list_to_atom([T1, T2]), Scope, Tokens);
tokenize([T1,T2|Rest], Line, Scope, Tokens) when ?stab_op(T1, T2) ->
handle_op(Rest, Line, stab_op, list_to_atom([T1, T2]), Scope, Tokens);
tokenize([T1,T2|Rest], Line, Scope, Tokens) when ?type_op(T1, T2) ->
handle_op(Rest, Line, type_op, list_to_atom([T1, T2]), Scope, Tokens);
% ## 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);
handle_unary_op(Rest, Line, at_op, list_to_atom([T]), Scope, Tokens);
tokenize([T|Rest], Line, Scope, Tokens) when ?unary_op(T) ->
handle_nonl_op(Rest, Line, unary_op, list_to_atom([T]), Scope, Tokens);
handle_unary_op(Rest, Line, unary_op, list_to_atom([T]), Scope, Tokens);
tokenize([T|Rest], Line, Scope, Tokens) when ?comp_op(T) ->
handle_op(Rest, Line, comp_op, list_to_atom([T]), Scope, Tokens);
tokenize([T|Rest], Line, Scope, Tokens) when ?dual_op(T) ->
handle_nonl_op(Rest, Line, dual_op, list_to_atom([T]), Scope, Tokens);
handle_unary_op(Rest, Line, dual_op, list_to_atom([T]), Scope, Tokens);
tokenize([T|Rest], Line, Scope, Tokens) when ?mult_op(T) ->
handle_op(Rest, Line, mult_op, list_to_atom([T]), Scope, Tokens);
@@ -427,11 +397,14 @@ tokenize([T|Rest], Line, Scope, Tokens) when ?mult_op(T) ->
tokenize([T|Rest], Line, Scope, Tokens) when ?match_op(T) ->
handle_op(Rest, Line, match_op, list_to_atom([T]), Scope, Tokens);
tokenize([T|Rest], Line, Scope, Tokens) when ?in_match_op(T) ->
handle_op(Rest, Line, in_match_op, list_to_atom([T]), Scope, Tokens);
tokenize([T|Rest], Line, Scope, Tokens) when ?pipe_op(T) ->
handle_op(Rest, Line, pipe_op, list_to_atom([T]), Scope, Tokens);
tokenize([$.|Rest], Line, Scope, Tokens) ->
tokenize(Rest, Line, Scope, add_token_with_nl({ '.', Line }, Tokens));
% Dot
tokenize([$.|T], Line, Scope, Tokens) ->
{ Rest, Counter } = strip_space(T, 0),
handle_dot([$.|Rest], Line + Counter, Scope, Tokens);
% Integers and floats
@@ -481,15 +454,22 @@ tokenize([Space, Sign, NotMarker|T], Line, Scope, [{ Identifier, _, _ } = H|Toke
% Spaces
tokenize([T|Rest], Line, Scope, Tokens) when ?is_horizontal_space(T) ->
tokenize(Rest, Line, Scope, Tokens);
%% TODO: This token is deprecated once continuable heredocs are removed
tokenize([{line,Line}|Rest], _Line, Scope, Tokens) ->
tokenize(Rest, Line, Scope, Tokens);
tokenize(strip_horizontal_space(Rest), Line, Scope, Tokens);
tokenize(T, Line, _Scope, Tokens) ->
{ error, { Line, "invalid token: ", until_eol(T) }, T, Tokens }.
strip_horizontal_space([H|T]) when ?is_horizontal_space(H) ->
strip_horizontal_space(T);
strip_horizontal_space(T) ->
T.
strip_space(T, Counter) ->
case strip_horizontal_space(T) of
"\r\n" ++ Rest -> strip_space(Rest, Counter + 1);
"\n" ++ Rest -> strip_space(Rest, Counter + 1);
Rest -> { Rest, Counter }
end.
until_eol("\r\n" ++ _) -> [];
until_eol("\n" ++ _) -> [];
until_eol([]) -> [];
@@ -503,11 +483,11 @@ escape_char(List) ->
handle_heredocs(T, Line, H, Scope, Tokens) ->
case extract_heredoc_with_interpolation(Line, Scope, true, T, H) of
{ error, Reason } ->
{ error, Reason, [H, H, H] ++ T, Tokens };
{ Parts, Rest } ->
{ ok, NewLine, Parts, Rest } ->
Token = { string_type(H), Line, unescape_tokens(Parts) },
tokenize(Rest, Line, Scope, [Token|Tokens])
tokenize(Rest, NewLine, Scope, [Token|Tokens]);
{ error, Reason } ->
{ error, Reason, [H, H, H] ++ T, Tokens }
end.
handle_strings(T, Line, H, Scope, Tokens) ->
@@ -531,17 +511,62 @@ handle_strings(T, Line, H, Scope, Tokens) ->
tokenize(Rest, NewLine, Scope, [Token|Tokens])
end.
handle_nonl_op([$:|Rest], Line, _Kind, Op, Scope, Tokens) when ?is_space(hd(Rest)) ->
handle_unary_op([$:|Rest], Line, _Kind, Op, Scope, Tokens) when ?is_space(hd(Rest)) ->
tokenize(Rest, Line, Scope, [{ kw_identifier, Line, Op }|Tokens]);
handle_nonl_op(Rest, Line, Kind, Op, Scope, Tokens) ->
tokenize(Rest, Line, Scope, [{ Kind, Line, Op }|Tokens]).
handle_unary_op(Rest, Line, Kind, Op, Scope, Tokens) ->
case strip_space(Rest, 0) of
{ [$/|_], _ } -> tokenize(Rest, Line, Scope, [{ identifier, Line, Op }|Tokens]);
_ -> tokenize(Rest, Line, Scope, [{ Kind, Line, Op }|Tokens])
end.
handle_op([$:|Rest], Line, _Kind, Op, Scope, Tokens) when ?is_space(hd(Rest)) ->
tokenize(Rest, Line, Scope, [{ kw_identifier, Line, Op }|Tokens]);
handle_op(Rest, Line, Kind, Op, Scope, Tokens) ->
tokenize(Rest, Line, Scope, add_token_with_nl({ Kind, Line, Op }, Tokens)).
case strip_space(Rest, 0) of
{ [$/|_], _ } -> tokenize(Rest, Line, Scope, [{ identifier, Line, Op }|Tokens]);
_ -> tokenize(Rest, Line, Scope, add_token_with_nl({ Kind, Line, Op }, Tokens))
end.
% ## Three Token Operators
handle_dot([$.,T1,T2,T3|Rest], Line, Scope, Tokens) when
?unary_op3(T1, T2, T3); ?comp_op3(T1, T2, T3); ?and_op3(T1, T2, T3); ?or_op3(T1, T2, T3);
?arrow_op3(T1, T2, T3); ?exp_op3(T1, T2, T3) ->
handle_call_identifier(Rest, Line, list_to_atom([T1, T2, T3]), Scope, Tokens);
% ## Two Token Operators
handle_dot([$.,T1,T2|Rest], Line, Scope, Tokens) when
?comp_op2(T1, T2); ?and_op(T1, T2); ?or_op(T1, T2); ?arrow_op(T1, T2);
?in_match_op(T1, T2); ?two_op(T1, T2); ?stab_op(T1, T2) ->
handle_call_identifier(Rest, Line, list_to_atom([T1, T2]), Scope, Tokens);
% ## Single Token Operators
handle_dot([$.,T|Rest], Line, Scope, Tokens) when
?at_op(T); ?unary_op(T); ?dual_op(T); ?mult_op(T); ?comp_op(T);
?match_op(T); ?pipe_op(T) ->
handle_call_identifier(Rest, Line, list_to_atom([T]), Scope, Tokens);
% ## Exception for .( as it needs to be treated specially in the parser
handle_dot([$.,$(|Rest], Line, Scope, Tokens) ->
tokenize([$(|Rest], Line, Scope, add_token_with_nl({ dot_call_op, Line, '.' }, Tokens));
handle_dot([$.,H|T] = Original, Line, Scope, Tokens) when ?is_quote(H) ->
case elixir_interpolation:extract(Line, Scope, true, T, H) of
{ NewLine, [Part], Rest } when is_binary(Part) ->
case unsafe_to_atom(Part, Line, Scope) of
{ ok, Atom } ->
Token = check_call_identifier(identifier, Line, Atom, Rest),
tokenize(Rest, NewLine, Scope, [Token|add_token_with_nl({ '.', Line }, Tokens)]);
{ error, Reason } ->
{ error, Reason, Original, Tokens }
end;
{ error, Reason } ->
interpolation_error(Reason, Original, Tokens, " (for function name starting at line ~B)", [Line])
end;
handle_dot([$.|Rest], Line, Scope, Tokens) ->
tokenize(Rest, Line, Scope, add_token_with_nl({ '.', Line }, Tokens)).
handle_call_identifier(Rest, Line, Op, Scope, Tokens) ->
Token = check_call_identifier(identifier, Line, Op, Rest),
@@ -575,34 +600,33 @@ collect_modifiers(Rest, Buffer) ->
%% Heredocs
extract_heredoc_with_interpolation(Line, Scope, Interpol, T, H) ->
case extract_heredoc(Line, T, H, Scope) of
{ error, _ } = Error ->
Error;
{ Body, Rest } ->
case extract_heredoc(Line, T, H) of
{ ok, NewLine, Body, Rest } ->
case elixir_interpolation:extract(Line + 1, Scope, Interpol, Body, 0) of
{ error, Reason } ->
{ error, interpolation_format(Reason, " (for heredoc starting at line ~B)", [Line]) };
{ _, Parts, [] } ->
{ Parts, Rest }
end
{ ok, NewLine, Parts, Rest }
end;
{ error, _ } = Error ->
Error
end.
extract_heredoc(Line0, Rest0, Marker, Scope) ->
case extract_heredoc_header(Rest0, [], {Line0,Scope#elixir_tokenizer.file}) of
{ ok, Extra, Rest1 } ->
extract_heredoc(Line0, Rest0, Marker) ->
case extract_heredoc_header(Rest0) of
{ ok, Rest1 } ->
%% We prepend a new line so we can transparently remove
%% spaces later. This new line is removed by calling `tl`
%% in the final heredoc body three lines below.
case extract_heredoc_body(Line0, Marker, [$\n|Rest1], []) of
{ Line1, Body, Rest2, Spaces } ->
{ tl(remove_heredoc_spaces(Body, Spaces)), merge_heredoc_extra(Line1, Extra, Rest2) };
{ ok, Line1, Body, Rest2, Spaces } ->
{ ok, Line1, tl(remove_heredoc_spaces(Body, Spaces)), Rest2 };
{ error, ErrorLine } ->
Terminator = [Marker, Marker, Marker],
Message = "missing terminator: ~ts (for heredoc starting at line ~B)",
{ error, { ErrorLine, io_lib:format(Message, [Terminator, Line0]), [] } }
end;
error ->
%% TODO: Test me once the deprecated continuable heredocs are removed
Terminator = [Marker, Marker, Marker],
Message = "heredoc start ~ts must be followed by a new line",
{ error, { Line0, io_lib:format(Message, [Terminator]), [] } }
@@ -625,32 +649,25 @@ remove_heredoc_spaces([], Buffer, _Spaces, _Original) ->
%% Extract the heredoc header.
extract_heredoc_header("\r\n" ++ Rest, Buffer, _Scope) ->
{ ok, [$\n|Buffer], Rest };
extract_heredoc_header("\n" ++ Rest, Buffer, _Scope) ->
{ ok, [$\n|Buffer], Rest };
extract_heredoc_header([H|T], Buffer, {Line,File}) when not ?is_horizontal_space(H) ->
elixir_errors:deprecation([{line,Line}], File, "continuable heredocs are deprecated, parsing will no longer continue on the same line as the heredoc starts"),
extract_heredoc_header(T, [H|Buffer], nil);
extract_heredoc_header([H|T], Buffer, _Scope) ->
extract_heredoc_header(T, [H|Buffer], _Scope);
extract_heredoc_header(_, _, _Scope) ->
extract_heredoc_header("\r\n" ++ Rest) ->
{ ok, Rest };
extract_heredoc_header("\n" ++ Rest) ->
{ ok, Rest };
extract_heredoc_header([_|T]) ->
extract_heredoc_header(T);
extract_heredoc_header(_) ->
error.
%% Extract heredoc body. It returns the heredoc body (in reverse order),
%% the remaining of the document and the number of spaces the heredoc
%% is aligned.
%% TODO: This token is deprecated once continuable heredocs are removed
extract_heredoc_body(_Line, Marker, [{line,NewLine}|Rest], Buffer) ->
extract_heredoc_body(NewLine, Marker, Rest, Buffer);
extract_heredoc_body(Line, Marker, Rest, Buffer) ->
case extract_heredoc_line(Marker, Rest, Buffer, 0) of
{ ok, NewBuffer, NewRest } ->
extract_heredoc_body(Line + 1, Marker, NewRest, NewBuffer);
{ ok, NewBuffer, NewRest, Spaces } ->
{ Line, NewBuffer, NewRest, Spaces };
{ ok, Line, NewBuffer, NewRest, Spaces };
{ error, eof } ->
{ error, Line }
end.
@@ -675,17 +692,6 @@ extract_heredoc_line(Marker, [Marker,Marker,Marker|T], Buffer, Counter) ->
extract_heredoc_line(_Marker, Rest, Buffer, _Counter) ->
extract_heredoc_line(Rest, Buffer).
%% Merge heredoc extra by replying it on the buffer. It also adds
%% a special { line, Line } token to force a line change along the way.
%% TODO: This token is deprecated once continuable heredocs are removed
merge_heredoc_extra(Line, Extra, Buffer) ->
merge_heredoc_extra(Extra, [{line,Line}|Buffer]).
merge_heredoc_extra([H|T], Buffer) ->
merge_heredoc_extra(T, [H|Buffer]);
merge_heredoc_extra([], Buffer) ->
Buffer.
%% Integers and floats
%% At this point, we are at least sure the first digit is a number.
+43 -13
View File
@@ -86,14 +86,19 @@ translate({'try', Meta, [Clauses]}, RS) when is_list(Clauses) ->
Catch = [Tuple || { X, _ } = Tuple <- Clauses, X == 'rescue' orelse X == 'catch'],
{ TCatch, SC } = elixir_try:clauses(Meta, Catch, mergec(S, SB)),
After = proplists:get_value('after', Clauses, nil),
{ TAfter, SA } = translate(After, mergec(S, SC)),
case lists:keyfind('after', 1, Clauses) of
{ 'after', After } ->
{ TBlock, SA } = translate(After, mergec(S, SC)),
TAfter = unblock(TBlock);
false ->
{ TAfter, SA } = { [], mergec(S, SC) }
end,
Else = elixir_clauses:get_pairs(else, Clauses),
{ TElse, SE } = elixir_clauses:clauses(Meta, Else, mergec(S, SA)),
SF = (mergec(S, SE))#elixir_scope{noname=RS#elixir_scope.noname},
{ { 'try', ?line(Meta), unblock(TDo), TElse, TCatch, unblock(TAfter) }, SF };
{ { 'try', ?line(Meta), unblock(TDo), TElse, TCatch, TAfter }, SF };
%% Receive
@@ -144,6 +149,8 @@ translate({ '^', Meta, [ { Name, VarMeta, Kind } = Var ] },
Tuple = { Name, var_kind(VarMeta, Kind) },
case orddict:find(Tuple, S#elixir_scope.backup_vars) of
{ ok, { Value, _Counter } } ->
elixir_errors:deprecation(Meta, S#elixir_scope.file, "using ^ inside function clause heads is "
"deprecated, please use a guard instead"),
Line = ?line(Meta),
{ TVar, TS } = translate(Var, S),
Guard = { op, Line, '=:=', { var, ?line(Meta), Value }, TVar },
@@ -198,17 +205,23 @@ translate({ Name, Meta, Args }, S) when is_atom(Name), is_list(Meta), is_list(Ar
translate({ { '.', _, [Left, Right] }, Meta, Args }, S)
when (is_tuple(Left) orelse is_atom(Left)), is_atom(Right), is_list(Meta), is_list(Args) ->
{ TLeft, SL } = translate(Left, S),
{ TRight, SR } = translate(Right, mergec(S, SL)),
{ TLeft, SL } = translate(Left, S),
{ TArgs, SA } = translate_args(Args, mergec(S, SL)),
case S#elixir_scope.context of
Context when Left /= erlang, (Context == match) orelse (Context == guard) ->
compile_error(Meta, S#elixir_scope.file, "cannot invoke remote function ~ts.~ts/~B inside ~ts",
['Elixir.Macro':to_string(Left), Right, length(Args), Context]);
_ ->
Line = ?line(Meta),
{ TArgs, SA } = translate_args(Args, mergec(S, SR)),
{ { call, Line, { remote, Line, TLeft, TRight }, TArgs }, mergev(SL, mergev(SR, SA)) }
Line = ?line(Meta),
Arity = length(Args),
%% We need to rewrite erlang function calls as operators
%% because erl_eval chokes on them. We can remove this
%% once a fix is merged into Erlang, keeping only the
%% list operators one (since it is required for inlining
%% [1,2,3] ++ Right in matches).
case (Left == erlang) andalso erl_op(Right, Arity) of
true ->
{ list_to_tuple([op, Line, Right] ++ TArgs), mergev(SL, SA) };
false ->
assert_allowed_in_context(Meta, Left, Right, Arity, S),
{ { call, Line, { remote, Line, TLeft, { atom, 0, Right } }, TArgs }, mergev(SL, SA) }
end;
%% Anonymous function calls
@@ -236,6 +249,12 @@ translate(Other, S) ->
%% Helpers
erl_op(Op, Arity) ->
erl_internal:list_op(Op, Arity) orelse
erl_internal:comp_op(Op, Arity) orelse
erl_internal:bool_op(Op, Arity) orelse
erl_internal:arith_op(Op, Arity).
translate_list([{ '|', _, [_, _]=Args}], Fun, Acc, List) ->
{ [TLeft,TRight], TAcc } = lists:mapfoldl(Fun, Acc, Args),
{ build_list([TLeft|List], TRight), TAcc };
@@ -334,3 +353,14 @@ assert_module_scope(_Meta, _Kind, #elixir_scope{module=Module}) -> Module.
assert_function_scope(Meta, Kind, #elixir_scope{function=nil,file=File}) ->
compile_error(Meta, File, "cannot invoke ~ts outside function", [Kind]);
assert_function_scope(_Meta, _Kind, #elixir_scope{function=Function}) -> Function.
assert_allowed_in_context(Meta, Left, Right, Arity, #elixir_scope{context=Context} = S)
when (Context == match) orelse (Context == guard) ->
case (Left == erlang) andalso erl_internal:guard_bif(Right, Arity) of
true -> ok;
false ->
compile_error(Meta, S#elixir_scope.file, "cannot invoke remote function ~ts.~ts/~B inside ~ts",
['Elixir.Macro':to_string(Left), Right, Arity, Context])
end;
assert_allowed_in_context(_, _, _, _, _) ->
ok.
+1
View File
@@ -22,6 +22,7 @@ defmodule KernelTest do
doctest OptionParser
doctest Path
doctest Protocol.Consolidation
doctest Range
doctest Regex
doctest Set
doctest Stream
+2 -2
View File
@@ -58,14 +58,14 @@ defmodule BehaviourTest do
assert_raise ArgumentError, fn ->
defmodule WithDefault do
use Behaviour
defcallback hello(num // 0 :: integer) :: integer
defcallback hello(num \\ 0 :: integer) :: integer
end
end
assert_raise ArgumentError, fn ->
defmodule WithDefault do
use Behaviour
defcallback hello(num :: integer // 0) :: integer
defcallback hello(num :: integer \\ 0) :: integer
end
end
end
+243 -200
View File
@@ -1,20 +1,26 @@
Code.require_file "test_helper.exs", __DIR__
# A TestDict implementation used only for testing.
defmodule TestDict do
def new(list \\ []) when is_list(list) do
{ TestDict, list }
end
def reduce({ TestDict, list }, acc, fun) do
Enumerable.reduce(list, acc, fun)
end
def size({ TestDict, list }) do
length(list)
end
end
defmodule DictTest.Common do
defmacro __using__(_) do
quote location: :keep do
# Most underlying Dict implementations have no key
# order guarantees, sort them before we compare:
defmacrop dicts_equal(actual, expected) do
quote do
cmp = fn { k1, _ }, { k2, _ } -> k1 < k2 end
Enum.sort(unquote(expected), cmp) == Enum.sort(unquote(actual), cmp)
end
end
import Enum, only: [sort: 1]
defp empty_dict, do: dict_impl.new
defp new_dict(list // [{"first_key", 1}, {"second_key", 2}]) do
defp new_dict(list \\ [{"first_key", 1}, {"second_key", 2}]) do
dict_impl.new list
end
@@ -26,284 +32,304 @@ defmodule DictTest.Common do
dict_impl.new [{1,1}]
end
test :access do
dict = new_dict [{"first_key", 1}, {"second_key", 2}]
test "access" do
dict = new_dict()
assert dict["first_key"] == 1
assert dict["third_key"] == nil
assert dict["other_key"] == nil
end
test :access_with_match do
assert int_dict[1] == 1
assert int_dict[1.0] == nil
test "access uses match operation" do
dict = int_dict()
assert dict[1] == 1
assert dict[1.0] == nil
end
test :new_pairs do
dict = new_dict [{"first_key", 1}, {"second_key", 2}]
assert 2 == Dict.size dict
assert ["first_key", "second_key"] == Enum.sort Dict.keys dict
assert [1, 2] == Enum.sort Dict.values dict
test "new/1" do
dict = new_dict()
assert Dict.size(dict) == 2
assert Enum.sort(dict) == [{"first_key", 1}, {"second_key", 2}]
end
test :new_pairs_with_transform do
dict = new_dict [{1}, {2}, {3}], fn {x} -> { <<x + 64>>, x } end
assert 3 == Dict.size dict
assert ["A", "B", "C"] == Enum.sort Dict.keys dict
assert [1, 2, 3] == Enum.sort Dict.values dict
test "new/2" do
dict = new_dict([{1}, {2}, {3}], fn {x} -> { <<x + 64>>, x } end)
assert Dict.size(dict) == 3
assert Enum.sort(dict) == [{"A", 1}, {"B", 2}, {"C", 3}]
end
test :get do
assert 1 == Dict.get(new_dict, "first_key")
assert 2 == Dict.get(new_dict, "second_key")
assert nil == Dict.get(new_dict, "other_key")
assert "default" == Dict.get(empty_dict, "first_key", "default")
test "get/2 and get/3" do
dict = new_dict()
assert Dict.get(dict, "first_key") == 1
assert Dict.get(dict, "second_key") == 2
assert Dict.get(dict, "other_key") == nil
assert Dict.get(dict, "other_key", 3) == 3
end
test :get_with_match do
test "get/2 with match" do
assert Dict.get(int_dict, 1) == 1
assert Dict.get(int_dict, 1.0) == nil
end
test :fetch do
assert { :ok, 1 } == Dict.fetch(new_dict, "first_key")
assert { :ok, 2 } == Dict.fetch(new_dict, "second_key")
assert :error == Dict.fetch(new_dict, "other_key")
test "fetch/2" do
dict = new_dict()
assert Dict.fetch(dict, "first_key") == {:ok, 1}
assert Dict.fetch(dict, "second_key") == {:ok, 2}
assert Dict.fetch(dict, "other_key") == :error
end
test :fetch_with_match do
assert Dict.fetch(int_dict, 1) == { :ok, 1 }
test "fetch/2 with match" do
assert Dict.fetch(int_dict, 1) == {:ok, 1}
assert Dict.fetch(int_dict, 1.0) == :error
end
test :fetch! do
assert 1 == Dict.fetch!(new_dict, "first_key")
assert 2 == Dict.fetch!(new_dict, "second_key")
test "fetch!/2" do
dict = new_dict()
assert Dict.fetch!(dict, "first_key") == 1
assert Dict.fetch!(dict, "second_key") == 2
assert_raise KeyError, fn ->
Dict.fetch!(new_dict, "other_key")
Dict.fetch!(dict, "other_key")
end
end
test :fetch_with_match! do
assert Dict.fetch!(int_dict, 1) == 1
assert_raise KeyError, fn ->
assert Dict.fetch!(int_dict, 1.0)
end
test "put/3" do
dict = new_dict() |> Dict.put("first_key", {1})
assert Dict.get(dict, "first_key") == {1}
assert Dict.get(dict, "second_key") == 2
end
test :put do
dict = Dict.put(new_dict, "first_key", {1})
assert {1} == Dict.get dict, "first_key"
assert 2 == Dict.get dict, "second_key"
test "put/3 with_match" do
dict = int_dict()
assert Dict.get(Dict.put(dict, 1, :other), 1) == :other
assert Dict.get(Dict.put(dict, 1.0, :other), 1) == 1
assert Dict.get(Dict.put(dict, 1, :other), 1.0) == nil
assert Dict.get(Dict.put(dict, 1.0, :other), 1.0) == :other
end
test :put_with_match do
assert Dict.get(Dict.put(int_dict, 1, :other), 1) == :other
assert Dict.get(Dict.put(int_dict, 1.0, :other), 1) == 1
assert Dict.get(Dict.put(int_dict, 1, :other), 1.0) == nil
assert Dict.get(Dict.put(int_dict, 1.0, :other), 1.0) == :other
test "put_new/3" do
dict = Dict.put_new(new_dict(), "first_key", {1})
assert Dict.get(dict, "first_key") == 1
end
test :put_new do
dict = Dict.put_new(new_dict, "first_key", {1})
assert 1 == Dict.get dict, "first_key"
end
test :put_new_with_match do
test "put_new/3 with_match" do
assert Dict.get(Dict.put_new(int_dict, 1, :other), 1) == 1
assert Dict.get(Dict.put_new(int_dict, 1.0, :other), 1) == 1
assert Dict.get(Dict.put_new(int_dict, 1, :other), 1.0) == nil
assert Dict.get(Dict.put_new(int_dict, 1.0, :other), 1.0) == :other
end
test :keys do
assert Enum.sort(Dict.keys new_dict) == ["first_key", "second_key"]
assert Dict.keys(empty_dict) == []
test "keys/1" do
assert Enum.sort(Dict.keys(new_dict())) == ["first_key", "second_key"]
assert Dict.keys(new_dict([])) == []
end
test :values do
assert Enum.sort(Dict.values(new_dict)) == [1, 2]
assert Dict.values(empty_dict) == []
test "values/1" do
assert Enum.sort(Dict.values(new_dict())) == [1, 2]
assert Dict.values(new_dict([])) == []
end
test :delete do
mdict = Dict.delete new_dict, "second_key"
assert Dict.size(mdict) == 1
assert Dict.has_key? mdict, "first_key"
refute Dict.has_key? mdict, "second_key"
test "delete/2" do
dict = Dict.delete(new_dict(), "second_key")
assert Dict.size(dict) == 1
assert Dict.has_key?(dict, "first_key")
refute Dict.has_key?(dict, "second_key")
mdict = Dict.delete(new_dict, "other_key")
assert mdict == new_dict
assert Dict.size(Dict.delete(empty_dict, "other_key")) == 0
dict = Dict.delete(new_dict(), "other_key")
assert dict == new_dict()
assert Dict.size(dict) == 2
end
test :delete_with_match do
test "delete/2 with match" do
assert Dict.get(Dict.delete(int_dict, 1), 1) == nil
assert Dict.get(Dict.delete(int_dict, 1.0), 1) == 1
end
test :merge do
dict = new_dict
assert dict == Dict.merge empty_dict, dict
assert dict == Dict.merge dict, empty_dict
assert dict == Dict.merge dict, dict
assert empty_dict == Dict.merge empty_dict, empty_dict
test "merge/2" do
dict = new_dict()
assert Dict.merge(new_dict([]), dict) == dict
assert Dict.merge(dict, new_dict([])) == dict
assert Dict.merge(dict, dict) == dict
assert Dict.merge(new_dict([]), new_dict([])) == new_dict([])
dict1 = new_dict Enum.zip ["a", "b", "c"], [1, 2, 3]
dict2 = new_dict Enum.zip ["a", "c", "d"], [3, :a, 0]
actual = Dict.merge(dict1, dict2)
expected = new_dict Enum.zip ["a", "b", "c", "d"], [3, 2, :a, 0]
assert dicts_equal actual, expected
dict1 = new_dict [{"a", 1}, {"b", 2}, {"c", 3}]
dict2 = new_dict [{"a", 3}, {"c", :a}, {"d", 0}]
assert Dict.merge(dict1, dict2) |> Enum.sort ==
[{"a", 3}, {"b", 2}, {"c", :a}, {"d", 0}]
end
test :merge_with_enum do
dict1 = new_dict Enum.zip ["a", "b", "c"], [1, 2, 3]
dict2 = Enum.zip ["a", "c", "d"], [3, :a, 0]
test "merge/2 with other dict" do
dict1 = new_dict [{"a", 1}, {"b", 2}, {"c", 3}]
dict2 = TestDict.new [{"a",3}, {"c",:a}, {"d",0}]
actual = Dict.merge(dict1, dict2)
expected = new_dict(Enum.zip ["a", "b", "c", "d"], [3, 2, :a, 0])
assert dicts_equal actual, expected
assert Dict.merge(dict1, dict2) |> Enum.sort ==
[{"a", 3}, {"b", 2}, {"c", :a}, {"d", 0}]
end
test :merge_with_function do
dict1 = new_dict Enum.zip ["a", "b"], [1, 2]
dict2 = new_dict Enum.zip ["a", "d"], [3, 4]
test "merge/3" do
dict1 = new_dict [{"a", 1}, {"b", 2}]
dict2 = new_dict [{"a", 3}, {"d", 4}]
actual = Dict.merge dict1, dict2, fn _k, v1, v2 -> v1 + v2 end
expected = new_dict(Enum.zip ["a", "b", "d"], [4, 2, 4])
assert dicts_equal actual, expected
assert Enum.sort(actual) == [{"a", 4}, {"b", 2}, {"d", 4}]
end
test :has_key do
dict = new_dict [{"a", 1}]
assert Dict.has_key?(dict, "a")
refute Dict.has_key?(dict, "b")
test "has_key?/2" do
dict = new_dict()
assert Dict.has_key?(dict, "first_key")
refute Dict.has_key?(dict, "other_key")
end
test :has_key_with_match do
test "has_key?/2 with match" do
assert Dict.has_key?(int_dict, 1)
refute Dict.has_key?(int_dict, 1.0)
end
test :size do
assert 2 == Dict.size new_dict
assert 0 == Dict.size empty_dict
test "size/1" do
assert Dict.size(new_dict()) == 2
assert Dict.size(new_dict([])) == 0
end
test :update do
dict = Dict.update!(new_dict, "first_key", fn val -> -val end)
test "update!/3" do
dict = Dict.update!(new_dict(), "first_key", fn val -> -val end)
assert Dict.get(dict, "first_key") == -1
dict = Dict.update(dict, "non-existent", "...", fn val -> -val end)
assert_raise KeyError, fn ->
Dict.update!(new_dict(), "non-existent", fn val -> -val end)
end
end
test "update!/3 with match" do
assert Dict.get(Dict.update!(int_dict(), 1, &(&1 + 1)), 1) == 2
end
test "update/4" do
dict = Dict.update(new_dict(), "first_key", 0, fn val -> -val end)
assert Dict.get(dict, "first_key") == -1
dict = Dict.update(new_dict(), "non-existent", "...", fn val -> -val end)
assert Dict.get(dict, "non-existent") == "..."
end
test :update_with_match do
assert Dict.get(Dict.update!(int_dict, 1, &(&1 + 1)), 1) == 2
assert Dict.get(Dict.update(int_dict, 1.0, 2, &(&1 + 1)), 1) == 1
assert Dict.get(Dict.update(int_dict, 1.0, 2, &(&1 + 1)), 1.0) == 2
test "update/4 with match" do
dict = int_dict()
assert Dict.get(Dict.update(dict, 1.0, 2, &(&1 + 1)), 1) == 1
assert Dict.get(Dict.update(dict, 1.0, 2, &(&1 + 1)), 1.0) == 2
end
test :pop do
{v, actual} = Dict.pop(new_dict, "first_key")
assert 1 == v
assert dicts_equal actual, Dict.delete(new_dict, "first_key")
test "pop/2 and pop/3" do
dict = new_dict()
{v, actual} = Dict.pop(new_dict, "second_key")
assert 2 == v
assert dicts_equal actual, Dict.delete(new_dict, "second_key")
{v, actual} = Dict.pop(new_dict, "other_key")
assert nil == v
assert dicts_equal actual, new_dict
{v, actual} = Dict.pop(empty_dict, "first_key", "default")
assert "default" == v
assert dicts_equal actual, empty_dict
{v, actual} = Dict.pop(new_dict, "other_key", "default")
assert "default" == v
assert dicts_equal actual, new_dict
end
test :pop_with_match do
{v, actual} = Dict.pop(int_dict, 1)
{v, actual} = Dict.pop(dict, "first_key")
assert v == 1
assert dicts_equal actual, []
assert actual == new_dict([{"second_key", 2}])
{v, actual} = Dict.pop(int_dict, 1.0)
{v, actual} = Dict.pop(dict, "other_key")
assert v == nil
assert dicts_equal actual, int_dict
assert dict == actual
{v, actual} = Dict.pop(dict, "other_key", "default")
assert v == "default"
assert dict == actual
end
test :split do
split_keys = []
{take, drop} = Dict.split(new_dict, split_keys)
assert dicts_equal take, Dict.empty(new_dict)
assert dicts_equal drop, new_dict
test "pop/2 and pop/3 with match" do
dict = int_dict()
split_keys = ["unknown_key"]
{take, drop} = Dict.split(new_dict, split_keys)
assert dicts_equal take, Dict.empty(new_dict)
assert dicts_equal drop, new_dict
{v, actual} = Dict.pop(dict, 1)
assert v == 1
assert Enum.sort(actual) == []
{v, actual} = Dict.pop(dict, 1.0)
assert v == nil
assert actual == dict
end
test "split/2" do
dict = new_dict()
{take, drop} = Dict.split(dict, [])
assert take == new_dict([])
assert drop == dict
{take, drop} = Dict.split(dict, ["unknown_key"])
assert take == new_dict([])
assert drop == dict
split_keys = ["first_key", "second_key", "unknown_key"]
{take, drop} = Dict.split(new_dict, split_keys)
{take, drop} = Dict.split(dict, split_keys)
first_val = Dict.get(new_dict, "first_key")
second_val = Dict.get(new_dict, "second_key")
take_expected = Dict.empty(new_dict)
take_expected = Dict.put(take_expected, "first_key", first_val)
take_expected = Dict.put(take_expected, "second_key", second_val)
drop_expected = new_dict
drop_expected = Dict.delete(drop_expected, "first_key")
drop_expected = Dict.delete(drop_expected, "second_key")
take_expected = new_dict([])
|> Dict.put("first_key", 1)
|> Dict.put("second_key", 2)
assert dicts_equal take, take_expected
assert dicts_equal drop, drop_expected
drop_expected = new_dict([])
|> Dict.delete("first_key")
|> Dict.delete("second_key")
assert Enum.sort(take) == Enum.sort(take_expected)
assert Enum.sort(drop) == Enum.sort(drop_expected)
end
test :split_with_match do
{ take, drop } = Dict.split(int_dict, [1])
assert dicts_equal take, int_dict
assert dicts_equal drop, []
test "split/2 with match" do
dict = int_dict()
{ take, drop } = Dict.split(dict, [1])
assert take == dict
assert drop == new_dict([])
{ take, drop } = Dict.split(int_dict, [1.0])
assert dicts_equal take, []
assert dicts_equal drop, int_dict
{ take, drop } = Dict.split(dict, [1.0])
assert take == new_dict([])
assert drop == dict
end
test :take do
result = Dict.take(new_dict, ["unknown_key"])
assert dicts_equal result, Dict.empty(new_dict)
result = Dict.take(new_dict, ["first_key"])
first_val = Dict.get(new_dict, "first_key")
expected = Dict.put(Dict.empty(new_dict), "first_key", first_val)
assert dicts_equal result, expected
test "split/2 with enum" do
dict = int_dict()
{ take, drop } = Dict.split(dict, 1..3)
assert take == dict
assert drop == new_dict([])
end
test :take_with_match do
assert dicts_equal Dict.take(int_dict, [1]), int_dict
assert dicts_equal Dict.take(int_dict, [1.0]), []
test "take/2" do
dict = new_dict()
take = Dict.take(dict, ["unknown_key"])
assert take == new_dict([])
take = Dict.take(dict, ["first_key"])
assert take == new_dict([{"first_key", 1}])
end
test :drop do
result = Dict.drop(new_dict, ["unknown_key"])
assert dicts_equal result, new_dict
result = Dict.drop(new_dict, ["first_key"])
assert dicts_equal result, Dict.delete(new_dict, "first_key")
test "take/2 with match" do
dict = int_dict()
assert Dict.take(dict, [1]) == dict
assert Dict.take(dict, [1.0]) == new_dict([])
end
test :drop_with_match do
assert dicts_equal Dict.drop(int_dict, [1]), []
assert dicts_equal Dict.drop(int_dict, [1.0]), int_dict
test "take/2 with enum" do
dict = int_dict()
assert Dict.take(dict, 1..3) == dict
end
test :empty do
assert empty_dict == Dict.empty new_dict
test "drop/2" do
dict = new_dict()
drop = Dict.drop(dict, ["unknown_key"])
assert drop == dict
drop = Dict.drop(dict, ["first_key"])
assert drop == new_dict([{"second_key", 2}])
end
test :equal? do
test "drop/2 with match" do
dict = int_dict()
assert Dict.drop(dict, [1]) == new_dict([])
assert Dict.drop(dict, [1.0]) == dict
end
test "drop/2 with enum" do
dict = int_dict()
assert Dict.drop(dict, 1..3) == new_dict([])
end
test "empty" do
assert Dict.empty(new_dict) == new_dict([])
end
test "equal?/2" do
dict1 = new_dict(a: 2, b: 3, f: 5, c: 123)
dict2 = new_dict(a: 2, b: 3, f: 5, c: 123)
assert dict_impl.equal?(dict1, dict2)
@@ -318,20 +344,37 @@ defmodule DictTest.Common do
refute Dict.equal?(dict3, dict1)
end
test :equal_with_match? do
test "equal?/2 with match" do
dict1 = new_dict([{1,1}])
dict2 = new_dict([{1.0,1}])
assert dict_impl.equal?(dict1, dict1)
refute dict_impl.equal?(dict1, dict2)
assert Dict.equal?(dict1, dict1)
refute Dict.equal?(dict1, dict2)
end
test :enum_member do
assert Enum.member?(new_dict([{1,1}]), { 1, 1 })
refute Enum.member?(new_dict([{1,1}]), { 1.0, 1 })
refute Enum.member?(new_dict([{1,1}]), { 1, 1.0 })
refute Enum.member?(new_dict([{1,1}]), { 1.0, 1.0 })
test "equal?/2 with other dict" do
dict = new_dict([{1,1}])
assert Dict.equal?(dict, TestDict.new([{1,1}]))
refute Dict.equal?(dict, TestDict.new([{1.0,1}]))
end
test "implements Enumerable" do
dict = new_dict()
assert Enum.empty?(new_dict([]))
refute Enum.empty?(dict)
assert Enum.member?(dict, { "first_key", 1 })
refute Enum.member?(dict, { "first_key", 2 })
assert Enum.count(dict) == 2
assert Enum.reduce(dict, 0, fn({ k, v }, acc) -> v + acc end) == 3
end
test "is zippable" do
dict = new_dict()
list = Dict.to_list(dict)
assert Enum.zip(list, list) == Enum.zip(dict, dict)
dict = new_dict(1..120, fn i -> { i, i } end)
list = Dict.to_list(dict)
assert Enum.zip(list, list) == Enum.zip(dict, dict)
end
end
end
+9 -13
View File
@@ -830,31 +830,27 @@ defmodule EnumTest.Others do
import ExUnit.CaptureIO
test :reverse do
test "reverse custom enumerable" do
assert Enum.reverse(URI.query_decoder("foo=bar&baz=bat")) ==
[{ "baz", "bat" }, { "foo", "bar" }]
end
test :count do
assert Enum.count(URI.query_decoder("foo=bar&baz=bat")) == 2
end
test :sort do
d = HashDict.new [a: 1, another: 1, some: 1, multi_word: 1,
this: 2, punctuation: 1, is: 2, sentence: 2, with: 1]
test "sort with custom enumerable" do
d = HashDict.new [a: 7, another: 4, some: 2, multi_word: 3,
this: 9, punctuation: 1, is: 8, sentence: 6, with: 5]
assert Enum.sort(d, fn({_, v1}, {_, v2}) -> v1 > v2 end) ==
[this: 2, is: 2, sentence: 2, with: 1, a: 1, another: 1,
multi_word: 1, some: 1, punctuation: 1]
[this: 9, is: 8, a: 7, sentence: 6, with: 5,
another: 4, multi_word: 3, some: 2, punctuation: 1]
end
test :take_with_side_effects do
test "take with side effects" do
stream = Stream.unfold(1, fn x -> IO.puts x; { x, x + 1 } end)
assert capture_io(fn ->
Enum.take(stream, 1)
end) == "1\n"
end
test :take_does_not_consume_next_without_a_need do
test "takes does not consume next without a need" do
import PathHelpers
File.open!(fixture_path("one-liner.txt"), [], fn file ->
iterator = IO.stream(file, :line)
@@ -863,7 +859,7 @@ defmodule EnumTest.Others do
end)
end
test :take_with_no_item_works_as_no_op do
test "take with no item works as no-op" do
import PathHelpers
iterator = File.stream!(fixture_path("unknown.txt"))
+13 -9
View File
@@ -4,7 +4,7 @@ defmodule Kernel.ExceptionTest do
use ExUnit.Case, async: true
# Ensure fields passed through an expression are valid
defexception Custom, %w(message)a
defexception Custom, ~w(message)a
test "is_exception" do
assert is_exception(RuntimeError.new)
@@ -42,13 +42,17 @@ defmodule Kernel.ExceptionTest do
end
test "format_stacktrace_entry with application" do
assert Exception.format_stacktrace_entry({Exception, :bar, [], [file: 'file.ex']}) == "(elixir) file.ex: Exception.bar()"
assert Exception.format_stacktrace_entry({Exception, :bar, [], [file: 'file.ex', line: 10]}) == "(elixir) file.ex:10: Exception.bar()"
assert Exception.format_stacktrace_entry({:lists, :bar, [1, 2, 3], []}) == "(stdlib) :lists.bar(1, 2, 3)" end
assert Exception.format_stacktrace_entry({Exception, :bar, [], [file: 'file.ex']}) ==
"(elixir) file.ex: Exception.bar()"
assert Exception.format_stacktrace_entry({Exception, :bar, [], [file: 'file.ex', line: 10]}) ==
"(elixir) file.ex:10: Exception.bar()"
assert Exception.format_stacktrace_entry({:lists, :bar, [1, 2, 3], []}) ==
"(stdlib) :lists.bar(1, 2, 3)"
end
test "format_stacktrace_entry with fun" do
assert Exception.format_stacktrace_entry({fn(x) -> x end, [1], []}) =~ %r/#Function<.+>\(1\)/
assert Exception.format_stacktrace_entry({fn(x, y) -> { x, y } end, 2, []}) =~ %r"#Function<.+>/2"
assert Exception.format_stacktrace_entry({fn(x) -> x end, [1], []}) =~ ~r/#Function<.+>\(1\)/
assert Exception.format_stacktrace_entry({fn(x, y) -> { x, y } end, 2, []}) =~ ~r"#Function<.+>/2"
end
test "format_mfa" do
@@ -58,11 +62,12 @@ defmodule Kernel.ExceptionTest do
assert Exception.format_mfa(nil, :bar, []) == "nil.bar()"
assert Exception.format_mfa(:foo, :bar, [1, 2]) == ":foo.bar(1, 2)"
assert Exception.format_mfa(Foo, :"bar baz", 1) == "Foo.\"bar baz\"/1"
assert Exception.format_mfa(Foo, :"-func/2-fun-0-", 4) == "anonymous fn/4 in Foo.func/2"
end
test "format_fa" do
assert Exception.format_fa(fn -> end, 1) =~
%r"#Function<\d\.\d+ in Kernel\.ExceptionTest\.test format_fa/1>/1"
~r"#Function<\d\.\d+/0 in Kernel\.ExceptionTest\.test format_fa/1>/1"
end
test "runtime error message" do
@@ -98,10 +103,9 @@ defmodule Kernel.ExceptionTest do
[top|_] = System.stacktrace
top
end
file = __ENV__.file |> Path.relative_to_cwd |> String.to_char_list!
assert {Kernel.ExceptionTest, :"test raise preserves the stacktrace", _,
[file: ^file, line: 96]} = stacktrace
[file: ^file, line: 101]} = stacktrace
end
test "defexception" do
+42 -88
View File
@@ -44,16 +44,13 @@ defmodule FileTest do
end
test :cp_with_src_file_and_dest_dir do
src = fixture_path("file.txt")
dest = tmp_path("tmp")
final = Path.join(dest, "file.txt")
src = fixture_path("file.txt")
dest = tmp_path("tmp")
File.mkdir(dest)
try do
refute File.exists?(final)
assert File.cp(src, dest) == :ok
assert File.exists?(final)
assert File.cp(src, dest) == { :error, :eisdir }
after
File.rm_rf dest
end
@@ -154,14 +151,11 @@ defmodule FileTest do
test :cp_r_with_src_file_and_dest_dir do
src = fixture_path("file.txt")
dest = tmp_path("tmp")
final = Path.join(dest, "file.txt")
File.mkdir(dest)
try do
refute File.exists?(final)
assert File.cp_r(src, dest) == { :ok, [final] }
assert File.exists?(final)
assert io_error? File.cp_r(src, dest)
after
File.rm_rf dest
end
@@ -186,30 +180,6 @@ defmodule FileTest do
File.mkdir(dest)
try do
refute File.exists?(tmp_path("tmp/cp_r/a/1.txt"))
refute File.exists?(tmp_path("tmp/cp_r/a/a/2.txt"))
refute File.exists?(tmp_path("tmp/cp_r/b/3.txt"))
{ :ok, files } = File.cp_r(src, dest)
assert length(files) == 7
assert tmp_path("tmp/cp_r/a") in files
assert tmp_path("tmp/cp_r/a/1.txt") in files
assert File.exists?(tmp_path("tmp/cp_r/a/1.txt"))
assert File.exists?(tmp_path("tmp/cp_r/a/a/2.txt"))
assert File.exists?(tmp_path("tmp/cp_r/b/3.txt"))
after
File.rm_rf dest
end
end
test :cp_r_with_src_dir_dot_and_dest_dir do
src = fixture_path("cp_r/.")
dest = tmp_path("tmp")
File.mkdir(dest)
try do
refute File.exists?(tmp_path("tmp/a/1.txt"))
refute File.exists?(tmp_path("tmp/a/a/2.txt"))
@@ -217,6 +187,8 @@ defmodule FileTest do
{ :ok, files } = File.cp_r(src, dest)
assert length(files) == 7
assert tmp_path("tmp/a") in files
assert tmp_path("tmp/a/1.txt") in files
assert File.exists?(tmp_path("tmp/a/1.txt"))
assert File.exists?(tmp_path("tmp/a/a/2.txt"))
@@ -243,16 +215,16 @@ defmodule FileTest do
dest = tmp_path("tmp")
try do
refute File.exists?(tmp_path("tmp/cp_r/a/1.txt"))
refute File.exists?(tmp_path("tmp/cp_r/a/a/2.txt"))
refute File.exists?(tmp_path("tmp/cp_r/b/3.txt"))
refute File.exists?(tmp_path("tmp/a/1.txt"))
refute File.exists?(tmp_path("tmp/a/a/2.txt"))
refute File.exists?(tmp_path("tmp/b/3.txt"))
{ :ok, files } = File.cp_r(src, dest)
assert length(files) == 7
assert File.exists?(tmp_path("tmp/cp_r/a/1.txt"))
assert File.exists?(tmp_path("tmp/cp_r/a/a/2.txt"))
assert File.exists?(tmp_path("tmp/cp_r/b/3.txt"))
assert File.exists?(tmp_path("tmp/a/1.txt"))
assert File.exists?(tmp_path("tmp/a/a/2.txt"))
assert File.exists?(tmp_path("tmp/b/3.txt"))
after
File.rm_rf dest
end
@@ -265,20 +237,20 @@ defmodule FileTest do
end
test :cp_r_with_dir_and_file_conflict do
src = fixture_path("cp_r/.")
src = fixture_path("cp_r")
dest = tmp_path("tmp")
try do
File.mkdir(dest)
File.write!(Path.join(dest, "a"), "hello")
assert (File.cp_r(src, dest) |> io_error?)
assert io_error? File.cp_r(src, dest)
after
File.rm_rf dest
end
end
test :cp_r_with_src_dir_and_dest_dir_using_lists do
src = fixture_path("cp_r/.") |> to_char_list
src = fixture_path("cp_r") |> to_char_list
dest = tmp_path("tmp") |> to_char_list
File.mkdir(dest)
@@ -301,7 +273,7 @@ defmodule FileTest do
end
test :cp_r_with_src_with_file_conflict do
src = fixture_path("cp_r/.")
src = fixture_path("cp_r")
dest = tmp_path("tmp")
File.mkdir_p tmp_path("tmp/a")
@@ -317,7 +289,7 @@ defmodule FileTest do
end
test :cp_r_with_src_with_file_conflict_callback do
src = fixture_path("cp_r/.")
src = fixture_path("cp_r")
dest = tmp_path("tmp")
File.mkdir_p tmp_path("tmp/a")
@@ -337,7 +309,7 @@ defmodule FileTest do
end
test :cp_r! do
src = fixture_path("cp_r/.")
src = fixture_path("cp_r")
dest = tmp_path("tmp")
File.mkdir(dest)
@@ -595,7 +567,7 @@ defmodule FileTest do
fixture = fixture_path("file.txt")
invalid = Path.join fixture, "test"
assert File.exists?(fixture)
assert (File.mkdir(invalid) |> io_error?)
assert io_error? File.mkdir(invalid)
refute File.exists?(invalid)
end
@@ -614,7 +586,7 @@ defmodule FileTest do
fixture = fixture_path("file.txt")
invalid = Path.join fixture, "test"
assert File.exists?(fixture)
assert_raise File.Error, %r"^could not make directory #{escape invalid}: (not a directory|no such file or directory)", fn ->
assert_raise File.Error, ~r"^could not make directory #{escape invalid}: (not a directory|no such file or directory)", fn ->
File.mkdir!(invalid)
end
end
@@ -676,7 +648,7 @@ defmodule FileTest do
test :mkdir_p_with_invalid_path do
assert File.exists?(fixture_path("file.txt"))
invalid = Path.join fixture_path("file.txt"), "test/foo"
assert (File.mkdir(invalid) |> io_error?)
assert io_error? File.mkdir(invalid)
refute File.exists?(invalid)
end
@@ -695,16 +667,15 @@ defmodule FileTest do
fixture = fixture_path("file.txt")
invalid = Path.join fixture, "test"
assert File.exists?(fixture)
assert_raise File.Error, %r"^could not make directory \(with -p\) #{escape invalid}: (not a directory|no such file or directory)", fn ->
assert_raise File.Error, ~r"^could not make directory \(with -p\) #{escape invalid}: (not a directory|no such file or directory)", fn ->
File.mkdir_p!(invalid)
end
end
defp io_error?(result) do
{:error,errorcode} = result
{ :error, errorcode } = result
errorcode in [:enotdir, :eio, :enoent, :eisdir]
end
end
defmodule Rm do
@@ -749,7 +720,7 @@ defmodule FileTest do
end
test :rmdir_with_file do
assert (File.rmdir(fixture_path("file.txt")) |> io_error?)
assert io_error? File.rmdir(fixture_path("file.txt"))
end
test :rmdir! do
@@ -762,7 +733,7 @@ defmodule FileTest do
test :rmdir_with_file! do
fixture = fixture_path("file.txt")
assert_raise File.Error, %r"^could not remove directory #{escape fixture}: (not a directory|I/O error)", fn ->
assert_raise File.Error, ~r"^could not remove directory #{escape fixture}: (not a directory|I/O error)", fn ->
File.rmdir!(fixture)
end
end
@@ -770,7 +741,7 @@ defmodule FileTest do
test :rm_rf do
fixture = tmp_path("tmp")
File.mkdir(fixture)
File.cp_r!(fixture_path("cp_r/."), fixture)
File.cp_r!(fixture_path("cp_r"), fixture)
assert File.exists?(tmp_path("tmp/a/1.txt"))
assert File.exists?(tmp_path("tmp/a/a/2.txt"))
@@ -811,7 +782,7 @@ defmodule FileTest do
test :rm_rf_with_char_list do
fixture = tmp_path("tmp") |> to_char_list
File.mkdir(fixture)
File.cp_r!(fixture_path("cp_r/."), fixture)
File.cp_r!(fixture_path("cp_r"), fixture)
assert File.exists?(tmp_path("tmp/a/1.txt"))
assert File.exists?(tmp_path("tmp/a/a/2.txt"))
@@ -847,7 +818,7 @@ defmodule FileTest do
test :rm_rf! do
fixture = tmp_path("tmp")
File.mkdir(fixture)
File.cp_r!(fixture_path("cp_r/."), fixture)
File.cp_r!(fixture_path("cp_r"), fixture)
assert File.exists?(tmp_path("tmp/a/1.txt"))
assert File.exists?(tmp_path("tmp/a/a/2.txt"))
@@ -995,23 +966,6 @@ defmodule FileTest do
end
end
test :stream_to do
src = fixture_path("file.txt")
dest = tmp_path("tmp_test.txt")
try do
stream = File.stream!(src)
|> Stream.map(&String.replace(&1, "O", "A"))
|> File.stream_to!(dest)
refute File.exists?(dest)
assert Stream.run(stream) == :ok
assert File.read(dest) == { :ok, "FAA\n" }
after
File.rm(dest)
end
end
test :stream_bytes do
src = fixture_path("file.txt")
dest = tmp_path("tmp_test.txt")
@@ -1131,11 +1085,11 @@ defmodule FileTest do
end
test :invalid_cd do
assert(File.cd(fixture_path("file.txt")) |> io_error?)
assert io_error? File.cd(fixture_path("file.txt"))
end
test :invalid_cd! do
message = %r"^could not set current working directory to #{escape fixture_path("file.txt")}: (not a directory|no such file or directory)"
message = ~r"^could not set current working directory to #{escape fixture_path("file.txt")}: (not a directory|no such file or directory)"
assert_raise File.Error, message, fn ->
File.cd!(fixture_path("file.txt"))
end
@@ -1182,7 +1136,7 @@ defmodule FileTest do
test :touch_with_failure do
fixture = fixture_path("file.txt/bar")
assert (File.touch(fixture) |> io_error?)
assert io_error? File.touch(fixture)
end
test :touch_with_success! do
@@ -1191,7 +1145,7 @@ defmodule FileTest do
test :touch_with_failure! do
fixture = fixture_path("file.txt/bar")
assert_raise File.Error, %r"could not touch #{escape fixture}: (not a directory|no such file or directory)", fn ->
assert_raise File.Error, ~r"could not touch #{escape fixture}: (not a directory|no such file or directory)", fn ->
File.touch!(fixture)
end
end
@@ -1234,52 +1188,52 @@ defmodule FileTest do
end
end
test :chmod_with_failue do
test :chmod_with_failure do
fixture = tmp_path("tmp_test.txt")
File.rm(fixture)
assert File.chmod(fixture, 0100777) == {:error,:enoent}
end
test :chmod_with_failue! do
test :chmod_with_failure! do
fixture = tmp_path("tmp_test.txt")
File.rm(fixture)
message = %r"could not change mode for #{escape fixture}: no such file or directory"
message = ~r"could not change mode for #{escape fixture}: no such file or directory"
assert_raise File.Error, message, fn ->
File.chmod!(fixture, 0100777)
end
end
test :chgrp_with_failue do
test :chgrp_with_failure do
fixture = tmp_path("tmp_test.txt")
File.rm(fixture)
assert File.chgrp(fixture, 1) == {:error,:enoent}
end
test :chgrp_with_failue! do
test :chgrp_with_failure! do
fixture = tmp_path("tmp_test.txt")
File.rm(fixture)
message = %r"could not change group for #{escape fixture}: no such file or directory"
message = ~r"could not change group for #{escape fixture}: no such file or directory"
assert_raise File.Error, message, fn ->
File.chgrp!(fixture, 1)
end
end
test :chown_with_failue do
test :chown_with_failure do
fixture = tmp_path("tmp_test.txt")
File.rm(fixture)
assert File.chown(fixture, 1) == {:error,:enoent}
end
test :chown_with_failue! do
test :chown_with_failure! do
fixture = tmp_path("tmp_test.txt")
File.rm(fixture)
message = %r"could not change owner for #{escape fixture}: no such file or directory"
message = ~r"could not change owner for #{escape fixture}: no such file or directory"
assert_raise File.Error, message, fn ->
File.chown!(fixture, 1)
end
@@ -1294,7 +1248,7 @@ defmodule FileTest do
end
defp io_error?(result) do
{:error,errorcode} = result
{ :error, errorcode } = result
errorcode in [:enotdir, :eio, :enoent, :eisdir]
end
end
@@ -0,0 +1,3 @@
defmodule AttributeWarning do
@foo
end
@@ -5,7 +5,7 @@ defmodule CompiledWithDocs do
def example(false), do: 0
def example(var), do: var && private
def nodoc(var // 0)
def nodoc(var \\ 0)
def nodoc(_), do: 2
defp private, do: 1
@@ -1,43 +0,0 @@
Code.require_file "../test_helper.exs", __DIR__
defmodule GenFSM.BehaviourTest do
use ExUnit.Case
setup_all do
:error_logger.tty(false)
:ok
end
teardown_all do
:error_logger.tty(true)
:ok
end
defmodule Sample do
use GenFSM.Behaviour
def init(args) do
{ :ok, :sample, args }
end
end
test "sync event stops server on unknown requests" do
Process.flag(:trap_exit, true)
assert { :ok, pid } = :gen_fsm.start_link(Sample, [:hello], [])
catch_exit(:gen_fsm.sync_send_all_state_event(pid, :unknown_request))
assert_receive { :EXIT, ^pid, {:bad_sync_event, :sample, :unknown_request} }
after
Process.flag(:trap_exit, false)
end
test "event stops server on unknown requests" do
Process.flag(:trap_exit, true)
assert { :ok, pid } = :gen_fsm.start_link(Sample, [:hello], [])
:gen_fsm.send_all_state_event(pid, :unknown_request)
assert_receive { :EXIT, ^pid, {:bad_event, :sample, :unknown_request} }
after
Process.flag(:trap_exit, false)
end
end
+7 -240
View File
@@ -28,98 +28,7 @@ defmodule HashDictTest do
smoke_test(1200..1)
end
test "fetch!/2" do
dict = filled_dict(8)
assert HashDict.fetch!(dict, 1) == 1
assert_raise KeyError, fn ->
HashDict.fetch!(dict, 11)
end
end
test "empty/1" do
assert HashDict.empty filled_dict(8) == HashDict.new
assert HashDict.empty filled_dict(20) == HashDict.new
assert HashDict.empty filled_dict(120) == HashDict.new
end
test "fetch/2" do
dict = filled_dict(8)
assert HashDict.fetch(dict, 4) == { :ok, 4 }
assert HashDict.fetch(dict, 16) == :error
end
test "equal?/2" do
assert HashDict.equal?(filled_dict(3), filled_dict(3)) == true
assert HashDict.equal?(HashDict.new([{:a, 1}, {:b, 2}]),
HashDict.new([{:a, 2}, {:b, 3}])) == false
end
test "has_key?/2" do
dict = filled_dict(8)
assert HashDict.has_key? dict, 4
refute HashDict.has_key? dict, 16
dict = filled_dict(20)
assert HashDict.has_key? dict, 10
refute HashDict.has_key? dict, 30
dict = filled_dict(120)
assert HashDict.has_key? dict, 60
refute HashDict.has_key? dict, 240
end
test "put_new/3" do
dict = filled_dict(8)
dict = HashDict.put_new(dict, 1, 11)
assert HashDict.get(dict, 1) == 1
assert HashDict.size(dict) == 8
dict = HashDict.put_new(dict, 11, 13)
assert HashDict.get(dict, 11) == 13
assert HashDict.size(dict) == 9
dict = HashDict.put_new(dict, 11.0, 15)
assert HashDict.get(dict, 11.0) == 15
assert HashDict.get(dict, 11) == 13
assert HashDict.size(dict) == 10
end
test "update/3" do
dict = filled_dict(8)
dict = HashDict.update!(dict, 1, &(&1 * 2))
assert HashDict.get(dict, 1) == 2
assert HashDict.size(dict) == 8
assert_raise KeyError, fn ->
HashDict.update!(dict, 11, &(&1 * 2))
end
dict = HashDict.update(dict, 3, 3, &(&1 * 2))
assert HashDict.get(dict, 3) == 6
assert HashDict.size(dict) == 8
dict = HashDict.update(dict, 11, 13, &(&1 * 2))
assert HashDict.get(dict, 11) == 13
assert HashDict.size(dict) == 9
assert_raise KeyError, fn->
HashDict.update!(dict, 11.0, &(&1 * 2))
end
dict = HashDict.update(dict, 11.0, 15, &(&1 * 2))
assert HashDict.get(dict, 11.0) == 15
assert HashDict.size(dict) == 10
dict = HashDict.update(dict, 11.0, 15, &(&1 * 2))
assert HashDict.get(dict, 11.0) == 30
assert HashDict.get(dict, 11) == 13
assert HashDict.size(dict) == 10
end
test "to_list/1" do
test "reduce/3 (via to_list)" do
dict = filled_dict(8)
list = dict |> HashDict.to_list
assert length(list) == 8
@@ -139,158 +48,16 @@ defmodule HashDictTest do
assert list == Enum.to_list(dict)
end
test "keys/1" do
list = filled_dict(8) |> HashDict.keys
assert length(list) == 8
assert 1 in list
list = filled_dict(20) |> HashDict.keys
assert length(list) == 20
assert 1 in list
list = filled_dict(120) |> HashDict.keys
assert length(list) == 120
assert 1 in list
end
test "values/1" do
list = filled_dict(8) |> HashDict.values
assert length(list) == 8
assert 1 in list
list = filled_dict(20) |> HashDict.values
assert length(list) == 20
assert 1 in list
list = filled_dict(120) |> HashDict.values
assert length(list) == 120
assert 1 in list
end
test "implements Enumerable" do
dict = filled_dict(10)
assert Enum.empty?(HashDict.new)
refute Enum.empty?(dict)
assert Enum.member?(dict, { 5, 5 })
refute Enum.member?(dict, { 5, 8 })
assert Enum.count(dict) == 10
assert Enum.map(filled_dict(3), fn({ k, v }) -> k + v end) == [2, 4, 6]
end
test "access" do
assert filled_dict(8)[1] == 1
assert filled_dict(8)[5] == 5
assert filled_dict(8)[9] == nil
end
test "inspect" do
assert inspect(filled_dict(8)) =~ "#HashDict<"
end
test "small dict merge" do
dict1 = filled_dict(8)
dict2 = Enum.reduce 6..10, HashDict.new, fn(i, d) -> HashDict.put(d, i, i * 2) end
test "comparison when subsets" do
d1 = HashDict.new(a: 0)
d2 = HashDict.new(a: 0, b: 1)
dict = HashDict.merge(dict1, dict2)
assert HashDict.get(dict, 3) == 3
assert HashDict.get(dict, 6) == 12
assert HashDict.get(dict, 10) == 20
assert HashDict.size(dict) == 10
dict = HashDict.merge(dict2, dict1)
assert HashDict.get(dict, 3) == 3
assert HashDict.get(dict, 6) == 6
assert HashDict.get(dict, 10) == 20
assert HashDict.size(dict) == 10
dict = HashDict.merge(dict1, dict2, fn(k, v1, v2) -> 3*k + 2*v1 + v2 end)
assert HashDict.get(dict, 3) == 3
assert HashDict.get(dict, 6) == 6 * 7
assert HashDict.get(dict, 10) == 20
assert HashDict.size(dict) == 10
list = Enum.map 6..10, fn x -> { x, x * 2 } end
dict = HashDict.merge(dict1, list)
assert HashDict.get(dict, 3) == 3
assert HashDict.get(dict, 6) == 12
assert HashDict.get(dict, 10) == 20
assert HashDict.size(dict) == 10
end
test "medium dict merge" do
dict1 = filled_dict(20)
dict2 = Enum.reduce 18..22, HashDict.new, fn(i, d) -> HashDict.put(d, i, i * 2) end
dict = HashDict.merge(dict1, dict2)
assert HashDict.get(dict, 16) == 16
assert HashDict.get(dict, 18) == 36
assert HashDict.get(dict, 22) == 44
assert HashDict.size(dict) == 22
dict = HashDict.merge(dict2, dict1)
assert HashDict.get(dict, 16) == 16
assert HashDict.get(dict, 18) == 18
assert HashDict.get(dict, 22) == 44
assert HashDict.size(dict) == 22
dict = HashDict.merge(dict1, dict2, fn(k, v1, v2) -> 3*k + 2*v1 + v2 end)
assert HashDict.get(dict, 16) == 16
assert HashDict.get(dict, 18) == 18 * 7
assert HashDict.get(dict, 22) == 44
assert HashDict.size(dict) == 22
list = Enum.map 18..22, fn x -> { x, x * 2 } end
dict = HashDict.merge(dict1, list)
assert HashDict.get(dict, 16) == 16
assert HashDict.get(dict, 18) == 36
assert HashDict.get(dict, 22) == 44
assert HashDict.size(dict) == 22
end
test "large dict merge" do
dict1 = filled_dict(120)
dict2 = Enum.reduce 118..122, HashDict.new, fn(i, d) -> HashDict.put(d, i, i * 2) end
dict = HashDict.merge(dict1, dict2)
assert HashDict.get(dict, 116) == 116
assert HashDict.get(dict, 118) == 236
assert HashDict.get(dict, 122) == 244
assert HashDict.size(dict) == 122
dict = HashDict.merge(dict2, dict1)
assert HashDict.get(dict, 116) == 116
assert HashDict.get(dict, 118) == 118
assert HashDict.get(dict, 122) == 244
assert HashDict.size(dict) == 122
dict = HashDict.merge(dict1, dict2, fn(k, v1, v2) -> 3*k + 2*v1 + v2 end)
assert HashDict.get(dict, 116) == 116
assert HashDict.get(dict, 118) == 118 * 7
assert HashDict.get(dict, 122) == 244
assert HashDict.size(dict) == 122
list = Enum.map 118..122, fn x -> { x, x * 2 } end
dict = HashDict.merge(dict1, list)
assert HashDict.get(dict, 116) == 116
assert HashDict.get(dict, 118) == 236
assert HashDict.get(dict, 122) == 244
assert HashDict.size(dict) == 122
end
test "trie contraction" do
dict = filled_dict(120)
dict = Enum.reduce 16..120, dict, fn(x, acc) -> HashDict.delete(acc, x) end
assert (Enum.filter 1..120, fn(x) -> HashDict.get(dict, x) == x end) == (Enum.sort 1..15)
end
test "is zippable" do
dict = filled_dict(8)
list = Dict.to_list(dict)
assert Enum.zip(list, list) == Enum.zip(dict, dict)
dict = filled_dict(120)
list = Dict.to_list(dict)
assert Enum.zip(list, list) == Enum.zip(dict, dict)
refute HashDict.equal?(d1, d2)
refute HashDict.equal?(d2, d1)
end
defp smoke_test(range) do
@@ -312,7 +79,7 @@ defmodule HashDictTest do
{ acc, i - 1 }
end
dict
assert dict == HashDict.new
end
defp filled_dict(range) do
+9 -78
View File
@@ -3,65 +3,28 @@ Code.require_file "test_helper.exs", __DIR__
defmodule HashSetTest do
use ExUnit.Case, async: true
test "union with ordered sets" do
assert HashSet.union(HashSet.new([1, 2, 3]), HashSet.new([3, 4, 5])) == HashSet.new([1, 2, 3, 4, 5])
assert HashSet.union(HashSet.new, HashSet.new) == HashSet.new
end
test "union with nodes" do
test "union" do
assert HashSet.union(filled_set(21), filled_set(22)) == filled_set(22)
assert HashSet.union(filled_set(121), filled_set(120)) == filled_set(121)
end
test "intersection with ordered sets" do
assert HashSet.intersection(HashSet.new([1, 2, 3, 4, 6]), HashSet.new([3, 4, 5])) == HashSet.new([3, 4])
assert HashSet.intersection(HashSet.new, filled_set(8)) == HashSet.new
assert HashSet.intersection(filled_set(8), HashSet.new) == HashSet.new
assert HashSet.intersection(HashSet.new, HashSet.new) == HashSet.new
end
test "intersection with nodes" do
test "intersection" do
assert HashSet.intersection(filled_set(21), filled_set(20)) == filled_set(20)
assert HashSet.equal?(HashSet.intersection(filled_set(120), filled_set(121)), filled_set(120))
end
test "difference with ordered sets" do
assert HashSet.difference(HashSet.new([1, 2, 3, 5]), HashSet.new([3, 4, 5])) == HashSet.new([1, 2])
assert HashSet.difference(HashSet.new, HashSet.new([1])) == HashSet.new([])
assert HashSet.difference(HashSet.new, HashSet.new) == HashSet.new
assert HashSet.difference(HashSet.new([1]), HashSet.new) == HashSet.new([1])
end
test "difference with nodes" do
test "difference" do
assert HashSet.equal?(HashSet.difference(filled_set(20), filled_set(21)), HashSet.new([]))
diff = HashSet.difference(filled_set(9000), filled_set(9000))
assert HashSet.equal?(diff, HashSet.new([]))
# Ensure the trie has correctly resized
assert { HashSet, 0, 0, 80, 2, _ } = diff
end
test "member? with ordered sets" do
assert HashSet.member?(filled_set(8), 8)
refute HashSet.member?(filled_set(8), 10)
end
test "member? with nodes" do
assert HashSet.member?(filled_set(20), 19)
refute HashSet.member?(filled_set(20), 100)
assert HashSet.member?(filled_set(120), 90)
refute HashSet.member?(filled_set(120), 200)
assert HashSet.size(diff) == 0
end
test "subset?" do
assert HashSet.subset?(HashSet.new, HashSet.new)
assert HashSet.subset?(filled_set(6), filled_set(10))
assert HashSet.subset?(filled_set(6), filled_set(120))
refute HashSet.subset?(filled_set(120), filled_set(6))
end
@@ -71,54 +34,22 @@ defmodule HashSetTest do
assert HashSet.equal?(filled_set(120), filled_set(120))
end
test "empty" do
assert HashSet.empty filled_set(8) == HashSet.new
assert HashSet.empty filled_set(20) == HashSet.new
assert HashSet.empty filled_set(120) == HashSet.new
end
test "to_list" do
set = filled_set(8)
list = set |> HashSet.to_list
assert length(list) == 8
assert 1 in list
assert list == Enum.to_list(set)
set = filled_set(20)
list = set |> HashSet.to_list
list = HashSet.to_list(set)
assert length(list) == 20
assert 1 in list
assert list == Enum.to_list(set)
assert Enum.sort(list) == Enum.sort(1..20)
set = filled_set(120)
list = set |> HashSet.to_list
list = HashSet.to_list(set)
assert length(list) == 120
assert 1 in list
assert list == Enum.to_list(set)
end
test "delete" do
assert HashSet.delete(filled_set(8), 8) == filled_set(7)
assert HashSet.delete(filled_set(8), 9) == filled_set(8)
assert HashSet.delete(HashSet.new, 10) == HashSet.new
assert HashSet.delete(filled_set(21), 21) == filled_set(20)
assert HashSet.delete(filled_set(121), 121) == filled_set(120)
end
test "is zippable" do
set = filled_set(8)
list = Dict.to_list(set)
assert Enum.zip(list, list) == Enum.zip(set, set)
set = filled_set(120)
list = Dict.to_list(set)
assert Enum.zip(list, list) == Enum.zip(set, set)
assert Enum.sort(list) == Enum.sort(1..120)
end
defp filled_set(range) do
Enum.reduce 1..range, HashSet.new, &HashSet.put(&2, &1)
HashSet.new(1..range)
end
end
+41 -34
View File
@@ -17,94 +17,93 @@ defmodule Inspect.AlgebraTest do
def factor(doc, w), do: format(w, 0, [{0, :flat, group(doc)}])
test :empty do
test "empty doc" do
# Consistence with definitions
assert empty == :doc_nil
# Consistence of corresponding sdoc
assert factor(empty, 80) == []
# Consistent formatting
assert pretty(empty, 80) == ""
end
test :break do
test "break doc" do
# Consistence with definitions
## Normal case
assert break("break") == { :doc_break, "break" }
## Degeneracy
assert break("") == { :doc_break, "" }
## ong argument type
# Wrong argument type
assert_raise FunctionClauseError, fn -> break(42) end
# Consistence of corresponding sdoc
assert factor(break("_"), 80) == ["_"]
# Consistent formatting
assert pretty(break("_"), 80) == "_"
end
test :glue do
test "glue doc" do
# Consistence with definitions
assert glue("a", "->", "b") == { :doc_cons,
"a", { :doc_cons, { :doc_break, "->" }, "b" }
}
assert glue("a", "b") == glue("a", " ", "b")
## Wrong argument type
# Wrong argument type
assert_raise FunctionClauseError, fn -> glue("a", 42, "b") end
end
test :text do
# Consistence of corresponding docfactor
test "text doc" do
# Consistence of corresponding sdoc
assert factor("_", 80) == ["_"]
# Consistent formatting
assert pretty("_", 80) == "_"
end
test :space do
test "space doc" do
# Consistency with definitions
assert space("a", "b") == { :doc_cons,
"a", { :doc_cons, " ", "b" }
}
end
test :nest do
test "nest doc" do
# Consistence with definitions
## Normal case
assert nest(empty, 1) == { :doc_nest, 1, empty }
## Degeneracy
assert nest(empty, 0) == :doc_nil
## ong argument type
# Wrong argument type
assert_raise FunctionClauseError, fn -> nest("foo", empty) end
a1 = fn -> nest("a", 1) end
alb1 = fn -> nest(glue("a", "b"), 1) end
# Consistence of corresponding sdoc
## Trivial case
assert factor(a1.(), 80) == ["a"]
## Correctly indenting line forcing linebreak
assert format(2, 0, [{0, :break, alb1.()}]) == ["a", "\n", " ", "b"]
assert factor(nest("a", 1), 80) == ["a"]
assert format(2, 0, [{0, :break, nest(glue("a", "b"), 1)}]) == ["a", "\n ", "b"]
# Consistent formatting
## Trivial case
assert pretty(a1.(), 80) == "a"
## Correctly indenting line
assert render(format 2, 0, [{0, :break, alb1.()}]) == "a\n b"
assert pretty(nest("a", 1), 80) == "a"
assert render(format 2, 0, [{0, :break, nest(glue("a", "b"), 1)}]) == "a\n b"
end
test :infinity do
# w = :infinity should disable pretty printer
s = String.duplicate "x", 50
g = ";"
big_document = group(glue(s, g, s) |> glue(g, s) |> glue(g, s) |> glue(g, s))
test "line doc" do
# Consistency with definitions
assert line("a", "b") ==
{ :doc_cons, "a", { :doc_cons, :doc_line, "b" } }
assert pretty(big_document, :infinity) == s <> g <> s <> g <> s <> g <> s <> g <> s
# Consistence of corresponding sdoc
assert factor(line("a", "b"), 1) == ["a", "\n", "b"]
assert factor(line("a", "b"), 9) == ["a", "\n", "b"]
# Consistent formatting
assert pretty(line(glue("aaa", "bbb"), glue("ccc", "ddd")), 10) ==
"aaa bbb\nccc ddd"
end
test :group do
test "group doc" do
# Consistency with definitions
## Normal case
assert group(glue("a", "b")) ==
{ :doc_group, { :doc_cons, "a", concat(break, "b") }}
## Degeneracy
assert group(empty) == { :doc_group, empty }
# Consistence of corresponding sdoc
@@ -115,4 +114,12 @@ defmodule Inspect.AlgebraTest do
assert pretty(helloabcd, 5) == "hello\na b\ncd"
assert pretty(helloabcd, 80) == "hello a b cd"
end
test "formatting with infinity" do
s = String.duplicate "x", 50
g = ";"
doc = group(glue(s, g, s) |> glue(g, s) |> glue(g, s) |> glue(g, s))
assert pretty(doc, :infinity) == s <> g <> s <> g <> s <> g <> s <> g <> s
end
end
+28 -17
View File
@@ -37,11 +37,22 @@ defmodule Inspect.AtomTest do
end
test :op do
assert inspect(:@) == ":@"
assert inspect(:+) == ":+"
assert inspect(:&&&) == ":&&&"
assert inspect(:~~~) == ":~~~"
end
test :... do
assert inspect(:...) == ":..."
end
test :@ do
assert inspect(:@) == ":@"
assert inspect(:foo@bar) == ":foo@bar"
assert inspect(:foo@bar@) == ":foo@bar@"
assert inspect(:foo@bar@baz) == ":foo@bar@baz"
end
test :container do
assert inspect(:<<>>) == ":<<>>"
assert inspect(:{}) == ":{}"
@@ -71,15 +82,15 @@ defmodule Inspect.BitStringTest do
end
test :opt_infer do
assert inspect(<<"eric", 193, "mj">>, binaries: :infer) == %s(<<101, 114, 105, 99, 193, 109, 106>>)
assert inspect(<<"eric">>, binaries: :infer) == %s("eric")
assert inspect(<<193>>, binaries: :infer) == %s(<<193>>)
assert inspect(<<"eric", 193, "mj">>, binaries: :infer) == ~s(<<101, 114, 105, 99, 193, 109, 106>>)
assert inspect(<<"eric">>, binaries: :infer) == ~s("eric")
assert inspect(<<193>>, binaries: :infer) == ~s(<<193>>)
end
test :opt_as_strings do
assert inspect(<<"eric", 193, "mj">>, binaries: :as_strings) == %s("eric\\301mj")
assert inspect(<<"eric">>, binaries: :as_strings) == %s("eric")
assert inspect(<<193>>, binaries: :as_strings) == %s("\\301")
assert inspect(<<"eric", 193, "mj">>, binaries: :as_strings) == ~s("eric\\301mj")
assert inspect(<<"eric">>, binaries: :as_strings) == ~s("eric")
assert inspect(<<193>>, binaries: :as_strings) == ~s("\\301")
end
test :opt_as_binaries do
@@ -145,20 +156,20 @@ defmodule Inspect.TupleTest do
assert inspect(RuntimeError.new) == "RuntimeError[message: \"runtime error\"]"
end
defrecord :something, [:a, :b]
test :non_module_record do
# They are on purpose not treated as Elixir records
# otherwise it affects inspect performance very strongly
assert inspect(:something.new) == "{:something, nil, nil}"
end
defrecord Rec, value: 1
test :two_items_record do
assert inspect({ Rec[value: 1], 1 }) == "{Inspect.TupleTest.Rec[value: 1], 1}"
end
test :false_positives do
import ExUnit.CaptureIO
assert capture_io(:stderr, fn ->
assert inspect({ Range, nil }) == "{Range, nil}"
end) =~ "** (Inspect.Error) Got FunctionClauseError with message no function clause matching in Inspect.Range.inspect/2"
end
test :empty do
assert inspect({}) == "{}"
end
@@ -188,7 +199,7 @@ defmodule Inspect.ListTest do
assert inspect([a: 1]) == "[a: 1]"
assert inspect([a: 1, b: 2]) == "[a: 1, b: 2]"
assert inspect([a: 1, a: 2, b: 2]) == "[a: 1, a: 2, b: 2]"
assert inspect(["123": 1]) == %s(["123": 1])
assert inspect(["123": 1]) == ~s(["123": 1])
assert inspect([foo: [1,2,3,:bar], bazzz: :bat], [pretty: true, width: 30]) ==
"[foo: [1, 2, 3, :bar],\n bazzz: :bat]"
@@ -270,6 +281,6 @@ defmodule Inspect.OthersTest do
end
test :regex do
"%r\"foo\"m" = inspect(%r(foo)m)
"~r\"foo\"m" = inspect(~r(foo)m)
end
end
+3
View File
@@ -2,8 +2,10 @@ Code.require_file "test_helper.exs", __DIR__
defmodule IntegerTest do
use ExUnit.Case, async: true
require Integer
test :odd? do
assert Integer.odd?(0) == false
assert Integer.odd?(1) == true
assert Integer.odd?(2) == false
assert Integer.odd?(3) == true
@@ -13,6 +15,7 @@ defmodule IntegerTest do
end
test :even? do
assert Integer.even?(0) == true
assert Integer.even?(1) == false
assert Integer.even?(2) == true
assert Integer.even?(3) == false
+10 -4
View File
@@ -35,6 +35,12 @@ bar """
"""
end
test :heredoc_in_call do
assert "foo\nbar" == Kernel.<>("""
foo
""", "bar")
end
test :utf8 do
assert size(" ゆんゆん") == 13
end
@@ -112,19 +118,19 @@ bar """
test :literal_errors do
assert_raise CompileError, fn ->
Code.eval_string(%s[<< "foo" :: integer >>])
Code.eval_string(~s[<< "foo" :: integer >>])
end
assert_raise CompileError, fn ->
Code.eval_string(%s[<< "foo" :: float >>])
Code.eval_string(~s[<< "foo" :: float >>])
end
assert_raise CompileError, fn ->
Code.eval_string(%s[<< 'foo' :: binary >>])
Code.eval_string(~s[<< 'foo' :: binary >>])
end
assert_raise ArgumentError, fn ->
Code.eval_string(%s[<<1::size(4)>> <> "foo"])
Code.eval_string(~s[<<1::size(4)>> <> "foo"])
end
end
@@ -12,7 +12,7 @@ defmodule Kernel.ComprehensionTest do
end
test :list_comprehensions_with_truthy_object do
assert [2, 4, 6] == lc x inlist [1, 2, 3], 1, do: x * 2
assert [2, 4, 6] == lc x inlist [1, 2, 3], List.first([x]), do: x * 2
end
test :list_comprehensions_with_inlist_of_bins do
@@ -48,7 +48,7 @@ defmodule Kernel.ComprehensionTest do
end
test :bit_comprehensions_with_truthy_object do
assert <<2, 4, 6>> == bc x inlist [1, 2, 3], 1, do: <<x * 2>>
assert <<2, 4, 6>> == bc x inlist [1, 2, 3], List.first([1]), do: <<x * 2>>
end
test :bit_comprehensions_with_inlist_of_bins do
+1 -1
View File
@@ -11,7 +11,7 @@ defmodule Kernel.DocsTest do
expected = [
{{:example, 1}, 5, :def, [{:var, [line: 6], nil}], "Some example"},
{{:nodoc, 1}, 8, :def, [{:"//", [line: 8], [{:var, [line: 8], nil}, 0]}], nil}
{{:nodoc, 1}, 8, :def, [{:\\, [line: 8], [{:var, [line: 8], nil}, 0]}], nil}
]
assert CompiledWithDocs.__info__(:docs) == expected
+29 -21
View File
@@ -25,16 +25,16 @@ defmodule Kernel.ErrorsTest do
test :sigil_terminator do
assert_compile_fail TokenMissingError,
"nofile:3: missing terminator: \" (for sigil %r\" starting at line 1)",
'%r"foo\n\n'
"nofile:3: missing terminator: \" (for sigil ~r\" starting at line 1)",
'~r"foo\n\n'
assert_compile_fail TokenMissingError,
"nofile:3: missing terminator: } (for sigil %r{ starting at line 1)",
'%r{foo\n\n'
"nofile:3: missing terminator: } (for sigil ~r{ starting at line 1)",
'~r{foo\n\n'
assert_compile_fail TokenMissingError,
"nofile:3: missing terminator: ) (for sigil %r( starting at line 1)",
'%r(f(oo)\n\n'
"nofile:3: missing terminator: ) (for sigil ~r( starting at line 1)",
'~r(f(oo)\n\n'
end
test :dot_terminator do
@@ -49,6 +49,12 @@ defmodule Kernel.ErrorsTest do
'"bar'
end
test :heredoc_start do
assert_compile_fail TokenMissingError,
"nofile:1: heredoc start \"\"\" must be followed by a new line",
'"""bar'
end
test :heredoc_terminator do
assert_compile_fail TokenMissingError,
"nofile:2: missing terminator: \"\"\" (for heredoc starting at line 1)",
@@ -98,11 +104,9 @@ defmodule Kernel.ErrorsTest do
assert_compile_fail SyntaxError, msg, 'foo 1, foo 2, 3'
assert_compile_fail SyntaxError, msg, 'foo(1, foo 2, 3)'
assert is_list []
assert is_list do: 1
assert is_list List.flatten [1]
assert is_atom is_record 1..3, Range
assert is_atom(is_record 1..3, Range)
assert is_atom is_record range, Range
assert is_atom(is_record range, Range)
end
test :syntax_error_with_no_token do
@@ -115,10 +119,10 @@ defmodule Kernel.ErrorsTest do
assert_compile_fail CompileError,
"nofile:3: def hello/1 has default values and multiple clauses, " <>
"use a separate clause for declaring defaults",
'''
~C'''
defmodule ErrorsTest do
def hello(arg // 0), do: nil
def hello(arg // 1), do: nil
def hello(arg \\ 0), do: nil
def hello(arg \\ 1), do: nil
end
'''
end
@@ -136,19 +140,19 @@ defmodule Kernel.ErrorsTest do
test :different_defs_with_defaults do
assert_compile_fail CompileError,
"nofile:3: def hello/3 defaults conflicts with def hello/2",
'''
~C'''
defmodule ErrorsTest do
def hello(a, b // nil), do: a + b
def hello(a, b // nil, c // nil), do: a + b + c
def hello(a, b \\ nil), do: a + b
def hello(a, b \\ nil, c \\ nil), do: a + b + c
end
'''
assert_compile_fail CompileError,
"nofile:3: def hello/2 conflicts with defaults from def hello/3",
'''
~C'''
defmodule ErrorsTest do
def hello(a, b // nil, c // nil), do: a + b + c
def hello(a, b // nil), do: a + b
def hello(a, b \\ nil, c \\ nil), do: a + b + c
def hello(a, b \\ nil), do: a + b
end
'''
end
@@ -238,7 +242,7 @@ defmodule Kernel.ErrorsTest do
"nofile:1: imported Kernel.&&/2 conflicts with local function",
'''
defmodule ErrorsTest do
1 && 2
def other, do: 1 && 2
def _ && _, do: :error
end
'''
@@ -485,7 +489,7 @@ defmodule Kernel.ErrorsTest do
end
test :invalid_access_protocol_not_keywords do
assert_raise ArgumentError, "expected contents inside brackets to be a keyword list, got: [0]", fn ->
assert_raise ArgumentError, "expected contents inside brackets to be a keyword list or an atom, got: [0]", fn ->
defmodule ErrorsTest do
def sample(Kernel.ErrorsTest.Config[0]), do: true
end
@@ -648,6 +652,10 @@ defmodule Kernel.ErrorsTest do
quote(do: _)
end
defp range do
1..3
end
## Helpers
defp rescue_stacktrace(expr) do
@@ -127,10 +127,10 @@ defmodule Kernel.ExpansionTest do
test "vars: forces variable to exist" do
assert expand(quote do: (var!(a) = 1; var!(a)))
message = %r"expected var a to expand to an existing variable or be a part of a match"
message = ~r"expected var a to expand to an existing variable or be a part of a match"
assert_raise CompileError, message, fn -> expand(quote do: var!(a)) end
message = %r"expected var a \(context Unknown\) to expand to an existing variable or be a part of a match"
message = ~r"expected var a \(context Unknown\) to expand to an existing variable or be a part of a match"
assert_raise CompileError, message, fn -> expand(quote do: var!(a, Unknown)) end
end
@@ -139,13 +139,13 @@ defmodule Kernel.ExpansionTest do
end
test "^: raises outside match" do
assert_raise CompileError, %r"cannot use \^a outside of match clauses", fn ->
assert_raise CompileError, ~r"cannot use \^a outside of match clauses", fn ->
expand(quote do: ^a)
end
end
test "^: raises without var" do
assert_raise CompileError, %r"invalid argument for unary operator \^, expected an existing variable, got: \^1", fn ->
assert_raise CompileError, ~r"invalid argument for unary operator \^, expected an existing variable, got: \^1", fn ->
expand(quote do: ^1 = 1)
end
end
@@ -162,6 +162,11 @@ defmodule Kernel.ExpansionTest do
quote(do: :"Elixir.Hello".a())
end
test "locals: in guards" do
assert expand(quote(do: fn pid when :erlang.==(pid, self) -> pid end)) ==
quote(do: fn pid when :erlang.==(pid, :erlang.self()) -> pid end)
end
test "locals: custom imports" do
assert expand(quote do: (import Kernel.ExpansionTarget; seventeen)) ==
quote do: (import :"Elixir.Kernel.ExpansionTarget", []; 17)
@@ -187,7 +192,7 @@ defmodule Kernel.ExpansionTest do
end
test "anonymous calls: raises on atom base" do
assert_raise CompileError, %r"invalid function call :foo.()", fn ->
assert_raise CompileError, ~r"invalid function call :foo.()", fn ->
expand(quote do: :foo.(a))
end
end
@@ -213,7 +218,7 @@ defmodule Kernel.ExpansionTest do
end
test "remote calls: raises when not required" do
msg = %r"you must require Kernel\.ExpansionTarget before invoking the macro Kernel\.ExpansionTarget\.seventeen/0"
msg = ~r"you must require Kernel\.ExpansionTarget before invoking the macro Kernel\.ExpansionTarget\.seventeen/0"
assert_raise CompileError, msg, fn ->
expand(quote do: Kernel.ExpansionTarget.seventeen)
end
@@ -381,9 +386,13 @@ defmodule Kernel.ExpansionTest do
## Invalid
test "handles invalid expressions" do
assert_raise CompileError, %r"invalid quoted expression: {1, 2, 3}", fn ->
assert_raise CompileError, ~r"invalid quoted expression: {1, 2, 3}", fn ->
expand(quote do: unquote({ 1, 2, 3 }))
end
assert_raise CompileError, ~r"invalid quoted expression: #Function<", fn ->
expand(quote do: unquote({ :sample, fn -> end }))
end
end
## Helpers
+5 -5
View File
@@ -11,11 +11,6 @@ defmodule Kernel.FnTest do
assert (fn +1 -> :ok end).(1) == :ok
end
test "clause with ^" do
x = 1
assert (fn ^x -> :ok; _ -> :error end).(1) == :ok
end
test "capture remote" do
assert (&:erlang.atom_to_list/1).(:a) == 'a'
assert (&Kernel.atom_to_list/1).(:a) == 'a'
@@ -52,6 +47,11 @@ defmodule Kernel.FnTest do
assert (&Kernel.to_string(&1)).(:a) == "a"
end
test "capture operator" do
assert is_function &+/2
assert is_function &(&&/2)
end
test "local partial application" do
assert (&atb(&1, :utf8)).(:a) == "a"
assert (&atb(list_to_atom(&1), :utf8)).('a') == "a"
@@ -21,7 +21,7 @@ defmodule Kernel.MacrosTest do
quote do: 1 + 3
end
defmacro my_macro_with_default(value // 5) do
defmacro my_macro_with_default(value \\ 5) do
quote do: 1 + unquote(value)
end
+9 -3
View File
@@ -27,7 +27,7 @@ defmodule Kernel.QuoteTest do
end
test :unquote_call do
assert quote(do: foo(bar)[:baz])
assert quote(do: foo(bar)[unquote(:baz)]) == quote(do: foo(bar)[:baz])
assert quote(do: unquote(:bar)()) == quote(do: bar())
assert quote(do: unquote(:bar)(1) do 2 + 3 end) == quote(do: bar(1) do 2 + 3 end)
assert quote(do: foo.unquote(:bar)) == quote(do: foo.bar)
@@ -151,6 +151,12 @@ defmodule Kernel.QuoteTest do
test :with_dynamic_opts do
assert quote(dynamic_opts, do: bar(1, 2, 3)) == { :bar, [line: 3], [1, 2, 3] }
end
test :unary_with_integer_precedence do
assert quote(do: +1.foo) == quote(do: (+1).foo)
assert quote(do: @1.foo) == quote(do: (@1).foo)
assert quote(do: &1.foo) == quote(do: (&1).foo)
end
end
## DO NOT MOVE THIS LINE
@@ -180,7 +186,7 @@ defmodule Kernel.QuoteTest.ErrorsTest do
mod = Kernel.QuoteTest.ErrorsTest
file = __ENV__.file |> Path.relative_to_cwd |> String.to_char_list!
assert [{ ^mod, :add, 2, [file: ^file, line: 160] }|_] = System.stacktrace
assert [{ ^mod, :add, 2, [file: ^file, line: 166] }|_] = System.stacktrace
end
test :outside_function_error do
@@ -190,7 +196,7 @@ defmodule Kernel.QuoteTest.ErrorsTest do
mod = Kernel.QuoteTest.ErrorsTest
file = __ENV__.file |> Path.relative_to_cwd |> String.to_char_list!
assert [{ ^mod, _, _, [file: ^file, line: 188] }|_] = System.stacktrace
assert [{ ^mod, _, _, [file: ^file, line: 194] }|_] = System.stacktrace
end
end
@@ -1,6 +1,8 @@
Code.require_file "../test_helper.exs", __DIR__
defrecord BadRange, first: 0, last: 0 do
defrecord Rec, first: 0, last: 0
defrecord BadRec, first: 0, last: 0 do
defoverridable [first: 1]
def first(_) do
@@ -67,11 +69,11 @@ defmodule Kernel.RecordRewriterTest do
end
test "with tuple match" do
clause = clause(fn({ x, y } = { Macro.Env[], Range[] }) -> :foo end)
assert optimize_clause(clause) == { clause, [x: Macro.Env, y: Range], nil }
clause = clause(fn({ x, y } = { Macro.Env[], Rec[] }) -> :foo end)
assert optimize_clause(clause) == { clause, [x: Macro.Env, y: Rec], nil }
clause = clause(fn({ Macro.Env[], y } = { x, Range[] }) -> :foo end)
assert optimize_clause(clause) == { clause, [x: Macro.Env, y: Range], nil }
clause = clause(fn({ Macro.Env[], y } = { x, Rec[] }) -> :foo end)
assert optimize_clause(clause) == { clause, [x: Macro.Env, y: Rec], nil }
end
test "inside body" do
@@ -80,13 +82,13 @@ defmodule Kernel.RecordRewriterTest do
end
test "inside body with variable overridden" do
clause = clause(fn(x = Macro.Env[], y = Range[]) -> y = x end)
assert optimize_clause(clause) == { clause, [x: Macro.Env, y: Range, "y@1": Macro.Env], { Macro.Env, nil } }
clause = clause(fn(x = Macro.Env[], y = Rec[]) -> y = x end)
assert optimize_clause(clause) == { clause, [x: Macro.Env, y: Rec, "y@1": Macro.Env], { Macro.Env, nil } }
end
test "inside body with nested tuple" do
clause = clause(fn(x = Range[]) -> ^x = Range[first: { :hello, 2 }] end)
assert optimize_clause(clause) == { clause, [x: Range], { Range, [nil, { :hello, nil }, nil] } }
clause = clause(fn(x = Rec[]) -> ^x = Rec[first: { :hello, 2 }] end)
assert optimize_clause(clause) == { clause, [x: Rec], { Rec, [nil, { :hello, nil }, nil] } }
end
test "with setelement" do
@@ -102,7 +104,7 @@ defmodule Kernel.RecordRewriterTest do
end
test "conflicting definition" do
clause = clause(fn(x = Macro.Env[]) -> ^x = Range[]; :foo end)
clause = clause(fn(x = Macro.Env[]) -> ^x = Rec[]; :foo end)
assert optimize_clause(clause) == { clause, [x: nil], nil }
end
@@ -132,13 +134,13 @@ defmodule Kernel.RecordRewriterTest do
end
test "inside local call" do
clause = clause(fn -> (x = Macro.Env[]).(y = Range[]) end)
assert optimize_clause(clause) == { clause, [x: Macro.Env, y: Range], nil }
clause = clause(fn -> (x = Macro.Env[]).(y = Rec[]) end)
assert optimize_clause(clause) == { clause, [x: Macro.Env, y: Rec], nil }
end
test "inside remote call" do
clause = clause(fn -> x.call(y = Range[]) end)
assert optimize_clause(clause) == { clause, [y: Range], nil }
clause = clause(fn -> x.call(y = Rec[]) end)
assert optimize_clause(clause) == { clause, [y: Rec], nil }
end
test "inside list comprehension" do
@@ -161,19 +163,19 @@ defmodule Kernel.RecordRewriterTest do
clause = clause(fn -> case something do 1 -> x = Macro.Env[]; 2 -> x = Macro.Env[] end end)
assert optimize_clause(clause) == { clause, [x: Macro.Env], { Macro.Env, nil } }
clause = clause(fn -> case something do 1 -> x = Macro.Env[]; 2 -> x = Range[] end end)
clause = clause(fn -> case something do 1 -> x = Macro.Env[]; 2 -> x = Rec[] end end)
assert optimize_clause(clause) == { clause, [x: nil], nil }
clause = clause(fn -> case something do x = Macro.Env[] -> x; x = Range[] -> x; _ -> :ok end end)
clause = clause(fn -> case something do x = Macro.Env[] -> x; x = Rec[] -> x; _ -> :ok end end)
assert optimize_clause(clause) == { clause, [], nil }
end
test "inside case with nested tuple" do
clause = clause(fn -> case something do x = Range[first: { :foo, 2 }] -> x = x; Range[] = x -> x = x end end)
assert optimize_clause(clause) == { clause, ["x@4": Range], { Range, nil } }
clause = clause(fn -> case something do x = Rec[first: { :foo, 2 }] -> x = x; Rec[] = x -> x = x end end)
assert optimize_clause(clause) == { clause, ["x@4": Rec], { Rec, nil } }
clause = clause(fn -> case something do x = Range[first: { :foo, 2 }] -> x = x; Range[first: { :foo, 2 }] = x -> x = x end end)
assert optimize_clause(clause) == { clause, ["x@4": Range], { Range, [nil, { :foo, nil }, nil] } }
clause = clause(fn -> case something do x = Rec[first: { :foo, 2 }] -> x = x; Rec[first: { :foo, 2 }] = x -> x = x end end)
assert optimize_clause(clause) == { clause, ["x@4": Rec], { Rec, [nil, { :foo, nil }, nil] } }
end
test "empty receive" do
@@ -188,7 +190,7 @@ defmodule Kernel.RecordRewriterTest do
clause = clause(fn -> receive do 1 -> x = Macro.Env[]; 2 -> x = Macro.Env[] end end)
assert optimize_clause(clause) == { clause, [x: Macro.Env], { Macro.Env, nil } }
clause = clause(fn -> receive do 1 -> x = Macro.Env[]; 2 -> x = Range[] end end)
clause = clause(fn -> receive do 1 -> x = Macro.Env[]; 2 -> x = Rec[] end end)
assert optimize_clause(clause) == { clause, [x: nil], nil }
end
@@ -196,7 +198,7 @@ defmodule Kernel.RecordRewriterTest do
clause = clause(fn -> receive do 1 -> x = Macro.Env[]; after 2 -> x = Macro.Env[]; end end)
assert optimize_clause(clause) == { clause, [x: Macro.Env], { Macro.Env, nil } }
clause = clause(fn -> receive do 1 -> x = Macro.Env[]; after 2 -> x = Range[]; end end)
clause = clause(fn -> receive do 1 -> x = Macro.Env[]; after 2 -> x = Rec[]; end end)
assert optimize_clause(clause) == { clause, [x: nil], nil }
end
@@ -215,54 +217,54 @@ defmodule Kernel.RecordRewriterTest do
test "getter call is rewriten" do
{ clause, rewriten } =
{ clause(fn(x = Range[]) -> x.first end), clause(fn(x = Range[]) -> :erlang.element(2, x) end) }
{ clause(fn(x = Rec[]) -> x.first end), clause(fn(x = Rec[]) -> :erlang.element(2, x) end) }
assert optimize_clause(clause) == { rewriten, [x: Range], nil }
assert optimize_clause(clause) == { rewriten, [x: Rec], nil }
end
test "direct getter call is rewriten" do
{ clause, rewriten } =
{ clause(fn() -> Range[].first end), clause(fn() -> :erlang.element(2, Range[]) end) }
{ clause(fn() -> Rec[].first end), clause(fn() -> :erlang.element(2, Rec[]) end) }
assert optimize_clause(clause) == { rewriten, [], nil }
end
test "setter call is rewriten" do
{ clause, rewriten } =
{ clause(fn(x = Range[]) -> x.first(:first) end), clause(fn(x = Range[]) -> :erlang.setelement(2, x, :first) end) }
{ clause(fn(x = Rec[]) -> x.first(:first) end), clause(fn(x = Rec[]) -> :erlang.setelement(2, x, :first) end) }
assert optimize_clause(clause) == { rewriten, [x: Range], { Range, nil } }
assert optimize_clause(clause) == { rewriten, [x: Rec], { Rec, nil } }
end
test "nested setter call is rewriten" do
{ clause, rewriten } =
{ clause(fn(x = Range[]) -> x.first(:first).last(:last) end), clause(fn(x = Range[]) -> :erlang.setelement(3, :erlang.setelement(2, x, :first), :last) end) }
{ clause(fn(x = Rec[]) -> x.first(:first).last(:last) end), clause(fn(x = Rec[]) -> :erlang.setelement(3, :erlang.setelement(2, x, :first), :last) end) }
assert optimize_clause(clause) == { rewriten, [x: Range], { Range, nil } }
assert optimize_clause(clause) == { rewriten, [x: Rec], { Rec, nil } }
end
test "updater call is rewriten" do
{ clause, rewriten } =
{ clause(fn(x = Range[]) -> x.update_first(&(&1 + 1)) end), clause(fn(x = Range[]) -> Range.update_first(&(&1 + 1), x) end) }
assert optimize_clause(clause) == { rewriten, [x: Range], { Range, nil } }
{ clause(fn(x = Rec[]) -> x.update_first(&(&1 + 1)) end), clause(fn(x = Rec[]) -> Rec.update_first(&(&1 + 1), x) end) }
assert optimize_clause(clause) == { rewriten, [x: Rec], { Rec, nil } }
end
test "update call is rewriten" do
{ clause, rewriten } =
{ clause(fn(x = Range[]) -> x.update(first: 1) end), clause(fn(x = Range[]) -> Range.update([first: 1], x) end) }
assert optimize_clause(clause) == { rewriten, [x: Range], { Range, nil } }
{ clause(fn(x = Rec[]) -> x.update(first: 1) end), clause(fn(x = Rec[]) -> Rec.update([first: 1], x) end) }
assert optimize_clause(clause) == { rewriten, [x: Rec], { Rec, nil } }
end
test "fallback for unknown fields" do
{ clause, rewriten } =
{ clause(fn(x = Range[]) -> x.unknown(1, 2) end), clause(fn(x = Range[]) -> Range.unknown(1, 2, x) end) }
assert optimize_clause(clause) == { rewriten, [x: Range], nil }
{ clause(fn(x = Rec[]) -> x.unknown(1, 2) end), clause(fn(x = Rec[]) -> Rec.unknown(1, 2, x) end) }
assert optimize_clause(clause) == { rewriten, [x: Rec], nil }
end
test "fallback for rewriten fields" do
{ clause, rewriten } =
{ clause(fn(x = BadRange[]) -> x.first end), clause(fn(x = BadRange[]) -> BadRange.first(x) end) }
assert optimize_clause(clause) == { rewriten, [x: BadRange], nil }
{ clause(fn(x = BadRec[]) -> x.first end), clause(fn(x = BadRec[]) -> BadRec.first(x) end) }
assert optimize_clause(clause) == { rewriten, [x: BadRec], nil }
end
test "noop for not records fields" do
@@ -271,7 +273,7 @@ defmodule Kernel.RecordRewriterTest do
end
test "noop for conflicting inference" do
clause = clause(fn(x = Macro.Env[]) -> ^x = Range[]; x.first end)
clause = clause(fn(x = Macro.Env[]) -> ^x = Rec[]; x.first end)
assert optimize_clause(clause) == { clause, [x: nil], nil }
end
end
@@ -169,7 +169,7 @@ defmodule Kernel.RescueTest do
test :badarity_error do
fun = fn(x) -> x end
string = "bad arity error: #{inspect(fun)} called with (1, 2)"
string = "#{inspect(fun)} with arity 1 called with 2 arguments (1, 2)"
result = try do
fun.(1, 2)
@@ -194,25 +194,25 @@ defmodule Kernel.RescueTest do
test :badmatch_error do
x = :example
result = try do
^x = :other
^x = zero(0)
rescue
x in [MatchError] -> x.message
end
assert result == "no match of right hand side value: :other"
assert result == "no match of right hand side value: 0"
end
test :case_clause_error do
x = :example
result = try do
case :other do
case zero(0) do
^x -> nil
end
rescue
x in [CaseClauseError] -> x.message
end
assert result == "no case clause matching: :other"
assert result == "no case clause matching: 0"
end
test :try_clause_error do
+53 -51
View File
@@ -4,105 +4,107 @@ defmodule Kernel.SigilsTest do
use ExUnit.Case, async: true
test :sigil_s do
assert %s(foo) == "foo"
assert %s(f#{:o}o) == "foo"
assert %s(f\no) == "f\no"
assert ~s(foo) == "foo"
assert ~s(f#{:o}o) == "foo"
assert ~s(f\no) == "f\no"
end
test :sigil_s_with_heredoc do
assert " foo\n\n" == %s"""
assert " foo\n\n" == ~s"""
f#{:o}o\n
"""
end
test :sigil_S do
assert %S(foo) == "foo"
assert %S[foo] == "foo"
assert %S{foo} == "foo"
assert %S'foo' == "foo"
assert %S"foo" == "foo"
assert %S|foo| == "foo"
assert %S(f#{o}o) == "f\#{o}o"
assert %S(f\no) == "f\\no"
assert ~S(foo) == "foo"
assert ~S[foo] == "foo"
assert ~S{foo} == "foo"
assert ~S'foo' == "foo"
assert ~S"foo" == "foo"
assert ~S<foo> == "foo"
assert ~S/foo/ == "foo"
assert ~S|foo| == "foo"
assert ~S(f#{o}o) == "f\#{o}o"
assert ~S(f\no) == "f\\no"
end
test :sigil_S_with_heredoc do
assert " f\#{o}o\\n\n" == %S"""
assert " f\#{o}o\\n\n" == ~S"""
f#{o}o\n
"""
end
test :sigil_c do
assert %c(foo) == 'foo'
assert %c(f#{:o}o) == 'foo'
assert %c(f\no) == 'f\no'
assert ~c(foo) == 'foo'
assert ~c(f#{:o}o) == 'foo'
assert ~c(f\no) == 'f\no'
end
test :sigil_C do
assert %C(foo) == 'foo'
assert %C[foo] == 'foo'
assert %C{foo} == 'foo'
assert %C'foo' == 'foo'
assert %C"foo" == 'foo'
assert %C|foo| == 'foo'
assert %C(f#{o}o) == 'f\#{o}o'
assert %C(f\no) == 'f\\no'
assert ~C(foo) == 'foo'
assert ~C[foo] == 'foo'
assert ~C{foo} == 'foo'
assert ~C'foo' == 'foo'
assert ~C"foo" == 'foo'
assert ~C|foo| == 'foo'
assert ~C(f#{o}o) == 'f\#{o}o'
assert ~C(f\no) == 'f\\no'
end
test :sigil_w do
assert %w() == []
assert %w(foo bar baz) == ["foo", "bar", "baz"]
assert %w(foo #{:bar} baz) == ["foo", "bar", "baz"]
assert ~w() == []
assert ~w(foo bar baz) == ["foo", "bar", "baz"]
assert ~w(foo #{:bar} baz) == ["foo", "bar", "baz"]
assert %w(
assert ~w(
foo
bar
baz
) == ["foo", "bar", "baz"]
assert %w(foo bar baz)s == ["foo", "bar", "baz"]
assert %w(foo bar baz)a == [:foo, :bar, :baz]
assert %w(foo bar baz)c == ['foo', 'bar', 'baz']
assert ~w(foo bar baz)s == ["foo", "bar", "baz"]
assert ~w(foo bar baz)a == [:foo, :bar, :baz]
assert ~w(foo bar baz)c == ['foo', 'bar', 'baz']
bad_modifier = quote do: %w(foo bar baz)x
bad_modifier = quote do: ~w(foo bar baz)x
assert ArgumentError[] = catch_error(Code.eval_quoted(bad_modifier))
assert %w(Foo Bar)a == [:"Foo", :"Bar"]
assert %w(Foo.#{Bar}.Baz)a == [:"Foo.Elixir.Bar.Baz"]
assert %w(Foo.Bar)s == ["Foo.Bar"]
assert %w(Foo.#{Bar})c == ['Foo.Elixir.Bar']
assert ~w(Foo Bar)a == [:"Foo", :"Bar"]
assert ~w(Foo.#{Bar}.Baz)a == [:"Foo.Elixir.Bar.Baz"]
assert ~w(Foo.Bar)s == ["Foo.Bar"]
assert ~w(Foo.#{Bar})c == ['Foo.Elixir.Bar']
# Ensure it is fully expanded at compile time
assert Macro.expand(quote(do: %w(a b c)a), __ENV__) == [:a, :b, :c]
assert Macro.expand(quote(do: ~w(a b c)a), __ENV__) == [:a, :b, :c]
end
test :sigil_W do
assert %W(foo #{bar} baz) == ["foo", "\#{bar}", "baz"]
assert ~W(foo #{bar} baz) == ["foo", "\#{bar}", "baz"]
assert %W(
assert ~W(
foo
bar
baz
) == ["foo", "bar", "baz"]
assert %W(foo bar baz)s == ["foo", "bar", "baz"]
assert %W(foo bar baz)a == [:foo, :bar, :baz]
assert %W(foo bar baz)c == ['foo', 'bar', 'baz']
assert ~W(foo bar baz)s == ["foo", "bar", "baz"]
assert ~W(foo bar baz)a == [:foo, :bar, :baz]
assert ~W(foo bar baz)c == ['foo', 'bar', 'baz']
bad_modifier = quote do: %W(foo bar baz)x
bad_modifier = quote do: ~W(foo bar baz)x
assert ArgumentError[] = catch_error(Code.eval_quoted(bad_modifier))
assert %W(Foo #{Bar})a == [:"Foo", :"\#{Bar}"]
assert %W(Foo.Bar.Baz)a == [:"Foo.Bar.Baz"]
assert ~W(Foo #{Bar})a == [:"Foo", :"\#{Bar}"]
assert ~W(Foo.Bar.Baz)a == [:"Foo.Bar.Baz"]
end
test :sigils_matching do
assert %s(f(o)o) == "f(o)o"
assert %s(f\(oo) == "f(oo"
assert %s(fo\)o) == "fo)o"
assert %s(f\(o\)o) == "f(o)o"
assert ~s(f(o)o) == "f(o)o"
assert ~s(f\(oo) == "f(oo"
assert ~s(fo\)o) == "fo)o"
assert ~s(f\(o\)o) == "f(o)o"
assert %s(f[oo) == "f[oo"
assert %s(fo]o) == "fo]o"
assert ~s(f[oo) == "f[oo"
assert ~s(fo]o) == "fo]o"
end
end

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