Compare commits

...
519 Commits
Author SHA1 Message Date
José Valim 59d6448609 Release v0.8.3 2013-05-22 14:32:01 +02:00
José Valim 31b4290797 Add IEx processing to Kernel.CLI 2013-05-22 12:53:55 +02:00
José Valim 39c33c7bb1 Fix h(c/2), closes #1101 2013-05-22 11:01:47 +02:00
José Valim 37054c8226 Deprecate Exception.format_entry in favor of Exception.format_stacktrace_entry 2013-05-22 00:55:46 +02:00
José Valim 9751ca70c1 IEx.Remsh should be private 2013-05-22 00:31:00 +02:00
José Valim 054ff457be Remove Enum.equal?
There is a branch work refactoring Enum and Enum.equal?
contains some limitations. We are removing it for now
since it was not officially released yet.
2013-05-21 20:30:19 +02:00
José Valim b5fce07ec0 Do not raise if a dependency can't be found, closes #1052 2013-05-21 14:22:35 +02:00
José Valim d722685c6a Update CHANGELOG 2013-05-21 13:19:33 +02:00
José Valim beaa746fcb Support MIX_PATH as alternative load paths, closes #889 2013-05-21 13:16:30 +02:00
José Valim c3d6a96dac Consider load time on ExUnit reports, closes #893 2013-05-21 12:57:22 +02:00
José Valim d238d50579 Deprecate Code.eval in favor of Code.eval_string, closes #953 2013-05-21 12:57:22 +02:00
José Valim 4bde3c85ed Remove empty? from the Enum protocol, closes #1097 2013-05-21 12:57:22 +02:00
José Valim 9b0353a747 Improve error messages when there is a space in between function name and parenthesis 2013-05-21 12:57:22 +02:00
José Valim b6cb6ca88a Merge pull request #1098 from alco/path-expand-doc
Fix behavior of Path.expand/2. Improve docstrings
2013-05-21 00:46:02 -07:00
Alexei Sholik 33ab2895a0 Mention Path.expand/2 in the Changelog 2013-05-21 02:24:29 +03:00
Alexei Sholik 245bc09d7b Fix behavior of Path.expand/2. Improve docstrings 2013-05-21 02:17:23 +03:00
José Valim 0abeef0724 Update CHANGELOG 2013-05-20 21:57:48 +02:00
José Valim 771708764f Improve docs for the IEx module and some error messages as well 2013-05-20 21:31:38 +02:00
José Valim 08e6afe5a9 Merge pull request #1096 from alco/iex-break-1089
Implement a break-trigger for incomplete expressions in IEx.
2013-05-20 05:50:40 -07:00
Alexei Sholik c4c0798c7e Change the break-trigger for IEx to #iex:break 2013-05-20 15:09:47 +03:00
Alexei Sholik 392a2fbef2 Document the break-trigger in h/0 2013-05-20 15:09:46 +03:00
Alexei Sholik bcd4b97253 Call it simply break-trigger in the docstring 2013-05-20 15:09:46 +03:00
Alexei Sholik 614dc52af4 Implement a break-trigger for incomplete expressions in IEx. Closes #1089 2013-05-20 15:09:46 +03:00
José Valim 8907edca3e Improve error message for invalid patterns in guards and matches
Closes #1092
2013-05-19 21:39:44 +02:00
José Valim a893d805aa Merge pull request #1095 from mururu/fix-getn-doc
Fix docs for IO.getn
2013-05-19 12:29:11 -07:00
Yuki Ito 0142587fd2 Fix docs for IO.getn 2013-05-20 04:12:11 +09:00
José Valim 56a52f162b Merge pull request #1094 from mururu/getb-to-getn
Deprecate IO.getb in favor of IO.getn
2013-05-19 11:45:57 -07:00
Yuki Ito 5ec2de4b76 Deprecate IO.getb in favor of IO.getn 2013-05-20 03:29:29 +09:00
José Valim d9b98a62c8 Check if the module is in the same context before checking if it is open 2013-05-19 19:42:13 +02:00
José Valim 78a0abc08f Check if we are in the current module context as a fast path 2013-05-19 18:41:08 +02:00
José Valim 0328d867a9 Check if a module is open or not when defining a record 2013-05-19 18:33:39 +02:00
José Valim 05b650f373 Change preference of |> to support constructs like 1..5 |> Enum.to_list 2013-05-19 18:33:14 +02:00
José Valim b3e8a3d8fe Improve docs for records 2013-05-19 15:54:33 +02:00
José Valim 021dc541b8 Remove unsupported matching on rescue clause 2013-05-19 15:54:33 +02:00
José Valim af0970fd0b Merge pull request #1093 from bellthoven/fix-mix-compile-quick
Only mix compile -q recompiles unnecessarly
2013-05-19 06:54:08 -07:00
Gustavo Dutra 565fc8bffe Only mix compile -q recompiles unnecessarly
mix compile --quick was recompiling previously compiled files even when nothing had changed
2013-05-19 10:24:24 -03:00
José Valim a6317d525e Merge pull request #1090 from alco/require_file-docfix
Fix docstring for require_file
2013-05-18 04:59:53 -07:00
Alexei Sholik e96963c4a4 Fix docstring for require_file 2013-05-18 13:25:56 +03:00
José Valim a57be7379b Merge pull request #1087 from alco/iex-docs
Review IEx docs
2013-05-17 09:16:23 -07:00
Alexei Sholik 79020c768f Improve docs for IEx.Helpers 2013-05-17 19:10:58 +03:00
Alexei Sholik 714b4440e3 Update moduledoc for IEx.Remsh 2013-05-17 18:50:20 +03:00
Alexei Sholik 1c505ad451 Add a docstring for IEx.Server.start 2013-05-17 18:49:39 +03:00
José Valim 5cbe0baa2d Merge pull request #1081 from alco/iex-token-missing-error
TokenMissingError fix
2013-05-17 07:23:44 -07:00
Alexei Sholik 98ed3045bb Clean up the code some more. Closes #1051 2013-05-17 15:29:43 +03:00
José Valim 441c333939 Optimize HashDict.take and HashDict.split, update CHANGELOG 2013-05-17 12:34:11 +02:00
Jake Mitchell bf356495a4 Use faster HashDict.drop/2, ListDict.take/2 and ListDict.drop/2 implementations 2013-05-17 12:26:19 +02:00
José Valim 08211b7c52 Merge pull request #1085 from TanYewWei/issue_1069
Issue #1069: Add Dict.drop, Dict.take and Dict.split
2013-05-17 03:12:41 -07:00
Yew-wei Tan 216019c34f fix inappropriate doctests for drop/2, take/2, and split/2 functions of Dict module 2013-05-17 19:59:33 +10:00
José Valim 16555c974a Update ExDoc directory 2013-05-17 09:54:28 +02:00
José Valim 869e90f2e5 Split SOURCE_REF into its own makefile function 2013-05-17 09:50:08 +02:00
José Valim 80633a2ec6 Use :stdio as default argument in capture_io 2013-05-17 09:47:39 +02:00
José Valim b5d20d3135 Merge pull request #1078 from alco/custom-doc-rev
Retrieve the most suitable revision for source links in the docs
2013-05-17 00:47:31 -07:00
José Valim 911eb23ee2 Merge pull request #1083 from ericmj/mix-cmd
Remove unnecessary Mix test output
2013-05-17 00:46:45 -07:00
Yew-wei Tan 4a3e741070 Issue #1069: Add Dict.drop, Dict.take and Dict.split 2013-05-17 16:04:41 +10:00
Eric Meadows-Jonsson 3e44e98299 Remove unnecessary Mix test output 2013-05-17 01:38:12 +02:00
Alexei Sholik b08289768a Replace getrev.sh with a one-liner (thanks @bellthoven) 2013-05-17 01:35:48 +03:00
José Valim 6e5c4feac0 Merge pull request #1082 from ericmj/mix-umbrella-appfile2
Clean up mix dependency retrieving and converging
2013-05-16 11:58:19 -07:00
José Valim be16bf487a Remove deprecated usage of keywords in Mix 2013-05-16 20:57:51 +02:00
José Valim 647344ea9d Update CHANGELOG 2013-05-16 20:53:23 +02:00
José Valim 6589d17527 Deprecate Enum.at! and Dict.get! in favor of Enum.fetch! and Dict.fetch!
Previously, `Dict.get!` had a different API than `Dict.get` as
it didn't allow a default argument. Furthermore, having `Dict.fetch`
returning a tuple and `Dict.fetch!` returning an error is more
inline to the bang functions existent in the File module.
2013-05-16 20:49:09 +02:00
Eric Meadows-Jonsson 2d870cf528 Clean up mix dependency retrieving and converging
Rename Mix.Deps.Project -> Mix.Deps.Retriever.
Retrieve the entire dependency tree in Retriever and let Converger
only handle converging.
Don't require setting app: false on umbrella deps.
2013-05-16 20:42:37 +02:00
Alexei Sholik 36fab9dafb Fix stray file variable 2013-05-16 21:38:01 +03:00
Alexei Sholik d61a82565b Remove IExTokenMissingError and clean up the code 2013-05-16 21:28:24 +03:00
Alexei Sholik a63e4da167 Distinguish between TokenMissingError raised during parsing from the one
raised during evaluation in IEx
2013-05-16 21:06:07 +03:00
José Valim de1d47f9d5 Add Enum.at/2, Enum.at/3 and Enum.fetch/2 2013-05-16 18:32:01 +02:00
José Valim 1051b61605 Also convert String.to_integer tests to use === 2013-05-16 18:16:21 +02:00
José Valim f8f37c2b24 Remove if in favor of extra function clause 2013-05-16 18:14:14 +02:00
José Valim 5e0f171655 Merge pull request #1079 from jdeisenberg/string_to_int_float
Change == to === in tests for String.to_integer and String.to_float.
2013-05-16 07:02:12 -07:00
J David Eisenberg 521249edfe Change == to === in tests for String.to_integer and String.to_float. 2013-05-16 05:58:04 -07:00
Alexei Sholik a67e445883 Retrieve the most suitable revision for source links in the docs 2013-05-16 15:55:09 +03:00
José Valim a0544478a6 Merge pull request #1077 from meh/string-slice
Make String.slice consistent with Ruby semantics, closes #1034
2013-05-16 05:20:32 -07:00
José Valim 056df8c5d3 Merge pull request #1076 from mururu/default_args
Fix warning for unused default arguments
2013-05-16 05:19:50 -07:00
meh 6d6ec1ea11 Make String.slice consistent with Ruby semantics, closes #1034 2013-05-16 11:56:21 +02:00
Yuki Ito 020c5b0dd2 Fix warning for unused default arguments 2013-05-16 13:58:29 +09:00
José Valim d8e374df56 Merge pull request #1044 from jdeisenberg/string_to_int_float
String.to_integer and String.to_float
2013-05-15 15:59:24 -07:00
José Valim 37ff5a6fda Merge pull request #1072 from ericmj/mix-umbrella-dirfile
Ignore files in umbrella apps directory
2013-05-15 15:03:56 -07:00
Eric Meadows-Jonsson ba5a9326ed Ignore files in umbrella apps directory
Closes #1071
2013-05-15 23:48:28 +02:00
José Valim 5d885a97d3 Optimize HashDict.pop 2013-05-15 13:59:02 -06:00
ILYA Khlopotov eedd68e661 Implementation of Dict.pop (proposal #856 for Dict) 2013-05-15 13:14:25 -06:00
José Valim 1aa589afa0 Update CHANGELOG 2013-05-15 13:06:04 -06:00
José Valim c0b9c2545a Move List.Dict to ListDict 2013-05-15 13:03:40 -06:00
José Valim 1bf4d053b9 Use level two headings when appripriate 2013-05-15 12:04:41 -06:00
José Valim b81bf4a839 Add some sample pull requests 2013-05-15 12:01:22 -06:00
José Valim 06f1f3ccd7 Improvements to contributing guide 2013-05-15 11:41:12 -06:00
José Valim abc5d6f954 Also allow explicitly capturing of stdio and stderr 2013-05-15 10:19:01 -06:00
José Valim 69fe431dd9 Remove unused variable 2013-05-15 10:03:08 -06:00
José Valim ad2a6774b1 Ensure no group_leader leakage on capture_io and remove stderr test which was printing on run 2013-05-15 10:02:29 -06:00
José Valim 44a140490e Merge pull request #1059 from mururu/io_protocol
Implement capture_io for ex_unit
2013-05-15 08:54:43 -07:00
José Valim 1250c70d93 Merge pull request #1068 from ericmj/mix-propagate-deps
Propagate dependencies in mix tasks
2013-05-15 08:52:44 -07:00
Eric Meadows-Jonsson 62b6ed1b4c Propagate dependencies in mix tasks
Closes #1036
2013-05-15 16:29:34 +02:00
Yuki Ito 66eb369b3d Implement capture_io for ex_unit 2013-05-15 19:12:50 +09:00
José Valim 6e018c5226 Ensure record args are evaluated in the parent context, closes #1065 2013-05-14 22:51:37 -06:00
José Valim 95377a36a5 Expand macros and quoted variables inside << >>, closes #1063 2013-05-14 22:33:16 -06:00
José Valim 05c1630605 Add missing unquote/unquote_splicing documentation 2013-05-14 22:12:11 -06:00
José Valim 46fc45df9b Revert "Expand macros and quoted variables inside << >>, closes #1063"
It broke the build. Reopens #1063.

This reverts commit 93f71c8624.
2013-05-14 22:04:14 -06:00
José Valim 93f71c8624 Expand macros and quoted variables inside << >>, closes #1063 2013-05-14 21:50:45 -06:00
José Valim 8154066d79 Update CHANGELOG and verbage from previous pull request 2013-05-14 17:12:00 -06:00
José Valim a0a06941ec Merge pull request #1030 from ericmj/report-setup-all-error
Report setup_all errors and mark tests as invalid
2013-05-14 16:00:34 -07:00
José Valim b47bae9e4c Update CHANGELOG 2013-05-13 22:20:20 -06:00
José Valim 79dde1e97e Accept macro/@attribute options on quote 2013-05-13 22:14:34 -06:00
José Valim 23cbf3e853 Support options from macros and @attributes in import, alias and require 2013-05-13 21:51:07 -06:00
José Valim 51529dab0d Make String.valid? work on characters 2013-05-13 19:17:49 -06:00
José Valim 26aee950fd Merge pull request #1058 from meh/string-valid
Add String.valid?
2013-05-13 18:01:45 -07:00
meh 1d6fd3c4c8 Add String.valid? 2013-05-14 01:41:17 +02:00
meh feed5c13ff Fix String.valid_codepoint? tests 2013-05-14 01:39:15 +02:00
José Valim baf5fcf096 Ensure nested modules are properly defined, closes #1057 2013-05-13 16:24:29 -06:00
José Valim e18c1602ba Only add quoted: true to required ast nodes 2013-05-13 15:56:15 -06:00
José Valim 32ae6d3424 Ensure imports respect the quote context 2013-05-13 15:56:15 -06:00
José Valim 02d0aa1d9a Deprecate var_context in favor of context 2013-05-13 15:56:15 -06:00
José Valim 5ce473152f Provide a more robust alias and import expansion
Now that we support { quoted, true }, we can know
before hand if an alias or import comes from a macro
and act accordingly.
2013-05-13 15:56:15 -06:00
José Valim f38cf28cbd Store in the AST if contents come from a quoted expression
This provides a more robust way of checking from where
the original expressions are coming from compared to the
lexical check_clauses field we had in elixir_scope.

A direct consequence of this commit is that, the following
code:

    defrecord Sample, [:hello] do
      import List
    end

Now raises a warning if the List import is not used,
where it didn't previously.
2013-05-13 15:56:15 -06:00
José Valim 1385bde667 Merge pull request #1056 from meh/uri-downcase
Downcase host and scheme in URIs
2013-05-13 07:50:32 -07:00
meh 06f8a12bc8 Downcase host and scheme in URIs 2013-05-13 16:42:19 +02:00
José Valim dec997e3a4 Merge pull request #1055 from alco/record-rewriter-fix
Add missing trivial clauses to record rewriter
2013-05-13 06:59:57 -07:00
Alexei Sholik fcba92606c A test case for empty receive block 2013-05-13 16:29:58 +03:00
Alexei Sholik 609d66adce Add missing trivial clauses to record rewriter 2013-05-13 13:13:09 +03:00
José Valim 8de27bf9af Clean up and speed up exceptions 2013-05-12 20:51:39 -06:00
José Valim 9118b36069 Improve warnings related to default arguments
* "default arguments in some_fun/3 are never used"
* "the first default argument in some_fun/3 is never used"
* "the first 2 default arguments in some_fun/3 are never used"

Closes #929
2013-05-12 19:31:34 -06:00
José Valim 3d9fa35680 Merge pull request #1048 from ericmj/mix-new-umbrella
Add --umbrella option to 'mix new'
2013-05-12 17:03:26 -07:00
José Valim fc30aaf68c Use a manifest to store generated files
This allows us to remove trailing .beam files
when a file is removed and more robust compilation
detection.

Closes #717, closes #954
2013-05-12 17:03:46 -06:00
José Valim b90b8d1385 Improve cons cell and other operators precedence, closes #862 2013-05-12 14:24:35 -06:00
Eric Meadows-Jonsson 57f1a1c29c Remove :app project setting in umbrella projects 2013-05-12 19:38:48 +02:00
José Valim 930be9d99f Improve function(some_macro/x) error message 2013-05-12 11:34:43 -06:00
José Valim b9d25cb2b7 Merge pull request #1049 from ericmj/deps-get-quiet
Add '--quiet' option to 'mix deps.get'
2013-05-12 10:33:44 -07:00
Eric Meadows-Jonsson 0c6ee2d297 Add '--quiet' option to 'mix deps.get'
Closes #1028.
2013-05-12 19:28:14 +02:00
Eric Meadows-Jonsson 9867b8c296 Add --umbrella option to 'mix new' 2013-05-12 19:01:35 +02:00
Eric Meadows-Jonsson dfee7d7443 Remove unnecessary task handling deps.compile 2013-05-12 18:59:12 +02:00
J David Eisenberg 63b93d5dc9 Add tests for String.to_integer and String.to_float 2013-05-11 07:33:22 -07:00
J David Eisenberg e40f0c1ec5 Make String.to_float always return a float
Added code to make String.to_float("34") return {34.0, ""}
2013-05-10 15:06:57 -07:00
J David Eisenberg 26aa75318b Add String.to_integer/1 and String.to_float/1
Given a string that starts with a valid integer, String.to_integer/1
returns a tuple {result, remainder} where remainder is the character
string following the valid integer. If the input is not valid as
specified by Erlang's :string.to_integer/1, the function returns
:error.

Given a string that starts with a valid float, String.to_float/1
returns a tuple {result, remainder} where remainder is the character
string following the valid float. If the input is not valid as
specified by Erlang's :string.to_float/1, the function then calls
String.to_integer/1 and returns that value, which is either an
{integer result, remainder} or :error.

This latter behavior allows you to say String.to_float("34")
without generating an error.
2013-05-10 13:36:38 -07:00
José Valim 117667de0c Remove outdate docs and example 2013-05-10 10:34:54 -06:00
José Valim da45fde90c Use in_project in deps.compile (tks @ericmj) 2013-05-10 09:35:46 -06:00
José Valim 4df1149ec5 Move the umbrella check to Mix.Project.recur
We have named the function from Mix.Project.recursive
to recur in order to imply action.
2013-05-09 23:48:40 -06:00
José Valim 16128651dc Make Mix.Project.load_project private in favor of in_project 2013-05-09 23:36:59 -06:00
José Valim b97343faff Add documentation about import warnings 2013-05-09 18:10:50 -06:00
José Valim 7b41ff3c01 Merge pull request #1026 from elixir-lang/jv-no-warn-unused-imports
Warn on unused imports
2013-05-09 16:55:16 -07:00
José Valim 132ea1569e Merge pull request #1040 from ericmj/umbrella-as-dep
Add support for umbrella project as dependency
2013-05-09 16:55:03 -07:00
José Valim 95a30f3227 Merge pull request #1042 from pragdave/master
One liner in mix do_rebar in deps.compile
2013-05-09 16:54:01 -07:00
Dave Thomas 10675504d5 Fix problem with an in-dep rebar not being found—need to pass in the full path to it, and not just ./rebar 2013-05-09 18:38:23 -05:00
Eric Meadows-Jonsson 54988ae535 Fix mix function names and docs 2013-05-09 23:20:41 +02:00
José Valim 03a11b45fd Merge pull request #1041 from pragdave/fix-regex-run
Fix badmatch with Regex.captures(%r/(.)/g, "cat")
2013-05-09 10:44:35 -07:00
Dave Thomas dda7a4daf7 Fix badmatch with Regex.captures(%r/(.)/g, "cat") 2013-05-09 12:37:48 -05:00
Eric Meadows-Jonsson dd9ba46641 Add support for umbrella project as dependency
Closes #1031
2013-05-09 17:17:29 +02:00
José Valim 6f26ff3ba0 Clarify the CHANGELOG 2013-05-08 15:20:10 -06:00
José Valim 0e8bac7e9e IO.gets and IO.getb now return binaries when reading from stdio 2013-05-08 15:13:06 -06:00
José Valim bef8caf8c6 Fix a bug on RecordRewriter when a variable is matched in many clauses, closes #1037 2013-05-08 14:26:13 -06:00
José Valim a514bfa97c Improve specs and docs for String module 2013-05-08 11:10:55 -06:00
Eric Meadows-Jonsson 993c28a4de Report setup_all errors and mark tests as invalid
Closes #878
2013-05-08 01:14:30 +02:00
José Valim 5e2557a3a9 Merge pull request #1024 from ericmj/umbrellacompile2
Run tasks for umbrella projects in correct order
2013-05-07 07:13:04 -07:00
Eric Meadows-Jonsson 3b8b127753 Run tasks for umbrella projects in correct order
Fixes #1018
2013-05-07 16:05:25 +02:00
Yurii Rashkovskii 7bbb6806e3 Fix regression introduced by 18c4e2b810
It is important to avoid rendering ANSI codes in the documentation as

1) IO.ANSI's own documentation becomes effectively unreadable as one
   can't see the syntax format
2) Markdown is not expected to have ANSI codes rendered
2013-05-07 04:11:17 -07:00
José Valim 7bae0029f2 Automatically detect when to warn on import 2013-05-06 20:22:18 -06:00
José Valim 65f7d56a8e Don't warn on unused imports 2013-05-06 20:17:31 -06:00
José Valim 315d5b5608 Merge pull request #1022 from ericmj/listat
Add List.insert_at/3 and List.delete_at/2
2013-05-06 18:52:23 -07:00
José Valim c6222a05da Refactor elixir_import to mention special forms list 2013-05-06 19:18:43 -06:00
José Valim 1bd8484146 Improve docs for String.split 2013-05-06 19:17:37 -06:00
José Valim 18c4e2b810 Don't output if there is no doc, closes #1025 2013-05-06 19:17:19 -06:00
José Valim 6d2569ff9b Merge pull request #1023 from alco/eex-unicode
Add support for unicode to EEx.Tokenizer
2013-05-06 16:21:56 -07:00
Alexei Sholik ce39f23388 Add support for unicode to EEx.Tokenizer 2013-05-07 02:14:48 +03:00
José Valim f09453440a Fix typo 2013-05-06 16:04:04 -06:00
José Valim 5a9cb7970b Another attempt at fixing the build on R15 2013-05-06 15:52:14 -06:00
José Valim 78ce20742d Use the macro to automatically transform into R15 call 2013-05-06 15:25:57 -06:00
José Valim 45ad425793 Improve representation of floats and integers, closes #1021 2013-05-06 15:18:48 -06:00
Eric Meadows-Jonsson 3f73a73da1 Add List.insert_at/3 2013-05-06 22:41:05 +02:00
José Valim 1d2b4de7dd Add Dict.fetch 2013-05-06 13:31:58 -06:00
José Valim a3a136bb81 Ensure directories have the same name as their applications 2013-05-06 12:57:44 -06:00
José Valim a93264be81 Private on_load functions should not trigger unused warning 2013-05-06 11:31:05 -06:00
José Valim cbe82e0912 Add __DIR__, closes #1019 2013-05-06 11:03:37 -06:00
José Valim 560510c71a Merge pull request #1010 from devinus/contributing-guidelines
Add a CONTRIBUTING.md
2013-05-06 08:19:17 -07:00
Devin Torres 975d7f8dfb Add a CONTRIBUTING.md 2013-05-06 10:17:46 -05:00
José Valim ddb87a96ca Private functions used by private macros no longer emit an unused warning, closes #885 2013-05-05 21:59:40 -06:00
José Valim c53314dfc4 Ensure lexical scopes are not password forward by accident 2013-05-05 20:28:59 -06:00
José Valim fa3a7031f0 Merge pull request #1016 from ericmj/mixumbrella
Add support for umbrella projects in mix
2013-05-05 13:46:35 -07:00
Eric Meadows-Jonsson f9fe24f524 Optimize Mix.Shell.output_app? 2013-05-05 22:39:47 +02:00
Eric Meadows-Jonsson 3b7cd15fbc Don't use the full stack for umbrella test 2013-05-05 22:08:52 +02:00
José Valim 90093bd4e2 Support --cookie, --hidden, Node.get_cookie and Node.set_cookie, closes #910 2013-05-05 12:34:15 -06:00
Eric Meadows-Jonsson ace80048f7 Fix tasks may run multiple times
Store each task as a {task, application} pair once run. This fixes
that a task run for a sub project in umbrella projects wasn't
properly registered as run.
2013-05-05 20:25:17 +02:00
Eric Meadows-Jonsson 2b866abb6c Add support for umbrella projects in mix
Fixes #667
2013-05-05 20:05:48 +02:00
José Valim 74e244a129 escriptize should yield arguments as binaries, closes #995 2013-05-05 11:13:30 -06:00
José Valim a8ab6b5a2a Deprecate List.member?/2 in favor of Enum.member?/2 2013-05-05 10:10:03 -06:00
José Valim 5841f6849b Deprecate assert inlist in favor of assert in 2013-05-05 09:52:00 -06:00
José Valim d77ea82a27 Update CHANGELOG 2013-05-05 09:34:35 -06:00
José Valim 7d1a439e29 Keyword.keyword? should work with any atom as key
Closes #1011
2013-05-05 09:34:03 -06:00
José Valim 4b65fa1601 Merge pull request #1013 from ericmj/enumuniq
Add Enum.uniq/2
2013-05-05 08:21:09 -07:00
Eric Meadows-Jonsson 38539faaf4 Add Enum.uniq/2 2013-05-05 17:16:57 +02:00
José Valim 1019879bd0 Improve previous clause does not match warning
We have redefined the warning to say:

  "trying to override previously defined function NAME/ARITY"

We have also relaxed the rules that causes the warning to
emitted. For example, if any kind of meta-programming or
unquoting is detected in the function definition, we skip
the warning altogether.

Closes #934.
2013-05-04 14:59:15 -06:00
José Valim c14ee51d15 Refactor elixir_quote to work on Elixir expressions
This allows us to re-use the code from Macro.escape,
reducing the duplication, and gives us more visibility
into the transformation process.
2013-05-04 14:31:58 -06:00
José Valim a0787fb4bf Merge pull request #1009 from devinus/liberate-in
Liberate `in`, fixes #817
2013-05-03 14:26:40 -07:00
Devin Torres 901fedf266 Liberate in, fixes #817 2013-05-03 16:12:30 -05:00
José Valim c1c4d7571f Merge pull request #1008 from devinus/expanded-enum-protocol
Add Enum.empty? and Enum.member? to the Enum.Iterator protocol, fixes #804
2013-05-03 13:25:43 -07:00
Devin Torres 2f981b914d Add Enum.empty? and Enum.member? to the Enum.Iterator protocol, fixes #804 2013-05-03 14:29:57 -05:00
José Valim 4c6dbcf719 Ensure s and t helpers work with code compiled via c 2013-05-03 12:46:36 -06:00
José Valim 33367e4224 Merge pull request #1006 from alco/kernel-doc-clarifications
Kernel doc clarifications
2013-05-03 09:55:35 -07:00
Alexei Sholik 82e597e31e unless argument clarification 2013-05-03 19:50:02 +03:00
Alexei Sholik e4fd9fd4c1 Fix doctests for unless 2013-05-03 19:50:02 +03:00
Alexei Sholik 66756e2686 Replace "execute" with "evaluate" for consistency 2013-05-03 19:50:02 +03:00
Alexei Sholik b59888a6b7 x in "x in [1,2,3]" is evaluated only once 2013-05-03 19:49:41 +03:00
José Valim 0af35cd011 Merge pull request #1007 from ericmj/is_record
Evaluate first argument in is_record once
2013-05-03 09:39:05 -07:00
Eric Meadows-Jonsson 5c96708ce5 Evaluate first argument in is_record once 2013-05-03 16:43:06 +02:00
Rafael Mendonça França 6bb284d776 Merge pull request #1005 from alco/clarify-sort-doc
Clarify on the return value of Enum.sort
2013-05-03 06:46:18 -07:00
Alexei Sholik e33c31e4ee Fix "evalute" typos 2013-05-03 15:34:05 +03:00
Alexei Sholik 5c58956812 "executes" -> "evaluates and returns" 2013-05-03 15:34:05 +03:00
Alexei Sholik 88d674b0b3 Better explanation for XOR 2013-05-03 15:34:05 +03:00
Alexei Sholik 44ec3705f3 Clarify on the return value of Enum.sort 2013-05-03 15:30:42 +03:00
José Valim 23a625b8de Update CHANGELOG 2013-05-02 12:03:57 -07:00
Yuki Ito 83c257937d Implement Enum.min/1 and Enum.min/2
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2013-05-02 12:03:56 -07:00
Yuki Ito 43f8448623 Implement Enum.max/1 and Enum.max/2
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2013-05-02 12:03:56 -07:00
José Valim 47931bb5dd Merge pull request #1001 from mururu/correct_error
Fix unexpected error in Kernel.CLI.main
2013-05-02 07:54:10 -07:00
Yuki Ito 3ddf8edc93 Add test for SyntaxError in Kernel.CLI 2013-05-02 16:27:44 +09:00
Yuki Ito 644dd0f4fb Print Exception in Kernel.CLI.run on standard_out. 2013-05-02 16:27:44 +09:00
Yuki Ito 475bdea0bb Encode Kernel.CLI.main argv in unicode. 2013-05-02 16:27:25 +09:00
José Valim 6bf1d2b84d Merge pull request #998 from ericmj/inspect
Remove custom dispatch to records in Binary.Inspect.Tuple
2013-05-01 12:42:58 -07:00
José Valim 80ae2e9bf2 Ensure String.Unicode is in elixir.app on clean install, closes #999 2013-05-01 12:12:02 -07:00
Eric Meadows-Jonsson ee0643c17c Remove custom dispatch to records in Binary.Inspect.Tuple 2013-05-01 05:17:55 -07:00
José Valim 16e18240c6 Remove special case for implicit splicing inside quote 2013-05-01 00:29:40 -07:00
José Valim f484c9960f Add a test for inline keyword blocks 2013-04-30 23:34:24 -07:00
José Valim 17cbf7bd98 Merge pull request #996 from bryansum/master
Fixing typo in docstring for List.keyfind
2013-04-30 19:54:11 -07:00
Bryan Summersett 3f7dda69fb Fixing typo in docstring for List.keyfind 2013-04-30 19:22:03 -07:00
José Valim 35abb1d7ef Ensure we don't splice keyword args unecessarily, closes #989 2013-04-30 15:23:50 -07:00
José Valim ee9f77600b Consider current project rebar if it is available, closes #962 2013-04-30 15:03:31 -07:00
José Valim 61727e5c5d Ensure do/else|*/end are inlinable, closes #966 2013-04-30 14:47:14 -07:00
José Valim f791bd117c Properly encode unicode binary chars, closes #961 2013-04-30 14:34:46 -07:00
José Valim 55d1e3767e Update CHANGELOG 2013-04-30 14:21:40 -07:00
José Valim 5a22ba8aa2 Move read_path to Mix.Utils for now 2013-04-30 13:04:33 -07:00
José Valim f5c08e9df3 Merge pull request #964 from pragdave/local.rebar
Add local.rebar task
2013-04-30 12:50:55 -07:00
José Valim 034cb18af8 Ensure IEx won't crash on bad error messages and invalid stacktraceqs, closes #974 2013-04-30 12:50:39 -07:00
Yurii Rashkovskii 1080e290a0 Remove unused variable warning in Enum 2013-04-29 09:54:05 -07:00
José Valim 6c0d43c1c7 Merge pull request #992 from meh/enum-member
Implement Enum.member?
2013-04-29 08:29:32 -07:00
José Valim 0b77a541eb Merge pull request #991 from ericmj/exunit_exit
Use monitors in ExUnit and document callbacks
2013-04-29 08:15:42 -07:00
meh 028421ffea Implement Enum.member? 2013-04-29 17:12:44 +02:00
José Valim cebbbd5102 Remove short paths on Enum
They basically cost one local function call and
are rarely used (just when both iterators are empty)
2013-04-29 07:51:00 -07:00
Eric Meadows-Jonsson 7d1a7635e5 Add more documentation to ExUnit.Callbacks 2013-04-29 07:40:49 -07:00
Eric Meadows-Jonsson af76eff96a Use monitors instead of links in exunit 2013-04-29 07:33:59 -07:00
José Valim 6619e3277e Define missing Kernel functions 2013-04-29 07:30:20 -07:00
José Valim ebb867d361 Merge pull request #983 from meh/enum-equal
More Enum.equal? stuff
2013-04-29 07:17:09 -07:00
José Valim 77bde77d25 Calculate target just once 2013-04-28 18:17:29 -07:00
José Valim 2e96d1de4d Ensure imports are passed around arg by arg, closes #990 2013-04-28 18:09:23 -07:00
José Valim b5ab795cdc Clean up conversion to list 2013-04-28 18:09:23 -07:00
José Valim 7f9e95e223 Return nil from import 2013-04-28 18:09:23 -07:00
José Valim f134066de3 Merge pull request #988 from meh/dict-equal
Return early in Dict.equal? if the dicts have different size
2013-04-28 11:26:43 -07:00
meh 90a9be6cce Return early in Dict.equal? if the dicts have different size 2013-04-28 16:05:08 +02:00
José Valim f45dbea495 Merge pull request #980 from meh/dict-equal
Implement Dict.equal?
2013-04-27 21:39:03 -07:00
José Valim 46c5c69211 Merge pull request #986 from meh/keyword-delete_first
Implement Keyword.delete_first
2013-04-27 21:38:04 -07:00
José Valim 4d1b22b69c Merge pull request #985 from ericmj/exunit_exit
Handle exit messages from tests in ex_unit
2013-04-27 21:35:58 -07:00
meh f76347235b Implement Dict.equal? 2013-04-27 22:33:39 +02:00
meh a13eaaf1c3 Implement Keyword.delete_first, closes #984 2013-04-27 21:15:16 +02:00
Eric Meadows-Jonsson 7f388d8bf7 Handle exit messages from tests in ex_unit
Fixes #982
2013-04-27 11:48:48 -07:00
meh 01e848058a Implement Enum.equal? with equality callback 2013-04-27 19:21:18 +02:00
José Valim 2e26d17d83 Merge pull request #978 from meh/enum-equal
Implement Enum.equal?
2013-04-27 07:19:26 -07:00
José Valim e2fd5c257b Merge pull request #981 from ericmj/cli_switches
Improve flag error messages in CLI
2013-04-26 22:11:29 -07:00
Eric Meadows-Jonsson ae0a6cefd0 Improve flag error messages in CLI
Now consistent in error messages and tries to show as many error messages
as possible before exiting.
2013-04-26 17:23:18 -07:00
José Valim e9a91d1f7a Merge pull request #976 from ericmj/require
Flags -p and -pr fails if pattern match no files
2013-04-26 10:24:20 -07:00
Eric Meadows-Jonsson c37101a52c Flags -p and -pr fails if pattern match no files
Fixes #851
2013-04-26 10:15:07 -07:00
meh aad2d97baf Implement Enum.equal? 2013-04-26 18:28:48 +02:00
José Valim f1b2980e8e Merge pull request #979 from meh/enum-to_list
Implement Enum.to_list, closes #977
2013-04-26 08:23:27 -07:00
meh 8a4b550a90 Implement Enum.to_list, closes #977 2013-04-26 16:39:53 +02:00
Yurii Rashkovskii 52f36a4a96 Fix ls IEx helper to render file types correctly for all paths 2013-04-25 08:38:43 -07:00
José Valim 92ac9ab144 Merge pull request #973 from ericmj/master
Don't swallow UndefinedFunctionError in Binary.Inspect
2013-04-25 08:30:59 -07:00
Eric Meadows-Jonsson 7bd288c7cb Dont swallow undef errors in Binary.Inspect
Fixes #960
2013-04-25 08:06:39 -07:00
Yurii Rashkovskii efb61518d7 Improve ls IEx helper output when ANSI colouring is available
Closes #972
2013-04-24 16:42:45 -07:00
José Valim 414fc86a7d Merge pull request #971 from alco/ls-helper
Ls helper
2013-04-24 15:38:26 -07:00
Alexei Sholik 2eb8fc766a Remove accidental logs 2013-04-25 01:33:50 +03:00
Alexei Sholik 2889d7c998 Simplify coloring logic (thanks ericmj) 2013-04-25 01:32:07 +03:00
Alexei Sholik 4f54dc07f5 Expand ~ in cd 2013-04-25 01:14:16 +03:00
José Valim b71d14fc03 Improvements to mix 2013-04-24 16:13:36 -06:00
Alexei Sholik 02bb4970dd Expand ~ in ls 2013-04-25 01:12:39 +03:00
Alexei Sholik 4a996bea17 Multi-column output + coloring for ls 2013-04-25 01:10:55 +03:00
José Valim 3e6e6ee99b Update exdoc links 2013-04-24 11:16:28 -06:00
Alexei Sholik 9f60f46044 ls helper 2013-04-24 17:48:42 +03:00
José Valim ea29be40a4 Make it clear the current section in Typespec is about anonymous functions 2013-04-24 08:47:47 -06:00
José Valim e793ee2c67 Merge pull request #969 from jeremyjh/parens-stdlib-eg
add parens for function calls in examples
2013-04-23 21:32:18 -07:00
Jeremy Huffman bf3dd3f265 add parens for function calls in examples 2013-04-23 23:07:41 -04:00
Dave Thomas c03ea65115 Add local.rebar task 2013-04-22 11:00:03 -05:00
José Valim cd7879b1b9 Remove last use of eval in IO.ANSI 2013-04-21 22:11:27 -06:00
José Valim 160631b2d5 Revert "Use unquote instead of @ in HashDict"
Using @attributes is more readable.

