Compare commits

...
298 Commits
Author SHA1 Message Date
José Valim a613445f78 Release v0.9.2 2013-06-13 15:54:52 +02:00
José Valim e4ea0a6e36 Update CHANGELOG 2013-06-13 15:51:08 +02:00
José Valim a03639fe1a Deprecate Regex.index/2 2013-06-13 15:23:03 +02:00
José Valim e7b1242e98 Fix a bug on Path.split/1 2013-06-13 15:13:56 +02:00
José Valim d3d83b17f4 Merge pull request #1199 from alco/1148-match-tilde-op
Reimplement =~ to use Regex.match? or String.contains?
2013-06-13 06:13:32 -07:00
José Valim 78eb14edae Merge pull request #1187 from alco/pipeline-op
Pipeline op fix + additional assertion
2013-06-13 06:12:25 -07:00
José Valim a7d931ce21 Merge pull request #1237 from edgurgel/fix-typo-on-list-doc
Fix typo on List documentation
2013-06-12 20:43:58 -07:00
Eduardo Gurgel b1cd1ee7e9 Fix typo on List documentation 2013-06-12 22:30:12 -03:00
José Valim 1513ca7c14 Merge pull request #1235 from mururu/update-docs
Update docs for `defoverridable` and `super`
2013-06-12 11:11:37 -07:00
Yuki Ito 016480bdcc Update docs for defoverridable and super 2013-06-13 02:35:13 +09:00
José Valim 857af06aff Merge pull request #1234 from mururu/deprecate-implicit-args
Implicit super arguments is deprecated
2013-06-12 08:56:55 -07:00
Yuki Ito 961099746e Implicit super arguments is deprecated 2013-06-13 00:42:34 +09:00
José Valim 8247382967 Merge pull request #1233 from alco/1226-doctest-perf
Optimize doctest generation. Closes #1226
2013-06-12 06:10:00 -07:00
Alexei Sholik 9b0d972571 Optimize doctest generation. Closes #1226 2013-06-12 15:55:06 +03:00
Alexei Sholik b19c389cee Replace @export_type with @typep in the docs
Elixir automatically generates corresponding @export_type attributes for
public types
2013-06-12 12:58:40 +03:00
Alexei Sholik 2243235e08 Merge pull request #1229 from alco/1132-doc-module-attr
Provide reference documentation for module attributes
2013-06-11 10:30:07 -07:00
Alexei Sholik a72457112f Add support for h AnyModule.__info__. Closes #1230 2013-06-11 20:27:01 +03:00
Alexei Sholik 3f3e86d7f4 Document __info__/1 2013-06-11 20:14:14 +03:00
Alexei Sholik 15bf0fb16e Provide reference documentation for module attributes 2013-06-11 16:46:55 +03:00
Alexei Sholik 5184edd432 Fix code escaping in ExUnit.Callbacks docstring 2013-06-11 14:05:19 +03:00
José Valim fc9506e28e Add missing rm -rf ebin back to docs task 2013-06-10 21:24:16 +08:00
José Valim 5c01be91ea Fix Makefile dependencies 2013-06-10 15:10:42 +08:00
José Valim fed406a2e6 Rename test/doctest.exs file to test/doc_test.exs to follow file name convention 2013-06-10 14:27:38 +08:00
José Valim 26904197d5 Merge pull request #1222 from jjh42/doc-error
Added more informative error to Makefile when ex_doc is not available.
2013-06-09 18:29:14 -07:00
José Valim f552d7620f Merge pull request #1221 from alexrp/mix-local-install
Add a --force option to `mix local.install`.
2013-06-09 18:25:45 -07:00
Jonathan J Hunt ce55936b45 Added more informative error to Makefile when ex_doc is not available.
Added more information to README about building docs.

fixes #1151
2013-06-09 16:04:48 -07:00
alexrp 3167d78711 Add a --force option to mix local.install.
Useful for makefiles.
2013-06-09 23:34:54 +02:00
José Valim 05eb42612b Merge pull request #1217 from ericmj/mix-outofdatedepserror
Improve error message for diverged deps
2013-06-09 07:58:40 -07:00
Eric Meadows-Jonsson 6720522707 Improve error message for diverged deps 2013-06-09 16:02:33 +02:00
José Valim 0520b138f8 Merge pull request #1218 from zambal/remove-bash-dependencies
remove dependency on bash in shell scripts
2013-06-09 06:54:39 -07:00
zambal 0ddb31992b removed use of local variables in shell scripts in order to remove dependency on bash 2013-06-09 14:51:29 +02:00
Alexei Sholik 4a1dfccbec Use =~ with strings where it makes sense 2013-06-08 15:58:34 +03:00
Alexei Sholik c62a7e1001 Reimplement =~ to use Regex.match? or String.contains? 2013-06-08 15:00:09 +03:00
Alexei Sholik 2b8854c63e Add a few sanity check tests for upcase & downcase 2013-06-08 14:33:04 +03:00
José Valim b535f0746e No need for accumulator on downcase/upcase 2013-06-08 16:22:44 +08:00
José Valim 4b795a042b Optimize downcase, upcase and rstrip in Unicode 2013-06-08 15:24:51 +08:00
José Valim c80e52ee39 Merge pull request #1198 from ericmj/patch-1194
Dont run deps tasks recursively when they are started non-recursively
2013-06-07 18:14:44 -07:00
Eric Meadows-Jonsson 520415b013 Dont run any tasks recursively from another task
The only time a task may run recursively is after a new project has been pushed to the stack.
2013-06-07 18:10:34 +02:00
José Valim 27682320d4 Merge pull request #1211 from mururu/to-binary-uri-path
to_binary for URI.Info do not ignore the path
2013-06-07 05:50:32 -07:00
Eric Meadows-Jonsson 98ade3b8fb Convert project stack to records 2013-06-07 14:45:26 +02:00
José Valim 263c76f5e5 Merge pull request #1209 from mururu/rm-case
Remove unneeded `case`
2013-06-07 05:39:17 -07:00
Yuki Ito bd984a545d to_binary for URI.Info do not ignore the path 2013-06-07 20:28:56 +09:00
Yuki Ito 115e022025 Remove unneeded case
`:os.type/0` always returns a tuple in R16B
2013-06-07 18:45:34 +09:00
José Valim dc5fe48639 Merge pull request #1208 from mururu/fix-spec
Fix Enum.uniq/2 spec
2013-06-07 02:02:42 -07:00
Yuki Ito 155b937327 Fix Enum.uniq/2 spec 2013-06-07 17:59:09 +09:00
José Valim 6ef10d4b0d Add tests for compile_string and compile_quoted, closes #1206 2013-06-07 14:48:19 +08:00
José Valim 4f2c2ceb95 Wrap rebar command on windows inside escript.exe 2013-06-07 14:35:41 +08:00
José Valim 078522d014 Fix iex -S mix on Windows 2013-06-07 13:50:04 +08:00
José Valim c765a95b91 Ensure default calls are connected in dispatch tracker 2013-06-07 13:34:41 +08:00
José Valim 8836405bc5 Merge pull request #1201 from alco/exunit-docs
Extend ExUnit docs
2013-06-06 16:21:18 -07:00
Alexei Sholik dd2e0f6064 Mention mix and testing conventions in ExUnit's doc 2013-06-06 19:44:10 +03:00
Alexei Sholik 23982f1744 Extend the documentation for ExUnit.Callbacks. Closes #1142 2013-06-06 19:44:02 +03:00
Alexei Sholik a6f04a2224 Fix Record's moduledoc 2013-06-06 13:03:14 +03:00
Alexei Sholik 7291abb26f Move the description of records to Record's moduledoc. Closes #1166 2013-06-06 12:52:22 +03:00
Alexei Sholik 55cb8ab436 Merge pull request #1195 from alco/string-fixes
String fixes
2013-06-05 09:20:54 -07:00
Alexei Sholik 32cddf2738 Reorganize tests for starts_with? and co. 2013-06-05 18:58:29 +03:00
Alexei Sholik 7893852f2f Fix edge case for String.contains? 2013-06-05 18:58:16 +03:00
Alexei Sholik f1b625ccd2 Don't support nested lists in starts_with? and co. 2013-06-05 18:44:22 +03:00
Alexei Sholik caa7d41e96 Refactor String.ends_with?
Using :binary.match should save us an allocation. Plus, the function
now looks more similar to starts_with?
2013-06-05 18:22:48 +03:00
Alexei Sholik b96a714240 Fix edge cases for starts_with?, ends_with? and contains? 2013-06-05 18:14:38 +03:00
Yurii Rashkovskii d39a52ae79 Fix minor compile-time warnings 2013-06-05 07:55:10 -07:00
José Valim 3e66197318 Merge pull request #1193 from ericmj/docs-overridable
Document `super` and add more docs to `defoverridable`
2013-06-05 07:44:23 -07:00
Eric Meadows-Jonsson 065b7f3f8f Document super and add more docs to defoverridable 2013-06-05 16:05:11 +02:00
José Valim 6004a3614f Update CHANGELOG 2013-06-05 20:11:12 +08:00
José Valim 22627c44df Merge pull request #1188 from josephwilk/string-contains
Adding String.contains?
2013-06-05 05:04:44 -07:00
José Valim 6e959733dd Merge pull request #1186 from ericmj/mix-umbrella-deps
Add support for direct dependencies of umbrella project
2013-06-05 05:03:04 -07:00
Eric Meadows-Jonsson 7d2d352236 Add deps to 'mix new --umbrella' 2013-06-05 13:24:16 +02:00
Eric Meadows-Jonsson 9d61e5886c Add support for direct dependencies of umbrella 2013-06-05 13:20:54 +02:00
Alexei Sholik 37c8867d9f Implement assert_compile_fail
The format_rescue function has been found useful in multiple instances,
so it made sense to generalize it into its own assertion.
2013-06-05 13:14:29 +03:00
Alexei Sholik da7e830bf7 Disallow &-partial calls on the right side of |> 2013-06-05 13:08:16 +03:00
José Valim c78d3b3717 Clean up DocTestTest 2013-06-05 18:00:26 +08:00
José Valim cafbd2c2d4 Ensure --cookie is set before remsh starts, closes #1190 2013-06-05 17:55:48 +08:00
josephwilk ae8def380a :binary.match supports a list of matches 2013-06-05 11:27:08 +02:00
José Valim 37cb1801b5 Merge pull request #1181 from jjh42/add-version-option
Added -v --version option to all elixir commands.
2013-06-04 23:07:50 -07:00
Jonathan J Hunt dd43e799c8 Added -v --version option to all elixir commands.
Fixes #1178
2013-06-04 23:01:29 -07:00
José Valim e93d4197ef Merge pull request #1183 from alco/system-bootstrap
Bring system.ex back to bootstrap
2013-06-04 17:02:15 -07:00
Alexei Sholik d55a1703bd Bring system.ex back to bootstrap. Closes #1155 2013-06-05 02:50:50 +03:00
José Valim 87268e9c78 Merge pull request #1185 from meh/uri-to_binary
Implement Binary.Chars for URI.Info
2013-06-04 16:41:02 -07:00
Joseph Wilk f0ecfbfac9 Adding String.contains?
Detect whether a string contains another string.
2013-06-04 19:20:11 +02:00
meh 854cab51bc Implement Binary.Chars for URI.Info 2013-06-04 16:12:55 +02:00
José Valim ffdf9e9a35 Merge pull request #1184 from ericmj/mix-skip-deps
Make rebar skip deps since mix compiles them
2013-06-04 02:55:11 -07:00
Eric Meadows-Jonsson 6d74511e92 Make rebar skip deps since mix compiles them 2013-06-04 11:45:58 +02:00
Alexei Sholik 75a1373bbb Merge pull request #1137 from alco/919-iex-test-fmwk
Test framework for IEx
2013-06-03 05:13:13 -07:00
Alexei Sholik d26e4b3dbc Minor comment cleanup 2013-06-03 15:07:47 +03:00
Alexei Sholik d636dc0ff0 Proof-read the doc for capture_io 2013-06-03 15:07:47 +03:00
Alexei Sholik ed0b161992 Add prompt test 2013-06-03 15:07:47 +03:00
Alexei Sholik 5e640336e0 Update the docs for Code.load_file and Code.require_file 2013-06-03 15:07:47 +03:00
Alexei Sholik 99c7d84f9c Update the r helper 2013-06-03 15:07:47 +03:00
Alexei Sholik 893a52a95a Clean up test code for module helpers 2013-06-03 15:07:47 +03:00
Alexei Sholik a8d2e45d2f Add docs for IEx test framework 2013-06-03 15:07:47 +03:00
Alexei Sholik ecd3c19c64 Add more tests for shell coloring 2013-06-03 15:07:47 +03:00
Alexei Sholik 7f9a2ed17b Don't process ANSI escapes in ExUnit output 2013-06-03 15:07:47 +03:00
Alexei Sholik 7951f73925 Update the tests for l and r helpers 2013-06-03 15:07:47 +03:00
Alexei Sholik a79d965248 Rewrite tests for helpers to use capture_iex and capture_io sparingly 2013-06-03 15:07:47 +03:00
Alexei Sholik 958550c5cd Write tests for IEx helpers 2013-06-03 15:07:47 +03:00
Alexei Sholik 997a798994 Implementation of a basic test framework for IEx. Closes #919 2013-06-03 15:07:47 +03:00
José Valim c0547a15ce Ensure Enum.to_list and Dict.to_list return exactly the same result 2013-06-03 20:35:18 +09:00
José Valim 3918b889f1 Merge pull request #1177 from fishcakez/hashdict-unordered
Fix HashDict so that key comparison uses exact match
2013-06-03 04:29:34 -07:00
James Fish d1fd5a3c2a Fix HashDict so that key comparison uses exact match
This change means that HashDict buckets are nolonger strictly ordered.
Previously keys which compared equal (==) but not exactly equal (===)
would be considered different for large sizes but equivalent for small
sizes. This ambiguity occured because hashes are only equal when terms
are exactly equal (===), so both now use exact (===) key comparisons.
Small HashDicts are still a single ordered list and large HashDicts
contain ordered buckets. Both of these may contain keys that compare
equal (==) but are not exactly equal (===), should these keys be present
in the same list or bucket the ordering of these keys (relative to each
other) is undefined.

See github issue #1171 for examples.

Note: If assumptions were made about the form of a HashDict they may
nolonger be valid. Particularly that HashDicts can nolonger be reliably
pattern matched on for equality.
2013-06-03 02:13:33 +01:00
José Valim a3aa1e8de5 Ensure noname scope does not leak after assignment 2013-06-03 00:24:14 +09:00
José Valim 7c379aa50a Merge pull request #1174 from mururu/capture_prompt_option
Option that enable to decide whether `capture_io` captures a prompt or not
2013-06-02 05:07:42 -07:00
Yuki Ito 1fa29be448 Option that enable to decide whether capture_io captures a prompt or not 2013-06-02 19:15:06 +09:00
José Valim b9a60b2668 Merge pull request #1170 from carlosmn/load-path-make
Set load paths for dependencies with Makefile
2013-06-01 20:02:34 -07:00
José Valim ab9735533e Merge pull request #1172 from carlosmn/iex-r
Fix IEx's module source lookup
2013-06-01 20:00:36 -07:00
Carlos Martín Nieto 6b2b928867 Fix IEx's module source lookup
Module.module_info(:compile)[:source] returns the value, not a tuple
as the code was expecting, making r/1 think that no module has a
source. Fix this expectation.
2013-06-02 00:33:31 +02:00
Carlos Martín Nieto 4b1ea18625 Set load paths for dependencies with Makefile
A recent update (036d016 2013-06-02; Rebar dep with no config is given
rebar as manager) introduced :make as a value for a dependency's
manager, but Mix.Deps.load_path has no function clause to handle this.

Add :make to the nil manager clause, as it seems the closest
equivalent.
2013-06-01 22:35:24 +02:00
José Valim 7cd98e149c Speed up escriptize tests 2013-06-02 00:36:02 +09:00
José Valim 036d016053 Rebar dep with no config is given rebar as manager 2013-06-02 00:35:30 +09:00
José Valim be2aedff98 Fix typo on |> docs, closes #1168 2013-06-01 23:57:58 +09:00
José Valim e19d666a14 Merge pull request #1167 from mururu/prompt
capture_io captures a prompt when capturing of stdio
2013-06-01 03:15:38 -07:00
Yuki Ito 9840ac266b capture_io captures a prompt when capturing of stdio 2013-06-01 17:07:28 +09:00
José Valim bcfc927455 Improve docs for IEx helper l/1 2013-06-01 10:45:34 +09:00
José Valim 014a2ec7a8 By default, start the application on escript 2013-06-01 08:12:47 +09:00
José Valim 4846d42101 Merge pull request #1160 from jjh42/alias-nil-correctly
Don't ignore nil during aliasing. Attempt to fix #1159
2013-05-31 16:10:48 -07:00
Jonathan J Hunt 636da42f2a Don't ignore nil during aliasing. Attempt to fix #1159 2013-05-31 15:58:15 -07:00
José Valim 7c4516f1ec Do not access another task directly, use Mix.Rebar module instead 2013-06-01 07:46:54 +09:00
Eric Meadows-Jonsson 65cc78034d Correctly add load_paths for rebar dependencies
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2013-06-01 07:41:06 +09:00
José Valim f97a384b82 Move dependencies callbacks back into Mix.Deps.Converger
The converger is the one that knows if a dependency exists or
not and, as such, is the best one to know if a dependency callback
should be invoked or not.

This commit also normalizes some of the Retriever internal
functions, for example, now we have mix_children and rebar_children.
The goal is to extract those functions (and the ones in Mix.Deps)
one by one into a Mix.DepsManager behaviour which will be implemented
by Mix.DepsManager.Mix and Mix.DepsManager.Rebar, removing all
the conditional calls existing in the code.
2013-06-01 07:19:23 +09:00
Alexei Sholik 7e856e1af0 Don't process ANSI escapes in strings in IEx 2013-05-31 21:48:31 +03:00
José Valim 893e333204 Update changelog and move version to 0.9.2.dev 2013-06-01 03:44:58 +09:00
José Valim 5e94a69693 Merge pull request #1163 from meh/regex-escape
Support extended syntax in Regex.escape
2013-05-31 11:13:16 -07:00
José Valim 5775d49c08 Merge pull request #1164 from sasa1977/document_record_features
documentation for record generated functions
2013-05-31 11:12:33 -07:00
José Valim 6397cedec6 Do not interpret ansi escape codes in IEx results 2013-06-01 03:10:11 +09:00
meh 3dd445bcce Add documentation to Regex.escape 2013-05-31 19:04:26 +02:00
meh 5e803487ef Improve Regex.escape tests 2013-05-31 19:04:26 +02:00
meh caeea8973c Support extended syntax in Regex.escape 2013-05-31 19:04:26 +02:00
Yurii Rashkovskii 6656f7c843 Minor indentation fix 2013-05-31 09:54:28 -07:00
Yurii Rashkovskii e7a872c975 Mix.Shell: Fix incorrect Windows' COMSPEC handling 2013-05-31 09:49:52 -07:00
sasa1977 ea797815a0 document update and to_keywords 2013-05-31 15:46:05 +02:00
José Valim 453d8835bd Merge pull request #1150 from alco/to_binary-error
Better error message for Binary.Chars.List.to_binary. Closes #1144
2013-05-31 02:21:16 -07:00
Alexei Sholik 2272dd1c46 Better error message for Binary.Chars.List.to_binary. Closes #1144 2013-05-31 12:14:51 +03:00
José Valim 41b5f4c826 Merge pull request #1122 from ericmj/mix-rebar
Handle rebar dependencies better
2013-05-31 02:14:05 -07:00
José Valim ac0ac7f024 Merge pull request #1153 from meh/regex-escape
Implement Regex.escape
2013-05-30 19:38:51 -07:00
meh 5ff3b760d9 Implement Regex.escape 2013-05-31 04:26:43 +02:00
José Valim fe97c4886a Merge pull request #1152 from ericmj/mix-sh
Use the shell to run Mix.Shell.cmd commands
2013-05-30 17:06:01 -07:00
Eric Meadows-Jonsson 0b45b61de8 Use the shell to run Mix.Shell.cmd commands 2013-05-31 00:20:52 +02:00
Eric Meadows-Jonsson 406edcb07d Use rebar from project root directory 2013-05-30 15:22:05 +02:00
José Valim 24c6623a2b Release v0.9.1 2013-05-30 11:47:00 +09:00
José Valim 5ffec637b0 Ensure we can define a nested record named Record, closes #1133 2013-05-30 11:44:19 +09:00
José Valim bd1633c778 Remove get! from Dict behaviour spec 2013-05-30 11:42:23 +09:00
José Valim bcf9d814dd Remove deprecated code 2013-05-30 11:24:51 +09:00
José Valim 69a4138e8c Remove remaining function implementation from access protocol old versions 2013-05-30 11:20:08 +09:00
José Valim 0fa06a2183 Precompile groups regex 2013-05-30 11:20:08 +09:00
Eric Meadows-Jonsson 6c813a140d Change project/rebar to manager/source in Mix.Dep 2013-05-29 19:00:07 +02:00
Eric Meadows-Jonsson dfc40703f0 Use Mix.Rebar as module for project pushing 2013-05-29 17:02:27 +02:00
José Valim e208237669 Merge pull request #1136 from alco/1131-tests
Add tests for %w(BigAtom)a mentioned in #1131
2013-05-29 01:57:33 -07:00
Alexei Sholik 3ec014ff77 Merge pull request #1128 from alco/1020-extended-doctest
Implement extended doctests
2013-05-29 01:53:51 -07:00
Alexei Sholik c19e3d0446 Add tests for %w(BigAtom)a mentioned in #1131 2013-05-29 11:49:21 +03:00
Alexei Sholik a459e35537 Merge pull request #1135 from mururu/add-space
Add space to error message in mix
2013-05-29 00:53:33 -07:00
Yuki Ito bbaf6b4092 Add space to error message in mix 2013-05-29 15:59:47 +09:00
José Valim 99cdb412e3 Give higher precedence to |> than =~ 2013-05-29 03:19:45 +02:00
Alexei Sholik 4ce444ad76 Include location info in the multiple exceptions error 2013-05-29 00:34:18 +03:00
Eric Meadows-Jonsson f06f15184c Clean up code, add tests, fix bugs found in tests 2013-05-28 19:39:55 +02:00
Alexei Sholik da4565ee88 Don't test DocTest's doctests 2013-05-28 19:07:30 +03:00
Alexei Sholik 636275c064 Revert "Make test_doctest target depend on test_ex_unit"
This reverts commit b2d0355ef9.

Better keep the tests separate as before. It is sufficient to have
ex_unit tests invoked before doctest tests when doing the full `make test`
2013-05-28 18:54:22 +03:00
Alexei Sholik 6137d55d10 Test doctests for ExUnit.CaptureIO 2013-05-28 18:48:18 +03:00
Alexei Sholik 2fbc8682ca Review doctests in stdlib to use extended doctests 2013-05-28 18:47:47 +03:00
Alexei Sholik 2075a9b07a Minor formatting fixes 2013-05-28 17:55:38 +03:00
Alexei Sholik abb261443b Clean up the docs for doctests 2013-05-28 17:28:28 +03:00
Alexei Sholik 41b0ae4b37 Add a couple of comments explaining decisions 2013-05-28 17:08:04 +03:00
Alexei Sholik 4d793f6db1 Deduplicate test generation code 2013-05-28 17:01:33 +03:00
Alexei Sholik b2d0355ef9 Make test_doctest target depend on test_ex_unit 2013-05-28 17:00:48 +03:00
Alexei Sholik c82a8ee0fa Remove unused debug output 2013-05-28 16:36:53 +03:00
Alexei Sholik 7044f9c9da Add a test for leaking vars 2013-05-28 16:36:32 +03:00
Alexei Sholik ec8327967a Little refactoring 2013-05-28 16:36:19 +03:00
Alexei Sholik 5aa5268c5b Implement extended doctest sessions. Closes #1020 2013-05-28 13:53:18 +03:00
Alexei Sholik 619f345423 Test ex_unit before doctests 2013-05-28 13:53:18 +03:00
Alexei Sholik 99913d7018 Remove unintended connections between some doctests 2013-05-28 13:53:18 +03:00
José Valim fce97507de Do not include remotes on reachable results 2013-05-28 07:46:39 +02:00
José Valim 3f5e6deb07 Merge pull request #1126 from alco/1125-test
Add tests for body-less functions
2013-05-27 10:49:32 -07:00
Alexei Sholik 3c9f486567 Add tests for body-less functions 2013-05-27 20:42:20 +03:00
José Valim 846048855f Error if guards are given but no implementation, closes #1125 2013-05-27 19:24:24 +02:00
José Valim fed8796d18 Update CHANGELOG 2013-05-27 19:07:14 +02:00
José Valim 9a6259d333 Remove unused variable warning 2013-05-27 19:06:24 +02:00
José Valim 8c8e8b1193 Merge pull request #1121 from alco/iex-limit-history
Iex limit history
2013-05-27 08:57:37 -07:00
José Valim dad7f7bfb7 Allow keywords in typespecs 2013-05-27 17:54:04 +02:00
Alexei Sholik 3aaaf5e916 Implement the heuristics for garbage collection 2013-05-27 15:08:34 +03:00
José Valim a6b81bffd1 Inline nillify in Process 2013-05-27 14:01:15 +02:00
Alexei Sholik a9d4cdb7d7 Minor code formatting fix 2013-05-27 14:09:43 +03:00
Alexei Sholik 64c58f2a3f Use just the process dictionary for keeping history 2013-05-27 14:06:07 +03:00
José Valim 9e56691274 Ensure aliases from macros are properly expanded, closes #1123 2013-05-27 11:08:23 +02:00
Alexei Sholik 3f86e6d555 Make IEx helpers use inspect_opts where appropriate
For instance, the v helper would print the results in their entirety
ignoring the "limit" option.
2013-05-27 02:20:56 +03:00
Alexei Sholik 339082053e Extract history management in its own module 2013-05-27 01:24:12 +03:00
Eric Meadows-Jonsson 0cbf50c53a Handle raw option in rebar deps 2013-05-26 23:30:15 +02:00
Eric Meadows-Jonsson 1d87a778ef Handle rebar dependencies better
Download dependencies specified in rebar.config and go through
Converger to handle conflicts.
Handle `sub_dirs` in rebar.config when getting dependencies and
by the sub_dirs to the code load path.
2013-05-26 23:24:42 +02:00
Alexei Sholik 3b5e06763d s/less than 0/negative/ 2013-05-27 00:11:53 +03:00
Alexei Sholik a7eab9b426 Don't reverse v's output 2013-05-27 00:07:39 +03:00
Alexei Sholik 1211ed7f0f Document :history_size option 2013-05-27 00:06:19 +03:00
Alexei Sholik 2a08277ef1 Fix v helper to work with new :iex_history format 2013-05-27 00:00:17 +03:00
Alexei Sholik df94927e09 Add :history_size option to IEx 2013-05-26 23:39:55 +03:00
José Valim 3cd6e54fe9 development and questions 2013-05-26 22:25:31 +02:00
José Valim 336b9cf126 Update mailing list links 2013-05-26 21:02:29 +02:00
José Valim d19f6beca0 Provide source information to all dispatches and complement the API 2013-05-26 20:20:23 +02:00
José Valim da84a797b0 Module.DispatchTracker also records remotes 2013-05-26 20:20:23 +02:00
José Valim 69df6ea9f9 Merge pull request #1120 from alco/contributing-guidelines
Contributing guidelines
2013-05-26 10:45:06 -07:00
Alexei Sholik b1fa7d1834 "Thank you" sounds better 2013-05-26 20:34:20 +03:00
Alexei Sholik 6a0244bd88 Be more strict about changes in master 2013-05-26 20:33:45 +03:00
Alexei Sholik f37e5c6246 Update pull request creation guidelines 2013-05-26 20:30:53 +03:00
Alexei Sholik 96a431945d Don't mention the project owner, this is open source 2013-05-26 20:30:38 +03:00
José Valim fefad03595 Ensure warnings are proper with yanked and reattached overridables 2013-05-26 17:51:42 +02:00
José Valim f76aeba141 Check for imports conflicts in batches 2013-05-26 14:49:58 +02:00
José Valim fbb06f2086 Record each definition on tracker as soon as defined 2013-05-26 14:39:02 +02:00
José Valim ffc91e3004 Rename Kernel.LocalsTracker to Module.DispatchTracker 2013-05-26 14:39:02 +02:00
José Valim 32cc99767b Use precalculated binary size 2013-05-26 14:39:02 +02:00
José Valim c3e7543e48 Extract imports handling to locals tracker (soon to be renamed) 2013-05-26 14:39:02 +02:00
José Valim c7e86b0554 Start refactoring LocalsTracker to support more vertices 2013-05-26 14:39:02 +02:00
José Valim 99ce9250b5 Add @doc false to each gen* callback by default 2013-05-26 14:39:02 +02:00
Alexei Sholik adf4075f36 Don't mention inspect and IO.inspect in IEx.Options
Those function do not query `IEx.Options.get :inspect` by default
2013-05-26 15:30:45 +03:00
José Valim 3c4b6a636e Merge pull request #1117 from pragdave/master
Fix for a problem with String.ends_with?
2013-05-26 01:23:21 -07:00
Dave Thomas 239c41bee2 Switch to using pure binaries 2013-05-25 18:57:44 -05:00
Dave Thomas 478f9bbb52 Fixes a bug in Stringends_with?
iex> String.ends_with? "hello", ["hell", "lo"]
  false

 The problem was that the implementation uses :binary.matches, and that returns only
 one match. It would find "hell", but pos+len != size, and so returned false.

 Changed for a less efficient implementation.
