Compare commits

...
760 Commits
Author SHA1 Message Date
José Valim 32d4739dad Release v0.9.3 2013-06-23 23:15:58 +02:00
meh 650cfc0448 Use semver for development version
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2013-06-23 23:15:36 +02:00
José Valim d4d824022f Update docs for Record.import/2 2013-06-23 22:22:15 +02:00
José Valim c6284557e7 Rename sigils from __ to sigil_$, closes #1301 2013-06-23 20:38:37 +02:00
José Valim a8a2f86a23 Record type should be private by default, closes #1288 2013-06-23 20:20:47 +02:00
José Valim fc219e7dca IO.ANSI now uses binding 2013-06-23 19:32:58 +02:00
José Valim 96a103d222 Allow :binding as an option in quote
This commit also adds Macro.expand_once/2 and Macro.expand_all/2.
2013-06-23 19:23:32 +02:00
José Valim f525f5b8cb Add unquote: true support to Macro.escape 2013-06-23 16:32:07 +02:00
José Valim 5c30a687fa Inspect module before printing, closes #1309 2013-06-23 15:27:04 +02:00
José Valim 9ffe8b0e97 Clean up defdelegate docs 2013-06-23 13:36:01 +02:00
José Valim 12c8564a26 Update CHANGELOG after clean up on the operators table
by making it sane and clean in case we want to add
custom operators in the future.

The operator table with suggestions of extension points
(not yet implemented) can be found in the elixir_parser.yrl
file.

The only backwards incompatible change is the precedence
of the operators used by the Bitwise module.
2013-06-23 13:20:02 +02:00
José Valim 903aa60fec Finish operator table clean up 2013-06-23 13:18:25 +02:00
José Valim 969bc968ee Move ++, --, ** and // to new table 2013-06-23 13:18:25 +02:00
José Valim e941d78daf Move operator table clean up 2013-06-23 13:18:25 +02:00
José Valim 8d3475825b Group arrow-like ops together 2013-06-23 13:18:25 +02:00
José Valim 68c890e179 Move and and or operators to the new table 2013-06-23 13:18:25 +02:00
José Valim 6f73d7dbb9 Move comp_op to the new table 2013-06-23 13:18:25 +02:00
José Valim c2ac967123 Move all unary operators to new schema 2013-06-23 13:18:25 +02:00
José Valim dd573c9806 Initial work cleaning up op table 2013-06-23 13:18:25 +02:00
Yurii Rashkovskii 58ee415e63 Improve mix escript_app's documentation 2013-06-22 18:25:09 +01:00
José Valim 561397da7a Ensure escript_app doesn't break the app 2013-06-22 19:23:03 +02:00
José Valim 3d2438c625 Ensure esriptize does not try to read directories 2013-06-22 19:14:02 +02:00
José Valim a08710bfda Update CHANGELOG 2013-06-22 19:00:40 +02:00
José Valim 151695b43c Add tests for nested whens 2013-06-22 18:53:18 +02:00
José Valim 2d8259b432 Support (x, y) when z, closes #1313 2013-06-22 18:53:18 +02:00
José Valim ae03e10db5 Merge pull request #1300 from jdrexler/issue-1285
Added chgrp!, chown!, and chmod! to module File
2013-06-22 05:10:43 -07:00
José Valim 208fbaf5ef Fix typo (tks @ericmj) 2013-06-22 14:02:10 +02:00
José Valim 820ca0a57e Do not use Enum on bootstrap 2013-06-22 13:55:33 +02:00
José Valim cf309ede14 Add docs to Mix.Version and make version always the first arg 2013-06-22 13:43:41 +02:00
José Valim da0a95695d Allow types to be given to defrecordp
Part of #1288
2013-06-22 13:15:00 +02:00
José Valim b2541a1b93 Inline list funcs so we don't pay an extra dispatch cost 2013-06-22 13:15:00 +02:00
Johannes Drexler 5df40f7c42 Added functions for changing mode, owner and group to module File 2013-06-21 21:07:05 +02:00
José Valim 5e24dbaddb Merge pull request #1308 from justinweiss/patch-1
Fixed some incorrect markdown in binary_to_list/3's documentation.
2013-06-21 10:09:08 -07:00
Justin Weiss 3f8e3d4d38 Fixed some incorrect markdown in binary_to_list/3's documentation.
The docs for binary_to_list/3 weren't monospacing their reference to binary_to_list/1, which resulted in some bad italicization.
2013-06-21 10:38:04 -06:00
José Valim 391fee3ea2 ^x variables should always refer to the value before the expression
Closes #1307
2013-06-21 18:27:16 +02:00
José Valim 6cee8572e0 Clean up ExUnit tests 2013-06-21 15:16:02 +02:00
vanstee f2d9e802cd Include description and improve formatting
Switching from `AssertionError` to `ExpectationError` gives us a nicer
error message and allows us to easily include the incorrect result as a
description.

Fixes #1276

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2013-06-21 15:08:48 +02:00
José Valim 5247961185 Optimize temp_vars lookup 2013-06-21 11:43:41 +02:00
José Valim 53a2618cba Merge pull request #1302 from jwarwick/typos
Fixed doc typos
2013-06-20 23:36:04 -07:00
John Warwick d360addc65 Fixed ex_doc path in README 2013-06-20 21:37:47 -04:00
John Warwick 08e7da305c Fixed typos in file.ex docs 2013-06-20 21:35:47 -04:00
José Valim afab1f8d73 Fix typo on assertion message 2013-06-20 23:15:53 +02:00
José Valim 749266b649 Merge pull request #1299 from vanstee/assert-statement-if-boolean
Show original statement if assert or refute fails
2013-06-20 13:46:32 -07:00
José Valim c4dcaf540d Slight clean up on erlang tests 2013-06-20 22:44:22 +02:00
vanstee 561d6ea0d1 Show original statement if assert or refute fails
If the asserted statement evaluates to false, show the unevaluated
statement in the assertion error output message for more context about
the failure. Refute also now follows similar semantics.

Fixes #1276
2013-06-20 16:17:55 -04:00
José Valim 1574f61a8b Output next steps information after creating repos, closes #1297 2013-06-20 14:04:35 +02:00
José Valim 7dc941227f Ensure escriptize is performed recursively, closes #1296 2013-06-20 13:49:30 +02:00
José Valim ca026039a3 Stop parsing on -S, closes #1294 2013-06-20 12:25:00 +02:00
José Valim 4b06a2d316 Merge pull request #1293 from ToJans/WindowsBugfixing
Updated elixir.bat so one can also pass in non ".ex" files
2013-06-20 03:22:07 -07:00
Tom Janssens 00299fe7cb Removed "*.ex/file" parameter checking from elixir.bat
If it now finds an unknown parameter, it assumes all paremeters are parsed
and just passes all the rest to the elixir script.
2013-06-20 12:17:24 +02:00
José Valim 9e6b650efd Merge pull request #1295 from ToJans/WindowsRebarFix
Fix the rebar wrapper on windows
2013-06-20 03:00:49 -07:00
José Valim c56d0760f6 Update CHANGELOG 2013-06-20 12:00:02 +02:00
Tom Janssens aedc78536f Fix the rebar wrapper on windows
System.find_executable for rebar returns a rebar.cmd file, which should not
be wrapped by escript. We now have an extra check that verifies whether the
invoked file is indeed a cmd file.
2013-06-20 11:56:20 +02:00
José Valim 0c17fa0ab2 Use .bat executables on Windows 2013-06-20 09:08:51 +02:00
José Valim 752a09c988 Update error message in ExUnit 2013-06-20 08:36:49 +02:00
José Valim d5a8821ec1 Merge pull request #1292 from devinus/github-git-protocol
Use the significantly faster Git protocol for github: dependencies
2013-06-19 14:47:02 -07:00
Devin Torres aa7400aaad Use the significantly faster Git protocol for github: dependencies
See: http://git-scm.com/book/ch4-1.html#The-Git-Protocol
2013-06-19 16:17:46 -05:00
José Valim d849fc5dca Merge pull request #1289 from alco/typespec-doc-formatting
Fix formatting issues in Typespec's docs
2013-06-19 10:32:37 -07:00
Alexei Sholik 3d73d1a359 Fix formatting issues in Typespec's docs 2013-06-19 20:29:58 +03:00
José Valim 18bcef3ef4 Fix a bug when inspecting records nested in tuples 2013-06-19 19:17:15 +02:00
José Valim 115fd99bfb Move .gitattributes files to repo root 2013-06-19 18:42:44 +02:00
José Valim 879499b409 Deprecate (fun(...) -> ...) spec style
We are deprecating this style because it reads ambiguous
as a function that receives another function as argument.
2013-06-19 18:25:48 +02:00
José Valim e265771c6e Merge pull request #1287 from ToJans/WindowsBugfixing
Added .gitattribute for fixtures in tests, so CRLF is not autoconverted
2013-06-19 09:18:11 -07:00
Tom Janssens f9f728cb66 Added .gitattribute for fixtures in tests, so CRLF is not autoconverted 2013-06-19 18:02:50 +02:00
José Valim e321caee24 Merge pull request #1286 from ToJans/WindowsBugfixing
Fixed path tests when running on Windows: removed drive prefixes from check
2013-06-19 08:00:39 -07:00
Tom Janssens 5dd3a23752 Fixed path tests when running on Windows: removed drive prefixes from check 2013-06-19 16:48:26 +02:00
José Valim c572cb80fc Support binding/2 that goes across all contexts 2013-06-19 14:17:45 +02:00
José Valim b761f1a605 Wording on File's @moduledoc 2013-06-19 12:53:28 +02:00
José Valim c1f907b412 Clean up docs for File 2013-06-19 12:31:33 +02:00
José Valim c45d0f4f2f No longer support .beam files on mix local 2013-06-19 12:11:56 +02:00
José Valim 406f0dcffe qoute -> quote 2013-06-19 10:38:09 +02:00
José Valim 9dbc216c11 Merge pull request #1283 from andrzejsliwa/master
cleanup after refactoring of Code.string_to_qouted
2013-06-19 01:13:43 -07:00
Andrzej Sliwa 22b455fad4 cleanup after refactorization of Code.string_to_quoted 2013-06-19 09:03:42 +02:00
José Valim 9807b506eb Update CHANGELOG and minor fixes 2013-06-19 08:00:19 +02:00
José Valim 624423686d Merge pull request #1282 from andrzejsliwa/master
rename of Macro.to_binary and Code.string_to_ast regards to #1143
2013-06-18 22:57:38 -07:00
José Valim e5cba5e4b5 Merge pull request #1281 from meh/process-self
Add Process.self
2013-06-18 22:55:52 -07:00
meh 1f15bf72e7 Add Process.self 2013-06-19 00:23:02 +02:00
Andrzej Sliwa e6bc5a273c refactored/renamed from Code.string_to_ast to Code.string_to_quoted 2013-06-19 00:18:32 +02:00
Andrzej Sliwa 8fa6c4f96f refactor/rename Macro.to_binary to Macro.to_string with deprecation warning 2013-06-18 23:35:13 +02:00
José Valim b7e8f97de1 Merge pull request #1279 from ToJans/WindowsBugfixing
Windows bugfixing
2013-06-18 11:18:04 -07:00
José Valim 5fc68fe71b File.rm_rf! should not complain on invalid entries 2013-06-18 20:15:53 +02:00
Tom Janssens 808260cd1b Fixed unit test for File.Touch with failure on Windows 2013-06-18 20:13:55 +02:00
Tom Janssens d9152c08d8 Fixed a bug within File.rm_rf on windows 2013-06-18 20:09:23 +02:00
José Valim 4048e542d9 cp_r should pinpoint place of failure on errors 2013-06-18 19:52:06 +02:00
José Valim b08273700f Use regex on matching on some of File exceptions 2013-06-18 18:18:51 +02:00
José Valim 0d5512931e Merge pull request #1277 from ToJans/WindowsBugfixing
Some IO tests return alternate error codes on windows
2013-06-18 08:54:49 -07:00
Tom Janssens 7fc37de058 Some IO tests return alternate error codes on windows
Now these tests allow different kinds of error codes. However, I currently
duplicate the is_io_error? method; I would assume there would be an easier
way to do this; maybe just put it in a namespace and reference the
namespace?
2013-06-18 17:49:19 +02:00
José Valim c4dac6fd7a Catch invalid arguments for binding/1 2013-06-18 16:29:12 +02:00
José Valim d3122ddcc5 Add binding/0 and binding/1 for retrieving bindings 2013-06-18 16:14:03 +02:00
José Valim 74cfcda902 Validate eval options, closes #1238 2013-06-18 15:38:13 +02:00
José Valim 6fc6dd4352 Update CHANGELOG 2013-06-18 12:35:09 +02:00
José Valim 7014986f65 Add -f option to make clean_exbeam 2013-06-18 12:26:23 +02:00
José Valim 031f4e942f Get rid of unused variable warning 2013-06-18 12:20:37 +02:00
José Valim 9904bdce9d Warn on undefined module attributes, closes #963 2013-06-18 12:19:15 +02:00
José Valim 754418493f Merge pull request #1275 from ericmj/elixir-in-warning
Emit warning for 'x in []' in guards
2013-06-18 03:04:05 -07:00
Eric Meadows-Jonsson 27000ea1b4 Emit warning for 'x in []' in guards 2013-06-18 11:57:27 +02:00
José Valim 620ee4298c Raise proper warnings when default clauses from different definitions collide, closes #1244 2013-06-18 11:36:57 +02:00
José Valim 1d09da7e53 Use = instead of == on shell comparison 2013-06-18 10:42:49 +02:00
José Valim 5115256853 Use proper path separator in msys
For now, we are only checking the path on msys but
we may need to check it for cygwin too.

Thanks to @ToJans for providing guidance and debug
information to sort this issue out.
2013-06-18 10:30:51 +02:00
José Valim 0d715343de Merge pull request #1273 from rnowling/master
Fixed grammar and wrong word (exit, not exist) in Process.exit/2 documentation.
2013-06-17 23:20:57 -07:00
rnowling 441547d8e4 Fixed grammar and wrong word (exit, not exist) in Process.exit/2 documentation. 2013-06-17 23:25:23 -03:00
Yurii Rashkovskii 5e123e6519 Make sure we don't receive an EXIT message from the dependency tracker
It pollutes IEx and scripts execution

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2013-06-17 20:37:44 +02:00
José Valim 5b45ee5c8d Update CHANGELOG 2013-06-17 13:53:46 +02:00
José Valim 939942bdb3 Merge pull request #1270 from ToJans/master
With the new elixir.bat version the batch script only worked from the ./bin folder on windows
2013-06-17 04:46:46 -07:00
José Valim e64be43090 Update executable docs 2013-06-17 13:44:44 +02:00
Tom Janssens da898e1ac9 elixir.bat only worked from the bin folder
Apparently, when shifting parameters, the %~dp0 is not preserved, 
so it needs to keep this value. This will fix things like iex on Windows, which just crashed with the previous version.
2013-06-17 14:43:33 +03:00
José Valim 8dba46a0ac Merge pull request #1269 from ToJans/master
On Windows Erlang parameters were not getting passed to elixir.bat in a proper way
2013-06-17 04:19:31 -07:00
José Valim 633eb871ee Update CHANGELOG 2013-06-17 13:19:11 +02:00
José Valim 91c0fa71fa Avoid clash in between modules 2013-06-17 13:19:07 +02:00
José Valim 87da4e91bb Clean up error messages, move defaults warning to an error 2013-06-17 13:05:57 +02:00
Tom Janssens 7768828d9c Removeved the echo prefix from the elixir.bat file
Inline editing in github is dangerous!
2013-06-17 13:40:24 +03:00
Tom Janssens 8ece000489 Added parameter validation in the elixir.bat file
The batch file now allows both erlang and elixir commands before the ".ex" file; if any unknown parameter is passed before the ".ex file", an error is thrown.
2013-06-17 12:39:09 +02:00
José Valim 1297fa5953 Refactoring of some mix tasks 2013-06-17 12:11:43 +02:00
Tom Janssens c7a2442bc7 Make sure all Erlang parameters are extracted and parsed in elixir.bat 2013-06-17 11:19:44 +02:00
José Valim f88ee1c23a Do not check in utf8 directories into source control 2013-06-17 08:57:53 +02:00
Eric Meadows-Jonsson 64a22fc15d Merge pull request #1264 from edgurgel/mix-rebar-scripts
Fix rebar config scripts evaluation
2013-06-16 12:05:36 -07:00
Eduardo Gurgel 07cd772962 Fix rebar config scripts evaluation
Scripts are evaluated using the dependency directory as current directory

Closes #1262
2013-06-16 15:54:54 -03:00
José Valim 160a8255ef Merge pull request #1263 from elixir-lang/docs-standard
Change the bullet points on code.ex to follow the standard on this file
2013-06-16 09:20:16 -07:00
Rafael Mendonça França cca430522d Follow the bullet point standard on this file 2013-06-16 13:11:51 -03:00
Rafael Mendonça França db417110e7 Use the proper code highlight for functions 2013-06-16 13:11:28 -03:00
José Valim 67896b56c5 Ensure errors point to the proper -> clauses
This commit changes the -> clauses representation from:

    { :->, meta, list({ list(expr), expr }) }

To:

    { :->, meta, list({ list(expr), meta, expr }) }

Now clauses can store metadata, which allows us to provide
accurate reporting information.

Closes #1215
2013-06-16 17:27:14 +02:00
José Valim d21d603617 Avoid race condition on CLI tests 2013-06-16 13:57:52 +02:00
José Valim fdd108fc43 Improve error message matching on local.uninstall with archives 2013-06-16 13:23:13 +02:00
José Valim 19ff7a03f9 Ensure imports inside quotes are properly registered, closes #1225 2013-06-16 13:18:47 +02:00
José Valim 7f5744bde7 Ensure defmodule :atom does not define an alias 2013-06-16 13:10:59 +02:00
José Valim b87797f788 Return Foo for __MODULE__.Foo outside of a module 2013-06-16 13:10:59 +02:00
José Valim 48b19ec580 Improve mix test to rely on possible multiple messages 2013-06-16 13:10:59 +02:00
José Valim f2e200f832 Merge pull request #1261 from alco/ansi-doctest
Don't assume terminal availability in ANSI doctests
2013-06-16 03:07:23 -07:00
Alexei Sholik a541f53dd1 Don't assume terminal availability in ANSI doctests 2013-06-16 12:15:00 +03:00
José Valim 6ce77b1b49 Update CONTRIBUTING.md language 2013-06-16 10:07:35 +02:00
José Valim 5222c7737f Update docs for Macro.expand 2013-06-16 10:07:35 +02:00
José Valim b0cf65b5cd Merge pull request #1259 from elixir-lang/whitespaces
Uniform use of whitespace after commas on codebase
2013-06-15 22:52:38 -07:00
Rafael Mendonça França 7cdc25538e Uniform use of whitespace after commas on codebase
This make easier to read and also follow some standard in the code
2013-06-15 16:52:48 -03:00
José Valim b82cd8cbd0 Update CHANGELOG 2013-06-15 19:35:59 +02:00
José Valim 9365556ea4 Rename packages to archives 2013-06-15 19:33:43 +02:00
José Valim de376663f0 Merge pull request #1223 from jeremyjh/1124-mix-local-arch
add mix package and related tasks
2013-06-15 09:34:55 -07:00
José Valim f24ec671c2 Merge pull request #1258 from elixir-lang/fix-typo
Review documentation of `.split`, `.drop` and `.take` on `Dict`
2013-06-15 09:10:24 -07:00
José Valim c0f7a268c7 Avoid usage of defrecordp and move all parsing to Mix.Version.Parser 2013-06-15 18:09:48 +02:00
Rafael Mendonça França 7b4e0915b1 Remove documentation of .split, .drop and .take on HashDict and ListDict
They are already documented on Dict
2013-06-15 12:55:07 -03:00
Rafael Mendonça França 2ea7de7361 Fix Dict.drop documentation 2013-06-15 12:54:34 -03:00
José Valim 82cc435fd7 Merge pull request #1130 from meh/mix-version
Implement Mix.Version
2013-06-15 08:25:53 -07:00
José Valim 8eab556f55 Update CHANGELOG 2013-06-15 17:01:09 +02:00
José Valim 5a37594853 Handle --warnings-as-errors and other elixirc options in mix compile 2013-06-15 17:00:06 +02:00
Alexei Sholik 82f3fa00d0 Merge pull request #1256 from mururu/fix-typo
Fix typo in doc
2013-06-15 04:52:14 -07:00
Yuki Ito c97dc3bd15 Fix typo in doc 2013-06-15 20:35:15 +09:00
José Valim d6e99e2f5a Merge pull request #1255 from tonini/code-module-typo-fix
Fix typo in the @doc for Code.eval_string
2013-06-15 03:53:24 -07:00
Samuel Tonini 46b6f82473 fix typo in doc 2013-06-15 12:37:52 +02:00
José Valim c80e43ca42 Go to :binary.match(left, right) directly instead of hoping through String.contains? 2013-06-15 10:51:10 +02:00
José Valim 80e41f8b4b Allow +, -, and friends to be called dynamically
Our compiler still inline those calls. This commit simply
allows one to write:

    mod = Kernel
    mod.+(1, 2)
2013-06-15 10:45:12 +02:00
José Valim 9e16cf36ed There is no need for =~ to be a macro 2013-06-15 09:46:19 +02:00
José Valim 1d8e39a2df Only change compilation status if warnings_as_errors 2013-06-15 09:46:10 +02:00
José Valim 21db352039 Always print warnings to stderr 2013-06-15 09:32:53 +02:00
José Valim 45f033d15a Add a test for warnings_as_errors option 2013-06-15 08:41:27 +02:00
José Valim 8eba2fcba0 Merge pull request #1214 from baldmountain/feature/898-warnings-as-errors
Feature/898 warnings as errors
2013-06-14 23:20:25 -07:00
José Valim e476c06798 Merge pull request #1253 from Nicd/patch-1
Fix compiler_options docblock formatting errors
2013-06-14 23:08:22 -07:00
Nicd 0b6b7ea0e4 Fix compiler_options docblock formatting errors 2013-06-15 01:39:21 +03:00
José Valim e62ef92a4b Ensure Enum.take/2 doesn't peek ahead 2013-06-14 18:43:05 +02:00
José Valim 7be271876e Merge pull request #1245 from josephwilk/hash_dict_equals_bug
Bug: HashDict equal?
2013-06-14 04:40:49 -07:00
josephwilk 7c639f9252 document equal? 2013-06-14 11:49:16 +02:00
josephwilk f03e707e8e Bug: equal? on HashDict was not comparing the two dicts 2013-06-14 11:49:10 +02:00
Jeremy Huffman 38e63451d6 local.uninstall warn on uninstall package task 2013-06-13 21:37:34 -04:00
Jeremy Huffman 5e1366c946 changes from PR discussion
1: ensure_loaded?

3: ebin

2: assume path/ebin to be packaged

6: accept project path & assume ebin

8: include priv in archive

5: local.uninstall handles packages too

7: local.install [] will install a package

7: mix new .gitignore *.ez files
2013-06-13 21:37:34 -04:00
Jeremy Huffman 0470ebf5af implement mix package and related tasks re #1124 2013-06-13 21:37:34 -04:00
José Valim b71642045d Merge pull request #1240 from fishcakez/hashdict-contract
Improve efficiency of HashDict contract
2013-06-13 11:00:19 -07:00
James Fish 582e814422 Improve efficiency of HashDict contract
When contracting in size the HashDict does not need to relocate
(re-hash) key-value pairs. It is not required because each of the
deepest nodes in a trie become a bucket on contraction. This bucket
contains all the key-value pairs in that node.
2013-06-13 18:11:51 +01:00
José Valim d15ad4da80 Never use the dependency rebar 2013-06-13 17:31:48 +02:00
José Valim bc0089ff42 Simplify out of date deps error message 2013-06-13 17:14:03 +02:00
José Valim bff8a6ae75 Remove deprecated code branches 2013-06-13 16:48:31 +02:00
José Valim 6d4ad4ade6 Start v0.9.3.dev 2013-06-13 16:26:25 +02:00
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
meh 057691722e Implement Mix.Version, closes #1115 2013-06-09 15:36:24 +02: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
Geoffrey Clements 0e2752f7c2 print compiled even if compilation_status is not :ok it may have been from another file. Halt on an :error 2013-06-09 07:49:38 -04:00
Geoffrey Clements 76b2255d7b Don't pass warnings-as-errors to module since it isn't needed 2013-06-09 07:49:38 -04:00
Geoffrey Clements 93d5128db3 Implement José's suggestions.
1. Detect all places in elixir_errors:handle_file_warning/3 where we
output something with io
2. Change those places to go through an internal function that will
call elixir_compiler:get_opt and figure out if we should print to
stdout or stderr and act accordingly
3. In the same function above, whenever we print a warning as an error,
we should store somewhere that a warning as an error happened. I
recommend storing that in the elixir_code_server gen server
4. Provide a Code.compilation_status that would return it
2013-06-09 07:49:38 -04:00
Geoffrey Clements 6d98020923 Unwind a lot of the wrong direction, output warnings based on warnings-as-errors setting 2013-06-09 07:49:38 -04:00
Geoffrey Clements 77110f970b Update parallel compiler callback documentation 2013-06-09 07:49:38 -04:00
Geoffrey Clements cf6ce9a3fd Fix tests that expect the parallel compiler callback to only pass one parameter. 2013-06-09 07:49:38 -04:00
Geoffrey Clements be0dea7c0a Pass exit status to callback so we can decide there whether to stop or not. 2013-06-09 07:49:38 -04:00
Geoffrey Clements b0b444bbd3 a little closer, status is passed to main process 2013-06-09 07:49:37 -04:00
Geoffrey Clements 3e45188e88 Experiment with how to pass back status. process dictionary doesn't work. 2013-06-09 07:49:37 -04:00
Geoffrey Clements 51171cb8e1 Mostly done. Still need to fix warning printout. 2013-06-09 07:49:37 -04: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
José Valim 59d6448609 Release v0.8.3 2013-05-22 14:32:01 +02:00
José Valim 31b4290797 Add IEx processing to Kernel.CLI 2013-05-22 12:53:55 +02:00
José Valim 39c33c7bb1 Fix h(c/2), closes #1101 2013-05-22 11:01:47 +02:00
José Valim 37054c8226 Deprecate Exception.format_entry in favor of Exception.format_stacktrace_entry 2013-05-22 00:55:46 +02:00
José Valim 9751ca70c1 IEx.Remsh should be private 2013-05-22 00:31:00 +02:00
José Valim 054ff457be Remove Enum.equal?
There is a branch work refactoring Enum and Enum.equal?
contains some limitations. We are removing it for now
since it was not officially released yet.
2013-05-21 20:30:19 +02:00
José Valim b5fce07ec0 Do not raise if a dependency can't be found, closes #1052 2013-05-21 14:22:35 +02:00
José Valim d722685c6a Update CHANGELOG 2013-05-21 13:19:33 +02:00
José Valim beaa746fcb Support MIX_PATH as alternative load paths, closes #889 2013-05-21 13:16:30 +02:00
José Valim c3d6a96dac Consider load time on ExUnit reports, closes #893 2013-05-21 12:57:22 +02:00
José Valim d238d50579 Deprecate Code.eval in favor of Code.eval_string, closes #953 2013-05-21 12:57:22 +02:00
José Valim 4bde3c85ed Remove empty? from the Enum protocol, closes #1097 2013-05-21 12:57:22 +02:00
José Valim 9b0353a747 Improve error messages when there is a space in between function name and parenthesis 2013-05-21 12:57:22 +02:00
José Valim b6cb6ca88a Merge pull request #1098 from alco/path-expand-doc
Fix behavior of Path.expand/2. Improve docstrings
2013-05-21 00:46:02 -07:00
Alexei Sholik 33ab2895a0 Mention Path.expand/2 in the Changelog 2013-05-21 02:24:29 +03:00
Alexei Sholik 245bc09d7b Fix behavior of Path.expand/2. Improve docstrings 2013-05-21 02:17:23 +03:00
José Valim 0abeef0724 Update CHANGELOG 2013-05-20 21:57:48 +02:00
José Valim 771708764f Improve docs for the IEx module and some error messages as well 2013-05-20 21:31:38 +02:00
José Valim 08e6afe5a9 Merge pull request #1096 from alco/iex-break-1089
Implement a break-trigger for incomplete expressions in IEx.
2013-05-20 05:50:40 -07:00
Alexei Sholik c4c0798c7e Change the break-trigger for IEx to #iex:break 2013-05-20 15:09:47 +03:00
Alexei Sholik 392a2fbef2 Document the break-trigger in h/0 2013-05-20 15:09:46 +03:00
Alexei Sholik bcd4b97253 Call it simply break-trigger in the docstring 2013-05-20 15:09:46 +03:00
Alexei Sholik 614dc52af4 Implement a break-trigger for incomplete expressions in IEx. Closes #1089 2013-05-20 15:09:46 +03:00
José Valim 8907edca3e Improve error message for invalid patterns in guards and matches
Closes #1092
2013-05-19 21:39:44 +02:00
José Valim a893d805aa Merge pull request #1095 from mururu/fix-getn-doc
Fix docs for IO.getn
2013-05-19 12:29:11 -07:00
Yuki Ito 0142587fd2 Fix docs for IO.getn 2013-05-20 04:12:11 +09:00
José Valim 56a52f162b Merge pull request #1094 from mururu/getb-to-getn
Deprecate IO.getb in favor of IO.getn
2013-05-19 11:45:57 -07:00
Yuki Ito 5ec2de4b76 Deprecate IO.getb in favor of IO.getn 2013-05-20 03:29:29 +09:00
José Valim d9b98a62c8 Check if the module is in the same context before checking if it is open 2013-05-19 19:42:13 +02:00
José Valim 78a0abc08f Check if we are in the current module context as a fast path 2013-05-19 18:41:08 +02:00
José Valim 0328d867a9 Check if a module is open or not when defining a record 2013-05-19 18:33:39 +02:00
José Valim 05b650f373 Change preference of |> to support constructs like 1..5 |> Enum.to_list 2013-05-19 18:33:14 +02:00
José Valim b3e8a3d8fe Improve docs for records 2013-05-19 15:54:33 +02:00
José Valim 021dc541b8 Remove unsupported matching on rescue clause 2013-05-19 15:54:33 +02:00
José Valim af0970fd0b Merge pull request #1093 from bellthoven/fix-mix-compile-quick
Only mix compile -q recompiles unnecessarly
2013-05-19 06:54:08 -07:00
Gustavo Dutra 565fc8bffe Only mix compile -q recompiles unnecessarly
mix compile --quick was recompiling previously compiled files even when nothing had changed
2013-05-19 10:24:24 -03:00
José Valim a6317d525e Merge pull request #1090 from alco/require_file-docfix
Fix docstring for require_file
2013-05-18 04:59:53 -07:00
Alexei Sholik e96963c4a4 Fix docstring for require_file 2013-05-18 13:25:56 +03:00
José Valim a57be7379b Merge pull request #1087 from alco/iex-docs
Review IEx docs
2013-05-17 09:16:23 -07:00
Alexei Sholik 79020c768f Improve docs for IEx.Helpers 2013-05-17 19:10:58 +03:00
Alexei Sholik 714b4440e3 Update moduledoc for IEx.Remsh 2013-05-17 18:50:20 +03:00
Alexei Sholik 1c505ad451 Add a docstring for IEx.Server.start 2013-05-17 18:49:39 +03:00
José Valim 5cbe0baa2d Merge pull request #1081 from alco/iex-token-missing-error
TokenMissingError fix
2013-05-17 07:23:44 -07:00
Alexei Sholik 98ed3045bb Clean up the code some more. Closes #1051 2013-05-17 15:29:43 +03:00
José Valim 441c333939 Optimize HashDict.take and HashDict.split, update CHANGELOG 2013-05-17 12:34:11 +02:00
Jake Mitchell bf356495a4 Use faster HashDict.drop/2, ListDict.take/2 and ListDict.drop/2 implementations 2013-05-17 12:26:19 +02:00
José Valim 08211b7c52 Merge pull request #1085 from TanYewWei/issue_1069
Issue #1069: Add Dict.drop, Dict.take and Dict.split
2013-05-17 03:12:41 -07:00
Yew-wei Tan 216019c34f fix inappropriate doctests for drop/2, take/2, and split/2 functions of Dict module 2013-05-17 19:59:33 +10:00
José Valim 16555c974a Update ExDoc directory 2013-05-17 09:54:28 +02:00
José Valim 869e90f2e5 Split SOURCE_REF into its own makefile function 2013-05-17 09:50:08 +02:00
José Valim 80633a2ec6 Use :stdio as default argument in capture_io 2013-05-17 09:47:39 +02:00
José Valim b5d20d3135 Merge pull request #1078 from alco/custom-doc-rev
Retrieve the most suitable revision for source links in the docs
2013-05-17 00:47:31 -07:00
José Valim 911eb23ee2 Merge pull request #1083 from ericmj/mix-cmd
Remove unnecessary Mix test output
2013-05-17 00:46:45 -07:00
Yew-wei Tan 4a3e741070 Issue #1069: Add Dict.drop, Dict.take and Dict.split 2013-05-17 16:04:41 +10:00
Eric Meadows-Jonsson 3e44e98299 Remove unnecessary Mix test output 2013-05-17 01:38:12 +02:00
Alexei Sholik b08289768a Replace getrev.sh with a one-liner (thanks @bellthoven) 2013-05-17 01:35:48 +03:00
José Valim 6e5c4feac0 Merge pull request #1082 from ericmj/mix-umbrella-appfile2
Clean up mix dependency retrieving and converging
2013-05-16 11:58:19 -07:00
José Valim be16bf487a Remove deprecated usage of keywords in Mix 2013-05-16 20:57:51 +02:00
José Valim 647344ea9d Update CHANGELOG 2013-05-16 20:53:23 +02:00
José Valim 6589d17527 Deprecate Enum.at! and Dict.get! in favor of Enum.fetch! and Dict.fetch!
Previously, `Dict.get!` had a different API than `Dict.get` as
it didn't allow a default argument. Furthermore, having `Dict.fetch`
returning a tuple and `Dict.fetch!` returning an error is more
inline to the bang functions existent in the File module.
2013-05-16 20:49:09 +02:00
Eric Meadows-Jonsson 2d870cf528 Clean up mix dependency retrieving and converging
Rename Mix.Deps.Project -> Mix.Deps.Retriever.
Retrieve the entire dependency tree in Retriever and let Converger
only handle converging.
Don't require setting app: false on umbrella deps.
2013-05-16 20:42:37 +02:00
Alexei Sholik 36fab9dafb Fix stray file variable 2013-05-16 21:38:01 +03:00
Alexei Sholik d61a82565b Remove IExTokenMissingError and clean up the code 2013-05-16 21:28:24 +03:00
Alexei Sholik a63e4da167 Distinguish between TokenMissingError raised during parsing from the one
raised during evaluation in IEx
2013-05-16 21:06:07 +03:00
José Valim de1d47f9d5 Add Enum.at/2, Enum.at/3 and Enum.fetch/2 2013-05-16 18:32:01 +02:00
José Valim 1051b61605 Also convert String.to_integer tests to use === 2013-05-16 18:16:21 +02:00
José Valim f8f37c2b24 Remove if in favor of extra function clause 2013-05-16 18:14:14 +02:00
José Valim 5e0f171655 Merge pull request #1079 from jdeisenberg/string_to_int_float
Change == to === in tests for String.to_integer and String.to_float.
2013-05-16 07:02:12 -07:00
J David Eisenberg 521249edfe Change == to === in tests for String.to_integer and String.to_float. 2013-05-16 05:58:04 -07:00
Alexei Sholik a67e445883 Retrieve the most suitable revision for source links in the docs 2013-05-16 15:55:09 +03:00
José Valim a0544478a6 Merge pull request #1077 from meh/string-slice
Make String.slice consistent with Ruby semantics, closes #1034
2013-05-16 05:20:32 -07:00
José Valim 056df8c5d3 Merge pull request #1076 from mururu/default_args
Fix warning for unused default arguments
2013-05-16 05:19:50 -07:00
meh 6d6ec1ea11 Make String.slice consistent with Ruby semantics, closes #1034 2013-05-16 11:56:21 +02:00
Yuki Ito 020c5b0dd2 Fix warning for unused default arguments 2013-05-16 13:58:29 +09:00
José Valim d8e374df56 Merge pull request #1044 from jdeisenberg/string_to_int_float
String.to_integer and String.to_float
2013-05-15 15:59:24 -07:00
José Valim 37ff5a6fda Merge pull request #1072 from ericmj/mix-umbrella-dirfile
Ignore files in umbrella apps directory
2013-05-15 15:03:56 -07:00
Eric Meadows-Jonsson ba5a9326ed Ignore files in umbrella apps directory
Closes #1071
2013-05-15 23:48:28 +02:00
José Valim 5d885a97d3 Optimize HashDict.pop 2013-05-15 13:59:02 -06:00
ILYA Khlopotov eedd68e661 Implementation of Dict.pop (proposal #856 for Dict) 2013-05-15 13:14:25 -06:00
José Valim 1aa589afa0 Update CHANGELOG 2013-05-15 13:06:04 -06:00
José Valim c0b9c2545a Move List.Dict to ListDict 2013-05-15 13:03:40 -06:00
José Valim 1bf4d053b9 Use level two headings when appripriate 2013-05-15 12:04:41 -06:00
José Valim b81bf4a839 Add some sample pull requests 2013-05-15 12:01:22 -06:00
José Valim 06f1f3ccd7 Improvements to contributing guide 2013-05-15 11:41:12 -06:00
José Valim abc5d6f954 Also allow explicitly capturing of stdio and stderr 2013-05-15 10:19:01 -06:00
José Valim 69fe431dd9 Remove unused variable 2013-05-15 10:03:08 -06:00
José Valim ad2a6774b1 Ensure no group_leader leakage on capture_io and remove stderr test which was printing on run 2013-05-15 10:02:29 -06:00
José Valim 44a140490e Merge pull request #1059 from mururu/io_protocol
Implement capture_io for ex_unit
2013-05-15 08:54:43 -07:00
José Valim 1250c70d93 Merge pull request #1068 from ericmj/mix-propagate-deps
Propagate dependencies in mix tasks
2013-05-15 08:52:44 -07:00
Eric Meadows-Jonsson 62b6ed1b4c Propagate dependencies in mix tasks
Closes #1036
2013-05-15 16:29:34 +02:00
Yuki Ito 66eb369b3d Implement capture_io for ex_unit 2013-05-15 19:12:50 +09:00
José Valim 6e018c5226 Ensure record args are evaluated in the parent context, closes #1065 2013-05-14 22:51:37 -06:00
José Valim 95377a36a5 Expand macros and quoted variables inside << >>, closes #1063 2013-05-14 22:33:16 -06:00
José Valim 05c1630605 Add missing unquote/unquote_splicing documentation 2013-05-14 22:12:11 -06:00
José Valim 46fc45df9b Revert "Expand macros and quoted variables inside << >>, closes #1063"
It broke the build. Reopens #1063.

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

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

    defrecord Sample, [:hello] do
      import List
    end

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

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

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

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

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

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

  "trying to override previously defined function NAME/ARITY"

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

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

This reverts commit 238e7f4bc2.
2013-04-21 22:08:27 -06:00
José Valim 87d57a38a3 Do not go through eval if there is nothing to evaluate 2013-04-21 18:03:09 -06:00
José Valim 907920de44 Unfold the loop manually 2013-04-21 17:24:24 -06:00
José Valim 53d34099f1 Optimize elixir_tree_helpers:abstract_syntax 2013-04-21 17:07:25 -06:00
José Valim 4c70f1b3cd Store clauses in a separate table (thanks to yrashk for first iteration of this patch) 2013-04-21 14:56:52 -06:00
José Valim d2dbef1611 Bump to 0.8.3.dev 2013-04-20 19:22:40 -06:00
José Valim 70432ca7f2 Remove deprecations 2013-04-20 19:22:13 -06:00
José Valim 0cab23e3e7 Get rid of eval on IO.ANSI 2013-04-20 19:07:40 -06:00
293 changed files with 15359 additions and 6992 deletions
+1
View File
@@ -0,0 +1 @@
lib/elixir/test/elixir/fixtures/*.txt text eol=lf
+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
+146 -3
View File
@@ -1,3 +1,147 @@
# v0.9.3 (2013-06-23)
* enhancements
* [File] Add `File.chgrp`, `File.chmod` and `File.chown`
* [Kernel] Add --warnings-as-errors to Elixir's compiler options
* [Kernel] Print warnings to stderr
* [Kernel] Warn on undefined module attributes
* [Kernel] Emit warning for `x in []` in guards
* [Kernel] Add `binding/0` and `binding/1` for retrieving bindings
* [Kernel] `quote` now allows a binding as an option
* [Macro] Add `Macro.expand_once/2` and `Macro.expand_all/2`
* [Mix] Implement `Mix.Version` for basic versioniong semantics
* [Mix] Support creation and installation of archives (.ez files)
* [Mix] `github: ...` shortcut now uses the faster `git` schema instead of `https`
* [Record] Allow types to be given to `defrecord` and `defrecordp`
* bug fix
* [Kernel] The elixir executable on Windows now supports the same options as the UNIX one
* [Kernel] Improve error messages on default clauses clash
* [Kernel] `__MODULE__.Foo` now returns `Foo` when outside of a Module
* [Kernel] Improve error messages when default clauses from different definitions collide
* [Kernel] `^x` variables should always refer to the value before the expression
* [Kernel] Allow `(x, y) when z` in function clauses and try expressions
* [Mix] Mix now properly evaluates rebar scripts
* deprecations
* [Code] `Code.string_to_ast/1` has been deprecated in favor of `Code.string_to_quoted/1`
* [Macro] `Macro.to_binary/1` has been deprecated in favor of `Macro.to_string/1`
* [Typespec] Deprecate `(fun(...) -> ...)` in favor of `(... -> ...)`
* backwards incompatible changes
* [Bitwise] Precedence of operators used by the Bitwise module were changed. Check `elixir_parser.yrl` for more information.
* [File] `rm_rf` and `cp_r` now returns a tuple with three elements on failures
* [Kernel] The quoted representation for `->` clauses changed from a tuple with two elements to a tuple with three elements to support metadata
* [Kernel] Sigils now dispatch to `sigil_$` instead of `__$__` where `$` is the sigil caracter
* [Macro] `Macro.expand/2` now expands until final form. Although this is backwards incompatible, it is very likely you do not need to change your code, since expansion until its final form is recommended, particularly if you are expecting an atom out of it
* [Mix] No longer support beam files on `mix local`
# 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
* [CLI] Flags `-p` and `-pr` fails if pattern match no files
* [CLI] Support `--hidden` and `--cookie` flags for distributed Erlang
* [Enum] Add `Enum.to_list/1`, `Enum.member?/2`, `Enum.uniq/2`, `Enum.max/1`, `Enum.max/2`, `Enum.min/1` and `Enum.min/2`
* [ExUnit] Add `ExUnit.CaptureIO` for IO capturing during tests
* [ExUnit] Consider load time on ExUnit time reports
* [IEx] Support `ls` with colored output
* [IEx] Add `#iex:break` to break incomplete expressions
* [Kernel] Add `Enum.at`, `Enum.fetch` and `Enum.fetch!`
* [Kernel] Add `String.to_integer` and `String.to_float`
* [Kernel] Add `Dict.take`, `Dict.drop`, `Dict.split`, `Dict.pop` and `Dict.fetch!`
* [Kernel] Many optimizations for code compilation
* [Kernel] `in` can be used with right side expression outside guards
* [Kernel] Add `Node.get_cookie/0` and `Node.set_cookie/2`
* [Kernel] Add `__DIR__`
* [Kernel] Expand macros and attributes on quote, import, alias and require
* [Kernel] Improve warnings related to default arguments
* [Keyword] Add `Keyword.delete_first/2`
* [Mix] Add `local.rebar` to download a local copy of rebar, and change `deps.compile` to use it if needed
* [Mix] Support umbrella applications
* [Mix] Load beam files available at `MIX_PATH` on CLI usage
* [String] Add `String.valid?` and `String.valid_character?`
* bug fix
* [ExUnit] Handle exit messages from in ExUnit
* [ExUnit] Failures on ExUnit's setup_all now invalidates all tests
* [Kernel] Ensure we don't splice keyword args unecessarily
* [Kernel] Private functions used by private macros no longer emit an unused warning
* [Kernel] Ensure Elixir won't trip on empty receive blocks
* [Kernel] `String.slice` now returns an empty string when out of range by 1
* [Mix] Generate manifest files after compilation to avoid depending on directory timestamps and to remove unused .beam files
* [Path] `Path.expand/2` now correctly expands `~` in the second argument
* [Regex] Fix badmatch with `Regex.captures(%r/(.)/g, "cat")`
* [URI] Downcase host and scheme and URIs
* deprecations
* [Code] `Code.eval` is deprecated in favor of `Code.eval_string`
* [Exception] `Exception.format_entry` is deprecated in favor of `Exception.format_stacktrace_entry`
* [ExUnit] `assert left inlist right` is deprecated in favor of `assert left in right`
* [IO] `IO.getb` is deprecated in favor of `IO.getn`
* [List] `List.member?/2` is deprecated in favor of `Enum.member?/2`
* [Kernel] `var_context` in quote was deprecated in favor of `context`
* [Kernel] `Enum.at!` and `Dict.get!` is deprecated in favor of `Enum.fetch!` and `Dict.fetch!`
* backwards incompatible changes
* [Dict] `List.Dict` was moved to `ListDict`
* [IO] `IO.gets`, `IO.getn` and friends now return binaries when reading from stdio
* [Kernel] Precedence of `|>` has changed to lower to support constructs like `1..5 |> Enum.to_list`
* [Mix] `mix escriptize` now receives arguments as binaries
# v0.8.2 (2013-04-20)
* enhancements
@@ -73,7 +217,7 @@
* [Binary] Support syntax shortcut for specifying size in bit syntax
* [CLI] Support `--app` option to start an application and its dependencies
* [Dict] Support `put_new` in `Dict` and `Keyword`
* [Dict] Add `List.Dict` and a faster `HashDict` implementation
* [Dict] Add `ListDict` and a faster `HashDict` implementation
* [ExUnit] ExUnit now supports multiple runs in the same process
* [ExUnit] Failures in ExUnit now shows a tailored stacktrace
* [ExUnit] Introduce `ExUnit.ExpectationError` to provide better error messages
@@ -100,7 +244,7 @@
* [Kernel] The AST now allows metadata to be attached to each node. This means the second item in the AST is no longer an integer (representing the line), but a keywords list. Code that relies on the line information from AST or that manually generate AST nodes need to be properly updated
* deprecations
* [Dict] Deprecate `Binary.Dict` and `OrdDict` in favor of `HashDict` and `List.Dict`
* [Dict] Deprecate `Binary.Dict` and `OrdDict` in favor of `HashDict` and `ListDict`
* [File] Deprecate path related functions in favor of the module `Path`
* [Kernel] The `/>` operator has been deprecated in favor of `|>`
* [Mix] `Mix.Project.sources` is deprecated in favor of `Mix.Project.config_files`
@@ -129,7 +273,6 @@
* deprecations
* [Enum] `Enum.qsort` is deprecated in favor of `Enum.sort`
* [ExUnit] `assert left in right` is deprecated in favor of `assert left inlist right`
* [List] `List.sort` and `List.uniq` have been deprecated in favor of their `Enum` counterparts
* [Record] Default-based generated functions are deprecated
* [Typespec] Enhancements and deprecations to the `@spec/@callback` and the fun type syntax
+178
View File
@@ -0,0 +1,178 @@
# Contributing to Elixir
Please take a moment to review this document in order to make the contribution
process easy and effective for everyone involved!
## Using the issue tracker
The issue tracker is the preferred channel for [bug reports](#bugs-reports),
[features requests](#feature-requests) and [submitting pull
requests](#pull-requests), but please **do not** use the issue tracker for
personal support requests. Instead, use
[the mailing list](http://groups.google.com/group/elixir-lang-talk),
[Stack Overflow](http://stackoverflow.com/questions/ask?tags=elixir), or
[#elixir-lang](irc://chat.freenode.net/elixir-lang) on Freenode).
We do our best to keep the issues tracker tidy and organized, making it useful
for everyone. For example, we classify open issues per application and perceived
difficulty of the issue, making it easier for developers to
[contribute to Elixir](#contributing).
## Bug reports
A bug is a _demonstrable problem_ that is caused by the code in the repository.
Good bug reports are extremely helpful - thank you!
Guidelines for bug reports:
1. **Use the GitHub issue search** &mdash; check if the issue has already been
reported.
2. **Check if the issue has been fixed** &mdash; try to reproduce it using the
latest `master` or development branch in the repository.
3. **Isolate the problem** &mdash; ideally create a reduced test
case.
A good bug report shouldn't leave others needing to chase you up for more
information. Please try to be as detailed as possible in your report. What is
your environment? What steps will reproduce the issue? What version of Erlang
and Elixir experience the problem? What would you expect to be the outcome?
All these details will help people to fix any potential bugs.
Example:
> Short and descriptive example bug report title
>
> A summary of the issue and the environment in which it occurs. If suitable,
> include the steps required to reproduce the bug.
>
> 1. This is the first step
> 2. This is the second step
> 3. Further steps, etc.
>
> `<url>` - a link to the reduced test case (e.g. a GitHub Gist)
>
> Any other information you want to share that is relevant to the issue being
> reported. This might include the lines of code that you have identified as
> causing the bug, and potential solutions (and your opinions on their
> merits).
## Feature requests
Feature requests are welcome. But take a moment to find out whether your idea
fits with the scope and aims of the project. It's up to *you* to make a strong
case to convince the project's developers of the merits of this feature. Please
provide as much detail and context as possible.
## Contributing
We incentivate everyone to contribute to Elixir and help us tackle
existing issues! To do so, there are a few things you need to know
about the code. First, Elixir code is divided in applications inside
the `lib` folder:
* `elixir` - Contains Elixir's kernel and stdlib
* `eex` - Template engine that allows you to embed Elixir
* `ex_unit` - Simple test framework that ships with Elixir
* `iex` — IEx, Elixir's interactive shell
* `mix` — Elixir's build tool
You can run all tests in the root directory with `make test` and you can
also run tests for a specific framework `make test_#{NAME}`, for example,
`make test_ex_unit`.
With tests running and passing, you are ready to contribute to Elixir and
send your pull requests.
## Pull requests
Good pull requests - patches, improvements, new features - are a fantastic
help. They should remain focused in scope and avoid containing unrelated
commits.
**IMPORTANT**: By submitting a patch, you agree that your work 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**
otherwise you risk spending a lot of time working on something that the
project's developers might not want to merge into the project.
Please adhere to the coding conventions in the project (indentation,
accurate comments, etc.) and don't forget to add your own tests and
documentation. When working with git, we recommend the following process
in order to craft an excellent pull request:
1. [Fork](http://help.github.com/fork-a-repo/) the project, clone your fork,
and configure the remotes:
```bash
# Clone your fork of the repo into the current directory
git clone https://github.com/<your-username>/elixir
# Navigate to the newly cloned directory
cd elixir
# Assign the original repo to a remote called "upstream"
git remote add upstream https://github.com/elixir-lang/elixir
```
2. If you cloned a while ago, get the latest changes from upstream:
```bash
git checkout master
git pull upstream master
```
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>
```
4. Commit your changes in logical chunks. Keep your commit messages organized,
with a short description in the first line and more detailed information on
the following lines. Feel free to use Git's
[interactive rebase](https://help.github.com/articles/interactive-rebase)
feature to tidy up your commits before making them public.
5. Push your topic branch up to your fork:
```bash
git push origin <topic-branch-name>
```
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:
* https://github.com/elixir-lang/elixir/pull/992
* https://github.com/elixir-lang/elixir/pull/1041
* https://github.com/elixir-lang/elixir/pull/1058
* https://github.com/elixir-lang/elixir/pull/1059
Thank you for your contributions!
+20 -10
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 unicode lib/eex/ebin/Elixir-EEx.beam mix ex_unit eex iex
elixir: kernel lib/eex/ebin/Elixir.EEx.beam mix ex_unit eex iex
kernel: $(KERNEL) VERSION
$(KERNEL): lib/elixir/lib/*.ex lib/elixir/lib/*/*.ex
@@ -54,6 +54,7 @@ $(KERNEL): lib/elixir/lib/*.ex lib/elixir/lib/*/*.ex
fi
@ echo "==> kernel (compile)";
@ $(ELIXIRC) "lib/elixir/lib/**/*.ex" -o lib/elixir/ebin;
@ $(MAKE) unicode
@ rm -rf lib/elixir/ebin/elixir.app
@ cd lib/elixir && $(REBAR) compile
@@ -91,15 +92,24 @@ clean:
rm -rf lib/elixir/src/*_parser.erl
rm -rf lib/elixir/test/ebin
clean_exbeam:
@ rm -f lib/*/ebin/Elixir.*.beam
#==> Release tasks
docs: compile
SOURCE_REF = $(shell head="$$(git rev-parse HEAD)" tag="$$(git tag --points-at $$head | tail -1)" ; echo "$${tag:-$$head}\c")
docs: compile ../ex_doc/bin/ex_doc
mkdir -p ebin
rm -rf docs
cp -R -f lib/*/ebin/*.beam ./ebin
bin/elixir ../exdoc/bin/exdoc "Elixir" "$(VERSION)" -m Kernel -u "https://github.com/elixir-lang/elixir/blob/master/%{path}#L%{line}"
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
@@ -124,11 +134,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)"
+18 -19
View File
@@ -4,7 +4,7 @@
For more about Elixir, installation and documentation, [check Elixir's website](http://elixir-lang.org/).
# Usage
## Usage
If you want to contribute to Elixir or run it from source, clone this repository to your machine, compile and test it:
@@ -14,43 +14,42 @@ 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].
# Contributing
## Building documentation
If you want to contribute, Elixir code is divided in applications inside the `lib` folder:
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.
* `elixir` - Contains Elixir's kernel and stdlib;
# 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
* `eex` - Template engine that allows you to embed Elixir;
## Contributing
* `ex_unit` - Simple test framework that ships with Elixir;
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].
* `iex` — IEx, Elixir's interactive shell
* `mix` — Elixir's build tool
We usually keep a list of features and bugs [in the issue tracker][2].
# Important links
## Important links
* #elixir-lang on freenode IRC
* [Website][1]
* [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
## License
"Elixir" and the Elixir logo are copyright (c) 2012 Plataformatec.
Elixir source code is released under Apache 2 License with some parts under Erlang's license (EPL).
Check LEGAL and LICENSE files for more information.
Check LEGAL and LICENSE files for more information.
+2 -2
View File
@@ -2,7 +2,7 @@
This document simply outlines the release process:
1) Remove `.dev` extension from VERSION
1) Remove `+dev` extension from VERSION
2) Run `make clean test` to ensure all tests pass from scratch and the CI is green
@@ -18,7 +18,7 @@ This document simply outlines the release process:
8) Push package to expm with `expm publish package.exs`
9) After release, bump versions and add `.dev` back
9) After release, bump versions and add `+dev` back
## Places where version is mentioned
+1 -1
View File
@@ -1 +1 @@
0.8.2
0.9.3
+23 -7
View File
@@ -2,7 +2,7 @@
if [ $# -eq 0 ] || [ $1 = "--help" ] || [ $1 = "-h" ]; then
echo "Usage: `basename $0` [options] [.exs file] [data]
-v Prints version
-v Prints version and exit
-e \"command\" Evaluates the given command (*)
-r \"file\" Requires the given files/patterns (*)
-S \"script\"   Finds and executes the given script (*)
@@ -13,6 +13,8 @@ if [ $# -eq 0 ] || [ $1 = "--help" ] || [ $1 = "-h" ]; then
--erl \"switches\" Switches to be passed down to erlang (*)
--name \"name\" Makes and assigns a name to the distributed node
--sname \"name\" Makes and assigns a short name to the distributed node
--cookie \"cookie\" Sets a cookie for this distributed node
--hidden Makes a hidden node
--detached Starts the Erlang VM detached from console
--no-halt Does not halt the Erlang VM after execution
@@ -24,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
@@ -39,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|-S)
-e|-r|-pr|-pa|-pz|--remsh)
S=2
;;
--detached)
--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}"
@@ -67,12 +74,21 @@ done
SELF=$(readlink_f "$0")
SCRIPT_PATH=$(dirname "$SELF")
if [ "$ERL_LIBS" = "" ]
then
ERL_LIBS="$SCRIPT_PATH/../lib"
else
PATHSEP=":"
if [ "$OSTYPE" = "msys" ]; then PATHSEP=";" ; fi
ERL_LIBS="$ERL_LIBS$PATHSEP$SCRIPT_PATH/../lib"
fi
if [ -f "$HOME/.elixirrc" ]; then . "$HOME/.elixirrc"; fi
if [ "$ELIXIR_NO_CLI" != "1" ]; then ERL="$ERL -s elixir start_cli"; fi
if [ -f "$SCRIPT_PATH/../releases/RELEASES" ] && [ -f "$SCRIPT_PATH/erl" ]
then
exec "$SCRIPT_PATH"/erl -env ERL_LIBS $ERL_LIBS:"$SCRIPT_PATH/../lib" -boot elixir -noshell $ELIXIR_ERL_OPTS $ERL -extra "$@"
exec "$SCRIPT_PATH"/erl -env ERL_LIBS "$ERL_LIBS" -boot elixir -noshell $ELIXIR_ERL_OPTS $ERL -extra "$@"
else
exec erl -env ERL_LIBS $ERL_LIBS:"$SCRIPT_PATH/../lib" -noshell $ELIXIR_ERL_OPTS $ERL -extra "$@"
exec erl -env ERL_LIBS "$ERL_LIBS" -noshell $ELIXIR_ERL_OPTS $ERL -extra "$@"
fi
+102 -12
View File
@@ -1,25 +1,115 @@
@echo off
SETLOCAL enabledelayedexpansion
set argc=0
for %%x in (%*) do set /A argc+=1
if %argc% == 0 (
if %argc%== 0 (
goto documentation
) else (
goto run
goto parseopts
)
:documentation
echo Usage: %~nx0 [options] [.exs file] [data]
echo.
echo -v Prints version and exit
echo -e command Evaluates the given command (*)
echo -r command Requires the given file/pattern (*)
echo -pr command Requires the given file/pattern in parallel (*)
echo -pa path Prepend the given path to Erlang code path (*)
echo -pz path Append the given path to Erlang code path (*)
echo --app app Start the given app and its dependencies (*)
echo --no-halt Do not halt the Erlang VM after execution
echo -v Prints version and exit
echo -e command Evaluates the given command (*)
echo -r file Requires the given files/patterns (*)
echo -S script   Finds and executes the given script (*)
echo -pr file Requires the given files/patterns in parallel (*)
echo -pa path Prepends the given path to Erlang code path (*)
echo -pz path Appends the given path to Erlang code path (*)
echo --app app Start the given app and its dependencies (*)
echo --erl switches Switches to be passed down to erlang (*)
echo --name name Makes and assigns a name to the distributed node
echo --sname name Makes and assigns a short name to the distributed node
echo --cookie cookie Sets a cookie for this distributed node
echo --hidden Makes a hidden node
echo --detached Starts the Erlang VM detached from console
echo --no-halt Does not halt the Erlang VM after execution
echo.
echo ** Options marked with (*) can be given more than once
echo ** Options given after the .exs file or -- are passed down to the executed code
echo ** Options can be passed to the erlang runtime using ELIXIR_ERL_OPTS
echo ** Options can be passed to the erlang runtime using ELIXIR_ERL_OPTS or --erl
goto :EOF
:parseopts
rem Parameters for Erlang
set parsErlang=
rem Make sure we keep a copy of all parameters
set allPars=%*
rem Get the original path name from the batch file
set originPath=%~dp0
rem Optional parameters before the "-extra" parameter
set beforeExtra=
rem Recursive loop called for each parameter that parses the cmd line parameters
:startloop
set par=%1
shift
if "%par%"=="" (
rem if no parameters defined
goto :run
)
rem ******* ERLANG PARAMETERS **********************
for /f "usebackq" %%m in (`echo %par%^|findstr \--detached`) do (
set parsErlang=%parsErlang% -detached
goto:startloop
)
for /f "usebackq" %%m in (`echo %par%^|findstr \--hidden`) do (
set parsErlang=%parsErlang% -hidden
goto:startloop
)
for /f "usebackq" %%m in (`echo %par%^|findstr \--cookie`) do (
set parsErlang=%parsErlang% -setcookie %1
shift
goto:startloop
)
for /f "usebackq" %%m in (`echo %par%^|findstr \--sname`) do (
set parsErlang=%parsErlang% -sname %1
shift
goto:startloop
)
for /f "usebackq" %%m in (`echo %par%^|findstr \--name`) do (
set parsErlang=%parsErlang% -name %1
shift
goto:startloop
)
for /f "usebackq" %%m in (`echo %par%^|findstr \--erl`) do (
set beforeExtra=%beforeExtra% %~1
shift
goto:startloop
)
rem ******* elixir parameters **********************
for /f "usebackq" %%m in (`echo %par%^|findstr \--v`) do (
goto:startloop
)
for /f "usebackq" %%m in (`echo %par%^|findstr \--compile`) do (
goto:startloop
)
for /f "usebackq" %%m in (`echo %par%^|findstr \--no-halt`) do (
goto:startloop
)
for /f "usebackq" %%m in (`echo %par%^|findstr \+iex`) do (
goto:startloop
)
for /f "usebackq" %%m in (`echo %par%^|findstr \+compile`) do (
goto:startloop
)
for /f "usebackq" %%m in (`echo %par%^|findstr \-[er]`) do (
shift
goto:startloop
)
for /f "usebackq" %%m in (`echo %par%^|findstr \-p[raz]`) do (
shift
goto:startloop
)
for /f "usebackq" %%m in (`echo %par%^|findstr \--remsh`) do (
shift
goto:startloop
)
rem ******* assume all pre-params are parsed ********************
:run
erl -env ERL_LIBS %ERL_LIBS%;"%~dp0\..\lib" -noshell %ELIXIR_ERL_OPTS% -s elixir start_cli -extra %*
erl -env ERL_LIBS %ERL_LIBS%;"%originPath%\..\lib" -noshell %ELIXIR_ERL_OPTS% %parsErlang% -s elixir start_cli %beforeExtra% -extra %*
+3 -2
View File
@@ -6,6 +6,7 @@ if [ $# -eq 0 ]; then
--no-docs Do not attach documentation to compiled modules
--no-debug-info Do not attach debug info to compiled modules
--ignore-module-conflict
--warnings-as-errors Treat warnings as errors and return non-zero exit code
** Options given after -- are passed down to the executed code
** Options can be passed to the erlang runtime using ELIXIR_ERL_OPTS" >&2
@@ -14,7 +15,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
@@ -24,4 +25,4 @@ readlink_f () {
SELF=$(readlink_f "$0")
SCRIPT_PATH=$(dirname "$SELF")
exec "$SCRIPT_PATH"/elixir --compile "$@"
exec "$SCRIPT_PATH"/elixir +compile "$@"
+2 -1
View File
@@ -13,9 +13,10 @@ echo -o The directory to output compiled files
echo --no-docs Do not attach documentation to compiled modules
echo --no-debug-info Do not attach debug info to compiled modules
echo --ignore-module-conflict
echo --warnings-as-errors Treat warnings as errors and return non-zero exit code
echo.
echo ** Options marked with (*) can be given more than once
echo ** Options given after -- are passed down to the executed code
echo ** Options can be passed to the erlang runtime using ELIXIR_ERL_OPTS" >&2
:run
call "%~dp0\elixir.bat" --compile %*
call "%~dp0\elixir.bat" +compile %*
+7 -2
View File
@@ -13,7 +13,12 @@ if [ $# -gt 0 ] && ([ $1 = "--help" ] || [ $1 = "-h" ]); then
--erl \"switches\" Switches to be passed down to erlang (*)
--name \"name\" Makes and assigns a name to the distributed node
--sname \"name\" Makes and assigns a short name to the distributed node
--cookie \"cookie\" Sets a cookie for this distributed node
--hidden Makes a hidden node
--detached Starts the Erlang VM detached from console
--remsh \"name\" Connects to a node using a remote shell
--dot-iex \"path\" Overrides default .iex file and uses path instead;
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 +28,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 +38,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" "$@"
ELIXIR_NO_CLI=1 exec "$SCRIPT_PATH"/elixir --no-halt --erl "-user Elixir.IEx.CLI" +iex "$@"
+1 -1
View File
@@ -1,2 +1,2 @@
@echo off
call "%~dp0\elixir.bat" --no-halt -e "IEx.start" %*
call "%~dp0\elixir.bat" +iex --no-halt -e "IEx.start" %*
+1 -1
View File
@@ -75,7 +75,7 @@ defmodule EEx.Compiler do
# Check if the syntax node represents an empty string
defp is_empty?(bin) when is_binary(bin) do
bc(<<c>> inbits bin, not c in [?\s,?\t,?\r,?\n], do: <<c>>) == ""
bc(<<c>> inbits bin, not c in [?\s, ?\t, ?\r, ?\n], do: <<c>>) == ""
end
defp is_empty?({ :<>, _, [left, right] }) do
+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
+5 -5
View File
@@ -12,7 +12,7 @@ defmodule EEx.Tokenizer do
"""
def tokenize(bin, line) when is_binary(bin) do
tokenize(binary_to_list(bin), line)
tokenize(:unicode.characters_to_list(bin), line)
end
def tokenize(list, line) do
@@ -20,8 +20,8 @@ defmodule EEx.Tokenizer do
end
defp tokenize('<%%' ++ t, current_line, line, buffer, acc) do
{ buffer, new_line, rest } = tokenize_expr t, line, [?%,?<|buffer]
tokenize rest, current_line, new_line, [?>,?%|buffer], acc
{ buffer, new_line, rest } = tokenize_expr t, line, [?%, ?<|buffer]
tokenize rest, current_line, new_line, [?>, ?%|buffer], acc
end
defp tokenize('<%#' ++ t, current_line, line, buffer, acc) do
@@ -63,7 +63,7 @@ defmodule EEx.Tokenizer do
# Tokenize an expression until we find %>
defp tokenize_expr([?%,?>|t], line, buffer) do
defp tokenize_expr([?%, ?>|t], line, buffer) do
{ buffer, line, t }
end
@@ -154,6 +154,6 @@ defmodule EEx.Tokenizer do
end
defp tokenize_text(line, buffer, acc) do
[{ :text, line, list_to_binary(Enum.reverse(buffer)) } | acc]
[{ :text, line, :unicode.characters_to_binary(Enum.reverse(buffer)) } | acc]
end
end
+2 -2
View File
@@ -1,4 +1,4 @@
Code.require_file "../../test_helper.exs", __FILE__
Code.require_file "../test_helper.exs", __DIR__
defmodule EEx.SmartEngineTest do
use ExUnit.Case, async: true
@@ -12,7 +12,7 @@ defmodule EEx.SmartEngineTest do
end
test "evaluates with loops" do
assert_eval "1\n2\n3\n", "<%= lc x inlist [1,2,3] do %><%= x %>\n<% end %>"
assert_eval "1\n2\n3\n", "<%= lc x inlist [1, 2, 3] do %><%= x %>\n<% end %>"
end
defp assert_eval(expected, actual, binding // []) do
+2 -2
View File
@@ -1,4 +1,4 @@
Code.require_file "../../test_helper.exs", __FILE__
Code.require_file "../test_helper.exs", __DIR__
defmodule EEx.TokenizerTest do
use ExUnit.Case, async: true
@@ -21,7 +21,7 @@ defmodule EEx.TokenizerTest do
end
test "strings with more than one line" do
assert T.tokenize('foo\n<%= bar %>', 1) == [ { :text, 1, "foo\n" },{ :expr, 2, '=', ' bar ' } ]
assert T.tokenize('foo\n<%= bar %>', 1) == [ { :text, 1, "foo\n" }, { :expr, 2, '=', ' bar ' } ]
end
test "strings with more than one line and expression with more than one line" do
+15 -6
View File
@@ -1,4 +1,4 @@
Code.require_file "../test_helper.exs", __FILE__
Code.require_file "test_helper.exs", __DIR__
require EEx
@@ -10,7 +10,7 @@ defmodule EExText.Compiled do
{ :erlang, 1, 2 }.tuple_to_list
EEx.function_from_string :def, :string_sample, "<%= a + b %>", [:a, :b]
filename = Path.expand("../fixtures/eex_template_with_bindings.eex", __FILE__)
filename = Path.join(__DIR__, "fixtures/eex_template_with_bindings.eex")
EEx.function_from_file :defp, :private_file_sample, filename, [:bar]
def file_sample(arg), do: private_file_sample(arg)
@@ -101,7 +101,7 @@ defmodule EExTest do
end
test "evaluates with require code" do
assert_eval "foo 1,2,3", "foo <% require Enum, as: E %><%= E.join [1,2,3], \",\" %>"
assert_eval "foo 1,2,3", "foo <% require Enum, as: E %><%= E.join [1, 2, 3], \",\" %>"
end
test "evaluates with end of token" do
@@ -242,7 +242,7 @@ Number 3
"""
string = """
<%= Enum.map [1,2,3], fn x -> %>
<%= Enum.map [1, 2, 3], fn x -> %>
Number <%= x %>
<% end %>
"""
@@ -270,14 +270,23 @@ foo
assert_eval expected, string
end
test "unicode" do
template = """
• <%= "•" %> •
<%= "Jößé Vâlìm" %> Jößé Vâlìm
"""
result = EEx.eval_string(template)
assert result == " • • •\n Jößé Vâlìm Jößé Vâlìm\n"
end
test "evaluates the source from a given file" do
filename = Path.expand("../fixtures/eex_template.eex", __FILE__)
filename = Path.join(__DIR__, "fixtures/eex_template.eex")
result = EEx.eval_file(filename)
assert result == "foo bar.\n"
end
test "evaluates the source from a given file with bindings" do
filename = Path.expand("../fixtures/eex_template_with_bindings.eex", __FILE__)
filename = Path.join(__DIR__, "fixtures/eex_template_with_bindings.eex")
result = EEx.eval_file(filename, [bar: 1])
assert result == "foo 1\n"
end
+29 -9
View File
@@ -11,19 +11,18 @@
-record(elixir_scope, {
context=nil, %% can be assign, guards or nil
noname=false, %% when true, don't add new names (used by try)
check_clauses=true, %% when true, check def clauses ordering
super=false, %% when true, it means super was invoked
caller=false, %% when true, it means caller was invoked
name_args=false, %% when true, it means arguments should be named
module=nil, %% the current module
function=nil, %% the current function
vars=[], %% a dict of defined variables and their alias
temp_vars=[], %% a dict of all variables defined in a particular assign
backup_vars=nil, %% a copy of vars to be used on ^var
temp_vars=nil, %% a set of all variables defined in a particular assign
clause_vars=nil, %% a dict of all variables defined in a particular clause
extra_guards=nil, %% extra guards from args expansion
counter=[], %% a counter for the variables defined
local=nil, %% the scope to evaluate local functions against
scheduled=[], %% scheduled modules to be loaded
context_modules=[], %% modules defined in the current context
macro_aliases=[], %% keep aliases defined inside a macro
aliases, %% an orddict with aliases by new -> old names
file, %% the current scope filename
@@ -36,21 +35,42 @@
-record(elixir_quote, {
line=0,
vars_hygiene=nil,
context=nil,
vars_hygiene=true,
aliases_hygiene=true,
imports_hygiene=true,
unquote=true
unquote=true,
unquoted=false,
mark=true
}).
%% Introspection
-define(defs(Kind), Kind == def; Kind == defp; Kind == defmacro; Kind == defmacrop).
%% Used in tokenization and interpolation
-define(is_digit(S), S >= $0 andalso S =< $9).
%% Numbers
-define(is_hex(S), ?is_digit(S) orelse (S >= $A andalso S =< $F) orelse (S >= $a andalso S =< $f)).
-define(is_bin(S), S >= $0 andalso S =< $1).
-define(is_octal(S), S >= $0 andalso S =< $7).
-define(is_leading_octal(S), S >= $0 andalso S =< $3).
%% Digits and letters
-define(is_digit(S), S >= $0 andalso S =< $9).
-define(is_upcase(S), S >= $A andalso S =< $Z).
-define(is_downcase(S), S >= $a andalso S =< $z).
-define(is_word(S), ?is_digit(S) orelse ?is_upcase(S) orelse ?is_downcase(S)).
%% Atoms
-define(is_new_atom(S), ?is_quote(S) orelse ?is_upcase(S) orelse ?is_downcase(S) orelse (S == $_)).
-define(is_atom(S), ?is_identifier(S) orelse (S == $@)).
-define(is_identifier(S), ?is_digit(S) orelse ?is_upcase(S) orelse ?is_downcase(S) orelse (S == $_)).
-define(is_terminator(S), (S == $?) orelse (S == $!) orelse (S == $:)).
%% Quotes
-define(is_quote(S), S == $" orelse S == $').
-define(is_space(S), S == $\s; S == $\r; S == $\t; S == $\n).
%% Spaces
-define(is_horizontal_space(S), (S == $\s) orelse (S == $\t)).
-define(is_vertical_space(S), (S == $\r) orelse (S == $\n)).
-define(is_space(S), ?is_horizontal_space(S) orelse ?is_vertical_space(S)).
+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
+2 -2
View File
@@ -57,7 +57,7 @@ defmodule Behaviour do
case Macro.extract_args(fun) do
{ name, args } -> :ok
:error ->
raise ArgumentError, message: "invalid syntax in defcallback #{Macro.to_binary(fun)}"
raise ArgumentError, message: "invalid syntax in defcallback #{Macro.to_string(fun)}"
end
arity = length(args)
@@ -113,4 +113,4 @@ defmodule Behaviour do
end
end
end
end
end
+22 -6
View File
@@ -51,14 +51,22 @@ defimpl Binary.Chars, for: List do
## Examples
iex> to_binary 'foo'
iex> to_binary('foo')
"foo"
iex> to_binary ["foo", 'bar']
iex> to_binary(["foo", 'bar'])
"foobar"
"""
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
@@ -66,11 +74,19 @@ defimpl Binary.Chars, for: Number do
@doc """
Simply converts the number (integer or a float) to a binary.
"""
@digits 20
@limit :math.pow(10, @digits)
def to_binary(thing) when is_integer(thing) do
list_to_binary integer_to_list(thing)
integer_to_binary(thing)
end
def to_binary(thing) when thing > @limit do
float_to_binary(thing, scientific: @digits)
end
def to_binary(thing) do
list_to_binary float_to_list(thing)
float_to_binary(thing, compact: true, decimals: @digits)
end
end
end
+33 -34
View File
@@ -11,8 +11,6 @@ defprotocol Binary.Inspect do
printing.
"""
@only [BitString, List, Tuple, Atom, Number, Function, PID, Port, Reference]
def inspect(thing, opts)
end
@@ -34,15 +32,15 @@ defmodule Binary.Inspect.Utils do
end
defp do_container_join([h], opts, _counter) do
Binary.Inspect.inspect(h, opts)
Kernel.inspect(h, opts)
end
defp do_container_join([h|t], opts, counter) when is_list(t) do
Binary.Inspect.inspect(h, opts) <> "," <> do_container_join(t, opts, decrement(counter))
Kernel.inspect(h, opts) <> "," <> do_container_join(t, opts, decrement(counter))
end
defp do_container_join([h|t], opts, _counter) do
Binary.Inspect.inspect(h, opts) <> "|" <> Binary.Inspect.inspect(t, opts)
Kernel.inspect(h, opts) <> "|" <> Kernel.inspect(t, opts)
end
defp do_container_join([], _opts, _counter) do
@@ -146,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)
@@ -154,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
@@ -276,7 +274,7 @@ defimpl Binary.Inspect, for: List do
cond do
:io_lib.printable_list(thing) ->
escape(:unicode.characters_to_binary(thing), ?')
Keyword.keyword?(thing) ->
keyword?(thing) ->
"[" <> join_keywords(thing, opts) <> "]"
true ->
container_join(thing, "[", "]", opts)
@@ -285,7 +283,7 @@ defimpl Binary.Inspect, for: List do
defp join_keywords(thing, opts) do
Enum.join(lc {key, value} inlist thing do
key_to_binary(key, opts) <> ": " <> Binary.Inspect.inspect(value, opts)
key_to_binary(key, opts) <> ": " <> Kernel.inspect(value, opts)
end, ", ")
end
@@ -295,6 +293,16 @@ defimpl Binary.Inspect, for: List do
other -> other
end
end
defp keyword?([{ key, _value } | rest]) when is_atom(key) do
case atom_to_list(key) do
'Elixir.' ++ _ -> false
_ -> keyword?(rest)
end
end
defp keyword?([]), do: true
defp keyword?(_other), do: false
end
defimpl Binary.Inspect, for: Tuple do
@@ -306,7 +314,7 @@ defimpl Binary.Inspect, for: Tuple do
## Examples
iex> inspect({1,2,3})
iex> inspect({1, 2, 3})
"{1,2,3}"
iex> inspect(ArgumentError.new)
"ArgumentError[message: \\\"argument error\\\"]"
@@ -317,33 +325,17 @@ defimpl Binary.Inspect, for: Tuple do
def inspect(tuple, opts) do
unless opts[:raw] do
record_protocol(tuple, opts)
record_inspect(tuple, opts)
end || container_join(tuple, "{", "}", opts)
end
## Helpers
defp record_protocol(tuple, opts) do
name = elem(tuple, 0)
if is_atom(name) and match?("Elixir-" <> _, atom_to_binary(name)) do
unless name in [BitString, List, Tuple, Atom, Number, Any] do
try do
target = Module.concat(Binary.Inspect, name)
target.inspect(tuple, opts)
rescue
UndefinedFunctionError ->
record_inspect(tuple, opts)
end
end
end
end
defp record_inspect(record, opts) do
list = tuple_to_list(record)
[name|tail] = list
if (fields = record_fields(name)) && (length(fields) == size(record) - 1) do
if is_atom(name) && (fields = record_fields(name)) && (length(fields) == size(record) - 1) do
if Enum.first(tail) == :__exception__ do
record_join(name, tl(fields), tl(tail), opts)
else
@@ -367,12 +359,12 @@ defimpl Binary.Inspect, for: Tuple do
end
defp record_join([f], [v], opts) do
atom_to_binary(f, :utf8) <> ": " <> Binary.Inspect.inspect(v, opts)
atom_to_binary(f, :utf8) <> ": " <> Kernel.inspect(v, opts)
end
defp record_join([fh|ft], [vh|vt], opts) do
atom_to_binary(fh, :utf8) <> ": " <>
Binary.Inspect.inspect(vh, opts) <> ", " <>
Kernel.inspect(vh, opts) <> ", " <>
record_join(ft, vt, opts)
end
@@ -392,12 +384,19 @@ defimpl Binary.Inspect, for: Number do
"""
@digits 20
@limit :math.pow(10, @digits)
def inspect(thing, _) when is_integer(thing) do
list_to_binary integer_to_list(thing)
integer_to_binary(thing)
end
def inspect(thing, _) when thing > @limit do
float_to_binary(thing, scientific: @digits)
end
def inspect(thing, _) do
list_to_binary :io_lib.format("~p", [thing])
float_to_binary(thing, compact: true, decimals: @digits)
end
end
@@ -413,11 +412,11 @@ defimpl Binary.Inspect, for: Regex do
"""
def inspect(regex, _opts) when size(regex) == 5 do
"%r" <> Binary.Inspect.inspect(Regex.source(regex), []) <> Regex.opts(regex)
"%r" <> Kernel.inspect(Regex.source(regex), []) <> Regex.opts(regex)
end
def inspect(other, opts) do
Binary.Inspect.inspect other, Keyword.put(opts, :raw, true)
Kernel.inspect other, Keyword.put(opts, :raw, true)
end
end
+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
+120 -50
View File
@@ -58,7 +58,7 @@ defmodule Code do
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:
@@ -72,11 +72,13 @@ defmodule Code do
* `:aliases` - a list of tuples with the alias and its target
* `:requires` - a list of modules required
* `:functions` - a list of tuples where the first element is a module
and the second a list of imported function names and arity
and the second a list of imported function names and arity. The list
of function names and arity must be sorted;
* `:macros` - a list of tuples where the first element is a module
and the second a list of imported macro names and arity
and the second a list of imported macro names and arity. The list
of function names and arity must be sorted;
Notice that setting any ov the values above overrides Elixir default
Notice that setting any of the values above overrides Elixir default
values. For example, setting `:requires` to `[]`, will no longer
automatically required the `Kernel` module, in the same way setting
`:macros` will no longer auto-import `Kernel` macros as `if`, `case`,
@@ -84,63 +86,116 @@ defmodule Code do
## Examples
iex> Code.eval "a + b", [a: 1, b: 2], file: __ENV__.file, line: __ENV__.line
iex> Code.eval_string("a + b", [a: 1, b: 2], file: __ENV__.file, line: __ENV__.line)
{ 3, [ {:a, 1}, {:b, 2} ] }
For convenience, you can my pass `__ENV__` as argument and
all imports, requires and aliases will be automatically carried
over:
iex> Code.eval "a + b", [a: 1, b: 2], __ENV__
iex> Code.eval_string("a + b", [a: 1, b: 2], __ENV__)
{ 3, [ {:a, 1}, {:b, 2} ] }
"""
def eval(string, binding // [], opts // [])
def eval_string(string, binding // [], opts // [])
def eval(string, binding, Macro.Env[] = env) do
eval(string, binding, env.to_keywords)
def eval_string(string, binding, Macro.Env[] = env) do
do_eval_string(string, binding, env.to_keywords)
end
def eval(string, binding, opts) do
def eval_string(string, binding, opts) when is_list(opts) do
validate_eval_opts(opts)
do_eval_string(string, binding, opts)
end
defp do_eval_string(string, binding, opts) when is_list(binding) do
{ value, binding, _scope } =
:elixir.eval :unicode.characters_to_list(string), binding, opts
{ value, binding }
end
@doc """
Evalutes the quoted contents.
Evaluates the quoted contents.
This function accepts a list of environment options.
Check `Code.eval` for more information.
Check `Code.eval_string` for more information.
## Examples
iex> contents = quote hygiene: [vars: false], do: a + b
...> Code.eval_quoted contents, [a: 1, b: 2], file: __ENV__.file, line: __ENV__.line
iex> contents = quote(hygiene: [vars: false], do: a + b)
...> Code.eval_quoted(contents, [a: 1, b: 2], file: __ENV__.file, line: __ENV__.line)
{ 3, [ {:a, 1}, {:b, 2} ] }
For convenience, you can my pass `__ENV__` as argument and
all options will be automatically extracted from the environment:
iex> contents = quote hygiene: [vars: false], do: a + b
...> Code.eval_quoted contents, [a: 1, b: 2], __ENV__
iex> contents = quote(hygiene: [vars: false], do: a + b)
...> Code.eval_quoted(contents, [a: 1, b: 2], __ENV__)
{ 3, [ {:a, 1}, {:b, 2} ] }
"""
def eval_quoted(quoted, binding // [], opts // [])
def eval_quoted(quoted, binding, Macro.Env[] = env) do
eval_quoted(quoted, binding, env.to_keywords)
do_eval_quoted(quoted, binding, env.to_keywords)
end
def eval_quoted(quoted, binding, opts) do
def eval_quoted(quoted, binding, opts) when is_list(opts) do
validate_eval_opts(opts)
do_eval_quoted(quoted, binding, opts)
end
defp do_eval_quoted(quoted, binding, opts) when is_list(binding) do
{ value, binding, _scope } =
:elixir.eval_quoted [quoted], binding, opts
{ value, binding }
end
defp validate_eval_opts(opts) do
if f = opts[:functions], do: validate_imports(:functions, f)
if m = opts[:macros], do: validate_imports(:macros, m)
if a = opts[:aliases], do: validate_aliases(:aliases, a)
if r = opts[:requires], do: validate_requires(:requires, r)
end
defp validate_requires(kind, requires) do
valid = is_list(requires) and Enum.all?(requires, is_atom(&1))
unless valid do
raise ArgumentError, "expected :#{kind} option given to eval in the format: [module]"
end
end
defp validate_aliases(kind, aliases) do
valid = is_list(aliases) and Enum.all?(aliases, fn { k, v } ->
is_atom(k) and is_atom(v)
end)
unless valid do
raise ArgumentError, "expected :#{kind} option given to eval in the format: [{ module, module }]"
end
end
defp validate_imports(kind, imports) do
valid = is_list(imports) and Enum.all?(imports, fn { k, v } ->
is_atom(k) and is_list(v) and Enum.all?(v, fn { name, arity } ->
is_atom(name) and is_integer(arity)
end)
end)
unless valid do
raise ArgumentError, "expected :#{kind} option given to eval in the format: [{ module, [{ name, arity }] }]"
end
end
@doc false
def string_to_ast(string, opts // []) do
IO.write "[WARNING] Code.string_to_ast is deprecated, please use Code.string_to_quoted instead\n#{Exception.format_stacktrace}"
string_to_quoted(string, opts)
end
@doc """
Converts the given string to AST. It returns `{ :ok, ast }`
Converts the given string to quoted form. It returns `{ :ok, quoted_form }`
if it succeeds, `{ :error, { line, error, token } }` otherwise.
## Options
@@ -153,42 +208,53 @@ defmodule Code do
* `:existing_atoms_only` - When true, raises an error
when non-existing atoms are found by the tokenizer.
## Macro.to_string/1
The opposite of converting a string to its quoted form is
`Macro.to_string`, which converts a quoted form to a string/binary
representation.
"""
def string_to_ast(string, opts // []) do
def string_to_quoted(string, opts // []) do
file = Keyword.get opts, :file, "nofile"
line = Keyword.get opts, :line, 1
res = :elixir_translator.forms(:unicode.characters_to_list(string), line, file, opts)
case res do
{ :ok, ast } -> { :ok, unpack_ast(line, ast) }
{ :ok, forms } -> { :ok, unpack_quote(line, forms) }
_ -> res
end
end
@doc false
def string_to_ast!(string, opts // []) do
IO.write "[WARNING] Code.string_to_ast! is deprecated, please use Code.string_to_quoted! instead\n#{Exception.format_stacktrace}"
string_to_quoted!(string, opts)
end
@doc """
Converts the given string to AST. It returns the ast if it succeeds,
Converts the given string to quoted form. It returns the ast if it succeeds,
raises an exception otherwise. The exception is a TokenMissingError
in case a token is missing (usually because the expression is incomplete),
SyntaxError otherwise.
Check `Code.string_to_ast/2` for options information.
Check `Code.string_to_quoted/2` for options information.
"""
def string_to_ast!(string, opts // []) do
def string_to_quoted!(string, opts // []) do
file = Keyword.get opts, :file, "nofile"
line = Keyword.get opts, :line, 1
res = :elixir_translator.forms!(:unicode.characters_to_list(string), line, file, opts)
unpack_ast(line, res)
unpack_quote(line, res)
end
defp unpack_ast(_line, []), do: nil
defp unpack_ast(_line, [forms]) when not is_list(forms), do: forms
defp unpack_ast(line, forms), do: { :__block__, [line: line], forms }
defp unpack_quote(_line, []), do: nil
defp unpack_quote(_line, [forms]) when not is_list(forms), do: forms
defp unpack_quote(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
@@ -205,17 +271,17 @@ defmodule Code do
end
@doc """
Requires the given `file`. Accepts `relative_to` as an argument
to tell where the file is located. If the file was already
required/loaded, loads it again. It returns all the modules
defined in the file.
Requires the given `file`. Accepts `relative_to` as an argument to tell where
the file is located. 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 ones will block until the file is
available. I.e. if `require_file` is called N times with a given
file, the given file will be loaded only once. Check `load_file`
if you want a file to be loaded concurrently.
Notice that if `require_file` is invoked by different processes concurrently,
the first process to invoke `require_file` acquires a lock and the remaining
ones will block until the file is available. I.e. if `require_file` is called
N times with a given file, it will be loaded only once. The first process to
call `require_file` will get the list of loaded modules, others will get nil.
Check `load_file` if you want a file to be loaded concurrently.
"""
def require_file(file, relative_to // nil) when is_binary(file) do
file = find_file(file, relative_to)
@@ -234,7 +300,7 @@ defmodule Code do
@doc """
Loads the compilation options from the code server.
Check compiler_options/1 for more information.
Check `compiler_options/1` for more information.
"""
def compiler_options do
:elixir_code_server.call :compiler_options
@@ -246,13 +312,17 @@ defmodule Code do
Available options are:
* docs - when true, retain documentation in the compiled module.
True by default;
* debug_info - when true, retain debug information in the compiled module.
This allows a developer to reconstruct the original source
code, for such reasons, false by default;
* ignore_module_conflict - when true, override modules that were already defined
without raising errors, false by default;
* `:docs` - when true, retain documentation in the compiled module.
True by default;
* `:debug_info` - when true, retain debug information in the compiled module.
This allows a developer to reconstruct the original source
code, for such reasons, false by default;
* `:ignore_module_conflict` - when true, override modules that were already defined
without raising errors, false by default;
* `:warnings_as_errors` - cause compilation to fail when warnings are spewed;
"""
def compiler_options(opts) do
+212 -55
View File
@@ -25,15 +25,15 @@ defmodule Dict do
protocol:
iex> dict = HashDict.new
...> dict = Dict.put dict, :hello, :world
...> dict = Dict.put(dict, :hello, :world)
...> 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 } ->
Enum.each(dict, fn ({ k, v }) ->
IO.puts "#{k}: #{v}"
end
end)
"""
@@ -41,20 +41,26 @@ defmodule Dict do
@type key :: any
@type value :: any
@type keys :: [ key ]
@type t :: tuple | list
defcallback delete(t, key) :: t
defcallback drop(t, keys) :: t
defcallback empty(t) :: t
defcallback equal?(t, t) :: boolean
defcallback get(t, key) :: value
defcallback get(t, key, value) :: value
defcallback get!(t, key) :: value | no_return
defcallback has_key?(t, key) :: boolean
defcallback keys(t) :: list(key)
defcallback merge(t, t) :: t
defcallback merge(t, t, (key, value, value -> value)) :: t
defcallback pop(t, key) :: {value, t}
defcallback pop(t, key, value) :: {value, t}
defcallback put(t, key, value) :: t
defcallback put_new(t, key, value) :: t
defcallback size(t) :: non_neg_integer()
defcallback split(t, keys) :: {t, t}
defcallback take(t, keys) :: t
defcallback to_list(t) :: list()
defcallback update(t, key, (value -> value)) :: t | no_return
defcallback update(t, key, value, (value -> value)) :: t
@@ -66,7 +72,7 @@ defmodule Dict do
is_tuple(unquote(dict)) ->
elem(unquote(dict), 0)
is_list(unquote(dict)) ->
List.Dict
ListDict
end
end
end
@@ -78,8 +84,8 @@ defmodule Dict do
## Examples
iex> d = HashDict.new [a: 1, b: 2]
...> Enum.sort Dict.keys d
iex> d = HashDict.new([a: 1, b: 2])
...> Enum.sort(Dict.keys(d))
[:a,:b]
"""
@@ -93,8 +99,8 @@ defmodule Dict do
## Examples
iex> d = HashDict.new [a: 1, b: 2]
...> Enum.sort Dict.values d
iex> d = HashDict.new([a: 1, b: 2])
...> Enum.sort(Dict.values(d))
[1,2]
"""
@@ -108,8 +114,8 @@ defmodule Dict do
## Examples
iex> d = HashDict.new [a: 1, b: 2]
...> Dict.size d
iex> d = HashDict.new([a: 1, b: 2])
...> Dict.size(d)
2
"""
@@ -123,12 +129,10 @@ defmodule Dict do
## Examples
iex> d = HashDict.new [a: 1]
...> Dict.has_key?(d, :a)
iex> d = HashDict.new([a: 1])
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
"""
@@ -143,16 +147,12 @@ defmodule Dict do
## Examples
iex> d = HashDict.new [a: 1]
...> Dict.get d, :a
iex> d = HashDict.new([a: 1])
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
@@ -160,23 +160,45 @@ defmodule Dict do
target(dict).get(dict, key, default)
end
@doc false
def get!(dict, key) do
target(dict).get!(dict, key)
end
@doc """
Returns the `{ :ok, value }` associated with `key` in `dict`.
If `dict` does not contain `key`, returns `:error`.
## Examples
iex> d = HashDict.new([a: 1])
iex> Dict.fetch(d, :a)
{ :ok, 1 }
iex> Dict.fetch(d, :b)
:error
"""
@spec fetch(t, key) :: value
def fetch(dict, key) do
target(dict).fetch(dict, key)
end
@doc """
Returns the value associated with `key` in `dict`. If `dict` does not
contain `key`, it raises `KeyError`.
## Examples
iex> d = HashDict.new [a: 1]
...> Dict.get d, :a
iex> d = HashDict.new([a: 1])
iex> Dict.fetch!(d, :a)
1
iex> d = HashDict.new [a: 1]
...> Dict.get! d, :b
iex> Dict.fetch!(d, :b)
** (KeyError) key not found: :b
"""
@spec get!(t, key) :: value | no_return
def get!(dict, key) do
target(dict).get!(dict, key)
@spec fetch!(t, key) :: value | no_return
def fetch!(dict, key) do
target(dict).fetch!(dict, key)
end
@doc """
@@ -185,9 +207,9 @@ defmodule Dict do
## Examples
iex> d = HashDict.new [a: 1, b: 2]
...> d = Dict.put d, :a, 3
...> Dict.get d, :a
iex> d = HashDict.new([a: 1, b: 2])
...> d = Dict.put(d, :a, 3)
...> Dict.get(d, :a)
3
"""
@@ -201,9 +223,9 @@ defmodule Dict do
## Examples
iex> d = HashDict.new [a: 1, b: 2]
...> d = Dict.put_new d, :a, 3
...> Dict.get d, :a
iex> d = HashDict.new([a: 1, b: 2])
...> d = Dict.put_new(d, :a, 3)
...> Dict.get(d, :a)
1
"""
@@ -218,12 +240,12 @@ defmodule Dict do
## Examples
iex> d = HashDict.new [a: 1, b: 2]
...> d = Dict.delete d, :a
...> Dict.get d, :a
iex> d = HashDict.new([a: 1, b: 2])
...> d = Dict.delete(d, :a)
...> Dict.get(d, :a)
nil
iex> d = HashDict.new [b: 2]
iex> d = HashDict.new([b: 2])
...> Dict.delete(d, :a) == d
true
@@ -239,9 +261,9 @@ defmodule Dict do
## Examples
iex> d1 = HashDict.new [a: 1, b: 2]
...> d2 = HashDict.new [a: 3, d: 4]
...> d = Dict.merge d1, d2
iex> d1 = HashDict.new([a: 1, b: 2])
...> d2 = HashDict.new([a: 3, d: 4])
...> d = Dict.merge(d1, d2)
...> [a: Dict.get(d, :a), b: Dict.get(d, :b), d: Dict.get(d, :d)]
[a: 3, b: 2, d: 4]
@@ -258,11 +280,11 @@ defmodule Dict do
## Examples
iex> d1 = HashDict.new [a: 1, b: 2]
...> d2 = HashDict.new [a: 3, d: 4]
...> d = Dict.merge d1, d2, fn _k, v1, v2 ->
iex> d1 = HashDict.new([a: 1, b: 2])
...> d2 = HashDict.new([a: 3, d: 4])
...> d = Dict.merge(d1, d2, fn(_k, v1, v2) ->
...> v1 + v2
...> end
...> end)
...> [a: Dict.get(d, :a), b: Dict.get(d, :b), d: Dict.get(d, :d)]
[a: 4, b: 2, d: 4]
@@ -272,15 +294,42 @@ defmodule Dict do
target(dict).merge(dict, enum, fun)
end
@doc """
Returns the value associated with `key` in `dict` as
well as the `dict` without `key`.
## Examples
iex> dict = HashDict.new [a: 1]
...> {v, d} = Dict.pop dict, :a
...> {v, Enum.sort(d)}
{1,[]}
iex> dict = HashDict.new [a: 1]
...> {v, d} = Dict.pop dict, :b
...> {v, Enum.sort(d)}
{nil,[a: 1]}
iex> dict = HashDict.new [a: 1]
...> {v, d} = Dict.pop dict, :b, 3
...> {v, Enum.sort(d)}
{3,[a: 1]}
"""
@spec pop(t, key, value) :: {value, t}
def pop(dict, key, default // nil) do
target(dict).pop(dict, key, default)
end
@doc """
Update a value in `dict` by calling `fun` on the value to get a new
value. An exception is generated if `key` is not present in the dict.
## Examples
iex> d = HashDict.new [a: 1, b: 2]
...> d = Dict.update d, :a, fn val -> -val end
...> Dict.get d, :a
iex> d = HashDict.new([a: 1, b: 2])
...> d = Dict.update(d, :a, fn(val) -> -val end)
...> Dict.get(d, :a)
-1
"""
@@ -296,9 +345,9 @@ defmodule Dict do
## Examples
iex> d = HashDict.new [a: 1, b: 2]
...> d = Dict.update d, :c, 3, fn val -> -val end
...> Dict.get d, :c
iex> d = HashDict.new([a: 1, b: 2])
...> d = Dict.update(d, :c, 3, fn(val) -> -val end)
...> Dict.get(d, :c)
3
"""
@@ -307,6 +356,80 @@ defmodule Dict do
target(dict).update(dict, key, initial, fun)
end
@doc """
Returns a tuple of two dicts, where the first dict contains only
entries from `dict` with keys in `keys`, and the second dict
contains only entries from `dict` with keys not in `keys`
Any non-member keys are ignored.
## Examples
iex> d = HashDict.new([a: 1, b: 2])
...> { d1, d2 } = Dict.split(d, [:a, :c])
...> { Dict.to_list(d1), Dict.to_list(d2) }
{ [a: 1], [b: 2] }
iex> d = HashDict.new([])
...> { d1, d2 } = Dict.split(d, [:a, :c])
...> { Dict.to_list(d1), Dict.to_list(d2) }
{ [], [] }
iex> d = HashDict.new([a: 1, b: 2])
...> { d1, d2 } = Dict.split(d, [:a, :b, :c])
...> { Dict.to_list(d1), Dict.to_list(d2) }
{ [a: 1, b: 2], [] }
"""
@spec split(t, keys) :: {t, t}
def split(dict, keys) do
target(dict).split(dict, keys)
end
@doc """
Returns a new dict where the given `keys` are removed from `dict`.
Any non-member keys are ignored.
## Examples
iex> d = HashDict.new([a: 1, b: 2])
...> d = Dict.drop(d, [:a, :c, :d])
...> Dict.to_list(d)
[b: 2]
iex> d = HashDict.new([a: 1, b: 2])
...> d = Dict.drop(d, [:c, :d])
...> Dict.to_list(d)
[a: 1, b: 2]
"""
@spec drop(t, keys) :: t
def drop(dict, keys) do
target(dict).drop(dict, keys)
end
@doc """
Returns a new dict where only the keys in `keys` from `dict` are
included. Any non-member keys are ignored.
## Examples
iex> d = HashDict.new([a: 1, b: 2])
...>
...> d = Dict.take(d, [:a, :c, :d])
...> Dict.to_list(d)
[a: 1]
...>
...> d = Dict.take(d, [:c, :d])
...> Dict.to_list(d)
[]
"""
@spec take(t, keys) :: t
def take(dict, keys) do
target(dict).take(dict, keys)
end
@doc """
Returns an empty dict of the same type as `dict`.
"""
@@ -315,6 +438,40 @@ defmodule Dict do
target(dict).empty(dict)
end
@doc """
Check if two dicts are equal, if the dicts are of different types they're
first converted to lists.
## Examples
iex> a = HashDict.new(a: 2, b: 3, f: 5, c: 123)
...> b = ListDict.new(a: 2, b: 3, f: 5, c: 123)
...> Dict.equal?(a, b)
true
iex> a = HashDict.new(a: 2, b: 3, f: 5, c: 123)
...> b = []
...> Dict.equal?(a, b)
false
"""
@spec equal?(t, t) :: boolean
def equal?(a, b) do
a_target = target(a)
b_target = target(b)
cond do
a_target == b_target ->
a_target.equal?(a, b)
a_target.size(a) == b_target.size(b) ->
ListDict.equal?(a_target.to_list(a), b_target.to_list(b))
true ->
false
end
end
@doc """
Returns a list of key-value pairs stored in `dict`.
No particular order is enforced.
+614 -781
View File
File diff suppressed because it is too large Load Diff
+139 -129
View File
@@ -1,3 +1,99 @@
defexception RuntimeError, message: "runtime error"
defexception ArgumentError, message: "argument error"
defexception ArithmeticError, message: "bad argument in arithmetic expression"
defexception SystemLimitError, message: "a system limit has been reached"
defexception SyntaxError, [file: nil, line: nil, description: "syntax error"] do
def message(exception) do
"#{Exception.format_file_line(exception.file, exception.line, nil)}#{exception.description}"
end
end
defexception TokenMissingError, [file: nil, line: nil, description: "expression is incomplete"] do
def message(exception) do
"#{Exception.format_file_line(exception.file, exception.line, nil)}#{exception.description}"
end
end
defexception CompileError, [file: nil, line: nil, description: "compile error"] do
def message(exception) do
"#{Exception.format_file_line(exception.file, exception.line, nil)}#{exception.description}"
end
end
defexception BadFunctionError, [actual: nil] do
def message(exception) do
"expected a function, got: #{inspect(exception.actual)}"
end
end
defexception MatchError, [actual: nil] do
def message(exception) do
"no match of right hand side value: #{inspect(exception.actual)}"
end
end
defexception CaseClauseError, [actual: nil] do
def message(exception) do
"no case clause matching: #{inspect(exception.actual)}"
end
end
defexception BadArityError, [function: nil, args: nil] do
def message(exception) do
"bad arity error: #{inspect(exception.function)} called with #{inspect(exception.args)}"
end
end
defexception UndefinedFunctionError, [module: nil, function: nil, arity: nil] do
def message(exception) do
if exception.function do
formatted = Exception.format_module_fun_arity exception.module, exception.function, exception.arity
"undefined function: #{formatted}"
else
"undefined function"
end
end
end
defexception FunctionClauseError, [module: nil, function: nil, arity: nil] do
def message(exception) do
if exception.function do
formatted = Exception.format_module_fun_arity exception.module, exception.function, exception.arity
"no function clause matching in #{formatted}"
else
"no function clause matches"
end
end
end
defexception Protocol.UndefinedError, [protocol: nil, structure: nil, extra: nil] do
def message(exception) do
msg = "protocol #{inspect exception.protocol} not implemented for #{inspect exception.structure}"
if exception.extra do
msg <> ". " <> exception.extra
else
msg
end
end
end
defexception ErlangError, [original: nil] do
def message(exception) do
"erlang error: #{inspect(exception.original)}"
end
end
defexception KeyError, key: nil do
def message(exception) do
"key not found: #{inspect exception.key}"
end
end
defexception Enum.OutOfBoundsError, message: "out of bounds error"
defexception Enum.EmptyError, message: "empty error"
defmodule Exception do
@moduledoc """
Several convenience functions to work and pretty print
@@ -14,47 +110,49 @@ defmodule Exception do
end
def normalize(:badarg) do
ArgumentError.new
ArgumentError[]
end
def normalize(:badarith) do
ArithmeticError.new
ArithmeticError[]
end
def normalize(:system_limit) do
SystemLimitError.new
SystemLimitError[]
end
def normalize({ :badarity, { fun, args } }) do
BadArityError.new(function: fun, args: args)
BadArityError[function: fun, args: args]
end
def normalize({ :badfun, actual }) do
BadFunctionError.new(actual: actual)
BadFunctionError[actual: actual]
end
def normalize({ :badmatch, actual }) do
MatchError.new(actual: actual)
MatchError[actual: actual]
end
def normalize({ :case_clause, actual }) do
CaseClauseError.new(actual: actual)
CaseClauseError[actual: actual]
end
def normalize(:undef) do
UndefinedFunctionError.new from_stacktrace(:erlang.get_stacktrace)
{ mod, fun, arity } = from_stacktrace(:erlang.get_stacktrace)
UndefinedFunctionError[module: mod, function: fun, arity: arity]
end
def normalize(:function_clause) do
FunctionClauseError.new from_stacktrace(:erlang.get_stacktrace)
{ mod, fun, arity } = from_stacktrace(:erlang.get_stacktrace)
FunctionClauseError[module: mod, function: fun, arity: arity]
end
def normalize({ :badarg, payload }) do
ArgumentError.new message: "argument error: #{inspect(payload)}"
ArgumentError[message: "argument error: #{inspect(payload)}"]
end
def normalize(other) do
ErlangError.new original: other
ErlangError[original: other]
end
@doc """
@@ -62,28 +160,28 @@ defmodule Exception do
The current working directory may be given as argument, which
is used to prettify the stacktrace.
"""
def format_entry(entry, cwd // nil)
def format_stacktrace_entry(entry, cwd // nil)
# From Macro.Env.stacktrace
def format_entry({ module, :__MODULE__, 0, file_line }, cwd) do
def format_stacktrace_entry({ module, :__MODULE__, 0, file_line }, cwd) do
"#{format_file_line(file_line, cwd)}#{inspect module} (module)"
end
# From :elixir_compiler
def format_entry({ _module, :__MODULE__, 2, file_line }, cwd) do
def format_stacktrace_entry({ _module, :__MODULE__, 2, file_line }, cwd) do
"#{format_file_line(file_line, cwd)}(module)"
end
# From :elixir_compiler
def format_entry({ _module, :__FILE__, 2, file_line }, cwd) do
def format_stacktrace_entry({ _module, :__FILE__, 2, file_line }, cwd) do
"#{format_file_line(file_line, cwd)}(file)"
end
def format_entry({module, fun, arity, file_line}, cwd) do
def format_stacktrace_entry({module, fun, arity, file_line}, cwd) do
"#{format_file_line(file_line, cwd)}#{format_module_fun_arity(module, fun, arity)}"
end
def format_entry({fun, arity, file_line}, cwd) do
def format_stacktrace_entry({fun, arity, file_line}, cwd) do
"#{format_file_line(file_line, cwd)}#{format_fun_arity(fun, arity)}"
end
@@ -94,9 +192,7 @@ defmodule Exception do
calculates the current stacktrace and formats it. As consequence,
the value of `System.stacktrace` is changed.
"""
def format_stacktrace(trace // nil)
def format_stacktrace(trace) do
def format_stacktrace(trace // nil) do
trace = trace || try do
throw(:stacktrace)
catch
@@ -105,31 +201,31 @@ defmodule Exception do
case trace do
[] -> "\n"
s -> " " <> Enum.map_join(s, "\n ", format_entry(&1)) <> "\n"
s -> " " <> Enum.map_join(s, "\n ", format_stacktrace_entry(&1)) <> "\n"
end
end
@doc """
Prints the current stacktrace to standard output.
A stacktrace must be given as argument. If not, this function
calculates the current stacktrace and formats it. As consequence,
the value of `System.stacktrace` is changed.
Formats the caller, i.e. the first entry in the stacktrace.
Notice that doing to tail call optimization, the stacktrace
may not report the direct caller of the function.
"""
def print_stacktrace(trace // nil) do
IO.write format_stacktrace(trace)
def format_caller(trace // nil) do
trace = trace || try do
throw(:stacktrace)
catch
:stacktrace -> Enum.drop(:erlang.get_stacktrace, 1)
end
if entry = Enum.at(trace, 1) do
format_stacktrace_entry(entry)
else
"nofile:0: "
end
end
## Helpers
# Check the given module is a valid exception record.
@doc false
def check!(module) do
unless :erlang.function_exported(module, :message, 1) do
raise "Expected #{inspect module} to implement message/1"
end
end
# Format fun and arity
@doc false
def format_fun_arity(fun, arity) do
@@ -184,101 +280,15 @@ defmodule Exception do
end
end
defp from_stacktrace([{ module, function, args, _ }|_]) when is_list(args) do
{ module, function, length(args) }
end
defp from_stacktrace([{ module, function, arity, _ }|_]) do
[module: module, function: function, arity: arity]
{ module, function, arity }
end
defp from_stacktrace(_), do: []
end
defexception RuntimeError, message: "runtime error"
defexception ArgumentError, message: "argument error"
defexception ArithmeticError, message: "bad argument in arithmetic expression"
defexception SystemLimitError, message: "a system limit has been reached"
defexception SyntaxError, [file: nil, line: nil, description: "syntax error"] do
def message(exception) do
"#{Exception.format_file_line(exception.file, exception.line, nil)}#{exception.description}"
defp from_stacktrace(_) do
{ nil, nil, nil }
end
end
defexception TokenMissingError, [file: nil, line: nil, description: "expression is incomplete"] do
def message(exception) do
"#{Exception.format_file_line(exception.file, exception.line, nil)}#{exception.description}"
end
end
defexception CompileError, [file: nil, line: nil, description: "compile error"] do
def message(exception) do
"#{Exception.format_file_line(exception.file, exception.line, nil)}#{exception.description}"
end
end
defexception BadFunctionError, [actual: nil] do
def message(exception) do
"expected a function, got: #{inspect(exception.actual)}"
end
end
defexception MatchError, [actual: nil] do
def message(exception) do
"no match of right hand side value: #{inspect(exception.actual)}"
end
end
defexception CaseClauseError, [actual: nil] do
def message(exception) do
"no case clause matching: #{inspect(exception.actual)}"
end
end
defexception BadArityError, [function: nil, args: nil] do
def message(exception) do
"bad arity error: #{inspect(exception.function)} called with #{inspect(exception.args)}"
end
end
defexception UndefinedFunctionError, [module: nil, function: nil, arity: nil] do
def message(exception) do
if exception.function do
formatted = Exception.format_module_fun_arity exception.module, exception.function, to_arity(exception.arity)
"undefined function: #{formatted}"
else
"undefined function"
end
end
defp to_arity(arity) when is_integer(arity), do: arity
defp to_arity(list) when is_list(list), do: length(list)
end
defexception FunctionClauseError, [module: nil, function: nil, arity: nil] do
def message(exception) do
if exception.function do
formatted = Exception.format_module_fun_arity exception.module, exception.function, exception.arity
"no function clause matching: #{formatted}"
else
"no function clause matches"
end
end
end
defexception Protocol.UndefinedError, [protocol: nil, structure: nil] do
def message(exception) do
"protocol #{inspect exception.protocol} not implemented for #{inspect exception.structure}"
end
end
defexception ErlangError, [original: nil] do
def message(exception) do
"erlang error: #{inspect(exception.original)}"
end
end
defexception KeyError, key: nil do
def message(exception) do
"key not found: #{inspect exception.key}"
end
end
defexception Enum.OutOfBoundsError, message: "out of bounds error"
+213 -171
View File
@@ -38,10 +38,12 @@ defexception File.Error, [reason: nil, action: "", path: nil] do
end
end
defexception File.CopyError, [reason: nil, action: "", source: nil, destination: nil] do
defexception File.CopyError, [reason: nil, action: "", source: nil, destination: nil, on: nil] do
def message(exception) do
formatted = list_to_binary(:file.format_error(reason exception))
"could not #{action exception} from #{source exception} to #{destination exception}: #{formatted}"
location = if on = on(exception), do: ". #{on}", else: ""
"could not #{action exception} from #{source exception} to " <>
"#{destination exception}#{location}: #{formatted}"
end
end
@@ -54,28 +56,30 @@ end
defmodule File do
@moduledoc """
This module contains function to manipulate files.
Many of the functions that interact with the filesystem
have their naming based on its UNIX variants. For
example, deleting a file is done with `File.rm`.
Getting its stats with `File.stat`.
This module contains functions to manipulate files.
In order to write and read files, one must use the
functions in the IO module. By default, a file is
opened in binary mode which requires the functions
`IO.binread`, `IO.binwrite` and `IO.binreadline` to
interact with the file. A developer may pass `:utf8`
as an option when opening the file and then all other
functions from IO are available, since they work directly
Some of those functions are low-level, allowing the user
to interact with the file or IO devices, like `File.open/2`,
`File.copy/3` and others. This module also provides higher
level functions that works with filenames and have their naming
based on UNIX variants. For example, one can copy a file
via `File.cp/3` and remove files and directories recursively
via `File.rm_rf/2`
In order to write and read files, one must use the functions
in the IO module. By default, a file is opened in binary mode
which requires the functions `IO.binread`, `IO.binwrite` and
`IO.binreadline` to interact with the file. A developer may
pass `:utf8` as an option when opening the file and then all
other functions from IO are available, since they work directly
with Unicode data.
Most of the functions in this module return `:ok`
or `{ :ok, result }` in case of success, `{ :error, reason }`
otherwise. Those function are also followed by
a variant that ends with `!` which returns the
result (without the `{ :ok, result }` tuple) in
case of success or raises an exception in case it
fails. For example:
Most of the functions in this module return `:ok` or
`{ :ok, result }` in case of success, `{ :error, reason }`
otherwise. Those function are also followed by a variant
that ends with `!` which returns the result (without the
`{ :ok, result }` tuple) in case of success or raises an
exception in case it fails. For example:
File.read("hello.txt")
#=> { :ok, "World" }
@@ -89,16 +93,10 @@ defmodule File do
File.read!("invalid.txt")
#=> raises File.Error
In general, a developer should use the former in case
he wants to react in the fie does not exist. The latter
should be used when the developer expects his software
to fail in case the file cannot be read (i.e. it is
literally an exception).
Finally, the functions in this module accept either
a char lists or a binary. When manipulating paths, a char
list is returned if one is given as argument. However,
when reading files, binaries are always returned.
In general, a developer should use the former in case he wants
to react if the file does not exist. The latter should be used
when the developer expects his software to fail in case the
file cannot be read (i.e. it is literally an exception).
"""
alias :file, as: F
@@ -125,7 +123,7 @@ defmodule File do
end
@doc """
Returns true if the given argument exists.
Returns true if the given path exists.
It can be regular file, directory, socket,
symbolic link, named pipe or device file.
@@ -233,7 +231,7 @@ defmodule File do
@doc """
Returns information about the `path`. If it exists, it
returns a `{ :ok, info }` tuple, where info is as a
returns a `{ :ok, info }` tuple, where info is a
`File.Info` record. Retuns `{ :error, reason }` with
the same reasons as `File.read` if a failure occurs.
@@ -310,17 +308,24 @@ defmodule File do
end
@doc """
Copies the contents of `source` to `destination`. Both
parameters can be a filename or an io device opened with `File.open`.
`bytes_count` specifies the number of bytes to count, the default
being `:infinity`.
Copies the contents of `source` to `destination`.
If file `destination` already exists, it is overriden
Both parameters can be a filename or an io device opened
with `File.open/2`. `bytes_count` specifies the number of
bytes to copy, the default being `:infinity`.
If file `destination` already exists, it is overwritten
by the contents in `source`.
Returns `{ :ok, bytes_copied }` if successful,
`{ :error, reason }` otherwise.
Compared to the `File.cp/3`, this function is more low-level,
allowing a copy from device to device limited by a number of
bytes. On the other hand, `File.cp/3` performs more extensive
checks on both source and destination and it also preserves
the file mode after copy.
Typical error reasons are the same as in `open/2`,
`read/1` and `write/2`.
"""
@@ -329,7 +334,7 @@ defmodule File do
end
@doc """
The same as `copy/3` but raises an File.CopyError if it fails.
The same as `copy/3` but raises an `File.CopyError` if it fails.
Returns the `bytes_copied` otherwise.
"""
def copy!(source, destination, bytes_count // :infinity) do
@@ -342,40 +347,36 @@ defmodule File do
end
@doc """
Copies the contents in `source` to `destination`.
Similar to the command `cp -r` in Unix systems,
this function behaves differently depending
if `source` and `destination` are a file or a directory.
Copies the contents in `source` to `destination` preserving its mode.
If both are files, it simply copies `source` to
`destination`. However, if `destination` is a directory,
it copies the contents of `source` to `destination/source`
recursively.
Similar to the command `cp` in Unix systems, this function
behaves differently depending if `destination` is a directory
or not. In particular, if `destination` is a directory, it
copies the contents of `source` to `destination/source`.
If a file already exists in the destination,
it invokes a callback which should return
true if the existing file should be overriden,
false otherwise. It defaults to return true.
If a file already exists in the destination, it invokes a
callback which should return true if the existing file
should be overwritten, false otherwise. It defaults to return true.
It returns `:ok` in case of success, returns
`{ :error, reason }` otherwise.
If you want to copy contents from an io device to another device
or do a straight copy from a source to a destination without
preserving modes, check `File.copy/3` instead.
"""
def cp(source, destination, callback // fn(_, _) -> true end) do
if dir?(source) do
{ :error, :eisdir }
else
output =
if dir?(destination) do
mkdir(destination)
FN.join(destination, FN.basename(source))
else
destination
end
case do_cp_file(source, output, callback, []) do
{ :error, _ } = error -> error
_ -> :ok
output =
if dir?(destination) do
mkdir(destination)
FN.join(destination, FN.basename(source))
else
destination
end
case do_cp_file(source, output, callback, []) do
{ :error, reason, _ } -> { :error, reason }
_ -> :ok
end
end
@@ -388,7 +389,8 @@ defmodule File do
:ok -> :ok
{ :error, reason } ->
raise File.CopyError, reason: reason, action: "copy recursively",
source: :unicode.characters_to_binary(source), destination: :unicode.characters_to_binary(destination)
source: :unicode.characters_to_binary(source),
destination: :unicode.characters_to_binary(destination)
end
end
@@ -405,7 +407,7 @@ defmodule File do
If a file already exists in the destination,
it invokes a callback which should return
true if the existing file should be overriden,
true if the existing file should be overwritten,
false otherwise. It defaults to return true.
If a directory already exists in the destination
@@ -434,7 +436,7 @@ defmodule File do
# Same as before, but asks the user how to proceed in case of conflicts
File.cp_r "samples/.", "tmp", fn(source, destination) ->
IO.gets("Overriding #{destination} by #{source}. Type y to confirm.") == "y"
IO.gets("Overwriting #{destination} by #{source}. Type y to confirm.") == "y"
end
"""
@@ -448,7 +450,7 @@ defmodule File do
end
case do_cp_r(source, output, callback, []) do
{ :error, _ } = error -> error
{ :error, _, _ } = error -> error
res -> { :ok, res }
end
end
@@ -460,19 +462,26 @@ defmodule File do
def cp_r!(source, destination, callback // fn(_, _) -> true end) do
case cp_r(source, destination, callback) do
{ :ok, files } -> files
{ :error, reason } ->
{ :error, reason, file } ->
raise File.CopyError, reason: reason, action: "copy recursively",
source: :unicode.characters_to_binary(source), destination: :unicode.characters_to_binary(destination)
source: :unicode.characters_to_binary(source),
destination: :unicode.characters_to_binary(destination),
on: file
end
end
# src may be a file or a directory, dest is definitely
# a directory. Returns nil unless an error is found.
defp do_cp_r(src, dest, callback, acc) when is_list(acc) do
case F.read_link(src) do
{ :ok, link } ->
do_cp_link(link, src, dest, callback, acc)
_ ->
case :elixir.file_type(src) do
{ :ok, :regular } ->
do_cp_file(src, dest, callback, acc)
{ :ok, :symlink } ->
case F.read_link(src) do
{ :ok, link } -> do_cp_link(link, src, dest, callback, acc)
{ :error, reason } -> { :error, reason, :unicode.characters_to_binary(src) }
end
{ :ok, :directory } ->
case F.list_dir(src) do
{ :ok, files } ->
case mkdir(dest) do
@@ -480,12 +489,12 @@ defmodule File do
Enum.reduce(files, [dest|acc], fn(x, acc) ->
do_cp_r(FN.join(src, x), FN.join(dest, x), callback, acc)
end)
reason -> reason
{ :error, reason } -> { :error, reason, :unicode.characters_to_binary(dest) }
end
{ :error, :enotdir } ->
do_cp_file(src, dest, callback, acc)
reason -> reason
{ :error, reason } -> { :error, reason, :unicode.characters_to_binary(src) }
end
{ :ok, _ } -> { :error, :eio, src }
{ :error, reason } -> { :error, reason, :unicode.characters_to_binary(src) }
end
end
@@ -514,12 +523,12 @@ defmodule File do
{ :ok, _ } ->
copy_file_mode!(src, dest)
[dest|acc]
reason -> reason
{ :error, reason } -> { :error, reason, :unicode.characters_to_binary(src) }
end
else
acc
end
reason -> reason
{ :error, reason } -> { :error, reason, :unicode.characters_to_binary(src) }
end
end
@@ -533,12 +542,12 @@ defmodule File do
rm(dest)
case F.make_symlink(link, dest) do
:ok -> [dest|acc]
reason -> reason
{ :error, reason } -> { :error, reason, :unicode.characters_to_binary(src) }
end
else
acc
end
reason -> reason
{ :error, reason } -> { :error, reason, :unicode.characters_to_binary(src) }
end
end
@@ -638,7 +647,7 @@ defmodule File do
@doc """
Remove files and directories recursively at the given `path`.
Symlinks are not followed but simply removed, non existing
Symlinks are not followed but simply removed, non-existing
files are simply ignored (i.e. doesn't make this function fail).
Returns `{ :ok, files_and_directories }` with all files and
@@ -671,18 +680,18 @@ defmodule File do
case rmdir(path) do
:ok -> { :ok, [path|acc] }
{ :error, :enoent } -> res
reason -> reason
reason -> { :error, reason, :unicode.characters_to_binary(path) }
end
reason -> reason
reason -> { :error, reason, :unicode.characters_to_binary(path) }
end
{ :error, :enotdir } ->
{ :error, reason } when reason in [:enotdir, :eio] ->
case rm(path) do
:ok -> { :ok, [path|acc] }
{ :error, :enoent } -> entry
reason -> reason
{ :error, reason } when reason in [:enoent, :enotdir] -> entry
reason -> { :error, reason, :unicode.characters_to_binary(path) }
end
{ :error, :enoent } -> entry
reason -> reason
{ :error, reason } -> { :error, reason, :unicode.characters_to_binary(path) }
end
end
@@ -704,8 +713,10 @@ defmodule File do
def rm_rf!(path) do
case rm_rf(path) do
{ :ok, files } -> files
{ :error, reason } ->
raise File.Error, reason: reason, action: "remove files and directories recursively from", path: :unicode.characters_to_binary(path)
{ :error, reason, _ } ->
raise File.Error, reason: reason,
action: "remove files and directories recursively from",
path: :unicode.characters_to_binary(path)
end
end
@@ -753,14 +764,14 @@ defmodule File do
This function returns:
* { :ok, io_device } - The file has been opened in the requested mode.
* `{ :ok, io_device }` - The file has been opened in the requested mode.
`io_device` is actually the pid of the process which handles the file.
This process is linked to the process which originally opened the file.
If any process to which the io_device is linked terminates, the file will
be closed and the process itself will be terminated. An io_device returned
If any process to which the `io_device` is linked terminates, the file will
be closed and the process itself will be terminated. An `io_device` returned
from this call can be used as an argument to the `IO` module functions.
* { :error, reason } - The file could not be opened.
* `{ :error, reason }` - The file could not be opened.
## Examples
@@ -930,11 +941,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
@@ -944,13 +950,13 @@ defmodule File do
passed into `Enum`. The device is iterated line
by line, at the end of iteration the file is closed.
This reads the file as utf-8. CHeck out `File.biniterator`
This reads the file as utf-8. Check out `File.biniterator`
to handle the file as a raw binary.
## Examples
An example that lazily iterates a file replacing all double
quotes per single quotes and write each line to a target file
quotes per single quotes and writes each line to a target file
is shown below:
{ :ok, device } = File.open("README.md")
@@ -962,50 +968,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 +996,87 @@ 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`.
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
case open(file, mode) do
{ :ok, device } -> { :ok, biniterator(device) }
error -> error
def biniterator!(file, mode // []) do
fn(fun, acc) ->
device = open!(file, mode)
try do
do_biniterator(device, fun, acc)
after
F.close(device)
end
end
end
@doc """
Same as `biniterator/2` but raises if the file
cannot be opened.
Changes the unix file `mode` for a given `file`.
Returns `:ok` on success, or `{:error, reason}`
on failure.
"""
def biniterator!(file, mode // []) do
open!(file, mode) |> biniterator
def chmod(file, mode) do
F.change_mode(file, mode)
end
@doc """
Same as `chmod/2`, but raises an exception in case of failure. Otherwise `:ok`.
"""
def chmod!(file, mode) do
case chmod(file, mode) do
:ok -> :ok
{ :error, reason } ->
raise File.Error, reason: reason, action: "change mode for", path: :unicode.characters_to_binary(file)
end
end
@doc """
Changes the user group given by the group id `gid`
for a given `file`. Returns `:ok` on success, or
`{:error, reason}` on failure.
"""
def chgrp(file, gid) do
F.change_group(file, gid)
end
@doc """
Same as `chgrp/2`, but raises an exception in case of failure. Otherwise `:ok`.
"""
def chgrp!(file, gid) do
case chgrp(file, gid) do
:ok -> :ok
{ :error, reason } ->
raise File.Error, reason: reason, action: "change group for", path: :unicode.characters_to_binary(file)
end
end
@doc """
Changes the owner given by the user id `gid`
for a given `file`. Returns `:ok` on success,
or `{:error, reason}` on failure.
"""
def chown(file, uid) do
F.change_owner(file, uid)
end
@doc """
Same as `chown/2`, but raises an exception in case of failure. Otherwise `:ok`.
"""
def chown!(file, gid) do
case chown(file, gid) do
:ok -> :ok
{ :error, reason } ->
raise File.Error, reason: reason, action: "change owner for", path: :unicode.characters_to_binary(file)
end
end
## Helpers
@@ -1075,4 +1095,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
+209 -102
View File
@@ -33,27 +33,26 @@ defmodule HashDict do
# set of buckets
# The ordered record contains a single bucket
ordered_threshold = 8
@ordered_threshold 8
defrecordp :ordered,
size: 0,
bucket: []
# The bucketed record contains a series of buckets.
expand_load = 5
contract_load = 2
node_bitmap = 0b111
node_shift = 3
node_size = 8
node_template = :erlang.make_tuple(node_size, [])
node_escaped = Macro.escape(node_template)
@expand_load 5
@contract_load 2
@node_bitmap 0b111
@node_shift 3
@node_size 8
@node_template :erlang.make_tuple(@node_size, [])
defrecordp :trie,
size: 0,
depth: 0,
expand_on: node_size * expand_load,
contract_on: contract_load,
root: node_template
expand_on: @node_size * @expand_load,
contract_on: @contract_load,
root: @node_template
import Bitwise
@@ -73,7 +72,7 @@ defmodule HashDict do
## Examples
HashDict.new [{:b,1},{:a,2}]
HashDict.new [{:b, 1}, {:a, 2}]
#=> #HashDict<[a: 2, b: 1]>
"""
@@ -153,16 +152,24 @@ defmodule HashDict do
end
@doc """
Gets the value under key from the dict,
raises KeyError if such key does not exist.
Fetches the value under key from the dict
and return it in a tagged tuple.
"""
def get!(dict, key) when is_tuple(dict) do
def fetch(dict, key) do
case dict_get(dict, key) do
{ ^key, value } -> { :ok, value }
false -> :error
end
end
def fetch!(dict, key) when is_tuple(dict) do
case dict_get(dict, key) do
{ ^key, value } -> value
false -> raise(KeyError, key: key)
end
end
@doc """
Checks if the dict has the given key.
"""
@@ -171,35 +178,21 @@ defmodule HashDict do
end
@doc """
Deletes a value from the dict.
Returns the value under key from the dict as well as the dict without key.
"""
def delete(ordered(bucket: bucket, size: size) = dict, key) do
case bucket_delete(bucket, key) do
{ _, 0 } ->
dict
{ new_bucket, -1 } ->
ordered(dict, size: size - 1, bucket: new_bucket)
def pop(dict, key, default // nil) do
case dict_delete(dict, key) do
{ dict, _, 0 } -> { default, dict }
{ dict, value, _ } -> { value, dict }
end
end
def delete(trie(root: root, size: size, depth: depth) = dict, key) do
pos = bucket_hash(key)
case node_delete(root, depth, pos, key) do
{ _, 0 } ->
dict
{ root, -1 } ->
if depth > 0 and trie(dict, :contract_on) == size do
root = node_contract(root, depth, depth - 1)
trie(dict,
root: root,
size: size - 1,
depth: depth - 1,
contract_on: div(size, unquote(node_size)),
expand_on: div(trie(dict, :expand_on), unquote(node_size)))
else
trie(dict, size: size - 1, root: root)
end
end
@doc """
Deletes a value from the dict.
"""
def delete(dict, key) do
{ dict, _, _ } = dict_delete(dict, key)
dict
end
@doc """
@@ -216,6 +209,19 @@ defmodule HashDict do
ordered()
end
@doc """
Checks if two dicts are equal
"""
def equal?(dict1, dict2) do
size = elem(dict1, 1)
case elem(dict2, 1) do
^size ->
dict_equal?(dict1, dict2)
_ ->
false
end
end
@doc """
Converts the dict to a list.
"""
@@ -224,7 +230,7 @@ defmodule HashDict do
end
def to_list(dict) do
dict_fold(dict, [], [&1|&2])
dict_fold(dict, [], [&1|&2]) |> :lists.reverse
end
@doc """
@@ -264,14 +270,55 @@ defmodule HashDict do
end
end
def split(dict, keys) do
split(keys, new, dict)
end
defp split([], including, excluding) do
{ including, excluding }
end
defp split([key|keys], including, excluding) do
case dict_delete(excluding, key) do
{ excluding, _, 0 } -> split(keys, including, excluding)
{ excluding, value, _ } -> split(keys, put(including, key, value), excluding)
end
end
def take(dict, keys) do
take(dict, keys, new)
end
defp take(_dict, [], acc), do: acc
defp take(dict, [key|keys], acc) do
case fetch(dict, key) do
{ :ok, value } -> take(dict, keys, put(acc, key, value))
:error -> take(dict, keys, acc)
end
end
def drop(dict, []), do: dict
def drop(dict, [key|keys]) 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
@@ -279,12 +326,12 @@ defmodule HashDict do
end
defp dict_fold(trie(root: root, depth: depth), acc, fun) do
node_fold(root, depth, acc, fun, unquote(node_size))
node_fold(root, depth, acc, fun, @node_size)
end
defp dict_put(ordered(size: unquote(ordered_threshold), bucket: bucket), key, value) do
defp dict_put(ordered(size: @ordered_threshold, bucket: bucket), key, value) do
root = node_relocate(bucket, 0)
dict_put(trie(size: unquote(ordered_threshold), root: root), key, value)
dict_put(trie(size: @ordered_threshold, root: root), key, value)
end
defp dict_put(ordered(size: size, bucket: bucket) = dict, key, value) do
@@ -295,7 +342,7 @@ defmodule HashDict do
defp dict_put(trie(root: root, depth: depth, size: size, expand_on: size, contract_on: contract_on) = dict, key, value) do
root = node_expand(root, depth, depth + 1)
dict = trie(dict, root: root, depth: depth + 1,
expand_on: size * unquote(node_size), contract_on: contract_on * unquote(node_size))
expand_on: size * @node_size, contract_on: contract_on * @node_size)
dict_put(dict, key, value)
end
@@ -305,61 +352,122 @@ defmodule HashDict do
{ trie(dict, size: size + count, root: root), count }
end
defp dict_delete(ordered(bucket: bucket, size: size) = dict, key) do
case bucket_delete(bucket, key) do
{ _, value, 0 } ->
{ dict, value, 0 }
{ new_bucket, value, -1 } ->
{ ordered(dict, size: size - 1, bucket: new_bucket), value, -1 }
end
end
defp dict_delete(trie(root: root, size: size, depth: depth) = dict, key) do
pos = bucket_hash(key)
case node_delete(root, depth, pos, key) do
{ _, value, 0 } ->
{ dict, value, 0 }
{ root, value, -1 } ->
{ if depth > 0 and trie(dict, :contract_on) == size do
root = node_contract(root, depth)
trie(dict,
root: root,
size: size - 1,
depth: depth - 1,
contract_on: div(size, @node_size),
expand_on: div(trie(dict, :expand_on), @node_size))
else
trie(dict, size: size - 1, root: root)
end, value, -1 }
end
end
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
defp bucket_put([{key, _}|bucket], key, { :put, value }) do
{ [{key, value}|bucket], 0 }
end
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([{_,_}|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 }
end
defp bucket_put([], key, { :put, value }) do
{ [{key,value}], 1 }
{ [{key, value}], 1 }
end
defp bucket_put([], key, { :update, initial, _fun }) do
{ [{key,initial}], 1 }
{ [{key, initial}], 1 }
end
# 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!([], key, value), do: [{key,value}]
defp bucket_put!([{k, _}|_]=bucket, key, value) when k > key, do: [{key, value}|bucket]
defp bucket_put!([{key, _}|bucket], key, value), do: [{key, value}|bucket]
defp bucket_put!([{_, _}=e|bucket], key, value), do: [e|bucket_put!(bucket, key, value)]
defp bucket_put!([], key, value), do: [{key, value}]
# Deletes a key from the bucket
defp bucket_delete([{k,_}|_] = bucket, key) when key < k do
{ bucket, 0 }
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, count } = bucket_delete(bucket, key)
{ [e|rest], count }
defp bucket_delete([{key, value}|bucket], key) do
{ bucket, value, -1 }
end
defp bucket_delete([{_,_}|bucket], _key) do
{ bucket, -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
{ [], 0 }
{ [], nil, 0 }
end
# Folds the bucket
@@ -372,15 +480,15 @@ defmodule HashDict do
end
defp bucket_index(hash) do
hash &&& unquote(node_bitmap)
hash &&& @node_bitmap
end
defp bucket_nth_index(hash, n) do
(hash >>> (unquote(node_shift) * n)) &&& unquote(node_bitmap)
(hash >>> (@node_shift * n)) &&& @node_bitmap
end
defp bucket_next(hash) do
hash >>> unquote(node_shift)
hash >>> @node_shift
end
## Node helpers
@@ -412,16 +520,16 @@ defmodule HashDict do
defp node_delete(node, 0, hash, key) do
pos = bucket_index(hash)
case bucket_delete(elem(node, pos), key) do
{ _, 0 } -> { node, 0 }
{ new, -1 } -> { set_elem(node, pos, new), -1 }
{ _, value, 0 } -> { node, value, 0 }
{ new, value, -1 } -> { set_elem(node, pos, new), value, -1 }
end
end
defp node_delete(node, depth, hash, key) do
pos = bucket_index(hash)
case node_delete(elem(node, pos), depth - 1, bucket_next(hash), key) do
{ _, 0 } -> { node, 0 }
{ new, -1 } -> { set_elem(node, pos, new), -1 }
{ _, value, 0 } -> { node, value, 0 }
{ new, value, -1 } -> { set_elem(node, pos, new), value, -1 }
end
end
@@ -431,7 +539,7 @@ defmodule HashDict do
end
defp node_fold(node, depth, acc, fun, count) when count >= 1 do
acc = node_fold(:erlang.element(count, node), depth - 1, acc, fun, unquote(node_size))
acc = node_fold(:erlang.element(count, node), depth - 1, acc, fun, @node_size)
node_fold(node, depth, acc, fun, count - 1)
end
@@ -453,28 +561,25 @@ defmodule HashDict do
node_expand(b7, depth, n), node_expand(b8, depth, n) }
end
defp node_contract({ b1, b2, b3, b4, b5, b6, b7, b8 }, depth, n) when depth > 1 do
defp node_contract({ b1, b2, b3, b4, b5, b6, b7, b8 }, depth) when depth > 0 do
depth = depth - 1
{ node_contract(b1, depth, n), node_contract(b2, depth, n), node_contract(b3, depth, n),
node_contract(b4, depth, n), node_contract(b5, depth, n), node_contract(b6, depth, n),
node_contract(b7, depth, n), node_contract(b8, depth, n) }
{ node_contract(b1, depth), node_contract(b2, depth), node_contract(b3, depth),
node_contract(b4, depth), node_contract(b5, depth), node_contract(b6, depth),
node_contract(b7, depth), node_contract(b8, depth) }
end
defp node_contract({ b1, b2, b3, b4, b5, b6, b7, b8 }, 1, n) do
unquote(node_escaped)
|> each_contract(b1, n) |> each_contract(b2, n) |> each_contract(b3, n)
|> each_contract(b4, n) |> each_contract(b5, n) |> each_contract(b6, n)
|> each_contract(b7, n) |> each_contract(b8, n)
defp node_contract({ b1, b2, b3, b4, b5, b6, b7, b8 }, 0) do
b1 |> each_contract(b2) |> each_contract(b3) |> each_contract(b4)
|> each_contract(b5) |> each_contract(b6) |> each_contract(b7)
|> each_contract(b8)
end
defp each_contract(acc, { b1, b2, b3, b4, b5, b6, b7, b8 }, n) do
acc
|> node_relocate(b1, n) |> node_relocate(b2, n) |> node_relocate(b3, n)
|> node_relocate(b4, n) |> node_relocate(b5, n) |> node_relocate(b6, n)
|> node_relocate(b7, n) |> node_relocate(b8, n)
end
defp each_contract([{k, _v}=e|acc], [{key, _value}|_]=bucket) when k < key, do: [e|each_contract(acc, bucket)]
defp each_contract(acc, [e|bucket]), do: [e|each_contract(acc, bucket)]
defp each_contract([], bucket), do: bucket
defp each_contract(acc, []), do: acc
defp node_relocate(node // unquote(node_escaped), bucket, n) do
defp node_relocate(node // @node_template, bucket, n) do
:lists.foldl fn { key, value }, acc ->
pos = key |> bucket_hash() |> bucket_nth_index(n)
set_elem(acc, pos, bucket_put!(elem(acc, pos), key, value))
@@ -482,9 +587,11 @@ defmodule HashDict do
end
end
defimpl Enum.Iterator, for: HashDict do
def iterator(dict), do: HashDict.to_list(dict)
def count(dict), do: HashDict.size(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)
end
defimpl Access, for: HashDict do
@@ -495,6 +602,6 @@ defimpl Binary.Inspect, for: HashDict do
import Kernel, except: [inspect: 2]
def inspect(dict, opts) do
"#HashDict<" <> Binary.Inspect.inspect(HashDict.to_list(dict), opts) <> ">"
"#HashDict<" <> Kernel.inspect(HashDict.to_list(dict), opts) <> ">"
end
end
+33 -3
View File
@@ -125,6 +125,17 @@ defmodule IO do
:io.put_chars erl_dev, [to_iodata(item), ?\n]
end
@doc """
Writes the given arguments to stderr as a warning.
If `--warnings-as-errors` is true and this function is
called during compilation, compilation will fail.
"""
def warn(item) do
:elixir_code_server.cast(:register_warning)
:io.put_chars :standard_error, [to_iodata(item), ?\n]
end
@doc """
Inspects and writes the given argument to the device
followed by a new line. A set of options can be given.
@@ -142,12 +153,15 @@ defmodule IO do
Inspects the item with options using the given device.
"""
def inspect(device, item, opts) do
puts device, Binary.Inspect.inspect(item, opts)
puts device, Kernel.inspect(item, opts)
item
end
@doc """
Gets `count` bytes from the IO device. It returns:
Gets a number of bytes from the io device. If the
io device is a unicode device, count implies
the number of unicode codepoints to be retrieved.
Otherwise, the number of raw bytes. It returns:
* `data` - The input characters.
@@ -157,7 +171,23 @@ defmodule IO do
for instance {:error, :estale} if reading from an
NFS file system.
"""
def getb(device // group_leader(), prompt, count // 1) do
def getn(prompt, count // 1)
def getn(prompt, count) when is_integer(count) do
getn(group_leader, prompt, count)
end
def getn(device, prompt) do
getn(device, prompt, 1)
end
@doc """
Gets a number of bytes from the io device. If the
io device is a unicode device, count implies
the number of unicode codepoints to be retrieved.
Otherwise, the number of raw bytes.
"""
def getn(device, prompt, count) do
:io.get_chars(map_dev(device), to_iodata(prompt), count)
end
+23 -42
View File
@@ -2,14 +2,12 @@ defmodule IO.ANSI.Sequence do
@moduledoc false
defmacro defsequence(name, code) do
binary_name = atom_to_binary(name)
quote do
quote binding: [name: name, code: code] do
def unquote(name)() do
"\e[#{unquote(code)}m"
end
defp escape_sequence(<< unquote(binary_name), rest :: binary >>) do
defp escape_sequence(<< unquote(atom_to_binary(name)), rest :: binary >>) do
{ "\e[#{unquote(code)}m", rest }
end
end
@@ -22,9 +20,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
@@ -34,23 +29,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"
@@ -89,11 +72,9 @@ defmodule IO.ANSI do
@doc "Sets primary (default) font"
defsequence :primary_font, 10
lc font_n inlist [1,2,3,4,5,6,7,8,9] do
Module.eval_quoted __ENV__, (quote do
@doc "Sets alternative font #{unquote(font_n)}"
defsequence(unquote(:"font_#{font_n}"), unquote(font_n + 10))
end)
lc font_n inlist [1, 2, 3, 4, 5, 6, 7, 8, 9] do
@doc "Sets alternative font #{font_n}"
defsequence :"font_#{font_n}", font_n + 10
end
@doc "Normal color or intensity"
@@ -112,13 +93,11 @@ defmodule IO.ANSI do
colors = Enum.zip(0..(length(colors)-1), colors)
lc { code, color } inlist colors do
Module.eval_quoted __ENV__, (quote do
@doc "Sets foreground color to #{unquote(color)}"
defsequence unquote(color), unquote(code + 30)
@doc "Sets foreground color to #{color}"
defsequence color, code + 30
@doc "Sets background color to #{unquote(color)}"
defsequence unquote(:"#{color}_background"), unquote(code + 40)
end)
@doc "Sets background color to #{color}"
defsequence :"#{color}_background", code + 40
end
@doc "Default text color"
@@ -153,13 +132,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.
@@ -168,7 +147,7 @@ defmodule IO.ANSI do
## Example
iex> IO.ANSI.escape "Hello %{red,bright,green}yes"
iex> IO.ANSI.escape("Hello %{red,bright,green}yes", true)
"Hello \e[31m\e[1m\e[32myes\e[0m"
"""
@spec escape(String.t, emit :: boolean) :: String.t
@@ -182,10 +161,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.
@@ -194,8 +173,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", true)
"Hello \e[31m\e[1m\e[32myes"
iex> IO.ANSI.escape_fragment("%{reset}bye", true)
"\e[0mbye"
"""
@spec escape_fragment(String.t, emit :: boolean) :: String.t
+508 -449
View File
File diff suppressed because it is too large Load Diff
+163 -66
View File
@@ -3,19 +3,26 @@ defmodule Kernel.CLI do
Module responsible for controlling Elixir's CLI
"""
defrecord Config, commands: [], output: ".",
compile: [], halt: true, compiler_options: []
defrecord Config, commands: [], output: ".", compile: [],
halt: true, compiler_options: [], errors: []
# This is the API invoked by Elixir boot process.
@doc false
def main(argv) do
argv = lc arg inlist argv, do: list_to_binary(arg)
argv = lc arg inlist argv, do: :unicode.characters_to_binary(arg)
{ config, argv } = process_argv(argv, Kernel.CLI.Config.new)
:elixir_code_server.cast({ :argv, argv })
run fn ->
Enum.map Enum.reverse(config.commands), process_command(&1, config)
command_results = Enum.map(Enum.reverse(config.commands), process_command(&1, config))
command_errors = lc { :error, msg } inlist command_results, do: msg
errors = Enum.reverse(config.errors) ++ command_errors
if errors != [] do
Enum.each(errors, IO.puts(:stderr, &1))
System.halt(1)
end
end, config.halt
end
@@ -39,7 +46,7 @@ defmodule Kernel.CLI do
at_exit(1)
trace = System.stacktrace
IO.puts :stderr, "** (#{inspect exception.__record__(:name)}) #{exception.message}"
IO.puts Exception.format_stacktrace(trace)
IO.puts :stderr, Exception.format_stacktrace(trace)
System.halt(1)
catch
:exit, reason when is_integer(reason) ->
@@ -52,12 +59,12 @@ defmodule Kernel.CLI do
at_exit(1)
trace = System.stacktrace
IO.puts :stderr, "** (#{kind}) #{inspect(reason)}"
IO.puts Exception.format_stacktrace(trace)
IO.puts :stderr, Exception.format_stacktrace(trace)
System.halt(1)
end
end
## Private
## Helpers
defp at_exit(status) do
hooks = :elixir_code_server.call(:flush_at_exit)
@@ -69,12 +76,12 @@ defmodule Kernel.CLI do
exception ->
trace = System.stacktrace
IO.puts :stderr, "** (#{inspect exception.__record__(:name)}) #{exception.message}"
IO.puts Exception.format_stacktrace(trace)
IO.puts :stderr, Exception.format_stacktrace(trace)
catch
kind, reason ->
trace = System.stacktrace
IO.puts :stderr, "** #{kind} #{inspect(reason)}"
IO.puts Exception.format_stacktrace(trace)
IO.puts :stderr, Exception.format_stacktrace(trace)
end
end
@@ -83,15 +90,11 @@ defmodule Kernel.CLI do
unless hooks == [], do: at_exit(status)
end
defp invalid_option(option) do
IO.puts(:stderr, "Unknown option #{option}")
System.halt(1)
end
defp shared_option?(list, config, callback) do
case process_shared(list, config) do
{ [h|_], _ } when h == hd(list) ->
invalid_option h
{ [h|hs], _ } when h == hd(list) ->
new_config = config.update_errors ["#{h} : Unknown option" | &1]
callback.(hs, new_config)
{ new_list, new_config } ->
callback.(new_list, new_config)
end
@@ -99,44 +102,42 @@ 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}"
System.halt 0
end
defp process_shared(["-pa", h|t], config) do
Enum.each Path.wildcard(Path.expand(h)), Code.prepend_path(&1)
process_shared t, config
end
defp process_shared(["--app",h|t], config) do
process_shared t, config.update_commands [{:app,h}|&1]
defp process_shared(["-pz", h|t], config) do
Enum.each Path.wildcard(Path.expand(h)), Code.append_path(&1)
process_shared t, config
end
defp process_shared(["--app", h|t], config) do
process_shared t, config.update_commands [{:app, h}|&1]
end
defp process_shared(["--no-halt"|t], config) do
process_shared t, config.halt(false)
end
defp process_shared(["-e",h|t], config) do
process_shared t, config.update_commands [{:eval,h}|&1]
defp process_shared(["-e", h|t], config) do
process_shared t, config.update_commands [{:eval, h}|&1]
end
defp process_shared(["-pa",h|t], config) do
Enum.each Path.wildcard(Path.expand(h)), Code.prepend_path(&1)
process_shared t, config
defp process_shared(["-r", h|t], config) do
process_shared t, config.update_commands [{:require, h}|&1]
end
defp process_shared(["-pz",h|t], config) do
Enum.each Path.wildcard(Path.expand(h)), Code.append_path(&1)
process_shared t, config
defp process_shared(["-pr", h|t], config) do
process_shared t, config.update_commands [{:parallel_require, h}|&1]
end
defp process_shared(["-r",h|t], config) do
process_shared t, Enum.reduce(Path.wildcard(h), config, fn path, config ->
config.update_commands [{:require,path}|&1]
end)
end
defp process_shared(["-pr",h|t], config) do
process_shared t, config.update_commands [{:parallel_require,h}|&1]
end
defp process_shared([erl,_|t], config) when erl in ["--erl", "--sname", "--remsh", "--name"] do
defp process_shared([erl, _|t], config) when erl in ["--erl", "--sname", "--name", "--cookie"] do
process_shared t, config
end
@@ -150,18 +151,16 @@ defmodule Kernel.CLI do
{ config, t }
end
defp process_argv(["--compile"|t], config) do
defp process_argv(["+compile"|t], config) do
process_compiler t, config
end
defp process_argv(["-S",h|t], config) do
exec = System.find_executable(h)
if exec do
{ config.update_commands([{:require,exec}|&1]), t }
else
IO.puts(:stderr, "Could not find executable #{h}")
System.halt(1)
end
defp process_argv(["+iex"|t], config) do
process_iex t, config
end
defp process_argv(["-S", h|t], config) do
{ config.update_commands([{:script, h}|&1]), t }
end
defp process_argv([h|t] = list, config) do
@@ -169,7 +168,7 @@ defmodule Kernel.CLI do
"-" <> _ ->
shared_option? list, config, process_argv(&1, &2)
_ ->
{ config.update_commands([{:require,h}|&1]), t }
{ config.update_commands([{:file, h}|&1]), t }
end
end
@@ -183,20 +182,24 @@ defmodule Kernel.CLI do
{ config, t }
end
defp process_compiler(["-o",h|t], config) do
defp process_compiler(["-o", h|t], config) do
process_compiler t, config.output(h)
end
defp process_compiler(["--no-docs"|t], config) do
process_compiler t, config.update_compiler_options([{:docs,false}|&1])
process_compiler t, config.update_compiler_options([{:docs, false}|&1])
end
defp process_compiler(["--no-debug-info"|t], config) do
process_compiler t, config.update_compiler_options([{:debug_info,false}|&1])
process_compiler t, config.update_compiler_options([{:debug_info, false}|&1])
end
defp process_compiler(["--ignore-module-conflict"|t], config) do
process_compiler t, config.update_compiler_options([{:ignore_module_conflict,true}|&1])
process_compiler t, config.update_compiler_options([{:ignore_module_conflict, true}|&1])
end
defp process_compiler(["--warnings-as-errors"|t], config) do
process_compiler t, config.update_compiler_options([{:warnings_as_errors, true}|&1])
end
defp process_compiler([h|t] = list, config) do
@@ -204,51 +207,145 @@ 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
defp process_compiler([], config) do
{ config.update_commands([{:compile,config.compile}|&1]), [] }
{ config.update_commands([{:compile, config.compile}|&1]), [] }
end
# Process iex options
defp process_iex(["--"|t], config) 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
defp process_iex(["-S", h|t], config) do
{ config.update_commands([{:script, h}|&1]), t }
end
defp process_iex([h|t] = list, config) do
case h do
"-" <> _ ->
shared_option? list, config, process_iex(&1, &2)
_ ->
{ config.update_commands([{:file, h}|&1]), t }
end
end
defp process_iex([], config) do
{ config, [] }
end
# Process commands
defp process_command({:cookie, h}, _config) do
if Node.alive? do
Node.set_cookie(binary_to_atom(h))
:ok
else
{ :error, "--cookie : Cannot set cookie if the node is not alive (set --name or --sname)" }
end
end
defp process_command({:eval, expr}, _config) when is_binary(expr) do
Code.eval(expr, [])
Code.eval_string(expr, [])
:ok
end
defp process_command({:app, app}, _config) when is_binary(app) do
case Application.Behaviour.start(binary_to_atom(app)) do
{ :error, reason } ->
IO.puts(:stderr, "Could not start application #{app}: #{inspect reason}")
System.halt(1)
{ :error, "--app : Could not start application #{app}: #{inspect reason}" }
:ok ->
:ok
end
end
defp process_command({:require, file}, _config) when is_binary(file) do
Code.require_file(file)
defp process_command({:script, file}, _config) when is_binary(file) do
if exec = find_elixir_executable(file) do
Code.require_file(exec)
:ok
else
{ :error, "-S : Could not find executable #{file}" }
end
end
defp process_command({:file, file}, _config) when is_binary(file) do
if :filelib.is_regular(file) do
Code.require_file(file)
:ok
else
{ :error, "No file named #{file}" }
end
end
defp process_command({:require, pattern}, _config) when is_binary(pattern) do
files = Path.wildcard(pattern)
files = Enum.uniq(files)
files = Enum.filter files, :filelib.is_regular(&1)
if files != [] do
Enum.map files, Code.require_file(&1)
:ok
else
{ :error, "-r : No files matched pattern #{pattern}" }
end
end
defp process_command({:parallel_require, pattern}, _config) when is_binary(pattern) do
files = Path.wildcard(pattern)
files = Enum.uniq(files)
files = Enum.filter files, File.regular?(&1)
Kernel.ParallelRequire.files(files)
files = Enum.filter files, :filelib.is_regular(&1)
if files != [] do
Kernel.ParallelRequire.files(files)
:ok
else
{ :error, "-pr : No files matched pattern #{pattern}" }
end
end
defp process_command({:compile, patterns}, config) do
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)
Code.compiler_options(config.compiler_options)
Kernel.ParallelCompiler.files_to_path(files, config.output,
fn file -> IO.puts "Compiled #{file}" end)
if files != [] do
Code.compiler_options(config.compiler_options)
Kernel.ParallelCompiler.files_to_path(files, config.output,
fn file -> IO.puts "Compiled #{file}" end)
else
{ :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
+15 -3
View File
@@ -1,3 +1,5 @@
defexception Kernel.CompilationError, message: "compilation failed"
defmodule Kernel.ParallelCompiler do
alias :orddict, as: OrdDict
@@ -15,6 +17,10 @@ defmodule Kernel.ParallelCompiler do
the current file stops being compiled until the dependency is
resolved.
If there is any error during compilation or if warnings_as_errors
is set to true and there is a warning, this function will fail
with an exception.
A callback that is invoked every time a file is compiled
with its name can be optionally given as argument.
"""
@@ -32,8 +38,14 @@ defmodule Kernel.ParallelCompiler do
defp spawn_compilers(files, path, callback) do
Code.ensure_loaded(Kernel.ErrorHandler)
:elixir_code_server.cast(:reset_warnings)
schedulers = max(:erlang.system_info(:schedulers_online), 2)
spawn_compilers(files, path, callback, [], [], schedulers, [])
result = spawn_compilers(files, path, callback, [], [], schedulers, [])
case :elixir_code_server.call(:compilation_status) do
:ok -> result
:error -> raise Kernel.CompilationError, [], []
end
end
# We already have 4 currently running, don't spawn new ones
@@ -64,7 +76,7 @@ defmodule Kernel.ParallelCompiler do
end
end
spawn_compilers(t, output, callback, waiting, [{child,h}|queued], schedulers, result)
spawn_compilers(t, output, callback, waiting, [{child, h}|queued], schedulers, result)
end
# No more files, nothing waiting, queue is empty, we are done
@@ -138,4 +150,4 @@ defmodule Kernel.ParallelCompiler do
end
end
end
end
end
+11 -1
View File
@@ -72,7 +72,7 @@ defmodule Kernel.RecordRewriter do
case record_fields(module, record) do
{ fields, optimizable } ->
opt_call =
if List.member?(optimizable, { function, length(args) + 1 }) do
if :lists.member({ function, length(args) + 1 }, optimizable) do
case record_field_info(function) do
{ kind, field } ->
if index = Enum.find_index(fields, field == &1) do
@@ -350,6 +350,10 @@ defmodule Kernel.RecordRewriter do
vars
end
defp join_dict([]) do
[]
end
defp join_dict([{ _, dict, _ }|t]) do
join_dict(t, dict)
end
@@ -363,6 +367,8 @@ defmodule Kernel.RecordRewriter do
{ :ok, _ } -> :orddict.store(key, nil, acc)
:error -> :orddict.erase(key, acc)
end
{ key, nil }, acc ->
:orddict.store(key, nil, acc)
end
join_dict(t, other)
@@ -372,6 +378,10 @@ defmodule Kernel.RecordRewriter do
other
end
defp join_result([]) do
[]
end
defp join_result([{ _, _, res }|t]) do
join_result(t, res)
end
+189 -26
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 ]
"""
@@ -247,7 +247,7 @@ defmodule Kernel.SpecialForms do
for example:
iex> import List
...> flatten([1,[2],3])
...> flatten([1, [2], 3])
[1,2,3]
## Selector
@@ -305,6 +305,19 @@ defmodule Kernel.SpecialForms do
also accepts `as:` as an option so it automatically sets up
an alias. Please check `alias` for more information.
## Warnings
If you import a module and you don't use any of the imported
functions or macros from this module, Elixir is going to issue
a warning implying the import is not being used.
In case the import is generated automatically by a macro,
Elixir won't emit any warnings though, since the import
was not explicitly defined.
Both warning behaviors could be changed by explicitily
setting the `:warn` option to true or false.
## Ambiguous function/macro names
If two modules `A` and `B` are imported and they both contain
@@ -335,6 +348,11 @@ defmodule Kernel.SpecialForms do
"""
defmacro __FILE__
@doc """
Returns the current directory as a binary.
"""
defmacro __DIR__
@doc """
Allows you to get the representation of any expression.
@@ -346,18 +364,17 @@ defmodule Kernel.SpecialForms do
## Explanation
Any Elixir code can be represented using Elixir data structures.
The building block of Elixir homoiconicity is a tuple with three
elements, for example:
The building block of Elixir macros is a tuple with three elements,
for example:
{ :sum, 1, [1, 2, 3] }
{ :sum, [], [1, 2, 3] }
The tuple above represents a function call to sum passing 1, 2 and
3 as arguments. The tuple elements are:
* The first element of the tuple is always an atom or
another tuple in the same representation;
* The second element of the tuple is always an integer
representing the line number;
* The second element of the tuple represents metadata;
* The third element of the tuple are the arguments for the
function call. The third argument may be an atom, which is
usually a variable (or a local call);
@@ -370,6 +387,9 @@ defmodule Kernel.SpecialForms do
* `:location` - When set to `:keep`, keeps the current line and file on quotes.
Read the Stacktrace information section below for more information;
* `:hygiene` - Allows a developer to disable hygiene selectively;
* `:context` - Sets the context resolution happens at;
* `:binding` - Passes a binding to the macro. Whenever a binding is given,
unquote is automatically disabled;
## Macro literals
@@ -379,21 +399,41 @@ defmodule Kernel.SpecialForms do
:sum #=> Atoms
1 #=> Integers
2.0 #=> Floats
[1,2] #=> Lists
[1, 2] #=> Lists
"binaries" #=> Binaries
{key, value} #=> Tuple with two elements
## Hygiene
## Hygiene and context
Elixir macros are hygienic via means of deferred resolution.
This means variables, aliases and imports defined inside the
quoted refer to the context that defined the macro and not
the context where the macro is expanded.
This means aliases and imports defined inside the quoted refer
to the context that defined the macro and not the context
where the macro is expanded.
For this mechanism to work, every quoted code is attached
to a context. Consider the following example:
Furthermore, variables inside quote are also hygienic: a
variable defined in a macro cannot affect the variables where
the macro is expanded.
defmodule ContextSample do
def hello do
quote do: world
end
end
ContextSample.hello
#=> {:world,[],ContextSample}
Notice how the third element of the returned tuple is the
module name. This means that the variable is associated to the
ContextSample module and only code generated by this module
will be able to access that particular `world` variable.
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
context and we are going to explore each of them in the following
subsections.
### Hygiene in variables
@@ -591,6 +631,79 @@ defmodule Kernel.SpecialForms do
code as if it was defined inside `GenServer.Behaviour` file, in
particular, the macro `__FILE__` and exceptions happening inside
the quote will always point to `GenServer.Behaviour` file.
## Binding and unquote fragments
Elixir quote/unquote mechanisms provides a functionality called
unquote fragments. Unquote fragments provide an easy to generate
functions on the fly. Consider this example:
kv = [foo: 1, bar: 2]
Enum.each kv, fn { k, v } ->
def unquote(k)(), do: unquote(v)
end
In the example above, we have generated the function `foo/0` and
`bar/0` dynamically. Now, imagine that, we want to convert this
functionality into a macro:
defmacro defkv(kv) do
Enum.each kv, fn { k, v } ->
quote do
def unquote(k)(), do: unquote(v)
end
end
end
We can invoke this macro as:
defkv [foo: 1, bar: 2]
However, we can't invoke it as follows:
kv = [foo: 1, bar: 2]
defkv kv
This is because the macro is expecting its arguments to be a
key-value at **compilation** time. Since in the example above
we are passing the representation of the variable `kv`, our
code fails.
This is actually a common pitfall when developing macros. In
practive, we want to avoid doing work at compilation time as
much as we can. That said, let's attempt to improve our macro:
defmacro defkv(kv) do
quote do
Enum.each unquote(kv), fn { k, v } ->
def unquote(k)(), do: unquote(v)
end
end
end
If you try to run our new macro, you will notice it won't
even compile, complaining that the variables `k` and `v`
do not exist. This is because of the ambiguity: `unquote(k)`
can either be an unquote fragment, as previously, or a regular
unquote as in `unquote(kv)`.
One solution for this problem is to disable unquoting in the
macro, however, doing that would make it impossible to inject
`kv` representation into the tree. That's when the `:binding`
option comes to the rescue. By using `:binding`, we can
automatically disable unquoting while still injecting the
desired variables into the tree:
defmacro defkv(kv) do
quote binding: [kv: kv] do
Enum.each kv, fn { k, v } ->
def unquote(k)(), do: unquote(v)
end
end
end
In fact, the `:binding` option is recommended every time one
desires to inject a value into the quote.
"""
defmacro quote(opts, block)
@@ -605,6 +718,10 @@ defmodule Kernel.SpecialForms do
@doc """
Defines a variable in the given context.
If the context is false, it is not stored in any particular
context and the variable is not shared in between clauses.
Check `quote/2` for more information.
"""
defmacro var!(var, context)
@@ -616,10 +733,50 @@ defmodule Kernel.SpecialForms do
"""
defmacro alias!(alias)
@doc """
Unquotes the given expression from inside a macro.
## Examples
Imagine the situation you have a variable `name` and
you want to inject it inside some quote. The first attempt
would be:
value = 13
quote do: sum(1, value, 3)
Which would then return:
{ :sum, [], [1, { :value, [], quoted }, 3] }
Which is not the expected result. For this, we use unquote:
value = 13
quote do: sum(1, unquote(value), 3)
#=> { :sum, [], [1, 13, 3] }
"""
name = :unquote
defmacro unquote(name)(expr)
@doc """
Unquotes the given list expanding its arguments. Similar
to unquote.
## Examples
values = [2, 3, 4]
quote do: sum(1, unquote_splicing(values), 5)
#=> { :sum, [], [1, 2, 3, 4, 5] }
"""
name = :unquote_splicing
defmacro unquote(name)(expr)
@doc """
List comprehensions allow you to quickly build a list from another list:
iex> lc n inlist [1,2,3,4], do: n * 2
iex> lc n inlist [1, 2, 3, 4], do: n * 2
[2,4,6,8]
A comprehension accepts many generators and also filters. Generators
@@ -627,33 +784,33 @@ defmodule Kernel.SpecialForms do
to loop lists and bitstrings:
# A list generator:
iex> lc n inlist [1,2,3,4], do: n * 2
iex> lc n inlist [1, 2, 3, 4], do: n * 2
[2,4,6,8]
# A bit string generator:
iex> lc <<n>> inbits <<1,2,3,4>>, do: n * 2
iex> lc <<n>> inbits <<1, 2, 3, 4>>, do: n * 2
[2,4,6,8]
# A generator from a variable:
iex> list = [1,2,3,4]
iex> list = [1, 2, 3, 4]
...> lc n inlist list, do: n * 2
[2,4,6,8]
# A comprehension with two generators
iex> lc x inlist [1,2], y inlist [2,3], do: x*y
iex> lc x inlist [1, 2], y inlist [2, 3], do: x*y
[2,3,4,6]
Filters can also be given:
# A comprehension with a generator and a filter
iex> lc n inlist [1,2,3,4,5,6], rem(n, 2) == 0, do: n
iex> lc n inlist [1, 2, 3, 4, 5, 6], rem(n, 2) == 0, do: n
[2,4,6]
Bit string generators are quite useful when you need to
organize bit string streams:
iex> pixels = <<213,45,132,64,76,32,76,0,0,234,32,15>>
iex> lc <<r::8,g::8,b::8>> inbits pixels, do: {r,g,b}
iex> pixels = <<213, 45, 132, 64, 76, 32, 76, 0, 0, 234, 32, 15>>
iex> lc <<r::8, g::8, b::8>> inbits pixels, do: {r, g, b}
[{213,45,132},{64,76,32},{76,0,0},{234,32,15}]
"""
@@ -677,7 +834,7 @@ defmodule Kernel.SpecialForms do
and should not be invoked directly:
iex> quote do: (1; 2; 3)
{ :__block__, [], [1,2,3] }
{ :__block__, [], [1, 2, 3] }
"""
defmacro __block__(args)
@@ -689,7 +846,7 @@ defmodule Kernel.SpecialForms do
it belonged to another file.
quote location: :keep, do: 1
#=> { :__scope__, 1,[[file: "iex"],[do: 1]] }
#=> { :__scope__, [line: 1], [[file: "iex"],[do: 1]] }
Check `quote/1` for more information.
"""
@@ -732,4 +889,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
+99 -73
View File
@@ -1,6 +1,6 @@
defmodule Kernel.Typespec do
@moduledoc """
Holds macros and functions for working with typespecs.
Provides macros and functions for working with typespecs.
The attributes `@type`, `@opaque`, `@typep`, `@spec` and
`@callback` available in modules are handled by the equivalent
@@ -8,20 +8,19 @@ defmodule Kernel.Typespec do
## Defining a type
@type type_name :: type
@typep type_name :: type
@opaque type_name :: type
@type type_name :: type
@typep type_name :: type
@opaque type_name :: type
For more details, see documentation for deftype, deftypep and defopaque in
Kernel.Typespec
For more details, see documentation for `deftype`, `deftypep` and `defopaque`
below.
## Defining a specification
@spec function_name(type, type) :: type
@callback function_name(type, type) :: type
@spec function_name(type, type) :: type
@callback function_name(type, type) :: type
For more details, see documentation for defspec and defcallback in
Kernel.Typespec
For more details, see documentation for `defspec` and `defcallback` below.
## Types
@@ -30,9 +29,10 @@ defmodule Kernel.Typespec do
Most of the built-in types provided in Erlang (for example, `pid()`)
are expressed the same way: `pid()` or simply `pid`. Parametrized types
are also supported: `list(integer())` and so are remote types: `Enum.t`.
are also supported (`list(integer())`) and so are remote types (`Enum.t`).
Certain data type shortcuts ([...], <<>> and {...}) are supported as well.
Certain data type shortcuts (`[...]`, `<<>>` and `{...}`) are supported as
well.
Main differences lie in how bit strings and functions are defined:
@@ -46,26 +46,20 @@ defmodule Kernel.Typespec do
<<_ :: _ * 8>>
### Functions
### Anonymous functions
Any function:
Any anonymous function:
(fun(...) -> any)
or
((...) -> any)
or
(... -> any)
Function with arity of zero:
Anonymous function with arity of zero:
(fun() -> type)
or
(() -> type)
Function with some arity:
Anonymous function with some arity:
(fun(type, type) -> type)
or
((type, type) -> type)
or
(type, type -> type)
@@ -87,7 +81,7 @@ defmodule Kernel.Typespec do
@doc """
Defines a type.
This macro is the one responsible to handle the attribute @type.
This macro is the one responsible for handling the attribute `@type`.
## Examples
@@ -102,7 +96,7 @@ defmodule Kernel.Typespec do
@doc """
Defines an opaque type.
This macro is the one responsible to handle the attribute @opaque.
This macro is the one responsible for handling the attribute `@opaque`.
## Examples
@@ -117,7 +111,7 @@ defmodule Kernel.Typespec do
@doc """
Defines a private type.
This macro is the one responsible to handle the attribute @typep.
This macro is the one responsible for handling the attribute `@typep`.
## Examples
@@ -132,7 +126,7 @@ defmodule Kernel.Typespec do
@doc """
Defines a spec.
This macro is the one responsible to handle the attribute @spec.
This macro is the one responsible for handling the attribute `@spec`.
## Examples
@@ -147,7 +141,7 @@ defmodule Kernel.Typespec do
@doc """
Defines a callback.
This macro is the one responsible to handle the attribute @callback.
This macro is the one responsible for handling the attribute `@callback`.
## Examples
@@ -194,7 +188,7 @@ defmodule Kernel.Typespec do
end
@doc """
Returns true if the current module defines a given type
Returns `true` if the current module defines a given type
(private, opaque or not). This function is only available
for modules being compiled.
"""
@@ -205,7 +199,7 @@ defmodule Kernel.Typespec do
end
@doc """
Returns true if the current module defines a given spec.
Returns `true` if the current module defines a given spec.
This function is only available for modules being compiled.
"""
def defines_spec?(module, name, arity) do
@@ -214,7 +208,7 @@ defmodule Kernel.Typespec do
end
@doc """
Returns true if the current module defines a callback.
Returns `true` if the current module defines a callback.
This function is only available for modules being compiled.
"""
def defines_callback?(module, name, arity) do
@@ -225,7 +219,7 @@ defmodule Kernel.Typespec do
@doc """
Converts a spec clause back to Elixir AST.
"""
def spec_to_ast(name, { :type, line, :fun, [{:type, _, :product, args},result] }) do
def spec_to_ast(name, { :type, line, :fun, [{:type, _, :product, args}, result] }) do
args = lc arg inlist args, do: typespec_to_ast(arg)
{ :::, [line: line], [{ name, [line: line], args }, typespec_to_ast(result)] }
end
@@ -262,13 +256,13 @@ defmodule Kernel.Typespec do
end
@doc """
Returns all types available from the beam.
Returns all types available from the module's beam code.
It is returned as a list of tuples where the first
element is the type (`:typep`, `:type` and `:opaque`).
The module has to have a corresponding beam file
on the file system.
The module has to have a corresponding beam file on the disk which can be
located by the runtime system.
"""
def beam_types(module) do
case abstract_code(module) do
@@ -279,7 +273,7 @@ defmodule Kernel.Typespec do
lc { :attribute, _, kind, { name, _, args } = type } inlist abstract_code, kind in [:opaque, :type] do
cond do
kind == :opaque -> { :opaque, type }
List.member?(exported_types, { name, length(args) }) -> { :type, type }
:lists.member({ name, length(args) }, exported_types) -> { :type, type }
true -> { :typep, type }
end
end
@@ -289,26 +283,26 @@ defmodule Kernel.Typespec do
end
@doc """
Returns all specs available from the beam.
Returns all specs available from the module's beam code.
It is returned as a list of tuples where the first
element is spec name and arity and the second is the spec.
The module has to have a corresponding beam file
on the file system.
The module has to have a corresponding beam file on the disk which can be
located by the runtime system.
"""
def beam_specs(module) do
from_abstract_code(module, :spec)
end
@doc """
Returns all callbacks available from the beam.
Returns all callbacks available from the module's beam code.
It is returned as a list of tuples where the first
element is spec name and arity and the second is the spec.
The module has to have a corresponding beam file
on the file system.
The module has to have a corresponding beam file on the disk which can be
located by the runtime system.
"""
def beam_callbacks(module) do
from_abstract_code(module, :callback)
@@ -333,9 +327,9 @@ defmodule Kernel.Typespec do
end
defp abstract_code_beam(module) when is_atom(module) do
case :code.which(module) do
:non_existing -> module
file -> file
case :code.get_object_code(module) do
{ ^module, beam, _filename } -> beam
:error -> module
end
end
@@ -350,14 +344,13 @@ defmodule Kernel.Typespec do
do_deftype(kind, type, definition, caller)
end
def deftype(kind, {name, _meta, args} = type, caller) when
is_atom(name) and
not is_list(args) do
def deftype(kind, {name, _meta, args} = type, caller)
when is_atom(name) and not is_list(args) do
do_deftype(kind, type, { :term, [line: caller.line], nil }, caller)
end
def deftype(_kind, other, _caller) do
type_spec = Macro.to_binary(other)
type_spec = Macro.to_string(other)
raise ArgumentError, message: "invalid type specification #{type_spec}"
end
@@ -398,7 +391,7 @@ defmodule Kernel.Typespec do
end
def defspec(_type, other, _caller) do
spec = Macro.to_binary(other)
spec = Macro.to_string(other)
raise ArgumentError, message: "invalid function type specification #{spec}"
end
@@ -423,6 +416,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
@@ -441,17 +445,17 @@ defmodule Kernel.Typespec do
fn(arg, expr) -> { :|, [line: line], [expr, arg] } end
end
defp typespec_to_ast({ :type, line, :fun, [{:type, _, :product, args},result] }) do
defp typespec_to_ast({ :type, line, :fun, [{:type, _, :product, args}, result] }) do
args = lc arg inlist args, do: typespec_to_ast(arg)
{ :"->", [line: line], [{args, typespec_to_ast(result)}] }
{ :->, [line: line], [{args, [line: line], typespec_to_ast(result)}] }
end
defp typespec_to_ast({ :type, line, :fun, [args, result] }) do
{ :"->", [line: line], [{[typespec_to_ast(args)], typespec_to_ast(result)}] }
{ :->, [line: line], [{[typespec_to_ast(args)], [line: line], typespec_to_ast(result)}] }
end
defp typespec_to_ast({ :type, line, :fun, [] }) do
typespec_to_ast({ :type, line, :fun, [{:type, line, :any}, {:type,line,:any, []} ] })
typespec_to_ast({ :type, line, :fun, [{:type, line, :any}, {:type, line, :any, []} ] })
end
defp typespec_to_ast({ :type, line, :range, [left, right] }) do
@@ -465,16 +469,16 @@ defmodule Kernel.Typespec do
defp typespec_to_ast({ :var, line, var }) do
var =
case atom_to_binary(var) do
<<"_", c :: [binary, size(1)], rest :: binary>> ->
binary_to_atom("_#{String.downcase(c)}#{rest}")
<<c :: [binary, size(1)], rest :: binary>> ->
binary_to_atom("#{String.downcase(c)}#{rest}")
end
case atom_to_binary(var) do
<<"_", c :: [binary, size(1)], rest :: binary>> ->
binary_to_atom("_#{String.downcase(c)}#{rest}")
<<c :: [binary, size(1)], rest :: binary>> ->
binary_to_atom("#{String.downcase(c)}#{rest}")
end
{ 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
@@ -483,7 +487,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,14 +527,14 @@ defmodule Kernel.Typespec do
end
# Handle unions
defp typespec({ :|, meta, [_,_] } = exprs, vars, caller) do
exprs = :lists.reverse(collect_union(exprs))
defp typespec({ :|, meta, [_, _] } = exprs, vars, caller) do
exprs = Enum.reverse(collect_union(exprs))
union = lc e inlist exprs, do: typespec(e, vars, caller)
{ :type, line(meta), :union, union }
end
# Handle binaries
defp typespec({:<<>>, meta, []}, _,_) do
defp typespec({:<<>>, meta, []}, _, _) do
{:type, line(meta), :binary, [{:integer, line(meta), 0}, {:integer, line(meta), 0}]}
end
@@ -559,12 +562,13 @@ 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)
defp typespec({:->, meta, [{[{:fun, _, arguments}], cmeta, return}]}, vars, caller) when is_list(arguments) do
IO.warn "(fun(...) -> ...) in typespecs is deprecated, please drop fun() and " <>
"write (... -> ...) instead. #{Exception.format_stacktrace(caller.stacktrace)}"
typespec({:->, meta, [{arguments, cmeta, return}]}, vars, caller)
end
defp typespec({:->, meta, [{arguments, return}]}, vars, caller) when is_list(arguments) do
defp typespec({:->, meta, [{arguments, _, return}]}, vars, caller) when is_list(arguments) do
args = fn_args(meta, arguments, return, vars, caller)
{ :type, line(meta), :fun, args }
end
@@ -610,14 +614,13 @@ defmodule Kernel.Typespec do
end
# Handle blocks
defp typespec({:__block__, _meta, [arg]}, vars, caller) do
typespec(arg, vars, caller)
end
# Handle variables or local calls
defp typespec({name, meta, atom}, vars, caller) when is_atom(atom) do
if List.member?(vars, name) do
if :lists.member(name, vars) do
{ :var, line(meta), name }
else
typespec({name, meta, []}, vars, caller)
@@ -666,8 +669,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
@@ -685,9 +691,14 @@ 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,[]}] -> []
[{:type, _, :any}, {:type, _, :any, []}] -> []
x -> x
end
end
@@ -704,4 +715,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
+66 -29
View File
@@ -2,7 +2,7 @@ defmodule Keyword do
@moduledoc """
A keyword is a list of tuples where the first element
of the tuple is an atom and the second element can be
any value.
any value.
A keyword may have duplicated keys, so it is not strictly
a dictionary. However most of the functions in this module
@@ -36,10 +36,7 @@ defmodule Keyword do
"""
@spec keyword?(term) :: boolean
def keyword?([{ key, _value } | rest]) when is_atom(key) do
case atom_to_list(key) do
'Elixir-' ++ _ -> false
_ -> keyword?(rest)
end
keyword?(rest)
end
def keyword?([]), do: true
@@ -59,7 +56,7 @@ defmodule Keyword do
## Examples
iex> Keyword.new [{:b,1},{:a,2}]
iex> Keyword.new([{:b, 1}, {:a, 2}])
[a: 2, b: 1]
"""
@@ -77,7 +74,7 @@ defmodule Keyword do
## Examples
iex> Enum.sort Keyword.new [:a, :b], fn x -> {x,x} end
iex> Keyword.new([:a, :b], fn (x) -> {x, x} end) |> Enum.sort
[a: :a, b: :b]
"""
@@ -100,11 +97,11 @@ defmodule Keyword do
## Examples
iex> Keyword.get [a: 1], :a
iex> Keyword.get([a: 1], :a)
1
iex> Keyword.get [a: 1], :b
iex> Keyword.get([a: 1], :b)
nil
iex> Keyword.get [a: 1], :b, 3
iex> Keyword.get([a: 1], :b, 3)
3
"""
@@ -118,31 +115,53 @@ defmodule Keyword do
end
@doc """
Gets the value for specific key. If key does not exist,
Fetchs the value for specific key and return it in a tuple.
If the key does not exist, returns `:error`.
## Examples
iex> Keyword.fetch([a: 1], :a)
{ :ok, 1 }
iex> Keyword.fetch([a: 1], :b)
:error
"""
@spec fetch(t, key) :: value
def fetch(keywords, key) when is_atom(key) do
case :lists.keyfind(key, 1, keywords) do
{ ^key, value } -> { :ok, value }
false -> :error
end
end
@doc """
Fetches the value for specific key. If key does not exist,
an error is raised.
## Examples
iex> Keyword.get! [a: 1], :a
iex> Keyword.fetch!([a: 1], :a)
1
iex> Keyword.get! [a: 1], :b
iex> Keyword.fetch!([a: 1], :b)
** (KeyError) key not found: :b
"""
@spec get!(t, key) :: value | no_return
def get!(keywords, key) when is_atom(key) do
@spec fetch!(t, key) :: value | no_return
def fetch!(keywords, key) when is_atom(key) do
case :lists.keyfind(key, 1, keywords) do
{ ^key, value } -> value
false -> raise(KeyError, key: key)
end
end
end
@doc """
Gets all values for a specific key.
## Examples
iex> Keyword.get_values [a: 1, a: 2], :a
iex> Keyword.get_values([a: 1, a: 2], :a)
[1,2]
"""
@@ -157,7 +176,7 @@ defmodule Keyword do
## Examples
iex> Keyword.keys [a: 1, b: 2]
iex> Keyword.keys([a: 1, b: 2])
[:a,:b]
"""
@@ -171,7 +190,7 @@ defmodule Keyword do
## Examples
iex> Keyword.values [a: 1, b: 2]
iex> Keyword.values([a: 1, b: 2])
[1,2]
"""
@@ -188,9 +207,9 @@ defmodule Keyword do
## Examples
iex> Keyword.delete [a: 1, b: 2], :a
iex> Keyword.delete([a: 1, b: 2], :a)
[b: 2]
iex> Keyword.delete [b: 2], :a
iex> Keyword.delete([b: 2], :a)
[b: 2]
"""
@@ -199,6 +218,23 @@ defmodule Keyword do
lc { k, _ } = tuple inlist keywords, key != k, do: tuple
end
@doc """
Deletes the first entry in the keyword list for a specific key.
If the key does not exist, returns the keyword list unchanged.
## Examples
iex> Keyword.delete_first([a: 1, b: 2, a: 3], :a)
[b: 2, a: 3]
iex> Keyword.delete_first([b: 2], :a)
[b: 2]
"""
@spec delete_first(t, key) :: t
def delete_first(keywords, key) when is_atom(key) do
:lists.keydelete(key, 1, keywords)
end
@doc """
Puts the given `value` under `key`.
@@ -207,7 +243,7 @@ defmodule Keyword do
## Examples
iex> Keyword.put [a: 1, b: 2], :a, 3
iex> Keyword.put([a: 1, b: 2], :a, 3)
[a: 3, b: 2]
"""
@@ -222,7 +258,7 @@ defmodule Keyword do
## Examples
iex> Keyword.put_new [a: 1, b: 2], :a, 3
iex> Keyword.put_new([a: 1, b: 2], :a, 3)
[a: 1, b: 2]
"""
@@ -230,7 +266,7 @@ defmodule Keyword do
def put_new(keywords, key, value) when is_atom(key) do
case :lists.keyfind(key, 1, keywords) do
{ ^key, _ } -> keywords
false -> [{key,value}|keywords]
false -> [{key, value}|keywords]
end
end
@@ -240,7 +276,7 @@ defmodule Keyword do
## Examples
iex> Keyword.equal? [a: 1, b: 2], [b: 2, a: 1]
iex> Keyword.equal?([a: 1, b: 2], [b: 2, a: 1])
true
"""
@@ -255,7 +291,7 @@ defmodule Keyword do
## Examples
iex> Enum.sort Keyword.merge [a: 1, b: 2], [a: 3, d: 4]
iex> Keyword.merge([a: 1, b: 2], [a: 3, d: 4]) |> Enum.sort
[a: 3, b: 2, d: 4]
"""
@@ -270,9 +306,9 @@ defmodule Keyword do
## Examples
iex> Keyword.merge [a: 1, b: 2], [a: 3, d: 4], fn _k, v1, v2 ->
iex> Keyword.merge([a: 1, b: 2], [a: 3, d: 4], fn (_k, v1, v2) ->
...> v1 + v2
iex> end
iex> end)
[a: 4, b: 2, d: 4]
"""
@@ -313,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
@@ -342,7 +379,7 @@ defmodule Keyword do
[a: 1, b: 11]
"""
@spec update(t, key, value, (value -> value)) :: t
@spec update(t, key, value, (value -> value)) :: t
def update([{key, value}|keywords], key, _initial, fun) do
[{key, fun.(value)}|delete(keywords, key)]
end
+62 -39
View File
@@ -10,12 +10,14 @@ defmodule List do
first argument.
"""
@compile :inline_list_funcs
@doc """
Given a list of lists, concatenates the sublists into a single list.
## Examples
iex> List.concat [[1,[2],3], [4], [5,6]]
iex> List.concat([[1, [2], 3], [4], [5, 6]])
[1,[2],3,4,5,6]
"""
@@ -32,7 +34,7 @@ defmodule List do
## Examples
iex> List.concat [1,2,3], [4,5,6]
iex> List.concat([1, 2, 3], [4, 5, 6])
[1,2,3,4,5,6]
"""
@@ -47,7 +49,7 @@ defmodule List do
## Examples
iex> List.delete([1,2,3], 1)
iex> List.delete([1, 2, 3], 1)
[2,3]
"""
@@ -60,10 +62,10 @@ defmodule List do
## Examples
iex> List.duplicate "hello", 3
iex> List.duplicate("hello", 3)
["hello","hello","hello"]
iex> List.duplicate [1,2], 2
iex> List.duplicate([1, 2], 2)
[[1,2],[1,2]]
"""
def duplicate(elem, n) do
@@ -77,10 +79,10 @@ defmodule List do
## Examples
iex> List.flatten [1,[[2],3]]
iex> List.flatten([1, [[2], 3]])
[1,2,3]
iex> List.flatten [1,[[2],3]], [4,5]
iex> List.flatten([1, [[2], 3]], [4, 5])
[1,2,3,4,5]
"""
@@ -98,10 +100,10 @@ defmodule List do
## Examples
iex> List.foldl [5,5], 10, fn x, acc -> x + acc end
iex> List.foldl([5, 5], 10, fn (x, acc) -> x + acc end)
20
iex> List.foldl [1,2,3,4], 0, fn x, acc -> x - acc end
iex> List.foldl([1, 2, 3, 4], 0, fn (x, acc) -> x - acc end)
2
"""
@@ -115,7 +117,7 @@ defmodule List do
## Examples
iex> List.foldr [1,2,3,4], 0, fn x, acc -> x - acc end
iex> List.foldr([1, 2, 3, 4], 0, fn (x, acc) -> x - acc end)
-2
"""
@@ -128,11 +130,11 @@ defmodule List do
## Examples
iex> List.last []
iex> List.last([])
nil
iex> List.last [1]
iex> List.last([1])
1
iex> List.last [1, 2, 3]
iex> List.last([1, 2, 3])
3
"""
@@ -142,27 +144,9 @@ defmodule List do
:lists.last(list)
end
@doc """
Checks if the given `term` is included in the list.
This function simply delegates to `lists:member`
which is implemented in C for performance.
## Examples
iex> List.member? [1,2,3], 1
true
iex> List.member? [1,2,3], 0
false
"""
def member?(list, term) do
:lists.member(term, list)
end
@doc """
Receives a list of tuples and returns the first tuple
where the item at position `posistion` matches with the
where the item at position `position` matches with the
given `item`.
## Examples
@@ -183,7 +167,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
@@ -233,7 +217,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
@@ -259,7 +243,7 @@ defmodule List do
## Examples
iex> List.wrap [1,2,3]
iex> List.wrap([1, 2, 3])
[1,2,3]
"""
@@ -280,10 +264,10 @@ defmodule List do
## Examples
iex> List.zip [[1, 2], [3, 4], [5, 6]]
iex> List.zip([[1, 2], [3, 4], [5, 6]])
[{1, 3, 5}, {2, 4, 6}]
iex> List.zip [[1, 2], [3], [5, 6]]
iex> List.zip([[1, 2], [3], [5, 6]])
[{1, 3, 5}]
"""
@@ -298,10 +282,10 @@ defmodule List do
## Examples
iex> List.unzip [{1, 2}, {3, 4}]
iex> List.unzip([{1, 2}, {3, 4}])
[[1, 3], [2, 4]]
iex> List.unzip [{1, :a, "apple"}, {2, :b, "banana"}, {3, :c}]
iex> List.unzip([{1, :a, "apple"}, {2, :b, "banana"}, {3, :c}])
[[1, 2, 3], [:a, :b, :c]]
"""
@@ -309,8 +293,47 @@ defmodule List do
:lists.map tuple_to_list(&1), zip(list)
end
@doc """
Returns a list with an inserted value at specified index. Note that the index
is capped at the list length and that negative indicies wraps around at the
end of the list.
## Examples
iex> List.insert_at([1, 2, 3, 4], 2, 0)
[1, 2, 0, 3, 4]
iex> List.insert_at([1, 2, 3], 10, 0)
[1, 2, 3, 0]
iex> List.insert_at([1, 2, 3], -1, 0)
[1, 2, 0, 3]
"""
def insert_at(list, index, value) do
if index < 0 do
do_insert_at(list, length(list) + index, value)
else
do_insert_at(list, index, value)
end
end
## Helpers
# insert_at
defp do_insert_at([], _index, value) do
[ value ]
end
defp do_insert_at(list, index, value) when index <= 0 do
[ value | list ]
end
defp do_insert_at([h|t], index, value) do
[ h | do_insert_at(t, index - 1, value) ]
end
# zip
defp do_zip(list, acc) do
+15 -3
View File
@@ -27,6 +27,18 @@ defimpl List.Chars, for: List do
end
defimpl List.Chars, for: Number do
def to_char_list(integer) when is_integer(integer), do: integer_to_list(integer)
def to_char_list(float) when is_float(float), do: float_to_list(float)
end
@digits 20
@limit :math.pow(10, @digits)
def to_char_list(thing) when is_integer(thing) do
integer_to_list(thing)
end
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
end
@@ -1,4 +1,4 @@
defmodule List.Dict do
defmodule ListDict do
@moduledoc """
A Dict implementation that works on lists of two-items tuples.
@@ -7,19 +7,19 @@ defmodule List.Dict do
"""
@doc """
Returns a new `List.Dict`, i.e. an empty list.
Returns a new `ListDict`, i.e. an empty list.
"""
def new, do: []
@doc """
Creates a new `List.Dict` from the given pairs.
Creates a new `ListDict` from the given pairs.
"""
def new(pairs) do
Enum.map pairs, fn({ k, v }) -> { k, v } end
end
@doc """
Creates a new `List.Dict` from the given pairs
Creates a new `ListDict` from the given pairs
via the given transformation function.
"""
def new(list, transform) when is_function(transform) do
@@ -65,17 +65,36 @@ defmodule List.Dict do
end
end
@doc """
Returns the value under key from the given
dict in a tagged tuple, otherwise `:error`.
"""
def fetch(dict, key) do
case :lists.keyfind(key, 1, dict) do
{ ^key, value } -> { :ok, value }
false -> :error
end
end
@doc """
Returns the value under the given key
raises `KeyError` if the key does not exist.
"""
def get!(dict, key) do
def fetch!(dict, key) do
case :lists.keyfind(key, 1, dict) do
{ ^key, value } -> value
false -> raise(KeyError, key: key)
end
end
@doc """
Returns the value under the given key
from the dict as well as the dict without that key.
"""
def pop(dict, key, default // nil) do
{ get(dict, key, default), delete(dict, key) }
end
@doc """
Puts the given key-value pair in the dict.
"""
@@ -90,7 +109,7 @@ defmodule List.Dict do
def put_new(dict, key, val) do
case :lists.keyfind(key, 1, dict) do
{ ^key, _ } -> dict
false -> [{key,val}|dict]
false -> [{key, val}|dict]
end
end
@@ -112,6 +131,25 @@ defmodule List.Dict do
end
end
def split(dict, keys) do
acc = { new(), new() }
Enum.reduce dict, acc, fn({ k, v }, { take, drop }) ->
if :lists.member(k, keys) do
{ [{k, v}|take], drop }
else
{ take, [{k, v}|drop] }
end
end
end
def take(dict, keys) do
lc { k, _ } = tuple inlist dict, :lists.member(k, keys), do: tuple
end
def drop(dict, keys) do
lc { k, _ } = tuple inlist dict, not :lists.member(k, keys), do: tuple
end
@doc """
Updates the key in the dict according to the given function.
"""
@@ -144,10 +182,17 @@ defmodule List.Dict do
end
@doc """
Returns an empty `List.Dict`.
Returns an empty `ListDict`.
"""
def empty(_dict), do: []
@doc """
Check if the ListDict is equal to another ListDict.
"""
def equal?(dict, other) do
:lists.keysort(1, dict) == :lists.keysort(1, other)
end
@doc """
Converts the dict to a list.
"""
+192 -192
View File
@@ -1,4 +1,4 @@
import Kernel, except: [to_binary: 1]
import Kernel, except: [to_string: 1]
defmodule Macro do
@moduledoc """
@@ -14,10 +14,10 @@ defmodule Macro do
:===, :!==,
:==, :!=, :<=, :>=,
:&&, :||, :<>, :++, :--, :**, ://, :::, :<-, :.., :|>, :=~,
:<, :>,
:<, :>, :->,
:+, :-, :*, :/, :=, :|, :.,
:and, :or, :xor, :when, :in, :inlist, :inbits,
:<<<, :>>>, :|||, :&&&, :^^^, :~~~
:<<<, :>>>, :|||, :&&&, :^^^
]
end
@@ -26,7 +26,7 @@ defmodule Macro do
as a macro so it can be used in guard clauses.
"""
defmacro unary_ops do
[:!, :@, :^, :not, :+, :-]
[:!, :@, :^, :not, :+, :-, :~~~, :&]
end
@doc """
@@ -42,8 +42,8 @@ defmodule Macro do
extract_args(quote do: foo) == { :foo, [] }
extract_args(quote do: foo()) == { :foo, [] }
extract_args(quote do: foo(1,2,3)) == { :foo, [1,2,3] }
extract_args(quote do: 1.(1,2,3)) == :error
extract_args(quote do: foo(1, 2, 3)) == { :foo, [1, 2, 3] }
extract_args(quote do: 1.(1, 2, 3)) == :error
"""
def extract_args(expr) do
@@ -54,6 +54,10 @@ defmodule Macro do
Recursively escapes a value so it can be inserted
into a syntax tree.
One may pass `unquote: true` to `Macro.escape/2`
which leaves unquote statements unescaped, effectively
unquoting the contents on escape.
## Examples
iex> Macro.escape(:foo)
@@ -62,79 +66,12 @@ defmodule Macro do
iex> Macro.escape({ :a, :b, :c })
{ :{}, [], [:a, :b, :c] }
iex> Macro.escape({ :unquote, [], [1] }, unquote: true)
1
"""
def escape(expr) do
do_escape(expr, false)
end
defp do_escape({ { { :., meta, [left, :unquote] }, _, [expr] }, _, args }, true) do
all = [do_escape(meta, true), do_escape(left, true), expr, do_escape(args, true)]
quote do
apply :elixir_quote, :unquote, [unquote_splicing(all), __FILE__]
end
end
defp do_escape({ { :., meta, [left, :unquote] }, _, [expr] }, true) do
all = [do_escape(meta, true), do_escape(left, true), expr, nil]
quote do
apply :elixir_quote, :unquote, [unquote_splicing(all), __FILE__]
end
end
defp do_escape({ :unquote, _meta, [expr] }, true) do
expr
end
defp do_escape({ left, right }, unquote) when
(not is_tuple(left) or elem(left, 1) != :unquote_splicing) and
(not is_tuple(right) or elem(right, 1) != :unquote_splicing) do
{ do_escape(left, unquote), do_escape(right, unquote) }
end
defp do_escape({ :quote, meta, args }, true) when length(args) in 1..2 do
{ :{}, [], do_escape([:quote, meta, args], false) }
end
defp do_escape(tuple, unquote) when is_tuple(tuple) do
{ :{}, [], do_escape(tuple_to_list(tuple), unquote) }
end
defp do_escape(list, true) when is_list(list) do
do_splice(Enum.reverse(list))
end
defp do_escape(list, false) when is_list(list) do
lc item inlist list, do: do_escape(item, false)
end
defp do_escape(other, _unquote), do: other
defp do_splice([{ :|, meta, [{ :unquote_splicing, _, [left] }, right] }|t]) do
spliced = { :++, meta, [do_splice(t, [], []), left] }
{ :++, meta, [spliced, right] }
end
defp do_splice(list) do
do_splice(list, [], [])
end
defp do_splice([{ :unquote_splicing, _meta, [expr] }|t], buffer, acc) do
do_splice(t, [], do_splice_join(do_splice_join(expr, buffer), acc))
end
defp do_splice([h|t], buffer, acc) do
do_splice t, [do_escape(h, true)|buffer], acc
end
defp do_splice([], buffer, acc), do: do_splice_join(buffer, acc)
defp do_splice_join([], right), do: right
defp do_splice_join(left, []), do: left
defp do_splice_join(left, right), do: { :++, [], [left, right] }
@doc false
def escape_quoted(expr) do
do_escape(expr, true)
def escape(expr, opts // []) do
:elixir_quote.escape(expr, Keyword.get(opts, :unquote, false)) |> elem(0)
end
@doc %B"""
@@ -152,7 +89,7 @@ defmodule Macro do
## Examples
iex> Macro.unescape_binary "example\\n"
iex> Macro.unescape_binary("example\\n")
"example\n"
In the example above, we pass a string with `\n` escaped
@@ -216,7 +153,7 @@ defmodule Macro do
Only tokens that are binaries are unescaped, all others are
ignored. This function is useful when implementing your own
sigils. Check the implementation of `Kernel.__b__`
sigils. Check the implementation of `Kernel.sigil_b`
for examples.
"""
def unescape_tokens(tokens) do
@@ -231,112 +168,124 @@ defmodule Macro do
:elixir_interpolation.unescape_tokens(tokens, map)
end
@doc false
def to_binary(tree) do
IO.write "[WARNING] Macro.to_binary is deprecated, please use Macro.to_string instead\n#{Exception.format_stacktrace}"
to_string(tree)
end
@doc """
Converts the given expression to a binary.
## Examples
iex> Macro.to_binary(quote do: foo.bar(1, 2, 3))
iex> Macro.to_string(quote do: foo.bar(1, 2, 3))
"foo.bar(1, 2, 3)"
"""
def to_binary(tree)
def to_string(tree)
# Variables
def to_binary({ var, _, atom }) when is_atom(atom) do
def to_string({ var, _, atom }) when is_atom(atom) do
atom_to_binary(var, :utf8)
end
# Aliases
def to_binary({ :__aliases__, _, refs }) do
Enum.map_join(refs, ".", call_to_binary(&1))
def to_string({ :__aliases__, _, refs }) do
Enum.map_join(refs, ".", call_to_string(&1))
end
# Blocks
def to_binary({ :__block__, _, [expr] }) do
to_binary(expr)
def to_string({ :__block__, _, [expr] }) do
to_string(expr)
end
def to_binary({ :__block__, _, _ } = expr) do
block = adjust_new_lines block_to_binary(expr), "\n "
def to_string({ :__block__, _, _ } = expr) do
block = adjust_new_lines block_to_string(expr), "\n "
"(\n " <> block <> "\n)"
end
# Bits containers
def to_binary({ :<<>>, _, args }) do
"<<" <> Enum.map_join(args, ", ", to_binary(&1)) <> ">>"
def to_string({ :<<>>, _, args }) do
"<<" <> Enum.map_join(args, ", ", to_string(&1)) <> ">>"
end
# Tuple containers
def to_binary({ :{}, _, args }) do
"{" <> Enum.map_join(args, ", ", to_binary(&1)) <> "}"
def to_string({ :{}, _, args }) do
"{" <> Enum.map_join(args, ", ", to_string(&1)) <> "}"
end
# List containers
def to_binary({ :[], _, args }) do
"[" <> Enum.map_join(args, ", ", to_binary(&1)) <> "]"
def to_string({ :[], _, args }) do
"[" <> Enum.map_join(args, ", ", to_string(&1)) <> "]"
end
# Fn keyword
def to_binary({ :fn, _, [[do: { :->, _, [{_,tuple}] } = arrow]] })
def to_string({ :fn, _, [[do: { :->, _, [{_, _, tuple}] } = arrow]] })
when not is_tuple(tuple) or elem(tuple, 0) != :__block__ do
"fn " <> arrow_to_binary(arrow) <> " end"
"fn " <> arrow_to_string(arrow) <> " end"
end
def to_binary({ :fn, _, [[do: { :->, _, [_] } = block]] }) do
"fn " <> block_to_binary(block) <> "\nend"
def to_string({ :fn, _, [[do: { :->, _, [_] } = block]] }) do
"fn " <> block_to_string(block) <> "\nend"
end
def to_binary({ :fn, _, [[do: block]] }) do
block = adjust_new_lines block_to_binary(block), "\n "
def to_string({ :fn, _, [[do: block]] }) do
block = adjust_new_lines block_to_string(block), "\n "
"fn\n " <> block <> "\nend"
end
# Partial call
def to_binary({ :&, _, [num] }) do
def to_string({ :&, _, [num] }) do
"&#{num}"
end
# left -> right
def to_binary({ :->, _, _ } = arrow) do
"(" <> arrow_to_binary(arrow, true) <> ")"
def to_string({ :->, _, _ } = arrow) do
"(" <> arrow_to_string(arrow, true) <> ")"
end
# Binary ops
def to_binary({ op, _, [left, right] }) when op in binary_ops do
op_to_binary(left) <> " #{op} " <> op_to_binary(right)
def to_string({ op, _, [left, right] }) when op in binary_ops do
op_to_string(left) <> " #{op} " <> op_to_string(right)
end
# Splat when
def to_string({ :when, _, args }) do
{ left, right } = :elixir_tree_helpers.split_last(args)
"(" <> Enum.map_join(left, ", ", to_string(&1)) <> ") when " <> to_string(right)
end
# Unary ops
def to_binary({ :not, _, [arg] }) do
"not " <> to_binary(arg)
def to_string({ :not, _, [arg] }) do
"not " <> to_string(arg)
end
def to_binary({ op, _, [arg] }) when op in unary_ops do
atom_to_binary(op, :utf8) <> to_binary(arg)
def to_string({ op, _, [arg] }) when op in unary_ops do
atom_to_binary(op, :utf8) <> to_string(arg)
end
# All other calls
def to_binary({ target, _, args }) when is_list(args) do
def to_string({ target, _, args }) when is_list(args) do
{ list, last } = :elixir_tree_helpers.split_last(args)
case is_kw_blocks?(last) do
true -> call_to_binary_with_args(target, list) <> kw_blocks_to_binary(last)
false -> call_to_binary_with_args(target, args)
true -> call_to_string_with_args(target, list) <> kw_blocks_to_string(last)
false -> call_to_string_with_args(target, args)
end
end
# Two-item tuples
def to_binary({ left, right }) do
to_binary({ :{}, [], [left, right] })
def to_string({ left, right }) do
to_string({ :{}, [], [left, right] })
end
# Lists
def to_binary(list) when is_list(list) do
to_binary({ :[], [], list })
def to_string(list) when is_list(list) do
to_string({ :[], [], list })
end
# All other structures
def to_binary(other), do: Binary.Inspect.inspect(other, raw: true)
def to_string(other), do: inspect(other, raw: true)
# Block keywords
defmacrop kw_keywords, do: [:do, :catch, :rescue, :after, :else]
@@ -346,56 +295,56 @@ defmodule Macro do
end
defp is_kw_blocks?(_), do: false
defp module_to_binary(atom) when is_atom(atom), do: Binary.Inspect.inspect(atom, raw: true)
defp module_to_binary(other), do: call_to_binary(other)
defp module_to_string(atom) when is_atom(atom), do: inspect(atom, raw: true)
defp module_to_string(other), do: call_to_string(other)
defp call_to_binary(atom) when is_atom(atom), do: atom_to_binary(atom, :utf8)
defp call_to_binary({ :., _, [arg] }), do: module_to_binary(arg) <> "."
defp call_to_binary({ :., _, [left, right] }), do: module_to_binary(left) <> "." <> call_to_binary(right)
defp call_to_binary(other), do: to_binary(other)
defp call_to_string(atom) when is_atom(atom), do: atom_to_binary(atom, :utf8)
defp call_to_string({ :., _, [arg] }), do: module_to_string(arg) <> "."
defp call_to_string({ :., _, [left, right] }), do: module_to_string(left) <> "." <> call_to_string(right)
defp call_to_string(other), do: to_string(other)
defp call_to_binary_with_args(target, args) do
args = Enum.map_join(args, ", ", to_binary(&1))
call_to_binary(target) <> "(" <> args <> ")"
defp call_to_string_with_args(target, args) do
args = Enum.map_join(args, ", ", to_string(&1))
call_to_string(target) <> "(" <> args <> ")"
end
defp kw_blocks_to_binary(kw) do
defp kw_blocks_to_string(kw) do
Enum.reduce(kw_keywords, " ", fn(x, acc) ->
case Keyword.has_key?(kw, x) do
true -> acc <> kw_block_to_binary(x, Keyword.get(kw, x))
true -> acc <> kw_block_to_string(x, Keyword.get(kw, x))
false -> acc
end
end) <> "end"
end
defp kw_block_to_binary(key, value) do
block = adjust_new_lines block_to_binary(value), "\n "
defp kw_block_to_string(key, value) do
block = adjust_new_lines block_to_string(value), "\n "
atom_to_binary(key, :utf8) <> "\n " <> block <> "\n"
end
defp block_to_binary({ :->, _, exprs }) do
Enum.map_join(exprs, "\n", fn({ left, right }) ->
defp block_to_string({ :->, _, exprs }) do
Enum.map_join(exprs, "\n", fn({ left, _, right }) ->
left = comma_join_or_empty_paren(left, false)
left <> "->\n " <> adjust_new_lines block_to_binary(right), "\n "
left <> "->\n " <> adjust_new_lines block_to_string(right), "\n "
end)
end
defp block_to_binary({ :__block__, _, exprs }) do
Enum.map_join(exprs, "\n", to_binary(&1))
defp block_to_string({ :__block__, _, exprs }) do
Enum.map_join(exprs, "\n", to_string(&1))
end
defp block_to_binary(other), do: to_binary(other)
defp block_to_string(other), do: to_string(other)
defp op_to_binary({ op, _, [_, _] } = expr) when op in binary_ops do
"(" <> to_binary(expr) <> ")"
defp op_to_string({ op, _, [_, _] } = expr) when op in binary_ops do
"(" <> to_string(expr) <> ")"
end
defp op_to_binary(expr), do: to_binary(expr)
defp op_to_string(expr), do: to_string(expr)
defp arrow_to_binary({ :->, _, pairs }, paren // false) do
Enum.map_join(pairs, "; ", fn({ left, right }) ->
defp arrow_to_string({ :->, _, pairs }, paren // false) do
Enum.map_join(pairs, "; ", fn({ left, _, right }) ->
left = comma_join_or_empty_paren(left, paren)
left <> "-> " <> to_binary(right)
left <> "-> " <> to_string(right)
end)
end
@@ -403,7 +352,7 @@ defmodule Macro do
defp comma_join_or_empty_paren([], false), do: ""
defp comma_join_or_empty_paren(left, _) do
Enum.map_join(left, ", ", to_binary(&1)) <> " "
Enum.map_join(left, ", ", to_string(&1)) <> " "
end
defp adjust_new_lines(block, replacement) do
@@ -416,32 +365,18 @@ defmodule Macro do
end
@doc """
Receives an expression representation and expands it. The following
contents are expanded:
Receives a AST node and expands it once. The following contents are expanded:
* Macros (local or remote);
* Aliases are expanded (if possible) and return atoms;
* All pseudo-variables (__FILE__, __MODULE__, etc);
* Module attributes reader (@foo);
In case the expression cannot be expanded, it returns the expression itself.
Notice that `Macro.expand` is not recursive and it does not
expand child expressions. In this example
Macro.expand(quote(do: !some_macro), __ENV__)
`!some_macro` will expand to something like:
case some_macro do
false -> true
nil -> true
_ -> false
end
Notice that the `!` operator is a macro that expands to a case.
Even though `some_macro` is also a macro, it is not expanded
because it is a child expression given to `!` as argument.
In case the expression cannot be expanded, it returns the expression
itself. Notice that `Macro.expand_once/2` performs the expansion just
once and it is not recursive. Check `Macro.expand/2` for expansion
until the node no longer represents a macro and `Macro.expand_all/2`
for recursive expansion.
## Examples
@@ -504,45 +439,50 @@ defmodule Macro do
end
"""
def expand(aliases, env) do
expand(aliases, env, nil)
def expand_once(aliases, env) do
expand_once(aliases, env, nil) |> elem(0)
end
defp expand({ :__aliases__, _, _ } = original, env, cache) do
case :elixir_aliases.expand(original, env.aliases, []) do
atom when is_atom(atom) -> atom
defp expand_once({ :__aliases__, _, _ } = original, env, cache) do
case :elixir_aliases.expand(original, env.aliases, env.macro_aliases) do
atom when is_atom(atom) -> { atom, true, cache }
aliases ->
aliases = lc alias inlist aliases, do: expand(alias, env, cache)
aliases = lc alias inlist aliases, do: (expand_once(alias, env, cache) |> elem(0))
case :lists.all(is_atom(&1), aliases) do
true -> :elixir_aliases.concat(aliases)
false -> original
true -> { :elixir_aliases.concat(aliases), true, cache }
false -> { original, false, cache }
end
end
end
# Expand @ calls
defp expand({ :@, _, [{ name, _, args }] } = original, env, _cache) when is_atom(args) or args == [] do
defp expand_once({ :@, _, [{ name, _, args }] } = original, env, cache) when is_atom(args) or args == [] do
case (module = env.module) && Module.open?(module) do
true -> Module.get_attribute(module, name)
false -> original
true -> { Module.get_attribute(module, name), true, cache }
false -> { original, false, cache }
end
end
# Expand pseudo-variables
defp expand({ :__MODULE__, _, atom }, env, _cache) when is_atom(atom), do: env.module
defp expand({ :__FILE__, _, atom }, env, _cache) when is_atom(atom), do: env.file
defp expand({ :__ENV__, _, atom }, env, _cache) when is_atom(atom), do: env
defp expand_once({ :__MODULE__, _, atom }, env, cache) when is_atom(atom),
do: { env.module, true, cache }
defp expand_once({ :__FILE__, _, atom }, env, cache) when is_atom(atom),
do: { env.file, true, cache }
defp expand_once({ :__DIR__, _, atom }, env, cache) when is_atom(atom),
do: { :filename.dirname(env.file), true, cache }
defp expand_once({ :__ENV__, _, atom }, env, cache) when is_atom(atom),
do: { env, true, cache }
# Expand possible macro import invocation
defp expand({ atom, line, args } = original, env, cache) when is_atom(atom) do
defp expand_once({ atom, line, args } = original, env, cache) when is_atom(atom) do
args = case is_atom(args) do
true -> []
false -> args
end
case not is_partial?(args) do
false -> original
false -> { original, false, cache }
true ->
module = env.module
@@ -552,43 +492,103 @@ defmodule Macro do
[]
end
cache = to_erl_env(env, cache)
expand = :elixir_dispatch.expand_import(line, { atom, length(args) }, args,
env.module, extra, to_erl_env(env, cache))
env.module, extra, cache)
case expand do
{ :ok, _, expanded } -> expanded
{ :error, _ } -> original
{ :ok, _, expanded } -> { expanded, true, cache }
{ :error, _ } -> { original, false, cache }
end
end
end
# Expand possible macro require invocation
defp expand({ { :., _, [left, right] }, line, args } = original, env, cache) when is_atom(right) do
receiver = expand(left, env)
defp expand_once({ { :., _, [left, right] }, line, args } = original, env, cache) when is_atom(right) do
{ receiver, _, _ } = expand_once(left, env, cache)
case is_atom(receiver) and not is_partial?(args) do
false -> original
false -> { original, false, cache }
true ->
cache = to_erl_env(env, cache)
expand = :elixir_dispatch.expand_require(line, receiver, { right, length(args) },
args, env.module, to_erl_env(env, cache))
args, env.module, cache)
case expand do
{ :ok, _receiver, expanded } -> expanded
{ :error, _ } -> original
{ :ok, _receiver, expanded } -> { expanded, true, cache }
{ :error, _ } -> { original, false, cache }
end
end
end
# Anything else is just returned
defp expand(other, _env, _cache), do: other
defp expand_once(other, _env, cache), do: { other, false, cache }
defp to_erl_env(env, nil), do: :elixir_scope.to_erl_env(env)
defp to_erl_env(_env, cache), do: cache
## Helpers
defp is_partial?(args) do
:lists.any(match?({ :&, _, [_] }, &1), args)
end
@doc """
Receives a AST node and expands it until it no longer represents
a macro. Check `Macro.expand_once/2` for more information on how
expansion works and `Macro.expand_all/2` for recursive expansion.
"""
def expand(tree, env) do
expand(tree, env, nil) |> elem(0)
end
@doc false # Used internally by Elixir
def expand(tree, env, cache) do
expand_until({ tree, true, cache }, env)
end
defp expand_until({ tree, true, cache }, env) do
expand_until(expand_once(tree, env, cache), env)
end
defp expand_until({ tree, false, cache }, _env) do
{ tree, cache }
end
@doc """
Receives a AST node and expands it until it no longer represents
a macro. Then it expands all of its children recursively.
Check `Macro.expand_once/2` for more information on how expansion
works.
"""
def expand_all(tree, env) do
expand_all(tree, env, nil) |> elem(0)
end
@doc false # Used internally by Elixir
def expand_all(tree, env, cache) do
expand_all_until(expand(tree, env, cache), env)
end
defp expand_all_until({ { left, meta, right }, cache }, env) do
{ left, cache } = expand_all(left, env, cache)
{ right, cache } = expand_all(right, env, cache)
{ { left, meta, right }, cache }
end
defp expand_all_until({ { left, right }, cache }, env) do
{ left, cache } = expand_all(left, env, cache)
{ right, cache } = expand_all(right, env, cache)
{ { left, right }, cache }
end
defp expand_all_until({ list, cache }, env) when is_list(list) do
:lists.mapfoldl(expand_all(&1, env, &2), cache, list)
end
defp expand_all_until({ other, cache }, _env) do
{ other, cache }
end
@doc """
Recurs the quoted expression checking if all sub terms are
safe (i.e. they represented data structured and don't actually
+7 -3
View File
@@ -19,6 +19,8 @@ defmodule Macro.Env do
* `requires` - the list of required modules
* `functions` - a list of functions imported from each module
* `macros` - a list of macros imported from each module
* `context_modules` - a list of modules defined in the current context
* `macro_aliases` - a list of aliases defined inside the current macro
"""
@type name_arity :: { atom, non_neg_integer }
@@ -29,13 +31,15 @@ defmodule Macro.Env do
@type requires :: [module]
@type functions :: [{ module, [name_arity] }]
@type macros :: [{ module, [name_arity] }]
@type context_modules :: [module]
fields = [:module, :file, :line, :function,
:aliases, :context, :requires, :functions, :macros]
fields = [:module, :file, :line, :function, :aliases, :context, :requires,
:functions, :macros, :context_modules, :macro_aliases]
types = quote do: [module: module, file: file, line: line,
function: name_arity, aliases: aliases, requires: requires,
functions: functions, macros: macros]
functions: functions, macros: macros, context_modules: context_modules,
macro_aliases: aliases]
Record.deffunctions(fields, __MODULE__)
Record.deftypes(fields, types, __MODULE__)
+365 -71
View File
@@ -1,22 +1,300 @@
defmodule Module do
require :ets, as: ETS
defmacrop is_env(env) do
quote do
is_tuple(unquote(env)) and size(unquote(env)) > 1 and elem(unquote(env), 0) == Macro.Env
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_string(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
@@ -24,14 +302,14 @@ defmodule Module do
"""
def open?(module) do
table = data_table_for(module)
table == ETS.info(table, :name)
table == :ets.info(table, :name)
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` for more information.
See `Code.eval_string` for more information.
Raises an error if the module was already compiled.
@@ -116,11 +394,11 @@ defmodule Module do
## Examples
iex> Module.concat [Foo, Bar]
iex> Module.concat([Foo, Bar])
Foo.Bar
iex> Module.concat [Foo, "Bar"]
iex> Module.concat([Foo, "Bar"])
Foo.Bar
iex> Module.concat [Foo, 'Bar']
iex> Module.concat([Foo, 'Bar'])
Foo.Bar
"""
@@ -134,11 +412,11 @@ defmodule Module do
## Examples
iex> Module.concat Foo, Bar
iex> Module.concat(Foo, Bar)
Foo.Bar
iex> Module.concat Foo, "Bar"
iex> Module.concat(Foo, "Bar")
Foo.Bar
iex> Module.concat Foo, 'Bar'
iex> Module.concat(Foo, 'Bar')
Foo.Bar
"""
@@ -154,10 +432,10 @@ defmodule Module do
## Examples
iex> Module.safe_concat [Unknown, Module]
iex> Module.safe_concat([Unknown, Module])
** (ArgumentError) argument error
iex> Module.safe_concat [List, Chars]
iex> Module.safe_concat([List, Chars])
List.Chars
"""
@@ -173,10 +451,10 @@ defmodule Module do
## Examples
iex> Module.safe_concat Unknown, Module
iex> Module.safe_concat(Unknown, Module)
** (ArgumentError) argument error
iex> Module.safe_concat List, Chars
iex> Module.safe_concat(List, Chars)
List.Chars
"""
@@ -212,21 +490,19 @@ defmodule Module do
{ simplify_signature(x, line, acc), acc + 1 }
end
case ETS.lookup(table, tuple) do
case :ets.lookup(table, tuple) do
[] ->
ETS.insert(table, { tuple, line, kind, signature, doc })
: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 ->
ETS.insert(table, {
[{ 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
@@ -299,7 +575,7 @@ defmodule Module do
def defines?(module, tuple) when is_tuple(tuple) do
assert_not_compiled!(:defines?, module)
table = function_table_for(module)
ETS.lookup(table, tuple) != []
:ets.lookup(table, tuple) != []
end
@doc """
@@ -319,8 +595,8 @@ defmodule Module do
def defines?(module, tuple, kind) do
assert_not_compiled!(:defines?, module)
table = function_table_for(module)
case ETS.lookup(table, tuple) do
[{ _, ^kind, _, _, _, _, _, _ }] -> true
case :ets.lookup(table, tuple) do
[{ _, ^kind, _, _, _, _, _ }] -> true
_ -> false
end
end
@@ -339,7 +615,7 @@ defmodule Module do
def definitions_in(module) do
assert_not_compiled!(:definitions_in, module)
table = function_table_for(module)
lc { tuple, _, _, _, _, _, _, _ } inlist ETS.tab2list(table), do: tuple
lc { tuple, _, _, _, _, _, _ } inlist :ets.tab2list(table), do: tuple
end
@doc """
@@ -358,30 +634,33 @@ defmodule Module do
def definitions_in(module, kind) do
assert_not_compiled!(:definitions_in, module)
table = function_table_for(module)
lc { tuple, stored_kind, _, _, _, _, _, _ } inlist ETS.tab2list(table), stored_kind == kind, do: tuple
lc { tuple, stored_kind, _, _, _, _, _ } inlist :ets.tab2list(table), stored_kind == kind, do: tuple
end
@doc """
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)
table = function_table_for(module)
lc tuple inlist tuples do
case ETS.lookup(table, tuple) do
[clause] ->
ETS.delete(table, tuple)
old = get_attribute(module, :__overridable)
new = [ { tuple, { 1, clause, false } } ]
merged = :orddict.merge(fn(_k, { count, _, _ }, _v2) -> { count + 1, clause, false } end, old, new)
put_attribute(module, :__overridable, merged)
_ ->
case :elixir_def.lookup_definition(module, tuple) do
false ->
{ name, arity } = tuple
raise "Cannot make function #{name}/#{arity} overridable because it was not defined"
clause ->
:elixir_def.delete_definition(module, tuple)
neighbours = Module.DispatchTracker.yank(module, tuple)
old = get_attribute(module, :__overridable)
merged = :orddict.update(tuple, fn({ count, _, _, _ }) ->
{ count + 1, clause, neighbours, false }
end, { 1, clause, neighbours, false }, old)
put_attribute(module, :__overridable, merged)
end
end
end
@@ -409,26 +688,39 @@ defmodule Module do
assert_not_compiled!(:put_attribute, module)
table = data_table_for(module)
value = normalize_attribute(key, value)
acc = ETS.lookup_element(table, :__acc_attributes, 2)
acc = :ets.lookup_element(table, :__acc_attributes, 2)
new =
if List.member?(acc, key) do
case ETS.lookup(table, key) do
[{^key,old}] -> [value|old]
if :lists.member(key, acc) do
case :ets.lookup(table, key) do
[{^key, old}] -> [value|old]
[] -> [value]
end
else
value
end
ETS.insert(table, { key, new })
:ets.insert(table, { key, new })
end
@doc """
Gets the given attribute from a module. If the attribute
was marked as accumulate with `Module.register_attribute`,
was marked with `accumulate` with `Module.register_attribute`,
a list is always returned.
The `@` macro compiles to a call to this function. For example,
the following code:
@foo
Expands to:
Module.get_attribute(__MODULE__, @foo, true)
Notice the third argument is used to indicate if a warning
should be emitted when the attribute was not previously defined.
This is true for `@foo` attributes but false for direct calls.
## Examples
defmodule Foo do
@@ -441,15 +733,22 @@ defmodule Module do
end
"""
def get_attribute(module, key) when is_atom(key) do
def get_attribute(module, key, warn // false) when is_atom(key) do
assert_not_compiled!(:get_attribute, module)
table = data_table_for(module)
case ETS.lookup(table, key) do
[{^key,old}] -> old
case :ets.lookup(table, key) do
[{^key, val}] -> val
[] ->
acc = ETS.lookup_element(table, :__acc_attributes, 2)
if List.member?(acc, key), do: [], else: nil
acc = :ets.lookup_element(table, :__acc_attributes, 2)
if :lists.member(key, acc) do
[]
else
warn && IO.warn "#{Exception.format_caller} undefined module attribute @#{key}, " <>
"please remove access to @#{key} or explicitly set it to nil before access"
nil
end
end
end
@@ -467,7 +766,7 @@ defmodule Module do
def delete_attribute(module, key) when is_atom(key) do
assert_not_compiled!(:delete_attribute, module)
table = data_table_for(module)
ETS.delete(table, key)
:ets.delete(table, key)
end
@doc """
@@ -508,13 +807,13 @@ defmodule Module do
table = data_table_for(module)
if Keyword.get(opts, :persist, true) do
old = ETS.lookup_element(table, :__persisted_attributes, 2)
ETS.insert(table, { :__persisted_attributes, [new|old] })
old = :ets.lookup_element(table, :__persisted_attributes, 2)
:ets.insert(table, { :__persisted_attributes, [new|old] })
end
if Keyword.get(opts, :accumulate, true) do
old = ETS.lookup_element(table, :__acc_attributes, 2)
ETS.insert(table, { :__acc_attributes, [new|old] })
old = :ets.lookup_element(table, :__acc_attributes, 2)
:ets.insert(table, { :__acc_attributes, [new|old] })
end
end
@@ -528,7 +827,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 +836,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.
@@ -558,9 +854,7 @@ defmodule Module do
:ok ->
: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"
IO.warn "#{env.file}:#{line} function #{name}/#{arity} is private, @doc's are always discarded for private functions"
end
delete_attribute(module, :doc)
@@ -574,12 +868,12 @@ defmodule Module do
table = data_table_for(module)
new =
case ETS.lookup(table, key) do
[{^key,old}] -> [value|old]
case :ets.lookup(table, key) do
[{^key, old}] -> [value|old]
[] -> [value]
end
ETS.insert(table, { key, new })
:ets.insert(table, { key, new })
end
## Helpers
@@ -616,11 +910,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
+19
View File
@@ -164,4 +164,23 @@ defmodule Node do
def spawn_link(node, module, fun, args) do
:erlang.spawn_link(node, module, fun, args)
end
@doc """
Sets the magic cookie of `node` to the atom `cookie`. The default node
is `Node.self`, the local node. If node is the local node, the function also
sets the cookie of all other unknown nodes to `cookie`.
This function will raise `FunctionClauseError` if the given node is not alive.
"""
def set_cookie(node // Node.self, cookie) when is_atom(cookie) do
:erlang.set_cookie(node, cookie)
end
@doc """
Returns the magic cookie of the local node, if the node is alive;
otherwise `:nocookie`.
"""
def get_cookie() do
:erlang.get_cookie()
end
end
+1 -1
View File
@@ -166,7 +166,7 @@ defmodule OptionParser do
defp is_no?("no-" <> _), do: true
defp is_no?(_), do: false
defp is_switch_a?(kind, list) when is_list(list), do: List.member?(list, kind)
defp is_switch_a?(kind, list) when is_list(list), do: kind in list
defp is_switch_a?(kind, kind), do: true
defp is_switch_a?(_, _), do: false
end
+52 -41
View File
@@ -1,15 +1,15 @@
defmodule Path do
@moduledoc """
This module provides conveniences for manipulating or
retrieving filesystem paths.
retrieving file system paths.
The functions on this module may receive a char list or
a binary as argument and will return the given type.
The functions in this module may receive a char list or
a binary as argument and will return a value of the same
type.
The majority of the functions in this module do not
interact with the file system, unless some few functions
that needs to query the filesystem to retrieve paths
(like `Path.wildcard` and `Path.expand`).
interact with the file system, except for a few functions
that require it (like `Path.wildcard` and `Path.expand`).
"""
alias :filename, as: FN
@@ -18,9 +18,8 @@ defmodule Path do
@type r :: char_list | binary
@doc """
Converts the given filename and returns an absolute name.
Differently from `Path.expand/1`, no attempt is made to
resolve `..`, `.` or `~`.
Converts the given path to an absolute one. Differently from
`Path.expand/1`, no attempt is made to resolve `..`, `.` or `~`.
## Unix examples
@@ -43,9 +42,8 @@ defmodule Path do
end
@doc """
Converts the given filename and returns an absolute name
relative to the given location. If the path is already
an absolute path, the relative path is ignored.
Builds a path from `relative_to` to `path`. If `path` is already
an absolute path, `relative_to` is ignored. See also `Path.relative/2`.
Differently from `Path.expand/2`, no attempt is made to
resolve `..`, `.` or `~`.
@@ -64,8 +62,8 @@ defmodule Path do
end
@doc """
Expands the path by returning its absolute name and expanding
any `.` and `..` characters.
Converts the path to an absolute one and expands
any `.` and `..` characters and a leading `~`.
## Examples
@@ -78,12 +76,21 @@ defmodule Path do
end
@doc """
Expands the path to the relative location and expanding
any `.` and `..` characters. If the path is already an
absolute path, the relative location is ignored.
Expands the path relative to the path given as the second argument
expanding any `.` and `..` characters. If the path is already an
absolute path, `relative_to` is ignored.
Note, that this function treats `path` with leading `~` as
an absolute one.
The second argument is first expanded to an absolute path.
## Examples
# Assuming that the absolute path to baz is /quux/baz
Path.expand("foo/bar/../bar", "baz")
#=> "/quux/baz/foo/bar"
iex> Path.expand("foo/bar/../bar", "/baz")
"/baz/foo/bar"
iex> Path.expand("/foo/bar/../bar", "/baz")
@@ -91,7 +98,7 @@ defmodule Path do
"""
def expand(path, relative_to) do
normalize FN.absname(FN.absname(expand_home(path), relative_to), get_cwd(path))
normalize FN.absname(FN.absname(expand_home(path), expand_home(relative_to)), get_cwd(path))
end
@doc """
@@ -102,6 +109,7 @@ defmodule Path do
Path.type("/usr/local/bin") #=> :absolute
Path.type("usr/local/bin") #=> :relative
Path.type("../usr/local/bin") #=> :relative
Path.type("~/file") #=> :relative
## Windows examples
@@ -185,8 +193,9 @@ defmodule Path do
@doc """
Returns the given `path` relative to the given `from` path.
In other words, it tries to strip the `from` prefix from `path`.
This function does not query the filesystem, so it assumes
This function does not query the file system, so it assumes
no symlinks in between the paths.
In case a direct relative path cannot be found, it returns
@@ -267,12 +276,14 @@ defmodule Path do
end
@doc """
Return the `directory` component of `path`.
Returns the directory component of `path`.
## Examples
Path.dirname("/foo/bar.ex")
#=> "foo"
#=> "/foo"
Path.dirname("/foo/bar/baz.ex")
#=> "/foo/bar"
"""
def dirname(path) do
@@ -280,7 +291,7 @@ defmodule Path do
end
@doc """
Return the `extension` of the last component of `path`.
Returns the extension of the last component of `path`.
## Examples
@@ -326,8 +337,8 @@ defmodule Path do
end
@doc """
Returns a string with one or more paths components joint by the path separator.
This function should be used to convert a list of strings in a path.
Returns a string with one or more path components joined by the path separator.
This function should be used to convert a list of strings to a path.
## Examples
@@ -374,19 +385,16 @@ 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:
do_join(rest, relativename, [?:, uc_letter+?a-?A], :win32)
defp do_join(<<?\\,rest :: binary>>, relativename, result, :win32), do:
do_join(<<?/,rest :: binary>>, relativename, result, :win32)
defp do_join(<<?/,rest :: binary>>, relativename, [?., ?/|result], os_type), do:
defp do_join(<<?\\, rest :: binary>>, relativename, result, :win32), do:
do_join(<<?/, rest :: binary>>, relativename, result, :win32)
defp do_join(<<?/, rest :: binary>>, relativename, [?., ?/|result], os_type), do:
do_join(rest, relativename, [?/|result], os_type)
defp do_join(<<?/,rest :: binary>>, relativename, [?/|result], os_type), do:
defp do_join(<<?/, rest :: binary>>, relativename, [?/|result], os_type), do:
do_join(rest, relativename, [?/|result], os_type)
defp do_join(<<>>, <<>>, result, os_type), do:
list_to_binary(maybe_remove_dirsep(result, os_type))
@@ -396,7 +404,7 @@ defmodule Path do
do_join(relativename, <<>>, [?/|result], os_type)
defp do_join(<<>>, relativename, result, os_type), do:
do_join(relativename, <<>>, [?/|result], os_type)
defp do_join(<<char,rest :: binary>>, relativename, result, os_type) when is_integer(char), do:
defp do_join(<<char, rest :: binary>>, relativename, result, os_type) when is_integer(char), do:
do_join(rest, relativename, [char|result], os_type)
defp maybe_remove_dirsep([?/, ?:, letter], :win32), do:
@@ -409,19 +417,22 @@ defmodule Path do
:lists.reverse(name)
@doc """
Returns a list with the path splitted by the path separator.
If an empty string is given, then it returns the root path.
Returns a list with the path split by the path separator.
If an empty string is given, returns the root path.
## Examples
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
@@ -449,8 +460,8 @@ defmodule Path do
## Examples
Imagine you have a directory called `projects` with three Elixir projects
inside of it: `elixir`, `exdoc` and `dynamo`. You can find all `.beam` files
inside their ebin directories all projects as follows:
inside of it: `elixir`, `ex_doc` and `dynamo`. You can find all `.beam` files
inside the ebin directory of each project as follows:
Path.wildcard("projects/*/ebin/**/*.beam")
@@ -460,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
@@ -520,4 +531,4 @@ defmodule Path do
defp normalize([], acc) do
join Enum.reverse(acc)
end
end
end
+11 -9
View File
@@ -19,13 +19,6 @@ defmodule Process do
:erlang.is_process_alive(pid)
end
@doc false
def self do
IO.puts "Process.self is deprecated, please use Kernel.self instead"
Exception.print_stacktrace
:erlang.self()
end
@doc """
Returns all key-values in the dictionary.
"""
@@ -83,9 +76,9 @@ defmodule Process do
@doc """
Sends an exit signal with the given reason to the pid.
The following behavior apply if reason is any term except `:normal` or `:kill`:
The following behavior applies if reason is any term except `:normal` or `:kill`:
1) If pid is not trapping exits, pid itself will exist with the given reason;
1) If pid is not trapping exits, pid will exit with the given reason;
2) If pid is trapping exits, the exit signal is transformed into a message
{'EXIT', from, reason} and delivered to the message queue of pid;
@@ -108,6 +101,14 @@ defmodule Process do
:erlang.exit(pid, reason)
end
@doc """
Returns the pid (process identifier) of the calling process.
"""
@spec self() :: pid
def self() do
:erlang.self()
end
@doc """
Returns the pid of a new process started by the application of `fun`.
It behaves exactly the same as `Kernel.spawn/1`.
@@ -367,6 +368,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
+22 -18
View File
@@ -34,6 +34,8 @@ defmodule Protocol do
# Set up a clear slate to store defined functions
@functions []
@only nil
@except nil
# Invoke the user given block
unquote(block)
@@ -81,14 +83,14 @@ defmodule Protocol do
def assert_protocol(module) do
case Code.ensure_compiled(module) do
{ :module, ^module } -> nil
_ -> raise ArgumentError, message: "#{module} is not loaded"
_ -> raise ArgumentError, message: "#{inspect module} is not loaded"
end
try do
module.__protocol__(:name)
rescue
UndefinedFunctionError ->
raise ArgumentError, message: "#{module} is not a protocol"
raise ArgumentError, message: "#{inspect module} is not a protocol"
end
end
@@ -236,13 +238,13 @@ defmodule Protocol do
# Returns a quoted expression that allows to check
# if the first item in the tuple is a built-in or not.
defp is_builtin?([{h,_,_}]) do
defp is_builtin?([{h, _, _}]) do
quote do
first == unquote(h)
end
end
defp is_builtin?([{h,_,_}|t]) do
defp is_builtin?([{h, _, _}|t]) do
quote do
first == unquote(h) or unquote(is_builtin?(t))
end
@@ -267,7 +269,7 @@ defmodule Protocol do
true -> unquote(fallback)
false ->
case atom_to_list(first) do
'Elixir-' ++ _ -> __MODULE__.Record
'Elixir.' ++ _ -> __MODULE__.Record
_ -> unquote(fallback)
end
end
@@ -349,36 +351,38 @@ defmodule Protocol.DSL do
end
defp default_clause(name, args) do
quote do: { [other], apply(other, unquote(name), [unquote_splicing(args)]) }
{ [quote do: other],
[],
quote(do: apply(other, unquote(name), [unquote_splicing(args)])) }
end
defp nil_clause() do
quote do
{ [nil], raise(Protocol.UndefinedError, protocol: __MODULE__, structure: xA) }
end
{ [nil],
[],
quote(do: raise(Protocol.UndefinedError, protocol: __MODULE__, structure: xA)) }
end
defp record_clause(name, args, nil) do
quote do
{ [__MODULE__.Record],
Module.concat(__MODULE__, :erlang.element(1, xA)).unquote(name)(unquote_splicing(args)) }
end
{ [quote(do: __MODULE__.Record)],
[],
quote(do: Module.concat(__MODULE__, :erlang.element(1, xA)).unquote(name)(unquote_splicing(args))) }
end
defp record_clause(name, args, fallback) do
arity = length(args)
quote do
{ [__MODULE__.Record],
(target = Module.concat(__MODULE__, :erlang.element(1, xA))
{ [quote(do: __MODULE__.Record)],
[],
quote do
target = Module.concat(__MODULE__, :erlang.element(1, xA))
try do
target.unquote(name)(unquote_splicing(args))
catch
:error, :undef, [[{ ^target, name, args, _ }|_]|_] when
name == unquote(name) and length(args) == unquote(arity) ->
apply unquote(fallback), name, [unquote_splicing(args)]
end) }
end
end
end }
end
defmacro def(expression) do
+28 -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,10 +13,12 @@ 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
def count(Range[first: first] = range) do
@@ -32,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
@@ -57,6 +65,6 @@ defimpl Binary.Inspect, for: Range do
import Kernel, except: [inspect: 2]
def inspect(Range[first: first, last: last], opts) do
Binary.Inspect.inspect(first, opts) <> ".." <> Binary.Inspect.inspect(last, opts)
Kernel.inspect(first, opts) <> ".." <> Kernel.inspect(last, opts)
end
end
+233 -31
View File
@@ -1,6 +1,162 @@
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.
Those types can be passed at the moment the record is defined:
defrecord User,
name: "" :: string,
age: 0 :: integer
All the fields without a specified type are assumed to have type `term`.
Assuming the `User` record defined above, it could be used in typespecs
as follow:
@spec handle_user(User.t) :: boolean()
If the developer wants to define their own types to be used with the
record, Elixir allows a more lengthy definition with the help of the
`record_type` macro:
defrecord Config, counter: 0, failures: [] do
@type kind :: term
record_type counter: integer, failures: [kind]
end
## 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 """
@@ -24,32 +180,55 @@ defmodule Record do
"""
def defrecord(name, values, opts) do
block = Keyword.get(opts, :do, nil)
opts = Keyword.delete(opts, :do)
{ fields, types } = record_split(values)
quote do
unquoted_fields = unquote(fields)
defmodule unquote(name) do
@moduledoc false
import Record.DSL
import Elixir.Record.DSL
@record_fields []
@record_types []
@record_types unquote(types)
values = unquote(values)
opts = unquote(opts)
Record.deffunctions(values, __ENV__)
# Reassign fields to inner scope to
# avoid conflicts in nested records
fields = unquoted_fields
Elixir.Record.deffunctions(fields, __ENV__)
value = unquote(block)
Record.deftypes(values, @record_types, __ENV__)
Elixir.Record.deftypes(fields, @record_types, __ENV__)
value
end
end
end
defp record_split(fields) when is_list(fields) do
record_split(fields, [], [])
end
defp record_split(other) do
{ other, [] }
end
defp record_split([{ field, { :::, _, [default, type] }}|t], defaults, types) do
record_split t, [{ field, default }|defaults], [{ field, Macro.escape(type) }|types]
end
defp record_split([other|t], defaults, types) do
record_split t, [other|defaults], types
end
defp record_split([], defaults, types) do
{ :lists.reverse(defaults), types }
end
@doc """
Import public record definition as a set of private macros (as defined by defrecordp/2)
## Usage
Record.import Record.Module, as: macro_name
Import public record definition as a set of private macros,
as defined by `Kernel.defrecordp/2`. This is useful when one
desires to manipulate a record via a set of macros instead
of the regular access syntax.
## Example
@@ -62,7 +241,8 @@ defmodule Record do
"""
defmacro import(module, as: name) do
quote do
Record.defmacros(unquote(name), unquote(module).__record__(:fields), __ENV__, unquote(module))
Record.defmacros(unquote(name),
unquote(module).__record__(:fields), __ENV__, unquote(module))
end
end
@@ -72,12 +252,31 @@ defmodule Record do
in `values`. This is invoked directly by `Kernel.defrecordp`,
so check it for more information and documentation.
"""
def defrecordp(name, fields) do
def defrecordp(name, fields) when is_atom(name) and is_list(fields) do
{ fields, types, def_type } = recordp_split(fields, [], [], false)
type = :"#{name}_t"
quote do
Record.defmacros(unquote(name), unquote(fields), __ENV__)
if unquote(def_type) do
@typep unquote(type)() :: { unquote(name), unquote_splicing(types) }
end
end
end
defp recordp_split([{ field, { :::, _, [default, type] }}|t], defaults, types, _) do
recordp_split t, [{ field, default }|defaults], [type|types], true
end
defp recordp_split([other|t], defaults, types, def_type) do
recordp_split t, [other|defaults], [quote(do: term)|types], def_type
end
defp recordp_split([], defaults, types, def_type) do
{ :lists.reverse(defaults), :lists.reverse(types), def_type }
end
@doc """
Defines record functions skipping the module definition.
This is called directly by `defrecord`. It expects the record
@@ -157,7 +356,6 @@ defmodule Record do
end
contents = quote do
defmacrop unquote(name)() do
Record.access(unquote(tag) || __MODULE__, unquote(escaped), [], __CALLER__)
end
@@ -217,7 +415,7 @@ defmodule Record do
@doc false
def access(atom, fields, keyword, caller) do
unless is_keyword(keyword) do
raise "expected contents inside brackets to be a Keyword"
raise ArgumentError, message: "expected contents inside brackets to be a Keyword"
end
in_match = caller.in_match?
@@ -243,10 +441,11 @@ defmodule Record do
{ match, remaining } = :lists.mapfoldl(iterator, keyword, fields)
case remaining do
[] -> { :{}, [line: caller.line], [atom|match] }
[] ->
quote do: { unquote_splicing([atom|match]) }
_ ->
keys = lc { key, _ } inlist remaining, do: key
raise "record #{inspect atom} does not have the keys: #{inspect keys}"
raise ArgumentError, message: "record #{inspect atom} does not have the keys: #{inspect keys}"
end
end
@@ -266,11 +465,11 @@ defmodule Record do
@doc false
defp update(atom, fields, var, keyword, caller) do
unless is_keyword(keyword) do
raise "expected contents inside brackets to be a Keyword"
raise ArgumentError, message: "expected contents inside brackets to be a Keyword"
end
if caller.in_match? do
raise "cannot invoke update style macro inside match context"
raise ArgumentError, message: "cannot invoke update style macro inside match context"
end
Enum.reduce keyword, var, fn({ key, value }, acc) ->
@@ -280,7 +479,7 @@ defmodule Record do
:erlang.setelement(unquote(index + 2), unquote(acc), unquote(value))
end
else
raise "record #{inspect atom} does not have the key: #{inspect key}"
raise ArgumentError, message: "record #{inspect atom} does not have the key: #{inspect key}"
end
end
end
@@ -296,7 +495,7 @@ defmodule Record do
:erlang.element(unquote(index + 2), unquote(var))
end
else
raise "record #{inspect atom} does not have the key: #{inspect key}"
raise ArgumentError, message: "record #{inspect atom} does not have the key: #{inspect key}"
end
end
@@ -325,7 +524,7 @@ defmodule Record do
if index do
quote do: :erlang.element(unquote(index + 2), unquote(record))
else
raise "record #{inspect atom} does not have the key: #{inspect key}"
raise ArgumentError, message: "record #{inspect atom} does not have the key: #{inspect key}"
end
end
end
@@ -417,6 +616,7 @@ defmodule Record do
quoted = lc { k, _ } inlist values do
index = find_index(values, k, 0)
quote do
@doc false
def __index__(unquote(k)), do: unquote(index + 1)
end
end
@@ -459,27 +659,27 @@ defmodule Record do
#
# It will define four methods:
#
# def :atime.(record) do
# def atime(record) do
# elem(record, 1)
# end
#
# def :mtime.(record) do
# def mtime(record) do
# elem(record, 2)
# end
#
# def :atime.(value, record) do
# def atime(value, record) do
# set_elem(record, 1, value)
# end
#
# def :mtime.(record) do
# def mtime(record) do
# set_elem(record, 2, value)
# end
#
# def :atime.(callback, record) do
# def atime(callback, record) do
# set_elem(record, 1, callback.(elem(record, 1)))
# end
#
# def :mtime.(callback, record) do
# def mtime(callback, record) do
# set_elem(record, 2, callback.(elem(record, 2)))
# end
#
@@ -526,7 +726,7 @@ defmodule Record do
end
end
contents = { :{}, [], [(quote do: __MODULE__)|fields] }
contents = quote do: { __MODULE__, unquote_splicing(fields) }
quote do
@doc false
@@ -572,6 +772,8 @@ defmodule Record do
@spec new(options | tuple) :: t
@spec to_keywords(t) :: options
@spec update(options, t) :: t
@spec __record__(:name) :: atom
@spec __record__(:fields) :: [{atom, any}]
@spec __index__(atom) :: non_neg_integer | nil
end
end
+40 -35
View File
@@ -79,34 +79,14 @@ 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
"""
def index(regex(re_pattern: compiled), string) do
case :re.run(string, compiled, [{ :capture, :first, :index }]) do
:nomatch -> nil
{ :match, [{index,_}] } -> index
end
end
@doc """
Returns a boolean if there was a match or not.
## Examples
iex> Regex.match? %r/foo/, "foo"
iex> Regex.match?(%r/foo/, "foo")
true
iex> Regex.match? %r/foo/, "bar"
iex> Regex.match?(%r/foo/, "bar")
false
"""
@@ -116,14 +96,17 @@ defmodule Regex do
@doc """
Runs the regular expression against the given string.
It returns a list with all matches or nil if no match ocurred.
It returns a list with all matches, nil if no match ocurred, or []
if it matched, /g was specified, but nothing was captured.
## Examples
iex> Regex.run %r/c(d)/, "abcd"
iex> Regex.run(%r/c(d)/, "abcd")
["cd", "d"]
iex> Regex.run %r/e/, "abcd"
iex> Regex.run(%r/e/, "abcd")
nil
iex> Regex.run(%r/c(d)/, "abcd", return: :index)
[{2,2},{3,1}]
"""
def run(regex, string, options // [])
@@ -139,6 +122,7 @@ defmodule Regex do
case :re.run(string, compiled, [{ :capture, captures, return }]) do
:nomatch -> nil
:match -> []
{ :match, results } -> results
end
end
@@ -149,7 +133,7 @@ defmodule Regex do
## Examples
iex> Regex.captures %r/c(?<foo>d)/g, "abcd"
iex> Regex.captures(%r/c(?<foo>d)/g, "abcd")
[foo: "d"]
"""
@@ -178,7 +162,7 @@ defmodule Regex do
## Examples
iex> Regex.source %r(foo)
iex> Regex.source(%r(foo))
"foo"
"""
@@ -191,7 +175,7 @@ defmodule Regex do
## Examples
iex> Regex.opts %r(foo)m
iex> Regex.opts(%r(foo)m)
"m"
"""
@@ -204,7 +188,7 @@ defmodule Regex do
## Examples
iex> Regex.groups %r/(?<foo>foo)/g
iex> Regex.groups(%r/(?<foo>foo)/g)
[:foo]
"""
@@ -220,11 +204,11 @@ defmodule Regex do
## Examples
iex> Regex.scan %r/c(d|e)/, "abcd abce"
iex> Regex.scan(%r/c(d|e)/, "abcd abce")
[["d"], ["e"]]
iex> Regex.scan %r/c(?:d|e)/, "abcd abce"
iex> Regex.scan(%r/c(?:d|e)/, "abcd abce")
["cd", "ce"]
iex> Regex.scan %r/e/, "abcd"
iex> Regex.scan(%r/e/, "abcd")
[]
"""
@@ -289,6 +273,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
@@ -311,7 +314,7 @@ defmodule Regex do
defp translate_options(<<?x, t :: binary>>), do: [:extended|translate_options(t)]
defp translate_options(<<?f, t :: binary>>), do: [:firstline|translate_options(t)]
defp translate_options(<<?r, t :: binary>>), do: [:ungreedy|translate_options(t)]
defp translate_options(<<?s, t :: binary>>), do: [:dotall,{:newline,:anycrlf}|translate_options(t)]
defp translate_options(<<?s, t :: binary>>), do: [:dotall, {:newline, :anycrlf}|translate_options(t)]
defp translate_options(<<?m, t :: binary>>), do: [:multiline|translate_options(t)]
defp translate_options(<<?g, t :: binary>>), do: [:groups|translate_options(t)]
defp translate_options(<<>>), do: []
@@ -325,10 +328,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)
+261 -24
View File
@@ -62,7 +62,7 @@ defmodule String do
Although codepoints could be represented as integers, this
module represents all codepoints as strings. For example:
iex> String.codepoints "josé"
iex> String.codepoints("josé")
["j", "o", "s", "é"]
There are a couple of ways to retrieve a character integer
@@ -139,28 +139,32 @@ defmodule String do
def printable?(_), do: false
@doc """
Divides a string into sub string based on a pattern,
returning a list of these sub string. The pattern can
be a string, a list of strings or a regular expression.
The string is split into as many parts as possible by
default, unless the `global` option is set to false.
If a pattern is not specified, the string is split on
Unicode whitespace occurrences with leading and trailing
whitespace ignored.
It returns a list with the original string if the pattern
can't be matched.
Splits a string on sub strings at each Unicode whitespace
occurrence with leading and trailing whitespace ignored.
## Examples
iex> String.split("foo bar")
["foo", "bar"]
iex> String.split("foo" <> <<194,133>> <> "bar")
iex> String.split("foo" <> <<194, 133>> <> "bar")
["foo", "bar"]
iex> String.split(" foo bar ")
["foo", "bar"]
"""
@spec split(t) :: [t]
defdelegate split(binary), to: String.Unicode
@doc """
Divides a string into sub strings based on a pattern,
returning a list of these sub string. The pattern can
be a string, a list of strings or a regular expression.
The string is split into as many parts as possible by
default, unless the `global` option is set to false.
## Examples
iex> String.split("a,b,c", ",")
["a", "b", "c"]
iex> String.split("a,b,c", ",", global: false)
@@ -177,12 +181,8 @@ defmodule String do
["a,b"]
"""
@spec split(t) :: [t]
@spec split(t, t | [t] | Regex.t) :: [t]
@spec split(t, t | [t] | Regex.t, Keyword.t) :: [t]
defdelegate split(binary), to: String.Unicode
def split(binary, pattern, options // [])
def split(binary, pattern, options) when is_regex(pattern) do
@@ -383,7 +383,7 @@ defmodule String do
"a,[b],c"
iex> String.replace("a,b,c", ",", "[]", insert_replaced: 2)
"a[],b[],c"
iex> String.replace("a,b,c", ",", "[]", insert_replaced: [1,1])
iex> String.replace("a,b,c", ",", "[]", insert_replaced: [1, 1])
"a[,,]b[,,]c"
"""
@@ -399,7 +399,7 @@ defmodule String do
opts = if options[:global] != false, do: [:global], else: []
if insert = options[:insert_replaced] do
opts = [{:insert_replaced,insert}|opts]
opts = [{:insert_replaced, insert}|opts]
end
opts
@@ -458,6 +458,61 @@ defmodule String do
@spec next_codepoint(t) :: {codepoint, t} | :no_codepoint
defdelegate next_codepoint(string), to: String.Unicode
@doc %B"""
Checks whether `str` contains only valid characters.
## Examples
iex> String.valid?("a")
true
iex> String.valid?("ø")
true
iex> String.valid?(<<0xffff :: 16>>)
false
iex> String.valid?("asd" <> <<0xffff :: 16>>)
false
"""
@spec valid?(t) :: boolean
noncharacters = Enum.to_list(?\x{FDD0}..?\x{FDEF}) ++
[ ?\x{0FFFE}, ?\x{0FFFF}, ?\x{1FFFE}, ?\x{1FFFF}, ?\x{2FFFE}, ?\x{2FFFF},
?\x{3FFFE}, ?\x{3FFFF}, ?\x{4FFFE}, ?\x{4FFFF}, ?\x{5FFFE}, ?\x{5FFFF},
?\x{6FFFE}, ?\x{6FFFF}, ?\x{7FFFE}, ?\x{7FFFF}, ?\x{8FFFE}, ?\x{8FFFF},
?\x{9FFFE}, ?\x{9FFFF}, ?\x{10FFFE}, ?\x{10FFFF} ]
lc noncharacter inlist noncharacters do
def valid?(<< unquote(noncharacter) :: utf8, _ :: binary >>), do: false
end
def valid?(<<_ :: utf8, t :: binary>>), do: valid?(t)
def valid?(<<>>), do: true
def valid?(_), do: false
@doc %B"""
Checks whether `str` is a valid character.
All characters are codepoints, but some codepoints
are not valid characters. They may be reserved, private,
or other.
More info at: http://en.wikipedia.org/wiki/Mapping_of_Unicode_characters#Noncharacters
## Examples
iex> String.valid_character?("a")
true
iex> String.valid_character?("ø")
true
iex> String.valid_character?("\x{ffff}")
false
"""
@spec valid_character?(t) :: boolean
def valid_character?(<<_ :: utf8>> = codepoint), do: valid?(codepoint)
def valid_character?(_), do: false
@doc %B"""
Checks whether `str` is a valid codepoint.
@@ -470,7 +525,7 @@ defmodule String do
true
iex> String.valid_codepoint?("ø")
true
iex> String.valid_codepoint?("\xffff")
iex> String.valid_codepoint?(<<0xffff :: 16>>)
false
iex> String.valid_codepoint?("asdf")
false
@@ -505,11 +560,12 @@ defmodule String do
{ "j", "osé" }
"""
@spec next_grapheme(t) :: grapheme | :no_grapheme
@spec next_grapheme(t) :: { grapheme, t } | :no_grapheme
defdelegate next_grapheme(string), to: String.Unicode
@doc """
Returns the first grapheme from an utf8 string.
Returns the first grapheme from an utf8 string,
nil if the string is empty.
## Examples
@@ -528,7 +584,8 @@ defmodule String do
end
@doc """
Returns the last grapheme from an utf8 string.
Returns the last grapheme from an utf8 string,
nil if the string is empty.
## Examples
@@ -630,6 +687,12 @@ defmodule String do
"ixir"
iex> String.slice("elixir", -10, 3)
nil
iex> String.slice("a", 0, 1500)
"a"
iex> String.slice("a", 1, 1500)
""
iex> String.slice("a", 2, 1500)
nil
"""
@spec slice(t, integer, integer) :: grapheme | nil
@@ -662,10 +725,184 @@ defmodule String do
acc <> char
end
defp do_slice(:no_grapheme, start_pos, _, current_pos, acc) when start_pos == current_pos do
acc
end
defp do_slice(:no_grapheme, _, _, _, acc) do
case acc do
"" -> nil
_ -> acc
end
end
@doc """
Converts a string to an integer. If successful, returns a
tuple of form {integer, remainder of string}. If unsuccessful,
returns :error.
## Examples
iex> String.to_integer("34")
{34,""}
iex> String.to_integer("34.5")
{34,".5"}
iex> String.to_integer("three")
:error
"""
@spec to_integer(t) :: {integer, t} | :error
def to_integer(string) do
{result, remainder} = :string.to_integer(binary_to_list(string))
case result do
:error -> :error
_ -> {result, list_to_binary(remainder)}
end
end
@doc """
Converts a string to a float. If successful, returns a
tuple of form {float, remainder of string}. If unsuccessful,
returns :error. If given an integer value, will return
same as to_integer/1.
## Examples
iex> String.to_float("34")
{34.0,""}
iex> String.to_float("34.25")
{34.25,""}
iex> String.to_float("56.5xyz")
{56.5,"xyz"}
iex> String.to_float("pi")
:error
"""
@spec to_float(t) :: {integer, t} | :error
def to_float(string) do
charlist = binary_to_list(string)
{result, remainder} = :string.to_float(charlist)
case result do
:error ->
{int_result, int_remainder} = :string.to_integer(charlist)
case int_result do
:error -> :error
_ -> {float(int_result), list_to_binary(int_remainder)}
end
_ -> {result, list_to_binary(remainder)}
end
end
@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 List.member?(:erlang.module_info(:exports), { :halt, 2 }) 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
+46 -11
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
@@ -131,8 +133,16 @@ defmodule URI do
destructure [_, _, scheme, _, authority, path, _, query, _, fragment], parts
{ userinfo, host, port } = split_authority(authority)
if authority do
authority = ""
if userinfo, do: authority = authority <> userinfo <> "@"
if host, do: authority = authority <> host
if port, do: authority = authority <> ":" <> integer_to_binary(port)
end
info = URI.Info[
scheme: scheme, path: path, query: query,
scheme: scheme && String.downcase(scheme), path: path, query: query,
fragment: fragment, authority: authority,
userinfo: userinfo, host: host, port: port
]
@@ -140,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
@@ -150,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
@@ -169,7 +184,7 @@ defmodule URI do
components = Regex.run %r/(^(.*)@)?([^:]*)(:(\d*))?/, s
destructure [_, _, userinfo, host, _, port], nillify(components)
port = if port, do: binary_to_integer(port)
{ userinfo, host, port }
{ userinfo, host && String.downcase(host), port }
end
# Regex.run returns empty strings sometimes. We want
@@ -192,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
+29 -21
View File
@@ -16,7 +16,7 @@ defmodule String.Unicode do
end
end
data_path = Path.expand("../UnicodeData.txt", __FILE__)
data_path = Path.join(__DIR__, "UnicodeData.txt")
{ codes, whitespace } = Enum.reduce File.iterator!(data_path), { [], [] }, fn(line, { cacc, wacc }) ->
[ codepoint, _name, _category,
@@ -37,7 +37,7 @@ defmodule String.Unicode do
end
end
special_path = Path.expand("../SpecialCasing.txt", __FILE__)
special_path = Path.join(__DIR__, "SpecialCasing.txt")
codes = Enum.reduce File.iterator!(special_path), codes, fn(line, acc) ->
[ codepoint, lower, title, upper, _comment ] = :binary.split(line, "; ", [:global])
@@ -45,7 +45,7 @@ defmodule String.Unicode do
:lists.keystore(key, 1, acc, { key, to_binary.(upper), to_binary.(lower), to_binary.(title) })
end
seqs_path = Path.expand("../NamedSequences.txt", __FILE__)
seqs_path = Path.join(__DIR__, "NamedSequences.txt")
seqs = Enum.map File.iterator!(seqs_path), fn(line) ->
[ _name, codepoints ] = :binary.split(line, ";", [:global])
@@ -57,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).*"}.
+42 -23
View File
@@ -1,11 +1,12 @@
-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, file_type/1,
eval_quoted/2, eval_quoted/3, eval_quoted/4,
eval_forms/3, translate_forms/3]).
-include("elixir.hrl").
-compile({parse_transform, elixir_transform}).
-include_lib("kernel/include/file.hrl").
%% Top level types
-export_type([char_list/0, as_boolean/1]).
@@ -20,8 +21,8 @@ start(_Type, _Args) ->
%% Set the shell to unicode so printing inside scripts work
%% Those can take a while, so let's do it in a new process
spawn(fun() ->
io:setopts(standard_io, [{encoding,unicode}]),
io:setopts(standard_error, [{encoding,unicode}])
io:setopts(standard_io, [binary,{encoding,unicode}]),
io:setopts(standard_error, [binary,{encoding,unicode}])
end),
elixir_sup:start_link([]).
@@ -43,40 +44,58 @@ start_cli() ->
application:start(?MODULE),
'Elixir.Kernel.CLI':main(init:get_plain_arguments()).
%% Helpers
file_type(File) ->
case file:read_file_info(File) of
{ ok, #file_info{type=Type} } -> { ok, Type };
{ error, _ } = Error -> Error
end.
%% 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 }.
+34 -21
View File
@@ -1,8 +1,23 @@
-module(elixir_aliases).
-export([nesting_alias/2, last/1, concat/1, safe_concat/1,
format_error/1, ensure_loaded/3, expand/3]).
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;
store(Meta, New, Old, S) ->
SA = S#elixir_scope{
aliases=orddict:store(New, Old, S#elixir_scope.aliases)
},
case lists:keymember(context, 1, Meta) of
true ->
SA#elixir_scope{
macro_aliases=orddict:store(New, Old, S#elixir_scope.macro_aliases)
};
false ->
SA
end.
%% Expand an alias. It returns an atom (meaning that there
%% was an expansion) or a list of atoms.
@@ -41,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
@@ -49,28 +64,31 @@ expand_one(H, Aliases) ->
%% Ensure a module is loaded before its usage.
ensure_loaded(_Line, 'Elixir.Kernel', _S) ->
ensure_loaded(Line, Ref, S) ->
ensure_loaded(Line, S#elixir_scope.file, Ref, S#elixir_scope.context_modules).
ensure_loaded(_Line, _File, 'Elixir.Kernel', _FileModules) ->
ok;
ensure_loaded(Line, Ref, S) ->
ensure_loaded(Line, File, Ref, FileModules) ->
try
Ref:module_info(compile)
catch
error:undef ->
Kind = case lists:member(Ref, S#elixir_scope.scheduled) of
Kind = case lists:member(Ref, FileModules) of
true -> scheduled_module;
false -> unloaded_module
end,
elixir_errors:form_error(Line, S#elixir_scope.file, ?MODULE, { Kind, Ref })
elixir_errors:form_error(Line, File, ?MODULE, { Kind, Ref })
end.
%% Receives an atom and returns the last bit as an alias.
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.
@@ -99,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.
@@ -116,23 +134,18 @@ concat(Args) -> list_to_atom(raw_concat(Args)).
safe_concat(Args) -> list_to_existing_atom(raw_concat(Args)).
raw_concat(['Elixir'|Args]) -> do_concat(Args);
raw_concat([nil|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.
+36 -24
View File
@@ -1,10 +1,9 @@
%% 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,
assigns/3, assigns_block/5, assigns_block/6, extract_splat_guards/1,
get_pairs/4, get_pairs/5, match/3, extract_args/1, extract_guards/1]).
-import(elixir_scope, [umergec/2]).
-include("elixir.hrl").
%% Get pairs from a clause.
@@ -15,7 +14,7 @@ get_pairs(Meta, Key, Clauses, S) ->
get_pairs(Meta, Key, Clauses, S, AllowNil) ->
case lists:keyfind(Key, 1, Clauses) of
{ Key, { '->', _, Pairs } } ->
[{ Key, Left, Right } || { Left, Right } <- Pairs];
[{ Key, PMeta, Left, Right } || { Left, PMeta, Right } <- Pairs];
{ Key, nil } when AllowNil ->
[];
{ Key, _ } ->
@@ -26,9 +25,12 @@ get_pairs(Meta, Key, Clauses, S, AllowNil) ->
% Function for translating assigns.
assigns(Fun, Args, #elixir_scope{context=Context} = S) when Context /= match ->
{ Result, NewS } = assigns(Fun, Args, S#elixir_scope{context=match, temp_vars=orddict:new()}),
{ Result, NewS#elixir_scope{context=Context} };
assigns(Fun, Args, #elixir_scope{context=Context, temp_vars=TempVars,
backup_vars=BackupVars, vars=Vars} = S) when Context /= match ->
{ Result, NewS } = assigns(Fun, Args, S#elixir_scope{context=match,
temp_vars=ordsets:new(), backup_vars=Vars}),
{ Result, NewS#elixir_scope{context=Context,
temp_vars=TempVars, backup_vars=BackupVars} };
assigns(Fun, Args, S) -> Fun(Args, S).
@@ -78,13 +80,13 @@ extract_args({ Name, _, Args }) when is_atom(Name), is_atom(Args) -> { Name, []
extract_args({ Name, _, Args }) when is_atom(Name), is_list(Args) -> { Name, Args };
extract_args(_) -> error.
% Extract guards when it is in the last element of the args
% Extract guards when multiple left side args are allowed.
extract_last_guards([]) -> { [], [] };
extract_last_guards(Args) ->
{ Left, Right } = elixir_tree_helpers:split_last(Args),
{ Bare, Guards } = extract_guards(Right),
{ Left ++ [Bare], Guards }.
extract_splat_guards([{ 'when', _, [_,_|_] = Args }]) ->
{ Left, Right } = elixir_tree_helpers:split_last(Args),
{ Left, extract_or_clauses(Right, []) };
extract_splat_guards(Else) ->
{ Else, [] }.
% Function for translating macros with match style like case and receive.
@@ -96,16 +98,16 @@ match(Meta, Clauses, #elixir_scope{clause_vars=C1} = S) ->
do_match(_Meta, [], S) ->
{ [], S };
do_match(Meta, [DecoupledClause], S) ->
{ TDecoupledClause, TS } = each_clause(Meta, DecoupledClause, S),
do_match(_Meta, [DecoupledClause], S) ->
{ TDecoupledClause, TS } = each_clause(DecoupledClause, S),
{ [TDecoupledClause], TS };
do_match(Meta, DecoupledClauses, S) ->
% Transform tree just passing the variables counter forward
% and storing variables defined inside each clause.
Transformer = fun(X, {Acc, CV}) ->
{ TX, TAcc } = each_clause(Meta, X, Acc),
{ TX, { umergec(S, TAcc), [TAcc#elixir_scope.clause_vars|CV] } }
{ TX, TAcc } = each_clause(X, Acc),
{ TX, { merge_clauses_scope(S, TAcc), [TAcc#elixir_scope.clause_vars|CV] } }
end,
{ TClauses, { TS, ReverseCV } } = lists:mapfoldl(Transformer, {S, []}, DecoupledClauses),
@@ -164,21 +166,21 @@ expand_clauses(_Line, [], [], _FinalVars, Acc, S) ->
% Handle each key/value clause pair and translate them accordingly.
each_clause(Meta, { do, [Condition], Expr }, S) ->
each_clause({ do, Meta, [Condition], Expr }, S) ->
assigns_block(?line(Meta), fun elixir_translator:translate_each/2, Condition, [Expr], S);
each_clause(Meta, { else, [Condition], Expr }, S) ->
each_clause({ else, Meta, [Condition], Expr }, S) ->
assigns_block(?line(Meta), fun elixir_translator:translate_each/2, Condition, [Expr], S);
each_clause(Meta, { 'after', [Condition], Expr }, S) ->
each_clause({ 'after', Meta, [Condition], Expr }, S) ->
{ TCondition, SC } = elixir_translator:translate_each(Condition, S),
{ TBody, SB } = elixir_translator:translate([Expr], SC),
{ { clause, ?line(Meta), [TCondition], [], TBody }, SB };
each_clause(Meta, { Key, [_|_], _ }, S) when Key == do; Key == 'after' ->
each_clause({ Key, Meta, [_|_], _ }, S) when Key == do; Key == 'after' ->
elixir_errors:syntax_error(Meta, S#elixir_scope.file, "too many arguments given for ~ts", [Key]);
each_clause(Meta, { Key, _, _ }, S) ->
each_clause({ Key, Meta, _, _ }, S) ->
elixir_errors:syntax_error(Meta, S#elixir_scope.file, "invalid key ~ts", [Key]).
% Check if the given expression is a match tuple.
@@ -245,4 +247,14 @@ generate_match(Line, LeftVars, RightVars) ->
{ match, Line, { tuple, Line, LeftVars }, { tuple, Line, RightVars } }.
listify(Expr) when not is_list(Expr) -> [Expr];
listify(Expr) -> Expr.
listify(Expr) -> Expr.
%% We don't use umergec because imports, aliases and
%% what not are not passed from one clause to the other.
merge_clauses_scope(S1, S2) ->
S1#elixir_scope{
counter=S2#elixir_scope.counter,
extra_guards=S2#elixir_scope.extra_guards,
super=S1#elixir_scope.super orelse S2#elixir_scope.super,
caller=S1#elixir_scope.caller orelse S2#elixir_scope.caller
}.
+14 -1
View File
@@ -5,11 +5,12 @@
-behavior(gen_server).
-record(elixir_code_server, {
argv=[],
compilation_status=ok,
loaded=[],
at_exit=[],
pool=[],
counter=0,
compiler_options=[{docs,true},{debug_info,true}],
compiler_options=[{docs,true},{debug_info,true},{warnings_as_errors,false}],
waiting=[]
}).
@@ -58,6 +59,9 @@ handle_call(argv, _From, Config) ->
handle_call(compiler_options, _From, Config) ->
{ reply, Config#elixir_code_server.compiler_options, Config };
handle_call(compilation_status, _From, Config) ->
{ reply, Config#elixir_code_server.compilation_status, Config };
handle_call(retrieve_module_name, _From, Config) ->
case Config#elixir_code_server.pool of
[H|T] ->
@@ -80,6 +84,15 @@ handle_cast({ compiler_options, Options }, Config) ->
Final = orddict:merge(fun(_,_,V) -> V end, Config#elixir_code_server.compiler_options, Options),
{ noreply, Config#elixir_code_server{compiler_options=Final} };
handle_cast(register_warning, Config) ->
case orddict:find(warnings_as_errors, Config#elixir_code_server.compiler_options) of
{ ok, true } -> { noreply, Config#elixir_code_server{compilation_status=error} };
_ -> { noreply, Config }
end;
handle_cast(reset_warnings, Config) ->
{ noreply, Config#elixir_code_server{compilation_status=ok} };
handle_cast({ loaded, Path }, Config) ->
Current = Config#elixir_code_server.loaded,
case orddict:find(Path, Current) of
+5 -11
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
@@ -209,11 +208,10 @@ core_main() ->
[
"lib/elixir/lib/kernel.ex",
"lib/elixir/lib/keyword.ex",
"lib/elixir/lib/module.ex",
"lib/elixir/lib/list.ex",
"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).
+187 -129
View File
@@ -1,36 +1,60 @@
% Holds the logic responsible for functions definition (def(p) and defmacro(p)).
-module(elixir_def).
-export([table/1,
build_table/1,
delete_table/1,
clauses_table/1,
setup/1,
cleanup/1,
reset_last/1,
wrap_definition/5,
wrap_definition/7,
store_definition/6,
store_definition/8,
store_each/7,
lookup_definition/2,
delete_definition/2,
wrap_definition/6,
wrap_definition/8,
store_definition/7,
store_definition/9,
store_each/8,
unwrap_stored_definitions/2,
format_error/1]).
-include("elixir.hrl").
-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]).
table(Module) ->
ets:lookup_element(Module, ?attr, 2).
build_table(Module) ->
FunctionTable = table(Module),
ets:new(FunctionTable, [set, 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),
FunctionTable.
ok.
delete_table(Module) ->
ets:delete(table(Module)).
cleanup(Module) ->
ets:delete(table(Module)),
ets:delete(clauses_table(Module)).
%% Reset the last item. Useful when evaling code.
reset_last(Module) ->
ets:insert(table(Module), { last, [] }).
%% Looks up a definition from the database.
lookup_definition(Module, Tuple) ->
case ets:lookup(table(Module), Tuple) of
[Result] ->
CTable = clauses_table(Module),
{ Result, [Clause || { _, Clause } <- ets:lookup(CTable, Tuple)] };
_ ->
false
end.
delete_definition(Module, Tuple) ->
ets:delete(table(Module), Tuple),
ets:delete(clauses_table(Module), Tuple).
%% Wraps the function into a call to store_definition once the function
%% definition is read. The function is compiled into a meta tree to ensure
%% we will receive the full function.
@@ -47,81 +71,78 @@ reset_last(Module) ->
%% If we just analyzed the compiled structure (i.e. the function availables
%% before evaluating the function body), we would see both definitions.
wrap_definition(Kind, Meta, Call, Expr, S) ->
do_wrap_definition(Kind, Meta, [
wrap_definition(Kind, Meta, Call, Expr, CheckClauses, S) ->
do_wrap_definition(Kind, Meta, CheckClauses, [
Call, Expr, elixir_scope:serialize(S)
]).
wrap_definition(Kind, Meta, Name, Args, Guards, Expr, S) ->
do_wrap_definition(Kind, Meta, [
wrap_definition(Kind, Meta, Name, Args, Guards, Expr, CheckClauses, S) ->
do_wrap_definition(Kind, Meta, CheckClauses, [
Name, Args, Guards, Expr, elixir_scope:serialize(S)
]).
do_wrap_definition(Kind, Meta, Extra) ->
do_wrap_definition(Kind, Meta, CheckClauses, Extra) ->
Line = ?line(Meta),
Invoke =
[{atom, Line, Kind},
{integer, Line, Line},
{atom, Line, CheckClauses},
{var, Line, '_@MODULE'}] ++ Extra,
?wrap_call(Line, ?MODULE, store_definition, Invoke).
% Invoked by the wrap definition with the function abstract tree.
% Each function is then added to the function table.
store_definition(Kind, Line, Module, Call, Body, RawS) ->
store_definition(Kind, Line, CheckClauses, Module, Call, Body, RawS) ->
S = elixir_scope:deserialize(RawS),
{ NameAndArgs, Guards } = elixir_clauses:extract_guards(Call),
{ Name, Args } = case elixir_clauses:extract_args(NameAndArgs) of
error ->
Format = [Kind, 'Elixir.Macro':to_binary(NameAndArgs)],
Format = [Kind, 'Elixir.Macro':to_string(NameAndArgs)],
elixir_errors:syntax_error(Line, S#elixir_scope.file, "invalid syntax in ~ts ~ts", Format);
Tuple ->
Tuple
end,
assert_no_aliases_name(Line, Name, Args, S),
store_definition(Kind, Line, Module, Name, Args, Guards, Body, RawS).
store_definition(Kind, Line, CheckClauses, Module, Name, Args, Guards, Body, S).
store_definition(Kind, Line, nil, _Name, _Args, _Guards, _Body, RawS) ->
S = elixir_scope:deserialize(RawS),
elixir_errors:syntax_error(Line, S#elixir_scope.file, "cannot define function outside module, invalid scope for ~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, Module, Name, Args, Guards, Body, RawS) ->
do_store_definition(Kind, Line, Module, Name, Args, Guards, Body, elixir_scope:deserialize(RawS)).
do_store_definition(Kind, Line, Module, Name, Args, Guards, Body, DS) ->
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},
Expr = def_body(Line, Body),
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),
run_on_definition_callbacks(Kind, Line, Module, Name, Args, Guards, Body, S, CO),
{ Function, Defaults, TS } = translate_definition(Kind, Line, Name, Args, Guards, Expr, S),
{ Function, Defaults, TS } = translate_definition(Kind, Line, Name, Args, Guards, Body, S),
DefaultsLength = length(Defaults),
elixir_tracker:record_defaults(Tuple, Kind, Module, DefaultsLength),
File = TS#elixir_scope.file,
Table = table(Module),
CTable = clauses_table(Module),
%% Store function
if
(Body == nil) -> [];
true ->
compile_super(Module, TS),
CheckClauses = S#elixir_scope.check_clauses,
store_each(CheckClauses, Kind, File, Location,
Table, length(Defaults), Function)
end,
compile_super(Module, TS),
check_previous_defaults(Table, Line, Name, Arity, Kind, DefaultsLength, S),
[store_each(false, Kind, File, Location, Table, 0,
store_each(CheckClauses, Kind, File, Location,
Table, CTable, DefaultsLength, Function),
[store_each(false, Kind, File, Location, Table, CTable, 0,
default_function_for(Kind, Name, Default)) || Default <- Defaults],
{ Name, Arity }.
{ Name, Arity };
def_body(_Line, nil) -> nil;
def_body(_Line, [{ do, Expr }]) -> Expr;
def_body(Line, Else) -> { 'try', [{line,Line}], [Else] }.
store_definition(Kind, Line, CheckClauses, Module, Name, Args, Guards, Body, RawS) ->
store_definition(Kind, Line, CheckClauses, Module, Name, Args, Guards, Body, elixir_scope:deserialize(RawS)).
%% @on_definition
@@ -159,47 +180,53 @@ compile_super(_Module, _S) -> ok.
%% Translate the given call and expression given
%% and then store it in memory.
translate_definition(Kind, Line, Name, RawArgs, RawGuards, RawExpr, S) when is_integer(Line) ->
translate_definition(Kind, Line, Name, RawArgs, RawGuards, RawBody, S) when is_integer(Line) ->
Args = elixir_quote:linify(Line, RawArgs),
Guards = elixir_quote:linify(Line, RawGuards),
Expr = elixir_quote:linify(Line, RawExpr),
Body = elixir_quote:linify(Line, RawBody),
Arity = length(Args),
IsMacro = is_macro(Kind),
%% Macros receive a special argument on invocation. Notice it does
%% not affect the arity of the stored function, but the clause
%% already contains it.
ExtendedArgs = case IsMacro of
ExtendedArgs = case is_macro(Kind) of
true -> [{ '_@CALLER', [{line,Line}], nil }|Args];
false -> Args
end,
{ Unpacked, Defaults } = elixir_def_defaults:unpack(Kind, Name, ExtendedArgs, S),
{ Clauses, TS } = translate_clause(Line, Kind, Unpacked, Guards, Body, S),
Function = { function, Line, Name, Arity, Clauses },
{ Function, Defaults, TS }.
translate_clause(_Line, _Kind, _Unpacked, [], 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),
{ TClause, TS } = elixir_clauses:assigns_block(Line,
fun elixir_translator:translate/2, Unpacked, [Expr], Guards, S),
%% Add names to args
NClause = case TS#elixir_scope.name_args of
true ->
NArgs = elixir_def_overridable:assign_args(Line, element(3, TClause), TS),
setelement(3, TClause, NArgs);
false -> TClause
end,
%% Set __CALLER__ if used
FClause = case IsMacro andalso TS#elixir_scope.caller of
FClause = case is_macro(Kind) andalso TS#elixir_scope.caller of
true ->
FBody = { 'match', Line,
{ 'var', Line, '__CALLER__' },
?wrap_call(Line, elixir_scope, to_ex_env, [{ var, Line, '_@CALLER' }])
},
setelement(5, NClause, [FBody|element(5, NClause)]);
false -> NClause
setelement(5, TClause, [FBody|element(5, TClause)]);
false -> TClause
end,
Function = { function, Line, Name, Arity, [FClause] },
{ Function, Defaults, TS }.
{ [FClause], TS }.
expr_from_body(_Line, [{ do, Expr }]) -> Expr;
expr_from_body(Line, Else) -> { 'try', [{line,Line}], [Else] }.
is_macro(defmacro) -> true;
is_macro(defmacrop) -> true;
@@ -210,58 +237,59 @@ is_macro(_) -> false.
% and the body of all functions.
unwrap_stored_definitions(File, Module) ->
Table = table(Module),
CTable = clauses_table(Module),
ets:delete(Table, last),
unwrap_stored_definition(ets:tab2list(Table), File, [], [], [], [], [], {[],[]}).
unwrap_stored_definition(ets:tab2list(Table), CTable, File, [], [], [], [], [], []).
unwrap_stored_definition([Fun|T], File, Exports, Private, Def, Defmacro, Defmacrop, Functions) when element(2, Fun) == def ->
Tuple = element(1, Fun),
unwrap_stored_definition(
T, File, [Tuple|Exports], Private, [Tuple|Def], Defmacro, Defmacrop,
function_for_stored_definition(Fun, File, Functions)
);
unwrap_stored_definition([Fun|T], CTable, File, Exports, Private, Def, Defmacro, Functions, Tail) ->
Tuple = element(1, Fun),
Clauses = [Clause || { _, Clause } <- ets:lookup(CTable, Tuple)],
unwrap_stored_definition([Fun|T], File, Exports, Private, Def, Defmacro, Defmacrop, Functions) when element(2, Fun) == defmacro ->
{ Name, Arity } = Tuple = element(1, Fun),
{ NewFun, NewExports, NewPrivate, NewDef, NewDefmacro } =
case Clauses of
[] -> { false, Exports, Private, Def, Defmacro };
_ -> unwrap_stored_definition(element(2, Fun), Tuple, Fun, Exports, Private, Def, Defmacro)
end,
{ NewFunctions, NewTail } = case NewFun of
false -> { Functions, Tail };
_ -> function_for_stored_definition(NewFun, Clauses, File, Functions, Tail)
end,
unwrap_stored_definition(T, CTable, File, NewExports, NewPrivate,
NewDef, NewDefmacro, NewFunctions, NewTail);
unwrap_stored_definition([], _CTable, _File, Exports, Private, Def, Defmacro, Functions, Tail) ->
{ Exports, Private, ordsets:from_list(Def),
ordsets:from_list(Defmacro), lists:reverse(Tail ++ Functions) }.
unwrap_stored_definition(def, Tuple, Fun, Exports, Private, Def, Defmacro) ->
{ Fun, [Tuple|Exports], Private, [Tuple|Def], Defmacro };
unwrap_stored_definition(defmacro, { Name, Arity } = Tuple, Fun, Exports, Private, Def, Defmacro) ->
Macro = { ?elixir_macro(Name), Arity + 1 },
{ setelement(1, Fun, Macro), [Macro|Exports], Private, Def, [Tuple|Defmacro] };
unwrap_stored_definition(
T, File, [Macro|Exports], Private, Def, [Tuple|Defmacro], Defmacrop,
function_for_stored_definition(setelement(1, Fun, Macro), File, Functions)
);
unwrap_stored_definition(defp, Tuple, Fun, Exports, Private, Def, Defmacro) ->
%% { Name, Arity }, Kind, Line, Check, Defaults
Info = { Tuple, defp, element(3, Fun), element(5, Fun), element(7, Fun) },
{ Fun, Exports, [Info|Private], Def, Defmacro };
unwrap_stored_definition([Fun|T], File, Exports, Private, Def, Defmacro, Defmacrop, Functions) when element(2, Fun) == defp ->
unwrap_stored_definition(
T, File, Exports, [element(1, Fun)|Private], Def, Defmacro, Defmacrop,
function_for_stored_definition(Fun, File, Functions)
);
unwrap_stored_definition([Fun|T], File, Exports, Private, Def, Defmacro, Defmacrop, Functions) when element(2, Fun) == defmacrop ->
Tuple = element(1, Fun),
unwrap_stored_definition(
T, File, Exports, [Tuple|Private], Def, Defmacro,
[{ Tuple, element(3, Fun), element(5, Fun) }|Defmacrop], Functions
);
unwrap_stored_definition([], _File, Exports, Private, Def, Defmacro, Defmacrop, {Functions,Tail}) ->
{ Exports, Private, ordsets:from_list(Def), ordsets:from_list(Defmacro),
ordsets:from_list(Defmacrop), lists:reverse(Tail ++ Functions) }.
unwrap_stored_definition(defmacrop, Tuple, Fun, Exports, Private, Def, Defmacro) ->
%% { Name, Arity }, Kind, Line, Check, Defaults
Info = { Tuple, defmacrop, element(3, Fun), element(5, Fun), element(7, Fun) },
{ false, Exports, [Info|Private], Def, Defmacro }.
%% Helpers
function_for_stored_definition({{Name, Arity}, _, Line, _, _, { File, _ }, _, Clauses}, File, {Functions,Tail}) ->
{
[{ function, Line, Name, Arity, lists:reverse(Clauses) }|Functions],
Tail
};
function_for_stored_definition({{Name,Arity}, _, Line, _, _, {File, _}, _}, Clauses, File, Functions, Tail) ->
{ [{ function, Line, Name, Arity, Clauses }|Functions], Tail };
function_for_stored_definition({{Name, Arity}, _, Line, _, _, Location, _, Clauses}, _File, {Functions,Tail}) ->
{
Functions,
[
{ function, Line, Name, Arity, lists:reverse(Clauses) },
{ attribute, Line, file, Location } | Tail
]
}.
function_for_stored_definition({{Name,Arity}, _, Line, _, _, Location, _}, Clauses, _File, Functions, Tail) ->
{ Functions, [
{ function, Line, Name, Arity, Clauses },
{ attribute, Line, file, Location } | Tail
] }.
default_function_for(Kind, Name, { clause, Line, Args, _Guards, _Exprs } = Clause)
when Kind == defmacro; Kind == defmacrop ->
@@ -274,22 +302,24 @@ default_function_for(_, Name, { clause, Line, Args, _Guards, _Exprs } = Clause)
%% This function also checks and emit warnings in case
%% the kind, of the visibility of the function changes.
store_each(Check, Kind, File, Location, Table, Defaults, {function, Line, Name, Arity, Clauses}) ->
case ets:lookup(Table, {Name, Arity}) of
[{{Name, Arity}, StoredKind, _, _, StoredCheck, StoredLocation, StoredDefaults, StoredClauses}] ->
store_each(Check, Kind, File, Location, Table, CTable, Defaults, {function, Line, Name, Arity, Clauses}) ->
Tuple = { Name, Arity },
case ets:lookup(Table, Tuple) of
[{ Tuple, StoredKind, StoredLine, _, StoredCheck, StoredLocation, StoredDefaults }] ->
FinalLine = StoredLine,
FinalLocation = StoredLocation,
FinalDefaults = Defaults + StoredDefaults,
FinalClauses = Clauses ++ StoredClauses,
FinalDefaults = max(Defaults, StoredDefaults),
check_valid_kind(Line, File, Name, Arity, Kind, StoredKind),
check_valid_defaults(Line, File, Name, Arity, FinalDefaults),
(Check and StoredCheck) andalso check_valid_clause(Line, File, Name, Arity, Table);
(Check and StoredCheck) andalso check_valid_clause(Line, File, Name, Arity, Kind, Table),
check_valid_defaults(Line, File, Name, Arity, Kind, Defaults, StoredDefaults);
[] ->
FinalLine = Line,
FinalLocation = Location,
FinalDefaults = Defaults,
FinalClauses = Clauses,
Check andalso ets:insert(Table, { last, { Name, Arity } })
FinalDefaults = Defaults
end,
ets:insert(Table, {{Name, Arity}, Kind, Line, File, Check, FinalLocation, FinalDefaults, FinalClauses}).
Check andalso ets:insert(Table, { last, { Name, Arity } }),
ets:insert(CTable, [{ Tuple, Clause } || Clause <- Clauses ]),
ets:insert(Table, { Tuple, Kind, FinalLine, File, Check, FinalLocation, FinalDefaults }).
%% Validations
@@ -298,33 +328,61 @@ 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} -> [];
[] -> [];
{ElseName, ElseArity} ->
_ ->
elixir_errors:handle_file_warning(File, { Line, ?MODULE,
{ changed_clause, { { Name, Arity }, { ElseName, ElseArity } } } })
{ override_function, { Kind, Name, Arity } } })
end.
check_valid_defaults(_Line, _File, _Name, _Arity, 0) -> [];
check_valid_defaults(Line, File, Name, Arity, _) ->
elixir_errors:handle_file_warning(File, { Line, ?MODULE, { clauses_with_docs, { Name, Arity } } }).
check_valid_defaults(_Line, _File, _Name, _Arity, _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:form_error(Line, File, ?MODULE, { clauses_with_defaults, { Kind, Name, Arity } }).
check_previous_defaults(Table, Line, Name, Arity, Kind, Defaults, S) ->
Matches = ets:match(Table, { { Name, '$2' }, '$1', '_', '_', '_', '_', '$3' }),
[ begin
elixir_errors:form_error(Line, S#elixir_scope.file, ?MODULE,
{ defs_with_defaults, Name, { Kind, Arity }, { K, A } })
end || [K, A, D] <- Matches, A /= Arity, D /= 0, defaults_conflict(A, D, Arity, Defaults)].
defaults_conflict(A, D, Arity, Defaults) ->
((Arity >= (A - D)) andalso (Arity < A)) orelse
((A >= (Arity - Defaults)) andalso (A < 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_docs,{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({changed_clause,{{Name,Arity},{ElseName,ElseArity}}}) ->
io_lib:format("function ~ts/~B does not match previous clause ~ts/~B", [Name, Arity, ElseName, ElseArity]);
format_error({defs_with_defaults, Name, { Kind, Arity }, { K, A } }) when Arity > A ->
io_lib:format("~ts ~ts/~B defaults conflicts with ~ts ~ts/~B",
[Kind, Name, Arity, K, Name, A]);
format_error({defs_with_defaults, Name, { Kind, Arity }, { K, A } }) when Arity < A ->
io_lib:format("~ts ~ts/~B conflicts with defaults from ~ts ~ts/~B",
[Kind, Name, Arity, K, Name, A]);
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({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]).
io_lib:format("~ts ~ts/~B already defined as ~ts", [Current, Name, Arity, Previous]).
+13 -29
View File
@@ -1,19 +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_macros/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) ->
try ets:lookup(elixir_def:table(Module), Tuple) of
[{Tuple, Kind, Line, _, _, _, _, Clauses}] when Kind == defmacro; All, Kind == defmacrop ->
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_tracker:record_local(Tuple, Module, Function),
get_function(Line, Module, Clauses);
_ ->
false
@@ -21,26 +16,25 @@ 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 ets:lookup(elixir_def:table(Module), Tuple) of
[{Tuple, _, Line, _, _, _, _, Clauses}] ->
case elixir_def:lookup_definition(Module, Tuple) of
{ { Tuple, _, Line, _, _, _, _ }, Clauses } ->
%% 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, lists:reverse(RewrittenClauses)} },
Fun = { 'fun', Line, {clauses, RewrittenClauses } },
{ value, Result, _Binding } = erl_eval:exprs([Fun], []),
Result.
%% TODO: Consider caching functions in a table for performance.
rewrite_clause({ call, Line, { atom, Line, RawName }, Args }, Module) ->
Remote = { remote, Line,
{ atom, Line, ?MODULE },
@@ -65,14 +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_macros(File, Recorded, Defmacrop) ->
[elixir_errors:handle_file_warning(File,
{ Line, ?MODULE, { unused_macro, Fun } }) || { Fun, Line, Check } <- Defmacrop,
Check, not lists:member(Fun, Recorded)].
format_error({unused_macro,{Name, Arity}}) ->
io_lib:format("macro ~ts/~B is unused", [Name, Arity]).
rewrite_clause(Else, _) -> Else.
+13 -21
View File
@@ -1,9 +1,8 @@
% Holds the logic responsible for defining overridable functions and handling super.
-module(elixir_def_overridable).
-export([store_pending/1, is_defined/2, ensure_defined/4,
assign_args/3, retrieve_args/3, name/2, store/3, format_error/1]).
assign_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,25 +15,16 @@ 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.
%% Retrieve args defined for the given arity.
retrieve_args(Meta, Arity, S) ->
Line = ?line(Meta),
{
[ { var, Line, super_arg(X) } || X <- lists:seq(1, Arity) ],
S#elixir_scope{name_args=true}
}.
%% Assign pseudo variables to the given vars.
assign_args(Line, Args, S) when is_integer(Line) ->
@@ -54,7 +44,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,31 +52,33 @@ 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)),
{ { Name, Arity }, Kind, Line, File, _Check, Location, Defaults, Clauses } = Clause,
{ _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
true -> { defp, name(Module, Function, Overridable) };
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), Defaults, Def)
elixir_def:table(Module), elixir_def:clauses_table(Module), Defaults, Def)
end.
%% 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]).
+93 -72
View File
@@ -8,25 +8,29 @@
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, elixir_imported_functions() } ].
default_macros() ->
[ { ?BUILTIN, ordsets:union(in_elixir_macros(), in_erlang_macros()) } ].
[ { ?builtin, elixir_imported_macros() } ].
default_requires() ->
[ ?BUILTIN, 'Elixir.Kernel.Typespec', 'Elixir.Record' ].
[ ?builtin, 'Elixir.Kernel.Typespec', 'Elixir.Record' ].
find_import(Meta, Name, Arity, S) ->
Tuple = { Name, Arity },
case find_dispatch(Meta, Tuple, S) of
{ function, Receiver } -> Receiver;
{ macro, Receiver } -> Receiver;
_ -> false
{ function, Receiver } ->
elixir_tracker:record_import(Tuple, Receiver, S#elixir_scope.module, S#elixir_scope.function),
Receiver;
{ macro, Receiver } ->
elixir_tracker:record_import(Tuple, Receiver, S#elixir_scope.module, S#elixir_scope.function),
Receiver;
_ ->
false
end.
%% Function retrieval
@@ -35,13 +39,14 @@ import_function(Meta, Name, Arity, S) ->
Tuple = { Name, Arity },
case find_dispatch(Meta, Tuple, S) of
{ function, Receiver } ->
elixir_import:record(import, 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 ->
elixir_tracker:record_local(Tuple, S#elixir_scope.module, S#elixir_scope.function),
{ { 'fun', ?line(Meta), { function, Name, Arity } }, S }
end.
@@ -50,7 +55,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
@@ -62,20 +69,21 @@ dispatch_import(Meta, Name, Args, S, Callback) ->
case find_dispatch(Meta, Tuple, S) of
{ function, Receiver } ->
elixir_import:record(import, Tuple, Receiver, Module),
Endpoint = case (Receiver == ?BUILTIN) andalso is_element(Tuple, in_erlang_functions()) of
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(import, 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)
@@ -87,8 +95,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 ->
@@ -96,6 +105,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)
@@ -110,51 +120,54 @@ 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(import, 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(import, 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 };
_ ->
elixir_import:record(import, Tuple, Module, Module),
{ ok, Module, expand_macro_fun(Meta, Fun, Module, Name, Args, Module, S) }
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;
_ ->
elixir_import:record(import, Tuple, Receiver, Module),
{ ok, Receiver, expand_macro_fun(Meta, Fun, Receiver, Name, Args, Module, S) }
end.
@@ -187,27 +200,10 @@ expand_macro_named(Meta, Receiver, Name, Arity, Args, Module, S) ->
expand_macro_fun(Meta, Fun, Receiver, Name, Args, Module, S).
translate_expansion(Meta, Tree, S) ->
{ TR, TS } = elixir_translator:translate_each(
elixir_translator:translate_each(
elixir_quote:linify(?line(Meta), Tree),
S#elixir_scope{
check_clauses=false,
macro_functions=[],
macro_macros=[],
macro_aliases=[]
}
),
{ TR, TS#elixir_scope{
check_clauses=S#elixir_scope.check_clauses,
macro_functions=merge_imports(S#elixir_scope.macro_functions, TS#elixir_scope.macro_functions),
macro_macros=merge_imports(S#elixir_scope.macro_macros, TS#elixir_scope.macro_macros),
macro_aliases=merge_aliases(S#elixir_scope.macro_aliases, TS#elixir_scope.macro_aliases)
} }.
merge_aliases(A1, A2) ->
orddict:merge(fun(_K,_V1,V2) -> V2 end, A1, A2).
merge_imports(A1, A2) ->
A2 ++ lists:foldl(fun({ Key, _ }, Acc) -> lists:keydelete(Key, 1, Acc) end, A1, A2).
S
).
%% Helpers
@@ -246,15 +242,25 @@ find_dispatch(Tuple, List) ->
is_import(Meta, Tuple, S) ->
case lists:keyfind(import, 1, Meta) of
{ import, _ } = Import ->
not_an_import(Tuple, S#elixir_scope.macro_functions)
andalso not_an_import(Tuple, S#elixir_scope.macro_macros)
andalso Import;
case lists:keyfind(context, 1, Meta) of
{ context, Context } ->
not_an_import(Tuple, Context, S#elixir_scope.macro_functions)
andalso not_an_import(Tuple, Context, S#elixir_scope.macro_macros)
andalso Import;
false ->
false
end;
false ->
false
end.
not_an_import(Tuple, Pairs) ->
not lists:any(fun({ _, List }) -> lists:member(Tuple, List) end, Pairs).
not_an_import(Tuple, Context, Dict) ->
case orddict:find(Context, Dict) of
{ ok, Pairs } ->
not lists:any(fun({ _, List }) -> lists:member(Tuple, List) end, Pairs);
error ->
true
end.
munge_stacktrace(Info, [{ _, _, [S|_], _ }|_], S) ->
[Info];
@@ -268,24 +274,11 @@ munge_stacktrace(Info, [H|T], S) ->
munge_stacktrace(_, [], _) ->
[].
%% ERROR HANDLING
format_error({ unrequired_module, { Receiver, Name, Arity, Required }}) ->
String = string:join([elixir_errors:inspect(R) || R <- Required], ", "),
io_lib:format("tried to invoke macro ~ts.~ts/~B but module was not required. Required: ~ts",
[elixir_errors:inspect(Receiver), Name, Arity, String]);
format_error({ ambiguous_call, { Mod1, Mod2, Name, Arity }}) ->
io_lib:format("function ~ts/~B imported from both ~ts and ~ts, call is ambiguous",
[Name, Arity, elixir_errors:inspect(Mod1), elixir_errors:inspect(Mod2)]).
%% INTROSPECTION
remote_function(Meta, Receiver, Name, Arity, S) ->
Line = ?line(Meta),
Final =
case (Receiver == ?BUILTIN) andalso is_element({ Name, Arity }, in_erlang_functions()) of
case (Receiver == ?builtin) andalso is_element({ Name, Arity }, in_erlang_functions()) of
true -> erlang;
false -> Receiver
end,
@@ -296,6 +289,20 @@ remote_function(Meta, Receiver, Name, Arity, S) ->
{ integer, Line, Arity}
} }, S }.
%% ERROR HANDLING
format_error({ unrequired_module, { Receiver, Name, Arity, Required }}) ->
String = 'Elixir.Enum':join([elixir_errors:inspect(R) || R <- Required], ", "),
io_lib:format("tried to invoke macro ~ts.~ts/~B but module was not required. Required: ~ts",
[elixir_errors:inspect(Receiver), Name, Arity, String]);
format_error({ ambiguous_call, { Mod1, Mod2, Name, Arity }}) ->
io_lib:format("function ~ts/~B imported from both ~ts and ~ts, call is ambiguous",
[Name, Arity, elixir_errors:inspect(Mod1), elixir_errors:inspect(Mod2)]).
%% INTROSPECTION
%% Do not try to get macros from Erlang. Speeds up compilation a bit.
get_optional_macros(erlang) -> [];
@@ -310,20 +317,25 @@ get_optional_macros(Receiver) ->
{ error, _ } -> []
end.
%% Functions imported from Kernel module. Sorted on compilation.
in_elixir_functions() ->
elixir_imported_functions() ->
try
?BUILTIN:'__info__'(functions)
?builtin:'__info__'(functions)
catch
error:undef -> []
error:undef -> in_erlang_functions()
end.
elixir_imported_macros() ->
try
?builtin:'__info__'(macros)
catch
error:undef -> in_erlang_macros()
end.
%% Macros imported from Kernel module. Sorted on compilation.
in_elixir_macros() ->
try
?BUILTIN:'__info__'(macros)
?builtin:'__info__'(macros)
catch
error:undef -> []
end.
@@ -337,6 +349,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 },
@@ -347,8 +363,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 },
@@ -428,6 +447,9 @@ in_erlang_macros() ->
{'and',2},
{apply,2},
{apply,3},
{binding,0},
{binding,1},
{binding,2},
{'case',2},
{def,1},
{def,2},
@@ -443,7 +465,6 @@ in_erlang_macros() ->
{defp,2},
{defp,4},
{function,1},
{function,2},
{function,3},
{in,2},
{'not',1},
+16 -17
View File
@@ -9,20 +9,11 @@
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
inspect(Atom) when is_atom(Atom) ->
case atom_to_list(Atom) of
"Elixir-" ++ Rest -> [to_dot(R) || R <- Rest];
Else -> Else
end;
inspect(Other) -> Other.
to_dot($-) -> $.;
to_dot(L) -> L.
'Elixir.Binary.Inspect.Atom':inspect(Atom, []).
%% Raised during macros translation.
@@ -37,7 +28,7 @@ syntax_error(Meta, File, Message) when is_binary(Message) ->
syntax_error(Meta, File, Format, Args) ->
Message = io_lib:format(Format, Args),
raise(Meta, File, 'Elixir.SyntaxError', iolist_to_binary(Message)).
raise(Meta, File, 'Elixir.SyntaxError', unicode:characters_to_binary(Message)).
%% Raised on tokenizing/parsing
@@ -61,7 +52,7 @@ parse_error(Meta, File, Error, Token) ->
BinToken = if
Token == [] -> <<>>;
true -> iolist_to_binary(Token)
true -> unicode:characters_to_binary(Token)
end,
Message = <<BinError / binary, BinToken / binary >>,
@@ -84,6 +75,11 @@ deprecation(Meta, File, Message, Args) ->
%% Handle warnings and errors (called during module compilation)
%% output warning based on warnings going to stdout or stderr.
output_warning(Warning) ->
elixir_code_server:cast(register_warning),
io:format(standard_error, Warning, []).
%% Ignore on bootstrap
handle_file_warning(true, _File, { _Line, sys_core_fold, nomatch_guard }) -> [];
handle_file_warning(true, _File, { _Line, sys_core_fold, { nomatch_shadow, _ } }) -> [];
@@ -94,19 +90,22 @@ handle_file_warning(_, _File, { _Line, sys_core_fold, useless_building }) -> [];
%% This is an Erlang bug, it considers { tuple, _ }.call to always fail
handle_file_warning(_, _File, { _Line, v3_kernel, bad_call }) -> [];
%% We handle unused local warnings ourselves
handle_file_warning(_, _File, { _Line, erl_lint, { unused_function, _ } }) -> [];
%% Rewrite
handle_file_warning(_, File, {Line,erl_lint,{undefined_behaviour_func,{Fun,Arity},Module}}) ->
Kind = protocol_or_behaviour(Module),
Raw = "undefined ~ts function ~ts/~B (for ~ts ~ts)",
Message = io_lib:format(Raw, [Kind, Fun, Arity, Kind, inspect(Module)]),
io:format(file_format(Line, File, Message));
output_warning(file_format(Line, File, Message));
handle_file_warning(_, File, {Line,erl_lint,{undefined_behaviour,Module}}) ->
case elixir_compiler:get_opt(internal) of
true -> [];
false ->
Message = io_lib:format("behaviour ~ts undefined", [inspect(Module)]),
io:format(file_format(Line, File, Message))
output_warning(file_format(Line, File, Message))
end;
handle_file_warning(_, _File, {Line,erl_lint,{unused_var,_Var}}) when Line =< 0 ->
@@ -114,16 +113,16 @@ handle_file_warning(_, _File, {Line,erl_lint,{unused_var,_Var}}) when Line =< 0
handle_file_warning(_, File, {Line,erl_lint,{unused_var,Var}}) ->
Message = format_error(erl_lint, { unused_var, format_var(Var) }),
io:format(file_format(Line, File, Message));
output_warning(file_format(Line, File, Message));
handle_file_warning(_, File, {Line,erl_lint,{shadowed_var,Var,Where}}) ->
Message = format_error(erl_lint, { shadowed_var, format_var(Var), Where }),
io:format(file_format(Line, File, Message));
output_warning(file_format(Line, File, Message));
%% Default behavior
handle_file_warning(_, File, {Line,Module,Desc}) ->
Message = format_error(Module, Desc),
io:format(file_format(Line, File, Message)).
output_warning(file_format(Line, File, Message)).
handle_file_warning(File, Desc) ->
handle_file_warning(false, File, Desc).
-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).
+51 -104
View File
@@ -1,35 +1,12 @@
%% Module responsible for handling imports and conflicts.
%% Module responsible for handling imports and conflicts
%% in between local functions and imports.
%% For imports dispatch, please check elixir_dispatch.
-module(elixir_import).
-export([import/5, recorded_locals/1, format_error/1,
ensure_no_import_conflict/4, ensure_no_local_conflict/4,
build_table/1, delete_table/1, record/4]).
-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), [set, named_table, public]).
delete_table(Module) ->
ets:delete(table(Module)).
record(_Kind, _Tuple, _Receiver, nil) ->
false;
record(import, Tuple, Receiver, Module) ->
try
ets:insert(table(Module), { Tuple, Receiver })
catch
error:badarg -> false
end.
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.
@@ -60,9 +37,19 @@ import(Meta, Ref, Opts, Selector, S) ->
SF#elixir_scope{macros=Macros, macro_macros=TempM}
end,
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
@@ -70,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 } },
@@ -83,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
@@ -97,34 +82,24 @@ calculate(Meta, Key, Opts, Old, Temp, AvailableFun, S) ->
Final = remove_internals(Set),
case Final of
[] -> { keydelete(Key, Old), keydelete(Key, Temp) };
[] -> { keydelete(Key, Old), if_quoted(Meta, Temp, fun(Value) -> keydelete(Key, Value) end) };
_ ->
ensure_no_in_erlang_macro_conflict(Meta, File, Key, Final, internal_conflict),
{ [{ Key, Final }|keydelete(Key, Old)], [{ Key, Final }|keydelete(Key, Temp)] }
ensure_no_special_form_conflict(Meta, File, Key, Final, internal_conflict),
{ [{ Key, Final }|keydelete(Key, Old)],
if_quoted(Meta, Temp, fun(Value) -> [{ Key, Final }|keydelete(Key, Value)] end) }
end.
%% 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]).
if_quoted(Meta, Temp, Callback) ->
case lists:keyfind(context, 1, Meta) of
{ context, Context } ->
Current = case orddict:find(Context, Temp) of
{ ok, Value } -> Value;
error -> []
end,
orddict:store(Context, Callback(Current), Temp);
_ ->
Temp
end.
%% Retrieve functions and macros from modules
@@ -165,58 +140,34 @@ 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_in_erlang_macro_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, { Key, Name, Arity } },
elixir_errors:form_error(Meta, File, ?MODULE, Tuple);
[] ->
ok
end.
ensure_no_special_form_conflict(Meta, File, Module, AllDefined, local_conflict).
%% Ensure the given functions don't clash with any
%% of Elixir non overridable macros.
ensure_no_in_erlang_macro_conflict(Meta, File, Key, [{Name,Arity}|T], Reason) ->
ensure_no_special_form_conflict(Meta, File, Key, [{Name,Arity}|T], Reason) ->
Values = lists:filter(fun({X,Y}) ->
(Name == X) andalso ((Y == '*') orelse (Y == Arity))
end, non_overridable_macros()),
end, special_form()),
case Values /= [] of
true ->
Tuple = { Reason, { Key, Name, Arity } },
elixir_errors:form_error(Meta, File, ?MODULE, Tuple);
false -> ensure_no_in_erlang_macro_conflict(Meta, File, Key, T, Reason)
false -> ensure_no_special_form_conflict(Meta, File, Key, T, Reason)
end;
ensure_no_in_erlang_macro_conflict(_Meta, _File, _Key, [], _) -> ok.
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]);
@@ -243,30 +194,25 @@ intersection([H|T], All) ->
intersection([], _All) -> [].
%% INTROSPECTION
%% Internal funs that are never imported etc.
remove_underscored(default, List) -> remove_underscored(List);
remove_underscored(_, List) -> List.
remove_underscored(default, List) ->
lists:filter(fun({ Name, _ }) ->
case atom_to_list(Name) of
"_" ++ _ -> false;
_ -> true
end
end, List);
remove_underscored([{ Name, _ } = H|T]) when Name < a ->
case atom_to_list(Name) of
[$_, $_, _, $_, $_] -> [H|remove_underscored(T)];
"_" ++ _ -> remove_underscored(T);
_ -> [H|remove_underscored(T)]
end;
remove_underscored(T) ->
T.
remove_underscored(_, List) -> List.
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.
non_overridable_macros() ->
special_form() ->
[
{'^',1},
{'=',2},
@@ -275,7 +221,7 @@ non_overridable_macros() ->
{'__ambiguousop__','*'},
{'__scope__',2},
{'__block__','*'},
{'->','2'},
{'->','*'},
{'<<>>','*'},
{'{}','*'},
{'[]','*'},
@@ -290,6 +236,7 @@ non_overridable_macros() ->
{'__CALLER__',0},
{'__MODULE__',0},
{'__FILE__',0},
{'__DIR__',0},
{'__aliases__','*'},
{'quote',1},
{'quote',2},
-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
+38 -57
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, [umergev/2, umergec/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
@@ -29,33 +29,23 @@ translate({ '{}', Meta, Args } = Original, S) when is_list(Args) ->
translate({ '[]', _Meta, [] }, S) ->
{ { nil, 0 }, S };
translate({ '[]', _Meta, Args } = Original, S) when is_list(Args) ->
translate({ '[]', Meta, Args } = Original, S) when is_list(Args) ->
case elixir_partials:handle(Original, S, allow_tail) of
error ->
[RTail|RArgs] = lists:reverse(Args),
case RTail of
{'|',_,[Left,Right]} ->
Exprs = [Left|RArgs],
{ Tail, ST } = translate_each(Right, S),
ListS = umergec(S, ST);
RExprs = [Left|RArgs],
TailFun = fun(ST) -> translate_each(Right, ST) end;
_ ->
Exprs = [RTail|RArgs],
Tail = { nil, 0 },
ST = S,
ListS = S
RExprs = [RTail|RArgs],
TailFun = fun(ST) -> { { nil, ?line(Meta) }, ST } end
end,
{ FExprs, FS } = case S#elixir_scope.context of
match ->
elixir_tree_helpers:build_reverse_list(fun elixir_translator:translate_each/2, Exprs, 0, ListS, Tail);
_ ->
{ TArgs, { SC, SV } } =
elixir_tree_helpers:build_reverse_list(fun elixir_translator:translate_arg/2, Exprs, 0, { ListS, ListS }, Tail),
{ TArgs, umergec(SV, SC) }
end,
{ FExprs, umergev(ST, FS) };
{ Exprs, SE } = translate_args(lists:reverse(RExprs), S),
{ Tail, ST } = TailFun(SE),
{ elixir_tree_helpers:list_to_cons(?line(Meta), Exprs, Tail), ST };
Else -> Else
end;
@@ -75,7 +65,7 @@ translate(Atom, S) when is_atom(Atom) ->
{ { atom, 0, Atom }, S };
translate(Bitstring, S) when is_bitstring(Bitstring) ->
{ elixir_tree_helpers:abstract_syntax(Bitstring), S }.
{ elixir_tree_helpers:elixir_to_erl(Bitstring), S }.
%% Helpers
@@ -106,37 +96,28 @@ build_bitstr_each(Fun, [{'::',_,[H,V]}|T], Meta, S, Acc) ->
build_bitstr_each(Fun, [H|T], Meta, S, Acc) ->
build_bitstr_each(Fun, T, Meta, S, Acc, H, default, default).
build_bitstr_each(Fun, T, Meta, S, Acc, H, Size, Types) when is_list(H) ->
case is_default_or_utf(Types) of
true ->
{ NewAcc, NewS } = lists:foldl(fun(L, { LA, LS }) ->
{ FL, FS } = Fun(L, LS),
{ [{ bin_element, ?line(Meta), FL, Size, Types }|LA], FS }
end, { Acc, S }, H),
build_bitstr_each(Fun, T, Meta, NewS, NewAcc);
false ->
build_bitstr_default(Fun, T, Meta, S, Acc, H, Size, Types)
end;
build_bitstr_each(Fun, T, Meta, S, Acc, H, Size, Types) when is_bitstring(H) ->
case is_default_or_utf(Types) of
true ->
Line = ?line(Meta),
{ bin, _, Elements } = elixir_tree_helpers:abstract_syntax(H),
NewAcc = lists:foldl(fun({ bin_element, _, Expr, _, _ }, FinalAcc) ->
[{ bin_element, Line, Expr, Size, Types }|FinalAcc]
end, Acc, Elements),
build_bitstr_each(Fun, T, Meta, S, NewAcc);
false ->
build_bitstr_default(Fun, T, Meta, S, Acc, H, Size, Types)
end;
build_bitstr_each(Fun, T, Meta, S, Acc, H, Size, Types) ->
build_bitstr_default(Fun, T, Meta, S, Acc, H, Size, Types).
build_bitstr_default(Fun, T, Meta, S, Acc, H, Size, Types) ->
{ Expr, NS } = Fun(H, S),
build_bitstr_each(Fun, T, Meta, NS, [{ bin_element, ?line(Meta), Expr, Size, Types }|Acc]).
case (is_default_or_utf(Types) andalso Expr) of
{ bin, _, BinElements } ->
build_bitstr_each(Fun, T, Meta, NS, rehash_bin_elements(BinElements, Size, Types, []) ++ Acc);
{ cons, _, _, _ } = Cons ->
build_bitstr_each(Fun, T, Meta, NS, rehash_cons(Cons, Size, Types, []) ++ Acc);
{ nil, _ } ->
build_bitstr_each(Fun, T, Meta, NS, Acc);
_ ->
build_bitstr_each(Fun, T, Meta, NS, [{ bin_element, ?line(Meta), Expr, Size, Types }|Acc])
end.
rehash_cons({ nil, _ }, _Size, _Types, Acc) -> Acc;
rehash_cons({ cons, Line, Left, Right }, Size, Types, Acc) ->
rehash_cons(Right, Size, Types, [{ bin_element, Line, Left, Size, Types }|Acc]).
rehash_bin_elements([{ bin_element, Line, Expr, _S, _T }|T], Size, Types, Acc) ->
rehash_bin_elements(T, Size, Types, [{ bin_element, Line, Expr, Size, Types }|Acc]);
rehash_bin_elements([], _Size, _Types, Acc) ->
Acc.
is_default_or_utf(default) -> true;
is_default_or_utf([UTF|_]) when UTF == utf8; UTF == utf16; UTF == utf32 -> true;
@@ -175,8 +156,8 @@ extract_bit_values(Meta, [Size|T], default, Types, S) when is_integer(Size) anda
extract_bit_values(Meta, T, {integer, Meta, Size}, Types, S);
extract_bit_values(Meta, [{ '|', _, [Left, Right] }], Size, Types, S) ->
Expanded = 'Elixir.Macro':expand(Right, elixir_scope:to_ex_env({ ?line(Meta), S })),
extract_bit_values(Meta, [Left|join_expansion(Expanded,[])], Size, Types, S);
{ Expanded, ES } = 'Elixir.Macro':expand_all(Right, elixir_scope:to_ex_env({ ?line(Meta), S }), S),
extract_bit_values(Meta, [Left|join_expansion(Expanded,[])], Size, Types, ES);
extract_bit_values(Meta, [_|_] = All, Size, Types, S) ->
handle_unknown_specifier(Meta, All, Size, Types, S);
@@ -217,11 +198,11 @@ extract_bit_type_or_size(_Value, _Args, _Other, _Types) ->
{ error, unknown }.
handle_unknown_specifier(Meta, [H|T], Size, Types, S) ->
case 'Elixir.Macro':expand(H, elixir_scope:to_ex_env({ ?line(Meta), S })) of
H ->
elixir_errors:syntax_error(Meta, S#elixir_scope.file, "unknown bitstring specifier ~ts", ['Elixir.Macro':to_binary(H)]);
E ->
extract_bit_values(Meta, join_expansion(E,T), Size, Types, S)
case 'Elixir.Macro':expand_all(H, elixir_scope:to_ex_env({ ?line(Meta), S }), S) of
{ H, ES } ->
elixir_errors:syntax_error(Meta, ES#elixir_scope.file, "unknown bitstring specifier ~ts", ['Elixir.Macro':to_string(H)]);
{ E, ES } ->
extract_bit_values(Meta, join_expansion(E,T), Size, Types, ES)
end.
join_expansion({ '__block__', _, [Expanded] }, Tail) -> join_expansion(Expanded, Tail);
+130 -65
View File
@@ -6,12 +6,9 @@
-import(elixir_scope, [umergec/2]).
-import(elixir_errors, [syntax_error/3, syntax_error/4,
assert_no_function_scope/3, assert_module_scope/3, assert_no_match_or_guard_scope/3]).
-include("elixir.hrl").
-define(FUNS(Kind), Kind == def; Kind == defp; Kind == defmacro; Kind == defmacrop).
-define(IN_TYPES(Kind), Kind == atom orelse Kind == integer orelse Kind == float).
-compile({parse_transform, elixir_transform}).
-define(opt_in_types(Kind), Kind == atom orelse Kind == integer orelse Kind == float).
%% Operators
@@ -63,13 +60,17 @@ translate({ function, MetaFA, [{ '/', _, [{F, Meta, C}, A]}] }, S) when is_atom(
assert_no_match_or_guard_scope(Meta, 'function', S),
WrappedMeta =
case (lists:keyfind(import, 1, Meta) == false) andalso lists:keyfind(import_fa, 1, MetaFA) of
{ import_fa, Receiver } -> [{ import, Receiver }|Meta];
case lists:keyfind(import_fa, 1, MetaFA) of
{ import_fa, { Receiver, Context } } ->
lists:keystore(context, 1,
lists:keystore(import, 1, Meta, { import, Receiver }),
{ context, Context }
);
false -> Meta
end,
case elixir_dispatch:import_function(WrappedMeta, F, A, S) of
false -> syntax_error(WrappedMeta, S#elixir_scope.file, "cannot convert a macro to a function");
false -> syntax_error(WrappedMeta, S#elixir_scope.file, "expected ~ts/~B to be a function, but it is a macro", [F, A]);
Else -> Else
end;
@@ -77,20 +78,6 @@ translate({ function, Meta, [_] }, S) ->
assert_no_match_or_guard_scope(Meta, 'function', S),
syntax_error(Meta, S#elixir_scope.file, "invalid args for function");
translate({ function, Meta, [_, _] = Args }, S) ->
assert_no_match_or_guard_scope(Meta, 'function', S),
case translate_args(Args, S) of
{ [{atom,_,Name}, {integer,_,Arity}], SA } ->
elixir_errors:deprecation(Meta, S#elixir_scope.file, "function(:~ts, ~B) is deprecated, please use function(~ts/~B)", [Name, Arity, Name, Arity]),
case elixir_dispatch:import_function(Meta, Name, Arity, SA) of
false -> syntax_error(Meta, S#elixir_scope.file, "cannot convert a macro to a function");
Else -> Else
end;
_ ->
syntax_error(Meta, S#elixir_scope.file, "cannot dynamically retrieve local function, use function/3 instead")
end;
translate({ function, Meta, [_,_,_] = Args }, S) when is_list(Args) ->
assert_no_match_or_guard_scope(Meta, 'function', S),
{ [A,B,C], SA } = translate_args(Args, S),
@@ -132,17 +119,66 @@ translate({'@', Meta, [{ Name, _, Args }]}, S) ->
translate_each({
{ '.', Meta, ['Elixir.Module', get_attribute] },
Meta,
[ { '__MODULE__', Meta, false }, Name ]
[ { '__MODULE__', Meta, false }, Name, true ]
}, S);
_ ->
Contents = 'Elixir.Module':get_attribute(S#elixir_scope.module, Name),
{ elixir_tree_helpers:abstract_syntax(Contents), S }
{ elixir_tree_helpers:elixir_to_erl(Contents), S }
end;
_ ->
syntax_error(Meta, S#elixir_scope.file, "expected 0 or 1 argument for @~ts, got: ~p", [Name, length(Args)])
end
end;
%% Binding
translate({ 'binding', Meta, [] }, S) ->
translate({ 'binding', Meta, [false] }, S);
translate({ 'binding', Meta, [false] }, S) ->
Line = ?line(Meta),
{ elixir_tree_helpers:list_to_cons(Line,
[ to_var_value_tuple(Line, Name, Var) || { { Name, nil }, Var } <- S#elixir_scope.vars]), S };
translate({ 'binding', Meta, [true] }, S) ->
Line = ?line(Meta),
{ elixir_tree_helpers:list_to_cons(Line,
[ to_var_value_tuple(Line, Name, Kind, Var) || { { Name, Kind }, Var } <- S#elixir_scope.vars]), S };
translate({ 'binding', Meta, [List] }, #elixir_scope{vars=Vars} = S) when is_list(List) ->
Line = ?line(Meta),
Dict = lists:foldl(fun
(Name, Acc) when is_atom(Name) ->
case orddict:find({ Name, nil }, Vars) of
{ ok, Var } ->
orddict:store(Name, Var, Acc);
error ->
Acc
end;
(Name, _Acc) ->
elixir_errors:syntax_error(Line, S#elixir_scope.file, "binding/1 expects a list of atoms "
"at compilation time, got: ~ts", ['Elixir.Macro':to_string(Name)])
end, [], List),
{ elixir_tree_helpers:list_to_cons(Line,
[ to_var_value_tuple(Line, Name, Var) || { Name, Var } <- Dict]), S };
translate({ 'binding', Meta, [List, true] }, #elixir_scope{vars=Vars} = S) when is_list(List) ->
Line = ?line(Meta),
Dict = lists:foldl(fun
(Tuple, Acc) when is_tuple(Tuple) ->
case orddict:find(Tuple, Vars) of
{ ok, Var } ->
orddict:store(Tuple, Var, Acc);
error ->
Acc
end;
(Tuple, _Acc) ->
elixir_errors:syntax_error(Line, S#elixir_scope.file, "binding/2 expects a list of tuples "
"at compilation time, got: ~ts", ['Elixir.Macro':to_string(Tuple)])
end, [], List),
{ elixir_tree_helpers:list_to_cons(Line,
[ to_var_value_tuple(Line, Name, Kind, Var) || { { Name, Kind }, Var } <- Dict]), S };
%% Case
translate({'case', Meta, [Expr, KV]}, S) ->
@@ -160,12 +196,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)),
@@ -176,7 +211,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
@@ -211,43 +247,41 @@ translate({defmodule, Meta, [Ref, KV]}, S) ->
FullModule = expand_module(Ref, Module, S),
RS = case elixir_aliases:nesting_alias(S#elixir_scope.module, FullModule) of
{ New, Old } ->
S#elixir_scope{
aliases=orddict:store(New, Old, S#elixir_scope.aliases),
macro_aliases=orddict:store(New, Old, S#elixir_scope.macro_aliases)};
false ->
S
{ New, Old } -> elixir_aliases:store(Meta, New, Old, S);
false -> S
end,
{
{ atom, Meta, FullModule },
RS#elixir_scope{scheduled=[FullModule|S#elixir_scope.scheduled]}
RS#elixir_scope{context_modules=[FullModule|S#elixir_scope.context_modules]}
};
_ ->
{ TRef, S }
end,
MS = FS#elixir_scope{check_clauses=true,local=nil},
MS = FS#elixir_scope{local=nil},
{ elixir_module:translate(Meta, FRef, Block, MS), FS };
translate({Kind, Meta, [Call]}, S) when ?FUNS(Kind) ->
translate({Kind, Meta, [Call]}, S) when ?defs(Kind) ->
translate({Kind, Meta, [Call, nil]}, S);
translate({Kind, Meta, [Call, Expr]}, S) when ?FUNS(Kind) ->
translate({Kind, Meta, [Call, Expr]}, S) when ?defs(Kind) ->
assert_module_scope(Meta, Kind, S),
assert_no_function_scope(Meta, Kind, S),
{ TCall, SC } = elixir_quote:user_quote(Call, S),
{ TExpr, SE } = elixir_quote:user_quote(Expr, SC),
{ elixir_def:wrap_definition(Kind, Meta, TCall, TExpr, SE), SE };
{ TCall, QC, SC } = elixir_quote:erl_escape(Call, true, S),
{ TExpr, QE, SE } = elixir_quote:erl_escape(Expr, true, SC),
CheckClauses = (not lists:keymember(context, 1, Meta)) andalso
(not QC#elixir_quote.unquoted) andalso (not QE#elixir_quote.unquoted),
{ elixir_def:wrap_definition(Kind, Meta, TCall, TExpr, CheckClauses, SE), SE };
translate({Kind, Meta, [Name, Args, Guards, Expr]}, S) when ?FUNS(Kind) ->
translate({Kind, Meta, [Name, Args, Guards, Expr]}, S) when ?defs(Kind) ->
assert_module_scope(Meta, Kind, S),
assert_no_function_scope(Meta, Kind, S),
{ TName, NS } = translate_each(Name, S),
{ TArgs, AS } = translate_each(Args, NS),
{ TGuards, GS } = translate_each(Guards, AS),
{ TExpr, ES } = translate_each(Expr, GS),
{ elixir_def:wrap_definition(Kind, Meta, TName, TArgs, TGuards, TExpr, ES), ES };
{ TName, SN } = translate_each(Name, S),
{ TArgs, SA } = translate_each(Args, SN),
{ TGuards, SG } = translate_each(Guards, SA),
{ TExpr, SE } = translate_each(Expr, SG),
{ elixir_def:wrap_definition(Kind, Meta, TName, TArgs, TGuards, TExpr, false, SE), SE };
%% Apply - Optimize apply by checking what doesn't need to be dispatched dynamically
@@ -281,21 +315,26 @@ translate_in(Meta, Left, Right, S) ->
false -> { TLeft, SR }
end,
Expr = case TRight of
{ InGuard, TExpr } = case TRight of
{ nil, _ } ->
Expr = { atom, Line, false },
{ Cache, Expr };
{ cons, _, _, _ } ->
[H|T] = elixir_tree_helpers:cons_to_list(TRight),
lists:foldr(fun(X, Acc) ->
Expr = lists:foldr(fun(X, Acc) ->
{ op, Line, 'orelse', { op, Line, '==', Var, X }, Acc }
end, { op, Line, '==', Var, H }, T);
end, { op, Line, '==', Var, H }, T),
{ Cache, Expr };
{ string, _, [H|T] } ->
lists:foldl(fun(X, Acc) ->
Expr = lists:foldl(fun(X, Acc) ->
{ op, Line, 'orelse', { op, Line, '==', Var, { integer, Line, X } }, Acc }
end, { op, Line, '==', Var, { integer, Line, H } }, T);
end, { op, Line, '==', Var, { integer, Line, H } }, T),
{ Cache, Expr };
{ tuple, _, [{ atom, _, 'Elixir.Range' }, Start, End] } ->
case { Start, End } of
{ { K1, _, StartInt }, { K2, _, EndInt } } when ?IN_TYPES(K1), ?IN_TYPES(K2), StartInt =< EndInt ->
Expr = case { Start, End } of
{ { K1, _, StartInt }, { K2, _, EndInt } } when ?opt_in_types(K1), ?opt_in_types(K2), StartInt =< EndInt ->
increasing_compare(Line, Var, Start, End);
{ { K1, _, _ }, { K2, _, _ } } when ?IN_TYPES(K1), ?IN_TYPES(K2) ->
{ { K1, _, _ }, { K2, _, _ } } when ?opt_in_types(K1), ?opt_in_types(K2) ->
decreasing_compare(Line, Var, Start, End);
_ ->
{ op, Line, 'orelse',
@@ -305,14 +344,20 @@ translate_in(Meta, Left, Right, S) ->
{ op, Line, 'andalso',
{ op, Line, '<', End, Start},
decreasing_compare(Line, Var, Start, End) } }
end;
end,
{ Cache, Expr };
_ ->
syntax_error(Meta, S#elixir_scope.file, "invalid args for operator in, it expects an explicit array or an explicit range on the right side")
case Cache of
true ->
{ false, ?wrap_call(Line, 'Elixir.Enum', 'member?', [TRight, TLeft]) };
false ->
syntax_error(Meta, S#elixir_scope.file, "invalid args for operator in, it expects an explicit array or an explicit range on the right side when used in guard expressions")
end
end,
case Cache of
true -> { Var, { block, Line, [ { match, Line, Var, TLeft }, Expr ] }, SV };
false -> { Var, Expr, SV }
case InGuard of
true -> { Var, { block, Line, [ { match, Line, Var, TLeft }, TExpr ] }, SV };
false -> { Var, TExpr, SV }
end.
increasing_compare(Line, Var, Start, End) ->
@@ -325,16 +370,22 @@ decreasing_compare(Line, Var, Start, End) ->
{ op, Line, '=<', Var, Start },
{ op, Line, '>=', Var, End } }.
rewrite_case_clauses([{do,[{in,_,[{'_',_,_},[false,nil]]}],False},{do,[{'_',_,_}],True}]) ->
[{do,[false],False},{do,[true],True}];
rewrite_case_clauses([{do,Meta1,[{in,_,[{'_',_,_},[false,nil]]}],False},{do,Meta2,[{'_',_,_}],True}]) ->
[{do,Meta1,[false],False},{do,Meta2,[true],True}];
rewrite_case_clauses(Clauses) ->
Clauses.
expand_module(Raw, Module, #elixir_scope{module=Nesting}) when is_atom(Raw); Nesting == nil ->
Module;
%% defmodule :foo
expand_module(Raw, _Module, _S) when is_atom(Raw) ->
Raw;
expand_module({ '__aliases__', _, List } = Alias, Module, S) when List /= ['Elixir'] ->
%% defmodule Hello
expand_module({ '__aliases__', _, [H] }, _Module, S) ->
elixir_aliases:concat([S#elixir_scope.module, H]);
%% defmodule Hello.World
expand_module({ '__aliases__', _, _ } = Alias, Module, S) ->
case elixir_aliases:expand(Alias, S#elixir_scope.aliases, S#elixir_scope.macro_aliases) of
Atom when is_atom(Atom) ->
Module;
@@ -342,6 +393,7 @@ expand_module({ '__aliases__', _, List } = Alias, Module, S) when List /= ['Elix
elixir_aliases:concat([S#elixir_scope.module, Module])
end;
%% defmodule Elixir.Hello.World
expand_module(_Raw, Module, S) ->
elixir_aliases:concat([S#elixir_scope.module, Module]).
@@ -349,6 +401,19 @@ is_reserved_data(moduledoc) -> true;
is_reserved_data(doc) -> true;
is_reserved_data(_) -> false.
to_var_value_tuple(Line, Name, Var) ->
{ tuple, Line,
[ { atom, Line, Name },
{ var, Line, Var } ] }.
to_var_value_tuple(Line, Name, Kind, Var) ->
{ tuple, Line,
[ { tuple, Line, [
{ atom, Line, Name },
{ atom, Line, Kind }
] },
{ var, Line, Var } ] }.
spec_to_macro(type) -> deftype;
spec_to_macro(typep) -> deftypep;
spec_to_macro(opaque) -> defopaque;
+60 -47
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),
@@ -15,7 +18,7 @@ eval_quoted(Module, Quoted, RawBinding, Opts) ->
false -> Line = 1
end,
{ Value, FinalBinding, _Scope } = elixir:eval_quoted([Quoted], Binding, Line, Scope#elixir_scope{check_clauses=false}),
{ Value, FinalBinding, _Scope } = elixir:eval_quoted([Quoted], Binding, Line, Scope),
{ Value, FinalBinding }.
scope_for_eval(Module, #elixir_scope{} = S) ->
@@ -32,7 +35,7 @@ data_table(Module) ->
Module.
docs_table(Module) ->
?atom_concat([o, Module]).
ets:lookup_element(Module, ?docs_attr, 2).
%% TRANSFORMATION FUNCTIONS
@@ -42,7 +45,7 @@ docs_table(Module) ->
translate(Meta, Ref, Block, S) ->
Line = ?line(Meta),
MetaBlock = elixir_tree_helpers:abstract_syntax(Block),
MetaBlock = elixir_tree_helpers:elixir_to_erl(Block),
{ MetaS, Vars } = elixir_scope:serialize_with_vars(Line, S),
Args = [{integer, Line, Line}, Ref, MetaBlock, Vars, MetaS],
@@ -50,8 +53,13 @@ translate(Meta, Ref, Block, S) ->
%% The compilation hook.
compile(Line, Module, Block, Vars, #elixir_scope{} = S) when is_atom(Module) ->
compile(Line, Module, Block, Vars, #elixir_scope{context_modules=FileModules} = RawS) when is_atom(Module) ->
C = elixir_compiler:get_opts(),
S = case lists:member(Module, FileModules) of
true -> RawS;
false -> RawS#elixir_scope{context_modules=[Module|FileModules]}
end,
File = S#elixir_scope.file,
FileList = binary_to_list(File),
@@ -60,14 +68,23 @@ compile(Line, Module, Block, Vars, #elixir_scope{} = S) when is_atom(Module) ->
try
Result = eval_form(Line, Module, Block, Vars, S),
{ Export, Private, Def, Defmacro, Defmacrop, Functions } = elixir_def:unwrap_stored_definitions(FileList, Module),
{ Export, Private, Def, Defmacro, Functions } = elixir_def:unwrap_stored_definitions(FileList, Module),
{ All, Forms0 } = functions_form(Line, File, Module, Export, Private, Def, Defmacro, Defmacrop, Functions, C),
Forms1 = specs_form(Line, Module, Defmacro, Defmacrop, Forms0, C),
{ All, Forms0 } = functions_form(Line, File, Module, Export, Private, Def, Defmacro, Functions, C),
Forms1 = specs_form(Line, Module, Private, Defmacro, Forms0, C),
Forms2 = attributes_form(Line, File, Module, Forms1),
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 } },
@@ -77,10 +94,10 @@ compile(Line, Module, Block, Vars, #elixir_scope{} = S) when is_atom(Module) ->
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}) ->
@@ -113,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.
@@ -140,7 +150,8 @@ eval_form(Line, Module, Block, Vars, RawS) ->
Value.
%% Return the form with exports and function declarations.
functions_form(Line, File, Module, Export, Private, Def, Defmacro, Defmacrop, RawFunctions, C) ->
functions_form(Line, File, Module, Export, Private, Def, Defmacro, RawFunctions, C) ->
Functions = case elixir_compiler:get_opt(internal, C) of
true -> RawFunctions;
false -> record_rewrite_functions(Module, RawFunctions)
@@ -149,10 +160,8 @@ functions_form(Line, File, Module, Export, Private, Def, Defmacro, Defmacrop, Ra
{ FinalExport, FinalFunctions } =
add_info_function(Line, File, Module, Export, Functions, Def, Defmacro, C),
Recorded = elixir_import:recorded_locals(Module),
elixir_def_local:check_unused_local_macros(File, Recorded, Defmacrop),
{ FinalExport ++ Private, [
PrivateTuple = [Tuple || { Tuple, _, _, _, _ } <- Private],
{ FinalExport ++ PrivateTuple, [
{attribute, Line, export, lists:sort(FinalExport)} | FinalFunctions
] }.
@@ -191,8 +200,8 @@ attributes_form(Line, _File, Module, Current) ->
%% Specs
specs_form(Line, Module, Defmacro, DefmacropWithLine, Forms, C) ->
Defmacrop = [Tuple || { Tuple, _, _ } <- DefmacropWithLine],
specs_form(Line, Module, Private, Defmacro, Forms, C) ->
Defmacrop = [Tuple || { Tuple, defmacrop, _, _, _ } <- Private],
case elixir_compiler:get_opt(internal, C) of
true -> Forms;
_ ->
@@ -289,11 +298,11 @@ add_info_function(Line, File, Module, Export, Functions, Def, Defmacro, C) ->
end.
functions_clause(Def) ->
{ clause, 0, [{ atom, 0, functions }], [], [elixir_tree_helpers:abstract_syntax(Def)] }.
{ clause, 0, [{ atom, 0, functions }], [], [elixir_tree_helpers:elixir_to_erl(Def)] }.
macros_clause(Module, Def, Defmacro) ->
All = handle_builtin_macros(Module, Def, Defmacro),
{ clause, 0, [{ atom, 0, macros }], [], [elixir_tree_helpers:abstract_syntax(All)] }.
{ clause, 0, [{ atom, 0, macros }], [], [elixir_tree_helpers:elixir_to_erl(All)] }.
handle_builtin_macros('Elixir.Kernel', Def, Defmacro) ->
ordsets:subtract(ordsets:union(Defmacro,
@@ -305,14 +314,14 @@ module_clause(Module) ->
docs_clause(Module, true) ->
Docs = ordsets:from_list(ets:tab2list(docs_table(Module))),
{ clause, 0, [{ atom, 0, docs }], [], [elixir_tree_helpers:abstract_syntax(Docs)] };
{ clause, 0, [{ atom, 0, docs }], [], [elixir_tree_helpers:elixir_to_erl(Docs)] };
docs_clause(_Module, _) ->
{ clause, 0, [{ atom, 0, docs }], [], [{ atom, 0, nil }] }.
moduledoc_clause(Line, Module, true) ->
Docs = 'Elixir.Module':get_attribute(Module, moduledoc),
{ clause, 0, [{ atom, 0, moduledoc }], [], [elixir_tree_helpers:abstract_syntax({ Line, Docs })] };
{ clause, 0, [{ atom, 0, moduledoc }], [], [elixir_tree_helpers:elixir_to_erl({ Line, Docs })] };
moduledoc_clause(_Line, _Module, _) ->
{ clause, 0, [{ atom, 0, moduledoc }], [], [{ atom, 0, nil }] }.
@@ -323,8 +332,7 @@ else_clause() ->
% HELPERS
eval_callbacks(Line, Module, Name, Args, RawS) ->
S = RawS#elixir_scope{check_clauses=false},
eval_callbacks(Line, Module, Name, Args, S) ->
Binding = binding_for_eval(Module, []),
Callbacks = lists:reverse(ets:lookup_element(data_table(Module), Name, 2)),
Meta = [{line,Line},{require,false}],
@@ -332,15 +340,20 @@ eval_callbacks(Line, Module, Name, Args, RawS) ->
lists:foreach(fun({M,F}) ->
{ Tree, _ } = elixir_dispatch:dispatch_require(Meta, M, F, Args, S, fun() ->
apply(M, F, Args),
{ { nil, 0 }, S }
{ { atom, 0, nil }, S }
end),
try
erl_eval:exprs([Tree], Binding)
catch
Kind:Reason ->
Info = { M, F, Args, [{ file, binary_to_list(S#elixir_scope.file) }, { line, Line }] },
erlang:raise(Kind, Reason, [Info|erlang:get_stacktrace()])
case Tree of
{ atom, _, Atom } ->
Atom;
_ ->
try
erl_eval:exprs([Tree], Binding)
catch
Kind:Reason ->
Info = { M, F, Args, [{ file, binary_to_list(S#elixir_scope.file) }, { line, Line }] },
erlang:raise(Kind, Reason, [Info|erlang:get_stacktrace()])
end
end
end, Callbacks).
+200 -204
View File
@@ -2,16 +2,18 @@ Nonterminals
grammar expr_list
expr paren_expr block_expr fn_expr bracket_expr call_expr bracket_at_expr max_expr
base_expr matched_expr matched_op_expr unmatched_expr op_expr
add_op mult_op unary_op two_op pipeline_op bin_concat_op
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
comp_op_eol at_op_eol unary_op_eol and_op_eol or_op_eol tail_op_eol
add_op_eol mult_op_eol exp_op_eol two_op_eol type_op_eol stab_op_eol
arrow_op_eol range_op_eol than_op_eol default_op_eol match_op_eol
when_op_eol in_op_eol inc_op_eol
open_paren close_paren empty_paren
open_bracket close_bracket
open_curly close_curly
open_bit close_bit
base_comma_expr comma_expr optional_comma_expr matched_comma_expr
call_args call_args_parens call_args_parens_not_one call_args_no_parens parens_call
stab stab_op stab_expr stab_maybe_expr
call_args call_args_parens parens_call
call_args_no_parens call_args_no_parens_strict call_args_parens_not_one
stab stab_eol stab_expr stab_maybe_expr
kw_eol kw_expr kw_comma kw_base
matched_kw_expr matched_kw_comma matched_kw_base
dot_op dot_ref dot_identifier dot_op_identifier dot_do_identifier
@@ -25,45 +27,40 @@ Terminals
bracket_identifier paren_identifier do_identifier block_identifier
fn 'end' aliases
number signed_number atom bin_string list_string sigil
dot_call_op comp_op op_identifier
'not' 'and' 'or' 'xor' 'when' 'in' 'inlist' 'inbits' 'do'
'true' 'false' 'nil'
'=' '+' '-' '*' '/' '++' '--' '**' '//'
'(' ')' '[' ']' '{' '}' '<<' '>>' '::'
eol ',' '&' '|' '.' '^' '@' '<-' '<>' '->' '|>' '/>' '=~'
'&&' '||' '!' '...' '..'
'<<<' '>>>' '&&&' '|||' '^^^' '~~~'
dot_call_op op_identifier
comp_op at_op unary_op and_op or_op arrow_op match_op
range_op in_op inc_op when_op than_op default_op tail_op
dual_op add_op mult_op exp_op two_op type_op stab_op
'true' 'false' 'nil' 'do' eol ',' '.' '&' '...'
'(' ')' '[' ']' '{' '}' '<<' '>>'
.
Rootsymbol grammar.
Left 5 do.
Right 10 stab_op.
Left 20 ','. % Solve nested call_args conflicts
Right 30 colon_colon_op.
Right 40 when_op.
Right 50 default_op.
Left 60 pipe_op.
Left 70 inc_op.
Right 80 match_op.
Right 110 send_op.
Left 120 oror_op.
Left 130 andand_op.
Left 140 or_op.
Left 150 and_op.
Left 160 comp_expr_op.
Left 170 in_op.
Left 180 range_op.
Left 190 three_op.
Left 200 add_op.
Left 210 mult_op.
Right 220 bin_concat_op.
Right 230 two_op.
Right 290 pipeline_op.
Nonassoc 300 unary_op.
Right 10 stab_op_eol. %% ->
Left 20 ','.
Right 30 type_op_eol. %% ::
Right 40 when_op_eol. %% when
Left 50 inc_op_eol. %% inlist, inbits
Right 60 default_op_eol. %% //
Left 70 tail_op_eol. %% |
Right 80 match_op_eol. %% =
Left 130 or_op_eol. %% ||, |||, or, xor
Left 140 and_op_eol. %% &&, &&&, and
Left 150 comp_op_eol. %% <, >, <=, >=, ==, !=, =~, ===, !==
Right 160 arrow_op_eol. %% < (op), (op) > (e.g <-, |>, <<<, >>>)
Left 170 in_op_eol. %% in
Left 200 range_op_eol. %% ..
Left 210 add_op_eol. %% + (op), - (op)
Left 220 mult_op_eol. %% * (op), / (op)
Right 230 than_op_eol. %% < (op) > (e.g <>)
Right 240 two_op_eol. %% ++, --, **
Left 250 exp_op_eol. %% ^ (op) (e.g ^^^)
Nonassoc 300 unary_op_eol. %% +, -, !, ^, not, ~~~
Left 310 dot_call_op.
Left 310 dot_op.
Nonassoc 320 at_op.
Left 310 dot_op. %% .
Nonassoc 320 at_op_eol. %% @ (op)
Nonassoc 330 var.
%%% MAIN FLOW OF EXPRESSIONS
@@ -87,71 +84,65 @@ expr -> matched_expr : '$1'.
expr -> unmatched_expr : '$1'.
matched_expr -> matched_expr matched_op_expr : build_op(element(1, '$2'), '$1', element(2, '$2')).
matched_expr -> unary_op matched_expr : build_unary_op('$1', '$2').
matched_expr -> at_op matched_expr : build_unary_op('$1', '$2').
matched_expr -> unary_op_eol matched_expr : build_unary_op('$1', '$2').
matched_expr -> at_op_eol matched_expr : build_unary_op('$1', '$2').
matched_expr -> bracket_at_expr : '$1'.
matched_expr -> fn_expr : '$1'.
unmatched_expr -> matched_expr op_expr : build_op(element(1, '$2'), '$1', element(2, '$2')).
unmatched_expr -> unmatched_expr op_expr : build_op(element(1, '$2'), '$1', element(2, '$2')).
unmatched_expr -> unary_op expr : build_unary_op('$1', '$2').
unmatched_expr -> at_op expr : build_unary_op('$1', '$2').
unmatched_expr -> unary_op_eol expr : build_unary_op('$1', '$2').
unmatched_expr -> at_op_eol expr : build_unary_op('$1', '$2').
unmatched_expr -> block_expr : '$1'.
op_expr -> match_op expr : { '$1', '$2' }.
op_expr -> add_op expr : { '$1', '$2' }.
op_expr -> mult_op expr : { '$1', '$2' }.
op_expr -> two_op expr : { '$1', '$2' }.
op_expr -> pipeline_op expr : { '$1', '$2' }.
op_expr -> andand_op expr : { '$1', '$2' }.
op_expr -> three_op expr : { '$1', '$2' }.
op_expr -> oror_op expr : { '$1', '$2' }.
op_expr -> and_op expr : { '$1', '$2' }.
op_expr -> or_op expr : { '$1', '$2' }.
op_expr -> pipe_op expr : { '$1', '$2' }.
op_expr -> bin_concat_op expr : { '$1', '$2' }.
op_expr -> in_op expr : { '$1', '$2' }.
op_expr -> inc_op expr : { '$1', '$2' }.
op_expr -> when_op expr : { '$1', '$2' }.
op_expr -> send_op expr : { '$1', '$2' }.
op_expr -> range_op expr : { '$1', '$2' }.
op_expr -> default_op expr : { '$1', '$2' }.
op_expr -> colon_colon_op expr : { '$1', '$2' }.
op_expr -> comp_expr_op expr : { '$1', '$2' }.
op_expr -> match_op_eol expr : { '$1', '$2' }.
op_expr -> add_op_eol expr : { '$1', '$2' }.
op_expr -> mult_op_eol expr : { '$1', '$2' }.
op_expr -> exp_op_eol expr : { '$1', '$2' }.
op_expr -> two_op_eol expr : { '$1', '$2' }.
op_expr -> and_op_eol expr : { '$1', '$2' }.
op_expr -> or_op_eol expr : { '$1', '$2' }.
op_expr -> tail_op_eol expr : { '$1', '$2' }.
op_expr -> than_op_eol expr : { '$1', '$2' }.
op_expr -> in_op_eol expr : { '$1', '$2' }.
op_expr -> inc_op_eol expr : { '$1', '$2' }.
op_expr -> when_op_eol expr : { '$1', '$2' }.
op_expr -> range_op_eol expr : { '$1', '$2' }.
op_expr -> default_op_eol expr : { '$1', '$2' }.
op_expr -> type_op_eol expr : { '$1', '$2' }.
op_expr -> comp_op_eol expr : { '$1', '$2' }.
op_expr -> arrow_op_eol expr : { '$1', '$2' }.
matched_op_expr -> match_op matched_expr : { '$1', '$2' }.
matched_op_expr -> add_op matched_expr : { '$1', '$2' }.
matched_op_expr -> mult_op matched_expr : { '$1', '$2' }.
matched_op_expr -> two_op matched_expr : { '$1', '$2' }.
matched_op_expr -> pipeline_op matched_expr : { '$1', '$2' }.
matched_op_expr -> andand_op matched_expr : { '$1', '$2' }.
matched_op_expr -> three_op matched_expr : { '$1', '$2' }.
matched_op_expr -> oror_op matched_expr : { '$1', '$2' }.
matched_op_expr -> and_op matched_expr : { '$1', '$2' }.
matched_op_expr -> or_op matched_expr : { '$1', '$2' }.
matched_op_expr -> pipe_op matched_expr : { '$1', '$2' }.
matched_op_expr -> bin_concat_op matched_expr : { '$1', '$2' }.
matched_op_expr -> in_op matched_expr : { '$1', '$2' }.
matched_op_expr -> inc_op matched_expr : { '$1', '$2' }.
matched_op_expr -> when_op matched_expr : { '$1', '$2' }.
matched_op_expr -> send_op matched_expr : { '$1', '$2' }.
matched_op_expr -> range_op matched_expr : { '$1', '$2' }.
matched_op_expr -> default_op matched_expr : { '$1', '$2' }.
matched_op_expr -> colon_colon_op matched_expr : { '$1', '$2' }.
matched_op_expr -> comp_expr_op matched_expr : { '$1', '$2' }.
matched_op_expr -> match_op_eol matched_expr : { '$1', '$2' }.
matched_op_expr -> add_op_eol matched_expr : { '$1', '$2' }.
matched_op_expr -> mult_op_eol matched_expr : { '$1', '$2' }.
matched_op_expr -> exp_op_eol matched_expr : { '$1', '$2' }.
matched_op_expr -> two_op_eol matched_expr : { '$1', '$2' }.
matched_op_expr -> and_op_eol matched_expr : { '$1', '$2' }.
matched_op_expr -> or_op_eol matched_expr : { '$1', '$2' }.
matched_op_expr -> tail_op_eol matched_expr : { '$1', '$2' }.
matched_op_expr -> than_op_eol matched_expr : { '$1', '$2' }.
matched_op_expr -> in_op_eol matched_expr : { '$1', '$2' }.
matched_op_expr -> inc_op_eol matched_expr : { '$1', '$2' }.
matched_op_expr -> when_op_eol matched_expr : { '$1', '$2' }.
matched_op_expr -> range_op_eol matched_expr : { '$1', '$2' }.
matched_op_expr -> default_op_eol matched_expr : { '$1', '$2' }.
matched_op_expr -> type_op_eol matched_expr : { '$1', '$2' }.
matched_op_expr -> comp_op_eol matched_expr : { '$1', '$2' }.
matched_op_expr -> arrow_op_eol matched_expr : { '$1', '$2' }.
block_expr -> parens_call call_args_parens do_block : build_identifier('$1', '$2' ++ '$3').
block_expr -> parens_call call_args_parens call_args_parens do_block : build_nested_parens('$1', '$2', '$3' ++ '$4').
block_expr -> dot_punctuated_identifier call_args_no_parens do_block : build_identifier('$1', '$2' ++ '$3').
block_expr -> dot_punctuated_identifier call_args_no_parens_strict do_block : build_identifier('$1', '$2' ++ '$3').
block_expr -> dot_do_identifier do_block : build_identifier('$1', '$2').
block_expr -> dot_identifier call_args_no_parens do_block : build_identifier('$1', '$2' ++ '$3').
block_expr -> dot_identifier call_args_no_parens_strict do_block : build_identifier('$1', '$2' ++ '$3').
fn_expr -> fn_eol stab end_eol : build_fn('$1', build_stab(lists:reverse('$2'))).
fn_expr -> call_expr : '$1'.
call_expr -> dot_punctuated_identifier call_args_no_parens : build_identifier('$1', '$2').
call_expr -> dot_op_identifier call_args_no_parens : build_identifier('$1', '$2').
call_expr -> dot_identifier call_args_no_parens : build_identifier('$1', '$2').
call_expr -> dot_punctuated_identifier call_args_no_parens_strict : build_identifier('$1', '$2').
call_expr -> dot_op_identifier call_args_no_parens_strict : build_identifier('$1', '$2').
call_expr -> dot_identifier call_args_no_parens_strict : build_identifier('$1', '$2').
call_expr -> dot_punctuated_identifier : build_identifier('$1', []).
call_expr -> dot_do_identifier : build_identifier('$1', nil).
call_expr -> var : build_identifier('$1', nil).
@@ -167,24 +158,27 @@ max_expr -> open_paren stab close_paren : build_stab(lists:reverse('$2')).
bracket_expr -> dot_bracket_identifier bracket_access : build_access(build_identifier('$1', nil), '$2').
bracket_expr -> max_expr bracket_access : build_access('$1', '$2').
bracket_at_expr -> at_op dot_bracket_identifier bracket_access : build_access(build_unary_op('$1', build_identifier('$2', nil)), '$3').
bracket_at_expr -> at_op max_expr bracket_access : build_access(build_unary_op('$1', '$2'), '$3').
bracket_at_expr -> bracket_at_expr bracket_access : build_access('$1', '$2').
bracket_at_expr -> at_op_eol dot_bracket_identifier bracket_access :
build_access(build_unary_op('$1', build_identifier('$2', nil)), '$3').
bracket_at_expr -> at_op_eol max_expr bracket_access :
build_access(build_unary_op('$1', '$2'), '$3').
bracket_at_expr -> bracket_at_expr bracket_access :
build_access('$1', '$2').
base_expr -> number : ?exprs('$1').
base_expr -> signed_number : { element(4, '$1'), [{line,?line('$1')}], ?exprs('$1') }.
base_expr -> atom : build_atom('$1').
base_expr -> list : '$1'.
base_expr -> tuple : '$1'.
base_expr -> 'true' : ?op('$1').
base_expr -> 'false' : ?op('$1').
base_expr -> 'nil' : ?op('$1').
base_expr -> 'true' : ?id('$1').
base_expr -> 'false' : ?id('$1').
base_expr -> 'nil' : ?id('$1').
base_expr -> aliases : { '__aliases__', [{line,?line('$1')}], ?exprs('$1') }.
base_expr -> bin_string : build_bin_string('$1').
base_expr -> list_string : build_list_string('$1').
base_expr -> bit_string : '$1'.
base_expr -> '&' : { '&', [{line,?line('$1')}], ?exprs('$1') }.
base_expr -> '...' : { ?op('$1'), [{line,?line('$1')}], [] }.
base_expr -> '...' : { ?id('$1'), [{line,?line('$1')}], [] }.
base_expr -> sigil : build_sigil('$1').
%% Blocks
@@ -192,7 +186,7 @@ base_expr -> sigil : build_sigil('$1').
do_block -> do_eol 'end' : [[{do,nil}]].
do_block -> do_eol stab end_eol : [[{ do, build_stab(lists:reverse('$2')) }]].
do_block -> do_eol block_list 'end' : [[{ do, nil }|'$2']].
do_block -> do_eol stab eol block_list 'end' : [[{ do, build_stab(lists:reverse('$2')) }|'$4']].
do_block -> do_eol stab_eol block_list 'end' : [[{ do, build_stab(lists:reverse('$2')) }|'$3']].
fn_eol -> 'fn' : '$1'.
fn_eol -> 'fn' eol : '$1'.
@@ -209,15 +203,22 @@ block_eol -> block_identifier eol : '$1'.
stab -> stab_expr : ['$1'].
stab -> stab eol stab_expr : ['$3'|'$1'].
stab_eol -> stab : '$1'.
stab_eol -> stab eol : '$1'.
stab_expr -> expr : '$1'.
stab_expr -> stab_op stab_maybe_expr : build_op('$1', [], '$2').
stab_expr -> call_args_no_parens stab_op stab_maybe_expr : build_op('$2', '$1', '$3').
stab_expr -> call_args_parens_not_one stab_op stab_maybe_expr : build_op('$2', '$1', '$3').
stab_expr -> stab_op_eol stab_maybe_expr : build_op('$1', [], '$2').
stab_expr -> call_args_no_parens stab_op_eol stab_maybe_expr :
build_op('$2', unwrap_when(unwrap_splice('$1')), '$3').
stab_expr -> call_args_parens_not_one stab_op_eol stab_maybe_expr :
build_op('$2', unwrap_splice('$1'), '$3').
stab_expr -> call_args_parens_not_one when_op matched_expr stab_op_eol stab_maybe_expr :
build_op('$4', [{ 'when', [{line,?line('$2')}], unwrap_splice('$1') ++ ['$3'] }], '$5').
stab_maybe_expr -> 'expr' : '$1'.
stab_maybe_expr -> '$empty' : nil.
block_item -> block_eol stab eol : { ?exprs('$1'), build_stab(lists:reverse('$2')) }.
block_item -> block_eol stab_eol : { ?exprs('$1'), build_stab(lists:reverse('$2')) }.
block_item -> block_eol : { ?exprs('$1'), nil }.
block_list -> block_item : ['$1'].
@@ -232,7 +233,7 @@ open_paren -> '(' eol : '$1'.
close_paren -> ')' : '$1'.
close_paren -> eol ')' : '$2'.
empty_paren -> open_paren ')' : nil.
empty_paren -> open_paren ')' : '$1'.
open_bracket -> '[' : '$1'.
open_bracket -> '[' eol : '$1'.
@@ -251,108 +252,69 @@ close_curly -> eol '}' : '$2'.
% Operators
add_op -> '+' : '$1'.
add_op -> '-' : '$1'.
add_op -> '+' eol : '$1'.
add_op -> '-' eol : '$1'.
add_op_eol -> add_op : '$1'.
add_op_eol -> add_op eol : '$1'.
add_op_eol -> dual_op : '$1'.
add_op_eol -> dual_op eol : '$1'.
mult_op -> '*' : '$1'.
mult_op -> '/' : '$1'.
mult_op -> '*' eol : '$1'.
mult_op -> '/' eol : '$1'.
mult_op_eol -> mult_op : '$1'.
mult_op_eol -> mult_op eol : '$1'.
two_op -> '++' : '$1'.
two_op -> '--' : '$1'.
two_op -> '++' eol : '$1'.
two_op -> '--' eol : '$1'.
two_op -> '**' : '$1'.
two_op -> '**' eol : '$1'.
exp_op_eol -> exp_op : '$1'.
exp_op_eol -> exp_op eol : '$1'.
pipeline_op -> '=~' : '$1'.
pipeline_op -> '=~' eol : '$1'.
pipeline_op -> '|>' : '$1'.
pipeline_op -> '|>' eol : '$1'.
pipeline_op -> '/>' : '$1'.
pipeline_op -> '/>' eol : '$1'.
two_op_eol -> two_op : '$1'.
two_op_eol -> two_op eol : '$1'.
three_op -> '&&&' : '$1'.
three_op -> '&&&' eol : '$1'.
three_op -> '|||' : '$1'.
three_op -> '|||' eol : '$1'.
three_op -> '<<<' : '$1'.
three_op -> '<<<' eol : '$1'.
three_op -> '>>>' : '$1'.
three_op -> '>>>' eol : '$1'.
three_op -> '^^^' : '$1'.
three_op -> '^^^' eol : '$1'.
default_op_eol -> default_op : '$1'.
default_op_eol -> default_op eol : '$1'.
default_op -> '//' : '$1'.
default_op -> '//' eol : '$1'.
type_op_eol -> type_op : '$1'.
type_op_eol -> type_op eol : '$1'.
colon_colon_op -> '::' : '$1'.
colon_colon_op -> '::' eol : '$1'.
unary_op_eol -> unary_op : '$1'.
unary_op_eol -> unary_op eol : '$1'.
unary_op_eol -> dual_op : '$1'.
unary_op_eol -> dual_op eol : '$1'.
unary_op -> '+' : '$1'.
unary_op -> '+' eol : '$1'.
unary_op -> '-' : '$1'.
unary_op -> '-' eol : '$1'.
unary_op -> '!' : '$1'.
unary_op -> '!' eol : '$1'.
unary_op -> '^' : '$1'.
unary_op -> '^' eol : '$1'.
unary_op -> 'not' : '$1'.
unary_op -> 'not' eol : '$1'.
unary_op -> '~~~' : '$1'.
unary_op -> '~~~' eol : '$1'.
match_op_eol -> match_op : '$1'.
match_op_eol -> match_op eol : '$1'.
at_op -> '@' : '$1'.
at_op -> '@' eol : '$1'.
and_op_eol -> and_op : '$1'.
and_op_eol -> and_op eol : '$1'.
match_op -> '=' : '$1'.
match_op -> '=' eol : '$1'.
or_op_eol -> or_op : '$1'.
or_op_eol -> or_op eol : '$1'.
andand_op -> '&&' : '$1'.
andand_op -> '&&' eol : '$1'.
tail_op_eol -> tail_op : '$1'.
tail_op_eol -> tail_op eol : '$1'.
oror_op -> '||' : '$1'.
oror_op -> '||' eol : '$1'.
than_op_eol -> than_op : '$1'.
than_op_eol -> than_op eol : '$1'.
and_op -> 'and' : '$1'.
and_op -> 'and' eol : '$1'.
in_op_eol -> in_op : '$1'.
in_op_eol -> in_op eol : '$1'.
or_op -> 'or' : '$1'.
or_op -> 'or' eol : '$1'.
or_op -> 'xor' : '$1'.
or_op -> 'xor' eol : '$1'.
inc_op_eol -> inc_op : '$1'.
inc_op_eol -> inc_op eol : '$1'.
pipe_op -> '|' : '$1'.
pipe_op -> '|' eol : '$1'.
when_op_eol -> when_op : '$1'.
when_op_eol -> when_op eol : '$1'.
bin_concat_op -> '<>' : '$1'.
bin_concat_op -> '<>' eol : '$1'.
stab_op_eol -> stab_op : '$1'.
stab_op_eol -> stab_op eol : '$1'.
in_op -> 'in' : '$1'.
in_op -> 'in' eol : '$1'.
range_op_eol -> range_op : '$1'.
range_op_eol -> range_op eol : '$1'.
inc_op -> 'inlist' : '$1'.
inc_op -> 'inlist' eol : '$1'.
inc_op -> 'inbits' : '$1'.
inc_op -> 'inbits' eol : '$1'.
at_op_eol -> at_op : '$1'.
at_op_eol -> at_op eol : '$1'.
when_op -> 'when' : '$1'.
when_op -> 'when' eol : '$1'.
comp_op_eol -> comp_op : '$1'.
comp_op_eol -> comp_op eol : '$1'.
stab_op -> '->' : '$1'.
stab_op -> '->' eol : '$1'.
send_op -> '<-' : '$1'.
send_op -> '<-' eol : '$1'.
range_op -> '..' : '$1'.
range_op -> '..' eol : '$1'.
comp_expr_op -> comp_op : '$1'.
comp_expr_op -> comp_op eol : '$1'.
arrow_op_eol -> arrow_op : '$1'.
arrow_op_eol -> arrow_op eol : '$1'.
% Dot operator
@@ -387,11 +349,16 @@ parens_call -> matched_expr dot_call_op : { '.', [{line,?line('$2')}], ['$1'] }.
matched_comma_expr -> matched_expr : ['$1'].
matched_comma_expr -> matched_comma_expr ',' matched_expr : ['$3'|'$1'].
call_args_no_parens_strict -> call_args_no_parens : '$1'.
call_args_no_parens_strict -> empty_paren : throw_no_parens_strict('$1').
call_args_no_parens_strict -> open_paren matched_kw_base close_paren : throw_no_parens_strict('$1').
call_args_no_parens_strict -> open_paren matched_expr ',' call_args_no_parens close_paren : throw_no_parens_strict('$1').
call_args_no_parens -> matched_comma_expr : lists:reverse('$1').
call_args_no_parens -> matched_kw_base : ['$1'].
call_args_no_parens -> matched_comma_expr ',' matched_kw_base : lists:reverse(['$3'|'$1']).
call_args_parens_not_one -> open_paren ')' : [].
call_args_parens_not_one -> empty_paren : [].
call_args_parens_not_one -> open_paren matched_kw_base close_paren : ['$2'].
call_args_parens_not_one -> open_paren matched_expr ',' call_args_no_parens close_paren : ['$2'|'$4'].
@@ -409,7 +376,7 @@ optional_comma_expr -> paren_expr ',' : '$1'.
call_args -> comma_expr : lists:reverse('$1').
call_args_parens -> open_paren ')' : [].
call_args_parens -> empty_paren : [].
call_args_parens -> open_paren call_args close_paren : '$2'.
% KV
@@ -455,7 +422,7 @@ bit_string -> open_bit call_args close_bit : { '<<>>', [{line,?line('$1')}], '$2
Erlang code.
-define(op(Node), element(1, Node)).
-define(id(Node), element(1, Node)).
-define(line(Node), element(2, Node)).
-define(exprs(Node), element(3, Node)).
@@ -468,17 +435,14 @@ Erlang code.
%% Operators
build_op({ _, _, _ } = Op, Left, Right) ->
{ ?exprs(Op), [{line,?line(Op)}], [Left, Right] };
build_op({ BOp, Line }, { UOp, _, [Left] }, Right) when ?rearrange_bop(BOp), ?rearrange_uop(UOp) ->
build_op({ _Kind, Line, BOp }, { UOp, _, [Left] }, Right) when ?rearrange_bop(BOp), ?rearrange_uop(UOp) ->
{ UOp, [{line,Line}], [{ BOp, [{line,Line}], [Left, Right] }] };
build_op(Op, Left, Right) ->
{ ?op(Op), [{line,?line(Op)}], [Left, Right] }.
build_op({ _Kind, Line, Op }, Left, Right) ->
{ Op, [{line,Line}], [Left, Right] }.
build_unary_op(Op, Expr) ->
{ ?op(Op), [{line,?line(Op)}], [Expr] }.
build_unary_op({ _Kind, Line, Op }, Expr) ->
{ Op, [{line,Line}], [Expr] }.
build_tuple(_Marker, [Left, Right]) ->
{ Left, Right };
@@ -490,6 +454,8 @@ build_tuple(Marker, Args) ->
build_block([nil]) -> { '__block__', [], [nil] };
build_block([{Op,_,[_]}]=Exprs) when ?rearrange_uop(Op) -> { '__block__', [], Exprs };
build_block([{unquote_splicing,_,Args}]=Exprs) when
length(Args) =< 2 -> { '__block__', [], Exprs };
build_block([Expr]) when not is_list(Expr) -> Expr;
build_block(Exprs) -> { '__block__', [], Exprs }.
@@ -542,13 +508,13 @@ 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, ?id(Access) ] }.
%% Interpolation aware
build_sigil({ sigil, Line, Sigil, Parts, Modifiers }) ->
Meta = [{line,Line}],
{ list_to_atom([$_,$_,Sigil,$_,$_]), Meta, [ { '<<>>', Meta, Parts }, Modifiers ] }.
{ list_to_atom("sigil_" ++ [Sigil]), Meta, [ { '<<>>', Meta, Parts }, Modifiers ] }.
build_bin_string({ bin_string, _Line, [H] }) when is_binary(H) -> H;
build_bin_string({ bin_string, Line, Args }) -> { '<<>>', [{line,Line}], Args }.
@@ -566,18 +532,48 @@ build_atom({ atom, Line, Args }) ->
%% Keywords
build_stab([{ '->', Meta, [Left, Right] }|T]) ->
{ '->', Meta, build_stab(T, Left, [Right], []) };
{ '->', Meta, build_stab(Meta, T, Left, [Right], []) };
build_stab(Else) ->
build_block(Else).
build_stab([{ '->', _, [Left, Right] }|T], Marker, Temp, Acc) ->
H = { Marker, build_block(lists:reverse(Temp)) },
build_stab(T, Left, [Right], [H|Acc]);
build_stab(Old, [{ '->', New, [Left, Right] }|T], Marker, Temp, Acc) ->
H = { Marker, Old, build_block(lists:reverse(Temp)) },
build_stab(New, T, Left, [Right], [H|Acc]);
build_stab([H|T], Marker, Temp, Acc) ->
build_stab(T, Marker, [H|Temp], Acc);
build_stab(Meta, [H|T], Marker, Temp, Acc) ->
build_stab(Meta, T, Marker, [H|Temp], Acc);
build_stab([], Marker, Temp, Acc) ->
H = { Marker, build_block(lists:reverse(Temp)) },
build_stab(Meta, [], Marker, Temp, Acc) ->
H = { Marker, Meta, 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 clause,
%% 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.
unwrap_when(Args) ->
case elixir_tree_helpers:split_last(Args) of
{ Start, { 'when', Meta, [_, _] = End } } ->
[{ 'when', Meta, Start ++ End }];
{ _, _ } ->
Args
end.
%% Errors
throw(Line, Error, Token) ->
throw({ error, { Line, ?MODULE, [Error, Token] }}).
throw_no_parens_strict(Token) ->
throw(?line(Token), "invalid comma inside parenthesis. If you are making a "
"function call, do not insert spaces in between the function name and the "
"opening parentheses. Syntax error before: ", ")").
+2 -2
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).
@@ -11,7 +11,7 @@ handle({ _, Meta, Args } = Original, S, Opt) when is_list(Args), S#elixir_scope.
{ Call, Def, SC } when Def /= [] ->
Final = validate(Meta, Def, SC),
Block = setelement(3, Original, Call),
elixir_translator:translate_fn(Meta, [{ Final, Block }], SC);
elixir_translator:translate_fn(Meta, [{ Final, Meta, Block }], SC);
_ -> error
end;
+144 -133
View File
@@ -1,5 +1,6 @@
%% Implements Elixir quote.
-module(elixir_quote).
-export([quote/3, linify/2, unquote/5, user_quote/2]).
-export([escape/2, erl_escape/3, erl_quote/4, linify/2, unquote/5]).
-include("elixir.hrl").
%% Apply the line from site call on quoted contents.
@@ -22,48 +23,111 @@ do_linify(Line, List) when is_list(List) ->
do_linify(_, Else) -> Else.
%% Some expressions cannot be unquoted at compilation time.
%% This function is responisble for doing runtime unquoting.
unquote(Meta, Left, { '__aliases__', _, Args }, nil, _File) ->
%% This function is responsible for doing runtime unquoting.
unquote(_File, Meta, Left, { '__aliases__', _, Args }, nil) ->
{ '__aliases__', Meta, [Left|Args] };
unquote(Meta, Left, Right, nil, _File) when is_atom(Right) ->
unquote(_File, Meta, Left, Right, nil) when is_atom(Right) ->
case atom_to_list(Right) of
"Elixir-" ++ _ ->
"Elixir." ++ _ ->
{ '__aliases__', Meta, [Left, Right] };
_ ->
{ { '.', Meta, [Left, Right] }, Meta, [] }
end;
unquote(Meta, Left, Right, Args, _File) when is_atom(Right) ->
unquote(_File, Meta, Left, Right, Args) when is_atom(Right) ->
{ { '.', Meta, [Left, Right] }, Meta, Args };
unquote(Meta, _Left, _Right, _Args, File) ->
unquote(File, Meta, _Left, _Right, _Args) ->
elixir_errors:syntax_error(Meta, File, "expected unquote after dot to return an atom or an alias").
%% Similar to quote, but the given code is meant to come directly from the user.
%% Basically, lines are kept and hygiene mechanisms are disabled.
user_quote(Expr, S) ->
quote(Expr, #elixir_quote{
%% Escapes the given expression. It is similar to quote, but
%% lines are kept and hygiene mechanisms are disabled.
escape(Expr, Unquote) ->
quote(Expr, nil, #elixir_quote{
line=keep,
vars_hygiene=nil,
vars_hygiene=false,
aliases_hygiene=false,
imports_hygiene=false,
unquote=true
}, S).
unquote=Unquote,
mark=false
}, nil).
%% Quotes an expression into Erlang's AST
erl_escape(Expr, Unquote, S) ->
{ QExpr, TQ } = escape(Expr, Unquote),
{ TExpr, TS } = elixir_translator:translate_each(QExpr, S),
{ TExpr, TQ, TS }.
quote({ 'unquote_splicing', Meta, [_] } = Expr, #elixir_quote{unquote=true} = Q, S) ->
do_quote({ '__block__', Meta, [Expr] }, Q, S);
%% Quotes an expression and return its quoted Elixir AST.
quote(Else, Q, S) ->
do_quote(Else, Q, S).
quote(Expr, nil, Q, S) ->
do_quote(Expr, Q, S);
quote(Expr, Binding, Q, S) ->
Context = Q#elixir_quote.context,
Vars = [ { '{}', [],
[ '=', [], [
{ '{}', [], [K, [], Context] },
V
] ]
} || { K, V } <- Binding],
{ TExprs, TQ } = do_quote(Expr, Q, S),
{ { '{}',[], ['__block__',[], Vars ++ [TExprs] ] }, TQ }.
erl_quote(Expr, Binding, Q, S) ->
{ QExpr, TQ } = quote(Expr, Binding, Q, S),
{ TExpr, TS } = elixir_translator:translate_each(QExpr, S),
{ TExpr, TQ, TS }.
%% Actual quoting and helpers
do_quote({ quote, _, Args } = Tuple, #elixir_quote{unquote=true} = Q, S) when length(Args) == 1; length(Args) == 2 ->
do_quote_tuple(Tuple, Q#elixir_quote{unquote=false}, S);
{ TTuple, TQ } = do_quote_tuple(Tuple, Q#elixir_quote{unquote=false}, S),
{ TTuple, TQ#elixir_quote{unquote=true} };
do_quote({ unquote, _Meta, [Expr] }, #elixir_quote{unquote=true}, S) ->
elixir_translator:translate_each(Expr, S);
do_quote({ unquote, _Meta, [Expr] }, #elixir_quote{unquote=true} = Q, _) ->
{ Expr, Q#elixir_quote{unquoted=true} };
%% Context mark
do_quote({ Def, Meta, Args }, #elixir_quote{mark=true} = Q, S) when ?defs(Def); Def == defmodule; Def == alias; Def == import ->
NewMeta = lists:keystore(context, 1, Meta, { context, Q#elixir_quote.context }),
do_quote_tuple({ Def, NewMeta, Args }, Q, S);
do_quote({ { '.', _, [_, Def] } = Target, Meta, Args }, #elixir_quote{mark=true} = Q, S) when ?defs(Def); Def == defmodule; Def == alias; Def == import ->
NewMeta = lists:keystore(context, 1, Meta, { context, Q#elixir_quote.context }),
do_quote_tuple({ Target, NewMeta, Args }, Q, S);
%% Aliases
do_quote({ 'alias!', _Meta, [Expr] }, #elixir_quote{aliases_hygiene=true} = Q, S) ->
{ TExpr, TQ } = do_quote(Expr, Q#elixir_quote{aliases_hygiene=false}, S),
{ TExpr, TQ#elixir_quote{aliases_hygiene=true} };
do_quote({ '__aliases__', Meta, [H|T] } = Alias, #elixir_quote{aliases_hygiene=true} = Q, S) when is_atom(H) and (H /= 'Elixir') ->
Annotation = case elixir_aliases:expand(Alias, S#elixir_scope.aliases, S#elixir_scope.macro_aliases) of
Atom when is_atom(Atom) -> Atom;
Aliases when is_list(Aliases) -> false
end,
AliasMeta = lists:keystore(alias, 1, Meta, { alias, Annotation }),
do_quote_tuple({ '__aliases__', AliasMeta, [H|T] }, Q, S);
%% Vars
do_quote({ Left, Meta, nil }, #elixir_quote{vars_hygiene=true} = Q, S) when is_atom(Left) ->
do_quote_tuple({ Left, Meta, Q#elixir_quote.context }, Q, S);
%% Unquote
do_quote({ { { '.', Meta, [Left, unquote] }, _, [Expr] }, _, Args }, #elixir_quote{unquote=true} = Q, S) ->
do_quote_call(Left, Meta, Expr, Args, Q, S);
do_quote({ { '.', Meta, [Left, unquote] }, _, [Expr] }, #elixir_quote{unquote=true} = Q, S) ->
do_quote_call(Left, Meta, Expr, nil, Q, S);
%% Imports
do_quote({ function, Meta, [{ '/', _, [{F, _, C}, A]}] = Args },
#elixir_quote{imports_hygiene=true} = Q, S) when is_atom(F), is_integer(A), is_atom(C) ->
@@ -73,43 +137,6 @@ do_quote({ { '.', _, [_, function] } = Target, Meta, [{ '/', _, [{F, _, C}, A]}]
#elixir_quote{imports_hygiene=true} = Q, S) when is_atom(F), is_integer(A), is_atom(C) ->
do_quote_fa(Target, Meta, Args, F, A, Q, S);
do_quote({ 'alias!', _Meta, [Expr] }, Q, S) ->
do_quote(Expr, Q#elixir_quote{aliases_hygiene=false}, S);
do_quote({ '__aliases__', Meta, [H|T] } = Alias, #elixir_quote{aliases_hygiene=true} = Q, S) when is_atom(H) and (H /= 'Elixir') ->
Annotation = case elixir_aliases:expand(Alias, S#elixir_scope.aliases, S#elixir_scope.macro_aliases) of
Atom when is_atom(Atom) -> Atom;
Aliases when is_list(Aliases) -> false
end,
Line = ?line(Meta),
{ TAliases, SA } = do_quote([H|T], Q, S),
{ { tuple, Line, [
{ atom, Line, '__aliases__' },
meta([{alias,Annotation}|Meta], Q),
TAliases
] }, SA };
do_quote({ { { '.', Meta, [Left, unquote] }, _, [Expr] }, _, Args }, #elixir_quote{unquote=true} = Q, S) ->
All = [Left, { unquote, Meta, [Expr] }, Args, S#elixir_scope.file],
{ TAll, TS } = lists:mapfoldl(fun(X, Acc) -> do_quote(X, Q, Acc) end, S, All),
{ ?wrap_call(?line(Meta), elixir_quote, unquote, [meta(Meta, Q)|TAll]), TS };
do_quote({ { '.', Meta, [Left, unquote] }, _, [Expr] }, #elixir_quote{unquote=true} = Q, S) ->
All = [Left, { unquote, Meta, [Expr] }, nil, S#elixir_scope.file],
{ TAll, TS } = lists:mapfoldl(fun(X, Acc) -> do_quote(X, Q, Acc) end, S, All),
{ ?wrap_call(?line(Meta), elixir_quote, unquote, [meta(Meta, Q)|TAll]), TS };
do_quote({ Left, Meta, nil }, Q, S) when is_atom(Left) ->
Line = ?line(Meta),
Tuple = { tuple, Line, [
{ atom, Line, Left },
meta(Meta, Q),
{ atom, Line, Q#elixir_quote.vars_hygiene }
] },
{ Tuple, S };
do_quote({ Name, Meta, ArgsOrAtom } = Tuple, #elixir_quote{imports_hygiene=true} = Q, S) when is_atom(Name) ->
Arity = case is_atom(ArgsOrAtom) of
true -> 0;
@@ -119,101 +146,85 @@ do_quote({ Name, Meta, ArgsOrAtom } = Tuple, #elixir_quote{imports_hygiene=true}
case (lists:keyfind(import, 1, Meta) == false) andalso
elixir_dispatch:find_import(Meta, Name, Arity, S) of
false -> do_quote_tuple(Tuple, Q, S);
Receiver -> do_quote_tuple({ Name, [{import,Receiver}|Meta], ArgsOrAtom }, Q, S)
Receiver ->
ImportMeta = lists:keystore(import, 1,
lists:keystore(context, 1, Meta, { context, Q#elixir_quote.context }),
{ import, Receiver }),
do_quote_tuple({ Name, ImportMeta, ArgsOrAtom }, Q, S)
end;
do_quote({ _, _, _ } = Tuple, Q, S) ->
do_quote_tuple(Tuple, Q, S);
% Handle two item tuples but still allow them to be spliced.
do_quote({ Left, Right }, Q, S) when
not is_tuple(Left) orelse (element(1, Left) /= unquote_splicing),
not is_tuple(Right) orelse (element(1, Right) /= unquote_splicing) ->
{ TLeft, LS } = do_quote(Left, Q, S),
{ TRight, RS } = do_quote(Right, Q, LS),
{ { tuple, line(Q), [TLeft, TRight] }, RS };
%% Literals
do_quote({ Left, Right }, #elixir_quote{unquote=true} = Q, S) when
is_tuple(Left) andalso (element(1, Left) == unquote_splicing);
is_tuple(Right) andalso (element(1, Right) == unquote_splicing) ->
do_quote({ '{}', [], [Left, Right] }, Q, S);
do_quote({ Left, Right }, Q, S) ->
do_quote({ '{}', [{line,line(Q)}], [Left, Right] }, Q, S);
{ TLeft, LQ } = do_quote(Left, Q, S),
{ TRight, RQ } = do_quote(Right, LQ, S),
{ { TLeft, TRight }, RQ };
do_quote(Tuple, Q, S) when is_tuple(Tuple) ->
{ TT, TQ } = do_quote(tuple_to_list(Tuple), Q, S),
{ { '{}', [], TT }, TQ };
do_quote(List, Q, S) when is_list(List) ->
splice(List, Q, [], [], S);
do_splice(lists:reverse(List), Q, S);
do_quote(Number, Q, S) when is_integer(Number) ->
{ { integer, line(Q), Number }, S };
do_quote(Other, Q, _) ->
{ Other, Q }.
do_quote(Number, Q, S) when is_float(Number) ->
{ { float, line(Q), Number }, S };
%% Quote helpers
do_quote(Atom, Q, S) when is_atom(Atom) ->
{ { atom, line(Q), Atom }, S };
do_quote(Bitstring, _Q, S) when is_bitstring(Bitstring) ->
{ elixir_tree_helpers:abstract_syntax(Bitstring), S }.
do_quote_tuple({ Left, Meta, Right }, Q, S) ->
{ TLeft, LS } = do_quote(Left, Q, S),
{ TRight, RS } = do_quote(Right, Q, LS),
%% We need to remove line numbers from quoted exprs
%% otherwise the line number quotes in the macro will
%% get mixed with the unquoted contents
Tuple = { tuple, ?line(Meta), [TLeft, meta(Meta, Q), TRight] },
{ Tuple, RS }.
do_quote_call(Left, Meta, Expr, Args, Q, S) ->
All = [meta(Meta, Q), Left, { unquote, Meta, [Expr] }, Args],
{ TAll, TQ } = lists:mapfoldl(fun(X, Acc) -> do_quote(X, Acc, S) end, Q, All),
{ { { '.', Meta, [elixir_quote, unquote] }, Meta, [{ '__FILE__', [], nil }|TAll] }, TQ }.
do_quote_fa(Target, Meta, Args, F, A, Q, S) ->
NewMeta =
case (lists:keyfind(import_fa, 1, Meta) == false) andalso
elixir_dispatch:find_import(Meta, F, A, S) of
elixir_dispatch:find_import(Meta, F, A, S) of
false -> Meta;
Receiver -> [{ import_fa, Receiver }|Meta]
Receiver -> lists:keystore(import_fa, 1, Meta, { import_fa, { Receiver, Q#elixir_quote.context } })
end,
do_quote_tuple({ Target, NewMeta, Args }, Q, S).
% Loop through the list finding each unquote_splicing entry.
splice([{ unquote_splicing, _, [Args] }|T], #elixir_quote{unquote=true} = Q, Buffer, Acc, S) ->
{ NewAcc, NewS } = from_buffer_to_acc(Buffer, Q, Acc, S),
{ TArgs, TS } = elixir_translator:translate_each(Args, NewS),
splice(T, Q, [], [TArgs|NewAcc], TS);
splice([{ '|', Meta, [{ unquote_splicing, _, [_] } = Left, Right] }], #elixir_quote{unquote=true} = Q, Buffer, Acc, S) ->
{ TLeft, SL } = splice([Left], Q, Buffer, Acc, S),
{ TRight, SR } = do_quote(Right, Q, SL),
{ { op, ?line(Meta), '++', TLeft, TRight }, SR };
splice([H|T], Q, Buffer, Acc, S) ->
splice(T, Q, [H|Buffer], Acc, S);
splice([], Q, Buffer, Acc, S) ->
{ NewAcc, NewS } = from_buffer_to_acc(Buffer, Q, Acc, S),
case NewAcc of
[] ->
{ { nil, line(Q) }, NewS };
[H|T] ->
Line = line(Q),
{ lists:foldl(fun(L, R) -> { op, Line, '++', L, R } end, H, T), NewS }
end.
from_buffer_to_acc([], _Q, Acc, S) ->
{ Acc, S };
from_buffer_to_acc(Buffer, Q, Acc, S) ->
{ New, NewS } = elixir_tree_helpers:build_reverse_list(
fun(X, AccS) -> do_quote(X, Q, AccS) end, Buffer, line(Q), S),
{ [New|Acc], NewS }.
do_quote_tuple({ Left, Meta, Right }, Q, S) ->
{ TLeft, LQ } = do_quote(Left, Q, S),
{ TRight, RQ } = do_quote(Right, LQ, S),
{ { '{}', [], [TLeft, meta(Meta, Q), TRight] }, RQ }.
meta(Meta, #elixir_quote{line=keep}) ->
elixir_tree_helpers:abstract_syntax(Meta);
Meta;
meta(Meta, #elixir_quote{line=nil}) ->
elixir_tree_helpers:abstract_syntax(lists:keydelete(line, 1, Meta));
meta(Meta, #elixir_quote{line=Line} = Q) ->
{ cons,
line(Q),
{ tuple, line(Q), [{ atom, line(Q), line }, Line] },
elixir_tree_helpers:abstract_syntax(lists:keydelete(line, 1, Meta))
}.
lists:keydelete(line, 1, Meta);
meta(Meta, #elixir_quote{line=Line}) ->
lists:keystore(line, 1, Meta, { line, Line }).
line(#elixir_quote{line={ integer, _, Line }}) -> Line;
line(_) -> 0.
%% Quote splicing
do_splice([{ '|', _, [{ unquote_splicing, _, [Left] }, Right] }|T], #elixir_quote{unquote=true} = Q, S) ->
{ TT, TQ } = do_splice(T, Q, S, [], []),
{ do_splice_join(do_splice_join(TT, Left), Right), TQ#elixir_quote{unquoted=true} };
do_splice(List, Q, S) ->
do_splice(List, Q, S, [], []).
do_splice([{ unquote_splicing, _, [Expr] }|T], #elixir_quote{unquote=true} = Q, S, Buffer, Acc) ->
do_splice(T, Q#elixir_quote{unquoted=true}, S, [], do_splice_join(do_splice_join(Expr, Buffer), Acc));
do_splice([H|T], Q, S, Buffer, Acc) ->
{ TH, TQ } = do_quote(H, Q, S),
do_splice(T, TQ, S, [TH|Buffer], Acc);
do_splice([], Q, _S, Buffer, Acc) ->
{ do_splice_join(Buffer, Acc), Q }.
do_splice_join(Left, []) -> Left;
do_splice_join([], Right) -> Right;
do_splice_join(Left, Right) -> { { '.', [], ['Elixir.Kernel', '++'] }, [], [Left, Right] }.
+39 -30
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,11 +8,11 @@
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),
Vars = S#elixir_scope.vars,
Tuple = { Name, Kind },
case Name of
'_' ->
@@ -22,9 +21,9 @@ translate_var(Meta, Name, Kind, S, Callback) ->
case S#elixir_scope.context of
match ->
TempVars = S#elixir_scope.temp_vars,
case { orddict:is_key({ Name, Kind }, Vars), orddict:find(Name, TempVars) } of
{ true, { ok, Kind } } ->
{ { var, Line, orddict:fetch({ Name, Kind }, Vars) }, S };
case { orddict:is_key(Tuple, Vars), is_list(TempVars) andalso ordsets:is_element(Tuple, TempVars) } of
{ true, true } ->
{ { var, Line, orddict:fetch(Tuple, Vars) }, S };
{ Else, _ } ->
{ NewVar, NS } = if
Kind /= nil -> build_erl_var(Line, S);
@@ -35,11 +34,14 @@ translate_var(Meta, Name, Kind, S, Callback) ->
RealName = element(3, NewVar),
ClauseVars = S#elixir_scope.clause_vars,
{ NewVar, NS#elixir_scope{
vars=orddict:store({ Name, Kind }, RealName, Vars),
temp_vars=orddict:store(Name, Kind, TempVars),
vars=orddict:store(Tuple, RealName, Vars),
temp_vars=if
TempVars == nil -> TempVars;
true -> ordsets:add_element(Tuple, TempVars)
end,
clause_vars=if
ClauseVars == nil -> nil;
true -> orddict:store({ Name, Kind }, RealName, ClauseVars)
ClauseVars == nil; Kind == false -> ClauseVars;
true -> orddict:store(Tuple, RealName, ClauseVars)
end
} }
end;
@@ -77,24 +79,28 @@ 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 }) ->
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}.
requires=Requires,macros=Macros,functions=Functions,
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} }) when is_integer(Line) ->
{ 'Elixir.Macro.Env', Module, File, Line, Function, Aliases, Context, Requires, Functions, Macros }.
requires=Requires,macros=Macros,functions=Functions,
context_modules=ContextModules,macro_aliases=MacroAliases} }) when is_integer(Line) ->
{ 'Elixir.Macro.Env', Module, File, Line, Function, Aliases,
Context, Requires, Functions, Macros, ContextModules, MacroAliases }.
% Provides a tuple with only the scope information we want to serialize.
serialize(S) ->
elixir_tree_helpers:abstract_syntax(
{ S#elixir_scope.file, S#elixir_scope.functions, S#elixir_scope.check_clauses,
elixir_tree_helpers:elixir_to_erl(
{ S#elixir_scope.file, S#elixir_scope.functions,
S#elixir_scope.requires, S#elixir_scope.macros, S#elixir_scope.aliases,
S#elixir_scope.macro_functions, S#elixir_scope.macro_macros, S#elixir_scope.macro_aliases,
S#elixir_scope.scheduled }
S#elixir_scope.context_modules }
).
serialize_with_vars(Line, S) when is_integer(Line) ->
@@ -112,19 +118,18 @@ serialize_with_vars(Line, S) when is_integer(Line) ->
deserialize(Tuple) -> deserialize_with_vars(Tuple, []).
deserialize_with_vars({ File, Functions, CheckClauses, Requires, Macros,
Aliases, MacroFunctions, MacroMacros, MacroAliases, Scheduled }, Vars) ->
deserialize_with_vars({ File, Functions, Requires, Macros,
Aliases, MacroFunctions, MacroMacros, MacroAliases, FileModules }, Vars) ->
#elixir_scope{
file=File,
functions=Functions,
check_clauses=CheckClauses,
requires=Requires,
macros=Macros,
aliases=Aliases,
macro_functions=MacroFunctions,
macro_macros=MacroMacros,
macro_aliases=MacroAliases,
scheduled=Scheduled,
context_modules=FileModules,
vars=orddict:from_list(Vars),
counter=[{'',length(Vars)}]
}.
@@ -138,26 +143,30 @@ umergev(S1, S2) ->
C1 = S1#elixir_scope.clause_vars,
C2 = S2#elixir_scope.clause_vars,
S2#elixir_scope{
vars=orddict:merge(fun var_merger/3, V1, V2),
vars=merge_vars(V1, V2),
clause_vars=merge_clause_vars(C1, C2)
}.
% Receives two scopes and return a new scope based on the first
% with the counter values from the second one.
% Receives two scopes and return the later scope
% keeping the variables from the first (counters,
% imports and everything else are passed forward).
umergec(S1, S2) ->
S1#elixir_scope{
counter=S2#elixir_scope.counter,
extra_guards=S2#elixir_scope.extra_guards,
super=S1#elixir_scope.super orelse S2#elixir_scope.super,
caller=S1#elixir_scope.caller orelse S2#elixir_scope.caller,
name_args=S1#elixir_scope.name_args orelse S2#elixir_scope.name_args
S2#elixir_scope{
vars=S1#elixir_scope.vars,
temp_vars=S1#elixir_scope.temp_vars,
clause_vars=S1#elixir_scope.clause_vars
}.
% Merge variables trying to find the most recently created.
merge_vars(V, V) -> V;
merge_vars(V1, V2) ->
orddict:merge(fun var_merger/3, V1, V2).
merge_clause_vars(nil, _C2) -> nil;
merge_clause_vars(_C1, nil) -> nil;
merge_clause_vars(C, C) -> C;
merge_clause_vars(C1, C2) ->
orddict:merge(fun var_merger/3, C1, C2).
+298 -230
View File
@@ -10,73 +10,93 @@
existing_atoms_only=false
}).
-define(container2(T1, T2),
-define(container(T1, T2),
T1 == ${, T2 == $};
T1 == $[, T2 == $]
).
-define(comp3(T1, T2, T3),
T1 == $=, T2 == $=, T3 == $=;
T1 == $!, T2 == $=, T3 == $=
).
%% New ops table
-define(op3(T1, T2, T3),
T1 == $<, T2 == $<, T3 == $<;
T1 == $>, T2 == $>, T3 == $>;
T1 == $^, T2 == $^, T3 == $^;
T1 == $~, T2 == $~, T3 == $~;
T1 == $&, T2 == $&, T3 == $&;
T1 == $|, T2 == $|, T3 == $|
).
-define(comp2(T1, T2),
T1 == $=, T2 == $=;
T1 == $!, T2 == $=;
T1 == $<, T2 == $=;
T1 == $>, T2 == $=
).
-define(op2(T1, T2),
T1 == $&, T2 == $&;
T1 == $|, T2 == $|;
T1 == $<, T2 == $>;
T1 == $+, T2 == $+;
T1 == $-, T2 == $-;
T1 == $*, T2 == $*;
T1 == $/, T2 == $/;
T1 == $:, T2 == $:;
T1 == $<, T2 == $-;
T1 == $-, T2 == $>;
T1 == $., T2 == $.;
T1 == $|, T2 == $>;
T1 == $/, T2 == $>;
T1 == $=, T2 == $~
).
-define(comp1(T),
T == $<;
T == $>
).
-define(op1(T),
T == $+;
T == $-;
T == $*;
T == $/;
T == $=;
T == $|;
T == $!;
T == $^;
T == $@
).
-define(at_op(T),
T == $@).
-define(unary_op(T),
T == '+';
T == '-';
T == '@';
T == '!';
T == '^'
).
% T == $&;
T == $!;
T == $^).
-define(unary_op3(T1, T2, T3),
T1 == $~, T2 == $~, T3 == $~).
-define(exp_op3(T1, T2, T3),
T1 == $^, T2 == $^, T3 == $^).
-define(two_op(T1, T2),
T1 == $+, T2 == $+;
T1 == $-, T2 == $-;
T1 == $*, T2 == $*).
-define(than_op(T1, T2),
T1 == $<, T2 == $>).
-define(mult_op(T),
T == $* orelse T == $/).
-define(dual_op(T),
T == $+ orelse T == $-).
-define(range_op(T1, T2),
T1 == $., T2 == $.).
-define(arrow_op3(T1, T2, T3),
T1 == $<, T2 == $<, T3 == $<;
T1 == $>, T2 == $>, T3 == $>).
-define(arrow_op(T1, T2),
T1 == $<, T2 == $-;
T1 == $|, T2 == $>).
-define(comp_op(T),
T == $<;
T == $>).
-define(comp_op2(T1, T2),
T1 == $=, T2 == $=;
T1 == $=, T2 == $~;
T1 == $!, T2 == $=;
T1 == $<, T2 == $=;
T1 == $>, T2 == $=).
-define(comp_op3(T1, T2, T3),
T1 == $=, T2 == $=, T3 == $=;
T1 == $!, T2 == $=, T3 == $=).
-define(and_op(T1, T2),
T1 == $&, T2 == $&).
-define(or_op(T1, T2),
T1 == $|, T2 == $|).
-define(and_op3(T1, T2, T3),
T1 == $&, T2 == $&, T3 == $&).
-define(or_op3(T1, T2, T3),
T1 == $|, T2 == $|, T3 == $|).
-define(match_op(T),
T == $=).
-define(tail_op(T),
T == $|).
-define(default_op(T1, T2),
T1 == $/, T2 == $/).
-define(stab_op(T1, T2),
T1 == $-, T2 == $>).
-define(type_op(T1, T2),
T1 == $:, T2 == $:).
tokenize(String, Line, Opts) ->
File = case lists:keyfind(file, 1, Opts) of
@@ -141,7 +161,7 @@ tokenize([$%,S,H,H,H|T], Line, #scope{file=File} = Scope, Tokens) when ?is_quote
tokenize(Final, Line, Scope, [{ sigil, Line, S, Parts, Modifiers }|Tokens])
end;
tokenize([$%,S,H|T], Line, #scope{file=File} = Scope, Tokens) when not(?is_word(H)), ?is_upcase(S) orelse ?is_downcase(S) ->
tokenize([$%,S,H|T], Line, #scope{file=File} = Scope, Tokens) when not(?is_identifier(H)), not(?is_terminator(H)), ?is_upcase(S) orelse ?is_downcase(S) ->
case elixir_interpolation:extract(Line, File, ?is_downcase(S), T, sigil_terminator(H)) of
{ NewLine, Parts, Rest } ->
{ Final, Modifiers } = collect_modifiers(Rest, []),
@@ -205,13 +225,10 @@ tokenize([$?,Char|T], Line, Scope, Tokens) ->
tokenize(T, Line, Scope, [{ number, Line, Char }|Tokens]);
% Dot identifier/operators
tokenize("..." ++ Rest, Line, Scope, Tokens) ->
Token = tokenize_call_identifier(identifier, Line, '...', Rest),
Token = check_call_identifier(identifier, Line, '...', Rest),
tokenize(Rest, Line, Scope, [Token|Tokens]);
% ## Containers
tokenize([$.,T|Tail], Line, Scope, Tokens) when ?is_space(T) ->
case [T|Tail] of
[$\r,$\n|Rest] -> tokenize([$.|Rest], Line + 1, Scope, Tokens);
@@ -219,22 +236,30 @@ tokenize([$.,T|Tail], Line, Scope, Tokens) when ?is_space(T) ->
[_|Rest] -> tokenize([$.|Rest], Line, Scope, Tokens)
end;
% ## Containers
tokenize(".<<>>" ++ Rest, Line, Scope, Tokens) ->
handle_call_identifier(Rest, Line, '<<>>', Scope, Tokens);
tokenize([$.,T1,T2|Rest], Line, Scope, Tokens) when ?container2(T1, T2) ->
tokenize([$.,T1,T2|Rest], Line, Scope, Tokens) when ?container(T1, T2) ->
handle_call_identifier(Rest, Line, list_to_atom([T1, T2]), Scope, Tokens);
% ## Three Token Operators
tokenize([$.,T1,T2,T3|Rest], Line, Scope, Tokens) when ?comp3(T1, T2, T3); ?op3(T1, T2, T3) ->
tokenize([$.,T1,T2,T3|Rest], Line, Scope, Tokens) when
?unary_op3(T1, T2, T3); ?comp_op3(T1, T2, T3); ?and_op3(T1, T2, T3); ?or_op3(T1, T2, T3);
?arrow_op3(T1, T2, T3); ?exp_op3(T1, T2, T3) ->
handle_call_identifier(Rest, Line, list_to_atom([T1, T2, T3]), Scope, Tokens);
% ## Two Token Operators
tokenize([$.,T1,T2|Rest], Line, Scope, Tokens) when ?comp2(T1, T2); ?op2(T1, T2) ->
tokenize([$.,T1,T2|Rest], Line, Scope, Tokens) when
?comp_op2(T1, T2); ?and_op(T1, T2); ?or_op(T1, T2); ?arrow_op(T1, T2);
?range_op(T1, T2); ?than_op(T1, T2); ?default_op(T1, T2); ?two_op(T1, T2);
?stab_op(T1, T2); ?type_op(T1, T2) ->
handle_call_identifier(Rest, Line, list_to_atom([T1, T2]), Scope, Tokens);
% ## Single Token Operators
tokenize([$.,T|Rest], Line, Scope, Tokens) when ?comp1(T); ?op1(T); T == $& ->
tokenize([$.,T|Rest], Line, Scope, Tokens) when
?at_op(T); ?unary_op(T); ?dual_op(T); ?mult_op(T); ?comp_op(T);
?match_op(T); ?tail_op(T); T == $& ->
handle_call_identifier(Rest, Line, list_to_atom([T]), Scope, Tokens);
% Dot call
@@ -247,7 +272,7 @@ tokenize([$.,H|T], Line, #scope{file=File} = Scope, Tokens) when ?is_quote(H) ->
case elixir_interpolation:extract(Line, File, true, T, H) of
{ NewLine, [Part], Rest } when is_binary(Part) ->
Atom = unsafe_to_atom(Part, Scope),
Token = tokenize_call_identifier(identifier, Line, Atom, Rest),
Token = check_call_identifier(identifier, Line, Atom, Rest),
tokenize(Rest, NewLine, Scope, [Token|add_token_with_nl({ '.', Line }, Tokens)]);
Error ->
interpolation_error(Error, " (for function name starting at line ~B)", [Line])
@@ -270,10 +295,6 @@ tokenize([$'|T], Line, Scope, Tokens) ->
% Atoms
tokenize([$:,T|String], Line, Scope, Tokens) when ?is_upcase(T); ?is_downcase(T); T == $_ ->
{ Rest, Atom } = tokenize_atom([T|String], [], Scope),
tokenize(Rest, Line, Scope, [{ atom, Line, Atom }|Tokens]);
tokenize([$:,H|T], Line, #scope{file=File} = Scope, Tokens) when ?is_quote(H) ->
case elixir_interpolation:extract(Line, File, true, T, H) of
{ NewLine, Parts, Rest } ->
@@ -286,6 +307,10 @@ tokenize([$:,H|T], Line, #scope{file=File} = Scope, Tokens) when ?is_quote(H) ->
interpolation_error(Error, " (for atom starting at line ~B)", [Line])
end;
tokenize([$:,T|String], Line, Scope, Tokens) when ?is_new_atom(T) ->
{ Rest, Atom } = tokenize_atom([T|String], [], Scope),
tokenize(Rest, Line, Scope, [{ atom, Line, Atom }|Tokens]);
% Atom identifiers/operators
tokenize(":..." ++ Rest, Line, Scope, Tokens) ->
@@ -295,19 +320,26 @@ tokenize(":..." ++ Rest, Line, Scope, Tokens) ->
tokenize(":<<>>" ++ Rest, Line, Scope, Tokens) ->
tokenize(Rest, Line, Scope, [{ atom, Line, '<<>>' }|Tokens]);
tokenize([$:,T1,T2|Rest], Line, Scope, Tokens) when ?container2(T1, T2) ->
tokenize([$:,T1,T2|Rest], Line, Scope, Tokens) when ?container(T1, T2) ->
tokenize(Rest, Line, Scope, [{ atom, Line, list_to_atom([T1,T2]) }|Tokens]);
% ## Three Token Operators
tokenize([$:,T1,T2,T3|Rest], Line, Scope, Tokens) when ?comp3(T1, T2, T3); ?op3(T1, T2, T3) ->
tokenize([$:,T1,T2,T3|Rest], Line, Scope, Tokens) when
?unary_op3(T1, T2, T3); ?comp_op3(T1, T2, T3); ?and_op3(T1, T2, T3); ?or_op3(T1, T2, T3);
?arrow_op3(T1, T2, T3); ?exp_op3(T1, T2, T3) ->
tokenize(Rest, Line, Scope, [{ atom, Line, list_to_atom([T1,T2,T3]) }|Tokens]);
% ## Two Token Operators
tokenize([$:,T1,T2|Rest], Line, Scope, Tokens) when ?comp2(T1, T2); ?op2(T1, T2) ->
tokenize([$:,T1,T2|Rest], Line, Scope, Tokens) when
?comp_op2(T1, T2); ?and_op(T1, T2); ?or_op(T1, T2); ?arrow_op(T1, T2);
?range_op(T1, T2); ?than_op(T1, T2); ?default_op(T1, T2); ?two_op(T1, T2);
?stab_op(T1, T2); ?type_op(T1, T2) ->
tokenize(Rest, Line, Scope, [{ atom, Line, list_to_atom([T1,T2]) }|Tokens]);
% ## Single Token Operators
tokenize([$:,T|Rest], Line, Scope, Tokens) when ?comp1(T); ?op1(T); T == $&; T == $. ->
tokenize([$:,T|Rest], Line, Scope, Tokens) when
?at_op(T); ?unary_op(T); ?dual_op(T); ?mult_op(T); ?comp_op(T);
?match_op(T); ?tail_op(T); T == $&; T == $. ->
tokenize(Rest, Line, Scope, [{ atom, Line, list_to_atom([T]) }|Tokens]);
% End of line
@@ -336,45 +368,87 @@ tokenize("\r\n" ++ Rest, Line, Scope, Tokens) ->
tokenize([$&,H|Rest], Line, Scope, Tokens) when ?is_digit(H) ->
tokenize(Rest, Line, Scope, [{ '&', Line, [list_to_integer([H])] }|Tokens]);
% ## Comparison three token operators
tokenize([T1,T2,T3|Rest], Line, Scope, Tokens) when ?comp3(T1, T2, T3) ->
handle_comp_op(Rest, Line, list_to_atom([T1,T2,T3]), Scope, Tokens);
% ## Three token operators
tokenize([T1,T2,T3|Rest], Line, Scope, Tokens) when ?op3(T1, T2, T3) ->
handle_op(Rest, Line, list_to_atom([T1,T2,T3]), Scope, Tokens);
tokenize([T1,T2,T3|Rest], Line, Scope, Tokens) when ?unary_op3(T1, T2, T3) ->
handle_nonl_op(Rest, Line, unary_op, list_to_atom([T1,T2,T3]), Scope, Tokens);
tokenize([T1,T2,T3|Rest], Line, Scope, Tokens) when ?comp_op3(T1, T2, T3) ->
handle_op(Rest, Line, comp_op, list_to_atom([T1,T2,T3]), Scope, Tokens);
tokenize([T1,T2,T3|Rest], Line, Scope, Tokens) when ?and_op3(T1, T2, T3) ->
handle_op(Rest, Line, and_op, list_to_atom([T1,T2,T3]), Scope, Tokens);
tokenize([T1,T2,T3|Rest], Line, Scope, Tokens) when ?or_op3(T1, T2, T3) ->
handle_op(Rest, Line, or_op, list_to_atom([T1,T2,T3]), Scope, Tokens);
tokenize([T1,T2,T3|Rest], Line, Scope, Tokens) when ?arrow_op3(T1, T2, T3) ->
handle_op(Rest, Line, arrow_op, list_to_atom([T1,T2,T3]), Scope, Tokens);
tokenize([T1,T2,T3|Rest], Line, Scope, Tokens) when ?exp_op3(T1, T2, T3) ->
handle_op(Rest, Line, exp_op, list_to_atom([T1,T2,T3]), Scope, Tokens);
% ## Containers + punctuation tokens
tokenize([T,T|Rest], Line, Scope, Tokens) when T == $<; T == $> ->
Token = { list_to_atom([T,T]), Line },
case handle_terminator(Token, Scope) of
{ error, _ } = Error -> Error;
New -> tokenize(Rest, Line, New, [Token|Tokens])
end;
handle_terminator(Rest, Line, Scope, Token, Tokens);
tokenize([T|Rest], Line, Scope, Tokens) when T == $(;
T == ${; T == $}; T == $[; T == $]; T == $); T == $, ->
Token = { list_to_atom([T]), Line },
case handle_terminator(Token, Scope) of
{ error, _ } = Error -> Error;
New -> tokenize(Rest, Line, New, [Token|Tokens])
end;
% ## Comparison two token operators
tokenize([T1,T2|Rest], Line, Scope, Tokens) when ?comp2(T1, T2) ->
handle_comp_op(Rest, Line, list_to_atom([T1, T2]), Scope, Tokens);
handle_terminator(Rest, Line, Scope, Token, Tokens);
% ## Two Token Operators
tokenize([T1,T2|Rest], Line, Scope, Tokens) when ?op2(T1, T2) ->
handle_op(Rest, Line, list_to_atom([T1, T2]), Scope, Tokens);
tokenize([T1,T2|Rest], Line, Scope, Tokens) when ?two_op(T1, T2) ->
handle_op(Rest, Line, two_op, list_to_atom([T1, T2]), Scope, Tokens);
% ## Comparison single token operators
tokenize([T|Rest], Line, Scope, Tokens) when ?comp1(T) ->
handle_comp_op(Rest, Line, list_to_atom([T]), Scope, Tokens);
tokenize([T1,T2|Rest], Line, Scope, Tokens) when ?than_op(T1, T2) ->
handle_op(Rest, Line, than_op, list_to_atom([T1, T2]), Scope, Tokens);
tokenize([T1,T2|Rest], Line, Scope, Tokens) when ?range_op(T1, T2) ->
handle_op(Rest, Line, range_op, list_to_atom([T1, T2]), Scope, Tokens);
tokenize([T1,T2|Rest], Line, Scope, Tokens) when ?arrow_op(T1, T2) ->
handle_op(Rest, Line, arrow_op, list_to_atom([T1, T2]), Scope, Tokens);
tokenize([T1,T2|Rest], Line, Scope, Tokens) when ?comp_op2(T1, T2) ->
handle_op(Rest, Line, comp_op, list_to_atom([T1, T2]), Scope, Tokens);
tokenize([T1,T2|Rest], Line, Scope, Tokens) when ?and_op(T1, T2) ->
handle_op(Rest, Line, and_op, list_to_atom([T1, T2]), Scope, Tokens);
tokenize([T1,T2|Rest], Line, Scope, Tokens) when ?or_op(T1, T2) ->
handle_op(Rest, Line, or_op, list_to_atom([T1, T2]), Scope, Tokens);
tokenize([T1,T2|Rest], Line, Scope, Tokens) when ?default_op(T1, T2) ->
handle_op(Rest, Line, default_op, list_to_atom([T1, T2]), Scope, Tokens);
tokenize([T1,T2|Rest], Line, Scope, Tokens) when ?stab_op(T1, T2) ->
handle_op(Rest, Line, stab_op, list_to_atom([T1, T2]), Scope, Tokens);
tokenize([T1,T2|Rest], Line, Scope, Tokens) when ?type_op(T1, T2) ->
handle_op(Rest, Line, type_op, list_to_atom([T1, T2]), Scope, Tokens);
% ## Single Token Operators
tokenize([T|Rest], Line, Scope, Tokens) when ?op1(T) ->
handle_op(Rest, Line, list_to_atom([T]), Scope, Tokens);
tokenize([T|Rest], Line, Scope, Tokens) when ?at_op(T) ->
handle_nonl_op(Rest, Line, at_op, list_to_atom([T]), Scope, Tokens);
tokenize([T|Rest], Line, Scope, Tokens) when ?unary_op(T) ->
handle_nonl_op(Rest, Line, unary_op, list_to_atom([T]), Scope, Tokens);
tokenize([T|Rest], Line, Scope, Tokens) when ?comp_op(T) ->
handle_op(Rest, Line, comp_op, list_to_atom([T]), Scope, Tokens);
tokenize([T|Rest], Line, Scope, Tokens) when ?dual_op(T) ->
handle_nonl_op(Rest, Line, dual_op, list_to_atom([T]), Scope, Tokens);
tokenize([T|Rest], Line, Scope, Tokens) when ?mult_op(T) ->
handle_op(Rest, Line, mult_op, list_to_atom([T]), Scope, Tokens);
tokenize([T|Rest], Line, Scope, Tokens) when ?match_op(T) ->
handle_op(Rest, Line, match_op, list_to_atom([T]), Scope, Tokens);
tokenize([T|Rest], Line, Scope, Tokens) when ?tail_op(T) ->
handle_op(Rest, Line, tail_op, list_to_atom([T]), Scope, Tokens);
tokenize([$.|Rest], Line, Scope, Tokens) ->
tokenize(Rest, Line, Scope, add_token_with_nl({ '.', Line }, Tokens));
@@ -388,10 +462,10 @@ tokenize([H|_] = String, Line, Scope, Tokens) when ?is_digit(H) ->
% Aliases
tokenize([H|_] = String, Line, Scope, Tokens) when ?is_upcase(H) ->
{ Rest, Alias } = tokenize_identifier(String, [], false),
{ Rest, Alias } = tokenize_identifier(String, []),
Atom = unsafe_to_atom(Alias, Scope),
case Rest of
[$:|T] when hd(T) /= $: ->
[$:|T] when ?is_space(hd(T)) ->
tokenize(T, Line, Scope, [{ kw_identifier, Line, Atom }|Tokens]);
_ ->
tokenize(Rest, Line, Scope, [{ aliases, Line, [Atom] }|Tokens])
@@ -400,34 +474,33 @@ tokenize([H|_] = String, Line, Scope, Tokens) when ?is_upcase(H) ->
% Identifier
tokenize([H|_] = String, Line, Scope, Tokens) when ?is_downcase(H); H == $_ ->
{ Rest, { Kind, _, Identifier } } = tokenize_any_identifier(String, Line, [], Scope),
case handle_keyword(Line, Kind, Identifier, Tokens) of
nomatch ->
tokenize(Rest, Line, Scope, [{ Kind, Line, Identifier }|Tokens]);
{ ok, [Check|T] } ->
case handle_terminator(Check, Scope) of
{ error, _ } = Error -> Error;
New -> tokenize(Rest, Line, New, [Check|T])
end;
{ error, Token } ->
{ error, { Line, "syntax error before: ", Token } }
case tokenize_any_identifier(String, Line, [], Scope) of
{ error, _ } = Error -> Error;
{ Rest, { Kind, _, Identifier } } ->
case check_keyword(Line, Kind, Identifier, Tokens) of
nomatch ->
tokenize(Rest, Line, Scope, [{ Kind, Line, Identifier }|Tokens]);
{ ok, [Check|T] } ->
handle_terminator(Rest, Line, Scope, Check, T);
{ error, Token } ->
{ error, { Line, "syntax error before: ", Token } }
end
end;
% Ambiguous unary/binary operators tokens
tokenize([Space, Sign, NotMarker|T], Line, Scope, [{ Identifier, _, _ } = H|Tokens]) when
Sign == $+ orelse Sign == $-,
Space == $\s orelse Space == $\t,
NotMarker /= $\s, NotMarker /= $\t, NotMarker /= $\r,
NotMarker /= $\n, NotMarker /= $:, NotMarker /= $(,
NotMarker /= $+, NotMarker /= $-, NotMarker /= $>,
Identifier == identifier orelse Identifier == punctuated_identifier ->
?dual_op(Sign),
?is_horizontal_space(Space),
not(?is_space(NotMarker)),
NotMarker /= $(, NotMarker /= $+, NotMarker /= $-, NotMarker /= $>,
Identifier == identifier orelse Identifier == punctuated_identifier ->
Rest = [NotMarker|T],
tokenize(Rest, Line, Scope, [{ list_to_atom([Sign]), Line }, setelement(1, H, op_identifier)|Tokens]);
tokenize(Rest, Line, Scope, [{ dual_op, Line, list_to_atom([Sign]) }, setelement(1, H, op_identifier)|Tokens]);
% Spaces
tokenize([T|Rest], Line, Scope, Tokens) when T == $\s; T == $\r; T == $\t ->
tokenize([T|Rest], Line, Scope, Tokens) when ?is_horizontal_space(T) ->
tokenize(Rest, Line, Scope, Tokens);
tokenize([{line,Line}|Rest], _Line, Scope, Tokens) ->
@@ -454,7 +527,7 @@ handle_heredocs(T, Line, H, #scope{file=File} = Scope, Tokens) ->
handle_strings(T, Line, H, #scope{file=File} = Scope, Tokens) ->
case elixir_interpolation:extract(Line, File, true, T, H) of
{ NewLine, Parts, [$:|Rest] } when hd(Rest) /= $: ->
{ NewLine, Parts, [$:|Rest] } when ?is_space(hd(Rest)) ->
case Parts of
[Bin] when is_binary(Bin) ->
Atom = unsafe_to_atom(unescape_chars(Bin), Scope),
@@ -469,25 +542,20 @@ handle_strings(T, Line, H, #scope{file=File} = Scope, Tokens) ->
interpolation_error(Error, " (for string starting at line ~B)", [Line])
end.
handle_comp_op([$:|Rest], Line, Op, Scope, Tokens) when hd(Rest) /= $: ->
verify_kw_and_space(Line, Op, Rest, Scope),
handle_nonl_op([$:|Rest], Line, _Kind, Op, Scope, Tokens) when ?is_space(hd(Rest)) ->
tokenize(Rest, Line, Scope, [{ kw_identifier, Line, Op }|Tokens]);
handle_comp_op(Rest, Line, Op, Scope, Tokens) ->
tokenize(Rest, Line, Scope, add_token_with_nl({ comp_op, Line, Op }, Tokens)).
handle_nonl_op(Rest, Line, Kind, Op, Scope, Tokens) ->
tokenize(Rest, Line, Scope, [{ Kind, Line, Op }|Tokens]).
handle_op([$:|Rest], Line, Op, Scope, Tokens) when hd(Rest) /= $: ->
verify_kw_and_space(Line, Op, Rest, Scope),
handle_op([$:|Rest], Line, _Kind, Op, Scope, Tokens) when ?is_space(hd(Rest)) ->
tokenize(Rest, Line, Scope, [{ kw_identifier, Line, Op }|Tokens]);
handle_op(Rest, Line, Op, Scope, Tokens) when ?unary_op(Op) ->
tokenize(Rest, Line, Scope, [{ Op, Line }|Tokens]);
handle_op(Rest, Line, Op, Scope, Tokens) ->
tokenize(Rest, Line, Scope, add_token_with_nl({ Op, Line }, Tokens)).
handle_op(Rest, Line, Kind, Op, Scope, Tokens) ->
tokenize(Rest, Line, Scope, add_token_with_nl({ Kind, Line, Op }, Tokens)).
handle_call_identifier(Rest, Line, Op, Scope, Tokens) ->
Token = tokenize_call_identifier(identifier, Line, Op, Rest),
Token = check_call_identifier(identifier, Line, Op, Rest),
tokenize(Rest, Line, Scope, [Token|add_token_with_nl({ '.', Line }, Tokens)]).
%% Helpers
@@ -511,7 +579,7 @@ collect_modifiers([H|T], Buffer) when ?is_downcase(H) ->
collect_modifiers(Rest, Buffer) ->
{ Rest, lists:reverse(Buffer) }.
% Extract heredocs
%% Heredocs
extract_heredoc_with_interpolation(Line, File, Interpol, T, H) ->
case extract_heredoc(Line, T, H) of
@@ -553,7 +621,7 @@ remove_heredoc_spaces(Body, 0) ->
remove_heredoc_spaces(Body, Spaces) ->
remove_heredoc_spaces([0|Body], [], Spaces, Spaces).
remove_heredoc_spaces([H,$\n|T], [Backtrack|Buffer], Spaces, Original) when Spaces > 0, H == $\s orelse H == $\t ->
remove_heredoc_spaces([H,$\n|T], [Backtrack|Buffer], Spaces, Original) when Spaces > 0, ?is_horizontal_space(H) ->
remove_heredoc_spaces([Backtrack,$\n|T], Buffer, Spaces - 1, Original);
remove_heredoc_spaces([$\n=H|T], Buffer, _Spaces, Original) ->
@@ -595,7 +663,7 @@ extract_heredoc_line(_, _) ->
%% Extract each heredoc line trying to find a match according to the marker.
extract_heredoc_line(Marker, [H|T], Buffer, Counter) when H == $\t; H == $\s ->
extract_heredoc_line(Marker, [H|T], Buffer, Counter) when ?is_horizontal_space(H) ->
extract_heredoc_line(Marker, T, [H|Buffer], Counter + 1);
extract_heredoc_line(Marker, [Marker,Marker,Marker|T] = Rest, Buffer, Counter) ->
@@ -607,9 +675,9 @@ extract_heredoc_line(Marker, [Marker,Marker,Marker|T] = Rest, Buffer, Counter) -
extract_heredoc_line(_Marker, Rest, Buffer, _Counter) ->
extract_heredoc_line(Rest, Buffer).
next_is_break([H|T]) when H == $\t; H == $\s -> next_is_break(T);
next_is_break([$\r,$\n|T]) -> T;
next_is_break([$\n|T]) -> T;
next_is_break([H|T]) when ?is_horizontal_space(H) -> next_is_break(T);
next_is_break("\r\n" ++ T) -> T;
next_is_break("\n" ++ T) -> T;
next_is_break([]) -> [];
next_is_break(_) -> false.
@@ -625,121 +693,113 @@ merge_heredoc_extra([H|T], Buffer) ->
merge_heredoc_extra([], Buffer) ->
Buffer.
% Integers and floats
% At this point, we are at least sure the first digit is a number.
%% Integers and floats
%% At this point, we are at least sure the first digit is a number.
% Check if we have a point followed by a number;
%% Check if we have a point followed by a number;
tokenize_number([$.,H|T], Acc, false) when ?is_digit(H) ->
tokenize_number(T, [H,$.|Acc], true);
% Check if we have an underscore followed by a number;
%% Check if we have an underscore followed by a number;
tokenize_number([$_,H|T], Acc, Bool) when ?is_digit(H) ->
tokenize_number(T, [H|Acc], Bool);
% Check if we have e- followed by numbers. Valid only for floats.
%% Check if we have e- followed by numbers (valid only for floats);
tokenize_number([$e,S,H|T], Acc, true) when ?is_digit(H), S == $+ orelse S == $- ->
tokenize_number(T, [H,S,$e|Acc], true);
% Check if we have e followed by numbers. Valid only for floats.
%% Check if we have e followed by numbers (valid only for floats);
tokenize_number([$e,H|T], Acc, true) when ?is_digit(H) ->
tokenize_number(T, [H,$e|Acc], true);
% Just numbers;
%% Finally just numbers.
tokenize_number([H|T], Acc, Bool) when ?is_digit(H) ->
tokenize_number(T, [H|Acc], Bool);
% Cast to float...
%% Cast to float...
tokenize_number(Rest, Acc, true) ->
{ Rest, list_to_float(lists:reverse(Acc)) };
% Or integer.
%% Or integer.
tokenize_number(Rest, Acc, false) ->
{ Rest, list_to_integer(lists:reverse(Acc)) }.
% Hex
tokenize_hex([H|T], Acc) when ?is_hex(H) -> tokenize_hex(T, [H|Acc]);
tokenize_hex(Rest, Acc) -> { Rest, list_to_integer(lists:reverse(Acc), 16) }.
% Octal
tokenize_octal([H|T], Acc) when ?is_octal(H) -> tokenize_octal(T, [H|Acc]);
tokenize_octal(Rest, Acc) -> { Rest, list_to_integer(lists:reverse(Acc), 8) }.
% Bin
tokenize_bin([H|T], Acc) when ?is_bin(H) -> tokenize_bin(T, [H|Acc]);
tokenize_bin(Rest, Acc) -> { Rest, list_to_integer(lists:reverse(Acc), 2) }.
% Comments
%% Comments
tokenize_comment("\r\n" ++ _ = Rest) -> Rest;
tokenize_comment("\n" ++ _ = Rest) -> Rest;
tokenize_comment([_|Rest]) -> tokenize_comment(Rest);
tokenize_comment([]) -> [].
% Identifiers
%% Atoms
%% Handle atoms specially since they support @
% Tokenize identifier. At this point, the validity of
% the first character was already verified.
tokenize_atom([H|T], Acc, Scope) when ?is_atom(H) ->
tokenize_atom(T, [H|Acc], Scope);
tokenize_identifier([H|T], Acc, Marker) when Marker == atom, H == $@;
?is_digit(H); ?is_upcase(H); ?is_downcase(H); H == $_ ->
tokenize_identifier(T, [H|Acc], Marker);
tokenize_atom([H|T], Acc, Scope) when H == $?; H == $! ->
{ T, unsafe_to_atom(lists:reverse([H|Acc]), Scope) };
tokenize_identifier([], Acc, _Marker) ->
{ [], lists:reverse(Acc) };
tokenize_atom(Rest, Acc, Scope) ->
{ Rest, unsafe_to_atom(lists:reverse(Acc), Scope) }.
tokenize_identifier(Rest, Acc, _Marker) ->
%% Identifiers
%% At this point, the validity of the first character was already verified.
tokenize_identifier([H|T], Acc) when ?is_identifier(H) ->
tokenize_identifier(T, [H|Acc]);
tokenize_identifier(Rest, Acc) ->
{ Rest, lists:reverse(Acc) }.
% Tokenize atom identifier, which also accepts punctuated identifiers
tokenize_atom(String, Acc, Scope) ->
{ Rest, Identifier } = tokenize_identifier(String, Acc, atom),
case Rest of
[H|T] when H == $?; H == $! ->
{ T, unsafe_to_atom(Identifier ++ [H], Scope) };
_ ->
{ Rest, unsafe_to_atom(Identifier, Scope) }
end.
%% Tokenize any identifier, handling kv, punctuated, paren, bracket and do identifiers.
% Tokenize any identifier, handling kv, punctuated, paren, bracket and do identifiers.
tokenize_any_identifier(String, Line, Acc, Scope) ->
{ Rest, Identifier } = tokenize_identifier(String, Acc, false),
{ Rest, Identifier } = tokenize_identifier(String, Acc),
case Rest of
[H,$:|T] when H == $? orelse H == $!, hd(T) /= $: ->
Atom = unsafe_to_atom(Identifier ++ [H], Scope),
verify_kw_and_space(Line, Atom, T, Scope),
{ T, { kw_identifier, Line, Atom } };
[H|T] when H == $?; H == $! ->
Atom = unsafe_to_atom(Identifier ++ [H], Scope),
{ T, tokenize_call_identifier(punctuated_identifier, Line, Atom, T) };
[$:|T] when hd(T) /= $: ->
tokenize_kw_or_call_identifier(punctuated_identifier, Line, Atom, T);
_ ->
Atom = unsafe_to_atom(Identifier, Scope),
verify_kw_and_space(Line, Atom, T, Scope),
{ T, { kw_identifier, Line, Atom } };
_ ->
{ Rest, tokenize_call_identifier(identifier, Line, unsafe_to_atom(Identifier, Scope), Rest) }
tokenize_kw_or_call_identifier(identifier, Line, Atom, Rest)
end.
tokenize_call_identifier(Kind, Line, Atom, Rest) ->
case Rest of
[$(|_] -> { paren_identifier, Line, Atom };
[$[|_] -> { bracket_identifier, Line, Atom };
_ ->
case next_is_block(Rest) of
false -> { Kind, Line, Atom };
BlockIdentifier -> { BlockIdentifier, Line, Atom }
end
end.
%% Tokenize kw or call identifier (paren | bracket | do)
verify_kw_and_space(_Line, _Atom, [H|_], #scope{}) when ?is_space(H) -> ok;
verify_kw_and_space(Line, Atom, _, #scope{file=File}) ->
io:format("~ts:~w: keyword argument ~ts: must be followed by space~n", [File, Line, Atom]).
tokenize_kw_or_call_identifier(_Kind, Line, Atom, [$:,H|T]) when ?is_space(H) ->
{ [H|T], { kw_identifier, Line, Atom } };
tokenize_kw_or_call_identifier(_Kind, Line, Atom, [$:,H|_]) when ?is_new_atom(H) ->
{ error, { Line, "keyword argument must be followed by space after: ", atom_to_list(Atom) ++ [$:] } };
tokenize_kw_or_call_identifier(Kind, Line, Atom, Rest) ->
{ Rest, check_call_identifier(Kind, Line, Atom, Rest) }.
%% Check if it is a call identifier (paren | bracket | do)
check_call_identifier(_Kind, Line, Atom, [$(|_]) -> { paren_identifier, Line, Atom };
check_call_identifier(_Kind, Line, Atom, [$[|_]) -> { bracket_identifier, Line, Atom };
check_call_identifier(Kind, Line, Atom, Rest) ->
case next_is_block(Rest) of
false -> { Kind, Line, Atom };
BlockIdentifier -> { BlockIdentifier, Line, Atom }
end.
next_is_block([Space|Tokens]) when Space == $\t; Space == $\s ->
next_is_block(Tokens);
next_is_block([$d,$o,H|_]) when
?is_digit(H); ?is_upcase(H); ?is_downcase(H); H == $_; H == $: ->
next_is_block([$d,$o,H|_]) when ?is_identifier(H); ?is_terminator(H) ->
false;
next_is_block([$d,$o|_]) ->
@@ -751,21 +811,18 @@ next_is_block(_) ->
add_token_with_nl(Left, [{eol,_,newline}|T]) -> [Left|T];
add_token_with_nl(Left, T) -> [Left|T].
% Error handling
%% Error handling
interpolation_error({ error, { Line, Message, Token } }, Extension, Args) ->
{ error, { Line, io_lib:format("~ts" ++ Extension, [Message|Args]), Token } }.
% Terminators
%% Terminators
string_type($") -> bin_string;
string_type($') -> list_string.
sigil_terminator($() -> $);
sigil_terminator($[) -> $];
sigil_terminator(${) -> $};
sigil_terminator($<) -> $>;
sigil_terminator(O) -> O.
handle_terminator(Rest, Line, Scope, Token, Tokens) ->
case handle_terminator(Token, Scope) of
{ error, _ } = Error -> Error;
New -> tokenize(Rest, Line, New, [Token|Tokens])
end.
handle_terminator(_, #scope{check_terminators=false} = Scope) ->
Scope;
@@ -808,6 +865,15 @@ check_terminator({ E, Line }, []) when
check_terminator(_, Terminators) ->
Terminators.
string_type($") -> bin_string;
string_type($') -> list_string.
sigil_terminator($() -> $);
sigil_terminator($[) -> $];
sigil_terminator(${) -> $};
sigil_terminator($<) -> $>;
sigil_terminator(O) -> O.
terminator('fn') -> 'end';
terminator('do') -> 'end';
terminator('(') -> ')';
@@ -815,50 +881,52 @@ terminator('[') -> ']';
terminator('{') -> '}';
terminator('<<') -> '>>'.
% Keywords check
handle_keyword(Line, Identifier, Atom, [{ '.', _ }|_] = Tokens) ->
%% Keywords checking
check_keyword(Line, Identifier, Atom, [{ '.', _ }|_] = Tokens) ->
{ ok, [{ Identifier, Line, Atom }|Tokens] };
handle_keyword(Line, Identifier, Atom, Tokens) when
check_keyword(Line, Identifier, Atom, Tokens) when
Identifier == identifier; Identifier == do_identifier;
Identifier == bracket_identifier; Identifier == paren_identifier ->
case keyword(Atom) of
true -> { ok, [{ Atom, Line }|Tokens] };
op -> { ok, add_token_with_nl({ Atom, Line }, Tokens) };
block -> { ok, [{ block_identifier, Line, Atom }|Tokens] };
do ->
do ->
case do_keyword_valid(Tokens) of
true -> { ok, [{ Atom, Line }|Tokens] };
false -> { error, "do" }
end;
false -> nomatch
false -> nomatch;
true -> { ok, [{ Atom, Line }|Tokens] };
block -> { ok, [{ block_identifier, Line, Atom }|Tokens] };
unary_op -> { ok, [{ unary_op, Line, Atom }|Tokens] };
Kind -> { ok, add_token_with_nl({ Kind, Line, Atom }, Tokens) }
end;
handle_keyword(_, _, _, _) -> nomatch.
check_keyword(_, _, _, _) -> nomatch.
do_keyword_valid([{ Atom, _ }|_]) ->
is_boolean(keyword(Atom));
do_keyword_valid(_) -> true.
% Keywords
% Regular keywords
keyword('fn') -> true;
keyword('end') -> true;
keyword('true') -> true;
keyword('false') -> true;
keyword('nil') -> true;
keyword('not') -> true;
% Special handling for do
keyword('do') -> do;
% Bin operator keywords
keyword('and') -> op;
keyword('or') -> op;
keyword('xor') -> op;
keyword('when') -> op;
keyword('in') -> op;
keyword('inlist') -> op;
keyword('inbits') -> op;
% Operators keywords
keyword('not') -> unary_op;
keyword('and') -> and_op;
keyword('or') -> or_op;
keyword('xor') -> or_op;
keyword('when') -> when_op;
keyword('in') -> in_op;
keyword('inlist') -> inc_op;
keyword('inbits') -> inc_op;
% Block keywords
keyword('after') -> block;
+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) -> unlink(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.
+203 -175
View File
@@ -9,14 +9,15 @@
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
{ ok, Tokens } ->
case elixir_parser:parse(Tokens) of
try elixir_parser:parse(Tokens) of
{ ok, Forms } -> { ok, Forms };
{ error, { Line, _, [Error, Token] } } -> { error, { Line, Error, Token } }
catch
{ error, { Line, _, [Error, Token] } } -> { error, { Line, Error, Token } }
end;
{ error, { _, _, _ } } = Else -> Else
catch
@@ -35,13 +36,10 @@ translate(Forms, S) ->
%% Those macros are "low-level". They are the basic mechanism
%% that makes the language work and cannot be partially applied
%% nor overwritten.
%%
%% =, ^, import, require and alias could be made non-special
%% forms without causing any side effects.
%% Assignment operator
translate_each({'=', Meta, [Left, Right]}, S) ->
translate_each({ '=', Meta, [Left, Right] }, S) ->
assert_no_guard_scope(Meta, '=', S),
{ TRight, SR } = translate_each(Right, S),
{ TLeft, SL } = elixir_clauses:assigns(fun translate_each/2, Left, SR),
@@ -98,43 +96,12 @@ translate_each({ alias, Meta, [Ref] }, S) ->
translate_each({ alias, Meta, [Ref, KV] }, S) ->
assert_no_match_or_guard_scope(Meta, alias, S),
validate_opts(Meta, alias, [as], KV, S),
{ TRef, SR } = translate_each(Ref, S),
{ TKV, ST } = translate_opts(Meta, alias, [as], no_alias_opts(KV), SR),
case TRef of
{ atom, _, Old } ->
{ New, SF } = case lists:keyfind(as, 1, KV) of
Opt when Opt == { as, true }; Opt == false ->
{ elixir_aliases:last(Old), SR };
{ as, false } ->
{ Old, SR };
{ as, Other } ->
{ TOther, SA } = translate_each(no_alias_expansion(Other),
SR#elixir_scope{aliases=[],macro_aliases=[]}),
case TOther of
{ atom, _, Atom } -> { Atom, SA };
_ -> syntax_error(Meta, S#elixir_scope.file,
"invalid args for alias, expected an atom or alias as argument")
end
end,
%% Avoid creating aliases if first == last
%% unecessarily polluting the aliases dict
case New == Old of
true -> { { nil, ?line(Meta) }, SF };
false ->
case string:tokens(atom_to_list(New), "-") of
[_,_] -> [];
_ -> syntax_error(Meta, S#elixir_scope.file,
"invalid args for alias, cannot create nested alias ~s", [elixir_errors:inspect(New)])
end,
{ { nil, ?line(Meta) }, SF#elixir_scope{
aliases=orddict:store(New, Old, S#elixir_scope.aliases),
macro_aliases=orddict:store(New, Old, S#elixir_scope.macro_aliases)
} }
end;
translate_alias(Meta, true, Old, TKV, ST);
_ ->
syntax_error(Meta, S#elixir_scope.file, "invalid args for alias, expected an atom or alias as argument")
end;
@@ -143,25 +110,15 @@ translate_each({ require, Meta, [Ref] }, S) ->
translate_each({ require, Meta, [Ref, []] }, S);
translate_each({ require, Meta, [Ref, KV] }, S) ->
assert_no_match_or_guard_scope(Meta, 'require', S),
validate_opts(Meta, require, [as], KV, S),
assert_no_match_or_guard_scope(Meta, require, S),
{ TRef, SR } = translate_each(Ref, S),
As = case lists:keyfind(as, 1, KV) of
false -> false;
{ as, Value } -> Value
end,
{ TKV, ST } = translate_opts(Meta, require, [as], no_alias_opts(KV), SR),
case TRef of
{ atom, _, Old } ->
elixir_aliases:ensure_loaded(Meta, Old, SR),
SF = SR#elixir_scope{
requires=ordsets:add_element(Old, S#elixir_scope.requires)
},
translate_each({ alias, Meta, [Ref, [{ as, As }]] }, SF);
elixir_aliases:ensure_loaded(Meta, Old, ST),
translate_require(Meta, Old, TKV, ST);
_ ->
syntax_error(Meta, S#elixir_scope.file, "invalid args for require, expected an atom or alias as argument")
end;
@@ -172,34 +129,34 @@ translate_each({ import, Meta, [Left] }, S) ->
translate_each({ import, Meta, [Left,Opts] }, S) when is_list(Opts) ->
translate_each({ import, Meta, [default, Left, Opts]}, S);
translate_each({ import, Meta, [Selector, Left] }, S) ->
translate_each({ import, Meta, [Selector, Left, []]}, S);
translate_each({ import, Meta, [Left,Right] }, S) ->
%% Second argument is ambiguous, translate it and take a peek
case translate_each(Right, S) of
{ { atom, _, _ }, _ } ->
translate_each({ import, Meta, [Left, Right, []]}, S);
_ ->
translate_each({ import, Meta, [default, Left, Right]}, S)
end;
translate_each({ import, Meta, [Left, Right, Opts] }, S) ->
assert_no_match_or_guard_scope(Meta, 'import', S),
translate_each({ import, Meta, [Left, Right, KV] }, S) ->
assert_no_match_or_guard_scope(Meta, import, S),
{ TSelector, SL } = translate_each(Left, S),
{ TRef, SR } = translate_each(Right, SL),
{ TKV, ST } = translate_opts(Meta, import, [as, only, except, warn], no_alias_opts(KV), SR),
Selector = case TSelector of
{ atom, _, SelectorAtom } -> SelectorAtom;
_ -> syntax_error(Meta, S#elixir_scope.file, "invalid selector for import, expected an atom")
end,
Ref = case TRef of
{ atom, _, RefAtom } -> RefAtom;
_ -> syntax_error(Meta, S#elixir_scope.file, "invalid name for import, expected an atom or alias")
end,
validate_opts(Meta, import, [as, only, except], Opts, S),
As = case lists:keyfind(as, 1, Opts) of
false -> false;
{ as, Value } -> Value
end,
elixir_aliases:ensure_loaded(Meta, Ref, SR),
SF = elixir_import:import(Meta, Ref, Opts, Selector, SR),
translate_each({ require, Meta, [Ref, [{ as, As }]] }, SF);
case TRef of
{ atom, _, Old } ->
elixir_aliases:ensure_loaded(Meta, Old, ST),
SF = elixir_import:import(Meta, Old, TKV, Selector, ST),
translate_require(Meta, Old, TKV, SF);
_ ->
syntax_error(Meta, S#elixir_scope.file, "invalid name for import, expected an atom or alias")
end;
%% Pseudo variables
@@ -209,9 +166,12 @@ translate_each({ '__MODULE__', Meta, Atom }, S) when is_atom(Atom) ->
translate_each({ '__FILE__', _Meta, Atom }, S) when is_atom(Atom) ->
translate_each(S#elixir_scope.file, S);
translate_each({ '__DIR__', _Meta, Atom }, S) when is_atom(Atom) ->
translate_each(filename:dirname(S#elixir_scope.file), S);
translate_each({ '__ENV__', Meta, Atom }, S) when is_atom(Atom) ->
Env = elixir_scope:to_ex_env({ ?line(Meta), S }),
{ elixir_tree_helpers:abstract_syntax(Env), S };
{ elixir_tree_helpers:elixir_to_erl(Env), S };
translate_each({ '__CALLER__', Meta, Atom }, S) when is_atom(Atom) ->
{ { var, ?line(Meta), '__CALLER__' }, S#elixir_scope{caller=true} };
@@ -229,7 +189,7 @@ translate_each({ '__aliases__', Meta, _ } = Alias, S) ->
Atoms = [Atom || { atom, _, Atom } <- TAliases],
{ { atom, ?line(Meta), elixir_aliases:concat(Atoms) }, SA };
false ->
Args = [elixir_tree_helpers:build_simple_list(?line(Meta), TAliases)],
Args = [elixir_tree_helpers:list_to_cons(?line(Meta), TAliases)],
{ ?wrap_call(?line(Meta), elixir_aliases, concat, Args), SA }
end
end;
@@ -239,103 +199,96 @@ translate_each({ '__aliases__', Meta, _ } = Alias, S) ->
translate_each({ Unquote, Meta, [_|_] }, S) when Unquote == unquote; Unquote == unquote_splicing ->
syntax_error(Meta, S#elixir_scope.file, "~p called outside quote", [Unquote]);
translate_each({ quote, Meta, [Left, Right] }, S) ->
translate_each({ quote, Meta, [orddict:from_list(Left ++ Right)] }, S);
translate_each({ quote, Meta, [Opts] }, S) when is_list(Opts) ->
case lists:keyfind(do, 1, Opts) of
{ do, Do } ->
translate_each({ quote, Meta, [lists:keydelete(do, 1, Opts), [{do,Do}]] }, S);
false ->
syntax_error(Meta, S#elixir_scope.file, "invalid args for quote, do is missing")
end;
translate_each({ quote, Meta, [T] }, S) when is_list(T) ->
translate_each({ quote, Meta, [_] }, S) ->
syntax_error(Meta, S#elixir_scope.file, "invalid args for quote");
translate_each({ quote, Meta, [KV, Do] }, S) when is_list(Do) ->
Exprs =
case lists:keyfind(do, 1, T) of
case lists:keyfind(do, 1, Do) of
{ do, E } -> E;
false ->
syntax_error(Meta, S#elixir_scope.file, "invalid args for quote")
false -> syntax_error(Meta, S#elixir_scope.file, "invalid args for quote")
end,
Hygiene = case lists:keyfind(hygiene, 1, T) of
{ hygiene, true } ->
elixir_errors:deprecation(Meta, S#elixir_scope.file, "hygiene: true is deprecated, please set it to [] instead", []),
[];
ValidOpts = [hygiene, context, var_context, location, line, file, unquote, binding],
{ TKV, ST } = translate_opts(Meta, quote, ValidOpts, KV, S),
Hygiene = case lists:keyfind(hygiene, 1, TKV) of
{ hygiene, List } when is_list(List) ->
List;
{ hygiene, false } ->
elixir_errors:deprecation(Meta, S#elixir_scope.file, "hygiene: false is deprecated, please set it to [vars: false] instead", []),
[{vars,false}, {imports,false}, {aliases,false}];
false ->
[]
end,
Vars = case lists:keyfind(var_context, 1, T) of
{ var_context, VarContext } ->
expand_var_context(Meta, VarContext, "invalid argument given for var_context in quote", S);
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(vars, 1, Hygiene) of
{ vars, false } -> nil;
_ ->
case S#elixir_scope.module of
nil -> 'Elixir';
Mod -> Mod
end
case S#elixir_scope.module of
nil -> 'Elixir';
Mod -> Mod
end
end,
GetExpansion = fun(Key, New) ->
case lists:keyfind(Key, 1, T) of
{ Key, Bool } when is_boolean(Bool) ->
elixir_errors:deprecation(Meta, S#elixir_scope.file, "the ~s option for quote is deprecated, please use hygiene: [~s: ~s] instead", [Key, New, Bool]),
Bool;
false ->
not(lists:keyfind(New, 1, Hygiene) == { New, false })
end
end,
Vars = lists:keyfind(vars, 1, Hygiene) /= { vars, false },
Aliases = lists:keyfind(aliases, 1, Hygiene) /= { aliases, false },
Imports = lists:keyfind(imports, 1, Hygiene) /= { imports, false },
Aliases = GetExpansion(aliases_hygiene, aliases),
Imports = GetExpansion(imports_hygiene, imports),
{ DefaultLine, DefaultFile } = case lists:keyfind(location, 1, T) of
{ location, keep } -> { keep, keep };
{ DefaultLine, DefaultFile } = case lists:keyfind(location, 1, TKV) of
{ location, keep } -> { keep, keep };
false -> { nil, nil }
end,
Line = case lists:keyfind(line, 1, T) of
Line = case lists:keyfind(line, 1, TKV) of
{ line, LineValue } -> LineValue;
false -> DefaultLine
end,
{ TLine, SL } = case Line of
keep -> { keep, S };
nil -> { nil, S };
_ -> translate_each(Line, S)
end,
File = case lists:keyfind(file, 1, T) of
File = case lists:keyfind(file, 1, TKV) of
{ file, FileValue } -> FileValue;
false -> DefaultFile
end,
TFile = case File of
Scope = case File of
keep -> S#elixir_scope.file;
_ -> File
end,
TExprs = if
is_binary(TFile) ->
{ '__scope__', Meta, [[{file,TFile}],[{do,Exprs}]] };
QExprs = if
is_binary(Scope) ->
{ '__scope__', Meta, [[{file,Scope}],[{do,Exprs}]] };
File == nil ->
Exprs;
true ->
syntax_error(Meta, S#elixir_scope.file, "invalid args for quote, expected :file to be a binary")
end,
Unquote = case lists:keyfind(unquote, 1, T) of
{ unquote, false } -> false;
_ -> true
{ Binding, DefaultUnquote } = case lists:keyfind(binding, 1, TKV) of
{ binding, B } when is_list(B) -> { B, false };
false -> { nil, true }
end,
Q = #elixir_quote{vars_hygiene=Vars, line=TLine, unquote=Unquote,
aliases_hygiene=Aliases, imports_hygiene=Imports},
Unquote = case lists:keyfind(unquote, 1, TKV) of
{ unquote, Bool } when is_boolean(Bool) -> Bool;
false -> DefaultUnquote
end,
elixir_quote:quote(TExprs, Q, SL);
Q = #elixir_quote{vars_hygiene=Vars, line=Line, unquote=Unquote,
aliases_hygiene=Aliases, imports_hygiene=Imports, context=Context},
translate_each({ quote, Meta, [_] }, S) ->
{ TExprs, _TQ, TS } = elixir_quote:erl_quote(QExprs, Binding, Q, ST),
{ TExprs, TS };
translate_each({ quote, Meta, [_, _] }, S) ->
syntax_error(Meta, S#elixir_scope.file, "invalid args for quote");
translate_each({ 'alias!', _Meta, [Arg] }, S) ->
@@ -348,7 +301,7 @@ translate_each({ 'var!', Meta, [{Name, _, Atom}, Kind] }, S) when is_atom(Name),
translate_each({ 'var!', Meta, [Name, Kind] }, S);
translate_each({ 'var!', Meta, [Name, Kind] }, S) when is_atom(Name) ->
Expanded = expand_var_context(Meta, Kind, "invalid second argument for var!", S),
Expanded = expand_quote_context(Meta, Kind, "invalid second argument for var!", S),
elixir_scope:translate_var(Meta, Name, Expanded, S, fun() ->
syntax_error(Meta, S#elixir_scope.file, "expected var!(~ts) to expand to an existing variable or be a part of a match", [Name])
end);
@@ -369,7 +322,7 @@ translate_each({ Kind, Meta, Args }, S) when is_list(Args), (Kind == lc) orelse
%% Super (exceptionally supports partial application)
translate_each({ super, Meta, Args } = Original, S) ->
translate_each({ super, Meta, Args } = Original, S) when is_list(Args) ->
case elixir_partials:handle(Original, S) of
error ->
assert_no_match_or_guard_scope(Meta, super, S),
@@ -380,8 +333,6 @@ translate_each({ super, Meta, Args } = Original, S) ->
{ _, Arity } = Function,
{ TArgs, TS } = if
is_atom(Args) ->
elixir_def_overridable:retrieve_args(Meta, Arity, S);
length(Args) == Arity ->
translate_args(Args, S);
true ->
@@ -402,7 +353,7 @@ translate_each({ 'super?', Meta, [] }, S) ->
%% Variables
translate_each({ '^', Meta, [ { Name, _, Kind } ] }, #elixir_scope{context=match} = S) when is_atom(Name), is_atom(Kind) ->
case orddict:find({ Name, Kind }, S#elixir_scope.vars) of
case orddict:find({ Name, Kind }, S#elixir_scope.backup_vars) of
{ ok, Value } ->
{ { var, Meta, Value }, S };
error ->
@@ -415,7 +366,7 @@ translate_each({ '^', Meta, [ { Name, _, Kind } ] }, S) when is_atom(Name), is_a
translate_each({ '^', Meta, [ Expr ] }, S) ->
syntax_error(Meta, S#elixir_scope.file,
"the unary operator ^ can only be used with variables, invalid expression ^~ts", ['Elixir.Macro':to_binary(Expr)]);
"the unary operator ^ can only be used with variables, invalid expression ^~ts", ['Elixir.Macro':to_string(Expr)]);
translate_each({ Name, Meta, Kind }, S) when is_atom(Name), is_atom(Kind) ->
elixir_scope:translate_var(Meta, Name, Kind, S, fun() ->
@@ -433,10 +384,12 @@ translate_each({ Atom, Meta, Args } = Original, S) when is_atom(Atom) ->
Callback = fun() ->
case S#elixir_scope.context of
match ->
syntax_error(Meta, S#elixir_scope.file, "cannot invoke function ~ts/~B inside match", [Atom, length(Args)]);
guard ->
Arity = length(Args),
File = S#elixir_scope.file,
case Arity of
0 -> syntax_error(Meta, File, "unknown variable ~ts or cannot invoke local ~ts/~B inside guard", [Atom, Atom, Arity]);
0 -> syntax_error(Meta, File, "unknown variable ~ts or cannot invoke function ~ts/~B inside guard", [Atom, Atom, Arity]);
_ -> syntax_error(Meta, File, "cannot invoke local ~ts/~B inside guard", [Atom, Arity])
end;
_ ->
@@ -467,9 +420,23 @@ translate_each({ { '.', _, [Left, Right] }, Meta, Args } = Original, S) when is_
case TLeft of
{ atom, _, Receiver } ->
elixir_dispatch:dispatch_require(Meta, Receiver, Right, Args, umergev(SL, SR), Callback);
elixir_dispatch:dispatch_require(Meta, Receiver, Right, Args, umergev(SL, SR), fun() ->
case S#elixir_scope.context of
Context when Receiver /= erlang, (Context == match) orelse (Context == guard) ->
syntax_error(Meta, S#elixir_scope.file, "cannot invoke remote function ~ts.~ts/~B inside ~ts",
[elixir_errors:inspect(Receiver), Right, length(Args), Context]);
_ ->
Callback()
end
end);
_ ->
Callback()
case S#elixir_scope.context of
Context when Context == match; Context == guard ->
syntax_error(Meta, S#elixir_scope.file, "cannot invoke remote function ~ts/~B inside ~ts",
[Right, length(Args), Context]);
_ ->
Callback()
end
end;
Else -> Else
end;
@@ -482,14 +449,7 @@ translate_each({ { '.', _, [Expr] }, Meta, Args } = Original, S) ->
{ TExpr, SE } = translate_each(Expr, S),
case TExpr of
{ atom, _, Atom } ->
case atom_to_list(Atom) of
[H|_] when (H >= $a andalso H =< $z); H == $_ ->
elixir_errors:deprecation(Meta, S#elixir_scope.file, "the :~s.() syntax is deprecated, please use ~s() instead", [Atom, Atom]);
_ ->
elixir_errors:deprecation(Meta, S#elixir_scope.file, "the :~s.() syntax is deprecated", [Atom])
end,
translate_each({ Atom, Meta, Args }, S);
syntax_error(Meta, S#elixir_scope.file, "invalid function call :~ts.()", [Atom]);
_ ->
case elixir_partials:handle(Original, S) of
error ->
@@ -503,7 +463,7 @@ translate_each({ { '.', _, [Expr] }, Meta, Args } = Original, S) ->
translate_each({ Invalid, Meta, _Args }, S) ->
syntax_error(Meta, S#elixir_scope.file, "unexpected parenthesis after ~ts",
['Elixir.Macro':to_binary(Invalid)]);
['Elixir.Macro':to_string(Invalid)]);
%% Literals
@@ -512,6 +472,25 @@ translate_each(Literal, S) ->
%% Helpers
%% Opts
translate_opts(Meta, Kind, Allowed, Opts, S) ->
{ Expanded, TS } = case literal_opts(Opts) orelse skip_expansion(S#elixir_scope.module) of
true -> { Opts, S };
false -> 'Elixir.Macro':expand_all(Opts, elixir_scope:to_ex_env({ ?line(Meta), S }), S)
end,
validate_opts(Meta, Kind, Allowed, Expanded, TS),
{ Expanded, TS }.
skip_expansion('Elixir.Kernel') -> true;
skip_expansion('Elixir.Kernel.Typespec') -> true;
skip_expansion(_) -> false.
literal_opts({ X, Y }) -> literal_opts(X) andalso literal_opts(Y);
literal_opts(Opts) when is_list(Opts) -> lists:all(fun literal_opts/1, Opts);
literal_opts(Opts) when is_atom(Opts); is_number(Opts); is_bitstring(Opts) -> true;
literal_opts(_) -> false.
validate_opts(Meta, Kind, Allowed, Opts, S) when is_list(Opts) ->
[begin
syntax_error(Meta, S#elixir_scope.file, "unsupported option ~s given to ~s", [Key, Kind])
@@ -520,34 +499,10 @@ validate_opts(Meta, Kind, Allowed, Opts, S) when is_list(Opts) ->
validate_opts(Meta, Kind, _Allowed, _Opts, S) ->
syntax_error(Meta, S#elixir_scope.file, "invalid options for ~s, expected a keyword list", [Kind]).
translate_fn(Meta, Clauses, S) ->
Line = ?line(Meta),
%% Quote
Transformer = fun({ ArgsWithGuards, Expr }, Acc) ->
{ Args, Guards } = elixir_clauses:extract_last_guards(ArgsWithGuards),
elixir_clauses:assigns_block(Line, fun elixir_translator:translate/2, Args, [Expr], Guards, umergec(S, Acc))
end,
{ TClauses, NS } = lists:mapfoldl(Transformer, S, Clauses),
Arities = [length(Args) || { clause, _Line, Args, _Guards, _Exprs } <- TClauses],
case length(lists:usort(Arities)) of
1 ->
{ { 'fun', Line, { clauses, TClauses } }, umergec(S, NS) };
_ ->
syntax_error(Meta, S#elixir_scope.file, "cannot mix clauses with different arities in function definition")
end.
translate_local(Meta, Name, Args, #elixir_scope{local=nil} = S) ->
Line = ?line(Meta),
{ TArgs, NS } = translate_args(Args, S),
{ { call, Line, { atom, Line, Name }, TArgs }, NS };
translate_local(Meta, Name, Args, S) ->
translate_each({ { '.', Meta, [S#elixir_scope.local, Name] }, Meta, Args }, S).
expand_var_context(_Meta, Atom, _Msg, _S) when is_atom(Atom) -> Atom;
expand_var_context(Meta, Alias, Msg, S) ->
expand_quote_context(_Meta, Atom, _Msg, _S) when is_atom(Atom) -> Atom;
expand_quote_context(Meta, Alias, Msg, S) ->
case translate_each(Alias, S) of
{ { atom, _, Atom }, _ } ->
Atom;
@@ -555,12 +510,85 @@ expand_var_context(Meta, Alias, Msg, S) ->
syntax_error(Meta, S#elixir_scope.file, "~ts, expected a compile time available alias or an atom", [Msg])
end.
%% Require
translate_require(Meta, Old, TKV, S) ->
SF = S#elixir_scope{
requires=ordsets:add_element(Old, S#elixir_scope.requires)
},
translate_alias(Meta, false, Old, TKV, SF).
%% Aliases
translate_alias(Meta, IncludeByDefault, Old, TKV, S) ->
New = case lists:keyfind(as, 1, TKV) of
{ as, true } ->
elixir_aliases:last(Old);
{ as, false } ->
Old;
{ as, Atom } when is_atom(Atom) ->
Atom;
false ->
if
IncludeByDefault -> elixir_aliases:last(Old);
true -> Old
end;
_ ->
syntax_error(Meta, S#elixir_scope.file,
"invalid args for alias, expected an atom or alias in option :as")
end,
case (New == Old) orelse (length(string:tokens(atom_to_list(New), ".")) == 2) of
true -> ok;
false -> syntax_error(Meta, S#elixir_scope.file,
"invalid args for alias, cannot create nested alias ~s", [elixir_errors:inspect(New)])
end,
{ { atom, ?line(Meta), nil }, elixir_aliases:store(Meta, New, Old, S) }.
no_alias_opts(KV) when is_list(KV) ->
case lists:keyfind(as, 1, KV) of
{ as, As } -> lists:keystore(as, 1, KV, { as, no_alias_expansion(As) });
false -> KV
end;
no_alias_opts(KV) -> KV.
no_alias_expansion({ '__aliases__', Meta, [H|T] }) when (H /= 'Elixir') and is_atom(H) ->
{ '__aliases__', Meta, ['Elixir',H|T] };
no_alias_expansion(Other) ->
Other.
%% Function
translate_fn(Meta, Clauses, S) ->
Transformer = fun({ ArgsWithGuards, CMeta, Expr }, Acc) ->
{ Args, Guards } = elixir_clauses:extract_splat_guards(ArgsWithGuards),
elixir_clauses:assigns_block(?line(CMeta), fun elixir_translator:translate/2, Args, [Expr], Guards, umergec(S, Acc))
end,
{ TClauses, NS } = lists:mapfoldl(Transformer, S, Clauses),
Arities = [length(Args) || { clause, _Line, Args, _Guards, _Exprs } <- TClauses],
case length(lists:usort(Arities)) of
1 ->
{ { 'fun', ?line(Meta), { clauses, TClauses } }, umergec(S, NS) };
_ ->
syntax_error(Meta, S#elixir_scope.file, "cannot mix clauses with different arities in function definition")
end.
%% Locals
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 };
translate_local(Meta, Name, Args, S) ->
translate_each({ { '.', Meta, [S#elixir_scope.local, Name] }, Meta, Args }, S).
%% Translate args
%% Variables in arguments are not propagated from one
+29 -44
View File
@@ -1,13 +1,10 @@
%% 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([abstract_syntax/1, split_last/1, cons_to_list/1,
convert_to_boolean/5, returns_boolean/1, get_line/1,
build_list/4, build_list/5, build_simple_list/2,
build_reverse_list/4, build_reverse_list/5, build_simple_reverse_list/2]).
-export([elixir_to_erl/1, split_last/1,
cons_to_list/1, list_to_cons/2, list_to_cons/3,
convert_to_boolean/5, returns_boolean/1, get_line/1]).
-include("elixir.hrl").
get_line(Opts) ->
@@ -21,53 +18,41 @@ split_last(List) -> split_last(List, []).
split_last([H], Acc) -> { lists:reverse(Acc), H };
split_last([H|T], Acc) -> split_last(T, [H|Acc]).
abstract_syntax(Tree) ->
erl_syntax:revert(erl_syntax:abstract(Tree)).
cons_to_list({ nil, _ }) -> [];
cons_to_list({ cons, _, Left, Right }) -> [Left|cons_to_list(Right)].
cons_to_list({ cons, _, Left, { nil, _ } }) ->
[Left];
list_to_cons(Line, List) ->
list_to_cons(Line, List, { nil, Line }).
cons_to_list({ cons, _, Left, Right }) ->
[Left|cons_to_list(Right)].
list_to_cons(Line, List, Tail) ->
lists:foldr(fun(X, Acc) ->
{ cons, Line, X, Acc }
end, Tail, List).
% Build a list translating each expression and accumulating
% vars in one pass. It uses tail-recursive form.
%
% It receives a function to translate each expression given
% in Exprs, a Line used to build the List and the variables
% scope V is passed down item by item.
%
% The function needs to return a tuple where the first element
% is an erlang abstract form and the second is the new variables
% list.
build_list(Fun, Exprs, Line, S) ->
build_list(Fun, Exprs, Line, S, {nil, Line}).
%% erl <-> elixir
build_list(Fun, Exprs, Line, S, Tail) when is_integer(Line) ->
build_list_each(Fun, lists:reverse(Exprs), Line, S, Tail).
elixir_to_erl(Tree) when is_tuple(Tree) ->
{ tuple, 0, [elixir_to_erl(X) || X <- tuple_to_list(Tree)] };
% Same as build_list, but the list given is in reverse other.
build_reverse_list(Fun, Exprs, Line, S) ->
build_list_each(Fun, Exprs, Line, S, {nil,Line}).
elixir_to_erl(Tree) when is_list(Tree) ->
elixir_to_erl_cons(lists:reverse(Tree), { nil, 0 });
build_reverse_list(Fun, Exprs, Line, S, Tail) when is_integer(Line) ->
build_list_each(Fun, Exprs, Line, S, Tail).
elixir_to_erl(Tree) when is_atom(Tree) ->
{ atom, 0, Tree };
% Builds a simple list, without translatation, just by generating the cons-cell.
build_simple_list(Line, Args) ->
{ List, [] } = build_list(fun(X,Y) -> {X,Y} end, Args, Line, []),
List.
elixir_to_erl(Tree) when is_integer(Tree) ->
{ integer, 0, Tree };
build_simple_reverse_list(Line, Args) ->
{ List, [] } = build_reverse_list(fun(X,Y) -> {X,Y} end, Args, Line, []),
List.
elixir_to_erl(Tree) when is_float(Tree) ->
{ float, 0, Tree };
build_list_each(_Fun, [], _Line, S, Acc) ->
{ Acc, S };
elixir_to_erl(Tree) when is_binary(Tree) ->
{ bin, 0, [{ bin_element, 0, { string, 0, binary_to_list(Tree) }, default, default }] }.
build_list_each(Fun, [H|T], Line, S, Acc) ->
{ Expr, NS } = Fun(H, S),
build_list_each(Fun, T, Line, NS, { cons, Line, Expr, Acc }).
elixir_to_erl_cons([H|T], Acc) ->
elixir_to_erl_cons(T, { cons, 0, elixir_to_erl(H), Acc });
elixir_to_erl_cons([], Acc) -> Acc.
%% Others
@@ -93,7 +78,7 @@ returns_boolean({ call, _, { remote, _, { atom, _, erlang }, { atom, _, Fun } },
returns_boolean({ call, _, { remote, _, { atom, _, erlang }, { atom, _, Fun } }, [_,_,_] }) when
Fun == function_exported -> true;
returns_boolean({ atom, _, Bool }) when Bool == true; Bool == false -> true;
returns_boolean({ atom, _, Bool }) when is_boolean(Bool) -> true;
returns_boolean({ 'case', _, _, Clauses }) ->
lists:all(fun
+25 -50
View File
@@ -2,16 +2,15 @@
-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),
Rescue = elixir_clauses:get_pairs(Meta, rescue, Clauses, S),
Transformer = fun(X, Acc) -> each_clause(Meta, X, umergec(S, Acc)) end,
Transformer = fun(X, Acc) -> each_clause(X, umergec(S, Acc)) end,
lists:mapfoldl(Transformer, S, Rescue ++ Catch).
each_clause(Meta, { 'catch', Raw, Expr }, S) ->
{ Args, Guards } = elixir_clauses:extract_last_guards(Raw),
each_clause({ 'catch', Meta, Raw, Expr }, S) ->
{ Args, Guards } = elixir_clauses:extract_splat_guards(Raw),
Final = case Args of
[X] -> [throw, X, { '_', Meta, nil }];
@@ -24,19 +23,19 @@ each_clause(Meta, { 'catch', Raw, Expr }, S) ->
Condition = { '{}', Meta, Final },
elixir_clauses:assigns_block(?line(Meta), fun elixir_translator:translate_each/2, Condition, [Expr], Guards, S);
each_clause(Meta, { rescue, [Condition|T], Expr }, S) ->
each_clause({ rescue, Meta, [Condition], Expr }, S) ->
case normalize_rescue(Meta, Condition, S) of
{ Left, Right } ->
case Left of
{ '_', _, _ } ->
{ ClauseVar, CS } = elixir_scope:build_ex_var(?line(Meta), S),
{ Clause, _ } = rescue_guards(Meta, ClauseVar, Right, S),
each_clause(Meta, { 'catch', [error, Clause|T], Expr }, CS);
each_clause({ 'catch', Meta, Clause, Expr }, CS);
_ ->
{ Clause, Safe } = rescue_guards(Meta, Left, Right, S),
case Safe of
true ->
each_clause(Meta, { 'catch', [error, Clause|T], Expr }, S);
each_clause({ 'catch', Meta, Clause, Expr }, S);
false ->
{ ClauseVar, CS } = elixir_scope:build_ex_var(?line(Meta), S),
{ FinalClause, _ } = rescue_guards(Meta, ClauseVar, Right, S),
@@ -45,18 +44,17 @@ each_clause(Meta, { rescue, [Condition|T], Expr }, S) ->
{ { '.', Meta, ['Elixir.Exception', normalize] }, Meta, [ClauseVar] }
] },
FinalExpr = prepend_to_block(Meta, Match, Expr),
each_clause(Meta, { 'catch', [error, FinalClause|T], FinalExpr }, CS)
each_clause({ 'catch', Meta, FinalClause, FinalExpr }, CS)
end
end;
_ ->
validate_rescue_access(Meta, Condition, S),
each_clause(Meta, { 'catch', [error, Condition|T], Expr }, S)
each_clause({ 'catch', Meta, [error, Condition], Expr }, S)
end;
each_clause(Meta, {rescue,_,_}, S) ->
each_clause({rescue,Meta,_,_}, S) ->
elixir_errors:syntax_error(Meta, S#elixir_scope.file, "too many arguments given for rescue");
each_clause(Meta, {Key,_,_}, S) ->
each_clause({Key,Meta,_,_}, S) ->
elixir_errors:syntax_error(Meta, S#elixir_scope.file, "invalid key ~ts in try", [Key]).
%% Helpers
@@ -65,8 +63,12 @@ each_clause(Meta, {Key,_,_}, S) ->
normalize_rescue(Meta, List, S) when is_list(List) ->
normalize_rescue(Meta, { in, Meta, [{ '_', Meta, nil }, List] }, S);
%% rescue _
normalize_rescue(_, { '_', _, Atom }, _S) when is_atom(Atom) ->
false;
%% rescue var -> var in _
normalize_rescue(_, { Name, Meta, Atom } = Rescue, S) when is_atom(Name), is_atom(Atom), Name /= '_' ->
normalize_rescue(_, { Name, Meta, Atom } = Rescue, S) when is_atom(Name), is_atom(Atom) ->
normalize_rescue(Meta, { in, Meta, [Rescue, { '_', Meta, nil }] }, S);
%% rescue var in [Exprs]
@@ -77,34 +79,25 @@ normalize_rescue(_, { in, Meta, [Left, Right] }, S) ->
_ when is_list(Right) ->
is_valid_rescue_list(Right, S) andalso { Left, Right };
_ ->
Expanded = 'Elixir.Macro':expand(Right, elixir_scope:to_ex_env({ ?line(Meta), S })),
case is_valid_rescue_list(Expanded, S) of
{ Expanded, ES } = 'Elixir.Macro':expand_all(Right, elixir_scope:to_ex_env({ ?line(Meta), S }), S),
case is_valid_rescue_list(Expanded, ES) of
true -> { Left, Expanded };
false ->
elixir_errors:syntax_error(Meta, S#elixir_scope.file, "invalid use of operator \"in\" in rescue inside try")
elixir_errors:syntax_error(Meta, ES#elixir_scope.file, "invalid use of operator \"in\" in rescue inside try")
end
end;
normalize_rescue(_, { '=', Meta, [{ '__aliases__', _, _ } = Alias, { Name, _, Atom } = Var] }, S)
when is_atom(Name) and is_atom(Atom) ->
elixir_errors:handle_file_warning(S#elixir_scope.file, { Meta, ?MODULE, { rescue_no_match, Var, Alias } }),
false;
normalize_rescue(_, { '=', Meta, [{ Name, _, Atom } = Var, { '__aliases__', _, _ } = Alias] }, S)
when is_atom(Name) and is_atom(Atom) ->
elixir_errors:handle_file_warning(S#elixir_scope.file, { Meta, ?MODULE, { rescue_no_match, Var, Alias } }),
false;
normalize_rescue(Meta, Condition, S) ->
case elixir_translator:translate_each(Condition, S#elixir_scope{context=match}) of
{ { atom, _, Atom }, _ } ->
normalize_rescue(Meta, { in, Meta, [{ '_', Meta, nil }, [Atom]] }, S);
_ ->
false
elixir_errors:syntax_error(Meta, S#elixir_scope.file, "invalid rescue clause. The clause should match on an alias, "
"a variable or be in the `var in [alias]` format")
end.
%% Convert rescue clauses into guards.
rescue_guards(_, Var, nil, _) -> { Var, false };
rescue_guards(_, Var, nil, _) -> { [error, Var], false };
rescue_guards(Meta, Var, Guards, S) ->
{ RawElixir, RawErlang } = rescue_each_var(Meta, Var, Guards),
@@ -121,7 +114,7 @@ rescue_guards(Meta, Var, Guards, S) ->
[join(Meta, 'and', [IsTuple, IsException, OrElse])|Erlang]
end,
{
{ 'when', Meta, [Var, reverse_join(Meta, 'when', Final)] },
[{ 'when', Meta, [error, Var, reverse_join(Meta, 'when', Final)] }],
Safe
}.
@@ -242,29 +235,11 @@ erlang_rescue_guard_for(Meta, Var, 'Elixir.ErlangError') ->
] },
{ 'or', Meta, [IsNotTuple, IsException] }.
%% Validate rescue access
validate_rescue_access(Meta, { '=', _, [Left, Right] }, S) ->
validate_rescue_access(Meta, Left, S),
validate_rescue_access(Meta, Right, S);
validate_rescue_access(Meta, { { '.', _, ['Elixir.Kernel', 'access'] }, _, [Element, _] }, S) ->
case elixir_translator:translate_each(Element, S) of
{ { atom, _, Atom }, _ } ->
case lists:member(Atom, erlang_rescues()) of
false -> [];
true -> elixir_errors:syntax_error(Meta, S#elixir_scope.file, "cannot (yet) pattern match against erlang exceptions")
end;
_ -> []
end;
validate_rescue_access(_, _, _) -> [].
%% Helpers
format_error({ rescue_no_match, Var, Alias }) ->
VarBinary = 'Elixir.Macro':to_binary(Var),
AliasBinary = 'Elixir.Macro':to_binary(Alias),
VarBinary = 'Elixir.Macro':to_string(Var),
AliasBinary = 'Elixir.Macro':to_string(Alias),
Message = "rescue clause (~ts = ~ts) can never match, maybe you meant to write: ~ts in [~ts] ?",
io_lib:format(Message, [AliasBinary, VarBinary, VarBinary, AliasBinary]).
@@ -294,4 +269,4 @@ prepend_to_block(_Meta, Expr, { '__block__', Meta, Args }) ->
{ '__block__', Meta, [Expr|Args] };
prepend_to_block(Meta, Expr, Args) ->
{ '__block__', Meta, [Expr, Args] }.
{ '__block__', Meta, [Expr, Args] }.
+3 -8
View File
@@ -1,4 +1,4 @@
Code.require_file "../test_helper.exs", __FILE__
Code.require_file "test_helper.exs", __DIR__
defmodule AccessTest do
use ExUnit.Case, async: true
@@ -13,21 +13,16 @@ 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.
@config nil
nil = @config[:foo]
@config [foo: :bar]
:bar = @config[:foo]
@mod :lists
[1,2,3] = @mod.flatten([1,[2],3])
[1, 2, 3] = @mod.flatten([1, [2], 3])
test :atom do
exception = assert_raise RuntimeError, fn ->
+3 -3
View File
@@ -1,4 +1,4 @@
Code.require_file "../test_helper.exs", __FILE__
Code.require_file "test_helper.exs", __DIR__
defmodule BehaviourTest do
use ExUnit.Case, async: true
@@ -16,8 +16,8 @@ defmodule BehaviourTest do
test :docs do
docs = Enum.sort(Sample.__behaviour__(:docs))
assert docs == [
{{:bar,2},13,"Bar"},
{{:foo,2},10,"Foo"}
{{:bar, 2}, 13, "Bar"},
{{:foo, 2}, 10, "Foo"}
]
end
+91 -6
View File
@@ -1,4 +1,4 @@
Code.require_file "../../test_helper.exs", __FILE__
Code.require_file "../test_helper.exs", __DIR__
defmodule Binary.Chars.AtomTest do
use ExUnit.Case, async: true
@@ -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
@@ -51,9 +51,8 @@ defmodule Binary.Chars.NumberTest do
end
test :float do
assert to_binary(1.0) == "1.00000000000000000000e+00"
assert to_binary(1.0e10) == "1.00000000000000000000e+10"
assert to_binary(1.0e+10) == "1.00000000000000000000e+10"
assert to_binary(1.0) == "1.0"
assert to_binary(1.0e10) == "10000000000.0"
end
end
@@ -61,7 +60,7 @@ defmodule Binary.Chars.ListTest do
use ExUnit.Case, async: true
test :basic do
assert to_binary([ 1, "b", 3 ]) == <<1,98,3>>
assert to_binary([ 1, "b", 3 ]) == <<1, 98, 3>>
end
test :printable do
@@ -72,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
+16 -5
View File
@@ -1,4 +1,4 @@
Code.require_file "../../test_helper.exs", __FILE__
Code.require_file "../test_helper.exs", __DIR__
defmodule Binary.Inspect.AtomTest do
use ExUnit.Case, async: true
@@ -88,9 +88,8 @@ defmodule Binary.Inspect.NumberTest do
test :float do
assert inspect(1.0) == "1.0"
assert inspect(1.0e10) == "1.0e10"
assert inspect(1.0e+10) == "1.0e10"
assert inspect(1.0e-10) == "1.0e-10"
assert inspect(1.0e10) == "10000000000.0"
assert inspect(1.0e-10) == "0.0000000001"
end
end
@@ -131,6 +130,18 @@ defmodule Binary.Inspect.TupleTest do
assert inspect(RuntimeError.new) == "RuntimeError[message: \"runtime error\"]"
end
defrecord :something, [:a, :b]
test :non_module_record do
assert inspect(:something.new) == ":something[a: nil, b: nil]"
end
defrecord Rec, value: 1
test :two_items_record do
assert inspect({ Rec[value: 1], 1 }) == "{Binary.Inspect.TupleTest.Rec[value: 1],1}"
end
test :empty do
assert inspect({}) == "{}"
end
@@ -167,7 +178,7 @@ defmodule Binary.Inspect.ListTest do
end
test :non_printable do
assert inspect([{:b, 1},{:a,1}]) == "[b: 1, a: 1]"
assert inspect([{:b, 1}, {:a, 1}]) == "[b: 1, a: 1]"
end
test :unproper do
+23 -15
View File
@@ -1,4 +1,4 @@
Code.require_file "../test_helper.exs", __FILE__
Code.require_file "test_helper.exs", __DIR__
defmodule Binary.LiteralTest do
use ExUnit.Case, async: true
@@ -53,7 +53,7 @@ bar
assert "\123" == "S"
assert "\377" == "ÿ"
assert "\128" == "\n8"
assert "\18" == <<1,?8>>
assert "\18" == <<1, ?8>>
end
test :hex do
@@ -62,10 +62,10 @@ bar
assert "\xFF" == "ÿ"
assert "\x{A}"== "\n"
assert "\x{E9}"== "é"
assert "\x{10F}" == <<196,143>>
assert "\x{10FF}" == <<225,131,191>>
assert "\x{10FFF}" == <<240,144,191,191>>
assert "\x{10FFFF}" == <<244,143,191,191>>
assert "\x{10F}" == <<196, 143>>
assert "\x{10FF}" == <<225, 131, 191>>
assert "\x{10FFF}" == <<240, 144, 191, 191>>
assert "\x{10FFFF}" == <<244, 143, 191, 191>>
end
test :match do
@@ -85,17 +85,25 @@ bar
end
test :bitsyntax_with_utf do
assert <<106,111,115,101>> == << "jose" :: utf8 >>
assert <<106,111,115,101>> == << 'jose' :: utf8 >>
assert <<106, 111, 115, 101>> == << "jose" :: utf8 >>
assert <<106, 111, 115, 101>> == << 'jose' :: utf8 >>
assert <<0,106,0,111,0,115,0,101>> == << "jose" :: utf16 >>
assert <<0,106,0,111,0,115,0,101>> == << 'jose' :: utf16 >>
assert <<0, 106, 0, 111, 0, 115, 0, 101>> == << "jose" :: utf16 >>
assert <<0, 106, 0, 111, 0, 115, 0, 101>> == << 'jose' :: utf16 >>
assert <<106,0,111,0,115,0,101,0>> == << "jose" :: [utf16, little] >>
assert <<106,0,111,0,115,0,101,0>> == << 'jose' :: [utf16, little] >>
assert <<106, 0, 111, 0, 115, 0, 101, 0>> == << "jose" :: [utf16, little] >>
assert <<106, 0, 111, 0, 115, 0, 101, 0>> == << 'jose' :: [utf16, little] >>
assert <<0,0,0,106,0,0,0,111,0,0,0,115,0,0,0,101>> == << "jose" :: utf32 >>
assert <<0,0,0,106,0,0,0,111,0,0,0,115,0,0,0,101>> == << 'jose' :: utf32 >>
assert <<0, 0, 0, 106, 0, 0, 0, 111, 0, 0, 0, 115, 0, 0, 0, 101>> == << "jose" :: utf32 >>
assert <<0, 0, 0, 106, 0, 0, 0, 111, 0, 0, 0, 115, 0, 0, 0, 101>> == << 'jose' :: utf32 >>
end
@binary "new "
@charlist 'old '
test :bitsyntax_with_expansion do
assert <<@binary, "world">> == "new world"
assert <<@charlist, "world">> == "old world"
end
test :bitsyntax_translation do
@@ -140,4 +148,4 @@ bar
defp is_match?(_, _) do
false
end
end
end
+5 -5
View File
@@ -1,4 +1,4 @@
Code.require_file "../test_helper.exs", __FILE__
Code.require_file "test_helper.exs", __DIR__
defmodule Bitwise.FunctionsTest do
use ExUnit.Case, async: true
@@ -38,11 +38,11 @@ defmodule Bitwise.OperatorsTest do
end
test :band do
assert 1 &&& 1 == 1
assert (1 &&& 1) == 1
end
test :bor do
assert 0 ||| 1 == 1
assert (0 ||| 1) == 1
end
test :bxor do
@@ -50,10 +50,10 @@ defmodule Bitwise.OperatorsTest do
end
test :bsl do
assert 1 <<< 1 == 2
assert (1 <<< 1) == 2
end
test :bsr do
assert 1 >>> 1 == 0
assert (1 >>> 1) == 0
end
end
+1 -1
View File
@@ -1,4 +1,4 @@
Code.require_file "../test_helper.exs", __FILE__
Code.require_file "test_helper.exs", __DIR__
defmodule CharListTest do
use ExUnit.Case, async: true
+50 -34
View File
@@ -1,4 +1,4 @@
Code.require_file "../test_helper.exs", __FILE__
Code.require_file "test_helper.exs", __DIR__
defmodule CodeTest do
use ExUnit.Case, async: true
@@ -14,17 +14,30 @@ defmodule CodeTest do
Code.eval_quoted contents, [], file: "sample.ex", line: 13
test :eval do
assert Code.eval("1 + 2") == { 3, [] }
assert { 3, _ } = Code.eval("a + b", [a: 1, b: 2], __ENV__.location)
test :eval_string do
assert Code.eval_string("1 + 2") == { 3, [] }
assert { 3, _ } = Code.eval_string("a + b", [a: 1, b: 2], __ENV__.location)
end
test :eval_with_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("one", [], delegate_locals_to: __MODULE__) == { 1, [] }
assert Code.eval_string("one", [], delegate_locals_to: __MODULE__) == { 1, [] }
end
test :eval_options do
assert Code.eval_string("is_atom(:foo) and is_record(1..2, Range) and K.is_list([])", [],
functions: [{ Kernel, [is_atom: 1] }],
macros: [{ Kernel, [..: 2, and: 2, is_record: 2]}],
aliases: [{K, Kernel}],
requires: [Kernel]) == { true, [] }
end
test :eval_with_requires do
assert Code.eval("Kernel.if true, do: :ok", [], requires: [Z, Kernel]) == { :ok, [] }
assert Code.eval_string("Kernel.if true, do: :ok", [], requires: [Z, Kernel]) == { :ok, [] }
end
test :eval_quoted do
@@ -34,67 +47,56 @@ defmodule CodeTest do
test :eval_quoted_with_env do
alias :lists, as: MyList
assert Code.eval_quoted(quote(do: MyList.flatten [[1,2,3]]), [], __ENV__) == { [1,2,3],[] }
assert Code.eval_quoted(quote(do: MyList.flatten [[1, 2, 3]]), [], __ENV__) == { [1, 2, 3],[] }
end
test :require do
Code.require_file fixture_path("code_sample.exs")
assert fixture_path("code_sample.exs") inlist Code.loaded_files
assert fixture_path("code_sample.exs") in Code.loaded_files
assert Code.require_file(fixture_path("code_sample.exs")) == nil
Code.unload_files [fixture_path("code_sample.exs")]
refute fixture_path("code_sample.exs") inlist Code.loaded_files
refute fixture_path("code_sample.exs") in Code.loaded_files
assert Code.require_file(fixture_path("code_sample.exs")) != nil
end
test :path_manipulation do
path = Path.expand("../binary", __FILE__)
path = Path.join(__DIR__, "binary")
Code.prepend_path path
assert binary_to_list(path) inlist :code.get_path
assert binary_to_list(path) in :code.get_path
Code.delete_path path
refute binary_to_list(path) inlist :code.get_path
refute binary_to_list(path) in :code.get_path
end
test :file do
assert :filename.absname(__FILE__) == __FILE__
end
test :string_to_ast do
assert Code.string_to_ast("1 + 2") == { :ok, quote hygiene: [imports: false], line: 1, do: 1 + 2 }
assert Code.string_to_ast("1 + 2; 3 + 4") == { :ok, quote hygiene: [imports: false], line: 1, do: (1 + 2; 3 + 4) }
assert { :error, _ } = Code.string_to_ast("a.1")
test :string_to_quoted do
assert Code.string_to_quoted("1 + 2") == { :ok, { :+, [line: 1], [1, 2] } }
assert { :error, _ } = Code.string_to_quoted("a.1")
end
test :string_to_ast_existing_atoms_only do
assert :badarg = catch_error(Code.string_to_ast(":thereisnosuchatom", existing_atoms_only: true))
assert :badarg = catch_error(Code.string_to_ast!(":thereisnosuchatom", existing_atoms_only: true))
test :string_to_quoted_existing_atoms_only do
assert :badarg = catch_error(Code.string_to_quoted(":thereisnosuchatom", existing_atoms_only: true))
assert :badarg = catch_error(Code.string_to_quoted!(":thereisnosuchatom", existing_atoms_only: true))
end
test :string_to_ast! do
assert Code.string_to_ast!("1 + 2") == quote hygiene: [imports: false], line: 1, do: 1 + 2
test :string_to_quoted! do
assert Code.string_to_quoted!("1 + 2") == { :+, [line: 1], [1, 2] }
assert_raise SyntaxError, fn ->
Code.string_to_ast!("a.1")
Code.string_to_quoted!("a.1")
end
assert_raise TokenMissingError, fn ->
Code.string_to_ast!("1 +")
Code.string_to_quoted!("1 +")
end
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
@@ -102,6 +104,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)

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