This reverts commit 238e7f4bc2.
2013-04-21 22:08:27 -06:00
José Valim 87d57a38a3 Do not go through eval if there is nothing to evaluate 2013-04-21 18:03:09 -06:00
José Valim 907920de44 Unfold the loop manually 2013-04-21 17:24:24 -06:00
José Valim 53d34099f1 Optimize elixir_tree_helpers:abstract_syntax 2013-04-21 17:07:25 -06:00
José Valim 4c70f1b3cd Store clauses in a separate table (thanks to yrashk for first iteration of this patch) 2013-04-21 14:56:52 -06:00
José Valim d2dbef1611 Bump to 0.8.3.dev 2013-04-20 19:22:40 -06:00
José Valim 70432ca7f2 Remove deprecations 2013-04-20 19:22:13 -06:00
José Valim 0cab23e3e7 Get rid of eval on IO.ANSI 2013-04-20 19:07:40 -06:00
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
255 changed files with 11005 additions and 4930 deletions
+2
View File
@@ -6,6 +6,8 @@ notifications:
- jose.valim@plataformatec.com.br
- yrashk@gmail.com
otp_release:
- R16B
- R15B03
- R15B02
- R15B01
- R15B
+121 -3
View File
@@ -1,3 +1,122 @@
# v0.8.3 (2013-05-22)
* enhancements
* [CLI] Flags `-p` and `-pr` fails if pattern match no files
* [CLI] Support `--hidden` and `--cookie` flags for distributed Erlang
* [Enum] Add `Enum.to_list/1`, `Enum.member?/2`, `Enum.uniq/2`, `Enum.max/1`, `Enum.max/2`, `Enum.min/1` and `Enum.min/2`
* [ExUnit] Add `ExUnit.CaptureIO` for IO capturing during tests
* [ExUnit] Consider load time on ExUnit time reports
* [IEx] Support `ls` with colored output
* [IEx] Add `#iex:break` to break incomplete expressions
* [Kernel] Add `Enum.at`, `Enum.fetch` and `Enum.fetch!`
* [Kernel] Add `String.to_integer` and `String.to_float`
* [Kernel] Add `Dict.take`, `Dict.drop`, `Dict.split`, `Dict.pop` and `Dict.fetch!`
* [Kernel] Many optimizations for code compilation
* [Kernel] `in` can be used with right side expression outside guards
* [Kernel] Add `Node.get_cookie/0` and `Node.set_cookie/2`
* [Kernel] Add `__DIR__`
* [Kernel] Expand macros and attributes on quote, import, alias and require
* [Kernel] Improve warnings related to default arguments
* [Keyword] Add `Keyword.delete_first/2`
* [Mix] Add `local.rebar` to download a local copy of rebar, and change `deps.compile` to use it if needed
* [Mix] Support umbrella applications
* [Mix] Load beam files available at `MIX_PATH` on CLI usage
* [String] Add `String.valid?` and `String.valid_character?`
* bug fix
* [ExUnit] Handle exit messages from in ExUnit
* [ExUnit] Failures on ExUnit's setup_all now invalidates all tests
* [Kernel] Ensure we don't splice keyword args unecessarily
* [Kernel] Private functions used by private macros no longer emit an unused warning
* [Kernel] Ensure Elixir won't trip on empty receive blocks
* [Kernel] `String.slice` now returns an empty string when out of range by 1
* [Mix] Generate manifest files after compilation to avoid depending on directory timestamps and to remove unused .beam files
* [Path] `Path.expand/2` now correctly expands `~` in the second argument
* [Regex] Fix badmatch with `Regex.captures(%r/(.)/g, "cat")`
* [URI] Downcase host and scheme and URIs
* deprecations
* [Code] `Code.eval` is deprecated in favor of `Code.eval_string`
* [Exception] `Exception.format_entry` is deprecated in favor of `Exception.format_stacktrace_entry`
* [ExUnit] `assert left inlist right` is deprecated in favor of `assert left in right`
* [IO] `IO.getb` is deprecated in favor of `IO.getn`
* [List] `List.member?/2` is deprecated in favor of `Enum.member?/2`
* [Kernel] `var_context` in quote was deprecated in favor of `context`
* [Kernel] `Enum.at!` and `Dict.get!` is deprecated in favor of `Enum.fetch!` and `Dict.fetch!`
* backwards incompatible changes
* [Dict] `List.Dict` was moved to `ListDict`
* [IO] `IO.gets`, `IO.getn` and friends now return binaries when reading from stdio
* [Kernel] Precedence of `|>` has changed to lower to support constructs like `1..5 |> Enum.to_list`
* [Mix] `mix escriptize` now receives arguments as binaries
# 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
@@ -6,7 +125,7 @@
* [Binary] Support syntax shortcut for specifying size in bit syntax
* [CLI] Support `--app` option to start an application and its dependencies
* [Dict] Support `put_new` in `Dict` and `Keyword`
* [Dict] Add `List.Dict` and a faster `HashDict` implementation
* [Dict] Add `ListDict` and a faster `HashDict` implementation
* [ExUnit] ExUnit now supports multiple runs in the same process
* [ExUnit] Failures in ExUnit now shows a tailored stacktrace
* [ExUnit] Introduce `ExUnit.ExpectationError` to provide better error messages
@@ -33,7 +152,7 @@
* [Kernel] The AST now allows metadata to be attached to each node. This means the second item in the AST is no longer an integer (representing the line), but a keywords list. Code that relies on the line information from AST or that manually generate AST nodes need to be properly updated
* deprecations
* [Dict] Deprecate `Binary.Dict` and `OrdDict` in favor of `HashDict` and `List.Dict`
* [Dict] Deprecate `Binary.Dict` and `OrdDict` in favor of `HashDict` and `ListDict`
* [File] Deprecate path related functions in favor of the module `Path`
* [Kernel] The `/>` operator has been deprecated in favor of `|>`
* [Mix] `Mix.Project.sources` is deprecated in favor of `Mix.Project.config_files`
@@ -62,7 +181,6 @@
* deprecations
* [Enum] `Enum.qsort` is deprecated in favor of `Enum.sort`
* [ExUnit] `assert left in right` is deprecated in favor of `assert left inlist right`
* [List] `List.sort` and `List.uniq` have been deprecated in favor of their `Enum` counterparts
* [Record] Default-based generated functions are deprecated
* [Typespec] Enhancements and deprecations to the `@spec/@callback` and the fun type syntax
+163
View File
@@ -0,0 +1,163 @@
# Contributing to Elixir
Please take a moment to review this document in order to make the contribution
process easy and effective for everyone involved!
## Using the issue tracker
The issue tracker is the preferred channel for [bug reports](#bugs-reports),
[features requests](#feature-requests) and [submitting pull
requests](#pull-requests), but please **do not** use the issue tracker for
personal support requests. Instead, use
[the mailing list](http://groups.google.com/group/elixir-lang-core),
[Stack Overflow](http://stackoverflow.com/questions/ask?tags=elixir), or
[#elixir-lang](irc://chat.freenode.net/elixir-lang) on Freenode).
We do our best to keep the issues tracker tidy and organized, making it useful
for everyone. For example, we classify open issues per application and perceived
difficulty of the issue, making it easier for developers to
[contribute to Elixir](#contributing).
## Bug reports
A bug is a _demonstrable problem_ that is caused by the code in the repository.
Good bug reports are extremely helpful - thank you!
Guidelines for bug reports:
1. **Use the GitHub issue search** &mdash; check if the issue has already been
reported.
2. **Check if the issue has been fixed** &mdash; try to reproduce it using the
latest `master` or development branch in the repository.
3. **Isolate the problem** &mdash; ideally create a reduced test
case.
A good bug report shouldn't leave others needing to chase you up for more
information. Please try to be as detailed as possible in your report. What is
your environment? What steps will reproduce the issue? What version of Erlang
and Elixir experience the problem? What would you expect to be the outcome?
All these details will help people to fix any potential bugs.
Example:
> Short and descriptive example bug report title
>
> A summary of the issue and the environment in which it occurs. If suitable,
> include the steps required to reproduce the bug.
>
> 1. This is the first step
> 2. This is the second step
> 3. Further steps, etc.
>
> `<url>` - a link to the reduced test case (e.g. a GitHub Gist)
>
> Any other information you want to share that is relevant to the issue being
> reported. This might include the lines of code that you have identified as
> causing the bug, and potential solutions (and your opinions on their
> merits).
## Feature requests
Feature requests are welcome. But take a moment to find out whether your idea
fits with the scope and aims of the project. It's up to *you* to make a strong
case to convince the project's developers of the merits of this feature. Please
provide as much detail and context as possible.
## Contributing
We incentivate everyone to contribute to Elixir and help us tackle
existing issues! To do so, there are a few things you need to know
about the code. First, Elixir code is divided in applications inside
the `lib` folder:
* `elixir` - Contains Elixir's kernel and stdlib
* `eex` - Template engine that allows you to embed Elixir
* `ex_unit` - Simple test framework that ships with Elixir
* `iex` — IEx, Elixir's interactive shell
* `mix` — Elixir's build tool
You can run all tests in the root directory with `make test` and you can
also run tests for a specific framework `make test_#{NAME}`, for example,
`make test_ex_unit`.
With tests running and passing, you are ready to contribute to Elixir and
send your pull requests.
## Pull requests
Good pull requests - patches, improvements, new features - are a fantastic
help. They should remain focused in scope and avoid containing unrelated
commits.
**IMPORTANT**: By submitting a patch, you agree to allow the project owner to
license your work under the same license as that used by the project.
If you have any significant pull request in mind (e.g. implementing features,
refactoring code, porting to a different language), **please ask first**
otherwise you risk spending a lot of time working on something that the
project's developers might not want to merge into the project.
Please adhere to the coding conventions in the project (indentation,
accurate comments, etc.) and don't forget to add your own tests and
documentation. When working with git, we recommend the following process
in order to craft an excellent pull request:
1. [Fork](http://help.github.com/fork-a-repo/) the project, clone your fork,
and configure the remotes:
```bash
# Clone your fork of the repo into the current directory
git clone https://github.com/<your-username>/elixir
# Navigate to the newly cloned directory
cd elixir
# Assign the original repo to a remote called "upstream"
git remote add upstream https://github.com/elixir-lang/elixir
```
2. If you cloned a while ago, get the latest changes from upstream:
```bash
git checkout <dev-branch>
git pull upstream <dev-branch>
```
3. Create a new topic branch (off master) to contain your feature, change, or fix:
```bash
git checkout -b <topic-branch-name>
```
4. Commit your changes in logical chunks. Keep your commit messages organized,
with a short description in the first line and more detailed information on
the following lines. Feel free to use Git's
[interactive rebase](https://help.github.com/articles/interactive-rebase)
feature to tidy up your commits before making them public.
5. Locally merge (or rebase) the upstream development branch into your topic branch:
```bash
git pull [--rebase] upstream <dev-branch>
```
6. Push your topic branch up to your fork:
```bash
git push origin <topic-branch-name>
```
7. [Open a Pull Request](https://help.github.com/articles/using-pull-requests/)
with a clear title and description.
We have saved some excellent pull requests we have received in the past in case
you are looking for some examples:
* https://github.com/elixir-lang/elixir/pull/992
* https://github.com/elixir-lang/elixir/pull/1041
* https://github.com/elixir-lang/elixir/pull/1058
* https://github.com/elixir-lang/elixir/pull/1059
+15 -8
View File
@@ -44,9 +44,9 @@ erlang:
# Since Mix depends on EEx and EEx depends on
# Mix, we first compile EEx without the .app
# file, then mix and then compile eex fully
elixir: kernel unicode lib/eex/ebin/Elixir-EEx.beam mix ex_unit eex iex
elixir: kernel 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)"; \
@@ -54,6 +54,7 @@ $(KERNEL): lib/elixir/lib/*.ex lib/elixir/lib/*/*.ex
fi
@ echo "==> kernel (compile)";
@ $(ELIXIRC) "lib/elixir/lib/**/*.ex" -o lib/elixir/ebin;
@ $(MAKE) unicode
@ rm -rf lib/elixir/ebin/elixir.app
@ cd lib/elixir && $(REBAR) compile
@@ -93,23 +94,25 @@ clean:
#==> Release tasks
docs:
SOURCE_REF = $(shell head="$$(git rev-parse HEAD)" tag="$$(git tag --points-at $$head | tail -1)" ; echo "$${tag:-$$head}\c")
docs: compile
mkdir -p ebin
rm -rf docs
cp -R -f lib/*/ebin/*.beam ./ebin
bin/elixir ../exdoc/bin/exdoc
bin/elixir ../ex_doc/bin/ex_doc "Elixir" "$(VERSION)" -m Kernel -u "https://github.com/elixir-lang/elixir" --source-ref "$(call SOURCE_REF)"
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 +127,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)"
+5 -17
View File
@@ -4,7 +4,7 @@
For more about Elixir, installation and documentation, [check Elixir's website](http://elixir-lang.org/).
# Usage
## Usage
If you want to contribute to Elixir or run it from source, clone this repository to your machine, compile and test it:
@@ -20,23 +20,11 @@ However, if tests fail, it is likely you have an outdated Erlang version (Elixir
If you have the correct version and tests still fail, feel free to [open an issue][2].
# Contributing
## Contributing
If you want to contribute, Elixir code is divided in applications inside the `lib` folder:
We appreciate any contribution to Elixir, so check out our [CONTRIBUTING.md](CONTRIBUTING.md) guide for more information. We usually keep a list of features and bugs [in the issue tracker][2].
* `elixir` - Contains Elixir's kernel and stdlib;
* `eex` - Template engine that allows you to embed Elixir;
* `ex_unit` - Simple test framework that ships with Elixir;
* `iex` — IEx, Elixir's interactive shell
* `mix` — Elixir's build tool
We usually keep a list of features and bugs [in the issue tracker][2].
# Important links
## Important links
* #elixir-lang on freenode IRC
* [Website][1]
@@ -47,7 +35,7 @@ We usually keep a list of features and bugs [in the issue tracker][2].
[2]: https://github.com/elixir-lang/elixir/issues
[3]: http://groups.google.com/group/elixir-lang-core
# License
## License
"Elixir" and the Elixir logo are copyright (c) 2012 Plataformatec.
+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.3
+9 -7
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,8 @@ 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)
--cookie \"cookie\" Sets a cookie for this distributed node
--hidden Makes a hidden node
--detached Starts the Erlang VM detached from console
--no-halt Does not halt the Erlang VM after execution
@@ -42,10 +43,10 @@ while [ $I -le $# ]; do
case "$PEEK" in
-v|--compile|--no-halt)
;;
-e|-r|-pr|-pa|-pz|--remsh|-S)
-e|-r|-pr|-pa|-pz|--remsh|--cookie|-S)
S=2
;;
--detached)
--detached|--hidden)
ERL="$ERL `echo $PEEK | cut -c 2-`"
;;
--sname|--name)
@@ -69,10 +70,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 %*
+1 -1
View File
@@ -24,4 +24,4 @@ readlink_f () {
SELF=$(readlink_f "$0")
SCRIPT_PATH=$(dirname "$SELF")
exec "$SCRIPT_PATH"/elixir --compile "$@"
exec "$SCRIPT_PATH"/elixir +compile "$@"
+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" +iex "$@"
+1 -1
View File
@@ -1,2 +1,2 @@
@echo off
call "%~dp0\elixir.bat" --no-halt -e "IEx.start" %*
call "%~dp0\elixir.bat" +iex --no-halt -e "IEx.start" %*
+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
+3 -3
View File
@@ -12,7 +12,7 @@ defmodule EEx.Tokenizer do
"""
def tokenize(bin, line) when is_binary(bin) do
tokenize(binary_to_list(bin), line)
tokenize(:unicode.characters_to_list(bin), line)
end
def tokenize(list, line) do
@@ -112,7 +112,7 @@ defmodule EEx.Tokenizer do
else
:middle_expr
end
error ->
_error ->
:middle_expr
end
end
@@ -154,6 +154,6 @@ defmodule EEx.Tokenizer do
end
defp tokenize_text(line, buffer, acc) do
[{ :text, line, list_to_binary(Enum.reverse(buffer)) } | acc]
[{ :text, line, :unicode.characters_to_binary(Enum.reverse(buffer)) } | acc]
end
end
+1 -1
View File
@@ -1,4 +1,4 @@
Code.require_file "../../test_helper.exs", __FILE__
Code.require_file "../test_helper.exs", __DIR__
defmodule EEx.SmartEngineTest do
use ExUnit.Case, async: true
+1 -1
View File
@@ -1,4 +1,4 @@
Code.require_file "../../test_helper.exs", __FILE__
Code.require_file "../test_helper.exs", __DIR__
defmodule EEx.TokenizerTest do
use ExUnit.Case, async: true
+13 -4
View File
@@ -1,4 +1,4 @@
Code.require_file "../test_helper.exs", __FILE__
Code.require_file "test_helper.exs", __DIR__
require EEx
@@ -10,7 +10,7 @@ defmodule EExText.Compiled do
{ :erlang, 1, 2 }.tuple_to_list
EEx.function_from_string :def, :string_sample, "<%= a + b %>", [:a, :b]
filename = Path.expand("../fixtures/eex_template_with_bindings.eex", __FILE__)
filename = Path.join(__DIR__, "fixtures/eex_template_with_bindings.eex")
EEx.function_from_file :defp, :private_file_sample, filename, [:bar]
def file_sample(arg), do: private_file_sample(arg)
@@ -270,14 +270,23 @@ foo
assert_eval expected, string
end
test "unicode" do
template = """
• <%= "•" %> •
<%= "Jößé Vâlìm" %> Jößé Vâlìm
"""
result = EEx.eval_string(template)
assert result == " • • •\n Jößé Vâlìm Jößé Vâlìm\n"
end
test "evaluates the source from a given file" do
filename = Path.expand("../fixtures/eex_template.eex", __FILE__)
filename = Path.join(__DIR__, "fixtures/eex_template.eex")
result = EEx.eval_file(filename)
assert result == "foo bar.\n"
end
test "evaluates the source from a given file with bindings" do
filename = Path.expand("../fixtures/eex_template_with_bindings.eex", __FILE__)
filename = Path.join(__DIR__, "fixtures/eex_template_with_bindings.eex")
result = EEx.eval_file(filename, [bar: 1])
assert result == "foo 1\n"
end
+36 -14
View File
@@ -11,8 +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
name_args=false, %% when true, it means arguments should be named
@@ -24,31 +22,55 @@
extra_guards=nil, %% extra guards from args expansion
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
context_modules=[], %% modules defined in the current context
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,
unquote=true
context=nil,
vars_hygiene=true,
aliases_hygiene=true,
imports_hygiene=true,
unquote=true,
unquoted=false,
mark=true
}).
%% Introspection
-define(defs(Kind), Kind == def; Kind == defp; Kind == defmacro; Kind == defmacrop).
%% Used in tokenization and interpolation
-define(is_digit(S), S >= $0 andalso S =< $9).
%% Numbers
-define(is_hex(S), ?is_digit(S) orelse (S >= $A andalso S =< $F) orelse (S >= $a andalso S =< $f)).
-define(is_bin(S), S >= $0 andalso S =< $1).
-define(is_octal(S), S >= $0 andalso S =< $7).
-define(is_leading_octal(S), S >= $0 andalso S =< $3).
%% Digits and letters
-define(is_digit(S), S >= $0 andalso S =< $9).
-define(is_upcase(S), S >= $A andalso S =< $Z).
-define(is_downcase(S), S >= $a andalso S =< $z).
-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).
%% Atoms
-define(is_new_atom(S), ?is_quote(S) orelse ?is_upcase(S) orelse ?is_downcase(S) orelse (S == $_)).
-define(is_atom(S), ?is_identifier(S) orelse (S == $@)).
-define(is_identifier(S), ?is_digit(S) orelse ?is_upcase(S) orelse ?is_downcase(S) orelse (S == $_)).
-define(is_terminator(S), (S == $?) orelse (S == $!) orelse (S == $:)).
%% Quotes
-define(is_quote(S), S == $" orelse S == $').
%% Spaces
-define(is_horizontal_space(S), (S == $\s) orelse (S == $\t)).
-define(is_vertical_space(S), (S == $\r) orelse (S == $\n)).
-define(is_space(S), ?is_horizontal_space(S) orelse ?is_vertical_space(S)).
+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
+25 -6
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
@@ -64,11 +66,28 @@ defimpl Binary.Chars, for: Number do
@doc """
Simply converts the number (integer or a float) to a binary.
"""
@digits 20
@limit :math.pow(10, @digits)
def to_binary(thing) when is_integer(thing) do
list_to_binary integer_to_list(thing)
integer_to_binary(thing)
end
def to_binary(thing) do
list_to_binary float_to_list(thing)
to_binary = :proplists.get_value(:float_to_binary,
:proplists.get_value(:exports, :erlang.module_info, []))
if to_binary == 2 do
def to_binary(thing) when thing > @limit do
float_to_binary(thing, scientific: @digits)
end
def to_binary(thing) do
float_to_binary(thing, compact: true, decimals: @digits)
end
else
def to_binary(thing) do
float_to_binary(thing)
end
end
end
end
+101 -89
View File
@@ -11,8 +11,6 @@ defprotocol Binary.Inspect do
printing.
"""
@only [BitString, List, Tuple, Atom, Number, Function, PID, Port, Reference]
def inspect(thing, opts)
end
@@ -34,15 +32,15 @@ defmodule Binary.Inspect.Utils do
end
defp do_container_join([h], opts, _counter) do
Binary.Inspect.inspect(h, opts)
Kernel.inspect(h, opts)
end
defp do_container_join([h|t], opts, counter) when is_list(t) do
Binary.Inspect.inspect(h, opts) <> "," <> do_container_join(t, opts, decrement(counter))
Kernel.inspect(h, opts) <> "," <> do_container_join(t, opts, decrement(counter))
end
defp do_container_join([h|t], opts, _counter) do
Binary.Inspect.inspect(h, opts) <> "|" <> Binary.Inspect.inspect(t, opts)
Kernel.inspect(h, opts) <> "|" <> Kernel.inspect(t, opts)
end
defp do_container_join([], _opts, _counter) do
@@ -54,47 +52,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 +114,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 +192,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 +259,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,9 +272,9 @@ defimpl Binary.Inspect, for: List do
def inspect(thing, opts) do
cond do
printable?(thing) ->
escape(list_to_binary(thing), ?')
Keyword.keyword?(thing) ->
:io_lib.printable_list(thing) ->
escape(:unicode.characters_to_binary(thing), ?')
keyword?(thing) ->
"[" <> join_keywords(thing, opts) <> "]"
true ->
container_join(thing, "[", "]", opts)
@@ -272,7 +283,7 @@ defimpl Binary.Inspect, for: List do
defp join_keywords(thing, opts) do
Enum.join(lc {key, value} inlist thing do
key_to_binary(key, opts) <> ": " <> Binary.Inspect.inspect(value, opts)
key_to_binary(key, opts) <> ": " <> Kernel.inspect(value, opts)
end, ", ")
end
@@ -283,18 +294,15 @@ defimpl Binary.Inspect, for: List do
end
end
## printable?
defp printable?([c|cs]) when is_integer(c) and c in 32..126 do
printable?(cs)
defp keyword?([{ key, _value } | rest]) when is_atom(key) do
case atom_to_list(key) do
'Elixir-' ++ _ -> false
_ -> keyword?(rest)
end
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
defp keyword?([]), do: true
defp keyword?(_other), do: false
end
defimpl Binary.Inspect, for: Tuple do
@@ -306,8 +314,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\\\"]"
"""
@@ -315,28 +325,12 @@ defimpl Binary.Inspect, for: Tuple do
def inspect(tuple, opts) do
unless opts[:raw] do
record_protocol(tuple, opts)
record_inspect(tuple, opts)
end || container_join(tuple, "{", "}", opts)
end
## Helpers
defp record_protocol(tuple, opts) do
name = elem(tuple, 0)
if is_atom(name) and match?("Elixir-" <> _, atom_to_binary(name)) do
unless name in [BitString, List, Tuple, Atom, Number, Any] do
try do
target = Module.concat(Binary.Inspect, name)
target.inspect(tuple, opts)
rescue
UndefinedFunctionError ->
record_inspect(tuple, opts)
end
end
end
end
defp record_inspect(record, opts) do
list = tuple_to_list(record)
[name|tail] = list
@@ -365,12 +359,12 @@ defimpl Binary.Inspect, for: Tuple do
end
defp record_join([f], [v], opts) do
atom_to_binary(f, :utf8) <> ": " <> Binary.Inspect.inspect(v, opts)
atom_to_binary(f, :utf8) <> ": " <> Kernel.inspect(v, opts)
end
defp record_join([fh|ft], [vh|vt], opts) do
atom_to_binary(fh, :utf8) <> ": " <>
Binary.Inspect.inspect(vh, opts) <> ", " <>
Kernel.inspect(vh, opts) <> ", " <>
record_join(ft, vt, opts)
end
@@ -385,16 +379,33 @@ defimpl Binary.Inspect, for: Number do
## Examples
inspect(1) #=> "1"
iex> inspect(1)
"1"
"""
@digits 20
@limit :math.pow(10, @digits)
def inspect(thing, _) when is_integer(thing) do
list_to_binary integer_to_list(thing)
integer_to_binary(thing)
end
def inspect(thing, _) do
list_to_binary :io_lib.format("~p", [thing])
to_binary = :proplists.get_value(:float_to_binary,
:proplists.get_value(:exports, :erlang.module_info, []))
if to_binary == 2 do
def inspect(thing, _) when thing > @limit do
float_to_binary(thing, scientific: @digits)
end
def inspect(thing, _) do
float_to_binary(thing, compact: true, decimals: @digits)
end
else
def inspect(thing, _) do
float_to_binary(thing)
end
end
end
@@ -404,16 +415,17 @@ defimpl Binary.Inspect, for: Regex do
## Examples
inspect(%r/foo/m) #=> "%r\"foo\"m"
iex> inspect(%r/foo/m)
"%r\"foo\"m"
"""
def inspect(regex, _opts) when size(regex) == 5 do
"%r" <> Binary.Inspect.inspect(Regex.source(regex), []) <> Regex.opts(regex)
"%r" <> Kernel.inspect(Regex.source(regex), []) <> Regex.opts(regex)
end
def inspect(other, opts) do
Binary.Inspect.inspect other, Keyword.put(opts, :raw, true)
Kernel.inspect other, Keyword.put(opts, :raw, true)
end
end
+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
"""
+47 -27
View File
@@ -57,6 +57,12 @@ defmodule Code do
:code.del_path(Path.expand to_char_list(path))
end
@doc false
def eval(string, binding // [], opts // []) do
IO.write "[WARNING] Code.eval is deprecated, please use Code.eval_string instead\n#{Exception.format_stacktrace}"
eval_string(string, binding, opts)
end
@doc """
Evalutes the contents given by string. The second argument is the
binding (which should be a keyword) followed by a keyword list of
@@ -84,24 +90,24 @@ 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_string("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_string("a + b", [a: 1, b: 2], __ENV__)
{ 3, [ {:a, 1}, {:b, 2} ] }
"""
def eval(string, binding // [], opts // [])
def eval_string(string, binding // [], opts // [])
def eval(string, binding, Macro.Env[] = env) do
eval(string, binding, env.to_keywords)
def eval_string(string, binding, Macro.Env[] = env) do
eval_string(string, binding, env.to_keywords)
end
def eval(string, binding, opts) do
def eval_string(string, binding, opts) do
{ value, binding, _scope } =
:elixir.eval :unicode.characters_to_list(string), binding, opts
{ value, binding }
@@ -111,20 +117,20 @@ defmodule Code do
Evalutes the quoted contents.
This function accepts a list of environment options.
Check `Code.eval` for more information.
Check `Code.eval_string` for more information.
## 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 +146,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
@@ -153,6 +159,11 @@ defmodule Code do
* `:existing_atoms_only` - When true, raises an error
when non-existing atoms are found by the tokenizer.
## Macro.to_binary/1
The opposite of converting a string to its AST is
`Macro.to_binary`, which converts a AST to a binary
representation.
"""
def string_to_ast(string, opts // []) do
file = Keyword.get opts, :file, "nofile"
@@ -205,17 +216,17 @@ defmodule Code do
end
@doc """
Requires the given `file`. Accepts `relative_to` as an argument
to tell where the file is located. If the file was already
required/loaded, loads it again. It returns all the modules
defined in the file.
Requires the given `file`. Accepts `relative_to` as an argument to tell where
the file is located. It returns all the modules defined in the file. If the
file was already required/loaded, doesn't do anything and returns nil.
Notice that if `require_file` is invoked by different processes
concurrently, the first process to invoke `require_file` acquires
a lock and the remaining ones will block until the file is
available. I.e. if `require_file` is called N times with a given
file, the given file will be loaded only once. Check `load_file`
if you want a file to be loaded concurrently.
Notice that if `require_file` is invoked by different processes concurrently,
the first process to invoke `require_file` acquires a lock and the remaining
ones will block until the file is available. I.e. if `require_file` is called
N times with a given file, it will be loaded only once. The first process to
call `require_file` will get the list of loaded modules, others will get nil.
Check `load_file` if you want a file to be loaded concurrently.
"""
def require_file(file, relative_to // nil) when is_binary(file) do
file = find_file(file, relative_to)
@@ -270,6 +281,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,
+260 -53
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,23 +13,41 @@ 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
@type key :: any
@type value :: any
@type keys :: [ key ]
@type t :: tuple | list
defcallback delete(t, key) :: t
defcallback drop(t, keys) :: t
defcallback empty(t) :: t
defcallback equal?(t, t) :: boolean
defcallback get(t, key) :: value
defcallback get(t, key, value) :: value
defcallback get!(t, key) :: value | no_return
@@ -37,9 +55,13 @@ defmodule Dict do
defcallback keys(t) :: list(key)
defcallback merge(t, t) :: t
defcallback merge(t, t, (key, value, value -> value)) :: t
defcallback pop(t, key) :: {value, t}
defcallback pop(t, key, value) :: {value, t}
defcallback put(t, key, value) :: t
defcallback put_new(t, key, value) :: t
defcallback size(t) :: non_neg_integer()
defcallback split(t, keys) :: {t, t}
defcallback take(t, keys) :: t
defcallback to_list(t) :: list()
defcallback update(t, key, (value -> value)) :: t | no_return
defcallback update(t, key, value, (value -> value)) :: t
@@ -51,7 +73,7 @@ defmodule Dict do
is_tuple(unquote(dict)) ->
elem(unquote(dict), 0)
is_list(unquote(dict)) ->
List.Dict
ListDict
end
end
end
@@ -63,8 +85,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 +100,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 +115,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 +130,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,31 +150,65 @@ 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
target(dict).get(dict, key, default)
end
@doc false
def get!(dict, key) do
target(dict).get!(dict, key)
end
@doc """
Returns the `{ :ok, value }` associated with `key` in `dict`.
If `dict` does not contain `key`, returns `:error`.
## Examples
iex> d = HashDict.new([a: 1])
...> Dict.fetch(d, :a)
{ :ok, 1 }
iex> d = HashDict.new([a: 1])
...> Dict.fetch(d, :b)
:error
"""
@spec fetch(t, key) :: value
def fetch(dict, key) do
target(dict).fetch(dict, key)
end
@doc """
Returns the value associated with `key` in `dict`. If `dict` does not
contain `key`, it raises `KeyError`.
## 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.fetch!(d, :a)
1
iex> d = HashDict.new([a: 1])
...> Dict.fetch!(d, :b)
** (KeyError) key not found: :b
"""
@spec get!(t, key) :: value | no_return
def get!(dict, key) do
target(dict).get!(dict, key)
@spec fetch!(t, key) :: value | no_return
def fetch!(dict, key) do
target(dict).fetch!(dict, key)
end
@doc """
@@ -154,9 +217,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 +233,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 +250,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 +271,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 +290,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
@@ -234,15 +304,43 @@ defmodule Dict do
target(dict).merge(dict, enum, fun)
end
@doc """
Returns the value associated with `key` in `dict` as
well as the `dict` without `key`.
## Examples
iex> dict = HashDict.new [a: 1]
...> {v, d} = Dict.pop dict, :a
...> {v, Enum.sort(d)}
{1,[]}
iex> dict = HashDict.new [a: 1]
...> {v, d} = Dict.pop dict, :b
...> {v, Enum.sort(d)}
{nil,[a: 1]}
iex> dict = HashDict.new [a: 1]
...> {v, d} = Dict.pop dict, :b, 3
...> {v, Enum.sort(d)}
{3,[a: 1]}
"""
@spec pop(t, key, value) :: {value, t}
def pop(dict, key, default // nil) do
target(dict).pop(dict, key, default)
end
@doc """
Update a value in `dict` by calling `fun` on the value to get a new
value. An exception is generated if `key` is not present in the dict.
## 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 +355,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
@@ -267,6 +366,80 @@ defmodule Dict do
target(dict).update(dict, key, initial, fun)
end
@doc """
Returns a tuple of two dicts, where the first dict contains only
entries from `dict` with keys in `keys`, and the second dict
contains only entries from `dict` with keys not in `keys`
Any non-member keys are ignored.
## Examples
iex> d = HashDict.new([a: 1, b: 2])
...> { d1, d2 } = Dict.split(d, [:a, :c])
...> { Dict.to_list(d1), Dict.to_list(d2) }
{ [a: 1], [b: 2] }
iex> d = HashDict.new([])
...> { d1, d2 } = Dict.split(d, [:a, :c])
...> { Dict.to_list(d1), Dict.to_list(d2) }
{ [], [] }
iex> d = HashDict.new([a: 1, b: 2])
...> { d1, d2 } = Dict.split(d, [:a, :b, :c])
...> { Dict.to_list(d1), Dict.to_list(d2) }
{ [a: 1, b: 2], [] }
"""
@spec split(t, keys) :: {t, t}
def split(dict, keys) do
target(dict).split(dict, keys)
end
@doc """
Returns a new dict where the the given `keys` a removed from `dict`.
Any non-member keys are ignored.
## Examples
iex> d = HashDict.new([a: 1, b: 2])
...> d = Dict.drop(d, [:a, :c, :d])
...> Dict.to_list(d)
[b: 2]
iex> d = HashDict.new([a: 1, b: 2])
...> d = Dict.drop(d, [:c, :d])
...> Dict.to_list(d)
[a: 1, b: 2]
"""
@spec drop(t, keys) :: t
def drop(dict, keys) do
target(dict).drop(dict, keys)
end
@doc """
Returns a new dict where only the keys in `keys` from `dict` are
included. Any non-member keys are ignored.
## Examples
iex> d = HashDict.new([a: 1, b: 2])
...> d = Dict.take(d, [:a, :c, :d])
...> Dict.to_list(d)
[a: 1]
iex> d = HashDict.new([a: 1, b: 2])
...> d = Dict.take(d, [:c, :d])
...> Dict.to_list(d)
[]
"""
@spec take(t, keys) :: t
def take(dict, keys) do
target(dict).take(dict, keys)
end
@doc """
Returns an empty dict of the same type as `dict`.
"""
@@ -275,6 +448,40 @@ defmodule Dict do
target(dict).empty(dict)
end
@doc """
Check if two dicts are equal, if the dicts are of different types they're
first converted to lists.
## Examples
iex> a = HashDict.new(a: 2, b: 3, f: 5, c: 123)
...> b = ListDict.new(a: 2, b: 3, f: 5, c: 123)
...> Dict.equal?(a, b)
true
iex> a = HashDict.new(a: 2, b: 3, f: 5, c: 123)
...> b = []
...> Dict.equal?(a, b)
false
"""
@spec equal?(t, t) :: boolean
def equal?(a, b) do
a_target = target(a)
b_target = target(b)
cond do
a_target == b_target ->
a_target.equal?(a, b)
a_target.size(a) == b_target.size(b) ->
ListDict.equal?(a_target.to_list(a), b_target.to_list(b))
true ->
false
end
end
@doc """
Returns a list of key-value pairs stored in `dict`.
No particular order is enforced.
-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
+560 -203
View File
File diff suppressed because it is too large Load Diff
+124 -124
View File
@@ -1,3 +1,94 @@
defexception RuntimeError, message: "runtime error"
defexception ArgumentError, message: "argument error"
defexception ArithmeticError, message: "bad argument in arithmetic expression"
defexception SystemLimitError, message: "a system limit has been reached"
defexception SyntaxError, [file: nil, line: nil, description: "syntax error"] do
def message(exception) do
"#{Exception.format_file_line(exception.file, exception.line, nil)}#{exception.description}"
end
end
defexception TokenMissingError, [file: nil, line: nil, description: "expression is incomplete"] do
def message(exception) do
"#{Exception.format_file_line(exception.file, exception.line, nil)}#{exception.description}"
end
end
defexception CompileError, [file: nil, line: nil, description: "compile error"] do
def message(exception) do
"#{Exception.format_file_line(exception.file, exception.line, nil)}#{exception.description}"
end
end
defexception BadFunctionError, [actual: nil] do
def message(exception) do
"expected a function, got: #{inspect(exception.actual)}"
end
end
defexception MatchError, [actual: nil] do
def message(exception) do
"no match of right hand side value: #{inspect(exception.actual)}"
end
end
defexception CaseClauseError, [actual: nil] do
def message(exception) do
"no case clause matching: #{inspect(exception.actual)}"
end
end
defexception BadArityError, [function: nil, args: nil] do
def message(exception) do
"bad arity error: #{inspect(exception.function)} called with #{inspect(exception.args)}"
end
end
defexception UndefinedFunctionError, [module: nil, function: nil, arity: nil] do
def message(exception) do
if exception.function do
formatted = Exception.format_module_fun_arity exception.module, exception.function, exception.arity
"undefined function: #{formatted}"
else
"undefined function"
end
end
end
defexception FunctionClauseError, [module: nil, function: nil, arity: nil] do
def message(exception) do
if exception.function do
formatted = Exception.format_module_fun_arity exception.module, exception.function, exception.arity
"no function clause matching in #{formatted}"
else
"no function clause matches"
end
end
end
defexception Protocol.UndefinedError, [protocol: nil, structure: nil] do
def message(exception) do
"protocol #{inspect exception.protocol} not implemented for #{inspect exception.structure}"
end
end
defexception ErlangError, [original: nil] do
def message(exception) do
"erlang error: #{inspect(exception.original)}"
end
end
defexception KeyError, key: nil do
def message(exception) do
"key not found: #{inspect exception.key}"
end
end
defexception Enum.OutOfBoundsError, message: "out of bounds error"
defexception Enum.EmptyError, message: "empty error"
defmodule Exception do
@moduledoc """
Several convenience functions to work and pretty print
@@ -14,47 +105,55 @@ defmodule Exception do
end
def normalize(:badarg) do
ArgumentError.new
ArgumentError[]
end
def normalize(:badarith) do
ArithmeticError.new
ArithmeticError[]
end
def normalize(:system_limit) do
SystemLimitError.new
SystemLimitError[]
end
def normalize({ :badarity, { fun, args } }) do
BadArityError.new(function: fun, args: args)
BadArityError[function: fun, args: args]
end
def normalize({ :badfun, actual }) do
BadFunctionError.new(actual: actual)
BadFunctionError[actual: actual]
end
def normalize({ :badmatch, actual }) do
MatchError.new(actual: actual)
MatchError[actual: actual]
end
def normalize({ :case_clause, actual }) do
CaseClauseError.new(actual: actual)
CaseClauseError[actual: actual]
end
def normalize(:undef) do
UndefinedFunctionError.new from_stacktrace(:erlang.get_stacktrace)
{ mod, fun, arity } = from_stacktrace(:erlang.get_stacktrace)
UndefinedFunctionError[module: mod, function: fun, arity: arity]
end
def normalize(:function_clause) do
FunctionClauseError.new from_stacktrace(:erlang.get_stacktrace)
{ mod, fun, arity } = from_stacktrace(:erlang.get_stacktrace)
FunctionClauseError[module: mod, function: fun, arity: arity]
end
def normalize({ :badarg, payload }) do
ArgumentError.new message: "argument error: #{inspect(payload)}"
ArgumentError[message: "argument error: #{inspect(payload)}"]
end
def normalize(other) do
ErlangError.new original: other
ErlangError[original: other]
end
@doc false
def format_entry(entry, cwd // nil) do
IO.write "[WARNING] Exception.format_stacktrace is deprecated, please use Exception.format_stacktrace_entry instead\n#{Exception.format_stacktrace}"
format_stacktrace_entry(entry, cwd)
end
@doc """
@@ -62,28 +161,28 @@ defmodule Exception do
The current working directory may be given as argument, which
is used to prettify the stacktrace.
"""
def format_entry(entry, cwd // nil)
def format_stacktrace_entry(entry, cwd // nil)
# From Macro.Env.stacktrace
def format_entry({ module, :__MODULE__, 0, file_line }, cwd) do
def format_stacktrace_entry({ module, :__MODULE__, 0, file_line }, cwd) do
"#{format_file_line(file_line, cwd)}#{inspect module} (module)"
end
# From :elixir_compiler
def format_entry({ _module, :__MODULE__, 2, file_line }, cwd) do
def format_stacktrace_entry({ _module, :__MODULE__, 2, file_line }, cwd) do
"#{format_file_line(file_line, cwd)}(module)"
end
# From :elixir_compiler
def format_entry({ _module, :__FILE__, 2, file_line }, cwd) do
def format_stacktrace_entry({ _module, :__FILE__, 2, file_line }, cwd) do
"#{format_file_line(file_line, cwd)}(file)"
end
def format_entry({module, fun, arity, file_line}, cwd) do
def format_stacktrace_entry({module, fun, arity, file_line}, cwd) do
"#{format_file_line(file_line, cwd)}#{format_module_fun_arity(module, fun, arity)}"
end
def format_entry({fun, arity, file_line}, cwd) do
def format_stacktrace_entry({fun, arity, file_line}, cwd) do
"#{format_file_line(file_line, cwd)}#{format_fun_arity(fun, arity)}"
end
@@ -96,11 +195,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)
@@ -110,7 +204,7 @@ defmodule Exception do
case trace do
[] -> "\n"
s -> " " <> Enum.map_join(s, "\n ", format_entry(&1)) <> "\n"
s -> " " <> Enum.map_join(s, "\n ", format_stacktrace_entry(&1)) <> "\n"
end
end
@@ -127,14 +221,6 @@ defmodule Exception do
## Helpers
# Check the given module is a valid exception record.
@doc false
def check!(module) do
unless :erlang.function_exported(module, :message, 1) do
raise "Expected #{inspect module} to implement message/1"
end
end
# Format fun and arity
@doc false
def format_fun_arity(fun, arity) do
@@ -189,101 +275,15 @@ defmodule Exception do
end
end
defp from_stacktrace([{ module, function, args, _ }|_]) when is_list(args) do
{ module, function, length(args) }
end
defp from_stacktrace([{ module, function, arity, _ }|_]) do
[module: module, function: function, arity: arity]
{ module, function, arity }
end
defp from_stacktrace(_), do: []
end
defexception RuntimeError, message: "runtime error"
defexception ArgumentError, message: "argument error"
defexception ArithmeticError, message: "bad argument in arithmetic expression"
defexception SystemLimitError, message: "a system limit has been reached"
defexception SyntaxError, [file: nil, line: nil, description: "syntax error"] do
def message(exception) do
"#{Exception.format_file_line(exception.file, exception.line, nil)}#{exception.description}"
defp from_stacktrace(_) do
{ nil, nil, nil }
end
end
defexception TokenMissingError, [file: nil, line: nil, description: "expression is incomplete"] do
def message(exception) do
"#{Exception.format_file_line(exception.file, exception.line, nil)}#{exception.description}"
end
end
defexception CompileError, [file: nil, line: nil, description: "compile error"] do
def message(exception) do
"#{Exception.format_file_line(exception.file, exception.line, nil)}#{exception.description}"
end
end
defexception BadFunctionError, [actual: nil] do
def message(exception) do
"expected a function, got: #{inspect(exception.actual)}"
end
end
defexception MatchError, [actual: nil] do
def message(exception) do
"no match of right hand side value: #{inspect(exception.actual)}"
end
end
defexception CaseClauseError, [actual: nil] do
def message(exception) do
"no case clause matching: #{inspect(exception.actual)}"
end
end
defexception BadArityError, [function: nil, args: nil] do
def message(exception) do
"bad arity error: #{inspect(exception.function)} called with #{inspect(exception.args)}"
end
end
defexception UndefinedFunctionError, [module: nil, function: nil, arity: nil] do
def message(exception) do
if exception.function do
formatted = Exception.format_module_fun_arity exception.module, exception.function, to_arity(exception.arity)
"undefined function: #{formatted}"
else
"undefined function"
end
end
defp to_arity(arity) when is_integer(arity), do: arity
defp to_arity(list) when is_list(list), do: length(list)
end
defexception FunctionClauseError, [module: nil, function: nil, arity: nil] do
def message(exception) do
if exception.function do
formatted = Exception.format_module_fun_arity exception.module, exception.function, exception.arity
"no function clause matching: #{formatted}"
else
"no function clause matches"
end
end
end
defexception Protocol.UndefinedError, [protocol: nil, structure: nil] do
def message(exception) do
"protocol #{inspect exception.protocol} not implemented for #{inspect exception.structure}"
end
end
defexception ErlangError, [original: nil] do
def message(exception) do
"erlang error: #{inspect(exception.original)}"
end
end
defexception KeyError, key: nil do
def message(exception) do
"key not found: #{inspect exception.key}"
end
end
defexception Enum.OutOfBoundsError, message: "out of bounds error"
+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
+152 -48
View File
@@ -62,6 +62,7 @@ defmodule HashDict do
@doc """
Creates a new empty dict.
"""
@spec new :: Dict.t
def new do
ordered()
end
@@ -72,9 +73,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 +90,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 +126,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
@@ -148,17 +151,34 @@ defmodule HashDict do
end
end
@doc """
Gets the value under key from the dict,
raises KeyError if such key does not exist.
"""
@doc false
def get!(dict, key) when is_tuple(dict) do
IO.write "[WARNING] Dict.get! and HashDict.get! are deprecated, please use Dict.fetch! and HashDict.fetch! instead\n#{Exception.format_stacktrace}"
case dict_get(dict, key) do
{ ^key, value } -> value
false -> raise(KeyError, key: key)
end
end
@doc """
Fetches the value under key from the dict
and return it in a tagged tuple.
"""
def fetch(dict, key) do
case dict_get(dict, key) do
{ ^key, value } -> { :ok, value }
false -> :error
end
end
def fetch!(dict, key) when is_tuple(dict) do
case dict_get(dict, key) do
{ ^key, value } -> value
false -> raise(KeyError, key: key)
end
end
@doc """
Checks if the dict has the given key.
"""
@@ -167,35 +187,21 @@ defmodule HashDict do
end
@doc """
Deletes a value from the dict.
Returns the value under key from the dict as well as the dict without key.
"""
def delete(ordered(bucket: bucket, size: size) = dict, key) do
case bucket_delete(bucket, key) do
{ _, 0 } ->
dict
{ new_bucket, -1 } ->
ordered(dict, size: size - 1, bucket: new_bucket)
def pop(dict, key, default // nil) do
case dict_delete(dict, key) do
{ dict, _, 0 } -> { default, dict }
{ dict, value, _ } -> { value, dict }
end
end
def delete(trie(root: root, size: size, depth: depth) = dict, key) do
pos = bucket_hash(key)
case node_delete(root, depth, pos, key) do
{ _, 0 } ->
dict
{ root, -1 } ->
res = 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))
else
trie(dict, size: size - 1, root: root)
end
end
@doc """
Deletes a value from the dict.
"""
def delete(dict, key) do
{ dict, _, _ } = dict_delete(dict, key)
dict
end
@doc """
@@ -212,6 +218,26 @@ defmodule HashDict do
ordered()
end
def equal?(ordered(bucket: a, size: size), ordered(bucket: b, size: ^size)) do
a == b
end
def equal?(trie(size: size) = a, trie(size: ^size) = b) do
a == b
end
def equal?(ordered() = a, trie() = b) do
equal?(b, a)
end
def equal?(trie(size: size) = a, ordered(bucket: b, size: ^size)) do
:lists.keysort(1, to_list(a)) == :lists.keysort(1, b)
end
def equal?(_, _) do
false
end
@doc """
Converts the dict to a list.
"""
@@ -260,6 +286,53 @@ defmodule HashDict do
end
end
@doc """
Splits a dict into two dicts,
one containing entries with key in the keys list,
and another containing entries with key not in keys.
Returns a 2-tuple of the new dicts.
"""
def split(dict, keys) do
split(keys, new, dict)
end
defp split([], including, excluding) do
{ including, excluding }
end
defp split([key|keys], including, excluding) do
case dict_delete(excluding, key) do
{ excluding, _, 0 } -> split(keys, including, excluding)
{ excluding, value, _ } -> split(keys, put(including, key, value), excluding)
end
end
@doc """
Returns a new dict with only the entries
which key is in keys.
"""
def take(dict, keys) do
take(dict, keys, new)
end
defp take(_dict, [], acc), do: acc
defp take(dict, [key|keys], acc) do
case fetch(dict, key) do
{ :ok, value } -> take(dict, keys, put(acc, key, value))
:error -> take(dict, keys, acc)
end
end
@doc """
Returns a new dict with only the entries
which key is not in keys
"""
def drop(dict, []), do: dict
def drop(dict, [key|keys]) do
drop(delete(dict, key), keys)
end
## Dict-wide functions
defp dict_get(ordered(bucket: bucket), key) do
@@ -301,6 +374,35 @@ defmodule HashDict do
{ trie(dict, size: size + count, root: root), count }
end
defp dict_delete(ordered(bucket: bucket, size: size) = dict, key) do
case bucket_delete(bucket, key) do
{ _, value, 0 } ->
{ dict, value, 0 }
{ new_bucket, value, -1 } ->
{ ordered(dict, size: size - 1, bucket: new_bucket), value, -1 }
end
end
defp dict_delete(trie(root: root, size: size, depth: depth) = dict, key) do
pos = bucket_hash(key)
case node_delete(root, depth, pos, key) do
{ _, value, 0 } ->
{ dict, value, 0 }
{ root, value, -1 } ->
{ if depth > 0 and trie(dict, :contract_on) == size do
root = node_contract(root, depth, 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))
else
trie(dict, size: size - 1, root: root)
end, value, -1 }
end
end
## Bucket helpers
# Puts a value in the bucket
@@ -342,20 +444,20 @@ defmodule HashDict do
# Deletes a key from the bucket
defp bucket_delete([{k,_}|_] = bucket, key) when key < k do
{ bucket, 0 }
{ bucket, nil, 0 }
end
defp bucket_delete([{k,_}=e|bucket], key) when key > k do
{ rest, count } = bucket_delete(bucket, key)
{ [e|rest], count }
{ rest, value, count } = bucket_delete(bucket, key)
{ [e|rest], value, count }
end
defp bucket_delete([{_,_}|bucket], _key) do
{ bucket, -1 }
defp bucket_delete([{_,value}|bucket], _key) do
{ bucket, value, -1 }
end
defp bucket_delete([], _key) do
{ [], 0 }
{ [], nil, 0 }
end
# Folds the bucket
@@ -395,29 +497,29 @@ 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
defp node_delete(node, 0, hash, key) do
pos = bucket_index(hash)
case bucket_delete(elem(node, pos), key) do
{ _, 0 } -> { node, 0 }
{ new, -1 } -> { setelem(node, pos, new), -1 }
{ _, value, 0 } -> { node, value, 0 }
{ new, value, -1 } -> { set_elem(node, pos, new), value, -1 }
end
end
defp node_delete(node, depth, hash, key) do
pos = bucket_index(hash)
case node_delete(elem(node, pos), depth - 1, bucket_next(hash), key) do
{ _, 0 } -> { node, 0 }
{ new, -1 } -> { setelem(node, pos, new), -1 }
{ _, value, 0 } -> { node, value, 0 }
{ new, value, -1 } -> { set_elem(node, pos, new), value, -1 }
end
end
@@ -471,14 +573,16 @@ defmodule HashDict do
defp node_relocate(node // @node_template, 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
defimpl Enum.Iterator, for: HashDict do
def iterator(dict), do: HashDict.to_list(dict)
def count(dict), do: HashDict.size(dict)
def iterator(dict), do: HashDict.to_list(dict)
def member?(dict, { k, v }), do: match?({ :ok, ^v }, HashDict.fetch(dict, k))
def member?(_dict, _), do: false
def count(dict), do: HashDict.size(dict)
end
defimpl Access, for: HashDict do
@@ -489,6 +593,6 @@ defimpl Binary.Inspect, for: HashDict do
import Kernel, except: [inspect: 2]
def inspect(dict, opts) do
"#HashDict<" <> Binary.Inspect.inspect(HashDict.to_list(dict), opts) <> ">"
"#HashDict<" <> Kernel.inspect(HashDict.to_list(dict), opts) <> ">"
end
end
+65 -13
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,22 +120,37 @@ 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
puts device, Binary.Inspect.inspect(item, opts)
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, Kernel.inspect(item, opts)
item
end
@doc """
Gets `count` bytes from the IO device. It returns:
Gets a number of bytes from the io device. If the
io device is a unicode device, count implies
the number of unicode codepoints to be retrieved.
Otherwise, the number of raw bytes. It returns:
* `data` - The input characters.
@@ -143,10 +160,45 @@ defmodule IO do
for instance {:error, :estale} if reading from an
NFS file system.
"""
def getb(device // :stdio, prompt, count // 1) do
def getn(prompt, count // 1)
def getn(prompt, count) when is_integer(count) do
getn(group_leader, prompt, count)
end
def getn(device, prompt) do
getn(device, prompt, 1)
end
@doc """
Gets a number of bytes from the io device. If the
io device is a unicode device, count implies
the number of unicode codepoints to be retrieved.
Otherwise, the number of raw bytes.
"""
def getn(device, prompt, count) do
:io.get_chars(map_dev(device), to_iodata(prompt), count)
end
@doc false
def getb(prompt, count // 1)
def getb(prompt, count) when is_integer(count) do
IO.write "[WARNING] IO.getb is deprecated, please use IO.getn instead\n#{Exception.format_stacktrace}"
getn(prompt, count)
end
def getb(device, prompt) do
IO.write "[WARNING] IO.getb is deprecated, please use IO.getn instead\n#{Exception.format_stacktrace}"
getn(device, prompt)
end
@doc false
def getb(device, prompt, count) do
IO.write "[WARNING] IO.getb is deprecated, please use IO.getn instead\n#{Exception.format_stacktrace}"
getn(device, prompt, count)
end
@doc """
Reads a line from the IO device. It returns:
@@ -159,7 +211,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
+231
View File
@@ -0,0 +1,231 @@
defmodule IO.ANSI.Sequence do
@moduledoc false
defmacro defsequence(name, code) do
quote do
name = unquote(name)
code = unquote(code)
def name, [], [], do:
quote do: "\e[#{unquote(code)}m"
args =
quote do: [<< unquote(atom_to_binary(name)), rest :: binary >> ]
defp :escape_sequence, args, [], do:
quote do: { "\e[#{unquote(code)}m", rest }
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
@doc "Sets alternative font #{font_n}"
defsequence :"font_#{font_n}", font_n + 10
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
@doc "Sets foreground color to #{color}"
defsequence color, code + 30
@doc "Sets background color to #{color}"
defsequence :"#{color}_background", code + 40
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
+786 -426
View File
File diff suppressed because it is too large Load Diff
+125 -62
View File
@@ -3,34 +3,27 @@ defmodule Kernel.CLI do
Module responsible for controlling Elixir's CLI
"""
defrecord Config, commands: [], output: ".",
compile: [], halt: true, compiler_options: []
defrecord Config, commands: [], output: ".", compile: [],
halt: true, compiler_options: [], errors: []
# This is the API invoked by Elixir boot process.
@doc false
def main(argv) do
argv = lc arg inlist argv, do: list_to_binary(arg)
argv = lc arg inlist argv, do: :unicode.characters_to_binary(arg)
{ config, argv } = process_argv(argv, Kernel.CLI.Config.new)
:elixir_code_server.cast({ :argv, argv })
run fn ->
Enum.map Enum.reverse(config.commands), process_command(&1, config)
:elixir_code_server.cast(:finished)
end, config.halt
end
command_results = Enum.map(Enum.reverse(config.commands), process_command(&1, config))
command_errors = lc { :error, msg } inlist command_results, do: msg
errors = Enum.reverse(config.errors) ++ command_errors
@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
if errors != [] do
Enum.each(errors, IO.puts(:stderr, &1))
System.halt(1)
end
end, config.halt
end
@doc """
@@ -53,7 +46,7 @@ defmodule Kernel.CLI do
at_exit(1)
trace = System.stacktrace
IO.puts :stderr, "** (#{inspect exception.__record__(:name)}) #{exception.message}"
IO.puts Exception.format_stacktrace(trace)
IO.puts :stderr, Exception.format_stacktrace(trace)
System.halt(1)
catch
:exit, reason when is_integer(reason) ->
@@ -66,12 +59,12 @@ defmodule Kernel.CLI do
at_exit(1)
trace = System.stacktrace
IO.puts :stderr, "** (#{kind}) #{inspect(reason)}"
IO.puts Exception.format_stacktrace(trace)
IO.puts :stderr, Exception.format_stacktrace(trace)
System.halt(1)
end
end
## Private
## Helpers
defp at_exit(status) do
hooks = :elixir_code_server.call(:flush_at_exit)
@@ -83,12 +76,12 @@ defmodule Kernel.CLI do
exception ->
trace = System.stacktrace
IO.puts :stderr, "** (#{inspect exception.__record__(:name)}) #{exception.message}"
IO.puts Exception.format_stacktrace(trace)
IO.puts :stderr, Exception.format_stacktrace(trace)
catch
kind, reason ->
trace = System.stacktrace
IO.puts :stderr, "** #{kind} #{inspect(reason)}"
IO.puts Exception.format_stacktrace(trace)
IO.puts :stderr, Exception.format_stacktrace(trace)
end
end
@@ -97,15 +90,11 @@ defmodule Kernel.CLI do
unless hooks == [], do: at_exit(status)
end
defp invalid_option(option) do
IO.puts(:stderr, "Unknown option #{option}")
System.halt(1)
end
defp shared_option?(list, config, callback) do
case process_shared(list, config) do
{ [h|t], _ } when h == hd(list) ->
invalid_option h
{ [h|hs], _ } when h == hd(list) ->
new_config = config.update_errors ["#{h} : Unknown option" | &1]
callback.(hs, new_config)
{ new_list, new_config } ->
callback.(new_list, new_config)
end
@@ -118,6 +107,16 @@ defmodule Kernel.CLI do
process_shared t, config
end
defp process_shared(["-pa",h|t], config) do
Enum.each Path.wildcard(Path.expand(h)), Code.prepend_path(&1)
process_shared t, config
end
defp process_shared(["-pz",h|t], config) do
Enum.each Path.wildcard(Path.expand(h)), Code.append_path(&1)
process_shared t, config
end
defp process_shared(["--app",h|t], config) do
process_shared t, config.update_commands [{:app,h}|&1]
end
@@ -130,27 +129,19 @@ defmodule Kernel.CLI do
process_shared t, config.update_commands [{:eval,h}|&1]
end
defp process_shared(["-pa",h|t], config) do
Enum.each Path.wildcard(Path.expand(h)), Code.prepend_path(&1)
process_shared t, config
end
defp process_shared(["-pz",h|t], config) do
Enum.each Path.wildcard(Path.expand(h)), Code.append_path(&1)
process_shared t, config
end
defp process_shared(["-r",h|t], config) do
process_shared t, Enum.reduce(Path.wildcard(h), config, fn path, config ->
config.update_commands [{:require,path}|&1]
end)
process_shared t, config.update_commands [{:require,h}|&1]
end
defp process_shared(["-pr",h|t], config) do
process_shared t, config.update_commands [{:parallel_require,h}|&1]
end
defp process_shared([erl,_|t], config) when erl in ["--erl", "--sname", "--remsh", "--name"] do
defp process_shared(["--cookie",h|t], config) do
process_shared t, config.update_commands [{:cookie,h}|&1]
end
defp process_shared([erl,_|t], config) when erl in ["--erl", "--sname", "--name"] do
process_shared t, config
end
@@ -164,18 +155,16 @@ defmodule Kernel.CLI do
{ config, t }
end
defp process_argv(["--compile"|t], config) do
defp process_argv(["+compile"|t], config) do
process_compiler t, config
end
defp process_argv(["+iex"|t], config) do
process_iex t, config
end
defp process_argv(["-S",h|t], config) do
exec = System.find_executable(h)
if exec do
{ config.update_commands([{:require,exec}|&1]), t }
else
IO.puts(:stderr, "Could not find executable #{h}")
System.halt(1)
end
{ config.update_commands([{:script,h}|&1]), t }
end
defp process_argv([h|t] = list, config) do
@@ -183,7 +172,7 @@ defmodule Kernel.CLI do
"-" <> _ ->
shared_option? list, config, process_argv(&1, &2)
_ ->
{ config.update_commands([{:require,h}|&1]), t }
{ config.update_commands([{:file,h}|&1]), t }
end
end
@@ -227,31 +216,100 @@ defmodule Kernel.CLI do
{ config.update_commands([{:compile,config.compile}|&1]), [] }
end
# Process iex options
defp process_iex(["--"|t], config) do
{ config, t }
end
defp process_iex([opt,_|t], config) when opt in ["--remsh"] do
process_iex t, config
end
defp process_iex(["-S",h|t], config) do
{ config.update_commands([{:script,h}|&1]), t }
end
defp process_iex([h|t] = list, config) do
case h do
"-" <> _ ->
shared_option? list, config, process_iex(&1, &2)
_ ->
{ config.update_commands([{:file,h}|&1]), t }
end
end
defp process_iex([], config) do
{ config, [] }
end
# Process commands
defp process_command({:cookie,h}, _config) do
if Node.alive? do
Node.set_cookie(binary_to_atom(h))
:ok
else
{ :error, "--cookie : Cannot set cookie if the node is not alive (set --name or --sname)" }
end
end
defp process_command({:eval, expr}, _config) when is_binary(expr) do
Code.eval(expr, [])
Code.eval_string(expr, [])
:ok
end
defp process_command({:app, app}, _config) when is_binary(app) do
case Application.Behaviour.start(binary_to_atom(app)) do
{ :error, reason } ->
IO.puts(:stderr, "Could not start application #{app}: #{inspect reason}")
System.halt(1)
{ :error, "--app : Could not start application #{app}: #{inspect reason}" }
:ok ->
:ok
end
end
defp process_command({:require, file}, _config) when is_binary(file) do
Code.require_file(file)
defp process_command({:script, file}, _config) when is_binary(file) do
if exec = System.find_executable(file) do
Code.require_file(exec)
:ok
else
{ :error, "-S : Could not find executable #{file}" }
end
end
defp process_command({:file, file}, _config) when is_binary(file) do
if File.regular?(file) do
Code.require_file(file)
:ok
else
{ :error, "No file named #{file}" }
end
end
defp process_command({:require, pattern}, _config) when is_binary(pattern) do
files = Path.wildcard(pattern)
files = Enum.uniq(files)
files = Enum.filter files, File.regular?(&1)
if files != [] do
Enum.map files, Code.require_file(&1)
:ok
else
{ :error, "-r : No files matched pattern #{pattern}" }
end
end
defp process_command({:parallel_require, pattern}, _config) when is_binary(pattern) do
files = Path.wildcard(pattern)
files = Enum.uniq(files)
files = Enum.filter files, File.regular?(&1)
Kernel.ParallelRequire.files(files)
if files != [] do
Kernel.ParallelRequire.files(files)
:ok
else
{ :error, "-pr : No files matched pattern #{pattern}" }
end
end
defp process_command({:compile, patterns}, config) do
@@ -261,8 +319,13 @@ defmodule Kernel.CLI do
files = Enum.uniq(List.concat(files))
files = Enum.filter files, File.regular?(&1)
Code.compiler_options(config.compiler_options)
Kernel.ParallelCompiler.files_to_path(files, config.output,
fn file -> IO.puts "Compiled #{file}" end)
if files != [] do
Code.compiler_options(config.compiler_options)
Kernel.ParallelCompiler.files_to_path(files, config.output,
fn file -> IO.puts "Compiled #{file}" end)
:ok
else
{ :error, "--compile : No files matched patterns #{Enum.join(patterns, ",")}" }
end
end
end
+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
+97 -79
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,16 +63,16 @@ 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
if :lists.member({ function, length(args) + 1 }, optimizable) do
case record_field_info(function) do
{ kind, field } ->
if index = Enum.find_index(fields, field == &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
@@ -342,6 +350,10 @@ defmodule Kernel.RecordRewriter do
vars
end
defp join_dict([]) do
[]
end
defp join_dict([{ _, dict, _ }|t]) do
join_dict(t, dict)
end
@@ -355,6 +367,8 @@ defmodule Kernel.RecordRewriter do
{ :ok, _ } -> :orddict.store(key, nil, acc)
:error -> :orddict.erase(key, acc)
end
{ key, nil }, acc ->
:orddict.store(key, nil, acc)
end
join_dict(t, other)
@@ -364,6 +378,10 @@ defmodule Kernel.RecordRewriter do
other
end
defp join_result([]) do
[]
end
defp join_result([{ _, _, res }|t]) do
join_result(t, res)
end
@@ -383,4 +401,4 @@ defmodule Kernel.RecordRewriter do
defp join_result([], res) do
res
end
end
end
+134 -75
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,26 @@ 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.
## Warnings
If you import a module and you don't use any of the imported
functions or macros from this module, Elixir is going to issue
a warning implying the import is not being used.
In case the import is generated automatically by a macro,
Elixir won't emit any warnings though, since the import
was not explicitly defined.
Both warning behaviors could be changed by explicitily
setting the `:warn` option to true or false.
## 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)
@@ -326,44 +348,46 @@ defmodule Kernel.SpecialForms do
"""
defmacro __FILE__
@doc """
Returns the current directory as a binary.
"""
defmacro __DIR__
@doc """
Allows you to get the representation of any expression.
## Examples
quote do: sum(1, 2, 3)
#=> { :sum, [], [1, 2, 3] }
iex> quote do: sum(1, 2, 3)
{ :sum, [], [1, 2, 3] }
## Explanation
Any Elixir code can be represented using Elixir data structures.
The building block of Elixir homoiconicity is a tuple with three
elements, for example:
The building block of Elixir macros is a tuple with three elements,
for example:
{ :sum, 1, [1, 2, 3] }
{ :sum, [], [1, 2, 3] }
The tuple above represents a function call to sum passing 1, 2 and
3 as arguments. The tuple elements are:
* The first element of the tuple is always an atom or
another tuple in the same representation;
* The second element of the tuple is always an integer
representing the line number;
* The second element of the tuple represents metadata;
* The third element of the tuple are the arguments for the
function call. The third argument may be an atom, which is
usually a variable (or a local call);
## 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;
* `:context` - Sets the context resolution happens at;
## Macro literals
@@ -377,19 +401,34 @@ defmodule Kernel.SpecialForms do
"binaries" #=> Binaries
{key, value} #=> Tuple with two elements
## Hygiene
## Hygiene and context
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.
This means variables, aliases and imports defined inside the
quoted refer to the context that defined the macro and not
the context where the macro is expanded.
Furthermore, variables inside quote are also hygienic. That
said, a variable defined in a macro cannot affect the scope
where the macro is included.
For this mechanism to work, every quoted code is attached
to a context. Consider the following example:
The option `hygiene` can turn off all the hygiene mechanisms
defined below. However, they can also be changed in a one by
one basis.
defmodule ContextSample do
def hello do
quote do: world
end
end
ContextSample.hello
#=> {:world,[],ContextSample}
Notice how the third element of the returned tuple is the
module name. This means that the variable is associated to the
ContextSample module and only code generated by this module
will be able to access that particular `world` variable.
The context can be disabled or changed by explicitly setting
the context option. All hygiene mechanisms are based on such
context and we are going to explore each of them in the following
subsections.
### Hygiene in variables
@@ -451,14 +490,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 +508,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 +593,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 +627,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
@@ -617,7 +674,8 @@ defmodule Kernel.SpecialForms do
#=> { :sum, [], [1, 13, 3] }
"""
defmacro unquote(expr)
name = :unquote
defmacro unquote(name)(expr)
@doc """
Unquotes the given list expanding its arguments. Similar
@@ -630,46 +688,47 @@ defmodule Kernel.SpecialForms do
#=> { :sum, [], [1, 2, 3, 4, 5] }
"""
defmacro unquote_splicing(expr)
name = :unquote_splicing
defmacro unquote(name)(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 +740,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 +751,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)
@@ -705,7 +764,7 @@ defmodule Kernel.SpecialForms do
it belonged to another file.
quote location: :keep, do: 1
#=> { :__scope__, 1,[[file: "iex"],[do: 1]] }
#=> { :__scope__, [line: 1], [[file: "iex"],[do: 1]] }
Check `quote/1` for more information.
"""
@@ -715,13 +774,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,
+41 -14
View File
@@ -46,9 +46,9 @@ defmodule Kernel.Typespec do
<<_ :: _ * 8>>
### Functions
### Anonymous functions
Any function:
Any anonymous function:
(fun(...) -> any)
or
@@ -56,13 +56,13 @@ defmodule Kernel.Typespec do
or
(... -> any)
Function with arity of zero:
Anonymous function with arity of zero:
(fun() -> type)
or
(() -> type)
Function with some arity:
Anonymous function with some arity:
(fun(type, type) -> type)
or
@@ -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
@@ -279,7 +279,7 @@ defmodule Kernel.Typespec do
lc { :attribute, _, kind, { name, _, args } = type } inlist abstract_code, kind in [:opaque, :type] do
cond do
kind == :opaque -> { :opaque, type }
List.member?(exported_types, { name, length(args) }) -> { :type, type }
:lists.member({ name, length(args) }, exported_types) -> { :type, type }
true -> { :typep, type }
end
end
@@ -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 }
_ ->
[]
@@ -333,9 +333,9 @@ defmodule Kernel.Typespec do
end
defp abstract_code_beam(module) when is_atom(module) do
case :code.which(module) do
:non_existing -> module
file -> file
case :code.get_object_code(module) do
{ ^module, beam, _filename } -> beam
:error -> module
end
end
@@ -350,10 +350,16 @@ 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 +396,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 +478,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 +558,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 +583,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,9 +608,15 @@ 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
if :lists.member(name, vars) do
{ :var, line(meta), name }
else
typespec({name, meta, []}, vars, caller)
@@ -612,6 +635,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 +703,4 @@ defmodule Kernel.Typespec do
defp variable({name, meta, _}) do
{:var, line(meta), name}
end
end
end
+108 -55
View File
@@ -2,7 +2,7 @@ defmodule Keyword do
@moduledoc """
A keyword is a list of tuples where the first element
of the tuple is an atom and the second element can be
any value.
any value.
A keyword may have duplicated keys, so it is not strictly
a dictionary. However most of the functions in this module
@@ -36,10 +36,7 @@ defmodule Keyword do
"""
@spec keyword?(term) :: boolean
def keyword?([{ key, _value } | rest]) when is_atom(key) do
case atom_to_list(key) do
'Elixir-' ++ _ -> false
_ -> keyword?(rest)
end
keyword?(rest)
end
def keyword?([]), do: true
@@ -59,8 +56,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 +74,8 @@ defmodule Keyword do
## Examples
Keyword.new [:a, :b], fn x -> {x,x} end
#=> [a: :a, b: :b]
iex> Keyword.new([:a, :b], fn (x) -> {x,x} end) |> Enum.sort
[a: :a, b: :b]
"""
@spec new(Enum.t, ({key, value} -> {key, value})) :: t
@@ -100,9 +97,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
@@ -114,31 +114,63 @@ defmodule Keyword do
end
end
@doc """
Gets the value for specific key. If key does not exist,
an error is raised.
## Examples
Keyword.get! [a: 1], :a #=> 1
Keyword.get! [a: 1], :b #=> raises KeyError[key: :b]
"""
@spec get!(t, key) :: value | no_return
@doc false
def get!(keywords, key) when is_atom(key) do
IO.write "[WARNING] Keyword.get! is deprecated, please use Keyword.fetch! instead\n#{Exception.format_stacktrace}"
case :lists.keyfind(key, 1, keywords) do
{ ^key, value } -> value
false -> raise(KeyError, key: key)
end
end
end
@doc """
Fetchs the value for specific key and return it in a tuple.
If the key does not exist, returns `:error`.
## Examples
iex> Keyword.fetch([a: 1], :a)
{ :ok, 1 }
iex> Keyword.fetch([a: 1], :b)
:error
"""
@spec fetch(t, key) :: value
def fetch(keywords, key) when is_atom(key) do
case :lists.keyfind(key, 1, keywords) do
{ ^key, value } -> { :ok, value }
false -> :error
end
end
@doc """
Fetches the value for specific key. If key does not exist,
an error is raised.
## Examples
iex> Keyword.fetch!([a: 1], :a)
1
iex> Keyword.fetch!([a: 1], :b)
** (KeyError) key not found: :b
"""
@spec fetch!(t, key) :: value | no_return
def fetch!(keywords, key) when is_atom(key) do
case :lists.keyfind(key, 1, keywords) do
{ ^key, value } -> value
false -> raise(KeyError, key: key)
end
end
@doc """
Gets all values for a specific key.
## 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 +184,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 +198,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 +215,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
@@ -190,6 +226,23 @@ defmodule Keyword do
lc { k, _ } = tuple inlist keywords, key != k, do: tuple
end
@doc """
Deletes the first entry in the keyword list for a specific key.
If the key does not exist, returns the keyword list unchanged.
## Examples
iex> Keyword.delete_first([a: 1, b: 2, a: 3], :a)
[b: 2, a: 3]
iex> Keyword.delete_first([b: 2], :a)
[b: 2]
"""
@spec delete_first(t, key) :: t
def delete_first(keywords, key) when is_atom(key) do
:lists.keydelete(key, 1, keywords)
end
@doc """
Puts the given `value` under `key`.
@@ -198,8 +251,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 +266,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 +284,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 +299,8 @@ defmodule Keyword do
## Examples
Keyword.merge [a: 1, b: 2], [a: 3, d: 4]
#=> [a:3, b:2, d: 4]
iex> Keyword.merge([a: 1, b: 2], [a: 3, d: 4]) |> Enum.sort
[a: 3, b: 2, d: 4]
"""
@spec merge(t, t) :: t
@@ -261,10 +314,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 +338,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 +355,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,13 +380,13 @@ 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
@spec update(t, key, value, (value -> value)) :: t
def update([{key, value}|keywords], key, _initial, fun) do
[{key, fun.(value)}|delete(keywords, key)]
end
+100 -72
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
@@ -142,39 +142,27 @@ defmodule List do
:lists.last(list)
end
@doc """
Checks if the given `term` is included in the list.
This function simply delegates to `lists:member`
which is implemented in C for performance.
## Examples
List.member? [1,2,3], 1
#=> true
List.member? [1,2,3], 0
#=> false
"""
@doc false
def member?(list, term) do
IO.write "[WARNING] List.member? is deprecated, please use Enum.member? instead\n#{Exception.format_stacktrace}"
:lists.member(term, list)
end
@doc """
Receives a list of tuples and returns the first tuple
where the item at position `posistion` matches with the
where the item at position `position` matches with the
given `item`.
## 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 +176,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 +196,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 +211,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 +226,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 +247,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 +268,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,19 +286,58 @@ 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
:lists.map tuple_to_list(&1), zip(list)
end
@doc """
Returns a list with an inserted value at specified index. Note that the index
is capped at the list length and that negative indicies wraps around at the
end of the list.
## Examples
iex> List.insert_at([1, 2, 3, 4], 2, 0)
[1, 2, 0, 3, 4]
iex> List.insert_at([1, 2, 3], 10, 0)
[1, 2, 3, 0]
iex> List.insert_at([1, 2, 3], -1, 0)
[1, 2, 0, 3]
"""
def insert_at(list, index, value) do
if index < 0 do
do_insert_at(list, length(list) + index, value)
else
do_insert_at(list, index, value)
end
end
## Helpers
# insert_at
defp do_insert_at([], _index, value) do
[ value ]
end
defp do_insert_at(list, index, value) when index <= 0 do
[ value | list ]
end
defp do_insert_at([h|t], index, value) do
[ h | do_insert_at(t, index - 1, value) ]
end
# zip
defp do_zip(list, acc) do
+23 -2
View File
@@ -27,6 +27,27 @@ defimpl List.Chars, for: List do
end
defimpl List.Chars, for: Number do
def to_char_list(integer) when is_integer(integer), do: integer_to_list(integer)
def to_char_list(float) when is_float(float), do: float_to_list(float)
@digits 20
@limit :math.pow(10, @digits)
def to_char_list(thing) when is_integer(thing) do
integer_to_list(thing)
end
to_list = :proplists.get_value(:float_to_list,
:proplists.get_value(:exports, :erlang.module_info, []))
if to_list == 2 do
def to_char_list(thing) when thing > @limit do
float_to_list(thing, scientific: @digits)
end
def to_char_list(thing) do
float_to_list(thing, compact: true, decimals: @digits)
end
else
def to_char_list(thing) do
float_to_list(thing)
end
end
end
@@ -1,4 +1,4 @@
defmodule List.Dict do
defmodule ListDict do
@moduledoc """
A Dict implementation that works on lists of two-items tuples.
@@ -7,19 +7,19 @@ defmodule List.Dict do
"""
@doc """
Returns a new `List.Dict`, i.e. an empty list.
Returns a new `ListDict`, i.e. an empty list.
"""
def new, do: []
@doc """
Creates a new `List.Dict` from the given pairs.
Creates a new `ListDict` from the given pairs.
"""
def new(pairs) do
Enum.map pairs, fn({ k, v }) -> { k, v } end
end
@doc """
Creates a new `List.Dict` from the given pairs
Creates a new `ListDict` from the given pairs
via the given transformation function.
"""
def new(list, transform) when is_function(transform) do
@@ -65,17 +65,45 @@ defmodule List.Dict do
end
end
@doc """
Returns the value under the given key
raises `KeyError` if the key does not exist.
"""
@doc false
def get!(dict, key) do
IO.write "[WARNING] Dict.get! and ListDict.get! are deprecated, please use Dict.fetch! and ListDict.fetch! instead\n#{Exception.format_stacktrace}"
case :lists.keyfind(key, 1, dict) do
{ ^key, value } -> value
false -> raise(KeyError, key: key)
end
end
@doc """
Returns the value under key from the given
dict in a tagged tuple, otherwise `:error`.
"""
def fetch(dict, key) do
case :lists.keyfind(key, 1, dict) do
{ ^key, value } -> { :ok, value }
false -> :error
end
end
@doc """
Returns the value under the given key
raises `KeyError` if the key does not exist.
"""
def fetch!(dict, key) do
case :lists.keyfind(key, 1, dict) do
{ ^key, value } -> value
false -> raise(KeyError, key: key)
end
end
@doc """
Returns the value under the given key
from the dict as well as the dict without that key.
"""
def pop(dict, key, default // nil) do
{ get(dict, key, default), delete(dict, key) }
end
@doc """
Puts the given key-value pair in the dict.
"""
@@ -112,6 +140,39 @@ defmodule List.Dict do
end
end
@doc """
Splits a dict into two dicts,
one containing entries with key in the keys list,
and another containing entries with key not in keys.
Returns a 2-tuple of the new dicts.
"""
def split(dict, keys) do
acc = { new(), new() }
Enum.reduce dict, acc, fn({ k, v }, { take, drop }) ->
if :lists.member(k, keys) do
{ [{k,v}|take], drop }
else
{ take, [{k,v}|drop] }
end
end
end
@doc """
Returns a new dict with only the entries
which key is in keys
"""
def take(dict, keys) do
lc { k, _ } = tuple inlist dict, :lists.member(k, keys), do: tuple
end
@doc """
Returns a new dict with only the entries
which key is not in keys
"""
def drop(dict, keys) do
lc { k, _ } = tuple inlist dict, not :lists.member(k, keys), do: tuple
end
@doc """
Updates the key in the dict according to the given function.
"""
@@ -144,10 +205,17 @@ defmodule List.Dict do
end
@doc """
Returns an empty `List.Dict`.
Returns an empty `ListDict`.
"""
def empty(_dict), do: []
@doc """
Check if the ListDict is equal to another ListDict.
"""
def equal?(dict, other) do
:lists.keysort(1, dict) == :lists.keysort(1, other)
end
@doc """
Converts the dict to a list.
"""
+68 -55
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,32 +51,27 @@ 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
:elixir_quote.escape(expr, false) |> elem(0)
end
def escape(tuple) when is_tuple(tuple) do
{ :{}, [], escape(tuple_to_list(tuple)) }
@doc false
def escape_quoted(expr) do
:elixir_quote.escape(expr, true) |> elem(0)
end
def escape(list) when is_list(list) do
lc item inlist list, do: escape(item)
end
def escape(other), do: other
@doc %B"""
Unescape the given chars. This is the unescaping behavior
used by default in Elixir single- and double-quoted strings.
@@ -93,8 +87,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 +99,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 +146,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 +160,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 +171,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)
@@ -275,7 +271,7 @@ defmodule Macro do
end
# All other structures
def to_binary(other), do: Binary.Inspect.inspect(other, raw: true)
def to_binary(other), do: inspect(other, raw: true)
# Block keywords
defmacrop kw_keywords, do: [:do, :catch, :rescue, :after, :else]
@@ -285,7 +281,7 @@ defmodule Macro do
end
defp is_kw_blocks?(_), do: false
defp module_to_binary(atom) when is_atom(atom), do: Binary.Inspect.inspect(atom, raw: true)
defp module_to_binary(atom) when is_atom(atom), do: inspect(atom, raw: true)
defp module_to_binary(other), do: call_to_binary(other)
defp call_to_binary(atom) when is_atom(atom), do: atom_to_binary(atom, :utf8)
@@ -366,9 +362,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 +439,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 +457,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 +465,13 @@ 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({ :__DIR__, _, atom }, env, _cache) when is_atom(atom), do: :filename.dirname(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 +480,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 +498,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 +535,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
+7 -5
View File
@@ -19,23 +19,25 @@ defmodule Macro.Env do
* `requires` - the list of required modules
* `functions` - a list of functions imported from each module
* `macros` - a list of macros imported from each module
* `context_modules` - a list of modules defined in the current context
"""
@type name_arity :: { atom, non_neg_integer }
@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] }]
@type context_modules :: [module]
fields = [:module, :file, :line, :function,
:aliases, :context, :requires, :functions, :macros]
fields = [:module, :file, :line, :function, :aliases,
:context, :requires, :functions, :macros, :context_modules]
types = quote do: [module: module, file: file, line: line,
function: name_arity, aliases: aliases, requires: requires,
functions: functions, macros: macros]
functions: functions, macros: macros, context_modules: context_modules]
Record.deffunctions(fields, __MODULE__)
Record.deftypes(fields, types, __MODULE__)
@@ -58,7 +60,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.
+38 -29
View File
@@ -31,7 +31,7 @@ defmodule Module do
Evalutes the quotes contents in the given module context.
A list of environment options can also be given as argument.
Check `Code.eval` for more information.
Check `Code.eval_string` for more information.
Raises an error if the module was already compiled.
@@ -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,
@@ -314,7 +320,7 @@ defmodule Module do
assert_not_compiled!(:defines?, module)
table = function_table_for(module)
case ETS.lookup(table, tuple) do
[{ _, ^kind, _, _, _, _, _, _ }] -> true
[{ _, ^kind, _, _, _, _, _ }] -> true
_ -> false
end
end
@@ -333,7 +339,7 @@ defmodule Module do
def definitions_in(module) do
assert_not_compiled!(:definitions_in, module)
table = function_table_for(module)
lc { tuple, _, _, _, _, _, _, _ } inlist ETS.tab2list(table), do: tuple
lc { tuple, _, _, _, _, _, _ } inlist ETS.tab2list(table), do: tuple
end
@doc """
@@ -352,7 +358,7 @@ defmodule Module do
def definitions_in(module, kind) do
assert_not_compiled!(:definitions_in, module)
table = function_table_for(module)
lc { tuple, stored_kind, _, _, _, _, _, _ } inlist ETS.tab2list(table), stored_kind == kind, do: tuple
lc { tuple, stored_kind, _, _, _, _, _ } inlist ETS.tab2list(table), stored_kind == kind, do: tuple
end
@doc """
@@ -362,20 +368,20 @@ defmodule Module do
"""
def make_overridable(module, tuples) do
assert_not_compiled!(:make_overridable, module)
table = function_table_for(module)
lc tuple inlist tuples do
case ETS.lookup(table, tuple) do
[clause] ->
ETS.delete(table, tuple)
case :elixir_def.lookup_definition(module, tuple) do
false ->
{ name, arity } = tuple
raise "Cannot make function #{name}/#{arity} overridable because it was not defined"
clause ->
:elixir_def.delete_definition(module, tuple)
old = get_attribute(module, :__overridable)
new = [ { tuple, { 1, clause, false } } ]
merged = :orddict.merge(fn(_k, { count, _, _ }, _v2) -> { count + 1, clause, false } end, old, new)
put_attribute(module, :__overridable, merged)
_ ->
{ name, arity } = tuple
raise "Cannot make function #{name}/#{arity} overridable because it was not defined"
end
end
end
@@ -406,7 +412,7 @@ defmodule Module do
acc = ETS.lookup_element(table, :__acc_attributes, 2)
new =
if List.member?(acc, key) do
if :lists.member(key, acc) do
case ETS.lookup(table, key) do
[{^key,old}] -> [value|old]
[] -> [value]
@@ -443,7 +449,7 @@ defmodule Module do
[{^key,old}] -> old
[] ->
acc = ETS.lookup_element(table, :__acc_attributes, 2)
if List.member?(acc, key), do: [], else: nil
if :lists.member(key, acc), do: [], else: nil
end
end
@@ -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)>>
+32 -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.
@@ -152,4 +164,23 @@ defmodule Node do
def spawn_link(node, module, fun, args) do
:erlang.spawn_link(node, module, fun, args)
end
@doc """
Sets the magic cookie of `node` to the atom `cookie`. The default node
is `Node.self`, the local node. If node is the local node, the function also
sets the cookie of all other unknown nodes to `cookie`.
This function will raise `FunctionClauseError` if the given node is not alive.
"""
def set_cookie(node // Node.self, cookie) when is_atom(cookie) do
:erlang.set_cookie(node, cookie)
end
@doc """
Returns the magic cookie of the local node, if the node is alive;
otherwise `:nocookie`.
"""
def get_cookie() do
:erlang.get_cookie()
end
end
+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: kind in list
defp is_switch_a?(kind, kind), do: true
defp is_switch_a?(_, _), do: false
end
+115 -97
View File
@@ -1,23 +1,25 @@
defmodule Path do
@moduledoc """
This module provides conveniences for manipulating or
retrieving filesystem paths.
retrieving file system paths.
The functions on this module may receive a char list or
a binary as argument and will return the given type.
The functions in this module may receive a char list or
a binary as argument and will return a value of the same
type.
The majority of the functions in this module do not
interact with the file system, unless some few functions
that needs to query the filesystem to retrieve paths
(like `Path.wildcard` and `Path.expand`).
interact with the file system, except for a few functions
that require it (like `Path.wildcard` and `Path.expand`).
"""
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
resolve `..`, `.` or `~`.
Converts the given path to an absolute one. Differently from
`Path.expand/1`, no attempt is made to resolve `..`, `.` or `~`.
## Unix examples
@@ -40,20 +42,19 @@ defmodule Path do
end
@doc """
Converts the given filename and returns an absolute name
relative to the given location. If the path is already
an absolute path, the relative path is ignored.
Builds a path from `relative_to` to `path`. If `path` is already
an absolute path, `relative_to` is ignored. See also `Path.relative/2`.
Differently from `Path.expand/2`, no attempt is made to
resolve `..`, `.` or `~`.
## 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
@@ -61,31 +62,43 @@ defmodule Path do
end
@doc """
Expands the path by returning its absolute name and expanding
any `.` and `..` characters.
Converts the path to an absolute one and expands
any `.` and `..` characters and a leading `~`.
## 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 """
Expands the path to the relative location and expanding
any `.` and `..` characters. If the path is already an
absolute path, the relative location is ignored.
Expands the path relative to the path given as the second argument
expanding any `.` and `..` characters. If the path is already an
absolute path, `relative_to` is ignored.
Note, that this function treats `path` with leading `~` as
an absolute one.
The second argument is first expanded to an absolute path.
## Examples
Path.expand("foo/bar/../bar", "/baz") == "/baz/foo/bar"
Path.expand("/foo/bar/../bar", "/baz") == "/foo/bar"
# Assuming that the absolute path to baz is /quux/baz
Path.expand("foo/bar/../bar", "baz")
#=> "/quux/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), expand_home(relative_to)), get_cwd(path))
end
@doc """
@@ -96,6 +109,7 @@ defmodule Path do
Path.type("/usr/local/bin") #=> :absolute
Path.type("usr/local/bin") #=> :relative
Path.type("../usr/local/bin") #=> :relative
Path.type("~/file") #=> :relative
## Windows examples
@@ -179,8 +193,9 @@ defmodule Path do
@doc """
Returns the given `path` relative to the given `from` path.
In other words, it tries to strip the `from` prefix from `path`.
This function does not query the filesystem, so it assumes
This function does not query the file system, so it assumes
no symlinks in between the paths.
In case a direct relative path cannot be found, it returns
@@ -188,9 +203,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 +242,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 +263,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
@@ -258,12 +276,14 @@ defmodule Path do
end
@doc """
Return the `directory` component of `path`.
Returns the directory component of `path`.
## Examples
Path.dirname("/foo/bar.ex")
#=> "foo"
#=> "/foo"
Path.dirname("/foo/bar/baz.ex")
#=> "/foo/bar"
"""
def dirname(path) do
@@ -271,14 +291,14 @@ defmodule Path do
end
@doc """
Return the `extension` of the last component of `path`.
Returns the extension of the last component of `path`.
## 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 +310,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 +326,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
@@ -317,17 +337,17 @@ defmodule Path do
end
@doc """
Returns a string with one or more paths components joint by the path separator.
This function should be used to convert a list of strings in a path.
Returns a string with one or more path components joined by the path separator.
This function should be used to convert a list of strings to a path.
## 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 +362,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:
@@ -400,17 +420,17 @@ defmodule Path do
:lists.reverse(name)
@doc """
Returns a list with the path splitted by the path separator.
If an empty string is given, then it returns the root path.
Returns a list with the path split by the path separator.
If an empty string is given, returns the root path.
## 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
@@ -440,8 +460,8 @@ defmodule Path do
## Examples
Imagine you have a directory called `projects` with three Elixir projects
inside of it: `elixir`, `exdoc` and `dynamo`. You can find all `.beam` files
inside their ebin directories all projects as follows:
inside of it: `elixir`, `ex_doc` and `dynamo`. You can find all `.beam` files
inside the ebin directory of each project as follows:
Path.wildcard("projects/*/ebin/**/*.beam")
@@ -456,12 +476,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 +504,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
end
-8
View File
@@ -19,14 +19,6 @@ defmodule Process do
:erlang.is_process_alive(pid)
end
@doc """
Returns the current process.
"""
@spec self :: pid
def self do
:erlang.self()
end
@doc """
Returns all key-values in the dictionary.
"""
+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
+5 -1
View File
@@ -26,6 +26,10 @@ defimpl Enum.Iterator, for: Range do
{ iterator, iterator.(first) }
end
def member?(Range[first: first, last: last], value) do
value in first..last
end
def count(Range[first: first] = range) do
Range.Iterator.count(first, range)
end
@@ -57,6 +61,6 @@ defimpl Binary.Inspect, for: Range do
import Kernel, except: [inspect: 2]
def inspect(Range[first: first, last: last], opts) do
Binary.Inspect.inspect(first, opts) <> ".." <> Binary.Inspect.inspect(last, opts)
Kernel.inspect(first, opts) <> ".." <> Kernel.inspect(last, opts)
end
end
+77 -37
View File
@@ -24,22 +24,47 @@ defmodule Record do
"""
def defrecord(name, values, opts) do
block = Keyword.get(opts, :do, nil)
opts = Keyword.delete(opts, :do)
quote do
values = unquote(values)
defmodule unquote(name) do
@moduledoc false
import Record.DSL
values = unquote(values)
opts = unquote(opts)
@record_fields []
@record_types []
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 +105,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 +149,31 @@ 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
@@ -216,7 +241,8 @@ defmodule Record do
{ match, remaining } = :lists.mapfoldl(iterator, keyword, fields)
case remaining do
[] -> { :{}, [line: caller.line], [atom|match] }
[] ->
quote do: { unquote_splicing([atom|match]) }
_ ->
keys = lc { key, _ } inlist remaining, do: key
raise "record #{inspect atom} does not have the keys: #{inspect keys}"
@@ -390,6 +416,7 @@ defmodule Record do
quoted = lc { k, _ } inlist values do
index = find_index(values, k, 0)
quote do
@doc false
def __index__(unquote(k)), do: unquote(index + 1)
end
end
@@ -432,28 +459,28 @@ defmodule Record do
#
# It will define four methods:
#
# def :atime.(record) do
# def atime(record) do
# elem(record, 1)
# end
#
# def :mtime.(record) do
# def mtime(record) do
# elem(record, 2)
# end
#
# def :atime.(value, record) do
# setelem(record, 1, value)
# def atime(value, record) do
# set_elem(record, 1, value)
# end
#
# def :mtime.(record) do
# setelem(record, 2, value)
# def mtime(record) do
# set_elem(record, 2, value)
# end
#
# def :atime.(callback, record) do
# setelem(record, 1, callback.(elem(record, 1)))
# def atime(callback, record) do
# set_elem(record, 1, callback.(elem(record, 1)))
# end
#
# def :mtime.(callback, record) do
# setelem(record, 2, callback.(elem(record, 2)))
# def mtime(callback, record) do
# 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
@@ -499,7 +526,7 @@ defmodule Record do
end
end
contents = { :{}, [], [(quote do: __MODULE__)|fields] }
contents = quote do: { __MODULE__, unquote_splicing(fields) }
quote do
@doc false
@@ -529,21 +556,34 @@ 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 __record__(:name) :: atom
@spec __record__(:fields) :: [{atom,any}]
@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 +647,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
+46 -26
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
@@ -113,12 +116,15 @@ defmodule Regex do
@doc """
Runs the regular expression against the given string.
It returns a list with all matches or nil if no match ocurred.
It returns a list with all matches, nil if no match ocurred, or []
if it matched, /g was specified, but nothing was captured.
## 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,22 +134,25 @@ 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
case :re.run(string, compiled, [{ :capture, captures, return }]) do
:nomatch -> nil
:match -> []
{ :match, results } -> results
end
end
@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 +160,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 +169,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 +180,8 @@ defmodule Regex do
## Examples
Regex.source %r(foo) #=> "foo"
iex> Regex.source(%r(foo))
"foo"
"""
def source(regex(source: source)) do
@@ -179,11 +189,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 +206,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 +222,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 +272,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 +322,7 @@ defmodule Regex do
lc result inlist results do
case result do
[t] -> t
[h|t] -> t
[_|t] -> t
end
end
end
+314 -97
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
@@ -112,36 +139,51 @@ defmodule String do
def printable?(_), do: false
@doc """
Divides a string into sub string based on a pattern,
Splits a string on sub strings at each Unicode whitespace
occurrence with leading and trailing whitespace ignored.
## Examples
iex> String.split("foo bar")
["foo", "bar"]
iex> String.split("foo" <> <<194,133>> <> "bar")
["foo", "bar"]
iex> String.split(" foo bar ")
["foo", "bar"]
"""
@spec split(t) :: [t]
defdelegate split(binary), to: String.Unicode
@doc """
Divides a string into sub strings based on a pattern,
returning a list of these sub string. The pattern can
be a string, a list of strings or a regular expression.
The string is split into as many parts as possible by
default, unless the `global` option is set to false.
If a pattern is not specified, the string is split on
whitespace occurrences.
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("a,b,c", ",")
["a", "b", "c"]
iex> String.split("a,b,c", ",", global: false)
["a", "b,c"]
String.split("foo bar") #=> ["foo", "bar"]
String.split("1,2 3,4", [" ", ","]) #=> ["1", "2", "3", "4"]
iex> String.split("1,2 3,4", [" ", ","])
["1", "2", "3", "4"]
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("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 // [])
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,12 +451,68 @@ 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
defdelegate next_codepoint(string), to: String.Unicode
@doc %B"""
Checks whether `str` contains only valid characters.
## Examples
iex> String.valid?("a")
true
iex> String.valid?("ø")
true
iex> String.valid?(<<0xffff :: 16>>)
false
iex> String.valid?("asd" <> <<0xffff :: 16>>)
false
"""
@spec valid?(t) :: boolean
noncharacters = Enum.to_list(?\x{FDD0}..?\x{FDEF}) ++
[ ?\x{0FFFE}, ?\x{0FFFF}, ?\x{1FFFE}, ?\x{1FFFF}, ?\x{2FFFE}, ?\x{2FFFF},
?\x{3FFFE}, ?\x{3FFFF}, ?\x{4FFFE}, ?\x{4FFFF}, ?\x{5FFFE}, ?\x{5FFFF},
?\x{6FFFE}, ?\x{6FFFF}, ?\x{7FFFE}, ?\x{7FFFF}, ?\x{8FFFE}, ?\x{8FFFF},
?\x{9FFFE}, ?\x{9FFFF}, ?\x{10FFFE}, ?\x{10FFFF} ]
lc noncharacter inlist noncharacters do
def valid?(<< unquote(noncharacter) :: utf8, _ :: binary >>), do: false
end
def valid?(<<_ :: utf8, t :: binary>>), do: valid?(t)
def valid?(<<>>), do: true
def valid?(_), do: false
@doc %B"""
Checks whether `str` is a valid character.
All characters are codepoints, but some codepoints
are not valid characters. They may be reserved, private,
or other.
More info at: http://en.wikipedia.org/wiki/Mapping_of_Unicode_characters#Noncharacters
## Examples
iex> String.valid_character?("a")
true
iex> String.valid_character?("ø")
true
iex> String.valid_character?("\x{ffff}")
false
"""
@spec valid_character?(t) :: boolean
def valid_character?(<<_ :: utf8>> = codepoint), do: valid?(codepoint)
def valid_character?(_), do: false
@doc %B"""
Checks whether `str` is a valid codepoint.
@@ -398,10 +521,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?(<<0xffff :: 16>>)
false
iex> String.valid_codepoint?("asdf")
false
"""
@spec valid_codepoint?(codepoint) :: boolean
@@ -413,7 +540,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,19 +556,23 @@ defmodule String do
## Examples
String.next_grapheme("josé") #=> { "j", "osé" }
iex> String.next_grapheme("josé")
{ "j", "osé" }
"""
@spec next_grapheme(t) :: grapheme | :no_grapheme
@spec next_grapheme(t) :: { grapheme, t } | :no_grapheme
defdelegate next_grapheme(string), to: String.Unicode
@doc """
Returns the first grapheme from an utf8 string.
Returns the first grapheme from an utf8 string,
nil if the string is empty.
## Examples
String.first("elixir") #=> "e"
String.first("եոգլի") #=> "ե"
iex> String.first("elixir")
"e"
iex> String.first("եոգլի")
"ե"
"""
@spec first(t) :: grapheme | nil
@@ -452,12 +584,15 @@ defmodule String do
end
@doc """
Returns the last grapheme from an utf8 string.
Returns the last grapheme from an utf8 string,
nil if the string is empty.
## Examples
String.last("elixir") #=> "r"
String.last("եոգլի") #=> "ի"
iex> String.last("elixir")
"r"
iex> String.last("եոգլի")
"ի"
"""
@spec last(t) :: grapheme | nil
@@ -476,8 +611,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 +634,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 +677,22 @@ 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
iex> String.slice("a", 0, 1500)
"a"
iex> String.slice("a", 1, 1500)
""
iex> String.slice("a", 2, 1500)
nil
"""
@spec slice(t, integer, integer) :: grapheme | nil
@@ -572,10 +725,74 @@ defmodule String do
acc <> char
end
defp do_slice(:no_grapheme, start_pos, _, current_pos, acc) when start_pos == current_pos do
acc
end
defp do_slice(:no_grapheme, _, _, _, acc) do
case acc do
"" -> nil
_ -> acc
end
end
@doc """
Converts a string to an integer. If successful, returns a
tuple of form {integer, remainder of string}. If unsuccessful,
returns :error.
## Examples
iex> String.to_integer("34")
{34,""}
iex> String.to_integer("34.5")
{34,".5"}
iex> String.to_integer("three")
:error
"""
@spec to_integer(t) :: {integer, t} | :error
def to_integer(string) do
{result, remainder} = :string.to_integer(binary_to_list(string))
case result do
:error -> :error
_ -> {result, list_to_binary(remainder)}
end
end
@doc """
Converts a string to a float. If successful, returns a
tuple of form {float, remainder of string}. If unsuccessful,
returns :error. If given an integer value, will return
same as to_integer/1.
## Examples
iex> String.to_float("34")
{34.0,""}
iex> String.to_float("34.25")
{34.25,""}
iex> String.to_float("56.5xyz")
{56.5,"xyz"}
iex> String.to_float("pi")
:error
"""
@spec to_float(t) :: {integer, t} | :error
def to_float(string) do
charlist = binary_to_list(string)
{result, remainder} = :string.to_float(charlist)
case result do
:error ->
{int_result, int_remainder} = :string.to_integer(charlist)
case int_result do
:error -> :error
_ -> {float(int_result), list_to_binary(int_remainder)}
end
_ -> {result, list_to_binary(remainder)}
end
end
end
+1 -1
View File
@@ -302,7 +302,7 @@ defmodule System do
end
# Support R15B
if List.member?(:erlang.module_info(:exports), { :halt, 2 }) do
if { :halt, 2 } in :erlang.module_info(:exports) do
defp do_halt(status, options), do: :erlang.halt(status, options)
else
IO.puts "Using limited halt support. Upgrade to R15B01 or later is recommended."
+10 -2
View File
@@ -131,8 +131,16 @@ defmodule URI do
destructure [_, _, scheme, _, authority, path, _, query, _, fragment], parts
{ userinfo, host, port } = split_authority(authority)
if authority do
authority = ""
if userinfo, do: authority = authority <> userinfo <> "@"
if host, do: authority = authority <> host
if port, do: authority = authority <> ":" <> integer_to_binary(port)
end
info = URI.Info[
scheme: scheme, path: path, query: query,
scheme: scheme && String.downcase(scheme), path: path, query: query,
fragment: fragment, authority: authority,
userinfo: userinfo, host: host, port: port
]
@@ -169,7 +177,7 @@ defmodule URI do
components = Regex.run %r/(^(.*)@)?([^:]*)(:(\d*))?/, s
destructure [_, _, userinfo, host, _, port], nillify(components)
port = if port, do: binary_to_integer(port)
{ userinfo, host, port }
{ userinfo, host && String.downcase(host), port }
end
# Regex.run returns empty strings sometimes. We want
+67 -43
View File
@@ -16,7 +16,7 @@ defmodule String.Unicode do
end
end
data_path = Path.expand("../UnicodeData.txt", __FILE__)
data_path = Path.join(__DIR__, "UnicodeData.txt")
{ codes, whitespace } = Enum.reduce File.iterator!(data_path), { [], [] }, fn(line, { cacc, wacc }) ->
[ codepoint, _name, _category,
@@ -37,7 +37,7 @@ defmodule String.Unicode do
end
end
special_path = Path.expand("../SpecialCasing.txt", __FILE__)
special_path = Path.join(__DIR__, "SpecialCasing.txt")
codes = Enum.reduce File.iterator!(special_path), codes, fn(line, acc) ->
[ codepoint, lower, title, upper, _comment ] = :binary.split(line, "; ", [:global])
@@ -45,7 +45,7 @@ defmodule String.Unicode do
:lists.keystore(key, 1, acc, { key, to_binary.(upper), to_binary.(lower), to_binary.(title) })
end
seqs_path = Path.expand("../NamedSequences.txt", __FILE__)
seqs_path = Path.join(__DIR__, "NamedSequences.txt")
seqs = Enum.map File.iterator!(seqs_path), fn(line) ->
[ _name, codepoints ] = :binary.split(line, ";", [:global])
@@ -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).*"}.
+5 -4
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
@@ -19,8 +20,8 @@ start(_Type, _Args) ->
%% Set the shell to unicode so printing inside scripts work
%% Those can take a while, so let's do it in a new process
spawn(fun() ->
io:setopts(standard_io, [{encoding,unicode}]),
io:setopts(standard_error, [{encoding,unicode}])
io:setopts(standard_io, [binary,{encoding,unicode}]),
io:setopts(standard_error, [binary,{encoding,unicode}])
end),
elixir_sup:start_link([]).
@@ -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),
+94 -34
View File
@@ -1,45 +1,89 @@
-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, ensure_loaded/4, expand/3, store/4]).
-include("elixir.hrl").
-compile({parse_transform, elixir_transform}).
%% Expand an alias returning an atom if possible.
%% Otherwise returns the list of aliases args.
%% Store an alias in the given scope
store(_Meta, New, New, S) -> S;
store(Meta, New, Old, S) ->
SA = S#elixir_scope{
aliases=orddict:store(New, Old, S#elixir_scope.aliases)
},
expand({ '__aliases__', _Line, [H] }, Aliases) when H /= 'Elixir' ->
expand_alias(H, Aliases);
case lists:keymember(context, 1, Meta) of
true ->
SA#elixir_scope{
macro_aliases=orddict:store(New, Old, S#elixir_scope.macro_aliases)
};
false ->
SA
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 an alias. It returns an atom (meaning that there
%% was an expansion) or a list of atoms.
expand({ '__aliases__', _Line, List }, _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__', _Meta, [H] }, Aliases) when H /= 'Elixir' ->
case expand_one(H, Aliases) of
false -> [H];
Atom -> Atom
end;
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.
ensure_loaded(_Line, 'Elixir.Kernel', _S) ->
ensure_loaded(Line, Ref, S) ->
ensure_loaded(Line, S#elixir_scope.file, Ref, S#elixir_scope.context_modules).
ensure_loaded(_Line, _File, 'Elixir.Kernel', _FileModules) ->
ok;
ensure_loaded(Line, Ref, S) ->
ensure_loaded(Line, File, Ref, FileModules) ->
try
Ref:module_info(compile)
catch
error:undef ->
Kind = case lists:member(Ref, S#elixir_scope.scheduled) of
Kind = case lists:member(Ref, FileModules) of
true -> scheduled_module;
false -> unloaded_module
end,
elixir_errors:form_error(Line, S#elixir_scope.file, ?MODULE, { Kind, Ref })
elixir_errors:form_error(Line, 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 +93,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 +153,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 +164,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)]).
+86 -89
View File
@@ -4,7 +4,6 @@
-export([
assigns/3, assigns_block/5, assigns_block/6, extract_last_guards/1,
get_pairs/4, get_pairs/5, match/3, extract_args/1, extract_guards/1]).
-import(elixir_scope, [umergec/2]).
-include("elixir.hrl").
%% Get pairs from a clause.
@@ -19,15 +18,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).
@@ -105,66 +104,61 @@ do_match(Meta, DecoupledClauses, S) ->
% and storing variables defined inside each clause.
Transformer = fun(X, {Acc, CV}) ->
{ TX, TAcc } = each_clause(Meta, X, Acc),
{ TX, { umergec(S, TAcc), [TAcc#elixir_scope.clause_vars|CV] } }
{ TX, { merge_clauses_scope(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 +175,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 +204,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 +219,40 @@ 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.
listify(Expr) -> Expr.
%% We don't use umergec because imports, aliases and
%% what not are not passed from one clause to the other.
merge_clauses_scope(S1, S2) ->
S1#elixir_scope{
counter=S2#elixir_scope.counter,
extra_guards=S2#elixir_scope.extra_guards,
super=S1#elixir_scope.super orelse S2#elixir_scope.super,
caller=S1#elixir_scope.caller orelse S2#elixir_scope.caller,
name_args=S1#elixir_scope.name_args orelse S2#elixir_scope.name_args
}.
-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}) ->
+170 -111
View File
@@ -1,12 +1,17 @@
% Holds the logic responsible for functions definition (def(p) and defmacro(p)).
-module(elixir_def).
-export([table/1,
clauses_table/1,
build_table/1,
delete_table/1,
reset_last/1,
wrap_definition/7,
store_definition/8,
store_each/7,
lookup_definition/2,
delete_definition/2,
wrap_definition/6,
wrap_definition/8,
store_definition/7,
store_definition/9,
store_each/8,
unwrap_stored_definitions/2,
format_error/1]).
-include("elixir.hrl").
@@ -15,20 +20,38 @@
%% Table management functions. Called internally.
table(Module) -> ?atom_concat([f, Module]).
clauses_table(Module) -> ?atom_concat([c, Module]).
build_table(Module) ->
FunctionTable = table(Module),
ets:new(FunctionTable, [set, named_table, public]),
FunctionsTable = table(Module),
ClausesTable = clauses_table(Module),
ets:new(FunctionsTable, [set, named_table, public]),
ets:new(ClausesTable, [bag, named_table, public]),
reset_last(Module),
FunctionTable.
{ FunctionsTable, ClausesTable }.
delete_table(Module) ->
ets:delete(table(Module)).
ets:delete(table(Module)),
ets:delete(clauses_table(Module)).
%% Reset the last item. Useful when evaling code.
reset_last(Module) ->
ets:insert(table(Module), { last, [] }).
%% Looks up a definition from the database.
lookup_definition(Module, Tuple) ->
case ets:lookup(table(Module), Tuple) of
[Result] ->
CTable = clauses_table(Module),
{ Result, [Clause || { _, Clause } <- ets:lookup(CTable, Tuple)] };
_ ->
false
end.
delete_definition(Module, Tuple) ->
ets:delete(table(Module), Tuple),
ets:delete(clauses_table(Module), Tuple).
%% Wraps the function into a call to store_definition once the function
%% definition is read. The function is compiled into a meta tree to ensure
%% we will receive the full function.
@@ -44,63 +67,73 @@ 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, CheckClauses, S) ->
do_wrap_definition(Kind, Meta, CheckClauses, [
Call, Expr, elixir_scope:serialize(S)
]).
wrap_definition(Kind, Meta, Name, Args, Guards, Expr, CheckClauses, S) ->
do_wrap_definition(Kind, Meta, CheckClauses, [
Name, Args, Guards, Expr, elixir_scope:serialize(S)
]).
do_wrap_definition(Kind, Meta, CheckClauses, Extra) ->
Line = ?line(Meta),
Invoke =
[{atom, Line, Kind},
{integer, Line, Line},
{atom, Line, CheckClauses},
{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, nil, _Name, _Args, _Guards, _Body, RawS) ->
store_definition(Kind, Line, CheckClauses, Module, Call, 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]);
{ NameAndArgs, Guards } = elixir_clauses:extract_guards(Call),
store_definition(Kind, Line, Module, Name, Args, Guards, Body, RawS) ->
{ 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, CheckClauses, Module, Name, Args, Guards, Body, S).
store_definition(Kind, Line, _CheckClauses, nil, _Name, _Args, _Guards, _Body, #elixir_scope{} = S) ->
elixir_errors:syntax_error(Line, S#elixir_scope.file, "cannot define function outside module, invalid scope for ~ts", [Kind]);
store_definition(Kind, Line, CheckClauses, Module, Name, Args, Guards, Body, #elixir_scope{} = DS) ->
Arity = length(Args),
DS = elixir_scope:deserialize(RawS),
S = DS#elixir_scope{function={Name,Arity}, module=Module},
Expr = def_body(Line, Body),
CO = elixir_compiler:get_opts(),
Location = retrieve_file(Line, Module, S, CO),
run_on_definition_callbacks(Kind, Line, Module, Name, Args, Guards, Body, S, CO),
{ Function, Defaults, TS } = translate_definition(Kind, Line, Name, Args, Guards, Expr, S),
{ Function, Defaults, TS } = translate_definition(Kind, Line, Name, Args, Guards, Body, S),
File = TS#elixir_scope.file,
Table = table(Module),
CTable = clauses_table(Module),
%% Store function
if
(Body == nil) -> [];
true ->
compile_super(Module, TS),
CheckClauses = S#elixir_scope.check_clauses,
store_each(CheckClauses, Kind, File, Location,
Table, length(Defaults), Function)
end,
compile_super(Module, TS),
store_each(CheckClauses, Kind, File, Location,
Table, CTable, length(Defaults), Function),
[store_each(false, Kind, File, Location, Table, 0,
[store_each(false, Kind, File, Location, Table, CTable, 0,
default_function_for(Kind, Name, Default)) || Default <- Defaults],
{ Name, Arity }.
{ Name, Arity };
def_body(_Line, nil) -> nil;
def_body(_Line, [{ do, Expr }]) -> Expr;
def_body(Line, Else) -> { 'try', [{line,Line}], [Else] }.
store_definition(Kind, Line, CheckClauses, Module, Name, Args, Guards, Body, RawS) ->
store_definition(Kind, Line, CheckClauses, Module, Name, Args, Guards, Body, elixir_scope:deserialize(RawS)).
%% @on_definition
@@ -110,8 +143,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
@@ -138,22 +171,31 @@ compile_super(_Module, _S) -> ok.
%% Translate the given call and expression given
%% and then store it in memory.
translate_definition(Kind, Line, Name, RawArgs, RawGuards, RawExpr, S) when is_integer(Line) ->
translate_definition(Kind, Line, Name, RawArgs, RawGuards, RawBody, S) when is_integer(Line) ->
Args = elixir_quote:linify(Line, RawArgs),
Guards = elixir_quote:linify(Line, RawGuards),
Expr = elixir_quote:linify(Line, RawExpr),
Body = elixir_quote:linify(Line, RawBody),
Arity = length(Args),
IsMacro = is_macro(Kind),
%% Macros receive a special argument on invocation. Notice it does
%% not affect the arity of the stored function, but the clause
%% already contains it.
ExtendedArgs = case IsMacro of
ExtendedArgs = case is_macro(Kind) of
true -> [{ '_@CALLER', [{line,Line}], nil }|Args];
false -> Args
end,
{ Unpacked, Defaults } = elixir_def_defaults:unpack(Kind, Name, ExtendedArgs, S),
{ Clauses, TS } = translate_clause(Line, Kind, Unpacked, Guards, Body, S),
Function = { function, Line, Name, Arity, Clauses },
{ Function, Defaults, TS }.
translate_clause(_Line, _Kind, _Unpacked, _Guards, nil, S) ->
{ [], S };
translate_clause(Line, Kind, Unpacked, Guards, Body, S) ->
Expr = expr_from_body(Line, Body),
{ TClause, TS } = elixir_clauses:assigns_block(Line,
fun elixir_translator:translate/2, Unpacked, [Expr], Guards, S),
@@ -167,7 +209,7 @@ translate_definition(Kind, Line, Name, RawArgs, RawGuards, RawExpr, S) when is_i
end,
%% Set __CALLER__ if used
FClause = case IsMacro andalso TS#elixir_scope.caller of
FClause = case is_macro(Kind) andalso TS#elixir_scope.caller of
true ->
FBody = { 'match', Line,
{ 'var', Line, '__CALLER__' },
@@ -177,8 +219,10 @@ translate_definition(Kind, Line, Name, RawArgs, RawGuards, RawExpr, S) when is_i
false -> NClause
end,
Function = { function, Line, Name, Arity, [FClause] },
{ Function, Defaults, TS }.
{ [FClause], TS }.
expr_from_body(_Line, [{ do, Expr }]) -> Expr;
expr_from_body(Line, Else) -> { 'try', [{line,Line}], [Else] }.
is_macro(defmacro) -> true;
is_macro(defmacrop) -> true;
@@ -189,58 +233,59 @@ is_macro(_) -> false.
% and the body of all functions.
unwrap_stored_definitions(File, Module) ->
Table = table(Module),
CTable = clauses_table(Module),
ets:delete(Table, last),
unwrap_stored_definition(ets:tab2list(Table), File, [], [], [], [], [], {[],[]}).
unwrap_stored_definition(ets:tab2list(Table), CTable, File, [], [], [], [], [], []).
unwrap_stored_definition([Fun|T], File, Exports, Private, Def, Defmacro, Defmacrop, Functions) when element(2, Fun) == def ->
Tuple = element(1, Fun),
unwrap_stored_definition(
T, File, [Tuple|Exports], Private, [Tuple|Def], Defmacro, Defmacrop,
function_for_stored_definition(Fun, File, Functions)
);
unwrap_stored_definition([Fun|T], CTable, File, Exports, Private, Def, Defmacro, Functions, Tail) ->
Tuple = element(1, Fun),
Clauses = [Clause || { _, Clause } <- ets:lookup(CTable, Tuple)],
unwrap_stored_definition([Fun|T], File, Exports, Private, Def, Defmacro, Defmacrop, Functions) when element(2, Fun) == defmacro ->
{ Name, Arity } = Tuple = element(1, Fun),
{ NewFun, NewExports, NewPrivate, NewDef, NewDefmacro } =
case Clauses of
[] -> { false, Exports, Private, Def, Defmacro };
_ -> unwrap_stored_definition(element(2, Fun), Tuple, Fun, Exports, Private, Def, Defmacro)
end,
{ NewFunctions, NewTail } = case NewFun of
false -> { Functions, Tail };
_ -> function_for_stored_definition(NewFun, Clauses, File, Functions, Tail)
end,
unwrap_stored_definition(T, CTable, File, NewExports, NewPrivate,
NewDef, NewDefmacro, NewFunctions, NewTail);
unwrap_stored_definition([], _CTable, _File, Exports, Private, Def, Defmacro, Functions, Tail) ->
{ Exports, Private, ordsets:from_list(Def),
ordsets:from_list(Defmacro), lists:reverse(Tail ++ Functions) }.
unwrap_stored_definition(def, Tuple, Fun, Exports, Private, Def, Defmacro) ->
{ Fun, [Tuple|Exports], Private, [Tuple|Def], Defmacro };
unwrap_stored_definition(defmacro, { Name, Arity } = Tuple, Fun, Exports, Private, Def, Defmacro) ->
Macro = { ?elixir_macro(Name), Arity + 1 },
{ setelement(1, Fun, Macro), [Macro|Exports], Private, Def, [Tuple|Defmacro] };
unwrap_stored_definition(
T, File, [Macro|Exports], Private, Def, [Tuple|Defmacro], Defmacrop,
function_for_stored_definition(setelement(1, Fun, Macro), File, Functions)
);
unwrap_stored_definition(defp, Tuple, Fun, Exports, Private, Def, Defmacro) ->
%% { Name, Arity }, Kind, Line, Check, Defaults
Info = { Tuple, defp, element(3, Fun), element(5, Fun), element(7, Fun) },
{ Fun, Exports, [Info|Private], Def, Defmacro };
unwrap_stored_definition([Fun|T], File, Exports, Private, Def, Defmacro, Defmacrop, Functions) when element(2, Fun) == defp ->
unwrap_stored_definition(
T, File, Exports, [element(1, Fun)|Private], Def, Defmacro, Defmacrop,
function_for_stored_definition(Fun, File, Functions)
);
unwrap_stored_definition([Fun|T], File, Exports, Private, Def, Defmacro, Defmacrop, Functions) when element(2, Fun) == defmacrop ->
Tuple = element(1, Fun),
unwrap_stored_definition(
T, File, Exports, [Tuple|Private], Def, Defmacro,
[{ Tuple, element(3, Fun), element(5, Fun) }|Defmacrop], Functions
);
unwrap_stored_definition([], _File, Exports, Private, Def, Defmacro, Defmacrop, {Functions,Tail}) ->
{ Exports, Private, ordsets:from_list(Def), ordsets:from_list(Defmacro),
ordsets:from_list(Defmacrop), lists:reverse(Tail ++ Functions) }.
unwrap_stored_definition(defmacrop, Tuple, Fun, Exports, Private, Def, Defmacro) ->
%% { Name, Arity }, Kind, Line, Check, Defaults
Info = { Tuple, defmacrop, element(3, Fun), element(5, Fun), element(7, Fun) },
{ false, Exports, [Info|Private], Def, Defmacro }.
%% Helpers
function_for_stored_definition({{Name, Arity}, _, Line, _, _, { File, _ }, _, Clauses}, File, {Functions,Tail}) ->
{
[{ function, Line, Name, Arity, lists:reverse(Clauses) }|Functions],
Tail
};
function_for_stored_definition({{Name,Arity}, _, Line, _, _, {File, _}, _}, Clauses, File, Functions, Tail) ->
{ [{ function, Line, Name, Arity, Clauses }|Functions], Tail };
function_for_stored_definition({{Name, Arity}, _, Line, _, _, Location, _, Clauses}, _File, {Functions,Tail}) ->
{
Functions,
[
{ function, Line, Name, Arity, lists:reverse(Clauses) },
{ attribute, Line, file, Location } | Tail
]
}.
function_for_stored_definition({{Name,Arity}, _, Line, _, _, Location, _}, Clauses, _File, Functions, Tail) ->
{ Functions, [
{ function, Line, Name, Arity, Clauses },
{ attribute, Line, file, Location } | Tail
] }.
default_function_for(Kind, Name, { clause, Line, Args, _Guards, _Exprs } = Clause)
when Kind == defmacro; Kind == defmacrop ->
@@ -253,22 +298,24 @@ default_function_for(_, Name, { clause, Line, Args, _Guards, _Exprs } = Clause)
%% This function also checks and emit warnings in case
%% the kind, of the visibility of the function changes.
store_each(Check, Kind, File, Location, Table, Defaults, {function, Line, Name, Arity, Clauses}) ->
case ets:lookup(Table, {Name, Arity}) of
[{{Name, Arity}, StoredKind, _, _, StoredCheck, StoredLocation, StoredDefaults, StoredClauses}] ->
store_each(Check, Kind, File, Location, Table, CTable, Defaults, {function, Line, Name, Arity, Clauses}) ->
Tuple = { Name, Arity },
case ets:lookup(Table, Tuple) of
[{ Tuple, StoredKind, StoredLine, _, StoredCheck, StoredLocation, StoredDefaults }] ->
FinalLine = StoredLine,
FinalLocation = StoredLocation,
FinalDefaults = Defaults + StoredDefaults,
FinalClauses = Clauses ++ StoredClauses,
FinalDefaults = max(Defaults, StoredDefaults),
check_valid_kind(Line, File, Name, Arity, Kind, StoredKind),
check_valid_defaults(Line, File, Name, Arity, FinalDefaults),
check_valid_defaults(Line, File, Name, Arity, Defaults, StoredDefaults),
(Check and StoredCheck) andalso check_valid_clause(Line, File, Name, Arity, Table);
[] ->
FinalLine = Line,
FinalLocation = Location,
FinalDefaults = Defaults,
FinalClauses = Clauses,
Check andalso ets:insert(Table, { last, { Name, Arity } })
FinalDefaults = Defaults
end,
ets:insert(Table, {{Name, Arity}, Kind, Line, File, Check, FinalLocation, FinalDefaults, FinalClauses}).
Check andalso ets:insert(Table, { last, { Name, Arity } }),
ets:insert(CTable, [{ Tuple, Clause } || Clause <- Clauses ]),
ets:insert(Table, { Tuple, Kind, FinalLine, File, Check, FinalLocation, FinalDefaults }).
%% Validations
@@ -281,22 +328,34 @@ check_valid_clause(Line, File, Name, Arity, Table) ->
case ets:lookup_element(Table, last, 2) of
{Name,Arity} -> [];
[] -> [];
{ElseName, ElseArity} ->
_ ->
elixir_errors:handle_file_warning(File, { Line, ?MODULE,
{ changed_clause, { { Name, Arity }, { ElseName, ElseArity } } } })
{ override_function, { Name, Arity } } })
end.
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 } } }).
check_valid_defaults(_Line, _File, _Name, _Arity, 0, _) -> [];
check_valid_defaults(Line, File, Name, Arity, _, 0) ->
elixir_errors:handle_file_warning(File, { Line, ?MODULE, { out_of_order_defaults, { Name, Arity } } });
check_valid_defaults(Line, File, Name, Arity, _, _) ->
elixir_errors:handle_file_warning(File, { Line, ?MODULE, { clauses_with_defaults, { 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]);
format_error({clauses_with_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]);
format_error({out_of_order_defaults,{Name,Arity}}) ->
io_lib:format("clause with defaults should be the first clause in function ~ts/~B", [Name, Arity]);
format_error({override_function,{Name,Arity}}) ->
io_lib:format("trying to override previously defined function ~ts/~B", [Name, Arity]);
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]).
+53 -13
View File
@@ -4,7 +4,7 @@
macro_for/3,
function_for/3,
format_error/1,
check_unused_local_macros/3
check_unused_local/3
]).
-include("elixir.hrl").
@@ -12,8 +12,9 @@
macro_for(_Tuple, _All, nil) -> false;
macro_for(Tuple, All, Module) ->
try ets:lookup(elixir_def:table(Module), Tuple) of
[{Tuple, Kind, Line, _, _, _, _, Clauses}] when Kind == defmacro; All, Kind == defmacrop ->
try elixir_def:lookup_definition(Module, Tuple) of
{ { Tuple, Kind, Line, _, _, _, _ }, Clauses } when Kind == defmacro; All, Kind == defmacrop ->
elixir_import:record(Tuple, Module, Module),
get_function(Line, Module, Clauses);
_ ->
false
@@ -24,8 +25,9 @@ macro_for(Tuple, All, Module) ->
%% Used on runtime by rewritten clauses, raises an error if function is not found
function_for(Module, Name, Arity) ->
Tuple = { Name, Arity },
case ets:lookup(elixir_def:table(Module), Tuple) of
[{Tuple, _, Line, _, _, _, _, Clauses}] ->
case elixir_def:lookup_definition(Module, Tuple) of
{ { Tuple, _, Line, _, _, _, _ }, Clauses } ->
elixir_import:record(Tuple, Module, Module),
get_function(Line, Module, Clauses);
_ ->
[_|T] = erlang:get_stacktrace(),
@@ -36,11 +38,10 @@ function_for(Module, Name, Arity) ->
get_function(Line, Module, Clauses) ->
RewrittenClauses = [rewrite_clause(Clause, Module) || Clause <- Clauses],
Fun = { 'fun', Line, {clauses, lists:reverse(RewrittenClauses)} },
Fun = { 'fun', Line, {clauses, RewrittenClauses } },
{ value, Result, _Binding } = erl_eval:exprs([Fun], []),
Result.
%% TODO: Consider caching functions in a table for performance.
rewrite_clause({ call, Line, { atom, Line, RawName }, Args }, Module) ->
Remote = { remote, Line,
{ atom, Line, ?MODULE },
@@ -69,10 +70,49 @@ rewrite_clause(Else, _) -> Else.
%% Error handling
check_unused_local_macros(File, Recorded, Defmacrop) ->
[elixir_errors:handle_file_warning(File,
{ Line, ?MODULE, { unused_macro, Fun } }) || { Fun, Line, Check } <- Defmacrop,
Check, not lists:member(Fun, Recorded)].
check_unused_local(File, Recorded, Private) ->
[check_unused_local(Fun, Kind, Line, File, Defaults, Recorded) ||
{ Fun, Kind, Line, true, Defaults } <- Private].
format_error({unused_macro,{Name, Arity}}) ->
io_lib:format("macro ~s/~B is unused", [Name, Arity]).
check_unused_local(Fun, Kind, Line, File, 0, Recorded) ->
not(lists:member(Fun, Recorded)) andalso
elixir_errors:handle_file_warning(File, { Line, ?MODULE, { unused_def, Kind, Fun } });
check_unused_local({ _, Arity } = Fun, Kind, Line, File, Defaults, Recorded) when Defaults > 0 ->
Min = Arity - Defaults,
Max = Arity,
Invoked = [A || { _, A } <- Recorded, A >= Min, A =< Max],
case Invoked of
[] ->
elixir_errors:handle_file_warning(File, { Line, ?MODULE, { unused_def, Kind, Fun } });
_ ->
UnusedArgs = lists:min(Invoked) - Min,
if
UnusedArgs == 0 ->
ok;
UnusedArgs == Defaults ->
elixir_errors:handle_file_warning(File, { Line, ?MODULE, { unused_args, Fun } });
true ->
elixir_errors:handle_file_warning(File, { Line, ?MODULE, { unused_args, Fun, UnusedArgs } })
end
end;
check_unused_local(_Fun, _Kind, _Line, _File, _Defaults, _Recorded) ->
ok.
format_error({unused_def,defp,{Name, Arity}}) ->
io_lib:format("function ~ts/~B is unused", [Name, Arity]);
format_error({unused_args,{Name, Arity}}) ->
io_lib:format("default arguments in ~ts/~B are never used", [Name, Arity]);
format_error({unused_args,{Name, Arity},1}) ->
io_lib:format("the first default argument in ~ts/~B is never used", [Name, Arity]);
format_error({unused_args,{Name, Arity},Count}) ->
io_lib:format("the first ~B default arguments in ~ts/~B are never used", [Count, Name, Arity]);
format_error({unused_def,defmacrop,{Name, Arity}}) ->
io_lib:format("macro ~ts/~B is unused", [Name, Arity]).
+3 -3
View File
@@ -65,7 +65,7 @@ store(Module, Function, GenerateName) ->
{ _Count, _Clause, true } -> ok;
{ Count, Clause, false } ->
overridable(Module, orddict:store(Function, { Count, Clause, true }, Overridable)),
{ { Name, Arity }, Kind, Line, File, _Check, Location, Defaults, Clauses } = Clause,
{ { { Name, Arity }, Kind, Line, File, _Check, Location, Defaults }, Clauses } = Clause,
{ FinalKind, FinalName } = case GenerateName of
true -> { defp, name(Module, Function, Overridable) };
@@ -74,7 +74,7 @@ store(Module, Function, GenerateName) ->
Def = { function, Line, FinalName, Arity, Clauses },
elixir_def:store_each(false, FinalKind, File, Location,
elixir_def:table(Module), Defaults, Def)
elixir_def:table(Module), elixir_def:clauses_table(Module), Defaults, Def)
end.
%% Store pending declarations that were not manually made concrete.
@@ -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 }) ->
+142 -86
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,33 @@ 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 ->
elixir_import:record(import, Tuple, Receiver, S#elixir_scope.module),
remote_function(Meta, Receiver, Name, Arity, S)
case find_dispatch(Meta, Tuple, S) of
{ function, Receiver } ->
elixir_import:record(Tuple, Receiver, S#elixir_scope.module),
remote_function(Meta, Receiver, Name, Arity, S);
{ macro, _Receiver } ->
false;
{ import, Receiver } ->
require_function(Meta, Receiver, Name, Arity, S);
nomatch ->
Module = S#elixir_scope.module,
elixir_import:record({ Name, Arity }, Module, Module),
{ { 'fun', ?line(Meta), { function, Name, Arity } }, S }
end.
require_function(Meta, Receiver, Name, Arity, S) ->
@@ -61,25 +62,26 @@ 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
{ error, noexpansion } ->
Callback();
{ error, internal } ->
elixir_import:record(import, Tuple, ?BUILTIN, Module),
elixir_macros:translate({ Name, Meta, Args }, S);
{ ok, _Receiver, Tree } ->
translate_expansion(Meta, Tree, S)
end;
Receiver ->
elixir_import:record(import, Tuple, Receiver, Module),
case find_dispatch(Meta, Tuple, S) of
{ function, Receiver } ->
elixir_import:record(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)
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 } ->
elixir_import:record(Tuple, ?BUILTIN, Module),
elixir_macros:translate({ Name, Meta, Args }, S);
{ ok, _Receiver, Tree } ->
translate_expansion(Meta, Tree, S)
end
end.
dispatch_require(Meta, Receiver, Name, Args, S, Callback) ->
@@ -89,112 +91,163 @@ 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(Tuple, ?BUILTIN, Module),
{ ok, ?BUILTIN, expand_macro_named(Meta, ?BUILTIN, Name, Arity, Args, Module, S) }
end;
{ macro, Receiver } ->
elixir_import:record(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_translator:translate_each(
elixir_quote:linify(?line(Meta), Tree),
S#elixir_scope{check_clauses=false}
),
{ TR, TS#elixir_scope{check_clauses=S#elixir_scope.check_clauses} }.
S
).
%% 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 ->
case lists:keyfind(context, 1, Meta) of
{ context, Context } ->
not_an_import(Tuple, Context, S#elixir_scope.macro_functions)
andalso not_an_import(Tuple, Context, S#elixir_scope.macro_macros)
andalso Import;
false ->
false
end;
false ->
false
end.
not_an_import(Tuple, Context, Dict) ->
case orddict:find(Context, Dict) of
{ ok, Pairs } ->
not lists:any(fun({ _, List }) -> lists:member(Tuple, List) end, Pairs);
error ->
true
end.
munge_stacktrace(Info, [{ _, _, [S|_], _ }|_], S) ->
[Info];
@@ -210,10 +263,14 @@ munge_stacktrace(_, [], _) ->
%% ERROR HANDLING
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]).
format_error({ unrequired_module, { Receiver, Name, Arity, Required }}) ->
String = 'Elixir.Enum':join([elixir_errors:inspect(R) || R <- Required], ", "),
io_lib:format("tried to invoke macro ~ts.~ts/~B but module was not required. Required: ~ts",
[elixir_errors:inspect(Receiver), Name, Arity, String]);
format_error({ 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 +278,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,
@@ -379,7 +436,6 @@ in_erlang_macros() ->
{defp,2},
{defp,4},
{function,1},
{function,2},
{function,3},
{in,2},
{'not',1},
@@ -387,4 +443,4 @@ in_erlang_macros() ->
{'receive',1},
{'try',1},
{'xor',2}
].
].
+31 -27
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").
@@ -14,15 +14,7 @@
%% Handle inspecting for exceptions
inspect(Atom) when is_atom(Atom) ->
case atom_to_list(Atom) of
"Elixir-" ++ Rest -> [to_dot(R) || R <- Rest];
Else -> Else
end;
inspect(Other) -> Other.
to_dot($-) -> $.;
to_dot(L) -> L.
'Elixir.Binary.Inspect.Atom':inspect(Atom, []).
%% Raised during macros translation.
@@ -37,7 +29,7 @@ syntax_error(Meta, File, Message) when is_binary(Message) ->
syntax_error(Meta, File, Format, Args) ->
Message = io_lib:format(Format, Args),
raise(Meta, File, 'Elixir.SyntaxError', iolist_to_binary(Message)).
raise(Meta, File, 'Elixir.SyntaxError', unicode:characters_to_binary(Message)).
%% Raised on tokenizing/parsing
@@ -61,7 +53,7 @@ parse_error(Meta, File, Error, Token) ->
BinToken = if
Token == [] -> <<>>;
true -> iolist_to_binary(Token)
true -> unicode:characters_to_binary(Token)
end,
Message = <<BinError / binary, BinToken / binary >>,
@@ -77,10 +69,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)
@@ -94,10 +86,13 @@ handle_file_warning(_, _File, { _Line, sys_core_fold, useless_building }) -> [];
%% This is an Erlang bug, it considers { tuple, _ }.call to always fail
handle_file_warning(_, _File, { _Line, v3_kernel, bad_call }) -> [];
%% We handle unused local warnings ourselves
handle_file_warning(_, _File, { _Line, erl_lint, { unused_function, _ } }) -> [];
%% Rewrite
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 +100,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 +132,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 +159,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]).
+79 -53
View File
@@ -1,29 +1,46 @@
%% Module responsible for handling imports and conflicts.
%% Module responsible for handling imports and conflicts
%% in between local functions and imports.
%% For imports dispatch, please check elixir_dispatch.
-module(elixir_import).
-export([import/5, recorded_locals/1, format_error/1,
ensure_no_import_conflict/4, ensure_no_local_conflict/4,
build_table/1, delete_table/1, record/4]).
ensure_all_imports_used/3,
build_table/1, delete_table/1, record/3]).
-include("elixir.hrl").
table(Module) -> ?atom_concat([i, Module]).
build_table(Module) ->
ets:new(table(Module), [set, named_table, public]).
ets:new(table(Module), [bag, named_table, public]).
delete_table(Module) ->
ets:delete(table(Module)).
record(_Kind, _Tuple, _Receiver, nil) ->
record(_Tuple, _Receiver, nil) ->
false;
record(import, Tuple, Receiver, Module) ->
record(Tuple, Receiver, Module) ->
try
ets:insert(table(Module), { Tuple, Receiver })
catch
error:badarg -> false
end.
record_warn(_Meta, _Ref, _Opts, #elixir_scope{module=nil}) -> false;
record_warn(Meta, Ref, Opts, S) ->
Table = table(S#elixir_scope.module),
ets:delete(Table, Ref),
Warn =
case keyfind(warn, Opts) of
{ warn, false } -> false;
{ warn, true } -> true;
false -> not lists:keymember(context, 1, Meta)
end,
Warn andalso ets:insert(Table, { Ref, ?line(Meta) }).
recorded_locals(Module) ->
Table = table(Module),
Match = { '$1', Module },
@@ -41,9 +58,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,20 +72,20 @@ 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,
record_warn(Meta, Ref, Opts, S),
SM.
%% IMPORT FUNCTION RELATED HELPERS
%% 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 +105,33 @@ 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), if_quoted(Meta, Temp, fun(Value) -> keydelete(Key, Value) end) };
_ ->
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]
ensure_no_special_form_conflict(Meta, File, Key, Final, internal_conflict),
{ [{ Key, Final }|keydelete(Key, Old)],
if_quoted(Meta, Temp, fun(Value) -> [{ Key, Final }|keydelete(Key, Value)] end) }
end.
if_quoted(Meta, Temp, Callback) ->
case lists:keyfind(context, 1, Meta) of
{ context, Context } ->
Current = case orddict:find(Context, Temp) of
{ ok, Value } -> Value;
error -> []
end,
orddict:store(Context, Callback(Current), Temp);
_ ->
Temp
end.
%% Ensure we are expanding macros and stuff
@@ -120,14 +148,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
@@ -172,7 +200,7 @@ get_optional_macros(Module) ->
%% conflicts with an import is automatically done by Erlang.
ensure_no_local_conflict(Meta, File, Module, AllDefined) ->
ensure_no_in_erlang_macro_conflict(Meta, File, Module, AllDefined, local_conflict).
ensure_no_special_form_conflict(Meta, File, Module, AllDefined, local_conflict).
%% Find conlicts in the given list of functions with
%% the recorded set of imports.
@@ -183,68 +211,65 @@ ensure_no_import_conflict(Meta, File, Module, AllDefined) ->
case Matches of
[{Name,Arity}|_] ->
Key = ets:lookup_element(Table, {Name, Arity }, 2),
Tuple = { import_conflict, { Key, Name, Arity } },
Key = ets:lookup_element(Table, { Name, Arity }, 2),
Tuple = { import_conflict, { hd(Key), Name, Arity } },
elixir_errors:form_error(Meta, File, ?MODULE, Tuple);
[] ->
ok
end.
ensure_all_imports_used(_Line, File, Module) ->
Table = table(Module),
[begin
elixir_errors:handle_file_warning(File, { L, ?MODULE, { unused_import, M } })
end || [M, L] <- ets:select(Table, module_line_spec()),
ets:match(Table, { '$1', M }) == []].
module_line_spec() ->
[{ { '$1', '$2' }, [{ is_integer, '$2' }], ['$$'] }].
%% Ensure the given functions don't clash with any
%% of Elixir non overridable macros.
ensure_no_in_erlang_macro_conflict(Meta, File, Key, [{Name,Arity}|T], Reason) ->
ensure_no_special_form_conflict(Meta, File, Key, [{Name,Arity}|T], Reason) ->
Values = lists:filter(fun({X,Y}) ->
(Name == X) andalso ((Y == '*') orelse (Y == Arity))
end, non_overridable_macros()),
end, special_form()),
case Values /= [] of
true ->
Tuple = { Reason, { Key, Name, Arity } },
elixir_errors:form_error(Meta, File, ?MODULE, Tuple);
false -> ensure_no_in_erlang_macro_conflict(Meta, File, Key, T, Reason)
false -> ensure_no_special_form_conflict(Meta, File, Key, T, Reason)
end;
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.
ensure_no_special_form_conflict(_Meta, _File, _Key, [], _) -> 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({ unused_import, Module }) ->
io_lib:format("unused import ~ts", [elixir_errors:inspect(Module)]);
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
@@ -285,7 +310,7 @@ remove_internals(Set) ->
%% Macros implemented in Erlang that are not overridable.
non_overridable_macros() ->
special_form() ->
[
{'^',1},
{'=',2},
@@ -309,6 +334,7 @@ non_overridable_macros() ->
{'__CALLER__',0},
{'__MODULE__',0},
{'__FILE__',0},
{'__DIR__',0},
{'__aliases__','*'},
{'quote',1},
{'quote',2},
+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);
+32 -51
View File
@@ -1,7 +1,7 @@
-module(elixir_literal).
-export([translate/2]).
-import(elixir_translator, [translate_each/2, translate_args/2]).
-import(elixir_scope, [umergev/2, umergec/2]).
-import(elixir_scope, [umergec/2]).
-include("elixir.hrl").
-compile({parse_transform, elixir_transform}).
@@ -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 }),
@@ -29,33 +29,23 @@ translate({ '{}', Meta, Args } = Original, S) when is_list(Args) ->
translate({ '[]', _Meta, [] }, S) ->
{ { nil, 0 }, S };
translate({ '[]', _Meta, Args } = Original, S) when is_list(Args) ->
translate({ '[]', Meta, Args } = Original, S) when is_list(Args) ->
case elixir_partials:handle(Original, S, allow_tail) of
error ->
[RTail|RArgs] = lists:reverse(Args),
case RTail of
{'|',_,[Left,Right]} ->
Exprs = [Left|RArgs],
{ Tail, ST } = translate_each(Right, S),
ListS = umergec(S, ST);
RExprs = [Left|RArgs],
TailFun = fun(ST) -> translate_each(Right, ST) end;
_ ->
Exprs = [RTail|RArgs],
Tail = { nil, 0 },
ST = S,
ListS = S
RExprs = [RTail|RArgs],
TailFun = fun(ST) -> { { nil, ?line(Meta) }, ST } end
end,
{ FExprs, FS } = case S#elixir_scope.context of
assign ->
elixir_tree_helpers:build_reverse_list(fun elixir_translator:translate_each/2, Exprs, 0, ListS, Tail);
_ ->
{ TArgs, { SC, SV } } =
elixir_tree_helpers:build_reverse_list(fun elixir_translator:translate_arg/2, Exprs, 0, { ListS, ListS }, Tail),
{ TArgs, umergec(SV, SC) }
end,
{ FExprs, umergev(ST, FS) };
{ Exprs, SE } = translate_args(lists:reverse(RExprs), S),
{ Tail, ST } = TailFun(SE),
{ elixir_tree_helpers:list_to_cons(?line(Meta), Exprs, Tail), ST };
Else -> Else
end;
@@ -75,7 +65,7 @@ translate(Atom, S) when is_atom(Atom) ->
{ { atom, 0, Atom }, S };
translate(Bitstring, S) when is_bitstring(Bitstring) ->
{ elixir_tree_helpers:abstract_syntax(Bitstring), S }.
{ elixir_tree_helpers:elixir_to_erl(Bitstring), S }.
%% Helpers
@@ -106,37 +96,28 @@ build_bitstr_each(Fun, [{'::',_,[H,V]}|T], Meta, S, Acc) ->
build_bitstr_each(Fun, [H|T], Meta, S, Acc) ->
build_bitstr_each(Fun, T, Meta, S, Acc, H, default, default).
build_bitstr_each(Fun, T, Meta, S, Acc, H, Size, Types) when is_list(H) ->
case is_default_or_utf(Types) of
true ->
{ NewAcc, NewS } = lists:foldl(fun(L, { LA, LS }) ->
{ FL, FS } = Fun(L, LS),
{ [{ bin_element, ?line(Meta), FL, Size, Types }|LA], FS }
end, { Acc, S }, H),
build_bitstr_each(Fun, T, Meta, NewS, NewAcc);
false ->
build_bitstr_default(Fun, T, Meta, S, Acc, H, Size, Types)
end;
build_bitstr_each(Fun, T, Meta, S, Acc, H, Size, Types) when is_bitstring(H) ->
case is_default_or_utf(Types) of
true ->
Line = ?line(Meta),
{ bin, _, Elements } = elixir_tree_helpers:abstract_syntax(H),
NewAcc = lists:foldl(fun({ bin_element, _, Expr, _, _ }, FinalAcc) ->
[{ bin_element, Line, Expr, Size, Types }|FinalAcc]
end, Acc, Elements),
build_bitstr_each(Fun, T, Meta, S, NewAcc);
false ->
build_bitstr_default(Fun, T, Meta, S, Acc, H, Size, Types)
end;
build_bitstr_each(Fun, T, Meta, S, Acc, H, Size, Types) ->
build_bitstr_default(Fun, T, Meta, S, Acc, H, Size, Types).
build_bitstr_default(Fun, T, Meta, S, Acc, H, Size, Types) ->
{ Expr, NS } = Fun(H, S),
build_bitstr_each(Fun, T, Meta, NS, [{ bin_element, ?line(Meta), Expr, Size, Types }|Acc]).
case (is_default_or_utf(Types) andalso Expr) of
{ bin, _, BinElements } ->
build_bitstr_each(Fun, T, Meta, NS, rehash_bin_elements(BinElements, Size, Types, []) ++ Acc);
{ cons, _, _, _ } = Cons ->
build_bitstr_each(Fun, T, Meta, NS, rehash_cons(Cons, Size, Types, []) ++ Acc);
{ nil, _ } ->
build_bitstr_each(Fun, T, Meta, NS, Acc);
_ ->
build_bitstr_each(Fun, T, Meta, NS, [{ bin_element, ?line(Meta), Expr, Size, Types }|Acc])
end.
rehash_cons({ nil, _ }, _Size, _Types, Acc) -> Acc;
rehash_cons({ cons, Line, Left, Right }, Size, Types, Acc) ->
rehash_cons(Right, Size, Types, [{ bin_element, Line, Left, Size, Types }|Acc]).
rehash_bin_elements([{ bin_element, Line, Expr, _S, _T }|T], Size, Types, Acc) ->
rehash_bin_elements(T, Size, Types, [{ bin_element, Line, Expr, Size, Types }|Acc]);
rehash_bin_elements([], _Size, _Types, Acc) ->
Acc.
is_default_or_utf(default) -> true;
is_default_or_utf([UTF|_]) when UTF == utf8; UTF == utf16; UTF == utf32 -> true;
@@ -219,7 +200,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.
+89 -86
View File
@@ -5,12 +5,10 @@
-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).
-define(IN_TYPES(Kind), Kind == atom orelse Kind == integer orelse Kind == float).
-define(opt_in_types(Kind), Kind == atom orelse Kind == integer orelse Kind == float).
-compile({parse_transform, elixir_transform}).
%% Operators
@@ -23,7 +21,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 +51,36 @@ 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),
translate({ function, Meta, [_] }, S) ->
assert_no_assign_or_guard_scope(Meta, 'function', S),
syntax_error(Meta, S#elixir_scope.file, "invalid args for function");
WrappedMeta =
case lists:keyfind(import_fa, 1, MetaFA) of
{ import_fa, { Receiver, Context } } ->
lists:keystore(context, 1,
lists:keystore(import, 1, Meta, { import, Receiver }),
{ context, Context }
);
false -> Meta
end,
translate({ function, Meta, [_, _] = Args }, S) ->
assert_no_assign_or_guard_scope(Meta, 'function', S),
case translate_args(Args, S) of
{ [{atom,_,Name}, {integer,_,Arity}], SA } ->
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")
case elixir_dispatch:import_function(WrappedMeta, F, A, S) of
false -> syntax_error(WrappedMeta, S#elixir_scope.file, "expected ~ts/~B to be a function, but it is a macro", [F, A]);
Else -> Else
end;
translate({ function, Meta, [_] }, S) ->
assert_no_match_or_guard_scope(Meta, 'function', S),
syntax_error(Meta, S#elixir_scope.file, "invalid args for function");
translate({ function, Meta, [_,_,_] = Args }, S) when is_list(Args) ->
assert_no_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 +112,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
@@ -124,17 +124,17 @@ translate({'@', Meta, [{ Name, _, Args }]}, S) ->
}, S);
_ ->
Contents = 'Elixir.Module':get_attribute(S#elixir_scope.module, Name),
{ elixir_tree_helpers:abstract_syntax(Contents), S }
{ elixir_tree_helpers:elixir_to_erl(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 +150,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 +169,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,54 +196,44 @@ 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 } -> elixir_aliases:store(Meta, New, Old, S);
false -> S
end,
{
{ atom, Meta, FullModule },
RS#elixir_scope{scheduled=[FullModule|S#elixir_scope.scheduled]}
RS#elixir_scope{context_modules=[FullModule|S#elixir_scope.context_modules]}
};
_ ->
{ TRef, S }
end,
{ elixir_module:translate(Meta, FRef, Block, S#elixir_scope{check_clauses=true}), FS };
MS = FS#elixir_scope{local=nil},
{ elixir_module:translate(Meta, FRef, Block, MS), FS };
translate({Kind, Meta, [Call]}, S) when ?FUNS(Kind) ->
translate({Kind, Meta, [Call]}, S) when ?defs(Kind) ->
translate({Kind, Meta, [Call, nil]}, S);
translate({Kind, Meta, [Call, Expr]}, S) when ?FUNS(Kind) ->
translate({Kind, Meta, [Call, Expr]}, S) when ?defs(Kind) ->
assert_module_scope(Meta, Kind, S),
assert_no_function_scope(Meta, Kind, S),
{ TCall, QC, SC } = elixir_quote:erl_escape(Call, true, S),
{ TExpr, QE, SE } = elixir_quote:erl_escape(Expr, true, SC),
CheckClauses = (not lists:keymember(context, 1, Meta)) andalso
(not QC#elixir_quote.unquoted) andalso (not QE#elixir_quote.unquoted),
{ elixir_def:wrap_definition(Kind, Meta, TCall, TExpr, CheckClauses, SE), SE };
{ 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 };
translate({Kind, Meta, [Name, Args, Guards, Expr]}, S) when ?FUNS(Kind) ->
translate({Kind, Meta, [Name, Args, Guards, Expr]}, S) when ?defs(Kind) ->
assert_module_scope(Meta, Kind, S),
assert_no_function_scope(Meta, Kind, S),
{ TName, NS } = translate_each(Name, S),
{ TArgs, AS } = translate_each(Args, NS),
{ TGuards, GS } = translate_each(Guards, AS),
{ TExpr, ES } = translate_each(Expr, GS),
{ elixir_def:wrap_definition(Kind, Meta, TName, TArgs, TGuards, TExpr, ES), ES };
{ TName, SN } = translate_each(Name, S),
{ TArgs, SA } = translate_each(Args, SN),
{ TGuards, SG } = translate_each(Guards, SA),
{ TExpr, SE } = translate_each(Expr, SG),
{ elixir_def:wrap_definition(Kind, Meta, TName, TArgs, TGuards, TExpr, false, SE), SE };
%% Apply - Optimize apply by checking what doesn't need to be dispatched dynamically
@@ -277,21 +267,23 @@ translate_in(Meta, Left, Right, S) ->
false -> { TLeft, SR }
end,
Expr = case TRight of
{ InGuard, TExpr } = case TRight of
{ cons, _, _, _ } ->
[H|T] = elixir_tree_helpers:cons_to_list(TRight),
lists:foldr(fun(X, Acc) ->
Expr = lists:foldr(fun(X, Acc) ->
{ op, Line, 'orelse', { op, Line, '==', Var, X }, Acc }
end, { op, Line, '==', Var, H }, T);
end, { op, Line, '==', Var, H }, T),
{ Cache, Expr };
{ string, _, [H|T] } ->
lists:foldl(fun(X, Acc) ->
Expr = lists:foldl(fun(X, Acc) ->
{ op, Line, 'orelse', { op, Line, '==', Var, { integer, Line, X } }, Acc }
end, { op, Line, '==', Var, { integer, Line, H } }, T);
end, { op, Line, '==', Var, { integer, Line, H } }, T),
{ Cache, Expr };
{ tuple, _, [{ atom, _, 'Elixir.Range' }, Start, End] } ->
case { Start, End } of
{ { K1, _, StartInt }, { K2, _, EndInt } } when ?IN_TYPES(K1), ?IN_TYPES(K2), StartInt =< EndInt ->
Expr = case { Start, End } of
{ { K1, _, StartInt }, { K2, _, EndInt } } when ?opt_in_types(K1), ?opt_in_types(K2), StartInt =< EndInt ->
increasing_compare(Line, Var, Start, End);
{ { K1, _, _ }, { K2, _, _ } } when ?IN_TYPES(K1), ?IN_TYPES(K2) ->
{ { K1, _, _ }, { K2, _, _ } } when ?opt_in_types(K1), ?opt_in_types(K2) ->
decreasing_compare(Line, Var, Start, End);
_ ->
{ op, Line, 'orelse',
@@ -301,14 +293,20 @@ translate_in(Meta, Left, Right, S) ->
{ op, Line, 'andalso',
{ op, Line, '<', End, Start},
decreasing_compare(Line, Var, Start, End) } }
end;
end,
{ Cache, Expr };
_ ->
syntax_error(Meta, S#elixir_scope.file, "invalid args for operator in, it expects an explicit array or an explicit range on the right side")
case Cache of
true ->
{ false, ?wrap_call(Line, 'Elixir-Enum', 'member?', [TRight, TLeft]) };
false ->
syntax_error(Meta, S#elixir_scope.file, "invalid args for operator in, it expects an explicit array or an explicit range on the right side when used in guard expressions")
end
end,
case Cache of
true -> { Var, { block, Line, [ { match, Line, Var, TLeft }, Expr ] }, SV };
false -> { Var, Expr, SV }
case InGuard of
true -> { Var, { block, Line, [ { match, Line, Var, TLeft }, TExpr ] }, SV };
false -> { Var, TExpr, SV }
end.
increasing_compare(Line, Var, Start, End) ->
@@ -327,14 +325,26 @@ 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 ->
%% defmodule :foo
expand_module(Raw, Module, #elixir_scope{module=Nesting}) when is_atom(Raw); Nesting == nil ->
Module;
module_ref({ '__aliases__', _, ['Elixir'|_] }, Module, _Nesting) ->
Module;
%% defmodule Hello
expand_module({ '__aliases__', _, [H] }, _Module, S) ->
elixir_aliases:concat([S#elixir_scope.module, H]);
module_ref(_Raw, Module, Nesting) ->
elixir_aliases:concat([Nesting, Module]).
%% defmodule Hello.World
expand_module({ '__aliases__', _, _ } = Alias, Module, S) ->
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;
%% defmodule Elixir.Hello.World
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 +359,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.
+57 -42
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}).
@@ -14,7 +15,7 @@ eval_quoted(Module, Quoted, RawBinding, Opts) ->
false -> Line = 1
end,
{ Value, FinalBinding, _Scope } = elixir:eval_quoted([Quoted], Binding, Line, Scope#elixir_scope{check_clauses=false}),
{ Value, FinalBinding, _Scope } = elixir:eval_quoted([Quoted], Binding, Line, Scope),
{ Value, FinalBinding }.
scope_for_eval(Module, #elixir_scope{} = S) ->
@@ -41,7 +42,7 @@ docs_table(Module) ->
translate(Meta, Ref, Block, S) ->
Line = ?line(Meta),
MetaBlock = elixir_tree_helpers:abstract_syntax(Block),
MetaBlock = elixir_tree_helpers:elixir_to_erl(Block),
{ MetaS, Vars } = elixir_scope:serialize_with_vars(Line, S),
Args = [{integer, Line, Line}, Ref, MetaBlock, Vars, MetaS],
@@ -49,8 +50,13 @@ translate(Meta, Ref, Block, S) ->
%% The compilation hook.
compile(Line, Module, Block, Vars, #elixir_scope{} = S) when is_atom(Module) ->
compile(Line, Module, Block, Vars, #elixir_scope{context_modules=FileModules} = RawS) when is_atom(Module) ->
C = elixir_compiler:get_opts(),
S = case lists:member(Module, FileModules) of
true -> RawS;
false -> RawS#elixir_scope{context_modules=[Module|FileModules]}
end,
File = S#elixir_scope.file,
FileList = binary_to_list(File),
@@ -59,12 +65,13 @@ compile(Line, Module, Block, Vars, #elixir_scope{} = S) when is_atom(Module) ->
try
Result = eval_form(Line, Module, Block, Vars, S),
{ Export, Private, Def, Defmacro, Defmacrop, Functions } = elixir_def:unwrap_stored_definitions(FileList, Module),
{ Export, Private, Def, Defmacro, Functions } = elixir_def:unwrap_stored_definitions(FileList, Module),
{ All, Forms0 } = functions_form(Line, File, Module, Export, Private, Def, Defmacro, Defmacrop, Functions, C),
Forms1 = specs_form(Line, Module, Defmacro, Defmacrop, Forms0, C),
{ All, Forms0 } = functions_form(Line, File, Module, Export, Private, Def, Defmacro, Functions, C),
Forms1 = specs_form(Line, Module, Private, Defmacro, Forms0, C),
Forms2 = attributes_form(Line, File, Module, Forms1),
elixir_import:ensure_all_imports_used(Line, File, Module),
elixir_import:ensure_no_local_conflict(Line, File, Module, All),
elixir_import:ensure_no_import_conflict(Line, File, Module, All),
@@ -133,32 +140,39 @@ 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.
%% Return the form with exports and function declarations.
functions_form(Line, File, Module, Export, Private, Def, Defmacro, Defmacrop, RawFunctions, C) ->
functions_form(Line, File, Module, Export, Private, Def, Defmacro, 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 } =
add_info_function(Line, File, Module, Export, Functions, Def, Defmacro, C),
Recorded = elixir_import:recorded_locals(Module),
elixir_def_local:check_unused_local_macros(File, Recorded, Defmacrop),
Recorded =
case ets:lookup(data_table(Module), 'on_load') of
[] -> elixir_import:recorded_locals(Module);
[{on_load,OnLoad}] -> elixir_import:recorded_locals(Module) ++ OnLoad
end,
{ FinalExport ++ Private, [
elixir_def_local:check_unused_local(File, Recorded, Private),
PrivateTuple = [Tuple || { Tuple, _, _, _, _ } <- Private],
{ FinalExport ++ PrivateTuple, [
{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 };
@@ -189,8 +203,8 @@ attributes_form(Line, _File, Module, Current) ->
%% Specs
specs_form(Line, Module, Defmacro, DefmacropWithLine, Forms, C) ->
Defmacrop = [Tuple || { Tuple, _, _ } <- DefmacropWithLine],
specs_form(Line, Module, Private, Defmacro, Forms, C) ->
Defmacrop = [Tuple || { Tuple, defmacrop, _, _, _ } <- Private],
case elixir_compiler:get_opt(internal, C) of
true -> Forms;
_ ->
@@ -238,7 +252,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) ->
@@ -286,11 +301,11 @@ add_info_function(Line, File, Module, Export, Functions, Def, Defmacro, C) ->
end.
functions_clause(Def) ->
{ clause, 0, [{ atom, 0, functions }], [], [elixir_tree_helpers:abstract_syntax(Def)] }.
{ clause, 0, [{ atom, 0, functions }], [], [elixir_tree_helpers:elixir_to_erl(Def)] }.
macros_clause(Module, Def, Defmacro) ->
All = handle_builtin_macros(Module, Def, Defmacro),
{ clause, 0, [{ atom, 0, macros }], [], [elixir_tree_helpers:abstract_syntax(All)] }.
{ clause, 0, [{ atom, 0, macros }], [], [elixir_tree_helpers:elixir_to_erl(All)] }.
handle_builtin_macros('Elixir.Kernel', Def, Defmacro) ->
ordsets:subtract(ordsets:union(Defmacro,
@@ -302,14 +317,14 @@ module_clause(Module) ->
docs_clause(Module, true) ->
Docs = ordsets:from_list(ets:tab2list(docs_table(Module))),
{ clause, 0, [{ atom, 0, docs }], [], [elixir_tree_helpers:abstract_syntax(Docs)] };
{ clause, 0, [{ atom, 0, docs }], [], [elixir_tree_helpers:elixir_to_erl(Docs)] };
docs_clause(_Module, _) ->
{ clause, 0, [{ atom, 0, docs }], [], [{ atom, 0, nil }] }.
moduledoc_clause(Line, Module, true) ->
Docs = 'Elixir.Module':get_attribute(Module, moduledoc),
{ clause, 0, [{ atom, 0, moduledoc }], [], [elixir_tree_helpers:abstract_syntax({ Line, Docs })] };
{ clause, 0, [{ atom, 0, moduledoc }], [], [elixir_tree_helpers:elixir_to_erl({ Line, Docs })] };
moduledoc_clause(_Line, _Module, _) ->
{ clause, 0, [{ atom, 0, moduledoc }], [], [{ atom, 0, nil }] }.
@@ -320,42 +335,42 @@ else_clause() ->
% HELPERS
eval_callbacks(Line, Module, Name, Args, RawS) ->
S = RawS#elixir_scope{check_clauses=false},
eval_callbacks(Line, Module, Name, Args, S) ->
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_dispatch:dispatch_require(Meta, M, F, Args, S, fun() ->
apply(M, F, Args),
{ { atom, 0, nil }, S }
end),
{ Tree, _ } = elixir_translator:translate_each(Expr, Scope),
try
erl_eval:exprs([Tree], Binding)
catch
Kind:Reason ->
Info = { M, F, Args, [{ file, binary_to_list(S#elixir_scope.file) }, { line, Line }] },
erlang:raise(Kind, Reason, [Info|erlang:get_stacktrace()])
case Tree of
{ atom, _, Atom } ->
Atom;
_ ->
try
erl_eval:exprs([Tree], Binding)
catch
Kind:Reason ->
Info = { M, F, Args, [{ file, binary_to_list(S#elixir_scope.file) }, { line, Line }] },
erlang:raise(Kind, Reason, [Info|erlang:get_stacktrace()])
end
end
end, Callbacks).
% 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)]).
+47 -28
View File
@@ -2,7 +2,7 @@ Nonterminals
grammar expr_list
expr paren_expr block_expr fn_expr bracket_expr call_expr bracket_at_expr max_expr
base_expr matched_expr matched_op_expr unmatched_expr op_expr
add_op mult_op unary_op two_op pipeline_op bin_concat_op
add_op mult_op unary_op two_op right_op bin_concat_op
match_op send_op default_op when_op pipe_op in_op inc_op range_op
andand_op oror_op and_op or_op comp_expr_op colon_colon_op three_op at_op
open_paren close_paren empty_paren
@@ -10,8 +10,9 @@ Nonterminals
open_curly close_curly
open_bit close_bit
base_comma_expr comma_expr optional_comma_expr matched_comma_expr
call_args call_args_parens call_args_parens_not_one call_args_no_parens parens_call
stab stab_op stab_expr stab_maybe_expr
call_args call_args_parens parens_call
call_args_no_parens call_args_no_parens_strict call_args_parens_not_one
stab stab_eol stab_op stab_expr stab_maybe_expr
kw_eol kw_expr kw_comma kw_base
matched_kw_expr matched_kw_comma matched_kw_base
dot_op dot_ref dot_identifier dot_op_identifier dot_do_identifier
@@ -30,7 +31,7 @@ Terminals
'true' 'false' 'nil'
'=' '+' '-' '*' '/' '++' '--' '**' '//'
'(' ')' '[' ']' '{' '}' '<<' '>>' '::'
eol ',' '&' '|' '.' '^' '@' '<-' '<>' '->' '|>' '/>' '=~'
eol ',' '&' '|' '.' '^' '@' '<-' '<>' '->' '|>' '=~'
'&&' '||' '!' '...' '..'
'<<<' '>>>' '&&&' '|||' '^^^' '~~~'
.
@@ -53,13 +54,13 @@ Left 140 or_op.
Left 150 and_op.
Left 160 comp_expr_op.
Left 170 in_op.
Left 180 range_op.
Left 190 three_op.
Left 200 add_op.
Left 210 mult_op.
Right 220 bin_concat_op.
Right 230 two_op.
Right 290 pipeline_op.
Right 180 right_op.
Left 190 range_op.
Left 200 three_op.
Left 210 add_op.
Left 220 mult_op.
Right 230 bin_concat_op.
Right 240 two_op.
Nonassoc 300 unary_op.
Left 310 dot_call_op.
Left 310 dot_op.
@@ -102,7 +103,7 @@ op_expr -> match_op expr : { '$1', '$2' }.
op_expr -> add_op expr : { '$1', '$2' }.
op_expr -> mult_op expr : { '$1', '$2' }.
op_expr -> two_op expr : { '$1', '$2' }.
op_expr -> pipeline_op expr : { '$1', '$2' }.
op_expr -> right_op expr : { '$1', '$2' }.
op_expr -> andand_op expr : { '$1', '$2' }.
op_expr -> three_op expr : { '$1', '$2' }.
op_expr -> oror_op expr : { '$1', '$2' }.
@@ -123,7 +124,7 @@ matched_op_expr -> match_op matched_expr : { '$1', '$2' }.
matched_op_expr -> add_op matched_expr : { '$1', '$2' }.
matched_op_expr -> mult_op matched_expr : { '$1', '$2' }.
matched_op_expr -> two_op matched_expr : { '$1', '$2' }.
matched_op_expr -> pipeline_op matched_expr : { '$1', '$2' }.
matched_op_expr -> right_op matched_expr : { '$1', '$2' }.
matched_op_expr -> andand_op matched_expr : { '$1', '$2' }.
matched_op_expr -> three_op matched_expr : { '$1', '$2' }.
matched_op_expr -> oror_op matched_expr : { '$1', '$2' }.
@@ -142,16 +143,16 @@ matched_op_expr -> comp_expr_op matched_expr : { '$1', '$2' }.
block_expr -> parens_call call_args_parens do_block : build_identifier('$1', '$2' ++ '$3').
block_expr -> parens_call call_args_parens call_args_parens do_block : build_nested_parens('$1', '$2', '$3' ++ '$4').
block_expr -> dot_punctuated_identifier call_args_no_parens do_block : build_identifier('$1', '$2' ++ '$3').
block_expr -> dot_punctuated_identifier call_args_no_parens_strict do_block : build_identifier('$1', '$2' ++ '$3').
block_expr -> dot_do_identifier do_block : build_identifier('$1', '$2').
block_expr -> dot_identifier call_args_no_parens do_block : build_identifier('$1', '$2' ++ '$3').
block_expr -> dot_identifier call_args_no_parens_strict do_block : build_identifier('$1', '$2' ++ '$3').
fn_expr -> fn_eol stab end_eol : build_fn('$1', build_stab(lists:reverse('$2'))).
fn_expr -> call_expr : '$1'.
call_expr -> dot_punctuated_identifier call_args_no_parens : build_identifier('$1', '$2').
call_expr -> dot_op_identifier call_args_no_parens : build_identifier('$1', '$2').
call_expr -> dot_identifier call_args_no_parens : build_identifier('$1', '$2').
call_expr -> dot_punctuated_identifier call_args_no_parens_strict : build_identifier('$1', '$2').
call_expr -> dot_op_identifier call_args_no_parens_strict : build_identifier('$1', '$2').
call_expr -> dot_identifier call_args_no_parens_strict : build_identifier('$1', '$2').
call_expr -> dot_punctuated_identifier : build_identifier('$1', []).
call_expr -> dot_do_identifier : build_identifier('$1', nil).
call_expr -> var : build_identifier('$1', nil).
@@ -192,7 +193,7 @@ base_expr -> sigil : build_sigil('$1').
do_block -> do_eol 'end' : [[{do,nil}]].
do_block -> do_eol stab end_eol : [[{ do, build_stab(lists:reverse('$2')) }]].
do_block -> do_eol block_list 'end' : [[{ do, nil }|'$2']].
do_block -> do_eol stab eol block_list 'end' : [[{ do, build_stab(lists:reverse('$2')) }|'$4']].
do_block -> do_eol stab_eol block_list 'end' : [[{ do, build_stab(lists:reverse('$2')) }|'$3']].
fn_eol -> 'fn' : '$1'.
fn_eol -> 'fn' eol : '$1'.
@@ -209,6 +210,9 @@ block_eol -> block_identifier eol : '$1'.
stab -> stab_expr : ['$1'].
stab -> stab eol stab_expr : ['$3'|'$1'].
stab_eol -> stab : '$1'.
stab_eol -> stab eol : '$1'.
stab_expr -> expr : '$1'.
stab_expr -> stab_op stab_maybe_expr : build_op('$1', [], '$2').
stab_expr -> call_args_no_parens stab_op stab_maybe_expr : build_op('$2', '$1', '$3').
@@ -217,7 +221,7 @@ stab_expr -> call_args_parens_not_one stab_op stab_maybe_expr : build_op('$2', '
stab_maybe_expr -> 'expr' : '$1'.
stab_maybe_expr -> '$empty' : nil.
block_item -> block_eol stab eol : { ?exprs('$1'), build_stab(lists:reverse('$2')) }.
block_item -> block_eol stab_eol : { ?exprs('$1'), build_stab(lists:reverse('$2')) }.
block_item -> block_eol : { ?exprs('$1'), nil }.
block_list -> block_item : ['$1'].
@@ -268,12 +272,10 @@ two_op -> '--' eol : '$1'.
two_op -> '**' : '$1'.
two_op -> '**' eol : '$1'.
pipeline_op -> '=~' : '$1'.
pipeline_op -> '=~' eol : '$1'.
pipeline_op -> '|>' : '$1'.
pipeline_op -> '|>' eol : '$1'.
pipeline_op -> '/>' : '$1'.
pipeline_op -> '/>' eol : '$1'.
right_op -> '=~' : '$1'.
right_op -> '=~' eol : '$1'.
right_op -> '|>' : '$1'.
right_op -> '|>' eol : '$1'.
three_op -> '&&&' : '$1'.
three_op -> '&&&' eol : '$1'.
@@ -387,6 +389,11 @@ parens_call -> matched_expr dot_call_op : { '.', [{line,?line('$2')}], ['$1'] }.
matched_comma_expr -> matched_expr : ['$1'].
matched_comma_expr -> matched_comma_expr ',' matched_expr : ['$3'|'$1'].
call_args_no_parens_strict -> call_args_no_parens : '$1'.
call_args_no_parens_strict -> open_paren ')' : throw_no_parens_strict('$1').
call_args_no_parens_strict -> open_paren matched_kw_base close_paren : throw_no_parens_strict('$1').
call_args_no_parens_strict -> open_paren matched_expr ',' call_args_no_parens close_paren : throw_no_parens_strict('$1').
call_args_no_parens -> matched_comma_expr : lists:reverse('$1').
call_args_no_parens -> matched_kw_base : ['$1'].
call_args_no_parens -> matched_comma_expr ',' matched_kw_base : lists:reverse(['$3'|'$1']).
@@ -490,6 +497,8 @@ build_tuple(Marker, Args) ->
build_block([nil]) -> { '__block__', [], [nil] };
build_block([{Op,_,[_]}]=Exprs) when ?rearrange_uop(Op) -> { '__block__', [], Exprs };
build_block([{unquote_splicing,_,Args}]=Exprs) when
length(Args) =< 2 -> { '__block__', [], Exprs };
build_block([Expr]) when not is_list(Expr) -> Expr;
build_block(Exprs) -> { '__block__', [], Exprs }.
@@ -553,10 +562,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 }) ->
@@ -581,3 +590,13 @@ build_stab([H|T], Marker, Temp, Acc) ->
build_stab([], Marker, Temp, Acc) ->
H = { Marker, build_block(lists:reverse(Temp)) },
lists:reverse([H|Acc]).
%% Errors
throw(Line, Error, Token) ->
throw({ error, { Line, ?MODULE, [Error, Token] }}).
throw_no_parens_strict(Token) ->
throw(?line(Token), "invalid comma inside parenthesis. If you are making a "
"function call, do not insert spaces in between the function name and the "
"opening parentheses. Syntax error before: ", ")").
+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),
+147 -110
View File
@@ -1,5 +1,6 @@
-module(elixir_quote).
-export([quote/3, linify/2, join_quoted/5]).
-export([escape/2, erl_escape/3, erl_quote/3, linify/2, unquote/5]).
-compile({parse_transform, elixir_transform}).
-include("elixir.hrl").
%% Apply the line from site call on quoted contents.
@@ -21,12 +22,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 responsible for doing runtime unquoting.
unquote(_File, Meta, Left, { '__aliases__', _, Args }, nil) ->
{ '__aliases__', Meta, [Left|Args] };
join_quoted(Meta, Left, Right, nil, _File) when is_atom(Right) ->
unquote(_File, Meta, Left, Right, nil) when is_atom(Right) ->
case atom_to_list(Right) of
"Elixir-" ++ _ ->
{ '__aliases__', Meta, [Left, Right] };
@@ -34,56 +35,100 @@ 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(_File, Meta, Left, Right, Args) when is_atom(Right) ->
{ { '.', Meta, [Left, Right] }, Meta, Args };
join_quoted(Meta, _Left, _Right, _Args, File) ->
unquote(File, Meta, _Left, _Right, _Args) ->
elixir_errors:syntax_error(Meta, File, "expected unquote after dot to return an atom or an alias").
%% Translation
%% Escapes the given expression. It is similar to quote, but
%% lines are kept and hygiene mechanisms are disabled.
escape(Expr, Unquote) ->
quote(Expr, #elixir_quote{
line=keep,
vars_hygiene=false,
aliases_hygiene=false,
imports_hygiene=false,
unquote=Unquote,
mark=false
}, nil).
quote({ 'unquote_splicing', Meta, _ } = Expr, #elixir_quote{unquote=true} = Q, S) ->
do_quote({ '__block__', Meta, [Expr] }, Q, S);
erl_escape(Expr, Unquote, S) ->
{ QExpr, TQ } = escape(Expr, Unquote),
{ TExpr, TS } = elixir_translator:translate_each(QExpr, S),
{ TExpr, TQ, TS }.
quote(Else, Q, S) ->
do_quote(Else, Q, S).
%% Quotes an expression and return its quoted Elixir AST.
do_quote({ unquote, _Meta, [Expr] }, #elixir_quote{unquote=true}, S) ->
elixir_translator:translate_each(Expr, S);
quote(Expr, Q, S) ->
do_quote(Expr, Q, S).
do_quote({ 'alias!', _Meta, [Expr] }, Q, S) ->
do_quote(Expr, Q#elixir_quote{expand_aliases=false}, S);
erl_quote({ 'unquote_splicing', Meta, [_] } = Expr, #elixir_quote{unquote=true} = Q, S) ->
elixir_errors:deprecation(Meta, S#elixir_scope.file, "unquote_splicing in the quote body is deprecated, please use (unquote_splicing()) instead", []),
erl_quote({ '__block__', Meta, [Expr] }, Q, S);
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),
erl_quote(Expr, Q, S) ->
{ QExpr, TQ } = quote(Expr, Q, S),
{ TExpr, TS } = elixir_translator:translate_each(QExpr, S),
{ TExpr, TQ, TS }.
{ { tuple, Line, [
{ atom, Line, '__aliases__' },
meta(Meta, Q),
TAliases
] }, SA };
%% Actual quoting and helpers
do_quote({ quote, _, Args } = Tuple, #elixir_quote{unquote=true} = Q, S) when length(Args) == 1; length(Args) == 2 ->
{ TTuple, TQ } = do_quote_tuple(Tuple, Q#elixir_quote{unquote=false}, S),
{ TTuple, TQ#elixir_quote{unquote=true} };
do_quote({ unquote, _Meta, [Expr] }, #elixir_quote{unquote=true} = Q, _) ->
{ Expr, Q#elixir_quote{unquoted=true} };
%% Context mark
do_quote({ Def, Meta, Args }, #elixir_quote{mark=true} = Q, S) when ?defs(Def); Def == defmodule; Def == alias; Def == import ->
NewMeta = lists:keystore(context, 1, Meta, { context, Q#elixir_quote.context }),
do_quote_tuple({ Def, NewMeta, Args }, Q, S);
do_quote({ { '.', _, [_, Def] } = Target, Meta, Args }, #elixir_quote{mark=true} = Q, S) when ?defs(Def); Def == defmodule; Def == alias; Def == import ->
NewMeta = lists:keystore(context, 1, Meta, { context, Q#elixir_quote.context }),
do_quote_tuple({ Target, NewMeta, Args }, Q, S);
%% Aliases
do_quote({ 'alias!', _Meta, [Expr] }, #elixir_quote{aliases_hygiene=true} = Q, S) ->
{ TExpr, TQ } = do_quote(Expr, Q#elixir_quote{aliases_hygiene=false}, S),
{ TExpr, TQ#elixir_quote{aliases_hygiene=true} };
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,
AliasMeta = lists:keystore(alias, 1, Meta, { alias, Annotation }),
do_quote_tuple({ '__aliases__', AliasMeta, [H|T] }, Q, S);
%% Vars
do_quote({ Left, Meta, nil }, #elixir_quote{vars_hygiene=true} = Q, S) when is_atom(Left) ->
do_quote_tuple({ Left, Meta, Q#elixir_quote.context }, Q, S);
%% Unquote
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 };
do_quote_call(Left, Meta, Expr, Args, Q, S);
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 };
do_quote_call(Left, Meta, Expr, nil, Q, S);
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 }
] },
{ Tuple, S };
%% Imports
do_quote({ Name, Meta, ArgsOrAtom } = Tuple, #elixir_quote{expand_imports=true} = Q, S) when is_atom(Name) ->
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({ 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)
@@ -92,93 +137,85 @@ do_quote({ Name, Meta, ArgsOrAtom } = Tuple, #elixir_quote{expand_imports=true}
case (lists:keyfind(import, 1, Meta) == false) andalso
elixir_dispatch:find_import(Meta, Name, Arity, S) of
false -> do_quote_tuple(Tuple, Q, S);
Receiver -> do_quote_tuple({ Name, [{import,Receiver}|Meta], ArgsOrAtom }, Q, S)
Receiver ->
ImportMeta = lists:keystore(import, 1,
lists:keystore(context, 1, Meta, { context, Q#elixir_quote.context }),
{ import, Receiver }),
do_quote_tuple({ Name, ImportMeta, ArgsOrAtom }, Q, S)
end;
do_quote({ _, _, _ } = Tuple, Q, S) ->
do_quote_tuple(Tuple, Q, S);
% Handle two item tuples but still allow them to be spliced.
do_quote({ Left, Right }, Q, S) when
not is_tuple(Left) orelse (element(1, Left) /= unquote_splicing),
not is_tuple(Right) orelse (element(1, Right) /= unquote_splicing) ->
{ TLeft, LS } = do_quote(Left, Q, S),
{ TRight, RS } = do_quote(Right, Q, LS),
{ { tuple, line(Q), [TLeft, TRight] }, RS };
%% Literals
do_quote({ Left, Right }, #elixir_quote{unquote=true} = Q, S) when
is_tuple(Left) andalso (element(1, Left) == unquote_splicing);
is_tuple(Right) andalso (element(1, Right) == unquote_splicing) ->
do_quote({ '{}', [], [Left, Right] }, Q, S);
do_quote({ Left, Right }, Q, S) ->
do_quote({ '{}', [{line,line(Q)}], [Left, Right] }, Q, S);
{ TLeft, LQ } = do_quote(Left, Q, S),
{ TRight, RQ } = do_quote(Right, LQ, S),
{ { TLeft, TRight }, RQ };
do_quote(Tuple, Q, S) when is_tuple(Tuple) ->
{ TT, TQ } = do_quote(tuple_to_list(Tuple), Q, S),
{ { '{}', [], TT }, TQ };
do_quote(List, Q, S) when is_list(List) ->
splice(List, Q, [], [], S);
do_splice(lists:reverse(List), Q, S);
do_quote(Number, Q, S) when is_integer(Number) ->
{ { integer, line(Q), Number }, S };
do_quote(Other, Q, _) ->
{ Other, Q }.
do_quote(Number, Q, S) when is_float(Number) ->
{ { float, line(Q), Number }, S };
%% Quote helpers
do_quote(Atom, Q, S) when is_atom(Atom) ->
{ { atom, line(Q), Atom }, S };
do_quote_call(Left, Meta, Expr, Args, Q, S) ->
All = [meta(Meta, Q), Left, { unquote, Meta, [Expr] }, Args],
{ TAll, TQ } = lists:mapfoldl(fun(X, Acc) -> do_quote(X, Acc, S) end, Q, All),
{ { { '.', Meta, [elixir_quote, unquote] }, Meta, [{ '__FILE__', [], nil }|TAll] }, TQ }.
do_quote(Bitstring, _Q, S) when is_bitstring(Bitstring) ->
{ elixir_tree_helpers:abstract_syntax(Bitstring), S }.
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 -> lists:keystore(import_fa, 1, Meta, { import_fa, { Receiver, Q#elixir_quote.context } })
end,
do_quote_tuple({ Target, NewMeta, Args }, Q, S).
do_quote_tuple({ Left, Meta, Right }, Q, S) ->
{ TLeft, LS } = do_quote(Left, Q, S),
{ TRight, RS } = do_quote(Right, Q, LS),
%% We need to remove line numbers from quoted exprs
%% otherwise the line number quotes in the macro will
%% get mixed with the unquoted contents
Tuple = { tuple, ?line(Meta), [TLeft, meta(Meta, Q), TRight] },
{ Tuple, RS }.
% Loop through the list finding each unquote_splicing entry.
splice([{ unquote_splicing, _, [Args] }|T], #elixir_quote{unquote=true} = Q, Buffer, Acc, S) ->
{ NewAcc, NewS } = from_buffer_to_acc(Buffer, Q, Acc, S),
{ TArgs, TS } = elixir_translator:translate_each(Args, NewS),
splice(T, Q, [], [TArgs|NewAcc], TS);
splice([{ '|', Meta, [{ unquote_splicing, _, [_] } = Left, Right] }], #elixir_quote{unquote=true} = Q, Buffer, Acc, S) ->
{ TLeft, SL } = splice([Left], Q, Buffer, Acc, S),
{ TRight, SR } = do_quote(Right, Q, SL),
{ { op, ?line(Meta), '++', TLeft, TRight }, SR };
splice([H|T], Q, Buffer, Acc, S) ->
splice(T, Q, [H|Buffer], Acc, S);
splice([], Q, Buffer, Acc, S) ->
{ NewAcc, NewS } = from_buffer_to_acc(Buffer, Q, 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 }
end.
from_buffer_to_acc([], _Q, Acc, S) ->
{ Acc, S };
from_buffer_to_acc(Buffer, Q, Acc, S) ->
{ New, NewS } = elixir_tree_helpers:build_reverse_list(
fun(X, AccS) -> do_quote(X, Q, AccS) end, Buffer, line(Q), S),
{ [New|Acc], NewS }.
{ TLeft, LQ } = do_quote(Left, Q, S),
{ TRight, RQ } = do_quote(Right, LQ, S),
{ { '{}', [], [TLeft, meta(Meta, Q), TRight] }, RQ }.
meta(Meta, #elixir_quote{line=keep}) ->
elixir_tree_helpers:abstract_syntax(Meta);
Meta;
meta(Meta, #elixir_quote{line=nil}) ->
elixir_tree_helpers:abstract_syntax(lists:keydelete(line, 1, Meta));
meta(Meta, #elixir_quote{line=Line} = Q) ->
{ cons,
line(Q),
{ tuple, line(Q), [{ atom, line(Q), line }, Line] },
elixir_tree_helpers:abstract_syntax(lists:keydelete(line, 1, Meta))
}.
lists:keydelete(line, 1, Meta);
meta(Meta, #elixir_quote{line=Line}) ->
lists:keystore(line, 1, Meta, { line, Line }).
line(#elixir_quote{line={ integer, _, Line }}) -> Line;
line(_) -> 0.
%% Quote splicing
do_splice([{ '|', _, [{ unquote_splicing, _, [Left] }, Right] }|T], #elixir_quote{unquote=true} = Q, S) ->
{ TT, TQ } = do_splice(T, Q, S, [], []),
{ do_splice_join(do_splice_join(TT, Left), Right), TQ#elixir_quote{unquoted=true} };
do_splice(List, Q, S) ->
do_splice(List, Q, S, [], []).
do_splice([{ unquote_splicing, _, [Expr] }|T], #elixir_quote{unquote=true} = Q, S, Buffer, Acc) ->
do_splice(T, Q#elixir_quote{unquoted=true}, S, [], do_splice_join(do_splice_join(Expr, Buffer), Acc));
do_splice([H|T], Q, S, Buffer, Acc) ->
{ TH, TQ } = do_quote(H, Q, S),
do_splice(T, TQ, S, [TH|Buffer], Acc);
do_splice([], Q, _S, Buffer, Acc) ->
{ do_splice_join(Buffer, Acc), Q }.
do_splice_join(Left, []) -> Left;
do_splice_join([], Right) -> Right;
do_splice_join(Left, Right) -> { { '.', [], ['Elixir.Kernel', '++'] }, [], [Left, Right] }.
+38 -35
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,27 @@ 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, FileModules }) ->
#elixir_scope{module=Module,file=File,
function=Function,aliases=Aliases,context=Context,
requires=Requires,macros=Macros,functions=Functions,
context_modules=FileModules}.
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).
requires=Requires,macros=Macros,functions=Functions,
context_modules=FileModules} }) when is_integer(Line) ->
{ 'Elixir.Macro.Env', Module, File, Line, Function, Aliases,
Context, Requires, Functions, Macros, FileModules }.
% 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 }
elixir_tree_helpers:elixir_to_erl(
{ S#elixir_scope.file, S#elixir_scope.functions,
S#elixir_scope.requires, S#elixir_scope.macros, S#elixir_scope.aliases,
S#elixir_scope.macro_functions, S#elixir_scope.macro_macros, S#elixir_scope.macro_aliases,
S#elixir_scope.context_modules }
).
serialize_with_vars(Line, S) when is_integer(Line) ->
@@ -119,15 +115,18 @@ 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, Requires, Macros,
Aliases, MacroFunctions, MacroMacros, MacroAliases, FileModules }, Vars) ->
#elixir_scope{
file=File,
functions=Functions,
check_clauses=CheckClauses,
requires=Requires,
macros=Macros,
aliases=Aliases,
scheduled=Scheduled,
macro_functions=MacroFunctions,
macro_macros=MacroMacros,
macro_aliases=MacroAliases,
context_modules=FileModules,
vars=orddict:from_list(Vars),
counter=[{'',length(Vars)}]
}.
@@ -141,26 +140,30 @@ umergev(S1, S2) ->
C1 = S1#elixir_scope.clause_vars,
C2 = S2#elixir_scope.clause_vars,
S2#elixir_scope{
vars=orddict:merge(fun var_merger/3, V1, V2),
vars=merge_vars(V1, V2),
clause_vars=merge_clause_vars(C1, C2)
}.
% Receives two scopes and return a new scope based on the first
% with the counter values from the second one.
% Receives two scopes and return the later scope
% keeping the variables from the first (counters,
% imports and everything else are passed forward).
umergec(S1, S2) ->
S1#elixir_scope{
counter=S2#elixir_scope.counter,
extra_guards=S2#elixir_scope.extra_guards,
super=S1#elixir_scope.super orelse S2#elixir_scope.super,
caller=S1#elixir_scope.caller orelse S2#elixir_scope.caller,
name_args=S1#elixir_scope.name_args orelse S2#elixir_scope.name_args
S2#elixir_scope{
vars=S1#elixir_scope.vars,
temp_vars=S1#elixir_scope.temp_vars,
clause_vars=S1#elixir_scope.clause_vars
}.
% Merge variables trying to find the most recently created.
merge_vars(V, V) -> V;
merge_vars(V1, V2) ->
orddict:merge(fun var_merger/3, V1, V2).
merge_clause_vars(nil, _C2) -> nil;
merge_clause_vars(_C1, nil) -> nil;
merge_clause_vars(C, C) -> C;
merge_clause_vars(C1, C2) ->
orddict:merge(fun var_merger/3, C1, C2).
+138 -127
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
@@ -141,14 +141,14 @@ tokenize([$%,S,H,H,H|T], Line, #scope{file=File} = Scope, Tokens) when ?is_quote
tokenize(Final, Line, Scope, [{ sigil, Line, S, Parts, Modifiers }|Tokens])
end;
tokenize([$%,S,H|T], Line, #scope{file=File} = Scope, Tokens) when not(?is_word(H)), ?is_upcase(S) orelse ?is_downcase(S) ->
tokenize([$%,S,H|T], Line, #scope{file=File} = Scope, Tokens) when not(?is_identifier(H)), not(?is_terminator(H)), ?is_upcase(S) orelse ?is_downcase(S) ->
case elixir_interpolation:extract(Line, File, ?is_downcase(S), T, sigil_terminator(H)) of
{ NewLine, Parts, Rest } ->
{ Final, Modifiers } = collect_modifiers(Rest, []),
tokenize(Final, NewLine, Scope, [{ sigil, Line, S, Parts, Modifiers }|Tokens]);
Error ->
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
@@ -207,10 +207,18 @@ tokenize([$?,Char|T], Line, Scope, Tokens) ->
% Dot identifier/operators
tokenize("..." ++ Rest, Line, Scope, Tokens) ->
Token = tokenize_call_identifier(identifier, Line, '...', Rest),
Token = check_call_identifier(identifier, Line, '...', Rest),
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);
@@ -239,7 +247,7 @@ tokenize([$.,H|T], Line, #scope{file=File} = Scope, Tokens) when ?is_quote(H) ->
case elixir_interpolation:extract(Line, File, true, T, H) of
{ NewLine, [Part], Rest } when is_binary(Part) ->
Atom = unsafe_to_atom(Part, Scope),
Token = tokenize_call_identifier(identifier, Line, Atom, Rest),
Token = check_call_identifier(identifier, Line, Atom, Rest),
tokenize(Rest, NewLine, Scope, [Token|add_token_with_nl({ '.', Line }, Tokens)]);
Error ->
interpolation_error(Error, " (for function name starting at line ~B)", [Line])
@@ -262,10 +270,6 @@ tokenize([$'|T], Line, Scope, Tokens) ->
% Atoms
tokenize([$:,T|String], Line, Scope, Tokens) when ?is_upcase(T); ?is_downcase(T); T == $_ ->
{ Rest, Atom } = tokenize_atom([T|String], [], Scope),
tokenize(Rest, Line, Scope, [{ atom, Line, Atom }|Tokens]);
tokenize([$:,H|T], Line, #scope{file=File} = Scope, Tokens) when ?is_quote(H) ->
case elixir_interpolation:extract(Line, File, true, T, H) of
{ NewLine, Parts, Rest } ->
@@ -278,6 +282,10 @@ tokenize([$:,H|T], Line, #scope{file=File} = Scope, Tokens) when ?is_quote(H) ->
interpolation_error(Error, " (for atom starting at line ~B)", [Line])
end;
tokenize([$:,T|String], Line, Scope, Tokens) when ?is_new_atom(T) ->
{ Rest, Atom } = tokenize_atom([T|String], [], Scope),
tokenize(Rest, Line, Scope, [{ atom, Line, Atom }|Tokens]);
% Atom identifiers/operators
tokenize(":..." ++ Rest, Line, Scope, Tokens) ->
@@ -339,18 +347,12 @@ tokenize([T1,T2,T3|Rest], Line, Scope, Tokens) when ?op3(T1, T2, T3) ->
% ## Containers + punctuation tokens
tokenize([T,T|Rest], Line, Scope, Tokens) when T == $<; T == $> ->
Token = { list_to_atom([T,T]), Line },
case handle_terminator(Token, Scope) of
{ error, _ } = Error -> Error;
New -> tokenize(Rest, Line, New, [Token|Tokens])
end;
handle_terminator(Rest, Line, Scope, Token, Tokens);
tokenize([T|Rest], Line, Scope, Tokens) when T == $(;
T == ${; T == $}; T == $[; T == $]; T == $); T == $, ->
Token = { list_to_atom([T]), Line },
case handle_terminator(Token, Scope) of
{ error, _ } = Error -> Error;
New -> tokenize(Rest, Line, New, [Token|Tokens])
end;
handle_terminator(Rest, Line, Scope, Token, Tokens);
% ## Comparison two token operators
tokenize([T1,T2|Rest], Line, Scope, Tokens) when ?comp2(T1, T2) ->
@@ -380,10 +382,10 @@ tokenize([H|_] = String, Line, Scope, Tokens) when ?is_digit(H) ->
% Aliases
tokenize([H|_] = String, Line, Scope, Tokens) when ?is_upcase(H) ->
{ Rest, Alias } = tokenize_identifier(String, [], false),
{ Rest, Alias } = tokenize_identifier(String, []),
Atom = unsafe_to_atom(Alias, Scope),
case Rest of
[$:|T] when hd(T) /= $: ->
[$:|T] when ?is_space(hd(T)) ->
tokenize(T, Line, Scope, [{ kw_identifier, Line, Atom }|Tokens]);
_ ->
tokenize(Rest, Line, Scope, [{ aliases, Line, [Atom] }|Tokens])
@@ -392,14 +394,16 @@ tokenize([H|_] = String, Line, Scope, Tokens) when ?is_upcase(H) ->
% Identifier
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 ->
tokenize(Rest, Line, Scope, [{ Kind, Line, Identifier }|Tokens]);
[Check|T] ->
case handle_terminator(Check, Scope) of
{ error, _ } = Error -> Error;
New -> tokenize(Rest, Line, New, [Check|T])
case tokenize_any_identifier(String, Line, [], Scope) of
{ error, _ } = Error -> Error;
{ Rest, { Kind, _, Identifier } } ->
case check_keyword(Line, Kind, Identifier, Tokens) of
nomatch ->
tokenize(Rest, Line, Scope, [{ Kind, Line, Identifier }|Tokens]);
{ ok, [Check|T] } ->
handle_terminator(Rest, Line, Scope, Check, T);
{ error, Token } ->
{ error, { Line, "syntax error before: ", Token } }
end
end;
@@ -407,17 +411,16 @@ tokenize([H|_] = String, Line, Scope, Tokens) when ?is_downcase(H); H == $_ ->
tokenize([Space, Sign, NotMarker|T], Line, Scope, [{ Identifier, _, _ } = H|Tokens]) when
Sign == $+ orelse Sign == $-,
Space == $\s orelse Space == $\t,
NotMarker /= $\s, NotMarker /= $\t, NotMarker /= $\r,
NotMarker /= $\n, NotMarker /= $:, NotMarker /= $(,
NotMarker /= $+, NotMarker /= $-, NotMarker /= $>,
?is_horizontal_space(Space),
not(?is_space(NotMarker)),
NotMarker /= $(, NotMarker /= $+, NotMarker /= $-, NotMarker /= $>,
Identifier == identifier orelse Identifier == punctuated_identifier ->
Rest = [NotMarker|T],
tokenize(Rest, Line, Scope, [{ list_to_atom([Sign]), Line }, setelement(1, H, op_identifier)|Tokens]);
% Spaces
tokenize([T|Rest], Line, Scope, Tokens) when T == $\s; T == $\r; T == $\t ->
tokenize([T|Rest], Line, Scope, Tokens) when ?is_horizontal_space(T) ->
tokenize(Rest, Line, Scope, Tokens);
tokenize([{line,Line}|Rest], _Line, Scope, Tokens) ->
@@ -444,13 +447,13 @@ handle_heredocs(T, Line, H, #scope{file=File} = Scope, Tokens) ->
handle_strings(T, Line, H, #scope{file=File} = Scope, Tokens) ->
case elixir_interpolation:extract(Line, File, true, T, H) of
{ NewLine, Parts, [$:|Rest] } when hd(Rest) /= $: ->
{ NewLine, Parts, [$:|Rest] } when ?is_space(hd(Rest)) ->
case Parts of
[Bin] when is_binary(Bin) ->
Atom = unsafe_to_atom(unescape_chars(Bin), Scope),
tokenize(Rest, NewLine, Scope, [{ kw_identifier, Line, Atom }|Tokens]);
_ ->
{ 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) },
@@ -459,15 +462,13 @@ handle_strings(T, Line, H, #scope{file=File} = Scope, Tokens) ->
interpolation_error(Error, " (for string starting at line ~B)", [Line])
end.
handle_comp_op([$:|Rest], Line, Op, Scope, Tokens) when hd(Rest) /= $: ->
verify_kw_and_space(Line, Op, Rest, Scope),
handle_comp_op([$:|Rest], Line, Op, Scope, Tokens) when ?is_space(hd(Rest)) ->
tokenize(Rest, Line, Scope, [{ kw_identifier, Line, Op }|Tokens]);
handle_comp_op(Rest, Line, Op, Scope, Tokens) ->
tokenize(Rest, Line, Scope, add_token_with_nl({ comp_op, Line, Op }, Tokens)).
handle_op([$:|Rest], Line, Op, Scope, Tokens) when hd(Rest) /= $: ->
verify_kw_and_space(Line, Op, Rest, Scope),
handle_op([$:|Rest], Line, Op, Scope, Tokens) when ?is_space(hd(Rest)) ->
tokenize(Rest, Line, Scope, [{ kw_identifier, Line, Op }|Tokens]);
handle_op(Rest, Line, Op, Scope, Tokens) when ?unary_op(Op) ->
@@ -477,7 +478,7 @@ handle_op(Rest, Line, Op, Scope, Tokens) ->
tokenize(Rest, Line, Scope, add_token_with_nl({ Op, Line }, Tokens)).
handle_call_identifier(Rest, Line, Op, Scope, Tokens) ->
Token = tokenize_call_identifier(identifier, Line, Op, Rest),
Token = check_call_identifier(identifier, Line, Op, Rest),
tokenize(Rest, Line, Scope, [Token|add_token_with_nl({ '.', Line }, Tokens)]).
%% Helpers
@@ -501,7 +502,7 @@ collect_modifiers([H|T], Buffer) when ?is_downcase(H) ->
collect_modifiers(Rest, Buffer) ->
{ Rest, lists:reverse(Buffer) }.
% Extract heredocs
%% Heredocs
extract_heredoc_with_interpolation(Line, File, Interpol, T, H) ->
case extract_heredoc(Line, T, H) of
@@ -532,7 +533,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.
@@ -543,7 +544,7 @@ remove_heredoc_spaces(Body, 0) ->
remove_heredoc_spaces(Body, Spaces) ->
remove_heredoc_spaces([0|Body], [], Spaces, Spaces).
remove_heredoc_spaces([H,$\n|T], [Backtrack|Buffer], Spaces, Original) when Spaces > 0, H == $\s orelse H == $\t ->
remove_heredoc_spaces([H,$\n|T], [Backtrack|Buffer], Spaces, Original) when Spaces > 0, ?is_horizontal_space(H) ->
remove_heredoc_spaces([Backtrack,$\n|T], Buffer, Spaces - 1, Original);
remove_heredoc_spaces([$\n=H|T], Buffer, _Spaces, Original) ->
@@ -585,7 +586,7 @@ extract_heredoc_line(_, _) ->
%% Extract each heredoc line trying to find a match according to the marker.
extract_heredoc_line(Marker, [H|T], Buffer, Counter) when H == $\t; H == $\s ->
extract_heredoc_line(Marker, [H|T], Buffer, Counter) when ?is_horizontal_space(H) ->
extract_heredoc_line(Marker, T, [H|Buffer], Counter + 1);
extract_heredoc_line(Marker, [Marker,Marker,Marker|T] = Rest, Buffer, Counter) ->
@@ -597,9 +598,9 @@ extract_heredoc_line(Marker, [Marker,Marker,Marker|T] = Rest, Buffer, Counter) -
extract_heredoc_line(_Marker, Rest, Buffer, _Counter) ->
extract_heredoc_line(Rest, Buffer).
next_is_break([H|T]) when H == $\t; H == $\s -> next_is_break(T);
next_is_break([$\r,$\n|T]) -> T;
next_is_break([$\n|T]) -> T;
next_is_break([H|T]) when ?is_horizontal_space(H) -> next_is_break(T);
next_is_break("\r\n" ++ T) -> T;
next_is_break("\n" ++ T) -> T;
next_is_break([]) -> [];
next_is_break(_) -> false.
@@ -615,121 +616,113 @@ merge_heredoc_extra([H|T], Buffer) ->
merge_heredoc_extra([], Buffer) ->
Buffer.
% Integers and floats
% At this point, we are at least sure the first digit is a number.
%% Integers and floats
%% At this point, we are at least sure the first digit is a number.
% Check if we have a point followed by a number;
%% Check if we have a point followed by a number;
tokenize_number([$.,H|T], Acc, false) when ?is_digit(H) ->
tokenize_number(T, [H,$.|Acc], true);
% Check if we have an underscore followed by a number;
%% Check if we have an underscore followed by a number;
tokenize_number([$_,H|T], Acc, Bool) when ?is_digit(H) ->
tokenize_number(T, [H|Acc], Bool);
% Check if we have e- followed by numbers. Valid only for floats.
%% Check if we have e- followed by numbers (valid only for floats);
tokenize_number([$e,S,H|T], Acc, true) when ?is_digit(H), S == $+ orelse S == $- ->
tokenize_number(T, [H,S,$e|Acc], true);
% Check if we have e followed by numbers. Valid only for floats.
%% Check if we have e followed by numbers (valid only for floats);
tokenize_number([$e,H|T], Acc, true) when ?is_digit(H) ->
tokenize_number(T, [H,$e|Acc], true);
% Just numbers;
%% Finally just numbers.
tokenize_number([H|T], Acc, Bool) when ?is_digit(H) ->
tokenize_number(T, [H|Acc], Bool);
% Cast to float...
%% Cast to float...
tokenize_number(Rest, Acc, true) ->
{ Rest, list_to_float(lists:reverse(Acc)) };
% Or integer.
%% Or integer.
tokenize_number(Rest, Acc, false) ->
{ Rest, list_to_integer(lists:reverse(Acc)) }.
% Hex
tokenize_hex([H|T], Acc) when ?is_hex(H) -> tokenize_hex(T, [H|Acc]);
tokenize_hex(Rest, Acc) -> { Rest, list_to_integer(lists:reverse(Acc), 16) }.
% Octal
tokenize_octal([H|T], Acc) when ?is_octal(H) -> tokenize_octal(T, [H|Acc]);
tokenize_octal(Rest, Acc) -> { Rest, list_to_integer(lists:reverse(Acc), 8) }.
% Bin
tokenize_bin([H|T], Acc) when ?is_bin(H) -> tokenize_bin(T, [H|Acc]);
tokenize_bin(Rest, Acc) -> { Rest, list_to_integer(lists:reverse(Acc), 2) }.
% Comments
%% Comments
tokenize_comment("\r\n" ++ _ = Rest) -> Rest;
tokenize_comment("\n" ++ _ = Rest) -> Rest;
tokenize_comment([_|Rest]) -> tokenize_comment(Rest);
tokenize_comment([]) -> [].
% Identifiers
%% Atoms
%% Handle atoms specially since they support @
% Tokenize identifier. At this point, the validity of
% the first character was already verified.
tokenize_atom([H|T], Acc, Scope) when ?is_atom(H) ->
tokenize_atom(T, [H|Acc], Scope);
tokenize_identifier([H|T], Acc, Marker) when Marker == atom, H == $@;
?is_digit(H); ?is_upcase(H); ?is_downcase(H); H == $_ ->
tokenize_identifier(T, [H|Acc], Marker);
tokenize_atom([H|T], Acc, Scope) when H == $?; H == $! ->
{ T, unsafe_to_atom(lists:reverse([H|Acc]), Scope) };
tokenize_identifier([], Acc, _Marker) ->
{ [], lists:reverse(Acc) };
tokenize_atom(Rest, Acc, Scope) ->
{ Rest, unsafe_to_atom(lists:reverse(Acc), Scope) }.
tokenize_identifier(Rest, Acc, _Marker) ->
%% Identifiers
%% At this point, the validity of the first character was already verified.
tokenize_identifier([H|T], Acc) when ?is_identifier(H) ->
tokenize_identifier(T, [H|Acc]);
tokenize_identifier(Rest, Acc) ->
{ Rest, lists:reverse(Acc) }.
% Tokenize atom identifier, which also accepts punctuated identifiers
tokenize_atom(String, Acc, Scope) ->
{ Rest, Identifier } = tokenize_identifier(String, Acc, atom),
case Rest of
[H|T] when H == $?; H == $! ->
{ T, unsafe_to_atom(Identifier ++ [H], Scope) };
_ ->
{ Rest, unsafe_to_atom(Identifier, Scope) }
end.
%% Tokenize any identifier, handling kv, punctuated, paren, bracket and do identifiers.
% Tokenize any identifier, handling kv, punctuated, paren, bracket and do identifiers.
tokenize_any_identifier(String, Line, Acc, Scope) ->
{ Rest, Identifier } = tokenize_identifier(String, Acc, false),
{ Rest, Identifier } = tokenize_identifier(String, Acc),
case Rest of
[H,$:|T] when H == $? orelse H == $!, hd(T) /= $: ->
Atom = unsafe_to_atom(Identifier ++ [H], Scope),
verify_kw_and_space(Line, Atom, T, Scope),
{ T, { kw_identifier, Line, Atom } };
[H|T] when H == $?; H == $! ->
Atom = unsafe_to_atom(Identifier ++ [H], Scope),
{ T, tokenize_call_identifier(punctuated_identifier, Line, Atom, T) };
[$:|T] when hd(T) /= $: ->
tokenize_kw_or_call_identifier(punctuated_identifier, Line, Atom, T);
_ ->
Atom = unsafe_to_atom(Identifier, Scope),
verify_kw_and_space(Line, Atom, T, Scope),
{ T, { kw_identifier, Line, Atom } };
_ ->
{ Rest, tokenize_call_identifier(identifier, Line, unsafe_to_atom(Identifier, Scope), Rest) }
tokenize_kw_or_call_identifier(identifier, Line, Atom, Rest)
end.
tokenize_call_identifier(Kind, Line, Atom, Rest) ->
case Rest of
[$(|_] -> { paren_identifier, Line, Atom };
[$[|_] -> { bracket_identifier, Line, Atom };
_ ->
case next_is_block(Rest) of
false -> { Kind, Line, Atom };
BlockIdentifier -> { BlockIdentifier, Line, Atom }
end
end.
%% Tokenize kw or call identifier (paren | bracket | do)
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]).
tokenize_kw_or_call_identifier(_Kind, Line, Atom, [$:,H|T]) when ?is_space(H) ->
{ [H|T], { kw_identifier, Line, Atom } };
tokenize_kw_or_call_identifier(_Kind, Line, Atom, [$:,H|_]) when ?is_new_atom(H) ->
{ error, { Line, "keyword argument must be followed by space after: ", atom_to_list(Atom) ++ [$:] } };
tokenize_kw_or_call_identifier(Kind, Line, Atom, Rest) ->
{ Rest, check_call_identifier(Kind, Line, Atom, Rest) }.
%% Check if it is a call identifier (paren | bracket | do)
check_call_identifier(_Kind, Line, Atom, [$(|_]) -> { paren_identifier, Line, Atom };
check_call_identifier(_Kind, Line, Atom, [$[|_]) -> { bracket_identifier, Line, Atom };
check_call_identifier(Kind, Line, Atom, Rest) ->
case next_is_block(Rest) of
false -> { Kind, Line, Atom };
BlockIdentifier -> { BlockIdentifier, Line, Atom }
end.
next_is_block([Space|Tokens]) when Space == $\t; Space == $\s ->
next_is_block(Tokens);
next_is_block([$d,$o,H|_]) when
?is_digit(H); ?is_upcase(H); ?is_downcase(H); H == $_; H == $: ->
next_is_block([$d,$o,H|_]) when ?is_identifier(H); ?is_terminator(H) ->
false;
next_is_block([$d,$o|_]) ->
@@ -741,21 +734,18 @@ next_is_block(_) ->
add_token_with_nl(Left, [{eol,_,newline}|T]) -> [Left|T];
add_token_with_nl(Left, T) -> [Left|T].
% Error handling
%% 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
%% Terminators
string_type($") -> bin_string;
string_type($') -> list_string.
sigil_terminator($() -> $);
sigil_terminator($[) -> $];
sigil_terminator(${) -> $};
sigil_terminator($<) -> $>;
sigil_terminator(O) -> O.
handle_terminator(Rest, Line, Scope, Token, Tokens) ->
case handle_terminator(Token, Scope) of
{ error, _ } = Error -> Error;
New -> tokenize(Rest, Line, New, [Token|Tokens])
end.
handle_terminator(_, #scope{check_terminators=false} = Scope) ->
Scope;
@@ -788,7 +778,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
@@ -798,6 +788,15 @@ check_terminator({ E, Line }, []) when
check_terminator(_, Terminators) ->
Terminators.
string_type($") -> bin_string;
string_type($') -> list_string.
sigil_terminator($() -> $);
sigil_terminator($[) -> $];
sigil_terminator(${) -> $};
sigil_terminator($<) -> $>;
sigil_terminator(O) -> O.
terminator('fn') -> 'end';
terminator('do') -> 'end';
terminator('(') -> ')';
@@ -805,31 +804,43 @@ terminator('[') -> ']';
terminator('{') -> '}';
terminator('<<') -> '>>'.
% Keywords check
handle_keyword(Line, Identifier, Atom, [{ '.', _ }|_] = Tokens) ->
[{ Identifier, Line, Atom }|Tokens];
%% Keywords checking
handle_keyword(Line, Identifier, Atom, Tokens) when
check_keyword(Line, Identifier, Atom, [{ '.', _ }|_] = Tokens) ->
{ ok, [{ Identifier, Line, Atom }|Tokens] };
check_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.
check_keyword(_, _, _, _) -> nomatch.
% Keywords
do_keyword_valid([{ Atom, _ }|_]) ->
is_boolean(keyword(Atom));
do_keyword_valid(_) -> true.
% Regular 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;
+216 -170
View File
@@ -7,16 +7,18 @@
-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}).
forms(String, StartLine, File, Opts) ->
try elixir_tokenizer:tokenize(String, StartLine, [{ file, File }|Opts]) of
{ ok, Tokens } ->
case elixir_parser:parse(Tokens) of
try elixir_parser:parse(Tokens) of
{ ok, Forms } -> { ok, Forms };
{ error, { Line, _, [Error, Token] } } -> { error, { Line, Error, Token } }
catch
{ error, { Line, _, [Error, Token] } } -> { error, { Line, Error, Token } }
end;
{ error, { _, _, _ } } = Else -> Else
catch
@@ -38,7 +40,7 @@ translate(Forms, S) ->
%% Assignment operator
translate_each({'=', Meta, [Left, Right]}, S) ->
translate_each({ '=', Meta, [Left, Right] }, S) ->
assert_no_guard_scope(Meta, '=', S),
{ TRight, SR } = translate_each(Right, S),
{ TLeft, SL } = elixir_clauses:assigns(fun translate_each/2, Left, SR),
@@ -61,6 +63,9 @@ translate_each({ '__block__', Meta, Args }, S) when is_list(Args) ->
{ TArgs, NS } = translate(Args, S),
{ { block, ?line(Meta), TArgs }, NS };
translate_each({ '__scope__', _Meta, [[{erl,true}],[{do,Expr}]] }, S) ->
{ Expr, S };
translate_each({ '__scope__', _Meta, [[{file,File}],[{do,Expr}]] }, S) ->
Old = S#elixir_scope.file,
{ TExpr, TS } = translate_each(Expr, S#elixir_scope{file=File}),
@@ -94,42 +99,13 @@ 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),
validate_opts(Meta, alias, [as], KV, S),
assert_no_match_or_guard_scope(Meta, alias, S),
{ TRef, SR } = translate_each(Ref, S),
{ TKV, ST } = translate_opts(Meta, alias, [as], no_alias_opts(KV), SR),
case TRef of
{ atom, _, Old } ->
{ New, SF } = case lists:keyfind(as, 1, KV) of
Opt when Opt == { as, true }; Opt == false ->
{ elixir_aliases:last(Old), SR };
{ as, false } ->
{ Old, SR };
{ as, Other } ->
{ TOther, SA } = translate_each(Other, SR#elixir_scope{aliases=[]}),
case TOther of
{ atom, _, Atom } -> { Atom, SA };
_ -> syntax_error(Meta, S#elixir_scope.file,
"invalid args for alias, expected an atom or alias as argument")
end
end,
%% Avoid creating aliases if first == last
%% unecessarily polluting the aliases dict
case New == Old of
true -> { { nil, ?line(Meta) }, SF };
false ->
case string:tokens(atom_to_list(New), "-") of
[_,_] -> [];
_ -> syntax_error(Meta, S#elixir_scope.file,
"invalid args for alias, cannot create nested alias ~s", [elixir_errors:inspect(New)])
end,
{ { nil, ?line(Meta) }, SF#elixir_scope{
aliases=orddict:store(New, Old, S#elixir_scope.aliases)
} }
end;
translate_alias(Meta, true, Old, TKV, ST);
_ ->
syntax_error(Meta, S#elixir_scope.file, "invalid args for alias, expected an atom or alias as argument")
end;
@@ -138,25 +114,15 @@ 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),
validate_opts(Meta, require, [as], KV, S),
assert_no_match_or_guard_scope(Meta, require, S),
{ TRef, SR } = translate_each(Ref, S),
As = case lists:keyfind(as, 1, KV) of
false -> false;
{ as, Value } -> Value
end,
{ TKV, ST } = translate_opts(Meta, require, [as], no_alias_opts(KV), SR),
case TRef of
{ atom, _, Old } ->
elixir_aliases:ensure_loaded(Meta, Old, SR),
SF = SR#elixir_scope{
requires=ordsets:add_element(Old, S#elixir_scope.requires)
},
translate_each({ alias, Meta, [Ref, [{ as, As }]] }, SF);
elixir_aliases:ensure_loaded(Meta, Old, ST),
translate_require(Meta, Old, TKV, ST);
_ ->
syntax_error(Meta, S#elixir_scope.file, "invalid args for require, expected an atom or alias as argument")
end;
@@ -167,34 +133,34 @@ translate_each({ import, Meta, [Left] }, S) ->
translate_each({ import, Meta, [Left,Opts] }, S) when is_list(Opts) ->
translate_each({ import, Meta, [default, Left, Opts]}, S);
translate_each({ import, Meta, [Selector, Left] }, S) ->
translate_each({ import, Meta, [Selector, Left, []]}, S);
translate_each({ import, Meta, [Left,Right] }, S) ->
%% Second argument is ambiguous, translate it and take a peek
case translate_each(Right, S) of
{ { atom, _, _ }, _ } ->
translate_each({ import, Meta, [Left, Right, []]}, S);
_ ->
translate_each({ import, Meta, [default, Left, Right]}, S)
end;
translate_each({ import, Meta, [Left, Right, Opts] }, S) ->
assert_no_assign_or_guard_scope(Meta, 'import', S),
translate_each({ import, Meta, [Left, Right, KV] }, S) ->
assert_no_match_or_guard_scope(Meta, import, S),
{ TSelector, SL } = translate_each(Left, S),
{ TRef, SR } = translate_each(Right, SL),
{ TKV, ST } = translate_opts(Meta, import, [as, only, except, warn], no_alias_opts(KV), SR),
Selector = case TSelector of
{ atom, _, SelectorAtom } -> SelectorAtom;
_ -> syntax_error(Meta, S#elixir_scope.file, "invalid selector for import, expected an atom")
end,
Ref = case TRef of
{ atom, _, RefAtom } -> RefAtom;
_ -> syntax_error(Meta, S#elixir_scope.file, "invalid name for import, expected an atom or alias")
end,
validate_opts(Meta, import, [as, only, except], Opts, S),
As = case lists:keyfind(as, 1, Opts) of
false -> false;
{ as, Value } -> Value
end,
elixir_aliases:ensure_loaded(Meta, Ref, SR),
SF = elixir_import:import(Meta, Ref, Opts, Selector, SR),
translate_each({ require, Meta, [Ref, [{ as, As }]] }, SF);
case TRef of
{ atom, _, Old } ->
elixir_aliases:ensure_loaded(Meta, Old, ST),
SF = elixir_import:import(Meta, Old, TKV, Selector, ST),
translate_require(Meta, Old, TKV, SF);
_ ->
syntax_error(Meta, S#elixir_scope.file, "invalid name for import, expected an atom or alias")
end;
%% Pseudo variables
@@ -204,8 +170,12 @@ translate_each({ '__MODULE__', Meta, Atom }, S) when is_atom(Atom) ->
translate_each({ '__FILE__', _Meta, Atom }, S) when is_atom(Atom) ->
translate_each(S#elixir_scope.file, S);
translate_each({ '__DIR__', _Meta, Atom }, S) when is_atom(Atom) ->
translate_each(filename:dirname(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:elixir_to_erl(Env), S };
translate_each({ '__CALLER__', Meta, Atom }, S) when is_atom(Atom) ->
{ { var, ?line(Meta), '__CALLER__' }, S#elixir_scope{caller=true} };
@@ -213,8 +183,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),
@@ -223,35 +193,55 @@ translate_each({ '__aliases__', Meta, _ } = Alias, S) ->
Atoms = [Atom || { atom, _, Atom } <- TAliases],
{ { atom, ?line(Meta), elixir_aliases:concat(Atoms) }, SA };
false ->
Args = [elixir_tree_helpers:build_simple_list(?line(Meta), TAliases)],
Args = [elixir_tree_helpers:list_to_cons(?line(Meta), TAliases)],
{ ?wrap_call(?line(Meta), elixir_aliases, concat, Args), SA }
end
end;
%% 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) ->
translate_each({ quote, Meta, [orddict:from_list(Left ++ Right)] }, S);
translate_each({ quote, Meta, [Opts] }, S) when is_list(Opts) ->
case lists:keyfind(do, 1, Opts) of
{ do, Do } ->
translate_each({ quote, Meta, [lists:keydelete(do, 1, Opts), [{do,Do}]] }, S);
false ->
syntax_error(Meta, S#elixir_scope.file, "invalid args for quote, do is missing")
end;
translate_each({ quote, Meta, [T] }, S) when is_list(T) ->
translate_each({ quote, Meta, [_] }, S) ->
syntax_error(Meta, S#elixir_scope.file, "invalid args for quote");
translate_each({ quote, Meta, [KV, Do] }, S) when is_list(Do) ->
Exprs =
case lists:keyfind(do, 1, T) of
case lists:keyfind(do, 1, Do) of
{ do, E } -> E;
false ->
syntax_error(Meta, S#elixir_scope.file, "invalid args for quote")
false -> 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);
ValidOpts = [hygiene, context, var_context, location, line, file, unquote],
{ TKV, ST } = translate_opts(Meta, quote, ValidOpts, KV, S),
Hygiene = case lists:keyfind(hygiene, 1, TKV) of
{ hygiene, List } when is_list(List) ->
List;
false ->
case lists:keyfind(hygiene, 1, T) of
{ hygiene, false } -> nil;
_ ->
[]
end,
Context = case lists:keyfind(var_context, 1, TKV) of
{ var_context, VarContext } ->
elixir_errors:deprecation(Meta, S#elixir_scope.file, "var_context in quote is deprecated, please use context instead"),
VarContext;
false ->
case lists:keyfind(context, 1, TKV) of
{ context, Atom } when is_atom(Atom) ->
Atom;
{ context, _ } ->
syntax_error(Meta, S#elixir_scope.file, "invalid :context for quote, expected an atom or an alias");
false ->
case S#elixir_scope.module of
nil -> 'Elixir';
Mod -> Mod
@@ -259,38 +249,21 @@ translate_each({ quote, Meta, [T] }, S) when is_list(T) ->
end
end,
GetExpansion = fun(Key) ->
case lists:keyfind(Key, 1, T) of
{ Key, Bool } when is_boolean(Bool) ->
Bool;
false ->
case lists:keyfind(hygiene, 1, T) of
{ hygiene, Bool } when is_boolean(Bool) -> Bool;
false -> true
end
end
end,
Vars = lists:keyfind(vars, 1, Hygiene) /= { vars, false },
Aliases = lists:keyfind(aliases, 1, Hygiene) /= { aliases, false },
Imports = lists:keyfind(imports, 1, Hygiene) /= { imports, false },
Aliases = GetExpansion(expand_aliases),
Imports = GetExpansion(expand_imports),
{ DefaultLine, DefaultFile } = case lists:keyfind(location, 1, T) of
{ location, keep } -> { keep, keep };
{ DefaultLine, DefaultFile } = case lists:keyfind(location, 1, TKV) of
{ location, keep } -> { keep, keep };
false -> { nil, nil }
end,
Line = case lists:keyfind(line, 1, T) of
Line = case lists:keyfind(line, 1, TKV) of
{ line, LineValue } -> LineValue;
false -> DefaultLine
end,
{ TLine, SL } = case Line of
keep -> { keep, S };
nil -> { nil, S };
_ -> translate_each(Line, S)
end,
File = case lists:keyfind(file, 1, T) of
File = case lists:keyfind(file, 1, TKV) of
{ file, FileValue } -> FileValue;
false -> DefaultFile
end,
@@ -300,7 +273,7 @@ translate_each({ quote, Meta, [T] }, S) when is_list(T) ->
_ -> File
end,
TExprs = if
WExprs = if
is_binary(TFile) ->
{ '__scope__', Meta, [[{file,TFile}],[{do,Exprs}]] };
File == nil ->
@@ -309,17 +282,18 @@ translate_each({ quote, Meta, [T] }, S) when is_list(T) ->
syntax_error(Meta, S#elixir_scope.file, "invalid args for quote, expected :file to be a binary")
end,
Unquote = case lists:keyfind(unquote, 1, T) of
Unquote = case lists:keyfind(unquote, 1, TKV) of
{ unquote, false } -> false;
_ -> 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=Line, unquote=Unquote,
aliases_hygiene=Aliases, imports_hygiene=Imports, context=Context},
elixir_quote:quote(TExprs, Q, SL);
{ TExprs, _TQ, TS } = elixir_quote:erl_quote(WExprs, Q, ST),
{ TExprs, TS };
translate_each({ quote, Meta, [_] }, S) ->
translate_each({ quote, Meta, [_, _] }, S) ->
syntax_error(Meta, S#elixir_scope.file, "invalid args for quote");
translate_each({ 'alias!', _Meta, [Arg] }, S) ->
@@ -332,7 +306,7 @@ translate_each({ 'var!', Meta, [{Name, _, Atom}, Kind] }, S) when is_atom(Name),
translate_each({ 'var!', Meta, [Name, Kind] }, S);
translate_each({ 'var!', Meta, [Name, Kind] }, S) when is_atom(Name) ->
Expanded = expand_var_context(Meta, Kind, "invalid second argument for var!", S),
Expanded = expand_quote_context(Meta, Kind, "invalid second argument for var!", S),
elixir_scope:translate_var(Meta, Name, Expanded, S, fun() ->
syntax_error(Meta, S#elixir_scope.file, "expected var!(~ts) to expand to an existing variable or be a part of a match", [Name])
end);
@@ -343,7 +317,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 +330,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 +359,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 +388,23 @@ 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 ->
syntax_error(Meta, S#elixir_scope.file, "cannot invoke function ~ts/~B inside match", [Atom, length(Args)]);
guard ->
Arity = length(Args),
File = S#elixir_scope.file,
case Arity of
0 -> syntax_error(Meta, File, "unknown variable ~ts or cannot invoke function ~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
@@ -457,9 +427,23 @@ translate_each({ { '.', _, [Left, Right] }, Meta, Args } = Original, S) when is_
case TLeft of
{ atom, _, Receiver } ->
elixir_dispatch:dispatch_require(Meta, Receiver, Right, Args, umergev(SL, SR), Callback);
elixir_dispatch:dispatch_require(Meta, Receiver, Right, Args, umergev(SL, SR), fun() ->
case S#elixir_scope.context of
Context when Receiver /= erlang, (Context == match) orelse (Context == guard) ->
syntax_error(Meta, S#elixir_scope.file, "cannot invoke remote function ~ts.~ts/~B inside ~ts",
[elixir_errors:inspect(Receiver), Right, length(Args), Context]);
_ ->
Callback()
end
end);
_ ->
Callback()
case S#elixir_scope.context of
Context when Context == match; Context == guard ->
syntax_error(Meta, S#elixir_scope.file, "cannot invoke remote function ~ts/~B inside ~ts",
[Right, length(Args), Context]);
_ ->
Callback()
end
end;
Else -> Else
end;
@@ -472,7 +456,7 @@ translate_each({ { '.', _, [Expr] }, Meta, Args } = Original, S) ->
{ TExpr, SE } = translate_each(Expr, S),
case TExpr of
{ atom, _, Atom } ->
translate_each({ Atom, Meta, Args }, S);
syntax_error(Meta, S#elixir_scope.file, "invalid function call :~ts.()", [Atom]);
_ ->
case elixir_partials:handle(Original, S) of
error ->
@@ -495,6 +479,14 @@ translate_each(Literal, S) ->
%% Helpers
%% Opts
translate_opts(Meta, Kind, Allowed, Opts, S) ->
{ TOpts, TS } = translate_each(Opts, S),
FOpts = elixir_tree_helpers:erl_to_elixir(TOpts),
validate_opts(Meta, Kind, Allowed, FOpts, TS),
{ FOpts, TS }.
validate_opts(Meta, Kind, Allowed, Opts, S) when is_list(Opts) ->
[begin
syntax_error(Meta, S#elixir_scope.file, "unsupported option ~s given to ~s", [Key, Kind])
@@ -503,6 +495,69 @@ validate_opts(Meta, Kind, Allowed, Opts, S) when is_list(Opts) ->
validate_opts(Meta, Kind, _Allowed, _Opts, S) ->
syntax_error(Meta, S#elixir_scope.file, "invalid options for ~s, expected a keyword list", [Kind]).
%% Quote
expand_quote_context(_Meta, Atom, _Msg, _S) when is_atom(Atom) -> Atom;
expand_quote_context(Meta, Alias, Msg, S) ->
case translate_each(Alias, S) of
{ { atom, _, Atom }, _ } ->
Atom;
_ ->
syntax_error(Meta, S#elixir_scope.file, "~ts, expected a compile time available alias or an atom", [Msg])
end.
%% Require
translate_require(Meta, Old, TKV, S) ->
SF = S#elixir_scope{
requires=ordsets:add_element(Old, S#elixir_scope.requires)
},
translate_alias(Meta, false, Old, TKV, SF).
%% Aliases
translate_alias(Meta, IncludeByDefault, Old, TKV, S) ->
New = case lists:keyfind(as, 1, TKV) of
{ as, true } ->
elixir_aliases:last(Old);
{ as, false } ->
Old;
{ as, Atom } when is_atom(Atom) ->
Atom;
false ->
if
IncludeByDefault -> elixir_aliases:last(Old);
true -> Old
end;
_ ->
syntax_error(Meta, S#elixir_scope.file,
"invalid args for alias, expected an atom or alias in option :as")
end,
case (New == Old) orelse (length(string:tokens(atom_to_list(New), "-")) == 2) of
true -> ok;
false -> syntax_error(Meta, S#elixir_scope.file,
"invalid args for alias, cannot create nested alias ~s", [elixir_errors:inspect(New)])
end,
{ { atom, ?line(Meta), nil }, elixir_aliases:store(Meta, New, Old, S) }.
no_alias_opts(KV) when is_list(KV) ->
case lists:keyfind(as, 1, KV) of
{ as, As } -> lists:keystore(as, 1, KV, { as, no_alias_expansion(As) });
false -> KV
end;
no_alias_opts(KV) -> KV.
no_alias_expansion({ '__aliases__', Meta, [H|T] }) when (H /= 'Elixir') and is_atom(H) ->
{ '__aliases__', Meta, ['Elixir',H|T] };
no_alias_expansion(Other) ->
Other.
%% Function
translate_fn(Meta, Clauses, S) ->
Line = ?line(Meta),
@@ -521,28 +576,16 @@ translate_fn(Meta, Clauses, S) ->
syntax_error(Meta, S#elixir_scope.file, "cannot mix clauses with different arities in function definition")
end.
translate_local(Meta, Name, Args, #elixir_scope{local=nil} = S) ->
%% Locals
translate_local(Meta, Name, Args, #elixir_scope{local=nil,module=Module} = S) ->
elixir_import:record({ Name, length(Args) }, Module, Module),
Line = ?line(Meta),
{ TArgs, NS } = translate_args(Args, 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 }.
expand_var_context(_Meta, Atom, _Msg, _S) when is_atom(Atom) -> Atom;
expand_var_context(Meta, Alias, Msg, S) ->
case translate_each(Alias, S) of
{ { atom, _, Atom }, _ } ->
Atom;
_ ->
syntax_error(Meta, S#elixir_scope.file, "~ts, expected a compile time available alias or an atom", [Msg])
end.
translate_each({ { '.', Meta, [S#elixir_scope.local, Name] }, Meta, Args }, S).
%% Translate args
@@ -563,9 +606,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 +684,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),
+44 -41
View File
@@ -4,10 +4,9 @@
-module(elixir_tree_helpers).
-compile({parse_transform, elixir_transform}).
-export([abstract_syntax/1, split_last/1, cons_to_list/1,
convert_to_boolean/5, returns_boolean/1, get_line/1,
build_list/4, build_list/5, build_simple_list/2,
build_reverse_list/4, build_reverse_list/5, build_simple_reverse_list/2]).
-export([elixir_to_erl/1, erl_to_elixir/1, split_last/1,
cons_to_list/1, list_to_cons/2, list_to_cons/3,
convert_to_boolean/5, returns_boolean/1, get_line/1]).
-include("elixir.hrl").
get_line(Opts) ->
@@ -21,53 +20,57 @@ split_last(List) -> split_last(List, []).
split_last([H], Acc) -> { lists:reverse(Acc), H };
split_last([H|T], Acc) -> split_last(T, [H|Acc]).
abstract_syntax(Tree) ->
erl_syntax:revert(erl_syntax:abstract(Tree)).
cons_to_list({ nil, _ }) -> [];
cons_to_list({ cons, _, Left, Right }) -> [Left|cons_to_list(Right)].
cons_to_list({ cons, _, Left, { nil, _ } }) ->
[Left];
list_to_cons(Line, List) ->
list_to_cons(Line, List, { nil, Line }).
cons_to_list({ cons, _, Left, Right }) ->
[Left|cons_to_list(Right)].
list_to_cons(Line, List, Tail) ->
lists:foldr(fun(X, Acc) ->
{ cons, Line, X, Acc }
end, Tail, List).
% Build a list translating each expression and accumulating
% vars in one pass. It uses tail-recursive form.
%
% It receives a function to translate each expression given
% in Exprs, a Line used to build the List and the variables
% scope V is passed down item by item.
%
% The function needs to return a tuple where the first element
% is an erlang abstract form and the second is the new variables
% list.
build_list(Fun, Exprs, Line, S) ->
build_list(Fun, Exprs, Line, S, {nil, Line}).
%% erl <-> elixir
build_list(Fun, Exprs, Line, S, Tail) when is_integer(Line) ->
build_list_each(Fun, lists:reverse(Exprs), Line, S, Tail).
elixir_to_erl(Tree) when is_tuple(Tree) ->
{ tuple, 0, [elixir_to_erl(X) || X <- tuple_to_list(Tree)] };
% Same as build_list, but the list given is in reverse other.
build_reverse_list(Fun, Exprs, Line, S) ->
build_list_each(Fun, Exprs, Line, S, {nil,Line}).
elixir_to_erl(Tree) when is_list(Tree) ->
elixir_to_erl_cons(lists:reverse(Tree), { nil, 0 });
build_reverse_list(Fun, Exprs, Line, S, Tail) when is_integer(Line) ->
build_list_each(Fun, Exprs, Line, S, Tail).
elixir_to_erl(Tree) when is_atom(Tree) ->
{ atom, 0, Tree };
% Builds a simple list, without translatation, just by generating the cons-cell.
build_simple_list(Line, Args) ->
{ List, [] } = build_list(fun(X,Y) -> {X,Y} end, Args, Line, []),
List.
elixir_to_erl(Tree) when is_integer(Tree) ->
{ integer, 0, Tree };
build_simple_reverse_list(Line, Args) ->
{ List, [] } = build_reverse_list(fun(X,Y) -> {X,Y} end, Args, Line, []),
List.
elixir_to_erl(Tree) when is_float(Tree) ->
{ float, 0, Tree };
build_list_each(_Fun, [], _Line, S, Acc) ->
{ Acc, S };
elixir_to_erl(Tree) when is_binary(Tree) ->
{ bin, 0, [{ bin_element, 0, { string, 0, binary_to_list(Tree) }, default, default }] }.
build_list_each(Fun, [H|T], Line, S, Acc) ->
{ Expr, NS } = Fun(H, S),
build_list_each(Fun, T, Line, NS, { cons, Line, Expr, Acc }).
elixir_to_erl_cons([H|T], Acc) ->
elixir_to_erl_cons(T, { cons, 0, elixir_to_erl(H), Acc });
elixir_to_erl_cons([], Acc) -> Acc.
erl_to_elixir({ tuple, _, Args }) ->
list_to_tuple([erl_to_elixir(X) || X <- Args]);
erl_to_elixir({ Kind, _, Value }) when Kind == atom; Kind == integer; Kind == float ->
Value;
erl_to_elixir({ bin, _, [{ bin_element, 0, { string, 0, String }, default, default }] }) ->
list_to_binary(String);
erl_to_elixir({ nil, _ }) -> [];
erl_to_elixir({ cons, _, Left, Right }) -> [erl_to_elixir(Left)|erl_to_elixir(Right)];
erl_to_elixir(Other) ->
{ '__scope__', [], [[{erl,true}],[{do,Other}]] }.
%% Others
+10 -34
View File
@@ -49,7 +49,6 @@ each_clause(Meta, { rescue, [Condition|T], Expr }, S) ->
end
end;
_ ->
validate_rescue_access(Meta, Condition, S),
each_clause(Meta, { 'catch', [error, Condition|T], Expr }, S)
end;
@@ -57,7 +56,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
@@ -65,8 +64,12 @@ each_clause(Meta, {Key,_,_}, S) ->
normalize_rescue(Meta, List, S) when is_list(List) ->
normalize_rescue(Meta, { in, Meta, [{ '_', Meta, nil }, List] }, S);
%% rescue _
normalize_rescue(_, { '_', _, Atom }, _S) when is_atom(Atom) ->
false;
%% rescue var -> var in _
normalize_rescue(_, { Name, Meta, Atom } = Rescue, S) when is_atom(Name), is_atom(Atom), Name /= '_' ->
normalize_rescue(_, { Name, Meta, Atom } = Rescue, S) when is_atom(Name), is_atom(Atom) ->
normalize_rescue(Meta, { in, Meta, [Rescue, { '_', Meta, nil }] }, S);
%% rescue var in [Exprs]
@@ -85,22 +88,13 @@ normalize_rescue(_, { in, Meta, [Left, Right] }, S) ->
end
end;
normalize_rescue(_, { '=', Meta, [{ '__aliases__', _, _ } = Alias, { Name, _, Atom } = Var] }, S)
when is_atom(Name) and is_atom(Atom) ->
elixir_errors:handle_file_warning(S#elixir_scope.file, { Meta, ?MODULE, { rescue_no_match, Var, Alias } }),
false;
normalize_rescue(_, { '=', Meta, [{ Name, _, Atom } = Var, { '__aliases__', _, _ } = Alias] }, S)
when is_atom(Name) and is_atom(Atom) ->
elixir_errors:handle_file_warning(S#elixir_scope.file, { Meta, ?MODULE, { rescue_no_match, Var, Alias } }),
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);
_ ->
false
elixir_errors:syntax_error(Meta, S#elixir_scope.file, "invalid rescue clause. The clause should match on an alias, "
"a variable or be in the `var in [alias]` format")
end.
%% Convert rescue clauses into guards.
@@ -242,30 +236,12 @@ erlang_rescue_guard_for(Meta, Var, 'Elixir.ErlangError') ->
] },
{ 'or', Meta, [IsNotTuple, IsException] }.
%% Validate rescue access
validate_rescue_access(Meta, { '=', _, [Left, Right] }, S) ->
validate_rescue_access(Meta, Left, S),
validate_rescue_access(Meta, Right, S);
validate_rescue_access(Meta, { { '.', _, ['Elixir.Kernel', 'access'] }, _, [Element, _] }, S) ->
case elixir_translator:translate_each(Element, S) of
{ { atom, _, Atom }, _ } ->
case lists:member(Atom, erlang_rescues()) of
false -> [];
true -> elixir_errors:syntax_error(Meta, S#elixir_scope.file, "cannot (yet) pattern match against erlang exceptions")
end;
_ -> []
end;
validate_rescue_access(_, _, _) -> [].
%% Helpers
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
+1 -1
View File
@@ -1,4 +1,4 @@
Code.require_file "../test_helper.exs", __FILE__
Code.require_file "test_helper.exs", __DIR__
defmodule AccessTest do
use ExUnit.Case, async: true
+1 -1
View File
@@ -1,4 +1,4 @@
Code.require_file "../test_helper.exs", __FILE__
Code.require_file "test_helper.exs", __DIR__
defmodule BehaviourTest do
use ExUnit.Case, async: true
+6 -5
View File
@@ -1,4 +1,4 @@
Code.require_file "../../test_helper.exs", __FILE__
Code.require_file "../test_helper.exs", __DIR__
defmodule Binary.Chars.AtomTest do
use ExUnit.Case, async: true
@@ -50,10 +50,11 @@ defmodule Binary.Chars.NumberTest do
assert to_binary(100) == "100"
end
test :float do
assert to_binary(1.0) == "1.00000000000000000000e+00"
assert to_binary(1.0e10) == "1.00000000000000000000e+10"
assert to_binary(1.0e+10) == "1.00000000000000000000e+10"
unless :erlang.system_info(:otp_release) < 'R16' do
test :float do
assert to_binary(1.0) == "1.0"
assert to_binary(1.0e10) == "10000000000.0"
end
end
end
+16 -7
View File
@@ -1,4 +1,4 @@
Code.require_file "../../test_helper.exs", __FILE__
Code.require_file "../test_helper.exs", __DIR__
defmodule Binary.Inspect.AtomTest do
use ExUnit.Case, async: true
@@ -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
@@ -84,11 +86,12 @@ defmodule Binary.Inspect.NumberTest do
assert inspect(100) == "100"
end
test :float do
assert inspect(1.0) == "1.0"
assert inspect(1.0e10) == "1.0e10"
assert inspect(1.0e+10) == "1.0e10"
assert inspect(1.0e-10) == "1.0e-10"
unless :erlang.system_info(:otp_release) < 'R16' do
test :float do
assert inspect(1.0) == "1.0"
assert inspect(1.0e10) == "10000000000.0"
assert inspect(1.0e-10) == "0.0000000001"
end
end
end
@@ -129,6 +132,12 @@ defmodule Binary.Inspect.TupleTest do
assert inspect(RuntimeError.new) == "RuntimeError[message: \"runtime error\"]"
end
defrecord :something, [:a, :b]
test :non_module_record do
assert inspect(:something.new) == ":something[a: nil, b: nil]"
end
test :empty do
assert inspect({}) == "{}"
end
@@ -173,7 +182,7 @@ defmodule Binary.Inspect.ListTest do
end
test :codepoints do
assert inspect('é') == "[195,169]"
assert inspect('é') == "'é'"
end
test :empty do
+9 -1
View File
@@ -1,4 +1,4 @@
Code.require_file "../test_helper.exs", __FILE__
Code.require_file "test_helper.exs", __DIR__
defmodule Binary.LiteralTest do
use ExUnit.Case, async: true
@@ -98,6 +98,14 @@ bar
assert <<0,0,0,106,0,0,0,111,0,0,0,115,0,0,0,101>> == << 'jose' :: utf32 >>
end
@binary "new "
@charlist 'old '
test :bitsyntax_with_expansion do
assert <<@binary, "world">> == "new world"
assert <<@charlist, "world">> == "old world"
end
test :bitsyntax_translation do
refb = "sample"
sec_data = "another"
+1 -1
View File
@@ -1,4 +1,4 @@
Code.require_file "../test_helper.exs", __FILE__
Code.require_file "test_helper.exs", __DIR__
defmodule Bitwise.FunctionsTest do
use ExUnit.Case, async: true
+6 -7
View File
@@ -1,4 +1,4 @@
Code.require_file "../test_helper.exs", __FILE__
Code.require_file "test_helper.exs", __DIR__
defmodule CharListTest do
use ExUnit.Case, async: true
@@ -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
+13 -14
View File
@@ -1,4 +1,4 @@
Code.require_file "../test_helper.exs", __FILE__
Code.require_file "test_helper.exs", __DIR__
defmodule CodeTest do
use ExUnit.Case, async: true
@@ -14,17 +14,17 @@ defmodule CodeTest do
Code.eval_quoted contents, [], file: "sample.ex", line: 13
test :eval do
assert Code.eval("1 + 2") == { 3, [] }
assert { 3, _ } = Code.eval("a + b", [a: 1, b: 2], __ENV__.location)
test :eval_string do
assert Code.eval_string("1 + 2") == { 3, [] }
assert { 3, _ } = Code.eval_string("a + b", [a: 1, b: 2], __ENV__.location)
end
test :eval_with_scope do
assert Code.eval("one", [], delegate_locals_to: __MODULE__) == { 1, [] }
assert Code.eval_string("one", [], delegate_locals_to: __MODULE__) == { 1, [] }
end
test :eval_with_requires do
assert Code.eval("Kernel.if true, do: :ok", [], requires: [Z, Kernel]) == { :ok, [] }
assert Code.eval_string("Kernel.if true, do: :ok", [], requires: [Z, Kernel]) == { :ok, [] }
end
test :eval_quoted do
@@ -39,21 +39,21 @@ defmodule CodeTest do
test :require do
Code.require_file fixture_path("code_sample.exs")
assert fixture_path("code_sample.exs") inlist Code.loaded_files
assert fixture_path("code_sample.exs") in Code.loaded_files
assert Code.require_file(fixture_path("code_sample.exs")) == nil
Code.unload_files [fixture_path("code_sample.exs")]
refute fixture_path("code_sample.exs") inlist Code.loaded_files
refute fixture_path("code_sample.exs") in Code.loaded_files
assert Code.require_file(fixture_path("code_sample.exs")) != nil
end
test :path_manipulation do
path = Path.expand("../binary", __FILE__)
path = Path.join(__DIR__, "binary")
Code.prepend_path path
assert binary_to_list(path) inlist :code.get_path
assert binary_to_list(path) in :code.get_path
Code.delete_path path
refute binary_to_list(path) inlist :code.get_path
refute binary_to_list(path) in :code.get_path
end
test :file do
@@ -61,8 +61,7 @@ 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, { :+, [line: 1], [1, 2] } }
assert { :error, _ } = Code.string_to_ast("a.1")
end
@@ -72,7 +71,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") == { :+, [line: 1], [1, 2] }
assert_raise SyntaxError, fn ->
Code.string_to_ast!("a.1")
+87 -7
View File
@@ -1,4 +1,4 @@
Code.require_file "../test_helper.exs", __FILE__
Code.require_file "test_helper.exs", __DIR__
defmodule DictTest.Common do
defmacro __using__(module) do
@@ -10,7 +10,7 @@ defmodule DictTest.Common do
defmacrop dicts_equal(actual, expected) do
quote do
cmp = fn { k1, _ }, { k2, _ } -> k1 < k2 end
Enum.sort(expected, cmp) == Enum.sort(actual, cmp)
Enum.sort(unquote(expected), cmp) == Enum.sort(unquote(actual), cmp)
end
end
@@ -43,11 +43,17 @@ defmodule DictTest.Common do
assert "default" == Dict.get(empty_dict, "first_key", "default")
end
test :get! do
assert 1 == Dict.get!(new_dict, "first_key")
assert 2 == Dict.get!(new_dict, "second_key")
test :fetch do
assert { :ok, 1 } == Dict.fetch(new_dict, "first_key")
assert { :ok, 2 } == Dict.fetch(new_dict, "second_key")
assert :error == Dict.fetch(new_dict, "other_key")
end
test :fetch! do
assert 1 == Dict.fetch!(new_dict, "first_key")
assert 2 == Dict.fetch!(new_dict, "second_key")
assert_raise KeyError, fn ->
Dict.get!(new_dict, "other_key")
Dict.fetch!(new_dict, "other_key")
end
end
@@ -132,6 +138,71 @@ defmodule DictTest.Common do
assert "..." == Dict.get dict, "non-existent"
end
test :pop do
{v, actual} = Dict.pop(new_dict, "first_key")
assert 1 == v
assert dicts_equal actual, Dict.delete(new_dict, "first_key")
{v, actual} = Dict.pop(new_dict, "second_key")
assert 2 == v
assert dicts_equal actual, Dict.delete(new_dict, "second_key")
{v, actual} = Dict.pop(new_dict, "other_key")
assert nil == v
assert dicts_equal actual, new_dict
{v, actual} = Dict.pop(empty_dict, "first_key", "default")
assert "default" == v
assert dicts_equal actual, empty_dict
{v, actual} = Dict.pop(new_dict, "other_key", "default")
assert "default" == v
assert dicts_equal actual, new_dict
end
test :split do
split_keys = []
{take, drop} = Dict.split(new_dict, split_keys)
assert dicts_equal take, Dict.empty(new_dict)
assert dicts_equal drop, new_dict
split_keys = ["unknown_key"]
{take, drop} = Dict.split(new_dict, split_keys)
assert dicts_equal take, Dict.empty(new_dict)
assert dicts_equal drop, new_dict
split_keys = ["first_key", "second_key", "unknown_key"]
{take, drop} = Dict.split(new_dict, split_keys)
first_val = Dict.get(new_dict, "first_key")
second_val = Dict.get(new_dict, "second_key")
take_expected = Dict.empty(new_dict)
take_expected = Dict.put(take_expected, "first_key", first_val)
take_expected = Dict.put(take_expected, "second_key", second_val)
drop_expected = new_dict
drop_expected = Dict.delete(drop_expected, "first_key")
drop_expected = Dict.delete(drop_expected, "second_key")
assert dicts_equal take, take_expected
assert dicts_equal drop, drop_expected
end
test :take do
result = Dict.take(new_dict, ["unknown_key"])
assert dicts_equal result, Dict.empty(new_dict)
result = Dict.take(new_dict, ["first_key"])
first_val = Dict.get(new_dict, "first_key")
expected = Dict.put(Dict.empty(new_dict), "first_key", first_val)
assert dicts_equal result, expected
end
test :drop do
result = Dict.drop(new_dict, ["unknown_key"])
assert dicts_equal result, new_dict
result = Dict.drop(new_dict, ["first_key"])
assert dicts_equal result, Dict.delete(new_dict, "first_key")
end
test :empty do
assert empty_dict == Dict.empty new_dict
end
@@ -145,6 +216,15 @@ defmodule DictTest.Common do
defp new_dict(list, transform) do
unquote(module).new list, transform
end
test :equal? do
dict1 = HashDict.new(a: 2, b: 3, f: 5, c: 123)
dict2 = ListDict.new(a: 2, b: 3, f: 5, c: 123)
assert Dict.equal?(dict1, dict2)
dict2 = Dict.put(dict2, :a, 3)
refute Dict.equal?(dict1, dict2)
end
end
end
end
@@ -154,5 +234,5 @@ defmodule Dict.HashDictTest do
end
defmodule Dict.ListTest do
use DictTest.Common, List.Dict
use DictTest.Common, ListDict
end
+102 -27
View File
@@ -1,8 +1,32 @@
Code.require_file "../test_helper.exs", __FILE__
Code.require_file "test_helper.exs", __DIR__
defmodule EnumTest.List do
use ExUnit.Case, async: true
test :empty? do
assert Enum.empty?([])
refute Enum.empty?([1,2,3])
refute Enum.empty?(1..3)
end
test :member? do
assert Enum.member?([1, 2, 3], 2)
refute Enum.member?([], 0)
refute Enum.member?([1, 2, 3], 0)
assert Enum.member?(1..3, 2)
refute Enum.member?(1..3, 0)
end
test :count do
assert Enum.count([1,2,3]) == 3
assert Enum.count([]) == 0
end
test :count_fun do
assert Enum.count([1,2,3], fn(x) -> rem(x, 2) == 0 end) == 1
assert Enum.count([], fn(x) -> rem(x, 2) == 0 end) == 0
end
test :all? do
assert Enum.all?([2,4,6], fn(x) -> rem(x, 2) == 0 end)
refute Enum.all?([2,3,4], fn(x) -> rem(x, 2) == 0 end)
@@ -23,24 +47,21 @@ defmodule EnumTest.List do
refute Enum.any?([])
end
test :at! do
assert Enum.at!([2,4,6], 0) == 2
assert Enum.at!([2,4,6], 2) == 6
test :at do
assert Enum.at([2,4,6], 0) == 2
assert Enum.at([2,4,6], 2) == 6
assert Enum.at([2,4,6], 4) == nil
assert Enum.at([2,4,6], 4, :none) == :none
end
test :fetch! do
assert Enum.fetch!([2,4,6], 0) == 2
assert Enum.fetch!([2,4,6], 2) == 6
assert_raise Enum.OutOfBoundsError, fn ->
Enum.at!([2,4,6], 4)
Enum.fetch!([2,4,6], 4)
end
end
test :count do
assert Enum.count([1,2,3]) == 3
assert Enum.count([]) == 0
end
test :count_fun do
assert Enum.count([1,2,3], fn(x) -> rem(x, 2) == 0 end) == 1
assert Enum.count([], fn(x) -> rem(x, 2) == 0 end) == 0
end
test :drop do
assert Enum.drop([1,2,3], 0) == [1,2,3]
assert Enum.drop([1,2,3], 1) == [2,3]
@@ -75,11 +96,6 @@ defmodule EnumTest.List do
assert Enum.find_index([2,3,4], fn(x) -> rem(x, 2) == 1 end) == 1
end
test :empty? do
assert Enum.empty?([])
refute Enum.empty?([1,2,3])
end
test :each do
try do
assert Enum.each([], fn(x) -> x end) == :ok
@@ -97,6 +113,12 @@ defmodule EnumTest.List do
end
end
test :fetch do
assert Enum.fetch([2,4,6], 0) == { :ok, 2 }
assert Enum.fetch([2,4,6], 2) == { :ok, 6 }
assert Enum.fetch([2,4,6], 4) == :error
end
test :first do
assert Enum.first([]) == nil
assert Enum.first([1,2,3]) == 1
@@ -240,8 +262,14 @@ defmodule EnumTest.List do
assert Enum.take_while([], fn(_) -> true end) == []
end
test :to_list do
assert Enum.to_list([]) == []
assert Enum.to_list(1 .. 3) == [1, 2, 3]
end
test :uniq do
assert Enum.uniq([1,2,3,2,1]) == [1,2,3]
assert Enum.uniq([1,2,3,2,1], fn x -> x end) == [1,2,3]
end
test :zip do
@@ -252,6 +280,34 @@ defmodule EnumTest.List do
assert Enum.zip([1], []) == [{ 1, nil }]
assert Enum.zip([], []) == []
end
test :max do
assert Enum.max([1]) == 1
assert Enum.max([1,2,3]) == 3
assert Enum.max([1,[],:a,{}]) == []
assert_raise Enum.EmptyError, fn ->
assert Enum.max([])
end
assert Enum.max(["a", "aa", "aaa"], fn(x) -> String.length(x) end) == "aaa"
assert_raise Enum.EmptyError, fn ->
Enum.max([], fn(x) -> String.length(x) end)
end
end
test :min do
assert Enum.min([1]) == 1
assert Enum.min([1,2,3]) == 1
assert Enum.min([[],:a,{}]) == :a
assert_raise Enum.EmptyError, fn ->
assert Enum.min([])
end
assert Enum.min(["a", "aa", "aaa"], fn(x) -> String.length(x) end) == "a"
assert_raise Enum.EmptyError, fn ->
Enum.min([], fn(x) -> String.length(x) end)
end
end
end
defmodule EnumTest.Range do
@@ -280,18 +336,18 @@ defmodule EnumTest.Range do
assert Enum.any?(range)
end
test :at! do
assert Enum.at!(2..6, 0) == 2
assert Enum.at!(2..6, 4) == 6
assert Enum.at!(-2..-6, 0) == -2
assert Enum.at!(-2..-6, 4) == -6
test :fetch! do
assert Enum.fetch!(2..6, 0) == 2
assert Enum.fetch!(2..6, 4) == 6
assert Enum.fetch!(-2..-6, 0) == -2
assert Enum.fetch!(-2..-6, 4) == -6
assert_raise Enum.OutOfBoundsError, fn ->
assert Enum.at!(2..6, 8)
assert Enum.fetch!(2..6, 8)
end
assert_raise Enum.OutOfBoundsError, fn ->
assert Enum.at!(-2..-6, 8)
assert Enum.fetch!(-2..-6, 8)
end
end
@@ -533,6 +589,7 @@ defmodule EnumTest.Range do
test :uniq do
assert Enum.uniq(1..3) == [1,2,3]
assert Enum.uniq(1..3, fn x -> x end) == [1,2,3]
end
test :zip do
@@ -548,6 +605,24 @@ defmodule EnumTest.Range do
assert Enum.zip(1..4, 1..2) == [{1, 1}, {2, 2}, {3, nil}, {4, nil}]
assert Enum.zip(1..2, 1..4) == [{1, 1}, {2, 2}]
end
test :max do
assert Enum.max(1..1) == 1
assert Enum.max(1..3) == 3
assert Enum.max(3..1) == 3
assert Enum.max(1..1, fn(x) -> :math.pow(-2, x) end) == 1
assert Enum.max(1..3, fn(x) -> :math.pow(-2, x) end) == 2
end
test :min do
assert Enum.min([1]) == 1
assert Enum.min([1,2,3]) == 1
assert Enum.min([[],:a,{}]) == :a
assert Enum.min(1..1, fn(x) -> :math.pow(-2, x) end) == 1
assert Enum.min(1..3, fn(x) -> :math.pow(-2, x) end) == 3
end
end
defmodule EnumTest.Others do
+28 -26
View File
@@ -1,39 +1,41 @@
Code.require_file "../test_helper.exs", __FILE__
Code.require_file "test_helper.exs", __DIR__
defmodule Kernel.ExceptionTest do
use ExUnit.Case, async: true
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
assert Exception.format_entry({Foo, :bar, [1, 2, 3], []}) == "Foo.bar(1, 2, 3)"
assert Exception.format_entry({Foo, :bar, [], []}) == "Foo.bar()"
assert Exception.format_entry({Foo, :bar, 1, []}) == "Foo.bar/1"
test :format_stacktrace_entry_with_no_file_or_line do
assert Exception.format_stacktrace_entry({Foo, :bar, [1, 2, 3], []}) == "Foo.bar(1, 2, 3)"
assert Exception.format_stacktrace_entry({Foo, :bar, [], []}) == "Foo.bar()"
assert Exception.format_stacktrace_entry({Foo, :bar, 1, []}) == "Foo.bar/1"
end
test :format_entry_with_file_and_line do
assert Exception.format_entry({Foo, :bar, [], [file: 'file.ex', line: 10]}) == "file.ex:10: Foo.bar()"
assert Exception.format_entry({Foo, :bar, [1, 2, 3], [file: 'file.ex', line: 10]}) == "file.ex:10: Foo.bar(1, 2, 3)"
assert Exception.format_entry({Foo, :bar, 1, [file: 'file.ex', line: 10]}) == "file.ex:10: Foo.bar/1"
test :format_stacktrace_entry_with_file_and_line do
assert Exception.format_stacktrace_entry({Foo, :bar, [], [file: 'file.ex', line: 10]}) == "file.ex:10: Foo.bar()"
assert Exception.format_stacktrace_entry({Foo, :bar, [1, 2, 3], [file: 'file.ex', line: 10]}) == "file.ex:10: Foo.bar(1, 2, 3)"
assert Exception.format_stacktrace_entry({Foo, :bar, 1, [file: 'file.ex', line: 10]}) == "file.ex:10: Foo.bar/1"
end
test :format_entry_with_file_and_line_and_cwd do
assert Exception.format_entry({Foo, :bar, [], [file: '/foo/file.ex', line: 10]}, "/foo") == "file.ex:10: Foo.bar()"
test :format_stacktrace_entry_with_file_and_line_and_cwd do
assert Exception.format_stacktrace_entry({Foo, :bar, [], [file: '/foo/file.ex', line: 10]}, "/foo") == "file.ex:10: Foo.bar()"
end
test :format_entry_with_file_no_line do
assert Exception.format_entry({Foo, :bar, [], [file: 'file.ex']}) == "file.ex: Foo.bar()"
assert Exception.format_entry({Foo, :bar, [], [file: 'file.ex', line: 0]}) == "file.ex: Foo.bar()"
assert Exception.format_entry({Foo, :bar, [1, 2, 3], [file: 'file.ex']}) == "file.ex: Foo.bar(1, 2, 3)"
assert Exception.format_entry({Foo, :bar, 1, [file: 'file.ex']}) == "file.ex: Foo.bar/1"
test :format_stacktrace_entry_with_file_no_line do
assert Exception.format_stacktrace_entry({Foo, :bar, [], [file: 'file.ex']}) == "file.ex: Foo.bar()"
assert Exception.format_stacktrace_entry({Foo, :bar, [], [file: 'file.ex', line: 0]}) == "file.ex: Foo.bar()"
assert Exception.format_stacktrace_entry({Foo, :bar, [1, 2, 3], [file: 'file.ex']}) == "file.ex: Foo.bar(1, 2, 3)"
assert Exception.format_stacktrace_entry({Foo, :bar, 1, [file: 'file.ex']}) == "file.ex: Foo.bar/1"
end
test :format_entry_with_fun do
assert Exception.format_entry({fn(x) -> x end, [1], []}) =~ %r"\(1\)"
assert Exception.format_entry({fn(x, y) -> { x, y } end, 2, []}) =~ %r"/2"
test :format_stacktrace_entry_with_fun do
assert Exception.format_stacktrace_entry({fn(x) -> x end, [1], []}) =~ %r"\(1\)"
assert Exception.format_stacktrace_entry({fn(x, y) -> { x, y } end, 2, []}) =~ %r"/2"
end
test :format_module_function_arity do
@@ -60,18 +62,18 @@ defmodule Kernel.ExceptionTest do
test :undefined_function_message do
assert UndefinedFunctionError.new.message == "undefined function"
assert UndefinedFunctionError.new(module: Foo, function: :bar, arity: 1).message == "undefined function: Foo.bar/1"
assert UndefinedFunctionError.new(module: Foo, function: :bar, arity: []).message == "undefined function: Foo.bar/0"
assert UndefinedFunctionError.new(module: :foo, function: :bar, arity: []).message == "undefined function: :foo.bar/0"
end
test :function_clause_message do
assert FunctionClauseError.new.message == "no function clause matches"
assert FunctionClauseError.new(module: Foo, function: :bar, arity: 1).message == "no function clause matching: Foo.bar/1"
assert FunctionClauseError.new(module: Foo, function: :bar, arity: []).message == "no function clause matching: Foo.bar()"
assert FunctionClauseError.new(module: :foo, function: :bar, arity: []).message == "no function clause matching: :foo.bar()"
assert FunctionClauseError.new(module: Foo, function: :bar, arity: 1).message == "no function clause matching in Foo.bar/1"
end
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
+36 -14
View File
@@ -1,4 +1,4 @@
Code.require_file "../test_helper.exs", __FILE__
Code.require_file "test_helper.exs", __DIR__
defmodule FileTest do
import PathHelpers
@@ -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
@@ -185,9 +187,9 @@ defmodule FileTest do
{ :ok, files } = File.cp_r(src, dest)
assert length(files) == 8
assert tmp_path("tmp/cp_r/a") inlist files
assert tmp_path("tmp/cp_r/c") inlist files
assert tmp_path("tmp/cp_r/a/1.txt") inlist files
assert tmp_path("tmp/cp_r/a") in files
assert tmp_path("tmp/cp_r/c") in files
assert tmp_path("tmp/cp_r/a/1.txt") in files
assert { :ok, 'certainly/invalid' } = :file.read_link(tmp_path("tmp/cp_r/c"))
@@ -395,6 +397,24 @@ defmodule FileTest do
end
end
test :ls do
{ :ok, value } = File.ls(fixture_path)
assert "code_sample.exs" in value
assert "file.txt" in value
{ :error, :enoent } = File.ls(fixture_path("non-existent-subdirectory"))
end
test :ls! do
value = File.ls!(fixture_path)
assert "code_sample.exs" in value
assert "file.txt" in value
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
@@ -755,8 +777,8 @@ defmodule FileTest do
{ :ok, files } = File.rm_rf(fixture)
assert length(files) == 8
assert fixture inlist files
assert tmp_path("tmp/a/1.txt") inlist files
assert fixture in files
assert tmp_path("tmp/a/1.txt") in files
refute File.exists?(tmp_path("tmp/a/1.txt"))
refute File.exists?(tmp_path("tmp/a/a/2.txt"))
@@ -789,8 +811,8 @@ defmodule FileTest do
{ :ok, files } = File.rm_rf(fixture)
assert length(files) == 8
assert fixture inlist files
assert (tmp_path("tmp/a/1.txt") |> to_char_list) inlist files
assert fixture in files
assert (tmp_path("tmp/a/1.txt") |> to_char_list) in files
refute File.exists?(tmp_path("tmp/a/1.txt"))
refute File.exists?(tmp_path("tmp/a/a/2.txt"))
@@ -825,8 +847,8 @@ defmodule FileTest do
files = File.rm_rf!(fixture)
assert length(files) == 8
assert fixture inlist files
assert tmp_path("tmp/a/1.txt") inlist files
assert fixture in files
assert tmp_path("tmp/a/1.txt") in files
refute File.exists?(tmp_path("tmp/a/1.txt"))
refute File.exists?(tmp_path("tmp/a/a/2.txt"))
@@ -0,0 +1,36 @@
Code.require_file "../test_helper.exs", __DIR__
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
@@ -1,4 +1,4 @@
Code.require_file "../../test_helper.exs", __FILE__
Code.require_file "../test_helper.exs", __DIR__
defmodule GenServer.BehaviourTest do
use ExUnit.Case, async: true

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