Compare commits

...
237 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
José Valim 90ff71b0cc Release v0.8.1 2013-02-17 21:28:17 -07:00
José Valim 7dd0aabbe2 Deprecate ExUnit.after_spawn callbacks 2013-02-17 20:46:58 -07:00
José Valim 658d7c7913 ExUnit callbacks must return :ok or { :ok, data } 2013-02-17 20:43:01 -07:00
José Valim 1f491dfbe2 Add support to case templates 2013-02-17 20:43:01 -07:00
José Valim 8ca4517563 Merge pull request #847 from jeremyjh/apply-doc-fix
correct apply/3 example
2013-02-17 18:38:51 -08:00
Jeremy Huffman 4e8690cd8e correct apply/3 example 2013-02-17 21:22:11 -05:00
José Valim 8f57b4c9af Add metadata support to ExUnit
Defining setup/teardown/test explicitly is now deprecated.
They should now be defined via their respective macros:

    def setup do
      ...
    end

Now becomes:

    setup do
      ...
    end

When defined using the new format, setup callbacks can
return metadata:

    setup do
      [do_this: :do_that]
    end

And now the test macro can access this metadata:

    test "check metadata", data do
      assert data[:do_this] == :do_that
    end
2013-02-17 18:23:31 -07:00
José Valim 19d402b7d5 Disable native compilation when on_load attributes is present
This is a work around an Erlang bug, see #845 for more information.
Closes #845.
2013-02-17 12:22:08 -07:00
José Valim cf6751a3c8 Merge pull request #844 from michaelklishin/issue843
Make Mix support --help, -h and -help
2013-02-17 09:20:54 -08:00
Michael Klishin 42a0719c8a Use fn clause matching
This does not perform downcasing and thus makes BASIC refugees
unhappy, but @yrashk suggests we can live with that.
2013-02-17 21:10:40 +04:00
Michael Klishin 4527c1e849 Make Mix CLI support --help, -h and -help, fixes #843 2013-02-17 21:05:38 +04:00
José Valim 5557e620ff Backwards incompatible change on before_compile and after_compile
Both callbacks now receive the environment as
first argument instead of the module.
2013-02-16 19:57:14 -07:00
José Valim 683153ef05 Switches are now overridden by default but can be kept in order if chosen, closes #782 2013-02-16 19:17:35 -07:00
José Valim 8a77e878ab Fix bug when running mix on directories containig space, closes #840 2013-02-16 18:38:29 -07:00
José Valim a19a4b893d Update CHANGELOG 2013-02-16 17:46:10 -07:00
José Valim b8689da1f5 Small refactoring on Mix tasks 2013-02-16 17:44:27 -07:00
José Valim a05592fdd9 Only accept the IO device on inspect when we have three args 2013-02-16 16:43:41 -07:00
José Valim 3147df88fa Merge pull request #776 from liveforeverx/compile-erl-new
WiP for compiling of erlang/leex/yecc source files
2013-02-16 15:43:32 -08:00
José Valim 1946feb4e1 Merge pull request #842 from happy4crazy/doc-fixes
Kernel doc fixes
2013-02-15 14:27:24 -08:00
Alan O'Donnell a0101bc056 Doc typos, misc. style cleanup 2013-02-15 17:08:22 -05:00
Yurii Rashkovskii 129725a168 Suppress minor warning (unused variable) in hash_dict.ex 2013-02-15 13:02:16 -08:00
José Valim e93613bc05 Merge pull request #841 from happy4crazy/iex-helpers
Tiny typo in IEx.Helpers.h/1
2013-02-15 12:57:20 -08:00
Alan O'Donnell 7b51500a60 Fix typo in IEx.Helpers.h/1 2013-02-15 15:01:04 -05:00
Alan O'Donnell f6058afb26 Misc. documentation cleanup 2013-02-15 14:59:25 -05:00
Alan O'Donnell 67b5236874 Fix invalid bit syntax in docs. 2013-02-15 13:07:03 -05:00
José Valim 1e18d8e08e Default mix task should be run, closes #834 2013-02-14 15:00:30 -07:00
José Valim 9ebce19f36 Push a test case related to the nested vars in clauses 2013-02-14 10:46:20 -07:00
José Valim fb82b0c679 Pick latest variable in between clauses instead of picking a random one 2013-02-14 09:43:54 -07:00
Yurii Rashkovskii 9dc7f44049 Drop HashDict.t type and spec HashDict.new with Dict.t 2013-02-12 12:39:23 -08:00
Yurii Rashkovskii 5e2c6e14be Add HashDict.t type 2013-02-12 11:36:55 -08:00
José Valim e896e311c1 Merge pull request #833 from kimshrier/process_self_deprecations
Change references or invocations of Process.self to Kernel.self
2013-02-09 14:58:47 -08:00
Kim Shrier ced13c159f Change references or invocations of Process.self to Kernel.self 2013-02-09 15:41:09 -07:00
José Valim 5fe1544ff3 Deprecate Process.self in favor of Kernel.self to make imports easier, closes #832 2013-02-09 12:10:15 -07:00
José Valim 96c7f68a62 Merge pull request #825 from khia/node_connect
Node.connect/1
2013-02-08 10:36:00 -08:00
Yurii Rashkovskii d0dc9f5c2c Minor indentation fix 2013-02-06 10:32:26 -08:00
Yurii Rashkovskii 8a130978b2 Remove some garbage 2013-02-05 11:20:04 -08:00
Yurii Rashkovskii 71e2252064 Unpack __block__ in type specs
Fixes #827
2013-02-05 11:18:49 -08:00
ILYA Khlopotov ddc9733cf2 Add Node.connect/1 method 2013-02-04 12:34:11 -08:00
ILYA Khlopotov 9bcc4be3f5 Fix typo in typespec of Node.disconnect 2013-02-04 12:32:29 -08:00
José Valim f66206b4f3 s/on/in 2013-02-04 08:38:52 -07:00
José Valim 28211b2714 Ensure aliases expansion also works for implicit aliases 2013-02-03 21:57:39 -07:00
Dima 93f1bb920b developing in progress for:
* check erlang files and dependencies before compiling
* error output for erlang compiling
* compiling of leex and yecc
2013-02-03 23:36:25 +01:00
José Valim 109c07e757 Improve aliases hygiene by using metadata 2013-02-03 15:12:38 -07:00
José Valim eb36c2b288 Improve docs for File 2013-02-03 14:03:59 -07:00
José Valim 3537bbbbf4 Refactor aliases handling 2013-02-03 13:07:20 -07:00
José Valim cd51c80362 Tidy up and clean up quote API, closes #814 2013-02-03 12:14:18 -07:00
Yurii Rashkovskii a1a576e9d1 Implement as_boolean/1 type
Closes #798
2013-02-03 10:40:35 -08:00
José Valim 1dc4bf0989 Use tag from VERSION on package.exs 2013-02-02 21:48:06 -07:00
Yurii Rashkovskii 1812e652db Minor IO.ANSI.terminal?/0 documentation improvement 2013-02-02 15:13:47 -08:00
Yurii Rashkovskii 7f73cb3128 Add missing specification for IO.ANSI.terminal?/1 2013-02-02 14:37:41 -08:00
Yurii Rashkovskii 0123edd8a3 Better error reporting for invalid ANSI escape sequences 2013-02-02 14:25:10 -08:00
Yurii Rashkovskii 87af330589 Don't emit ANSI sequence escape codes in shell in OTP prior to R16 2013-02-02 14:10:27 -08:00
José Valim 3ae634a13c Update CHANGELOG 2013-02-02 15:08:14 -07:00
José Valim de3bd8ec86 Expand current module macros with Macro.expand, closes #815 2013-02-02 15:04:30 -07:00
Yurii Rashkovskii 3fea472cc2 IO.ANSI.escape shouldn't emit a reset sequence if no ANSI sequence was emitted
Closes #807
2013-02-02 13:02:51 -08:00
Yurii Rashkovskii 7cb18091d0 Better error reporting for invalid type specifications
Closes #818
2013-02-02 12:47:17 -08:00
José Valim 8994feb0a3 Fix unused vars warnings 2013-02-02 13:33:00 -07:00
José Valim f4aed8e873 Reduce vars footprint in case/receive clauses 2013-02-02 13:20:59 -07:00
José Valim 008ee62f8b Take into account Erlang may pass line numbers as none 2013-02-02 09:00:43 -07:00
José Valim d543650f70 Merge pull request #813 from alexrp/master
Fix #812: Write a trailing newline character to the Mix lockfile.
2013-01-30 21:16:16 -08:00
alexrp febf5e46b6 Fix #812: Write a trailing newline character to the Mix lockfile. 2013-01-31 05:20:49 +01:00
José Valim 7196543060 Improve how aliases for defmodule are generated 2013-01-30 16:43:29 -07:00
José Valim fade5452e1 R16A fixes 2013-01-30 11:15:40 -07:00
Yurii Rashkovskii 9e46866851 Allow building Elixir on R16 2013-01-30 09:41:32 -08:00
José Valim 1dd1f2b213 Use hardcoded version on elixir.app.src for now
Related to #808
2013-01-30 10:04:23 -07:00
José Valim 3137bfc614 Move bootstrap code for IEx to IEx.CLI 2013-01-29 12:30:09 -07:00
José Valim 4d3d61e173 Deprecate :local.(args) syntax 2013-01-28 21:21:18 -07:00
Yurii Rashkovskii 2e66218f58 Minor IO.ANSI documentation fix 2013-01-28 12:22:05 -08:00
José Valim 3a1fbde95d Remove deprecated branches 2013-01-28 13:08:18 -07:00
José Valim 088978fa04 Update CHANGELOG 2013-01-28 13:04:09 -07:00
José Valim 249f83e82b IO should default to the group leader 2013-01-28 13:03:08 -07:00
José Valim 608f0a3dcb Allow mix projects with just one letter, closes #799 2013-01-28 12:47:09 -07:00
José Valim d8aa534e3a Change IO.ANSI escape from #[] to %{} 2013-01-28 12:45:14 -07:00
José Valim c2df5fa5ff Move ansi control to Mix.Shell 2013-01-28 12:32:01 -07:00
José Valim fee8836366 Merge pull request #800 from yrashk/mix-colors
Initial ANSIfication of mix
2013-01-28 11:18:20 -08:00
José Valim 35a43afb22 Define a sequence as a key, value pair 2013-01-28 12:13:47 -07:00
Yurii Rashkovskii c359f212f8 Initial ANSIfication of mix 2013-01-28 11:10:19 -08:00
José Valim 0f2dd3f7d4 Merge pull request #775 from yrashk/io-ansi
Initial IO.ANSI implementation (based on https://github.com/yrashk/ansiex)
2013-01-28 10:46:29 -08:00
José Valim 20942ee329 Better support for Unicode lists 2013-01-28 11:43:25 -07:00
José Valim d2f76453d6 Compile before release targets 2013-01-28 10:43:29 -07:00
Yurii Rashkovskii 50d375e33b IO.ANSI.escape implies terminating #[reset] now and IO.ANSI.escape_fragment has been introduced 2013-01-20 10:36:31 -08:00
Yurii Rashkovskii 119d48e093 Improve IO.ANSI test case so it can be run on non-terminals correctly 2013-01-19 20:22:16 -08:00
Yurii Rashkovskii 6960f856f4 Put proper IO.ANSI tests in 2013-01-19 20:17:59 -08:00
Yurii Rashkovskii eb11254369 Introduce IO.ANSI.escape/1,2 functions 2013-01-19 19:38:14 -08:00
Yurii Rashkovskii f686b08e5e Add a pre-R16 warning about ANSI escape sequences in iex/erl 2013-01-19 15:35:08 -08:00
Yurii Rashkovskii 80bd148852 Initial IO.ANSI implementation (based on https://github.com/yrashk/ansiex) 2013-01-19 15:28:35 -08:00
166 changed files with 5950 additions and 3035 deletions
+2
View File
@@ -6,6 +6,8 @@ notifications:
- jose.valim@plataformatec.com.br
- yrashk@gmail.com
otp_release:
- R16B
- R15B03
- R15B02
- R15B01
- R15B
+67
View File
@@ -1,3 +1,70 @@
# 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
* [ExUnit] Tests can now receive metadata set on setup/teardown callbacks
* [ExUnit] Add support to ExUnit.CaseTemplate to share callbacks in between test cases
* [IO] Add `IO.ANSI` to make it easy to write ANSI escape codes
* [Kernel] Better support for Unicode lists
* [Kernel] Reduce variables footprint in `case`/`receive` clauses
* [Kernel] Disable native compilation when on_load attributes is present to work around an Erlang bug
* [Macro] `Macro.expand` also considers macros from the current `__ENV__` module
* [Mix] Improve support for compilation of `.erl` files
* [Mix] Add support for compilation of `.yrl` and `.xrl` files
* [OptionParser] Switches are now overridden by default but can be kept in order if chosen
* [Typespec] Better error reporting for invalid typespecs
* bug fix
* [Mix] Allow Mix projects to be generated with just one letter
* backwards incompatible changes
* [Kernel] `before_compile` and `after_compile` callbacks now receive the environment as first argument instead of the module
* deprecations
* [ExUnit] Explicitly defined test/setup/teardown functions are deprecated
* [Kernel] Tidy up and clean `quote` API
* [Kernel] Old `:local.(args)` syntax is deprecated
* [Process] `Process.self` is deprecated in favor `Kernel.self`
# v0.8.0 (2013-01-28)
* enhancements
+11 -7
View File
@@ -46,7 +46,7 @@ erlang:
# file, then mix and then compile eex fully
elixir: kernel unicode lib/eex/ebin/Elixir-EEx.beam mix ex_unit eex iex
kernel: $(KERNEL)
kernel: $(KERNEL) VERSION
$(KERNEL): lib/elixir/lib/*.ex lib/elixir/lib/*/*.ex
@ if [ ! -f $(KERNEL) ]; then \
echo "==> bootstrap (compile)"; \
@@ -93,23 +93,23 @@ clean:
#==> Release tasks
docs:
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:
release_zip: compile
rm -rf v$(VERSION).zip
zip -9 -r v$(VERSION).zip bin CHANGELOG.md LEGAL lib/*/ebin LICENSE README.md rel VERSION
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:
release_erl: compile
@ rm -rf rel/elixir
@ cd rel && ../rebar generate
@@ -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)"
+7 -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,15 +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
* CHANGELOG
* src/elixir.app.src
+1 -1
View File
@@ -1 +1 @@
0.8.0
0.8.2
+5 -5
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
@@ -69,10 +68,11 @@ SELF=$(readlink_f "$0")
SCRIPT_PATH=$(dirname "$SELF")
if [ -f "$HOME/.elixirrc" ]; then . "$HOME/.elixirrc"; fi
if [ "$ELIXIR_NO_CLI" != "1" ]; then ERL="$ERL -s elixir start_cli"; fi
if [ -f "$SCRIPT_PATH/../releases/RELEASES" ] && [ -f "$SCRIPT_PATH/erl" ]
then
exec "$SCRIPT_PATH"/erl -env ERL_LIBS $ERL_LIBS:"$SCRIPT_PATH/../lib" -boot elixir -noshell $ELIXIR_ERL_OPTS $ERL -s elixir start_cli -extra "$@"
exec "$SCRIPT_PATH"/erl -env ERL_LIBS $ERL_LIBS:"$SCRIPT_PATH/../lib" -boot elixir -noshell $ELIXIR_ERL_OPTS $ERL -extra "$@"
else
exec erl -env ERL_LIBS $ERL_LIBS:"$SCRIPT_PATH/../lib" -noshell $ELIXIR_ERL_OPTS $ERL -s elixir start_cli -extra "$@"
exec erl -env ERL_LIBS $ERL_LIBS:"$SCRIPT_PATH/../lib" -noshell $ELIXIR_ERL_OPTS $ERL -extra "$@"
fi
+7 -5
View File
@@ -1,9 +1,11 @@
@echo off
if "%*" == "" (
goto documentation
set argc=0
for %%x in (%*) do set /A argc+=1
if %argc% == 0 (
goto documentation
) else (
goto run
)
goto run
)
:documentation
echo Usage: %~nx0 [options] [.exs file] [data]
echo.
@@ -20,4 +22,4 @@ echo ** Options marked with (*) can be given more than once
echo ** Options given after the .exs file or -- are passed down to the executed code
echo ** Options can be passed to the erlang runtime using ELIXIR_ERL_OPTS
:run
erl -env ERL_LIBS %ERL_LIBS%;"%~dp0\..\lib" -noshell %ELIXIR_ERL_OPTS% -s elixir start_cli -extra %*
erl -env ERL_LIBS %ERL_LIBS%;"%~dp0\..\lib" -noshell %ELIXIR_ERL_OPTS% -s elixir start_cli -extra %*
+7 -5
View File
@@ -1,9 +1,11 @@
@echo off
if "%*" == "" (
goto documentation
set argc=0
for %%x in (%*) do set /A argc+=1
if %argc% == 0 (
goto documentation
) else (
goto run
)
goto run
)
:documentation
echo Usage: %~nx0 [elixir switches] [compiler switches] [.ex files]
echo.
@@ -16,4 +18,4 @@ echo ** Options marked with (*) can be given more than once
echo ** Options given after -- are passed down to the executed code
echo ** Options can be passed to the erlang runtime using ELIXIR_ERL_OPTS" >&2
:run
call "%~dp0\elixir.bat" --compile %*
call "%~dp0\elixir.bat" --compile %*
+23 -1
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")"
@@ -11,4 +33,4 @@ readlink_f () {
SELF=$(readlink_f "$0")
SCRIPT_PATH=$(dirname "$SELF")
exec "$SCRIPT_PATH"/elixir --no-halt --erl "-user Elixir-IEx-CLI" "$@"
ELIXIR_NO_CLI=1 exec "$SCRIPT_PATH"/elixir --no-halt --erl "-user Elixir-IEx-CLI" "$@"
+2 -1
View File
@@ -1 +1,2 @@
call "%~dp0\elixir.bat" "%~dp0\mix" %*
@echo off
call "%~dp0\elixir.bat" "%~dp0\mix" %*
+1 -1
View File
@@ -191,7 +191,7 @@ defmodule EEx do
def function_from_quoted(module, kind, name, args, source, info) do
args = Enum.map args, fn arg -> { arg, [], nil } end
quote = quote do
unquote(kind).(unquote(name).(unquote_splicing(args)), do: unquote(source))
unquote(kind)(unquote(name)(unquote_splicing(args)), do: unquote(source))
end
Module.eval_quoted module, quote, [], info
end
+1 -1
View File
@@ -112,7 +112,7 @@ defmodule EEx.Tokenizer do
else
:middle_expr
end
error ->
_error ->
:middle_expr
end
end
+10 -8
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
@@ -25,17 +24,21 @@
counter=[], %% a counter for the variables defined
local=nil, %% the scope to evaluate local functions against
scheduled=[], %% scheduled modules to be loaded
file, %% the current scope filename
macro_aliases=[], %% keep aliases defined inside a macro
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,
var_context=nil,
expand_aliases=true,
expand_imports=true,
vars_hygiene=nil,
aliases_hygiene=true,
imports_hygiene=true,
unquote=true
}).
@@ -51,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
+65 -61
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]"
"""
@@ -261,8 +274,8 @@ defimpl Binary.Inspect, for: List do
def inspect(thing, opts) do
cond do
printable?(thing) ->
escape(list_to_binary(thing), ?')
:io_lib.printable_list(thing) ->
escape(:unicode.characters_to_binary(thing), ?')
Keyword.keyword?(thing) ->
"[" <> join_keywords(thing, opts) <> "]"
true ->
@@ -282,19 +295,6 @@ defimpl Binary.Inspect, for: List do
other -> other
end
end
## printable?
defp printable?([c|cs]) when is_integer(c) and c in 32..126 do
printable?(cs)
end
defp printable?([c|cs]) when c in [?\n, ?\r, ?\t, ?\v, ?\b, ?\f, ?\e, ?\a] do
printable?(cs)
end
defp printable?([]), do: true
defp printable?(_), do: false
end
defimpl Binary.Inspect, for: Tuple do
@@ -306,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\\\"]"
"""
@@ -385,7 +387,8 @@ defimpl Binary.Inspect, for: Number do
## Examples
inspect(1) #=> "1"
iex> inspect(1)
"1"
"""
@@ -404,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
-274
View File
@@ -1,274 +0,0 @@
defmodule Dict.Common do
@moduledoc false
defmacro __using__(_) do
quote do
@behavior Dict
@doc """
Creates a new empty dict.
"""
def new do
IO.puts "#{inspect __MODULE__} is deprecated, please use HashDict instead"
Exception.print_stacktrace
empty(nil)
end
@doc """
Creates a new dict from a list of pairs.
## Examples
#{inspect(__MODULE__)}.new [{:b,1},{:a,2}]
#=> [a: 1, b: 2]
"""
def new(pairs) do
IO.puts "#{inspect __MODULE__} is deprecated, please use HashDict instead"
Exception.print_stacktrace
Enum.reduce pairs, new, fn { k, v }, dict ->
put(dict, k, v)
end
end
@doc """
Creates a new dict from a list of elements with the
help of the transformation function.
## Examples
#{inspect(__MODULE__)}.new ["a", "b"], fn x -> {x, x} end
#=> ["a": "a", "b": "b"]
"""
def new(list, transform) when is_function(transform) do
IO.puts "#{inspect __MODULE__} is deprecated, please use HashDict instead"
Exception.print_stacktrace
Enum.reduce list, new(), fn i, dict ->
{ k, v } = transform.(i)
put(dict, k, v)
end
end
end
end
end
defmodule OrdDict do
@moduledoc """
This module implements a dictionary type that stores items
as a list of tuples. It is a simple wrapper around
[Erlang's orddict module](http://www.erlang.org/doc/man/orddict.html)
and exposed via the `Dict` module.
Check the `Dict` module for examples and documentation.
"""
use Dict.Common
defmacrop dict(data) do
quote do
{ OrdDict, unquote(data) }
end
end
@doc false
def keys(dict(data)) do
lc { k, _ } inlist data, do: k
end
@doc false
def values(dict(data)) do
lc { _, v } inlist data, do: v
end
@doc false
def size(dict(data)) do
length(data)
end
@doc false
def has_key?(dict(data), key) do
:orddict.is_key key, data
end
@doc false
def get(dict(data), key, default // nil) do
case :orddict.find(key, data) do
{:ok, value} -> value
:error -> default
end
end
@doc false
def get!(dict(data), key) do
case :orddict.find(key, data) do
{:ok, value} -> value
:error -> raise(KeyError, key: key)
end
end
@doc false
def put(dict(data), key, value) do
dict(:orddict.store key, value, data)
end
@doc false
def put_new(dict(data), key, value) do
dict(:orddict.update key, fn(x) -> x end, value, data)
end
@doc false
def delete(dict(data), key) do
dict(:orddict.erase key, data)
end
@doc false
def merge(d1, d2, fun // fn(_k, _v1, v2) -> v2 end)
def merge(dict(d1), dict(d2), fun) do
dict(:orddict.merge fun, d1, d2)
end
def merge(dict(_) = d1, d2, fun) do
merge(d1, new(d2), fun)
end
@doc false
def update(dict(data), key, fun) do
dict(:orddict.update key, fun, data)
end
@doc false
def update(dict(data), key, initial, fun) do
dict(:orddict.update key, fun, initial, data)
end
@doc false
def empty(_) do
dict([])
end
@doc false
def to_list(dict(data)) do
data
end
end
defimpl Enum.Iterator, for: OrdDict do
def iterator({ OrdDict, data }), do: data
def count({ OrdDict, data }), do: length(data)
end
defimpl Access, for: OrdDict do
def access(dict, key), do: OrdDict.get(dict, key, nil)
end
defmodule Binary.Dict do
@moduledoc """
This module implements a dictionary that forces the keys to be
converted to binaries on insertion. Currently it is implemented
using an `OrdDict`, but this may change in the future.
Check the `Dict` module for examples and documentation.
"""
use Dict.Common
defmacrop dict(data) do
quote do
{ Binary.Dict, unquote(data) }
end
end
@doc false
def keys(dict(data)) do
lc { k, _ } inlist data, do: k
end
@doc false
def values(dict(data)) do
lc { _, v } inlist data, do: v
end
@doc false
def size(dict(data)) do
length(data)
end
@doc false
def has_key?(dict(data), key) do
:orddict.is_key to_binary(key), data
end
@doc false
def get(dict(data), key, default // nil) do
case :orddict.find(to_binary(key), data) do
{:ok, value} -> value
:error -> default
end
end
@doc false
def get!(dict(data), key) do
case :orddict.find(to_binary(key), data) do
{:ok, value} -> value
:error -> raise(KeyError, key: key)
end
end
@doc false
def put(dict(data), key, value) do
dict(:orddict.store to_binary(key), value, data)
end
@doc false
def put_new(dict(data), key, value) do
dict(:orddict.update to_binary(key), fn(x) -> x end, value, data)
end
@doc false
def delete(dict(data), key) do
dict(:orddict.erase to_binary(key), data)
end
@doc false
def merge(d1, d2, fun // fn(_k, _v1, v2) -> v2 end)
def merge(dict(d1), dict(d2), fun) do
dict(:orddict.merge fun, d1, d2)
end
def merge(dict(_) = d1, d2, fun) do
merge(d1, new(d2), fun)
end
@doc false
def update(dict(data), key, fun) do
dict(:orddict.update to_binary(key), fun, data)
end
@doc false
def update(dict(data), key, initial, fun) do
dict(:orddict.update to_binary(key), fun, initial, data)
end
@doc false
def empty(_) do
dict([])
end
@doc false
def to_list(dict(data)) do
data
end
end
defimpl Enum.Iterator, for: Binary.Dict do
def iterator({ Binary.Dict, data }), do: data
def count({ Binary.Dict, data }), do: length(data)
end
defimpl Access, for: Binary.Dict do
def access(dict, key), do: Binary.Dict.get(dict, key, nil)
end
+145 -113
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,21 +81,23 @@ 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
@spec all?(t, (element -> boolean)) :: boolean
@spec all?(t, (element -> as_boolean(term))) :: boolean
def all?(collection, fun // fn(x) -> x end)
@@ -116,21 +120,23 @@ 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
@spec any?(t, (element -> boolean)) :: boolean
@spec any?(t, (element -> as_boolean(term))) :: boolean
def any?(collection, fun // fn(x) -> x end)
@@ -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
@@ -191,7 +201,7 @@ defmodule Enum do
@doc """
Counts for how many items the function returns true.
"""
@spec count(t, (element -> boolean)) :: non_neg_integer
@spec count(t, (element -> as_boolean(term))) :: non_neg_integer
def count(collection, fun) when is_list(collection) do
do_count(collection, fun)
end
@@ -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,10 +254,10 @@ 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 -> boolean)) :: list
@spec drop_while(t, (element -> as_boolean(term))) :: list
def drop_while(collection, fun) when is_list(collection) do
do_drop_while(collection, fun)
end
@@ -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,11 +334,11 @@ 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 -> boolean)) :: list
@spec filter(t, (element -> as_boolean(term))) :: list
def filter(collection, fun) when is_list(collection) do
lc item inlist collection, fun.(item), do: item
end
@@ -342,11 +357,11 @@ 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 -> boolean), (element -> element)) :: list
@spec filter_map(t, (element -> as_boolean(term)), (element -> element)) :: list
def filter_map(collection, filter, mapper) when is_list(collection) do
lc item inlist collection, filter.(item), do: mapper.(item)
end
@@ -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
@@ -497,7 +517,7 @@ defmodule Enum do
def join(collection, joiner // "")
def join(collection, joiner) when is_list(joiner) do
binary_to_list join(collection, list_to_binary(joiner))
:unicode.characters_to_list join(collection, :unicode.characters_to_binary(joiner))
end
def join(collection, joiner) when is_list(collection) and is_binary(joiner) do
@@ -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,15 +801,14 @@ 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 -> boolean)) :: {list, list}
@spec split_while(t, (element -> as_boolean(term))) :: {list, list}
def split_while(collection, fun) when is_list(collection) do
do_split_while(collection, fun, [])
end
@@ -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,11 +866,11 @@ 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 -> boolean)) :: list
@spec take_while(t, (element -> as_boolean(term))) :: list
def take_while(collection, fun) when is_list(collection) do
do_take_while(collection, fun)
end
@@ -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
-5
View File
@@ -96,11 +96,6 @@ defmodule Exception do
"""
def format_stacktrace(trace // nil)
def format_stacktrace(trace) when is_tuple(trace) do
IO.puts "Exception.format_stacktrace is deprecated, please use format_entry instead"
print_stacktrace
end
def format_stacktrace(trace) do
trace = trace || try do
throw(:stacktrace)
+53 -105
View File
@@ -62,7 +62,7 @@ defmodule File do
In order to write and read files, one must use the
functions in the IO module. By default, a file is
opened on binary mode which requires the functions
opened in binary mode which requires the functions
`IO.binread`, `IO.binwrite` and `IO.binreadline` to
interact with the file. A developer may pass `:utf8`
as an option when opening the file and then all other
@@ -105,90 +105,6 @@ defmodule File do
alias :filename, as: FN
alias :filelib, as: FL
@doc false
def expand_path(path) do
IO.puts "File.expand_path is deprecated, please use Path.expand instead"
Exception.print_stacktrace
Path.expand(path)
end
@doc false
def expand_path(path, relative_to) do
IO.puts "File.expand_path is deprecated, please use Path.expand instead"
Exception.print_stacktrace
Path.expand(path, relative_to)
end
@doc false
def basename(path) do
IO.puts "File.basename is deprecated, please use Path.basename instead"
Exception.print_stacktrace
FN.basename(path)
end
@doc false
def basename(path, extension) do
IO.puts "File.basename is deprecated, please use Path.basename instead"
Exception.print_stacktrace
FN.basename(path, extension)
end
@doc false
def dirname(path) do
IO.puts "File.dirname is deprecated, please use Path.dirname instead"
Exception.print_stacktrace
FN.dirname(path)
end
@doc false
def extname(path) do
IO.puts "File.extname is deprecated, please use Path.extname instead"
Exception.print_stacktrace
FN.extension(path)
end
@doc false
def rootname(path) do
IO.puts "File.rootname is deprecated, please use Path.rootname instead"
Exception.print_stacktrace
FN.rootname(path)
end
@doc false
def rootname(path, extension) do
IO.puts "File.rootname is deprecated, please use Path.rootname instead"
Exception.print_stacktrace
FN.rootname(path, extension)
end
@doc false
def join(paths) do
IO.puts "File.join is deprecated, please use Path.join instead"
Exception.print_stacktrace
FN.join(paths)
end
@doc false
def join(left, right) do
IO.puts "File.join is deprecated, please use Path.join instead"
Exception.print_stacktrace
FN.join(left, right)
end
@doc false
def split(path) do
IO.puts "File.split is deprecated, please use Path.split instead"
Exception.print_stacktrace
FN.split(path)
end
@doc false
def wildcard(glob) do
IO.puts "File.wildcard is deprecated, please use Path.wildcard instead"
Exception.print_stacktrace
Path.wildcard(glob)
end
@doc """
Returns true if the path is a regular file.
@@ -253,7 +169,7 @@ defmodule File do
case mkdir(path) do
:ok -> :ok
{ :error, reason } ->
raise File.Error, reason: reason, action: "make directory", path: to_binary(path)
raise File.Error, reason: reason, action: "make directory", path: :unicode.characters_to_binary(path)
end
end
@@ -278,7 +194,7 @@ defmodule File do
case mkdir_p(path) do
:ok -> :ok
{ :error, reason } ->
raise File.Error, reason: reason, action: "make directory (with -p)", path: to_binary(path)
raise File.Error, reason: reason, action: "make directory (with -p)", path: :unicode.characters_to_binary(path)
end
end
@@ -311,7 +227,7 @@ defmodule File do
{ :ok, binary } ->
binary
{ :error, reason } ->
raise File.Error, reason: reason, action: "read file", path: to_binary(path)
raise File.Error, reason: reason, action: "read file", path: :unicode.characters_to_binary(path)
end
end
@@ -346,7 +262,7 @@ defmodule File do
case stat(path, opts) do
{:ok, info} -> info
{:error, reason} ->
raise File.Error, reason: reason, action: "read file stats", path: to_binary(path)
raise File.Error, reason: reason, action: "read file stats", path: :unicode.characters_to_binary(path)
end
end
@@ -355,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 """
@@ -366,7 +282,7 @@ defmodule File do
case write_stat(path, stat, opts) do
:ok -> :ok
{ :error, reason } ->
raise File.Error, reason: reason, action: "write file stats", path: to_binary(path)
raise File.Error, reason: reason, action: "write file stats", path: :unicode.characters_to_binary(path)
end
end
@@ -389,7 +305,7 @@ defmodule File do
case touch(path, time) do
:ok -> :ok
{ :error, reason } ->
raise File.Error, reason: reason, action: "touch", path: to_binary(path)
raise File.Error, reason: reason, action: "touch", path: :unicode.characters_to_binary(path)
end
end
@@ -421,7 +337,7 @@ defmodule File do
{ :ok, bytes_count } -> bytes_count
{ :error, reason } ->
raise File.CopyError, reason: reason, action: "copy",
source: to_binary(source), destination: to_binary(destination)
source: :unicode.characters_to_binary(source), destination: :unicode.characters_to_binary(destination)
end
end
@@ -472,7 +388,7 @@ defmodule File do
:ok -> :ok
{ :error, reason } ->
raise File.CopyError, reason: reason, action: "copy recursively",
source: to_binary(source), destination: to_binary(destination)
source: :unicode.characters_to_binary(source), destination: :unicode.characters_to_binary(destination)
end
end
@@ -546,7 +462,7 @@ defmodule File do
{ :ok, files } -> files
{ :error, reason } ->
raise File.CopyError, reason: reason, action: "copy recursively",
source: to_binary(source), destination: to_binary(destination)
source: :unicode.characters_to_binary(source), destination: :unicode.characters_to_binary(destination)
end
end
@@ -651,7 +567,7 @@ defmodule File do
case F.write_file(path, content, modes) do
:ok -> :ok
{ :error, reason } ->
raise File.Error, reason: reason, action: "write to file", path: to_binary(path)
raise File.Error, reason: reason, action: "write to file", path: :unicode.characters_to_binary(path)
end
end
@@ -688,7 +604,7 @@ defmodule File do
case rm(path) do
:ok -> :ok
{ :error, reason } ->
raise File.Error, reason: reason, action: "remove file", path: to_binary(path)
raise File.Error, reason: reason, action: "remove file", path: :unicode.characters_to_binary(path)
end
end
@@ -716,7 +632,7 @@ defmodule File do
case rmdir(path) do
:ok -> :ok
{ :error, reason } ->
raise File.Error, reason: reason, action: "remove directory", path: to_binary(path)
raise File.Error, reason: reason, action: "remove directory", path: :unicode.characters_to_binary(path)
end
end
@@ -789,14 +705,20 @@ defmodule File do
case rm_rf(path) do
{ :ok, files } -> files
{ :error, reason } ->
raise File.Error, reason: reason, action: "remove files and directories recursively from", path: to_binary(path)
raise File.Error, reason: reason, action: "remove files and directories recursively from", path: :unicode.characters_to_binary(path)
end
end
@doc """
Opens the given `path` according to the given list of modes.
By default, the file is opened in read mode, as a binary with utf8 encoding.
In order to write and read files, one must use the functions
in the IO module. By default, a file is opened in binary mode
which requires the functions `IO.binread`, `IO.binwrite` and
`IO.binreadline` to interact with the file. A developer may pass
`:utf8` as an option when opening the file and then all other
functions from IO are available, since they work directly with
Unicode data.
The allowed modes:
@@ -873,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)
@@ -898,7 +820,7 @@ defmodule File do
case open(path, modes) do
{ :ok, device } -> device
{ :error, reason } ->
raise File.Error, reason: reason, action: "open", path: to_binary(path)
raise File.Error, reason: reason, action: "open", path: :unicode.characters_to_binary(path)
end
end
@@ -910,7 +832,7 @@ defmodule File do
case open(path, modes, function) do
{ :ok, device } -> device
{ :error, reason } ->
raise File.Error, reason: reason, action: "open", path: to_binary(path)
raise File.Error, reason: reason, action: "open", path: :unicode.characters_to_binary(path)
end
end
@@ -953,7 +875,7 @@ defmodule File do
case F.set_cwd(path) do
:ok -> :ok
{ :error, reason } ->
raise File.Error, reason: reason, action: "set current working directory to", path: to_binary(path)
raise File.Error, reason: reason, action: "set current working directory to", path: :unicode.characters_to_binary(path)
end
end
@@ -975,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.
@@ -1006,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
+42 -36
View File
@@ -33,26 +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
@@ -62,6 +63,7 @@ defmodule HashDict do
@doc """
Creates a new empty dict.
"""
@spec new :: Dict.t
def new do
ordered()
end
@@ -72,9 +74,10 @@ 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
def new(pairs) do
Enum.reduce pairs, ordered(), fn { k, v }, dict ->
put(dict, k, v)
@@ -88,9 +91,10 @@ 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
def new(list, transform) when is_function(transform) do
Enum.reduce list, new(), fn i, dict ->
{ k, v } = transform.(i)
@@ -123,7 +127,7 @@ defmodule HashDict do
case dict_put(dict, key, { :update, nil, fun }) do
{ dict, 0 } ->
dict
{ dict, 1 } ->
{ _dict, 1 } ->
raise KeyError, key: key
end
end
@@ -184,14 +188,14 @@ defmodule HashDict do
{ _, 0 } ->
dict
{ root, -1 } ->
res = if depth > 0 and trie(dict, :contract_on) == size do
if depth > 0 and trie(dict, :contract_on) == size do
root = node_contract(root, depth, depth - 1)
trie(dict,
root: root,
size: size - 1,
depth: depth - 1,
contract_on: div(size, @node_size),
expand_on: div(trie(dict, :expand_on), @node_size))
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
@@ -275,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
@@ -291,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
@@ -368,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
@@ -395,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
@@ -409,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
@@ -417,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
@@ -427,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
@@ -457,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
+25 -11
View File
@@ -23,6 +23,8 @@ defmodule IO do
"""
import :erlang, only: [group_leader: 0]
@doc """
Reads `count` bytes from the IO device. It returns:
@@ -34,7 +36,7 @@ defmodule IO do
for instance {:error, :estale} if reading from an
NFS file system.
"""
def read(device // :stdio, count) do
def read(device // group_leader(), count) do
:io.get_chars(map_dev(device), "", count)
end
@@ -44,7 +46,7 @@ defmodule IO do
Check `read/2` for more information.
"""
def binread(device // :stdio, count) do
def binread(device // group_leader(), count) do
case :file.read(map_dev(device), count) do
{ :ok, data } -> data
other -> other
@@ -65,7 +67,7 @@ defmodule IO do
This function does the same as `gets/2`,
except the prompt is not required as argument.
"""
def readline(device // :stdio) do
def readline(device // group_leader()) do
:io.get_line(map_dev(device), "")
end
@@ -75,7 +77,7 @@ defmodule IO do
Check `readline/1` for more information.
"""
def binreadline(device // :stdio) do
def binreadline(device // group_leader()) do
case :file.read_line(map_dev(device)) do
{ :ok, data } -> data
other -> other
@@ -99,7 +101,7 @@ defmodule IO do
#=> "error"
"""
def write(device // :stdio, item) do
def write(device // group_leader(), item) do
:io.put_chars map_dev(device), to_iodata(item)
end
@@ -109,7 +111,7 @@ defmodule IO do
Check `write/2` for more information.
"""
def binwrite(device // :stdio, item) do
def binwrite(device // group_leader(), item) do
:file.write map_dev(device), to_iodata(item)
end
@@ -118,16 +120,28 @@ defmodule IO do
but adds a new line at the end. The argument is expected
to be a chardata.
"""
def puts(device // :stdio, item) do
def puts(device // group_leader(), item) do
erl_dev = map_dev(device)
:io.put_chars erl_dev, [to_iodata(item), ?\n]
end
@doc """
Inspects and writes the given argument to the device
followed by a new line. Returns the item given.
followed by a new line. A set of options can be given.
## Examples
IO.inspect Process.list
"""
def inspect(device // :stdio, item, opts // []) do
def inspect(item, opts // []) do
inspect group_leader(), item, opts
end
@doc """
Inspects the item with options using the given device.
"""
def inspect(device, item, opts) do
puts device, Binary.Inspect.inspect(item, opts)
item
end
@@ -143,7 +157,7 @@ defmodule IO do
for instance {:error, :estale} if reading from an
NFS file system.
"""
def getb(device // :stdio, prompt, count // 1) do
def getb(device // group_leader(), prompt, count // 1) do
:io.get_chars(map_dev(device), to_iodata(prompt), count)
end
@@ -159,7 +173,7 @@ defmodule IO do
for instance {:error, :estale} if reading from an
NFS file system.
"""
def gets(device // :stdio, prompt) do
def gets(device // group_leader(), prompt) do
:io.get_line(map_dev(device), to_iodata(prompt))
end
+233
View File
@@ -0,0 +1,233 @@
defmodule IO.ANSI.Sequence do
@moduledoc false
defmacro defsequence(name, code) do
binary_name = atom_to_binary(name)
quote do
def unquote(name)() do
"\e[#{unquote(code)}m"
end
defp escape_sequence(<< unquote(binary_name), rest :: binary >>) do
{ "\e[#{unquote(code)}m", rest }
end
end
end
end
defmodule IO.ANSI do
@moduledoc """
This module provides functionality to render ANSI escape sequences
(http://en.wikipedia.org/wiki/ANSI_escape_code) — characters embedded
in the text used to control formatting, color, and other output options
on video text terminals.
Please be aware that in Erlang/OTP versions prior to R16, you will not
be able to render ANSI escape sequences in iex or erlang shell
"""
import IO.ANSI.Sequence
@doc """
Checks whether the default I/O device is a terminal or a file.
Used to identify whether printing ANSI escape sequences will likely
be printed as intended.
Please note that invoked while in shell (iex) in Erlang/OTP
prior to R16, terminal?/0 will always return false because
Erlang shell did not support ANSI escape sequences up until
R16.
"""
@spec terminal? :: boolean
@spec terminal?(:io.device) :: boolean
def terminal?(device // :erlang.group_leader) do
if :erlang.system_info(:otp_release) < 'R16' and
Process.whereis(:user) != device do
# Shell prior to R16 doesn't support ANSI escape
# sequences
false
else
match?({:ok, _}, :io.columns(device))
end
end
@doc "Resets all attributes"
defsequence :reset, 0
@doc "Bright (increased intensity) or Bold"
defsequence :bright, 1
@doc "Faint (decreased intensity), not widely supported"
defsequence :faint, 2
@doc "Italic: on. Not widely supported. Sometimes treated as inverse."
defsequence :italic, 3
@doc "Underline: Single"
defsequence :underline, 4
@doc "Blink: Slow. Less than 150 per minute"
defsequence :blink_slow, 5
@doc "Blink: Rapid. MS-DOS ANSI.SYS; 150 per minute or more; not widely supported"
defsequence :blink_rapid, 6
@doc "Image: Negative. Swap foreground and background"
defsequence :inverse, 7
@doc "Image: Negative. Swap foreground and background"
defsequence :reverse, 7
@doc "Conceal. Not widely supported"
defsequence :conceal, 8
@doc "Crossed-out. Characters legible, but marked for deletion. Not widely supported."
defsequence :crossed_out, 9
@doc "Sets primary (default) font"
defsequence :primary_font, 10
lc font_n inlist [1,2,3,4,5,6,7,8,9] do
Module.eval_quoted __ENV__, (quote do
@doc "Sets alternative font #{unquote(font_n)}"
defsequence(unquote(:"font_#{font_n}"), unquote(font_n + 10))
end)
end
@doc "Normal color or intensity"
defsequence :normal, 22
@doc "Not italic"
defsequence :not_italic, 23
@doc "Underline: None"
defsequence :no_underline, 24
@doc "Blink: off"
defsequence :blink_off, 25
colors = [:black, :red, :green, :yellow, :blue, :magenta, :cyan, :white]
colors = Enum.zip(0..(length(colors)-1), colors)
lc { code, color } inlist colors do
Module.eval_quoted __ENV__, (quote do
@doc "Sets foreground color to #{unquote(color)}"
defsequence unquote(color), unquote(code + 30)
@doc "Sets background color to #{unquote(color)}"
defsequence unquote(:"#{color}_background"), unquote(code + 40)
end)
end
@doc "Default text color"
defsequence :default_color, 39
@doc "Default background color"
defsequence :default_background, 49
@doc "Framed"
defsequence :framed, 51
@doc "Encircled"
defsequence :encircled, 52
@doc "Overlined"
defsequence :overlined, 53
@doc "Not framed or encircled"
defsequence :not_framed_encircled, 54
@doc "Not overlined"
defsequence :not_overlined, 55
# Catch spaces between codes
defp escape_sequence(<< ?\s, rest :: binary >>) do
escape_sequence(rest)
end
defp escape_sequence(other) do
[spec|_] = String.split(other, %r/(,|\})/)
raise ArgumentError, message: "invalid ANSI sequence specification: #{spec}"
end
@doc %B"""
Escapes a string coverting named ANSI sequences into actual ANSI codes.
The format for referring sequences is `%{red}` and `%{red,bright}` (for
multiple sequences)
It will also force a %{reset} to get appended to every string. If you don't
want this behaviour, use `escape_fragment/1` and `escape_fragment/2`.
An optional boolean parameter can be passed to enable or disable
emitting actual ANSI codes. When false, no ANSI codes will emitted.
By default, standard output will be checked if it is a terminal capable
of handling these sequences (using `terminal?/0` function)
## Example
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
def escape(string, emit // terminal?) do
{rendered, emitted} = do_escape(string, false, emit, false, [])
if emitted and emit do
rendered <> reset
else
rendered
end
end
@doc %B"""
Escapes a string coverting named ANSI sequences into actual ANSI codes.
The format for referring sequences is `%{red}` and `%{red,bright}` (for
multiple sequences)
An optional boolean parameter can be passed to enable or disable
emitting actual ANSI codes. When false, no ANSI codes will emitted.
By default, standard output will be checked if it is a terminal capable
of handling these sequences (using `terminal?/0` function)
## Example
iex> IO.ANSI.escape "Hello %{red,bright,green}yes"
"Hello \e[31m\e[1m\e[32myes\e[0m"
"""
@spec escape_fragment(String.t, emit :: boolean) :: String.t
def escape_fragment(string, emit // terminal?) do
{rendered, _emitted} = do_escape(string, false, emit, false, [])
rendered
end
defp do_escape(<< ?%, ?{, rest :: binary >>, false, emit, _emitted, acc) do
do_escape_sequence(rest, emit, acc)
end
defp do_escape(<< ?,, rest :: binary >>, true, emit, _emitted, acc) do
do_escape_sequence(rest, emit, acc)
end
defp do_escape(<< ?\s, rest :: binary >>, true, emit, emitted, acc) do
do_escape(rest, true, emit, emitted, acc)
end
defp do_escape(<< ?}, rest :: binary >>, true, emit, emitted, acc) do
do_escape(rest, false, emit, emitted, acc)
end
defp do_escape(<< x :: [binary, size(1)], rest :: binary>>, false, emit, emitted, acc) do
do_escape(rest, false, emit, emitted, [x|acc])
end
defp do_escape("", false, _emit, emitted, acc) do
{list_to_binary(Enum.reverse(acc)), emitted}
end
defp do_escape_sequence(rest, emit, acc) do
{code, rest} = escape_sequence(rest)
if emit do
acc = [code|acc]
end
do_escape(rest, true, emit, true, acc)
end
end
+658 -380
View File
File diff suppressed because it is too large Load Diff
+1 -15
View File
@@ -16,23 +16,9 @@ defmodule Kernel.CLI do
run fn ->
Enum.map Enum.reverse(config.commands), process_command(&1, config)
:elixir_code_server.cast(:finished)
end, config.halt
end
@doc """
Wait until the CLI finishes processing options.
"""
def wait_until_finished do
case :elixir_code_server.call({ :wait_until_finished, self }) do
:wait ->
receive do
{ :elixir_code_server, :finished } -> :ok
end
:ok -> :ok
end
end
@doc """
Runs the given function by catching any failure
and printing them to stdout. `at_exit` hooks are
@@ -104,7 +90,7 @@ defmodule Kernel.CLI do
defp shared_option?(list, config, callback) do
case process_shared(list, config) do
{ [h|t], _ } when h == hd(list) ->
{ [h|_], _ } when h == hd(list) ->
invalid_option h
{ new_list, new_config } ->
callback.(new_list, new_config)
+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
+25 -7
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
@@ -91,7 +91,7 @@ defmodule Kernel.ParallelCompiler do
# list because someone invoked try/rescue UndefinedFunctionError
new_waiting = List.keydelete(waiting, child, 0)
spawn_compilers(files, output, callback, new_waiting, new_queued, schedulers, result)
{ :module_available, child, module, binary } ->
{ :module_available, _child, module, binary } ->
new_waiting = release_waiting_processes(module, waiting)
new_result = [{module, binary}|result]
wait_for_messages(files, output, callback, new_waiting, queued, schedulers, new_result)
@@ -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
+86 -78
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
@@ -55,13 +63,13 @@ defmodule Kernel.RecordRewriter do
defp record_field_info(function) do
case atom_to_list(function) do
'update' -> nil
'update_' ++ field -> nil
'update_' ++ _field -> nil
_ -> { :accessor, function }
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,81 +155,81 @@ 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 =
case args do
[{ :atom, _, atom }|t] -> atom
[{ :atom, _, atom }|_] -> atom
_ -> nil
end
{ { :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
+88 -104
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
@@ -355,15 +364,12 @@ defmodule Kernel.SpecialForms do
## Options
* `:hygiene` - When false, disables hygiene for variables, aliases and imports;
* `:unquote` - When false, disables unquoting. Useful when you have a quote
inside another quote and want to control which quote is
able to unquote;
* `:location` - When set to `:keep`, keeps the current line and file on quotes.
Read the Stacktrace information section below for more information;
* `:expand_aliases` - When false, do not expand aliases;
* `:expand_imports` - When false, do not expand imports;
* `:var_context` - The context for quoted variables. Defaults to the current module;
* `:hygiene` - Allows a developer to disable hygiene selectively;
## Macro literals
@@ -379,17 +385,15 @@ defmodule Kernel.SpecialForms do
## Hygiene
Elixir macros are hygienic. This means aliases and imports
defined inside the quoted often refer to the context that
defined the macro.
Elixir macros are hygienic via means of deferred resolution.
Furthermore, variables inside quote are also hygienic. That
said, a variable defined in a macro cannot affect the scope
where the macro is included.
This means aliases and imports defined inside the quoted refer
to the context that defined the macro and not the context
where the macro is expanded.
The option `hygiene` can turn off all the hygiene mechanisms
defined below. However, they can also be changed in a one by
one basis.
Furthermore, variables inside quote are also hygienic: a
variable defined in a macro cannot affect the variables where
the macro is expanded.
### Hygiene in variables
@@ -451,14 +455,13 @@ defmodule Kernel.SpecialForms do
# Access the variable a from Hygiene1
quote do: var!(a, Hygiene1) = 2
Another option is to set the `var_context` option, affecting
all variables in the block:
Hygiene for variables can be disabled overall as:
quote var_context: Hygiene1, do: a = 2
quote hygiene: [vars: false], do: x
### Hygiene in aliases
Aliases inside quote are expanded by default.
Aliases inside quote are hygienic by default.
Consider the following example:
defmodule Hygiene do
@@ -470,24 +473,43 @@ defmodule Kernel.SpecialForms do
end
require Hygiene
Hygiene.no_interference #=> HashDict[]
Hygiene.no_interference #=> #HashDict<[]>
Notice that, even though the alias `D` is not available
in the context the macro is expanded, the code above works
because `D` was expanded when the quote was generated.
because `D` still expands to `HashDict`.
There are two ways to disable this behaviour. By giving
the `expand_aliases` equals to false to quote or by using
the `alias!` macro inside the quote:
In some particular cases you may want to access an alias
or a module defined in the caller. In such scenarios, you
can access it by disabling hygiene with `hygiene: [aliases: false]`
or by using the `alias!` macro inside the quote:
defmodule NoHygiene do
defmodule Hygiene do
# This will expand to Elixir.Nested.hello
defmacro no_interference do
quote do: Nested.hello
end
# This will expand to Nested.hello for
# whatever is Nested in the caller
defmacro interference do
quote do: alias!(D).new
quote do: alias!(Nested).hello
end
end
require NoHygiene
NoHygiene.interference #=> UndefinedFunctionError
defmodule Parent do
defmodule Nested do
def hello, do: "world"
end
require Hygiene
Hygiene.no_interference
#=> ** (UndefinedFunctionError) ...
Hygiene.interference
#=> "world"
end
## Hygiene in imports
@@ -536,7 +558,7 @@ defmodule Kernel.SpecialForms do
Lazy.return_size #=> 2
As in aliases, imports expansion can be explicitly disabled
via the `expand_imports` option.
via the `hygiene: [imports: false]` option.
## Stacktrace information
@@ -570,7 +592,7 @@ defmodule Kernel.SpecialForms do
particular, the macro `__FILE__` and exceptions happening inside
the quote will always point to `GenServer.Behaviour` file.
"""
defmacro quote(opts, do: contents)
defmacro quote(opts, block)
@doc """
When used inside quoting, marks that the variable should
@@ -594,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}]
"""
@@ -681,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"
"""
@@ -692,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)
@@ -715,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,
+33 -5
View File
@@ -75,7 +75,7 @@ defmodule Kernel.Typespec do
Elixir discourages the use of type `string()` as it might be confused
with binaries which are referred to as "strings" in Elixir (as opposed to
character lists). In order to use the type that is called `string()` in Erlang,
one has to use the `char_list()` type which is a synonym to `string()`. If yu
one has to use the `char_list()` type which is a synonym for `string()`. If you
use `string()`, you'll get a warning from the compiler.
If you want to refer to the "string" type (the one operated by functions in the
@@ -325,7 +325,7 @@ defmodule Kernel.Typespec do
defp abstract_code(module) do
case :beam_lib.chunks(abstract_code_beam(module), [:abstract_code]) do
{:ok, { _, [{ :abstract_code, { raw_abstract_v1, abstract_code } }] } } ->
{:ok, { _, [{ :abstract_code, { _raw_abstract_v1, abstract_code } }] } } ->
{ :ok, abstract_code }
_ ->
[]
@@ -350,10 +350,17 @@ defmodule Kernel.Typespec do
do_deftype(kind, type, definition, caller)
end
def deftype(kind, type, caller) do
def deftype(kind, {name, _meta, args} = type, caller) when
is_atom(name) and
not is_list(args) do
do_deftype(kind, type, { :term, [line: caller.line], nil }, caller)
end
def deftype(_kind, other, _caller) do
type_spec = Macro.to_binary(other)
raise ArgumentError, message: "invalid type specification #{type_spec}"
end
defp do_deftype(kind, { name, _, args }, definition, caller) do
args =
if is_atom(args) do
@@ -390,6 +397,11 @@ defmodule Kernel.Typespec do
code
end
def defspec(_type, other, _caller) do
spec = Macro.to_binary(other)
raise ArgumentError, message: "invalid function type specification #{spec}"
end
defp guard_to_constraints({ :is_subtype, meta, [{ name, _, _ }, type] }, caller) do
line = line(meta)
contraints = [{ :atom, line, :is_subtype }, [{:var, line, name}, typespec(type, [], caller)]]
@@ -467,6 +479,11 @@ defmodule Kernel.Typespec do
typespec_to_ast({:type, line, :char_list, []})
end
defp typespec_to_ast({ :remote_type, line, [{:atom, _, :elixir}, {:atom, _, :as_boolean}, [arg]] }) do
typespec_to_ast({:type, line, :as_boolean, [arg]})
end
defp typespec_to_ast({ :remote_type, line, [mod, name, args] }) do
args = lc arg inlist args, do: typespec_to_ast(arg)
dot = { :., [line: line], [typespec_to_ast(mod), typespec_to_ast(name)] }
@@ -542,6 +559,7 @@ defmodule Kernel.Typespec do
end
# Handle funs
defp typespec({:->, meta, [{[{:fun, _, arguments}], return}]}, vars, caller) when is_list(arguments) do
typespec({:->, meta, [{arguments, return}]}, vars, caller)
end
@@ -566,7 +584,7 @@ defmodule Kernel.Typespec do
# Handle access macro
defp typespec({{:., meta, [Kernel, :access]}, meta1, [target, args]}, vars, caller) do
access = {{:., meta, [Kernel, :access]}, meta1,
[target, args ++ [_: (quote hygiene: false, do: any)]]}
[target, args ++ [_: { :any, [], [] }]]}
typespec(Macro.expand(access, caller), vars, caller)
end
@@ -591,6 +609,12 @@ defmodule Kernel.Typespec do
{ :type, line(meta), :tuple, args }
end
# Handle blocks
defp typespec({:__block__, _meta, [arg]}, vars, caller) do
typespec(arg, vars, caller)
end
# Handle variables or local calls
defp typespec({name, meta, atom}, vars, caller) when is_atom(atom) do
if List.member?(vars, name) do
@@ -612,6 +636,10 @@ defmodule Kernel.Typespec do
typespec((quote do: :elixir.char_list(unquote_splicing(arguments))), vars, caller)
end
defp typespec({:as_boolean, _meta, arguments}, vars, caller) do
typespec((quote do: :elixir.as_boolean(unquote_splicing(arguments))), vars, caller)
end
defp typespec({name, meta, arguments}, vars, caller) do
arguments = lc arg inlist arguments, do: typespec(arg, vars, caller)
{ :type, line(meta), name, arguments }
@@ -676,4 +704,4 @@ defmodule Kernel.Typespec do
defp variable({name, meta, _}) do
{:var, line(meta), name}
end
end
end
+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
+127 -50
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
case :elixir_aliases.expand(original, env.aliases) 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
@@ -478,8 +544,16 @@ defmodule Macro do
case not is_partial?(args) do
false -> original
true ->
module = env.module
extra = if function_exported?(module, :__info__, 1) do
[{ module, module.__info__(:macros) }]
else
[]
end
expand = :elixir_dispatch.expand_import(line, { atom, length(args) }, args,
env.module, env.function, env.requires, env.macros, env)
env.module, extra, to_erl_env(env, cache))
case expand do
{ :ok, _, expanded } -> expanded
{ :error, _ } -> original
@@ -488,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
@@ -522,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.
+25 -16
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
@@ -210,7 +216,7 @@ defmodule Module do
[] ->
ETS.insert(table, { tuple, line, kind, signature, doc })
:ok
[{ tuple, line, old_kind, old_sign, old_doc }] when old_doc == nil or doc == nil or old_doc == doc ->
[{ tuple, line, _old_kind, old_sign, old_doc }] when old_doc == nil or doc == nil or old_doc == doc ->
ETS.insert(table, {
tuple,
line,
@@ -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)>>
+13 -1
View File
@@ -74,11 +74,23 @@ defmodule Node do
See http://www.erlang.org/doc/man/erlang.html#disconnect_node-1 for more info.
"""
@spec disconnect(t) :: boolean | :ginored
@spec disconnect(t) :: boolean | :ignored
def disconnect(node) do
:erlang.disconnect_node(node)
end
@doc """
Establishes a connection to Node. Returns true if successful,
false if not, and the atom `:ignored` if the local node is not
alive.
See http://erlang.org/doc/man/net_kernel.html#connect_node-1 for more info.
"""
@spec connect(t) :: boolean | :ignored
def connect(node) do
:net_kernel.connect_node(node)
end
@doc """
Returns the pid of a new process started by the application of `fun`
on `node`. If `node` does not exist, a useless pid is returned.
+48 -37
View File
@@ -5,45 +5,53 @@ 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
Extra information about switches can be given as argument too. This is useful
in order to say a switch must behave as a boolean, list, etc. The following
types are supported:
Extra information about switches can be given as argument too.
This is useful in order to say a switch must behave as a boolean
or if duplicated switches should be kept, overriden or accumulated.
* `:boolean` - They never consume the next value unless it is true/false;
The following types are supported:
* `:boolean` - Mark the given switch as boolean. Boolean switches
never consumes the following value unless it is
true or false;
The following extra options are supported:
* `:keep` - Keep duplicated items in the list instead of overriding;
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
@@ -58,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
@@ -69,11 +77,6 @@ defmodule OptionParser do
## Helpers
defp parse(argv, opts, bool) do
if flags = opts[:flags] do
IO.write "OptionParser option :flags is deprecated, please use :switches instead\n#{Exception.format_stacktrace}"
opts = Keyword.put(opts, :switches, lc(k inlist flags, do: { k, :boolean }))
end
aliases = opts[:aliases] || []
switches = opts[:switches] || []
parse(argv, aliases, switches, bool)
@@ -85,16 +88,18 @@ defmodule OptionParser do
defp parse(["-" <> option|t], aliases, switches, dict, args, all) do
{ option, value } = normalize_option(option, aliases)
kind = switches[option]
if value == nil do
{ value, t } =
case switch_type(switches, option) do
:boolean -> boolean_from_tail(t)
_ -> value_from_tail(t)
if is_switch_a? :boolean, kind do
boolean_from_tail(t)
else
value_from_tail(t)
end
end
dict = store_option dict, option, value
dict = store_option dict, option, value, kind
parse(t, aliases, switches, dict, args, all)
end
@@ -117,16 +122,22 @@ defmodule OptionParser do
defp value_from_tail([h|t]), do: { h, t }
defp value_from_tail([]), do: { true, [] }
defp store_option(dict, option, value) when value in ["false", "true"] do
store_option(dict, option, binary_to_atom(value))
defp store_option(dict, option, value, switches) when value in ["true", "false"] do
store_option dict, option, binary_to_atom(value), switches
end
defp store_option(dict, option, value) do
[{ option, value }|dict]
defp store_option(dict, option, value, kind) do
if is_switch_a? :keep, kind do
[{ option, value }|dict]
else
[{ option, value }|Keyword.delete(dict, option)]
end
end
defp reverse_dict(dict, switches) do
switches = lc { k, :boolean } inlist switches, not Keyword.has_key?(dict, k), do: { k, false }
switches = lc { k, v } inlist switches,
is_switch_a?(:boolean, v),
not Keyword.has_key?(dict, k), do: { k, false }
Enum.reverse switches ++ dict
end
@@ -155,7 +166,7 @@ defmodule OptionParser do
defp is_no?("no-" <> _), do: true
defp is_no?(_), do: false
defp switch_type(switches, option) do
switches[option] || :default
end
defp is_switch_a?(kind, list) when is_list(list), do: List.member?(list, kind)
defp is_switch_a?(kind, kind), do: true
defp is_switch_a?(_, _), do: false
end
+76 -69
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
@@ -456,12 +465,12 @@ defmodule Path do
end
def wildcard(glob) when is_list(glob) do
Enum.map wildcard(list_to_binary(glob)), binary_to_list(&1)
:elixir_glob.wildcard glob
end
## Helpers
defp get_cwd(path) when is_list(path), do: System.cwd! |> binary_to_list
defp get_cwd(path) when is_list(path), do: System.cwd! |> binary_to_filename_string
defp get_cwd(_), do: System.cwd!
defp binary_to_filename_string(binary) 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_list]
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
+3 -4
View File
@@ -19,11 +19,10 @@ defmodule Process do
:erlang.is_process_alive(pid)
end
@doc """
Returns the current process.
"""
@spec self :: pid
@doc false
def self do
IO.puts "Process.self is deprecated, please use Kernel.self instead"
Exception.print_stacktrace
:erlang.self()
end
+2 -2
View File
@@ -287,7 +287,7 @@ defmodule Protocol do
# Generate all others protocols.
defp each_impl_for({ kind, fun, _ }, _, _) do
quote do
defp __raw_impl__(arg) when unquote(fun).(arg) do
defp __raw_impl__(arg) when unquote(fun)(arg) do
__MODULE__.unquote(kind)
end
end
@@ -404,7 +404,7 @@ defmodule Protocol.DSL do
# Generate a fake definition with the user
# signature that will be used by docs
Kernel.def unquote(name).(unquote_splicing(args))
Kernel.def unquote(name)(unquote_splicing(args))
{ args, body } = Protocol.DSL.args_and_body(__MODULE__, name, arity)
Kernel.def unquote(name), args, [], do: body
+64 -26
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__)
defmacrop unquote(name)() do
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)
defmacrop unquote(name)(record) when is_tuple(record) do
Record.to_keywords(unquote(tag) || __MODULE__, unquote(escaped), record)
end
defmacrop unquote(name).(args) do
Record.access(__MODULE__, unquote(escaped), args, __CALLER__)
defmacrop unquote(name)(args) do
Record.access(unquote(tag) || __MODULE__, unquote(escaped), args, __CALLER__)
end
defmacrop unquote(name).(record, key) when is_atom(key) do
Record.get(__MODULE__, unquote(escaped), record, key)
defmacrop unquote(name)(record, key) when is_atom(key) do
Record.get(unquote(tag) || __MODULE__, unquote(escaped), record, key)
end
defmacrop unquote(name).(record, args) do
Record.dispatch(__MODULE__, unquote(escaped), record, args, __CALLER__)
defmacrop unquote(name)(record, args) do
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
@@ -465,17 +492,17 @@ defmodule Record do
contents = quote do
@doc false
def unquote(key).(record) do
def unquote(key)(record) do
:erlang.element(unquote(i + 1), record)
end
@doc false
def unquote(key).(value, record) do
def unquote(key)(value, record) do
:erlang.setelement(unquote(i + 1), record, value)
end
@doc false
def unquote(update).(function, record) do
def unquote(update)(function, record) do
:erlang.setelement(unquote(i + 1), record,
function.(:erlang.element(unquote(i + 1), record)))
end
@@ -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
+43 -25
View File
@@ -54,8 +54,7 @@ defmodule Regex do
It returns `{ :ok, regex }` in case of success,
`{ :error, reason }` otherwise.
"""
def compile(source, options // "") do
source = to_binary(source)
def compile(source, options // "") when is_binary(source) do
options = to_binary(options)
opts = translate_options(options)
re_opts = opts -- [:groups]
@@ -87,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
@@ -103,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
@@ -117,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 // [])
@@ -128,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
@@ -140,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
@@ -151,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
@@ -160,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
@@ -171,7 +178,8 @@ defmodule Regex do
## Examples
Regex.source %r(foo) #=> "foo"
iex> Regex.source %r(foo)
"foo"
"""
def source(regex(source: source)) do
@@ -179,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
@@ -195,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
@@ -210,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 // [])
@@ -257,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
@@ -302,7 +320,7 @@ defmodule Regex do
lc result inlist results do
case result do
[t] -> t
[h|t] -> t
[_|t] -> t
end
end
end
+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
@@ -24,4 +24,4 @@
{verbose, false}
]}.
{require_otp_vsn,"R15"}.
{require_otp_vsn,"(R15|R16).*"}.
+3 -2
View File
@@ -8,8 +8,9 @@
-compile({parse_transform, elixir_transform}).
%% Top level types
-export_type([char_list/0]).
-export_type([char_list/0, as_boolean/1]).
-type char_list() :: string().
-type as_boolean(T) :: T.
% OTP APPLICATION API
@@ -36,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),
+71 -30
View File
@@ -1,26 +1,51 @@
-module(elixir_aliases).
-export([nesting/2, last/1, concat/1, safe_concat/1, lookup/2,
format_error/1, ensure_loaded/3, expand/2]).
-export([nesting_alias/2, last/1, concat/1, safe_concat/1,
format_error/1, ensure_loaded/3, expand/3]).
-include("elixir.hrl").
-compile({parse_transform, elixir_transform}).
%% Expand an alias returning an atom if possible.
%% Otherwise returns the list of aliases args.
%% Expand an alias. It returns an atom (meaning that there
%% was an expansion) or a list of atoms.
expand({ '__aliases__', _Line, [H] }, Aliases) when H /= 'Elixir' ->
expand_alias(H, Aliases);
expand({ '__aliases__', Meta, _ } = Alias, Aliases, MacroAliases) ->
case lists:keyfind(alias, 1, Meta) of
{ alias, false } ->
expand(Alias, MacroAliases);
{ alias, Atom } when is_atom(Atom) ->
case expand(Alias, MacroAliases) of
OtherAtom when is_atom(OtherAtom) -> OtherAtom;
OtherAliases when is_list(OtherAliases) -> Atom
end;
false ->
expand(Alias, Aliases)
end.
expand({ '__aliases__', _Line, [H|T] }, Aliases) when is_atom(H) ->
concat(case H of
'Elixir' -> [H|T];
_ -> [expand_alias(H, Aliases)|T]
end);
expand({ '__aliases__', _Meta, [H] }, Aliases) when H /= 'Elixir' ->
case expand_one(H, Aliases) of
false -> [H];
Atom -> Atom
end;
expand({ '__aliases__', _Line, List }, _Aliases) ->
expand({ '__aliases__', _Meta, [H|T] }, Aliases) when is_atom(H) ->
case H of
'Elixir' ->
concat(T);
_ ->
case expand_one(H, Aliases) of
false -> [H|T];
Atom -> concat([Atom|T])
end
end;
expand({ '__aliases__', _Meta, List }, _Aliases) ->
List.
expand_alias(H, Aliases) ->
lookup(list_to_atom("Elixir-" ++ atom_to_list(H)), Aliases).
expand_one(H, Aliases) ->
Lookup = list_to_atom("Elixir-" ++ atom_to_list(H)),
case lookup(Lookup, Aliases) of
Lookup -> false;
Else -> Else
end.
%% Ensure a module is loaded before its usage.
@@ -39,7 +64,7 @@ ensure_loaded(Line, Ref, S) ->
elixir_errors:form_error(Line, S#elixir_scope.file, ?MODULE, { Kind, Ref })
end.
%% Receives an atom and returns the last alias.
%% Receives an atom and returns the last bit as an alias.
last(Atom) ->
Last = last(lists:reverse(atom_to_list(Atom)), []),
@@ -49,27 +74,43 @@ last([$-|_], Acc) -> Acc;
last([H|T], Acc) -> last(T, [H|Acc]);
last([], Acc) -> Acc.
%% Returns the nesting between two aliases.
%% Gets two modules names and return an alias
%% which can be passed down to the alias directive
%% and it will create a proper shortcut representing
%% the given nesting.
%%
%% Examples:
%%
%% nesting_alias('Elixir.Foo.Bar', 'Elixir.Foo.Bar.Baz.Bat')
%% { 'Elixir.Baz', 'Elixir.Foo.Bar.Baz' }
%%
%% When passed to alias, the example above will generate an
%% alias like:
%%
%% 'Elixir.Baz' => 'Elixir.Foo.Bar.Baz'
%%
nesting_alias(nil, _Full) -> false;
nesting(nil, _Full) -> false;
nesting(Prefix, Full) ->
nesting_alias(Prefix, Full) ->
PrefixList = list_nesting(Prefix),
FullList = list_nesting(Full),
(PrefixList /= []) andalso do_nesting(PrefixList, FullList, []).
(PrefixList /= []) andalso
(PrefixList /= FullList) andalso
lists:prefix(PrefixList, FullList) andalso
binary_to_atom(<<"Elixir-", (hd(FullList -- PrefixList))/binary>>, utf8).
do_nesting([X|PreTail], [X|Tail], Acc) ->
do_nesting(PreTail, Tail, [X|Acc]);
do_nesting([], [H|_], Acc) ->
{ list_to_atom("Elixir-" ++ H), concat(lists:reverse([H|Acc])) };
do_nesting(_, _, _Acc) ->
false.
list_nesting(Atom) ->
case binary:split(atom_to_binary(Atom, utf8), <<$->>, [global]) of
[<<"Elixir">>|T] -> T;
case string:tokens(atom_to_list(Atom), "-") of
["Elixir"|T] -> T;
_ -> []
end.
%% Receives a list of atoms representing modules
%% and concatenate them.
%% Receives a list of atoms, binaries or lists
%% representing modules and concatenates them.
concat(Args) -> list_to_atom(raw_concat(Args)).
safe_concat(Args) -> list_to_existing_atom(raw_concat(Args)).
@@ -93,7 +134,7 @@ dot_to_dash(List) ->
_ -> X
end || X <- List].
%% Lookup an alias in the current scope
%% Lookup an alias in the current scope.
lookup(Else, Dict) ->
case orddict:find(Else, Dict) of
@@ -104,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)]).
+73 -86
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).
@@ -108,63 +108,58 @@ do_match(Meta, DecoupledClauses, S) ->
{ TX, { umergec(S, TAcc), [TAcc#elixir_scope.clause_vars|CV] } }
end,
{ TClauses, { TS, RawCV } } = lists:mapfoldl(Transformer, {S, []}, DecoupledClauses),
{ TClauses, { TS, ReverseCV } } = lists:mapfoldl(Transformer, {S, []}, DecoupledClauses),
% Now get all the variables defined inside each clause
CV = lists:reverse(RawCV),
CV = lists:reverse(ReverseCV),
AllVars = lists:foldl(fun(KV, Acc) ->
elixir_scope:merge_clause_vars(Acc, KV)
end, orddict:new(), CV),
AllVars = lists:umerge([orddict:fetch_keys(X) || X <- CV]),
SharedVars = ordsets:intersection(CV),
% Create a new scope that contains a list of all variables
% defined inside all the clauses. It returns this new scope and
% a list of tuples where the first element is the variable name,
% the second one is the new pointer to the variable and the third
% is the old pointer.
{ FinalVars, FS } = lists:mapfoldl(fun({ Key, Ref }, Acc) ->
normalize_vars(Key, Ref, Acc)
end, TS, AllVars),
case AllVars of
[] -> { TClauses, TS };
_ ->
% Create a new scope that contains a list of all variables
% defined inside all the clauses. It returns this new scope and
% a list of tuples where the first element is the variable name,
% the second one is the new pointer to the variable and the third
% is the old pointer.
{ FinalVars, FS } = lists:mapfoldl(fun(X, Acc) -> normalize_vars(X, SharedVars, Acc) end, TS, AllVars),
% Expand all clauses by adding a match operation at the end
% that assigns variables missing in one clause to the others.
expand_clauses(?line(Meta), TClauses, CV, FinalVars, [], FS).
% Defines a tuple that will be used as left side of the match operator
Line = ?line(Meta),
LeftVars = [{var, Line, NewValue} || { _, _, NewValue, _ } <- FinalVars],
expand_clauses(Line, [Clause|T], [ClauseVars|V], FinalVars, Acc, S) ->
case generate_match_vars(FinalVars, ClauseVars, [], []) of
{ [], [] } ->
expand_clauses(Line, T, V, FinalVars, [Clause|Acc], S);
{ Left, Right } ->
MatchExpr = generate_match(Line, Left, Right),
ClauseExprs = element(5, Clause),
[Final|RawClauseExprs] = lists:reverse(ClauseExprs),
% Expand all clauses by adding a match operation at the end
% that assigns variables missing in one clause to the others.
expand_clauses(Meta, TClauses, CV, LeftVars, FinalVars, [], FS)
end.
% If the last sentence has a match clause, we need to assign its value
% in the variable list. If not, we insert the variable list before the
% final clause in order to keep it tail call optimized.
{ FinalClauseExprs, FS } = case has_match_tuple(Final) of
true ->
case Final of
{ match, _, { var, _, UserVarName } = UserVar, _ } when UserVarName /= '_' ->
{ [UserVar,MatchExpr,Final|RawClauseExprs], S };
_ ->
{ StorageVar, SS } = elixir_scope:build_erl_var(Line, S),
StorageExpr = { match, Line, StorageVar, Final },
{ [StorageVar,MatchExpr,StorageExpr|RawClauseExprs], SS }
end;
false ->
{ [Final,MatchExpr|RawClauseExprs], S }
end,
expand_clauses(Meta, [Clause|T], [ClauseVars|V], LeftVars, FinalVars, Acc, S) ->
RightVars = [normalize_clause_var(Var, Kind, OldValue, ClauseVars) ||
{ Var, Kind, _, OldValue } <- FinalVars],
FinalClause = setelement(5, Clause, lists:reverse(FinalClauseExprs)),
expand_clauses(Line, T, V, FinalVars, [FinalClause|Acc], FS)
end;
AssignExpr = generate_match(Meta, LeftVars, RightVars),
ClauseExprs = element(5, Clause),
[Final|RawClauseExprs] = lists:reverse(ClauseExprs),
% If the last sentence has a match clause, we need to assign its value
% in the variable list. If not, we insert the variable list before the
% final clause in order to keep it tail call optimized.
{ FinalClauseExprs, FS } = case has_match_tuple(Final) of
true ->
case Final of
{ match, _, { var, _, UserVarName } = UserVar, _ } when UserVarName /= '_' ->
{ [UserVar,AssignExpr,Final|RawClauseExprs], S };
_ ->
Line = ?line(Meta),
{ StorageVar, SS } = elixir_scope:build_erl_var(Line, S),
StorageExpr = { match, Line, StorageVar, Final },
{ [StorageVar,AssignExpr,StorageExpr|RawClauseExprs], SS }
end;
false ->
{ [Final,AssignExpr|RawClauseExprs], S }
end,
FinalClause = setelement(5, Clause, lists:reverse(FinalClauseExprs)),
expand_clauses(Meta, T, V, LeftVars, FinalVars, [FinalClause|Acc], FS);
expand_clauses(_Meta, [], [], _LeftVars, _FinalVars, Acc, S) ->
expand_clauses(_Line, [], [], _FinalVars, Acc, S) ->
{ lists:reverse(Acc), S }.
% Handle each key/value clause pair and translate them accordingly.
@@ -181,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
@@ -210,24 +205,14 @@ has_match_tuple(H) when is_list(H) ->
has_match_tuple(_) -> false.
% Normalize the given var checking its existence in the scope var dictionary.
% Normalize the given var in between clauses
% by picking one value as reference and retriving
% its previous value.
normalize_vars({ Var, Kind } = Key, Shared, #elixir_scope{vars=Vars,clause_vars=ClauseVars} = S) ->
{ NewValue, S1 } =
case orddict:find(Key, Shared) of
{ ok, SharedValue } ->
{ SharedValue, S };
error ->
{ { _, _, ErlValue }, ErlS } = case (Kind /= nil) or (S#elixir_scope.noname) of
true -> elixir_scope:build_erl_var(0, S);
false -> elixir_scope:build_erl_var(0, Var, "_@" ++ atom_to_list(Var), S)
end,
{ ErlValue, ErlS }
end,
S2 = S1#elixir_scope{
vars=orddict:store(Key, NewValue, Vars),
clause_vars=orddict:store(Key, NewValue, ClauseVars)
normalize_vars(Key, Value, #elixir_scope{vars=Vars,clause_vars=ClauseVars} = S) ->
FS = S#elixir_scope{
vars=orddict:store(Key, Value, Vars),
clause_vars=orddict:store(Key, Value, ClauseVars)
},
Expr = case orddict:find(Key, Vars) of
@@ -235,27 +220,29 @@ normalize_vars({ Var, Kind } = Key, Shared, #elixir_scope{vars=Vars,clause_vars=
error -> { atom, 0, nil }
end,
{ { Var, Kind, NewValue, Expr }, S2 }.
{ { Key, Value, Expr }, FS }.
% Normalize a var by checking if it was defined in the clause.
% If so, use it, otherwise use from main scope.
% Generate match vars by checking if they were updated
% or not and assigning the previous value.
normalize_clause_var(Var, Kind, OldValue, ClauseVars) ->
case orddict:find({ Var, Kind }, ClauseVars) of
{ ok, ClauseValue } -> { var, 0, ClauseValue };
error -> OldValue
end.
generate_match_vars([{ Key, NewValue, OldValue }|T], ClauseVars, Left, Right) ->
case orddict:find(Key, ClauseVars) of
{ ok, NewValue } ->
generate_match_vars(T, ClauseVars, Left, Right);
{ ok, ClauseValue } ->
generate_match_vars(T, ClauseVars, [{ var, 0, NewValue }|Left], [{ var, 0, ClauseValue }|Right]);
error ->
generate_match_vars(T, ClauseVars, [{ var, 0, NewValue }|Left], [OldValue|Right])
end;
%% generate_match
generate_match_vars([], _ClauseVars, Left, Right) ->
{ Left, Right }.
generate_match(Meta, [Left], [Right]) ->
{ match, ?line(Meta), Left, Right };
generate_match(Line, [Left], [Right]) ->
{ match, Line, Left, Right };
generate_match(Meta, LeftVars, RightVars) ->
Line = ?line(Meta),
generate_match(Line, LeftVars, RightVars) ->
{ match, Line, { tuple, Line, LeftVars }, { tuple, Line, RightVars } }.
%% Listify
listify(Expr) when not is_list(Expr) -> [Expr];
listify(Expr) -> Expr.
-12
View File
@@ -30,13 +30,6 @@ start_link() ->
init(_args) ->
{ ok, #elixir_code_server{} }.
handle_call({ wait_until_finished, Pid }, _, Config) ->
Waiting = Config#elixir_code_server.waiting,
case is_list(Waiting) of
true -> { reply, wait, Config#elixir_code_server{waiting=[Pid|Waiting]} };
false -> { reply, ok, Config }
end;
handle_call({ acquire, Path }, From, Config) ->
Current = Config#elixir_code_server.loaded,
case orddict:find(Path, Current) of
@@ -87,11 +80,6 @@ handle_cast({ compiler_options, Options }, Config) ->
Final = orddict:merge(fun(_,_,V) -> V end, Config#elixir_code_server.compiler_options, Options),
{ noreply, Config#elixir_code_server{compiler_options=Final} };
handle_cast(finished, Config) ->
Waiting = Config#elixir_code_server.waiting,
[Pid ! { elixir_code_server, finished } || Pid <- lists:reverse(Waiting)],
{ noreply, Config#elixir_code_server{waiting=done} };
handle_cast({ loaded, Path }, Config) ->
Current = Config#elixir_code_server.loaded,
case orddict:find(Path, Current) of
+54 -8
View File
@@ -1,6 +1,6 @@
-module(elixir_compiler).
-export([get_opts/0, get_opt/1, get_opt/2, string/2, file/1, file_to_path/2]).
-export([core/0, module/3, eval_forms/4]).
-export([core/0, module/3, eval_forms/4, format_error/1]).
-include("elixir.hrl").
-compile({parse_transform, elixir_transform}).
@@ -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
@@ -89,11 +92,17 @@ module(Forms, S, Callback) ->
end,
module(Forms, S#elixir_scope.file, Options, false, Callback).
module(Forms, File, Options, Bootstrap, Callback) when
is_binary(File), is_list(Forms), is_list(Options), is_boolean(Bootstrap), is_function(Callback) ->
module(Forms, File, RawOptions, Bootstrap, Callback) when
is_binary(File), is_list(Forms), is_list(RawOptions), is_boolean(Bootstrap), is_function(Callback) ->
{ Options, SkipNative } = compile_opts(Forms, RawOptions),
Listname = binary_to_list(File),
case compile:forms([no_auto_import()|Forms], [return,{source,Listname}|Options]) of
{ok, ModuleName, Binary, Warnings} ->
case compile:noenv_forms([no_auto_import()|Forms], [return,{source,Listname}|Options]) of
{ok, ModuleName, Binary, RawWarnings} ->
Warnings = case SkipNative of
true -> [{?MODULE,[{0,?MODULE,{skip_native,ModuleName}}]}|RawWarnings];
false -> RawWarnings
end,
format_warnings(Bootstrap, File, Warnings),
code:load_binary(ModuleName, Listname, Binary),
Callback(ModuleName, Binary);
@@ -112,6 +121,38 @@ core() ->
%% HELPERS
compile_opts(Forms, Options) ->
EnvOptions = env_default_opts(),
SkipNative = lists:member(native, EnvOptions) and contains_on_load(Forms),
case SkipNative or lists:member([{native,false}], Options) of
true -> { Options ++ lists:delete(native, EnvOptions), SkipNative };
false -> { Options ++ EnvOptions, SkipNative }
end.
env_default_opts() ->
Key = "ERL_COMPILER_OPTIONS",
case os:getenv(Key) of
false -> [];
Str when is_list(Str) ->
case erl_scan:string(Str) of
{ok,Tokens,_} ->
case erl_parse:parse_term(Tokens ++ [{dot, 1}]) of
{ok,List} when is_list(List) -> List;
{ok,Term} -> [Term];
{error,_Reason} ->
io:format("Ignoring bad term in ~ts\n", [Key]),
[]
end;
{error, {_,_,_Reason}, _} ->
io:format("Ignoring bad term in ~ts\n", [Key]),
[]
end
end.
contains_on_load([{ attribute, _, on_load, _ }|_]) -> true;
contains_on_load([_|T]) -> contains_on_load(T);
contains_on_load([]) -> false.
no_auto_import() ->
Bifs = [{ Name, Arity } || { Name, Arity } <- erlang:module_info(exports), erl_internal:bif(Name, Arity)],
{ attribute, 0, compile, { no_auto_import, Bifs } }.
@@ -157,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()]),
@@ -188,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",
@@ -196,8 +238,12 @@ core_main() ->
%% ERROR HANDLING
format_error({ skip_native, Module }) ->
io_lib:format("skipping native compilation for ~ts because it contains on_load attribute",
[elixir_errors:inspect(Module)]).
format_errors(_File, []) ->
exit({nocompile, "compilation failed but no error was raised"});
exit({ nocompile, "compilation failed but no error was raised" });
format_errors(File, Errors) ->
lists:foreach(fun ({_, Each}) ->
+48 -20
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),
@@ -110,8 +131,8 @@ run_on_definition_callbacks(Kind, Line, Module, Name, Args, Guards, Expr, S, CO)
ok;
_ ->
Env = elixir_scope:to_ex_env({ Line, S }),
Callbacks = 'Elixir.Module':get_attribute(Module, on_definition),
[Mod:Fun(Env, Kind, Name, Args, Guards, Expr) || { Mod, Fun } <- Callbacks]
elixir_module:eval_callbacks(Line, Module, on_definition,
[Env, Kind, Name, Args, Guards, Expr], S)
end.
%% Retrieve @file or fallback to default
@@ -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 }) ->
+140 -76
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,112 +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) 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}
S#elixir_scope{
check_clauses=false,
macro_functions=[],
macro_macros=[],
macro_aliases=[]
}
),
{ TR, TS#elixir_scope{check_clauses=S#elixir_scope.check_clauses} }.
{ TR, TS#elixir_scope{
check_clauses=S#elixir_scope.check_clauses,
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
find_dispatch(Tuple, [{ Name, Values }|T]) ->
case is_element(Tuple, Values) of
true -> Name;
false -> find_dispatch(Tuple, T)
end;
skip_require(Meta) ->
lists:keyfind(require, 1, Meta) == { require, false }.
find_dispatch(_Tuple, []) -> false.
find_dispatch(Meta, Tuple, S) ->
find_dispatch(Meta, Tuple, [], S).
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];
@@ -210,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
@@ -221,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,
@@ -387,4 +451,4 @@ in_erlang_macros() ->
{'receive',1},
{'try',1},
{'xor',2}
].
].
+25 -16
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").
@@ -77,10 +77,10 @@ form_error(Meta, File, Module, Desc) ->
%% Shows a deprecation message
deprecation(Line, File, Message) -> deprecation(Line, File, Message, []).
deprecation(Meta, File, Message) -> deprecation(Meta, File, Message, []).
deprecation(Line, File, Message, Args) ->
io:format(file_format(Line, File, io_lib:format(Message, Args))).
deprecation(Meta, File, Message, Args) ->
io:format(file_format(?line(Meta), File, io_lib:format(Message, Args))).
%% Handle warnings and errors (called during module compilation)
@@ -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,10 +105,13 @@ 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;
handle_file_warning(_, _File, {Line,erl_lint,{unused_var,_Var}}) when Line =< 0 ->
[];
handle_file_warning(_, File, {Line,erl_lint,{unused_var,Var}}) ->
Message = format_error(erl_lint, { unused_var, format_var(Var) }),
io:format(file_format(Line, File, Message));
@@ -134,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
@@ -161,11 +164,17 @@ assert_function_scope(_Meta, _Kind, #elixir_scope{function=Function}) -> Functio
raise(Meta, File, Kind, Message) when is_list(Meta) ->
raise(?line(Meta), File, Kind, Message);
raise(none, File, Kind, Message) ->
raise(0, File, Kind, Message);
raise(Line, File, Kind, Message) when is_integer(Line) ->
Stacktrace = erlang:get_stacktrace(),
Exception = Kind:new([{description, Message}, {file, iolist_to_binary(File)}, {line, Line}]),
erlang:raise(error, Exception, Stacktrace).
file_format(0, File, Message) ->
io_lib:format("~ts: ~ts~n", [File, Message]);
file_format(Line, File, Message) ->
io_lib:format("~ts:~w: ~ts~n", [File, Line, Message]).
+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.
+50 -48
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
@@ -196,11 +208,15 @@ translate({defmodule, Meta, [Ref, KV]}, S) ->
{ FRef, FS } = case TRef of
{ atom, _, Module } ->
FullModule = module_ref(Ref, Module, S#elixir_scope.module),
FullModule = expand_module(Ref, Module, S),
RS = case elixir_aliases:nesting(S#elixir_scope.module, FullModule) of
false -> S;
Alias -> element(2, translate_each({ alias, Meta, [FullModule, [{ as, Alias }]] }, S))
RS = case elixir_aliases:nesting_alias(S#elixir_scope.module, FullModule) of
{ New, Old } ->
S#elixir_scope{
aliases=orddict:store(New, Old, S#elixir_scope.aliases),
macro_aliases=orddict:store(New, Old, S#elixir_scope.macro_aliases)};
false ->
S
end,
{
@@ -211,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);
@@ -219,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),
@@ -327,14 +331,19 @@ rewrite_case_clauses([{do,[{in,_,[{'_',_,_},[false,nil]]}],False},{do,[{'_',_,_}
rewrite_case_clauses(Clauses) ->
Clauses.
module_ref(Raw, Module, Nesting) when is_atom(Raw); Nesting == nil ->
expand_module(Raw, Module, #elixir_scope{module=Nesting}) when is_atom(Raw); Nesting == nil ->
Module;
module_ref({ '__aliases__', _, ['Elixir'|_] }, Module, _Nesting) ->
Module;
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;
Aliases when is_list(Aliases) ->
elixir_aliases:concat([S#elixir_scope.module, Module])
end;
module_ref(_Raw, Module, Nesting) ->
elixir_aliases:concat([Nesting, Module]).
expand_module(_Raw, Module, S) ->
elixir_aliases:concat([S#elixir_scope.module, Module]).
is_reserved_data(moduledoc) -> true;
is_reserved_data(doc) -> true;
@@ -349,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.
+17 -18
View File
@@ -1,5 +1,6 @@
-module(elixir_module).
-export([translate/4, compile/5, data_table/1, eval_quoted/4, format_error/1]).
-export([translate/4, compile/5, data_table/1, eval_quoted/4,
format_error/1, eval_callbacks/5]).
-include("elixir.hrl").
-compile({parse_transform, elixir_transform}).
@@ -133,7 +134,8 @@ eval_form(Line, Module, Block, Vars, RawS) ->
S = scope_for_eval(Module, RawS),
{ Value, NewS } = elixir_compiler:eval_forms([Block], Line, Vars, S),
elixir_def_overridable:store_pending(Module),
eval_callbacks(Line, Module, before_compile, [Module], NewS),
Env = elixir_scope:to_ex_env({ Line, S }),
eval_callbacks(Line, Module, before_compile, [Env], NewS),
elixir_def_overridable:store_pending(Module),
Value.
@@ -141,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 } =
@@ -154,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 };
@@ -238,7 +240,8 @@ spec_for_macro(Else) -> Else.
load_form(Line, Forms, S) ->
elixir_compiler:module(Forms, S, fun(Module, Binary) ->
EvalS = scope_for_eval(Module, S),
eval_callbacks(Line, Module, after_compile, [Module, Binary], EvalS),
Env = elixir_scope:to_ex_env({ Line, EvalS }),
eval_callbacks(Line, Module, after_compile, [Env, Binary], EvalS),
case get(elixir_compiled) of
Current when is_list(Current) ->
@@ -324,17 +327,13 @@ eval_callbacks(Line, Module, Name, Args, RawS) ->
S = RawS#elixir_scope{check_clauses=false},
Binding = binding_for_eval(Module, []),
Callbacks = lists:reverse(ets:lookup_element(data_table(Module), Name, 2)),
Requires = S#elixir_scope.requires,
Meta = [{line,Line}],
Meta = [{line,Line},{require,false}],
lists:foreach(fun({M,F}) ->
Expr = { { '.', Meta, [M,F] }, Meta, Args },
Scope = case ordsets:is_element(M, Requires) of
true -> S;
false -> S#elixir_scope{requires=ordsets:add_element(M, Requires)}
end,
{ Tree, _ } = elixir_translator:translate_each(Expr, Scope),
{ Tree, _ } = elixir_dispatch:dispatch_require(Meta, M, F, Args, S, fun() ->
apply(M, F, Args),
{ { nil, 0 }, S }
end),
try
erl_eval:exprs([Tree], Binding)
@@ -348,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)]).
+2 -2
View File
@@ -553,10 +553,10 @@ build_sigil({ sigil, Line, Sigil, Parts, Modifiers }) ->
build_bin_string({ bin_string, _Line, [H] }) when is_binary(H) -> H;
build_bin_string({ bin_string, Line, Args }) -> { '<<>>', [{line,Line}], Args }.
build_list_string({ list_string, _Line, [H] }) when is_binary(H) -> binary_to_list(H);
build_list_string({ list_string, _Line, [H] }) when is_binary(H) -> unicode:characters_to_list(H);
build_list_string({ list_string, Line, Args }) ->
Meta = [{line,Line}],
{ { '.', Meta, [erlang, binary_to_list] }, Meta, [{ '<<>>', Meta, Args}] }.
{ { '.', Meta, [unicode, characters_to_list] }, Meta, [{ '<<>>', Meta, Args}] }.
build_atom({ atom, _Line, Atom }) when is_atom(Atom) -> Atom;
build_atom({ atom, Line, Args }) ->
+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),
+57 -22
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,56 +34,83 @@ 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{expand_aliases=false}, S);
do_quote(Expr, Q#elixir_quote{aliases_hygiene=false}, S);
do_quote({ '__aliases__', Meta, [H|T] } = Alias, #elixir_quote{aliases_hygiene=true} = Q, S) when is_atom(H) and (H /= 'Elixir') ->
Annotation = case elixir_aliases:expand(Alias, S#elixir_scope.aliases, S#elixir_scope.macro_aliases) of
Atom when is_atom(Atom) -> Atom;
Aliases when is_list(Aliases) -> false
end,
do_quote({ '__aliases__', Meta, [H|_] = Aliases }, #elixir_quote{expand_aliases=true} = Q, S) when is_atom(H) and H /= 'Elixir' ->
Line = ?line(Meta),
{ TAliases, SA } = do_quote(['Elixir'|Aliases], Q, S),
{ TAliases, SA } = do_quote([H|T], Q, S),
{ { tuple, Line, [
{ atom, Line, '__aliases__' },
meta(Meta, Q),
meta([{alias,Annotation}|Meta], Q),
TAliases
] }, SA };
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),
Tuple = { tuple, Line, [
{ atom, Line, Left },
meta(Meta, Q),
{ atom, Line, Q#elixir_quote.var_context }
{ atom, Line, Q#elixir_quote.vars_hygiene }
] },
{ Tuple, S };
do_quote({ Name, Meta, ArgsOrAtom } = Tuple, #elixir_quote{expand_imports=true} = Q, S) when is_atom(Name) ->
do_quote({ Name, Meta, ArgsOrAtom } = Tuple, #elixir_quote{imports_hygiene=true} = Q, S) when is_atom(Name) ->
Arity = case is_atom(ArgsOrAtom) of
true -> 0;
false -> length(ArgsOrAtom)
@@ -134,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) ->
@@ -154,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) ->
+17 -20
View File
@@ -3,13 +3,11 @@
-module(elixir_scope).
-export([translate_var/5,
build_erl_var/2, build_ex_var/2,
build_erl_var/3, build_ex_var/3,
build_erl_var/4, build_ex_var/4,
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}).
@@ -22,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 } } ->
@@ -30,8 +28,8 @@ translate_var(Meta, Name, Kind, S, Callback) ->
{ Else, _ } ->
{ NewVar, NS } = if
Kind /= nil -> build_erl_var(Line, S);
Else -> build_erl_var(Line, Name, S);
S#elixir_scope.noname -> build_erl_var(Line, Name, S);
Else -> build_erl_var(Line, Name, Name, S);
S#elixir_scope.noname -> build_erl_var(Line, Name, Name, S);
true -> { { var, Line, Name }, S }
end,
RealName = element(3, NewVar),
@@ -58,9 +56,6 @@ translate_var(Meta, Name, Kind, S, Callback) ->
build_ex_var(Line, S) -> build_ex_var(Line, '', "_", S).
build_erl_var(Line, S) -> build_erl_var(Line, '', "_", S).
build_ex_var(Line, Key, S) -> build_ex_var(Line, Key, Key, S).
build_erl_var(Line, Key, S) -> build_erl_var(Line, Key, Key, S).
build_var_counter(Key, #elixir_scope{counter=Counter} = S) ->
New = orddict:update_counter(Key, 1, Counter),
{ orddict:fetch(Key, New), S#elixir_scope{counter=New} }.
@@ -82,26 +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.scheduled }
S#elixir_scope.requires, S#elixir_scope.macros, S#elixir_scope.aliases,
S#elixir_scope.macro_functions, S#elixir_scope.macro_macros, S#elixir_scope.macro_aliases,
S#elixir_scope.scheduled }
).
serialize_with_vars(Line, S) when is_integer(Line) ->
@@ -119,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, Scheduled }, Vars) ->
deserialize_with_vars({ File, Functions, CheckClauses, Requires, Macros,
Aliases, MacroFunctions, MacroMacros, MacroAliases, Scheduled }, Vars) ->
#elixir_scope{
file=File,
functions=Functions,
@@ -127,6 +121,9 @@ 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),
counter=[{'',length(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;
+85 -65
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),
@@ -107,7 +110,8 @@ translate_each({ alias, Meta, [Ref, KV] }, S) ->
{ as, false } ->
{ Old, SR };
{ as, Other } ->
{ TOther, SA } = translate_each(Other, SR#elixir_scope{aliases=[]}),
{ TOther, SA } = translate_each(no_alias_expansion(Other),
SR#elixir_scope{aliases=[],macro_aliases=[]}),
case TOther of
{ atom, _, Atom } -> { Atom, SA };
_ -> syntax_error(Meta, S#elixir_scope.file,
@@ -127,7 +131,8 @@ translate_each({ alias, Meta, [Ref, KV] }, S) ->
end,
{ { nil, ?line(Meta) }, SF#elixir_scope{
aliases=orddict:store(New, Old, S#elixir_scope.aliases)
aliases=orddict:store(New, Old, S#elixir_scope.aliases),
macro_aliases=orddict:store(New, Old, S#elixir_scope.macro_aliases)
} }
end;
_ ->
@@ -138,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),
@@ -171,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),
@@ -205,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} };
@@ -213,8 +219,8 @@ translate_each({ '__CALLER__', Meta, Atom }, S) when is_atom(Atom) ->
%% Aliases
translate_each({ '__aliases__', Meta, _ } = Alias, S) ->
case elixir_aliases:expand(Alias, S#elixir_scope.aliases) of
Atom when is_atom(Atom) -> { { atom, ?line(Meta), Atom}, S };
case elixir_aliases:expand(Alias, S#elixir_scope.aliases, S#elixir_scope.macro_aliases) of
Atom when is_atom(Atom) -> { { atom, ?line(Meta), Atom }, S };
Aliases ->
{ TAliases, SA } = translate_args(Aliases, S),
@@ -230,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) ->
@@ -244,13 +250,25 @@ translate_each({ quote, Meta, [T] }, S) when is_list(T) ->
syntax_error(Meta, S#elixir_scope.file, "invalid args for quote")
end,
Context = case lists:keyfind(var_context, 1, T) of
{ var_context, VarContext } ->
expand_var_context(Meta, VarContext,
"invalid argument given for var_context in quote", S);
Hygiene = case lists:keyfind(hygiene, 1, T) of
{ hygiene, true } ->
elixir_errors:deprecation(Meta, S#elixir_scope.file, "hygiene: true is deprecated, please set it to [] instead", []),
[];
{ hygiene, List } when is_list(List) ->
List;
{ hygiene, false } ->
elixir_errors:deprecation(Meta, S#elixir_scope.file, "hygiene: false is deprecated, please set it to [vars: false] instead", []),
[{vars,false}, {imports,false}, {aliases,false}];
false ->
case lists:keyfind(hygiene, 1, T) of
{ hygiene, false } -> nil;
[]
end,
Vars = case lists:keyfind(var_context, 1, T) of
{ var_context, VarContext } ->
expand_var_context(Meta, VarContext, "invalid argument given for var_context in quote", S);
false ->
case lists:keyfind(vars, 1, Hygiene) of
{ vars, false } -> nil;
_ ->
case S#elixir_scope.module of
nil -> 'Elixir';
@@ -259,20 +277,18 @@ translate_each({ quote, Meta, [T] }, S) when is_list(T) ->
end
end,
GetExpansion = fun(Key) ->
GetExpansion = fun(Key, New) ->
case lists:keyfind(Key, 1, T) of
{ Key, Bool } when is_boolean(Bool) ->
elixir_errors:deprecation(Meta, S#elixir_scope.file, "the ~s option for quote is deprecated, please use hygiene: [~s: ~s] instead", [Key, New, Bool]),
Bool;
false ->
case lists:keyfind(hygiene, 1, T) of
{ hygiene, Bool } when is_boolean(Bool) -> Bool;
false -> true
end
not(lists:keyfind(New, 1, Hygiene) == { New, false })
end
end,
Aliases = GetExpansion(expand_aliases),
Imports = GetExpansion(expand_imports),
Aliases = GetExpansion(aliases_hygiene, aliases),
Imports = GetExpansion(imports_hygiene, imports),
{ DefaultLine, DefaultFile } = case lists:keyfind(location, 1, T) of
{ location, keep } -> { keep, keep };
@@ -314,8 +330,8 @@ translate_each({ quote, Meta, [T] }, S) when is_list(T) ->
_ -> true
end,
Q = #elixir_quote{var_context=Context, line=TLine, unquote=Unquote,
expand_aliases=Aliases, expand_imports=Imports},
Q = #elixir_quote{vars_hygiene=Vars, line=TLine, unquote=Unquote,
aliases_hygiene=Aliases, imports_hygiene=Imports},
elixir_quote:quote(TExprs, Q, SL);
@@ -343,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
@@ -356,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),
@@ -385,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() ->
@@ -413,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
@@ -472,6 +482,13 @@ translate_each({ { '.', _, [Expr] }, Meta, Args } = Original, S) ->
{ TExpr, SE } = translate_each(Expr, S),
case TExpr of
{ 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
@@ -527,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) ->
@@ -544,6 +555,12 @@ expand_var_context(Meta, Alias, Msg, S) ->
syntax_error(Meta, S#elixir_scope.file, "~ts, expected a compile time available alias or an atom", [Msg])
end.
no_alias_expansion({ '__aliases__', Meta, [H|T] }) when (H /= 'Elixir') and is_atom(H) ->
{ '__aliases__', Meta, ['Elixir',H|T] };
no_alias_expansion(Other) ->
Other.
%% Translate args
%% Variables in arguments are not propagated from one
@@ -563,9 +580,9 @@ expand_var_context(Meta, Alias, Msg, S) ->
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) ->
@@ -641,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
@@ -173,7 +175,7 @@ defmodule Binary.Inspect.ListTest do
end
test :codepoints do
assert inspect('é') == "[195,169]"
assert inspect('é') == "'é'"
end
test :empty do
+5 -6
View File
@@ -18,7 +18,7 @@ bar '''
end
test :utf8 do
assert length(' ゆんゆん') == 13
assert length(' ゆんゆん') == 5
end
test :octals do
@@ -37,10 +37,9 @@ bar '''
assert '\xfF' == 'ÿ'
assert '\x{A}' == '\n'
assert '\x{e9}' == 'é'
assert '\x{10F}' == [196,143]
assert '\x{10FF}' == [225,131,191]
assert '\x{10FFF}' == [240,144,191,191]
assert '\x{10FFFF}' == [244,143,191,191]
assert '\x{10F}' == [271]
assert '\x{10FF}' == [4351]
assert '\x{10FFF}' == [69631]
assert '\x{10FFFF}' == [1114111]
end
end
+3 -3
View File
@@ -61,8 +61,8 @@ defmodule CodeTest do
end
test :string_to_ast do
assert Code.string_to_ast("1 + 2") == { :ok, quote hygiene: false, line: 1, do: 1 + 2 }
assert Code.string_to_ast("1 + 2; 3 + 4") == { :ok, quote hygiene: false, line: 1, do: (1 + 2; 3 + 4) }
assert Code.string_to_ast("1 + 2") == { :ok, quote hygiene: [imports: false], line: 1, do: 1 + 2 }
assert Code.string_to_ast("1 + 2; 3 + 4") == { :ok, quote hygiene: [imports: false], line: 1, do: (1 + 2; 3 + 4) }
assert { :error, _ } = Code.string_to_ast("a.1")
end
@@ -72,7 +72,7 @@ defmodule CodeTest do
end
test :string_to_ast! do
assert Code.string_to_ast!("1 + 2") == quote hygiene: false, line: 1, do: 1 + 2
assert Code.string_to_ast!("1 + 2") == quote hygiene: [imports: false], line: 1, do: 1 + 2
assert_raise SyntaxError, fn ->
Code.string_to_ast!("a.1")
+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
+26 -4
View File
@@ -10,14 +10,16 @@ defmodule FileTest do
# cp_r/c is managed in setup/teardown because if it is stored in
# the repository, reltool can't build a release
def setup do
setup do
src = fixture_path("cp_r")
:file.make_symlink 'certainly/invalid', Path.join([src, "c"])
:ok
end
def teardown do
teardown do
src = fixture_path("cp_r")
File.rm Path.join([src, "c"])
:ok
end
test :cp_with_src_file_and_dest_file do
@@ -395,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
@@ -677,14 +697,16 @@ defmodule FileTest do
# cp_r/c is managed in setup/teardown because if it is stored in
# the repository, reltool can't build a release
def setup do
setup do
src = fixture_path("cp_r")
:file.make_symlink 'certainly/invalid', Path.join([src, "c"])
:ok
end
def teardown do
teardown do
src = fixture_path("cp_r")
File.rm Path.join([src, "c"])
:ok
end
test :rm_file do
@@ -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
+44
View File
@@ -0,0 +1,44 @@
Code.require_file "../../test_helper.exs", __FILE__
defmodule IO.ANSITest do
use ExUnit.Case, async: true
test :escape_single do
assert IO.ANSI.escape("Hello, %{red}world!", true) ==
"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
assert IO.ANSI.escape("Hello, %{red,bright}world!", true) ==
"Hello, #{IO.ANSI.red}#{IO.ANSI.bright}world!#{IO.ANSI.reset}"
assert IO.ANSI.escape("Hello, %{red, bright}world!", true) ==
"Hello, #{IO.ANSI.red}#{IO.ANSI.bright}world!#{IO.ANSI.reset}"
assert IO.ANSI.escape("Hello, %{red , bright}world!", true) ==
"Hello, #{IO.ANSI.red}#{IO.ANSI.bright}world!#{IO.ANSI.reset}"
end
test :no_emit do
assert IO.ANSI.escape("Hello, %{red,bright}world!", false) ==
"Hello, world!"
end
test :fragment do
assert IO.ANSI.escape("%{red}", true) == "#{IO.ANSI.red}#{IO.ANSI.reset}"
assert IO.ANSI.escape_fragment("", true) == ""
end
test :noop do
assert IO.ANSI.escape("") == ""
end
test :invalid do
assert_raise ArgumentError, "invalid ANSI sequence specification: brigh", fn ->
IO.ANSI.escape("%{brigh}, yes")
end
assert_raise ArgumentError, "invalid ANSI sequence specification: brigh", fn ->
IO.ANSI.escape("%{brigh,red}, yes")
end
end
end
+57 -1
View File
@@ -26,4 +26,60 @@ defmodule Kernel.AliasTest do
alias List, as: Nested
assert Nested.flatten([[13]]) == [13]
end
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
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
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
use ExUnit.Case, async: true
require Kernel.AliasNestingGenerator
Kernel.AliasNestingGenerator.create
test :aliases_nesting do
assert Parent.a == :a
assert Parent.Child.b == :a
end
end
defmodule Kernel.AliasMacroNestingTest do
use ExUnit.Case, async: true
require Kernel.AliasNestingGenerator
Kernel.AliasNestingGenerator.record
test :aliases_nesting do
assert is_record(Parent.new, Parent)
assert Parent.Child.b.message == "ok"
end
end
@@ -18,6 +18,18 @@ defmodule Kernel.CaseTest do
assert vars_case(-1, -1) == { 0, 1 }
end
test :nested_vars_match do
x = { :error, { :ok, :done } }
assert (case x do
{ :ok, right } ->
right
{ _left, right } ->
case right do
{ :ok, right } -> right
end
end) == :done
end
test :match_with_in do
assert(case 3 do
x in [1,2,3] ->
+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
+18 -13
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
@@ -270,7 +275,7 @@ defmodule Kernel.ErrorsTest do
end
test :macros_compiled_callback do
assert [{Kernel.ErrorsTest,:__before_compile__,[Foo],_}|_] =
assert [{Kernel.ErrorsTest,:__before_compile__,[Macro.Env[module: Foo]],_}|_] =
rescue_stacktrace("defmodule Foo do\nModule.put_attribute(__MODULE__, :before_compile, Kernel.ErrorsTest)\nend")
end
@@ -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
@@ -15,12 +15,6 @@ defmodule Kernel.PartialApplicationTest do
assert fun.(13, 6) == 7
end
test :partial_with_atom_call_and_one_item do
fun = :minus.(10, &1)
assert fun.(5) == 5
assert fun.(3) == 7
end
test :partial_with_funcall_and_one_item do
fun = minus(&1, &2)
fun = fun.(10, &1)
+107 -4
View File
@@ -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)
@@ -90,7 +120,7 @@ defmodule Kernel.QuoteTest.VarHygiene do
end
defmacro no_hygiene do
quote [hygiene: false] do
quote [hygiene: [vars: false]] do
a = 1
end
end
@@ -157,10 +187,43 @@ end
defmodule Kernel.QuoteTest.AliasHygieneTest do
use ExUnit.Case, async: true
alias Dict, as: SuperDict
defmacro dict do
quote do: Dict.Bar
end
defmacro super_dict do
quote do: SuperDict.Bar
end
test :annotate_aliases do
assert quote(do: Foo.Bar) == { :__aliases__, [alias: false], [:Foo, :Bar] }
assert quote(do: Dict.Bar) == { :__aliases__, [alias: false], [:Dict, :Bar] }
assert quote(do: SuperDict.Bar) == { :__aliases__, [alias: Dict.Bar], [:SuperDict, :Bar] }
assert quote(do: alias!(SuperDict.Bar)) == { :__aliases__, [], [:SuperDict, :Bar] }
end
test :expand_aliases do
assert Code.eval_quoted(quote do: Foo.Bar) == { Elixir.Foo.Bar, [] }
assert Code.eval_quoted(quote do: alias!(Foo.Bar)) == { Foo.Bar, [] }
assert Code.eval_quoted(quote expand_aliases: false, do: Foo.Bar) == { Foo.Bar, [] }
assert Code.eval_quoted(quote do: SuperDict.Bar) == { Elixir.Dict.Bar, [] }
assert Code.eval_quoted(quote do: alias!(SuperDict.Bar)) == { Elixir.SuperDict.Bar, [] }
assert Code.eval_quoted(quote do
alias HashDict, as: SuperDict
SuperDict.Bar
end) == { Elixir.HashDict.Bar, [] }
assert Code.eval_quoted(quote do
alias HashDict, as: Dict
require Kernel.QuoteTest.AliasHygieneTest
Kernel.QuoteTest.AliasHygieneTest.dict
end) == { Elixir.Dict.Bar, [] }
assert Code.eval_quoted(quote do
alias HashDict, as: SuperDict
require Kernel.QuoteTest.AliasHygieneTest
Kernel.QuoteTest.AliasHygieneTest.super_dict
end) == { Elixir.Dict.Bar, [] }
end
end
@@ -173,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
@@ -191,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
+25 -19
View File
@@ -58,7 +58,7 @@ defmodule KernelTest do
end
test :function_from___ENV__ do
assert __ENV__.function == { :test_function_from___ENV__, 0 }
assert __ENV__.function == { :test_function_from___ENV__, 1 }
end
defp x(value) when value in [1,2,3], do: true
@@ -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
@@ -347,7 +353,7 @@ defmodule KernelTest do
test :invalid_match do
a = 3
assert_raise CaseClauseError, fn ->
destructure [^a,b,c], a_list
destructure [^a,_b,_c], a_list
end
end
+2 -6
View File
@@ -3,13 +3,9 @@ Code.require_file "../test_helper.exs", __FILE__
defmodule ListTest do
use ExUnit.Case, async: true
test :brackets_function do
assert :[].(1,2,3) == [1,2,3]
end
test :optional_comma do
assert :[].(1,) == [ 1, ]
assert :[].(1,2,3,) == [1,2,3,]
assert [1] == [ 1, ]
assert [1,2,3] == [1,2,3,]
end
test :partial_application do
+62 -1
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
@@ -82,6 +135,14 @@ defmodule MacroTest do
assert Macro.expand(quote(do: :foo), __ENV__) == :foo
end
defmacro local_macro do
:local_macro
end
test :expand_local_macro do
assert Macro.expand(quote(do: local_macro), __ENV__) == :local_macro
end
test :expand_with_imported_macro do
assert Macro.expand(quote(do: 1 || false), __ENV__) == (quote var_context: Kernel do
case 1 do
@@ -288,7 +349,7 @@ defmodule MacroTest do
test :env_stacktrace do
env = __ENV__.file("foo").line(12)
assert env.stacktrace == [{ __MODULE__, :test_env_stacktrace, 0, [file: "foo", line: 12] }]
assert env.stacktrace == [{ __MODULE__, :test_env_stacktrace, 1, [file: "foo", line: 12] }]
env = env.function(nil)
assert env.stacktrace == [{ __MODULE__, :__MODULE__, 0, [file: "foo", line: 12] }]
env = env.module(nil)
+5 -4
View File
@@ -16,12 +16,12 @@ defmodule ModuleTest.ToBeUsed do
quote do: (def before_compile, do: true)
end
defmacro __after_compile__(ModuleTest.ToUse, bin) when is_binary(bin) do
defmacro __after_compile__(Macro.Env[module: ModuleTest.ToUse], bin) when is_binary(bin) do
# IO.puts "HELLO"
end
defmacro callback(target) do
value = Module.get_attribute(target, :has_callback)
defmacro callback(env) do
value = Module.get_attribute(env.module, :has_callback)
quote do
name = :original_value
args = [1]
@@ -57,7 +57,7 @@ defmodule ModuleTest do
Module.eval_quoted __MODULE__, contents, [], file: "sample.ex", line: 13
defmacrop in_module(block) do
quote expand_aliases: false do
quote do
defmodule Temp, unquote(block)
:code.purge(Temp)
:code.delete(Temp)
@@ -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
+10 -3
View File
@@ -71,11 +71,18 @@ defmodule OptionParserTest do
end
test "parses more than one key/value options" do
assert OptionParser.parse(["--source", "from_docs/", "--docs", "false"]) == { [source: "from_docs/", docs: false], [] }
assert OptionParser.parse(["--source", "from_docs/", "--docs", "false"]) ==
{ [source: "from_docs/", docs: false], [] }
end
test "accumulates options accumulators" do
assert OptionParser.parse(["--require", "foo", "--require", "bar", "baz"]) == { [require: "foo", require: "bar"], ["baz"] }
test "overrides options by default" do
assert OptionParser.parse(["--require", "foo", "--require", "bar", "baz"]) ==
{ [require: "bar"], ["baz"] }
end
test "keeps options on keep" do
assert OptionParser.parse(["--require", "foo", "--require", "bar", "baz"], switches: [require: :keep]) ==
{ [require: "foo", require: "bar"], ["baz"] }
end
test "parses mixed options" do
+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
+1 -1
View File
@@ -116,7 +116,7 @@ defmodule ProtocolTest do
assert_protocol_for(ProtocolTest.WithAll, Tuple, {Bar,2,3})
assert_protocol_for(ProtocolTest.WithAll, BitString, "foo")
assert_protocol_for(ProtocolTest.WithAll, BitString, <<1>>)
assert_protocol_for(ProtocolTest.WithAll, PID, Process.self)
assert_protocol_for(ProtocolTest.WithAll, PID, Kernel.self)
assert_protocol_for(ProtocolTest.WithAll, Port, hd(:erlang.ports))
assert_protocol_for(ProtocolTest.WithAll, Reference, make_ref)
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
-13
View File
@@ -127,19 +127,6 @@ end
defmodule Regex.ListTest do
use ExUnit.Case, async: true
test :compile do
assert is_record(Regex.compile!('foo'), Regex)
assert is_regex(Regex.compile!('foo'))
end
test :source do
assert Regex.source(Regex.compile!('foo')) == "foo"
end
test :opts do
assert Regex.opts(Regex.compile!('foo', 'u')) == "u"
end
test :match? do
assert Regex.match?(%r(foo), 'foo')
refute Regex.match?(%r(foo), 'FOO')
+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"]
+11 -3
View File
@@ -8,12 +8,20 @@ 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
assert :{}.(1,) == { 1, }
assert :{}.(1, 2, 3) == { 1, 2, 3, }
assert { 1 } == { 1, }
assert { 1, 2, 3 } == { 1, 2, 3, }
end
test :partial_application do

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