Compare commits

...
151 Commits
Author SHA1 Message Date
José Valim ed27611f48 Release v0.8.2 2013-04-20 17:45:21 -06:00
José Valim fd29173d6d Also dispatch to local macros 2013-04-16 12:09:38 -07:00
José Valim 963efc4593 Do not import IEx.Helpers since we delegate to them, closes, closes #957 2013-04-16 12:04:29 -07:00
José Valim cfc21d04c6 Merge pull request #958 from yrashk/gen-event-behaviour
Implement GenEvent.Behaviour
2013-04-16 11:01:42 -07:00
Yurii Rashkovskii 507fda525b Implement GenEvent.Behaviour 2013-04-16 10:26:42 -07:00
Yurii Rashkovskii e3c6cc9b85 IEx's h helper should not render ANSI codes in the documentation
pieces, otherwise it becomes impossible to read IO.ANSI documentation
2013-04-16 09:57:04 -07:00
José Valim c78c8ddf08 Improve some docs 2013-04-15 23:04:51 -07:00
José Valim 8f34d58829 Merge pull request #952 from alco/enum-iterator-docfix
Make Enum.Iterator's docstring more descriptive
2013-04-15 16:20:21 -07:00
Alexei Sholik 5b9c3e7aa6 Make Enum.Iterator's docstring more descriptive 2013-04-16 02:09:32 +03:00
José Valim c125636535 Ensure the macro system respects imports hygiene, closes #875 2013-04-13 18:25:43 -07:00
José Valim 531bd8da0c Ensure hygiene also works with directl calls to Kernel.function 2013-04-13 17:19:19 -07:00
José Valim a4d8fc2893 Ensure hygiene applies to function and partial applications 2013-04-13 17:19:19 -07:00
José Valim b96c3c52ae Remove check_require in favor of a meta tag 2013-04-13 17:19:19 -07:00
José Valim 3f7b867740 Refactor elixir_dispatch to rely more on elixir_scope 2013-04-13 17:19:19 -07:00
José Valim f2e78d3d39 function(:is_atom, 1) is deprecated in favor of function(is_atom/1)
This is required so we can identity the proper function/arity
from quoting and properly hygienize function retrievals.
2013-04-13 17:19:19 -07:00
José Valim 5d1d3f23e9 Merge pull request #922 from bellthoven/fix_function_calls
Allow 'foo . (42)' calls
2013-04-12 18:44:29 -07:00
José Valim 01cc9ed0ec Uncomment features related to unquote fragments 2013-04-12 18:14:34 -07:00
José Valim 4096b31cfe Ensure the proper scope is passed on args call, closes #949 2013-04-12 18:08:56 -07:00
José Valim 49a5b8f28c Properly quote | expressions, related to #944 2013-04-12 08:49:44 -07:00
Yurii Rashkovskii f49c351bb4 Minor defrecord documentation improvement 2013-04-12 05:23:47 -07:00
Yurii Rashkovskii aa45edc82c Mention Record.import in defrecord documentation 2013-04-12 04:30:10 -07:00
José Valim 2c36d8f625 Do less work at runtime for %w/%W 2013-04-11 22:49:30 -07:00
José Valim d3238b8778 Merge pull request #870 from devinus/words-with-modifiers
Add support for %w and %W (words) sigils with return type modifiers
2013-04-11 22:45:50 -07:00
José Valim ec69bdfbbe Merge pull request #945 from yrashk/record-import
Implement Record.import macro that allows using private macros on public...
2013-04-11 22:09:53 -07:00
José Valim 3d66ab35b5 Improve doctest expectation messages 2013-04-11 17:20:43 -07:00
Yurii Rashkovskii 8d2034f07c Implement Record.import macro that allows using private macros on public records 2013-04-11 16:28:44 -07:00
Devin Torres b82b51a870 Add support for %w and %W (words) sigils with return type modifiers 2013-04-11 15:32:27 -05:00
José Valim be5e20f7d4 Extract import functionality 2013-04-11 08:55:34 -07:00
Yurii Rashkovskii 26599e7812 Improve elixir application doctest coverage 2013-04-11 08:25:17 -07:00
José Valim 52aa1fd068 Compile process upfront and do not rely on it in the error handler 2013-04-11 08:21:40 -07:00
José Valim 728d08c3bd Revert "Enum.split_while did not behave as advertised and provided the wrong API. Provide Enum.split_with instead"
This reverts commit 1ffda62f2e.
2013-04-11 08:08:01 -07:00
José Valim 84cd94c2e5 Update CHANGELOG 2013-04-11 06:53:25 -07:00
José Valim b9ef56df27 Merge pull request #859 from yrashk/insert-delete-element
Add insert_elem/3 and delete_elem/2 functions
2013-04-11 06:41:00 -07:00
José Valim 1ffda62f2e Enum.split_while did not behave as advertised and provided the wrong API. Provide Enum.split_with instead 2013-04-11 06:37:30 -07:00
Yurii Rashkovskii 0fad1883df Add insert_elem/3 and delete_elem/2 functions, deprecate setelem
in favour of set_elem

For Erlang versions prior to R16B, emulate corresponding BIFs
2013-04-11 05:11:23 -07:00
Yurii Rashkovskii d4eda432ec Report compile-time doctest errors during test runtime 2013-04-11 04:22:44 -07:00
Yurii Rashkovskii 71831d654c Minor doctest source code improvement 2013-04-11 04:06:11 -07:00
Yurii Rashkovskii 13c334f70b Fix expectation error reporting in doctests 2013-04-11 04:02:49 -07:00
Yurii Rashkovskii 875b4c7ecf Improve doctest exception tracking and reporting 2013-04-11 03:52:20 -07:00
Yurii Rashkovskii bef41d7b55 Merge pull request #931 from yrashk/doctest-import
Add import: option to doctests
2013-04-11 03:33:51 -07:00
Yurii Rashkovskii 6512fcb06e Add import: option to doctests. It is false by default. 2013-04-11 03:29:32 -07:00
José Valim c2d1699248 Improve error messages on else do grammar mistakes, closes #888 2013-04-10 20:16:25 -07:00
José Valim e412ce6b22 Can defined a nested module called Elixir, closes #858 2013-04-10 16:32:30 -07:00
José Valim e01f958b1a Allow match syntax on nested records, closes #871 2013-04-10 16:22:38 -07:00
José Valim 35d0edb3b1 Make it clear module attributes are prepended, closes #872 2013-04-10 16:16:03 -07:00
José Valim ca456200cc Define Record.options for each record with the allowed options
Closes #916, #857
2013-04-10 16:13:50 -07:00
José Valim fcc7fc4351 Merge pull request #938 from alexrp/master
Clarify import conflict semantics.
2013-04-10 15:48:23 -07:00
José Valim ca0250a9d7 Refactor find_dispatch to loop data functions and macros lists just once 2013-04-10 15:47:37 -07:00
alexrp f859c9d350 Clarify import conflict semantics. 2013-04-11 00:32:57 +02:00
José Valim a576540159 Merge pull request #937 from alexrp/master
Only error on ambiguous imported function calls, not on the imports themselves.
2013-04-10 15:29:09 -07:00
alexrp a34f38ee40 Only error on ambiguous imported function calls, not on the imports themselves. Fixes #788. 2013-04-11 00:18:09 +02:00
José Valim de06764691 apply is a macro 2013-04-10 15:01:08 -07:00
Yurii Rashkovskii 4d0d7e8f2a If a doctest fails with an unexpected exception, test failure message
should contain the original expression that caused the exception to
be raised
2013-04-10 06:13:15 -07:00
José Valim 025c425514 Split doctests that expect an exception from the ones who does not 2013-04-09 23:58:43 -07:00
José Valim 48da7e8ed7 Allow doctests to be defined dynamically 2013-04-09 23:31:09 -07:00
José Valim f8c0729f3b Split doctest structure from other kernel tests 2013-04-09 19:14:55 -07:00
José Valim 0c50664817 Improve deprecation of :+.() and friends 2013-04-09 19:14:55 -07:00
Yurii Rashkovskii e7b329dbe2 Undoctest Kernel.float_to_binary/2 test as it can't be tested on R15B* 2013-04-09 19:10:49 -07:00
José Valim 5581c8255f Merge pull request #930 from yrashk/doctest-kernel
Doctest kernel
2013-04-09 18:59:29 -07:00
Yurii Rashkovskii 7be936eb6d Convert Kernel to doctests 2013-04-09 18:54:13 -07:00
Yurii Rashkovskii 26e3ec644b Somewhat better line reporting on doctest failures
The line isn't precise (because we don't know the line of the @doc
or @moduledoc), but it points to the line where the definition starts
2013-04-09 18:48:15 -07:00
Yurii Rashkovskii 29ea1df744 Capture the offending expression in DocTest if we can't AST it 2013-04-09 18:12:58 -07:00
Yurii Rashkovskii 1334849ff7 DocTest: add a note on using standard output 2013-04-09 17:56:29 -07:00
Yurii Rashkovskii dbf711b519 Add support for exception tracking in DocTest 2013-04-09 17:42:54 -07:00
Yurii Rashkovskii 0b0bbdee5e Initial ExUnit.DocTest implementation 2013-04-09 13:42:19 -07:00
José Valim 7b9f2bd298 {}, [] and friends are purely deprecated 2013-04-07 21:25:29 -06:00
Gustavo Dutra 9016a18fa5 Allow multiple lines 'foo . ()' calls 2013-04-07 15:00:54 -03:00
Gustavo Dutra bd6f27a5d5 Allow 'foo . (42)' calls 2013-04-07 01:38:30 -03:00
José Valim f9e74f944e Add Macro.escape_quoted instead of Macro.escape with options 2013-04-06 15:13:32 -06:00
José Valim 3a98369842 Split IEx introspection functions into its own module 2013-04-06 14:41:40 -06:00
José Valim a963269244 Fix a bug on Module.to_binary(Elixir), closes #920 2013-04-06 14:35:14 -06:00
Yurii Rashkovskii 0463407cbf Fix an example in lc documentation 2013-04-06 13:14:39 -07:00
José Valim c59be9b4a8 Make s and t helpers private too 2013-04-06 09:43:01 -06:00
José Valim 8d6aa70acb Make h/2 and h/3 private in IEx.Helpers 2013-04-06 09:37:39 -06:00
José Valim 4612877e78 Improvements to File.ls implementation 2013-04-06 08:49:33 -06:00
José Valim c0fbea8e9b Merge pull request #909 from devinus/unicode-split
Unicode split
2013-04-06 07:32:36 -07:00
José Valim c3e50546ec Merge pull request #899 from n1rvana/master
Added File.ls & File.ls!
2013-04-06 07:30:59 -07:00
José Valim b954d53585 Merge pull request #907 from devinus/fix-record-guard-for-tuples
Fix is_record tests to check tuple_size before accessing the first eleme...
2013-04-06 07:30:36 -07:00
José Valim 2f848953df Merge pull request #918 from alco/iex-h-fix
Make h() in IEx look at Kernel.SpecialForms too
2013-04-06 07:29:45 -07:00
Alexei Sholik 24a49295ef Replace length check with a check against []
length() is not constant time
2013-04-06 12:24:29 +03:00
Alexei Sholik 02edfed204 Remove trailing whitespace in iex/helpers.ex 2013-04-06 01:51:08 +03:00
Alexei Sholik 75fcdac740 Check also Kernel.SpecialForms in h(). Fixes #906 2013-04-06 01:51:08 +03:00
Yurii Rashkovskii 538a29ef70 Add documentation on @record_fields and @record_types to defrecord 2013-04-03 14:54:26 -07:00
José Valim 15ac6e947a Improve docs for function 2013-04-03 08:42:03 -06:00
José Valim 01d5701419 Make record types in record public as well 2013-04-01 14:53:17 -06:00
José Valim 0f91e24f31 Improve documentation for the String module 2013-03-30 11:35:39 -04:00
José Valim 7e9c5c97b2 Merge pull request #914 from yrashk/binary_to_updates
Update binary_to_ macros to use binary_to_ functions in R16B.
2013-03-29 14:26:55 -07:00
Yurii Rashkovskii a114702967 Include more recent Erlang versions into Travis-CI configuration 2013-03-29 14:19:47 -07:00
Yurii Rashkovskii 25bc992e76 Update binary_to_ macros to use binary_to_ functions in R16B.
Also add support for float_to_binary/2
2013-03-29 14:01:32 -07:00
Devin Torres 196358525a Unicode split 2013-03-27 11:31:52 -05:00
José Valim f7cad1cffc Improve error messages 2013-03-27 09:54:00 -06:00
Devin Torres fee5c5d762 Fix is_record tests to check tuple_size before accessing the first element 2013-03-26 17:17:41 -05:00
José Valim dc0897ec03 Also allow unquoting on setup and teardown callbacks 2013-03-26 14:07:13 -06:00
José Valim 369fd7506c Ensure unquotes are respected in the test macro 2013-03-26 13:36:54 -06:00
José Valim c6ff84e2b3 Improve docs and exceptions for regexes 2013-03-25 21:27:49 -06:00
José Valim 0e47d8cbdc Use at least 2 runners regardless of the number of schedulers 2013-03-25 09:16:20 -06:00
Nirvana Platform 34da5f883b File.ls & ls! tests now cover non-existent directory situation 2013-03-24 12:30:10 -03:00
Nirvana Platform 1c2d41974c renamed to ls, added unit tests 2013-03-23 21:08:05 -03:00
Nirvana Project 4ad1ca375b Fixed File.listdir! to return the list, without wrapping it in {:ok, _} 2013-03-23 17:52:05 -03:00
Nirvana Project 27d958063a Added File.listdir! variation 2013-03-23 17:42:14 -03:00
Nirvana Project a8c89e3703 Added File.listdir API 2013-03-23 17:33:42 -03:00
José Valim 0872fdd733 Revert "IEx autocompletion should show all exported functions, not only documented ones"
This reverts commit 3ffc5e9e84.

Closes #879.
2013-03-17 21:14:00 -06:00
José Valim e27707ec1a Add Path.t (for input types) and Path.r (for output), closes #873 2013-03-17 16:48:50 -06:00
José Valim 5a84acf607 Fail if an application can't be started, closes #869 2013-03-17 15:32:47 -06:00
José Valim 5d00a9ff58 Support --help and -h in elixir and iex, closes #854 2013-03-17 14:24:53 -06:00
José Valim 6024014482 Improve error message on unclosed interpolation/string, closes #794 2013-03-17 13:57:20 -06:00
José Valim 01d7e2124c Do not run cover tests before R16 2013-03-17 13:16:02 -06:00
José Valim 179b18a970 Add cover support to mix test 2013-03-17 13:06:23 -06:00
José Valim 618f7822b5 Fix the build 2013-03-17 12:45:12 -06:00
José Valim 866558710c Improve docs for Dict 2013-03-17 12:17:57 -06:00
José Valim 81d4063fe9 Add pwd and cd helpers to IEx 2013-03-17 11:55:47 -06:00
José Valim 2c5fa31ed7 Ensure Macro.escape also respects nested quotes 2013-03-11 08:17:53 -06:00
José Valim 238e7f4bc2 Use unquote instead of @ in HashDict 2013-03-10 22:07:20 -06:00
José Valim f10171bfd2 Clean up Unicode using the new unquote fragments 2013-03-10 22:01:08 -06:00
José Valim ecb23a4b42 Allow dynamic dispatch names on defdelegate 2013-03-10 21:44:18 -06:00
José Valim ff825b89bc Support unquoting on Macro.escape 2013-03-10 21:26:35 -06:00
José Valim 3788bf4a5e Fix formatting and examples in CHANGELOG 2013-03-10 18:24:29 -06:00
José Valim 6e8bcd9245 Update CHANGELOG 2013-03-10 18:12:50 -06:00
José Valim 2817b2ee90 Allow unquote in def*/2
This makes meta-programming in Elixir much easier and more friendly.
For example, here is how to convert a keywords argument into a bunch
of functions:

    Enum.each [foo: 1, bar: 2, baz: 3], fn { k, v } ->
      def unquote(k)(), do: unquote(v)
    end
2013-03-10 17:59:04 -06:00
José Valim affc44d020 unquote now applies to the closest quote 2013-03-10 17:23:02 -06:00
José Valim b5e58473d9 Merge pull request #877 from kimshrier/file_comment_fixes
change comments to match the code
2013-03-08 06:58:03 -08:00
Kim Shrier 30fa89e270 change comments to match the code 2013-03-07 23:37:54 -07:00
José Valim 9084d2a4c4 Merge pull request #874 from kimshrier/new_test_parenthesis_fix
add some missing parenthesis in an assert
2013-03-05 07:05:24 -08:00
Kim Shrier 592d7d9338 add some missing parenthesis in an assert 2013-03-05 01:14:37 -07:00
José Valim e811f0a538 Add space and parenthesis were appropriate 2013-03-04 11:54:57 -07:00
José Valim 9ff534c6d1 Fix logic mistake in cond example 2013-03-04 08:29:14 -07:00
José Valim bc163abe1f Merge pull request #867 from yrashk/defrecord-result
defrecord should return body evaluation result
2013-02-28 07:45:26 -08:00
Yurii Rashkovskii 6093aa0384 defrecord should return body evaluation result
Closes #861
2013-02-28 07:38:56 -08:00
José Valim 4cc92aaf8d Improve formatting for assert left = right 2013-02-28 08:14:57 -07:00
José Valim e1e3bff3fb Show a better error message in cases of possible deadlocks 2013-02-27 16:44:24 -07:00
Yurii Rashkovskii 824168a163 ANSIfy iex helpers' output 2013-02-27 08:01:31 -08:00
Yurii Rashkovskii 07a2283845 Merge pull request #860 from yrashk/ansify-iex
Change the color of result printouts in IEx
2013-02-27 07:44:05 -08:00
Yurii Rashkovskii ae6a51fc83 Change the color of result printouts in IEx 2013-02-27 07:36:33 -08:00
José Valim 28ebdad467 Do not depend on terminal values for IO.ANSI tests 2013-02-26 20:08:31 -07:00
José Valim 98e488a52d Aliases defined inside macros should be carried over 2013-02-26 14:18:37 -07:00
Yurii Rashkovskii 0e39a552e6 Reimplement Binary.Inspect.Utils.escape/2 without bitstr comprehension
The reason for this is that

 `bc <<h, t :: binary>> inbits other`

is seemingly an unknown/undocumented "feature" that some consider
being a bug.

Besides, this new implementation seems to be roughly 1.5-2 times
faster.
2013-02-25 11:36:05 -08:00
Yurii Rashkovskii 88d915ffb6 Suppress an unnecessary warning 2013-02-25 09:50:48 -08:00
Yurii Rashkovskii 18f127d69b Minor CHANGELOG fix 2013-02-25 09:35:47 -08:00
José Valim ae0cad4ec3 Update CHANGELOG 2013-02-25 09:57:55 -07:00
José Valim 22da62600e Record rewriter should also optimize in-module calls 2013-02-25 09:55:41 -07:00
Yurii Rashkovskii 81aae93726 Binary.Inspect.Utils.escape/2 shouldn't escape a standalone hash (#)
Closes #852
2013-02-24 16:55:08 -08:00
Yurii Rashkovskii 62eddd822d Reimplement Binary.Inspect.Utils.escape/2 with bitstr comprehension 2013-02-24 15:43:33 -08:00
Yurii Rashkovskii e7274fdb51 Better error reporting for invalid bit string comprehension generators 2013-02-24 15:30:22 -08:00
José Valim c10b5e7e4f Fix bug in Path.expand 2013-02-21 20:10:50 -07:00
José Valim b396696c5c Use latest ExDoc 2013-02-19 12:14:45 -07:00
José Valim 6bf3ca1e89 Update CHANGELOG 2013-02-19 09:18:09 -07:00
José Valim 6a24bc4ef3 Add elapsed times to ExUnit runs 2013-02-19 08:57:42 -07:00
Yurii Rashkovskii 24ca9c0af5 Print a warning if ExUnit user config contains an invalid value, and assume an empty user config
This change prevents ExUnit from crashing when trying to merge invalid options and warns the user about the problem early on
2013-02-19 06:24:28 -08:00
José Valim 8a58722dd0 ANSIfy ExUnit formatter 2013-02-18 17:57:43 -07:00
Yurii Rashkovskii 5844698f39 Fix typo in RELEASE.md 2013-02-18 06:57:34 -08:00
José Valim b55a917048 Add more info to release process 2013-02-17 21:48:07 -07:00
111 changed files with 3895 additions and 1989 deletions
+2
View File
@@ -6,6 +6,8 @@ notifications:
- jose.valim@plataformatec.com.br
- yrashk@gmail.com
otp_release:
- R16B
- R15B03
- R15B02
- R15B01
- R15B
+40
View File
@@ -1,3 +1,43 @@
# v0.8.2 (2013-04-20)
* enhancements
* [ExUnit] Use ANSI escape codes in CLI output
* [ExUnit] Include suite run time on CLI results
* [ExUnit] Add support to doctests, allowing test cases to be generated from code samples
* [File] Add `File.ls` and `File.ls!`
* [IEx] Support `pwd` and `cd` helpers
* [Kernel] Better error reporting for invalid bitstring generators
* [Kernel] Improve meta-programming by allowing `unquote` on `def/2`, `defp/2`, `defmacro/2` and `defmacrop/2`
* [Kernel] Add support to R16B new functions: `insert_elem/3` and `delete_elem/2`
* [Kernel] Import conflicts are now lazily handled. If two modules import the same functions, it will fail only if the function is invoked
* [Mix] Support `--cover` on mix test and `test_coverage` on Mixfiles
* [Record] Each record now provides `Record.options` with the options supported by its `new` and `update` functions
* bug fix
* [Binary] inspect no longer escapes standalone hash `#`
* [IEx] The `h` helper can now retrieve docs for special forms
* [Kernel] Record optimizations were not being triggered in functions inside the record module
* [Kernel] Aliases defined inside macros should be carried over
* [Kernel] Fix a bug where nested records could not use the Record[] syntax
* [Path] Fix a bug on `Path.expand` when expanding paths starting with `~`
* deprecations
* [Kernel] `setelem/3` is deprecated in favor of `set_elem/3`
* [Kernel] `function(:is_atom, 1)` is deprecated in favor of `function(is_atom/1)`
* backwards incompatible changes
* [Kernel] `unquote` now only applies to the closest quote. If your code contains a quote that contains another quote that calls unquote, it will no longer work. Use `Macro.escape` instead and pass your quoted contents up in steps, for example:
quote do
quote do: unquote(x)
end
should become:
quote do
unquote(Macro.escape(x))
end
# v0.8.1 (2013-02-17)
* enhancements
+7 -3
View File
@@ -97,7 +97,7 @@ docs: compile
mkdir -p ebin
rm -rf docs
cp -R -f lib/*/ebin/*.beam ./ebin
bin/elixir ../exdoc/bin/exdoc
bin/elixir ../exdoc/bin/exdoc "Elixir" "$(VERSION)" -m Kernel -u "https://github.com/elixir-lang/elixir/blob/master/%{path}#L%{line}"
rm -rf ebin
release_zip: compile
@@ -107,7 +107,7 @@ release_zip: compile
release_docs: docs
cd ../elixir-lang.github.com && git checkout master
rm -rf ../elixir-lang.github.com/docs/master
mv output ../elixir-lang.github.com/docs/master
mv docs ../elixir-lang.github.com/docs/master
release_erl: compile
@ rm -rf rel/elixir
@@ -124,7 +124,11 @@ test_erlang: compile
@ $(ERL) -pa lib/elixir/test/ebin -s test_helper test -s erlang halt;
@ echo
test_elixir: test_kernel test_mix test_ex_unit test_eex test_iex
test_elixir: test_kernel test_doctest test_mix test_ex_unit test_eex test_iex
test_doctest: compile
@ echo "==> doctest (exunit)"
@ cd lib/elixir && ../../bin/elixir -r "test/doctest.exs";
test_kernel: compile
@ echo "==> kernel (exunit)"
+6 -6
View File
@@ -2,7 +2,7 @@
This document simply outlines the release process:
1) Remove .dev extension from current versions
1) Remove `.dev` extension from VERSION
2) Run `make clean test` to ensure all tests pass from scratch and the CI is green
@@ -12,16 +12,16 @@ This document simply outlines the release process:
5) Create tag from master branch
6) Release new docs, update elixir-lang.org
6) Release new docs with `make release_docs`, update elixir-lang.org
7) Push new zip to Elixir's downloads page
7) Release new zip with `make release_zip`, push new zip to Elixir's elixir-lang.org/packages.html
8) Push package to expm
8) Push package to expm with `expm publish package.exs`
9) After release, bump versions and add .dev back
9) After release, bump versions and add `.dev` back
## Places where version is mentioned
* VERSION
* CHANGELOG
* src/elixir.app.src
* src/elixir.app.src
+1 -1
View File
@@ -1 +1 @@
0.8.1
0.8.2
+2 -3
View File
@@ -1,8 +1,8 @@
#!/bin/sh
if [ $# -eq 0 ]; then
if [ $# -eq 0 ] || [ $1 = "--help" ] || [ $1 = "-h" ]; then
echo "Usage: `basename $0` [options] [.exs file] [data]
-v Prints version and exit
-v Prints version
-e \"command\" Evaluates the given command (*)
-r \"file\" Requires the given files/patterns (*)
-S \"script\"   Finds and executes the given script (*)
@@ -13,7 +13,6 @@ if [ $# -eq 0 ]; then
--erl \"switches\" Switches to be passed down to erlang (*)
--name \"name\" Makes and assigns a name to the distributed node
--sname \"name\" Makes and assigns a short name to the distributed node
--remsh \"name\" Connects to a node using a remote shell (with iex)
--detached Starts the Erlang VM detached from console
--no-halt Does not halt the Erlang VM after execution
+22
View File
@@ -1,4 +1,26 @@
#!/bin/sh
if [ $# -gt 0 ] && ([ $1 = "--help" ] || [ $1 = "-h" ]); then
echo "Usage: `basename $0` [options] [.exs file] [data]
-v Prints version
-e \"command\" Evaluates the given command (*)
-r \"file\" Requires the given files/patterns (*)
-S \"script\"   Finds and executes the given script (*)
-pr \"file\" Requires the given files/patterns in parallel (*)
-pa \"path\" Prepends the given path to Erlang code path (*)
-pz \"path\" Appends the given path to Erlang code path (*)
--app \"app\" Start the given app and its dependencies (*)
--erl \"switches\" Switches to be passed down to erlang (*)
--name \"name\" Makes and assigns a name to the distributed node
--sname \"name\" Makes and assigns a short name to the distributed node
--remsh \"name\" Connects to a node using a remote shell
** Options marked with (*) can be given more than once
** Options given after the .exs file or -- are passed down to the executed code
** Options can be passed to the erlang runtime using ELIXIR_ERL_OPTS or --erl" >&2
exit 1
fi
readlink_f () {
cd "$(dirname "$1")" > /dev/null
local filename="$(basename "$1")"
+5 -4
View File
@@ -11,7 +11,6 @@
-record(elixir_scope, {
context=nil, %% can be assign, guards or nil
noname=false, %% when true, don't add new names (used by try)
check_requires=true, %% when true, check requires
check_clauses=true, %% when true, check def clauses ordering
super=false, %% when true, it means super was invoked
caller=false, %% when true, it means caller was invoked
@@ -29,8 +28,11 @@
aliases, %% an orddict with aliases by new -> old names
file, %% the current scope filename
requires, %% a set with modules required
macros, %% a list with macros imported by module
functions}). %% a list with functions imported by module
macro_macros=[], %% a list with macros imported from module inside a macro
macros, %% a list with macros imported from module
macro_functions=[], %% a list with functions imported from module inside a macro
functions %% a list with functions imported from module
}).
-record(elixir_quote, {
line=0,
@@ -52,4 +54,3 @@
-define(is_word(S), ?is_digit(S) orelse ?is_upcase(S) orelse ?is_downcase(S)).
-define(is_quote(S), S == $" orelse S == $').
-define(is_space(S), S == $\s; S == $\r; S == $\t; S == $\n).
+6 -4
View File
@@ -25,11 +25,13 @@ defimpl Access, for: List do
## Examples
keywords = [a: 1, b: 2]
keywords[:a] #=> 1
iex> keywords = [a: 1, b: 2]
...> keywords[:a]
1
star_ratings = [{1.0, "★"}, {1.5, "★☆"}, {2.0, "★★"}]
star_ratings[1.5] #=> "★☆"
iex> star_ratings = [{1.0, "★"}, {1.5, "★☆"}, {2.0, "★★"}]
...> star_ratings[1.5]
"★☆"
"""
def access([], _key), do: nil
+4 -2
View File
@@ -51,8 +51,10 @@ defimpl Binary.Chars, for: List do
## Examples
to_binary 'foo' #=> "foo"
to_binary ["foo", 'bar'] #=> "foobar"
iex> to_binary 'foo'
"foo"
iex> to_binary ["foo", 'bar']
"foobar"
"""
def to_binary(thing) do
+63 -46
View File
@@ -54,47 +54,52 @@ defmodule Binary.Inspect.Utils do
## escape
# It is considerably faster to loop the binary
# and convert it to a list as we go compared
# to looping the binary and creating a binary
# as we go.
def escape(other, char) do
list_to_binary [char|do_escape(other, char)]
b = do_escape(other, char, <<>>)
<< char, b :: binary, char >>
end
defp do_escape(<<char, t :: binary>>, char) do
[?\\, char | do_escape(t, char)]
@compile {:inline, do_escape: 3}
defp do_escape(<<>>, _char, binary), do: binary
defp do_escape(<< char, t :: binary >>, char, binary) do
do_escape(t, char, << binary :: binary, ?\\, char >>)
end
defp do_escape(<<h, t :: binary>>, char) when
h == ?# or h == ?\a or
h == ?\b or h == ?\d or
h == ?\e or h == ?\f or
h == ?\n or h == ?\r or
h == ?\\ or h == ?\t or
h == ?\v do
[?\\, escape_map(h) | do_escape(t, char)]
defp do_escape(<<?#, ?{, t :: binary>>, char, binary) do
do_escape(t, char, << binary :: binary, ?\\, ?#, ?{ >>)
end
defp do_escape(<<h, t :: binary>>, char) do
[h | do_escape(t,char)]
defp do_escape(<<?\a, t :: binary>>, char, binary) do
do_escape(t, char, << binary :: binary, ?\\, ?a >>)
end
defp do_escape(<<>>, char) do
[char]
defp do_escape(<<?\b, t :: binary>>, char, binary) do
do_escape(t, char, << binary :: binary, ?\\, ?b >>)
end
defp do_escape(<<?\d, t :: binary>>, char, binary) do
do_escape(t, char, << binary :: binary, ?\\, ?d >>)
end
defp do_escape(<<?\e, t :: binary>>, char, binary) do
do_escape(t, char, << binary :: binary, ?\\, ?e >>)
end
defp do_escape(<<?\f, t :: binary>>, char, binary) do
do_escape(t, char, << binary :: binary, ?\\, ?f >>)
end
defp do_escape(<<?\n, t :: binary>>, char, binary) do
do_escape(t, char, << binary :: binary, ?\\, ?n >>)
end
defp do_escape(<<?\r, t :: binary>>, char, binary) do
do_escape(t, char, << binary :: binary, ?\\, ?r >>)
end
defp do_escape(<<?\\, t :: binary>>, char, binary) do
do_escape(t, char, << binary :: binary, ?\\, ?\\ >>)
end
defp do_escape(<<?\t, t :: binary>>, char, binary) do
do_escape(t, char, << binary :: binary, ?\\, ?t >>)
end
defp do_escape(<<?\v, t :: binary>>, char, binary) do
do_escape(t, char, << binary :: binary, ?\\, ?v >>)
end
defp do_escape(<<h, t :: binary>>, char, binary) do
do_escape(t, char, << binary :: binary, h >>)
end
defp escape_map(?#), do: ?#
defp escape_map(?\a), do: ?a
defp escape_map(?\b), do: ?b
defp escape_map(?\d), do: ?d
defp escape_map(?\e), do: ?e
defp escape_map(?\f), do: ?f
defp escape_map(?\n), do: ?n
defp escape_map(?\r), do: ?r
defp escape_map(?\\), do: ?\\
defp escape_map(?\t), do: ?t
defp escape_map(?\v), do: ?v
end
defimpl Binary.Inspect, for: Atom do
@@ -111,9 +116,12 @@ defimpl Binary.Inspect, for: Atom do
## Examples
inspect(:foo) #=> ":foo"
inspect(nil) #=> "nil"
inspect(Foo.Bar) #=> "Foo.Bar"
iex> inspect(:foo)
":foo"
iex> inspect(nil)
"nil"
iex> inspect(Foo.Bar)
"Foo.Bar"
"""
@@ -186,8 +194,10 @@ defimpl Binary.Inspect, for: BitString do
## Examples
inspect("bar") #=> "bar"
inspect("f\"oo") #=> "f\"oo"
iex> inspect("bar")
"\"bar\""
iex> inspect("f\"oo")
"\"f\\\"oo\""
"""
@@ -251,9 +261,12 @@ defimpl Binary.Inspect, for: List do
## Examples
inspect('bar') #=> 'bar'
inspect([0|'bar']) #=> "[0,98,97,114]"
inspect([:foo,:bar]) #=> "[:foo, :bar]"
iex> inspect('bar')
"'bar'"
iex> inspect([0|'bar'])
"[0,98,97,114]"
iex> inspect([:foo,:bar])
"[:foo,:bar]"
"""
@@ -293,8 +306,10 @@ defimpl Binary.Inspect, for: Tuple do
## Examples
inspect({1,2,3}) #=> "{1,2,3}"
inspect(ArgumentError.new) #=> ArgumentError[message: "argument error"]
iex> inspect({1,2,3})
"{1,2,3}"
iex> inspect(ArgumentError.new)
"ArgumentError[message: \\\"argument error\\\"]"
"""
@@ -372,7 +387,8 @@ defimpl Binary.Inspect, for: Number do
## Examples
inspect(1) #=> "1"
iex> inspect(1)
"1"
"""
@@ -391,7 +407,8 @@ defimpl Binary.Inspect, for: Regex do
## Examples
inspect(%r/foo/m) #=> "%r\"foo\"m"
iex> inspect(%r/foo/m)
"%r\"foo\"m"
"""
+9 -5
View File
@@ -6,15 +6,19 @@ defmodule Bitwise do
The easiest way to use is to simply import them into
your module:
use Bitwise
iex> use Bitwise
...> bnot 1
-2
bnot 1 #=> -2
1 &&& 1 #=> 1
iex> use Bitwise
...> 1 &&& 1
1
You can select to include only or skip operators by passing options:
use Bitwise, only_operators: true
1 &&& 1 #=> 1
iex> use Bitwise, only_operators: true
...> 1 &&& 1
1
"""
+21 -12
View File
@@ -84,15 +84,15 @@ defmodule Code do
## Examples
Code.eval "a + b", [a: 1, b: 2], file: __ENV__.file, line: __ENV__.line
#=> { 3, [ {:a, 1}, {:b, 2} ] }
iex> Code.eval "a + b", [a: 1, b: 2], file: __ENV__.file, line: __ENV__.line
{ 3, [ {:a, 1}, {:b, 2} ] }
For convenience, you can my pass `__ENV__` as argument and
all imports, requires and aliases will be automatically carried
over:
Code.eval "a + b", [a: 1, b: 2], __ENV__
#=> { 3, [ {:a, 1}, {:b, 2} ] }
iex> Code.eval "a + b", [a: 1, b: 2], __ENV__
{ 3, [ {:a, 1}, {:b, 2} ] }
"""
def eval(string, binding // [], opts // [])
@@ -115,16 +115,16 @@ defmodule Code do
## Examples
contents = quote hygiene: false, do: a + b
Code.eval_quoted contents, [a: 1, b: 2], file: __ENV__.file, line: __ENV__.line
#=> { 3, [ {:a, 1}, {:b, 2} ] }
iex> contents = quote hygiene: [vars: false], do: a + b
...> Code.eval_quoted contents, [a: 1, b: 2], file: __ENV__.file, line: __ENV__.line
{ 3, [ {:a, 1}, {:b, 2} ] }
For convenience, you can my pass `__ENV__` as argument and
all options will be automatically extracted from the environment:
Code.eval_quoted contents, [a: 1, b: 2], __ENV__
#=> { 3, [ {:a, 1}, {:b, 2} ] }
iex> contents = quote hygiene: [vars: false], do: a + b
...> Code.eval_quoted contents, [a: 1, b: 2], __ENV__
{ 3, [ {:a, 1}, {:b, 2} ] }
"""
def eval_quoted(quoted, binding // [], opts // [])
@@ -140,8 +140,8 @@ defmodule Code do
end
@doc """
Converts the given string to AST. It returns { :ok, ast }
if it succeeds, { :error, { line, error, token } } otherwise.
Converts the given string to AST. It returns `{ :ok, ast }`
if it succeeds, `{ :error, { line, error, token } }` otherwise.
## Options
@@ -270,6 +270,15 @@ defmodule Code do
:elixir_compiler.string :unicode.characters_to_list(string), file
end
@doc """
Compiles the quoted expression and returns a list of tuples where
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
:elixir_compiler.quoted [quoted], file
end
@doc """
Ensures the given module is loaded. If the module is already
loaded, it works as no-op. If the module was not loaded yet,
+89 -49
View File
@@ -1,5 +1,5 @@
defmodule Dict do
@moduledoc """
@moduledoc %B"""
This module specifies the Dict API expected to be
implemented by different dictionaries. It also provides
functions that redirect to the underlying Dict, allowing
@@ -13,13 +13,28 @@ defmodule Dict do
For simplicity's sake, in the examples below everytime
`new` is used, it implies one of the module-specific
calls like the two above. Likewise, when the result of
a function invocation is shown in the form `[a: 1, b: 2]`,
it implies that the returned value is actually of the
same dict type as the input one.
calls like above. Likewise, when the result of a function
invocation is shown in the form `[a: 1, b: 2]`, it implies
that the returned value is actually of the same dict type
as the input one.
## Protocols
Besides implementing the functions in this module, all
dictionaries are also required to implement the `Access`
protocol:
iex> dict = HashDict.new
...> dict = Dict.put dict, :hello, :world
...> dict[:hello]
:world
And also the `Enum.Iterator` protocol, allowing one to write:
Enum.each dict, fn { k, v } ->
IO.puts "#{k}: #{v}"
end
Keep in mind that all dicts are also required to
implement both `Access` and `Enum.Iterator` protocols.
"""
use Behaviour
@@ -63,8 +78,9 @@ defmodule Dict do
## Examples
d = new [a: 1, b: 2]
Dict.keys d #=> [:a,:b]
iex> d = HashDict.new [a: 1, b: 2]
...> Enum.sort Dict.keys d
[:a,:b]
"""
@spec keys(t) :: [key]
@@ -77,8 +93,9 @@ defmodule Dict do
## Examples
d = new [a: 1, b: 2]
Dict.values d #=> [1,2]
iex> d = HashDict.new [a: 1, b: 2]
...> Enum.sort Dict.values d
[1,2]
"""
@spec values(t) :: [value]
@@ -91,8 +108,9 @@ defmodule Dict do
## Examples
d = new [a: 1, b: 2]
Dict.size d #=> 2
iex> d = HashDict.new [a: 1, b: 2]
...> Dict.size d
2
"""
@spec size(t) :: non_neg_integer
@@ -105,9 +123,13 @@ defmodule Dict do
## Examples
d = new [a: 1]
Dict.has_key?(d, :a) #=> true
Dict.has_key?(d, :b) #=> false
iex> d = HashDict.new [a: 1]
...> Dict.has_key?(d, :a)
true
iex> d = HashDict.new [a: 1]
...> Dict.has_key?(d, :b)
false
"""
@spec has_key?(t, key) :: boolean
@@ -121,11 +143,17 @@ defmodule Dict do
## Examples
d = new [a: 1]
Dict.get d, :a #=> 1
Dict.get d, :b #=> nil
Dict.get d, :b, 3 #=> 3
iex> d = HashDict.new [a: 1]
...> Dict.get d, :a
1
iex> d = HashDict.new [a: 1]
...> Dict.get d, :b
nil
iex> d = HashDict.new [a: 1]
...> Dict.get d, :b, 3
3
"""
@spec get(t, key, value) :: value
def get(dict, key, default // nil) do
@@ -138,9 +166,12 @@ defmodule Dict do
## Examples
d = new [a: 1]
Dict.get d, :a #=> 1
Dict.get d, :b #=> raises KeyError[key: :b]
iex> d = HashDict.new [a: 1]
...> Dict.get d, :a
1
iex> d = HashDict.new [a: 1]
...> Dict.get! d, :b
** (KeyError) key not found: :b
"""
@spec get!(t, key) :: value | no_return
@@ -154,9 +185,10 @@ defmodule Dict do
## Examples
d = new [a: 1, b: 2]
Dict.put d, :a, 3
#=> [a: 3, b: 2]
iex> d = HashDict.new [a: 1, b: 2]
...> d = Dict.put d, :a, 3
...> Dict.get d, :a
3
"""
@spec put(t, key, value) :: t
@@ -169,9 +201,10 @@ defmodule Dict do
## Examples
d = new [a: 1, b: 2]
Dict.put_new d, :a, 3
#=> [a: 1, b: 2]
iex> d = HashDict.new [a: 1, b: 2]
...> d = Dict.put_new d, :a, 3
...> Dict.get d, :a
1
"""
@spec put_new(t, key, value) :: t
@@ -185,11 +218,14 @@ defmodule Dict do
## Examples
d = new [a: 1, b: 2]
Dict.delete d, :a #=> [b: 2]
iex> d = HashDict.new [a: 1, b: 2]
...> d = Dict.delete d, :a
...> Dict.get d, :a
nil
d = new [b: 2]
Dict.delete d, :a #=> [b: 2]
iex> d = HashDict.new [b: 2]
...> Dict.delete(d, :a) == d
true
"""
@spec delete(t, key) :: t
@@ -203,10 +239,11 @@ defmodule Dict do
## Examples
d1 = new [a: 1, b: 2]
d2 = new [a: 3, d: 4]
Dict.merge d1, d2
#=> [a: 3, b: 2, d: 4]
iex> d1 = HashDict.new [a: 1, b: 2]
...> d2 = HashDict.new [a: 3, d: 4]
...> d = Dict.merge d1, d2
...> [a: Dict.get(d, :a), b: Dict.get(d, :b), d: Dict.get(d, :d)]
[a: 3, b: 2, d: 4]
"""
@spec merge(t, t) :: t
@@ -221,12 +258,13 @@ defmodule Dict do
## Examples
d1 = new [a: 1, b: 2]
d2 = new [a: 3, d: 4]
Dict.merge d1, d2, fn _k, v1, v2 ->
v1 + v2
end
#=> [a: 4, b: 2, d: 4]
iex> d1 = HashDict.new [a: 1, b: 2]
...> d2 = HashDict.new [a: 3, d: 4]
...> d = Dict.merge d1, d2, fn _k, v1, v2 ->
...> v1 + v2
...> end
...> [a: Dict.get(d, :a), b: Dict.get(d, :b), d: Dict.get(d, :d)]
[a: 4, b: 2, d: 4]
"""
@spec merge(t, t, (key, value, value -> value)) :: t
@@ -240,9 +278,10 @@ defmodule Dict do
## Examples
d = new [a: 1, b: 2]
Dict.update d, :a, fn val -> -val end
#=> [a: -1, b: 2]
iex> d = HashDict.new [a: 1, b: 2]
...> d = Dict.update d, :a, fn val -> -val end
...> Dict.get d, :a
-1
"""
@spec update(t, key, (value -> value)) :: t
@@ -257,9 +296,10 @@ defmodule Dict do
## Examples
d = new [a: 1, b: 2]
Dict.update d, :c, 3, fn val -> -val end
#=> [a: 1, b: 2, c: 3]
iex> d = HashDict.new [a: 1, b: 2]
...> d = Dict.update d, :c, 3, fn val -> -val end
...> Dict.get d, :c
3
"""
@spec update(t, key, value, (value -> value)) :: t
+136 -104
View File
@@ -1,53 +1,55 @@
defprotocol Enum.Iterator do
@moduledoc """
This is the protocol used by the `Enum` module.
Usually, when you invoke a function in the module `Enum`,
the first argument passed to `Enum` is a collection which
is forwarded to this protocol in order to retrieve information
on how to iterate the collection. That said, when:
Usually, when you invoke a function in the module `Enum`, the first argument
passed to it is a collection which is forwarded to this protocol in order to
retrieve information on how to iterate the collection.
For example, in the expression
Enum.map [1,2,3], &1 * 2
Is invoked, it invokes `Enum.Iterator.iterator([1,2,3])`
which returns all the information required by Enum.
Read each function documentation below for more information.
`Enum.map` invokes `Enum.Iterator.iterator([1,2,3])` to retrieve the iterator
function that will drive the iteration process.
"""
@only [List, Record, Function]
@doc """
Iteration in Elixir happens with the help of a iterator
function. Every time this function is called, it must
return a tuple with two elements. The first element
is the next item and the second can be any Elixir term
which the function is going to receive as argument the
next time it is invoked.
This function must return a tuple of the form `{ iter, step }` where
`iter` is a function that yields successive values from the collection
each time it is invoked and `step` is the first step of iteration.
When there are no more items to be iterated, the function
must return the atom `:stop`.
Iteration in Elixir happens with the help of an _iterator function_ (named
`iter` in the paragraph above). When it is invoked, it must return a tuple
with two elements. The first element is the next successive value from the
collection and the second element can be any Elixir term which `iter` is
going to receive as its argument the next time it is invoked.
In order to retrieve this iterator function, Elixir invokes
`Enum.Iterator.iterator(collection)` which should return a
tuple with two elements: the first element is the iterator
function and the second is the first step of iteration.
When there are no more items left to yield, `iter` must return the atom
`:stop`.
As an example, here is the implementation of iterator for lists:
As an example, here is the implementation of `iterator` for lists:
def iterator(list), do: { iterate(&1), iterate(list) }
defp iterate([h|t]), do: { h, t }
defp iterate([]), do: :stop
Here, `iterate` is the _iterator function_ and `{ h, t }` is a step of
iteration.
## Iterating lists
If a data structure needs to be converted to a list in order
to be iterated, the iterator function can simply return the
list and the Enum module will be able to take over the list
and retrieve the proper iterator function.
As a special case, if a data structure needs to be converted to a list in
order to be iterated, `iterator` can simply return the list and the `Enum`
module will be able to take over the list and produce a proper iterator
function for it.
"""
def iterator(collection)
@doc """
The function used to retrieve the collection size.
The function used to retrieve the collection's size.
"""
def count(collection)
end
@@ -79,17 +81,19 @@ defmodule Enum do
## Examples
Enum.all? [2,4,6], fn(x) -> rem(x, 2) == 0 end
#=> true
iex> Enum.all? [2,4,6], fn(x) -> rem(x, 2) == 0 end
true
Enum.all? [2,3,4], fn(x) -> rem(x, 2) == 0 end
#=> false
iex> Enum.all? [2,3,4], fn(x) -> rem(x, 2) == 0 end
false
If no function is given, it defaults to checking if
all items in the collection evaluate to true.
Enum.all? [1,2,3] #=> true
Enum.all? [1,nil,3] #=> false
iex> Enum.all? [1,2,3]
true
iex> Enum.all? [1,nil,3]
false
"""
@spec all?(t) :: boolean
@@ -116,17 +120,19 @@ defmodule Enum do
## Examples
Enum.any? [2,4,6], fn(x) -> rem(x, 2) == 1 end
#=> false
iex> Enum.any? [2,4,6], fn(x) -> rem(x, 2) == 1 end
false
Enum.any? [2,3,4], fn(x) -> rem(x, 2) == 1 end
#=> true
iex> Enum.any? [2,3,4], fn(x) -> rem(x, 2) == 1 end
true
If no function is given, it defaults to checking if
at least one item in the collection evaluates to true.
Enum.any? [false,false,false] #=> false
Enum.any? [false,true,false] #=> true
iex> Enum.any? [false,false,false]
false
iex> Enum.any? [false,true,false]
true
"""
@spec any?(t) :: boolean
@@ -156,9 +162,12 @@ defmodule Enum do
## Examples
Enum.at! [2,4,6], 0 #=> 2
Enum.at! [2,4,6], 2 #=> 6
Enum.at! [2,4,6], 4 #=> raises Enum.OutOfBoundsError
iex> Enum.at! [2,4,6], 0
2
iex> Enum.at! [2,4,6], 2
6
iex> Enum.at! [2,4,6], 4
** (Enum.OutOfBoundsError) out of bounds error
"""
@spec at!(t, index) :: element | no_return
@@ -180,7 +189,8 @@ defmodule Enum do
## Examples
Enum.count [1,2,3] #=> 3
iex> Enum.count [1,2,3]
3
"""
@spec count(t) :: non_neg_integer
@@ -211,9 +221,12 @@ defmodule Enum do
## Examples
Enum.drop [1,2,3], 2 #=> [3]
Enum.drop [1,2,3], 10 #=> []
Enum.drop [1,2,3], 0 #=> [1,2,3]
iex> Enum.drop [1,2,3], 2
[3]
iex> Enum.drop [1,2,3], 10
[]
iex> Enum.drop [1,2,3], 0
[1,2,3]
"""
@spec drop(t, integer) :: list
@@ -241,8 +254,8 @@ defmodule Enum do
## Examples
Enum.drop_while [1,2,3,4,5], fn(x) -> x < 3 end
#=> [3,4,5]
iex> Enum.drop_while [1,2,3,4,5], fn(x) -> x < 3 end
[3,4,5]
"""
@spec drop_while(t, (element -> as_boolean(term))) :: list
def drop_while(collection, fun) when is_list(collection) do
@@ -297,8 +310,10 @@ defmodule Enum do
## Examples
Enum.empty? [] #=> true
Enum.empty? [1,2,3] #=> false
iex> Enum.empty? []
true
iex> Enum.empty? [1,2,3]
false
"""
@spec empty?(t) :: boolean
@@ -319,8 +334,8 @@ defmodule Enum do
## Examples
Enum.filter [1, 2, 3], fn(x) -> rem(x, 2) == 0 end
#=> [2]
iex> Enum.filter [1, 2, 3], fn(x) -> rem(x, 2) == 0 end
[2]
"""
@spec filter(t, (element -> as_boolean(term))) :: list
@@ -342,8 +357,8 @@ defmodule Enum do
## Examples
Enum.filter_map [1, 2, 3], fn(x) -> rem(x, 2) == 0 end, &1 * 2
#=> [4]
iex> Enum.filter_map [1, 2, 3], fn(x) -> rem(x, 2) == 0 end, &1 * 2
[4]
"""
@spec filter_map(t, (element -> as_boolean(term)), (element -> element)) :: list
@@ -366,14 +381,14 @@ defmodule Enum do
## Examples
Enum.find [2,4,6], fn(x) -> rem(x, 2) == 1 end
#=> nil
iex> Enum.find [2,4,6], fn(x) -> rem(x, 2) == 1 end
nil
Enum.find [2,4,6], 0, fn(x) -> rem(x, 2) == 1 end
#=> 0
iex> Enum.find [2,4,6], 0, fn(x) -> rem(x, 2) == 1 end
0
Enum.find [2,3,4], fn(x) -> rem(x, 2) == 1 end
#=> 3
iex> Enum.find [2,3,4], fn(x) -> rem(x, 2) == 1 end
3
"""
@spec find(t, (element -> any)) :: element | :nil
@@ -400,11 +415,11 @@ defmodule Enum do
## Examples
Enum.find_value [2,4,6], fn(x) -> rem(x, 2) == 1 end
#=> nil
iex> Enum.find_value [2,4,6], fn(x) -> rem(x, 2) == 1 end
nil
Enum.find_value [2,3,4], fn(x) -> rem(x, 2) == 1 end
#=> true
iex> Enum.find_value [2,3,4], fn(x) -> rem(x, 2) == 1 end
true
"""
@spec find_value(t, (element -> any)) :: any | :nil
@@ -433,11 +448,11 @@ defmodule Enum do
## Examples
Enum.find_index [2,4,6], fn(x) -> rem(x, 2) == 1 end
#=> nil
iex> Enum.find_index [2,4,6], fn(x) -> rem(x, 2) == 1 end
nil
Enum.find_index [2,3,4], fn(x) -> rem(x, 2) == 1 end
#=> 2
iex> Enum.find_index [2,3,4], fn(x) -> rem(x, 2) == 1 end
1
"""
@spec find_index(t, (element -> any)) :: index | :nil
@@ -459,8 +474,10 @@ defmodule Enum do
## Examples
Enum.first [] #=> nil
Enum.first [1,2,3] #=> 1
iex> Enum.first []
nil
iex> Enum.first [1,2,3]
1
"""
@spec first(t) :: :nil | element
@@ -487,9 +504,12 @@ defmodule Enum do
## Examples
Enum.join([1,2,3]) #=> "123"
Enum.join([1,2,3], " = ") #=> "1 = 2 = 3"
Enum.join([1,2,3], ' = ') #=> '1 = 2 = 3'
iex> Enum.join([1,2,3])
"123"
iex> Enum.join([1,2,3], " = ")
"1 = 2 = 3"
iex> Enum.join([1,2,3], ' = ')
'1 = 2 = 3'
"""
@spec join(t) :: String.t
@@ -523,11 +543,11 @@ defmodule Enum do
## Examples
Enum.map [1, 2, 3], fn(x) -> x * 2 end
#=> [2, 4, 6]
iex> Enum.map [1, 2, 3], fn(x) -> x * 2 end
[2, 4, 6]
Enum.map [a: 1, b: 2], fn({k, v}) -> { k, -v } end
#=> [a: -1, b: -2]
iex> Enum.map [a: 1, b: 2], fn({k, v}) -> { k, -v } end
[a: -1, b: -2]
"""
@spec map(t, (element -> any) | (element, index -> any)) :: list
@@ -562,9 +582,12 @@ defmodule Enum do
## Examples
Enum.map_join([1,2,3], &1 * 2) #=> "246"
Enum.map_join([1,2,3], " = ", &1 * 2) #=> "2 = 4 = 6"
Enum.map_join([1,2,3], ' = ', &1 * 2) #=> '2 = 4 = 6'
iex> Enum.map_join([1,2,3], &1 * 2)
"246"
iex> Enum.map_join([1,2,3], " = ", &1 * 2)
"2 = 4 = 6"
iex> Enum.map_join([1,2,3], ' = ', &1 * 2)
'2 = 4 = 6'
"""
@spec map_join(t, (element -> any)) :: String.t
@@ -599,8 +622,8 @@ defmodule Enum do
## Examples
Enum.map_reduce [1, 2, 3], 0, fn(x, acc) -> { x * 2, x + acc } end
#=> { [2, 4, 6], 6 }
iex> Enum.map_reduce [1, 2, 3], 0, fn(x, acc) -> { x * 2, x + acc } end
{ [2, 4, 6], 6 }
"""
@spec map_reduce(t, any, (element, any -> any)) :: any
@@ -624,8 +647,8 @@ defmodule Enum do
## Examples
Enum.partition [1, 2, 3], fn(x) -> rem(x, 2) == 0 end
#=> { [2], [1,3] }
iex> Enum.partition [1, 2, 3], fn(x) -> rem(x, 2) == 0 end
{ [2], [1,3] }
"""
@spec partition(t, (element -> any)) :: {list, list}
@@ -649,8 +672,8 @@ defmodule Enum do
## Examples
Enum.reduce [1, 2, 3], 0, fn(x, acc) -> x + acc end
#=> 6
iex> Enum.reduce [1, 2, 3], 0, fn(x, acc) -> x + acc end
6
"""
@spec reduce(t, any, (element, any -> any)) :: any
@@ -672,8 +695,8 @@ defmodule Enum do
## Examples
Enum.reverse [1, 2, 3]
#=> [3, 2, 1]
iex> Enum.reverse [1, 2, 3]
[3, 2, 1]
"""
@spec reverse(t) :: list
@@ -693,7 +716,8 @@ defmodule Enum do
## Examples
Enum.sort [3,2,1] #=> [1,2,3]
iex> Enum.sort [3,2,1]
[1,2,3]
"""
@spec sort(t) :: list
@@ -715,7 +739,8 @@ defmodule Enum do
## Examples
Enum.sort [3,2,1], &1 > &2 #=> [1,2,3]
iex> Enum.sort [1,2,3], &1 > &2
[3,2,1]
"""
@spec sort(t, (element, element -> boolean)) :: list
@@ -744,11 +769,16 @@ defmodule Enum do
## Examples
Enum.split [1,2,3], 2 #=> { [1,2], [3] }
Enum.split [1,2,3], 10 #=> { [1,2,3], [] }
Enum.split [1,2,3], 0 #=> { [], [1,2,3] }
Enum.split [1,2,3], -1 #=> { [1,2], [3] }
Enum.split [1,2,3], -5 #=> { [], [1,2,3] }
iex> Enum.split [1,2,3], 2
{ [1,2], [3] }
iex> Enum.split [1,2,3], 10
{ [1,2,3], [] }
iex> Enum.split [1,2,3], 0
{ [], [1,2,3] }
iex> Enum.split [1,2,3], -1
{ [1,2], [3] }
iex> Enum.split [1,2,3], -5
{ [], [1,2,3] }
"""
@spec split(t, integer) :: {list, list}
@@ -771,13 +801,12 @@ defmodule Enum do
end
@doc """
Splits `collection` at the first element, for which `fun` returns true.
Expects an ordered collection.
Splits `collection` in two while `fun` returns true.
## Examples
Enum.split_while [1,2,3,4], fn x -> x == 2 end
#=> { [1], [2, 3, 4] }
iex> Enum.split_while [1,2,3,4], fn(x) -> x < 3 end
{ [1, 2], [3, 4] }
"""
@spec split_while(t, (element -> as_boolean(term))) :: {list, list}
def split_while(collection, fun) when is_list(collection) do
@@ -799,9 +828,12 @@ defmodule Enum do
## Examples
Enum.take [1,2,3], 2 #=> [1,2]
Enum.take [1,2,3], 10 #=> [1,2,3]
Enum.take [1,2,3], 0 #=> []
iex> Enum.take [1,2,3], 2
[1,2]
iex> Enum.take [1,2,3], 10
[1,2,3]
iex> Enum.take [1,2,3], 0
[]
"""
@spec take(t, integer) :: list
@@ -834,8 +866,8 @@ defmodule Enum do
## Examples
Enum.take_while [1,2,3], fn(x) -> x < 3 end
#=> [1, 2]
iex> Enum.take_while [1,2,3], fn(x) -> x < 3 end
[1, 2]
"""
@spec take_while(t, (element -> as_boolean(term))) :: list
@@ -857,8 +889,8 @@ defmodule Enum do
## Examples
Enum.uniq [1,2,3,2,1]
#=> [1, 2, 3]
iex> Enum.uniq [1,2,3,2,1]
[1, 2, 3]
"""
@spec uniq(t) :: list
+29 -3
View File
@@ -271,7 +271,7 @@ defmodule File do
path. Returns `:ok` or `{ :error, reason }`.
"""
def write_stat(path, File.Stat[] = stat, opts // []) do
F.write_file_info(path, setelem(stat, 0, :file_info), opts)
F.write_file_info(path, set_elem(stat, 0, :file_info), opts)
end
@doc """
@@ -795,7 +795,7 @@ defmodule File do
## Examples
File.open!("file.txt", [:read, :write], fn(file) ->
File.open("file.txt", [:read, :write], fn(file) ->
IO.readline(file)
end)
@@ -897,6 +897,31 @@ defmodule File do
end
end
@doc """
Returns list of files in the given directory.
It returns `{ :ok, [files] }` in case of success,
`{ :error, reason }` otherwise.
"""
def ls(path // ".") do
case F.list_dir(path) do
{ :ok, file_list } -> { :ok, Enum.map file_list, :unicode.characters_to_binary(&1) }
{ :error, _ } = error -> error
end
end
@doc """
The same as `ls/1` but raises `File.Error`
in case of an error.
"""
def ls!(dir // ".") do
case ls(dir) do
{ :ok, value } -> value
{ :error, reason } ->
raise File.Error, reason: reason, action: "list directory", path: :unicode.characters_to_binary(dir)
end
end
@doc """
Closes the file referenced by `io_device`. It mostly returns `:ok`, except
for some severe errors such as out of memory.
@@ -928,7 +953,8 @@ defmodule File do
quotes per single quotes and write each line to a target file
is shown below:
source = File.iterator("README.md")
{ :ok, device } = File.open("README.md")
source = File.iterator(device)
File.open "NEWREADME.md", [:write], fn(target) ->
Enum.each source, fn(line) ->
IO.write target, Regex.replace(%r/"/, line, "'")
+90
View File
@@ -0,0 +1,90 @@
defmodule GenEvent.Behaviour do
@moduledoc """
This module is a convenience to define GenEvent callbacks in Elixir.
GenEvent is an OTP behaviour that encapsulates event handling functionality.
## Example
Bellow follows an example of a GenEvent that stores notifications
until they are fetched:
defmodule MyEventHandler do
use GenEvent.Behaviour
# Callbacks
def init(_) do
{ :ok, [] }
end
def handle_event({:notification, x}, notifications) do
{ :ok, [x|notifications] }
end
def handle_call(:notifications, notifications) do
{:ok, Enum.reverse(notifications), []}
end
end
{ :ok, pid } = :gen_event.start_link
:gen_event.add_handler(pid, MyEventHandler, [])
:gen_event.notify(pid, {:notification, 1})
:gen_event.notify(pid, {:notification, 2})
:gen_event.call(pid, MyEventHandler, :notifications)
#=> [1, 2]
:gen_event.call(pid, MyEventHandler, :notifications)
#=> []
Notice we never call the server callbacks directly, they are called
by OTP whenever we interact with the server.
Starting and sending messages to gen_event is done
via Erlang's `:gen_event` module. For more information,
please refer to the following:
http://www.erlang.org/doc/man/gen_event.html
http://learnyousomeerlang.com/event-handlers
"""
@doc false
defmacro __using__(_) do
quote location: :keep do
@behavior :gen_event
def init(args) do
{ :ok, args }
end
def handle_event(_event, state) do
{ :ok, state }
end
def handle_call(_request, state) do
{ :ok, :ok, state }
end
def handle_info(_msg, state) do
{ :ok, state }
end
def terminate(reason, state) do
:ok
end
def code_change(_old, state, _extra) do
{ :ok, state }
end
defoverridable [init: 1,
handle_event: 2,
handle_call: 2, handle_info: 2,
terminate: 2, code_change: 3]
end
end
end
+37 -35
View File
@@ -33,27 +33,27 @@ defmodule HashDict do
# set of buckets
# The ordered record contains a single bucket
@ordered_threshold 8
ordered_threshold = 8
defrecordp :ordered,
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
@node_template :erlang.make_tuple(@node_size, [])
expand_load = 5
contract_load = 2
node_bitmap = 0b111
node_shift = 3
node_size = 8
node_template = :erlang.make_tuple(node_size, [])
node_escaped = Macro.escape(node_template)
defrecordp :trie,
size: 0,
depth: 0,
expand_on: @node_size * @expand_load,
contract_on: @contract_load,
root: @node_template
expand_on: node_size * expand_load,
contract_on: contract_load,
root: node_template
import Bitwise
@@ -74,7 +74,7 @@ defmodule HashDict do
## Examples
HashDict.new [{:b,1},{:a,2}]
#=> HashDict[a: 1, b: 2]
#=> #HashDict<[a: 2, b: 1]>
"""
@spec new(list({key :: term, value :: term})) :: Dict.t
@@ -91,7 +91,7 @@ defmodule HashDict do
## Examples
HashDict.new ["a", "b"], fn x -> {x, x} end
#=> HashDict[{ "a", "a" }, { "b", "b" }]
#=> #HashDict<[{"a","a"},{"b","b"}]>
"""
@spec new(list, (term -> {key :: term, value ::term})) :: Dict.t
@@ -194,8 +194,8 @@ defmodule HashDict do
root: root,
size: size - 1,
depth: depth - 1,
contract_on: div(size, @node_size),
expand_on: div(trie(dict, :expand_on), @node_size))
contract_on: div(size, unquote(node_size)),
expand_on: div(trie(dict, :expand_on), unquote(node_size)))
else
trie(dict, size: size - 1, root: root)
end
@@ -279,12 +279,12 @@ defmodule HashDict do
end
defp dict_fold(trie(root: root, depth: depth), acc, fun) do
node_fold(root, depth, acc, fun, @node_size)
node_fold(root, depth, acc, fun, unquote(node_size))
end
defp dict_put(ordered(size: @ordered_threshold, bucket: bucket), key, value) do
defp dict_put(ordered(size: unquote(ordered_threshold), bucket: bucket), key, value) do
root = node_relocate(bucket, 0)
dict_put(trie(size: @ordered_threshold, root: root), key, value)
dict_put(trie(size: unquote(ordered_threshold), root: root), key, value)
end
defp dict_put(ordered(size: size, bucket: bucket) = dict, key, value) do
@@ -295,7 +295,7 @@ defmodule HashDict do
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)
expand_on: size * unquote(node_size), contract_on: contract_on * unquote(node_size))
dict_put(dict, key, value)
end
@@ -372,15 +372,15 @@ defmodule HashDict do
end
defp bucket_index(hash) do
hash &&& @node_bitmap
hash &&& unquote(node_bitmap)
end
defp bucket_nth_index(hash, n) do
(hash >>> (@node_shift * n)) &&& @node_bitmap
(hash >>> (unquote(node_shift) * n)) &&& unquote(node_bitmap)
end
defp bucket_next(hash) do
hash >>> @node_shift
hash >>> unquote(node_shift)
end
## Node helpers
@@ -399,13 +399,13 @@ defmodule HashDict do
defp node_put(node, 0, hash, key, value) do
pos = bucket_index(hash)
{ new, count } = bucket_put(elem(node, pos), key, value)
{ setelem(node, pos, new), count }
{ 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)
{ setelem(node, pos, new), count }
{ set_elem(node, pos, new), count }
end
# Deletes a key from the bucket
@@ -413,7 +413,7 @@ defmodule HashDict do
pos = bucket_index(hash)
case bucket_delete(elem(node, pos), key) do
{ _, 0 } -> { node, 0 }
{ new, -1 } -> { setelem(node, pos, new), -1 }
{ new, -1 } -> { set_elem(node, pos, new), -1 }
end
end
@@ -421,7 +421,7 @@ defmodule HashDict do
pos = bucket_index(hash)
case node_delete(elem(node, pos), depth - 1, bucket_next(hash), key) do
{ _, 0 } -> { node, 0 }
{ new, -1 } -> { setelem(node, pos, new), -1 }
{ new, -1 } -> { set_elem(node, pos, new), -1 }
end
end
@@ -431,7 +431,7 @@ defmodule HashDict do
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)
acc = node_fold(:erlang.element(count, node), depth - 1, acc, fun, unquote(node_size))
node_fold(node, depth, acc, fun, count - 1)
end
@@ -461,21 +461,23 @@ defmodule HashDict do
end
defp node_contract({ b1, b2, b3, b4, b5, b6, b7, b8 }, 1, n) do
@node_template |> each_contract(b1, n) |> each_contract(b2, n) |> each_contract(b3, n)
|> each_contract(b4, n) |> each_contract(b5, n) |> each_contract(b6, n)
|> each_contract(b7, n) |> each_contract(b8, n)
unquote(node_escaped)
|> each_contract(b1, n) |> each_contract(b2, n) |> each_contract(b3, n)
|> each_contract(b4, n) |> each_contract(b5, n) |> each_contract(b6, n)
|> each_contract(b7, n) |> each_contract(b8, n)
end
defp each_contract(acc, { b1, b2, b3, b4, b5, b6, b7, b8 }, n) do
acc |> 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)
acc
|> 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_relocate(node // @node_template, bucket, n) do
defp node_relocate(node // unquote(node_escaped), bucket, n) do
:lists.foldl fn { key, value }, acc ->
pos = key |> bucket_hash() |> bucket_nth_index(n)
setelem(acc, pos, bucket_put!(elem(acc, pos), key, value))
set_elem(acc, pos, bucket_put!(elem(acc, pos), key, value))
end, node, bucket
end
end
+2 -2
View File
@@ -168,7 +168,7 @@ defmodule IO.ANSI do
## Example
IO.ANSI.escape "Hello %{red,bright,green}yes" #=>
iex> IO.ANSI.escape "Hello %{red,bright,green}yes"
"Hello \e[31m\e[1m\e[32myes\e[0m"
"""
@spec escape(String.t, emit :: boolean) :: String.t
@@ -194,7 +194,7 @@ defmodule IO.ANSI do
## Example
IO.ANSI.escape "Hello %{red,bright,green}yes%{reset}" #=>
iex> IO.ANSI.escape "Hello %{red,bright,green}yes"
"Hello \e[31m\e[1m\e[32myes\e[0m"
"""
+605 -316
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -17,7 +17,7 @@ defmodule Kernel.ErrorHandler do
case Code.ensure_loaded(module) do
{ :module, _ } -> []
{ :error, _ } ->
parent = Process.get(:elixir_compiler_pid)
parent = :erlang.get(:elixir_compiler_pid)
parent <- { :waiting, self(), module }
receive do
{ :release, ^parent } -> :ok
+24 -6
View File
@@ -32,7 +32,7 @@ defmodule Kernel.ParallelCompiler do
defp spawn_compilers(files, path, callback) do
Code.ensure_loaded(Kernel.ErrorHandler)
schedulers = :erlang.system_info(:schedulers_online)
schedulers = max(:erlang.system_info(:schedulers_online), 2)
spawn_compilers(files, path, callback, [], [], schedulers, [])
end
@@ -99,16 +99,34 @@ defmodule Kernel.ParallelCompiler do
new_waiting = OrdDict.store(child, on, waiting)
spawn_compilers(files, output, callback, new_waiting, queued, schedulers, result)
{ :failure, child, kind, reason, stacktrace } ->
extra = if match?({^child, module}, List.keyfind(waiting, child, 0)) do
" (undefined module #{inspect module})"
if many_missing?(child, files, waiting, queued) do
IO.puts "== Compilation failed =="
IO.puts "Compilation failed on the following files:\n"
Enum.each Enum.reverse(queued), fn { pid, file } ->
case List.keyfind(waiting, pid, 0) do
{ _, mod } -> IO.puts "* #{file} is missing module #{inspect mod}"
_ -> :ok
end
end
IO.puts "\nThe first failure is shown below..."
end
{^child, file} = List.keyfind(queued, child, 0)
IO.puts "== Compilation error on file #{file}#{extra} =="
:erlang.raise(kind, reason, stacktrace)
{^child, file} = List.keyfind(queued, child, 0)
IO.puts "== Compilation error on file #{file} =="
:erlang.raise(kind, reason, stacktrace)
end
end
defp many_missing?(child, files, waiting, queued) do
waiting_length = length(waiting)
match?({ ^child, _ }, List.keyfind(waiting, child, 0)) and
waiting_length > 1 and files == [] and
waiting_length == length(queued)
end
# Release waiting processes that are waiting for the given module
defp release_waiting_processes(module, waiting) do
Enum.filter waiting, fn { child, waiting_module } ->
+1 -1
View File
@@ -12,7 +12,7 @@ defmodule Kernel.ParallelRequire do
can be optionally given as argument.
"""
def files(files, callback // default_callback) do
schedulers = :erlang.system_info(:schedulers_online)
schedulers = max(:erlang.system_info(:schedulers_online), 2)
spawn_requires(files, [], callback, schedulers, [])
end
+84 -76
View File
@@ -5,42 +5,50 @@
defmodule Kernel.RecordRewriter do
@moduledoc false
def optimize_clause(clause) do
optimize_clause(clause, :orddict.new)
def optimize_clause(module, clause) do
optimize_clause(clause, module, :orddict.new)
end
## Clause
defp optimize_clause({ :clause, line, args, guards, body }, dict) do
{ args, dict } = optimize_args(args, dict)
{ body, dict, res } = optimize_body(body, dict, [])
defp optimize_clause({ :clause, line, args, guards, body }, module, dict) do
{ args, dict } = optimize_args(args, module, dict)
{ body, dict, res } = optimize_body(body, module, dict, [])
{ { :clause, line, args, guards, body }, dict, res }
end
defp optimize_args(args, dict) do
defp optimize_args(args, module, dict) do
Enum.map_reduce args, dict, fn(arg, acc) ->
{ new_arg, new_acc, _res } = optimize_expr(arg, acc)
{ new_arg, new_acc, _res } = optimize_expr(arg, module, acc)
{ new_arg, new_acc }
end
end
defp optimize_body([], dict, _acc) do
defp optimize_body([], _module, dict, _acc) do
{ [], dict, nil }
end
defp optimize_body([h], dict, acc) do
{ new_expr, new_dict, new_res } = optimize_expr(h, dict)
defp optimize_body([h], module, dict, acc) do
{ new_expr, new_dict, new_res } = optimize_expr(h, module, dict)
{ Enum.reverse([new_expr|acc]), new_dict, new_res }
end
defp optimize_body([h|t], dict, acc) do
{ new_expr, new_dict, _ } = optimize_expr(h, dict)
optimize_body(t, new_dict, [new_expr|acc])
defp optimize_body([h|t], module, dict, acc) do
{ new_expr, new_dict, _ } = optimize_expr(h, module, dict)
optimize_body(t, module, new_dict, [new_expr|acc])
end
## Record helpers
defp record_fields(record) do
defp record_fields(record, record) do
fields = Module.get_attribute(record, :record_fields)
optimizable = Module.get_attribute(record, :record_optimizable)
unless nil?(fields) or nil?(optimizable) do
{ (lc { k, _ } inlist fields, do: k), optimizable }
end
end
defp record_fields(_module, record) do
if Code.ensure_loaded?(record) && function_exported?(record, :__record__, 1) do
try do
fields = lc { k, _ } inlist record.__record__(:fields), do: k
@@ -60,8 +68,8 @@ defmodule Kernel.RecordRewriter do
end
end
defp optimize_call(line, { record, _ } = res, left, { :atom, _, function }, args) do
case record_fields(record) do
defp optimize_call(line, module, { record, _ } = res, left, { :atom, _, function }, args) do
case record_fields(module, record) do
{ fields, optimizable } ->
opt_call =
if List.member?(optimizable, { function, length(args) + 1 }) do
@@ -81,7 +89,7 @@ defmodule Kernel.RecordRewriter do
end
end
defp optimize_call(_line, _res, _left, _right, _args) do
defp optimize_call(_line, _module, _res, _left, _right, _args) do
nil
end
@@ -111,12 +119,12 @@ defmodule Kernel.RecordRewriter do
## Expr
defp optimize_expr({ :call, call_line, { :remote, line, left, right }, args }, dict) do
{ left, dict, res } = optimize_expr(left, dict)
{ right, dict, _ } = optimize_expr(right, dict)
{ args, dict } = optimize_args(args, dict)
defp optimize_expr({ :call, call_line, { :remote, line, left, right }, args }, module, dict) do
{ left, dict, res } = optimize_expr(left, module, dict)
{ right, dict, _ } = optimize_expr(right, module, dict)
{ args, dict } = optimize_args(args, module, dict)
case optimize_call(call_line, res, left, right, args) do
case optimize_call(call_line, module, res, left, right, args) do
{ call, call_res } ->
{ call, dict, call_res }
nil ->
@@ -124,15 +132,15 @@ defmodule Kernel.RecordRewriter do
end
end
defp optimize_expr({ :call, line, expr, args }, dict) do
{ expr, dict, _ } = optimize_expr(expr, dict)
{ args, dict } = optimize_args(args, dict)
defp optimize_expr({ :call, line, expr, args }, module, dict) do
{ expr, dict, _ } = optimize_expr(expr, module, dict)
{ args, dict } = optimize_args(args, module, dict)
{ { :call, line, expr, args }, dict, nil }
end
defp optimize_expr({ :match, line, left, right }, dict) do
{ left, dict, left_res } = optimize_expr(left, dict)
{ right, dict, right_res } = optimize_expr(right, dict)
defp optimize_expr({ :match, line, left, right }, module, dict) do
{ left, dict, left_res } = optimize_expr(left, module, dict)
{ right, dict, right_res } = optimize_expr(right, module, dict)
match = { :match, line, left, right }
@@ -147,40 +155,40 @@ defmodule Kernel.RecordRewriter do
{ match, dict, right_res || left_res }
end
defp optimize_expr({ :op, line, op, left, right }, dict) do
{ left, dict, _ } = optimize_expr(left, dict)
{ right, dict, _ } = optimize_expr(right, dict)
defp optimize_expr({ :op, line, op, left, right }, module, dict) do
{ left, dict, _ } = optimize_expr(left, module, dict)
{ right, dict, _ } = optimize_expr(right, module, dict)
{ { :op, line, op, left, right }, dict, nil }
end
defp optimize_expr({ :op, line, op, expr }, dict) do
{ expr, dict, _ } = optimize_expr(expr, dict)
defp optimize_expr({ :op, line, op, expr }, module, dict) do
{ expr, dict, _ } = optimize_expr(expr, module, dict)
{ { :op, line, op, expr }, dict, nil }
end
defp optimize_expr({ :bin, line, elements }, dict) do
{ elements, dict } = optimize_args(elements, dict)
defp optimize_expr({ :bin, line, elements }, module, dict) do
{ elements, dict } = optimize_args(elements, module, dict)
{ { :bin, line, elements }, dict, nil }
end
defp optimize_expr({ :bin_element, line, expr, type1, type2 }, dict) do
{ expr, dict, _ } = optimize_expr(expr, dict)
defp optimize_expr({ :bin_element, line, expr, type1, type2 }, module, dict) do
{ expr, dict, _ } = optimize_expr(expr, module, dict)
{ { :bin_element, line, expr, type1, type2 }, dict, nil }
end
defp optimize_expr({ :cons, line, left, right }, dict) do
{ left, dict, _ } = optimize_expr(left, dict)
{ right, dict, _ } = optimize_expr(right, dict)
defp optimize_expr({ :cons, line, left, right }, module, dict) do
{ left, dict, _ } = optimize_expr(left, module, dict)
{ right, dict, _ } = optimize_expr(right, module, dict)
{ { :cons, line, left, right }, dict, nil }
end
defp optimize_expr({ :block, line, args }, dict) do
{ args, dict, res } = optimize_body(args, dict, [])
defp optimize_expr({ :block, line, args }, module, dict) do
{ args, dict, res } = optimize_body(args, module, dict, [])
{ { :block, line, args }, dict, res }
end
defp optimize_expr({ :tuple, line, args }, dict) do
{ args, dict, args_res } = optimize_tuple_args(args, dict)
defp optimize_expr({ :tuple, line, args }, module, dict) do
{ args, dict, args_res } = optimize_tuple_args(args, module, dict)
args_res = if Enum.any?(args_res), do: args_res, else: nil
res =
@@ -192,36 +200,36 @@ defmodule Kernel.RecordRewriter do
{ { :tuple, line, args }, dict, { res, args_res } }
end
defp optimize_expr({ :var, _, name } = var, dict) do
defp optimize_expr({ :var, _, name } = var, _module, dict) do
case :orddict.find(name, dict) do
{ :ok, res } -> { var, dict, res }
:error -> { var, dict, nil }
end
end
defp optimize_expr({ :case, line, expr, clauses }, dict) do
{ expr, dict, _ } = optimize_expr(expr, dict)
tuples = lc clause inlist clauses, do: optimize_clause(clause, dict)
defp optimize_expr({ :case, line, expr, clauses }, module, dict) do
{ expr, dict, _ } = optimize_expr(expr, module, dict)
tuples = lc clause inlist clauses, do: optimize_clause(clause, module, dict)
clauses = lc { clause, _, _ } inlist tuples, do: clause
dict = join_dict(tuples)
res = join_result(tuples)
{ { :case, line, expr, clauses }, dict, res }
end
defp optimize_expr({ :receive, line, clauses }, dict) do
tuples = lc clause inlist clauses, do: optimize_clause(clause, dict)
defp optimize_expr({ :receive, line, clauses }, module, dict) do
tuples = lc clause inlist clauses, do: optimize_clause(clause, module, dict)
clauses = lc { clause, _, _ } inlist tuples, do: clause
dict = join_dict(tuples)
res = join_result(tuples)
{ { :receive, line, clauses }, dict, res }
end
defp optimize_expr({ :receive, line, clauses, after_key, after_value }, dict) do
tuples = lc clause inlist clauses, do: optimize_clause(clause, dict)
defp optimize_expr({ :receive, line, clauses, after_key, after_value }, module, dict) do
tuples = lc clause inlist clauses, do: optimize_clause(clause, module, dict)
clauses = lc { clause, _, _ } inlist tuples, do: clause
{ after_key, dict, _ } = optimize_expr(after_key, dict)
{ after_value, dict, res } = optimize_body(after_value, dict, [])
{ after_key, dict, _ } = optimize_expr(after_key, module, dict)
{ after_value, dict, res } = optimize_body(after_value, module, dict, [])
dict = join_dict(tuples, dict)
res = join_result(tuples, res)
@@ -229,58 +237,58 @@ defmodule Kernel.RecordRewriter do
{ { :receive, line, clauses, after_key, after_value }, dict, res }
end
defp optimize_expr({ :try, line, body, elses, catches, try_after }, dict) do
tuples = lc clause inlist catches, do: optimize_clause(clause, dict)
defp optimize_expr({ :try, line, body, elses, catches, try_after }, module, dict) do
tuples = lc clause inlist catches, do: optimize_clause(clause, module, dict)
catches = lc { clause, _, _ } inlist tuples, do: clause
tuples = lc clause inlist elses, do: optimize_clause(clause, dict)
tuples = lc clause inlist elses, do: optimize_clause(clause, module, dict)
elses = lc { clause, _, _ } inlist tuples, do: clause
{ body, _, res } = optimize_body(body, dict, [])
{ body, _, res } = optimize_body(body, module, dict, [])
res = join_result(tuples, res)
{ try_after, _, _ } = optimize_body(try_after, dict, [])
{ try_after, _, _ } = optimize_body(try_after, module, dict, [])
{ { :try, line, body, elses, catches, try_after }, dict, res }
end
defp optimize_expr({ :fun, line, { :function, module, name, arity } }, dict) do
{ module, dict, _ } = optimize_expr(module, dict)
{ name, dict, _ } = optimize_expr(name, dict)
{ arity, dict, _ } = optimize_expr(arity, dict)
{ { :fun, line, { :function, module, name, arity } }, dict, nil }
defp optimize_expr({ :fun, line, { :function, receiver, name, arity } }, module, dict) do
{ receiver, dict, _ } = optimize_expr(receiver, module, dict)
{ name, dict, _ } = optimize_expr(name, module, dict)
{ arity, dict, _ } = optimize_expr(arity, module, dict)
{ { :fun, line, { :function, receiver, name, arity } }, dict, nil }
end
defp optimize_expr({ :fun, line, { :clauses, clauses } }, dict) do
defp optimize_expr({ :fun, line, { :clauses, clauses } }, module, dict) do
clauses = lc clause inlist clauses do
{ clause, _, _ } = optimize_clause(clause, dict)
{ clause, _, _ } = optimize_clause(clause, module, dict)
clause
end
{ { :fun, line, { :clauses, clauses } }, dict, nil }
end
defp optimize_expr({ comprehension, line, expr, args }, dict) when comprehension in [:lc, :bc] do
{ args, new_dict } = optimize_args(args, dict)
{ expr, _, _ } = optimize_expr(expr, new_dict)
defp optimize_expr({ comprehension, line, expr, args }, module, dict) when comprehension in [:lc, :bc] do
{ args, new_dict } = optimize_args(args, module, dict)
{ expr, _, _ } = optimize_expr(expr, module, new_dict)
{ { comprehension, line, expr, args }, dict, nil }
end
defp optimize_expr({ generate, line, left, right }, dict) when generate in [:generate, :b_generate] do
{ left, dict, _ } = optimize_expr(left, dict)
{ right, dict, _ } = optimize_expr(right, dict)
defp optimize_expr({ generate, line, left, right }, module, dict) when generate in [:generate, :b_generate] do
{ left, dict, _ } = optimize_expr(left, module, dict)
{ right, dict, _ } = optimize_expr(right, module, dict)
{ { generate, line, left, right }, dict, nil }
end
defp optimize_expr(other, dict) when elem(other, 0) in [:string, :atom, :integer, :float, :nil, :fun] do
defp optimize_expr(other, _module, dict) when elem(other, 0) in [:string, :atom, :integer, :float, :nil, :fun] do
{ other, dict, nil }
end
## Helpers
defp optimize_tuple_args(args, dict) do
defp optimize_tuple_args(args, module, dict) do
{ final_args, { final_dict, final_acc } } =
Enum.map_reduce args, { dict, [] }, fn(arg, { acc_dict, acc_res }) ->
{ new_arg, new_acc, res } = optimize_expr(arg, acc_dict)
{ new_arg, new_acc, res } = optimize_expr(arg, module, acc_dict)
{ new_arg, { new_acc, [res|acc_res] } }
end
@@ -383,4 +391,4 @@ defmodule Kernel.RecordRewriter do
defp join_result([], res) do
res
end
end
end
+47 -76
View File
@@ -46,7 +46,7 @@ defmodule Kernel.SpecialForms do
## Examples
:<<>>.(1,2,3)
iex> << 1, 2, 3 >>
<< 1, 2, 3 >>
## Bitstring types
@@ -55,16 +55,16 @@ defmodule Kernel.SpecialForms do
specific types. Most of the time, Elixir will figure out
the part's type and won't require any work from you:
<<102, "oo">>
#=> "foo"
iex> <<102, "oo">>
"foo"
Above we have two parts: the first is an integer and the
second is a binary. If we use any other Elixir expression,
Elixir can no longer guess the type:
rest = "oo"
<<102, rest>>
#=> ** (ArgumentError) argument error
iex> rest = "oo"
...> <<102, rest>>
** (ArgumentError) argument error
When a variable or expression is given as a binary part,
Elixir defaults the type of that part to an unsigned
@@ -94,8 +94,8 @@ defmodule Kernel.SpecialForms do
signedness only matters for matching. If unspecified, it
defaults to unsigned. Example:
<<-100 :: signed, rest :: binary>> = <<-100, "foo">>
#=> <<156,102,111,111>>
iex> <<-100 :: signed, _rest :: binary>> = <<-100, "foo">>
<<156,102,111,111>>
This match would have failed if we did not specify that the
value -100 is signed. If we're matching into a variable instead
@@ -103,9 +103,9 @@ defmodule Kernel.SpecialForms do
will simply be interpreted as having the given (or implied)
signedness, e.g.:
<<val, rest :: binary>> = <<-100, "foo">>
val
#=> 156
iex> <<val, _rest :: binary>> = <<-100, "foo">>
...> val
156
Here, `val` is interpreted as unsigned.
@@ -130,13 +130,13 @@ defmodule Kernel.SpecialForms do
unit is multiplied by the size to give the effective size of
the part:
<<102, rest :: [size(2), unit(8)]>> = "foo"
iex> <<102, _rest :: [size(2), unit(8)]>> = "foo"
"foo"
<<102, rest :: size(16)>> = "foo"
iex> <<102, _rest :: size(16)>> = "foo"
"foo"
<<102, rest :: size(32)>> = "foo"
iex> <<102, _rest :: size(32)>> = "foo"
** (MatchError) no match of right hand side value: "foo"
In the example above, the first two expressions matches
@@ -158,7 +158,8 @@ defmodule Kernel.SpecialForms do
writing `size(8)`, one can write just `8` and it will be interpreted
as `size(8)`
<< 1 :: 3 >> == << 1 :: size(3) >> #=> true
iex> << 1 :: 3 >> == << 1 :: size(3) >>
true
The default unit for integers, floats, and bitstrings is 1. For
binaries, it is 8.
@@ -245,8 +246,9 @@ defmodule Kernel.SpecialForms do
import those functions and reference them as local functions,
for example:
import List
flatten([1,[2],3]) #=> [1,2,3]
iex> import List
...> flatten([1,[2],3])
[1,2,3]
## Selector
@@ -302,6 +304,13 @@ defmodule Kernel.SpecialForms do
All imported modules are also required by default. `import`
also accepts `as:` as an option so it automatically sets up
an alias. Please check `alias` for more information.
## Ambiguous function/macro names
If two modules `A` and `B` are imported and they both contain
a `foo` function with an arity of `1`, an error is only emitted
if an ambiguous call to `foo/1` is actually made; that is, the
errors are emitted lazily, not eagerly.
"""
defmacro import(module, opts)
@@ -331,8 +340,8 @@ defmodule Kernel.SpecialForms do
## Examples
quote do: sum(1, 2, 3)
#=> { :sum, [], [1, 2, 3] }
iex> quote do: sum(1, 2, 3)
{ :sum, [], [1, 2, 3] }
## Explanation
@@ -607,82 +616,44 @@ defmodule Kernel.SpecialForms do
"""
defmacro alias!(alias)
@doc """
Unquotes the given expression from inside a macro.
## Examples
Imagine the situation you have a variable `name` and
you want to inject it inside some quote. The first attempt
would be:
value = 13
quote do: sum(1, value, 3)
Which would then return:
{ :sum, [], [1, { :value, [], quoted }, 3] }
Which is not the expected result. For this, we use unquote:
value = 13
quote do: sum(1, unquote(value), 3)
#=> { :sum, [], [1, 13, 3] }
"""
defmacro unquote(expr)
@doc """
Unquotes the given list expanding its arguments. Similar
to unquote.
## Examples
values = [2,3,4]
quote do: sum(1, unquote_splicing(values), 5)
#=> { :sum, [], [1, 2, 3, 4, 5] }
"""
defmacro unquote_splicing(expr)
@doc """
List comprehensions allow you to quickly build a list from another list:
lc n inlist [1,2,3,4], do: n * 2
#=> [2,4,6,8]
iex> lc n inlist [1,2,3,4], do: n * 2
[2,4,6,8]
A comprehension accepts many generators and also filters. Generators
are defined using both `inlist` and `inbits` operators, allowing you
to loop lists and bitstrings:
# A list generator:
lc n inlist [1,2,3,4], do: n * 2
#=> [2,4,6,8]
iex> lc n inlist [1,2,3,4], do: n * 2
[2,4,6,8]
# A bit string generator:
lc <<n>> inbits <<1,2,3,4>>, do: n * 2
#=> [2,4,6,8]
iex> lc <<n>> inbits <<1,2,3,4>>, do: n * 2
[2,4,6,8]
# A generator from a variable:
list = [1,2,3,4]
lc n inlist list, do: n * 2
#=> [2,4,6,8]
iex> list = [1,2,3,4]
...> lc n inlist list, do: n * 2
[2,4,6,8]
# A comprehension with two generators
lc x inlist [1,2], y inlist [2,3], do: x*y
#=> [2,3,4,6]
iex> lc x inlist [1,2], y inlist [2,3], do: x*y
[2,3,4,6]
Filters can also be given:
# A comprehension with a generator and a filter
lc n inlist [1,2,3,4,5,6], rem(n, 2) == 0, do: n
#=> [2,4,6]
iex> lc n inlist [1,2,3,4,5,6], rem(n, 2) == 0, do: n
[2,4,6]
Bit string generators are quite useful when you need to
organize bit string streams:
iex> pixels = <<213,45,132,64,76,32,76,0,0,234,32,15>>
iex> lc <<r:8,g:8,b:8>> inbits pixels, do: {r,g,b}
iex> lc <<r::8,g::8,b::8>> inbits pixels, do: {r,g,b}
[{213,45,132},{64,76,32},{76,0,0},{234,32,15}]
"""
@@ -694,7 +665,7 @@ defmodule Kernel.SpecialForms do
be a bitstring. For example, here is how to remove all
spaces from a string:
bc <<c>> inbits " hello world ", c != ? , do: <<c>>
iex> bc <<c>> inbits " hello world ", c != ? , do: <<c>>
"helloworld"
"""
@@ -705,8 +676,8 @@ defmodule Kernel.SpecialForms do
of expressions in Elixir. This special form is private
and should not be invoked directly:
quote do: (1; 2; 3)
#=> { :__block__, [], [1,2,3] }
iex> quote do: (1; 2; 3)
{ :__block__, [], [1,2,3] }
"""
defmacro __block__(args)
@@ -728,13 +699,13 @@ defmodule Kernel.SpecialForms do
This is the special form used to hold aliases information.
It is usually compiled to an atom:
quote do: Foo.Bar
quote do: Foo.Bar #=>
{ :__aliases__, [], [:Foo,:Bar] }
Elixir represents `Foo.Bar` as `__aliases__` so calls can be
unambiguously identified by the operator `:.`. For example:
quote do: Foo.bar
quote do: Foo.bar #=>
{{:.,[],[{:__aliases__,[],[:Foo]},:bar]},[],[]}
Whenever an expression iterator sees a `:.` as the tuple key,
+48 -39
View File
@@ -59,8 +59,8 @@ defmodule Keyword do
## Examples
Keyword.new [{:b,1},{:a,2}]
#=> [a: 2, b: 1]
iex> Keyword.new [{:b,1},{:a,2}]
[a: 2, b: 1]
"""
@spec new(Enum.t) :: t
@@ -77,8 +77,8 @@ defmodule Keyword do
## Examples
Keyword.new [:a, :b], fn x -> {x,x} end
#=> [a: :a, b: :b]
iex> Enum.sort Keyword.new [:a, :b], fn x -> {x,x} end
[a: :a, b: :b]
"""
@spec new(Enum.t, ({key, value} -> {key, value})) :: t
@@ -100,9 +100,12 @@ defmodule Keyword do
## Examples
Keyword.get [a: 1], :a #=> 1
Keyword.get [a: 1], :b #=> nil
Keyword.get [a: 1], :b, 3 #=> 3
iex> Keyword.get [a: 1], :a
1
iex> Keyword.get [a: 1], :b
nil
iex> Keyword.get [a: 1], :b, 3
3
"""
@spec get(t, key) :: value
@@ -120,8 +123,10 @@ defmodule Keyword do
## Examples
Keyword.get! [a: 1], :a #=> 1
Keyword.get! [a: 1], :b #=> raises KeyError[key: :b]
iex> Keyword.get! [a: 1], :a
1
iex> Keyword.get! [a: 1], :b
** (KeyError) key not found: :b
"""
@spec get!(t, key) :: value | no_return
@@ -137,8 +142,8 @@ defmodule Keyword do
## Examples
Keyword.get_values [a: 1, a: 2], :a
#=> [1,2]
iex> Keyword.get_values [a: 1, a: 2], :a
[1,2]
"""
@spec get_values(t, key) :: [value]
@@ -152,7 +157,8 @@ defmodule Keyword do
## Examples
Keyword.keys [a: 1, b: 2] #=> [:a,:b]
iex> Keyword.keys [a: 1, b: 2]
[:a,:b]
"""
@spec keys(t) :: [key]
@@ -165,7 +171,8 @@ defmodule Keyword do
## Examples
Keyword.values [a: 1, b: 2] #=> [1,2]
iex> Keyword.values [a: 1, b: 2]
[1,2]
"""
@spec values(t) :: [value]
@@ -181,8 +188,10 @@ defmodule Keyword do
## Examples
Keyword.delete [a: 1, b: 2], :a #=> [b: 2]
Keyword.delete [b: 2], :a #=> [b: 2]
iex> Keyword.delete [a: 1, b: 2], :a
[b: 2]
iex> Keyword.delete [b: 2], :a
[b: 2]
"""
@spec delete(t, key) :: t
@@ -198,8 +207,8 @@ defmodule Keyword do
## Examples
Keyword.put [a: 1, b: 2], :a, 3
#=> [a: 3, b: 2]
iex> Keyword.put [a: 1, b: 2], :a, 3
[a: 3, b: 2]
"""
@spec put(t, key, value) :: t
@@ -213,8 +222,8 @@ defmodule Keyword do
## Examples
Keyword.put_new [a: 1, b: 2], :a, 3
#=> [a: 1, b: 2]
iex> Keyword.put_new [a: 1, b: 2], :a, 3
[a: 1, b: 2]
"""
@spec put_new(t, key, value) :: t
@@ -231,8 +240,8 @@ defmodule Keyword do
## Examples
Keyword.equal? [a: 1, b: 2], [b: 2, a: 1]
#=> true
iex> Keyword.equal? [a: 1, b: 2], [b: 2, a: 1]
true
"""
@spec equal?(t, t) :: boolean
@@ -246,8 +255,8 @@ defmodule Keyword do
## Examples
Keyword.merge [a: 1, b: 2], [a: 3, d: 4]
#=> [a:3, b:2, d: 4]
iex> Enum.sort Keyword.merge [a: 1, b: 2], [a: 3, d: 4]
[a: 3, b: 2, d: 4]
"""
@spec merge(t, t) :: t
@@ -261,10 +270,10 @@ defmodule Keyword do
## Examples
Keyword.merge [a: 1, b: 2], [a: 3, d: 4], fn _k, v1, v2 ->
v1 + v2
end
#=> [a:4, b:2, d: 4]
iex> Keyword.merge [a: 1, b: 2], [a: 3, d: 4], fn _k, v1, v2 ->
...> v1 + v2
iex> end
[a: 4, b: 2, d: 4]
"""
@spec merge(t, t, (key, value, value -> value)) :: t
@@ -285,10 +294,10 @@ defmodule Keyword do
## Examples
Keyword.has_key?([a: 1], :a)
#=> true
Keyword.has_key?([a: 1], :b)
#=> false
iex> Keyword.has_key?([a: 1], :a)
true
iex> Keyword.has_key?([a: 1], :b)
false
"""
@spec has_key?(t, key) :: boolean
@@ -302,10 +311,10 @@ defmodule Keyword do
## Examples
Keyword.update([a: 1], :a, &1 * 2)
#=> [a: 2]
Keyword.update([a: 1], :b, &1 * 2)
#=> KeyError
iex> Keyword.update([a: 1], :a, &1 * 2)
[a: 2]
iex> Keyword.update([a: 1], :b, &1 * 2)
** (KeyError) key not found: :b
"""
@spec update(t, key, (value -> value)) :: t | no_return
@@ -327,10 +336,10 @@ defmodule Keyword do
## Examples
Keyword.update([a: 1], :a, 13, &1 * 2)
#=> [a: 2]
Keyword.update([a: 1], :b, 11, &1 * 2)
#=> [a: 1, b: 11]
iex> Keyword.update([a: 1], :a, 13, &1 * 2)
[a: 2]
iex> Keyword.update([a: 1], :b, 11, &1 * 2)
[a: 1, b: 11]
"""
@spec update(t, key, value, (value -> value)) :: t
+62 -61
View File
@@ -15,8 +15,8 @@ defmodule List do
## Examples
List.concat [[1,[2],3], [4], [5,6]]
#=> [1,[2],3,4,5,6]
iex> List.concat [[1,[2],3], [4], [5,6]]
[1,[2],3,4,5,6]
"""
def concat(list) when is_list(list) do
@@ -32,8 +32,8 @@ defmodule List do
## Examples
List.concat [1,2,3], [4,5,6]
#=> [1,2,3,4,5,6]
iex> List.concat [1,2,3], [4,5,6]
[1,2,3,4,5,6]
"""
def concat(list, elements) when is_list(list) and is_list(elements) do
@@ -47,8 +47,8 @@ defmodule List do
## Examples
List.delete([1,2,3], 1)
#=> [2,3]
iex> List.delete([1,2,3], 1)
[2,3]
"""
def delete(list, item) do
@@ -60,11 +60,11 @@ defmodule List do
## Examples
List.duplicate "hello", 3
#=> ["hello","hello","hello"]
iex> List.duplicate "hello", 3
["hello","hello","hello"]
List.duplicate [1,2], 2
#=> [[1,2],[1,2]]
iex> List.duplicate [1,2], 2
[[1,2],[1,2]]
"""
def duplicate(elem, n) do
:lists.duplicate(n, elem)
@@ -77,11 +77,11 @@ defmodule List do
## Examples
List.flatten [1,[[2],3]]
#=> [1,2,3]
iex> List.flatten [1,[[2],3]]
[1,2,3]
List.flatten [1,[[2],3]], [4,5]
#=> [1,2,3,4,5]
iex> List.flatten [1,[[2],3]], [4,5]
[1,2,3,4,5]
"""
def flatten(list) do
@@ -98,11 +98,11 @@ defmodule List do
## Examples
List.foldl [5,5], 10, fn x, acc -> x + acc end
#=> 20
iex> List.foldl [5,5], 10, fn x, acc -> x + acc end
20
List.foldl [1,2,3,4], 0, fn x, acc -> x - acc end
#=> 2
iex> List.foldl [1,2,3,4], 0, fn x, acc -> x - acc end
2
"""
def foldl(list, acc, function) when is_list(list) and is_function(function) do
@@ -115,8 +115,8 @@ defmodule List do
## Examples
List.foldr [1,2,3,4], 0, fn x, acc -> x - acc end
#=> -2
iex> List.foldr [1,2,3,4], 0, fn x, acc -> x - acc end
-2
"""
def foldr(list, acc, function) when is_list(list) and is_function(function) do
@@ -128,12 +128,12 @@ defmodule List do
## Examples
List.last []
#=> nil
List.last [1]
#=> 1
List.last [1, 2, 3]
#=> 3
iex> List.last []
nil
iex> List.last [1]
1
iex> List.last [1, 2, 3]
3
"""
def last([]), do: nil
@@ -149,11 +149,11 @@ defmodule List do
## Examples
List.member? [1,2,3], 1
#=> true
iex> List.member? [1,2,3], 1
true
List.member? [1,2,3], 0
#=> false
iex> List.member? [1,2,3], 0
false
"""
def member?(list, term) do
@@ -167,14 +167,14 @@ defmodule List do
## Examples
List.keyfind([a: 1, b: 2], :a, 0)
#=> { :a, 1 }
iex> List.keyfind([a: 1, b: 2], :a, 0)
{ :a, 1 }
List.keyfind([a: 1, b: 2], 2, 1)
#=> { :b, 2 }
iex> List.keyfind([a: 1, b: 2], 2, 1)
{ :b, 2 }
List.keyfind([a: 1, b: 2], :c, 0)
#=> nil
iex> List.keyfind([a: 1, b: 2], :c, 0)
nil
"""
def keyfind(list, key, position, default // nil) do
@@ -188,14 +188,14 @@ defmodule List do
## Examples
List.keymember?([a: 1, b: 2], :a, 0)
#=> true
iex> List.keymember?([a: 1, b: 2], :a, 0)
true
List.keymember?([a: 1, b: 2], 2, 1)
#=> true
iex> List.keymember?([a: 1, b: 2], 2, 1)
true
List.keymember?([a: 1, b: 2], :c, 0)
#=> false
iex> List.keymember?([a: 1, b: 2], :c, 0)
false
"""
def keymember?(list, key, position) do
@@ -208,8 +208,8 @@ defmodule List do
## Examples
List.keyreplace([a: 1, b: 2], :a, 0, { :a, 3 })
#=> [a: 3, b: 2]
iex> List.keyreplace([a: 1, b: 2], :a, 0, { :a, 3 })
[a: 3, b: 2]
"""
def keyreplace(list, key, position, new_tuple) do
@@ -223,8 +223,8 @@ defmodule List do
## Examples
List.keystore([a: 1, b: 2], :a, 0, { :a, 3 })
#=> [a: 3, b: 2]
iex> List.keystore([a: 1, b: 2], :a, 0, { :a, 3 })
[a: 3, b: 2]
"""
def keystore(list, key, position, new_tuple) do
@@ -238,14 +238,14 @@ defmodule List do
## Examples
List.keydelete([a: 1, b: 2], :a, 0)
#=> [{ :b, 2 }]
iex> List.keydelete([a: 1, b: 2], :a, 0)
[{ :b, 2 }]
List.keydelete([a: 1, b: 2], 2, 1)
#=> [{ :a, 1 }]
iex> List.keydelete([a: 1, b: 2], 2, 1)
[{ :a, 1 }]
List.keydelete([a: 1, b: 2], :c, 0)
#=> [{ :a, 1 }, { :b, 2 }]
iex> List.keydelete([a: 1, b: 2], :c, 0)
[{ :a, 1 }, { :b, 2 }]
"""
def keydelete(list, key, position) do
@@ -259,7 +259,8 @@ defmodule List do
## Examples
List.wrap [1,2,3] #=> [1,2,3]
iex> List.wrap [1,2,3]
[1,2,3]
"""
def wrap(list) when is_list(list) do
@@ -279,11 +280,11 @@ defmodule List do
## Examples
List.zip [[1, 2], [3, 4], [5, 6]]
#=> [{1, 3, 5}, {2, 4, 6}]
iex> List.zip [[1, 2], [3, 4], [5, 6]]
[{1, 3, 5}, {2, 4, 6}]
List.zip [[1, 2], [3], [5, 6]]
#=> [{1, 3, 5}]
iex> List.zip [[1, 2], [3], [5, 6]]
[{1, 3, 5}]
"""
def zip([]), do: []
@@ -297,11 +298,11 @@ defmodule List do
## Examples
List.unzip [{1, 2}, {3, 4}]
#=> [[1, 3], [2, 4]]
iex> List.unzip [{1, 2}, {3, 4}]
[[1, 3], [2, 4]]
List.unzip [{1, :a, "apple"}, {2, :b, "banana"}, {3, :c}]
#=> [[1, 2, 3], [:a, :b, :c]]
iex> List.unzip [{1, :a, "apple"}, {2, :b, "banana"}, {3, :c}]
[[1, 2, 3], [:a, :b, :c]]
"""
def unzip(list) when is_list(list) do
+118 -49
View File
@@ -42,7 +42,6 @@ defmodule Macro do
extract_args(quote do: foo) == { :foo, [] }
extract_args(quote do: foo()) == { :foo, [] }
extract_args(quote do: :foo.()) == { :foo, [] }
extract_args(quote do: foo(1,2,3)) == { :foo, [1,2,3] }
extract_args(quote do: 1.(1,2,3)) == :error
@@ -52,31 +51,91 @@ defmodule Macro do
end
@doc """
Recursively escapes the given value so it can be inserted
into a syntax tree. Structures that are valid syntax nodes
(like atoms, integers, binaries) are represented by themselves.
Recursively escapes a value so it can be inserted
into a syntax tree.
## Examples
Macro.escape(:foo)
#=> :foo
iex> Macro.escape(:foo)
:foo
iex> Macro.escape({ :a, :b, :c })
{ :{}, [], [:a, :b, :c] }
Macro.escape({ :a, :b, :c })
#=> { :{}, [], [:a, :b, :c] }
"""
def escape({ left, right }) do
{ escape(left), escape(right) }
def escape(expr) do
do_escape(expr, false)
end
def escape(tuple) when is_tuple(tuple) do
{ :{}, [], escape(tuple_to_list(tuple)) }
defp do_escape({ { { :., meta, [left, :unquote] }, _, [expr] }, _, args }, true) do
all = [do_escape(meta, true), do_escape(left, true), expr, do_escape(args, true)]
quote do
apply :elixir_quote, :unquote, [unquote_splicing(all), __FILE__]
end
end
def escape(list) when is_list(list) do
lc item inlist list, do: escape(item)
defp do_escape({ { :., meta, [left, :unquote] }, _, [expr] }, true) do
all = [do_escape(meta, true), do_escape(left, true), expr, nil]
quote do
apply :elixir_quote, :unquote, [unquote_splicing(all), __FILE__]
end
end
def escape(other), do: other
defp do_escape({ :unquote, _meta, [expr] }, true) do
expr
end
defp do_escape({ left, right }, unquote) when
(not is_tuple(left) or elem(left, 1) != :unquote_splicing) and
(not is_tuple(right) or elem(right, 1) != :unquote_splicing) do
{ do_escape(left, unquote), do_escape(right, unquote) }
end
defp do_escape({ :quote, meta, args }, true) when length(args) in 1..2 do
{ :{}, [], do_escape([:quote, meta, args], false) }
end
defp do_escape(tuple, unquote) when is_tuple(tuple) do
{ :{}, [], do_escape(tuple_to_list(tuple), unquote) }
end
defp do_escape(list, true) when is_list(list) do
do_splice(Enum.reverse(list))
end
defp do_escape(list, false) when is_list(list) do
lc item inlist list, do: do_escape(item, false)
end
defp do_escape(other, _unquote), do: other
defp do_splice([{ :|, meta, [{ :unquote_splicing, _, [left] }, right] }|t]) do
spliced = { :++, meta, [do_splice(t, [], []), left] }
{ :++, meta, [spliced, right] }
end
defp do_splice(list) do
do_splice(list, [], [])
end
defp do_splice([{ :unquote_splicing, _meta, [expr] }|t], buffer, acc) do
do_splice(t, [], do_splice_join(do_splice_join(expr, buffer), acc))
end
defp do_splice([h|t], buffer, acc) do
do_splice t, [do_escape(h, true)|buffer], acc
end
defp do_splice([], buffer, acc), do: do_splice_join(buffer, acc)
defp do_splice_join([], right), do: right
defp do_splice_join(left, []), do: left
defp do_splice_join(left, right), do: { :++, [], [left, right] }
@doc false
def escape_quoted(expr) do
do_escape(expr, true)
end
@doc %B"""
Unescape the given chars. This is the unescaping behavior
@@ -93,8 +152,8 @@ defmodule Macro do
## Examples
Macro.unescape_binary "example\\n"
#=> "example\n"
iex> Macro.unescape_binary "example\\n"
"example\n"
In the example above, we pass a string with `\n` escaped
and we return a version with it unescaped.
@@ -105,8 +164,8 @@ defmodule Macro do
@doc %B"""
Unescape the given chars according to the map given.
Check `unescape/1` if you want to use the same map as
Elixir single- and double-quoted strings.
Check `unescape_binary/1` if you want to use the same map
as Elixir single- and double-quoted strings.
## Map
@@ -152,11 +211,13 @@ defmodule Macro do
@doc """
Unescape the given tokens according to the default map.
Check `unescape/1` and `unescape/2` for more information
about unescaping. Only tokens that are binaries are
unescaped, all others are ignored. This function is useful
when implementing your own sigils. Check the implementation
of `Kernel.__b__` for examples.
Check `unescape_binary/1` and `unescape_binary/2` for more
information about unescaping.
Only tokens that are binaries are unescaped, all others are
ignored. This function is useful when implementing your own
sigils. Check the implementation of `Kernel.__b__`
for examples.
"""
def unescape_tokens(tokens) do
:elixir_interpolation.unescape_tokens(tokens)
@@ -164,7 +225,7 @@ defmodule Macro do
@doc """
Unescape the given tokens according to the given map.
Check `unescape_tokens/1` and `unescaped/2` for more information.
Check `unescape_tokens/1` and `unescape_binary/2` for more information.
"""
def unescape_tokens(tokens, map) do
:elixir_interpolation.unescape_tokens(tokens, map)
@@ -175,8 +236,8 @@ defmodule Macro do
## Examples
Macro.to_binary(quote do: foo.bar(1, 2, 3))
#=> "foo.bar(1, 2, 3)"
iex> Macro.to_binary(quote do: foo.bar(1, 2, 3))
"foo.bar(1, 2, 3)"
"""
def to_binary(tree)
@@ -366,9 +427,12 @@ defmodule Macro do
In case the expression cannot be expanded, it returns the expression itself.
Notice that `Macro.expand` is not recursive and it does not
expand child expressions. For example, `!some_macro` will expand as:
expand child expressions. In this example
Macro.expand(quote(do: !some_macro), __ENV__)
`!some_macro` will expand to something like:
iex> IO.puts Macro.to_binary Macro.expand(quote(do: !some_macro), __ENV__)
case some_macro do
false -> true
nil -> true
@@ -440,13 +504,15 @@ defmodule Macro do
end
"""
def expand(aliases, env)
def expand(aliases, env) do
expand(aliases, env, nil)
end
def expand({ :__aliases__, _, _ } = original, env) do
defp expand({ :__aliases__, _, _ } = original, env, cache) do
case :elixir_aliases.expand(original, env.aliases, []) do
atom when is_atom(atom) -> atom
aliases ->
aliases = lc alias inlist aliases, do: expand(alias, env)
aliases = lc alias inlist aliases, do: expand(alias, env, cache)
case :lists.all(is_atom(&1), aliases) do
true -> :elixir_aliases.concat(aliases)
@@ -456,7 +522,7 @@ defmodule Macro do
end
# Expand @ calls
def expand({ :@, _, [{ name, _, args }] } = original, env) when is_atom(args) or args == [] do
defp expand({ :@, _, [{ name, _, args }] } = original, env, _cache) when is_atom(args) or args == [] do
case (module = env.module) && Module.open?(module) do
true -> Module.get_attribute(module, name)
false -> original
@@ -464,12 +530,12 @@ defmodule Macro do
end
# Expand pseudo-variables
def expand({ :__MODULE__, _, atom }, env) when is_atom(atom), do: env.module
def expand({ :__FILE__, _, atom }, env) when is_atom(atom), do: env.file
def expand({ :__ENV__, _, atom }, env) when is_atom(atom), do: env
defp expand({ :__MODULE__, _, atom }, env, _cache) when is_atom(atom), do: env.module
defp expand({ :__FILE__, _, atom }, env, _cache) when is_atom(atom), do: env.file
defp expand({ :__ENV__, _, atom }, env, _cache) when is_atom(atom), do: env
# Expand possible macro import invocation
def expand({ atom, line, args } = original, env) when is_atom(atom) do
defp expand({ atom, line, args } = original, env, cache) when is_atom(atom) do
args = case is_atom(args) do
true -> []
false -> args
@@ -487,7 +553,7 @@ defmodule Macro do
end
expand = :elixir_dispatch.expand_import(line, { atom, length(args) }, args,
env.module, env.function, env.requires, extra ++ env.macros, env)
env.module, extra, to_erl_env(env, cache))
case expand do
{ :ok, _, expanded } -> expanded
{ :error, _ } -> original
@@ -496,23 +562,26 @@ defmodule Macro do
end
# Expand possible macro require invocation
def expand({ { :., _, [left, right] }, line, args } = original, env) when is_atom(right) do
defp expand({ { :., _, [left, right] }, line, args } = original, env, cache) when is_atom(right) do
receiver = expand(left, env)
case is_atom(receiver) and not is_partial?(args) do
false -> original
true ->
expand = :elixir_dispatch.expand_require(line, receiver, { right, length(args) },
args, env.module, env.function, env.requires, env)
args, env.module, to_erl_env(env, cache))
case expand do
{ :ok, expanded } -> expanded
{ :error, _ } -> original
{ :ok, _receiver, expanded } -> expanded
{ :error, _ } -> original
end
end
end
# Anything else is just returned
def expand(other, _env), do: other
defp expand(other, _env, _cache), do: other
defp to_erl_env(env, nil), do: :elixir_scope.to_erl_env(env)
defp to_erl_env(_env, cache), do: cache
## Helpers
@@ -530,16 +599,16 @@ defmodule Macro do
do_safe_term(terms) || :ok
end
def do_safe_term({ local, _, terms }) when local in [:{}, :[], :__aliases__] do
defp do_safe_term({ local, _, terms }) when local in [:{}, :[], :__aliases__] do
do_safe_term(terms)
end
def do_safe_term({ unary, _, [term] }) when unary in [:+, :-] do
defp do_safe_term({ unary, _, [term] }) when unary in [:+, :-] do
do_safe_term(term)
end
def do_safe_term({ left, right }), do: do_safe_term(left) || do_safe_term(right)
def do_safe_term(terms) when is_list(terms), do: Enum.find_value(terms, do_safe_term(&1))
def do_safe_term(terms) when is_tuple(terms), do: { :unsafe, terms }
def do_safe_term(_), do: nil
defp do_safe_term({ left, right }), do: do_safe_term(left) || do_safe_term(right)
defp do_safe_term(terms) when is_list(terms), do: Enum.find_value(terms, do_safe_term(&1))
defp do_safe_term(terms) when is_tuple(terms), do: { :unsafe, terms }
defp do_safe_term(_), do: nil
end
+2 -2
View File
@@ -25,7 +25,7 @@ defmodule Macro.Env do
@type file :: binary
@type line :: non_neg_integer
@type aliases :: [{ module, module }]
@type context :: :assign | :guard | nil
@type context :: :match | :guard | nil
@type requires :: [module]
@type functions :: [{ module, [name_arity] }]
@type macros :: [{ module, [name_arity] }]
@@ -58,7 +58,7 @@ defmodule Macro.Env do
Returns wether the compilation environment is currently
inside a match clause.
"""
def in_match?(record), do: context(record) == :assign
def in_match?(record), do: context(record) == :match
@doc """
Returns the environment stacktrace.
+24 -15
View File
@@ -116,9 +116,12 @@ defmodule Module do
## Examples
Module.concat [Foo, Bar] #=> Foo.Bar
Module.concat [Foo, "Bar"] #=> Foo.Bar
Module.concat [Foo, 'Bar'] #=> Foo.Bar
iex> Module.concat [Foo, Bar]
Foo.Bar
iex> Module.concat [Foo, "Bar"]
Foo.Bar
iex> Module.concat [Foo, 'Bar']
Foo.Bar
"""
def concat(list) when is_list(list) do
@@ -131,9 +134,12 @@ defmodule Module do
## Examples
Module.concat Foo, Bar #=> Foo.Bar
Module.concat Foo, "Bar" #=> Foo.Bar
Module.concat Foo, 'Bar' #=> Foo.Bar
iex> Module.concat Foo, Bar
Foo.Bar
iex> Module.concat Foo, "Bar"
Foo.Bar
iex> Module.concat Foo, 'Bar'
Foo.Bar
"""
def concat(left, right) do
@@ -148,11 +154,11 @@ defmodule Module do
## Examples
Module.safe_concat [Unknown, Module]
#=> ArgumentError
iex> Module.safe_concat [Unknown, Module]
** (ArgumentError) argument error
Module.safe_concat [List, Chars]
#=> List.Chars
iex> Module.safe_concat [List, Chars]
List.Chars
"""
def safe_concat(list) when is_list(list) do
@@ -167,11 +173,11 @@ defmodule Module do
## Examples
Module.safe_concat Unknown, Module
#=> ArgumentError
iex> Module.safe_concat Unknown, Module
** (ArgumentError) argument error
Module.safe_concat List, Chars
#=> List.Chars
iex> Module.safe_concat List, Chars
List.Chars
"""
def safe_concat(left, right) do
@@ -474,7 +480,8 @@ defmodule Module do
When registering an attribute, two options can be given:
* `:accumulate` - Several calls to the same attribute will
accumulate instead of override the previous one;
accumulate instead of override the previous one. New attributes
are always added to the top of the accumulated list.
* `:persist` - The attribute will be persisted in the Erlang
Abstract Format. Useful when interfacing with Erlang libraries.
@@ -527,6 +534,8 @@ defmodule Module do
@doc """
Convert a module name to binary without the Elixir prefix.
"""
def to_binary(Elixir), do: "Elixir"
def to_binary(module) do
"Elixir-" <> rest = Binary.Chars.to_binary(module)
bc <<r>> inbits rest, do: <<to_dot(r)>>
+16 -16
View File
@@ -5,21 +5,21 @@ defmodule OptionParser do
## Examples
OptionParser.parse(["--debug"])
#=> { [debug: true], [] }
iex> OptionParser.parse(["--debug"])
{ [debug: true], [] }
OptionParser.parse(["--source", "lib"])
#=> { [source: "lib"], [] }
iex> OptionParser.parse(["--source", "lib"])
{ [source: "lib"], [] }
OptionParser.parse(["--source", "lib", "test/enum_test.exs", "--verbose"])
#=> { [source: "lib", verbose: true], ["test/enum_test.exs"] }
iex> OptionParser.parse(["--source", "lib", "test/enum_test.exs", "--verbose"])
{ [source: "lib", verbose: true], ["test/enum_test.exs"] }
## Aliases
A set of aliases can be given as second argument:
OptionParser.parse(["-d"], aliases: [d: :debug])
#=> { [debug: true], [] }
iex> OptionParser.parse(["-d"], aliases: [d: :debug])
{ [debug: true], [] }
## Switches
@@ -39,19 +39,19 @@ defmodule OptionParser do
Examples:
OptionParser.parse(["--unlock path/to/file"], switches: [unlock: :boolean])
#=> { [unlock: true], ["path/to/file"] }
iex> OptionParser.parse(["--unlock", "path/to/file"], switches: [unlock: :boolean])
{ [unlock: true], ["path/to/file"] }
OptionParser.parse(["--unlock false path/to/file"], switches: [unlock: :boolean])
#=> { [unlock: false], ["path/to/file"] }
iex> OptionParser.parse(["--unlock", "false", "path/to/file"], switches: [unlock: :boolean])
{ [unlock: false], ["path/to/file"] }
## Negation switches
Any switches starting with `--no-` are always considered to be
booleans and never parse the next value:
OptionParser.parse(["--no-op path/to/file"])
#=> { [no_op: true], ["path/to/file"] }
iex> OptionParser.parse(["--no-op", "path/to/file"])
{ [no_op: true], ["path/to/file"] }
"""
def parse(argv, opts // []) when is_list(argv) and is_list(opts) do
@@ -66,8 +66,8 @@ defmodule OptionParser do
## Example
OptionParser.parse_head(["--source", "lib", "test/enum_test.exs", "--verbose"])
#=> { [source: "lib"], ["test/enum_test.exs", "--verbose"] }
iex> OptionParser.parse_head(["--source", "lib", "test/enum_test.exs", "--verbose"])
{ [source: "lib"], ["test/enum_test.exs", "--verbose"] }
"""
def parse_head(argv, opts // []) when is_list(argv) and is_list(opts) do
+74 -67
View File
@@ -14,6 +14,9 @@ defmodule Path do
alias :filename, as: FN
@type t :: char_list | atom | binary
@type r :: char_list | binary
@doc """
Converts the given filename and returns an absolute name.
Differently from `Path.expand/1`, no attempt is made to
@@ -49,11 +52,11 @@ defmodule Path do
## Examples
Path.absname("foo", "bar")
#=> "bar/foo"
iex> Path.absname("foo", "bar")
"bar/foo"
Path.absname("../x", "bar")
#=> "bar/../x"
iex> Path.absname("../x", "bar")
"bar/../x"
"""
def absname(path, relative_to) do
@@ -66,11 +69,12 @@ defmodule Path do
## Examples
Path.expand("/foo/bar/../bar") == "/foo/bar"
iex> Path.expand("/foo/bar/../bar")
"/foo/bar"
"""
def expand(path) do
normalize FN.absname(path, get_cwd(path))
normalize FN.absname(expand_home(path), get_cwd(path))
end
@doc """
@@ -80,12 +84,14 @@ defmodule Path do
## Examples
Path.expand("foo/bar/../bar", "/baz") == "/baz/foo/bar"
Path.expand("/foo/bar/../bar", "/baz") == "/foo/bar"
iex> Path.expand("foo/bar/../bar", "/baz")
"/baz/foo/bar"
iex> Path.expand("/foo/bar/../bar", "/baz")
"/foo/bar"
"""
def expand(path, relative_to) do
normalize FN.absname(FN.absname(path, relative_to), get_cwd(path))
normalize FN.absname(FN.absname(expand_home(path), relative_to), get_cwd(path))
end
@doc """
@@ -188,9 +194,12 @@ defmodule Path do
## Examples
Path.relative_to("/usr/local/foo", "/usr/local") #=> "foo"
Path.relative_to("/usr/local/foo", "/") #=> "foo"
Path.relative_to("/usr/local/foo", "/etc") #=> "/usr/local/foo"
iex> Path.relative_to("/usr/local/foo", "/usr/local")
"foo"
iex> Path.relative_to("/usr/local/foo", "/")
"usr/local/foo"
iex> Path.relative_to("/usr/local/foo", "/etc")
"/usr/local/foo"
"""
def relative_to(path, from) when is_list(path) and is_binary(from) do
@@ -224,14 +233,14 @@ defmodule Path do
## Examples
Path.basename("foo")
#=> "foo"
iex> Path.basename("foo")
"foo"
Path.basename("foo/bar")
#=> "bar"
iex> Path.basename("foo/bar")
"bar"
Path.basename("/")
#=> ""
iex> Path.basename("/")
""
"""
def basename(path) do
@@ -245,12 +254,12 @@ defmodule Path do
## Examples
Path.basename("~/foo/bar.ex", ".ex")
#=> "bar"
Path.basename("~/foo/bar.exs", ".ex")
#=> "bar.exs"
Path.basename("~/foo/bar.old.ex", ".ex")
#=> "bar.old"
iex> Path.basename("~/foo/bar.ex", ".ex")
"bar"
iex> Path.basename("~/foo/bar.exs", ".ex")
"bar.exs"
iex> Path.basename("~/foo/bar.old.ex", ".ex")
"bar.old"
"""
def basename(path, extension) do
@@ -275,10 +284,10 @@ defmodule Path do
## Examples
Path.extname("foo.erl")
#=> ".erl"
Path.extname("~/foo/bar")
#=> ""
iex> Path.extname("foo.erl")
".erl"
iex> Path.extname("~/foo/bar")
""
"""
def extname(path) do
@@ -290,10 +299,10 @@ defmodule Path do
## Examples
Path.rootname("/foo/bar")
#=> "/foo/bar"
Path.rootname("/foo/bar.ex")
#=> "/foo/bar"
iex> Path.rootname("/foo/bar")
"/foo/bar"
iex> Path.rootname("/foo/bar.ex")
"/foo/bar"
"""
def rootname(path) do
@@ -306,10 +315,10 @@ defmodule Path do
## Examples
Path.rootname("/foo/bar.erl", ".erl")
#=> "/foo/bar"
Path.rootname("/foo/bar.erl", ".ex")
#=> "/foo/bar.erl"
iex> Path.rootname("/foo/bar.erl", ".erl")
"/foo/bar"
iex> Path.rootname("/foo/bar.erl", ".ex")
"/foo/bar.erl"
"""
def rootname(path, extension) do
@@ -322,12 +331,12 @@ defmodule Path do
## Examples
Path.join(["~", "foo"])
#=> "~/foo"
Path.join(["foo"])
#=> "foo"
Path.join(["/", "foo", "bar"])
#=> "/foo/bar"
iex> Path.join(["~", "foo"])
"~/foo"
iex> Path.join(["foo"])
"foo"
iex> Path.join(["/", "foo", "bar"])
"/foo/bar"
"""
def join([name1, name2|rest]), do:
@@ -342,8 +351,8 @@ defmodule Path do
## Examples
Path.join("foo", "bar")
#=> "foo/bar"
iex> Path.join("foo", "bar")
"foo/bar"
"""
def join(left, right) when is_binary(left) and is_binary(right), do:
@@ -405,12 +414,12 @@ defmodule Path do
## Examples
Path.split("")
#=> ["/"]
Path.split("foo")
#=> ["foo"]
Path.split("/foo/bar")
#=> ["/", "foo", "bar"]
iex> Path.split("")
["/"]
iex> Path.split("foo")
["foo"]
iex> Path.split("/foo/bar")
["/", "foo", "bar"]
"""
def split(path) do
@@ -484,33 +493,31 @@ defmodule Path do
# Normalize the given path by expanding "..", "." and "~".
defp normalize(path), do: do_normalize(FN.split(path))
defp do_normalize(["~"|t]) do
do_normalize t, [System.user_home!]
defp expand_home(<<?~, rest :: binary>>) do
System.user_home! <> rest
end
defp do_normalize(['~'|t]) do
do_normalize t, [System.user_home! |> binary_to_filename_string]
defp expand_home('~' ++ rest) do
(System.user_home! |> binary_to_filename_string) ++ rest
end
defp do_normalize(t) do
do_normalize t, []
defp expand_home(other), do: other
defp normalize(path), do: normalize(FN.split(path), [])
defp normalize([top|t], [_|acc]) when top in ["..", '..'] do
normalize t, acc
end
defp do_normalize([top|t], [_|acc]) when top in ["..", '..'] do
do_normalize t, acc
defp normalize([top|t], acc) when top in [".", '.'] do
normalize t, acc
end
defp do_normalize([top|t], acc) when top in [".", '.'] do
do_normalize t, acc
defp normalize([h|t], acc) do
normalize t, [h|acc]
end
defp do_normalize([h|t], acc) do
do_normalize t, [h|acc]
end
defp do_normalize([], acc) do
defp normalize([], acc) do
join Enum.reverse(acc)
end
end
+56 -18
View File
@@ -31,15 +31,41 @@ defmodule Record do
@moduledoc false
import Record.DSL
@record_fields []
@record_types []
values = unquote(values)
opts = unquote(opts)
Record.deffunctions(values, __ENV__)
unquote(block)
Record.deftypes(values, @record_type, __ENV__)
value = unquote(block)
Record.deftypes(values, @record_types, __ENV__)
value
end
end
end
@doc """
Import public record definition as a set of private macros (as defined by defrecordp/2)
## Usage
Record.import Record.Module, as: macro_name
## 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
Record.defmacros(unquote(name), unquote(module).__record__(:fields), __ENV__, unquote(module))
end
end
@doc """
Main entry point for private records definition. It defines
a set of macros with the given `name` and the fields specified
@@ -80,7 +106,7 @@ defmodule Record do
switch_recorder()
]
contents = [quote(do: @__record__ unquote(escaped))|contents]
contents = [quote(do: @record_fields unquote(escaped))|contents]
# Special case for bootstraping purposes
if env == Macro.Env do
@@ -124,31 +150,32 @@ defmodule Record do
end
"""
def defmacros(name, values, env) do
def defmacros(name, values, env, tag // nil) do
escaped = lc value inlist values do
{ key, value } = convert_value(value)
{ key, Macro.escape(value) }
end
contents = quote do
defmacrop unquote(name)() do
Record.access(__MODULE__, unquote(escaped), [], __CALLER__)
Record.access(unquote(tag) || __MODULE__, unquote(escaped), [], __CALLER__)
end
defmacrop unquote(name)(record) when is_tuple(record) do
Record.to_keywords(__MODULE__, unquote(escaped), record)
Record.to_keywords(unquote(tag) || __MODULE__, unquote(escaped), record)
end
defmacrop unquote(name)(args) do
Record.access(__MODULE__, unquote(escaped), args, __CALLER__)
Record.access(unquote(tag) || __MODULE__, unquote(escaped), args, __CALLER__)
end
defmacrop unquote(name)(record, key) when is_atom(key) do
Record.get(__MODULE__, unquote(escaped), record, key)
Record.get(unquote(tag) || __MODULE__, unquote(escaped), record, key)
end
defmacrop unquote(name)(record, args) do
Record.dispatch(__MODULE__, unquote(escaped), record, args, __CALLER__)
Record.dispatch(unquote(tag) || __MODULE__, unquote(escaped), record, args, __CALLER__)
end
end
@@ -441,19 +468,19 @@ defmodule Record do
# end
#
# def :atime.(value, record) do
# setelem(record, 1, value)
# set_elem(record, 1, value)
# end
#
# def :mtime.(record) do
# setelem(record, 2, value)
# set_elem(record, 2, value)
# end
#
# def :atime.(callback, record) do
# setelem(record, 1, callback.(elem(record, 1)))
# set_elem(record, 1, callback.(elem(record, 1)))
# end
#
# def :mtime.(callback, record) do
# setelem(record, 2, callback.(elem(record, 2)))
# set_elem(record, 2, callback.(elem(record, 2)))
# end
#
defp accessors([{ :__exception__, _ }|t], 1) do
@@ -529,21 +556,32 @@ defmodule Record do
## Types/specs generation
defp core_specs(values) do
types = lc { _, _, spec } inlist values, do: spec
types = lc { _, _, spec } inlist values, do: spec
options = if values == [], do: [], else: [options_specs(values)]
quote do
unless Kernel.Typespec.defines_type?(__MODULE__, :t, 0) do
@type t :: { __MODULE__, unquote_splicing(types) }
end
unless Kernel.Typespec.defines_type?(__MODULE__, :options, 0) do
@type options :: unquote(options)
end
@spec new :: t
@spec new(Keyword.t | tuple) :: t
@spec to_keywords(t) :: Keyword.t
@spec update(Keyword.t, t) :: t
@spec new(options | tuple) :: t
@spec to_keywords(t) :: options
@spec update(options, t) :: t
@spec __index__(atom) :: non_neg_integer | nil
end
end
defp options_specs([{ k, _, v }|t]) do
:lists.foldl fn { k, _, v }, acc ->
{ :|, [], [{ k, v }, acc] }
end, { k, v }, t
end
defp accessor_specs([{ :__exception__, _, _ }|t], 1, acc) do
accessor_specs(t, 2, acc)
end
@@ -607,7 +645,7 @@ defmodule Record.DSL do
escaped = lc { k, v } inlist opts, do: { k, Macro.escape(v) }
quote do
@record_type Keyword.merge(@record_type || [], unquote(escaped))
@record_types Keyword.merge(@record_types || [], unquote(escaped))
end
end
end
+41 -22
View File
@@ -86,8 +86,10 @@ defmodule Regex do
## Examples
Regex.index %r/c(d)/, "abcd" #=> 3
Regex.index %r/e/, "abcd" #=> nil
iex> Regex.index %r/c(d)/, "abcd"
2
iex> Regex.index %r/e/, "abcd"
nil
"""
def index(regex(re_pattern: compiled), string) do
@@ -102,8 +104,10 @@ defmodule Regex do
## Examples
Regex.match? %r/foo/, "foo" #=> true
Regex.match? %r/foo/, "bar" #=> false
iex> Regex.match? %r/foo/, "foo"
true
iex> Regex.match? %r/foo/, "bar"
false
"""
def match?(regex(re_pattern: compiled), string) do
@@ -116,8 +120,10 @@ defmodule Regex do
## Examples
Regex.run %r/c(d)/, "abcd" #=> ["cd", "d"]
Regex.run %r/e/, "abcd" #=> nil
iex> Regex.run %r/c(d)/, "abcd"
["cd", "d"]
iex> Regex.run %r/e/, "abcd"
nil
"""
def run(regex, string, options // [])
@@ -127,7 +133,7 @@ defmodule Regex do
captures =
case Keyword.get(options, :capture, :all) do
:groups -> groups || raise "regex was not compiled with g"
:groups -> groups || raise ArgumentError, message: "regex was not compiled with g"
others -> others
end
@@ -139,10 +145,12 @@ defmodule Regex do
@doc """
Returns the given captures as a list of tuples.
Requires the regex to be compiled with the groups option.
## Examples
Regex.captures %r/c(?<foo>d)/g, "abcd" #=> [{:foo, ["d"]}]
iex> Regex.captures %r/c(?<foo>d)/g, "abcd"
[foo: "d"]
"""
def captures(regex(groups: groups) = regex, string, options // []) do
@@ -150,7 +158,7 @@ defmodule Regex do
captures = if groups do
Enum.sort(groups)
else
raise "Regex was not compiled with g"
raise ArgumentError, message: "regex was not compiled with g"
end
options = Keyword.put(options, :capture, captures)
end
@@ -159,7 +167,7 @@ defmodule Regex do
end
@doc """
Returns the underlying re_pattern in the regular expression.
Returns the underlying `re_pattern` in the regular expression.
"""
def re_pattern(regex(re_pattern: compiled)) do
compiled
@@ -170,7 +178,8 @@ defmodule Regex do
## Examples
Regex.source %r(foo) #=> "foo"
iex> Regex.source %r(foo)
"foo"
"""
def source(regex(source: source)) do
@@ -178,11 +187,12 @@ defmodule Regex do
end
@doc """
Returns the regex options as a list.
Returns the regex options as a string.
## Examples
Regex.opts %r(foo)m #=> 'm'
iex> Regex.opts %r(foo)m
"m"
"""
def opts(regex(options: options)) do
@@ -194,7 +204,8 @@ defmodule Regex do
## Examples
Regex.groups %r/(?<foo>foo)/g #=> ["foo"]
iex> Regex.groups %r/(?<foo>foo)/g
[:foo]
"""
def groups(regex(groups: groups)) do
@@ -209,9 +220,12 @@ defmodule Regex do
## Examples
Regex.scan %r/c(d|e)/, "abcd abce" #=> [["d"], ["e"]]
Regex.scan %r/c(?:d|e)/, "abcd abce" #=> ["cd", "ce"]
Regex.scan %r/e/, "abcd" #=> []
iex> Regex.scan %r/c(d|e)/, "abcd abce"
[["d"], ["e"]]
iex> Regex.scan %r/c(?:d|e)/, "abcd abce"
["cd", "ce"]
iex> Regex.scan %r/e/, "abcd"
[]
"""
def scan(regex, string, options // [])
@@ -256,11 +270,16 @@ defmodule Regex do
## Examples
Regex.replace(%r/d/, "abc", "d") #=> "abc"
Regex.replace(%r/b/, "abc", "d") #=> "adc"
Regex.replace(%r/b/, "abc", "[&]") #=> "a[b]c"
Regex.replace(%r/b/, "abc", "[\\&]") #=> "a[&]c"
Regex.replace(%r/(b)/, "abc", "[\\1]") #=> "a[b]c"
iex> Regex.replace(%r/d/, "abc", "d")
"abc"
iex> Regex.replace(%r/b/, "abc", "d")
"adc"
iex> Regex.replace(%r/b/, "abc", "[&]")
"a[b]c"
iex> Regex.replace(%r/b/, "abc", "[\\&]")
"a[&]c"
iex> Regex.replace(%r/(b)/, "abc", "[\\1]")
"a[b]c"
"""
def replace(regex(re_pattern: compiled), string, replacement, options // []) do
+177 -87
View File
@@ -1,17 +1,38 @@
defmodule String do
@moduledoc %B"""
A string in Elixir is a UTF-8 encoded binary.
A String in Elixir is a UTF-8 encoded binary.
## String and binary operations
The functions in this module act according to the
Unicode Standard, version 6.2.0. A codepoint is a
Unicode Character, which may be represented by one
or more bytes. For example, the character "é" is
represented with two bytes:
Unicode Standard, version 6.2.0. For example,
`titlecase`, `downcase`, `strip` are provided by this
module.
string = "é"
#=> "é"
size(string)
#=> 2
Besides this module, Elixir provides more low-level
operations that works directly with binaries. Some
of those can be found in the `Kernel` module, as:
* `binary_part/2` and `binary_part/3` - retrieves part of the binary
* `bit_size/1` and `byte_size/1` - size related functions
* `is_bitstring/1` and `is_binary/1` - type checking function
* Plus a bunch of conversion functions, like `binary_to_atom/2`,
`binary_to_integer/2`, `binary_to_term/1` and their opposite
like `integer_to_binary/2`
Finally, [the `:binary` module](http://erlang.org/doc/man/binary.html)
provides a couple other functions that works on the byte level.
## Codepoints and graphemes
As per the Unicode Standard, a codepoint is an Unicode
Character, which may be represented by one or more bytes.
For example, the character "é" is represented with two
bytes:
iex> string = "é"
...> byte_size(string)
2
Furthermore, this module also presents the concept of
graphemes, which are multiple characters that may be
@@ -19,10 +40,9 @@ defmodule String do
the same "é" character written above could be represented
by the letter "e" followed by the accent ́:
string = "\x{0065}\x{0301}"
#=> "é"
size(string)
#=> 3
iex> string = "\x{0065}\x{0301}"
...> byte_size(string)
3
Although the example above is made of two characters, it is
perceived by users as one.
@@ -40,26 +60,30 @@ defmodule String do
## Integer codepoints
Although codepoints could be represented as integers, this
module represents all codepoints as binaries. For example:
module represents all codepoints as strings. For example:
String.codepoints "josé" #=> ["j", "o", "s", "é"]
iex> String.codepoints "josé"
["j", "o", "s", "é"]
There are a couple of ways to retrieve a character integer
codepoint. One may use the `?` special macro:
?j #=> 106
?é #=> 233
iex> ?j
106
iex> ?é
233
Or also via pattern matching:
<< eacute :: utf8 >> = "é"
eacute #=> 233
iex> << eacute :: utf8 >> = "é"
...> eacute
233
As we have seen above, codepoints can be inserted into
a string by their hexadecimal code:
string = "jos\x{0065}\x{0301}"
#=> "josé"
"jos\x{0065}\x{0301}" #=>
"josé"
## Self-synchronization
@@ -72,8 +96,10 @@ defmodule String do
characters. For example, `String.length` is going to return
a correct result even if an invalid codepoint is fed into it.
In the future, bang version of such functions may be
provided which will rather raise on such invalid data.
In other words, this module expects invalid data to be detected
when retrieving data from the external source. For example, a
driver that reads strings from a database will be the one
responsible to check the validity of the encoding.
"""
@type t :: binary
@@ -86,7 +112,8 @@ defmodule String do
## Examples
String.printable?("abc") #=> true
iex> String.printable?("abc")
true
"""
@spec printable?(t) :: boolean
@@ -119,29 +146,44 @@ defmodule String do
The string is split into as many parts as possible by
default, unless the `global` option is set to false.
If a pattern is not specified, the string is split on
whitespace occurrences.
Unicode whitespace occurrences with leading and trailing
whitespace ignored.
It returns a list with the original string if the pattern
can't be matched.
## Examples
String.split("a,b,c", ",") #=> ["a", "b", "c"]
String.split("a,b,c", ",", global: false) #=> ["a", "b,c"]
iex> String.split("foo bar")
["foo", "bar"]
iex> String.split("foo" <> <<194,133>> <> "bar")
["foo", "bar"]
iex> String.split(" foo bar ")
["foo", "bar"]
String.split("foo bar") #=> ["foo", "bar"]
String.split("1,2 3,4", [" ", ","]) #=> ["1", "2", "3", "4"]
iex> String.split("a,b,c", ",")
["a", "b", "c"]
iex> String.split("a,b,c", ",", global: false)
["a", "b,c"]
String.split("a,b,c", %r{,}) #=> ["a", "b", "c"]
String.split("a,b,c", %r{,}, global: false) #=> ["a", "b,c"]
String.split("a,b", %r{\.}) #=> ["a,b"]
iex> String.split("1,2 3,4", [" ", ","])
["1", "2", "3", "4"]
iex> String.split("a,b,c", %r{,})
["a", "b", "c"]
iex> String.split("a,b,c", %r{,}, global: false)
["a", "b,c"]
iex> String.split("a,b", %r{\\.})
["a,b"]
"""
@spec split(t) :: [t]
@spec split(t, t | [t] | Regex.t) :: [t]
@spec split(t, t | [t] | Regex.t, Keyword.t) :: [t]
def split(binary, pattern // " ", options // [])
defdelegate split(binary), to: String.Unicode
def split(binary, pattern, options // [])
def split(binary, pattern, options) when is_regex(pattern) do
Regex.split(pattern, binary, global: options[:global])
@@ -157,9 +199,12 @@ defmodule String do
## Examples
String.upcase("abcd") #=> "ABCD"
String.upcase("ab 123 xpto") #=> "AB 123 XPTO"
String.upcase("josé") #=> "JOSÉ"
iex> String.upcase("abcd")
"ABCD"
iex> String.upcase("ab 123 xpto")
"AB 123 XPTO"
iex> String.upcase("josé")
"JOSÉ"
"""
@spec upcase(t) :: t
@@ -170,9 +215,12 @@ defmodule String do
## Examples
String.downcase("ABCD") #=> "abcd"
String.downcase("AB 123 XPTO") #=> "ab 123 xpto"
String.downcase("JOSÉ") #=> "josé"
iex> String.downcase("ABCD")
"abcd"
iex> String.downcase("AB 123 XPTO")
"ab 123 xpto"
iex> String.downcase("JOSÉ")
"josé"
"""
@spec downcase(t) :: t
@@ -189,9 +237,12 @@ defmodule String do
## Examples
String.capitalize("abcd") #=> "Abcd"
String.capitalize("fin") #=> "Fin"
String.capitalize("josé") #=> "José"
iex> String.capitalize("abcd")
"Abcd"
iex> String.capitalize("fin")
"Fin"
iex> String.capitalize("josé")
"José"
"""
@spec capitalize(t) :: t
@@ -201,12 +252,13 @@ defmodule String do
end
@doc """
Returns a string where trailing whitespace characters
and new line have been removed.
Returns a string where trailing Unicode whitespace
has been removed.
## Examples
String.rstrip(" abc ") #=> " abc"
iex> String.rstrip(" abc ")
" abc"
"""
@spec rstrip(t) :: t
@@ -217,7 +269,8 @@ defmodule String do
## Examples
String.rstrip(" abc _", ?_) #=> " abc "
iex> String.rstrip(" abc _", ?_)
" abc "
"""
@spec rstrip(t, char) :: t
@@ -248,12 +301,13 @@ defmodule String do
end
@doc """
Returns a string where leading whitespace characters
have been removed.
Returns a string where leading Unicode whitespace
has been removed.
## Examples
String.lstrip(" abc ") #=> "abc "
iex> String.lstrip(" abc ")
"abc "
"""
defdelegate lstrip(binary), to: String.Unicode
@@ -263,7 +317,8 @@ defmodule String do
## Examples
String.lstrip("_ abc _", ?_) #=> " abc _"
iex> String.lstrip("_ abc _", ?_)
" abc _"
"""
@@ -278,12 +333,13 @@ defmodule String do
end
@doc """
Returns a string where leading/trailing whitespace
and new line characters have been removed.
Returns a string where leading/trailing Unicode whitespace
has been removed.
## Examples
String.strip(" abc ") #=> "abc"
iex> String.strip(" abc ")
"abc"
"""
@spec strip(t) :: t
@@ -298,7 +354,8 @@ defmodule String do
## Examples
String.strip("a abc a", ?a) #=> " abc "
iex> String.strip("a abc a", ?a)
" abc "
"""
@spec strip(t, char) :: t
@@ -318,11 +375,16 @@ defmodule String do
## Examples
String.replace("a,b,c", ",", "-") #=> "a-b-c"
String.replace("a,b,c", ",", "-", global: false) #=> "a-b,c"
String.replace("a,b,c", "b", "[]", insert_replaced: 1) #=> "a,[b],c"
String.replace("a,b,c", ",", "[]", insert_replaced: 2) #=> "a[],b[],c"
String.replace("a,b,c", ",", "[]", insert_replaced: [1,1]) #=> "a[,,]b[,,]c"
iex> String.replace("a,b,c", ",", "-")
"a-b-c"
iex> String.replace("a,b,c", ",", "-", global: false)
"a-b,c"
iex> String.replace("a,b,c", "b", "[]", insert_replaced: 1)
"a,[b],c"
iex> String.replace("a,b,c", ",", "[]", insert_replaced: 2)
"a[],b[],c"
iex> String.replace("a,b,c", ",", "[]", insert_replaced: [1,1])
"a[,,]b[,,]c"
"""
@spec replace(t, t, t) :: t
@@ -348,8 +410,10 @@ defmodule String do
## Examples
String.duplicate("abc", 1) #=> "abc"
String.duplicate("abc", 2) #=> "abcabc"
iex> String.duplicate("abc", 1)
"abc"
iex> String.duplicate("abc", 2)
"abcabc"
"""
@spec duplicate(t, pos_integer) :: t
@@ -362,9 +426,12 @@ defmodule String do
## Examples
String.codepoints("josé") #=> ["j", "o", "s", "é"]
String.codepoints("оптими зации") #=> ["о","п","т","и","м","и"," ","з","а","ц","и","и"]
String.codepoints("ἅἪῼ") #=> ["ἅ","Ἢ","ῼ"]
iex> String.codepoints("josé")
["j", "o", "s", "é"]
iex> String.codepoints("оптими зации")
["о","п","т","и","м","и"," ","з","а","ц","и","и"]
iex> String.codepoints("ἅἪῼ")
["ἅ","Ἢ","ῼ"]
"""
@spec codepoints(t) :: [codepoint]
@@ -384,7 +451,8 @@ defmodule String do
## Examples
String.next_codepoint("josé") #=> { "j", "osé" }
iex> String.next_codepoint("josé")
{ "j", "osé" }
"""
@spec next_codepoint(t) :: {codepoint, t} | :no_codepoint
@@ -398,10 +466,14 @@ defmodule String do
## Examples
String.valid_codepoint?("a") #=> true
String.valid_codepoint?("ø") #=> true
String.valid_codepoint?("\xffff") #=> false
String.valid_codepoint?("asdf") #=> false
iex> String.valid_codepoint?("a")
true
iex> String.valid_codepoint?("ø")
true
iex> String.valid_codepoint?("\xffff")
false
iex> String.valid_codepoint?("asdf")
false
"""
@spec valid_codepoint?(codepoint) :: boolean
@@ -413,7 +485,8 @@ defmodule String do
## Examples
String.graphemes("Ā̀stute") # => ["Ā̀","s","t","u","t","e"]
iex> String.graphemes("Ā̀stute")
["Ā̀","s","t","u","t","e"]
"""
@spec graphemes(t) :: [grapheme]
@@ -428,7 +501,8 @@ defmodule String do
## Examples
String.next_grapheme("josé") #=> { "j", "osé" }
iex> String.next_grapheme("josé")
{ "j", "osé" }
"""
@spec next_grapheme(t) :: grapheme | :no_grapheme
@@ -439,8 +513,10 @@ defmodule String do
## Examples
String.first("elixir") #=> "e"
String.first("եոգլի") #=> "ե"
iex> String.first("elixir")
"e"
iex> String.first("եոգլի")
"ե"
"""
@spec first(t) :: grapheme | nil
@@ -456,8 +532,10 @@ defmodule String do
## Examples
String.last("elixir") #=> "r"
String.last("եոգլի") #=> "ի"
iex> String.last("elixir")
"r"
iex> String.last("եոգլի")
"ի"
"""
@spec last(t) :: grapheme | nil
@@ -476,8 +554,10 @@ defmodule String do
## Examples
String.length("elixir") #=> 6
String.length("եոգլի") #=> 5
iex> String.length("elixir")
6
iex> String.length("եոգլի")
5
"""
@spec length(t) :: non_neg_integer
@@ -497,11 +577,16 @@ defmodule String do
## Examples
String.at("elixir", 0) #=> "e"
String.at("elixir", 1) #=> "l"
String.at("elixir", 10) #=> nil
String.at("elixir", -1) #=> "r"
String.at("elixir", -10) #=> nil
iex> String.at("elixir", 0)
"e"
iex> String.at("elixir", 1)
"l"
iex> String.at("elixir", 10)
nil
iex> String.at("elixir", -1)
"r"
iex> String.at("elixir", -10)
nil
"""
@spec at(t, integer) :: grapheme | nil
@@ -535,11 +620,16 @@ defmodule String do
## Examples
String.slice("elixir", 1, 3) #=> "lix"
String.slice("elixir", 1, 10) #=> "lixir"
String.slice("elixir", 10, 3) #=> nil
String.slice("elixir", -4, 4) #=> "ixi"
String.slice("elixir", -10, 3) #=> nil
iex> String.slice("elixir", 1, 3)
"lix"
iex> String.slice("elixir", 1, 10)
"lixir"
iex> String.slice("elixir", 10, 3)
nil
iex> String.slice("elixir", -4, 4)
"ixir"
iex> String.slice("elixir", -10, 3)
nil
"""
@spec slice(t, integer, integer) :: grapheme | nil
+64 -40
View File
@@ -57,60 +57,54 @@ defmodule String.Unicode do
# Downcase
def downcase(""), do: ""
lc { codepoint, _upper, lower, _title } inlist codes, lower && lower != codepoint do
args = quote do: [unquote(codepoint) <> t]
code = quote do: unquote(lower) <> downcase(t)
def :downcase, args, [], do: code
def downcase(unquote(codepoint) <> rest) do
unquote(lower) <> downcase(rest)
end
end
def downcase(<< h, t :: binary >>) do
<< h >> <> downcase(t)
end
def downcase(<< >>) do
<< >>
def downcase(<< char, rest :: binary >>) do
<< char >> <> downcase(rest)
end
# Upcase
def upcase(""), do: ""
lc { codepoint, upper, _lower, _title } inlist codes, upper && upper != codepoint do
args = quote do: [unquote(codepoint) <> t]
code = quote do: unquote(upper) <> upcase(t)
def :upcase, args, [], do: code
def upcase(unquote(codepoint) <> rest) do
unquote(upper) <> upcase(rest)
end
end
def upcase(<< h, t :: binary >>) do
<< h >> <> upcase(t)
end
def upcase(<< >>) do
<< >>
def upcase(<< char, rest :: binary >>) do
<< char >> <> upcase(rest)
end
# Titlecase once
def titlecase_once(""), do: { "", "" }
lc { codepoint, _upper, _lower, title } inlist codes, title && title != codepoint do
args = quote do: [unquote(codepoint) <> t]
code = quote do: { unquote(title), t }
def :titlecase_once, args, [], do: code
def titlecase_once(unquote(codepoint) <> rest) do
{ unquote(title), rest }
end
end
def titlecase_once(<< h, t :: binary >>) do
{ <<h>>, t }
end
def titlecase_once(<< >>) do
{ <<>>, <<>> }
def titlecase_once(<< char, rest :: binary >>) do
{ << char >>, rest }
end
# Strip
def lstrip(""), do: ""
lc char inlist whitespace do
args = quote do: [unquote(char) <> rest]
exprs = quote do: lstrip(rest)
def :lstrip, args, [], do: exprs
lc codepoint inlist whitespace do
def lstrip(unquote(codepoint) <> rest) do
lstrip(rest)
end
end
def lstrip(other) when is_binary(other), do: other
@@ -121,24 +115,54 @@ defmodule String.Unicode do
do_rstrip(string, "")
end
lc char inlist whitespace do
args = quote do: [unquote(char) <> rest, buffer]
exprs = quote do: do_rstrip(rest, unquote(char) <> buffer)
defp :do_rstrip, args, [], do: exprs
lc codepoint inlist whitespace do
defp do_rstrip(unquote(codepoint) <> rest, buffer) do
do_rstrip(rest, unquote(codepoint) <> buffer)
end
end
defp do_rstrip(<< char, string :: binary >>, buffer) do
<< buffer :: binary, char, do_rstrip(string, "") :: binary >>
defp do_rstrip(<< char, rest :: binary >>, buffer) do
<< buffer :: binary, char, do_rstrip(rest, "") :: binary >>
end
defp do_rstrip(<<>>, _), do: <<>>
# Split
def split(""), do: ""
def split(string) when is_binary(string) do
:lists.reverse do_split(string, "", [])
end
lc codepoint inlist whitespace do
defp do_split(unquote(codepoint) <> rest, buffer, acc) do
if buffer != "" do
do_split(rest, "", [buffer | acc])
else
do_split(rest, buffer, acc)
end
end
end
defp do_split(<< char, rest :: binary >>, buffer, acc) do
do_split(rest, << buffer :: binary, char >>, acc)
end
defp do_split(<<>>, buffer, acc) do
if buffer != "" do
[buffer | acc]
else
acc
end
end
# Graphemes
lc codepoints inlist seqs do
seq_args = quote do: [<< unquote_splicing(codepoints), t :: binary >>]
seq_code = quote do: {<< unquote_splicing(codepoints) >>, t}
def :next_grapheme, seq_args, [], do: seq_code
def next_grapheme(<< unquote_splicing(codepoints), t :: binary >>) do
{ << unquote_splicing(codepoints) >>, t }
end
end
def next_grapheme(<<>>) do
+1 -1
View File
@@ -37,7 +37,7 @@ main(Args) ->
application:start(?MODULE),
'Elixir.Kernel.CLI':main(Args).
% Boot and process given options. Invoked by Elixir's script.
%% Boot and process given options. Invoked by Elixir's script.
start_cli() ->
application:start(?MODULE),
+2 -2
View File
@@ -145,8 +145,8 @@ lookup(Else, Dict) ->
%% Errors
format_error({unloaded_module, Module}) ->
io_lib:format("module ~s is not loaded and could not be found", [elixir_errors:inspect(Module)]);
io_lib:format("module ~ts is not loaded and could not be found", [elixir_errors:inspect(Module)]);
format_error({scheduled_module, Module}) ->
io_lib:format("module ~s is not loaded but was defined. This happens because you are trying to use a module in the same context it is defined. Try defining the module outside the context that requires it.",
io_lib:format("module ~ts is not loaded but was defined. This happens because you are trying to use a module in the same context it is defined. Try defining the module outside the context that requires it.",
[elixir_errors:inspect(Module)]).
+5 -5
View File
@@ -19,15 +19,15 @@ get_pairs(Meta, Key, Clauses, S, AllowNil) ->
{ Key, nil } when AllowNil ->
[];
{ Key, _ } ->
elixir_errors:syntax_error(Meta, S#elixir_scope.file, "expected pairs with -> for key ~s", [Key]);
elixir_errors:syntax_error(Meta, S#elixir_scope.file, "expected pairs with -> for key ~ts", [Key]);
_ ->
[]
end.
% Function for translating assigns.
assigns(Fun, Args, #elixir_scope{context=Context} = S) when Context /= assign ->
{ Result, NewS } = assigns(Fun, Args, S#elixir_scope{context=assign, temp_vars=orddict:new()}),
assigns(Fun, Args, #elixir_scope{context=Context} = S) when Context /= match ->
{ Result, NewS } = assigns(Fun, Args, S#elixir_scope{context=match, temp_vars=orddict:new()}),
{ Result, NewS#elixir_scope{context=Context} };
assigns(Fun, Args, S) -> Fun(Args, S).
@@ -176,10 +176,10 @@ each_clause(Meta, { 'after', [Condition], Expr }, S) ->
{ { clause, ?line(Meta), [TCondition], [], TBody }, SB };
each_clause(Meta, { Key, [_|_], _ }, S) when Key == do; Key == 'after' ->
elixir_errors:syntax_error(Meta, S#elixir_scope.file, "too many arguments given for ~s", [Key]);
elixir_errors:syntax_error(Meta, S#elixir_scope.file, "too many arguments given for ~ts", [Key]);
each_clause(Meta, { Key, _, _ }, S) ->
elixir_errors:syntax_error(Meta, S#elixir_scope.file, "invalid key ~s", [Key]).
elixir_errors:syntax_error(Meta, S#elixir_scope.file, "invalid key ~ts", [Key]).
% Check if the given expression is a match tuple.
% This is a small optimization to allow us to change
+9 -5
View File
@@ -22,11 +22,14 @@ get_opts() ->
%% Compiles the given string.
string(Contents, File) when is_list(Contents), is_binary(File) ->
Forms = elixir_translator:'forms!'(Contents, 1, File, []),
quoted(Forms, File).
quoted(Forms, File) when is_binary(File) ->
Previous = get(elixir_compiled),
try
put(elixir_compiled, []),
Forms = elixir_translator:'forms!'(Contents, 1, File, []),
eval_forms(Forms, 1, [], elixir:scope_for_eval([{file,File}])),
lists:reverse(get(elixir_compiled))
after
@@ -137,11 +140,11 @@ env_default_opts() ->
{ok,List} when is_list(List) -> List;
{ok,Term} -> [Term];
{error,_Reason} ->
io:format("Ignoring bad term in ~s\n", [Key]),
io:format("Ignoring bad term in ~ts\n", [Key]),
[]
end;
{error, {_,_,_Reason}, _} ->
io:format("Ignoring bad term in ~s\n", [Key]),
io:format("Ignoring bad term in ~ts\n", [Key]),
[]
end
end.
@@ -195,7 +198,7 @@ core_file(File) ->
try
Lists = file(list_to_binary(File)),
[binary_to_path(X, "lib/elixir/ebin") || X <- Lists],
io:format("Compiled ~s~n", [File])
io:format("Compiled ~ts~n", [File])
catch
Kind:Reason ->
io:format("~p: ~p~nstacktrace: ~p~n", [Kind, Reason, erlang:get_stacktrace()]),
@@ -226,6 +229,7 @@ core_main() ->
"lib/elixir/lib/access.ex",
"lib/elixir/lib/regex.ex",
"lib/elixir/lib/system.ex",
"lib/elixir/lib/process.ex",
"lib/elixir/lib/kernel/cli.ex",
"lib/elixir/lib/kernel/error_handler.ex",
"lib/elixir/lib/kernel/parallel_compiler.ex",
@@ -235,7 +239,7 @@ core_main() ->
%% ERROR HANDLING
format_error({ skip_native, Module }) ->
io_lib:format("skipping native compilation for ~s because it contains on_load attribute",
io_lib:format("skipping native compilation for ~ts because it contains on_load attribute",
[elixir_errors:inspect(Module)]).
format_errors(_File, []) ->
+46 -18
View File
@@ -4,7 +4,9 @@
build_table/1,
delete_table/1,
reset_last/1,
wrap_definition/5,
wrap_definition/7,
store_definition/6,
store_definition/8,
store_each/7,
unwrap_stored_definitions/2,
@@ -44,33 +46,52 @@ reset_last(Module) ->
%%
%% If we just analyzed the compiled structure (i.e. the function availables
%% before evaluating the function body), we would see both definitions.
wrap_definition(Kind, Meta, Name, Args, Guards, Expr, S) ->
Line = ?line(Meta),
MetaS = elixir_scope:serialize(S),
Invoke = [
{atom, Line, Kind},
{integer, Line, Line},
{var, Line, '_@MODULE'},
Name,
Args,
Guards,
Expr,
MetaS
],
wrap_definition(Kind, Meta, Call, Expr, S) ->
do_wrap_definition(Kind, Meta, [
Call, Expr, elixir_scope:serialize(S)
]).
wrap_definition(Kind, Meta, Name, Args, Guards, Expr, S) ->
do_wrap_definition(Kind, Meta, [
Name, Args, Guards, Expr, elixir_scope:serialize(S)
]).
do_wrap_definition(Kind, Meta, Extra) ->
Line = ?line(Meta),
Invoke =
[{atom, Line, Kind},
{integer, Line, Line},
{var, Line, '_@MODULE'}] ++ Extra,
?wrap_call(Line, ?MODULE, store_definition, Invoke).
% Invoked by the wrap definition with the function abstract tree.
% Each function is then added to the function table.
store_definition(Kind, Line, Module, Call, Body, RawS) ->
S = elixir_scope:deserialize(RawS),
{ NameAndArgs, Guards } = elixir_clauses:extract_guards(Call),
{ Name, Args } = case elixir_clauses:extract_args(NameAndArgs) of
error ->
Format = [Kind, 'Elixir.Macro':to_binary(NameAndArgs)],
elixir_errors:syntax_error(Line, S#elixir_scope.file, "invalid syntax in ~ts ~ts", Format);
Tuple ->
Tuple
end,
assert_no_aliases_name(Line, Name, Args, S),
store_definition(Kind, Line, Module, Name, Args, Guards, Body, RawS).
store_definition(Kind, Line, nil, _Name, _Args, _Guards, _Body, RawS) ->
S = elixir_scope:deserialize(RawS),
elixir_errors:syntax_error(Line, S#elixir_scope.file, "cannot define function outside module, invalid scope for ~s", [Kind]);
elixir_errors:syntax_error(Line, S#elixir_scope.file, "cannot define function outside module, invalid scope for ~ts", [Kind]);
store_definition(Kind, Line, Module, Name, Args, Guards, Body, RawS) ->
do_store_definition(Kind, Line, Module, Name, Args, Guards, Body, elixir_scope:deserialize(RawS)).
do_store_definition(Kind, Line, Module, Name, Args, Guards, Body, DS) ->
Arity = length(Args),
DS = elixir_scope:deserialize(RawS),
S = DS#elixir_scope{function={Name,Arity}, module=Module},
Expr = def_body(Line, Body),
@@ -290,13 +311,20 @@ check_valid_defaults(_Line, _File, _Name, _Arity, 0) -> [];
check_valid_defaults(Line, File, Name, Arity, _) ->
elixir_errors:handle_file_warning(File, { Line, ?MODULE, { clauses_with_docs, { Name, Arity } } }).
assert_no_aliases_name(Line, '__aliases__', [Atom], #elixir_scope{file=File}) when is_atom(Atom) ->
Message = "function names should start with lowercase characters or underscore, invalid name ~ts",
elixir_errors:syntax_error(Line, File, Message, [atom_to_binary(Atom, utf8)]);
assert_no_aliases_name(_Meta, _Aliases, _Args, _S) ->
ok.
%% Format errors
format_error({clauses_with_docs,{Name,Arity}}) ->
io_lib:format("function ~s/~B has default values and multiple clauses, use a separate clause for declaring defaults", [Name, Arity]);
io_lib:format("function ~ts/~B has default values and multiple clauses, use a separate clause for declaring defaults", [Name, Arity]);
format_error({changed_clause,{{Name,Arity},{ElseName,ElseArity}}}) ->
io_lib:format("function ~s/~B does not match previous clause ~s/~B", [Name, Arity, ElseName, ElseArity]);
io_lib:format("function ~ts/~B does not match previous clause ~ts/~B", [Name, Arity, ElseName, ElseArity]);
format_error({changed_kind,{Name,Arity,Previous,Current}}) ->
io_lib:format("~s ~s/~B already defined as ~s", [Current, Name, Arity, Previous]).
io_lib:format("~ts ~ts/~B already defined as ~ts", [Current, Name, Arity, Previous]).
+1 -1
View File
@@ -75,4 +75,4 @@ check_unused_local_macros(File, Recorded, Defmacrop) ->
Check, not lists:member(Fun, Recorded)].
format_error({unused_macro,{Name, Arity}}) ->
io_lib:format("macro ~s/~B is unused", [Name, Arity]).
io_lib:format("macro ~ts/~B is unused", [Name, Arity]).
+1 -1
View File
@@ -88,7 +88,7 @@ store_pending(Module) ->
format_error({ no_super, Module, { Name, Arity } }) ->
Bins = [ format_fa(X) || { X, { _, _, _ } } <- overridable(Module)],
Joined = 'Elixir.Enum':join(Bins, <<", ">>),
io_lib:format("no super defined for ~s/~B in module ~s. Overridable functions available are: ~s",
io_lib:format("no super defined for ~ts/~B in module ~ts. Overridable functions available are: ~ts",
[Name, Arity, elixir_errors:inspect(Module), Joined]).
format_fa({ Name, Arity }) ->
+135 -78
View File
@@ -5,7 +5,7 @@
-export([default_macros/0, default_functions/0, default_requires/0,
dispatch_require/6, dispatch_import/5,
require_function/5, import_function/4,
expand_import/8, expand_require/8, find_import/4,
expand_import/6, expand_require/6, find_import/4,
format_error/1, in_erlang_functions/0, in_erlang_macros/0]).
-include("elixir.hrl").
-compile({parse_transform, elixir_transform}).
@@ -18,32 +18,31 @@ default_functions() ->
default_macros() ->
[ { ?BUILTIN, ordsets:union(in_elixir_macros(), in_erlang_macros()) } ].
default_requires() ->
[ ?BUILTIN, 'Elixir.Kernel.Typespec' ].
[ ?BUILTIN, 'Elixir.Kernel.Typespec', 'Elixir.Record' ].
find_import(_Meta, Name, Arity, S) ->
find_import(Meta, Name, Arity, S) ->
Tuple = { Name, Arity },
case find_dispatch(Tuple, S#elixir_scope.functions) of
false ->
case find_dispatch(Tuple, S#elixir_scope.macros) of
false -> false;
Receiver -> Receiver
end;
Receiver -> Receiver
case find_dispatch(Meta, Tuple, S) of
{ function, Receiver } -> Receiver;
{ macro, Receiver } -> Receiver;
_ -> false
end.
%% Function retrieval
import_function(Meta, Name, Arity, S) ->
Tuple = { Name, Arity },
case find_dispatch(Tuple, S#elixir_scope.functions) of
false ->
case find_dispatch(Tuple, S#elixir_scope.macros) of
false -> { { 'fun', ?line(Meta), { function, Name, Arity } }, S };
_ -> false
end;
Receiver ->
case find_dispatch(Meta, Tuple, S) of
{ function, Receiver } ->
elixir_import:record(import, Tuple, Receiver, S#elixir_scope.module),
remote_function(Meta, Receiver, Name, Arity, S)
remote_function(Meta, Receiver, Name, Arity, S);
{ macro, _Receiver } ->
false;
{ import, Receiver } ->
require_function(Meta, Receiver, Name, Arity, S);
nomatch ->
{ { 'fun', ?line(Meta), { function, Name, Arity } }, S }
end.
require_function(Meta, Receiver, Name, Arity, S) ->
@@ -61,10 +60,18 @@ dispatch_import(Meta, Name, Args, S, Callback) ->
Arity = length(Args),
Tuple = { Name, Arity },
case find_dispatch(Tuple, S#elixir_scope.functions) of
false ->
case expand_import(Meta, Tuple, Args, Module, S#elixir_scope.function,
S#elixir_scope.requires, S#elixir_scope.macros, S) of
case find_dispatch(Meta, Tuple, S) of
{ function, Receiver } ->
elixir_import:record(import, Tuple, Receiver, Module),
Endpoint = case (Receiver == ?BUILTIN) andalso is_element(Tuple, in_erlang_functions()) of
true -> erlang;
false -> Receiver
end,
elixir_translator:translate_each({ { '.', Meta, [Endpoint, Name] }, Meta, Args }, S);
{ import, Receiver } ->
elixir_translator:translate_each({ { '.', Meta, [Receiver, Name] }, [{require,false}|Meta], Args }, S);
Result ->
case do_expand_import(Meta, Tuple, Args, Module, S, Result) of
{ error, noexpansion } ->
Callback();
{ error, internal } ->
@@ -72,14 +79,7 @@ dispatch_import(Meta, Name, Args, S, Callback) ->
elixir_macros:translate({ Name, Meta, Args }, S);
{ ok, _Receiver, Tree } ->
translate_expansion(Meta, Tree, S)
end;
Receiver ->
elixir_import:record(import, Tuple, Receiver, Module),
Endpoint = case (Receiver == ?BUILTIN) andalso is_element(Tuple, in_erlang_functions()) of
true -> erlang;
false -> Receiver
end,
elixir_translator:translate_each({ { '.', Meta, [Endpoint, Name] }, Meta, Args }, S)
end
end.
dispatch_require(Meta, Receiver, Name, Args, S, Callback) ->
@@ -89,119 +89,172 @@ dispatch_require(Meta, Receiver, Name, Args, S, Callback) ->
case (Receiver == ?BUILTIN) andalso is_element(Tuple, in_erlang_functions()) of
true ->
elixir_translator:translate_each({ { '.', Meta, [erlang, Name] }, Meta, Args }, S);
{ TArgs, SA } = elixir_translator:translate_args(Args, S),
{ ?wrap_call(?line(Meta), erlang, Name, TArgs), SA };
false ->
case expand_require(Meta, Receiver, Tuple, Args, Module,
S#elixir_scope.function, S#elixir_scope.requires, S) of
case expand_require(Meta, Receiver, Tuple, Args, Module, S) of
{ error, noexpansion } ->
Callback();
{ error, internal } ->
elixir_macros:translate({ Name, Meta, Args }, S);
{ ok, Tree } ->
{ ok, _Receiver, Tree } ->
translate_expansion(Meta, Tree, S)
end
end.
%% Macros expansion
expand_import(Meta, { Name, Arity } = Tuple, Args, Module, Function, Requires, Macros, SEnv) ->
case find_dispatch(Tuple, Macros) of
false ->
Fun = (Function /= Tuple) andalso
expand_import(Meta, Tuple, Args, Module, Extra, S) ->
Result = find_dispatch(Meta, Tuple, Extra, S),
do_expand_import(Meta, Tuple, Args, Module, S, Result).
do_expand_import(Meta, { Name, Arity } = Tuple, Args, Module, S, Result) ->
case Result of
{ macro, ?BUILTIN } ->
case is_element(Tuple, in_erlang_macros()) of
true -> { error, internal };
false ->
elixir_import:record(import, Tuple, ?BUILTIN, Module),
{ ok, ?BUILTIN, expand_macro_named(Meta, ?BUILTIN, Name, Arity, Args, Module, S) }
end;
{ macro, Receiver } ->
elixir_import:record(import, Tuple, Receiver, Module),
{ ok, Receiver, expand_macro_named(Meta, Receiver, Name, Arity, Args, Module, S) };
{ import, Receiver } ->
expand_require(Meta, Receiver, Tuple, Args, Module, S);
_ ->
Fun = (S#elixir_scope.function /= Tuple) andalso
elixir_def_local:macro_for(Tuple, true, Module),
case Fun of
false -> { error, noexpansion };
_ ->
elixir_import:record(import, Tuple, Module, Module),
{ ok, Module, expand_macro_fun(Meta, Fun, Module, Name, Args, Module, Requires, SEnv) }
end;
?BUILTIN ->
case is_element(Tuple, in_erlang_macros()) of
true -> { error, internal };
false ->
elixir_import:record(import, Tuple, ?BUILTIN, Module),
{ ok, ?BUILTIN, expand_macro_named(Meta, ?BUILTIN, Name, Arity, Args, Module, Requires, SEnv) }
end;
Receiver ->
elixir_import:record(import, Tuple, Receiver, Module),
{ ok, Receiver, expand_macro_named(Meta, Receiver, Name, Arity, Args, Module, Requires, SEnv) }
{ ok, Module, expand_macro_fun(Meta, Fun, Module, Name, Args, Module, S) }
end
end.
expand_require(Meta, ?BUILTIN, { Name, Arity } = Tuple, Args, Module, _Function, Requires, SEnv) ->
expand_require(Meta, ?BUILTIN, { Name, Arity } = Tuple, Args, Module, S) ->
case is_element(Tuple, in_erlang_macros()) of
true -> { error, internal };
false ->
case is_element(Tuple, in_elixir_macros()) of
true -> { ok, expand_macro_named(Meta, ?BUILTIN, Name, Arity, Args, Module, Requires, SEnv) };
true -> { ok, ?BUILTIN, expand_macro_named(Meta, ?BUILTIN, Name, Arity, Args, Module, S) };
false -> { error, noexpansion }
end
end;
expand_require(Meta, Receiver, { Name, Arity } = Tuple, Args, Module, Function, Requires, SEnv) ->
Fun = (Module == Receiver) andalso (Function /= Tuple) andalso
expand_require(Meta, Receiver, { Name, Arity } = Tuple, Args, Module, S) ->
Fun = (Module == Receiver) andalso (S#elixir_scope.function /= Tuple) andalso
elixir_def_local:macro_for(Tuple, false, Module),
case Fun of
false ->
case is_element(Tuple, get_optional_macros(Receiver)) of
true -> { ok, expand_macro_named(Meta, Receiver, Name, Arity, Args, Module, Requires, SEnv) };
true -> { ok, Receiver, expand_macro_named(Meta, Receiver, Name, Arity, Args, Module, S) };
false -> { error, noexpansion }
end;
_ ->
elixir_import:record(import, Tuple, Receiver, Module),
{ ok, expand_macro_fun(Meta, Fun, Receiver, Name, Args, Module, Requires, SEnv) }
{ ok, Receiver, expand_macro_fun(Meta, Fun, Receiver, Name, Args, Module, S) }
end.
%% Expansion helpers
expand_macro_fun(Meta, Fun, Receiver, Name, Args, Module, Requires, SEnv) ->
case (Receiver == Module) or is_element(Receiver, Requires) or skip_requires(SEnv) of
expand_macro_fun(Meta, Fun, Receiver, Name, Args, Module, S) ->
Requires = S#elixir_scope.requires,
case (Receiver == Module) or is_element(Receiver, Requires) or skip_require(Meta) of
true -> ok;
false ->
Tuple = { unrequired_module, { Receiver, Name, length(Args), Requires } },
elixir_errors:form_error(Meta, elixir_scope:filename(SEnv), ?MODULE, Tuple)
elixir_errors:form_error(Meta, S#elixir_scope.file, ?MODULE, Tuple)
end,
Line = ?line(Meta),
SArg = {Line,SEnv},
SArg = {Line,S},
try
apply(Fun, [SArg|Args])
catch
Kind:Reason ->
Info = { Receiver, Name, length(Args), [{ file, elixir_scope:filename(SEnv) }, { line, Line }] },
Info = { Receiver, Name, length(Args), [{ file, S#elixir_scope.file }, { line, Line }] },
erlang:raise(Kind, Reason, munge_stacktrace(Info, erlang:get_stacktrace(), SArg))
end.
expand_macro_named(Meta, Receiver, Name, Arity, Args, Module, Requires, SEnv) ->
expand_macro_named(Meta, Receiver, Name, Arity, Args, Module, S) ->
ProperName = ?elixir_macro(Name),
ProperArity = Arity + 1,
Fun = fun Receiver:ProperName/ProperArity,
expand_macro_fun(Meta, Fun, Receiver, Name, Args, Module, Requires, SEnv).
expand_macro_fun(Meta, Fun, Receiver, Name, Args, Module, S).
translate_expansion(Meta, Tree, S) ->
{ TR, TS } = elixir_translator:translate_each(
elixir_quote:linify(?line(Meta), Tree),
S#elixir_scope{
check_clauses=false,
macro_aliases=[] }
macro_functions=[],
macro_macros=[],
macro_aliases=[]
}
),
{ TR, TS#elixir_scope{
check_clauses=S#elixir_scope.check_clauses,
macro_aliases=S#elixir_scope.macro_aliases } }.
macro_functions=merge_imports(S#elixir_scope.macro_functions, TS#elixir_scope.macro_functions),
macro_macros=merge_imports(S#elixir_scope.macro_macros, TS#elixir_scope.macro_macros),
macro_aliases=merge_aliases(S#elixir_scope.macro_aliases, TS#elixir_scope.macro_aliases)
} }.
merge_aliases(A1, A2) ->
orddict:merge(fun(_K,_V1,V2) -> V2 end, A1, A2).
merge_imports(A1, A2) ->
A2 ++ lists:foldl(fun({ Key, _ }, Acc) -> lists:keydelete(Key, 1, Acc) end, A1, A2).
%% Helpers
skip_requires(#elixir_scope{check_requires=false}) -> true;
skip_requires(_) -> false.
skip_require(Meta) ->
lists:keyfind(require, 1, Meta) == { require, false }.
find_dispatch(Tuple, [{ Name, Values }|T]) ->
case is_element(Tuple, Values) of
true -> Name;
false -> find_dispatch(Tuple, T)
end;
find_dispatch(Meta, Tuple, S) ->
find_dispatch(Meta, Tuple, [], S).
find_dispatch(_Tuple, []) -> false.
find_dispatch(Meta, Tuple, Extra, S) ->
case is_import(Meta, Tuple, S) of
{ import, _ } = Import ->
Import;
false ->
Functions = S#elixir_scope.functions,
Macros = Extra ++ S#elixir_scope.macros,
File = S#elixir_scope.file,
FunMatch = find_dispatch(Tuple, Functions),
MacMatch = find_dispatch(Tuple, Macros),
case { FunMatch, MacMatch } of
{ [], [Receiver] } -> { macro, Receiver };
{ [Receiver], [] } -> { function, Receiver };
{ [], [] } -> nomatch;
_ ->
{ Name, Arity } = Tuple,
[First, Second|_] = FunMatch ++ MacMatch,
Error = { ambiguous_call, { First, Second, Name, Arity } },
elixir_errors:form_error(Meta, File, ?MODULE, Error)
end
end.
find_dispatch(Tuple, List) ->
[Receiver || { Receiver, Set } <- List, is_element(Tuple, Set)].
is_import(Meta, Tuple, S) ->
case lists:keyfind(import, 1, Meta) of
{ import, _ } = Import ->
not_an_import(Tuple, S#elixir_scope.macro_functions)
andalso not_an_import(Tuple, S#elixir_scope.macro_macros)
andalso Import;
false ->
false
end.
not_an_import(Tuple, Pairs) ->
not lists:any(fun({ _, List }) -> lists:member(Tuple, List) end, Pairs).
munge_stacktrace(Info, [{ _, _, [S|_], _ }|_], S) ->
[Info];
@@ -217,10 +270,14 @@ munge_stacktrace(_, [], _) ->
%% ERROR HANDLING
format_error({ unrequired_module,{Receiver, Name, Arity, Required }}) ->
format_error({ unrequired_module, { Receiver, Name, Arity, Required }}) ->
String = string:join([elixir_errors:inspect(R) || R <- Required], ", "),
io_lib:format("tried to invoke macro ~s.~s/~B but module was not required. Required: ~s",
[elixir_errors:inspect(Receiver), Name, Arity, String]).
io_lib:format("tried to invoke macro ~ts.~ts/~B but module was not required. Required: ~ts",
[elixir_errors:inspect(Receiver), Name, Arity, String]);
format_error({ ambiguous_call, { Mod1, Mod2, Name, Arity }}) ->
io_lib:format("function ~ts/~B imported from both ~ts and ~ts, call is ambiguous",
[Name, Arity, elixir_errors:inspect(Mod1), elixir_errors:inspect(Mod2)]).
%% INTROSPECTION
@@ -228,7 +285,7 @@ remote_function(Meta, Receiver, Name, Arity, S) ->
Line = ?line(Meta),
Final =
case Receiver == ?BUILTIN andalso is_element({ Name, Arity }, in_erlang_functions()) of
case (Receiver == ?BUILTIN) andalso is_element({ Name, Arity }, in_erlang_functions()) of
true -> erlang;
false -> Receiver
end,
@@ -394,4 +451,4 @@ in_erlang_macros() ->
{'receive',1},
{'try',1},
{'xor',2}
].
].
+13 -13
View File
@@ -4,8 +4,8 @@
-export([syntax_error/3, syntax_error/4, inspect/1,
form_error/4, parse_error/4, assert_module_scope/3,
assert_no_function_scope/3, assert_function_scope/3,
assert_no_assign_scope/3, assert_no_guard_scope/3,
assert_no_assign_or_guard_scope/3,
assert_no_match_scope/3, assert_no_guard_scope/3,
assert_no_match_or_guard_scope/3,
handle_file_warning/2, handle_file_warning/3, handle_file_error/2,
deprecation/3, deprecation/4, file_format/3]).
-include("elixir.hrl").
@@ -97,7 +97,7 @@ handle_file_warning(_, _File, { _Line, v3_kernel, bad_call }) -> [];
%% Rewrite
handle_file_warning(_, File, {Line,erl_lint,{undefined_behaviour_func,{Fun,Arity},Module}}) ->
Kind = protocol_or_behaviour(Module),
Raw = "undefined ~s function ~s/~B (for ~s ~s)",
Raw = "undefined ~ts function ~ts/~B (for ~ts ~ts)",
Message = io_lib:format(Raw, [Kind, Fun, Arity, Kind, inspect(Module)]),
io:format(file_format(Line, File, Message));
@@ -105,7 +105,7 @@ handle_file_warning(_, File, {Line,erl_lint,{undefined_behaviour,Module}}) ->
case elixir_compiler:get_opt(internal) of
true -> [];
false ->
Message = io_lib:format("behaviour ~s undefined", [inspect(Module)]),
Message = io_lib:format("behaviour ~ts undefined", [inspect(Module)]),
io:format(file_format(Line, File, Message))
end;
@@ -137,26 +137,26 @@ handle_file_error(File, {Line,Module,Desc}) ->
assert_no_function_scope(_Meta, _Kind, #elixir_scope{function=nil}) -> [];
assert_no_function_scope(Meta, Kind, S) ->
syntax_error(Meta, S#elixir_scope.file, "cannot invoke ~s inside a function", [Kind]).
syntax_error(Meta, S#elixir_scope.file, "cannot invoke ~ts inside a function", [Kind]).
assert_no_assign_or_guard_scope(Meta, Kind, S) ->
assert_no_assign_scope(Meta, Kind, S),
assert_no_match_or_guard_scope(Meta, Kind, S) ->
assert_no_match_scope(Meta, Kind, S),
assert_no_guard_scope(Meta, Kind, S).
assert_no_assign_scope(Meta, Kind, #elixir_scope{context=assign} = S) ->
syntax_error(Meta, S#elixir_scope.file, "cannot invoke ~s inside assign", [Kind]);
assert_no_assign_scope(_Meta, _Kind, _S) -> [].
assert_no_match_scope(Meta, Kind, #elixir_scope{context=match} = S) ->
syntax_error(Meta, S#elixir_scope.file, "cannot invoke ~ts inside match clause", [Kind]);
assert_no_match_scope(_Meta, _Kind, _S) -> [].
assert_no_guard_scope(Meta, Kind, #elixir_scope{context=guard} = S) ->
syntax_error(Meta, S#elixir_scope.file, "cannot invoke ~s inside guard", [Kind]);
syntax_error(Meta, S#elixir_scope.file, "cannot invoke ~ts inside guard", [Kind]);
assert_no_guard_scope(_Meta, _Kind, _S) -> [].
assert_module_scope(Meta, Kind, #elixir_scope{module=nil,file=File}) ->
syntax_error(Meta, File, "cannot invoke ~s outside module", [Kind]);
syntax_error(Meta, File, "cannot invoke ~ts outside module", [Kind]);
assert_module_scope(_Meta, _Kind, #elixir_scope{module=Module}) -> Module.
assert_function_scope(Meta, Kind, #elixir_scope{function=nil,file=File}) ->
syntax_error(Meta, File, "cannot invoke ~s outside function", [Kind]);
syntax_error(Meta, File, "cannot invoke ~ts outside function", [Kind]);
assert_function_scope(_Meta, _Kind, #elixir_scope{function=Function}) -> Function.
%% Helpers
+20 -39
View File
@@ -41,9 +41,9 @@ import(Meta, Ref, Opts, Selector, S) ->
false -> S;
true ->
FunctionsFun = fun(K) -> remove_underscored(K andalso Selector, get_functions(Ref)) end,
Functions = calculate(Meta, Ref, Opts,
S#elixir_scope.functions, FunctionsFun, S),
S#elixir_scope{functions=Functions}
{ Functions, TempF } = calculate(Meta, Ref, Opts,
S#elixir_scope.functions, S#elixir_scope.macro_functions, FunctionsFun, S),
S#elixir_scope{functions=Functions, macro_functions=TempF}
end,
SM = case IncludeAll or (Selector == macros) of
@@ -55,9 +55,9 @@ import(Meta, Ref, Opts, Selector, S) ->
false -> get_macros(Meta, Ref, SF)
end
end,
Macros = calculate(Meta, Ref, Opts,
SF#elixir_scope.macros, MacrosFun, SF),
SF#elixir_scope{macros=Macros}
{ Macros, TempM } = calculate(Meta, Ref, Opts,
SF#elixir_scope.macros, SF#elixir_scope.macro_macros, MacrosFun, SF),
SF#elixir_scope{macros=Macros, macro_macros=TempM}
end,
SM.
@@ -66,9 +66,8 @@ import(Meta, Ref, Opts, Selector, S) ->
%% Calculates the imports based on only and except
calculate(Meta, Key, Opts, Old, AvailableFun, S) ->
calculate(Meta, Key, Opts, Old, Temp, AvailableFun, S) ->
File = S#elixir_scope.file,
All = keydelete(Key, Old),
New = case keyfind(only, Opts) of
{ only, RawOnly } ->
@@ -88,22 +87,20 @@ calculate(Meta, Key, Opts, Old, AvailableFun, S) ->
Except = expand_fun_arity(Meta, except, RawExcept, S),
case keyfind(Key, Old) of
false -> AvailableFun(true) -- Except;
{Key,ToRemove} -> ToRemove -- Except
{Key,OldImports} -> OldImports -- Except
end
end
end,
%% Normalize the data before storing it
Set = ordsets:from_list(New),
Final = remove_internals(Set),
Set = ordsets:from_list(New),
Final = remove_internals(Set),
case Final of
[] -> All;
[] -> { keydelete(Key, Old), keydelete(Key, Temp) };
_ ->
ensure_no_conflicts(Meta, File, Final, keydelete(Key, S#elixir_scope.macros)),
ensure_no_conflicts(Meta, File, Final, keydelete(Key, S#elixir_scope.functions)),
ensure_no_in_erlang_macro_conflict(Meta, File, Key, Final, internal_conflict),
[{ Key, Final }|All]
{ [{ Key, Final }|keydelete(Key, Old)], [{ Key, Final }|keydelete(Key, Temp)] }
end.
%% Ensure we are expanding macros and stuff
@@ -120,14 +117,14 @@ cons_to_keywords(Meta, Kind, { cons, _, Left, Right }, S) ->
cons_to_keywords(Meta, Kind, _, S) ->
elixir_errors:syntax_error(Meta, S#elixir_scope.file,
"invalid value for :~s, expected a list with functions and arities", [Kind]).
"invalid value for :~ts, expected a list with functions and arities", [Kind]).
tuple_to_fun_arity(_Meta, _Kind, { tuple, _, [{ atom, _, Atom }, { integer, _, Integer }] }, _S) ->
{ Atom, Integer };
tuple_to_fun_arity(Meta, Kind, _, S) ->
elixir_errors:syntax_error(Meta, S#elixir_scope.file,
"invalid value for :~s, expected a list with functions and arities", [Kind]).
"invalid value for :~ts, expected a list with functions and arities", [Kind]).
%% Retrieve functions and macros from modules
@@ -207,44 +204,28 @@ ensure_no_in_erlang_macro_conflict(Meta, File, Key, [{Name,Arity}|T], Reason) ->
ensure_no_in_erlang_macro_conflict(_Meta, _File, _Key, [], _) -> ok.
%% Find conlicts in the given list of functions with the set of imports.
%% Used internally to ensure a newly imported fun or macro does not
%% conflict with an already imported set.
ensure_no_conflicts(Meta, File, Functions, [{Key,Value}|T]) ->
Filtered = lists:filter(fun(X) -> lists:member(X, Functions) end, Value),
case Filtered of
[{Name,Arity}|_] ->
Tuple = { already_imported, { Key, Name, Arity } },
elixir_errors:form_error(Meta, File, ?MODULE, Tuple);
[] ->
ensure_no_conflicts(Meta, File, Functions, T)
end;
ensure_no_conflicts(_Meta, _File, _Functions, _S) -> ok.
%% ERROR HANDLING
format_error({already_imported,{Receiver, Name, Arity}}) ->
io_lib:format("function ~s/~B already imported from ~s", [Name, Arity, elixir_errors:inspect(Receiver)]);
io_lib:format("function ~ts/~B already imported from ~ts", [Name, Arity, elixir_errors:inspect(Receiver)]);
format_error({invalid_import,{Receiver, Name, Arity}}) ->
io_lib:format("cannot import ~s.~s/~B because it doesn't exist",
io_lib:format("cannot import ~ts.~ts/~B because it doesn't exist",
[elixir_errors:inspect(Receiver), Name, Arity]);
format_error({import_conflict,{Receiver, Name, Arity}}) ->
io_lib:format("imported ~s.~s/~B conflicts with local function",
io_lib:format("imported ~ts.~ts/~B conflicts with local function",
[elixir_errors:inspect(Receiver), Name, Arity]);
format_error({local_conflict,{_, Name, Arity}}) ->
io_lib:format("cannot define local ~s/~B because it conflicts with Elixir special forms", [Name, Arity]);
io_lib:format("cannot define local ~ts/~B because it conflicts with Elixir special forms", [Name, Arity]);
format_error({internal_conflict,{Receiver, Name, Arity}}) ->
io_lib:format("cannot import ~s.~s/~B because it conflicts with Elixir special forms",
io_lib:format("cannot import ~ts.~ts/~B because it conflicts with Elixir special forms",
[elixir_errors:inspect(Receiver), Name, Arity]);
format_error({ no_macros, Module }) ->
io_lib:format("could not load macros from module ~s", [elixir_errors:inspect(Module)]).
io_lib:format("could not load macros from module ~ts", [elixir_errors:inspect(Module)]).
%% LIST HELPERS
+2 -2
View File
@@ -19,8 +19,8 @@ extract(Line, _File, _Interpol, [], _Buffer, [], _Output, Last) ->
extract(Line, File, _Interpol, [Last|Remaining], Buffer, [], Output, Last) ->
finish_extraction(Line, File, Buffer, Output, Remaining);
extract(Line, _File, _Interpol, [Last], _Buffer, Search, _Output, Last) ->
{ error, { Line, io_lib:format("unexpected end of string: ~ts", [[hd(Search)]]), [Last] } };
extract(Line, _File, _Interpol, End, _Buffer, _Search, _Output, Last) when End == [Last]; End == [] ->
{ error, { Line, io_lib:format("missing terminator: ~ts", [[Last]]), [] } };
extract(Line, File, Interpol, [$\n|Rest], Buffer, Search, Output, Last) ->
extract(Line+1, File, Interpol, Rest, [$\n|Buffer], Search, Output, Last);
+3 -3
View File
@@ -9,7 +9,7 @@ translate({ '<<>>', Meta, Args } = Original, S) when is_list(Args) ->
case elixir_partials:handle(Original, S, allow_tail) of
error ->
case S#elixir_scope.context of
assign ->
match ->
build_bitstr(fun elixir_translator:translate_each/2, Args, Meta, S);
_ ->
{ TArgs, { SC, SV } } = build_bitstr(fun elixir_translator:translate_arg/2, Args, Meta, { S, S }),
@@ -47,7 +47,7 @@ translate({ '[]', _Meta, Args } = Original, S) when is_list(Args) ->
end,
{ FExprs, FS } = case S#elixir_scope.context of
assign ->
match ->
elixir_tree_helpers:build_reverse_list(fun elixir_translator:translate_each/2, Exprs, 0, ListS, Tail);
_ ->
{ TArgs, { SC, SV } } =
@@ -219,7 +219,7 @@ extract_bit_type_or_size(_Value, _Args, _Other, _Types) ->
handle_unknown_specifier(Meta, [H|T], Size, Types, S) ->
case 'Elixir.Macro':expand(H, elixir_scope:to_ex_env({ ?line(Meta), S })) of
H ->
elixir_errors:syntax_error(Meta, S#elixir_scope.file, "unknown bitstring specifier ~s", ['Elixir.Macro':to_binary(H)]);
elixir_errors:syntax_error(Meta, S#elixir_scope.file, "unknown bitstring specifier ~ts", ['Elixir.Macro':to_binary(H)]);
E ->
extract_bit_values(Meta, join_expansion(E,T), Size, Types, S)
end.
+33 -40
View File
@@ -5,7 +5,7 @@
-import(elixir_translator, [translate_each/2, translate_args/2, translate_apply/7]).
-import(elixir_scope, [umergec/2]).
-import(elixir_errors, [syntax_error/3, syntax_error/4,
assert_no_function_scope/3, assert_module_scope/3, assert_no_assign_or_guard_scope/3]).
assert_no_function_scope/3, assert_module_scope/3, assert_no_match_or_guard_scope/3]).
-include("elixir.hrl").
-define(FUNS(Kind), Kind == def; Kind == defp; Kind == defmacro; Kind == defmacrop).
@@ -23,7 +23,7 @@ translate({ '-', _Meta, [Expr] }, S) when is_number(Expr) ->
translate({ Op, Meta, Exprs }, S) when is_list(Exprs),
Op == '<-' orelse Op == '--' ->
assert_no_assign_or_guard_scope(Meta, Op, S),
assert_no_match_or_guard_scope(Meta, Op, S),
translate_each({ '__op__', Meta, [Op|Exprs] }, S);
translate({ Op, Meta, Exprs }, S) when is_list(Exprs),
@@ -53,34 +53,46 @@ translate({ in, Meta, [Left, Right] }, #elixir_scope{extra_guards=Extra} = S) ->
%% Functions
translate({ function, Meta, [[{do,{ '->',_,Pairs}}]] }, S) ->
assert_no_assign_or_guard_scope(Meta, 'function', S),
assert_no_match_or_guard_scope(Meta, 'function', S),
elixir_translator:translate_fn(Meta, Pairs, S);
translate({ function, Meta, [{ '/', _, [{{ '.', _ ,[M, F] }, _ , [] }, A]}] }, S) ->
translate({ function, _, [{ '/', _, [{{ '.', Meta, [M, F] }, _ , []}, A]}] }, S) when is_atom(F), is_integer(A) ->
translate({ function, Meta, [M, F, A] }, S);
translate({ function, Meta, [{ '/', _, [{F, _, Q}, A]}] }, S) when is_atom(Q) ->
translate({ function, Meta, [F, A] }, S);
translate({ function, MetaFA, [{ '/', _, [{F, Meta, C}, A]}] }, S) when is_atom(F), is_integer(A), is_atom(C) ->
assert_no_match_or_guard_scope(Meta, 'function', S),
WrappedMeta =
case (lists:keyfind(import, 1, Meta) == false) andalso lists:keyfind(import_fa, 1, MetaFA) of
{ import_fa, Receiver } -> [{ import, Receiver }|Meta];
false -> Meta
end,
case elixir_dispatch:import_function(WrappedMeta, F, A, S) of
false -> syntax_error(WrappedMeta, S#elixir_scope.file, "cannot convert a macro to a function");
Else -> Else
end;
translate({ function, Meta, [_] }, S) ->
assert_no_assign_or_guard_scope(Meta, 'function', S),
assert_no_match_or_guard_scope(Meta, 'function', S),
syntax_error(Meta, S#elixir_scope.file, "invalid args for function");
translate({ function, Meta, [_, _] = Args }, S) ->
assert_no_assign_or_guard_scope(Meta, 'function', S),
assert_no_match_or_guard_scope(Meta, 'function', S),
case translate_args(Args, S) of
{ [{atom,_,Name}, {integer,_,Arity}], SA } ->
elixir_errors:deprecation(Meta, S#elixir_scope.file, "function(:~ts, ~B) is deprecated, please use function(~ts/~B)", [Name, Arity, Name, Arity]),
case elixir_dispatch:import_function(Meta, Name, Arity, SA) of
false -> syntax_error(Meta, S#elixir_scope.file, "cannot convert a macro to a function");
Else -> Else
end;
_ ->
syntax_error(Meta, S#elixir_scope.file, "cannot dynamically retrieve local function. use function(module, fun, arity) instead")
syntax_error(Meta, S#elixir_scope.file, "cannot dynamically retrieve local function, use function/3 instead")
end;
translate({ function, Meta, [_,_,_] = Args }, S) when is_list(Args) ->
assert_no_assign_or_guard_scope(Meta, 'function', S),
assert_no_match_or_guard_scope(Meta, 'function', S),
{ [A,B,C], SA } = translate_args(Args, S),
{ { 'fun', ?line(Meta), { function, A, B, C } }, SA };
@@ -112,7 +124,7 @@ translate({'@', Meta, [{ Name, _, Args }]}, S) ->
}, S);
_ ->
syntax_error(Meta, S#elixir_scope.file,
"cannot dynamically set attribute @~s inside a function", [Name])
"cannot dynamically set attribute @~ts inside a function", [Name])
end;
_ when is_atom(Args) or (Args == []) ->
case S#elixir_scope.function of
@@ -127,14 +139,14 @@ translate({'@', Meta, [{ Name, _, Args }]}, S) ->
{ elixir_tree_helpers:abstract_syntax(Contents), S }
end;
_ ->
syntax_error(Meta, S#elixir_scope.file, "expected 0 or 1 argument for @~s, got: ~p", [Name, length(Args)])
syntax_error(Meta, S#elixir_scope.file, "expected 0 or 1 argument for @~ts, got: ~p", [Name, length(Args)])
end
end;
%% Case
translate({'case', Meta, [Expr, KV]}, S) ->
assert_no_assign_or_guard_scope(Meta, 'case', S),
assert_no_match_or_guard_scope(Meta, 'case', S),
Clauses = elixir_clauses:get_pairs(Meta, do, KV, S),
{ TExpr, NS } = translate_each(Expr, S),
@@ -150,7 +162,7 @@ translate({'case', Meta, [Expr, KV]}, S) ->
translate({'try', Meta, [Clauses]}, RawS) ->
S = RawS#elixir_scope{noname=true},
assert_no_assign_or_guard_scope(Meta, 'try', S),
assert_no_match_or_guard_scope(Meta, 'try', S),
Do = proplists:get_value('do', Clauses, nil),
{ TDo, SB } = elixir_translator:translate_each(Do, S),
@@ -169,7 +181,7 @@ translate({'try', Meta, [Clauses]}, RawS) ->
%% Receive
translate({'receive', Meta, [KV] }, S) ->
assert_no_assign_or_guard_scope(Meta, 'receive', S),
assert_no_match_or_guard_scope(Meta, 'receive', S),
Do = elixir_clauses:get_pairs(Meta, do, KV, S, true),
case lists:keyfind('after', 1, KV) of
@@ -215,7 +227,8 @@ translate({defmodule, Meta, [Ref, KV]}, S) ->
{ TRef, S }
end,
{ elixir_module:translate(Meta, FRef, Block, S#elixir_scope{check_clauses=true}), FS };
MS = FS#elixir_scope{check_clauses=true,local=nil},
{ elixir_module:translate(Meta, FRef, Block, MS), FS };
translate({Kind, Meta, [Call]}, S) when ?FUNS(Kind) ->
translate({Kind, Meta, [Call, nil]}, S);
@@ -223,22 +236,9 @@ translate({Kind, Meta, [Call]}, S) when ?FUNS(Kind) ->
translate({Kind, Meta, [Call, Expr]}, S) when ?FUNS(Kind) ->
assert_module_scope(Meta, Kind, S),
assert_no_function_scope(Meta, Kind, S),
{ TCall, Guards } = elixir_clauses:extract_guards(Call),
{ Name, Args } = case elixir_clauses:extract_args(TCall) of
error -> syntax_error(Meta, S#elixir_scope.file,
"invalid syntax in ~s ~s", [Kind, 'Elixir.Macro':to_binary(TCall)]);
Tuple -> Tuple
end,
assert_no_aliases_name(Meta, Name, Args, S),
TName = elixir_tree_helpers:abstract_syntax(Name),
TArgs = elixir_tree_helpers:abstract_syntax(Args),
TGuards = elixir_tree_helpers:abstract_syntax(Guards),
TExpr = elixir_tree_helpers:abstract_syntax(Expr),
{ elixir_def:wrap_definition(Kind, Meta, TName, TArgs, TGuards, TExpr, S), S };
{ TCall, SC } = elixir_quote:user_quote(Call, S),
{ TExpr, SE } = elixir_quote:user_quote(Expr, SC),
{ elixir_def:wrap_definition(Kind, Meta, TCall, TExpr, SE), SE };
translate({Kind, Meta, [Name, Args, Guards, Expr]}, S) when ?FUNS(Kind) ->
assert_module_scope(Meta, Kind, S),
@@ -334,7 +334,7 @@ rewrite_case_clauses(Clauses) ->
expand_module(Raw, Module, #elixir_scope{module=Nesting}) when is_atom(Raw); Nesting == nil ->
Module;
expand_module({ '__aliases__', _, _ } = Alias, Module, S) ->
expand_module({ '__aliases__', _, List } = Alias, Module, S) when List /= ['Elixir'] ->
case elixir_aliases:expand(Alias, S#elixir_scope.aliases, S#elixir_scope.macro_aliases) of
Atom when is_atom(Atom) ->
Module;
@@ -358,10 +358,3 @@ spec_to_macro(callback) -> defcallback.
% Pack a list of expressions from a block.
pack({ 'block', _, Exprs }) -> Exprs;
pack(Expr) -> [Expr].
assert_no_aliases_name(Meta, '__aliases__', [Atom], #elixir_scope{file=File}) when is_atom(Atom) ->
Message = "function names should start with lowercase characters or underscore, invalid name ~s",
syntax_error(Meta, File, Message, [atom_to_binary(Atom, utf8)]);
assert_no_aliases_name(_Meta, _Aliases, _Args, _S) ->
ok.
+8 -8
View File
@@ -143,7 +143,7 @@ eval_form(Line, Module, Block, Vars, RawS) ->
functions_form(Line, File, Module, Export, Private, Def, Defmacro, Defmacrop, RawFunctions, C) ->
Functions = case elixir_compiler:get_opt(internal, C) of
true -> RawFunctions;
false -> record_rewrite_functions(RawFunctions)
false -> record_rewrite_functions(Module, RawFunctions)
end,
{ FinalExport, FinalFunctions } =
@@ -156,11 +156,11 @@ functions_form(Line, File, Module, Export, Private, Def, Defmacro, Defmacrop, Ra
{attribute, Line, export, lists:sort(FinalExport)} | FinalFunctions
] }.
record_rewrite_functions(Functions) ->
record_rewrite_functions(Module, Functions) ->
lists:map(fun
({ function, Line, Name, Arity, Clauses }) ->
Rewriten = [begin
{ C, _, _ } = 'Elixir.Kernel.RecordRewriter':optimize_clause(Clause),
{ C, _, _ } = 'Elixir.Kernel.RecordRewriter':optimize_clause(Module, Clause),
C
end || Clause <- Clauses],
{ function, Line, Name, Arity, Rewriten };
@@ -324,10 +324,10 @@ else_clause() ->
% HELPERS
eval_callbacks(Line, Module, Name, Args, RawS) ->
S = RawS#elixir_scope{check_clauses=false,check_requires=false},
S = RawS#elixir_scope{check_clauses=false},
Binding = binding_for_eval(Module, []),
Callbacks = lists:reverse(ets:lookup_element(data_table(Module), Name, 2)),
Meta = [{line,Line}],
Meta = [{line,Line},{require,false}],
lists:foreach(fun({M,F}) ->
{ Tree, _ } = elixir_dispatch:dispatch_require(Meta, M, F, Args, S, fun() ->
@@ -347,14 +347,14 @@ eval_callbacks(Line, Module, Name, Args, RawS) ->
% ERROR HANDLING
format_error({ internal_function_overridden, { Name, Arity } }) ->
io_lib:format("function ~s/~B is internal and should not be overridden", [Name, Arity]);
io_lib:format("function ~ts/~B is internal and should not be overridden", [Name, Arity]);
format_error({ invalid_module, Module}) ->
io_lib:format("invalid module name: ~p", [Module]);
format_error({ module_defined, Module }) ->
io_lib:format("redefining module ~s", [elixir_errors:inspect(Module)]);
io_lib:format("redefining module ~ts", [elixir_errors:inspect(Module)]);
format_error({ module_in_definition, Module }) ->
io_lib:format("cannot define module ~s because it is currently being defined",
io_lib:format("cannot define module ~ts because it is currently being defined",
[elixir_errors:inspect(Module)]).
+1 -1
View File
@@ -6,7 +6,7 @@
handle(Original, S) ->
handle(Original, S, default).
handle({ _, Meta, Args } = Original, S, Opt) when is_list(Args), S#elixir_scope.context /= assign ->
handle({ _, Meta, Args } = Original, S, Opt) when is_list(Args), S#elixir_scope.context /= match ->
case convert(Args, S, Opt) of
{ Call, Def, SC } when Def /= [] ->
Final = validate(Meta, Def, SC),
+46 -16
View File
@@ -1,5 +1,5 @@
-module(elixir_quote).
-export([quote/3, linify/2, join_quoted/5]).
-export([quote/3, linify/2, unquote/5, user_quote/2]).
-include("elixir.hrl").
%% Apply the line from site call on quoted contents.
@@ -21,12 +21,12 @@ do_linify(Line, List) when is_list(List) ->
do_linify(_, Else) -> Else.
%% Join quoted checks for quote arguments at runtime
%% in order to properly insert them into the tree
join_quoted(Meta, Left, { '__aliases__', _, Args }, nil, _File) ->
%% Some expressions cannot be unquoted at compilation time.
%% This function is responisble for doing runtime unquoting.
unquote(Meta, Left, { '__aliases__', _, Args }, nil, _File) ->
{ '__aliases__', Meta, [Left|Args] };
join_quoted(Meta, Left, Right, nil, _File) when is_atom(Right) ->
unquote(Meta, Left, Right, nil, _File) when is_atom(Right) ->
case atom_to_list(Right) of
"Elixir-" ++ _ ->
{ '__aliases__', Meta, [Left, Right] };
@@ -34,23 +34,45 @@ join_quoted(Meta, Left, Right, nil, _File) when is_atom(Right) ->
{ { '.', Meta, [Left, Right] }, Meta, [] }
end;
join_quoted(Meta, Left, Right, Args, _File) when is_atom(Right) ->
unquote(Meta, Left, Right, Args, _File) when is_atom(Right) ->
{ { '.', Meta, [Left, Right] }, Meta, Args };
join_quoted(Meta, _Left, _Right, _Args, File) ->
unquote(Meta, _Left, _Right, _Args, File) ->
elixir_errors:syntax_error(Meta, File, "expected unquote after dot to return an atom or an alias").
%% Translation
%% Similar to quote, but the given code is meant to come directly from the user.
%% Basically, lines are kept and hygiene mechanisms are disabled.
user_quote(Expr, S) ->
quote(Expr, #elixir_quote{
line=keep,
vars_hygiene=nil,
aliases_hygiene=false,
imports_hygiene=false,
unquote=true
}, S).
quote({ 'unquote_splicing', Meta, _ } = Expr, #elixir_quote{unquote=true} = Q, S) ->
%% Quotes an expression into Erlang's AST
quote({ 'unquote_splicing', Meta, [_] } = Expr, #elixir_quote{unquote=true} = Q, S) ->
do_quote({ '__block__', Meta, [Expr] }, Q, S);
quote(Else, Q, S) ->
do_quote(Else, Q, S).
do_quote({ quote, _, Args } = Tuple, #elixir_quote{unquote=true} = Q, S) when length(Args) == 1; length(Args) == 2 ->
do_quote_tuple(Tuple, Q#elixir_quote{unquote=false}, S);
do_quote({ unquote, _Meta, [Expr] }, #elixir_quote{unquote=true}, S) ->
elixir_translator:translate_each(Expr, S);
do_quote({ function, Meta, [{ '/', _, [{F, _, C}, A]}] = Args },
#elixir_quote{imports_hygiene=true} = Q, S) when is_atom(F), is_integer(A), is_atom(C) ->
do_quote_fa(function, Meta, Args, F, A, Q, S);
do_quote({ { '.', _, [_, function] } = Target, Meta, [{ '/', _, [{F, _, C}, A]}] = Args },
#elixir_quote{imports_hygiene=true} = Q, S) when is_atom(F), is_integer(A), is_atom(C) ->
do_quote_fa(Target, Meta, Args, F, A, Q, S);
do_quote({ 'alias!', _Meta, [Expr] }, Q, S) ->
do_quote(Expr, Q#elixir_quote{aliases_hygiene=false}, S);
@@ -72,12 +94,12 @@ do_quote({ '__aliases__', Meta, [H|T] } = Alias, #elixir_quote{aliases_hygiene=t
do_quote({ { { '.', Meta, [Left, unquote] }, _, [Expr] }, _, Args }, #elixir_quote{unquote=true} = Q, S) ->
All = [Left, { unquote, Meta, [Expr] }, Args, S#elixir_scope.file],
{ TAll, TS } = lists:mapfoldl(fun(X, Acc) -> do_quote(X, Q, Acc) end, S, All),
{ ?wrap_call(?line(Meta), elixir_quote, join_quoted, [meta(Meta, Q)|TAll]), TS };
{ ?wrap_call(?line(Meta), elixir_quote, unquote, [meta(Meta, Q)|TAll]), TS };
do_quote({ { '.', Meta, [Left, unquote] }, _, [Expr] }, #elixir_quote{unquote=true} = Q, S) ->
All = [Left, { unquote, Meta, [Expr] }, nil, S#elixir_scope.file],
{ TAll, TS } = lists:mapfoldl(fun(X, Acc) -> do_quote(X, Q, Acc) end, S, All),
{ ?wrap_call(?line(Meta), elixir_quote, join_quoted, [meta(Meta, Q)|TAll]), TS };
{ ?wrap_call(?line(Meta), elixir_quote, unquote, [meta(Meta, Q)|TAll]), TS };
do_quote({ Left, Meta, nil }, Q, S) when is_atom(Left) ->
Line = ?line(Meta),
@@ -139,6 +161,16 @@ do_quote_tuple({ Left, Meta, Right }, Q, S) ->
Tuple = { tuple, ?line(Meta), [TLeft, meta(Meta, Q), TRight] },
{ Tuple, RS }.
do_quote_fa(Target, Meta, Args, F, A, Q, S) ->
NewMeta =
case (lists:keyfind(import_fa, 1, Meta) == false) andalso
elixir_dispatch:find_import(Meta, F, A, S) of
false -> Meta;
Receiver -> [{ import_fa, Receiver }|Meta]
end,
do_quote_tuple({ Target, NewMeta, Args }, Q, S).
% Loop through the list finding each unquote_splicing entry.
splice([{ unquote_splicing, _, [Args] }|T], #elixir_quote{unquote=true} = Q, Buffer, Acc, S) ->
@@ -159,11 +191,9 @@ splice([], Q, Buffer, Acc, S) ->
case NewAcc of
[] ->
{ { nil, line(Q) }, NewS };
[List] ->
{ List, NewS };
_ ->
List = elixir_tree_helpers:build_simple_reverse_list(line(Q), NewAcc),
{ ?wrap_call(line(Q), lists, append, [List]), NewS }
[H|T] ->
Line = line(Q),
{ lists:foldl(fun(L, R) -> { op, Line, '++', L, R } end, H, T), NewS }
end.
from_buffer_to_acc([], _Q, Acc, S) ->
+13 -13
View File
@@ -5,9 +5,9 @@
build_erl_var/2, build_ex_var/2,
serialize/1, deserialize/1,
serialize_with_vars/2, deserialize_with_vars/2,
to_erl_env/1, to_ex_env/1, filename/1,
to_erl_env/1, to_ex_env/1,
umergev/2, umergec/2, merge_clause_vars/2
]).
]).
-include("elixir.hrl").
-compile({parse_transform, elixir_transform}).
@@ -20,7 +20,7 @@ translate_var(Meta, Name, Kind, S, Callback) ->
{ { var, Line, Name }, S };
_ ->
case S#elixir_scope.context of
assign ->
match ->
TempVars = S#elixir_scope.temp_vars,
case { orddict:is_key({ Name, Kind }, Vars), orddict:find(Name, TempVars) } of
{ true, { ok, Kind } } ->
@@ -77,27 +77,24 @@ build_ex_var(Line, Key, Name, S) when is_integer(Line) ->
% Handle Macro.Env conversion
to_erl_env(Scope) ->
elixir_tree_helpers:abstract_syntax(to_ex_env(Scope)).
to_ex_env({ Line, Tuple }) when element(1, Tuple) == 'Elixir.Macro.Env', is_integer(Line) ->
setelement(4, Tuple, Line);
to_erl_env({ 'Elixir.Macro.Env', Module, File, _Line, Function, Aliases, Context, Requires, Functions, Macros }) ->
#elixir_scope{module=Module,file=File,
function=Function,aliases=Aliases,context=Context,
requires=Requires,macros=Macros,functions=Functions}.
to_ex_env({ Line, #elixir_scope{module=Module,file=File,
function=Function,aliases=Aliases,context=Context,
requires=Requires,macros=Macros,functions=Functions} }) when is_integer(Line) ->
{ 'Elixir.Macro.Env', Module, File, Line, Function, Aliases, Context, Requires, Functions, Macros }.
filename(#elixir_scope{file=File}) -> File;
filename(Other) -> element(3, Other).
% Provides a tuple with only the scope information we want to serialize.
serialize(S) ->
elixir_tree_helpers:abstract_syntax(
{ S#elixir_scope.file, S#elixir_scope.functions, S#elixir_scope.check_clauses,
S#elixir_scope.requires, S#elixir_scope.macros, S#elixir_scope.aliases,
S#elixir_scope.macro_aliases, S#elixir_scope.scheduled }
S#elixir_scope.macro_functions, S#elixir_scope.macro_macros, S#elixir_scope.macro_aliases,
S#elixir_scope.scheduled }
).
serialize_with_vars(Line, S) when is_integer(Line) ->
@@ -115,7 +112,8 @@ serialize_with_vars(Line, S) when is_integer(Line) ->
deserialize(Tuple) -> deserialize_with_vars(Tuple, []).
deserialize_with_vars({ File, Functions, CheckClauses, Requires, Macros, Aliases, MacroAliases, Scheduled }, Vars) ->
deserialize_with_vars({ File, Functions, CheckClauses, Requires, Macros,
Aliases, MacroFunctions, MacroMacros, MacroAliases, Scheduled }, Vars) ->
#elixir_scope{
file=File,
functions=Functions,
@@ -123,6 +121,8 @@ deserialize_with_vars({ File, Functions, CheckClauses, Requires, Macros, Aliases
requires=Requires,
macros=Macros,
aliases=Aliases,
macro_functions=MacroFunctions,
macro_macros=MacroMacros,
macro_aliases=MacroAliases,
scheduled=Scheduled,
vars=orddict:from_list(Vars),
+38 -17
View File
@@ -107,7 +107,7 @@ tokenize([], _Line, #scope{terminators=[]}, Tokens) ->
tokenize([], EndLine, #scope{terminators=[{ Start, StartLine }|_]}, _Tokens) ->
End = terminator(Start),
Message = io_lib:format("missing terminator: ~s (for \"~s\" starting at line ~B)", [End, Start, StartLine]),
Message = io_lib:format("missing terminator: ~ts (for \"~ts\" starting at line ~B)", [End, Start, StartLine]),
{ error, { EndLine, Message, [] } };
% Base integers
@@ -148,7 +148,7 @@ tokenize([$%,S,H|T], Line, #scope{file=File} = Scope, Tokens) when not(?is_word(
tokenize(Final, NewLine, Scope, [{ sigil, Line, S, Parts, Modifiers }|Tokens]);
Error ->
Sigil = [$%,S,H],
interpolation_error(Error, " (for sigil ~s starting at line ~B)", [Sigil, Line])
interpolation_error(Error, " (for sigil ~ts starting at line ~B)", [Sigil, Line])
end;
% Char tokens
@@ -211,6 +211,14 @@ tokenize("..." ++ Rest, Line, Scope, Tokens) ->
tokenize(Rest, Line, Scope, [Token|Tokens]);
% ## Containers
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;
tokenize(".<<>>" ++ Rest, Line, Scope, Tokens) ->
handle_call_identifier(Rest, Line, '<<>>', Scope, Tokens);
@@ -394,13 +402,15 @@ tokenize([H|_] = String, Line, Scope, Tokens) when ?is_upcase(H) ->
tokenize([H|_] = String, Line, Scope, Tokens) when ?is_downcase(H); H == $_ ->
{ Rest, { Kind, _, Identifier } } = tokenize_any_identifier(String, Line, [], Scope),
case handle_keyword(Line, Kind, Identifier, Tokens) of
false ->
nomatch ->
tokenize(Rest, Line, Scope, [{ Kind, Line, Identifier }|Tokens]);
[Check|T] ->
{ ok, [Check|T] } ->
case handle_terminator(Check, Scope) of
{ error, _ } = Error -> Error;
New -> tokenize(Rest, Line, New, [Check|T])
end
end;
{ error, Token } ->
{ error, { Line, "syntax error before: ", Token } }
end;
% Ambiguous unary/binary operators tokens
@@ -450,7 +460,7 @@ handle_strings(T, Line, H, #scope{file=File} = Scope, Tokens) ->
Atom = unsafe_to_atom(unescape_chars(Bin), Scope),
tokenize(Rest, NewLine, Scope, [{ kw_identifier, Line, Atom }|Tokens]);
_ ->
{ error, { Line, "invalid interpolation in key", [$"|T] } }
{ error, { Line, "invalid interpolation in key ", [$"|T] } }
end;
{ NewLine, Parts, Rest } ->
Token = { string_type(H),Line,unescape_tokens(Parts) },
@@ -532,7 +542,7 @@ extract_heredoc(Line0, Rest0, Marker) ->
heredoc_error(ErrorLine, StartLine, Marker) ->
Terminator = [Marker, Marker, Marker],
Message = io_lib:format("missing terminator: ~s (for heredoc starting at line ~B)", [Terminator, StartLine]),
Message = io_lib:format("missing terminator: ~ts (for heredoc starting at line ~B)", [Terminator, StartLine]),
{ error, { ErrorLine, Message, [] } }.
%% Remove spaces from heredoc based on the position of the final quotes.
@@ -723,7 +733,7 @@ tokenize_call_identifier(Kind, Line, Atom, Rest) ->
verify_kw_and_space(_Line, _Atom, [H|_], #scope{}) when ?is_space(H) -> ok;
verify_kw_and_space(Line, Atom, _, #scope{file=File}) ->
io:format("~ts:~w: keyword argument ~s: must be followed by space~n", [File, Line, Atom]).
io:format("~ts:~w: keyword argument ~ts: must be followed by space~n", [File, Line, Atom]).
next_is_block([Space|Tokens]) when Space == $\t; Space == $\s ->
next_is_block(Tokens);
@@ -744,7 +754,7 @@ add_token_with_nl(Left, T) -> [Left|T].
% Error handling
interpolation_error({ error, { Line, Message, Token } }, Extension, Args) ->
{ error, { Line, io_lib:format("~s" ++ Extension, [Message|Args]), Token } }.
{ error, { Line, io_lib:format("~ts" ++ Extension, [Message|Args]), Token } }.
% Terminators
@@ -788,7 +798,7 @@ check_terminator({ E, _ }, [{ S, _ }|Terminators]) when
check_terminator({ E, Line }, [{ Start, StartLine }|_]) when
E == 'end'; E == ')'; E == ']'; E == '}'; E == '>>' ->
End = terminator(Start),
Message = io_lib:format("missing terminator: ~s (for \"~s\" starting at line ~B)", [End, Start, StartLine]),
Message = io_lib:format("missing terminator: ~ts (for \"~ts\" starting at line ~B)", [End, Start, StartLine]),
{ error, { Line, Message, [] } };
check_terminator({ E, Line }, []) when
@@ -807,29 +817,40 @@ terminator('<<') -> '>>'.
% Keywords check
handle_keyword(Line, Identifier, Atom, [{ '.', _ }|_] = Tokens) ->
[{ Identifier, Line, Atom }|Tokens];
{ ok, [{ Identifier, Line, Atom }|Tokens] };
handle_keyword(Line, Identifier, Atom, Tokens) when
Identifier == identifier; Identifier == do_identifier;
Identifier == bracket_identifier; Identifier == paren_identifier ->
case keyword(Atom) of
true -> [{ Atom, Line }|Tokens];
op -> add_token_with_nl({ Atom, Line }, Tokens);
block -> [{ block_identifier, Line, Atom }|Tokens];
false -> false
true -> { ok, [{ Atom, Line }|Tokens] };
op -> { ok, add_token_with_nl({ Atom, Line }, Tokens) };
block -> { ok, [{ block_identifier, Line, Atom }|Tokens] };
do ->
case do_keyword_valid(Tokens) of
true -> { ok, [{ Atom, Line }|Tokens] };
false -> { error, "do" }
end;
false -> nomatch
end;
handle_keyword(_, _, _, _) -> false.
handle_keyword(_, _, _, _) -> nomatch.
do_keyword_valid([{ Atom, _ }|_]) ->
is_boolean(keyword(Atom));
do_keyword_valid(_) -> true.
% Keywords
keyword('fn') -> true;
keyword('do') -> true;
keyword('end') -> true;
keyword('true') -> true;
keyword('false') -> true;
keyword('nil') -> true;
keyword('not') -> true;
% Special handling for do
keyword('do') -> do;
% Bin operator keywords
keyword('and') -> op;
keyword('or') -> op;
+48 -47
View File
@@ -7,7 +7,7 @@
-import(elixir_scope, [umergev/2, umergec/2]).
-import(elixir_errors, [syntax_error/3, syntax_error/4, parse_error/4,
assert_function_scope/3, assert_module_scope/3, assert_no_guard_scope/3,
assert_no_assign_or_guard_scope/3]).
assert_no_match_or_guard_scope/3]).
-include("elixir.hrl").
-compile({parse_transform, elixir_transform}).
@@ -35,6 +35,9 @@ translate(Forms, S) ->
%% Those macros are "low-level". They are the basic mechanism
%% that makes the language work and cannot be partially applied
%% nor overwritten.
%%
%% =, ^, import, require and alias could be made non-special
%% forms without causing any side effects.
%% Assignment operator
@@ -94,7 +97,7 @@ translate_each({ alias, Meta, [Ref] }, S) ->
translate_each({ alias, Meta, [Ref,[]] }, S);
translate_each({ alias, Meta, [Ref, KV] }, S) ->
assert_no_assign_or_guard_scope(Meta, alias, S),
assert_no_match_or_guard_scope(Meta, alias, S),
validate_opts(Meta, alias, [as], KV, S),
{ TRef, SR } = translate_each(Ref, S),
@@ -140,7 +143,7 @@ translate_each({ require, Meta, [Ref] }, S) ->
translate_each({ require, Meta, [Ref, []] }, S);
translate_each({ require, Meta, [Ref, KV] }, S) ->
assert_no_assign_or_guard_scope(Meta, 'require', S),
assert_no_match_or_guard_scope(Meta, 'require', S),
validate_opts(Meta, require, [as], KV, S),
{ TRef, SR } = translate_each(Ref, S),
@@ -173,7 +176,7 @@ translate_each({ import, Meta, [Selector, Left] }, S) ->
translate_each({ import, Meta, [Selector, Left, []]}, S);
translate_each({ import, Meta, [Left, Right, Opts] }, S) ->
assert_no_assign_or_guard_scope(Meta, 'import', S),
assert_no_match_or_guard_scope(Meta, 'import', S),
{ TSelector, SL } = translate_each(Left, S),
{ TRef, SR } = translate_each(Right, SL),
@@ -207,7 +210,8 @@ translate_each({ '__FILE__', _Meta, Atom }, S) when is_atom(Atom) ->
translate_each(S#elixir_scope.file, S);
translate_each({ '__ENV__', Meta, Atom }, S) when is_atom(Atom) ->
{ elixir_scope:to_erl_env({ ?line(Meta), S }), S };
Env = elixir_scope:to_ex_env({ ?line(Meta), S }),
{ elixir_tree_helpers:abstract_syntax(Env), S };
translate_each({ '__CALLER__', Meta, Atom }, S) when is_atom(Atom) ->
{ { var, ?line(Meta), '__CALLER__' }, S#elixir_scope{caller=true} };
@@ -232,7 +236,7 @@ translate_each({ '__aliases__', Meta, _ } = Alias, S) ->
%% Quoting
translate_each({ Unquote, Meta, _Args }, S) when Unquote == unquote; Unquote == unquote_splicing ->
translate_each({ Unquote, Meta, [_|_] }, S) when Unquote == unquote; Unquote == unquote_splicing ->
syntax_error(Meta, S#elixir_scope.file, "~p called outside quote", [Unquote]);
translate_each({ quote, Meta, [Left, Right] }, S) ->
@@ -355,7 +359,7 @@ translate_each({ 'var!', Meta, [_, _] }, S) ->
%% Functions
translate_each({ fn, Meta, [[{do, { '->', _, Pairs }}]] }, S) ->
assert_no_assign_or_guard_scope(Meta, 'fn', S),
assert_no_match_or_guard_scope(Meta, 'fn', S),
translate_fn(Meta, Pairs, S);
%% Comprehensions
@@ -368,7 +372,7 @@ translate_each({ Kind, Meta, Args }, S) when is_list(Args), (Kind == lc) orelse
translate_each({ super, Meta, Args } = Original, S) ->
case elixir_partials:handle(Original, S) of
error ->
assert_no_assign_or_guard_scope(Meta, super, S),
assert_no_match_or_guard_scope(Meta, super, S),
Module = assert_module_scope(Meta, super, S),
Function = assert_function_scope(Meta, super, S),
elixir_def_overridable:ensure_defined(Meta, Module, Function, S),
@@ -397,20 +401,21 @@ translate_each({ 'super?', Meta, [] }, S) ->
%% Variables
translate_each({ '^', Meta, [ { Name, _, Kind } ] }, S) when is_list(Kind) ->
syntax_error(Meta, S#elixir_scope.file, "cannot use ^ with expression at ^~s, ^ must be used only with variables", [Name]);
translate_each({ '^', Meta, [ { Name, _, Kind } ] }, #elixir_scope{context=assign} = S) when is_atom(Kind) ->
translate_each({ '^', Meta, [ { Name, _, Kind } ] }, #elixir_scope{context=match} = S) when is_atom(Name), is_atom(Kind) ->
case orddict:find({ Name, Kind }, S#elixir_scope.vars) of
{ ok, Value } ->
{ { var, Meta, Value }, S };
error ->
syntax_error(Meta, S#elixir_scope.file, "unbound variable ^~s", [Name])
syntax_error(Meta, S#elixir_scope.file, "unbound variable ^~ts", [Name])
end;
translate_each({ '^', Meta, [ { Name, _, Kind } ] }, S) when is_atom(Kind) ->
translate_each({ '^', Meta, [ { Name, _, Kind } ] }, S) when is_atom(Name), is_atom(Kind) ->
syntax_error(Meta, S#elixir_scope.file,
"cannot access variable ^~s outside of assignment", [Name]);
"cannot use ^~ts outside of match clauses", [Name]);
translate_each({ '^', Meta, [ Expr ] }, S) ->
syntax_error(Meta, S#elixir_scope.file,
"the unary operator ^ can only be used with variables, invalid expression ^~ts", ['Elixir.Macro':to_binary(Expr)]);
translate_each({ Name, Meta, Kind }, S) when is_atom(Name), is_atom(Kind) ->
elixir_scope:translate_var(Meta, Name, Kind, S, fun() ->
@@ -425,28 +430,21 @@ translate_each({ Atom, Meta, Args } = Original, S) when is_atom(Atom) ->
false ->
case elixir_partials:handle(Original, S) of
error ->
case lists:keyfind(import, 1, Meta) of
{ import, Receiver } ->
{ TRes, TS } = translate_each({ { '.', Meta, [Receiver, Atom] }, Meta, Args },
S#elixir_scope{check_requires=false}),
{ TRes, TS#elixir_scope{check_requires=S#elixir_scope.check_requires} };
false ->
Callback = fun() ->
case S#elixir_scope.context of
guard ->
Arity = length(Args),
File = S#elixir_scope.file,
case Arity of
0 -> syntax_error(Meta, File, "unknown variable ~s or cannot invoke local ~s/~B inside guard", [Atom, Atom, Arity]);
_ -> syntax_error(Meta, File, "cannot invoke local ~s/~B inside guard", [Atom, Arity])
end;
_ ->
translate_local(Meta, Atom, Args, S)
end
end,
Callback = fun() ->
case S#elixir_scope.context of
match ->
Arity = length(Args),
File = S#elixir_scope.file,
case Arity of
0 -> syntax_error(Meta, File, "unknown variable ~ts or cannot invoke local ~ts/~B inside guard", [Atom, Atom, Arity]);
_ -> syntax_error(Meta, File, "cannot invoke local ~ts/~B inside guard", [Atom, Arity])
end;
_ ->
translate_local(Meta, Atom, Args, S)
end
end,
elixir_dispatch:dispatch_import(Meta, Atom, Args, S, Callback)
end;
elixir_dispatch:dispatch_import(Meta, Atom, Args, S, Callback);
Else -> Else
end;
Else -> Else
@@ -484,7 +482,13 @@ translate_each({ { '.', _, [Expr] }, Meta, Args } = Original, S) ->
{ TExpr, SE } = translate_each(Expr, S),
case TExpr of
{ atom, _, Atom } ->
elixir_errors:deprecation(Meta, S#elixir_scope.file, "the :~s.() syntax is deprecated, please use ~s() instead", [Atom, Atom]),
case atom_to_list(Atom) of
[H|_] when (H >= $a andalso H =< $z); H == $_ ->
elixir_errors:deprecation(Meta, S#elixir_scope.file, "the :~s.() syntax is deprecated, please use ~s() instead", [Atom, Atom]);
_ ->
elixir_errors:deprecation(Meta, S#elixir_scope.file, "the :~s.() syntax is deprecated", [Atom])
end,
translate_each({ Atom, Meta, Args }, S);
_ ->
case elixir_partials:handle(Original, S) of
@@ -540,13 +544,7 @@ translate_local(Meta, Name, Args, #elixir_scope{local=nil} = S) ->
{ { call, Line, { atom, Line, Name }, TArgs }, NS };
translate_local(Meta, Name, Args, S) ->
Line = ?line(Meta),
{ TArgs, NS } = translate_args(Args, S),
Remote = { remote, Line,
{ atom, Line, S#elixir_scope.local },
{ atom, Line, Name }
},
{ { call, Line, Remote, TArgs }, NS }.
translate_each({ { '.', Meta, [S#elixir_scope.local, Name] }, Meta, Args }, S).
expand_var_context(_Meta, Atom, _Msg, _S) when is_atom(Atom) -> Atom;
expand_var_context(Meta, Alias, Msg, S) ->
@@ -582,9 +580,9 @@ no_alias_expansion(Other) ->
translate_arg(Arg, { Acc, S }) ->
{ TArg, TAcc } = translate_each(Arg, Acc),
{ TArg, { umergec(S, TAcc), umergev(S, TAcc) } }.
{ TArg, { umergec(Acc, TAcc), umergev(S, TAcc) } }.
translate_args(Args, #elixir_scope{context=assign} = S) ->
translate_args(Args, #elixir_scope{context=match} = S) ->
translate(Args, S);
translate_args(Args, S) ->
@@ -660,11 +658,14 @@ translate_comprehension_do(Meta, bc, _Expr, S) ->
translate_comprehension_do(_Meta, _Kind, Expr, S) ->
translate_each(Expr, S).
translate_comprehension_clause(_Meta, {inbits, Meta, [Left, Right]}, S) ->
translate_comprehension_clause(_Meta, {inbits, Meta, [{ '<<>>', _, _} = Left, Right]}, S) ->
{ TRight, SR } = translate_each(Right, S),
{ TLeft, SL } = elixir_clauses:assigns(fun elixir_translator:translate_each/2, Left, SR),
{ { b_generate, ?line(Meta), TLeft, TRight }, SL };
translate_comprehension_clause(_Meta, {inbits, Meta, [_Left, _Right]}, S) ->
syntax_error(Meta, S#elixir_scope.file, "a bit comprehension expects a bit string << >> to be used in inbits generators");
translate_comprehension_clause(_Meta, {inlist, Meta, [Left, Right]}, S) ->
{ TRight, SR } = translate_each(Right, S),
{ TLeft, SL } = elixir_clauses:assigns(fun elixir_translator:translate_each/2, Left, SR),
+3 -3
View File
@@ -57,7 +57,7 @@ each_clause(Meta, {rescue,_,_}, S) ->
elixir_errors:syntax_error(Meta, S#elixir_scope.file, "too many arguments given for rescue");
each_clause(Meta, {Key,_,_}, S) ->
elixir_errors:syntax_error(Meta, S#elixir_scope.file, "invalid key ~s in try", [Key]).
elixir_errors:syntax_error(Meta, S#elixir_scope.file, "invalid key ~ts in try", [Key]).
%% Helpers
@@ -96,7 +96,7 @@ normalize_rescue(_, { '=', Meta, [{ Name, _, Atom } = Var, { '__aliases__', _, _
false;
normalize_rescue(Meta, Condition, S) ->
case elixir_translator:translate_each(Condition, S#elixir_scope{context=assign}) of
case elixir_translator:translate_each(Condition, S#elixir_scope{context=match}) of
{ { atom, _, Atom }, _ } ->
normalize_rescue(Meta, { in, Meta, [{ '_', Meta, nil }, [Atom]] }, S);
_ ->
@@ -265,7 +265,7 @@ validate_rescue_access(_, _, _) -> [].
format_error({ rescue_no_match, Var, Alias }) ->
VarBinary = 'Elixir.Macro':to_binary(Var),
AliasBinary = 'Elixir.Macro':to_binary(Alias),
Message = "rescue clause (~s = ~s) can never match, maybe you meant to write: ~s in [~s] ?",
Message = "rescue clause (~ts = ~ts) can never match, maybe you meant to write: ~ts in [~ts] ?",
io_lib:format(Message, [AliasBinary, VarBinary, VarBinary, AliasBinary]).
is_var({ Name, _, Atom }) when is_atom(Name), is_atom(Atom) -> true;
+28
View File
@@ -0,0 +1,28 @@
ExUnit.start []
defmodule KernelTest do
use ExUnit.Case, async: true
doctest Kernel
doctest Kernel.SpecialForms
doctest Access.List
doctest Binary.Chars.List
doctest Binary.Inspect.Atom
doctest Binary.Inspect.BitString
doctest Binary.Inspect.List
doctest Binary.Inspect.Tuple
doctest Binary.Inspect.Number
doctest Binary.Inspect.Regex
doctest Bitwise
doctest Code
doctest Dict
doctest Enum
doctest IO.ANSI
doctest Regex
doctest String
doctest OptionParser
doctest Path
doctest Module
doctest Macro
doctest List
doctest Keyword
end
@@ -62,6 +62,8 @@ defmodule Binary.Inspect.BitStringTest do
assert inspect("f\no") == "\"f\\no\""
assert inspect("f\\o") == "\"f\\\\o\""
assert inspect("f\ao") == "\"f\\ao\""
assert inspect(%B{#{#a}) == "\"\\\#{#a\""
assert inspect(%B{ #{#a}) == "\" \\\#{#a\""
end
test :utf8 do
+8 -2
View File
@@ -5,7 +5,9 @@ defmodule Kernel.ExceptionTest do
test :is_exception do
assert is_exception(RuntimeError.new)
refute is_exception({ :foo, :bar })
refute is_exception(empty_tuple)
refute is_exception(a_tuple)
refute is_exception(a_list)
end
test :format_entry_with_no_file_or_line do
@@ -74,4 +76,8 @@ defmodule Kernel.ExceptionTest do
test :erlang_error_message do
assert ErlangError.new(original: :sample).message == "erlang error: :sample"
end
end
defp empty_tuple, do: {}
defp a_tuple, do: { :foo, :bar, :baz }
defp a_list, do: [ :foo, :bar, :baz ]
end
+18
View File
@@ -397,6 +397,24 @@ defmodule FileTest do
end
end
test :ls do
{ :ok, value } = File.ls(fixture_path)
assert List.member?(value, "code_sample.exs")
assert List.member?(value, "file.txt")
{ :error, :enoent } = File.ls(fixture_path("non-existent-subdirectory"))
end
test :ls! do
value = File.ls!(fixture_path)
assert List.member?(value, "code_sample.exs")
assert List.member?(value, "file.txt")
assert_raise File.Error, fn ->
File.ls!(fixture_path("non-existent-subdirectory"))
end
end
defmodule OpenReadWrite do
use ExUnit.Case
@@ -0,0 +1,36 @@
Code.require_file "../../test_helper.exs", __FILE__
defmodule GenEvent.BehaviourTest do
use ExUnit.Case, async: true
defmodule MyEventHandler do
use GenEvent.Behaviour
# Callbacks
def init(_) do
{ :ok, [] }
end
def handle_event({:notification, x}, notifications) do
{ :ok, [x|notifications] }
end
def handle_call(:notifications, notifications) do
{:ok, Enum.reverse(notifications), []}
end
end
test :using do
{ :ok, pid } = :gen_event.start_link
:gen_event.add_handler(pid, MyEventHandler, [])
:gen_event.notify(pid, {:notification, 1})
:gen_event.notify(pid, {:notification, 2})
assert :gen_event.call(pid, MyEventHandler, :notifications) == [1,2]
assert :gen_event.call(pid, MyEventHandler, :notifications) == []
end
end
+2 -3
View File
@@ -8,7 +8,6 @@ defmodule IO.ANSITest do
"Hello, #{IO.ANSI.red}world!#{IO.ANSI.reset}"
assert IO.ANSI.escape("Hello, %{ red }world!", true) ==
"Hello, #{IO.ANSI.red}world!#{IO.ANSI.reset}"
end
test :escape_multiple do
@@ -26,8 +25,8 @@ defmodule IO.ANSITest do
end
test :fragment do
assert IO.ANSI.escape("%{red}") == "#{IO.ANSI.red}#{IO.ANSI.reset}"
assert IO.ANSI.escape_fragment("") == ""
assert IO.ANSI.escape("%{red}", true) == "#{IO.ANSI.red}#{IO.ANSI.reset}"
assert IO.ANSI.escape_fragment("", true) == ""
end
test :noop do
+23 -19
View File
@@ -26,6 +26,14 @@ defmodule Kernel.AliasTest do
alias List, as: Nested
assert Nested.flatten([[13]]) == [13]
end
defmodule Elixir do
def sample, do: 1
end
test :nested_elixir_alias do
assert Kernel.AliasTest.Elixir.sample == 1
end
end
defmodule Kernel.AliasNestingGenerator do
@@ -40,6 +48,16 @@ defmodule Kernel.AliasNestingGenerator do
end
end
end
defmacro record do
quote do
defexception Parent, message: nil
defmodule Parent.Child do
def b, do: Parent.new(message: "ok")
end
end
end
end
defmodule Kernel.AliasNestingTest do
@@ -54,28 +72,14 @@ defmodule Kernel.AliasNestingTest do
end
end
defmodule Kernel.FullAliasNestingGenerator do
defmacro create do
quote do
defmodule Parent do
def a, do: :a
end
defmodule Parent.Child do
def b, do: Parent.a
end
end
end
end
defmodule Kernel.FullAliasNestingTest do
defmodule Kernel.AliasMacroNestingTest do
use ExUnit.Case, async: true
require Kernel.FullAliasNestingGenerator
Kernel.FullAliasNestingGenerator.create
require Kernel.AliasNestingGenerator
Kernel.AliasNestingGenerator.record
test :aliases_nesting do
assert Parent.a == :a
assert Parent.Child.b == :a
assert is_record(Parent.new, Parent)
assert Parent.Child.b.message == "ok"
end
end
+5 -5
View File
@@ -96,11 +96,11 @@ defmodule Kernel.CLI.ParallelCompilerTest do
assert File.regular?(tmp_path "Elixir-Bar.beam")
end
test :deadlock_failure do
test :possible_deadlock do
output = elixirc('#{fixture_path("parallel_deadlock")} -o #{tmp_path}')
foo = '== Compilation error on file #{fixture_path "parallel_deadlock/foo.ex"} (undefined module Bar) =='
bar = '== Compilation error on file #{fixture_path "parallel_deadlock/bar.ex"} (undefined module Foo) =='
assert :string.str(output, foo) > 0 or :string.str(output, bar) > 0,
"Expected #{inspect output} to contain #{inspect foo} or #{inspect bar}"
foo = '* #{fixture_path "parallel_deadlock/foo.ex"} is missing module Bar'
bar = '* #{fixture_path "parallel_deadlock/bar.ex"} is missing module Foo'
assert :string.str(output, foo) > 0, "expected foo.ex to miss module Bar"
assert :string.str(output, bar) > 0, "expected bar.ex to miss module Foo"
end
end
+17 -12
View File
@@ -55,12 +55,12 @@ defmodule Kernel.ErrorsTest do
assert "nofile:1: unbound variable ^x" == format_rescue('^x = 1')
end
test :unbound_not_assignment do
assert "nofile:1: cannot access variable ^x outside of assignment" == format_rescue('^x')
test :unbound_not_match do
assert "nofile:1: cannot use ^x outside of match clauses" == format_rescue('^x')
end
test :unbound_expr do
assert "nofile:1: cannot use ^ with expression at ^x, ^ must be used only with variables" == format_rescue('^x(1)')
assert "nofile:1: the unary operator ^ can only be used with variables, invalid expression ^x(1)" == format_rescue('^x(1)')
end
test :name_for_defmodule do
@@ -108,9 +108,9 @@ defmodule Kernel.ErrorsTest do
format_rescue 'defmodule Foo do def Bar do :baz end\nend\n'
end
test :erlang_function_conflict do
assert "nofile:1: function exit/1 already imported from Kernel" ==
format_rescue 'defmodule Foo do import Kernel.ErrorsTest.UnproperMacro, only: [exit: 1]\nend'
test :function_import_conflict do
assert "nofile:2: function exit/1 imported from both erlang and Kernel, call is ambiguous" ==
format_rescue 'defmodule Foo do import :erlang\n def foo, do: exit(:test)\nend'
end
test :import_invalid_macro do
@@ -119,7 +119,7 @@ defmodule Kernel.ErrorsTest do
end
test :unrequired_macro do
assert "nofile:2: tried to invoke macro Kernel.ErrorsTest.UnproperMacro.unproper/1 but module was not required. Required: Kernel, Kernel.Typespec" ==
assert "nofile:2: tried to invoke macro Kernel.ErrorsTest.UnproperMacro.unproper/1 but module was not required. Required: Kernel, Kernel.Typespec, Record" ==
format_rescue 'defmodule Foo do\nKernel.ErrorsTest.UnproperMacro.unproper([])\nend'
end
@@ -200,7 +200,7 @@ defmodule Kernel.ErrorsTest do
end
test :invalid_access_protocol_not_alias do
assert "invalid usage of access protocol in signature" ==
assert "the access protocol cannot be used inside match clauses (for example, on the left hand side of a match or in function signatures)" ==
format_rescue 'defmodule Foo do\ndef sample(config[integer: 0]), do: true\nend'
end
@@ -224,7 +224,7 @@ defmodule Kernel.ErrorsTest do
format_rescue 'defmodule Foo do\ndef sample(Kernel.ErrorsTest.Config[foo: :bar]), do: true\nend'
end
test :invalid_access_protocol_invalid_keywords_outside_assignment do
test :invalid_access_protocol_invalid_keywords_outside_match do
assert "record Kernel.ErrorsTest.Config does not have the keys: [:foo]" ==
format_rescue 'Kernel.ErrorsTest.Config[foo: :bar]'
end
@@ -234,9 +234,14 @@ defmodule Kernel.ErrorsTest do
format_rescue 'try do\n1\nrescue\nUndefinedFunctionError[arity: 1] -> false\nend'
end
test :invalid_bc do
test :invalid_bc_return do
assert "nofile:1: a bit comprehension expects a bit string << >> to be returned" ==
format_rescue 'bc x in [1,2,3], do: x'
format_rescue 'bc x inlist [1,2,3], do: x'
end
test :invalid_bc_inbits_gen do
assert "nofile:1: a bit comprehension expects a bit string << >> to be used in inbits generators" ==
format_rescue 'bc x inbits "123", do: <<x>>'
end
test :unbound_cond do
@@ -303,4 +308,4 @@ defmodule Kernel.ErrorsTest do
result || raise(ExUnit.AssertionError, message: "Expected function given to format_rescue to fail")
end
end
end
+13 -1
View File
@@ -109,4 +109,16 @@ defmodule Kernel.ImportMacrosTest do
assert bor(0, 1) == 1
assert bnot(0) == -1
end
end
end
defmodule Kernel.AmbiguousImportTest do
use ExUnit.Case, async: true
test :import_ambiguous do
# Simply make sure that we can indeed import functions with
# the same name and arity from different modules without the
# import itself causing any errors.
import List
import String
end
end
@@ -62,6 +62,36 @@ defmodule Kernel.QuoteTest do
end
end
test :nested_quote do
assert { :quote, _, [[do: { :unquote, _, _ }]] } = quote(do: quote(do: unquote(x)))
end
defmacrop nested_quote_in_macro do
x = 1
quote do
x = unquote(x)
quote do
unquote(x)
end
end
end
test :nested_quote_in_macro do
assert nested_quote_in_macro == 1
end
Enum.each [foo: 1, bar: 2, baz: 3], fn { k, v } ->
def unquote(k)(arg) do
unquote(v) + arg
end
end
test :dynamic_definition_with_unquote do
assert foo(1) == 2
assert bar(2) == 4
assert baz(3) == 6
end
test :splice_on_root do
contents = [1, 2, 3]
assert quote(do: unquote_splicing(contents)) == quote do: (1; 2; 3)
@@ -206,9 +236,30 @@ defmodule Kernel.QuoteTest.ImportsHygieneTest do
end
end
defmacrop get_bin_size_with_partial do
quote do
size(&1).("hello")
end
end
defmacrop get_bin_size_with_function do
quote do
function(size/1).("hello")
end
end
defmacrop get_bin_size_with_kernel_function do
quote do
Kernel.function(size/1).("hello")
end
end
test :expand_imports do
import Kernel, except: [size: 1]
assert get_bin_size == 5
assert get_bin_size_with_partial == 5
assert get_bin_size_with_function == 5
assert get_bin_size_with_kernel_function == 5
end
defmacrop get_dict_size do
@@ -224,4 +275,23 @@ defmodule Kernel.QuoteTest.ImportsHygieneTest do
import Dict, only: [size: 1]
assert get_dict_size == 2
end
defmacrop with_size do
quote do
import Kernel, except: [size: 1]
import Dict, only: [size: 1]
size([a: 1, b: 2])
end
end
defmacrop with_nested_size do
quote do
with_size
end
end
test :explicitly_overriden_imports do
assert with_size == 2
assert with_nested_size == 2
end
end
@@ -24,7 +24,7 @@ defmodule Kernel.RecordRewriterTest do
end
defp optimize_clause(clause) do
{ clause, dict, res } = Kernel.RecordRewriter.optimize_clause(clause)
{ clause, dict, res } = Kernel.RecordRewriter.optimize_clause(__MODULE__, clause)
dict = Enum.map dict, fn { k, { v, _ } } -> { k, v }; other -> other; end
{ clause, dict, res }
end
@@ -134,11 +134,6 @@ defmodule Kernel.RecordRewriterTest do
assert optimize_clause(clause) == { clause, [], nil }
end
test "inside bit comprehension" do
clause = clause(fn -> bc x = Macro.Env[] inbits sample, do: <<x>> end)
assert optimize_clause(clause) == { clause, [], nil }
end
test "inside function retrieval" do
clause = clause(fn -> function(x = Macro.Env[], y, z) end)
assert optimize_clause(clause) == { clause, [x: Macro.Env], nil }
+36 -1
View File
@@ -48,4 +48,39 @@ defmodule Kernel.SigilsTest do
assert %C(f#{o}o) == 'f\#{o}o'
assert %C(f\no) == 'f\\no'
end
end
test :__w__ do
assert %w(foo bar baz) == ["foo", "bar", "baz"]
assert %w(foo #{:bar} baz) == ["foo", "bar", "baz"]
assert %w(
foo
bar
baz
) == ["foo", "bar", "baz"]
assert %w(foo bar baz)b == ["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
assert ArgumentError[] = catch_error(Code.eval_quoted(bad_modifier))
end
test :__W__ do
assert %W(foo #{bar} baz) == ["foo", "\#{bar}", "baz"]
assert %W(
foo
bar
baz
) == ["foo", "bar", "baz"]
assert %W(foo bar baz)b == ["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
assert ArgumentError[] = catch_error(Code.eval_quoted(bad_modifier))
end
end
+23 -17
View File
@@ -93,6 +93,20 @@ defmodule KernelTest do
assert float_to_binary(7.0) == "7.00000000000000000000e+00"
end
case :proplists.get_value(:float_to_binary,
:proplists.get_value(:exports, :erlang.module_info, [])) do
2 ->
# We can only test this where this functionality is available
test :float_to_binary_with_options do
assert float_to_binary(7.1, [decimals: 2]) == "7.10"
assert float_to_binary(7.1, [scientific: 2]) == "7.10e+00"
assert float_to_binary(7.1, [decimals: 2, compact: true]) == "7.1"
assert float_to_binary(7.1, [scientific: 2, compact: true]) == "7.10e+00"
assert float_to_binary(7.1, [decimals: 2, compact: false]) == "7.10"
end
_ ->
:ok
end
test :atom_to_binary_defaults_to_utf8 do
expected = atom_to_binary :some_binary, :utf8
actual = atom_to_binary :some_binary
@@ -127,32 +141,19 @@ defmodule KernelTest do
defmodule Function do
use ExUnit.Case, async: true
test :retrieve_remote_function do
assert is_function(function(:erlang, :atom_to_list, 1))
assert :erlang.fun_info(function(:erlang, :atom_to_list, 1), :arity) == {:arity, 1}
assert function(:erlang, :atom_to_list, 1).(:a) == 'a'
end
test :remote_syntax_function do
assert function(:erlang, :atom_to_list, 1).(:a) == 'a'
assert function(:erlang.atom_to_list/1) ==
function(:erlang, :atom_to_list, 1)
assert function(Enum.map/2) == function(Enum, :map, 2)
end
test :retrieve_local_function do
assert is_function(function(:atl, 1))
assert :erlang.fun_info(function(:atl, 1), :arity) == {:arity, 1}
assert function(:atl, 1).(:a) == 'a'
end
test :local_syntax_function do
assert function(atl/1).(:a) == 'a'
end
test :retrieve_imported_function do
assert is_function(function(:atom_to_list, 1))
assert :erlang.fun_info(function(:atom_to_list, 1), :arity) == {:arity, 1}
assert function(:atom_to_list, 1).(:a) == 'a'
assert function(atom_to_list/1).(:a) == 'a'
end
test :retrieve_dynamic_function do
@@ -160,8 +161,6 @@ defmodule KernelTest do
b = :atom_to_list
c = 1
assert is_function(function(a, b, c))
assert :erlang.fun_info(function(a, b, c), :arity) == {:arity, 1}
assert function(a, b, c).(:a) == 'a'
end
@@ -186,6 +185,9 @@ defmodule KernelTest do
defdelegate my_flatten(list), to: List, as: :flatten
defdelegate [map(callback, list)], to: :lists, append_first: true
dynamic = :dynamic_flatten
defdelegate unquote(dynamic)(list), to: List, as: :flatten
test :defdelegate_with_function do
assert my_flatten([[1]]) == [1]
end
@@ -193,6 +195,10 @@ defmodule KernelTest do
test :defdelegate_with_appended_handle do
assert map([1], fn(x) -> x + 1 end) == [2]
end
test :dynamic_defdelegate do
assert dynamic_flatten([[1]]) == [1]
end
end
defmodule PipelineOp do
+53
View File
@@ -36,6 +36,59 @@ defmodule MacroTest do
assert [1,{:{}, [], [:a,:b,:c]},3] == Macro.escape([1, { :a, :b, :c },3])
end
test :escape_with_unquote do
contents = quote unquote: false, do: unquote(1)
assert Macro.escape_quoted(contents) == 1
contents = quote unquote: false, do: unquote(x)
assert Macro.escape_quoted(contents) == { :x, [], MacroTest }
end
defp eval_escaped(contents) do
{ eval, [] } = Code.eval_quoted(Macro.escape_quoted(contents))
eval
end
test :escape_with_remote_unquote do
contents = quote unquote: false, do: Kernel.unquote(:is_atom)(:ok)
assert eval_escaped(contents) == quote(do: Kernel.is_atom(:ok))
end
test :escape_with_nested_unquote do
contents = quote do
quote do: unquote(x)
end
assert eval_escaped(contents) == quote do: (quote do: unquote(x))
end
test :escape_with_alias_or_no_args_remote_unquote do
contents = quote unquote: false, do: Kernel.unquote(:self)
assert eval_escaped(contents) == quote(do: Kernel.self())
contents = quote unquote: false, do: x.unquote(Foo)
assert eval_escaped(contents) == quote(do: x.unquote(Foo))
end
test :escape_with_splicing do
contents = quote unquote: false, do: [1,2,3,4,5]
assert Macro.escape_quoted(contents) == [1,2,3,4,5]
contents = quote unquote: false, do: [1,2,unquote_splicing([3,4,5])]
assert eval_escaped(contents) == [1,2,3,4,5]
contents = quote unquote: false, do: [unquote_splicing([1,2,3]), 4, 5]
assert eval_escaped(contents) == [1,2,3,4,5]
contents = quote unquote: false, do: [unquote_splicing([1,2,3]), unquote_splicing([4, 5])]
assert eval_escaped(contents) == [1,2,3,4,5]
contents = quote unquote: false, do: [1, unquote_splicing([2]), 3, unquote_splicing([4]), 5]
assert eval_escaped(contents) == [1,2,3,4,5]
contents = quote unquote: false, do: [1, unquote_splicing([2]), 3, unquote_splicing([4])|[5]]
assert eval_escaped(contents) == [1,2,3,4,5]
end
## Expand aliases
test :expand_with_raw_atom do
+1
View File
@@ -178,6 +178,7 @@ defmodule ModuleTest do
end
test :to_binary do
assert Module.to_binary(Elixir) == "Elixir"
assert Module.to_binary(Hello.World) == "Hello.World"
end
+2 -2
View File
@@ -98,10 +98,10 @@ defmodule PathTest do
end
test :expand_path_with_user_home do
assert is_binary Path.expand("~")
assert System.user_home! == Path.expand("~")
assert is_binary Path.expand("~/foo")
assert is_list Path.expand('~')
assert (System.user_home! |> :unicode.characters_to_list) == Path.expand('~')
assert is_list Path.expand('~/foo')
end
+35 -3
View File
@@ -3,6 +3,16 @@ Code.require_file "../test_helper.exs", __FILE__
defrecord RecordTest.FileInfo,
Record.extract(:file_info, from_lib: "kernel/include/file.hrl")
defmodule RecordTest.FileInfo.Helper do
Record.import RecordTest.FileInfo, as: :file_info
def new do
file_info
end
def size(file_info(size: size)), do: size
end
defrecord RecordTest.SomeRecord, a: 0, b: 1
defrecord RecordTest.WithNoField, []
@@ -33,6 +43,12 @@ end
defmodule RecordTest.Macros do
defrecordp :_user, name: "José", age: 25
defrecord Nested do
def nested_record_alias?(Nested[]) do
true
end
end
def new() do
_user()
end
@@ -103,6 +119,11 @@ defmodule RecordTest do
test :is_record do
assert is_record(RecordTest.FileInfo.new, RecordTest.FileInfo)
assert is_record(RecordTest.WithNoField.new)
refute is_record(empty_tuple)
refute is_record(a_list)
refute is_record(empty_tuple, RecordTest.FileInfo)
refute is_record(a_tuple, RecordTest.FileInfo)
refute is_record(a_list, RecordTest.FileInfo)
refute is_record(RecordTest.FileInfo.new, List)
end
@@ -166,12 +187,23 @@ defmodule RecordTest do
refute { :update_b, 2 } inlist RecordTest.DynamicName.__record__(:optimizable)
end
test :result do
assert { :module, _, _, "result"} = (defrecord WithResult, foo: :bar do
"result"
end)
end
test :import do
assert RecordTest.FileInfo.Helper.new == RecordTest.FileInfo.new
assert RecordTest.FileInfo.Helper.size(RecordTest.FileInfo.new(size: 100)) == 100
end
defp file_info do
{ :ok, file_info } = :file.read_file_info(__FILE__)
file_info
end
defp a_list do
[:a, :b, :c]
end
defp empty_tuple, do: {}
defp a_tuple, do: { :foo, :bar, :baz }
defp a_list, do: [ :foo, :bar, :baz ]
end
+7 -1
View File
@@ -17,8 +17,14 @@ defmodule StringTest do
test :split do
assert String.split("foo bar") == ["foo", "bar"]
assert String.split("a,b,c", ",") == ["a", "b", "c"]
assert String.split(" foo bar") == ["foo", "bar"]
assert String.split("foo bar ") == ["foo", "bar"]
assert String.split(" foo bar ") == ["foo", "bar"]
assert String.split("foo\t\n\v\f\r\sbar\n") == ["foo", "bar"]
assert String.split("foo" <> <<31>> <> "bar") == ["foo", "bar"]
assert String.split("foo" <> <<194,133>> <> "bar") == ["foo", "bar"]
assert String.split("a,b,c", ",") == ["a", "b", "c"]
assert String.split("a,b", ".") == ["a,b"]
assert String.split("1,2 3,4", [" ", ","]) == ["1", "2", "3", "4"]
+9 -1
View File
@@ -8,7 +8,15 @@ defmodule TupleTest do
end
test :setelem do
assert setelem({ :a, :b, :c }, 1, :d) == { :a, :d, :c }
assert set_elem({ :a, :b, :c }, 1, :d) == { :a, :d, :c }
end
test :insert_elem do
assert insert_elem({ :bar, :baz }, 0, :foo) == { :foo, :bar, :baz }
end
test :delete_elem do
assert delete_elem({ :foo, :bar, :baz }, 0) == { :bar, :baz }
end
test :optional_comma do
@@ -234,6 +234,9 @@ andand_test() ->
end,
test_helper:run_and_remove(F, ['Elixir.Bar']).
andand_with_literal_test() ->
{[nil, nil, nil], _} = eval("[nil && 2, nil && 3, nil && 4]").
oror_test() ->
F = fun() ->
eval("defmodule Bar do\ndef foo, do: true\ndef bar, do: false\n def baz(x), do: x == 1\nend"),
+23 -1
View File
@@ -88,6 +88,28 @@ function_calls_with_multiple_args_with_line_breaks_test() ->
function_calls_with_parenthesis_test() ->
{3, [{a,_},{b,1}]} = eval("a = (fn x -> x + 2 end).(b = 1)").
function_call_with_a_single_space_test() ->
{3, _} = eval("a = fn a, b -> a + b end; a. (1,2)"),
{3, _} = eval("a = fn a, b -> a + b end; a .(1,2)").
function_call_with_spaces_test() ->
{3, _} = eval("a = fn a, b -> a + b end; a . (1,2)").
function_call_without_assigning_with_spaces_test() ->
{3, _} = eval("(fn a, b -> a + b end) . (1,2)").
function_call_with_assignment_and_spaces_test() ->
{3, [{a,_},{c,3}]} = eval("a = fn x -> x + 2 end; c = a . (1)").
function_call_with_multiple_spaces_test() ->
{3, _} = eval("a = fn a, b -> a + b end; a . (1,2)").
function_call_with_multiline_test() ->
{3, _} = eval("a = fn a, b -> a + b end; a . \n (1,2)").
function_call_with_tabs_test() ->
{3, _} = eval("a = fn a, b -> a + b end; a .\n\t(1,2)").
%% Partial application
require_partial_application_test() ->
@@ -96,4 +118,4 @@ require_partial_application_test() ->
import_partial_application_test() ->
{ Fun, _ } = eval("is_atom(&1)"),
Fun = fun erlang:is_atom/1.
Fun = fun erlang:is_atom/1.
+2 -1
View File
@@ -98,7 +98,8 @@ bin_string_with_escaped_interpolation_test() ->
{<<"f#{'o}o">>, _} = eval("\"f\\#{'o}o\"").
invalid_string_interpolation_test() ->
?assertError({'Elixir.SyntaxError', _, _, _, _}, eval("\"f#{{}o\"")).
?assertError({'Elixir.TokenMissingError', _, _, _, _}, eval("\"f#{some\"")),
?assertError({'Elixir.TokenMissingError', _, _, _, _}, eval("\"f#{1+")).
unterminated_string_interpolation_test() ->
?assertError({'Elixir.TokenMissingError', _, _, _, _}, eval("\"foo")).
+9 -42
View File
@@ -1,4 +1,12 @@
defmodule ExUnit do
defrecord Test, [:name, :case, :failure] do
@moduledoc """
A record that keeps information about the test.
It is received by formatters and also accessible
in the metadata under the key `:test`.
"""
end
@moduledoc """
Basic unit test structure for Elixir.
@@ -43,30 +51,13 @@ defmodule ExUnit do
The `ExUnit.Assertions` module contains a set of macros to easily
generate assertions with appropriate error messages.
## User config
When started, ExUnit automatically reads a user configuration
from the following locations, in this order:
* $EXUNIT_CONFIG environment variable
* $HOME/.ex_unit.exs
If none found, no user config will be read.
User config is an elixir file which should return a keyword list
with ex_unit options. Please note that explicit options passed
to start/1 or configure/1 will take precedence over user options.
# User config example (~/.ex_unit.exs)
[formatter: ExUnit.Formatter.ANSI]
"""
use Application.Behaviour
@doc false
def start(_type, []) do
ExUnit.Sup.start_link(user_options)
ExUnit.Sup.start_link([])
end
@doc """
@@ -94,23 +85,6 @@ defmodule ExUnit do
end
end
@doc """
Returns the configured user options.
"""
def user_options(user_config // nil) do
user_config = user_config ||
System.get_env("EXUNIT_CONFIG") ||
Path.join(System.get_env("HOME"), ".ex_unit.exs")
case File.read(user_config) do
{ :ok, contents } ->
{ config, _ } = Code.eval(contents, [], file: user_config)
config
_ ->
[]
end
end
@doc """
Configures ExUnit.
@@ -129,13 +103,6 @@ defmodule ExUnit do
ExUnit.Server.merge_options(options)
end
@doc false
def after_spawn(callback) do
IO.puts "ExUnit.after_spawn is deprecated, please use setup_all instead"
Exception.print_stacktrace
ExUnit.Server.add_after_spawn(callback)
end
@doc """
API used to run the tests. It is invoked automatically
if ExUnit is started via `ExUnit.start`.
+55 -32
View File
@@ -1,10 +1,18 @@
defexception ExUnit.AssertionError, message: "assertion failed"
defexception ExUnit.ExpectationError, expected: nil, actual: nil, reason: "", negation: false, prelude: "Expected" do
defexception ExUnit.ExpectationError, expected: nil, actual: nil, reason: "", negation: false, prelude: "Expected", description: nil do
def message(exception) do
"#{exception.prelude} #{inspect exception.expected} to " <>
if(exception.negation, do: "not ", else: "") <>
"be #{exception.reason} #{inspect exception.actual}"
if desc = exception.description do
"#{exception.prelude} #{desc} #{exception.full_reason} " <>
"#{exception.expected}. Insted got #{exception.actual}"
else
"#{exception.prelude} #{exception.expected} " <>
"#{exception.full_reason} #{exception.actual}"
end
end
def full_reason(exception) do
"to" <> if(exception.negation, do: " not ", else: " ") <> exception.reason
end
end
@@ -73,84 +81,96 @@ defmodule ExUnit.Assertions do
## START HELPERS
defmacrop negation?(op) do
quote do: (var!(op) == :! or var!(op) == :not)
end
defp translate_assertion({ :=, _, [_, _] } = expr, _else) do
expr
defp translate_assertion({ :=, _, [left, right] }, _else) do
quote do
right = unquote(right)
case right do
unquote(left) ->
right
_ ->
raise ExUnit.ExpectationError,
expected: inspect(right),
actual: unquote(Macro.to_binary(left)),
reason: "match pattern (=)"
end
end
end
defp translate_assertion({ :==, _, [left, right] }, _else) do
assert_operator :==, left, right, "equal to (==)"
assert_operator :==, left, right, "be equal to (==)"
end
defp translate_assertion({ :<, _, [left, right] }, _else) do
assert_operator :<, left, right, "less than"
assert_operator :<, left, right, "be less than"
end
defp translate_assertion({ :>, _, [left, right] }, _else) do
assert_operator :>, left, right, "more than"
assert_operator :>, left, right, "be more than"
end
defp translate_assertion({ :<=, _, [left, right] }, _else) do
assert_operator :<=, left, right, "less than or equal to"
assert_operator :<=, left, right, "be less than or equal to"
end
defp translate_assertion({ :>=, _, [left, right] }, _else) do
assert_operator :>=, left, right, "more than or equal to"
assert_operator :>=, left, right, "be more than or equal to"
end
defp translate_assertion({ :===, _, [left, right] }, _else) do
assert_operator :===, left, right, "equal to (===)"
assert_operator :===, left, right, "be equal to (===)"
end
defp translate_assertion({ :!==, _, [left, right] }, _else) do
assert_operator :!==, left, right, "not equal to (!==)"
assert_operator :!==, left, right, "be not equal to (!==)"
end
defp translate_assertion({ :!=, _, [left, right] }, _else) do
assert_operator :!=, left, right, "not equal to (!=)"
assert_operator :!=, left, right, "be not equal to (!=)"
end
defp translate_assertion({ :=~, _, [left, right] }, _else) do
assert_operator :=~, left, right, "a match (=~) with"
assert_operator :=~, left, right, "match (=~)"
end
defp translate_assertion({ :inlist, _, [left, right] }, _else) do
quote do
left = unquote(left)
right = unquote(right)
assert List.member?(right, left), left, right, reason: "in"
assert List.member?(right, left), left, right, reason: "be in"
end
end
## Negative versions
defp translate_assertion({ op, _, [{ :=, _, [expected, received] }] }, _else) when negation?(op) do
defp translate_assertion({ :!, _, [{ :=, _, [left, right] }] }, _else) do
quote do
try do
unquote(expected) = x = unquote(received)
flunk "Unexpected right side #{inspect x} match"
rescue
_ in [MatchError] -> true
right = unquote(right)
case right do
unquote(left) ->
raise ExUnit.ExpectationError,
expected: inspect(right),
actual: unquote(Macro.to_binary(left)),
reason: "match pattern (=)",
negation: true
_ ->
nil
end
end
end
defp translate_assertion({ op, _, [{ :=~, _, [left, right] }] }, _else) when negation?(op) do
defp translate_assertion({ :!, _, [{ :=~, _, [left, right] }] }, _else) do
quote do
left = unquote(left)
right = unquote(right)
assert !(left =~ right), left, right, reason: "a match (=~) with", negation: true
assert !(left =~ right), left, right, reason: "match (=~)", negation: true
end
end
defp translate_assertion({ op, _, [{ :inlist, _, [left, right] }] }, _else) when negation?(op) do
defp translate_assertion({ negation, _, [{ :inlist, _, [left, right] }] }, _else) when negation in [:!, :not] do
quote do
left = unquote(left)
right = unquote(right)
assert !List.member?(right, left), left, right, reason: "in", negation: true
assert !List.member?(right, left), left, right, reason: "be in", negation: true
end
end
@@ -195,7 +215,10 @@ defmodule ExUnit.Assertions do
"""
def assert(value, expected, actual, opts) do
unless value, do: raise(ExUnit.ExpectationError, Keyword.merge([expected: expected, actual: actual], opts))
unless value do
raise ExUnit.ExpectationError,
Keyword.merge([expected: inspect(expected), actual: inspect(actual)], opts)
end
true
end
@@ -215,7 +238,7 @@ defmodule ExUnit.Assertions do
You can also match against specific patterns:
assert_received { :hello, _ }
assert_receive { :hello, _ }
"""
defmacro assert_receive(expected, timeout // 100, message // nil) do
+8 -4
View File
@@ -87,7 +87,7 @@ defmodule ExUnit.Callbacks do
defmacro setup(var // quote(do: _), block) do
quote do
name = :"__exunit_setup_#{length(@exunit_setup)}"
defp name, [unquote(Macro.escape var)], [], unquote(Macro.escape block)
defp name, [unquote(escape var)], [], unquote(escape block)
@exunit_setup [name|@exunit_setup]
end
end
@@ -95,7 +95,7 @@ defmodule ExUnit.Callbacks do
defmacro teardown(var // quote(do: _), block) do
quote do
name = :"__exunit_teardown_#{length(@exunit_teardown)}"
defp name, [unquote(Macro.escape var)], [], unquote(Macro.escape block)
defp name, [unquote(escape var)], [], unquote(escape block)
@exunit_teardown [name|@exunit_teardown]
end
end
@@ -103,7 +103,7 @@ defmodule ExUnit.Callbacks do
defmacro setup_all(var // quote(do: _), block) do
quote do
name = :"__exunit_setup_all_#{length(@exunit_setup_all)}"
defp name, [unquote(Macro.escape var)], [], unquote(Macro.escape block)
defp name, [unquote(escape var)], [], unquote(escape block)
@exunit_setup_all [name|@exunit_setup_all]
end
end
@@ -111,7 +111,7 @@ defmodule ExUnit.Callbacks do
defmacro teardown_all(var // quote(do: _), block) do
quote do
name = :"__exunit_teardown_all_#{length(@exunit_teardown_all)}"
defp name, [unquote(Macro.escape var)], [], unquote(Macro.escape block)
defp name, [unquote(escape var)], [], unquote(escape block)
@exunit_teardown_all [name|@exunit_teardown_all]
end
end
@@ -125,6 +125,10 @@ defmodule ExUnit.Callbacks do
" or { :ok, data }, got #{inspect failure} instead"
end
defp escape(contents) do
Macro.escape_quoted(contents)
end
defp compile_callbacks(env, kind) do
callbacks = Module.get_attribute(env.module, kind) |> Enum.reverse
+2 -1
View File
@@ -40,6 +40,7 @@ defmodule ExUnit.Case do
import ExUnit.Assertions
import ExUnit.Case
import ExUnit.DocTest, only: [doctest: 1, doctest: 2]
end
end
@@ -88,7 +89,7 @@ defmodule ExUnit.Case do
end
def message, [unquote(Macro.escape var)], [], do:
unquote(Macro.escape contents)
unquote(Macro.escape_quoted contents)
end
end
end
+99 -34
View File
@@ -8,7 +8,7 @@ defmodule ExUnit.CLIFormatter do
@timeout 30_000
use GenServer.Behaviour
import Exception, only: [format_entry: 1]
import Exception, only: [format_entry: 2]
defrecord Config, counter: 0, failures: []
## Behaviour
@@ -18,8 +18,8 @@ defmodule ExUnit.CLIFormatter do
pid
end
def suite_finished(id) do
:gen_server.call(id, :suite_finished, @timeout)
def suite_finished(id, ms) do
:gen_server.call(id, { :suite_finished, ms }, @timeout)
end
def case_started(_id, _test_case) do
@@ -30,12 +30,12 @@ defmodule ExUnit.CLIFormatter do
:ok
end
def test_started(_id, _test_case, _test) do
def test_started(_id, _test) do
:ok
end
def test_finished(id, test_case, test, result) do
:gen_server.cast(id, { :test_finished, test_case, test, result })
def test_finished(id, test) do
:gen_server.cast(id, { :test_finished, test })
end
## Callbacks
@@ -44,66 +44,83 @@ defmodule ExUnit.CLIFormatter do
{ :ok, Config.new }
end
def handle_call(:suite_finished, _from, config) do
IO.write "\n\n"
Enum.reduce Enum.reverse(config.failures), 1, print_failure(&1, &2)
failures_count = length(config.failures)
IO.puts "#{config.counter} tests, #{failures_count} failures."
{ :stop, :normal, failures_count, config }
def handle_call({ :suite_finished, ms }, _from, config) do
print_suite(config.counter, config.failures, ms)
{ :stop, :normal, length(config.failures), config }
end
def handle_call(_, _, _) do
super
end
def handle_cast({ :test_finished, _test_case, _test, nil }, config) do
IO.write "."
def handle_cast({ :test_finished, ExUnit.Test[failure: nil] }, config) do
IO.write success(".")
{ :noreply, config.update_counter(&1 + 1) }
end
def handle_cast({ :test_finished, test_case, test, failure }, config) do
IO.write "F"
def handle_cast({ :test_finished, test }, config) do
IO.write failure("F")
{ :noreply, config.update_counter(&1 + 1).
update_failures([{test_case, test, failure}|&1]) }
update_failures([test|&1]) }
end
def handle_cast(_, _) do
super
end
defp print_failure({test_case, test, { kind, reason, stacktrace }}, acc) do
IO.puts "#{acc}) #{test} (#{inspect test_case})"
defp print_suite(counter, [], ms) do
IO.write "\n\n"
IO.puts "Finished in #{format_ms ms} seconds"
IO.puts success("#{counter} tests, 0 failures")
end
defp print_suite(counter, failures, ms) do
IO.write "\n\nFailures:\n\n"
Enum.reduce Enum.reverse(failures), 1, print_failure(&1, &2, File.cwd!)
IO.puts "Finished in #{format_ms ms} seconds"
IO.puts failure("#{counter} tests, #{length(failures)} failures")
end
defp print_failure(ExUnit.Test[case: test_case, name: test, failure: { kind, reason, stacktrace }], acc, cwd) do
IO.puts " #{acc}) #{test} (#{inspect test_case})"
print_kind_reason(kind, reason)
print_stacktrace(stacktrace, test_case, test)
print_stacktrace(stacktrace, test_case, test, cwd)
IO.write "\n"
acc + 1
end
defp print_kind_reason(:error, record) when is_record(record, ExUnit.ExpectationError) do
left = String.downcase record.prelude
right = "to " <> if(record.negation, do: "not ", else: "") <> "be #{record.reason}"
max = max(size(left), size(right))
defp print_kind_reason(:error, ExUnit.ExpectationError[] = record) do
prelude = String.downcase record.prelude
reason = record.full_reason
max = max(size(prelude), size(reason))
IO.puts " ** (ExUnit.ExpectationError)"
IO.puts " #{pad(left, max)}: #{inspect record.expected}"
IO.puts " #{pad(right, max)}: #{inspect record.actual}"
IO.puts error_info "** (ExUnit.ExpectationError)"
if desc = record.description do
IO.puts error_info " #{pad(prelude, max)}: #{maybe_multiline(desc, max)}"
IO.puts error_info " #{pad(reason, max)}: #{maybe_multiline(record.expected, max)}"
IO.puts error_info " #{pad("instead got", max)}: #{maybe_multiline(record.actual, max)}"
else
IO.puts error_info " #{pad(prelude, max)}: #{maybe_multiline(record.expected, max)}"
IO.puts error_info " #{pad(reason, max)}: #{maybe_multiline(record.actual, max)}"
end
end
defp print_kind_reason(:error, exception) do
IO.puts " ** (#{inspect exception.__record__(:name)}) #{exception.message}"
IO.puts error_info "** (#{inspect exception.__record__(:name)}) #{exception.message}"
end
defp print_kind_reason(kind, reason) do
IO.puts " ** (#{kind}) #{inspect(reason)}"
IO.puts error_info "** (#{kind}) #{inspect(reason)}"
end
defp print_stacktrace([{ test_case, test, _, [ file: file, line: line ] }|_], test_case, test) do
IO.puts " at #{file}:#{line}"
defp print_stacktrace([{ test_case, test, _, [ file: file, line: line ] }|_], test_case, test, cwd) do
IO.puts location_info "at #{Path.relative_to(file, cwd)}:#{line}"
end
defp print_stacktrace(stacktrace, _case, _test) do
IO.puts " stacktrace:"
Enum.each stacktrace, fn(s) -> IO.puts " #{format_entry(s)}" end
defp print_stacktrace(stacktrace, _case, _test, cwd) do
IO.puts location_info "stacktrace:"
Enum.each stacktrace, fn(s) -> IO.puts stacktrace_info format_entry(s, cwd) end
end
defp pad(binary, max) do
@@ -114,4 +131,52 @@ defmodule ExUnit.CLIFormatter do
binary
end
end
defp format_ms(ms) do
if ms < 100000 do
"0.0#{div(ms, 10000)}"
else
ms = div ms, 100000
"#{div(ms, 10)}.#{rem(ms, 10)}"
end
end
defp maybe_multiline(str, max) do
unless multiline?(str) do
String.strip(str)
else
"\n" <>
Enum.join((lc line inlist String.split(str, %r/\n/), do: String.duplicate(" ", max) <> line ), "\n")
end
end
defp multiline?(<<>>), do: false
defp multiline?(<<?\n, _ :: binary>>) do
true
end
defp multiline?(<<_, rest :: binary>>) do
multiline?(rest)
end
# Print styles
defp success(msg) do
IO.ANSI.escape("%{green}" <> msg)
end
defp failure(msg) do
IO.ANSI.escape("%{red}" <> msg)
end
defp error_info(msg) do
IO.ANSI.escape("%{red} " <> msg)
end
defp location_info(msg) do
IO.ANSI.escape("%{cyan} " <> msg)
end
defp stacktrace_info(msg) do
" " <> msg
end
end
+357
View File
@@ -0,0 +1,357 @@
defmodule ExUnit.DocTest do
@moduledoc """
ExUnit.DocTest implements functionality similar to [Python's
doctest](http://docs.python.org/2/library/doctest.html).
In a nutshell, it allows us to generate tests from the code
examples existing in module/function/macro's documentation.
In order to do that, one needs to invoke `doctest/1` macro
from their test case and write their examples according
to some guidelines.
The syntax for examples is as follows. Every new test starts
on a new line, with an "iex>" prefix. Multiline expressions
can be employed if the following lines start with either
"...>" (recommended) or "iex>" prefix.
The expected result should start at the next line after "iex>"
or "...>" line(s) and is terminated either by a newline, new
"iex>" prefix or end of the string literal.
## Examples
Currently, the only way to run doctests is to include them into
an ExUnit case with a `doctest` macro:
defmodule MyModule.Test do
use ExUnit.Case, async: true
doctest MyModule
end
The `doctest` macro is going to loop all functions and macros
defined in `MyModule`, parsing their documentation in search for
code examples.
A very basic example is:
iex> 1+1
2
Multiline is also supported:
iex> Enum.map [1,2,3], fn(x) ->
...> x * 2
...> end
[2,4,6]
Similarly to iex you can use numbers in your "prompts":
iex(1)> [1+2,
...(1)> 3]
[3,3]
This is useful in two use cases:
* Being able to refer to specific numbered scenarios
* Copy-pasting examples from an actual iex sessions
We also allow you to select or skip some functions when calling
`doctest`. See its documentation documentation for more info.
## Exceptions
You can also showcase expressions raising an exception, for example:
iex(1)> binary_to_atom((fn() -> 1 end).())
** (ArgumentError) argument error
What DocTest will be looking for is a line starting with "** (" and it
will parse it accordingly to extract the exception name and the message.
At this moment, the exception parser would make the parser treat the next
line as a start of a completely new expression (if it is prefixed with iex>)
or a no-op line with documentation. Thus, multiline messages are not
supported.
## When not to use doctest
In general, doctests are not recommended when your code examples contain
side effects. For example, if a doctest prints to standard output, doctest
will not try to capture the output.
Similarly, doctest does not run in any kind of side box. So any module
defined in a code example is going to linger throughout the whole test suite
run.
"""
defrecord Test, fun_arity: nil, line: nil, expr: nil, expected: nil
@doc """
This macro is used to generate ExUnit test cases for doctests.
There are three ways this macro can be used:
* `doctest(Module)` — will generate tests for all doctests found
in the module `Module`
Options can also be supplied:
* `:except` — generate tests for all functions except those listed
(list of `{function, arity}` tuples)
* `:only` — generate tests only forfunctions listed
(list of `{function, arity}` tuples)
* `:import` — when true, one can test a function defined in the module
without referring to the module name. However, this is not
feasible when there is a clash with a number module like
Kernel. In these cases, `import` should be set to `false`
and a full `M.f` construct should be used.
## Examples
doctest MyModule, except: [trick_fun: 1]
This macro is auto-imported into every `ExUnit.Case`.
"""
defmacro doctest(mod, opts // []) do
quote do
lc { name, test } inlist unquote(__MODULE__).__doctests__(unquote(mod), unquote(opts)) do
def name, quote(do: [_]), [], do: test
end
end
end
@doc false
def __doctests__(module, opts) do
only = opts[:only] || []
except = opts[:except] || []
do_import = Keyword.get(opts, :import, false)
tests = Enum.filter(extract(module), fn(test) ->
fa = test.fun_arity
Enum.all?(except, &1 != fa) and Enum.all?(only, &1 == fa)
end)
Enum.map_reduce(tests, 1, fn(test, acc) ->
{ compile_test(test, module, do_import, acc), acc + 1 }
end) |> elem(0)
end
## Compilation of extracted tests
defp compile_test(test, module, do_import, n) do
{ test_name(test, module, n), test_content(test, module, do_import) }
end
defp test_name(Test[fun_arity: nil], m, n) do
:"test moduledoc at #{inspect m} (#{n})"
end
defp test_name(Test[fun_arity: { f, a }], m, n) do
:"test doc at #{inspect m}.#{f}/#{a} (#{n})"
end
defp test_content(Test[expected: { :test, expected }] = test, module, do_import) do
line = test.line
file = module.__info__(:compile)[:source]
location = [line: line, file: Path.relative_to(file, System.cwd!)]
stack = Macro.escape [{ module, :__MODULE__, 0, location }]
expr_ast = string_to_ast(module, line, file, test.expr)
expected_ast = string_to_ast(module, line, file, expected)
quote do
unquote_splicing(test_import(module, do_import))
try do
v = unquote(expected_ast)
case unquote(expr_ast) do
^v -> :ok
actual ->
raise ExUnit.ExpectationError,
[ prelude: "Expected doctest",
description: unquote(test.expr),
expected: inspect(v),
reason: "evaluate to",
actual: inspect(actual) ],
unquote(stack)
end
rescue
e in [ExUnit.ExpectationError] ->
raise e, [], unquote(stack)
actual ->
raise ExUnit.ExpectationError,
[ prelude: "Expected doctest",
description: unquote(test.expr),
expected: "without an exception",
reason: "complete",
actual: inspect(actual) ],
unquote(stack)
end
end
end
defp test_content(Test[expected: { :error, exception, message }] = test, module, do_import) do
line = test.line
file = module.__info__(:compile)[:source]
location = [line: line, file: Path.relative_to(file, System.cwd!)]
stack = Macro.escape [{ module, :__MODULE__, 0, location }]
expr_ast = string_to_ast(module, line, file, test.expr)
quote do
unquote_splicing(test_import(module, do_import))
try do
v = unquote(expr_ast)
raise ExUnit.ExpectationError,
[ prelude: "Expected doctest",
description: unquote(test.expr),
expected: "#{inspect unquote(exception)}[]",
reason: "raise",
actual: inspect(v) ],
unquote(stack)
rescue
e in [ExUnit.ExpectationError] -> raise(e)
error in [unquote(exception)] ->
unless error.message == unquote(message) do
raise ExUnit.ExpectationError,
[ prelude: "Expected doctest",
description: unquote(test.expr),
expected: "#{inspect unquote(exception)} with message #{inspect unquote(message)}",
reason: "raise",
actual: inspect(error) ],
unquote(stack)
end
error ->
raise ExUnit.ExpectationError,
[ prelude: "Expected doctest",
description: unquote(test.expr),
expected: "#{inspect unquote(exception)}",
reason: "raise",
actual: inspect(error) ],
unquote(stack)
end
end
end
defp test_import(_mod, false), do: []
defp test_import(mod, _) do
[quote do: import(unquote(mod))]
end
defp string_to_ast(module, line, file, expr) do
location = [line: line, file: Path.relative_to(file, System.cwd!)]
stack = Macro.escape [{ module, :__MODULE__, 0, location }]
try do
Code.string_to_ast!(expr, line: line, file: file)
rescue e ->
quote do
raise ExUnit.ExpectationError,
[ prelude: "Expected doctest",
description: unquote(expr),
expected: "successfully",
reason: "compile",
actual: unquote("** #{inspect e.__record__(:name)} #{e.message}") ],
unquote(stack)
end
end
end
## Extraction of the tests
defp extract(module) do
moduledocs = extract_from_moduledoc(module.__info__(:moduledoc))
docs = lc doc inlist module.__info__(:docs) do
extract_from_doc(doc)
end |> List.concat
moduledocs ++ docs
end
defp extract_from_moduledoc({_, negative}) when negative in [false, nil], do: []
defp extract_from_moduledoc({line, doc}) do
extract_tests(line, doc)
end
defp extract_from_doc({_, _, _, _, negative}) when negative in [false, nil], do: []
defp extract_from_doc({ fa, line, _, _, doc}) do
lc test inlist extract_tests(line, doc) do
test.fun_arity(fa)
end
end
defp extract_tests(line, doc) do
lines = String.split(doc, %r/\n/) |> Enum.map(function(String.strip/1))
extract_tests(lines, line, "", "", [])
end
defp extract_tests([], _line, "", "", acc), do: Enum.reverse(acc)
defp extract_tests([], line, expr_acc, expected_acc, acc) do
test = Test[expr: expr_acc, line: line, expected: { :test, expected_acc }]
Enum.reverse([test|acc])
end
defp extract_tests([<< "iex>", _ :: binary>>|_] = list, line, expr_acc, expected_acc, acc) when expr_acc != "" and expected_acc != "" do
test = Test[expr: expr_acc, line: line, expected: { :test, expected_acc }]
extract_tests(list, line, "", "", [test|acc])
end
defp extract_tests([<< "iex>", string :: binary>>|lines], line, expr_acc, expected_acc, acc) when expr_acc == "" do
extract_tests(lines, line, string, expected_acc, acc)
end
defp extract_tests([<< "iex>", string :: binary>>|lines], line, expr_acc, expected_acc, acc) do
extract_tests(lines, line, expr_acc <> "\n" <> string, expected_acc, acc)
end
defp extract_tests([<< "...>", string :: binary>>|lines], line, expr_acc, expected_acc, acc) when expr_acc != "" do
extract_tests(lines, line, expr_acc <> "\n" <> string, expected_acc, acc)
end
defp extract_tests([<< "iex(", _ :: 8, string :: binary>>|lines], line, expr_acc, expected_acc, acc) do
extract_tests(["iex" <> skip_iex_number(string)|lines], line, expr_acc, expected_acc, acc)
end
defp extract_tests([<< "...(", _ :: 8, string :: binary>>|lines], line, expr_acc, expected_acc, acc) do
extract_tests(["..." <> skip_iex_number(string)|lines], line, expr_acc, expected_acc, acc)
end
defp extract_tests([_|lines], line, "", "", acc) do
extract_tests(lines, line, "", "", acc)
end
defp extract_tests([""|lines], line, expr_acc, expected_acc, acc) do
test = Test[expr: expr_acc, line: line, expected: { :test, expected_acc }]
extract_tests(lines, line, "", "", [test|acc])
end
defp extract_tests([<< "** (", string :: binary >>|lines], line, expr_acc, "", acc) do
test = Test[expr: expr_acc, line: line, expected: extract_error(string, "")]
extract_tests(lines, line, "", "", [test|acc])
end
defp extract_tests([expected|lines], line, expr_acc, expected_acc, acc) do
extract_tests(lines, line, expr_acc, expected_acc <> "\n" <> expected, acc)
end
defp extract_error(<< ")", t :: binary >>, acc) do
{ :error, Module.concat([acc]), String.strip(t) }
end
defp extract_error(<< h, t :: binary >>, acc) do
extract_error(t, << acc :: binary, h >>)
end
defp skip_iex_number(<< ")", ">", string :: binary >>) do
">" <> string
end
defp skip_iex_number(<< _ :: 8, string :: binary >>) do
skip_iex_number(string)
end
end
+3 -3
View File
@@ -12,11 +12,11 @@ defmodule ExUnit.Formatter do
@type result :: { kind :: atom, reason :: term, stacktrace :: list } | nil
defcallback suite_started(opts :: list) :: id
defcallback suite_finished(id) :: non_neg_integer
defcallback suite_finished(id, miliseconds :: pos_integer) :: non_neg_integer
defcallback case_started(id, test_case) :: any
defcallback case_finished(id, test_case) :: any
defcallback test_started(id, test_case, test) :: any
defcallback test_finished(id, test_case, test, result) :: any
defcallback test_started(id, test :: ExUnit.Test.t) :: any
defcallback test_finished(id, test :: ExUnit.Test.t) :: any
end
+27 -22
View File
@@ -8,8 +8,13 @@ defmodule ExUnit.Runner do
config = Config[max_cases: :erlang.system_info(:schedulers_online)]
config = config.update(opts)
loop config.async_cases(async).sync_cases(sync).
formatter_id(config.formatter.suite_started(opts))
{ ms, config } =
:timer.tc fn ->
loop config.async_cases(async).sync_cases(sync).
formatter_id(config.formatter.suite_started(opts))
end
config.formatter.suite_finished(config.formatter_id, ms)
end
defp loop(Config[] = config) do
@@ -36,7 +41,7 @@ defmodule ExUnit.Runner do
# No more cases, we are done!
true ->
config.formatter.suite_finished(config.formatter_id)
config
end
end
@@ -45,8 +50,8 @@ defmodule ExUnit.Runner do
# cases counter and attempt to spawn new ones.
defp wait_until_available(config) do
receive do
{ _pid, :test_finished, { test_case, test, final } } ->
config.formatter.test_finished(config.formatter_id, test_case, test, final)
{ _pid, :test_finished, test } ->
config.formatter.test_finished(config.formatter_id, test)
wait_until_available config
{ _pid, :case_finished, test_case } ->
config.formatter.case_finished(config.formatter_id, test_case)
@@ -62,7 +67,6 @@ defmodule ExUnit.Runner do
defp spawn_case(config, test_case) do
pid = self()
spawn_link fn ->
ExUnit.Server.run_after_spawn
run_tests(config, pid, test_case)
end
end
@@ -72,42 +76,43 @@ defmodule ExUnit.Runner do
try do
tests = tests_for(test_case)
context = test_case.__exunit__(:setup_all, [])
Enum.each tests, run_test(config, pid, test_case, context, &1)
context = test_case.__exunit__(:setup_all, [case: test_case])
Enum.each tests, run_test(config, pid, test_case, &1, context)
test_case.__exunit__(:teardown_all, context)
after
pid <- { self, :case_finished, test_case }
end
end
defp run_test(config, pid, test_case, setup_context, test) do
config.formatter.test_started(config.formatter_id, test_case, test)
defp run_test(config, pid, test_case, test_name, context) do
test = ExUnit.Test[name: test_name, case: test_case]
config.formatter.test_started(config.formatter_id, test)
final = try do
context = test_case.__exunit__(:setup, Keyword.put(setup_context, :test, test))
test = try do
context = test_case.__exunit__(:setup, Keyword.put(context, :test, test))
partial = try do
apply test_case, test, [context]
nil
test = try do
apply test_case, test_name, [context]
test
rescue
error1 ->
{ :error, error1, filtered_stacktrace }
test.failure { :error, error1, filtered_stacktrace }
catch
kind1, error1 ->
{ kind1, error1, filtered_stacktrace }
test.failure { kind1, error1, filtered_stacktrace }
end
test_case.__exunit__(:teardown, context)
partial
test_case.__exunit__(:teardown, Keyword.put(context, :test, test))
test
rescue
error2 ->
{ :error, error2, filtered_stacktrace }
test.failure { :error, error2, filtered_stacktrace }
catch
kind2, error2 ->
{ kind2, error2, filtered_stacktrace }
test.failure { kind2, error2, filtered_stacktrace }
end
pid <- { self, :test_finished, { test_case, test, final } }
pid <- { self, :test_finished, test }
end
## Helpers
+1 -18
View File
@@ -4,7 +4,7 @@ defmodule ExUnit.Server do
@timeout 30_000
use GenServer.Behaviour
defrecord Config, options: [], async_cases: [], sync_cases: [], after_spawn: []
defrecord Config, options: [], async_cases: [], sync_cases: []
def start_link(options) do
:gen_server.start_link({ :local, __MODULE__ }, __MODULE__, options, [])
@@ -20,10 +20,6 @@ defmodule ExUnit.Server do
:gen_server.cast(__MODULE__, { :add_sync_case, name })
end
def add_after_spawn(fun) when is_function(fun) do
:gen_server.cast(__MODULE__, { :add_after_spawn, fun })
end
def merge_options(options) do
:gen_server.cast(__MODULE__, { :merge_options, options })
end
@@ -38,11 +34,6 @@ defmodule ExUnit.Server do
:gen_server.call(__MODULE__, :cases, @timeout)
end
def run_after_spawn do
funs = :gen_server.call(__MODULE__, :after_spawn, @timeout)
lc fun inlist funs, do: fun.()
end
## Callbacks
def init(options) do
@@ -53,10 +44,6 @@ defmodule ExUnit.Server do
{ :reply, config.options, config }
end
def handle_call(:after_spawn, _from, config) do
{ :reply, Enum.reverse(config.after_spawn), config }
end
def handle_call(:cases, _from, config) do
{ :reply,
{ config.async_cases, config.sync_cases },
@@ -75,10 +62,6 @@ defmodule ExUnit.Server do
{ :noreply, config.update_sync_cases [name|&1] }
end
def handle_cast({:add_after_spawn, fun}, config) do
{ :noreply, config.update_after_spawn [fun|&1] }
end
def handle_cast({:merge_options, options}, config) do
{ :noreply, config.update_options(&1 |> Keyword.merge(options)) }
end
+4 -4
View File
@@ -3,12 +3,12 @@ defmodule ExUnit.Sup do
use Supervisor.Behaviour
def start_link(user_options) do
:supervisor.start_link({ :local, __MODULE__ }, __MODULE__, user_options)
def start_link(options) do
:supervisor.start_link({ :local, __MODULE__ }, __MODULE__, options)
end
def init(user_options) do
tree = [ worker(ExUnit.Server, [user_options]) ]
def init(options) do
tree = [ worker(ExUnit.Server, [options]) ]
supervise(tree, strategy: :one_for_one)
end
end
+29 -7
View File
@@ -139,29 +139,51 @@ defmodule ExUnit.AssertionsTest do
end
end
test :assert_match_when_matches do
true = assert "foo" =~ %r(o)
test :assert_match_when_match do
{ :ok, true } = assert { :ok, _ } = { :ok, true }
end
test :assert_match_when_no_match do
try do
"This should never be tested" = assert "foo" =~ %r(a)
"This should never be tested" = assert { :ok, _ } = "bar"
rescue
error in [ExUnit.ExpectationError] ->
"Expected \"foo\" to be a match (=~) with %r\"a\"" = error.message
"Expected \"bar\" to match pattern (=) {:ok, _}" = error.message
end
end
test :refute_match_when_is_matches do
test :refute_match_when_match do
try do
"This should never be tested" = refute _ = "bar"
rescue
error in [ExUnit.ExpectationError] ->
"Expected \"bar\" to not match pattern (=) _" = error.message
end
end
test :assert_regex_match_when_matches do
true = assert "foo" =~ %r(o)
end
test :assert_regex_match_when_no_match do
try do
"This should never be tested" = assert "foo" =~ %r(a)
rescue
error in [ExUnit.ExpectationError] ->
"Expected \"foo\" to match (=~) %r\"a\"" = error.message
end
end
test :refute_regex_match_when_is_matches do
false = refute "foo" =~ %r(a)
end
test :refute_match_when_no_match do
test :refute_regex_match_when_no_match do
try do
"This should never be tested" = refute "foo" =~ %r(o)
rescue
error in [ExUnit.ExpectationError] ->
"Expected \"foo\" to not be a match (=~) with %r\"o\"" = error.message
"Expected \"foo\" to not match (=~) %r\"o\"" = error.message
end
end
+2 -2
View File
@@ -21,7 +21,7 @@ defmodule ExUnit.CallbacksTest do
if Process.get(:ex_unit_callback) do
raise "ex_unit_callback was not cleaned"
else
Process.put(:ex_unit_callback, context[:test])
Process.put(:ex_unit_callback, context[:test].name)
end
:ok
end
@@ -32,7 +32,7 @@ defmodule ExUnit.CallbacksTest do
end
teardown context do
assert Process.get(:ex_unit_callback) == context[:test]
assert Process.get(:ex_unit_callback) == context[:test].name
Process.delete(:ex_unit_callback)
:ok
end
@@ -36,6 +36,12 @@ end
defmodule ExUnit.CaseTemplateTest do
use ExUnit.SampleCase, async: true
two = 2
test "unquoting the value #{two}" do
assert 2 == unquote(two)
end
test "receives context from parent case", context do
assert context[:context] == :setup
end
@@ -44,7 +44,7 @@ defmodule ExUnit.CLIFormatterTest do
"""
out_put = exec_test("failure_test.exs")
assert out_put =~ %r/at\ #{Path.expand("failure_test.exs")}:6/
assert out_put =~ %r/at failure_test.exs:6/
after
File.rm("failure_test.exs")
end
@@ -0,0 +1,70 @@
Code.require_file "../../test_helper.exs", __FILE__
defmodule ExUnit.DocTestTest.GoodModule do
@doc """
iex> test_fun
1
iex> test_fun + 1
2
"""
def test_fun, do: 1
@doc """
iex> 1 + (fn() -> :a end).()
** (ArithmeticError) bad argument in arithmetic expression
"""
def exception_test, do: 1
end
defmodule ExUnit.DocTestTest.SomewhatGoodModule do
@doc """
iex> test_fun
1
iex> test_fun + 1
2
"""
def test_fun, do: 1
@doc """
iex> test_fun
1
iex> test_fun + 1
1
"""
def test_fun1, do: 1
end
defmodule ExUnit.DocTestTest.SomewhatGoodModule1 do
@doc """
iex> test_fun
1
iex> test_fun + 1
2
"""
def test_fun, do: 1
@doc """
iex> test_fun
1
iex> test_fun + 1
1
"""
def test_fun1, do: 1
end
defmodule ExUnit.DocTestTest.NoImport do
@doc """
iex> ExUnit.DocTestTest.NoImport.min(1,2)
2
"""
def min(a,b), do: max(a,b)
end
defmodule ExUnit.DocTestTest do
use ExUnit.Case
doctest ExUnit.DocTest
doctest ExUnit.DocTestTest.GoodModule, import: true
doctest ExUnit.DocTestTest.SomewhatGoodModule, only: [test_fun: 0], import: true
doctest ExUnit.DocTestTest.SomewhatGoodModule1, except: [test_fun1: 0], import: true
doctest ExUnit.DocTestTest.NoImport
end
+3 -11
View File
@@ -7,7 +7,7 @@ defmodule ExUnit.NilFormatter do
:ok
end
def suite_finished(:ok) do
def suite_finished(:ok, _ms) do
1
end
@@ -19,11 +19,11 @@ defmodule ExUnit.NilFormatter do
:ok
end
def test_started(:ok, _test_case, _test) do
def test_started(:ok, _test) do
:ok
end
def test_finished(:ok, _test_case, _test, _result) do
def test_finished(:ok, _test) do
:ok
end
end
@@ -31,14 +31,6 @@ end
defmodule ExUnitTest do
use ExUnit.Case, async: false
test "it can read user config" do
File.write("ex_unit.test.config","[extra_option: true]")
assert ExUnit.user_options("nosuchfile.config") == []
assert ExUnit.user_options("ex_unit.test.config") == [extra_option: true]
after
File.rm("ex_unit.test.config")
end
test "it supports many runs" do
ExUnit.configure(formatter: ExUnit.NilFormatter)
+5 -1
View File
@@ -253,7 +253,11 @@ defmodule IEx.Autocomplete do
defp get_funs(mod) do
if function_exported?(mod, :__info__, 1) do
(mod.__info__(:functions) -- [__info__: 1]) ++ mod.__info__(:macros)
if docs = mod.__info__(:docs) do
lc { tuple, _line, _kind, _sign, doc } inlist docs, doc != false, do: tuple
else
(mod.__info__(:functions) -- [__info__: 1]) ++ mod.__info__(:macros)
end
else
mod.module_info(:exports)
end
+45 -226
View File
@@ -12,15 +12,17 @@ defmodule IEx.Helpers do
There are many other helpers available:
* `c/2` - compiles a file in the given path
* `cd/1` - changes the current directory
* `flush/0` — flush all messages sent to the shell
* `h/0`, `h/1` - prints help/documentation
* `t/1` — prints type information
* `s/1` — prints spec information
* `m/0` - prints loaded modules
* `r/0`, `r/1` - recompiles and reloads the given module's source file
* `l/1` - loads given module beam code by purging the current version
* `m/0` - prints loaded modules
* `pwd/0` - prints the current working directory
* `r/0`, `r/1` - recompiles and reloads the given module's source file
* `s/1` — prints spec information
* `t/1` — prints type information
* `v/0` - prints all commands and values
* `v/1` - retrieves nth value from console
* `flush/0` — flush all messages sent to the shell
Help for functions in this module can be consulted
directly from the command line, as an example, try:
@@ -43,7 +45,7 @@ defmodule IEx.Helpers do
## Examples
c ["foo.ex"], "ebin"
#=> Foo
#=> [Foo]
"""
def c(files, path // ".") do
@@ -58,7 +60,7 @@ defmodule IEx.Helpers do
all = Enum.map :code.all_loaded, fn { mod, file } -> { inspect(mod), file } end
sorted = Enum.sort all
size = Enum.reduce sorted, 0, fn({ mod, _ }, acc) -> max(byte_size(mod), acc) end
format = "~-#{size}s ~s~n"
format = "~-#{size}s ~ts~n"
Enum.each sorted, fn({ mod, file }) ->
:io.format(format, [mod, file])
@@ -74,14 +76,14 @@ defmodule IEx.Helpers do
end
defp print_history(config) do
IO.puts "#{config.counter}: #{config.cache}#=> #{inspect config.result}\n"
IO.puts IO.ANSI.escape("%{yellow}#{config.counter}: #{config.cache}#=> #{inspect config.result}\n")
end
@doc """
Shows the documentation for IEx.Helpers.
"""
def h() do
h(IEx.Helpers, [])
IEx.Introspection.h(IEx.Helpers)
end
@doc """
@@ -103,137 +105,36 @@ defmodule IEx.Helpers do
"""
defmacro h({ :/, _, [{ { :., _, [mod, fun] }, _, [] }, arity] }) do
quote do
h(unquote(mod), unquote(fun), unquote(arity))
IEx.Introspection.h(unquote(mod), unquote(fun), unquote(arity))
end
end
defmacro h({ { :., _, [mod, fun] }, _, [] }) do
quote do
h(unquote(mod), unquote(fun))
IEx.Introspection.h(unquote(mod), unquote(fun))
end
end
defmacro h({ :/, _, [{ fun, _, args }, arity] }) when args == [] or is_atom(args) do
quote do
h(unquote(fun), unquote(arity))
IEx.Introspection.h(unquote(fun), unquote(arity))
end
end
defmacro h({ name, _, args }) when args == [] or is_atom(args) do
quote do
h(unquote(__MODULE__), unquote(name))
h(Kernel, unquote(name))
IEx.Introspection.h([unquote(__MODULE__), Kernel, Kernel.SpecialForms], unquote(name))
end
end
defmacro h(other) do
quote do
h(unquote(other), [])
IEx.Introspection.h(unquote(other))
end
end
@doc false
def h(:h, 1) do
h(__MODULE__, :h, 1)
end
def h(function, arity) when is_atom(function) and is_integer(arity) do
if function_exported?(__MODULE__, function, arity) or
macro_exported?(__MODULE__, function, arity) do
h(__MODULE__, function, arity)
else
h(Kernel, function, arity)
end
end
def h(module, []) when is_atom(module) do
case Code.ensure_loaded(module) do
{ :module, _ } ->
case module.__info__(:moduledoc) do
{ _, binary } when is_binary(binary) ->
IO.puts "# #{inspect module}\n"
IO.write binary
{ _, _ } ->
IO.puts "No docs for #{inspect module} have been found"
_ ->
IO.puts "#{inspect module} was not compiled with docs"
end
{ :error, reason } ->
IO.puts "Could not load module #{inspect module}: #{reason}"
end
end
def h(module, function) when is_atom(module) and is_atom(function) do
lc {{f, arity}, _line, _type, _args, doc } inlist module.__info__(:docs),
f == function and doc != false do
h(module, function, arity)
IO.puts ""
end
:ok
end
def h(_, _) do
IO.puts "Invalid h helper argument"
h()
end
@doc false
def h(module, function, arity) when is_atom(module) and is_atom(function) and is_integer(arity) do
if docs = module.__info__(:docs) do
doc =
cond do
d = find_doc(docs, function, arity) -> d
d = find_default_doc(docs, function, arity) -> d
true -> nil
end
if doc do
IO.write "\n" <> print_signature(doc)
else
IO.puts "No docs for #{inspect module}.#{function}/#{arity} have been found"
end
else
IO.puts "#{inspect module} was not compiled with docs"
end
end
defp find_doc(docs, function, arity) do
List.keyfind(docs, { function, arity }, 0)
end
defp find_default_doc(docs, function, min) do
Enum.find docs, fn(doc) ->
case elem(doc, 0) do
{ ^function, max } when max > min ->
defaults = Enum.count elem(doc, 3), match?({ ://, _, _ }, &1)
min + defaults >= max
_ ->
false
end
end
end
# Get the full signature from a function.
defp print_signature({ _info, _line, _kind, _args, false }) do
false
end
defp print_signature({ { name, _arity }, _line, kind, args, docs }) do
args = Enum.map_join(args, ", ", signature_arg(&1))
IO.puts "* #{kind} #{name}(#{args})"
docs || ""
end
defp signature_arg({ ://, _, [left, right] }) do
signature_arg(left) <> " // " <> Macro.to_binary(right)
end
defp signature_arg({ var, _, _ }) do
atom_to_binary(var)
end
@doc """
Prints all types for the given module or prints out a specified type's
Prints all types for the given module or prints out a specified type's
specification
## Examples
@@ -245,62 +146,22 @@ defmodule IEx.Helpers do
"""
defmacro t({ :/, _, [{ { :., _, [mod, fun] }, _, [] }, arity] }) do
quote do
t(unquote(mod), unquote(fun), unquote(arity))
IEx.Introspection.t(unquote(mod), unquote(fun), unquote(arity))
end
end
defmacro t({ { :., _, [mod, fun] }, _, [] }) do
quote do
t(unquote(mod), unquote(fun))
IEx.Introspection.t(unquote(mod), unquote(fun))
end
end
defmacro t(module) do
quote do
t(unquote(module), [])
IEx.Introspection.t(unquote(module))
end
end
@doc false
def t(module, type) when is_atom(type) do
types = lc {_, {t, _, _args}} = typespec inlist Kernel.Typespec.beam_types(module),
t == type do
print_type(typespec)
typespec
end
if types == [] do
IO.puts "No types for #{inspect module}.#{type} have been found"
end
:ok
end
def t(module, []) do
types = lc type inlist Kernel.Typespec.beam_types(module), do: print_type(type)
if types == [] do
IO.puts "No types for #{inspect module} have been found"
end
:ok
end
@doc false
def t(module, type, arity) do
types = lc {_, {t, _, args}} = typespec inlist Kernel.Typespec.beam_types(module),
length(args) == arity and t == type, do: typespec
case types do
[] ->
IO.puts "No types for #{inspect module}.#{type}/#{arity} have been found"
[type] ->
print_type(type)
end
:ok
end
@doc """
Prints all specs from a given module.
@@ -315,94 +176,34 @@ defmodule IEx.Helpers do
"""
defmacro s({ :/, _, [{ { :., _, [mod, fun] }, _, [] }, arity] }) do
quote do
s(unquote(mod), unquote(fun), unquote(arity))
IEx.Introspection.s(unquote(mod), unquote(fun), unquote(arity))
end
end
defmacro s({ { :., _, [mod, fun] }, _, [] }) do
quote do
s(unquote(mod), unquote(fun))
IEx.Introspection.s(unquote(mod), unquote(fun))
end
end
defmacro s({ fun, _, args }) when args == [] or is_atom(args) do
quote do
s(Kernel, unquote(fun))
IEx.Introspection.s(Kernel, unquote(fun))
end
end
defmacro s({ :/, _, [{ fun, _, args }, arity] }) when args == [] or is_atom(args) do
quote do
s(Kernel, unquote(fun), unquote(arity))
end
IEx.Introspection.s(Kernel, unquote(fun), unquote(arity))
end
end
defmacro s(module) do
quote do
s(unquote(module), [])
IEx.Introspection.s(unquote(module))
end
end
@doc false
def s(module, function) when is_atom(function) do
specs = lc {_kind, {{f, _arity}, _spec}} = spec inlist beam_specs(module),
f == function do
print_spec(spec)
spec
end
if specs == [] do
IO.puts "No specs for #{inspect module}.#{function} have been found"
end
:ok
end
def s(module, []) do
specs = lc spec inlist beam_specs(module), do: print_spec(spec)
if specs == [] do
IO.puts "No specs for #{inspect module} have been found"
end
:ok
end
@doc false
def s(module, function, arity) do
specs = lc {_kind, {{f, a}, _spec}} = spec inlist beam_specs(module),
f == function and a == arity do
print_spec(spec)
spec
end
if specs == [] do
IO.puts "No specs for #{inspect module}.#{function} have been found"
end
:ok
end
defp beam_specs(module) do
specs = Enum.map(Kernel.Typespec.beam_specs(module), {:spec, &1})
callbacks = Enum.map(Kernel.Typespec.beam_callbacks(module), {:callback, &1})
List.concat(specs, callbacks)
end
defp print_type({ kind, type }) do
ast = Kernel.Typespec.type_to_ast(type)
IO.puts "@#{kind} #{Macro.to_binary(ast)}"
true
end
defp print_spec({kind, { { name, _arity }, specs }}) do
Enum.each specs, fn(spec) ->
binary = Macro.to_binary Kernel.Typespec.spec_to_ast(name, spec)
IO.puts "@#{kind} #{binary}"
end
true
end
@doc """
Retrieves nth query's value from the history. Use negative
values to lookup query's value from latest to earliest.
@@ -459,7 +260,7 @@ defmodule IEx.Helpers do
flush
after
0 -> :ok
end
end
end
defp iex_reloaded do
@@ -485,4 +286,22 @@ defmodule IEx.Helpers do
_ -> nil
end
end
@doc """
Prints the current working directory.
"""
def pwd do
IO.puts IO.ANSI.escape("%{yellow}#{System.cwd!}")
end
@doc """
Changes the shell directory to the given path.
"""
def cd(directory) do
case File.cd(directory) do
:ok -> pwd
{ :error, :enoent } ->
IO.puts IO.ANSI.escape("%{red}No directory #{directory}")
end
end
end
+263
View File
@@ -0,0 +1,263 @@
defmodule IEx.Introspection do
# Convenience helpers for showing docs, specs and types
# from modules. Invoked directly from IEX.Helpers.
@moduledoc false
@doc false
def h(module) when is_atom(module) do
case Code.ensure_loaded(module) do
{ :module, _ } ->
case module.__info__(:moduledoc) do
{ _, binary } when is_binary(binary) ->
IO.puts IO.ANSI.escape("%{yellow}# #{inspect module}\n")
IO.write IO.ANSI.escape_fragment("%{yellow}") <> binary <> IO.ANSI.escape_fragment("%{reset}")
{ _, _ } ->
IO.puts IO.ANSI.escape("%{red}No docs for #{inspect module} have been found")
_ ->
IO.puts IO.ANSI.escape("%{red}#{inspect module} was not compiled with docs")
end
{ :error, reason } ->
IO.puts IO.ANSI.escape("%{red}Could not load module #{inspect module}: #{reason}")
end
end
def h(_) do
IO.puts IO.ANSI.escape("%{red}Invalid arguments for h helper")
end
@doc false
def h(modules, function) when is_list(modules) and is_atom(function) do
result =
Enum.reduce modules, :not_found, fn
module, :not_found -> h_mod_fun(module, function)
_module, acc -> acc
end
unless result == :ok, do:
IO.puts IO.ANSI.escape("%{red}No docs for #{function} have been found")
:ok
end
def h(module, function) when is_atom(module) and is_atom(function) do
case h_mod_fun(module, function) do
:ok ->
:ok
:no_docs ->
IO.puts IO.ANSI.escape("%{red}#{inspect module} was not compiled with docs")
:not_found ->
IO.puts IO.ANSI.escape("%{red}No docs for #{inspect module}.#{function} have been found")
end
:ok
end
def h(function, arity) when is_atom(function) and is_integer(arity) do
h([__MODULE__, Kernel, Kernel.SpecialForms], function, arity)
end
def h(_, _) do
IO.puts IO.ANSI.escape("%{red}Invalid arguments for h helper")
end
defp h_mod_fun(mod, fun) when is_atom(mod) and is_atom(fun) do
if docs = mod.__info__(:docs) do
result = lc { {f, arity}, _line, _type, _args, doc } inlist docs, fun == f, doc != false do
h(mod, fun, arity)
IO.puts ""
end
if result != [], do: :ok, else: :not_found
else
:no_docs
end
end
@doc false
def h(modules, function, arity) when is_list(modules) and is_atom(function) and is_integer(arity) do
result =
Enum.reduce modules, :not_found, fn
module, :not_found -> h_mod_fun_arity(module, function, arity)
_module, acc -> acc
end
unless result == :ok, do:
IO.puts IO.ANSI.escape("%{red}No docs for #{function}/#{arity} have been found")
:ok
end
def h(module, function, arity) when is_atom(module) and is_atom(function) and is_integer(arity) do
case h_mod_fun_arity(module, function, arity) do
:ok ->
:ok
:no_docs ->
IO.puts IO.ANSI.escape("%{red}#{inspect module} was not compiled with docs")
:not_found ->
IO.puts IO.ANSI.escape("%{red}No docs for #{inspect module}.#{function}/#{arity} have been found")
end
:ok
end
def h(_, _, _) do
IO.puts IO.ANSI.escape("%{red}Invalid arguments for h helper")
end
defp h_mod_fun_arity(mod, fun, arity) when is_atom(mod) and is_atom(fun) and is_integer(arity) do
if docs = mod.__info__(:docs) do
doc =
cond do
d = find_doc(docs, fun, arity) -> d
d = find_default_doc(docs, fun, arity) -> d
true -> nil
end
if doc do
print_doc(doc)
:ok
else
:not_found
end
else
:no_docs
end
end
defp find_doc(docs, function, arity) do
if doc = List.keyfind(docs, { function, arity }, 0) do
case elem(doc, 4) do
false -> nil
_ -> doc
end
end
end
defp find_default_doc(docs, function, min) do
Enum.find docs, fn(doc) ->
case elem(doc, 0) do
{ ^function, max } when max > min ->
defaults = Enum.count elem(doc, 3), match?({ ://, _, _ }, &1)
min + defaults >= max
_ ->
false
end
end
end
defp print_doc({ { fun, _ }, _line, kind, args, doc }) do
args = Enum.map_join(args, ", ", print_doc_arg(&1))
IO.puts IO.ANSI.escape("%{yellow}* #{kind} #{fun}(#{args})\n")
IO.write IO.ANSI.escape_fragment("%{yellow}") <> doc <> IO.ANSI.escape_fragment("%{reset}")
end
defp print_doc_arg({ ://, _, [left, right] }) do
print_doc_arg(left) <> " // " <> Macro.to_binary(right)
end
defp print_doc_arg({ var, _, _ }) do
atom_to_binary(var)
end
@doc false
def t(module) do
types = lc type inlist Kernel.Typespec.beam_types(module), do: print_type(type)
if types == [] do
IO.puts IO.ANSI.escape("%{red}No types for #{inspect module} have been found")
end
:ok
end
@doc false
def t(module, type) when is_atom(type) do
types = lc {_, {t, _, _args}} = typespec inlist Kernel.Typespec.beam_types(module),
t == type do
print_type(typespec)
typespec
end
if types == [] do
IO.puts IO.ANSI.escape("%{red}No types for #{inspect module}.#{type} have been found")
end
:ok
end
@doc false
def t(module, type, arity) do
types = lc {_, {t, _, args}} = typespec inlist Kernel.Typespec.beam_types(module),
length(args) == arity and t == type, do: typespec
case types do
[] ->
IO.puts IO.ANSI.escape("%{red}No types for #{inspect module}.#{type}/#{arity} have been found")
[type] ->
print_type(type)
end
:ok
end
@doc false
def s(module) do
specs = lc spec inlist beam_specs(module), do: print_spec(spec)
if specs == [] do
IO.puts IO.ANSI.escape("%{red}No specs for #{inspect module} have been found")
end
:ok
end
@doc false
def s(module, function) when is_atom(function) do
specs = lc {_kind, {{f, _arity}, _spec}} = spec inlist beam_specs(module),
f == function do
print_spec(spec)
spec
end
if specs == [] do
IO.puts IO.ANSI.escape("%{red}No specs for #{inspect module}.#{function} have been found")
end
:ok
end
@doc false
def s(module, function, arity) do
specs = lc {_kind, {{f, a}, _spec}} = spec inlist beam_specs(module),
f == function and a == arity do
print_spec(spec)
spec
end
if specs == [] do
IO.puts IO.ANSI.escape("%{red}No specs for #{inspect module}.#{function} have been found")
end
:ok
end
defp beam_specs(module) do
specs = Enum.map(Kernel.Typespec.beam_specs(module), {:spec, &1})
callbacks = Enum.map(Kernel.Typespec.beam_callbacks(module), {:callback, &1})
List.concat(specs, callbacks)
end
defp print_type({ kind, type }) do
ast = Kernel.Typespec.type_to_ast(type)
IO.puts IO.ANSI.escape("%{yellow}@#{kind} #{Macro.to_binary(ast)}")
true
end
defp print_spec({kind, { { name, _arity }, specs }}) do
Enum.each specs, fn(spec) ->
binary = Macro.to_binary Kernel.Typespec.spec_to_ast(name, spec)
IO.puts IO.ANSI.escape("%{yellow}@#{kind} #{binary}")
end
true
end
end
+2 -2
View File
@@ -4,7 +4,7 @@ defmodule IEx.Server do
def start(config) do
IO.puts "Interactive Elixir (#{System.version}) - press Ctrl+C to exit (type h() ENTER for help)"
Process.put :iex_history, []
{ _, _, scope } = :elixir.eval('import IEx.Helpers', [], 0, config.scope)
{ _, _, scope } = :elixir.eval('require IEx.Helpers', [], 0, config.scope)
do_loop(config.scope(scope))
end
@@ -64,7 +64,7 @@ defmodule IEx.Server do
end
defp io_put(result) do
IO.inspect :stdio, result, IEx.inspect_opts
IO.puts :stdio, IO.ANSI.escape("%{yellow}#{inspect(result, IEx.inspect_opts)}")
end
defp io_error(result) do
-5
View File
@@ -54,11 +54,6 @@ defmodule IEx.AutocompleteTest do
assert expand('System.ve') == {:yes, 'rsion', []}
end
test :elixir_function_nodoc do
{:yes, '', list} = expand('Keyword.get')
assert 'get/2' inlist list
end
test :elixir_function_completion_with_arity do
assert expand('String.printable?') == {:yes, '', ['printable?/1']}
assert expand('String.printable?/') == {:yes, '', ['printable?/1']}

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