2013-05-25 17:43:02 -05:00
José Valim c3dd4006b0 Reduce atom footprint when compiling an elixir module 2013-05-25 16:37:33 +02:00
José Valim e05448afd7 Remove *_table terminolgy in favor of setup/cleanup 2013-05-25 16:02:19 +02:00
José Valim 55bc80b4e5 Merge pull request #1113 from eproxus/string_end_begin_with
Add String.starts_with? and String.ends_with?
2013-05-25 06:41:41 -07:00
Adam Lindberg ce1b12422d Add String.starts_with? and String.ends_with? 2013-05-25 15:32:15 +02:00
José Valim bbcc640760 Rename Mix.SCM.equals? to Mix.SCM.equal? 2013-05-25 12:18:05 +02:00
Alexei Sholik e08bcbeef6 Mention IEx.Options in CHANGELOG 2013-05-25 01:57:44 +03:00
José Valim d757f31ccd Unused functions with cyclic dependencies are now also warned as unused
Closes #1105
2013-05-25 00:14:37 +02:00
Geoffrey Clements 9a0f8107bd Print all of the error messages using color :error.
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2013-05-24 18:48:17 +02:00
José Valim d4609c6f4e Get rid of elixir_glob now that we run on > R16 2013-05-24 18:01:52 +02:00
José Valim f29c6afe3d Reduce the number of files required for bootstrap 2013-05-24 16:39:42 +02:00
José Valim df51fbe74e Update rebar version 2013-05-24 13:36:59 +02:00
José Valim e6ff35c180 Default info to normal color and make print_help use it 2013-05-24 13:30:48 +02:00
José Valim c20fe6f396 Set encoding to unicode in io device and no longer process inspect_opts 2013-05-24 13:02:14 +02:00
José Valim 86e7069a0a Clean up how we store recorded locals in the imports table 2013-05-24 12:57:28 +02:00
José Valim 334f3ce78a Merge pull request #1106 from alco/iex-opts-1062
Implement IEx.Options per #1062
2013-05-24 03:56:26 -07:00
Alexei Sholik 0da2b590ba Mention IO.ANSI in the doc for :colors 2013-05-24 13:45:51 +03:00
Alexei Sholik 1133581d9e Mention value types for individual options 2013-05-24 12:25:52 +03:00
Alexei Sholik d9a10847a6 Mention IEx.Options in IEx's doc 2013-05-24 12:13:11 +03:00
Alexei Sholik d159e4f592 Make option docs in IEx.Options visible to ExDoc 2013-05-24 12:04:47 +03:00
Alexei Sholik 8530694c76 Move the list of inspect options back to Kernel's docstring 2013-05-24 12:04:47 +03:00
Alexei Sholik a916cf47b4 Fix a bug in option keys validation 2013-05-24 12:04:47 +03:00
Alexei Sholik 3e9b61855c Deprecate IEx.inspect_opts in favor of IEx.Options 2013-05-24 12:04:47 +03:00
Alexei Sholik 9f862be4eb Filter out unknown keyword lists' keys in IEx.Options 2013-05-24 12:04:47 +03:00
Alexei Sholik d2c5fa3ad4 Moduledoc for IEx.Options 2013-05-24 12:04:46 +03:00
Alexei Sholik a8d9ad50d9 Add an example of using the limit inspect opt 2013-05-24 12:04:46 +03:00
Alexei Sholik ffb1863d41 Fix a typo in the doc for IO.ANSI.escape 2013-05-24 12:04:46 +03:00
Alexei Sholik 156ead2c57 Add get/0 and set/0 2013-05-24 12:04:46 +03:00
Alexei Sholik 7ea8faeada Fully support inspect_opts in IEx.Options 2013-05-24 12:04:46 +03:00
Alexei Sholik 82655b61c7 Code and doc formatting fixes 2013-05-24 12:04:46 +03:00
Alexei Sholik 088753d901 Provide color options for escape_fragments 2013-05-24 12:04:46 +03:00
Alexei Sholik 7632c0b17e Fix docstring for escape_fragment 2013-05-24 12:04:46 +03:00
Alexei Sholik 7d594a1953 Simplify color enabling logic 2013-05-24 12:04:46 +03:00
Alexei Sholik 042f5e9c14 Add color options for ls helper 2013-05-24 12:04:45 +03:00
Alexei Sholik 3864b04ac2 Add color option for informational messages 2013-05-24 12:04:45 +03:00
Alexei Sholik 5fac52bf12 Add color option for error messages 2013-05-24 12:04:45 +03:00
Alexei Sholik 63890ef0b7 Initial implementation of IEx.Options 2013-05-24 12:04:45 +03:00
José Valim 0dcd5501f0 Fix a bug where unquote_splicing did not work on the left side of a stab op 2013-05-23 23:50:16 +02:00
José Valim 08e9faabe8 Fix a bug with nested record definitions 2013-05-23 22:53:35 +02:00
José Valim e5f9dbde1a Ensure Enum.to_list and Dict.to_list return the same results for dicts 2013-05-23 22:43:38 +02:00
José Valim f5b0b7160c Do not abort erl names processing when +iex or +compile appear 2013-05-23 22:10:24 +02:00
José Valim d0ca92ad4e Fix a bug where mix deps.get was not retrieving nested dependencies 2013-05-23 21:19:36 +02:00
José Valim ca25049feb Start v0.9.1.dev 2013-05-23 21:19:36 +02:00
José Valim 76a0a64725 Merge pull request #1107 from mururu/check-unused-func
Fix missing unused private function and add test for warnings
2013-05-23 09:02:50 -07:00
Yuki Ito c6e64aba18 Add tests for warning 2013-05-24 00:35:43 +09:00
Yuki Ito 231d65e879 Fix checking unused local functions and default arguments 2013-05-24 00:34:56 +09:00
José Valim 91e8e21242 Release v0.9.0 2013-05-23 16:00:19 +02:00
José Valim d3013aa1d1 Fix autocompletion tests for Enum 2013-05-23 14:40:13 +02:00
José Valim 2c98c0999d An Elixir module now translates to Elixir.Module instead of Elixir-Module 2013-05-23 14:36:56 +02:00
José Valim 94f9d4e3f6 Enum.Iterator -> Enumerable 2013-05-23 14:36:46 +02:00
José Valim 66476b31d1 Merge pull request #1102 from ericmj/reducers
Reducers
2013-05-23 05:25:10 -07:00
Alexei Sholik 7f84211bb3 Fix all "evalute" typos 2013-05-23 00:50:42 +03:00
Eric Meadows-Jonsson 474250e6f4 Update docs for Enum.Iterator and reduce 2013-05-22 21:14:18 +02:00
José Valim 65e67cdc27 Ensure we emit a warning for recursive non used calls 2013-05-22 19:41:03 +02:00
José Valim 6b91e46702 Add missing clause definitions 2013-05-22 19:41:03 +02:00
José Valim c83f136ad4 Merge pull request #1104 from mururu/remove-warning
Remove warning in capture_io
2013-05-22 10:27:48 -07:00
Yuki Ito 59a9ddb7d5 Remove warning in capture_io 2013-05-23 02:20:04 +09:00
José Valim 1ef67c570e Merge pull request #1103 from mururu/iex-test
Capture_io can has mock input and ExUnit.Server stops when getting :eof as an input
2013-05-22 10:03:51 -07:00
Yuki Ito 2403d0c4b9 IEx.Server break out of the loop when accepting :eof as input 2013-05-23 01:59:58 +09:00
Eric Meadows-Jonsson a1ec5f1552 Clean up and fixes in reducer code 2013-05-22 18:43:51 +02:00
Eric Meadows-Jonsson 45485fba59 Remove file open in File.iterator and File.biniterator 2013-05-22 18:03:56 +02:00
Yuki Ito 97ea102a79 Enable ExUnitCaptureIO.capture_io to receive input 2013-05-23 00:45:06 +09:00
José Valim 6e8fb6c630 Merge pull request #1100 from alco/dot-iex
Add support for .iex files
2013-05-22 06:40:41 -07:00
Eric Meadows-Jonsson 854942d3ee Remove iterator references in code 2013-05-22 15:21:29 +02:00
José Valim d99ec5addf Run just R16 on travis 2013-05-22 15:10:02 +02:00
Eric Meadows-Jonsson 3f946d4af0 Use reducers in Enum, File and URI
Remove Enum.equal?
Remove File.iterator/2 and File.biniterator/2
2013-05-22 15:07:13 +02:00
Eric Meadows-Jonsson 9318f747f4 Add reduce to Enum.Iterator protocol 2013-05-22 15:02:38 +02:00
Alexei Sholik e216f18010 Update CHANGELOG 2013-05-22 15:58:30 +03:00
Alexei Sholik ed14ce4bc8 Add --dot-iex command line option to IEx
It allows one to override the standard locations where .iex is searched
and also makes it possible to disable .iex loading completely.
2013-05-22 15:57:37 +03:00
Alexei Sholik 0cb936a600 Make import_file raise ArgumentError 2013-05-22 15:57:37 +03:00
Alexei Sholik 328541be87 We don't need import_file/2
Since the argument to import_file/1 has to be a literal, it's not that
difficult to construct desired path by hand
2013-05-22 15:57:36 +03:00
Alexei Sholik 85b76843b0 Fix sample .iex file in the doc 2013-05-22 15:57:36 +03:00
Alexei Sholik f688899b95 Add ability to disable loading of .iex 2013-05-22 15:57:36 +03:00
Alexei Sholik e3fb2a5acf Remove redundant blank lines in iex helpers' docs 2013-05-22 15:57:36 +03:00
Alexei Sholik 5a99fbfdcd Add import_file/1 and /2 helpers 2013-05-22 15:57:36 +03:00
Alexei Sholik c634a5d232 Add :elixir.scope_for_eval/2 convenience function 2013-05-22 15:57:36 +03:00
Alexei Sholik 9a6ac4f76c Add missing comma in the doc 2013-05-22 15:57:36 +03:00
Alexei Sholik 6af7e0cc6b Split the long :elixir.eval line into shorter ones 2013-05-22 15:57:36 +03:00
Alexei Sholik 7ff3e1f833 Fix a bug in ~/.iex expansion 2013-05-22 15:57:36 +03:00
Alexei Sholik d6d630316e Move load_dot_iex to IEx.Server, after require IEx.Helpers 2013-05-22 15:57:36 +03:00
Alexei Sholik 4714b54222 Don't mention the f() helper for now (it's not supported yet) 2013-05-22 15:57:36 +03:00
Alexei Sholik 4d51bd64f0 Pass the stacktrace explicitly to IEx.Util functions 2013-05-22 15:57:35 +03:00
Alexei Sholik fc1945acff Expand ~/.iex before using it 2013-05-22 15:57:35 +03:00
Alexei Sholik 5c0ddbb063 Include description of .iex in the doc for IEx 2013-05-22 15:57:35 +03:00
Alexei Sholik 880ae1116d Don't wrap Code.require_file 2013-05-22 15:57:35 +03:00
Alexei Sholik 68923112cc Use System.halt instead of :erlang.halt 2013-05-22 15:57:35 +03:00
Alexei Sholik 865608b5de Add initial support for .iex files 2013-05-22 15:57:35 +03:00
José Valim 26640e3940 Start v0.9.0.dev work 2013-05-22 14:56:26 +02:00
202 changed files with 6383 additions and 3373 deletions
+1
View File
@@ -14,3 +14,4 @@ erl_crash.dump
.dialyzer_plt
.dialyzer.base_plt
.*.swp
docs
-4
View File
@@ -7,7 +7,3 @@ notifications:
- yrashk@gmail.com
otp_release:
- R16B
- R15B03
- R15B02
- R15B01
- R15B
+54
View File
@@ -1,3 +1,57 @@
# v0.9.2 (2013-06-13)
* enhancements
* [ExUnit] `capture_io` now captures prompt by default
* [Mix] Automatically import git dependencies from Rebar
* [Mix] Support for dependencies directly from the umbrella application
* [Regex] Add `Regex.escape`
* [String] Add `String.contains?`
* [URI] Implement `Binary.Chars` (aka `to_binary`) for `URI.Info`
* bug fix
* [HashDict] Ensure HashDict uses exact match throughout its implementation
* [IEx] Do not interpret ANSI codes in IEx results
* [IEx] Ensure --cookie is set before accessing remote shell
* [Kernel] Do not ignore nil when dispatching protocols to avoid infinite loops
* [Mix] Fix usage of shell expressions in `Mix.Shell.cmd`
* [Mix] Start the application by default on escripts
* deprecations
* [Regex] `Regex.index/2` is deprecated in favor `Regex.run/3`
* [Kernel] `super` no longer supports implicit arguments
* backwards incompatible changes
* [Kernel] The `=~` operator now returns true or false instead of an index
# v0.9.1 (2013-05-30)
* enhancements
* [IEx] Limit the number of entries kept in history and allow it to be configured
* [Kernel] Add `String.start_with?` and `String.end_with?`
* [Typespec] Allow keywords, e.g. `[foo: integer, bar: boolean | module]`, in typespecs
* bug fix
* [Dict] `Enum.to_list` and `Dict.to_list` now return the same results for dicts
* [IEx] Enable shell customization via the `IEx.Options` module
* [Kernel] Fix a bug where `unquote_splicing` did not work on the left side of a stab op
* [Kernel] Unused functions with cyclic dependencies are now also warned as unused
* [Mix] Fix a bug where `mix deps.get` was not retrieving nested dependencies
* [Record] Fix a bug where nested records cannot be defined
* [Record] Fix a bug where a record named Record cannot be defined
# v0.9.0 (2013-05-23)
* enhancements
* [ExUnit] `ExUnit.CaptureIO` now accepts an input to be used during capture
* [IEx] Add support for .iex files that are loaded during shell's boot process
* [IEx] Add `import_file/1` helper
* backwards incompatible changes
* [Enum] `Enum.Iterator` was replaced by the more composable and functional `Enumerable` protocol which supports reductions
* [File] `File.iterator/1` and `File.biniterator/1` have been removed in favor of the safe `File.iterator!/1` and `File.biniterator!/1` ones
* [Kernel] Erlang R15 is no longer supported
* [Kernel] Elixir modules are now represented as `Elixir.ModuleName` (using `.` instead of `-` as separator)
# v0.8.3 (2013-05-22)
* enhancements
+29 -14
View File
@@ -9,7 +9,7 @@ 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),
[the mailing list](http://groups.google.com/group/elixir-lang-talk),
[Stack Overflow](http://stackoverflow.com/questions/ask?tags=elixir), or
[#elixir-lang](irc://chat.freenode.net/elixir-lang) on Freenode).
@@ -95,8 +95,8 @@ 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.
**IMPORTANT**: By submitting a patch, you agree that your work will be made
public forever and will be licensed under the license 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**
@@ -123,11 +123,16 @@ in order to craft an excellent pull request:
2. If you cloned a while ago, get the latest changes from upstream:
```bash
git checkout <dev-branch>
git pull upstream <dev-branch>
git checkout master
git pull upstream master
```
3. Create a new topic branch (off master) to contain your feature, change, or fix:
3. Create a new topic branch (off of `master`) to contain your feature, change,
or fix.
**IMPORTANT**: Making changes in `master` is discouraged. You should always
keep your local `master` in sync with upstream `master` and make your
changes in topic branches.
```bash
git checkout -b <topic-branch-name>
@@ -139,21 +144,29 @@ in order to craft an excellent pull request:
[interactive rebase](https://help.github.com/articles/interactive-rebase)
feature to tidy up your commits before making them public.
5. 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:
5. 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/)
6. [Open a Pull Request](https://help.github.com/articles/using-pull-requests/)
with a clear title and description.
7. If you haven't updated your pull request for a while, you should consider
rebasing on master and resolving any conflicts.
**IMPORTANT**: _Never ever_ merge upstream `master` into your branches. You
should always `git rebase` on `master` to bring your changes up to date when
necessary.
```bash
git checkout master
git pull upstream master
git checkout <your-topic-branch>
git rebase master
```
We have saved some excellent pull requests we have received in the past in case
you are looking for some examples:
@@ -161,3 +174,5 @@ you are looking for some examples:
* https://github.com/elixir-lang/elixir/pull/1041
* https://github.com/elixir-lang/elixir/pull/1058
* https://github.com/elixir-lang/elixir/pull/1059
Thank you for your contributions!
+13 -9
View File
@@ -11,12 +11,12 @@ INSTALL_PATH := /usr/local
#==> Templates
define APP_TEMPLATE
$(1): lib/$(1)/ebin/Elixir-$(2).beam lib/$(1)/ebin/$(1).app
$(1): lib/$(1)/ebin/Elixir.$(2).beam lib/$(1)/ebin/$(1).app
lib/$(1)/ebin/$(1).app:
@ cd lib/$(1) && ../../bin/elixir -e "Mix.Server.start_link(:dev)" -r mix.exs -e "Mix.Task.run('compile.app')"
lib/$(1)/ebin/Elixir-$(2).beam: $(wildcard lib/$(1)/lib/*.ex) $(wildcard lib/$(1)/lib/*/*.ex) $(wildcard lib/$(1)/lib/*/*/*.ex)
lib/$(1)/ebin/Elixir.$(2).beam: $(wildcard lib/$(1)/lib/*.ex) $(wildcard lib/$(1)/lib/*/*.ex) $(wildcard lib/$(1)/lib/*/*/*.ex)
@ echo "==> $(1) (compile)"
@ $$(ELIXIRC) "lib/$(1)/lib/**/*.ex" -o lib/$(1)/ebin
@@ -27,8 +27,8 @@ endef
#==> Compilation tasks
KERNEL:=lib/elixir/ebin/Elixir-Kernel.beam
UNICODE:=lib/elixir/ebin/Elixir-String-Unicode.beam
KERNEL:=lib/elixir/ebin/Elixir.Kernel.beam
UNICODE:=lib/elixir/ebin/Elixir.String.Unicode.beam
default: compile
@@ -44,7 +44,7 @@ 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 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) VERSION
$(KERNEL): lib/elixir/lib/*.ex lib/elixir/lib/*/*.ex
@@ -96,13 +96,17 @@ clean:
SOURCE_REF = $(shell head="$$(git rev-parse HEAD)" tag="$$(git tag --points-at $$head | tail -1)" ; echo "$${tag:-$$head}\c")
docs: compile
docs: compile ../ex_doc/bin/ex_doc
mkdir -p ebin
rm -rf docs
cp -R -f lib/*/ebin/*.beam ./ebin
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
../ex_doc/bin/ex_doc:
@ echo "ex_doc is not found in ../ex_doc as expected. See README for more information."
@ false
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
@@ -127,11 +131,11 @@ test_erlang: compile
@ $(ERL) -pa lib/elixir/test/ebin -s test_helper test -s erlang halt;
@ echo
test_elixir: test_kernel test_doctest test_mix test_ex_unit test_eex test_iex
test_elixir: test_kernel test_ex_unit test_doc_test test_mix test_eex test_iex
test_doctest: compile
test_doc_test: compile
@ echo "==> doctest (exunit)"
@ cd lib/elixir && ../../bin/elixir -r "test/doctest.exs";
@ cd lib/elixir && ../../bin/elixir -r "test/doc_test.exs";
test_kernel: compile
@ echo "==> kernel (exunit)"
+15 -4
View File
@@ -14,12 +14,21 @@ If you want to contribute to Elixir or run it from source, clone this repository
If tests pass, you are ready to move on to the [Getting Started guide][1] or to try Interactive Elixir by running: `bin/iex` in your terminal.
However, if tests fail, it is likely you have an outdated Erlang version (Elixir requires Erlang R15B or later). You can check your Erlang version by calling `erl` in the command line. You will see some information as follow:
However, if tests fail, it is likely you have an outdated Erlang version (Elixir requires Erlang R16B or later). You can check your Erlang version by calling `erl` in the command line. You will see some information as follow:
Erlang R15B (erts-5.8.4) [source] [64-bit] [smp:2:2] [rq:2] [async-threads:0] [hipe] [kernel-poll:false]
Erlang R16B (erts-5.10.1) [source] [64-bit] [smp:2:2] [rq:2] [async-threads:0] [hipe] [kernel-poll:false]
If you have the correct version and tests still fail, feel free to [open an issue][2].
## Building documentation
Building the documentation requires [ex_doc](https://github.com/elixir-lang/ex_doc) to be installed and built in the same containing folder as elixir.
# After cloning and compiling Elixir
$ git clone git://github.com/elixir-lang/ex_doc.git
$ cd ../ex_doc && ../elixir/bin/mix compile
$ cd ../elixir && make docs
## Contributing
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].
@@ -29,11 +38,13 @@ We appreciate any contribution to Elixir, so check out our [CONTRIBUTING.md](CON
* #elixir-lang on freenode IRC
* [Website][1]
* [Issue tracker][2]
* [Mailing list][3]
* [elixir-talk Mailing list (questions)][3]
* [elixir-core Mailing list (development)][4]
[1]: http://elixir-lang.org
[2]: https://github.com/elixir-lang/elixir/issues
[3]: http://groups.google.com/group/elixir-lang-core
[3]: http://groups.google.com/group/elixir-lang-talk
[4]: http://groups.google.com/group/elixir-lang-core
## License
+1 -1
View File
@@ -1 +1 @@
0.8.3
0.9.2
+8 -3
View File
@@ -26,7 +26,7 @@ fi
readlink_f () {
cd "$(dirname "$1")" > /dev/null
local filename="$(basename "$1")"
filename="$(basename "$1")"
if [ -h "$filename" ]; then
readlink_f "$(readlink "$filename")"
else
@@ -41,14 +41,19 @@ while [ $I -le $# ]; do
S=1
eval "PEEK=\${$I}"
case "$PEEK" in
-v|--compile|--no-halt)
-v|--compile|--no-halt|+iex|+compile)
;;
-e|-r|-pr|-pa|-pz|--remsh|--cookie|-S)
-e|-r|-pr|-pa|-pz|--remsh|-S)
S=2
;;
--detached|--hidden)
ERL="$ERL `echo $PEEK | cut -c 2-`"
;;
--cookie)
I=$(expr $I + 1)
eval "VAL=\${$I}"
ERL="$ERL -setcookie "$VAL""
;;
--sname|--name)
I=$(expr $I + 1)
eval "VAL=\${$I}"
+1 -1
View File
@@ -14,7 +14,7 @@ fi
readlink_f () {
cd "$(dirname "$1")" > /dev/null
local filename="$(basename "$1")"
filename="$(basename "$1")"
if [ -h "$filename" ]; then
readlink_f "$(readlink "$filename")"
else
+4 -2
View File
@@ -14,6 +14,8 @@ if [ $# -gt 0 ] && ([ $1 = "--help" ] || [ $1 = "-h" ]); then
--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
--dot-iex \"path\" Overrides default .iex file and uses path instead;
path can be empty, then no file will be loaded
** Options marked with (*) can be given more than once
** Options given after the .exs file or -- are passed down to the executed code
@@ -23,7 +25,7 @@ fi
readlink_f () {
cd "$(dirname "$1")" > /dev/null
local filename="$(basename "$1")"
filename="$(basename "$1")"
if [ -h "$filename" ]; then
readlink_f "$(readlink "$filename")"
else
@@ -33,4 +35,4 @@ readlink_f () {
SELF=$(readlink_f "$0")
SCRIPT_PATH=$(dirname "$SELF")
ELIXIR_NO_CLI=1 exec "$SCRIPT_PATH"/elixir --no-halt --erl "-user Elixir-IEx-CLI" +iex "$@"
ELIXIR_NO_CLI=1 exec "$SCRIPT_PATH"/elixir --no-halt --erl "-user Elixir.IEx.CLI" +iex "$@"
+3 -3
View File
@@ -76,8 +76,8 @@ defmodule EEx.AssignsEngine do
quote(do: Keyword.get var!(assigns), unquote(name))
end
defp transform(_) do
super
defp transform(arg) do
super(arg)
end
defoverridable [transform: 1]
@@ -94,4 +94,4 @@ defmodule EEx.SmartEngine do
`AssignsEngine` and other conveniences. Read
`EEx.AssignsEngine` for examples.
"""
end
end
+2 -13
View File
@@ -11,7 +11,7 @@ defprotocol Access do
following built-in types: keywords, records and functions.
"""
@only [List, Function, Record, Atom]
@only [List, Record, Atom]
@doc """
Receives the element being accessed and the access item.
@@ -53,20 +53,9 @@ defimpl Access, for: Atom do
def access(nil, _) do
nil
end
def access(atom, _) do
raise "The access protocol can only be invoked for atoms at " <>
"compilation time, tried to invoke it for #{inspect atom}"
end
end
defimpl Access, for: Function do
@doc """
The Access protocol for functions simply invokes
the function passing the item as argument. This
is useful because it allows a function to be
passed as argument in places a dict would also fit.
"""
def access(function, item) do
function.(item)
end
end
+1 -1
View File
@@ -48,7 +48,6 @@ defmodule Application.Behaviour do
"""
# Starts the given application and all of its dependencies that
# have not been started yet recursively.
#
@@ -95,6 +94,7 @@ defmodule Application.Behaviour do
quote location: :keep do
@behavior :application
@doc false
def stop(_state) do
:ok
end
+14 -15
View File
@@ -58,7 +58,15 @@ defimpl Binary.Chars, for: List do
"""
def to_binary(thing) do
iolist_to_binary(thing)
try do
iolist_to_binary(thing)
rescue
ArgumentError ->
raise Protocol.UndefinedError,
protocol: __MODULE__,
structure: thing,
extra: "Only iolists are supported"
end
end
end
@@ -74,20 +82,11 @@ defimpl Binary.Chars, for: Number do
integer_to_binary(thing)
end
to_binary = :proplists.get_value(:float_to_binary,
:proplists.get_value(:exports, :erlang.module_info, []))
def to_binary(thing) when thing > @limit do
float_to_binary(thing, scientific: @digits)
end
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
def to_binary(thing) do
float_to_binary(thing, compact: true, decimals: @digits)
end
end
+8 -17
View File
@@ -144,7 +144,7 @@ defimpl Binary.Inspect, for: Atom do
end
end
# Detect if atom is an atom alias (Elixir-Foo-Bar-Baz)
# Detect if atom is an atom alias (Elixir.Foo.Bar.Baz)
defp valid_ref_identifier?("Elixir" <> rest) do
valid_ref_piece?(rest)
@@ -152,7 +152,7 @@ defimpl Binary.Inspect, for: Atom do
defp valid_ref_identifier?(_), do: false
defp valid_ref_piece?(<<?-, h, t :: binary>>) when h in ?A..?Z do
defp valid_ref_piece?(<<?., h, t :: binary>>) when h in ?A..?Z do
valid_ref_piece? valid_identifier?(t)
end
@@ -296,7 +296,7 @@ defimpl Binary.Inspect, for: List do
defp keyword?([{ key, _value } | rest]) when is_atom(key) do
case atom_to_list(key) do
'Elixir-' ++ _ -> false
'Elixir.' ++ _ -> false
_ -> keyword?(rest)
end
end
@@ -391,21 +391,12 @@ defimpl Binary.Inspect, for: Number do
integer_to_binary(thing)
end
to_binary = :proplists.get_value(:float_to_binary,
:proplists.get_value(:exports, :erlang.module_info, []))
def inspect(thing, _) when thing > @limit do
float_to_binary(thing, scientific: @digits)
end
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
def inspect(thing, _) do
float_to_binary(thing, compact: true, decimals: @digits)
end
end
+3 -5
View File
@@ -7,11 +7,9 @@ defmodule Bitwise do
your module:
iex> use Bitwise
...> bnot 1
iex> bnot 1
-2
iex> use Bitwise
...> 1 &&& 1
iex> 1 &&& 1
1
You can select to include only or skip operators by passing options:
@@ -127,4 +125,4 @@ defmodule Bitwise do
defmacro left >>> right do
quote do: __op__ :bsr, unquote(left), unquote(right)
end
end
end
+8 -14
View File
@@ -57,14 +57,8 @@ 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
Evaluates the contents given by string. The second argument is the
binding (which should be a keyword) followed by a keyword list of
environment options. Those options can be:
@@ -114,7 +108,7 @@ defmodule Code do
end
@doc """
Evalutes the quoted contents.
Evaluates the quoted contents.
This function accepts a list of environment options.
Check `Code.eval_string` for more information.
@@ -196,10 +190,10 @@ defmodule Code do
defp unpack_ast(line, forms), do: { :__block__, [line: line], forms }
@doc """
Loads the given `file`. Accepts `relative_to` as an argument
to tell where the file is located. If the file was already
required/loaded, loads it again. It returns all the modules
defined in the file.
Loads the given `file`. Accepts `relative_to` as an argument to tell where
the file is located. If the file was already required/loaded, loads it again.
It returns a list of tuples { ModuleName, <<byte_code>> }, one tuple for each
module defined in the file.
Notice that if `load_file` is invoked by different processes
concurrently, the target file will be invoked concurrently
@@ -217,8 +211,8 @@ defmodule Code do
@doc """
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.
the file is located. The return value is the same as that of `load_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
+12 -22
View File
@@ -29,7 +29,7 @@ defmodule Dict do
...> dict[:hello]
:world
And also the `Enum.Iterator` protocol, allowing one to write:
And also the `Enumerable` protocol, allowing one to write:
Enum.each(dict, fn ({ k, v }) ->
IO.puts "#{k}: #{v}"
@@ -50,7 +50,6 @@ defmodule Dict do
defcallback equal?(t, t) :: boolean
defcallback get(t, key) :: value
defcallback get(t, key, value) :: value
defcallback get!(t, key) :: value | no_return
defcallback has_key?(t, key) :: boolean
defcallback keys(t) :: list(key)
defcallback merge(t, t) :: t
@@ -131,11 +130,9 @@ defmodule Dict do
## Examples
iex> d = HashDict.new([a: 1])
...> Dict.has_key?(d, :a)
iex> Dict.has_key?(d, :a)
true
iex> d = HashDict.new([a: 1])
...> Dict.has_key?(d, :b)
iex> Dict.has_key?(d, :b)
false
"""
@@ -151,15 +148,11 @@ defmodule Dict do
## Examples
iex> d = HashDict.new([a: 1])
...> Dict.get(d, :a)
iex> Dict.get(d, :a)
1
iex> d = HashDict.new([a: 1])
...> Dict.get(d, :b)
iex> Dict.get(d, :b)
nil
iex> d = HashDict.new([a: 1])
...> Dict.get(d, :b, 3)
iex> Dict.get(d, :b, 3)
3
"""
@spec get(t, key, value) :: value
@@ -179,11 +172,9 @@ defmodule Dict do
## Examples
iex> d = HashDict.new([a: 1])
...> Dict.fetch(d, :a)
iex> Dict.fetch(d, :a)
{ :ok, 1 }
iex> d = HashDict.new([a: 1])
...> Dict.fetch(d, :b)
iex> Dict.fetch(d, :b)
:error
"""
@@ -199,10 +190,9 @@ defmodule Dict do
## Examples
iex> d = HashDict.new([a: 1])
...> Dict.fetch!(d, :a)
iex> Dict.fetch!(d, :a)
1
iex> d = HashDict.new([a: 1])
...> Dict.fetch!(d, :b)
iex> Dict.fetch!(d, :b)
** (KeyError) key not found: :b
"""
@@ -425,11 +415,11 @@ defmodule Dict do
## 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)
[]
+340 -834
View File
File diff suppressed because it is too large Load Diff
+7 -8
View File
@@ -67,9 +67,14 @@ defexception FunctionClauseError, [module: nil, function: nil, arity: nil] do
end
end
defexception Protocol.UndefinedError, [protocol: nil, structure: nil] do
defexception Protocol.UndefinedError, [protocol: nil, structure: nil, extra: nil] do
def message(exception) do
"protocol #{inspect exception.protocol} not implemented for #{inspect exception.structure}"
msg = "protocol #{inspect exception.protocol} not implemented for #{inspect exception.structure}"
if exception.extra do
msg <> ". " <> exception.extra
else
msg
end
end
end
@@ -150,12 +155,6 @@ defmodule Exception do
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 """
Receives a tuple representing a stacktrace entry and formats it.
The current working directory may be given as argument, which
+48 -77
View File
@@ -930,11 +930,6 @@ defmodule File do
`close/1` might return an old write error and not even try to close the file.
See `open/2`.
"""
def close(io_device) when is_function(io_device) do
{ iterator, _ } = io_device.()
iterator.(:close)
end
def close(io_device) do
F.close(io_device)
end
@@ -962,50 +957,27 @@ defmodule File do
end
"""
def iterator(device)
def iterator(file) when is_binary(file) or is_list(file) do
iterator(file, [])
end
def iterator(device) do
fn ->
function = fn
:ok ->
case :io.get_line(device, '') do
:eof ->
close(device)
:stop
{ :error, reason } ->
raise File.IteratorError, reason: reason
data ->
{ data, :ok }
end
:close ->
close(device)
end
{ function, :ok }
fn(fun, acc) ->
do_iterator(device, fun, acc)
end
end
@doc """
Opens the given `file` with the given `mode` and
returns its iterator. Fails for the same reasons
as `File.open`.
"""
def iterator(file, mode) do
case open(file, mode) do
{ :ok, device } -> { :ok, iterator(device) }
error -> error
end
end
@doc """
Same as `iterator/2` but raises if the file
cannot be opened.
returns its iterator. The returned iterator will
fail for the same reasons as `File.open!`. Note
that the file is opened when the iteration begins.
"""
def iterator!(file, mode // []) do
open!(file, mode) |> iterator
fn(fun, acc) ->
device = open!(file, mode)
try do
do_iterator(device, fun, acc)
after
F.close(device)
end
end
end
@doc """
@@ -1013,50 +985,27 @@ defmodule File do
be passed into `Enum` to iterate line by line as a
binary. Check `iterator/1` for more information.
"""
def biniterator(device)
def biniterator(file) when is_binary(file) or is_list(file) do
biniterator(file, [])
end
def biniterator(device) do
fn ->
function = fn
:ok ->
case :file.read_line(device) do
:eof ->
close(device)
:stop
{ :error, reason } ->
raise File.IteratorError, reason: reason
{ :ok, data } ->
{ data, :ok }
end
:close ->
close(device)
end
{ function, :ok }
fn(fun, acc) ->
do_biniterator(device, fun, acc)
end
end
@doc """
Opens the given `file` with the given `mode` and
returns its biniterator. Fails for the same reasons
as `File.open`.
"""
def biniterator(file, mode) do
case open(file, mode) do
{ :ok, device } -> { :ok, biniterator(device) }
error -> error
end
end
@doc """
Same as `biniterator/2` but raises if the file
cannot be opened.
returns its biniterator. The returned iterator will
fail for the same reasons as `File.open!`. Note
that the file is opened when the iteration begins.
"""
def biniterator!(file, mode // []) do
open!(file, mode) |> biniterator
fn(fun, acc) ->
device = open!(file, mode)
try do
do_biniterator(device, fun, acc)
after
F.close(device)
end
end
end
## Helpers
@@ -1075,4 +1024,26 @@ defmodule File do
defp open_defaults([], true), do: [:binary]
defp open_defaults([], false), do: []
defp do_iterator(device, acc, fun) do
case :io.get_line(device, '') do
:eof ->
acc
{ :error, reason } ->
raise File.IteratorError, reason: reason
data ->
do_iterator(device, fun.(data, acc), fun)
end
end
defp do_biniterator(device, acc, fun) do
case F.read_line(device) do
:eof ->
acc
{ :error, reason } ->
raise File.IteratorError, reason: reason
{ :ok, data } ->
do_iterator(device, fun.(data, acc), fun)
end
end
end
+7 -1
View File
@@ -55,28 +55,34 @@ defmodule GenEvent.Behaviour do
@doc false
defmacro __using__(_) do
quote location: :keep do
@behavior :gen_event
@behaviour :gen_event
@doc false
def init(args) do
{ :ok, args }
end
@doc false
def handle_event(_event, state) do
{ :ok, state }
end
@doc false
def handle_call(_request, state) do
{ :ok, :ok, state }
end
@doc false
def handle_info(_msg, state) do
{ :ok, state }
end
@doc false
def terminate(reason, state) do
:ok
end
@doc false
def code_change(_old, state, _extra) do
{ :ok, state }
end
+7 -1
View File
@@ -88,26 +88,32 @@ defmodule GenServer.Behaviour do
quote location: :keep do
@behavior :gen_server
@doc false
def init(args) do
{ :ok, args }
end
@doc false
def handle_call(_request, _from, state) do
{ :noreply, state }
end
@doc false
def handle_info(_msg, state) do
{ :noreply, state }
end
@doc false
def handle_cast(_msg, state) do
{ :noreply, state }
end
def terminate(reason, state) do
@doc false
def terminate(_reason, _state) do
:ok
end
@doc false
def code_change(_old, state, _extra) do
{ :ok, state }
end
+74 -53
View File
@@ -151,15 +151,6 @@ defmodule HashDict do
end
end
@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.
@@ -218,24 +209,14 @@ 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
def equal?(dict1, dict2) do
size = elem(dict1, 1)
case elem(dict2, 1) do
^size ->
dict_equal?(dict1, dict1)
_ ->
false
end
end
@doc """
@@ -246,7 +227,7 @@ defmodule HashDict do
end
def to_list(dict) do
dict_fold(dict, [], [&1|&2])
dict_fold(dict, [], [&1|&2]) |> :lists.reverse
end
@doc """
@@ -333,14 +314,22 @@ defmodule HashDict do
drop(delete(dict, key), keys)
end
def reduce(ordered(bucket: bucket), acc, fun) do
:lists.foldl(fun, acc, bucket)
end
def reduce(trie() = dict, acc, fun) do
dict_fold(dict, acc, fun)
end
## Dict-wide functions
defp dict_get(ordered(bucket: bucket), key) do
:lists.keyfind(key, 1, bucket)
bucket_get(bucket, key)
end
defp dict_get(trie(root: root, depth: depth), key) do
:lists.keyfind(key, 1, node_bucket(root, depth, bucket_hash(key)))
bucket_get(node_bucket(root, depth, bucket_hash(key)), key)
end
defp dict_fold(ordered(bucket: bucket), acc, fun) do
@@ -403,28 +392,60 @@ defmodule HashDict do
end
end
defp dict_equal?(dict1, dict2) do
try do
reduce(dict1, true, fn({ key, value }, acc) ->
case fetch(dict2, key) do
{ _ok, ^value } ->
acc
_ ->
throw(:error)
end
end)
catch
:error -> false
end
end
## Bucket helpers
# Get value from the bucket
defp bucket_get([{k,_}|_bucket], key) when k > key do
false
end
defp bucket_get([{key,_}=e|_bucket], key) do
e
end
defp bucket_get([_e|bucket], key) do
bucket_get(bucket, key)
end
defp bucket_get([], _key) do
false
end
# Puts a value in the bucket
defp bucket_put([{k,_}=e|bucket], key, { :put, value }) when key < k do
{ [{key,value},e|bucket], 1 }
defp bucket_put([{k,_}|_]=bucket, key, { :put, value }) when k > key do
{ [{key, value}|bucket], 1 }
end
defp bucket_put([{k,_}=e|bucket], key, { :update, initial, _fun }) when key < k do
{ [{key,initial},e|bucket], 1 }
defp bucket_put([{k,_}|_]=bucket, key, { :update, initial, _fun }) when k > key do
{ [{key, initial}|bucket], 1 }
end
defp bucket_put([{k,_}=e|bucket], key, value) when key > k do
{ rest, count } = bucket_put(bucket, key, value)
{ [e|rest], count }
end
defp bucket_put([{_,_}|bucket], key, { :put, value }) do
defp bucket_put([{key,_}|bucket], key, { :put, value }) do
{ [{key,value}|bucket], 0 }
end
defp bucket_put([{_,value}|bucket], key, { :update, _initial, fun }) do
{ [{key,fun.(value)}|bucket], 0 }
defp bucket_put([{key,value}|bucket], key, { :update, _initial, fun }) do
{ [{key, fun.(value)}|bucket], 0 }
end
defp bucket_put([e|bucket], key, value) do
{ rest, count } = bucket_put(bucket, key, value)
{ [e|rest], count }
end
defp bucket_put([], key, { :put, value }) do
@@ -437,23 +458,23 @@ defmodule HashDict do
# Puts a value in the bucket without returning
# the operation value
defp bucket_put!([{k,_}=e|bucket], key, value) when key < k, do: [{key,value},e|bucket]
defp bucket_put!([{k,_}=e|bucket], key, value) when key > k, do: [e|bucket_put!(bucket, key, value)]
defp bucket_put!([{_,_}|bucket], key, value), do: [{key,value}|bucket]
defp bucket_put!([{k,_}|_]=bucket, key, value) when k > key, do: [{key,value}|bucket]
defp bucket_put!([{key,_}|bucket], key, value), do: [{key,value}|bucket]
defp bucket_put!([{_,_}=e|bucket], key, value), do: [e|bucket_put!(bucket, key, value)]
defp bucket_put!([], key, value), do: [{key,value}]
# Deletes a key from the bucket
defp bucket_delete([{k,_}|_] = bucket, key) when key < k do
defp bucket_delete([{k,_}|_]=bucket, key) when k > key do
{ bucket, nil, 0 }
end
defp bucket_delete([{k,_}=e|bucket], key) when key > k do
{ rest, value, count } = bucket_delete(bucket, key)
{ [e|rest], value, count }
defp bucket_delete([{key,value}|bucket], key) do
{ bucket, value, -1 }
end
defp bucket_delete([{_,value}|bucket], _key) do
{ bucket, value, -1 }
defp bucket_delete([e|bucket], key) do
{ rest, value, count } = bucket_delete(bucket, key)
{ [e|rest], value, count }
end
defp bucket_delete([], _key) do
@@ -578,8 +599,8 @@ defmodule HashDict do
end
end
defimpl Enum.Iterator, for: HashDict do
def iterator(dict), do: HashDict.to_list(dict)
defimpl Enumerable, for: HashDict do
def reduce(dict, acc, fun), do: HashDict.reduce(dict, acc, fun)
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)
-19
View File
@@ -180,25 +180,6 @@ defmodule IO 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:
+13 -26
View File
@@ -24,9 +24,6 @@ defmodule IO.ANSI do
(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
@@ -36,23 +33,11 @@ defmodule IO.ANSI do
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
match?({:ok, _}, :io.columns(device))
end
@doc "Resets all attributes"
@@ -151,13 +136,13 @@ defmodule IO.ANSI do
end
@doc %B"""
Escapes a string coverting named ANSI sequences into actual ANSI codes.
Escapes a string by converting named ANSI sequences into actual ANSI codes.
The format for referring sequences is `%{red}` and `%{red,bright}` (for
multiple sequences)
The format for referring to 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`.
It will also append a %{reset} to the 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.
@@ -180,10 +165,10 @@ defmodule IO.ANSI do
end
@doc %B"""
Escapes a string coverting named ANSI sequences into actual ANSI codes.
Escapes a string by converting named ANSI sequences into actual ANSI codes.
The format for referring sequences is `%{red}` and `%{red,bright}` (for
multiple sequences)
The format for referring to 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.
@@ -192,8 +177,10 @@ defmodule IO.ANSI do
## Example
iex> IO.ANSI.escape("Hello %{red,bright,green}yes")
"Hello \e[31m\e[1m\e[32myes\e[0m"
iex> IO.ANSI.escape_fragment("Hello %{red,bright,green}yes")
"Hello \e[31m\e[1m\e[32myes"
iex> IO.ANSI.escape_fragment("%{reset}bye")
"\e[0mbye"
"""
@spec escape_fragment(String.t, emit :: boolean) :: String.t
+134 -273
View File
@@ -1362,148 +1362,16 @@ defmodule Kernel do
@doc %B"""
Defines a record.
A record is a tagged tuple which contains one or more elements
and the first element is a module. This macro defines a module
that generates accessors to manipulate the record at both
compilation and runtime.
This macro defines a module that generates accessors to manipulate the record
at both compilation and runtime.
See the `Record` module's documentation for a detailed description of records
in Elixir.
## Examples
defrecord FileInfo, atime: nil, accesses: 0
The line above will define a module named `FileInfo` which
contains a function named `new` that returns a new record
and other functions to read and set the values in the
record:
file_info = FileInfo.new(atime: now())
file_info.atime #=> Returns the value of atime
file_info.atime(now()) #=> Updates the value of atime
A record is simply a tuple where the first element is the record
module name. We can get the record raw representation as follow:
inspect FileInfo.new, raw: true
#=> { FileInfo, nil, nil }
Besides defining readers and writers for each attribute, Elixir also
defines an `update_#{attribute}` function to update the value. Such
functions expect a function as argument that receives the current
value and must return the new one. For example, every time the file
is accessed, the accesses counter can be incremented with:
file_info.update_accesses(fn(old) -> old + 1 end)
Which can be also written as:
file_info.update_accesses(&1 + 1)
## Access syntax
Records in Elixir can be expanded at compilation time to provide
pattern matching and faster operations. For example, the clause
below will only match if a `FileInfo` is given and the number of
accesses is zero:
def enforce_no_access(FileInfo[accesses: 0]), do: :ok
The clause above will expand to:
def enforce_no_access({ FileInfo, _, 0 }), do: :ok
The downside of using such syntax is that, every time the record
changes, your code now needs to be recompiled (which is usually
not a concern since Elixir build tools by default recompiles the
whole project whenever there is a change).
Finally, keep in mind that Elixir triggers some optimizations whenever
the access syntax is used. For example:
def no_access?(FileInfo[] = file_info) do
file_info.accesses == 0
end
Is translated to:
def no_access?({ FileInfo, _, _ } = file_info) do
elem(file_info, 1) == 0
end
Which provides faster get and set times for record operations.
## Runtime introspection
At runtime, developers can use `__record__` to get information
about the given record:
FileInfo.__record__(:name)
#=> FileInfo
FileInfo.__record__(:fields)
#=> [atime: nil, accesses: 0]
In order to quickly access the index of a field, one can use
the `__index__` function:
FileInfo.__index__(:atime)
#=> 0
FileInfo.__index__(:unknown)
#=> nil
## Compile-time introspection
At the compile time, one can access following information about the record
from within the record module:
* `@record_fields` — a keyword list of record fields with defaults
* `@record_types` — a keyword list of record fields with types
defrecord Foo, bar: nil do
record_type bar: nil | integer
IO.inspect @record_fields
IO.inspect @record_types
end
prints out
[bar: nil]
[bar: {:|,[line: ...],[nil,{:integer,[line: ...],nil}]}]
where the last line is a quoted representation of
[bar: nil | integer]
## Documentation
By default records are not documented and have `@moduledoc` set to false.
## Types
Every record defines a type named `t` that can be accessed in typespecs.
For example, assuming the `Config` record defined above, it could be used
in typespecs as follow:
@spec handle_config(Config.t) :: boolean()
Inside the record definition, a developer can define his own types too:
defrecord Config, counter: 0, failures: [] do
@type kind :: term
record_type counter: integer, failures: [kind]
end
When defining a type, all the fields not mentioned in the type are
assumed to have type `term`.
## Importing records
It is also possible to import a public record (a record, defined using
`defrecord`) as a set of private macros (as if it was defined using `defrecordp`):
Record.import Config, as: :config
See `Record.import/2` and `defrecordp/2` documentation for more information
"""
defmacro defrecord(name, fields, do_block // [])
@@ -1515,18 +1383,18 @@ defmodule Kernel do
end
end
@doc """
@doc %B"""
Defines a record with a set of private macros to manipulate it.
A record is a tagged tuple which contains one or more elements
and the first element is a module. This macro defines a set of
macros private to the current module to manipulate the record
exclusively at compilation time.
This macro defines a set of macros private to the current module to
manipulate the record exclusively at compilation time.
`defrecordp` must be used instead of `defrecord` when there is
no interest in exposing the record as a whole. In many ways,
it is similar to Erlang records, since it is only available at
compilation time.
`defrecordp` must be used instead of `defrecord` when there is no interest in
exposing the record outside of the module it's defined in. In many ways, it
is similar to an Erlang record, since it is only available at compilation time.
See the `Record` module's documentation for a detailed description of records
in Elixir.
## Examples
@@ -1571,8 +1439,8 @@ defmodule Kernel do
1) Differently from records, exceptions are documented by default;
2) Exceptions **must** implement `message/1` as API that return a
binary as result;
2) Exceptions **must** implement `message/1` -- a function that returns a
string;
"""
defmacro defexception(name, fields, opts // [], do_block // []) do
@@ -1828,9 +1696,33 @@ defmodule Kernel do
end
@doc """
Makes the given functions in the current module overridable.
An overridable function is lazily defined, allowing a
developer to customize it.
Makes the given functions in the current module overridable. An overridable
function is lazily defined, allowing a developer to customize it.
## Example
defmodule DefaultMod do
defmacro __using__(_opts) do
quote do
def test(x, y) do
x + y
end
defoverridable [test: 2]
end
end
end
defmodule InheritMod do
use DefaultMod
def test(x, y) do
x * y + super(x, y)
end
end
As seen as in the example `super` can be used to call the default
implementation.
"""
defmacro defoverridable(tuples) do
quote do
@@ -1892,17 +1784,26 @@ defmodule Kernel do
The following options are supported:
* :raw - tuples are not formatted as the inspect protocol, they are
always shown as tuples, defaults to false;
* raw -- when true, record tuples are not formatted by the inspect protocol,
but are printed as just tuples; default: false
* :limit - the limit of items that are shown in tuples, bitstrings and
lists. Does not apply to strings;
* limit -- limits the number of items that are printed for tuples, bitstrings,
and lists; does not apply to strings
## Examples
iex> inspect(:foo)
":foo"
iex> inspect [1,2,3,4,5], limit: 3
"[1,2,3,...]"
inspect(ArgumentError[])
#=> "ArgumentError[message: \"argument error\"]"
inspect(ArgumentError[], raw: true)
#=> "{ArgumentError,:__exception__,\"argument error\"}"
Note that the inspect protocol does not necessarily return a valid
representation of an Elixir term. In such cases, the inspected result must
start with `#`. For example, inspecting a function will return:
@@ -1989,21 +1890,11 @@ defmodule Kernel do
quote do: :erlang.setelement(unquote(index) + 1, unquote(tuple), unquote(value))
end
@doc false
defmacro setelem(tuple, index, value) do
IO.puts "setelem is deprecated, please use set_elem instead\n#{Exception.format_stacktrace(__CALLER__.stacktrace)}"
quote do: :erlang.setelement(unquote(index) + 1, unquote(tuple), unquote(value))
end
@doc """
Define insert_elem to insert element into a tuple according to
Elixir conventions (i.e. it expects the tuple as first argument,
zero-index based).
Please note that in versions of Erlang prior to R16B there is no BIF
for this operation and it is emulated by converting the tuple to a list
and back and is, therefore, inefficient.
## Example
iex> tuple = { :bar, :baz }
@@ -2011,30 +1902,11 @@ defmodule Kernel do
{ :foo, :bar, :baz }
"""
defmacro insert_elem(tuple, index, value) when is_integer(index) do
case :proplists.get_value(:insert_element,
:proplists.get_value(:exports, :erlang.module_info,[])) do
3 ->
quote do: :erlang.insert_element(unquote(index + 1), unquote(tuple), unquote(value))
:undefined ->
do_insert_elem(tuple, index, value)
end
end
defmacro insert_elem(tuple, index, value) do
case :proplists.get_value(:insert_element,
:proplists.get_value(:exports, :erlang.module_info,[])) do
3 ->
quote do: :erlang.insert_element(unquote(index) + 1, unquote(tuple), unquote(value))
:undefined ->
do_insert_elem(tuple, index, value)
end
quote do: :erlang.insert_element(unquote(index + 1), unquote(tuple), unquote(value))
end
defp do_insert_elem(tuple, index, value) do
quote do
{h, t} = :lists.split(unquote(index),
tuple_to_list(unquote(tuple)))
list_to_tuple(h ++ [unquote(value)|t])
end
defmacro insert_elem(tuple, index, value) do
quote do: :erlang.insert_element(unquote(index) + 1, unquote(tuple), unquote(value))
end
@doc """
@@ -2053,30 +1925,11 @@ defmodule Kernel do
{ :bar, :baz }
"""
defmacro delete_elem(tuple, index) when is_integer(index) do
case :proplists.get_value(:delete_element,
:proplists.get_value(:exports, :erlang.module_info,[])) do
2 ->
quote do: :erlang.delete_element(unquote(index + 1), unquote(tuple))
:undefined ->
do_delete_elem(tuple, index)
end
end
defmacro delete_elem(tuple, index) do
case :proplists.get_value(:delete_element,
:proplists.get_value(:exports, :erlang.module_info,[])) do
2 ->
quote do: :erlang.delete_element(unquote(index) + 1, unquote(tuple))
:undefined ->
do_delete_elem(tuple, index)
end
quote do: :erlang.delete_element(unquote(index + 1), unquote(tuple))
end
defp do_delete_elem(tuple, index) do
quote do
{h, [_|t]} = :lists.split(unquote(index),
tuple_to_list(unquote(tuple)))
list_to_tuple(h ++ t)
end
defmacro delete_elem(tuple, index) do
quote do: :erlang.delete_element(unquote(index) + 1, unquote(tuple))
end
@doc """
@@ -2654,14 +2507,8 @@ defmodule Kernel do
123
"""
defmacro binary_to_integer(some_binary) do
case :proplists.get_value(:binary_to_integer,
:proplists.get_value(:exports, :erlang.module_info, [])) do
2 ->
quote do: :erlang.binary_to_integer(unquote(some_binary))
:undefined ->
quote do: list_to_integer(binary_to_list(unquote(some_binary)))
end
def binary_to_integer(some_binary) do
:erlang.binary_to_integer(some_binary)
end
@doc """
@@ -2674,14 +2521,8 @@ defmodule Kernel do
1023
"""
defmacro binary_to_integer(some_binary, base) do
case :proplists.get_value(:binary_to_integer,
:proplists.get_value(:exports, :erlang.module_info, [])) do
2 ->
quote do: :erlang.binary_to_integer(unquote(some_binary), unquote(base))
:undefined ->
quote do: list_to_integer(binary_to_list(unquote(some_binary)), unquote(base))
end
def binary_to_integer(some_binary, base) do
:erlang.binary_to_integer(some_binary, base)
end
@doc """
@@ -2693,14 +2534,8 @@ defmodule Kernel do
2.2017764
"""
defmacro binary_to_float(some_binary) do
case :proplists.get_value(:binary_to_float,
:proplists.get_value(:exports, :erlang.module_info, [])) do
1 ->
quote do: :erlang.binary_to_float(unquote(some_binary))
:undefined ->
quote do: list_to_float(binary_to_list(unquote(some_binary)))
end
def binary_to_float(some_binary) do
:erlang.binary_to_float(some_binary)
end
@doc """
@@ -2713,14 +2548,8 @@ defmodule Kernel do
"123"
"""
defmacro integer_to_binary(some_integer) do
case :proplists.get_value(:integer_to_binary,
:proplists.get_value(:exports, :erlang.module_info, [])) do
2 ->
quote do: :erlang.integer_to_binary(unquote(some_integer))
:undefined ->
quote do: list_to_binary(integer_to_list(unquote(some_integer)))
end
def integer_to_binary(some_integer) do
:erlang.integer_to_binary(some_integer)
end
@doc """
@@ -2729,18 +2558,12 @@ defmodule Kernel do
## Examples
iex> integer_to_binary(77)
"77"
iex> integer_to_binary(100, 16)
"64"
"""
defmacro integer_to_binary(some_integer, base) do
case :proplists.get_value(:integer_to_binary,
:proplists.get_value(:exports, :erlang.module_info, [])) do
2 ->
quote do: :erlang.integer_to_binary(unquote(some_integer), unquote(base))
:undefined ->
quote do: list_to_binary(integer_to_list(unquote(some_integer), unquote(base)))
end
def integer_to_binary(some_integer, base) do
:erlang.integer_to_binary(some_integer, base)
end
@doc """
@@ -2753,14 +2576,8 @@ defmodule Kernel do
"7.00000000000000000000e+00"
"""
defmacro float_to_binary(some_float) do
case :proplists.get_value(:float_to_binary,
:proplists.get_value(:exports, :erlang.module_info, [])) do
2 ->
quote do: :erlang.float_to_binary(unquote(some_float))
:undefined ->
quote do: list_to_binary(float_to_list(unquote(some_float)))
end
def float_to_binary(some_float) do
:erlang.float_to_binary(some_float)
end
@doc """
@@ -2996,29 +2813,59 @@ defmodule Kernel do
defmacro left in right
@doc """
Matches the term on the left against the regular expression
on the right. It returns nil if not match happened or the
first match otherwise.
Matches the term on the left against the regular expression or string on the
right. Returns true if `left` matches `right` (if it's a regular expression)
or contains `right` (if it's a string).
## Examples
iex> "abcd" =~ %r/c(d)/
2
true
iex> "abcd" =~ %r/e/
nil
false
iex> "abcd" =~ "bc"
true
iex> "abcd" =~ "ad"
false
"""
# fast path for literal binaries
defmacro left =~ right when is_binary(right) do
quote do
String.contains?(unquote(left), unquote(right))
end
end
# fast path for literal binaries
defmacro left =~ ({:<<>>, _, [_bin]} = right) do
quote do
String.contains?(unquote(left), unquote(right))
end
end
# slow path for everything else
defmacro left =~ right do
quote do
Regex.index(unquote(right), unquote(left))
str = unquote(left)
case unquote(right) do
bin when is_binary(bin) ->
String.contains?(str, bin)
re when is_regex(re) ->
Regex.match?(re, str)
other ->
raise ArgumentError, message: "bad argument on the right side of =~: #{inspect other}"
end
end
end
@doc """
`|>` is called the pipeline operator as it is useful
to write pipeline style expressions. This operator
tntroduces the expression on the left as the first
argument to the expression on the right.
introduces the expression on the left as the first
argument to the function call on the right.
## Examples
@@ -3029,8 +2876,8 @@ defmodule Kernel do
Enum.map(List.flatten([1,[2],3]), &1 * 2)
Please be aware of operator precendence, when using
this operator. For example, the following expression:
Be aware of operator precendence when using this operator.
For example, the following expression:
String.graphemes "Hello" |> Enum.reverse
@@ -3038,9 +2885,9 @@ defmodule Kernel do
String.graphemes("Hello" |> Enum.reverse)
Which will result in an error as Enum.Iterator protocol
Which will result in an error as Enumerable protocol
is not defined for binaries. Adding explicit parenthesis
is recommended:
resolves the ambiguity:
String.graphemes("Hello") |> Enum.reverse
@@ -3057,7 +2904,11 @@ defmodule Kernel do
{ call, line, [left] }
end
defp pipeline_op(left, { call, line, args }) when is_list(args) do
defp pipeline_op(left, { call, line, args }=right) when is_list(args) do
case validate_pipeline_args(args) do
:error -> pipeline_error(right)
_ -> nil
end
{ call, line, [left|args] }
end
@@ -3066,7 +2917,17 @@ defmodule Kernel do
end
defp pipeline_op(_, other) do
raise ArgumentError, message: "Unsupported expression in pipeline |> operator: #{inspect other}"
pipeline_error(other)
end
defp validate_pipeline_args([]), do: nil
defp validate_pipeline_args([ {:&,_,_ } | _ ]), do: :error
defp validate_pipeline_args([_|t]) do
validate_pipeline_args(t)
end
defp pipeline_error(arg) do
raise ArgumentError, message: "Unsupported expression in pipeline |> operator: #{Macro.to_binary arg}"
end
@doc """
+31 -14
View File
@@ -102,9 +102,9 @@ defmodule Kernel.CLI do
# Process shared options
defp process_shared(["-v"|t], config) do
defp process_shared([opt|_t], _config) when opt in ["-v", "--version"] do
IO.puts "Elixir #{System.version}"
process_shared t, config
System.halt 0
end
defp process_shared(["-pa",h|t], config) do
@@ -137,11 +137,7 @@ defmodule Kernel.CLI do
process_shared t, config.update_commands [{:parallel_require,h}|&1]
end
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
defp process_shared([erl,_|t], config) when erl in ["--erl", "--sname", "--name", "--cookie"] do
process_shared t, config
end
@@ -207,7 +203,7 @@ defmodule Kernel.CLI do
"-" <> _ ->
shared_option? list, config, process_compiler(&1, &2)
_ ->
pattern = if File.dir?(h), do: "#{h}/**/*.ex", else: h
pattern = if :filelib.is_dir(h), do: "#{h}/**/*.ex", else: h
process_compiler t, config.update_compile [pattern|&1]
end
end
@@ -222,6 +218,12 @@ defmodule Kernel.CLI do
{ config, t }
end
# This clause is here so that Kernel.CLI does not error out with "unknown
# option"
defp process_iex(["--dot-iex",_|t], config) do
process_iex t, config
end
defp process_iex([opt,_|t], config) when opt in ["--remsh"] do
process_iex t, config
end
@@ -269,7 +271,7 @@ defmodule Kernel.CLI do
end
defp process_command({:script, file}, _config) when is_binary(file) do
if exec = System.find_executable(file) do
if exec = find_elixir_executable(file) do
Code.require_file(exec)
:ok
else
@@ -278,7 +280,7 @@ defmodule Kernel.CLI do
end
defp process_command({:file, file}, _config) when is_binary(file) do
if File.regular?(file) do
if :filelib.is_regular(file) do
Code.require_file(file)
:ok
else
@@ -289,7 +291,7 @@ defmodule Kernel.CLI do
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)
files = Enum.filter files, :filelib.is_regular(&1)
if files != [] do
Enum.map files, Code.require_file(&1)
@@ -302,7 +304,7 @@ defmodule Kernel.CLI do
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)
files = Enum.filter files, :filelib.is_regular(&1)
if files != [] do
Kernel.ParallelRequire.files(files)
@@ -313,11 +315,11 @@ defmodule Kernel.CLI do
end
defp process_command({:compile, patterns}, config) do
File.mkdir_p(config.output)
:filelib.ensure_dir(:filename.join(config.output, "."))
files = Enum.map patterns, Path.wildcard(&1)
files = Enum.uniq(List.concat(files))
files = Enum.filter files, File.regular?(&1)
files = Enum.filter files, :filelib.is_regular(&1)
if files != [] do
Code.compiler_options(config.compiler_options)
@@ -328,4 +330,19 @@ defmodule Kernel.CLI do
{ :error, "--compile : No files matched patterns #{Enum.join(patterns, ",")}" }
end
end
defp find_elixir_executable(file) do
if exec = System.find_executable(file) do
# If we are on Windows, the executable is going to be
# a .bat file that must be in the same directory as
# the actual Elixir executable.
case :os.type() do
{ :win32, _ } ->
exec = Path.rootname(exec)
if File.regular?(exec), do: exec
_ ->
exec
end
end
end
end
+11 -2
View File
@@ -24,7 +24,7 @@ defmodule Kernel.SpecialForms do
## Examples
:{}.(1,2,3)
iex> { 1, 2, 3 }
{ 1, 2, 3 }
"""
@@ -35,7 +35,7 @@ defmodule Kernel.SpecialForms do
## Examples
:[].(1,2,3)
iex> [ 1, 2, 3 ]
[ 1, 2, 3 ]
"""
@@ -424,6 +424,9 @@ defmodule Kernel.SpecialForms do
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.
While this means macros from the same module could have
conflicting variables, it also allows different quotes from
the same module to access them.
The context can be disabled or changed by explicitly setting
the context option. All hygiene mechanisms are based on such
@@ -807,4 +810,10 @@ defmodule Kernel.SpecialForms do
"""
defmacro __aliases__(args)
@doc """
Calls the overriden function when overriding it with `defoverridable`.
See `Kernel.defoverridable` for more information and documentation.
"""
defmacro super(args)
end
+38 -7
View File
@@ -422,6 +422,17 @@ defmodule Kernel.Typespec do
{ :{}, [line: line], args }
end
defp typespec_to_ast({ :type, _line, :list, [arg] }) do
case unpack_typespec_kw(arg, []) do
{ :ok, ast } -> ast
:error -> [typespec_to_ast(arg)]
end
end
defp typespec_to_ast({ :type, _line, :list, args }) do
lc arg inlist args, do: typespec_to_ast(arg)
end
defp typespec_to_ast({ :type, line, :binary, [arg1, arg2] }) do
[arg1, arg2] = lc arg inlist [arg1, arg2], do: typespec_to_ast(arg)
cond do
@@ -473,7 +484,7 @@ defmodule Kernel.Typespec do
{ var, line, nil }
end
# special shortcut(s)
# Special shortcut(s)
defp typespec_to_ast({ :remote_type, line, [{:atom, _, :elixir}, {:atom, _, :char_list}, []] }) do
typespec_to_ast({:type, line, :char_list, []})
end
@@ -482,7 +493,6 @@ defmodule Kernel.Typespec 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)] }
@@ -524,7 +534,7 @@ defmodule Kernel.Typespec do
# Handle unions
defp typespec({ :|, meta, [_,_] } = exprs, vars, caller) do
exprs = :lists.reverse(collect_union(exprs))
exprs = Enum.reverse(collect_union(exprs))
union = lc e inlist exprs, do: typespec(e, vars, caller)
{ :type, line(meta), :union, union }
end
@@ -558,7 +568,6 @@ 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
@@ -609,7 +618,6 @@ defmodule Kernel.Typespec do
end
# Handle blocks
defp typespec({:__block__, _meta, [arg]}, vars, caller) do
typespec(arg, vars, caller)
end
@@ -665,8 +673,11 @@ defmodule Kernel.Typespec do
typespec({ :nonempty_list, [], [spec] }, vars, caller)
end
defp typespec(l, _, _) when is_list(l) do
raise ArgumentError, message: "Unexpected list #{inspect l}"
defp typespec([h|t] = l, vars, caller) do
union = Enum.reduce(t, validate_kw(h, l), fn(x, acc) ->
{ :|, [], [acc, validate_kw(x, l)] }
end)
typespec({ :list, [], [union] }, vars, caller)
end
defp typespec(t, vars, caller) when is_tuple(t) do
@@ -684,6 +695,11 @@ defmodule Kernel.Typespec do
defp collect_union({ :|, _, [a, b] }), do: [b|collect_union(a)]
defp collect_union(v), do: [v]
defp validate_kw({ key, _ } = t, _) when is_atom(key), do: t
defp validate_kw(_, original) do
raise ArgumentError, message: "unexpected list #{inspect original} in typespec"
end
defp fn_args(meta, args, return, vars, caller) do
case [fn_args(meta, args, vars, caller), typespec(return, vars, caller)] do
[{:type,_,:any},{:type,_,:any,[]}] -> []
@@ -703,4 +719,19 @@ defmodule Kernel.Typespec do
defp variable({name, meta, _}) do
{:var, line(meta), name}
end
defp unpack_typespec_kw({ :type, _, :union, [
next,
{ :type, _, :tuple, [{ :atom, _, atom }, type] }
] }, acc) do
unpack_typespec_kw(next, [{atom,typespec_to_ast(type)}|acc])
end
defp unpack_typespec_kw({ :type, _, :tuple, [{ :atom, _, atom }, type] }, acc) do
{ :ok, [{atom,typespec_to_ast(type)}|acc] }
end
defp unpack_typespec_kw(_, _acc) do
:error
end
end
+2 -9
View File
@@ -114,15 +114,6 @@ defmodule Keyword do
end
end
@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
@doc """
Fetchs the value for specific key and return it in a tuple.
If the key does not exist, returns `:error`.
@@ -152,6 +143,7 @@ defmodule Keyword do
iex> Keyword.fetch!([a: 1], :a)
1
iex> Keyword.fetch!([a: 1], :b)
** (KeyError) key not found: :b
@@ -357,6 +349,7 @@ defmodule Keyword do
iex> Keyword.update([a: 1], :a, &1 * 2)
[a: 2]
iex> Keyword.update([a: 1], :b, &1 * 2)
** (KeyError) key not found: :b
+2 -8
View File
@@ -142,12 +142,6 @@ defmodule List do
:lists.last(list)
end
@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 `position` matches with the
@@ -171,7 +165,7 @@ defmodule List do
@doc """
Receives a list of tuples and returns true if there is
a tuple where the item at position `posistion` matches
a tuple where the item at position `position` matches
with the given `item`.
## Examples
@@ -221,7 +215,7 @@ defmodule List do
@doc """
Receives a list of tuples and deletes the first tuple
where the item at position `posistion` matches with the
where the item at position `position` matches with the
given `item`. Returns the new tuple.
## Examples
+7 -16
View File
@@ -34,20 +34,11 @@ defimpl List.Chars, for: Number 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
def to_char_list(thing) when thing > @limit do
float_to_list(thing, scientific: @digits)
end
end
def to_char_list(thing) do
float_to_list(thing, compact: true, decimals: @digits)
end
end
-9
View File
@@ -65,15 +65,6 @@ defmodule ListDict do
end
end
@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`.
+1 -1
View File
@@ -444,7 +444,7 @@ defmodule Macro do
end
defp expand({ :__aliases__, _, _ } = original, env, cache) do
case :elixir_aliases.expand(original, env.aliases, []) do
case :elixir_aliases.expand(original, env.aliases, env.macro_aliases) do
atom when is_atom(atom) -> atom
aliases ->
aliases = lc alias inlist aliases, do: expand(alias, env, cache)
+5 -3
View File
@@ -20,6 +20,7 @@ defmodule Macro.Env do
* `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
* `macro_aliases` - a list of aliases defined inside the current macro
"""
@type name_arity :: { atom, non_neg_integer }
@@ -32,12 +33,13 @@ defmodule Macro.Env do
@type macros :: [{ module, [name_arity] }]
@type context_modules :: [module]
fields = [:module, :file, :line, :function, :aliases,
:context, :requires, :functions, :macros, :context_modules]
fields = [:module, :file, :line, :function, :aliases, :context, :requires,
:functions, :macros, :context_modules, :macro_aliases]
types = quote do: [module: module, file: file, line: line,
function: name_arity, aliases: aliases, requires: requires,
functions: functions, macros: macros, context_modules: context_modules]
functions: functions, macros: macros, context_modules: context_modules,
macro_aliases: aliases]
Record.deffunctions(fields, __MODULE__)
Record.deftypes(fields, types, __MODULE__)
+297 -21
View File
@@ -7,16 +7,296 @@ defmodule Module do
end
end
@moduledoc """
@moduledoc %B'''
This module provides many functions to deal with modules during
compilation time. It allows a developer to dynamically attach
documentation, add, delete and register attributes and so forth.
After the module is compiled, using many of the functions in
After a module is compiled, using many of the functions in
this module will raise errors, since it is out of their purpose
to inspect runtime data. Most of the runtime data can be inspected
via the `__info__(attr)` function attached to each compiled module.
## Module attributes
Each module can be decorated with one or more attributes. The following ones
are currently defined by Elixir:
* `@after_compile`
A hook that will be invoked right after the current module is compiled.
Accepts a module or a tuple `{ <module>, <function atom> }`. The function
must take two arguments: the module environment and its bytecode.
When just a module is provided, the function is assumed to be
`__after_compile__/2`.
**Example**
defmodule M do
@after_compile __MODULE__
def __after_compile__(env, _bytecode) do
IO.inspect env
end
end
* `@before_compile`
A hook that will be invoked before the module is compiled.
Accepts a module or a tuple `{ <module>, <function/macro atom> }`. The
function/macro must take one argument: the module environment. If it's a
macro, its returned value will be injected at the end of the module definition
before the compilation starts.
When just a module is provided, the function/macro is assumed to be
`__before_compile__/1`.
**Example**
defmodule M do
@before_compile __MODULE__
defmacro __before_compile__(_env) do
quote do
def hello, do: "world"
end
end
end
* `@behaviour` (notice the british spelling)
Specify an OTP or user-defined behaviour.
**Example**
defmodule M do
@behaviour gen_event
# ...
end
* `@compile`
Define options for module compilation that are passed to the Erlang
compiler.
Accepts an atom, a tuple, or a list of atoms and tuples.
See http://www.erlang.org/doc/man/compile.html for the list of supported
options.
**Example**
defmodule M do
@compile { :inline, myfun: 1 }
def myfun(arg) do
to_binary(arg)
end
end
* `@doc`
Provide documentation for the function or macro that follows the
attribute.
Accepts a string (often a heredoc) or `false` where `@doc false` will
make the function/macro invisible to the documentation extraction tools
like ExDoc.
Can be invoked more than once.
**Example**
defmodule M do
@doc "Hello world"
def hello do
"world"
end
@doc """
Sum.
"""
def sum(a, b) do
a + b
end
end
* `@file`
Change the filename used in stacktraces for the function or macro that
follows the attribute.
Accepts a string. Can be used more than once.
**Example**
defmodule M do
@doc "Hello world"
@file "hello.ex"
def hello do
"world"
end
end
* `@moduledoc`
Provide documentation for the current module.
Accepts a string (which is often a heredoc) or `false` where
`@moduledoc false` will make the module invisible to the
documentation extraction tools like ExDoc.
**Example**
defmodule M do
@moduledoc """
A very useful module
"""
end
* `@on_definition`
A hook that will be invoked after each function or macro in the current
module is defined. This makes it easy to annotate and customize
functions.
Accepts a module or a tuple `{ <module>, <function atom> }`. The function
must take 6 arguments:
- the module environment
- kind: `:def`, `:defp`, `:defmacro`, or `:defmacrop`
- function/macro name
- list of quoted arguments
- list of quoted guards
- quoted function body
If the function/macro being defined has multiple clauses, the hook will
be called for each clause.
When just a module is provided, the function is assumed to be
`__on_definition__/6`.
Note that you can't provide the current module to `@on_definition`
because the hook function will not be defined in time.
**Example**
defmodule H do
def on_def(_env, kind, name, args, guards, body) do
IO.puts "Defining #{kind} named #{name} with args:"
IO.inspect args
IO.puts "and guards"
IO.inspect guards
IO.puts "and body"
IO.puts Macro.to_binary(body)
end
end
defmodule M do
@on_definition { H, :on_def }
def hello(arg) when is_binary(arg) or is_list(arg) do
"Hello" <> to_binary(arg)
end
def hello(_) do
:ok
end
end
* `@on_load`
A hook that will be invoked whenever the module is loaded.
Accepts a function atom of a function in the current module. The function
must have arity 0 (no arguments) and has to return `:ok`, otherwise the
loading of the module will be aborted.
**Example**
defmodule M do
@on_load :load_check
def load_check do
if some_condition() do
:ok
else
nil
end
end
def some_condition do
false
end
end
* `@vsn`
Specify the module version. Accepts any valid Elixir value.
**Example**
defmodule M do
@vsn "1.0"
end
The following attributes are part of typespecs and are also reserved by
Elixir (see `Kernel.Typespec` for more information about typespecs):
* `@type` - defines a type to be used in `@spec`
* `@typep` - defines a private type to be used in `@spec`
* `@opaque` - defines an opaque type to be used in `@spec`
* `@spec` - provides a specification for a function
* `@callback` - provides a specification for the behavior callback
In addition to the built-in attributes outlined above, custom attributes may
also be added. A custom attribute is any valid identifier prefixed with an
`@` and followed by a valid Elixir value:
defmodule M do
@custom_attr [some: "stuff"]
end
For more advanced options available when defining custom attributes, see
`register_attribute/3`.
## Runtime information about a module
It is possible to query a module at runtime to find out which functions and
macros it defines, extract its docstrings, etc. See `__info__/1`.
'''
@doc """
Provides runtime information about functions and macros defined by the
module, enables docstring extraction, etc.
Each module gets an `__info__/1` function when it's compiled. The function
takes one of the following atoms:
* `:functions` - keyword list of public functions along with their arities
* `:macros` - keyword list of public macros along with their arities
* `:docs` - list of all docstrings attached to functions and macros
using the `@doc` attribute
* `:moduledoc` - tuple `{ <line>, <doc> }` where `line` is the line on
which module definition starts and `doc` is the string
attached to the module using the `@moduledoc` attribute
* `:module` - module name (`Module == Module.__info__(:module)`)
In addition to the above, you may also pass to `__info__/1` any atom supported
by Erlang's `module_info` function which also gets defined for each compiled
module. See http://erlang.org/doc/reference_manual/modules.html#id74571 for
more information.
"""
def __info__(kind)
@doc """
Check if a module is open, i.e. it is currently being defined
@@ -28,10 +308,10 @@ defmodule Module do
end
@doc """
Evalutes the quotes contents in the given module context.
Evaluates the quoted contents in the given module's context.
A list of environment options can also be given as argument.
Check `Code.eval_string` for more information.
See `Code.eval_string` for more information.
Raises an error if the module was already compiled.
@@ -216,17 +496,15 @@ 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 }] ->
ETS.insert(table, {
tuple,
line,
kind,
merge_signatures(old_sign, signature, 1),
doc || old_doc
if(nil?(doc), do: old_doc, else: doc)
})
:ok
_ ->
{ :error, :existing_doc }
end
end
@@ -364,7 +642,8 @@ defmodule Module do
@doc """
Makes the given functions in the given module overridable.
An overridable function is lazily defined, allowing a
developer to customize it.
developer to customize it. See `Kernel.defoverridable` for
more information and documentation.
"""
def make_overridable(module, tuples) do
assert_not_compiled!(:make_overridable, module)
@@ -376,10 +655,12 @@ defmodule Module do
raise "Cannot make function #{name}/#{arity} overridable because it was not defined"
clause ->
:elixir_def.delete_definition(module, tuple)
neighbours = Module.DispatchTracker.yank(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)
merged = :orddict.update(tuple, fn({ count, _, _, _ }) ->
{ count + 1, clause, neighbours, false }
end, { 1, clause, neighbours, false }, old)
put_attribute(module, :__overridable, merged)
end
@@ -528,7 +809,7 @@ defmodule Module do
"""
def split(module) do
tl(String.split(Binary.Chars.to_binary(module), "-"))
tl(String.split(Binary.Chars.to_binary(module), "."))
end
@doc """
@@ -537,13 +818,10 @@ defmodule Module do
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)>>
"Elixir." <> rest = Binary.Chars.to_binary(module)
rest
end
defp to_dot(?-), do: ?.
defp to_dot(l), do: l
@doc false
# Used internally to compile documentation. This function
# is private and must be used only internally.
@@ -559,8 +837,6 @@ defmodule Module do
:ok
{ :error, :private_doc } ->
IO.puts "#{env.file}:#{line} function #{name}/#{arity} is private, @doc's are always discarded for private functions"
{ :error, :existing_doc } ->
IO.puts "#{env.file}:#{line} @doc's for function #{name}/#{arity} have been given more than once, the first version is being kept"
end
delete_attribute(module, :doc)
@@ -616,11 +892,11 @@ defmodule Module do
end
defp function_table_for(module) do
list_to_atom :lists.concat([:f, module])
:elixir_def.table(module)
end
defp docs_table_for(module) do
list_to_atom :lists.concat([:o, module])
:elixir_module.docs_table(module)
end
defp assert_not_compiled!(fun, module) do
+436
View File
@@ -0,0 +1,436 @@
# This is a module Elixir responsible for tracking
# calls in order to extract Elixir modules' behaviour
# during compilation time.
#
# ## Implementation
#
# The implementation uses the digraph module to track
# all dependencies. The graph starts with three main
# vertices:
#
# * `:local` - points to local functions
# * `:import` - points to imported modules
# * `:warn` - points to imported modules that should be warned
# * `:remote` - points to remote modules
#
# Besides those, we have can the following vertices:
#
# * `Module` - a module that was invoked via an import or remotely
# * `{ name, arity }` - a local function/arity pair
# * `{ :import, name, arity }` - an invoked function/arity import
# * `{ :remote, name, arity }` - an remotely invoked function/arity
#
# Each of those vertices can associate to other vertices
# as described below:
#
# * `Module`
# * in neighbours: `:import`, `:remote`, `:warn`,
# `{ :import, name, arity }` and `{ :remote, name arity }`
# * out neighbours: `:warn`
#
# * `{ name, arity }`
# * in neighbours: `:local`, `{ name, arity }`
# * out neighbours: `{ :import, name, arity }` and `{ :remote, name arity }`
#
# * `{ :import, name, arity }`
# * in neighbours: `{ name, arity }`
# * out neighbours: `Module`
#
# * `{ :remote, name, arity }`
# * in neighbours: `{ name, arity }`
# * out neighbours: `Module`
#
# Note that since this is required for bootstrap, we can't use
# any of the `GenServer.Behaviour` conveniences.
defmodule Module.DispatchTracker do
@moduledoc false
@timeout 30_000
@behavior :gen_server
@type ref :: pid | module
@type name :: atom
@type name_arity :: { name, arity }
@type local :: { name, arity }
@type import :: { :import, name, arity }
@type remote :: { :remote, name, arity }
# Public API
@doc """
Receives a dispatch or a module and returns all dispatches
that calls it.
In case the argument is a module, the response will be
made by import and remote dispatches.
In case the argument is another dispatch, the response
will be made by local dispatches.
This function is not recursive, so if A dispatches to
B which dispatches to C, A does not appear in the result,
only B.
"""
@spec dispatches_to(ref, module) :: [import | remote]
@spec dispatches_to(ref, local | import | remote) :: [local]
def dispatches_to(ref, dispatch) do
d = :gen_server.call(to_pid(ref), :digraph, @timeout)
:digraph.in_neighbours(d, dispatch) |> only_tuples
end
@doc """
Receives a local and returns all dispatches from that local.
This function is not recursive, so if A dispatches to
B which dispatches to C, C does not appear in the result,
only B.
"""
@spec dispatches_from(ref, local) :: [local | import | remote]
def dispatches_from(ref, { name, arity }) do
d = :gen_server.call(to_pid(ref), :digraph, @timeout)
:digraph.out_neighbours(d, { name, arity }) |> only_tuples
end
@doc """
Returns all the modules which were imported.
All external dependencies to a module is the sum
of imports and remotes.
"""
@spec imports(ref) :: [module]
def imports(ref) do
d = :gen_server.call(to_pid(ref), :digraph, @timeout)
:digraph.out_neighbours(d, :import)
end
@doc """
Returns all imported modules that had the given
`{ name, arity }` invoked.
"""
@spec imports_with_dispatch(ref, name_arity) :: [module]
def imports_with_dispatch(ref, { name, arity }) do
d = :gen_server.call(to_pid(ref), :digraph, @timeout)
:digraph.out_neighbours(d, { :import, name, arity })
end
@doc """
Returns all the modules which were remotely dispatched
to. All external dependencies to a module is the sum
of imports and remotes.
"""
@spec remotes(ref) :: [module]
def remotes(ref) do
d = :gen_server.call(to_pid(ref), :digraph, @timeout)
:digraph.out_neighbours(d, :remote)
end
@doc """
Returns all modules that had the given `{ name, arity }`
invoked remotely.
"""
@spec remotes_with_dispatch(ref, name_arity) :: [module]
def remotes_with_dispatch(ref, { name, arity }) do
d = :gen_server.call(to_pid(ref), :digraph, @timeout)
:digraph.out_neighbours(d, { :remote, name, arity })
end
@doc """
Returns all locals that are reachable.
By default, all public functions are reachable.
A private function is only reachable if it has
a public function that it invokes directly.
"""
@spec reachable(ref) :: [local]
def reachable(ref) do
d = :gen_server.call(to_pid(ref), :digraph, @timeout)
reduce_reachable(d, :local, [])
end
defp reduce_reachable(d, vertex, vertices) do
neighbours = :digraph.out_neighbours(d, vertex)
neighbours = (lc { _, _ } = t inlist neighbours, do: t) |> :ordsets.from_list
remaining = :ordsets.subtract(neighbours, vertices)
vertices = :ordsets.union(neighbours, vertices)
:lists.foldl(reduce_reachable(d, &1, &2), vertices, remaining)
end
defp to_pid(pid) when is_pid(pid), do: pid
defp to_pid(mod) when is_atom(mod), do: Module.get_attribute(mod, :__dispatch_tracker)
defp only_tuples(list) do
lc x inlist list, is_tuple(x), do: x
end
# Internal API
# Starts the tracker and returns its pid.
@doc false
def start_link do
{ :ok, pid } = :gen_server.start_link(__MODULE__, [], [])
pid
end
# Adds a definition into the tracker. A public
# definition is connected with the :local node
# while a private one is left unreachable until
# a call is made to.
@doc false
def add_definition(pid, kind, tuple) when kind in [:def, :defp, :defmacro, :defmacrop] do
:gen_server.cast(pid, { :add_definition, kind, tuple })
end
# Adds and tracks defaults for a definition into the tracker.
@doc false
def add_defaults(pid, kind, tuple, defaults) when kind in [:def, :defp, :defmacro, :defmacrop] do
:gen_server.cast(pid, { :add_defaults, kind, tuple, defaults })
end
# Adds a local dispatch to the given target.
def add_local(pid, to) when is_tuple(to) do
:gen_server.cast(pid, { :add_local, :local, to })
end
# Adds a local dispatch from-to the given target.
@doc false
def add_local(pid, from, to) when is_tuple(from) and is_tuple(to) do
:gen_server.cast(pid, { :add_local, from, to })
end
# Adds a remote dispatch to the given target.
@doc false
def add_remote(pid, function, module, target) when is_atom(module) and is_tuple(target) do
:gen_server.cast(pid, { :add_remote, function, module, target })
end
# Adds a import dispatch to the given target.
@doc false
def add_import(pid, function, module, target) when is_atom(module) and is_tuple(target) do
:gen_server.cast(pid, { :add_import, function, module, target })
end
# Associates a module with a warn. This adds the given
# module and associates it with the `:import` vertex
# permanently, even if warn is false.
@doc false
def add_warnable(pid, module, warn, line) when is_atom(module) and is_boolean(warn) do
:gen_server.cast(pid, { :add_warnable, module, warn, line })
end
# Collect all unused imports where warn has been set to true.
def collect_unused_imports(pid) do
d = :gen_server.call(pid, :digraph, @timeout)
warnable = :digraph.out_neighbours(d, :warn)
lc mod inlist warnable, not has_imports?(d, mod), line = get_warn_line(d, mod) do
{ mod, line }
end
end
defp get_warn_line(d, mod) do
[edge] = :digraph.out_edges(d, mod)
{ ^edge, ^mod, :warn, line } = :digraph.edge(d, edge)
line
end
defp has_imports?(d, mod) do
Enum.any?(:digraph.in_neighbours(d, mod), match?({ :import, _, _ }, &1))
end
# Yanks a local node. Returns its in and out vertices in a tuple.
@doc false
def yank(pid, local) do
:gen_server.call(to_pid(pid), { :yank, local }, @timeout)
end
def reattach(pid, kind, tuple, neighbours) do
pid = to_pid(pid)
add_definition(pid, kind, tuple)
:gen_server.cast(pid, { :reattach, tuple, neighbours })
end
# Collecting all conflicting imports with the given functions
@doc false
def collect_imports_conflicts(pid, all_defined) do
d = :gen_server.call(pid, :digraph, @timeout)
lc { name, arity } inlist all_defined,
n = :digraph.out_neighbours(d, { :import, name, arity }),
n != [] do
{ n, name, arity }
end
end
# Collect all unused definitions based on the private
# given also accounting the expected amount of default
# clauses a private function have.
@doc false
def collect_unused_locals(pid, private) do
reachable = reachable(pid)
:lists.foldl(collect_unused_locals(&1, &2, reachable), [], private)
end
defp collect_unused_locals({ tuple, kind, 0 }, acc, reachable) do
if :lists.member(tuple, reachable) do
acc
else
[{ :unused_def, tuple, kind }|acc]
end
end
defp collect_unused_locals({ tuple, kind, default }, acc, reachable) when default > 0 do
{ name, arity } = tuple
min = arity - default
max = arity
invoked = lc { n, a } inlist reachable, n == name, a in min..max, do: a
if invoked == [] do
[{ :unused_def, tuple, kind }|acc]
else
case :lists.min(invoked) - min do
0 -> acc
^default -> [{ :unused_args, tuple }|acc]
unused_args -> [{ :unused_args, tuple, unused_args }|acc]
end
end
end
# Stops the gen server
@doc false
def stop(pid) do
:gen_server.cast(pid, :stop)
end
# Callbacks
def init([]) do
d = :digraph.new([:protected])
:digraph.add_vertex(d, :local)
:digraph.add_vertex(d, :import)
:digraph.add_vertex(d, :remote)
:digraph.add_vertex(d, :warn)
{ :ok, d }
end
def handle_call({ :yank, local }, _from, d) do
in_vertices = :digraph.in_neighbours(d, local)
out_vertices = :digraph.out_neighbours(d, local)
:digraph.del_vertex(d, local)
{ :reply, { in_vertices, out_vertices }, d }
end
def handle_call(:digraph, _from, d) do
{ :reply, d, d }
end
def handle_call(_request, _from, d) do
{ :noreply, d }
end
def handle_info(_msg, d) do
{ :noreply, d }
end
def handle_cast({ :add_local, from, to }, d) do
handle_add_local(d, from, to)
{ :noreply, d }
end
def handle_cast({ :add_remote, function, module, { name, arity } }, d) do
handle_import_or_remote(d, :remote, function, module, name, arity)
{ :noreply, d }
end
def handle_cast({ :add_import, function, module, { name, arity } }, d) do
handle_import_or_remote(d, :import, function, module, name, arity)
{ :noreply, d }
end
def handle_cast({ :add_warnable, module, warn, line }, d) do
:digraph.add_vertex(d, module)
replace_edge!(d, :import, module)
if warn do
:digraph.add_edge(d, :warn, module, line)
:digraph.add_edge(d, module, :warn, line)
else
:digraph.del_path(d, :warn, module)
end
{ :noreply, d }
end
def handle_cast({ :add_definition, kind, tuple }, d) do
handle_add_definition(d, kind, tuple)
{ :noreply, d }
end
def handle_cast({ :add_defaults, kind, { name, arity }, defaults }, d) do
lc i inlist :lists.seq(arity - defaults, arity - 1) do
handle_add_definition(d, kind, { name, i })
handle_add_local(d, { name, i }, { name, i + 1 })
end
{ :noreply, d }
end
def handle_cast({ :reattach, tuple, { in_neigh, out_neigh } }, d) do
lc from inlist in_neigh, do: replace_edge(d, from, tuple)
lc to inlist out_neigh, do: replace_edge(d, tuple, to)
{ :noreply, d }
end
def handle_cast(:stop, d) do
{ :stop, :normal, d }
end
def handle_cast(_msg, d) do
{ :noreply, d }
end
def terminate(_reason, _d) do
:ok
end
def code_change(_old, d, _extra) do
{ :ok, d }
end
defp handle_import_or_remote(d, kind, function, module, name, arity) do
:digraph.add_vertex(d, module)
replace_edge!(d, kind, module)
tuple = { kind, name, arity }
:digraph.add_vertex(d, tuple)
replace_edge!(d, tuple, module)
if function != nil do
replace_edge!(d, function, tuple)
end
end
defp handle_add_local(d, from, to) do
:digraph.add_vertex(d, to)
replace_edge!(d, from, to)
end
defp handle_add_definition(d, public, tuple) when public in [:def, :defmacro] do
:digraph.add_vertex(d, tuple)
replace_edge!(d, :local, tuple)
end
defp handle_add_definition(d, private, tuple) when private in [:defp, :defmacrop] do
:digraph.add_vertex(d, tuple)
end
defp replace_edge!(d, from, to) do
unless :lists.member(to, :digraph.out_neighbours(d, from)) do
[:"$e"|_] = :digraph.add_edge(d, from, to)
end
end
defp replace_edge(d, from, to) do
unless :lists.member(to, :digraph.out_neighbours(d, from)) do
:digraph.add_edge(d, from, to)
end
end
end
+7 -7
View File
@@ -385,10 +385,7 @@ defmodule Path do
join(left, atom_to_binary(right))
defp major_os_type do
case :os.type do
{ maj, _ } -> maj
maj -> maj
end
:os.type |> elem(0)
end
defp do_join(<<uc_letter, ?:, rest :: binary>>, relativename, [], :win32) when uc_letter in ?A..?Z, do:
@@ -426,13 +423,16 @@ defmodule Path do
## Examples
iex> Path.split("")
["/"]
[]
iex> Path.split("foo")
["foo"]
iex> Path.split("/foo/bar")
["/", "foo", "bar"]
"""
# Work around a bug in Erlang on UNIX
def split(""), do: []
def split(path) do
FN.split(path)
end
@@ -471,12 +471,12 @@ defmodule Path do
"""
def wildcard(glob) when is_binary(glob) do
paths = :elixir_glob.wildcard :unicode.characters_to_list(glob)
paths = :filelib.wildcard :unicode.characters_to_list(glob)
Enum.map paths, :unicode.characters_to_binary(&1)
end
def wildcard(glob) when is_list(glob) do
:elixir_glob.wildcard glob
:filelib.wildcard glob
end
## Helpers
+1
View File
@@ -360,6 +360,7 @@ defmodule Process do
:erlang.process_info(pid, spec)
end
@compile { :inline, nillify: 1 }
defp nillify(:undefined), do: nil
defp nillify(other), do: other
end
+1 -1
View File
@@ -267,7 +267,7 @@ defmodule Protocol do
true -> unquote(fallback)
false ->
case atom_to_list(first) do
'Elixir-' ++ _ -> __MODULE__.Record
'Elixir.' ++ _ -> __MODULE__.Record
_ -> unquote(fallback)
end
end
+24 -20
View File
@@ -5,14 +5,7 @@ defrecord Range, [:first, :last] do
end
defprotocol Range.Iterator do
@doc """
How to iterate the range, receives the first
and range as arguments. It needs to return a
function that receives an item and returns
a tuple with two elements: the given item
and the next item in the iteration.
"""
def iterator(first, range)
def reduce(first, range, acc, fun)
@doc """
Count how many items are in the range.
@@ -20,12 +13,10 @@ defprotocol Range.Iterator do
def count(first, range)
end
defimpl Enum.Iterator, for: Range do
def iterator(Range[first: first] = range) do
iterator = Range.Iterator.iterator(first, range)
{ iterator, iterator.(first) }
defimpl Enumerable, for: Range do
def reduce(Range[first: first] = range, acc, fun) do
Range.Iterator.reduce(first, range, acc, fun)
end
def member?(Range[first: first, last: last], value) do
value in first..last
end
@@ -36,16 +27,29 @@ defimpl Enum.Iterator, for: Range do
end
defimpl Range.Iterator, for: Number do
def iterator(first, Range[last: last]) when is_integer(first) and is_integer(last) and last >= first do
fn(current) ->
if current > last, do: :stop, else: { current, current + 1 }
def reduce(first, Range[last: last], acc, fun) when is_integer(first) and is_integer(last) do
reducer = if last >= first do
fn(acc, fun) -> do_reducer_up(first, last, acc, fun) end
else
fn(acc, fun) -> do_reducer_down(first, last, acc, fun) end
end
Enumerable.Function.reduce(reducer, acc, fun)
end
def iterator(first, Range[last: last]) when is_integer(first) and is_integer(last) do
fn(current) ->
if current < last, do: :stop, else: { current, current - 1 }
end
defp do_reducer_up(counter, last, acc, _fun) when counter > last do
acc
end
defp do_reducer_up(counter, last, acc, fun) do
do_reducer_up(counter + 1, last, fun.(counter, acc), fun)
end
defp do_reducer_down(counter, last, acc, _fun) when counter < last do
acc
end
defp do_reducer_down(counter, last, acc, fun) do
do_reducer_down(counter - 1, last, fun.(counter, acc), fun)
end
def count(first, Range[last: last]) when is_integer(first) and is_integer(last) and last >= first do
+158 -5
View File
@@ -1,6 +1,155 @@
defmodule Record do
@moduledoc """
@moduledoc %B"""
Functions to define Elixir records
A record is a tagged tuple which contains one or more elements and the first
element is a module. One creates a record by calling `defrecord` or
`defrecordp` which are documented in `Kernel`.
## Examples
defrecord FileInfo, atime: nil, accesses: 0
The line above will define a module named `FileInfo` which
contains a function named `new` that returns a new record
and other functions to read and set the values in the
record:
file_info = FileInfo.new(atime: now())
file_info.atime #=> Returns the value of atime
file_info.atime(now()) #=> Updates the value of atime
# Update multiple attributes at once:
file_info.update(atime: now(), accesses: 1)
# Obtain the keywords representation of a record:
file_info.to_keywords #=> [accesses: 1, atime: {1370,7171,911705}]
A record is simply a tuple where the first element is the record
module name. We can get the record raw representation as follow:
inspect FileInfo.new, raw: true
#=> { FileInfo, nil, nil }
Besides defining readers and writers for each attribute, Elixir also
defines an `update_#{attribute}` function to update the value. Such
functions expect a function as argument that receives the current
value and must return the new one. For example, every time the file
is accessed, the accesses counter can be incremented with:
file_info.update_accesses(fn(old) -> old + 1 end)
Which can be also written as:
file_info.update_accesses(&1 + 1)
## Access syntax
Records in Elixir can be expanded at compilation time to provide
pattern matching and faster operations. For example, the clause
below will only match if a `FileInfo` is given and the number of
accesses is zero:
def enforce_no_access(FileInfo[accesses: 0]), do: :ok
The clause above will expand to:
def enforce_no_access({ FileInfo, _, 0 }), do: :ok
The downside of using such syntax is that, every time the record
changes, your code now needs to be recompiled (which is usually
not a concern since Elixir build tools by default recompiles the
whole project whenever there is a change).
Finally, keep in mind that Elixir triggers some optimizations whenever
the access syntax is used. For example:
def no_access?(FileInfo[] = file_info) do
file_info.accesses == 0
end
Is translated to:
def no_access?({ FileInfo, _, _ } = file_info) do
elem(file_info, 1) == 0
end
Which provides faster get and set times for record operations.
## Runtime introspection
At runtime, developers can use `__record__` to get information
about the given record:
FileInfo.__record__(:name)
#=> FileInfo
FileInfo.__record__(:fields)
#=> [atime: nil, accesses: 0]
In order to quickly access the index of a field, one can use
the `__index__` function:
FileInfo.__index__(:atime)
#=> 0
FileInfo.__index__(:unknown)
#=> nil
## Compile-time introspection
At the compile time, one can access following information about the record
from within the record module:
* `@record_fields` — a keyword list of record fields with defaults
* `@record_types` — a keyword list of record fields with types
defrecord Foo, bar: nil do
record_type bar: nil | integer
IO.inspect @record_fields
IO.inspect @record_types
end
prints out
[bar: nil]
[bar: {:|,[line: ...],[nil,{:integer,[line: ...],nil}]}]
where the last line is a quoted representation of
[bar: nil | integer]
## Documentation
By default records are not documented and have `@moduledoc` set to false.
## Types
Every record defines a type named `t` that can be accessed in typespecs.
For example, assuming the `Config` record defined above, it could be used
in typespecs as follow:
@spec handle_config(Config.t) :: boolean()
Inside the record definition, a developer can define his own types too:
defrecord Config, counter: 0, failures: [] do
@type kind :: term
record_type counter: integer, failures: [kind]
end
When defining a type, all the fields not mentioned in the type are
assumed to have type `term`.
## Importing records
It is also possible to import a public record (a record, defined using
`defrecord`) as a set of private macros (as if it was defined using `defrecordp`):
Record.import Config, as: :config
See `Record.import/2` and `defrecordp/2` documentation for more information
"""
@doc """
@@ -26,18 +175,22 @@ defmodule Record do
block = Keyword.get(opts, :do, nil)
quote do
values = unquote(values)
unquoted_values = unquote(values)
defmodule unquote(name) do
@moduledoc false
import Record.DSL
import Elixir.Record.DSL
@record_fields []
@record_types []
Record.deffunctions(values, __ENV__)
# Reassign values to inner scope to
# avoid conflicts in nested records
values = unquoted_values
Elixir.Record.deffunctions(values, __ENV__)
value = unquote(block)
Record.deftypes(values, @record_types, __ENV__)
Elixir.Record.deftypes(values, @record_types, __ENV__)
value
end
end
+30 -15
View File
@@ -79,20 +79,12 @@ defmodule Regex do
end
end
@doc """
Runs the regular expression against the given string
and returns the index (zero indexes) where the first
match occurs, nil otherwise.
## Examples
iex> Regex.index(%r/c(d)/, "abcd")
2
iex> Regex.index(%r/e/, "abcd")
nil
"""
@doc false
def index(regex(re_pattern: compiled), string) do
IO.puts "Regex.index(re, string) is deprecated. Please use " <>
"Regex.run(re, string, return: :index) instead."
Exception.print_stacktrace
case :re.run(string, compiled, [{ :capture, :first, :index }]) do
:nomatch -> nil
{ :match, [{index,_}] } -> index
@@ -125,6 +117,8 @@ defmodule Regex do
["cd", "d"]
iex> Regex.run(%r/e/, "abcd")
nil
iex> Regex.run(%r/c(d)/, "abcd", return: :index)
[{2,2},{3,1}]
"""
def run(regex, string, options // [])
@@ -291,6 +285,25 @@ defmodule Regex do
:re.replace(string, compiled, replacement, opts)
end
{ :ok, pattern } = :re.compile(%B"[.^$*+?()[{\\\|\s#]", [:unicode])
@escape_pattern pattern
@doc %B"""
Escapes a string to be literally matched in a regex.
## Examples
iex> Regex.escape(".")
"\\."
iex> Regex.escape("\\what if")
"\\\\what\\ if"
"""
@spec escape(String.t | char_list) :: String.t | char_list
def escape(string) do
:re.replace(string, @escape_pattern, "\\\\&", [:global, { :return, return_for(string) }])
end
# Helpers
@doc false
@@ -327,10 +340,12 @@ defmodule Regex do
end
end
{ :ok, pattern } = :re.compile(%B"\(\?<(?<G>[^>]*)>")
@groups_pattern pattern
defp parse_groups(source) do
options = [:global, {:capture, ['G'], :binary}]
{:ok, pattern} = :re.compile(%B"\(\?<(?<G>[^>]*)>")
case :re.run(source, pattern, options) do
case :re.run(source, @groups_pattern, options) do
:nomatch -> []
{ :match, results } ->
lc [group] inlist results, do: binary_to_atom(group)
+112 -2
View File
@@ -752,7 +752,7 @@ defmodule String do
"""
@spec to_integer(t) :: {integer, t} | :error
def to_integer(string) do
{result, remainder} = :string.to_integer(binary_to_list(string))
case result do
@@ -785,7 +785,7 @@ defmodule String do
charlist = binary_to_list(string)
{result, remainder} = :string.to_float(charlist)
case result do
:error ->
:error ->
{int_result, int_remainder} = :string.to_integer(charlist)
case int_result do
:error -> :error
@@ -795,4 +795,114 @@ defmodule String do
end
end
@doc """
Returns true if `string` starts with any of the prefixes given, otherwise
false. `prefixes` can be either a single prefix or a list of prefixes.
## Examples
iex> String.starts_with? "elixir", "eli"
true
iex> String.starts_with? "elixir", ["erlang", "elixir"]
true
iex> String.starts_with? "elixir", ["erlang", "ruby"]
false
"""
@spec starts_with?(t, t | [t]) :: boolean
def starts_with?(string, prefixes) when is_list(prefixes) do
Enum.any?(prefixes, do_starts_with(string, &1))
end
def starts_with?(string, prefix) do
do_starts_with(string, prefix)
end
defp do_starts_with(_, "") do
true
end
defp do_starts_with(string, prefix) when is_binary(prefix) do
match?({0,_}, :binary.match(string, prefix))
end
defp do_starts_with(_, _) do
raise ArgumentError
end
@doc """
Returns true if `string` ends with any of the suffixes given, otherwise
false. `suffixes` can be either a single suffix or a list of suffixes.
## Examples
iex> String.ends_with? "language", "age"
true
iex> String.ends_with? "language", ["youth", "age"]
true
iex> String.ends_with? "language", ["youth", "elixir"]
false
"""
@spec ends_with?(t, t | [t]) :: boolean
def ends_with?(string, suffixes) when is_list(suffixes) do
Enum.any?(suffixes, do_ends_with(string, &1))
end
def ends_with?(string, suffix) do
do_ends_with(string, suffix)
end
defp do_ends_with(_, "") do
true
end
defp do_ends_with(string, suffix) when is_binary(suffix) do
string_size = size(string)
suffix_size = size(suffix)
scope = {string_size - suffix_size, suffix_size}
(suffix_size <= string_size) and (:nomatch != :binary.match(string, suffix, [scope: scope]))
end
defp do_ends_with(_, _) do
raise ArgumentError
end
@doc """
Returns true if `string` contains match, otherwise false.
`matches` can be either a single string or a list of strings.
## Examples
iex> String.contains? "elixir of life", "of"
true
iex> String.contains? "elixir of life", ["life", "death"]
true
iex> String.contains? "elixir of life", ["death", "mercury"]
false
"""
@spec contains?(t, t | [t]) :: boolean
def contains?(string, matches) when is_list(matches) do
Enum.any?(matches, do_contains(string, &1))
end
def contains?(string, match) do
do_contains(string, match)
end
defp do_contains(_, "") do
true
end
defp do_contains(string, match) when is_binary(match) do
:nomatch != :binary.match(string, match)
end
defp do_contains(_, _) do
raise ArgumentError
end
end
+52 -31
View File
@@ -14,20 +14,40 @@ defmodule System do
with the VM or the host system.
"""
# Read and strip the version from the `VERSION` file.
defmacrop get_version do
Regex.replace %r/^\s+|\s+$/, File.read!("VERSION"), ""
defp strip_re(iodata, pattern) do
:re.replace(iodata, pattern, "", [return: :binary])
end
# Tries to run `git describe --always --tags`. In case of success
# returns the most recent tag, otherwise returns an empty string.
defp read_stripped(path) do
case :file.read_file(path) do
{ :ok, binary } ->
strip_re(binary, "^\s+|\s+$")
_ -> ""
end
end
# Read and strip the version from the `VERSION` file.
defmacrop get_version do
case read_stripped("VERSION") do
"" -> raise CompileError, message: "could not read the version number from VERSION"
data -> data
end
end
# Tries to run `git describe --always --tags`. In the case of success returns
# the most recent tag. If that is not available, tries to read the commit hash
# from .git/HEAD. If that fails, returns an empty string.
defmacrop get_describe do
dotgit = Path.join(File.cwd!, ".git")
if :os.find_executable('git') && File.exists?(dotgit) do
data = :os.cmd('git describe --always --tags')
Regex.replace %r/\n/, to_binary(data), ""
else
""
dirpath = ".git"
case :file.read_file_info(dirpath) do
{ :ok, _ } ->
if :os.find_executable('git') do
data = :os.cmd('git describe --always --tags')
strip_re(data, "\n")
else
read_stripped(:filename.join(".git", "HEAD"))
end
_ -> ""
end
end
@@ -133,15 +153,22 @@ defmodule System do
end
defp write_env_tmp_dir(env) do
case System.get_env(env) do
case get_env(env) do
nil -> nil
tmp -> write_tmp_dir tmp
end
end
defp write_tmp_dir(dir) do
case File.stat(dir) do
{ :ok, File.Stat[type: :directory, access: access] } when access in [:read_write, :write] -> dir
case :file.read_file_info(dir) do
{:ok, info} ->
type_index = File.Stat.__index__ :type
access_index = File.Stat.__index__ :access
case { elem(info, type_index), elem(info, access_index) } do
{ :directory, access } when access in [:read_write, :write] ->
dir
_ -> nil
end
{ :error, _ } -> nil
end
end
@@ -177,21 +204,23 @@ defmodule System do
end
@doc """
This functions looks up an executable program given
This function looks up an executable program given
its name using the environment variable PATH on Unix
and Windows.
and Windows. It also considers the proper executable
extension for each OS, so for Windows it will try to
lookup files with `.com`, `.cmd` or similar extensions.
If `command` is a char list, a char list is returned.
If `program` is a char list, a char list is returned.
Returns a binary otherwise.
"""
@spec find_executable(char_list) :: char_list | nil
@spec find_executable(String.t) :: String.t | nil
def find_executable(command) when is_list(command) do
:os.find_executable(command) || nil
def find_executable(program) when is_list(program) do
:os.find_executable(program) || nil
end
def find_executable(command) do
case :os.find_executable(to_char_list(command)) do
def find_executable(program) do
case :os.find_executable(to_char_list(program)) do
false -> nil
other -> list_to_binary(other)
end
@@ -294,19 +323,11 @@ defmodule System do
def halt(status // 0, options // [])
def halt(status, options) when is_integer(status) or status == :abort do
do_halt(status, options)
:erlang.halt(status, options)
end
def halt(status, options) do
do_halt(to_char_list(status), options)
end
# Support R15B
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."
defp do_halt(status, _options), do: :erlang.halt(status)
:erlang.halt(to_char_list(status), options)
end
## Helpers
+36 -9
View File
@@ -37,14 +37,16 @@ defmodule URI do
the query string in steps.
"""
def query_decoder(q) when is_binary(q) do
fn -> { do_decoder(&1), q } end
fn(acc, fun) ->
do_decoder(q, acc, fun)
end
end
defp do_decoder("") do
:stop
defp do_decoder("", acc, _fun) do
acc
end
defp do_decoder(q) do
defp do_decoder(q, acc, fun) do
next =
case :binary.split(q, "&") do
[first, rest] -> rest
@@ -57,7 +59,7 @@ defmodule URI do
[ key ] -> { decode(key), nil }
end
{ current, next }
do_decoder(next, fun.(current, acc), fun)
end
defp pair({k, v}) do
@@ -148,7 +150,8 @@ defmodule URI do
scheme_specific(scheme, info)
end
defp scheme_specific(scheme, info) do
@doc false
def scheme_module(scheme) do
if scheme do
module =
try do
@@ -158,10 +161,14 @@ defmodule URI do
end
if module && Code.ensure_loaded?(module) do
module.parse(default_port(info, module))
else
info
module
end
end
end
defp scheme_specific(scheme, info) do
if module = scheme_module(scheme) do
module.parse(default_port(info, module))
else
info
end
@@ -200,3 +207,23 @@ defmodule URI do
:ok
end
end
defimpl Binary.Chars, for: URI.Info do
def to_binary(uri) do
result = ""
if module = URI.scheme_module(uri.scheme) do
if module.default_port == uri.port, do: uri = uri.port(nil)
end
if uri.scheme, do: result = result <> uri.scheme <> "://"
if uri.userinfo, do: result = result <> uri.userinfo <> "@"
if uri.host, do: result = result <> uri.host
if uri.port, do: result = result <> ":" <> integer_to_binary(uri.port)
if uri.path, do: result = result <> uri.path
if uri.query, do: result = result <> "?" <> uri.query
if uri.fragment, do: result = result <> "#" <> uri.fragment
result
end
end
+26 -18
View File
@@ -57,32 +57,36 @@ defmodule String.Unicode do
# Downcase
def downcase(""), do: ""
def downcase(string), do: do_downcase(string) |> list_to_binary
lc { codepoint, _upper, lower, _title } inlist codes, lower && lower != codepoint do
def downcase(unquote(codepoint) <> rest) do
unquote(lower) <> downcase(rest)
defp do_downcase(unquote(codepoint) <> rest) do
unquote(binary_to_list(lower)) ++ downcase(rest)
end
end
def downcase(<< char, rest :: binary >>) do
<< char >> <> downcase(rest)
defp do_downcase(<< char, rest :: binary >>) do
[char|do_downcase(rest)]
end
defp do_downcase(""), do: []
# Upcase
def upcase(""), do: ""
def upcase(string), do: do_upcase(string) |> list_to_binary
lc { codepoint, upper, _lower, _title } inlist codes, upper && upper != codepoint do
def upcase(unquote(codepoint) <> rest) do
unquote(upper) <> upcase(rest)
defp do_upcase(unquote(codepoint) <> rest) do
unquote(binary_to_list(upper)) ++ do_upcase(rest)
end
end
def upcase(<< char, rest :: binary >>) do
<< char >> <> upcase(rest)
defp do_upcase(<< char, rest :: binary >>) do
[char|do_upcase(rest)]
end
defp do_upcase(""), do: []
# Titlecase once
def titlecase_once(""), do: { "", "" }
@@ -109,23 +113,27 @@ defmodule String.Unicode do
def lstrip(other) when is_binary(other), do: other
def rstrip(""), do: ""
def rstrip(string) when is_binary(string) do
do_rstrip(string, "")
do_rstrip(string, [], [])
end
lc codepoint inlist whitespace do
defp do_rstrip(unquote(codepoint) <> rest, buffer) do
do_rstrip(rest, unquote(codepoint) <> buffer)
c = binary_to_list(codepoint) |> :lists.reverse
defp do_rstrip(unquote(codepoint) <> rest, acc1, acc2) do
do_rstrip(rest, unquote(c) ++ (acc1 || acc2), acc2)
end
end
defp do_rstrip(<< char, rest :: binary >>, buffer) do
<< buffer :: binary, char, do_rstrip(rest, "") :: binary >>
defp do_rstrip(<< char, rest :: binary >>, nil, acc2) do
do_rstrip(rest, nil, [char|acc2])
end
defp do_rstrip(<<>>, _), do: <<>>
defp do_rstrip(<< char, rest :: binary >>, acc1, _acc2) do
do_rstrip(rest, nil, [char|acc1])
end
defp do_rstrip(<<>>, _acc1, acc2), do: acc2 |> :lists.reverse |> list_to_binary
# Split
+1 -1
View File
@@ -24,4 +24,4 @@
{verbose, false}
]}.
{require_otp_vsn,"(R15|R16).*"}.
{require_otp_vsn,"(R16).*"}.
+31 -21
View File
@@ -1,11 +1,11 @@
-module(elixir).
-behaviour(application).
-export([main/1, start_cli/0,
scope_for_eval/1, eval/2, eval/3, eval/4,
scope_for_eval/1, scope_for_eval/2,
eval/2, eval/3, eval/4,
eval_quoted/2, eval_quoted/3, eval_quoted/4,
eval_forms/3, translate_forms/3]).
-include("elixir.hrl").
-compile({parse_transform, elixir_transform}).
%% Top level types
-export_type([char_list/0, as_boolean/1]).
@@ -46,37 +46,47 @@ start_cli() ->
%% EVAL HOOKS
scope_for_eval(Opts) ->
case lists:keyfind(file, 1, Opts) of
{ file, RawFile } -> File = to_binary(RawFile);
false -> File = <<"nofile">>
scope_for_eval(#elixir_scope{
file = <<"nofile">>,
local = nil,
aliases = [],
requires = elixir_dispatch:default_requires(),
functions = elixir_dispatch:default_functions(),
macros = elixir_dispatch:default_macros()
}, Opts).
scope_for_eval(Scope, Opts) ->
File = case lists:keyfind(file, 1, Opts) of
{ file, RawFile } -> to_binary(RawFile);
false -> Scope#elixir_scope.file
end,
case lists:keyfind(delegate_locals_to, 1, Opts) of
{ delegate_locals_to, Local } -> Local;
false -> Local = nil
Local = case lists:keyfind(delegate_locals_to, 1, Opts) of
{ delegate_locals_to, LocalOpt } -> LocalOpt;
false -> Scope#elixir_scope.local
end,
case lists:keyfind(aliases, 1, Opts) of
{ aliases, Aliases } -> Aliases;
false -> Aliases = []
Aliases = case lists:keyfind(aliases, 1, Opts) of
{ aliases, AliasesOpt } -> AliasesOpt;
false -> Scope#elixir_scope.aliases
end,
case lists:keyfind(requires, 1, Opts) of
{ requires, List } -> Requires = ordsets:from_list(List);
false -> Requires = elixir_dispatch:default_requires()
Requires = case lists:keyfind(requires, 1, Opts) of
{ requires, List } -> ordsets:from_list(List);
false -> Scope#elixir_scope.requires
end,
case lists:keyfind(functions, 1, Opts) of
{ functions, Functions } -> Functions;
false -> Functions = elixir_dispatch:default_functions()
Functions = case lists:keyfind(functions, 1, Opts) of
{ functions, FunctionsOpt } -> FunctionsOpt;
false -> Scope#elixir_scope.functions
end,
case lists:keyfind(macros, 1, Opts) of
{ macros, Macros } -> Macros;
false -> Macros = elixir_dispatch:default_macros()
Macros = case lists:keyfind(macros, 1, Opts) of
{ macros, MacrosOpt } -> MacrosOpt;
false -> Scope#elixir_scope.macros
end,
#elixir_scope{
Scope#elixir_scope{
file=File, local=Local,
macros=Macros, functions=Functions,
requires=Requires, aliases=Aliases }.
+9 -16
View File
@@ -2,7 +2,6 @@
-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}).
%% Store an alias in the given scope
store(_Meta, New, New, S) -> S;
@@ -57,7 +56,7 @@ expand({ '__aliases__', _Meta, List }, _Aliases) ->
List.
expand_one(H, Aliases) ->
Lookup = list_to_atom("Elixir-" ++ atom_to_list(H)),
Lookup = list_to_atom("Elixir." ++ atom_to_list(H)),
case lookup(Lookup, Aliases) of
Lookup -> false;
Else -> Else
@@ -87,9 +86,9 @@ ensure_loaded(Line, File, Ref, FileModules) ->
last(Atom) ->
Last = last(lists:reverse(atom_to_list(Atom)), []),
list_to_atom("Elixir-" ++ Last).
list_to_atom("Elixir." ++ Last).
last([$-|_], Acc) -> Acc;
last([$.|_], Acc) -> Acc;
last([H|T], Acc) -> last(T, [H|Acc]);
last([], Acc) -> Acc.
@@ -118,12 +117,12 @@ nesting_alias(Prefix, Full) ->
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])) };
{ list_to_atom("Elixir." ++ H), concat(lists:reverse([H|Acc])) };
do_nesting(_, _, _Acc) ->
false.
list_nesting(Atom) ->
case string:tokens(atom_to_list(Atom), "-") of
case string:tokens(atom_to_list(Atom), ".") of
["Elixir"|T] -> T;
_ -> []
end.
@@ -138,20 +137,14 @@ raw_concat(['Elixir'|Args]) -> do_concat(Args);
raw_concat(Args) -> do_concat(Args).
do_concat(Args) ->
Aliases = [to_partial(Arg) || Arg <- Args, Arg /= nil],
Aliases = [to_partial(Arg) || Arg <- Args],
"Elixir" ++ lists:concat(Aliases).
to_partial(Arg) when is_binary(Arg) -> to_partial(binary_to_list(Arg));
to_partial(Arg) when is_atom(Arg) -> to_partial(atom_to_list(Arg));
to_partial("Elixir-" ++ Arg) -> dot_to_dash([$-|Arg]);
to_partial([$-|_] = Arg) -> dot_to_dash(Arg);
to_partial(Arg) when is_list(Arg) -> [$-|dot_to_dash(Arg)].
dot_to_dash(List) ->
[case X of
$. -> $-;
_ -> X
end || X <- List].
to_partial("Elixir." ++ Arg) -> [$.|Arg];
to_partial([$.|_] = Arg) -> Arg;
to_partial(Arg) when is_list(Arg) -> [$.|Arg].
%% Lookup an alias in the current scope.
+2 -2
View File
@@ -1,5 +1,5 @@
%% Handle code related to rocket args, guard and -> matching
%% for case, fn, receive and friends. try is handled in elixir_try.
%% Handle code related to args, guard and -> matching for case,
%% fn, receive and friends. try is handled in elixir_try.
-module(elixir_clauses).
-export([
assigns/3, assigns_block/5, assigns_block/6, extract_last_guards/1,
+4 -10
View File
@@ -1,8 +1,7 @@
-module(elixir_compiler).
-export([get_opts/0, get_opt/1, get_opt/2, string/2, file/1, file_to_path/2]).
-export([get_opts/0, get_opt/1, get_opt/2, string/2, quoted/2, file/1, file_to_path/2]).
-export([core/0, module/3, eval_forms/4, format_error/1]).
-include("elixir.hrl").
-compile({parse_transform, elixir_transform}).
%% Public API
@@ -213,7 +212,6 @@ core_main() ->
"lib/elixir/lib/kernel/typespec.ex",
"lib/elixir/lib/module.ex",
"lib/elixir/lib/record.ex",
"lib/elixir/lib/record/extractor.ex",
"lib/elixir/lib/macro.ex",
"lib/elixir/lib/macro/env.ex",
"lib/elixir/lib/exception.ex",
@@ -222,18 +220,14 @@ core_main() ->
"lib/elixir/lib/enum.ex",
"lib/elixir/lib/binary/inspect.ex",
"lib/elixir/lib/binary/chars.ex",
"lib/elixir/lib/list/chars.ex",
"lib/elixir/lib/io.ex",
"lib/elixir/lib/file.ex",
"lib/elixir/lib/path.ex",
"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",
"lib/elixir/lib/kernel/record_rewriter.ex"
"lib/elixir/lib/kernel/record_rewriter.ex",
"lib/elixir/lib/module/dispatch_tracker.ex"
].
%% ERROR HANDLING
@@ -253,4 +247,4 @@ format_errors(File, Errors) ->
format_warnings(Bootstrap, File, Warnings) ->
lists:foreach(fun ({_, Each}) ->
lists:foreach(fun (Warning) -> elixir_errors:handle_file_warning(Bootstrap, File, Warning) end, Each)
end, Warnings).
end, Warnings).
+47 -33
View File
@@ -2,8 +2,8 @@
-module(elixir_def).
-export([table/1,
clauses_table/1,
build_table/1,
delete_table/1,
setup/1,
cleanup/1,
reset_last/1,
lookup_definition/2,
delete_definition/2,
@@ -15,22 +15,25 @@
unwrap_stored_definitions/2,
format_error/1]).
-include("elixir.hrl").
-compile({parse_transform, elixir_transform}).
-define(attr, '__def_table').
-define(clauses_attr, '__clauses_table').
%% Table management functions. Called internally.
table(Module) -> ?atom_concat([f, Module]).
clauses_table(Module) -> ?atom_concat([c, Module]).
table(Module) ->
ets:lookup_element(Module, ?attr, 2).
build_table(Module) ->
FunctionsTable = table(Module),
ClausesTable = clauses_table(Module),
ets:new(FunctionsTable, [set, named_table, public]),
ets:new(ClausesTable, [bag, named_table, public]),
clauses_table(Module) ->
ets:lookup_element(Module, ?clauses_attr, 2).
setup(Module) ->
ets:insert(Module, { ?attr, ets:new(Module, [set, public]) }),
ets:insert(Module, { ?clauses_attr, ets:new(Module, [bag, public]) }),
reset_last(Module),
{ FunctionsTable, ClausesTable }.
ok.
delete_table(Module) ->
cleanup(Module) ->
ets:delete(table(Module)),
ets:delete(clauses_table(Module)).
@@ -106,12 +109,14 @@ store_definition(Kind, Line, CheckClauses, Module, Call, Body, RawS) ->
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, nil, _Name, _Args, _Guards, _Body, #elixir_scope{file=File}) ->
elixir_errors:form_error(Line, File, ?MODULE, { no_module, Kind });
store_definition(Kind, Line, CheckClauses, Module, Name, Args, Guards, Body, #elixir_scope{} = DS) ->
Arity = length(Args),
S = DS#elixir_scope{function={Name,Arity}, module=Module},
Tuple = { Name, Arity },
S = DS#elixir_scope{module=Module, function=Tuple},
elixir_tracker:record_definition(Tuple, Kind, Module),
CO = elixir_compiler:get_opts(),
Location = retrieve_file(Line, Module, S, CO),
@@ -119,13 +124,16 @@ store_definition(Kind, Line, CheckClauses, Module, Name, Args, Guards, Body, #el
{ Function, Defaults, TS } = translate_definition(Kind, Line, Name, Args, Guards, Body, S),
DefaultsLength = length(Defaults),
elixir_tracker:record_defaults(Tuple, Kind, Module, DefaultsLength),
File = TS#elixir_scope.file,
Table = table(Module),
CTable = clauses_table(Module),
compile_super(Module, TS),
store_each(CheckClauses, Kind, File, Location,
Table, CTable, length(Defaults), Function),
Table, CTable, DefaultsLength, Function),
[store_each(false, Kind, File, Location, Table, CTable, 0,
default_function_for(Kind, Name, Default)) || Default <- Defaults],
@@ -191,9 +199,12 @@ translate_definition(Kind, Line, Name, RawArgs, RawGuards, RawBody, S) when is_i
Function = { function, Line, Name, Arity, Clauses },
{ Function, Defaults, TS }.
translate_clause(_Line, _Kind, _Unpacked, _Guards, nil, S) ->
translate_clause(_Line, _Kind, _Unpacked, [], nil, S) ->
{ [], S };
translate_clause(Line, Kind, _Unpacked, _Guards, nil, #elixir_scope{file=File}) ->
elixir_errors:syntax_error(Line, File, "missing keyword do in ~ts", [Kind]);
translate_clause(Line, Kind, Unpacked, Guards, Body, S) ->
Expr = expr_from_body(Line, Body),
@@ -306,8 +317,8 @@ store_each(Check, Kind, File, Location, Table, CTable, Defaults, {function, Line
FinalLocation = StoredLocation,
FinalDefaults = max(Defaults, StoredDefaults),
check_valid_kind(Line, File, Name, Arity, Kind, StoredKind),
check_valid_defaults(Line, File, Name, Arity, Defaults, StoredDefaults),
(Check and StoredCheck) andalso check_valid_clause(Line, File, Name, Arity, Table);
check_valid_defaults(Line, File, Name, Arity, Kind, Defaults, StoredDefaults),
(Check and StoredCheck) andalso check_valid_clause(Line, File, Name, Arity, Kind, Table);
[] ->
FinalLine = Line,
FinalLocation = Location,
@@ -324,38 +335,41 @@ check_valid_kind(Line, File, Name, Arity, Kind, StoredKind) ->
elixir_errors:form_error(Line, File, ?MODULE,
{ changed_kind, { Name, Arity, StoredKind, Kind } }).
check_valid_clause(Line, File, Name, Arity, Table) ->
check_valid_clause(Line, File, Name, Arity, Kind, Table) ->
case ets:lookup_element(Table, last, 2) of
{Name,Arity} -> [];
[] -> [];
_ ->
elixir_errors:handle_file_warning(File, { Line, ?MODULE,
{ override_function, { Name, Arity } } })
{ override_function, { Kind, Name, Arity } } })
end.
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 } } }).
check_valid_defaults(_Line, _File, _Name, _Arity, _Kind, 0, _) -> [];
check_valid_defaults(Line, File, Name, Arity, Kind, _, 0) ->
elixir_errors:handle_file_warning(File, { Line, ?MODULE, { out_of_order_defaults, { Kind, Name, Arity } } });
check_valid_defaults(Line, File, Name, Arity, Kind, _, _) ->
elixir_errors:handle_file_warning(File, { Line, ?MODULE, { clauses_with_defaults, { Kind, 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)]);
elixir_errors:syntax_error(Line, File, Message, [Atom]);
assert_no_aliases_name(_Meta, _Aliases, _Args, _S) ->
ok.
%% Format errors
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({no_module,{Kind,Name,Arity}}) ->
io_lib:format("cannot define function outside module, invalid scope for ~ts ~ts/~B", [Kind, Name, Arity]);
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({clauses_with_defaults,{Kind,Name,Arity}}) ->
io_lib:format("~ts ~ts/~B has default values and multiple clauses, use a separate clause for declaring defaults", [Kind, Name, Arity]);
format_error({override_function,{Name,Arity}}) ->
io_lib:format("trying to override previously defined function ~ts/~B", [Name, Arity]);
format_error({out_of_order_defaults,{Kind,Name,Arity}}) ->
io_lib:format("clause with defaults should be the first clause in ~ts ~ts/~B", [Kind, Name, Arity]);
format_error({override_function,{Kind,Name,Arity}}) ->
io_lib:format("trying to override previously defined ~ts ~ts/~B", [Kind, Name, Arity]);
format_error({changed_kind,{Name,Arity,Previous,Current}}) ->
io_lib:format("~ts ~ts/~B already defined as ~ts", [Current, Name, Arity, Previous]).
+8 -64
View File
@@ -1,20 +1,14 @@
%% Module responsible for local invocation of macros and functions.
-module(elixir_def_local).
-export([
macro_for/3,
function_for/3,
format_error/1,
check_unused_local/3
]).
-export([macro_for/3, function_for/3]).
-include("elixir.hrl").
%% Used by elixir_dispatch, returns false if no macro is found
macro_for(_Tuple, _All, nil) -> false;
macro_for(_Tuple, _All, #elixir_scope{module=nil}) -> false;
macro_for(Tuple, All, Module) ->
macro_for(Tuple, All, #elixir_scope{module=Module,function=Function}) ->
try elixir_def:lookup_definition(Module, Tuple) of
{ { Tuple, Kind, Line, _, _, _, _ }, Clauses } when Kind == defmacro; All, Kind == defmacrop ->
elixir_import:record(Tuple, Module, Module),
elixir_tracker:record_local(Tuple, Module, Function),
get_function(Line, Module, Clauses);
_ ->
false
@@ -22,20 +16,19 @@ macro_for(Tuple, All, Module) ->
error:badarg -> false
end.
%% Used on runtime by rewritten clauses, raises an error if function is not found
function_for(Module, Name, Arity) ->
Tuple = { Name, Arity },
case elixir_def:lookup_definition(Module, Tuple) of
{ { Tuple, _, Line, _, _, _, _ }, Clauses } ->
elixir_import:record(Tuple, Module, Module),
%% There is no need to record such calls
%% since they come from funtions that were
%% already analyzed at compilation time.
get_function(Line, Module, Clauses);
_ ->
[_|T] = erlang:get_stacktrace(),
erlang:raise(error, undef, [{Module,Name,Arity,[]}|T])
end.
%% Helpers
get_function(Line, Module, Clauses) ->
RewrittenClauses = [rewrite_clause(Clause, Module) || Clause <- Clauses],
Fun = { 'fun', Line, {clauses, RewrittenClauses } },
@@ -66,53 +59,4 @@ rewrite_clause(Tuple, Module) when is_tuple(Tuple) ->
rewrite_clause(List, Module) when is_list(List) ->
[rewrite_clause(Item, Module) || Item <- List];
rewrite_clause(Else, _) -> Else.
%% Error handling
check_unused_local(File, Recorded, Private) ->
[check_unused_local(Fun, Kind, Line, File, Defaults, Recorded) ||
{ Fun, Kind, Line, true, Defaults } <- Private].
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]).
rewrite_clause(Else, _) -> Else.
+10 -9
View File
@@ -3,7 +3,6 @@
-export([store_pending/1, is_defined/2, ensure_defined/4,
assign_args/3, retrieve_args/3, name/2, store/3, format_error/1]).
-include("elixir.hrl").
-compile({parse_transform, elixir_transform}).
overridable(Module) ->
ets:lookup_element(elixir_module:data_table(Module), '__overridable', 2).
@@ -16,13 +15,13 @@ overridable(Module, Value) ->
is_defined(Module, Tuple) ->
Overridable = overridable(Module),
case orddict:find(Tuple, Overridable) of
{ ok, { _, _, _ } } -> true;
{ ok, { _, _, _, _ } } -> true;
_ -> false
end.
ensure_defined(Meta, Module, Tuple, S) ->
case is_defined(Module, Tuple) of
true -> [];
true -> ok;
_ -> elixir_errors:form_error(Meta, S#elixir_scope.file, ?MODULE, { no_super, Module, Tuple })
end.
@@ -54,7 +53,7 @@ name(Module, Function) ->
name(Module, Function, overridable(Module)).
name(_Module, { Name, _ } = Function, Overridable) ->
{ Count, _, _ } = orddict:fetch(Function, Overridable),
{ Count, _, _, _ } = orddict:fetch(Function, Overridable),
?atom_concat([Name, " (overridable ", Count, ")"]).
%% Store
@@ -62,9 +61,9 @@ name(_Module, { Name, _ } = Function, Overridable) ->
store(Module, Function, GenerateName) ->
Overridable = overridable(Module),
case orddict:fetch(Function, Overridable) of
{ _Count, _Clause, true } -> ok;
{ Count, Clause, false } ->
overridable(Module, orddict:store(Function, { Count, Clause, true }, Overridable)),
{ _Count, _Clause, _Neighbours, true } -> ok;
{ Count, Clause, Neighbours, false } ->
overridable(Module, orddict:store(Function, { Count, Clause, Neighbours, true }, Overridable)),
{ { { Name, Arity }, Kind, Line, File, _Check, Location, Defaults }, Clauses } = Clause,
{ FinalKind, FinalName } = case GenerateName of
@@ -72,6 +71,8 @@ store(Module, Function, GenerateName) ->
false -> { Kind, Name }
end,
'Elixir.Module.DispatchTracker':reattach(Module, Kind, { Name, Arity }, Neighbours),
Def = { function, Line, FinalName, Arity, Clauses },
elixir_def:store_each(false, FinalKind, File, Location,
elixir_def:table(Module), elixir_def:clauses_table(Module), Defaults, Def)
@@ -80,13 +81,13 @@ store(Module, Function, GenerateName) ->
%% Store pending declarations that were not manually made concrete.
store_pending(Module) ->
[store(Module, X, false) || { X, { _, _, false } } <- overridable(Module),
[store(Module, X, false) || { X, { _, _, _, false } } <- overridable(Module),
not 'Elixir.Module':'defines?'(Module, X)].
%% Error handling
format_error({ no_super, Module, { Name, Arity } }) ->
Bins = [ format_fa(X) || { X, { _, _, _ } } <- overridable(Module)],
Bins = [format_fa(X) || { X, { _, _, _, _ } } <- overridable(Module)],
Joined = 'Elixir.Enum':join(Bins, <<", ">>),
io_lib:format("no super defined for ~ts/~B in module ~ts. Overridable functions available are: ~ts",
[Name, Arity, elixir_errors:inspect(Module), Joined]).
+56 -40
View File
@@ -8,17 +8,16 @@
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}).
-import(ordsets, [is_element/2]).
-define(BUILTIN, 'Elixir.Kernel').
-define(builtin, 'Elixir.Kernel').
default_functions() ->
[ { ?BUILTIN, ordsets:union(in_elixir_functions(), in_erlang_functions()) } ].
[ { ?builtin, ordsets:union(in_elixir_functions(), in_erlang_functions()) } ].
default_macros() ->
[ { ?BUILTIN, ordsets:union(in_elixir_macros(), in_erlang_macros()) } ].
[ { ?builtin, ordsets:union(in_elixir_macros(), in_erlang_macros()) } ].
default_requires() ->
[ ?BUILTIN, 'Elixir.Kernel.Typespec', 'Elixir.Record' ].
[ ?builtin, 'Elixir.Kernel.Typespec', 'Elixir.Record' ].
find_import(Meta, Name, Arity, S) ->
Tuple = { Name, Arity },
@@ -35,15 +34,14 @@ import_function(Meta, Name, Arity, S) ->
Tuple = { Name, Arity },
case find_dispatch(Meta, Tuple, S) of
{ function, Receiver } ->
elixir_import:record(Tuple, Receiver, S#elixir_scope.module),
elixir_tracker:record_import(Tuple, Receiver, S#elixir_scope.module, S#elixir_scope.function),
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),
elixir_tracker:record_local(Tuple, S#elixir_scope.module, S#elixir_scope.function),
{ { 'fun', ?line(Meta), { function, Name, Arity } }, S }
end.
@@ -52,7 +50,9 @@ require_function(Meta, Receiver, Name, Arity, S) ->
case is_element(Tuple, get_optional_macros(Receiver)) of
true -> false;
false -> remote_function(Meta, Receiver, Name, Arity, S)
false ->
elixir_tracker:record_remote(Tuple, Receiver, S#elixir_scope.module, S#elixir_scope.function),
remote_function(Meta, Receiver, Name, Arity, S)
end.
%% Function dispatch
@@ -64,20 +64,21 @@ dispatch_import(Meta, Name, Args, S, Callback) ->
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
elixir_tracker:record_import(Tuple, Receiver, Module, S#elixir_scope.function),
Endpoint = case (Receiver == ?builtin) andalso is_element(Tuple, in_erlang_functions()) of
true -> erlang;
false -> Receiver
end,
elixir_translator:translate_each({ { '.', Meta, [Endpoint, Name] }, Meta, Args }, S);
{ import, Receiver } ->
elixir_tracker:record_remote(Tuple, Receiver, S#elixir_scope.module, S#elixir_scope.function),
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_tracker:record_import(Tuple, ?builtin, Module, S#elixir_scope.function),
elixir_macros:translate({ Name, Meta, Args }, S);
{ ok, _Receiver, Tree } ->
translate_expansion(Meta, Tree, S)
@@ -89,8 +90,9 @@ dispatch_require(Meta, Receiver, Name, Args, S, Callback) ->
Arity = length(Args),
Tuple = { Name, Arity },
case (Receiver == ?BUILTIN) andalso is_element(Tuple, in_erlang_functions()) of
case (Receiver == ?builtin) andalso is_element(Tuple, in_erlang_functions()) of
true ->
elixir_tracker:record_remote(Tuple, Receiver, S#elixir_scope.module, S#elixir_scope.function),
{ TArgs, SA } = elixir_translator:translate_args(Args, S),
{ ?wrap_call(?line(Meta), erlang, Name, TArgs), SA };
false ->
@@ -98,6 +100,7 @@ dispatch_require(Meta, Receiver, Name, Args, S, Callback) ->
{ error, noexpansion } ->
Callback();
{ error, internal } ->
elixir_tracker:record_remote(Tuple, ?builtin, S#elixir_scope.module, S#elixir_scope.function),
elixir_macros:translate({ Name, Meta, Args }, S);
{ ok, _Receiver, Tree } ->
translate_expansion(Meta, Tree, S)
@@ -112,21 +115,21 @@ expand_import(Meta, Tuple, Args, Module, Extra, S) ->
do_expand_import(Meta, { Name, Arity } = Tuple, Args, Module, S, Result) ->
case Result of
{ macro, ?BUILTIN } ->
{ 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) }
elixir_tracker:record_import(Tuple, ?builtin, Module, S#elixir_scope.function),
{ ok, ?builtin, expand_macro_named(Meta, ?builtin, Name, Arity, Args, Module, S) }
end;
{ macro, Receiver } ->
elixir_import:record(Tuple, Receiver, Module),
elixir_tracker:record_import(Tuple, Receiver, Module, S#elixir_scope.function),
{ 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),
elixir_def_local:macro_for(Tuple, true, S),
case Fun of
false -> { error, noexpansion };
_ ->
@@ -134,24 +137,29 @@ do_expand_import(Meta, { Name, Arity } = Tuple, Args, Module, S, Result) ->
end
end.
expand_require(Meta, ?BUILTIN, { Name, Arity } = Tuple, Args, Module, S) ->
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, ?BUILTIN, expand_macro_named(Meta, ?BUILTIN, Name, Arity, Args, Module, S) };
false -> { error, noexpansion }
true ->
elixir_tracker:record_remote(Tuple, ?builtin, S#elixir_scope.module, S#elixir_scope.function),
{ 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, S) ->
Fun = (Module == Receiver) andalso (S#elixir_scope.function /= Tuple) andalso
elixir_def_local:macro_for(Tuple, false, Module),
elixir_def_local:macro_for(Tuple, false, S),
case Fun of
false ->
case is_element(Tuple, get_optional_macros(Receiver)) of
true -> { ok, Receiver, expand_macro_named(Meta, Receiver, Name, Arity, Args, Module, S) };
true ->
elixir_tracker:record_remote(Tuple, Receiver, S#elixir_scope.module, S#elixir_scope.function),
{ ok, Receiver, expand_macro_named(Meta, Receiver, Name, Arity, Args, Module, S) };
false -> { error, noexpansion }
end;
_ ->
@@ -261,6 +269,22 @@ munge_stacktrace(Info, [H|T], S) ->
munge_stacktrace(_, [], _) ->
[].
remote_function(Meta, Receiver, Name, Arity, S) ->
Line = ?line(Meta),
Final =
case (Receiver == ?builtin) andalso is_element({ Name, Arity }, in_erlang_functions()) of
true -> erlang;
false -> Receiver
end,
{ { 'fun', Line, { function,
{ atom, Line, Final },
{ atom, Line, Name },
{ integer, Line, Arity}
} }, S }.
%% ERROR HANDLING
format_error({ unrequired_module, { Receiver, Name, Arity, Required }}) ->
@@ -274,21 +298,6 @@ format_error({ ambiguous_call, { Mod1, Mod2, Name, Arity }}) ->
%% INTROSPECTION
remote_function(Meta, Receiver, Name, Arity, S) ->
Line = ?line(Meta),
Final =
case (Receiver == ?BUILTIN) andalso is_element({ Name, Arity }, in_erlang_functions()) of
true -> erlang;
false -> Receiver
end,
{ { 'fun', Line, { function,
{ atom, Line, Final },
{ atom, Line, Name },
{ integer, Line, Arity}
} }, S }.
%% Do not try to get macros from Erlang. Speeds up compilation a bit.
get_optional_macros(erlang) -> [];
@@ -307,7 +316,7 @@ get_optional_macros(Receiver) ->
in_elixir_functions() ->
try
?BUILTIN:'__info__'(functions)
?builtin:'__info__'(functions)
catch
error:undef -> []
end.
@@ -316,7 +325,7 @@ in_elixir_functions() ->
in_elixir_macros() ->
try
?BUILTIN:'__info__'(macros)
?builtin:'__info__'(macros)
catch
error:undef -> []
end.
@@ -330,6 +339,10 @@ in_erlang_functions() ->
{ binary_part, 3 },
{ binary_to_atom, 2 },
{ binary_to_existing_atom, 2 },
{ binary_to_float, 1 },
{ binary_to_float, 2 },
{ binary_to_integer, 1 },
{ binary_to_integer, 2 },
{ binary_to_list, 1 },
{ binary_to_list, 3 },
{ binary_to_term, 1 },
@@ -340,8 +353,11 @@ in_erlang_functions() ->
% { date, 0 },
{ exit, 1 },
{ float, 1 },
{ float_to_binary, 1 },
{ float_to_list, 1 },
{ hd, 1 },
{ integer_to_binary, 1 },
{ integer_to_binary, 2 },
{ integer_to_list, 1 },
{ integer_to_list, 2 },
{ iolist_size, 1 },
-1
View File
@@ -9,7 +9,6 @@
handle_file_warning/2, handle_file_warning/3, handle_file_error/2,
deprecation/3, deprecation/4, file_format/3]).
-include("elixir.hrl").
-compile({parse_transform, elixir_transform}).
%% Handle inspecting for exceptions
-284
View File
@@ -1,284 +0,0 @@
%% Modified version of filelib:wildcard that can handle "**"
%% and automatically skips directories starting with "." by default.
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 1997-2010. All Rights Reserved.
%%
%% The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
%% compliance with the License. You should have received a copy of the
%% Erlang Public License along with this software. If not, it can be
%% retrieved online at http://www.erlang.org/.
%%
%% Software distributed under the License is distributed on an "AS IS"
%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
%% the License for the specific language governing rights and limitations
%% under the License.
%%
%% %CopyrightEnd%
-module(elixir_glob).
-compile({no_auto_import,[error/1]}).
-export([wildcard/1]).
-include_lib("kernel/include/file.hrl").
-define(HANDLE_ERROR(Expr),
try
Expr
catch
error:{badpattern,_}=UnUsUalVaRiAbLeNaMe ->
%% Get the stack backtrace correct.
erlang:error(UnUsUalVaRiAbLeNaMe)
end).
wildcard(Pattern) when is_list(Pattern) ->
?HANDLE_ERROR(do_wildcard(Pattern, file)).
do_wildcard(Pattern, Mod) when is_list(Pattern) ->
do_wildcard_comp(do_compile_wildcard(Pattern), Mod).
do_wildcard_comp({compiled_wildcard,{exists,File}}, Mod) ->
case eval_read_file_info(File, Mod) of
{ok,_} -> [File];
_ -> []
end;
do_wildcard_comp({compiled_wildcard,[Base|Rest]}, Mod) ->
do_wildcard_1([Base], Rest, Mod).
%%%
%%% Pattern matching using a compiled wildcard.
%%%
do_wildcard_1(Files, Pattern, Mod) ->
do_wildcard_2(Files, Pattern, [], Mod).
do_wildcard_2([File|Rest], Pattern, Result, Mod) ->
do_wildcard_2(Rest, Pattern, do_wildcard_3(File, Pattern, Result, Mod), Mod);
do_wildcard_2([], _, Result, _Mod) ->
Result.
do_wildcard_3(Base, [[double_star]|Rest], Result, Mod) ->
lists:sort(do_double_star(current, [Base], Rest, Result, Mod, true));
do_wildcard_3(Base, [Pattern|Rest], Result, Mod) ->
case do_list_dir(Base, Mod) of
{ok, Files0} ->
Files = lists:sort(Files0),
Matches = wildcard_4(Pattern, Files, Base, []),
do_wildcard_2(Matches, Rest, Result, Mod);
_ ->
Result
end;
do_wildcard_3(Base, [], Result, _Mod) ->
[Base|Result].
wildcard_4(Pattern, [File|Rest], Base, Result) when is_binary(File) ->
case wildcard_5(Pattern, binary_to_list(File)) of
true ->
wildcard_4(Pattern, Rest, Base, [join(Base, File)|Result]);
false ->
wildcard_4(Pattern, Rest, Base, Result)
end;
wildcard_4(Pattern, [File|Rest], Base, Result) ->
case wildcard_5(Pattern, File) of
true ->
wildcard_4(Pattern, Rest, Base, [join(Base, File)|Result]);
false ->
wildcard_4(Pattern, Rest, Base, Result)
end;
wildcard_4(_Patt, [], _Base, Result) ->
Result.
wildcard_5([question|Rest1], [_|Rest2]) ->
wildcard_5(Rest1, Rest2);
wildcard_5([accept], _) ->
true;
wildcard_5([double_star], _) ->
true;
wildcard_5([star|Rest], File) ->
do_star(Rest, File);
wildcard_5([{one_of, Ordset}|Rest], [C|File]) ->
case ordsets:is_element(C, Ordset) of
true -> wildcard_5(Rest, File);
false -> false
end;
wildcard_5([{alt, Alts}], File) ->
do_alt(Alts, File);
wildcard_5([C|Rest1], [C|Rest2]) when is_integer(C) ->
wildcard_5(Rest1, Rest2);
wildcard_5([X|_], [Y|_]) when is_integer(X), is_integer(Y) ->
false;
wildcard_5([], []) ->
true;
wildcard_5([], [_|_]) ->
false;
wildcard_5([_|_], []) ->
false.
do_double_star(Base, [H|T], Rest, Result, Mod, Root) ->
Full = join(Base, H),
Result1 = case do_list_dir(Full, Mod) of
{ok, Files} ->
do_double_star(Full, Files, Rest, Result, Mod, false);
_ -> Result
end,
Result2 = case Root andalso Rest == [] of
true -> Result1;
false -> do_wildcard_3(Full, Rest, Result1, Mod)
end,
do_double_star(Base, T, Rest, Result2, Mod, Root);
do_double_star(_Base, [], _Rest, Result, _Mod, _Root) ->
Result.
do_star(Pattern, [X|Rest]) ->
case wildcard_5(Pattern, [X|Rest]) of
true -> true;
false -> do_star(Pattern, Rest)
end;
do_star(Pattern, []) ->
wildcard_5(Pattern, []).
do_alt([Alt|Rest], File) ->
case wildcard_5(Alt, File) of
true -> true;
false -> do_alt(Rest, File)
end;
do_alt([], _File) ->
false.
do_list_dir(current, Mod) -> eval_list_dir(".", Mod);
do_list_dir(Dir, Mod) -> eval_list_dir(Dir, Mod).
join(current, File) -> File;
join(Base, File) -> filename:join(Base, File).
%%% Compiling a wildcard.
do_compile_wildcard(Pattern) ->
{compiled_wildcard,compile_wildcard_1(Pattern)}.
compile_wildcard_1(Pattern) ->
[Root|Rest] = filename:split(Pattern),
case filename:pathtype(Root) of
relative ->
compile_wildcard_2([Root|Rest], current);
_ ->
compile_wildcard_2(Rest, [Root])
end.
compile_wildcard_2([Part|Rest], Root) ->
case compile_part(Part) of
Part ->
compile_wildcard_2(Rest, join(Root, Part));
Pattern ->
compile_wildcard_3(Rest, [Pattern,Root])
end;
compile_wildcard_2([], Root) -> {exists,Root}.
compile_wildcard_3([Part|Rest], Result) ->
compile_wildcard_3(Rest, [compile_part(Part)|Result]);
compile_wildcard_3([], Result) ->
lists:reverse(Result).
compile_part(Part) ->
compile_part(Part, false, []).
compile_part_to_sep(Part) ->
compile_part(Part, true, []).
compile_part([], true, _) ->
error(missing_delimiter);
compile_part([$,|Rest], true, Result) ->
{ok, $,, lists:reverse(Result), Rest};
compile_part([$}|Rest], true, Result) ->
{ok, $}, lists:reverse(Result), Rest};
compile_part([$?|Rest], Upto, Result) ->
compile_part(Rest, Upto, [question|Result]);
compile_part([$*,$*], Upto, Result) ->
compile_part([], Upto, [double_star|Result]);
compile_part([$*,$*|Rest], Upto, Result) ->
compile_part(Rest, Upto, [star|Result]);
compile_part([$*], Upto, Result) ->
compile_part([], Upto, [accept|Result]);
compile_part([$*|Rest], Upto, Result) ->
compile_part(Rest, Upto, [star|Result]);
compile_part([$[|Rest], Upto, Result) ->
case compile_charset(Rest, ordsets:new()) of
{ok, Charset, Rest1} ->
compile_part(Rest1, Upto, [Charset|Result]);
error ->
compile_part(Rest, Upto, [$[|Result])
end;
compile_part([${|Rest], Upto, Result) ->
case compile_alt(Rest) of
{ok, Alt} ->
lists:reverse(Result, [Alt]);
error ->
compile_part(Rest, Upto, [${|Result])
end;
compile_part([X|Rest], Upto, Result) ->
compile_part(Rest, Upto, [X|Result]);
compile_part([], _Upto, Result) ->
lists:reverse(Result).
compile_charset([$]|Rest], Ordset) ->
compile_charset1(Rest, ordsets:add_element($], Ordset));
compile_charset([$-|Rest], Ordset) ->
compile_charset1(Rest, ordsets:add_element($-, Ordset));
compile_charset([], _Ordset) ->
error;
compile_charset(List, Ordset) ->
compile_charset1(List, Ordset).
compile_charset1([Lower, $-, Upper|Rest], Ordset) when Lower =< Upper ->
compile_charset1(Rest, compile_range(Lower, Upper, Ordset));
compile_charset1([$]|Rest], Ordset) ->
{ok, {one_of, Ordset}, Rest};
compile_charset1([X|Rest], Ordset) ->
compile_charset1(Rest, ordsets:add_element(X, Ordset));
compile_charset1([], _Ordset) ->
error.
compile_range(Lower, Current, Ordset) when Lower =< Current ->
compile_range(Lower, Current-1, ordsets:add_element(Current, Ordset));
compile_range(_, _, Ordset) ->
Ordset.
compile_alt(Pattern) ->
compile_alt(Pattern, []).
compile_alt(Pattern, Result) ->
case compile_part_to_sep(Pattern) of
{ok, $,, AltPattern, Rest} ->
compile_alt(Rest, [AltPattern|Result]);
{ok, $}, AltPattern, Rest} ->
NewResult = [AltPattern|Result],
RestPattern = compile_part(Rest),
{ok, {alt, [Alt++RestPattern || Alt <- NewResult]}};
Pattern ->
error
end.
error(Reason) ->
erlang:error({badpattern,Reason}).
eval_read_file_info(File, file) ->
file:read_file_info(File);
eval_read_file_info(File, erl_prim_loader) ->
case erl_prim_loader:read_file_info(File) of
error -> {error, erl_prim_loader};
Res-> Res
end;
eval_read_file_info(File, Mod) ->
Mod:read_file_info(File).
eval_list_dir(Dir, file) ->
file:list_dir(Dir);
eval_list_dir(Dir, erl_prim_loader) ->
case erl_prim_loader:list_dir(Dir) of
error -> {error, erl_prim_loader};
Res-> Res
end;
eval_list_dir(Dir, Mod) ->
Mod:list_dir(Dir).
+17 -112
View File
@@ -2,51 +2,11 @@
%% 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,
ensure_all_imports_used/3,
build_table/1, delete_table/1, record/3]).
-export([import/5, format_error/1,
ensure_no_local_conflict/4]).
-include("elixir.hrl").
table(Module) -> ?atom_concat([i, Module]).
build_table(Module) ->
ets:new(table(Module), [bag, named_table, public]).
delete_table(Module) ->
ets:delete(table(Module)).
record(_Tuple, _Receiver, nil) ->
false;
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 },
Result = ets:match(Table, Match),
ets:match_delete(Table, Match),
lists:append(Result).
%% IMPORT HELPERS
%% Update the scope to consider the imports for aliases
%% based on the given options and selector.
@@ -80,7 +40,16 @@ import(Meta, Ref, Opts, Selector, S) ->
record_warn(Meta, Ref, Opts, S),
SM.
%% IMPORT FUNCTION RELATED HELPERS
record_warn(_Meta, _Ref, _Opts, #elixir_scope{module=nil}) -> false;
record_warn(Meta, Ref, Opts, #elixir_scope{module=Module}) ->
Warn =
case keyfind(warn, Opts) of
{ warn, false } -> false;
{ warn, true } -> true;
false -> not lists:keymember(context, 1, Meta)
end,
elixir_tracker:record_warn(Ref, Warn, ?line(Meta), Module).
%% Calculates the imports based on only and except
@@ -88,8 +57,7 @@ calculate(Meta, Key, Opts, Old, Temp, AvailableFun, S) ->
File = S#elixir_scope.file,
New = case keyfind(only, Opts) of
{ only, RawOnly } ->
Only = expand_fun_arity(Meta, only, RawOnly, S),
{ only, Only } ->
case Only -- get_exports(Key) of
[{Name,Arity}|_] ->
Tuple = { invalid_import, { Key, Name, Arity } },
@@ -101,8 +69,7 @@ calculate(Meta, Key, Opts, Old, Temp, AvailableFun, S) ->
case keyfind(except, Opts) of
false -> AvailableFun(true);
{ except, [] } -> AvailableFun(true);
{ except, RawExcept } ->
Except = expand_fun_arity(Meta, except, RawExcept, S),
{ except, Except } ->
case keyfind(Key, Old) of
false -> AvailableFun(true) -- Except;
{Key,OldImports} -> OldImports -- Except
@@ -134,29 +101,6 @@ if_quoted(Meta, Temp, Callback) ->
Temp
end.
%% Ensure we are expanding macros and stuff
expand_fun_arity(Meta, Kind, Value, S) ->
{ TValue, _S } = elixir_translator:translate_each(Value, S),
cons_to_keywords(Meta, Kind, TValue, S).
cons_to_keywords(Meta, Kind, { cons, _, Left, { nil, _ } }, S) ->
[tuple_to_fun_arity(Meta, Kind, Left, S)];
cons_to_keywords(Meta, Kind, { cons, _, Left, Right }, S) ->
[tuple_to_fun_arity(Meta, Kind, Left, S)|cons_to_keywords(Meta, Kind, Right, S)];
cons_to_keywords(Meta, Kind, _, S) ->
elixir_errors:syntax_error(Meta, S#elixir_scope.file,
"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 :~ts, expected a list with functions and arities", [Kind]).
%% Retrieve functions and macros from modules
get_exports(Module) ->
@@ -196,38 +140,11 @@ get_optional_macros(Module) ->
%% VALIDATION HELPERS
%% Check if any of the locals defined conflicts with an invoked
%% Elixir "implemented in Erlang" macro. Checking if a local
%% conflicts with an import is automatically done by Erlang.
%% Elixir "implemented in Erlang" macro.
ensure_no_local_conflict(Meta, File, Module, AllDefined) ->
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.
ensure_no_import_conflict(Meta, File, Module, AllDefined) ->
Table = table(Module),
Matches = [X || X <- AllDefined, ets:member(Table, X)],
case Matches of
[{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.
@@ -247,17 +164,10 @@ ensure_no_special_form_conflict(_Meta, _File, _Key, [], _) -> ok.
%% ERROR HANDLING
format_error({already_imported,{Receiver, Name, Arity}}) ->
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 ~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 ~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 ~ts/~B because it conflicts with Elixir special forms", [Name, Arity]);
@@ -265,9 +175,6 @@ format_error({internal_conflict,{Receiver, Name, Arity}}) ->
io_lib:format("cannot import ~ts.~ts/~B because it conflicts with Elixir special forms",
[elixir_errors:inspect(Receiver), Name, Arity]);
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 ~ts", [elixir_errors:inspect(Module)]).
@@ -287,8 +194,6 @@ intersection([H|T], All) ->
intersection([], _All) -> [].
%% INTROSPECTION
%% Internal funs that are never imported etc.
remove_underscored(default, List) -> remove_underscored(List);
@@ -308,7 +213,7 @@ remove_internals(Set) ->
ordsets:del_element({ module_info, 1 },
ordsets:del_element({ module_info, 0 }, Set)).
%% Macros implemented in Erlang that are not overridable.
%% Macros implemented in Erlang that are not importable.
special_form() ->
[
-1
View File
@@ -3,7 +3,6 @@
-export([extract/5, unescape_chars/1, unescape_chars/2,
unescape_tokens/1, unescape_tokens/2, unescape_map/1]).
-include("elixir.hrl").
-compile({parse_transform, elixir_transform}).
%% Extract string interpolations
+1 -1
View File
@@ -1,9 +1,9 @@
%% Handle translation of Elixir literals to Erlang AST.
-module(elixir_literal).
-export([translate/2]).
-import(elixir_translator, [translate_each/2, translate_args/2]).
-import(elixir_scope, [umergec/2]).
-include("elixir.hrl").
-compile({parse_transform, elixir_transform}).
translate({ '<<>>', Meta, Args } = Original, S) when is_list(Args) ->
case elixir_partials:handle(Original, S, allow_tail) of
+5 -6
View File
@@ -9,7 +9,6 @@
-include("elixir.hrl").
-define(opt_in_types(Kind), Kind == atom orelse Kind == integer orelse Kind == float).
-compile({parse_transform, elixir_transform}).
%% Operators
@@ -148,12 +147,11 @@ translate({'case', Meta, [Expr, KV]}, S) ->
%% Try
translate({'try', Meta, [Clauses]}, RawS) ->
S = RawS#elixir_scope{noname=true},
translate({'try', Meta, [Clauses]}, 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),
{ TDo, SB } = elixir_translator:translate_each(Do, S#elixir_scope{noname=true}),
Catch = [Tuple || { X, _ } = Tuple <- Clauses, X == 'rescue' orelse X == 'catch'],
{ TCatch, SC } = elixir_try:clauses(Meta, Catch, umergec(S, SB)),
@@ -164,7 +162,8 @@ translate({'try', Meta, [Clauses]}, RawS) ->
Else = elixir_clauses:get_pairs(Meta, else, Clauses, S),
{ TElse, SE } = elixir_clauses:match(Meta, Else, umergec(S, SA)),
{ { 'try', ?line(Meta), pack(TDo), TElse, TCatch, pack(TAfter) }, umergec(RawS, SE) };
SF = (umergec(S, SE))#elixir_scope{noname=S#elixir_scope.noname},
{ { 'try', ?line(Meta), pack(TDo), TElse, TCatch, pack(TAfter) }, SF };
%% Receive
@@ -298,7 +297,7 @@ translate_in(Meta, Left, Right, S) ->
_ ->
case Cache of
true ->
{ false, ?wrap_call(Line, 'Elixir-Enum', 'member?', [TRight, TLeft]) };
{ 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
+26 -29
View File
@@ -1,8 +1,11 @@
-module(elixir_module).
-export([translate/4, compile/5, data_table/1, eval_quoted/4,
format_error/1, eval_callbacks/5]).
-export([translate/4, compile/5, data_table/1, docs_table/1,
eval_quoted/4, format_error/1, eval_callbacks/5]).
-include("elixir.hrl").
-compile({parse_transform, elixir_transform}).
-define(docs_attr, '__docs_table').
-define(acc_attr, '__acc_attributes').
-define(persisted_attr, '__persisted_attributes').
eval_quoted(Module, Quoted, RawBinding, Opts) ->
Binding = binding_for_eval(Module, RawBinding),
@@ -32,7 +35,7 @@ data_table(Module) ->
Module.
docs_table(Module) ->
?atom_concat([o, Module]).
ets:lookup_element(Module, ?docs_attr, 2).
%% TRANSFORMATION FUNCTIONS
@@ -71,9 +74,17 @@ compile(Line, Module, Block, Vars, #elixir_scope{context_modules=FileModules} =
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),
case ets:lookup(data_table(Module), 'on_load') of
[] -> ok;
[{on_load,OnLoad}] ->
[elixir_tracker:record_local(Tuple, Module) || Tuple <- OnLoad]
end,
elixir_tracker:warn_unused_local(File, Module, Private),
elixir_tracker:ensure_no_import_conflict(Line, File, Module, All),
elixir_tracker: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),
Final = [
{ attribute, Line, file, { FileList, Line } },
@@ -83,10 +94,10 @@ compile(Line, Module, Block, Vars, #elixir_scope{context_modules=FileModules} =
Binary = load_form(Line, Final, S),
{ module, Module, Binary, Result }
after
ets:delete(data_table(Module)),
elixir_tracker:cleanup(Module),
elixir_def:cleanup(Module),
ets:delete(docs_table(Module)),
elixir_def:delete_table(Module),
elixir_import:delete_table(Module)
ets:delete(data_table(Module))
end;
compile(Line, Other, _Block, _Vars, #elixir_scope{file=File}) ->
@@ -119,20 +130,13 @@ build(Line, File, Module) ->
end,
Attributes = [behavior, behaviour, on_load, spec, type, export_type, opaque, callback, compile],
ets:insert(DataTable, { '__acc_attributes', [before_compile,after_compile,on_definition|Attributes] }),
ets:insert(DataTable, { '__persisted_attributes', [vsn|Attributes] }),
ets:insert(DataTable, { ?acc_attr, [before_compile,after_compile,on_definition|Attributes] }),
ets:insert(DataTable, { ?persisted_attr, [vsn|Attributes] }),
ets:insert(DataTable, { ?docs_attr, ets:new(DataTable, [ordered_set, public]) }),
%% Keep docs in another table since we don't want to pull out
%% all the binaries every time a new documentation is stored.
DocsTable = docs_table(Module),
ets:new(DocsTable, [ordered_set, named_table, public]),
%% We keep a separated table for function definitions
%% and another one for imports. We keep them in different
%% tables for organization and speed purpose (since the
%% imports table is frequently written to).
elixir_def:build_table(Module),
elixir_import:build_table(Module).
%% Setup other modules
elixir_def:setup(Module),
elixir_tracker:setup(Module).
%% Receives the module representation and evaluates it.
@@ -156,13 +160,6 @@ functions_form(Line, File, Module, Export, Private, Def, Defmacro, RawFunctions,
{ FinalExport, FinalFunctions } =
add_info_function(Line, File, Module, Export, Functions, Def, Defmacro, C),
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,
elixir_def_local:check_unused_local(File, Recorded, Private),
PrivateTuple = [Tuple || { Tuple, _, _, _, _ } <- Private],
{ FinalExport ++ PrivateTuple, [
{attribute, Line, export, lists:sort(FinalExport)} | FinalFunctions
+29 -13
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 right_op bin_concat_op
add_op mult_op unary_op two_op regex_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
@@ -54,13 +54,14 @@ Left 140 or_op.
Left 150 and_op.
Left 160 comp_expr_op.
Left 170 in_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.
Right 180 regex_op.
Right 190 right_op.
Left 200 range_op.
Left 210 three_op.
Left 220 add_op.
Left 230 mult_op.
Right 240 bin_concat_op.
Right 250 two_op.
Nonassoc 300 unary_op.
Left 310 dot_call_op.
Left 310 dot_op.
@@ -103,6 +104,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 -> regex_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' }.
@@ -124,6 +126,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 -> regex_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' }.
@@ -215,8 +218,8 @@ 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').
stab_expr -> call_args_parens_not_one stab_op stab_maybe_expr : build_op('$2', '$1', '$3').
stab_expr -> call_args_no_parens stab_op stab_maybe_expr : build_op('$2', unwrap_splice('$1'), '$3').
stab_expr -> call_args_parens_not_one stab_op stab_maybe_expr : build_op('$2', unwrap_splice('$1'), '$3').
stab_maybe_expr -> 'expr' : '$1'.
stab_maybe_expr -> '$empty' : nil.
@@ -272,8 +275,9 @@ two_op -> '--' eol : '$1'.
two_op -> '**' : '$1'.
two_op -> '**' eol : '$1'.
right_op -> '=~' : '$1'.
right_op -> '=~' eol : '$1'.
regex_op -> '=~' : '$1'.
regex_op -> '=~' eol : '$1'.
right_op -> '|>' : '$1'.
right_op -> '|>' eol : '$1'.
@@ -551,7 +555,7 @@ build_fn(Op, Stab) ->
build_access(Expr, Access) ->
Meta = [{line,?line(Access)}],
{ { '.', Meta, ['Elixir-Kernel', access] }, Meta, [ Expr, ?op(Access) ] }.
{ { '.', Meta, ['Elixir.Kernel', access] }, Meta, [ Expr, ?op(Access) ] }.
%% Interpolation aware
@@ -591,6 +595,18 @@ build_stab([], Marker, Temp, Acc) ->
H = { Marker, build_block(lists:reverse(Temp)) },
lists:reverse([H|Acc]).
%% Every time the parser sees a (unquote_splicing())
%% it assumes that a block is being spliced, wrapping
%% the splicing in a __block__. But in the stab cause,
%% we can have (unquote_splicing(1,2,3)) -> :ok, in such
%% case, we don't actually want the block, since it is
%% an arg style call. unwrap_splice unwraps the splice
%% from such blocks.
unwrap_splice([{ '__block__', [], [{ unquote_splicing, _, _ }] = Splice }]) ->
Splice;
unwrap_splice(Other) -> Other.
%% Errors
throw(Line, Error, Token) ->
+1 -1
View File
@@ -1,7 +1,7 @@
%% Responsible handling Elixir partials with &1, &2 and so on.
-module(elixir_partials).
-export([handle/2, handle/3, is_sequential/1]).
-include("elixir.hrl").
-compile({parse_transform, elixir_transform}).
handle(Original, S) ->
handle(Original, S, default).
+2 -6
View File
@@ -1,6 +1,6 @@
%% Implements Elixir quote.
-module(elixir_quote).
-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.
@@ -29,7 +29,7 @@ unquote(_File, Meta, Left, { '__aliases__', _, Args }, nil) ->
unquote(_File, Meta, Left, Right, nil) when is_atom(Right) ->
case atom_to_list(Right) of
"Elixir-" ++ _ ->
"Elixir." ++ _ ->
{ '__aliases__', Meta, [Left, Right] };
_ ->
{ { '.', Meta, [Left, Right] }, Meta, [] }
@@ -63,10 +63,6 @@ erl_escape(Expr, Unquote, S) ->
quote(Expr, Q, S) ->
do_quote(Expr, Q, 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);
erl_quote(Expr, Q, S) ->
{ QExpr, TQ } = quote(Expr, Q, S),
{ TExpr, TS } = elixir_translator:translate_each(QExpr, S),
+6 -7
View File
@@ -1,5 +1,4 @@
%% Convenience functions used to manipulate scope
%% and its variables.
%% Convenience functions used to manipulate scope and its variables.
-module(elixir_scope).
-export([translate_var/5,
build_erl_var/2, build_ex_var/2,
@@ -9,7 +8,6 @@
umergev/2, umergec/2, merge_clause_vars/2
]).
-include("elixir.hrl").
-compile({parse_transform, elixir_transform}).
translate_var(Meta, Name, Kind, S, Callback) ->
Line = ?line(Meta),
@@ -77,18 +75,19 @@ build_ex_var(Line, Key, Name, S) when is_integer(Line) ->
% Handle Macro.Env conversion
to_erl_env({ 'Elixir.Macro.Env', Module, File, _Line, Function, Aliases, Context, Requires, Functions, Macros, FileModules }) ->
to_erl_env({ 'Elixir.Macro.Env', Module, File, _Line, Function, Aliases, Context,
Requires, Functions, Macros, ContextModules, MacroAliases }) ->
#elixir_scope{module=Module,file=File,
function=Function,aliases=Aliases,context=Context,
requires=Requires,macros=Macros,functions=Functions,
context_modules=FileModules}.
context_modules=ContextModules,macro_aliases=MacroAliases}.
to_ex_env({ Line, #elixir_scope{module=Module,file=File,
function=Function,aliases=Aliases,context=Context,
requires=Requires,macros=Macros,functions=Functions,
context_modules=FileModules} }) when is_integer(Line) ->
context_modules=ContextModules,macro_aliases=MacroAliases} }) when is_integer(Line) ->
{ 'Elixir.Macro.Env', Module, File, Line, Function, Aliases,
Context, Requires, Functions, Macros, FileModules }.
Context, Requires, Functions, Macros, ContextModules, MacroAliases }.
% Provides a tuple with only the scope information we want to serialize.
+141
View File
@@ -0,0 +1,141 @@
%% Module responsible for tracking invocations of locals,
%% imports and requires.
-module(elixir_tracker).
-export([
setup/1, cleanup/1,
record_local/2, record_local/3,
record_import/4, record_remote/4,
record_warn/4, record_definition/3,
record_defaults/4,
ensure_no_import_conflict/4, ensure_all_imports_used/3,
warn_unused_local/3, format_error/1
]).
-include("elixir.hrl").
-define(attr, '__dispatch_tracker').
-define(tracker, 'Elixir.Module.DispatchTracker').
%% RECORD
setup(Module) ->
case code:ensure_loaded(?tracker) of
{ module, _ } -> ets:insert(Module, { ?attr, ?tracker:start_link() });
{ error, _ } -> ok
end.
cleanup(Module) ->
if_tracker(Module, fun(Pid) -> ?tracker:stop(Pid) end).
record_local(Tuple, Module) when is_atom(Module) ->
if_tracker(Module, fun(Pid) ->
?tracker:add_local(Pid, Tuple),
true
end).
record_local(Tuple, _Module, Function)
when Function == nil; Function == Tuple -> false;
record_local(Tuple, Module, Function) ->
if_tracker(Module, fun(Pid) ->
?tracker:add_local(Pid, Function, Tuple),
true
end).
record_warn(Ref, Warn, Line, Module) ->
if_tracker(Module, fun(Pid) ->
?tracker:add_warnable(Pid, Ref, Warn, Line),
true
end).
record_import(_Tuple, Receiver, Module, _Function)
when Module == nil; Module == Receiver -> false;
record_import(Tuple, Receiver, Module, Function) ->
try
Pid = ets:lookup_element(Module, ?attr, 2),
?tracker:add_import(Pid, Function, Receiver, Tuple)
catch
error:badarg -> false
end.
record_remote(_Tuple, Receiver, Module, _Function)
when Module == nil; Module == Receiver -> false;
record_remote(Tuple, Receiver, Module, Function) ->
try
Pid = ets:lookup_element(Module, ?attr, 2),
?tracker:add_remote(Pid, Function, Receiver, Tuple)
catch
error:badarg -> false
end.
record_definition(Tuple, Kind, Module) ->
if_tracker(Module, fun(Pid) ->
?tracker:add_definition(Pid, Kind, Tuple),
true
end).
record_defaults(_Tuple, _Kind, _Module, 0) ->
true;
record_defaults(Tuple, Kind, Module, Defaults) ->
if_tracker(Module, fun(Pid) ->
?tracker:add_defaults(Pid, Kind, Tuple, Defaults),
true
end).
%% HELPERS
if_tracker(Module, Callback) ->
case ets:lookup(Module, ?attr) of
[{ ?attr, Pid }] -> Callback(Pid);
_ -> false
end.
%% ERROR HANDLING
ensure_no_import_conflict(Meta, File, Module, AllDefined) ->
if_tracker(Module, fun(Pid) ->
[ begin
elixir_errors:form_error(Meta, File, ?MODULE, { import_conflict, Error })
end || Error <- ?tracker:collect_imports_conflicts(Pid, AllDefined) ]
end),
ok.
ensure_all_imports_used(_Line, File, Module) ->
if_tracker(Module, fun(Pid) ->
[ begin
elixir_errors:handle_file_warning(File, { L, ?MODULE, { unused_import, M } })
end || { M, L } <- ?tracker:collect_unused_imports(Pid)]
end),
ok.
warn_unused_local(File, Module, Private) ->
if_tracker(Module, fun(Pid) ->
Args = [ { Fun, Kind, Defaults } ||
{ Fun, Kind, _Line, true, Defaults } <- Private],
Unused = ?tracker:collect_unused_locals(Pid, Args),
[ begin
{ _, _, Line, _, _ } = lists:keyfind(element(2, Error), 1, Private),
elixir_errors:handle_file_warning(File, { Line, ?MODULE, Error })
end || Error <- Unused ]
end).
format_error({import_conflict,{Receivers, Name, Arity}}) ->
io_lib:format("imported ~ts.~ts/~B conflicts with local function",
[elixir_errors:inspect(hd(Receivers)), Name, Arity]);
format_error({ unused_import, Module }) ->
io_lib:format("unused import ~ts", [elixir_errors:inspect(Module)]);
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,{Name, Arity},defp}) ->
io_lib:format("function ~ts/~B is unused", [Name, Arity]);
format_error({unused_def,{Name, Arity},defmacrop}) ->
io_lib:format("macro ~ts/~B is unused", [Name, Arity]).
-38
View File
@@ -1,38 +0,0 @@
-module(elixir_transform).
-export([parse_transform/2]).
parse_transform(Forms, _) ->
do_transform(Forms).
do_transform({ atom, Line, Atom }) ->
case atom_to_list(Atom) of
"Elixir." ++ _ = List ->
Module = [case T of
$. -> $-;
_ -> T
end || T <- List],
{ atom, Line, list_to_atom(Module) };
_ ->
{ atom, Line, Atom }
end;
do_transform({ Name, Item1 }) ->
{ Name, do_transform(Item1) };
do_transform({ Name, Item1, Item2 }) ->
{ Name, do_transform(Item1), do_transform(Item2) };
do_transform({ Name, Item1, Item2, Item3 }) ->
{ Name, do_transform(Item1), do_transform(Item2), do_transform(Item3) };
do_transform({ Name, Item1, Item2, Item3, Item4 }) ->
{ Name, do_transform(Item1), do_transform(Item2), do_transform(Item3), do_transform(Item4) };
do_transform({ Name, Item1, Item2, Item3, Item4, Item5 }) ->
{ Name, do_transform(Item1), do_transform(Item2), do_transform(Item3), do_transform(Item4), do_transform(Item5) };
do_transform(List) when is_list(List) ->
[do_transform(X) || X <- List];
do_transform(Other) when not is_tuple(Other) ->
Other.
+16 -20
View File
@@ -9,7 +9,6 @@
assert_function_scope/3, assert_module_scope/3, assert_no_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
@@ -231,21 +230,15 @@ translate_each({ quote, Meta, [KV, Do] }, S) when is_list(Do) ->
[]
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;
Context = 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 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
end
case S#elixir_scope.module of
nil -> 'Elixir';
Mod -> Mod
end
end,
@@ -338,9 +331,12 @@ translate_each({ super, Meta, Args } = Original, S) ->
{ _, Arity } = Function,
{ TArgs, TS } = if
is_atom(Args) ->
is_atom(Args) andalso Arity == 0 ->
elixir_def_overridable:retrieve_args(Meta, Arity, S);
length(Args) == Arity ->
is_atom(Args) ->
elixir_errors:deprecation(Meta, S#elixir_scope.file, "omitting arguments of super is deprecated, please call super with arguments instead"),
elixir_def_overridable:retrieve_args(Meta, Arity, S);
not is_atom(Args) andalso length(Args) == Arity ->
translate_args(Args, S);
true ->
syntax_error(Meta, S#elixir_scope.file, "super must be called with the same number of arguments as the current function")
@@ -534,7 +530,7 @@ translate_alias(Meta, IncludeByDefault, Old, TKV, S) ->
"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
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)])
@@ -578,8 +574,8 @@ translate_fn(Meta, Clauses, S) ->
%% Locals
translate_local(Meta, Name, Args, #elixir_scope{local=nil,module=Module} = S) ->
elixir_import:record({ Name, length(Args) }, Module, Module),
translate_local(Meta, Name, Args, #elixir_scope{local=nil,module=Module,function=Function} = S) ->
elixir_tracker:record_local({ Name, length(Args) }, Module, Function),
Line = ?line(Meta),
{ TArgs, NS } = translate_args(Args, S),
{ { call, Line, { atom, Line, Name }, TArgs }, NS };
-2
View File
@@ -1,9 +1,7 @@
%% Convenience functions used throughout elixir source code
%% to generate erlang abstract format for basic structures
%% as lists, condition clauses, etc.
-module(elixir_tree_helpers).
-compile({parse_transform, elixir_transform}).
-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]).
-1
View File
@@ -2,7 +2,6 @@
-export([clauses/3, format_error/1]).
-import(elixir_scope, [umergec/2]).
-include("elixir.hrl").
-compile({parse_transform, elixir_transform}).
clauses(Meta, Clauses, S) ->
Catch = elixir_clauses:get_pairs(Meta, 'catch', Clauses, S),
-6
View File
@@ -13,12 +13,6 @@ defmodule AccessTest do
assert nil[:foo] == nil
end
test :function do
function = fn x -> x == :foo end
assert function[:foo] == true
assert function[:bar] == false
end
# Test nil at compilation time does not fail
# and that @config[:foo] has proper precedence.
nil = @config[:foo]
+90 -6
View File
@@ -23,7 +23,7 @@ defmodule Binary.Chars.AtomTest do
end
test :alias_atom do
assert to_binary(Foo.Bar) == "Elixir-Foo-Bar"
assert to_binary(Foo.Bar) == "Elixir.Foo.Bar"
end
end
@@ -50,11 +50,9 @@ defmodule Binary.Chars.NumberTest do
assert to_binary(100) == "100"
end
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
test :float do
assert to_binary(1.0) == "1.0"
assert to_binary(1.0e10) == "10000000000.0"
end
end
@@ -73,3 +71,89 @@ defmodule Binary.Chars.ListTest do
assert to_binary([]) == ""
end
end
defmodule Binary.Chars.ErrorsTest do
use ExUnit.Case, async: true
test :list do
assert to_binary([0,1, 2, 3,255]) == <<0,1,2,3,255>>
assert to_binary([0,[1, "hello"], 2, [["bye"]]]) == <<0,1,104,101,108,108,111,2,98,121,101>>
assert_raise Protocol.UndefinedError, "protocol Binary.Chars.List not implemented for [256]. Only iolists are supported", fn ->
to_binary([256])
end
assert_raise Protocol.UndefinedError, "protocol Binary.Chars.List not implemented for [1001,10001,100001]. Only iolists are supported", fn ->
to_binary([1001, 10001, 100001])
end
assert_raise Protocol.UndefinedError, "protocol Binary.Chars.List not implemented for [:atom,13,\"hello\"]. Only iolists are supported", fn ->
to_binary([:atom, 13, "hello"])
end
end
test :tuple do
assert_raise Protocol.UndefinedError, "protocol Binary.Chars not implemented for {1,2,3}", fn ->
to_binary({1,2,3})
end
end
test :nested_in_tuple do
assert_raise Protocol.UndefinedError, "protocol Binary.Chars not implemented for {1,[2],:atom}", fn ->
to_binary({1, [2], :atom})
end
end
test :nested_tuple do
assert_raise Protocol.UndefinedError, "protocol Binary.Chars.List not implemented for [1,{[2],:atom}]. Only iolists are supported", fn ->
to_binary([1, {[2], :atom}])
end
end
test :nested_pid do
assert_raise Protocol.UndefinedError, %r"^protocol Binary\.Chars\.List not implemented for \[#PID<.+?>,:atom\]\. Only iolists are supported$", fn ->
to_binary([self(), :atom])
end
end
test :nested_fun do
assert_raise Protocol.UndefinedError, %r"^protocol Binary\.Chars\.List not implemented for \[:atom,#Function<.+?>\]\. Only iolists are supported$", fn ->
to_binary([:atom, fn -> end])
end
end
test :record do
# FIXME: is this expected error message?
assert_raise UndefinedFunctionError, "undefined function: Binary.Chars.ArgumentError.to_binary/1", fn ->
to_binary(ArgumentError[])
end
# FIXME: is this expected error message?
assert_raise UndefinedFunctionError, "undefined function: Binary.Chars.File.Stat.to_binary/1", fn ->
to_binary(File.Stat[])
end
end
test :pid do
assert_raise Protocol.UndefinedError, %r"^protocol Binary\.Chars not implemented for #PID<.+?>$", fn ->
to_binary(self())
end
end
test :ref do
assert_raise Protocol.UndefinedError, %r"^protocol Binary\.Chars not implemented for #Reference<.+?>$", fn ->
to_binary(make_ref()) == ""
end
end
test :function do
assert_raise Protocol.UndefinedError, %r"^protocol Binary\.Chars not implemented for #Function<.+?>$", fn ->
to_binary(fn -> end)
end
end
test :port do
[port|_] = Port.list
assert_raise Protocol.UndefinedError, %r"^protocol Binary\.Chars not implemented for #Port<.+?>$", fn ->
to_binary(port)
end
end
end
@@ -86,12 +86,10 @@ defmodule Binary.Inspect.NumberTest do
assert inspect(100) == "100"
end
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
test :float do
assert inspect(1.0) == "1.0"
assert inspect(1.0e10) == "10000000000.0"
assert inspect(1.0e-10) == "0.0000000001"
end
end
+20 -11
View File
@@ -19,6 +19,11 @@ defmodule CodeTest do
assert { 3, _ } = Code.eval_string("a + b", [a: 1, b: 2], __ENV__.location)
end
test :eval_with_unnamed_scopes do
assert { RuntimeError[], [a: RuntimeError[]] } =
Code.eval_string("a = (try do (raise \"hello\") rescue e -> e end)")
end
test :eval_with_scope do
assert Code.eval_string("one", [], delegate_locals_to: __MODULE__) == { 1, [] }
end
@@ -83,17 +88,7 @@ defmodule CodeTest do
end
test :compile_source do
compile = __MODULE__.__info__(:compile)
# Erlang has a bug that does not allow us to set the source
# when compiling forms. In such cases, the source will be
# under compile option. This is fixed and future Erlang
# version will return the proper source always (source2).
options = :proplists.get_value(:options, compile, [])
source1 = :proplists.get_value(:source, options, nil)
source2 = :proplists.get_value(:source, compile, nil)
assert (source1 || source2) == binary_to_list(__FILE__)
assert __MODULE__.__info__(:compile)[:source] == binary_to_list(__FILE__)
end
test :compile_info_returned_with_source_accessible_through_keyword_module do
@@ -101,6 +96,20 @@ defmodule CodeTest do
assert Keyword.get(compile, :source) != nil
end
test :compile_string do
assert [{ CompileStringSample, _ }] = Code.compile_string("defmodule CompileStringSample, do: :ok")
after
:code.purge CompileSimpleSample
:code.delete CompileSimpleSample
end
test :compile_quoted do
assert [{ CompileQuotedSample, _ }] = Code.compile_string("defmodule CompileQuotedSample, do: :ok")
after
:code.purge CompileQuotedSample
:code.delete CompileQuotedSample
end
test :ensure_loaded? do
assert Code.ensure_loaded?(__MODULE__)
refute Code.ensure_loaded?(Unknown.Module)
+15 -14
View File
@@ -169,6 +169,14 @@ defmodule EnumTest.List do
assert Enum.map_join([1,2,3], ' = ', &1 * 2) == '2 = 4 = 6'
end
test :join_empty do
fun = fn (acc, _) -> acc end
assert Enum.join(fun, ".") == ""
assert Enum.map_join(fun, ".", &1 + 0) == ""
assert Enum.join(fun, '.') == ''
assert Enum.map_join(fun, '.', &1 + 0) == ''
end
test :map do
assert Enum.map([], fn x -> x * 2 end) == []
assert Enum.map([1,2,3], fn x -> x * 2 end) == [2,4,6]
@@ -231,28 +239,21 @@ defmodule EnumTest.List do
test :take_does_not_consume_next_without_a_need do
import PathHelpers
iterator = File.iterator!(fixture_path("one-liner.txt"))
try do
File.open!(fixture_path("one-liner.txt"), [], fn file ->
iterator = File.iterator(file)
assert Enum.take(iterator, 1) == ["ONE"]
assert Enum.take(iterator, 5) == []
after
File.close(iterator)
end
end)
end
test :take_with_no_item_works_as_no_op do
import PathHelpers
iterator = File.iterator!(fixture_path("one-liner.txt"))
try do
assert Enum.take(iterator, 0) == []
assert Enum.take(iterator, 0) == []
assert Enum.take(iterator, 0) == []
assert Enum.take(iterator, 0) == []
after
File.close(iterator)
end
assert Enum.take(iterator, 0) == []
assert Enum.take(iterator, 0) == []
assert Enum.take(iterator, 0) == []
assert Enum.take(iterator, 0) == []
end
test :take_while do
+2 -2
View File
@@ -34,8 +34,8 @@ defmodule Kernel.ExceptionTest do
end
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"
assert Exception.format_stacktrace_entry({fn(x) -> x end, [1], []}) =~ %r/#Function<.+>\(1\)/
assert Exception.format_stacktrace_entry({fn(x, y) -> { x, y } end, 2, []}) =~ %r"#Function<.+>/2"
end
test :format_module_function_arity do
+2 -14
View File
@@ -905,7 +905,7 @@ defmodule FileTest do
dest = tmp_path("tmp_test.txt")
try do
{ :ok, iterator } = File.iterator(src)
iterator = File.iterator!(src)
File.open dest, [:write], fn(target) ->
Enum.each iterator, fn(line) ->
IO.write target, Regex.replace(%r/"/, line, "'")
@@ -934,12 +934,6 @@ defmodule FileTest do
end
end
test :iterator_can_be_closed do
src = fixture_path("file.txt")
iterator = File.iterator!(src)
assert File.close(iterator) == :ok
end
test :biniterator do
src = File.open! fixture_path("file.txt")
dest = tmp_path("tmp_test.txt")
@@ -962,7 +956,7 @@ defmodule FileTest do
dest = tmp_path("tmp_test.txt")
try do
{ :ok, iterator } = File.biniterator(src)
iterator = File.biniterator!(src)
File.open dest, [:write], fn(target) ->
Enum.each iterator, fn(line) ->
IO.write target, Regex.replace(%r/"/, line, "'")
@@ -991,12 +985,6 @@ defmodule FileTest do
end
end
test :biniterator_can_be_closed do
src = fixture_path("file.txt")
iterator = File.biniterator!(src)
assert File.close(iterator) == :ok
end
test :copy do
src = fixture_path("file.txt")
dest = tmp_path("tmp_test.txt")
@@ -16,8 +16,8 @@ defmodule GenServer.BehaviourTest do
{ :stop, :normal, :ok, config }
end
def handle_call(_request, _from, _config) do
super
def handle_call(request, from, config) do
super(request, from, config)
end
def handle_cast({ :push, item }, config) do
+26 -4
View File
@@ -62,6 +62,11 @@ defmodule HashDictTest do
dict = HashDict.put_new(dict, 11, 13)
assert HashDict.get(dict, 11) == 13
assert HashDict.size(dict) == 9
dict = HashDict.put_new(dict, 11.0, 15)
assert HashDict.get(dict, 11.0) == 15
assert HashDict.get(dict, 11) == 13
assert HashDict.size(dict) == 10
end
test :update do
@@ -82,20 +87,37 @@ defmodule HashDictTest do
dict = HashDict.update(dict, 11, 13, &1 * 2)
assert HashDict.get(dict, 11) == 13
assert HashDict.size(dict) == 9
assert_raise KeyError, fn->
HashDict.update(dict, 11.0, &1 * 2)
end
dict = HashDict.update(dict, 11.0, 15, &1 * 2)
assert HashDict.get(dict, 11.0) == 15
assert HashDict.size(dict) == 10
dict = HashDict.update(dict, 11.0, 15, &1 * 2)
assert HashDict.get(dict, 11.0) == 30
assert HashDict.get(dict, 11) == 13
assert HashDict.size(dict) == 10
end
test :to_list do
list = filled_dict(8) |> HashDict.to_list
dict = filled_dict(8)
list = dict |> HashDict.to_list
assert length(list) == 8
assert { 1, 1 } in list
assert list == Enum.to_list(dict)
list = filled_dict(20) |> HashDict.to_list
dict = filled_dict(20)
list = dict |> HashDict.to_list
assert length(list) == 20
assert { 1, 1 } in list
assert list == Enum.to_list(dict)
list = filled_dict(120) |> HashDict.to_list
dict = filled_dict(120)
list = dict |> HashDict.to_list
assert length(list) == 120
assert { 1, 1 } in list
assert list == Enum.to_list(dict)
end
test :keys do
@@ -143,7 +165,7 @@ defmodule HashDictTest do
end
test :inspect do
assert inspect(filled_dict(8)) =~ %r"#HashDict<"
assert inspect(filled_dict(8)) =~ "#HashDict<"
end
test :small_range_merge do
+2 -2
View File
@@ -12,8 +12,8 @@ defmodule Kernel.AliasTest do
test :alias_erlang do
alias :lists, as: MyList
assert MyList.flatten([1,[2],3]) == [1,2,3]
assert Elixir.MyList.Bar == :"Elixir-MyList-Bar"
assert MyList.Bar == :"Elixir-lists-Bar"
assert Elixir.MyList.Bar == :"Elixir.MyList.Bar"
assert MyList.Bar == :"Elixir.lists.Bar"
end
test :double_alias do
+3 -3
View File
@@ -78,7 +78,7 @@ defmodule Kernel.CLI.CompileTest do
fixture = fixture_path "compile_sample.ex"
assert elixirc('#{fixture} -o #{tmp_path}') ==
'Compiled #{fixture}\n'
assert File.regular?(tmp_path "Elixir-CompileSample.beam")
assert File.regular?(tmp_path "Elixir.CompileSample.beam")
end
end
@@ -96,8 +96,8 @@ defmodule Kernel.CLI.ParallelCompilerTest do
output = elixirc('#{fixture_path("parallel_compiler")} -o #{tmp_path}')
assert :string.str(output, 'message_from_foo') > 0,
"Expected #{inspect output} to contain 'message_from_foo'"
assert File.regular?(tmp_path "Elixir-Foo.beam")
assert File.regular?(tmp_path "Elixir-Bar.beam")
assert File.regular?(tmp_path "Elixir.Foo.beam")
assert File.regular?(tmp_path "Elixir.Bar.beam")
end
test :possible_deadlock do
+124 -112
View File
@@ -2,6 +2,7 @@ Code.require_file "../test_helper.exs", __DIR__
defmodule Kernel.ErrorsTest do
use ExUnit.Case, async: true
import CompileAssertion
defmodule UnproperMacro do
defmacro unproper(args), do: args
@@ -11,278 +12,300 @@ defmodule Kernel.ErrorsTest do
defrecord Config, integer: 0
test :invalid_token do
assert "nofile:1: invalid token: \end" == format_rescue '\end\nlol\nbarbecue'
assert_compile_fail SyntaxError, "nofile:1: invalid token: \end", '\end\nlol\nbarbecue'
end
test :sigil_terminator do
assert "nofile:3: missing terminator: \" (for sigil %r\" starting at line 1)" == format_rescue '%r"foo\n\n'
assert "nofile:3: missing terminator: } (for sigil %r{ starting at line 1)" == format_rescue '%r{foo\n\n'
assert_compile_fail TokenMissingError, "nofile:3: missing terminator: \" (for sigil %r\" starting at line 1)", '%r"foo\n\n'
assert_compile_fail TokenMissingError, "nofile:3: missing terminator: } (for sigil %r{ starting at line 1)", '%r{foo\n\n'
end
test :dot_terminator do
assert "nofile:1: missing terminator: \" (for function name starting at line 1)" == format_rescue 'foo."bar'
assert_compile_fail TokenMissingError, "nofile:1: missing terminator: \" (for function name starting at line 1)", 'foo."bar'
end
test :string_terminator do
assert "nofile:1: missing terminator: \" (for string starting at line 1)" == format_rescue '"bar'
assert_compile_fail TokenMissingError, "nofile:1: missing terminator: \" (for string starting at line 1)", '"bar'
end
test :heredoc_terminator do
assert "nofile:2: missing terminator: \"\"\" (for heredoc starting at line 1)" == format_rescue '"""\nbar'
assert_compile_fail TokenMissingError, "nofile:2: missing terminator: \"\"\" (for heredoc starting at line 1)", '"""\nbar'
end
test :invalid_partial do
assert "nofile:1: partial variable &2 cannot be defined without &1" == format_rescue '&2 + 3'
assert_compile_fail SyntaxError, "nofile:1: partial variable &2 cannot be defined without &1", '&2 + 3'
end
test :unexpected_end do
assert "nofile:1: unexpected token: end" == format_rescue '1 end'
assert_compile_fail SyntaxError, "nofile:1: unexpected token: end", '1 end'
end
test :syntax_error do
assert "nofile:1: syntax error before: '.'" == format_rescue '+.foo'
assert_compile_fail SyntaxError, "nofile:1: syntax error before: '.'", '+.foo'
end
test :syntax_error_on_parens_call do
assert "nofile:1: invalid comma inside parenthesis. If you are making a function call, " <>
assert_compile_fail SyntaxError, "nofile:1: 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: )" == format_rescue 'foo (hello, world)'
"Syntax error before: )", 'foo (hello, world)'
end
test :syntax_error_with_no_token do
assert "nofile:1: missing terminator: ) (for \"(\" starting at line 1)" == format_rescue 'case 1 ('
assert_compile_fail TokenMissingError, "nofile:1: missing terminator: ) (for \"(\" starting at line 1)", 'case 1 ('
end
test :bad_form do
assert "nofile:2: function bar/0 undefined" == format_rescue 'defmodule Foo do\ndef foo, do: bar\nend'
assert_compile_fail CompileError, "nofile:2: function bar/0 undefined", 'defmodule Foo do\ndef foo, do: bar\nend'
end
test :unbound_var do
assert "nofile:1: unbound variable ^x" == format_rescue('^x = 1')
assert_compile_fail SyntaxError, "nofile:1: unbound variable ^x", '^x = 1'
end
test :unbound_not_match do
assert "nofile:1: cannot use ^x outside of match clauses" == format_rescue('^x')
assert_compile_fail SyntaxError, "nofile:1: cannot use ^x outside of match clauses", '^x'
end
test :unbound_expr do
assert "nofile:1: the unary operator ^ can only be used with variables, invalid expression ^x(1)" == format_rescue('^x(1)')
assert_compile_fail SyntaxError, "nofile:1: the unary operator ^ can only be used with variables, invalid expression ^x(1)", '^x(1)'
end
test :name_for_defmodule do
assert "nofile:1: invalid module name: 3" == format_rescue 'defmodule 1 + 2, do: 3'
assert_compile_fail CompileError, "nofile:1: invalid module name: 3", 'defmodule 1 + 2, do: 3'
end
test :invalid_scope_for_function do
assert "nofile:1: cannot invoke def outside module" == format_rescue 'def Foo, do: 2'
assert "nofile:3: cannot invoke defmacro outside module" == format_rescue '\n\ndefmacro Foo, do: 2'
assert_compile_fail SyntaxError, "nofile:1: cannot invoke def outside module", 'def Foo, do: 2'
assert_compile_fail SyntaxError, "nofile:3: cannot invoke defmacro outside module", '\n\ndefmacro Foo, do: 2'
end
test :invalid_unquote do
assert "nofile:1: unquote called outside quote" == format_rescue 'unquote 1'
assert_compile_fail SyntaxError, "nofile:1: unquote called outside quote", 'unquote 1'
end
test :invalid_quote_args do
assert "nofile:1: invalid args for quote" == format_rescue 'quote 1'
assert_compile_fail SyntaxError, "nofile:1: invalid args for quote", 'quote 1'
end
test :invalid_parens do
assert "nofile:1: unexpected parenthesis after foo(1)" == format_rescue 'foo(1)(2)'
assert_compile_fail SyntaxError, "nofile:1: unexpected parenthesis after foo(1)", 'foo(1)(2)'
end
test :invalid_fn_args do
assert "nofile:1: missing terminator: end (for \"fn\" starting at line 1)" == format_rescue 'fn 1'
assert_compile_fail TokenMissingError, "nofile:1: missing terminator: end (for \"fn\" starting at line 1)", 'fn 1'
end
test :macro_conflict do
assert "nofile:1: imported Kernel.defrecord/2 conflicts with local function" ==
format_rescue 'defmodule Foo do\ndefrecord(Kernel.ErrorsTest.MacroConflict, a: 1)\ndef defrecord(_, _), do: OMG\nend'
assert_compile_fail CompileError, "nofile:1: imported Kernel.defrecord/2 conflicts with local function",
'defmodule Foo do\ndefrecord(Kernel.ErrorsTest.MacroConflict, a: 1)\ndef defrecord(_, _), do: OMG\nend'
end
test :macro_with_undefined_local do
assert "undefined function: Foo.unknown/1" ==
format_rescue 'defmodule Foo do\ndefmacrop bar, do: unknown(1)\ndef baz, do: bar()\nend'
assert_compile_fail UndefinedFunctionError, "undefined function: Foo.unknown/1",
'defmodule Foo do\ndefmacrop bar, do: unknown(1)\ndef baz, do: bar()\nend'
end
test :private_macro do
assert "undefined function: Foo.foo/0" ==
format_rescue 'defmodule Foo do\ndefmacrop foo, do: 1\ndefmacro bar, do: __MODULE__.foo\ndefmacro baz, do: bar\nend'
assert_compile_fail UndefinedFunctionError, "undefined function: Foo.foo/0",
'defmodule Foo do\ndefmacrop foo, do: 1\ndefmacro bar, do: __MODULE__.foo\ndefmacro baz, do: bar\nend'
end
test :function_definition_with_alias do
assert "nofile:1: function names should start with lowercase characters or underscore, invalid name Bar" ==
format_rescue 'defmodule Foo do def Bar do :baz end\nend\n'
assert_compile_fail SyntaxError, "nofile:1: function names should start with lowercase characters or underscore, invalid name Bar",
'defmodule Foo do def Bar do :baz end\nend\n'
end
test :function_import_conflict do
assert "nofile:2: function exit/1 imported from both :erlang and Kernel, call is ambiguous" ==
format_rescue 'defmodule Foo do import :erlang\n def foo, do: exit(:test)\nend'
assert_compile_fail CompileError, "nofile:2: function exit/1 imported from both :erlang and Kernel, call is ambiguous",
'defmodule Foo do import :erlang\n def foo, do: exit(:test)\nend'
end
test :import_invalid_macro do
assert "nofile:2: cannot import Kernel.invalid/1 because it doesn't exist" ==
format_rescue 'defmodule Foo do\nimport Kernel, only: [invalid: 1]\nend'
assert_compile_fail CompileError, "nofile:2: cannot import Kernel.invalid/1 because it doesn't exist",
'defmodule Foo do\nimport Kernel, only: [invalid: 1]\nend'
end
test :unrequired_macro do
assert "nofile:2: tried to invoke macro Kernel.ErrorsTest.UnproperMacro.unproper/1 but module was not required. Required: Kernel, Kernel.Typespec, Record" ==
format_rescue 'defmodule Foo do\nKernel.ErrorsTest.UnproperMacro.unproper([])\nend'
assert_compile_fail CompileError, "nofile:2: tried to invoke macro Kernel.ErrorsTest.UnproperMacro.unproper/1 but module was not required. Required: Kernel, Kernel.Typespec, Record",
'defmodule Foo do\nKernel.ErrorsTest.UnproperMacro.unproper([])\nend'
end
test :def_defmacro_clause_change do
assert "nofile:3: defmacro foo/1 already defined as def" ==
format_rescue 'defmodule Foo do\ndef foo(1), do: 1\ndefmacro foo(x), do: x\nend'
assert_compile_fail CompileError, "nofile:3: defmacro foo/1 already defined as def",
'defmodule Foo do\ndef foo(1), do: 1\ndefmacro foo(x), do: x\nend'
end
test :internal_function_overridden do
assert "nofile:1: function __info__/1 is internal and should not be overridden" ==
format_rescue 'defmodule Foo do\ndef __info__(_), do: []\nend'
assert_compile_fail CompileError, "nofile:1: function __info__/1 is internal and should not be overridden",
'defmodule Foo do\ndef __info__(_), do: []\nend'
end
test :no_macros do
assert "nofile:2: could not load macros from module :lists" ==
format_rescue 'defmodule Foo do\nimport :macros, :lists\nend'
assert_compile_fail CompileError, "nofile:2: could not load macros from module :lists",
'defmodule Foo do\nimport :macros, :lists\nend'
end
test :unloaded_module do
assert "nofile:1: module Certainly.Doesnt.Exist is not loaded and could not be found" ==
format_rescue 'import Certainly.Doesnt.Exist'
assert_compile_fail CompileError, "nofile:1: module Certainly.Doesnt.Exist is not loaded and could not be found",
'import Certainly.Doesnt.Exist'
end
test :scheduled_module do
assert "nofile:1: module Foo.Hygiene 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." ==
format_rescue 'defmodule Foo do; defmodule Hygiene do; end; import Foo.Hygiene; end'
assert_compile_fail CompileError, "nofile:1: module Foo.Hygiene 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.",
'defmodule Foo do; defmodule Hygiene do; end; import Foo.Hygiene; end'
end
test :already_compiled_module do
assert "could not call eval_quoted on module Record because it was already compiled" ==
format_rescue 'Module.eval_quoted Record, quote(do: 1), [], file: __FILE__'
assert_compile_fail ArgumentError, "could not call eval_quoted on module Record because it was already compiled",
'Module.eval_quoted Record, quote(do: 1), [], file: __FILE__'
end
test :interpolation_error do
assert "nofile:1: missing terminator: end (for \"do\" starting at line 1)" == format_rescue '"foo\#{case 1 do )}bar"'
assert_compile_fail TokenMissingError, "nofile:1: missing terminator: end (for \"do\" starting at line 1)", '"foo\#{case 1 do )}bar"'
end
test :cant_define_local_due_to_in_erlang_macros_conflict do
assert "nofile:1: cannot define local quote/1 because it conflicts with Elixir special forms" ==
format_rescue 'defmodule Foo do\ndef quote(x), do: x\ndef bar(x), do: quote(do: x)\nend'
assert_compile_fail CompileError, "nofile:1: cannot define local quote/1 because it conflicts with Elixir special forms",
'defmodule Foo do\ndef quote(x), do: x\ndef bar(x), do: quote(do: x)\nend'
end
test :in_definition_module do
assert "nofile:1: cannot define module Foo because it is currently being defined" ==
format_rescue 'defmodule Foo, do: (defmodule Elixir.Foo, do: true)'
assert_compile_fail CompileError, "nofile:1: cannot define module Foo because it is currently being defined",
'defmodule Foo, do: (defmodule Elixir.Foo, do: true)'
end
test :invalid_definition do
assert "nofile:1: invalid syntax in def 1.(hello)" ==
format_rescue 'defmodule Foo, do: (def 1.(hello), do: true)'
assert_compile_fail SyntaxError, "nofile:1: invalid syntax in def 1.(hello)",
'defmodule Foo, do: (def 1.(hello), do: true)'
end
test :duplicated_bitstring_size do
assert "nofile:1: duplicated size definition for bitstring" == format_rescue '<<1 :: [size(12), size(13)]>>'
assert_compile_fail SyntaxError, "nofile:1: duplicated size definition for bitstring", '<<1 :: [size(12), size(13)]>>'
end
test :invalid_bitstring_specified do
assert "nofile:1: unknown bitstring specifier :atom" == format_rescue '<<1 :: :atom>>'
assert "nofile:1: unknown bitstring specifier unknown" == format_rescue '<<1 :: unknown>>'
assert "nofile:1: unknown bitstring specifier another(12)" == format_rescue '<<1 :: another(12)>>'
assert "nofile:1: size in bitstring expects an integer or a variable as argument" == format_rescue '<<1 :: size(:a)>>'
assert "nofile:1: unit in bitstring expects an integer as argument" == format_rescue '<<1 :: unit(x)>>'
assert_compile_fail SyntaxError, "nofile:1: unknown bitstring specifier :atom", '<<1 :: :atom>>'
assert_compile_fail SyntaxError, "nofile:1: unknown bitstring specifier unknown", '<<1 :: unknown>>'
assert_compile_fail SyntaxError, "nofile:1: unknown bitstring specifier another(12)", '<<1 :: another(12)>>'
assert_compile_fail SyntaxError, "nofile:1: size in bitstring expects an integer or a variable as argument", '<<1 :: size(:a)>>'
assert_compile_fail SyntaxError, "nofile:1: unit in bitstring expects an integer as argument", '<<1 :: unit(x)>>'
end
test :invalid_var! do
assert "nofile:1: expected var!(y) to expand to an existing variable or be a part of a match",
format_rescue 'var!(x)'
assert_compile_fail SyntaxError, "nofile:1: expected var!(x) to expand to an existing variable or be a part of a match", 'var!(x)'
end
test :invalid_alias do
assert "nofile:1: invalid args for alias, cannot create nested alias Sample.Lists" ==
format_rescue 'alias :lists, as: Sample.Lists'
assert_compile_fail SyntaxError, "nofile:1: invalid args for alias, cannot create nested alias Sample.Lists",
'alias :lists, as: Sample.Lists'
end
test :invalid_import_option do
assert "nofile:1: unsupported option ops given to import" ==
format_rescue 'import :lists, [ops: 1]'
assert_compile_fail SyntaxError, "nofile:1: unsupported option ops given to import",
'import :lists, [ops: 1]'
end
test :invalid_access_protocol_not_alias do
assert "the access protocol cannot be used inside match clauses (for example, on the left hand side of a match or in function signatures)" ==
format_rescue 'defmodule Foo do\ndef sample(config[integer: 0]), do: true\nend'
assert_raise RuntimeError, "the access protocol cannot be used inside match clauses (for example, on the left hand side of a match or in function signatures)", fn ->
defmodule Foo do
def sample(config[integer: 0]), do: true
end
end
end
test :invalid_access_protocol_not_available do
assert "nofile:2: module Unknown is not loaded and could not be found" ==
format_rescue 'defmodule Foo do\ndef sample(Unknown[integer: 0]), do: true\nend'
assert_compile_fail CompileError, "nofile:2: module Unknown is not loaded and could not be found",
'defmodule Foo do\ndef sample(Unknown[integer: 0]), do: true\nend'
end
test :invalid_access_protocol_not_record do
assert "cannot use module Kernel.ErrorsTest in access protocol because it does not export __record__/1" ==
format_rescue 'defmodule Foo do\ndef sample(Kernel.ErrorsTest[integer: 0]), do: true\nend'
assert_raise RuntimeError, "cannot use module Kernel.ErrorsTest in access protocol because it does not export __record__/1", fn ->
defmodule Foo do
def sample(Kernel.ErrorsTest[integer: 0]), do: true
end
end
end
test :invalid_access_protocol_not_keywords do
assert "expected contents inside brackets to be a Keyword" ==
format_rescue 'defmodule Foo do\ndef sample(Kernel.ErrorsTest.Config[0]), do: true\nend'
assert_raise RuntimeError, "expected contents inside brackets to be a Keyword", fn ->
defmodule Foo do
def sample(Kernel.ErrorsTest.Config[0]), do: true
end
end
end
test :invalid_access_protocol_invalid_keywords do
assert "record Kernel.ErrorsTest.Config does not have the keys: [:foo]" ==
format_rescue 'defmodule Foo do\ndef sample(Kernel.ErrorsTest.Config[foo: :bar]), do: true\nend'
assert_raise RuntimeError, "record Kernel.ErrorsTest.Config does not have the keys: [:foo]", fn ->
defmodule Foo do
def sample(Kernel.ErrorsTest.Config[foo: :bar]), do: true
end
end
end
test :invalid_access_protocol_invalid_keywords_outside_match do
assert "record Kernel.ErrorsTest.Config does not have the keys: [:foo]" ==
format_rescue 'Kernel.ErrorsTest.Config[foo: :bar]'
# FIXME: why RuntimeError?
assert_compile_fail RuntimeError, "record Kernel.ErrorsTest.Config does not have the keys: [:foo]",
'Kernel.ErrorsTest.Config[foo: :bar]'
end
test :invalid_rescue_clause do
assert "nofile:1: invalid rescue clause. The clause should match on an alias, a variable or be in the `var in [alias]` format" ==
format_rescue 'try do\n1\nrescue\nUndefinedFunctionError[arity: 1] -> false\nend'
assert_compile_fail SyntaxError, "nofile:1: invalid rescue clause. The clause should match on an alias, a variable or be in the `var in [alias]` format",
'try do\n1\nrescue\nUndefinedFunctionError[arity: 1] -> false\nend'
end
test :invalid_bc_return do
assert "nofile:1: a bit comprehension expects a bit string << >> to be returned" ==
format_rescue 'bc x inlist [1,2,3], do: x'
assert_compile_fail SyntaxError, "nofile:1: a bit comprehension expects a bit string << >> to be returned",
'bc x inlist [1,2,3], do: x'
end
test :invalid_bc_inbits_gen do
assert "nofile:1: a bit comprehension expects a bit string << >> to be used in inbits generators" ==
format_rescue 'bc x inbits "123", do: <<x>>'
assert_compile_fail SyntaxError, "nofile:1: a bit comprehension expects a bit string << >> to be used in inbits generators",
'bc x inbits "123", do: <<x>>'
end
test :unbound_cond do
assert "unbound variable _ inside cond. If you want the last clause to match, you probably meant to use true ->" ==
format_rescue 'cond do _ -> true end'
assert_compile_fail ArgumentError, "unbound variable _ inside cond. If you want the last clause to match, you probably meant to use true ->",
'cond do _ -> true end'
end
test :fun_different_arities do
assert "nofile:1: cannot mix clauses with different arities in function definition" ==
format_rescue 'fn x -> x; x, y -> x + y end'
assert_compile_fail SyntaxError, "nofile:1: cannot mix clauses with different arities in function definition",
'fn x -> x; x, y -> x + y end'
end
test :new_line_error do
assert "nofile:3: syntax error before: newline" ==
format_rescue 'if true do\n foo = [],\n baz\nend'
assert_compile_fail SyntaxError, "nofile:3: syntax error before: newline",
'if true do\n foo = [],\n baz\nend'
end
test :invalid_var_or_function_on_guard do
assert "nofile:1: unknown variable something_that_does_not_exist or cannot invoke function something_that_does_not_exist/0 inside guard" =
format_rescue('case [] do; [] when something_that_does_not_exist == [] -> :ok; end')
assert_compile_fail SyntaxError, "nofile:1: unknown variable something_that_does_not_exist or cannot invoke function something_that_does_not_exist/0 inside guard",
('case [] do; [] when something_that_does_not_exist == [] -> :ok; end')
end
test :bodyless_function do
assert {{:module, M, _, {:foo,1}}, [], _} =
:elixir.eval(to_char_list("defmodule M do\ndef foo(n)\nend"), [])
end
test :bodyless_function_with_guard do
assert_compile_fail SyntaxError, "nofile:2: missing keyword do in def",
("defmodule Foo do\ndef foo(n) when is_number(n)\nend")
end
test :invalid_function_on_match do
assert "nofile:1: cannot invoke function something_that_does_not_exist/0 inside match" =
format_rescue('case [] do; something_that_does_not_exist() -> :ok; end')
assert_compile_fail SyntaxError, "nofile:1: cannot invoke function something_that_does_not_exist/0 inside match",
('case [] do; something_that_does_not_exist() -> :ok; end')
end
test :invalid_remote_on_match do
assert "nofile:1: cannot invoke remote function Hello.something_that_does_not_exist/0 inside match" =
format_rescue('case [] do; Hello.something_that_does_not_exist() -> :ok; end')
assert_compile_fail SyntaxError, "nofile:1: cannot invoke remote function Hello.something_that_does_not_exist/0 inside match",
('case [] do; Hello.something_that_does_not_exist() -> :ok; end')
end
test :invalid_remote_on_guard do
assert "nofile:1: cannot invoke remote function Hello.something_that_does_not_exist/0 inside guard" =
format_rescue('case [] do; [] when Hello.something_that_does_not_exist == [] -> :ok; end')
assert_compile_fail SyntaxError, "nofile:1: cannot invoke remote function Hello.something_that_does_not_exist/0 inside guard",
('case [] do; [] when Hello.something_that_does_not_exist == [] -> :ok; end')
end
test :macros_error_stacktrace do
@@ -313,17 +336,6 @@ defmodule Kernel.ErrorsTest do
## Helpers
defp format_rescue(expr) do
result = try do
:elixir.eval(to_char_list(expr), [])
nil
rescue
error -> error.message
end
result || raise(ExUnit.AssertionError, message: "Expected function given to format_rescue to fail")
end
defp rescue_stacktrace(expr) do
result = try do
:elixir.eval(to_char_list(expr), [])
@@ -332,6 +344,6 @@ defmodule Kernel.ErrorsTest do
_ -> System.stacktrace
end
result || raise(ExUnit.AssertionError, message: "Expected function given to format_rescue to fail")
result || raise(ExUnit.AssertionError, message: "Expected function given to rescue_stacktrace to fail")
end
end
@@ -47,10 +47,6 @@ defmodule Kernel.Overridable do
x + y
end
def super_with_implicit_args(x, y) do
x + y
end
def many_clauses(0) do
11
end
@@ -59,7 +55,7 @@ defmodule Kernel.Overridable do
13
end
defoverridable [implicit_nested_super: 0, super_with_implicit_args: 2,
defoverridable [implicit_nested_super: 0,
super_with_explicit_args: 2, many_clauses: 1]
def without_super do
@@ -82,14 +78,6 @@ defmodule Kernel.Overridable do
super x, y * 2
end
def super_with_implicit_args(x, y) do
if true do
x + y + super
else
raise :NEVER
end
end
def many_clauses(2) do
17
end
@@ -98,8 +86,8 @@ defmodule Kernel.Overridable do
super(0) + super(1)
end
def many_clauses(_) do
super
def many_clauses(x) do
super(x)
end
end
@@ -131,10 +119,6 @@ defmodule Kernel.OverridableTest do
assert Overridable.super_with_explicit_args(1, 2) == 5
end
test "calling super with implicit args" do
assert Overridable.super_with_implicit_args(1, 2) == 6
end
test "function without overridable returns false for super?" do
assert Overridable.no_overridable == { :no_overridable, false }
end
@@ -160,4 +144,4 @@ defmodule Kernel.OverridableTest do
"Overridable functions available are: bar/0")
end
end
end
end
@@ -102,6 +102,16 @@ defmodule Kernel.QuoteTest do
assert quote(do: [unquote_splicing(contents)|[1,2,3]]) == [1,2,3,1,2,3]
end
test :splice_on_stab do
{ fun, [] } =
Code.eval_quoted(quote(do: fn(unquote_splicing([1,2,3])) -> :ok end), [])
assert fun.(1,2,3) == :ok
{ fun, [] } =
Code.eval_quoted(quote(do: fn(1, unquote_splicing([2,3])) -> :ok end), [])
assert fun.(1,2,3) == :ok
end
test :stab do
assert { :->, _, [{[],_}] } = (quote do -> end)
assert { :->, _, [{[],_}] } = (quote do: (->))
@@ -65,6 +65,11 @@ defmodule Kernel.SigilsTest do
bad_modifier = quote do: %w(foo bar baz)x
assert ArgumentError[] = catch_error(Code.eval_quoted(bad_modifier))
assert %w(Foo Bar)a == [:"Foo", :"Bar"]
assert %w(Foo.#{Bar}.Baz)a == [:"Foo.Elixir.Bar.Baz"]
assert %w(Foo.Bar)b == ["Foo.Bar"]
assert %w(Foo.#{Bar})c == ['Foo.Elixir.Bar']
end
test :__W__ do
@@ -82,5 +87,8 @@ defmodule Kernel.SigilsTest do
bad_modifier = quote do: %W(foo bar baz)x
assert ArgumentError[] = catch_error(Code.eval_quoted(bad_modifier))
assert %W(Foo #{Bar})a == [:"Foo", :"\#{Bar}"]
assert %W(Foo.Bar.Baz)a == [:"Foo.Bar.Baz"]
end
end
@@ -0,0 +1,211 @@
Code.require_file "../test_helper.exs", __DIR__
defmodule Kernel.WarningTest do
use ExUnit.Case, async: true
import ExUnit.CaptureIO
test :unused_variable do
assert capture_io(fn ->
Code.eval_string """
defmodule Sample do
def hello(arg), do: nil
end
"""
end) =~ "variable arg is unused"
after
purge Sample
end
test :unused_function do
assert capture_io(fn ->
Code.eval_string """
defmodule Sample1 do
defp hello, do: nil
end
"""
end) =~ "function hello/0 is unused"
assert capture_io(fn ->
Code.eval_string """
defmodule Sample2 do
defp hello(0), do: hello(1)
defp hello(1), do: :ok
end
"""
end) =~ "function hello/1 is unused"
assert capture_io(fn ->
Code.eval_string """
defmodule Sample3 do
def a, do: nil
def b, do: d(10)
defp c(x, y // 1), do: [x, y]
defp d(x), do: x
end
"""
end) =~ "function c/2 is unused"
after
purge [Sample1, Sample2, Sample3]
end
test :unused_cyclic_functions do
assert capture_io(fn ->
Code.eval_string """
defmodule Sample do
defp a, do: b
defp b, do: a
end
"""
end) =~ "function a/0 is unused"
after
purge Sample
end
test :unused_macro do
assert capture_io(fn ->
Code.eval_string """
defmodule Sample do
defmacrop hello, do: nil
end
"""
end) =~ "macro hello/0 is unused"
after
purge Sample
end
test :unused_default_args do
assert capture_io(fn ->
Code.eval_string """
defmodule Sample1 do
def a, do: b(1,2,3)
defp b(arg1 // 1, arg2 // 2, arg3 // 3), do: [arg1, arg2, arg3]
end
"""
end) =~ "default arguments in b/3 are never used"
assert capture_io(fn ->
Code.eval_string """
defmodule Sample2 do
def a, do: b(1,2)
defp b(arg1 // 1, arg2 // 2, arg3 // 3), do: [arg1, arg2, arg3]
end
"""
end) =~ "the first 2 default arguments in b/3 are never used"
assert capture_io(fn ->
Code.eval_string """
defmodule Sample3 do
def a, do: b(1)
defp b(arg1 // 1, arg2 // 2, arg3 // 3), do: [arg1, arg2, arg3]
end
"""
end) =~ "the first default argument in b/3 is never used"
assert capture_io(fn ->
Code.eval_string """
defmodule Sample4 do
def a, do: b(1)
defp b(arg1 // 1, arg2, arg3 // 3), do: [arg1, arg2, arg3]
end
"""
end) == nil
after
purge [Sample1, Sample2, Sample3, Sample4]
end
test :unused_import do
assert capture_io(fn ->
Code.eval_string """
defmodule Sample1 do
def hello, do: nil
end
defmodule Sample2 do
import Sample1
def a, do: nil
end
"""
end) =~ "unused import Sample1"
after
purge [Sample1, Sample2]
end
test :clause_not_match do
assert capture_io(fn ->
Code.eval_string """
defmodule Sample do
def hello, do: nil
def hello, do: nil
end
"""
end) =~ "this clause cannot match because a previous clause at line 2 always matches"
after
purge Sample
end
test :clause_with_defaults_should_be_first do
assert capture_io(fn ->
Code.eval_string """
defmodule Sample do
def hello(arg), do: nil
def hello(arg // 0), do: nil
end
"""
end) =~ "clause with defaults should be the first clause in def hello/1"
after
purge Sample
end
test :multiple_clauses_with_defaults do
assert capture_io(fn ->
Code.eval_string """
defmodule Sample do
def hello(arg // 0), do: nil
def hello(arg // 1), do: nil
end
"""
end) =~ "def hello/1 has default values and multiple clauses, use a separate clause for declaring defaults"
after
purge Sample
end
test :unused_with_local_with_overridable do
assert capture_io(fn ->
Code.eval_string """
defmodule Sample do
def hello, do: world
defp world, do: :ok
defoverridable [hello: 0]
def hello, do: :ok
end
"""
end) =~ "function world/0 is unused"
after
purge Sample
end
test :used_with_local_with_reattached_overridable do
assert nil? capture_io(fn ->
Code.eval_string """
defmodule Sample do
def hello, do: world
defp world, do: :ok
defoverridable [hello: 0, world: 0]
end
"""
end)
after
purge Sample
end
defp purge(list) when is_list(list) do
Enum.each list, purge(&1)
end
defp purge(module) when is_atom(module) do
:code.delete module
:code.purge module
end
end
+38 -16
View File
@@ -4,8 +4,24 @@ defmodule KernelTest do
use ExUnit.Case, async: true
test :match do
assert "abcd" =~ %r/c(d)/
refute "abcd" =~ %r/e/
assert ("abcd" =~ %r/c(d)/) == true
assert ("abcd" =~ %r/e/) == false
string = "^ab+cd*$"
assert (string =~ "ab+") == true
assert (string =~ "bb") == false
assert_raise ArgumentError, "bad argument on the right side of =~: [\"^a\",\"*$\"]", fn ->
string =~ ["^a", "*$"]
end
assert_raise ArgumentError, "argument error", fn ->
1234 =~ "hello"
end
assert_raise ArgumentError, "argument error", fn ->
1234 =~ %r"hello"
end
end
test :nil? do
@@ -63,7 +79,7 @@ defmodule KernelTest do
end
test :__MODULE__ do
assert __MODULE__ == :"Elixir-KernelTest"
assert __MODULE__ == :"Elixir.KernelTest"
end
test :function_from___ENV__ do
@@ -102,20 +118,14 @@ defmodule KernelTest do
assert float_to_binary(7.0) == "7.00000000000000000000e+00"
end
case :proplists.get_value(:float_to_binary,
:proplists.get_value(:exports, :erlang.module_info, [])) do
2 ->
# We can only test this where this functionality is available
test :float_to_binary_with_options do
assert float_to_binary(7.1, [decimals: 2]) == "7.10"
assert float_to_binary(7.1, [scientific: 2]) == "7.10e+00"
assert float_to_binary(7.1, [decimals: 2, compact: true]) == "7.1"
assert float_to_binary(7.1, [scientific: 2, compact: true]) == "7.10e+00"
assert float_to_binary(7.1, [decimals: 2, compact: false]) == "7.10"
end
_ ->
:ok
test :float_to_binary_with_options do
assert float_to_binary(7.1, [decimals: 2]) == "7.10"
assert float_to_binary(7.1, [scientific: 2]) == "7.10e+00"
assert float_to_binary(7.1, [decimals: 2, compact: true]) == "7.1"
assert float_to_binary(7.1, [scientific: 2, compact: true]) == "7.10e+00"
assert float_to_binary(7.1, [decimals: 2, compact: false]) == "7.10"
end
test :atom_to_binary_defaults_to_utf8 do
expected = atom_to_binary :some_binary, :utf8
actual = atom_to_binary :some_binary
@@ -233,6 +243,18 @@ defmodule KernelTest do
assert __MODULE__ |> :constant == 13
end
test "non-call" do
assert 1 |> (&1*2).() == 2
assert [1] |> hd(&1).() == 1
import CompileAssertion
# FIXME: this mustn't work, but it doesn't call pipeline_op at all
#assert_compile_fail ArgumentError, "Unsupported expression in pipeline |> operator: &1", "1 |> &1"
assert_compile_fail ArgumentError, "Unsupported expression in pipeline |> operator: &1 * 2", "1 |> &1*2"
assert_compile_fail ArgumentError, "Unsupported expression in pipeline |> operator: hd(&1)", "[1] |> hd(&1)"
end
def constant, do: 13
defp twice(a), do: a * 2
+1 -1
View File
@@ -25,7 +25,7 @@ defmodule KeywordTest do
list = [{:b,2},{:a,1},{:c,3}]
dict = HashDict.new list
assert Keyword.from_enum(list) == [b: 2, a: 1, c: 3]
assert Keyword.from_enum(dict) == [a: 1, b: 2, c: 3]
assert Keyword.equal?(Keyword.from_enum(dict), [a: 1, b: 2, c: 3])
end
test :keyword? do
+2 -4
View File
@@ -23,10 +23,8 @@ defmodule List.Chars.NumberTest do
assert to_char_list(1) == '1'
end
unless :erlang.system_info(:otp_release) < 'R16' do
test :float do
assert to_char_list(1.0) == '1.0'
end
test :float do
assert to_char_list(1.0) == '1.0'
end
end
@@ -0,0 +1,215 @@
Code.require_file "../test_helper.exs", __DIR__
defmodule Module.DispatchTrackerTest do
use ExUnit.Case, async: true
alias Module.DispatchTracker, as: D
setup do
{ :ok, [pid: D.start_link] }
end
teardown config do
D.stop(config[:pid])
:ok
end
## Locals
test "can add definitions", config do
D.add_definition(config[:pid], :def, { :foo, 1 })
D.add_definition(config[:pid], :defp, { :bar, 1 })
end
test "can add locals", config do
D.add_definition(config[:pid], :def, { :foo, 1 })
D.add_local(config[:pid], { :foo, 1 }, { :bar, 1 })
end
test "public definitions are always reachable", config do
D.add_definition(config[:pid], :def, { :public, 1 })
assert { :public, 1 } in D.reachable(config[:pid])
D.add_definition(config[:pid], :defmacro, { :public, 2 })
assert { :public, 2 } in D.reachable(config[:pid])
end
test "private definitions are never reachable", config do
D.add_definition(config[:pid], :defp, { :private, 1 })
refute { :private, 1 } in D.reachable(config[:pid])
D.add_definition(config[:pid], :defmacrop, { :private, 2 })
refute { :private, 2 } in D.reachable(config[:pid])
end
test "private definitions are reachable when connected to local", config do
D.add_definition(config[:pid], :defp, { :private, 1 })
refute { :private, 1 } in D.reachable(config[:pid])
D.add_local(config[:pid], { :private, 1 })
assert { :private, 1 } in D.reachable(config[:pid])
end
test "private definitions are reachable when connected through a public one", config do
D.add_definition(config[:pid], :defp, { :private, 1 })
refute { :private, 1 } in D.reachable(config[:pid])
D.add_definition(config[:pid], :def, { :public, 1 })
D.add_local(config[:pid], { :public, 1 }, { :private, 1 })
assert { :private, 1 } in D.reachable(config[:pid])
end
@unused [
{ { :private, 1 }, :defp, 0 }
]
test "unused private definitions are marked as so", config do
D.add_definition(config[:pid], :def, { :public, 1 })
unused = D.collect_unused_locals(config[:pid], @unused)
assert unused == [{ :unused_def, { :private, 1 }, :defp }]
D.add_local(config[:pid], { :public, 1 }, { :private, 1 })
unused = D.collect_unused_locals(config[:pid], @unused)
refute unused == [{ :unused_def, { :private, 1 }, :defp }]
end
@unused [
{ { :private, 3 }, :defp, 3 }
]
test "private definitions with unused default arguments", config do
D.add_definition(config[:pid], :def, { :public, 1 })
unused = D.collect_unused_locals(config[:pid], @unused)
assert unused == [{ :unused_def, { :private, 3 }, :defp }]
D.add_local(config[:pid], { :public, 1 }, { :private, 3 })
unused = D.collect_unused_locals(config[:pid], @unused)
assert unused == [{ :unused_args, { :private, 3 }}]
end
test "private definitions with some unused default arguments", config do
D.add_definition(config[:pid], :def, { :public, 1 })
D.add_local(config[:pid], { :public, 1 }, { :private, 1 })
unused = D.collect_unused_locals(config[:pid], @unused)
assert unused == [{ :unused_args, { :private, 3 }, 1}]
end
test "private definitions with all used default arguments", config do
D.add_definition(config[:pid], :def, { :public, 1 })
D.add_local(config[:pid], { :public, 1 }, { :private, 0 })
unused = D.collect_unused_locals(config[:pid], @unused)
assert unused == []
end
## Defaults
test "can add defaults", config do
D.add_definition(config[:pid], :def, { :foo, 4 })
D.add_defaults(config[:pid], :def, { :foo, 4 }, 2)
end
test "defaults are reachable if public", config do
D.add_definition(config[:pid], :def, { :foo, 4 })
D.add_defaults(config[:pid], :def, { :foo, 4 }, 2)
assert { :foo, 2 } in D.reachable(config[:pid])
assert { :foo, 3 } in D.reachable(config[:pid])
end
test "defaults are not reachable if private", config do
D.add_definition(config[:pid], :defp, { :foo, 4 })
D.add_defaults(config[:pid], :defp, { :foo, 4 }, 2)
refute { :foo, 2 } in D.reachable(config[:pid])
refute { :foo, 3 } in D.reachable(config[:pid])
end
test "defaults are connected", config do
D.add_definition(config[:pid], :defp, { :foo, 4 })
D.add_defaults(config[:pid], :defp, { :foo, 4 }, 2)
D.add_local(config[:pid], { :foo, 2 })
assert { :foo, 2 } in D.reachable(config[:pid])
assert { :foo, 3 } in D.reachable(config[:pid])
assert { :foo, 4 } in D.reachable(config[:pid])
end
## Imports
test "can add import", config do
D.add_import(config[:pid], nil, Module, { :concat, 1 })
end
test "can retrieve imports", config do
D.add_import(config[:pid], nil, Module, { :concat, 1 })
assert Module in D.imports(config[:pid])
end
test "find imports from dispatch", config do
D.add_import(config[:pid], nil, Module, { :concat, 1 })
assert Module in D.imports_with_dispatch(config[:pid], { :concat, 1 })
refute Module in D.imports_with_dispatch(config[:pid], { :unknown, 1 })
end
test "can add warnable", config do
D.add_warnable(config[:pid], Module, true, 15)
assert Module in D.imports(config[:pid])
end
test "find unused imports", config do
D.add_warnable(config[:pid], Module, true, 15)
assert { Module, 15 } in D.collect_unused_imports(config[:pid])
D.add_warnable(config[:pid], Module, false, 15)
refute { Module, 15 } in D.collect_unused_imports(config[:pid])
end
test "find import conflicts", config do
refute { [Module], :conflict, 1 } in D.collect_imports_conflicts(config[:pid], [conflict: 1])
D.add_import(config[:pid], nil, Module, { :conflict, 1 })
assert { [Module], :conflict, 1 } in D.collect_imports_conflicts(config[:pid], [conflict: 1])
end
## Remotes
test "can add remote", config do
D.add_remote(config[:pid], nil, Module, { :concat, 1 })
end
test "can retrieve remotes", config do
D.add_remote(config[:pid], nil, Module, { :concat, 1 })
assert Module in D.remotes(config[:pid])
end
test "find remotes from dispatch", config do
D.add_remote(config[:pid], nil, Module, { :concat, 1 })
assert Module in D.remotes_with_dispatch(config[:pid], { :concat, 1 })
refute Module in D.remotes_with_dispatch(config[:pid], { :unknown, 1 })
end
test "remotes are not reachable", config do
D.add_definition(config[:pid], :def, { :public, 1 })
D.add_remote(config[:pid], { :public, 1 }, Module, { :concat, 1 })
assert D.reachable(config[:pid]) == [{ :public, 1 }]
end
## All
test "can query dispatches", config do
D.add_definition(config[:pid], :def, { :public, 1 })
D.add_definition(config[:pid], :def, { :caller, 1 })
D.add_local(config[:pid], { :caller, 1 }, { :public, 1 })
D.add_remote(config[:pid], { :public, 1 }, Module, { :concat, 1 })
D.add_import(config[:pid], { :public, 1 }, Kernel, { :if, 2 })
assert D.dispatches_to(config[:pid], { :public, 1 }) == [{ :caller, 1 }]
assert D.dispatches_to(config[:pid], { :import, :if, 2 }) == [{ :public, 1 }]
assert D.dispatches_to(config[:pid], { :remote, :concat, 1 }) == [{ :public, 1 }]
assert D.dispatches_to(config[:pid], Module) == [{ :remote, :concat, 1 }]
assert D.dispatches_to(config[:pid], Kernel) == [{ :import, :if, 2 }]
assert { :public, 1 } in D.dispatches_from(config[:pid], { :caller, 1 })
assert { :remote, :concat, 1 } in D.dispatches_from(config[:pid], { :public, 1 })
assert { :import, :if, 2 } in D.dispatches_from(config[:pid], { :public, 1 })
end
end
+3 -2
View File
@@ -115,7 +115,7 @@ defmodule ModuleTest do
end
test :alias_with_raw_atom do
defmodule :"Elixir-ModuleTest-RawModule" do
defmodule :"Elixir.ModuleTest.RawModule" do
def hello, do: :world
end
@@ -161,6 +161,7 @@ defmodule ModuleTest do
assert Module.concat(Foo, 'Bar') == Foo.Bar
assert Module.concat(Foo, Bar.Baz) == Foo.Bar.Baz
assert Module.concat(Foo, "Bar.Baz") == Foo.Bar.Baz
assert Module.concat(Bar, :nil) == :"Elixir.Bar.nil"
end
test :safe_concat do
@@ -173,7 +174,7 @@ defmodule ModuleTest do
test :split do
module = Very.Long.Module.Name.And.Even.Longer
assert Module.split(module) == ["Very", "Long", "Module", "Name", "And", "Even", "Longer"]
assert Module.split("Elixir-Very-Long") == ["Very", "Long"]
assert Module.split("Elixir.Very.Long") == ["Very", "Long"]
assert Module.concat(Module.split(module)) == module
end
+5 -5
View File
@@ -64,15 +64,15 @@ defmodule PathTest do
assert Path.type("C:\\usr\\local\\bin") == :absolute
assert Path.type("C:usr\\local\\bin") == :volumerelative
assert Path.type("/usr/local/bin") == :absolute
assert Path.type("/usr/local/bin") == :volumerelative
assert Path.type("usr/local/bin") == :relative
assert Path.type("../usr/local/bin") == :relative
assert Path.type('/usr/local/bin') == :absolute
assert Path.type('/usr/local/bin') == :volumerelative
assert Path.type('usr/local/bin') == :relative
assert Path.type('../usr/local/bin') == :relative
assert Path.type(['/usr/', 'local/bin']) == :absolute
assert Path.type(['/usr/', 'local/bin']) == :volumerelative
assert Path.type(['usr/', 'local/bin']) == :relative
assert Path.type(['../usr', '/local/bin']) == :relative
end
@@ -238,13 +238,13 @@ defmodule PathTest do
end
test :split_with_binary do
assert Path.split("") == ["/"]
assert Path.split("") == []
assert Path.split("foo") == ["foo"]
assert Path.split("/foo/bar") == ["/", "foo", "bar"]
end
test :split_with_list do
assert Path.split('') == ''
assert Path.split('') == []
assert Path.split('foo') == ['foo']
assert Path.split('/foo/bar') == ['/', 'foo', 'bar']
end
+6
View File
@@ -104,6 +104,12 @@ defmodule ProtocolTest do
false = ProtocolTest.WithAll.blank(ProtocolTest.Foo.new(a: 1))
end
test :protocol_with_nil_record do
assert_raise UndefinedFunctionError, fn ->
ProtocolTest.WithOnly.blank({:nil})
end
end
test :protocol_for do
assert_protocol_for(ProtocolTest.WithAll, Atom, :foo)
assert_protocol_for(ProtocolTest.WithAll, Function, fn(x) -> x end)
+20
View File
@@ -52,12 +52,27 @@ end
defmodule RecordTest.Macros do
defrecordp :_user, name: "José", age: 25
defmacro gen do
quote do
alias RecordTest.Macros.Nested
def this_works, do: RecordTest.Macros.Nested[]
def this_should_too, do: Nested[]
end
end
defrecord Nested do
def nested_record_alias?(Nested[]) do
true
end
defrecord NestedInNested, it_compiles: true
end
# Ensure there is no conflict in a nested module
# named record.
defrecord Record, [a: 1, b: 2]
def new() do
_user()
end
@@ -94,6 +109,11 @@ end
defmodule RecordTest do
use ExUnit.Case, async: true
# Check the access from the generated macro works
# as expected. If it compiles, we are good to go.
require RecordTest.Macros
RecordTest.Macros.gen
test :record_access_with_nil_keyword do
record = RecordTest.DynamicName.new(a: nil)
record_access = RecordTest.DynamicName[a: nil]
+37 -11
View File
@@ -8,6 +8,10 @@ defmodule Regex.BinaryTest do
assert Regex.match?(%r/^b$/m, "a\nb\nc")
end
test :precedence do
assert { "aa", :unknown } |> elem(0) =~ %r/(a)\1/
end
test :backreference do
assert "aa" =~ %r/(a)\1/
end
@@ -36,7 +40,7 @@ defmodule Regex.BinaryTest do
end
test :unicode do
assert ("josé" =~ %r"\p{Latin}$"u) == 3
assert ("josé" =~ %r"\p{Latin}$"u)
end
test :groups do
@@ -89,11 +93,6 @@ defmodule Regex.BinaryTest do
assert Regex.run(%r"e", "abcd", return: :index) == nil
end
test :index do
assert Regex.index(%r"c(d)", "abcd") == 2
assert Regex.index(%r"e", "abcd") == nil
end
test :scan do
assert Regex.scan(%r"c(d|e)", "abcd abce") == [["d"], ["e"]]
assert Regex.scan(%r"c(?:d|e)", "abcd abce") == ["cd", "ce"]
@@ -123,6 +122,38 @@ defmodule Regex.BinaryTest do
assert Regex.replace(%r(b), "abcbe", "[\\&]") == "a[&]c[&]e"
assert Regex.replace(%r[(b)], "abcbe", "[\\1]") == "a[b]c[b]e"
end
test :escape do
assert matches_escaped?(".")
refute matches_escaped?(".", "x")
assert matches_escaped?("[\w]")
refute matches_escaped?("[\w]", "x")
assert matches_escaped?("\\")
assert matches_escaped?("\\xff", "\\xff")
refute matches_escaped?("\\xff", "\xff")
assert matches_escaped?("(")
assert matches_escaped?("()")
assert matches_escaped?("(?:foo)")
assert matches_escaped?("\\A \\z")
assert matches_escaped?(" x ")
assert matches_escaped?("  x    x ") # unicode spaces here
assert matches_escaped?("# lol")
assert matches_escaped?("\\A.^$*+?()[{\\| \t\n\xff\\z #hello\x{202F}\x{205F}")
end
defp matches_escaped?(string) do
matches_escaped?(string, string)
end
defp matches_escaped?(string, match) do
Regex.match? %r/#{Regex.escape(string)}/usimx, match
end
end
defmodule Regex.ListTest do
@@ -147,11 +178,6 @@ defmodule Regex.ListTest do
assert Regex.run(%r"c(d)", "abcd", return: :binary) == ["cd", "d"]
end
test :index do
assert Regex.index(%r'c(d)', 'abcd') == 2
assert Regex.index(%r'e', 'abcd') == nil
end
test :indexes do
assert Regex.run(%r'c(d)', 'abcd', return: :index) == [{2,2},{3,1}]
assert Regex.run(%r'e', 'abcd', return: :index) == nil
+90 -1
View File
@@ -42,6 +42,7 @@ defmodule StringTest do
test :upcase do
assert String.upcase("123 abcd 456 efg hij ( %$#) kl mnop @ qrst = -_ uvwxyz") == "123 ABCD 456 EFG HIJ ( %$#) KL MNOP @ QRST = -_ UVWXYZ"
assert String.upcase("") == ""
assert String.upcase("abcD") == "ABCD"
end
test :upcase_utf8 do
@@ -51,16 +52,19 @@ defmodule StringTest do
test :upcase_utf8_multibyte do
assert String.upcase("straße") == "STRASSE"
assert String.upcase("áüÈß") == "ÁÜÈSS"
end
test :downcase do
assert String.downcase("123 ABcD 456 EfG HIJ ( %$#) KL MNOP @ QRST = -_ UVWXYZ") == "123 abcd 456 efg hij ( %$#) kl mnop @ qrst = -_ uvwxyz"
assert String.downcase("abcD") == "abcd"
assert String.downcase("") == ""
end
test :downcase_utf8 do
assert String.downcase("& % # ÀÁÂ ÃÄÅ 1 2 Ç Æ") == "& % # àáâ ãäå 1 2 ç æ"
assert String.downcase("ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝÞ") == "àáâãäåæçèéêëìíîïðñòóôõöøùúûüýþ"
assert String.downcase("áüÈß") == "áüèß"
end
test :capitalize do
@@ -280,5 +284,90 @@ defmodule StringTest do
assert String.to_float("pi") === :error
end
test :starts_with? do
## Normal cases ##
assert String.starts_with? "hello", "he"
assert String.starts_with? "hello", "hello"
assert String.starts_with? "hello", ["hellö", "hell"]
assert String.starts_with? "エリクシア", "エリ"
refute String.starts_with? "hello", "lo"
refute String.starts_with? "hello", "hellö"
refute String.starts_with? "hello", ["hellö", "goodbye"]
refute String.starts_with? "エリクシア", "仙丹"
## Edge cases ##
assert String.starts_with? "", ""
assert String.starts_with? "", ["", "a"]
assert String.starts_with? "b", ["", "a"]
assert String.starts_with? "abc", ""
assert String.starts_with? "abc", [""]
refute String.starts_with? "", "abc"
refute String.starts_with? "", [" "]
## Sanity checks ##
assert String.starts_with? "", ["", ""]
assert String.starts_with? "abc", ["", ""]
assert_raise ArgumentError, fn ->
String.starts_with? "abc", [["a"], "a"]
end
end
test :ends_with? do
## Normal cases ##
assert String.ends_with? "hello", "lo"
assert String.ends_with? "hello", "hello"
assert String.ends_with? "hello", ["hell", "lo", "xx"]
assert String.ends_with? "hello", ["hellö", "lo"]
assert String.ends_with? "エリクシア", "シア"
refute String.ends_with? "hello", "he"
refute String.ends_with? "hello", "hellö"
refute String.ends_with? "hello", ["hel", "goodbye"]
refute String.ends_with? "エリクシア", "仙丹"
## Edge cases ##
assert String.ends_with? "", ""
assert String.ends_with? "", ["", "a"]
refute String.ends_with? "", ["a", "b"]
assert String.ends_with? "abc", ""
assert String.ends_with? "abc", ["", "x"]
refute String.ends_with? "", "abc"
refute String.ends_with? "", [" "]
## Sanity checks ##
assert String.ends_with? "", ["", ""]
assert String.ends_with? "abc", ["", ""]
assert_raise ArgumentError, fn ->
String.ends_with? "abc", [["c"], "c"]
end
end
test :contains? do
## Normal cases ##
assert String.contains? "elixir of life", "of"
assert String.contains? "エリクシア", "シ"
assert String.contains? "elixir of life", ["mercury", "life"]
refute String.contains? "exlixir of life", "death"
refute String.contains? "エリクシア", "仙"
refute String.contains? "elixir of life", ["death", "mercury", "eternal life"]
## Edge cases ##
assert String.contains? "", ""
assert String.contains? "abc", ""
assert String.contains? "abc", ["", "x"]
refute String.contains? "", " "
refute String.contains? "", "a"
## Sanity checks ##
assert String.contains? "", ["", ""]
assert String.contains? "abc", ["", ""]
assert_raise ArgumentError, fn ->
String.contains? "abc", [["b"], "b"]
end
end
end

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