Compare commits

...
520 Commits
Author SHA1 Message Date
José Valim 83591b9d76 Release v0.10.0 2013-07-15 11:29:40 +02:00
José Valim b0bc02ef70 Do not call :io.columns if width is set 2013-07-13 22:42:31 +02:00
José Valim a7ffebc915 Tidy up CHANGELOG 2013-07-13 22:42:15 +02:00
José Valim 40653e433b Deprecate mix run EXPR in favor of mix run -e EXPR 2013-07-12 12:53:10 +02:00
José Valim c0b007af9a Never allow a comprehension to behave like a guard 2013-07-12 12:46:17 +02:00
José Valim 2fee17e709 Deprecate Kernel.float/1 in favor of explicit conversion 2013-07-12 12:38:08 +02:00
José Valim ebb5500086 Clean up Elixir internal errors
In particular, we reserve SyntaxError for synatx errors (doh)
and for macros that expects a given format at compilation time.

For example, defmodule/2 expects a :do keyword in the second
argument, not providing one is a syntax error.

In other places we impose similar restrictions bue the values
are allowed to be any expression as long as the final result
at compilation time is a given expression, for example:

    when foo in @value

The right side of in can be anything as long it is a list or
a range at compilation time. Those kind of errors are now
CompileError.
2013-07-12 11:44:49 +02:00
José Valim 9754f056dd Improve error messages for in-erlang macros 2013-07-12 10:15:01 +02:00
José Valim e19dbf04ed Update CHANGELOG 2013-07-12 10:04:46 +02:00
José Valim c654159a2b Do not support inline types with defrecord 2013-07-12 10:02:47 +02:00
José Valim 6715c528df Improve error messages from typespecs, closes #1401 2013-07-12 09:53:05 +02:00
José Valim 1c7a37a2b7 Raise proper exception on invalid quoted expression
We also documented valid types in Macro.t

Closes #1411
2013-07-12 09:29:57 +02:00
José Valim 84a0985a39 Deprecate quote binding: in favor of quote bind_quoted: 2013-07-12 08:54:01 +02:00
José Valim 73b9ac7449 Add deprecation warning to __index__ on Records 2013-07-12 08:44:35 +02:00
José Valim 8e659930c0 Merge pull request #1417 from jwarwick/issue_1414
Fix for Issue 1414, Record.__record__(:index) syntax
2013-07-11 23:35:49 -07:00
José Valim 095a91a31f Merge pull request #1419 from brunoro/raw-kwlist
kwlist are inspected as a list of tuples with raw: true
2013-07-11 23:34:55 -07:00
José Valim 7393973d96 Merge pull request #1402 from jwarwick/retriever_typos
Fixed typos in Mix.Deps.Retriever docs
2013-07-11 23:34:21 -07:00
José Valim c3c8c3c71d Update CHANGELOG 2013-07-12 08:33:10 +02:00
José Valim 73eeec2eec Keep head|tail format on splice, closes #1393 2013-07-12 08:32:01 +02:00
Gustavo Brunoro c0bb6d73ae kwlist are inspected as a list of tuples with raw: true 2013-07-11 16:17:00 -03:00
John Warwick 82da86afcb Updated System and Mix to use __record__(:index) syntax 2013-07-11 12:56:42 -04:00
John Warwick c1a7c039fe Updated docs to use __record__(:index) instead of __index__ 2013-07-11 12:52:01 -04:00
John Warwick 5b754c8630 Implemented Record.__record__(:index) syntax, Issue 1414. 2013-07-11 12:47:16 -04:00
José Valim d195429470 Merge pull request #1415 from jwarwick/mix_help_typos
Fixed doc typos in Mix.Tasks.Test
2013-07-11 07:21:36 -07:00
John Warwick 6cc9a4b9f4 Fixed doc typos in Mix.Tasks.Test 2013-07-11 09:57:47 -04:00
José Valim 08aa22616a Merge pull request #1412 from jwarwick/optionparser_docs
Fixed doc typos in OptionParser
2013-07-10 15:24:11 -07:00
John Warwick e4ddfc2118 Fixed doc typos in OptionParser 2013-07-10 18:05:31 -04:00
José Valim 4955eb9deb Start app before perfoming requires 2013-07-10 23:40:29 +02:00
José Valim befa36ce1e Optimize stale handling 2013-07-10 19:32:00 +02:00
José Valim 5e8f9284d2 Clean up mix tests 2013-07-10 16:57:11 +02:00
José Valim c0a6dc4b47 Simplify manifest API and put tests back on green 2013-07-10 16:14:34 +02:00
José Valim 99da29b430 Ensure eval callbacks gets a fresh vars dict 2013-07-10 16:04:36 +02:00
José Valim 3c3c989ef7 Merge pull request #1408 from meh/mix-leex-yecc
Delete files in the manifest instead of removing the loadpath
2013-07-10 07:04:29 -07:00
meh 977ec32476 Delete files in the manifest instead of removing the loadpath 2013-07-10 15:49:58 +02:00
José Valim 27b4ebe34e Merge pull request #1407 from gentoo/Makefile
Makefile improvements
2013-07-10 06:12:58 -07:00
hasufell b0f9d7e9bc MAKE: respect LIBDIR variable
Some distributions do not use plain "lib", rather "lib64"
and "lib32". Allow to control this variable.
2013-07-10 14:54:53 +02:00
hasufell f9afc520ca MAKE: standardize install variables
- convert to autotools-like variables commonly used in build scripts
- respect DESTDIR (used for temporary install locations)
- fix symlink logic to work with DESTDIR
${foo##bar} is posix, see:
http://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html
under "2.6.2 Parameter Expansion"
- quote all variables that can contain whitespace
(make does not take care of that!)
2013-07-10 14:52:48 +02:00
hasufell a6589c62a4 MAKE: allow more verbose build output
people can now run:
  make Q=
to get a the maximum of build output
2013-07-10 14:27:40 +02:00
José Valim 77155c55a6 Merge pull request #1406 from jwarwick/mix_typos
Fixed typos in various Mix modules
2013-07-09 23:26:00 -07:00
John Warwick 776b07f2f0 Fixed typos in Mix.Deps. Updated error strings in test cases 2013-07-09 21:46:03 -04:00
John Warwick 1f00e01679 Fixed doc typos in Mix.Archive 2013-07-09 21:24:46 -04:00
John Warwick a7fa43338e Fixed doc typos in Mix 2013-07-09 21:18:19 -04:00
John Warwick 394ccb3f42 Fixed doc typos in Mix.Tasks.Deps.* 2013-07-09 21:09:39 -04:00
José Valim e7c0a2a3a7 Merge pull request #1403 from devinus/raise-on-unsupported-dictset
Raise an error when Dict/Set doesn't support the target
2013-07-09 14:13:02 -07:00
Devin Torres f808da8ffb Raise an error when Dict/Set doesn't support the target 2013-07-09 16:04:48 -05:00
José Valim c0c8f7f72b Allow -boot start to be overriden on releases 2013-07-09 21:29:35 +02:00
José Valim 290403f7d7 Check the key on register_attribute 2013-07-09 21:24:25 +02:00
José Valim 967ca97455 Continue checking first for a mixfile before a rebarconfig 2013-07-09 21:24:25 +02:00
Yurii Rashkovskii ba00a1ffc6 Erlang release for Elixir should use start.boot instead of elixir.boot 2013-07-09 12:05:12 -07:00
John Warwick a209dc72e3 Fixed typos in Mix.Deps.Retriever docs 2013-07-09 13:23:49 -04:00
José Valim b6ea6b63ed Merge pull request #1400 from ericmj/mix-deps-rebar
All deps of a rebar dep should be handled by rebar
2013-07-09 09:24:30 -07:00
Eric Meadows-Jonsson df4cee50d5 All deps of a rebar dep should be handled by rebar 2013-07-09 17:48:01 +02:00
José Valim 318eaebf3f Add some comments to Mix.Deps.Retriever 2013-07-09 14:30:00 +02:00
José Valim 0a182b4590 Merge pull request #1399 from ericmj/patch-1396
Dont use manager to decide where a dep was defined
2013-07-09 05:25:43 -07:00
José Valim 88ac1f6a7b Improve coverage for lexical scope on try and case 2013-07-09 14:24:57 +02:00
Eric Meadows-Jonsson 621db32ffb Dont use manager to decide where a dep was defined 2013-07-09 13:19:43 +02:00
José Valim 5b2f44d1d7 Ensure --app switch does not halt upcoming ones, closes #1398 2013-07-09 11:24:34 +02:00
José Valim ad3cf4e451 Merge pull request #1395 from devinus/split-empty-string
String.split("") should be [""], %w() should be []
2013-07-08 23:44:01 -07:00
José Valim 209c88ab0b Merge pull request #1397 from jwarwick/typos
Fixed doc typos in Kernel and Record
2013-07-08 23:38:59 -07:00
John Warwick 3c3cf143e7 Fixed legacy typos in Kernel 2013-07-08 23:24:56 -04:00
John Warwick 45454ebaf0 Fixed typos in Record docs 2013-07-08 22:59:49 -04:00
John Warwick e321550127 Fixed typos in Kernel docs 2013-07-08 22:39:14 -04:00
José Valim 2dc1e41aca Pretty printer now prints grouped elements in the same line, closes #1383
This implementation slightly changes the semantic of Lindig's algorithm
to allow elements that belong to the same group to be printed together
in the same line, even if they do not fit the line fully. This was achieved
by changing `:break` to mean a possible break and `:flat` to force a flat
structure. Then deciding if a break works as a newline is just a matter
of checking if we have enough space until the next break that is not
inside a group (which is still flat).
2013-07-09 02:24:59 +02:00
Devin Torres 74c82f8590 String.split("") should be [""], %w() should be [] 2013-07-08 17:48:18 -05:00
José Valim 63cfe89096 Improve types for Stream 2013-07-08 22:32:04 +02:00
José Valim 5e5bf1ddb1 Install rebar from elixir repo which is more up to date 2013-07-08 21:48:56 +02:00
José Valim 262a47d6ee Remove hopefully uneeded rebar hacks 2013-07-08 21:01:47 +02:00
José Valim f8dfc485ea Merge pull request #1392 from pragdave/update-rebar
Update rebar to rebar 2.1.0-pre. This (among other things) fixes a crash...
2013-07-08 11:59:46 -07:00
José Valim 52ab64d60b Ensure unquote_splicing followed by tail works, closes #1393 2013-07-08 20:57:51 +02:00
Dave Thomas 3de7f42155 Forgot the changelog 2013-07-08 13:09:17 -05:00
Dave Thomas 3cf4c21ad1 Update rebar to rebar 2.1.0-pre. This (among other things) fixes a crash if an environment variable contains non-ascii characters 2013-07-08 10:26:24 -05:00
José Valim 802b0d11a6 Update CHANGELOG 2013-07-08 11:22:17 +02:00
José Valim 67443d3691 First element of defrecordp is now the record name and longer the current module 2013-07-08 11:22:08 +02:00
José Valim 85371e4e45 Ensure record rewriter keeps tags after setelement 2013-07-08 10:35:37 +02:00
José Valim 2da5326549 Allow quoting on aliases with calls 2013-07-08 09:42:42 +02:00
José Valim a83547723e Just iterate the next value if we are going to use it 2013-07-08 09:28:09 +02:00
José Valim 879651cba9 Rename --docs tests file to docs_test.exs 2013-07-08 09:21:19 +02:00
José Valim cc3634ed37 Merge pull request #1390 from pragdave/iterate
Iterate
2013-07-08 00:15:38 -07:00
José Valim 5a5ab18a57 Merge pull request #1391 from jwarwick/iex_docs
Fixed doc typos in IEx, IEx.Options and IEx.Helpers
2013-07-08 00:13:48 -07:00
John Warwick 8fca8a965b Fixed doc typos in IEx.Helpers 2013-07-07 23:25:15 -04:00
John Warwick 0beb65298d Fixed doc typos in the IEx module 2013-07-07 23:02:57 -04:00
John Warwick 218552af3f Fixed typos in IEx.Options docs 2013-07-07 22:53:57 -04:00
Dave Thomas a2c6630793 Add Stream.repeatedly 2013-07-07 18:12:08 -05:00
Dave Thomas 8c3c0a9149 Add Stream.iterate 2013-07-07 17:50:01 -05:00
José Valim b1181320d3 Small refactoring on IEx.Options 2013-07-07 23:17:52 +02:00
José Valim 9742c1c17d Merge pull request #1389 from alco/iex-options-fix
Refactor IEx.Options and raise on bad key
2013-07-07 14:13:41 -07:00
Alexei Sholik eb4dda0b1e Refactor IEx.Options and raise on bad key 2013-07-08 00:02:22 +03:00
José Valim bde91c325c Ensure Stream.take/2 halts execution 2013-07-07 22:50:49 +02:00
Dave Thomas 7c2f25fc85 Add Stream.seq() for generating potentially infinite streams of values 2013-07-07 14:41:24 -05:00
José Valim d82f315fa7 inspect_kw no longer is 2013-07-07 21:21:06 +02:00
José Valim fc505f8a82 Replace ?is_new_atom macro by ?is_atom_start 2013-07-07 21:14:36 +02:00
José Valim 241cd1747d Merge pull request #1376 from ericmj/mix-deps-version
Use Mix.Version for dependencies
2013-07-07 12:10:10 -07:00
Yurii Rashkovskii dd6fb4acc2 Minor doc fix 2013-07-07 10:04:09 -07:00
José Valim a056f0ac40 Projects without a mix.exs can't be recursive
Otherwise, recursive_enabled? always returns true, even
when it should be disabled, affecting the output of tasks.

Closes #1374
2013-07-07 18:04:22 +02:00
José Valim b830485541 Ensure Elixir can run on directories with :, closes #1375 2013-07-07 10:00:46 +02:00
José Valim 15d347fb96 Merge pull request #1385 from jwarwick/inspect_docs
Fixed doc typos for Inspect
2013-07-06 23:55:29 -07:00
John Warwick c11d50e451 Fixed doc typos in the Inspect protocol 2013-07-06 23:33:10 -04:00
John Warwick 64e6037986 Fixed doc typos in Inspect.Algebra 2013-07-06 23:17:35 -04:00
José Valim 8a9927ea1b Optimize Inspect implementation 2013-07-06 16:17:09 +02:00
Yurii Rashkovskii 889de92187 Doc typo fix 2013-07-06 07:00:05 -07:00
José Valim 033fb6a5e9 Update CHANGELOG 2013-07-06 12:36:08 +02:00
José Valim 7ad7847596 Rearrange how ambiguous ops are handled, closes #1378 2013-07-06 12:32:22 +02:00
José Valim 9dcfc53f60 Merge pull request #1382 from jwarwick/think_different
Fixed doc typos in Path and Kernel
2013-07-06 02:50:03 -07:00
José Valim 26f9ce121a Merge pull request #1381 from jwarwick/set_docs
Fixed typos in Set module docs
2013-07-06 02:49:47 -07:00
José Valim 0c557d2766 Document the Inspect protocol and Inspect.Algebra 2013-07-06 11:43:59 +02:00
José Valim e13a516715 Get rid of Inspect.Utils 2013-07-06 11:10:12 +02:00
José Valim accf7dac82 Change inspect formatting to use spaces in between , 2013-07-06 10:07:54 +02:00
John Warwick b3a65eaca1 Fixing typos in Kernel module docs 2013-07-05 23:57:22 -04:00
John Warwick 3e29d9fa2e Fixing typos in Path module docs 2013-07-05 23:41:19 -04:00
John Warwick 0edf1882ff Fixed typos in Set module docs 2013-07-05 23:33:04 -04:00
José Valim 35d019559d Remove the need for Inspect.Utils.return and Inspect.Algebra.text 2013-07-05 23:19:39 +02:00
José Valim 7048236988 Use a Inspect.Opts record instead of passing opts around 2013-07-05 22:37:47 +02:00
José Valim b335b1663d Rename Binary.Inspect -> Inspect and Wadler -> Inspect.Algebra 2013-07-05 22:15:16 +02:00
José Valim f69ecfe8aa Make Wadler API conform with Elixir API and update doctests to actually run 2013-07-05 20:29:15 +02:00
José Valim 10a456823c Do not enable pretty by default 2013-07-05 19:27:55 +02:00
José Valim a26d4cf0cd Merge remote-tracking branch 'brunoro/master' into jv-pretty-printing 2013-07-05 19:27:37 +02:00
Eric Meadows-Jonsson 3f11eef086 Require override: true option to override a dep 2013-07-05 15:33:04 +02:00
Eric Meadows-Jonsson ad5885ba2e Use Mix.Version for dependencies 2013-07-05 15:32:20 +02:00
José Valim a42725910f Update CHANGELOG 2013-07-04 21:26:01 +02:00
José Valim a6f8e178c0 Consistently allow empty expressions on both side of operators 2013-07-04 20:29:39 +02:00
José Valim 7c2a661a5f Merge pull request #1369 from ericmj/mix-deps-errors
Improve Mix dependency error messages
2013-07-04 11:10:48 -07:00
Eric Meadows-Jonsson 15af0356a2 Improve Mix dependency error messages 2013-07-04 20:04:28 +02:00
José Valim ebe522ec50 Tighten up the grammar rules regarding parentheses omission
Prior to this commit, Elixir allowed too many ambiguous calls
by ommitting the parentheses:

    do_something 1, is_list [], 3
    [1, is_atom :foo, 3]

Both cases above would lead to a compilation error, as an attempt
to call is_list/2 and is_atom/2 would happen. Those examples will
now raise a syntax error, pointing to the ambiguous location.

We have disallowed such examples throughout the language with
the exception of one argument calls:

    assert is_list []

Examples as above are extremely frequent and were kept as is.
2013-07-04 18:48:55 +02:00
José Valim 6d491325d5 Update and remove out of date erl tests 2013-07-04 11:40:00 +02:00
José Valim f697ff4160 Ensure HashDict can be serialized into attribute, closes #1368 2013-07-04 11:35:14 +02:00
José Valim 4ae58d3bf4 Do not try to parse arguments for undefined functions 2013-07-04 11:16:14 +02:00
José Valim 650648a687 Clean up ExUnit.Case 2013-07-04 10:57:31 +02:00
José Valim b561c93db2 Clarify docs for Macro.expand_all/2 2013-07-04 10:30:13 +02:00
José Valim 15c1a9891c Implement bindings in Elixir 2013-07-04 10:18:46 +02:00
Gustavo Brunoro 10697628f9 to_binary always equals to 2. 2013-07-03 22:25:18 -03:00
Gustavo Brunoro 89da6e032d adding documentation for Binary.Inspect.Utils 2013-07-03 22:19:10 -03:00
Gustavo Brunoro e969a330d5 tests use tuples instead of recordp macros
* recordp builder/accessor macros aren't accessible on test cases.
2013-07-03 20:28:30 -03:00
Gustavo Brunoro 0f4598fca0 using defrecordp for all records on wadler.ex
* some tests still need to be refactored.
2013-07-03 20:03:04 -03:00
Gustavo Brunoro cb5bb2e548 Merge branch 'master' of https://github.com/elixir-lang/elixir 2013-07-03 18:54:33 -03:00
José Valim acf912c2c8 Clean up ExUnit.CaseTemplate to allow composition 2013-07-03 21:29:35 +02:00
José Valim 6b82967dd6 Improve error report on unhandled -> 2013-07-03 21:29:35 +02:00
José Valim 789d01ae2a Merge pull request #1366 from ericmj/iex-show-exit
Show exit messages in IEx
2013-07-03 08:30:54 -07:00
Eric Meadows-Jonsson 531434959f Show exit messages in IEx 2013-07-03 17:21:28 +02:00
Vladimir Dronnikov d9baf3c39e elixir.bat: accomodate for paths with spaces
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2013-07-03 15:05:45 +02:00
José Valim 91be13d128 Optimize HashDict to store pairs in a cons cell reducing storage per key by half 2013-07-03 15:05:08 +02:00
José Valim 62527cae9c Small clean up on parser file 2013-07-03 15:05:08 +02:00
José Valim 466c2b2e1b Merge pull request #1365 from jwarwick/mix_doc_typos
Fixing typos in Mix.Task docs
2013-07-03 05:52:23 -07:00
John Warwick 2ba6238dd1 Fixing typos in Mix.Task docs 2013-07-03 08:50:08 -04:00
José Valim 48cef0c65b Merge pull request #1361 from jwarwick/regex_examples
Doc cleanup and more examples for Regex module
2013-07-03 01:57:58 -07:00
John Warwick 3d208e219d Added doctest examples for Regex.split 2013-07-02 22:24:02 -04:00
John Warwick f8fd11bef0 Fixing doc typos in Regex module 2013-07-02 22:19:11 -04:00
John Warwick 9703c9968b More doctests for Regex.captures and fixed return type doc 2013-07-02 22:01:56 -04:00
José Valim 82f5868257 Merge pull request #1360 from AlbertMoscow/master
Fixed typos in Kernel
2013-07-02 11:02:18 -07:00
AlbertMoscow 3ab74433c0 Fixed typos in Kernel
This line of code is referenced from the blog post by the following link: https://peepcode.com/blog/2013/elixir-is-for-programmers
2013-07-02 10:55:53 -07:00
José Valim 31dec377f0 Merge pull request #1359 from justinj/kernel_typos
Fixed typos in Kernel
2013-07-02 10:00:52 -07:00
Justin Jaffray a6db102d05 Fixed typos in Kernel
Also reworded some docstrings to make more sense
2013-07-02 12:06:08 -04:00
José Valim 628d1b630e Fix surprising behaviour with Module.register_attribute/3 that caused attributes to be persisted unecessarily 2013-07-02 10:31:32 +02:00
José Valim fbd7c34211 Merge pull request #1356 from jwarwick/regex_typos
Fixed typos in Regex module docs
2013-07-02 00:07:46 -07:00
José Valim 25f5546d6f Merge pull request #1355 from jwarwick/kernel_record_typos
Fixed doc typos in Kernel.defrecord and Kernel.defrecordp
2013-07-02 00:06:59 -07:00
José Valim 288925c2fa Merge pull request #1354 from jwarwick/record_typos
Fixing Record doc typos
2013-07-02 00:06:44 -07:00
John Warwick 4a0b1aa384 Fixed typos in Regex module docs 2013-07-01 22:59:20 -04:00
John Warwick ee1b87089d Fixed doc typos for Kernel.defrecord and Kernel.defrecordp 2013-07-01 22:36:33 -04:00
John Warwick 074db53fbb Fixed typo in Record.update ArgumentError message 2013-07-01 22:11:41 -04:00
John Warwick 60693e48e5 Fixed typos in Record docs 2013-07-01 22:09:24 -04:00
José Valim e49a00d3cb Tidy up Record and access docs 2013-07-01 21:19:09 +02:00
José Valim 1fe756a2db Improve Record docs 2013-07-01 21:10:37 +02:00
José Valim 1da36c5b04 Re-organize records tests 2013-07-01 21:10:13 +02:00
José Valim 7a2aea160c Rewrite how records are introduced 2013-07-01 21:10:08 +02:00
José Valim 2837ce381b Get rid of specifics 2013-07-01 21:09:35 +02:00
José Valim 3ae417cbb2 Merge pull request #1352 from elixir-lang/jv-uri
Remove the URI lookup mechanism in favor of application env
2013-07-01 12:09:11 -07:00
José Valim 12c5cf0515 Fix typo on ExUnit.CLIFormatter 2013-07-01 20:27:01 +02:00
José Valim 26eafd1a4c Remove the URI lookup mechanism in favor of application env 2013-07-01 20:25:01 +02:00
José Valim cdd8e5862d Rename the ExUnit option from debug to trace 2013-07-01 16:41:34 +02:00
José Valim eee1993cdd Update CHANGELOG 2013-07-01 15:14:38 +02:00
José Valim 46e898e413 Add mix test --debug 2013-07-01 15:02:57 +02:00
José Valim 82e1c9337c No need to print 'test' before each test name 2013-07-01 14:50:07 +02:00
José Valim 3bac4ddec6 Support --debug on internal tests which are not run through mix 2013-07-01 14:20:33 +02:00
José Valim 0703be3863 Support :debug option on ExUnit 2013-07-01 14:18:22 +02:00
José Valim d087686f3d Remove unecessary overheard on ExUnit runner and ensure messages are in order 2013-07-01 13:18:26 +02:00
José Valim 595d343e05 Refactor and improve coverage of ExUnit formatters 2013-07-01 13:18:26 +02:00
José Valim dce2bee57e Merge pull request #1349 from jwarwick/code_typos
Fixing typos in the Code module
2013-07-01 00:46:07 -07:00
José Valim d512f545f4 Merge pull request #1348 from jwarwick/stream_typos
Fixing typos in Stream docs
2013-07-01 00:45:46 -07:00
John Warwick 1bdefbb3d1 Fixing typos in the Code module 2013-06-30 22:32:37 -04:00
John Warwick 0e4bef3ac8 Fixing typos in Stream docs 2013-06-30 21:50:05 -04:00
José Valim c5121feac5 Update CHANGELOG 2013-06-30 19:24:54 +02:00
José Valim 08f592610e Merge branch 'jv-streams' 2013-06-30 19:21:58 +02:00
José Valim 664d1ec0d3 Decide on IO.stream and unify read/readline API 2013-06-30 19:21:39 +02:00
José Valim 8ca28496e4 Add Stream.reject/2 and Stream.(drop|take)*/2 2013-06-30 19:21:39 +02:00
José Valim c5cd8c3fcd Improve the docs 2013-06-30 19:21:39 +02:00
José Valim cf98f9b975 Deprecate File.*iterator! in favor of File.*lines_stream! 2013-06-30 19:21:39 +02:00
José Valim 26e6fdbeb2 Improve Stream doctests 2013-06-30 19:21:39 +02:00
José Valim 35edf833b6 Add Stream.cycle/1 2013-06-30 19:21:39 +02:00
José Valim 1a343b01d6 Add Stream.with_index/1 and Enum.with_index/1 2013-06-30 19:21:39 +02:00
José Valim bc76627feb Add Streams 2013-06-30 19:21:39 +02:00
José Valim 0e0d93076c Update CHANGELOG 2013-06-30 16:48:57 +02:00
José Valim c04dcc1882 Avoid bootstraping issues 2013-06-30 16:33:04 +02:00
José Valim 5822cd153f Do not automatically nest defrecordp argument
This may lead people think that an alias is defined
and that is not the case.
2013-06-30 16:25:08 +02:00
José Valim 65c624ef55 Merge pull request #1339 from yrashk/defrecordp-3
defrecordp/3
2013-06-30 07:13:43 -07:00
José Valim 6c56bb8682 Put doc tests in alphabetical order 2013-06-30 15:52:10 +02:00
José Valim b666c696b4 Remove docs from functions documented on behaviours 2013-06-30 15:51:05 +02:00
José Valim 1f8c8f5292 Merge pull request #1241 from josephwilk/sets
Add Sets to Elixir
2013-06-30 06:43:36 -07:00
José Valim 03323b9bfa Macro.escape should also escape nested tuples, closes #1344 2013-06-30 15:30:44 +02:00
José Valim 8195662b98 Merge pull request #1345 from JordiPolo/feature/note_about_make_tests
when contributing it is important to run the tests
2013-06-30 05:19:23 -07:00
Joseph Wilk d6c0533395 We don't need the HashSet, its already one. 2013-06-30 13:44:19 +02:00
Jordi Carres 474d4f6532 when contributing it is important to run the tests 2013-06-30 18:59:45 +09:00
José Valim e548807605 Do not downcase host and inline common lookups 2013-06-30 10:57:11 +02:00
José Valim 25f82dcb46 Go directly through :elixir_errors.warn 2013-06-30 10:42:57 +02:00
José Valim 596670ab9c Merge pull request #1341 from jarsen/string-duplicate
Return empty string when given 0 for n in String.duplicate
2013-06-29 23:47:49 -07:00
Yurii Rashkovskii c20ee8f7e9 Flip the order of first two arguments in defrecordp/3 2013-06-30 08:36:24 +02:00
José Valim 3e13f77d1b Merge pull request #1342 from jwarwick/gen_event_typos
Fixing GenEvent.Behaviour doc typos
2013-06-29 23:23:27 -07:00
José Valim 76be461964 Merge pull request #1340 from jwarwick/gen_server_typos
Fixed GenServer.Behaviour docs typos and example bug
2013-06-29 23:23:09 -07:00
John Warwick 31431fa795 Added additional iex output to GenEvent.Behaviour example 2013-06-29 22:31:10 -04:00
John Warwick 3bf1b08af3 Fixing typos in GenEvent.Behaviour 2013-06-29 22:26:19 -04:00
Jason Larsen f1baed6434 Add test for String.duplicate with n of 0 2013-06-29 20:24:50 -06:00
John Warwick e9aa9b213b Fixed calls to super in GenServer.Behaviour example 2013-06-29 22:16:51 -04:00
John Warwick 93b3c6fc3d Fixed typos in GenServer.Behaviour docs 2013-06-29 22:10:48 -04:00
Jason Larsen 17eb2c77c8 make String.duplicate return empty string for n of 0
When passed 0 for n, return an empty string from String.duplicate.
:binary.copy already supports this, so no changes needed outside
adjusting the guard clause.
2013-06-29 19:51:42 -06:00
Yurii Rashkovskii 1b043c36bb defrecordp/3 should nest aliases under the parent module 2013-06-30 00:56:25 +02:00
Yurii Rashkovskii cda2986a63 Implement defrecordp/3 that enables specifying a custom record tag 2013-06-30 00:35:24 +02:00
Yurii Rashkovskii 9bc67bbfdf Fix defrecordp documentation with regards to the type generated 2013-06-30 00:04:16 +02:00
Yurii Rashkovskii 83c964ae46 Fix defrecordp definition of the <record>_t type
It should use the correct tag in the tuple to be correct.
2013-06-29 23:58:16 +02:00
Joseph Wilk 1b2d8bb915 Contraction trie after filtering if needed. 2013-06-29 23:21:03 +02:00
Joseph Wilk 34f0f93e8f Move HashSet examples over to Set. 2013-06-29 23:21:02 +02:00
Joseph Wilk e6cc35fbbd Doco Set functions. 2013-06-29 23:21:02 +02:00
josephwilk 6e9b8fb454 Adding Set behaviour
* Pushing some HashSet tests into the Set tests.
2013-06-29 23:21:02 +02:00
josephwilk 83173698da Remove unused var. 2013-06-29 23:21:02 +02:00
José Valim 72b7b8ebe2 Increase node size 2013-06-29 23:21:02 +02:00
José Valim 358e49378c Remove unecessary overhead on put/delete 2013-06-29 23:21:02 +02:00
José Valim e8bd90a1c8 Speed up HashSet.member? and HashSet.intersection 2013-06-29 23:21:02 +02:00
josephwilk 096711b517 Adding doco. 2013-06-29 23:21:02 +02:00
josephwilk d599e76366 Improve performance by using set_filter for difference with tries.
Beautiful.
2013-06-29 23:21:02 +02:00
josephwilk 1c16ebe2ff consistency with other bucket_... functions 2013-06-29 23:21:02 +02:00
josephwilk eb813f71d6 Support set_filter with tries.
Set intersection for tries now uses set_filter.
2013-06-29 23:21:01 +02:00
josephwilk ce683fc10e Refactor: Add filter.
Use filter for both intersection and difference.
2013-06-29 23:21:01 +02:00
Joseph Wilk 7dd8fa4c8d quieten warnings. 2013-06-29 23:21:01 +02:00
Joseph Wilk fd7e038927 Set renamed to HashSet.
Separate Set from the implementation strategy.
2013-06-29 23:21:01 +02:00
Joseph Wilk f3a0a52c10 Add examples for disjoint? and subset? 2013-06-29 23:21:01 +02:00
Joseph Wilk b7018f77f3 Use doctests. 2013-06-29 23:21:01 +02:00
Joseph Wilk 01230d68c7 Adding disjoint? for sets 2013-06-29 23:21:01 +02:00
Joseph Wilk e1bca3c65f Adding useful Enumerable functions. 2013-06-29 23:21:01 +02:00
Joseph Wilk f726b6bbdf Adding test subset? 2013-06-29 23:21:01 +02:00
Joseph Wilk 00e9098d19 Adding set equal? 2013-06-29 23:21:00 +02:00
Joseph Wilk 7b7c691196 Don't use new on set_difference, just create the ordered directly. 2013-06-29 23:21:00 +02:00
Joseph Wilk 94d714bb2f new was wasteful, just create the new ordered bucket. 2013-06-29 23:21:00 +02:00
Joseph Wilk 1be0a5329b formatting tweaks 2013-06-29 23:21:00 +02:00
Joseph Wilk 73fdf7442b Corrected definition of difference.
Set 1 minus Set 2 members.
2013-06-29 23:21:00 +02:00
Joseph Wilk 5598eb68d0 difference and intersection support for nodes.
Though not very efficient for nodes.
2013-06-29 23:21:00 +02:00
Joseph Wilk 99a03ffcf9 Refactor: removing unneeded comparisons 2013-06-29 23:21:00 +02:00
josephwilk 07e8a7318c documentation for functions. 2013-06-29 23:21:00 +02:00
josephwilk b7dac7e24a Correcting a bad merge 2013-06-29 23:21:00 +02:00
josephwilk 9b01bae463 nodes also don't contain duplicates 2013-06-29 23:20:59 +02:00
josephwilk ca4997e7f4 delete works with nodes 2013-06-29 23:20:59 +02:00
josephwilk 4c1ee4e261 union works with nodes 2013-06-29 23:20:59 +02:00
josephwilk cacb7d5997 member? for sets works with nodes 2013-06-29 23:20:59 +02:00
josephwilk 4b502985d4 Support expanding depth of nodes.
There is a lot of CRAZY resizing logic that is copied from HashDict. Once everything is working consider extracting.
2013-06-29 23:20:59 +02:00
josephwilk 48ecb7d6f0 put supports growing into a trie.
* to_list works with trie
2013-06-29 23:20:59 +02:00
josephwilk 023866b2b0 Adding set member? for buckets 2013-06-29 23:20:59 +02:00
josephwilk 72c3432042 Adding set difference for buckets 2013-06-29 23:20:59 +02:00
josephwilk d7facd11df Adding set intersection for buckets 2013-06-29 23:20:59 +02:00
josephwilk 76b8b9f3a9 Adding set union 2013-06-29 23:20:59 +02:00
Joseph Wilk 20f26909c8 I think member is a better name than value 2013-06-29 23:20:58 +02:00
Joseph Wilk 276eee072d delete from a set 2013-06-29 23:20:58 +02:00
josephwilk 37621d4a07 Adding Set with simple ops just for bucket (small sets) 2013-06-29 23:20:58 +02:00
Yurii Rashkovskii a768da2980 IEx autocompletion should support identifiers that include numbers
Closes #1337
2013-06-29 20:44:08 +02:00
José Valim 8e5f14dc74 Require Erlang 16B+ 2013-06-29 16:59:01 +02:00
José Valim c832d3a78e Import records that depends on others, closes #1334 2013-06-29 13:11:50 +02:00
José Valim c052558cbf Simplify runcmd on IEx 2013-06-29 13:11:50 +02:00
José Valim a0d225f891 Merge pull request #1335 from mururu/fix-typo
Fix typo in Module.get_attribute doc
2013-06-29 00:36:34 -07:00
Gustavo Brunoro 45862f8472 Merge branch 'master' of https://github.com/elixir-lang/elixir
Conflicts:
	lib/elixir/lib/binary/inspect.ex
	lib/mix/lib/mix/tasks/local.install.ex
2013-06-29 02:54:16 -03:00
Yuki Ito dbe2851cdb Fix typo in Module.get_attribute doc 2013-06-29 11:41:54 +09:00
José Valim 33d6780bb1 Merge pull request #1333 from ToJans/master
Some more fixes for #1280
2013-06-28 13:01:21 -07:00
Tom Janssens 8924fa4aee Fixed iex tests on windows
Elixirc was not properly invoked
2013-06-28 16:30:49 +02:00
Tom Janssens e4812ec185 Skip color tests on windows 2013-06-28 16:17:11 +02:00
Tom Janssens ea32d951ab Skip symlink tests on windows 2013-06-28 15:45:42 +02:00
Tom Janssens 598f0e0141 We no longer execute the doc_tests for elixir file helpers
Due to windows incompatibility, we removed some of the doctests for the
file helpers.
2013-06-28 15:43:51 +02:00
José Valim d484499917 Merge pull request #1332 from alco/1329-win-no-color
Disable coloring by default in IEx on Windows
2013-06-28 01:26:21 -07:00
Alexei Sholik 00a6753497 Disable coloring by default in IEx on Windows. Closes #1329 2013-06-28 11:11:35 +03:00
José Valim edb5016112 Merge pull request #1330 from jwarwick/typos
Doc cleanup for Keyword module
2013-06-27 23:23:59 -07:00
José Valim 672dc8c595 Merge pull request #1331 from jwarwick/iex_typos
Fixed doc typos in IEx module
2013-06-27 23:23:36 -07:00
John Warwick f203560832 Fixed doc typos in IEx module 2013-06-27 23:21:26 -04:00
John Warwick e3d086abb5 Added more doctests in Keyword module 2013-06-27 22:53:53 -04:00
John Warwick 74ea72246e Fixing typos and formatting in Keyword docs 2013-06-27 22:42:53 -04:00
José Valim 447a57750f Simplify binding handling 2013-06-27 17:47:45 +02:00
José Valim e1c3df3bef Remove verbose doctest 2013-06-27 14:33:19 +02:00
José Valim 4c5cb1bc7a Set a PROMPT when invoking rebar
This is a common failure when installing Elixir when
users have Unicode Characters in their PROMPT environment
variable. We have attempted to fix this issue in rebar
but it continues showing up, so we will temporarily set
the prompt ourselves
2013-06-27 09:27:38 +02:00
José Valim 81d36cd921 Merge pull request #1328 from jwarwick/typos
Fixed doc typos in Enum
2013-06-26 23:32:43 -07:00
John Warwick 88e4b5833f Fixed return type in doc of Enum.each 2013-06-26 23:55:04 -04:00
John Warwick d5323e3311 Added examples for Enum.count/2 and Enum.zip/2 2013-06-26 23:45:36 -04:00
John Warwick 1e73ea0527 Fixed doc example for Enum.each 2013-06-26 23:30:02 -04:00
John Warwick dc3ca821fb Fixed typo in Enum.min docs 2013-06-26 23:27:40 -04:00
John Warwick c9fe36c28b Fixed bad Example formatting in Enum 2013-06-26 23:23:58 -04:00
José Valim 7ba5733496 No need to wrap lists in blocks 2013-06-26 21:53:33 +02:00
José Valim e035dda2db Inline list funcs in HashDict too 2013-06-26 21:53:33 +02:00
Eric Meadows-Jonsson 8e8ebe73b9 Refer to the moduledoc from Record.defrecord 2013-06-25 15:10:27 +02:00
José Valim 30e56696c9 Merge pull request #1326 from jwarwick/mix_typo
Fixed typo in mix task local.uninstall doc
2013-06-25 05:47:18 -07:00
John Warwick c817e6a078 Fixed typo in mix task local.uninstall doc 2013-06-25 07:42:41 -04:00
José Valim c8a04ac0ad Update CHANGELOG 2013-06-25 11:57:42 +02:00
José Valim e66fe507bb Remove matchspec duplication 2013-06-25 11:17:24 +02:00
José Valim 6ea09bb2ef Merge pull request #1316 from meh/mix-system-version
Mix system version
2013-06-25 02:09:36 -07:00
José Valim 7a1065809a Erlang says that the first directory of an archive should be the same as the archive name 2013-06-25 10:43:54 +02:00
meh 8f194b7d19 Remove doctests from Mix.Version 2013-06-25 10:02:28 +02:00
meh 6a4fa93b00 Fix typo in mix project tests 2013-06-25 09:57:06 +02:00
meh cad30a73fe Fix Mix.Version being inconsistent with pre and build metadata 2013-06-25 09:57:06 +02:00
meh 0ba2162113 Validate Elixir version in projects, closes #1310 2013-06-25 09:57:06 +02:00
José Valim 42841a021e Merge pull request #1320 from jeremyjh/1284-version-archives
Add version number to mix archive files by default per #1284
2013-06-25 00:17:02 -07:00
José Valim 690687353f Don't end it with ? if it doesnt return a boolean 2013-06-25 09:11:42 +02:00
José Valim 25862cb7a3 Ensure variables from when in match? do not leak too 2013-06-25 08:58:19 +02:00
José Valim 227e7690a3 Merge pull request #1311 from vanstee/contain-match-scope
Don't leak matched variables when calling `match?`
2013-06-24 23:31:09 -07:00
vanstee a6f256fbe8 Remove variables defined in match? from scope
At @josevalim's suggestion, we loop over the ast given to the macro and
disable scope of any new variables defined, which effectively stops
variables from leaking into the outside scope.

Fixes #1304
2013-06-24 22:38:40 -04:00
José Valim 3ebca00872 quote calls them context, so let's keep it consistent 2013-06-24 22:38:41 +02:00
José Valim 7e11cea780 Ensure binding(..., false) also works 2013-06-24 16:24:08 +02:00
José Valim ad7f1c6a3b Merge pull request #1323 from ToJans/master
Skip windows test for CHMOD 777, as this does not work on windows
2013-06-24 04:23:38 -07:00
Tom Janssens a8b7663f37 Skip windows test for CHMOD 777, as this does not work on windows 2013-06-24 13:17:28 +02:00
Tom Janssens 3b6afdd7d4 Cleaned up test_helper some more 2013-06-24 12:47:30 +02:00
José Valim 943fabb548 Merge pull request #1322 from ToJans/master
Tests on windows now intercept stderr from elixir(c) invocations
2013-06-24 03:35:27 -07:00
Tom Janssens 53f5fe71bb Cleaned up the elixir test_helper.exs a bit 2013-06-24 12:33:12 +02:00
Tom Janssens c406ce4aac Grouped some windows functionality in test_helper for elixir 2013-06-24 12:16:38 +02:00
Tom Janssens 4b4a7e8d4a Tests on windows now intercept stderr from elixir(c) invocations
Previously tests on windows did not intercept stderr when they were
invoked through :os.cmd, so we now add a piper redirect from stderr
to stdout when invoking elixir(c) on windows.
2013-06-24 12:04:46 +02:00
José Valim 7d2214678e Remove useless assignment on defrecord 2013-06-24 10:30:08 +02:00
José Valim 81972106d9 Fix unused variable warning 2013-06-24 10:12:58 +02:00
José Valim 6a95d0517f Improve doctest error messages, closes #1318 2013-06-24 08:15:23 +02:00
José Valim 8abed86b1a Improve error message and optimize record macros 2013-06-24 08:14:38 +02:00
José Valim 5a882161ac Allow a record to import itself, closes #1319 2013-06-24 07:51:21 +02:00
José Valim ffb1d9be4f Merge pull request #1321 from guilleiguaran/enum_reject
Add Enum.reject
2013-06-23 22:39:35 -07:00
Guillermo Iguaran d5e2b41c37 Add Enum.reject
Enum.reject returns elements of collection for which `fun` returns false.
2013-06-24 00:26:52 -05:00
Jeremy Huffman c049c1388a mix archive version # & -o flag for #1284 2013-06-23 23:35:22 -04:00
José Valim eb93b9f7a9 Make release instructions clearer 2013-06-24 00:07:44 +02:00
José Valim 867d1eaf9b Bump to v0.9.4-dev 2013-06-23 23:58:19 +02:00
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
Gustavo Brunoro e8fd78b85d Mix not using pretty printing
* Added pretty: false option to all inspect calls.
2013-06-21 18:03:19 -03:00
Gustavo Brunoro 1a578f6574 fixing inspect pretty: false option
* inspect pretty: false calls Wadler.format with :infinity
  as line width.
* Wadler.fits? with w=:infinity always returns true.
* Wadler.format with w=:infinity will only use Flat mode,
  not adding any line breaks.
2013-06-21 17:51:26 -03:00
Johannes Drexler 5df40f7c42 Added functions for changing mode, owner and group to module File 2013-06-21 21:07:05 +02:00
Gustavo Brunoro 001c923f43 moving over to :lists.duplicate
* List.duplicate makes exponentially more :lists.duplicate/3 calls
  than :lists.duplicate/2.
* it also yields better slightly results than String.duplicate.
2013-06-21 15:47:12 -03: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
Gustavo Brunoro 8170ff7b2c adjusting failing tests relying on inspection
* some test cases rely on result from the inspection functions.
  Those were adjusted based on the current result of the inspect
  function, but a more clever method of comparing the result may
  be needed if changes on the tests recur.
2013-06-18 02:03:33 -03:00
Gustavo Brunoro f442fc0d77 stripped spaces from record pretty printing
* brackets are not followed/preceded by spaces when inspecting.
2013-06-18 01:12:10 -03: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
Gustavo Brunoro c608f8279a adjusting nesting tests and behaviour on wadler.ex
* surround/3 function aware that nesting only adds spaces after a
  linebreak
* nesting tests should force Break format mode, as Lindig pretty
  printer defaults to Flat printing for small strings.
2013-06-16 21:17:52 -03:00
Gustavo Brunoro d99a5c5e9e adapted wadler.ex unit tests to current interface 2013-06-16 20:46:10 -03:00
Gustavo Brunoro 5999caed62 using iolist_to_binary over Enum.join on wadler.ex
* iolist_to_binary assumed to have better performance.
2013-06-16 16:55:42 -03:00
Gustavo Brunoro 163485b5ba Merge remote-tracking branch 'upstream/master' into wadler-lindig
Conflicts:
	lib/elixir/lib/binary/inspect.ex
	lib/elixir/lib/kernel.ex
2013-06-16 16:20:15 -03: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
Gustavo Brunoro f7c8ed0dd6 Optimizing string concatenation calls on wadler.ex
* Modified render method to render sdoc records to a string list
  followed by a Enum.join call.
2013-06-15 19:51:22 -03: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
Gustavo Brunoro 54097e11d7 adapted IO.inspect calls to Lindig interface
* group1 and line functions were changed
* relying on `surround` helper
* using a default identation of 2, this should be an option?
2013-06-14 18:57:50 -03:00
Gustavo Brunoro a41528d297 using Lindig's strict pretty printer
* Implemented Wadler document algebra with explicit groups, meaning
  that a break should be rendered to a linebreak or a space as
  specified by a mode: `Flat` or `Break`.
* This algorithm reduces the search space for document layouts, as
  no `best` function is used to search lazily over layouts with a
  flat or newline breaks.
2013-06-14 15:56:44 -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
meh 057691722e Implement Mix.Version, closes #1115 2013-06-09 15:36:24 +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
Jonn Mostovoy 0060190580 Binary.Inspect now uses pretty printing library 2013-05-11 11:29:03 +03:00
Jonn Mostovoy 1da4844103 Put pretty printing library into core 2013-05-10 07:42:10 +03:00
Jonn Mostovoy 7f2862762a Merged (Fri May 10 06:22:13 EEST 2013) 2013-05-10 06:22:05 +03:00
Jonn Mostovoy 3e2af17a83 Tests for wadler; Guards for primitive types. 2013-05-10 06:21:42 +03:00
Jonn Mostovoy 1f7a8065fa Add pretty printing library to Elixir stdlib 2013-05-07 05:55:02 +03:00
262 changed files with 10627 additions and 5456 deletions
+1
View File
@@ -0,0 +1 @@
lib/elixir/test/elixir/fixtures/*.txt text eol=lf
+83 -1
View File
@@ -1,3 +1,85 @@
# v0.10.0 (2013-07-15)
* enhancements
* [ExUnit] Support `trace: true` option which gives detailed reporting on test runs
* [HashDict] Optimize `HashDict` to store pairs in a cons cell reducing storage per key by half
* [Kernel] Add pretty printing support for inspect
* [Kernel] Add document algebra library used as the foundation for pretty printing
* [Kernel] Add `defrecordp/3` that enables specifying the first element of the tuple
* [Kernel] Add the `Set` API and a hash based implementation via `HashSet`
* [Kernel] Add `Stream` as composable, lazy-enumerables
* [Mix] `mix archive` now includes the version of the generated archive
* [Mix] Mix now requires explicit dependency overriding to be given with `override: true`
* [Mix] Projects can now define an `:elixir` key to outline supported Elixir versions
* [Typespec] Improve error messages to contain file, line and the typespec itself
* bug fix
* [CLI] Elixir can now run on Unix directories with `:` in its path
* [Kernel] `match?/2` does not leak variables to outer scope
* [Kernel] Keep `head|tail` format when splicing at the tail
* [Kernel] Ensure variables defined in the module body are not passed to callbacks
* [Mix] On dependencies conflict, show from where each source is coming from
* [Mix] Empty projects no longer leave empty ebin files on `mix compile`
* [Module] Calling `Module.register_attribute/3` no longer automatically changes it to persisted or accumulated
* deprecations
* [Enum] Receiving the index of iteration in `Enum.map/2` and `Enum.each/2` is deprecated in favor of `Stream.with_index/1`
* [File] `File.iterator/1` and `File.biniterator/1` are deprecated in favor of `IO.stream/1` and `IO.binstream/1`
* [File] `File.iterator!/2` and `File.biniterator!/2` are deprecated in favor of `File.stream!/2` and `File.binstream!/2`
* [Kernel] Deprecate recently added `quote binding: ...` in favor of the clearer `quote bind_quoted: ...`
* [Kernel] Deprecate `Kernel.float/1` in favor of a explicit conversion
* [Mix] Deprecate `mix run EXPR` in favor of `mix run -e EXPR`
* [Record] `Record.__index__/2` deprecated in favor of `Record.__record__(:index, key)`
* backwards incompatible changes
* [Kernel] The `Binary.Inspect` protocol has been renamed to `Inspect`
* [Kernel] Tighten up the grammar rules regarding parentheses omission, previously the examples below would compile but now they raise an error message:
do_something 1, is_list [], 3
[1, is_atom :foo, 3]
* [Module] Calling `Module.register_attribute/3` no longer automatically changes it to persisted or accumulated
* [Record] First element of a record via `defrecordp` is now the `defrecordp` name and no longer the current atom
* [URI] Remove custom URI parsers in favor of `URI.default_port/2`
# 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 versioning 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 `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
@@ -11,7 +93,7 @@
* 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
* [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
+15 -5
View File
@@ -95,8 +95,8 @@ Good pull requests - patches, improvements, new features - are a fantastic
help. They should remain focused in scope and avoid containing unrelated
commits.
**IMPORTANT**: By submitting a patch, you agree that your work will be made
public forever and will be licensed under the license used by the project.
**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**
@@ -144,16 +144,26 @@ in order to craft an excellent pull request:
[interactive rebase](https://help.github.com/articles/interactive-rebase)
feature to tidy up your commits before making them public.
5. Push your topic branch up to your fork:
5. Make sure all the tests are still passing.
```bash
make test
```
This command will compile the code in your branch and use that
version of Elixir to run the tests. This is needed to ensure your changes can
pass all the tests.
6. 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/)
7. [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
8. 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
+45 -33
View File
@@ -1,9 +1,16 @@
REBAR := $(shell echo `pwd`/rebar)
REBAR := "$(shell echo `pwd`/rebar)"
ELIXIRC := bin/elixirc --ignore-module-conflict $(ELIXIRC_OPTS)
ERLC := erlc -I lib/elixir/include
ERL := erl -I lib/elixir/include -noshell -pa lib/elixir/ebin
VERSION := $(strip $(shell cat VERSION))
INSTALL_PATH := /usr/local
Q := @
PREFIX := /usr/local
LIBDIR := lib
INSTALL = install
INSTALL_DIR = $(INSTALL) -m755 -d
INSTALL_DATA = $(INSTALL) -m644
INSTALL_PROGRAM = $(INSTALL) -m755
.PHONY: install compile erlang elixir dialyze test clean docs release_docs release_zip release_erl
.NOTPARALLEL: compile
@@ -14,15 +21,15 @@ define APP_TEMPLATE
$(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')"
$(Q) 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)
@ echo "==> $(1) (compile)"
@ $$(ELIXIRC) "lib/$(1)/lib/**/*.ex" -o lib/$(1)/ebin
$(Q) $$(ELIXIRC) "lib/$(1)/lib/**/*.ex" -o lib/$(1)/ebin
test_$(1): $(1)
@ echo "==> $(1) (exunit)"
@ cd lib/$(1) && ../../bin/elixir -r "test/test_helper.exs" -pr "test/**/*_test.exs";
$(Q) cd lib/$(1) && ../../bin/elixir -r "test/test_helper.exs" -pr "test/**/*_test.exs";
endef
#==> Compilation tasks
@@ -35,11 +42,11 @@ default: compile
compile: lib/elixir/src/elixir.app.src erlang elixir
lib/elixir/src/elixir.app.src: src/elixir.app.src
@ rm -rf lib/elixir/src/elixir.app.src
@ cp src/elixir.app.src lib/elixir/src/elixir.app.src
$(Q) rm -rf lib/elixir/src/elixir.app.src
$(Q) cp src/elixir.app.src lib/elixir/src/elixir.app.src
erlang:
@ cd lib/elixir && $(REBAR) compile
$(Q) cd lib/elixir && $(REBAR) compile
# Since Mix depends on EEx and EEx depends on
# Mix, we first compile EEx without the .app
@@ -48,21 +55,21 @@ 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
@ if [ ! -f $(KERNEL) ]; then \
$(Q) if [ ! -f $(KERNEL) ]; then \
echo "==> bootstrap (compile)"; \
$(ERL) -s elixir_compiler core -s erlang halt; \
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
$(Q) $(ELIXIRC) "lib/elixir/lib/**/*.ex" -o lib/elixir/ebin;
$(Q) $(MAKE) unicode
$(Q) rm -rf lib/elixir/ebin/elixir.app
$(Q) cd lib/elixir && $(REBAR) compile
unicode: $(UNICODE)
$(UNICODE): lib/elixir/priv/unicode.ex lib/elixir/priv/UnicodeData.txt lib/elixir/priv/NamedSequences.txt
@ echo "==> unicode (compile)";
@ echo "This step can take up to a minute to compile in order to embed the Unicode database"
@ $(ELIXIRC) lib/elixir/priv/unicode.ex -o lib/elixir/ebin;
$(Q) $(ELIXIRC) lib/elixir/priv/unicode.ex -o lib/elixir/ebin;
$(eval $(call APP_TEMPLATE,ex_unit,ExUnit))
$(eval $(call APP_TEMPLATE,eex,EEx))
@@ -72,16 +79,18 @@ $(eval $(call APP_TEMPLATE,iex,IEx))
install: compile
@ echo "==> elixir (install)"
for dir in lib/*; do \
install -m755 -d $(INSTALL_PATH)/lib/elixir/$$dir/ebin; \
install -m644 $$dir/ebin/* $(INSTALL_PATH)/lib/elixir/$$dir/ebin; \
$(INSTALL_DIR) "$(DESTDIR)$(PREFIX)/$(LIBDIR)/elixir/$$dir/ebin"; \
$(INSTALL_DATA) $$dir/ebin/* "$(DESTDIR)$(PREFIX)/$(LIBDIR)/elixir/$$dir/ebin"; \
done
$(INSTALL_DIR) "$(DESTDIR)$(PREFIX)/$(LIBDIR)/elixir/bin"
$(INSTALL_PROGRAM) $(filter-out %.bat, $(wildcard bin/*)) "$(DESTDIR)$(PREFIX)/$(LIBDIR)/elixir/bin"
$(INSTALL_DIR) "$(DESTDIR)$(PREFIX)/bin"
for file in "$(DESTDIR)$(PREFIX)"/$(LIBDIR)/elixir/bin/* ; do \
ln -sf "../$(LIBDIR)/elixir/bin/$${file##*/}" "$(DESTDIR)$(PREFIX)/bin/" ; \
done
install -m755 -d $(INSTALL_PATH)/lib/elixir/bin
install -m755 $(filter-out %.bat, $(wildcard bin/*)) $(INSTALL_PATH)/lib/elixir/bin
install -m755 -d $(INSTALL_PATH)/bin
ln -sf $(INSTALL_PATH)/lib/elixir/bin/* $(INSTALL_PATH)/bin
clean:
@ cd lib/elixir && $(REBAR) clean
$(Q) cd lib/elixir && $(REBAR) clean
rm -rf ebin
rm -rf lib/*/ebin
rm -rf lib/*/test/tmp
@@ -92,6 +101,9 @@ clean:
rm -rf lib/elixir/src/*_parser.erl
rm -rf lib/elixir/test/ebin
clean_exbeam:
$(Q) rm -f lib/*/ebin/Elixir.*.beam
#==> Release tasks
SOURCE_REF = $(shell head="$$(git rev-parse HEAD)" tag="$$(git tag --points-at $$head | tail -1)" ; echo "$${tag:-$$head}\c")
@@ -117,8 +129,8 @@ release_docs: docs
mv docs ../elixir-lang.github.com/docs/master
release_erl: compile
@ rm -rf rel/elixir
@ cd rel && ../rebar generate
$(Q) rm -rf rel/elixir
$(Q) cd rel && ../rebar generate
#==> Tests tasks
@@ -126,29 +138,29 @@ test: test_erlang test_elixir
test_erlang: compile
@ echo "==> elixir (eunit)"
@ mkdir -p lib/elixir/test/ebin
@ $(ERLC) -pa lib/elixir/ebin -o lib/elixir/test/ebin lib/elixir/test/erlang/*.erl
@ $(ERL) -pa lib/elixir/test/ebin -s test_helper test -s erlang halt;
$(Q) mkdir -p lib/elixir/test/ebin
$(Q) $(ERLC) -pa lib/elixir/ebin -o lib/elixir/test/ebin lib/elixir/test/erlang/*.erl
$(Q) $(ERL) -pa lib/elixir/test/ebin -s test_helper test -s erlang halt;
@ echo
test_elixir: test_kernel test_ex_unit test_doc_test test_mix test_eex test_iex
test_doc_test: compile
@ echo "==> doctest (exunit)"
@ cd lib/elixir && ../../bin/elixir -r "test/doc_test.exs";
$(Q) cd lib/elixir && ../../bin/elixir -r "test/doc_test.exs";
test_kernel: compile
@ echo "==> kernel (exunit)"
@ cd lib/elixir && ../../bin/elixir -r "test/elixir/test_helper.exs" -pr "test/elixir/**/*_test.exs";
$(Q) cd lib/elixir && ../../bin/elixir -r "test/elixir/test_helper.exs" -pr "test/elixir/**/*_test.exs";
.dialyzer.base_plt:
@ echo "==> Adding Erlang/OTP basic applications to a new base PLT"
@ dialyzer --output_plt .dialyzer.base_plt --build_plt --apps erts kernel stdlib compiler syntax_tools inets crypto ssl
$(Q) dialyzer --output_plt .dialyzer.base_plt --build_plt --apps erts kernel stdlib compiler syntax_tools inets crypto ssl
dialyze: .dialyzer.base_plt
@ rm -f .dialyzer_plt
@ cp .dialyzer.base_plt .dialyzer_plt
$(Q) rm -f .dialyzer_plt
$(Q) cp .dialyzer.base_plt .dialyzer_plt
@ echo "==> Adding Elixir to PLT..."
@ dialyzer --plt .dialyzer_plt --add_to_plt -r lib/elixir/ebin lib/ex_unit/ebin lib/mix/ebin lib/iex/ebin lib/eex/ebin
$(Q) dialyzer --plt .dialyzer_plt --add_to_plt -r lib/elixir/ebin lib/ex_unit/ebin lib/mix/ebin lib/iex/ebin lib/eex/ebin
@ echo "==> Dialyzing Elixir..."
@ dialyzer --plt .dialyzer_plt -r lib/elixir/ebin lib/ex_unit/ebin lib/mix/ebin lib/iex/ebin lib/eex/ebin
$(Q) dialyzer --plt .dialyzer_plt -r lib/elixir/ebin lib/ex_unit/ebin lib/mix/ebin lib/iex/ebin lib/eex/ebin
+2 -2
View File
@@ -26,7 +26,7 @@ Building the documentation requires [ex_doc](https://github.com/elixir-lang/ex_d
# After cloning and compiling Elixir
$ git clone git://github.com/elixir-lang/ex_doc.git
$ cd ../ex_doc && ../elixir/bin/mix compile
$ cd ex_doc && ../elixir/bin/mix compile
$ cd ../elixir && make docs
## Contributing
@@ -52,4 +52,4 @@ We appreciate any contribution to Elixir, so check out our [CONTRIBUTING.md](CON
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.
+6 -4
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
@@ -12,13 +12,15 @@ This document simply outlines the release process:
5) Create tag from master branch
6) Release new docs with `make release_docs`, update elixir-lang.org
6) Release new docs with `make release_docs`, move docs to `docs/stable`
7) Release new zip with `make release_zip`, push new zip to Elixir's elixir-lang.org/packages.html
7) Release new zip with `make release_zip`, push new zip to Github Releases
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
10) `make release_docs` once again and push `elixir-lang.github.com`
## Places where version is mentioned
+1 -1
View File
@@ -1 +1 @@
0.9.2
0.10.0
+4 -4
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 (*)
@@ -43,7 +43,7 @@ while [ $I -le $# ]; do
case "$PEEK" in
-v|--compile|--no-halt|+iex|+compile)
;;
-e|-r|-pr|-pa|-pz|--remsh|-S)
-e|-r|-pr|-pa|-pz|--remsh|--app)
S=2
;;
--detached|--hidden)
@@ -79,7 +79,7 @@ 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 -pa "$SCRIPT_PATH"/../lib/*/ebin -noshell $ELIXIR_ERL_OPTS $ERL -boot start -extra "$@"
else
exec erl -env ERL_LIBS $ERL_LIBS:"$SCRIPT_PATH/../lib" -noshell $ELIXIR_ERL_OPTS $ERL -extra "$@"
exec erl -pa "$SCRIPT_PATH"/../lib/*/ebin -noshell $ELIXIR_ERL_OPTS $ERL -extra "$@"
fi
+106 -12
View File
@@ -1,25 +1,119 @@
@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 \--app`) 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 %*
+1
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
+1
View File
@@ -13,6 +13,7 @@ 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
+3
View File
@@ -13,6 +13,9 @@ 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
+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
+1 -1
View File
@@ -73,7 +73,7 @@ defmodule EEx.AssignsEngine do
defmacro __using__(_) do
quote unquote: false do
defp transform({ :@, line, [{ name, _, atom }] }) when is_atom(name) and is_atom(atom) do
quote(do: Keyword.get var!(assigns), unquote(name))
quote do: Keyword.get(var!(assigns), unquote(name))
end
defp transform(arg) do
+3 -3
View File
@@ -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
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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
+2 -2
View File
@@ -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 %>
"""
+1 -1
View File
@@ -1 +1 @@
ExUnit.start []
ExUnit.start [trace: "--trace" in System.argv]
+5 -4
View File
@@ -13,11 +13,12 @@
noname=false, %% when true, don't add new names (used by try)
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
list_vars=nil, %% a list of vars passed down to Macro.Env
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
@@ -41,7 +42,7 @@
imports_hygiene=true,
unquote=true,
unquoted=false,
mark=true
escape=false
}).
%% Introspection
@@ -61,7 +62,7 @@
-define(is_downcase(S), S >= $a andalso S =< $z).
%% Atoms
-define(is_new_atom(S), ?is_quote(S) orelse ?is_upcase(S) orelse ?is_downcase(S) orelse (S == $_)).
-define(is_atom_start(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 == $_)).
+5 -5
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)
@@ -88,13 +88,13 @@ defmodule Behaviour do
def store_docs(module, line, name, arity) do
doc = Module.get_attribute module, :doc
Module.delete_attribute module, :doc
Module.put_attribute module, :__behaviour_docs, { { name, arity }, line, doc }
Module.put_attribute module, :__behaviour_docs__, { { name, arity }, line, doc }
end
@doc false
defmacro __using__(_) do
quote do
Module.register_attribute(__MODULE__, :__behaviour_docs, accumulate: true)
Module.register_attribute(__MODULE__, :__behaviour_docs__, accumulate: true)
@before_compile unquote(__MODULE__)
import unquote(__MODULE__)
end
@@ -109,8 +109,8 @@ defmodule Behaviour do
end
def __behaviour__(:docs) do
@__behaviour_docs
@__behaviour_docs__
end
end
end
end
end
+1 -9
View File
@@ -74,19 +74,11 @@ 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
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
float_to_binary(thing, compact: true, decimals: @digits)
list_to_binary(:io_lib_format.fwrite_g(thing))
end
end
-462
View File
@@ -1,462 +0,0 @@
import Kernel, except: [inspect: 1]
defprotocol Binary.Inspect do
@moduledoc """
The `Binary.Inspect` protocol is responsible for
converting any structure to a binary for textual
representation. All basic data structures
(tuple, list, function, pid, etc) implement the
inspect protocol. Other structures are advised to
implement the protocol in order to provide pretty
printing.
"""
def inspect(thing, opts)
end
defmodule Binary.Inspect.Utils do
@moduledoc false
## container_join
def container_join(tuple, first, last, opts) when is_tuple(tuple) do
container_join(tuple_to_list(tuple), first, last, opts)
end
def container_join(list, first, last, opts) do
first <> do_container_join(list, opts, Keyword.get(opts, :limit, :infinity)) <> last
end
defp do_container_join(_, _opts, 0) do
"..."
end
defp do_container_join([h], opts, _counter) do
Kernel.inspect(h, opts)
end
defp do_container_join([h|t], opts, counter) when is_list(t) do
Kernel.inspect(h, opts) <> "," <> do_container_join(t, opts, decrement(counter))
end
defp do_container_join([h|t], opts, _counter) do
Kernel.inspect(h, opts) <> "|" <> Kernel.inspect(t, opts)
end
defp do_container_join([], _opts, _counter) do
""
end
defp decrement(:infinity), do: :infinity
defp decrement(counter), do: counter - 1
## escape
def escape(other, char) do
b = do_escape(other, char, <<>>)
<< char, b :: binary, char >>
end
@compile {:inline, do_escape: 3}
defp do_escape(<<>>, _char, binary), do: binary
defp do_escape(<< char, t :: binary >>, char, binary) do
do_escape(t, char, << binary :: binary, ?\\, char >>)
end
defp do_escape(<<?#, ?{, t :: binary>>, char, binary) do
do_escape(t, char, << binary :: binary, ?\\, ?#, ?{ >>)
end
defp do_escape(<<?\a, t :: binary>>, char, binary) do
do_escape(t, char, << binary :: binary, ?\\, ?a >>)
end
defp do_escape(<<?\b, t :: binary>>, char, binary) do
do_escape(t, char, << binary :: binary, ?\\, ?b >>)
end
defp do_escape(<<?\d, t :: binary>>, char, binary) do
do_escape(t, char, << binary :: binary, ?\\, ?d >>)
end
defp do_escape(<<?\e, t :: binary>>, char, binary) do
do_escape(t, char, << binary :: binary, ?\\, ?e >>)
end
defp do_escape(<<?\f, t :: binary>>, char, binary) do
do_escape(t, char, << binary :: binary, ?\\, ?f >>)
end
defp do_escape(<<?\n, t :: binary>>, char, binary) do
do_escape(t, char, << binary :: binary, ?\\, ?n >>)
end
defp do_escape(<<?\r, t :: binary>>, char, binary) do
do_escape(t, char, << binary :: binary, ?\\, ?r >>)
end
defp do_escape(<<?\\, t :: binary>>, char, binary) do
do_escape(t, char, << binary :: binary, ?\\, ?\\ >>)
end
defp do_escape(<<?\t, t :: binary>>, char, binary) do
do_escape(t, char, << binary :: binary, ?\\, ?t >>)
end
defp do_escape(<<?\v, t :: binary>>, char, binary) do
do_escape(t, char, << binary :: binary, ?\\, ?v >>)
end
defp do_escape(<<h, t :: binary>>, char, binary) do
do_escape(t, char, << binary :: binary, h >>)
end
end
defimpl Binary.Inspect, for: Atom do
require Macro
import Binary.Inspect.Utils
@moduledoc """
Represents the atom as an Elixir term. The atoms false, true
and nil are simply quoted. Modules are properly represented
as modules using the dot notation.
Notice that in Elixir, all operators can be represented using
literal atoms (`:+`, `:-`, etc).
## Examples
iex> inspect(:foo)
":foo"
iex> inspect(nil)
"nil"
iex> inspect(Foo.Bar)
"Foo.Bar"
"""
def inspect(false, _), do: "false"
def inspect(true, _), do: "true"
def inspect(nil, _), do: "nil"
def inspect(:"", _), do: ":\"\""
def inspect(Elixir, _), do: "Elixir"
def inspect(atom, _) do
binary = atom_to_binary(atom)
cond do
valid_atom_identifier?(binary) ->
":" <> binary
valid_ref_identifier?(binary) ->
Module.to_binary(atom)
atom in Macro.binary_ops or atom in Macro.unary_ops ->
":" <> binary
true ->
":" <> escape(binary, ?")
end
end
# Detect if atom is an atom alias (Elixir.Foo.Bar.Baz)
defp valid_ref_identifier?("Elixir" <> rest) do
valid_ref_piece?(rest)
end
defp valid_ref_identifier?(_), do: false
defp valid_ref_piece?(<<?., h, t :: binary>>) when h in ?A..?Z do
valid_ref_piece? valid_identifier?(t)
end
defp valid_ref_piece?(<<>>), do: true
defp valid_ref_piece?(_), do: false
# Detect if atom
defp valid_atom_identifier?(<<h, t :: binary>>) when h in ?a..?z or h in ?A..?Z or h == ?_ do
case valid_identifier?(t) do
<<>> -> true
<<??>> -> true
<<?!>> -> true
_ -> false
end
end
defp valid_atom_identifier?(_), do: false
defp valid_identifier?(<<h, t :: binary>>)
when h in ?a..?z
when h in ?A..?Z
when h in ?0..?9
when h == ?_ do
valid_identifier? t
end
defp valid_identifier?(other), do: other
end
defimpl Binary.Inspect, for: BitString do
import Binary.Inspect.Utils
@moduledoc %B"""
Represents the string as itself escaping
all necessary characters.
## Examples
iex> inspect("bar")
"\"bar\""
iex> inspect("f\"oo")
"\"f\\\"oo\""
"""
def inspect(thing, opts) when is_binary(thing) do
if String.printable?(thing) do
escape(thing, ?")
else
as_bitstring(thing, opts)
end
end
def inspect(thing, opts) do
as_bitstring(thing, opts)
end
## Bitstrings
defp as_bitstring(bitstring, opts) do
"<<" <> each_bit(bitstring, Keyword.get(opts, :limit, :infinity)) <> ">>"
end
defp each_bit(_, 0) do
"..."
end
defp each_bit(<<h, t :: bitstring>>, counter) when t != <<>> do
integer_to_binary(h) <> "," <> each_bit(t, decrement(counter))
end
defp each_bit(<<h :: size(8)>>, _counter) do
integer_to_binary(h)
end
defp each_bit(<<>>, _counter) do
<<>>
end
defp each_bit(bitstring, _counter) do
size = bit_size(bitstring)
<<h :: size(size)>> = bitstring
integer_to_binary(h) <> "::size(" <> integer_to_binary(size) <> ")"
end
defp decrement(:infinity), do: :infinity
defp decrement(counter), do: counter - 1
end
defimpl Binary.Inspect, for: List do
import Binary.Inspect.Utils
@moduledoc %B"""
Represents a list checking if it can be printed or not.
If so, a single-quoted representation is returned,
otherwise the brackets syntax is used.
Inspecting a list is conservative as it does not try
to guess how the list is encoded. That said, `'josé'`
will likely be inspected as `[106,111,115,195,169]`
because we can't know if it is encoded in utf-8
or iso-5569-1, which is common in Erlang libraries.
## Examples
iex> inspect('bar')
"'bar'"
iex> inspect([0|'bar'])
"[0,98,97,114]"
iex> inspect([:foo,:bar])
"[:foo,:bar]"
"""
def inspect([], _), do: "[]"
def inspect(thing, opts) do
cond do
:io_lib.printable_list(thing) ->
escape(:unicode.characters_to_binary(thing), ?')
keyword?(thing) ->
"[" <> join_keywords(thing, opts) <> "]"
true ->
container_join(thing, "[", "]", opts)
end
end
defp join_keywords(thing, opts) do
Enum.join(lc {key, value} inlist thing do
key_to_binary(key, opts) <> ": " <> Kernel.inspect(value, opts)
end, ", ")
end
defp key_to_binary(key, opts) do
case Binary.Inspect.Atom.inspect(key, opts) do
":" <> right -> right
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
import Binary.Inspect.Utils
@moduledoc """
Inspect tuples. If the tuple represents a record,
it shows it nicely formatted using the access syntax.
## Examples
iex> inspect({1,2,3})
"{1,2,3}"
iex> inspect(ArgumentError.new)
"ArgumentError[message: \\\"argument error\\\"]"
"""
def inspect({}, _), do: "{}"
def inspect(tuple, opts) do
unless opts[:raw] do
record_inspect(tuple, opts)
end || container_join(tuple, "{", "}", opts)
end
## Helpers
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 Enum.first(tail) == :__exception__ do
record_join(name, tl(fields), tl(tail), opts)
else
record_join(name, fields, tail, opts)
end
end
end
defp record_fields(name) do
try do
name.__record__(:fields)
rescue
_ -> nil
end
end
defp record_join(name, fields, tail, opts) do
fields = lc { field, _ } inlist fields, do: field
Binary.Inspect.Atom.inspect(name, opts) <> "[" <>
record_join(fields, tail, opts) <> "]"
end
defp record_join([f], [v], opts) do
atom_to_binary(f, :utf8) <> ": " <> Kernel.inspect(v, opts)
end
defp record_join([fh|ft], [vh|vt], opts) do
atom_to_binary(fh, :utf8) <> ": " <>
Kernel.inspect(vh, opts) <> ", " <>
record_join(ft, vt, opts)
end
defp record_join([], [], _opts) do
""
end
end
defimpl Binary.Inspect, for: Number do
@moduledoc """
Represents the number as a binary.
## Examples
iex> inspect(1)
"1"
"""
@digits 20
@limit :math.pow(10, @digits)
def inspect(thing, _) when is_integer(thing) do
integer_to_binary(thing)
end
def inspect(thing, _) when thing > @limit do
float_to_binary(thing, scientific: @digits)
end
def inspect(thing, _) do
float_to_binary(thing, compact: true, decimals: @digits)
end
end
defimpl Binary.Inspect, for: Regex do
@moduledoc %B"""
Represents the Regex using the `%r""` syntax.
## Examples
iex> inspect(%r/foo/m)
"%r\"foo\"m"
"""
def inspect(regex, _opts) when size(regex) == 5 do
"%r" <> Kernel.inspect(Regex.source(regex), []) <> Regex.opts(regex)
end
def inspect(other, opts) do
Kernel.inspect other, Keyword.put(opts, :raw, true)
end
end
defimpl Binary.Inspect, for: Function do
@moduledoc """
Inspect functions, when possible, in a literal form.
"""
def inspect(function, _opts) do
fun_info = :erlang.fun_info(function)
if fun_info[:type] == :external and fun_info[:env] == [] do
"function(#{Kernel.inspect(fun_info[:module])}.#{fun_info[:name]}/#{fun_info[:arity]})"
else
'#Fun' ++ rest = :erlang.fun_to_list(function)
"#Function" <> list_to_binary(rest)
end
end
end
defimpl Binary.Inspect, for: PID do
@moduledoc "Inspect PIDs"
def inspect(pid, _) do
"#PID" <> list_to_binary pid_to_list(pid)
end
end
defimpl Binary.Inspect, for: Port do
@moduledoc "Inspect ports"
def inspect(port, _) do
list_to_binary :erlang.port_to_list(port)
end
end
defimpl Binary.Inspect, for: Reference do
@moduledoc "Inspect references"
def inspect(ref, _) do
'#Ref' ++ rest = :erlang.ref_to_list(ref)
"#Reference" <> list_to_binary(rest)
end
end
+12 -12
View File
@@ -46,83 +46,83 @@ defmodule Bitwise do
Bitwise not.
"""
defmacro bnot(expr) do
quote do: __op__ :bnot, unquote(expr)
quote do: __op__(:bnot, unquote(expr))
end
@doc """
Bitwise not as operator.
"""
defmacro ~~~expr do
quote do: __op__ :bnot, unquote(expr)
quote do: __op__(:bnot, unquote(expr))
end
@doc """
Bitwise and.
"""
defmacro band(left, right) do
quote do: __op__ :band, unquote(left), unquote(right)
quote do: __op__(:band, unquote(left), unquote(right))
end
@doc """
Bitwise and as operator.
"""
defmacro left &&& right do
quote do: __op__ :band, unquote(left), unquote(right)
quote do: __op__(:band, unquote(left), unquote(right))
end
@doc """
Bitwise or.
"""
defmacro bor(left, right) do
quote do: __op__ :bor, unquote(left), unquote(right)
quote do: __op__(:bor, unquote(left), unquote(right))
end
@doc """
Bitwise or as operator.
"""
defmacro left ||| right do
quote do: __op__ :bor, unquote(left), unquote(right)
quote do: __op__(:bor, unquote(left), unquote(right))
end
@doc """
Bitwise xor.
"""
defmacro bxor(left, right) do
quote do: __op__ :bxor, unquote(left), unquote(right)
quote do: __op__(:bxor, unquote(left), unquote(right))
end
@doc """
Bitwise xor as operator.
"""
defmacro left ^^^ right do
quote do: __op__ :bxor, unquote(left), unquote(right)
quote do: __op__(:bxor, unquote(left), unquote(right))
end
@doc """
Arithmetic bitshift left.
"""
defmacro bsl(left, right) do
quote do: __op__ :bsl, unquote(left), unquote(right)
quote do: __op__(:bsl, unquote(left), unquote(right))
end
@doc """
Arithmetic bitshift left as operator.
"""
defmacro left <<< right do
quote do: __op__ :bsl, unquote(left), unquote(right)
quote do: __op__(:bsl, unquote(left), unquote(right))
end
@doc """
Arithmetic bitshift right.
"""
defmacro bsr(left, right) do
quote do: __op__ :bsr, unquote(left), unquote(right)
quote do: __op__(:bsr, unquote(left), unquote(right))
end
@doc """
Arithmetic bitshift right as operator.
"""
defmacro left >>> right do
quote do: __op__ :bsr, unquote(left), unquote(right)
quote do: __op__(:bsr, unquote(left), unquote(right))
end
end
+113 -50
View File
@@ -6,13 +6,11 @@ defmodule Code do
end
@moduledoc """
The Code module is responsible to manage code compilation,
The Code module is responsible for managing code compilation,
code evaluation and code loading.
It complements (Erlang's code module)[1] to add behavior
which is specific to Elixir.
[1]: (www.erlang.org/doc/man/code.html)
It complements [Erlang's code module](http://www.erlang.org/doc/man/code.html)
to add behavior which is specific to Elixir.
"""
@@ -26,8 +24,8 @@ defmodule Code do
@doc """
Removes the given files from the loaded files list.
The modules defined in the file are not removed,
calling this function only removes it from the list,
allowing it to be required again.
calling this function only removes them from the list,
allowing them to be required again.
"""
def unload_files(files) do
:elixir_code_server.cast { :unload_files, files }
@@ -35,7 +33,7 @@ defmodule Code do
@doc """
Appends a path to Erlang VM code path.
The path is expanded with `Path.expand` before added.
The path is expanded with `Path.expand` before being appended.
"""
def append_path(path) do
:code.add_pathz(Path.expand to_char_list(path))
@@ -43,7 +41,7 @@ defmodule Code do
@doc """
Prepends a path to Erlang VM code path.
The path is expanded with `Path.expand` before added.
The path is expanded with `Path.expand` before being prepended.
"""
def prepend_path(path) do
:code.add_patha(Path.expand to_char_list(path))
@@ -51,7 +49,7 @@ defmodule Code do
@doc """
Deletes a path from Erlang VM code path.
The path is expanded with `Path.expand` before deleted.
The path is expanded with `Path.expand` before being deleted.
"""
def delete_path(path) do
:code.del_path(Path.expand to_char_list(path))
@@ -59,27 +57,29 @@ defmodule Code do
@doc """
Evaluates the contents given by string. The second argument is the
binding (which should be a keyword) followed by a keyword list of
binding (which should be a keyword), followed by a keyword list of
environment options. Those options can be:
* `:file` - the file to be considered in the evaluation
* `:line` - the line the script starts
* `:line` - the line on which the script starts
* `:delegate_locals_to` - delegate local calls to the given module,
the default is to not delegate
Besides, the following scope values can be configured:
Additionally, the following scope values can be configured:
* `: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`,
automatically require the `Kernel` module; in the same way setting
`:macros` will no longer auto-import `Kernel` macros like `if`, `case`,
etc.
## Examples
@@ -87,7 +87,7 @@ defmodule Code do
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
For convenience, you can pass `__ENV__` as an argument and
all imports, requires and aliases will be automatically carried
over:
@@ -98,10 +98,15 @@ defmodule Code do
def eval_string(string, binding // [], opts // [])
def eval_string(string, binding, Macro.Env[] = env) do
eval_string(string, binding, env.to_keywords)
do_eval_string(string, binding, env.to_keywords)
end
def eval_string(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 }
@@ -119,7 +124,7 @@ defmodule Code do
...> 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
For convenience, you can pass `__ENV__` as an argument and
all options will be automatically extracted from the environment:
iex> contents = quote(hygiene: [vars: false], do: a + b)
@@ -130,17 +135,65 @@ defmodule Code do
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 its quoted form. Returns `{ :ok, quoted_form }`
if it succeeds, `{ :error, { line, error, token } }` otherwise.
## Options
@@ -153,41 +206,47 @@ defmodule Code do
* `:existing_atoms_only` - When true, raises an error
when non-existing atoms are found by the tokenizer.
## Macro.to_binary/1
## Macro.to_string/1
The opposite of converting a string to its AST is
`Macro.to_binary`, which converts a AST to a binary
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 its 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
@@ -197,7 +256,7 @@ defmodule Code do
Notice that if `load_file` is invoked by different processes
concurrently, the target file will be invoked concurrently
in many times. I.e. if `load_file` is called N times with
many times. I.e. if `load_file` is called N times with
a given file, the given file will be loaded N times. Check
`require_file` if you don't want a file to be loaded concurrently.
"""
@@ -239,25 +298,29 @@ 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
end
@doc """
Sets compilation options. Those options are global
Sets compilation options. These options are global
since they are stored by Elixir's Code Server.
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
@@ -289,7 +352,7 @@ defmodule Code do
loaded, it works as no-op. If the module was not loaded yet,
it tries to load it.
If it succeeds loading the module anyhow, it returns
If it succeeds loading the module, it returns
`{ :module, module }`. If not, returns `{ :error, reason }` with
the error reason.
@@ -297,7 +360,7 @@ defmodule Code do
Erlang has two modes to load code: interactive and embedded.
By default, the Erlang VM runs on interactive mode, where modules
By default, the Erlang VM runs in interactive mode, where modules
are loaded as needed. In embedded mode the opposite happens, as all
modules need to be loaded upfront or explicitly.
@@ -340,7 +403,7 @@ defmodule Code do
loaded yet, it checks if it needs to be compiled first and just
then tries to load it.
If it succeeds loading the module anyhow, it returns
If it succeeds loading the module, it returns
`{ :module, module }`. If not, returns `{ :error, reason }` with
the error reason.
+7 -1
View File
@@ -73,6 +73,8 @@ defmodule Dict do
elem(unquote(dict), 0)
is_list(unquote(dict)) ->
ListDict
true ->
unsupported_dict(unquote(dict))
end
end
end
@@ -387,7 +389,7 @@ defmodule Dict do
end
@doc """
Returns a new dict where the the given `keys` a removed from `dict`.
Returns a new dict where the given `keys` are removed from `dict`.
Any non-member keys are ignored.
## Examples
@@ -480,4 +482,8 @@ defmodule Dict do
def to_list(dict) do
target(dict).to_list(dict)
end
defp unsupported_dict(dict) do
raise ArgumentError, message: "unsupported dict: #{inspect dict}"
end
end
+135 -75
View File
@@ -8,7 +8,7 @@ defprotocol Enumerable do
For example, in the expression
Enum.map([1,2,3], &1 * 2)
Enum.map([1, 2, 3], &1 * 2)
`Enum.map` invokes `Enumerable.reduce` to perform the reducing operation
that builds a mapped list by calling the mapping function `&1 * 2` on every
@@ -58,13 +58,15 @@ defmodule Enum do
flavours. If a given collection implements the mentioned protocol (like
list, for instance), you can do:
Enum.map([1,2,3], fn(x) -> x * 2 end)
Enum.map([1, 2, 3], fn(x) -> x * 2 end)
Depending on the type of the collection, the user-provided function will
accept a certain type of argument. For dicts, the argument is always a
`{ key, value }` tuple.
"""
@compile :inline_list_funcs
@type t :: Enumerable.t
@type element :: any
@type index :: non_neg_integer
@@ -91,7 +93,7 @@ defmodule Enum do
## Examples
iex> Enum.count([1,2,3])
iex> Enum.count([1, 2, 3])
3
"""
@@ -102,6 +104,11 @@ defmodule Enum do
@doc """
Counts for how many items the function returns true.
## Examples
iex> Enum.count([1, 2, 3, 4, 5], fn(x) -> rem(x, 2) == 0 end)
2
"""
@spec count(t, (element -> as_boolean(term))) :: non_neg_integer
def count(collection, fun) do
@@ -117,18 +124,18 @@ defmodule Enum do
## Examples
iex> Enum.all?([2,4,6], fn(x) -> rem(x, 2) == 0 end)
iex> Enum.all?([2, 4, 6], fn(x) -> rem(x, 2) == 0 end)
true
iex> Enum.all?([2,3,4], fn(x) -> rem(x, 2) == 0 end)
iex> Enum.all?([2, 3, 4], fn(x) -> rem(x, 2) == 0 end)
false
If no function is given, it defaults to checking if
all items in the collection evaluate to true.
iex> Enum.all?([1,2,3])
iex> Enum.all?([1, 2, 3])
true
iex> Enum.all?([1,nil,3])
iex> Enum.all?([1, nil, 3])
false
"""
@@ -155,18 +162,18 @@ defmodule Enum do
## Examples
iex> Enum.any?([2,4,6], fn(x) -> rem(x, 2) == 1 end)
iex> Enum.any?([2, 4, 6], fn(x) -> rem(x, 2) == 1 end)
false
iex> Enum.any?([2,3,4], fn(x) -> rem(x, 2) == 1 end)
iex> Enum.any?([2, 3, 4], fn(x) -> rem(x, 2) == 1 end)
true
If no function is given, it defaults to checking if
at least one item in the collection evaluates to true.
iex> Enum.any?([false,false,false])
iex> Enum.any?([false, false, false])
false
iex> Enum.any?([false,true,false])
iex> Enum.any?([false, true, false])
true
"""
@@ -194,16 +201,16 @@ defmodule Enum do
Expects an ordered collection.
## Examples
## Examples
iex> Enum.at([2,4,6], 0)
2
iex> Enum.at([2,4,6], 2)
6
iex> Enum.at([2,4,6], 4)
nil
iex> Enum.at([2,4,6], 4, :none)
:none
iex> Enum.at([2, 4, 6], 0)
2
iex> Enum.at([2, 4, 6], 2)
6
iex> Enum.at([2, 4, 6], 4)
nil
iex> Enum.at([2, 4, 6], 4, :none)
:none
"""
@spec at(t, index) :: element | nil
@@ -221,11 +228,11 @@ defmodule Enum do
## Examples
iex> Enum.drop([1,2,3], 2)
iex> Enum.drop([1, 2, 3], 2)
[3]
iex> Enum.drop([1,2,3], 10)
iex> Enum.drop([1, 2, 3], 10)
[]
iex> Enum.drop([1,2,3], 0)
iex> Enum.drop([1, 2, 3], 0)
[1,2,3]
"""
@@ -253,7 +260,7 @@ defmodule Enum do
## Examples
iex> Enum.drop_while([1,2,3,4,5], fn(x) -> x < 3 end)
iex> Enum.drop_while([1, 2, 3, 4, 5], fn(x) -> x < 3 end)
[3,4,5]
"""
@spec drop_while(t, (element -> as_boolean(term))) :: list
@@ -270,19 +277,24 @@ defmodule Enum do
@doc """
Invokes the given `fun` for each item in the `collection`.
Returns the `collection` itself. `fun` can take two parameters,
in which case the second parameter will be the iteration index.
Returns `:ok`.
## Examples
Enum.each(['some', 'example'], fn(x) -> IO.puts x end)
Enum.each(["some", "example"], fn(x) -> IO.puts x end)
"some"
"example"
#=> :ok
"""
@spec each(t, (element -> any) | (element, index -> any)) :: :ok
def each(collection, fun) when is_list(collection) do
cond do
is_function(fun, 1) -> :lists.foreach(fun, collection)
is_function(fun, 2) -> :lists.foldl(fn(h, idx) -> fun.(h, idx); idx + 1 end, 0, collection)
is_function(fun, 2) ->
IO.write "[WARNING] Passing a funtion of arity 2 to Enum.each/2 is deprecated, " <>
"please use Stream.with_index/1 instead\n#{Exception.format_stacktrace}"
:lists.foldl(fn(h, idx) -> fun.(h, idx); idx + 1 end, 0, collection)
end
:ok
@@ -297,6 +309,9 @@ defmodule Enum do
end)
is_function(fun, 2) ->
IO.write "[WARNING] Passing a funtion of arity 2 to Enum.each/2 is deprecated, " <>
"please use Stream.with_index/1 instead\n#{Exception.format_stacktrace}"
Enumerable.reduce(collection, 0, fn(entry, idx) ->
fun.(entry, idx)
idx + 1
@@ -313,7 +328,7 @@ defmodule Enum do
iex> Enum.empty?([])
true
iex> Enum.empty?([1,2,3])
iex> Enum.empty?([1, 2, 3])
false
"""
@@ -334,14 +349,14 @@ defmodule Enum do
Expects an ordered collection.
## Examples
## Examples
iex> Enum.fetch([2,4,6], 0)
{ :ok, 2 }
iex> Enum.fetch([2,4,6], 2)
{ :ok, 6 }
iex> Enum.fetch([2,4,6], 4)
:error
iex> Enum.fetch([2, 4, 6], 0)
{ :ok, 2 }
iex> Enum.fetch([2, 4, 6], 2)
{ :ok, 6 }
iex> Enum.fetch([2, 4, 6], 4)
:error
"""
@spec fetch(t, index) :: { :ok, element } | :error
@@ -370,13 +385,13 @@ defmodule Enum do
## Examples
iex> Enum.fetch!([2,4,6], 0)
iex> Enum.fetch!([2, 4, 6], 0)
2
iex> Enum.fetch!([2,4,6], 2)
iex> Enum.fetch!([2, 4, 6], 2)
6
iex> Enum.fetch!([2,4,6], 4)
iex> Enum.fetch!([2, 4, 6], 4)
** (Enum.OutOfBoundsError) out of bounds error
"""
@@ -435,13 +450,13 @@ defmodule Enum do
## Examples
iex> Enum.find([2,4,6], fn(x) -> rem(x, 2) == 1 end)
iex> Enum.find([2, 4, 6], fn(x) -> rem(x, 2) == 1 end)
nil
iex> Enum.find([2,4,6], 0, fn(x) -> rem(x, 2) == 1 end)
iex> Enum.find([2, 4, 6], 0, fn(x) -> rem(x, 2) == 1 end)
0
iex> Enum.find([2,3,4], fn(x) -> rem(x, 2) == 1 end)
iex> Enum.find([2, 3, 4], fn(x) -> rem(x, 2) == 1 end)
3
"""
@@ -468,10 +483,10 @@ defmodule Enum do
## Examples
iex> Enum.find_value([2,4,6], fn(x) -> rem(x, 2) == 1 end)
iex> Enum.find_value([2, 4, 6], fn(x) -> rem(x, 2) == 1 end)
nil
iex> Enum.find_value([2,3,4], fn(x) -> rem(x, 2) == 1 end)
iex> Enum.find_value([2, 3, 4], fn(x) -> rem(x, 2) == 1 end)
true
"""
@@ -501,10 +516,10 @@ defmodule Enum do
## Examples
iex> Enum.find_index([2,4,6], fn(x) -> rem(x, 2) == 1 end)
iex> Enum.find_index([2, 4, 6], fn(x) -> rem(x, 2) == 1 end)
nil
iex> Enum.find_index([2,3,4], fn(x) -> rem(x, 2) == 1 end)
iex> Enum.find_index([2, 3, 4], fn(x) -> rem(x, 2) == 1 end)
1
"""
@@ -529,7 +544,7 @@ defmodule Enum do
iex> Enum.first([])
nil
iex> Enum.first([1,2,3])
iex> Enum.first([1, 2, 3])
1
"""
@@ -557,11 +572,11 @@ defmodule Enum do
## Examples
iex> Enum.join([1,2,3])
iex> Enum.join([1, 2, 3])
"123"
iex> Enum.join([1,2,3], " = ")
iex> Enum.join([1, 2, 3], " = ")
"1 = 2 = 3"
iex> Enum.join([1,2,3], ' = ')
iex> Enum.join([1, 2, 3], ' = ')
'1 = 2 = 3'
"""
@@ -583,8 +598,6 @@ defmodule Enum do
@doc """
Returns a new collection, where each item is the result
of invoking `fun` on each corresponding item of `collection`.
`fun` can take two parameters, in which case the second parameter
will be the iteration index.
For dicts, the function accepts a key-value tuple.
@@ -603,6 +616,8 @@ defmodule Enum do
is_function(fun, 1) ->
lc item inlist collection, do: fun.(item)
is_function(fun, 2) ->
IO.write "[WARNING] Passing a funtion of arity 2 to Enum.map/2 is deprecated, " <>
"please use Stream.with_index/1 instead\n#{Exception.format_stacktrace}"
mapper = fn(h, idx) -> { fun.(h, idx), idx + 1 } end
{ list, _ } = :lists.mapfoldl(mapper, 0, collection)
list
@@ -627,11 +642,11 @@ defmodule Enum do
## Examples
iex> Enum.map_join([1,2,3], &1 * 2)
iex> Enum.map_join([1, 2, 3], &1 * 2)
"246"
iex> Enum.map_join([1,2,3], " = ", &1 * 2)
iex> Enum.map_join([1, 2, 3], " = ", &1 * 2)
"2 = 4 = 6"
iex> Enum.map_join([1,2,3], ' = ', &1 * 2)
iex> Enum.map_join([1, 2, 3], ' = ', &1 * 2)
'2 = 4 = 6'
"""
@@ -714,6 +729,26 @@ defmodule Enum do
Enumerable.reduce(collection, acc, fun)
end
@doc """
Returns elements of collection for which `fun` returns false.
## Examples
iex> Enum.reject([1, 2, 3], fn(x) -> rem(x, 2) == 0 end)
[1, 3]
"""
@spec reject(t, (element -> as_boolean(term))) :: list
def reject(collection, fun) when is_list(collection) do
lc item inlist collection, !fun.(item), do: item
end
def reject(collection, fun) do
Enumerable.reduce(collection, [], fn(entry, acc) ->
unless fun.(entry), do: [entry|acc], else: acc
end) |> :lists.reverse
end
@doc """
Reverses the collection.
@@ -739,7 +774,7 @@ defmodule Enum do
## Examples
iex> Enum.sort([3,2,1])
iex> Enum.sort([3, 2, 1])
[1,2,3]
"""
@@ -759,7 +794,7 @@ defmodule Enum do
## Examples
iex> Enum.sort([1,2,3], &1 > &2)
iex> Enum.sort([1, 2, 3], &1 > &2)
[3,2,1]
"""
@@ -785,15 +820,15 @@ defmodule Enum do
## Examples
iex> Enum.split([1,2,3], 2)
iex> Enum.split([1, 2, 3], 2)
{ [1,2], [3] }
iex> Enum.split([1,2,3], 10)
iex> Enum.split([1, 2, 3], 10)
{ [1,2,3], [] }
iex> Enum.split([1,2,3], 0)
iex> Enum.split([1, 2, 3], 0)
{ [], [1,2,3] }
iex> Enum.split([1,2,3], -1)
iex> Enum.split([1, 2, 3], -1)
{ [1,2], [3] }
iex> Enum.split([1,2,3], -5)
iex> Enum.split([1, 2, 3], -5)
{ [], [1,2,3] }
"""
@@ -825,7 +860,7 @@ defmodule Enum do
## Examples
iex> Enum.split_while([1,2,3,4], fn(x) -> x < 3 end)
iex> Enum.split_while([1, 2, 3, 4], fn(x) -> x < 3 end)
{ [1, 2], [3, 4] }
"""
@spec split_while(t, (element -> as_boolean(term))) :: {list, list}
@@ -850,11 +885,11 @@ defmodule Enum do
## Examples
iex> Enum.take([1,2,3], 2)
iex> Enum.take([1, 2, 3], 2)
[1,2]
iex> Enum.take([1,2,3], 10)
iex> Enum.take([1, 2, 3], 10)
[1,2,3]
iex> Enum.take([1,2,3], 0)
iex> Enum.take([1, 2, 3], 0)
[]
"""
@@ -870,10 +905,10 @@ defmodule Enum do
def take(collection, count) when count > 0 do
{ list, _ } = Enumerable.reduce(collection, { [], count }, fn(entry, { list, count }) ->
if count > 0 do
if count > 1 do
{ [entry|list], count - 1 }
else
throw({ :enum_take, list })
throw({ :enum_take, [entry|list] })
end
end)
:lists.reverse(list)
@@ -892,7 +927,7 @@ defmodule Enum do
## Examples
iex> Enum.take_while([1,2,3], fn(x) -> x < 3 end)
iex> Enum.take_while([1, 2, 3], fn(x) -> x < 3 end)
[1, 2]
"""
@@ -938,10 +973,10 @@ defmodule Enum do
## Examples
iex> Enum.uniq([1,2,3,2,1])
iex> Enum.uniq([1, 2, 3, 2, 1])
[1, 2, 3]
iex> Enum.uniq([{1,:x}, {2,:y}, {1,:z}], fn {x,_} -> x end)
iex> Enum.uniq([{1, :x}, {2, :y}, {1, :z}], fn {x, _} -> x end)
[{1,:x}, {2,:y}]
"""
@@ -971,6 +1006,15 @@ defmodule Enum do
of tuples. The number of elements in the resulting list is
dictated by the first enum. In case the second list is shorter,
values are filled with nil.
## Examples
iex> Enum.zip([1, 2, 3], [:a, :b, :c])
[{1,:a},{2,:b},{3,:c}]
iex> Enum.zip([1,2,3,4,5], [:a, :b, :c])
[{1,:a},{2,:b},{3,:c},{4,nil},{5,nil}]
"""
@spec zip(t, t) :: [{any, any}]
def zip(coll1, coll2) when is_list(coll1) and is_list(coll2) do
@@ -996,7 +1040,7 @@ defmodule Enum do
## Examples
iex> Enum.max([1,2,3])
iex> Enum.max([1, 2, 3])
3
"""
@@ -1025,6 +1069,22 @@ defmodule Enum do
end
end
@doc """
Returns the collection with each element wrapped in a tuple
along side its index.
## Examples
iex> Enum.with_index [1,2,3]
[{1,0},{2,1},{3,2}]
"""
@spec with_index(t) :: list({ element, non_neg_integer })
def with_index(collection) do
map_reduce(collection, 0, fn x, acc ->
{ { x, acc }, acc + 1 }
end) |> elem(0)
end
@doc """
Returns the maximum value.
@@ -1066,12 +1126,12 @@ defmodule Enum do
end
@doc """
Returns the manimum value.
Returns the minimum value.
Raises empty error in case the collection is empty.
## Examples
iex> Enum.min([1,2,3])
iex> Enum.min([1, 2, 3])
1
"""
@@ -1106,7 +1166,7 @@ defmodule Enum do
end
@doc """
Returns the manimum value.
Returns the minimum value.
Raises empty error in case the collection is empty.
## Examples
+16 -10
View File
@@ -192,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
@@ -208,14 +206,22 @@ defmodule Exception do
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
+200 -168
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
with Unicode data.
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`
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:
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` and `IO.binwrite`
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:
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
@@ -714,11 +725,10 @@ defmodule File do
In order to write and read files, one must use the functions
in the IO module. By default, a file is opened 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.
which requires the functions `IO.binread` and `IO.binwrite`
to interact with the file. A developer may pass `:utf8` as an
option when opening the file and then all other functions from
`IO` are available, since they work directly with Unicode data.
The allowed modes:
@@ -753,19 +763,19 @@ 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
{ :ok, file } = File.open("foo.tar.gz", [:read, :compressed])
IO.readline(file)
IO.read(file, :line)
File.close(file)
"""
@@ -796,7 +806,7 @@ defmodule File do
## Examples
File.open("file.txt", [:read, :write], fn(file) ->
IO.readline(file)
IO.read(file, :line)
end)
"""
@@ -905,7 +915,7 @@ defmodule File do
"""
def ls(path // ".") do
case F.list_dir(path) do
{ :ok, file_list } -> { :ok, Enum.map file_list, :unicode.characters_to_binary(&1) }
{ :ok, file_list } -> { :ok, Enum.map(file_list, :unicode.characters_to_binary(&1)) }
{ :error, _ } = error -> error
end
end
@@ -934,80 +944,124 @@ defmodule File do
F.close(io_device)
end
@doc """
Converts the file device into an iterator that can be
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`
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
is shown below:
{ :ok, device } = File.open("README.md")
source = File.iterator(device)
File.open "NEWREADME.md", [:write], fn(target) ->
Enum.each source, fn(line) ->
IO.write target, Regex.replace(%r/"/, line, "'")
end
end
"""
@doc false
def iterator(device) do
fn(fun, acc) ->
do_iterator(device, fun, acc)
end
IO.write "[WARNING] File.iterator/1 is deprecated, please use IO.stream/1 instead\n#{Exception.format_stacktrace}"
IO.stream(device)
end
@doc """
Opens the given `file` with the given `mode` and
returns its iterator. The returned iterator will
returns its stream. The returned stream will
fail for the same reasons as `File.open!`. Note
that the file is opened when the iteration begins.
"""
def iterator!(file, mode // []) do
fn(fun, acc) ->
def stream!(file, mode // []) do
fn(acc, fun) ->
device = open!(file, mode)
try do
do_iterator(device, fun, acc)
IO.stream(device, acc, fun)
after
F.close(device)
end
end
end
@doc """
Converts the file device into an iterator that can
be passed into `Enum` to iterate line by line as a
binary. Check `iterator/1` for more information.
"""
@doc false
def iterator!(device, mode // []) do
IO.write "[WARNING] File.iterator!/2 is deprecated, please use File.stream!/2 instead\n#{Exception.format_stacktrace}"
stream!(device, mode)
end
@doc false
def biniterator(device) do
fn(fun, acc) ->
do_biniterator(device, fun, acc)
end
IO.write "[WARNING] File.biniterator/1 is deprecated, please use IO.binstream/1 instead\n#{Exception.format_stacktrace}"
IO.binstream(device)
end
@doc """
Opens the given `file` with the given `mode` and
returns its biniterator. The returned iterator will
returns its binstream. The returned stream will
fail for the same reasons as `File.open!`. Note
that the file is opened when the iteration begins.
"""
def biniterator!(file, mode // []) do
def binstream!(file, mode // []) do
fn(fun, acc) ->
device = open!(file, mode)
try do
do_biniterator(device, fun, acc)
IO.binstream(device, fun, acc)
after
F.close(device)
end
end
end
@doc false
def biniterator!(device, mode // []) do
IO.write "[WARNING] File.binstream!/2 is deprecated, please use File.binstream!/2 instead\n#{Exception.format_stacktrace}"
binstream!(device, mode)
end
@doc """
Changes the unix file `mode` for a given `file`.
Returns `:ok` on success, or `{:error, reason}`
on failure.
"""
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
defp open_defaults([:charlist|t], _add_binary) do
@@ -1024,26 +1078,4 @@ 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
+10 -5
View File
@@ -1,12 +1,12 @@
defmodule GenEvent.Behaviour do
@moduledoc """
This module is a convenience to define GenEvent callbacks in Elixir.
This module is a convenience for defining GenEvent callbacks in Elixir.
GenEvent is an OTP behaviour that encapsulates event handling functionality.
## Example
Bellow follows an example of a GenEvent that stores notifications
Below is an example of a GenEvent that stores notifications
until they are fetched:
defmodule MyEventHandler do
@@ -29,11 +29,16 @@ defmodule GenEvent.Behaviour do
end
{ :ok, pid } = :gen_event.start_link
#=> {:ok,#PID<0.42.0>}
:gen_event.add_handler(pid, MyEventHandler, [])
#=> :ok
:gen_event.notify(pid, {:notification, 1})
#=> :ok
:gen_event.notify(pid, {:notification, 2})
#=> :ok
:gen_event.call(pid, MyEventHandler, :notifications)
#=> [1, 2]
@@ -44,12 +49,12 @@ defmodule GenEvent.Behaviour do
Notice we never call the server callbacks directly, they are called
by OTP whenever we interact with the server.
Starting and sending messages to gen_event is done
Starting and sending messages to the GenEvent is done
via Erlang's `:gen_event` module. For more information,
please refer to the following:
http://www.erlang.org/doc/man/gen_event.html
http://learnyousomeerlang.com/event-handlers
* http://www.erlang.org/doc/man/gen_event.html
* http://learnyousomeerlang.com/event-handlers
"""
@doc false
+17 -17
View File
@@ -1,15 +1,15 @@
defmodule GenServer.Behaviour do
@moduledoc """
This module is a convenience to define GenServer callbacks in Elixir.
This module is a convenience for defining GenServer callbacks in Elixir.
A server is responsible to react to messages received from client
and a GenServer is an OTP behaviour that encapsulates common server
A server is responsible for reacting to messages received from a client.
A GenServer is an OTP behaviour that encapsulates common server
functionalities.
## Example
Bellow follows an example of a GenServer that push and pop items
into a stack:
Below is an example of a GenServer that pushes and pops items
onto a stack:
defmodule MyServer do
use GenServer.Behaviour
@@ -20,17 +20,17 @@ defmodule GenServer.Behaviour do
{ :reply, h, t }
end
def handle_call(_request, _from, _config) do
def handle_call(request, from, config) do
# Call the default implementation from GenServer.Behaviour
super
super(request, from, config)
end
def handle_cast({ :push, item }, config) do
{ :noreply, [item|config] }
end
def handle_cast(_request, _config) do
super
def handle_cast(request, config) do
super(request, config)
end
end
@@ -47,8 +47,8 @@ defmodule GenServer.Behaviour do
Notice we never call the server callbacks directly, they are called
by OTP whenever we interact with the server. **cast** messages are
asynchronous while **call** ones are synchronous. In the case of
GenServer's, there are 8 different values a callback such as
asynchronous while **call** ones are synchronous. For a
GenServer, there are 8 different values a callback such as
`handle_call` or `handle_cast` can return:
{ :reply, reply, new_state }
@@ -62,7 +62,7 @@ defmodule GenServer.Behaviour do
There are 6 callbacks required to be implemented in a GenServer. The
`GenServer.Behaviour` module defines all of them automatically, but
allows us to customize the ones we need. The list of callbacks are:
allows us to customize the ones we need. The required callbacks are:
* `init(args)` - invoked when the server is started;
* `handle_call(msg, from, state)` - invoked to handle call messages;
@@ -74,13 +74,13 @@ defmodule GenServer.Behaviour do
* `code_change(old_vsn, state, extra)` - called when the application
code is being upgraded live (hot code swap);
Starting and sending messages to the gen_server is done
Starting and sending messages to the GenServer is done
via Erlang's `:gen_server` module. For more information,
please refer to the following:
http://www.erlang.org/doc/man/gen_server.html
http://www.erlang.org/doc/design_principles/gen_server_concepts.html
http://learnyousomeerlang.com/clients-and-servers
* http://www.erlang.org/doc/man/gen_server.html
* http://www.erlang.org/doc/design_principles/gen_server_concepts.html
* http://learnyousomeerlang.com/clients-and-servers
"""
@doc false
@@ -122,4 +122,4 @@ defmodule GenServer.Behaviour do
handle_cast: 2, terminate: 2, code_change: 3]
end
end
end
end
+88 -146
View File
@@ -35,7 +35,7 @@ defmodule HashDict do
# The ordered record contains a single bucket
@ordered_threshold 8
defrecordp :ordered,
defrecordp :ordered, HashDict,
size: 0,
bucket: []
@@ -47,7 +47,7 @@ defmodule HashDict do
@node_size 8
@node_template :erlang.make_tuple(@node_size, [])
defrecordp :trie,
defrecordp :trie, HashDict,
size: 0,
depth: 0,
expand_on: @node_size * @expand_load,
@@ -57,6 +57,7 @@ defmodule HashDict do
import Bitwise
# Let's inline common instructions
@compile :inline_list_funcs
@compile { :inline, bucket_hash: 1, bucket_index: 1, bucket_nth_index: 2, bucket_next: 1 }
@doc """
@@ -72,7 +73,7 @@ defmodule HashDict do
## Examples
HashDict.new [{:b,1},{:a,2}]
HashDict.new [{:b, 1}, {:a, 2}]
#=> #HashDict<[a: 2, b: 1]>
"""
@@ -101,27 +102,15 @@ defmodule HashDict do
end
end
@doc """
Puts the given key and value in the dict.
"""
def put(dict, key, value) do
{ dict, _ } = dict_put(dict, key, { :put, value })
dict
end
@doc """
Puts the given value under key in the dictionary
only if one does not exist yet.
"""
def put_new(dict, key, value) do
update(dict, key, value, fn(v) -> v end)
end
@doc """
Updates the key in the dictionary according
to the given function. Raises if the key does
not exist in the dictionary.
"""
def update(dict, key, fun) when is_function(fun, 1) do
case dict_put(dict, key, { :update, nil, fun }) do
{ dict, 0 } ->
@@ -131,55 +120,37 @@ defmodule HashDict do
end
end
@doc """
Updates the key in the dictionary according
to the given function. Adds initial value if
the key does not exist in the dicionary.
"""
def update(dict, key, initial, fun) when is_function(fun, 1) do
{ dict, _ } = dict_put(dict, key, { :update, initial, fun })
dict
end
@doc """
Gets the value under key from the dict.
"""
def get(dict, key, default // nil) do
case dict_get(dict, key) do
{ ^key, value } -> value
false -> default
case fetch(dict, key) do
{ :ok, value } -> value
:error -> default
end
end
@doc """
Fetches the value under key from the dict
and return it in a tagged tuple.
"""
def fetch(dict, key) do
case dict_get(dict, key) do
{ ^key, value } -> { :ok, value }
false -> :error
end
def fetch(ordered(bucket: bucket), key) do
bucket_get(bucket, key)
end
def fetch(trie(root: root, depth: depth), key) do
bucket_get(node_bucket(root, depth, bucket_hash(key)), key)
end
def fetch!(dict, key) when is_tuple(dict) do
case dict_get(dict, key) do
{ ^key, value } -> value
false -> raise(KeyError, key: key)
case fetch(dict, key) do
{ :ok, value } -> value
:error -> raise(KeyError, key: key)
end
end
@doc """
Checks if the dict has the given key.
"""
def has_key?(dict, key) do
match? { ^key, _ }, dict_get(dict, key)
match? { :ok, _ }, fetch(dict, key)
end
@doc """
Returns the value under key from the dict as well as the dict without key.
"""
def pop(dict, key, default // nil) do
case dict_delete(dict, key) do
{ dict, _, 0 } -> { default, dict }
@@ -187,24 +158,15 @@ defmodule HashDict do
end
end
@doc """
Deletes a value from the dict.
"""
def delete(dict, key) do
{ dict, _, _ } = dict_delete(dict, key)
dict
end
@doc """
Returns the dict size.
"""
def size(dict) do
elem(dict, 1)
end
@doc """
Returns an empty dict.
"""
def empty(_) do
ordered()
end
@@ -213,50 +175,38 @@ defmodule HashDict do
size = elem(dict1, 1)
case elem(dict2, 1) do
^size ->
dict_equal?(dict1, dict1)
dict_equal?(dict1, dict2)
_ ->
false
end
end
@doc """
Converts the dict to a list.
"""
def to_list(ordered(bucket: bucket)) do
bucket
lc [k|v] inlist bucket, do: { k, v }
end
def to_list(dict) do
dict_fold(dict, [], [&1|&2]) |> :lists.reverse
reduce(dict, [], [&1|&2]) |> :lists.reverse
end
@doc """
Get all keys in the dict.
"""
def keys(dict) do
dict_fold(dict, [], fn { k, _ }, acc -> [k|acc] end)
dict_fold(dict, [], fn [k|_], acc -> [k|acc] end)
end
@doc """
Get all values in the dict.
"""
def values(dict) do
dict_fold(dict, [], fn { _, v }, acc -> [v|acc] end)
dict_fold(dict, [], fn [_|v], acc -> [v|acc] end)
end
@doc """
Merges two dictionaries.
"""
def merge(dict, enum, callback // fn(_k, _v1, v2) -> v2 end)
def merge(dict1, dict2, callback) when is_record(dict1, HashDict) and is_record(dict2, HashDict) and elem(dict1, 1) < elem(dict2, 1) do
dict_fold dict1, dict2, fn { k, v1 }, acc ->
dict_fold dict1, dict2, fn [k|v1], acc ->
update(acc, k, v1, callback.(k, v1, &1))
end
end
def merge(dict1, dict2, callback) when is_record(dict1, HashDict) and is_record(dict2, HashDict) do
dict_fold dict2, dict1, fn { k, v2 }, acc ->
dict_fold dict2, dict1, fn [k|v2], acc ->
update(acc, k, v2, callback.(k, &1, v2))
end
end
@@ -267,12 +217,6 @@ defmodule HashDict do
end
end
@doc """
Splits a dict into two dicts,
one containing entries with key in the keys list,
and another containing entries with key not in keys.
Returns a 2-tuple of the new dicts.
"""
def split(dict, keys) do
split(keys, new, dict)
end
@@ -288,10 +232,6 @@ defmodule HashDict do
end
end
@doc """
Returns a new dict with only the entries
which key is in keys.
"""
def take(dict, keys) do
take(dict, keys, new)
end
@@ -304,34 +244,23 @@ defmodule HashDict do
end
end
@doc """
Returns a new dict with only the entries
which key is not in keys
"""
def drop(dict, []), do: dict
def drop(dict, [key|keys]) do
drop(delete(dict, key), keys)
end
@doc false
def reduce(ordered(bucket: bucket), acc, fun) do
:lists.foldl(fun, acc, bucket)
bucket_reduce(bucket, acc, fun)
end
def reduce(trie() = dict, acc, fun) do
dict_fold(dict, acc, fun)
def reduce(trie(root: root, depth: depth), acc, fun) do
node_reduce(root, depth, acc, fun, @node_size)
end
## Dict-wide functions
defp dict_get(ordered(bucket: bucket), key) do
bucket_get(bucket, key)
end
defp dict_get(trie(root: root, depth: depth), key) do
bucket_get(node_bucket(root, depth, bucket_hash(key)), key)
end
defp dict_fold(ordered(bucket: bucket), acc, fun) do
bucket_fold(bucket, acc, fun)
end
@@ -379,7 +308,7 @@ defmodule HashDict do
{ dict, value, 0 }
{ root, value, -1 } ->
{ if depth > 0 and trie(dict, :contract_on) == size do
root = node_contract(root, depth, depth - 1)
root = node_contract(root, depth)
trie(dict,
root: root,
size: size - 1,
@@ -394,9 +323,9 @@ defmodule HashDict do
defp dict_equal?(dict1, dict2) do
try do
reduce(dict1, true, fn({ key, value }, acc) ->
dict_fold(dict1, true, fn [key|value], acc ->
case fetch(dict2, key) do
{ _ok, ^value } ->
{ :ok, ^value } ->
acc
_ ->
throw(:error)
@@ -410,12 +339,12 @@ defmodule HashDict do
## Bucket helpers
# Get value from the bucket
defp bucket_get([{k,_}|_bucket], key) when k > key do
false
defp bucket_get([[k|_]|_bucket], key) when k > key do
:error
end
defp bucket_get([{key,_}=e|_bucket], key) do
e
defp bucket_get([[key|value]|_bucket], key) do
{ :ok, value }
end
defp bucket_get([_e|bucket], key) do
@@ -423,24 +352,24 @@ defmodule HashDict do
end
defp bucket_get([], _key) do
false
:error
end
# Puts a value in the bucket
defp bucket_put([{k,_}|_]=bucket, key, { :put, value }) when k > key do
{ [{key, value}|bucket], 1 }
defp bucket_put([[k|_]|_]=bucket, key, { :put, value }) when k > key do
{ [[key|value]|bucket], 1 }
end
defp bucket_put([{k,_}|_]=bucket, key, { :update, initial, _fun }) when k > key do
{ [{key, initial}|bucket], 1 }
defp bucket_put([[k|_]|_]=bucket, key, { :update, initial, _fun }) when k > key do
{ [[key|initial]|bucket], 1 }
end
defp bucket_put([{key,_}|bucket], key, { :put, value }) do
{ [{key,value}|bucket], 0 }
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 }
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
@@ -449,26 +378,26 @@ defmodule HashDict do
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,_}|_]=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}]
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 k > key do
defp bucket_delete([[k|_]|_]=bucket, key) when k > key do
{ bucket, nil, 0 }
end
defp bucket_delete([{key,value}|bucket], key) do
defp bucket_delete([[key|value]|bucket], key) do
{ bucket, value, -1 }
end
@@ -481,7 +410,15 @@ defmodule HashDict do
{ [], nil, 0 }
end
# Folds the bucket
# Reduces the bucket
defp bucket_reduce([[k|v]|t], acc, fun) do
bucket_reduce(t, fun.({ k, v }, acc), fun)
end
defp bucket_reduce([], acc, _fun) do
acc
end
defp bucket_fold(bucket, acc, fun) do
:lists.foldl(fun, acc, bucket)
end
@@ -544,6 +481,20 @@ defmodule HashDict do
end
end
# Reduces a node recursively
defp node_reduce(bucket, -1, acc, fun, _) do
bucket_reduce(bucket, acc, fun)
end
defp node_reduce(node, depth, acc, fun, count) when count >= 1 do
acc = node_reduce(:erlang.element(count, node), depth - 1, acc, fun, @node_size)
node_reduce(node, depth, acc, fun, count - 1)
end
defp node_reduce(_node, _, acc, _fun, 0) do
acc
end
# Folds a node recursively
defp node_fold(bucket, -1, acc, fun, _) do
bucket_fold(bucket, acc, fun)
@@ -572,27 +523,26 @@ 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
@node_template |> each_contract(b1, n) |> each_contract(b2, n) |> each_contract(b3, n)
|> each_contract(b4, n) |> each_contract(b5, n) |> each_contract(b6, n)
|> each_contract(b7, n) |> each_contract(b8, n)
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 // @node_template, bucket, n) do
:lists.foldl fn { key, value }, acc ->
: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))
end, node, bucket
@@ -609,11 +559,3 @@ end
defimpl Access, for: HashDict do
def access(dict, key), do: HashDict.get(dict, key, nil)
end
defimpl Binary.Inspect, for: HashDict do
import Kernel, except: [inspect: 2]
def inspect(dict, opts) do
"#HashDict<" <> Kernel.inspect(HashDict.to_list(dict), opts) <> ">"
end
end
+492
View File
@@ -0,0 +1,492 @@
defmodule HashSet do
@moduledoc """
A set store.
The `HashSet` is meant to work well with both small and
large sets. It is an implementation of the `Set` behaviour.
For more information about the functions and their APIs,
please consult the `Set` module.
"""
@behaviour Set
# The ordered record contains a single bucket.
@ordered_threshold 8
defrecordp :ordered, HashSet,
size: 0,
bucket: []
# The bucketed record contains a series of buckets.
@expand_load 5
@contract_load 2
@node_bitmap 0b1111
@node_shift 4
@node_size 16
@node_template :erlang.make_tuple(@node_size, [])
@expand_default (@node_size * @expand_load)
@contract_default @contract_load
defrecordp :trie, HashSet,
size: 0,
depth: 0,
expand_on: @expand_default,
contract_on: @contract_default,
root: @node_template
import Bitwise
@compile :inline_list_funcs
@compile { :inline, bucket_hash: 1, bucket_index: 1, bucket_nth_index: 2, bucket_next: 1 }
@doc """
Creates a new empty set.
"""
def new() do
ordered()
end
@doc """
Creates a new set from the given enumerable.
## Examples
iex> HashSet.new [1, 1, 2, 3, 3] |> HashSet.to_list
[1,2,3]
"""
def new(members) do
Enum.reduce members, ordered(), fn member, set ->
put(set, member)
end
end
def union(set1, set2) when is_record(set1, HashSet) and is_record(set2, HashSet) and elem(set1, 1) <= elem(set2, 1) do
set_fold set1, set2, fn v1, acc ->
put(acc, v1)
end
end
def union(set1, set2) when is_record(set1, HashSet) and is_record(set2, HashSet) do
set_fold set2, set1, fn v, acc ->
put(acc, v)
end
end
def intersection(set1, set2) when is_record(set1, HashSet) and is_record(set2, HashSet) and elem(set1, 1) <= elem(set2, 1) do
set_filter set1, fn e -> set_member?(set2, e) end
end
def intersection(set1, set2) when is_record(set1, HashSet) and is_record(set2, HashSet) do
set_filter set2, fn e -> set_member?(set1, e) end
end
def difference(set1, set2) when is_record(set1, HashSet) and is_record(set2, HashSet) do
set_filter set1, fn m -> not set_member?(set2, m) end
end
def member?(set, member) when is_record(set, HashSet) do
set_member?(set, member)
end
def empty(_) do
ordered()
end
def size(set) do
elem(set, 1)
end
def to_list(ordered(bucket: bucket)) do
bucket
end
def to_list(set) do
set_fold(set, [], [&1|&2]) |> :lists.reverse
end
def put(set, member) do
{ set, _ } = set_put(set, member)
set
end
def delete(set, member) do
{ set, _ } = set_delete(set, member)
set
end
def equal?(set1, set2) do
size = elem(set1, 1)
case elem(set2, 1) do
^size ->
set_equal?(set1, set2)
_ ->
false
end
end
def subset?(set1, set2) do
set_equal?(set1, set2)
end
def disjoint?(set1, set2) do
set_disjoint?(set1, set2)
end
@doc false
def reduce(ordered(bucket: bucket), acc, fun) do
:lists.foldl(fun, acc, bucket)
end
def reduce(trie() = set, acc, fun) do
set_fold(set, acc, fun)
end
## HashSet-wide functions
defp set_filter(ordered(bucket: bucket, size: size), fun) do
{ new, removed_count } = bucket_filter(bucket, fun, [], 0)
ordered(bucket: new, size: size - removed_count)
end
defp set_filter(trie(root: root, depth: depth, size: size) = set, fun) do
{ new, removed_count } = node_filter(root, depth, fun, @node_size)
if depth > 0 and trie(set, :contract_on) >= (size - removed_count) do
contract_trie(trie(root: new,
size: size - removed_count,
depth: depth,
contract_on: trie(set, :contract_on),
expand_on: trie(set, :expand_on)))
else
trie(size: size - removed_count, root: new, depth: depth)
end
end
defp set_put(ordered(size: @ordered_threshold, bucket: bucket), member) do
root = node_relocate(bucket, 0)
set_put(trie(size: @ordered_threshold, root: root), member)
end
defp set_put(ordered(size: size, bucket: bucket) = set, member) do
{ new, count } = bucket_put(bucket, member)
{ ordered(set, size: size + count, bucket: new), count }
end
defp set_put(trie(root: root, depth: depth, size: size, expand_on: size, contract_on: contract_on) = set, member) do
root = node_expand(root, depth, depth + 1)
set = trie(set, root: root, depth: depth + 1,
expand_on: size * @node_size, contract_on: contract_on * @node_size)
set_put(set, member)
end
defp set_put(trie(root: root, size: size, depth: depth) = set, member) do
pos = bucket_hash(member)
{ root, count } = node_put(root, depth, pos, member)
{ trie(set, size: size + count, root: root), count }
end
defp set_member?(ordered(bucket: bucket), member) do
:lists.member(member, bucket)
end
defp set_member?(trie(root: root, depth: depth), member) do
:lists.member(member, node_bucket(root, depth, bucket_hash(member)))
end
defp set_delete(ordered(bucket: bucket, size: size) = set, member) do
case bucket_delete(bucket, member) do
{ _, 0 } ->
{ set, 0 }
{ new_bucket, -1 } ->
{ ordered(set, size: size - 1, bucket: new_bucket), -1 }
end
end
defp set_delete(trie(root: root, size: size, depth: depth) = set, member) do
pos = bucket_hash(member)
case node_delete(root, depth, pos, member) do
{ _, 0 } ->
{ set, 0 }
{ root, -1 } ->
{ if depth > 0 and trie(set, :contract_on) == size do
root = node_contract(root, depth)
trie(set,
root: root,
size: size - 1,
depth: depth - 1,
contract_on: div(size, @node_size),
expand_on: div(trie(set, :expand_on), @node_size))
else
trie(set, size: size - 1, root: root)
end, -1 }
end
end
defp set_equal?(set1, set2) do
try do
reduce(set1, true, fn member, acc ->
case member?(set2, member) do
true -> acc
_ -> throw(:error)
end
end)
catch
:error -> false
end
end
defp set_disjoint?(set1, set2) do
try do
reduce(set1, true, fn member, acc ->
case member?(set2, member) do
false -> acc
_ -> throw(:error)
end
end)
catch
:error -> false
end
end
defp set_fold(ordered(bucket: bucket), acc, fun) do
bucket_fold(bucket, acc, fun)
end
defp set_fold(trie(root: root, depth: depth), acc, fun) do
node_fold(root, depth, acc, fun, @node_size)
end
## Bucket helpers
defp bucket_filter([e|bucket], fun, acc, count) do
case fun.(e) do
true -> bucket_filter(bucket, fun, [e|acc], count)
false -> bucket_filter(bucket, fun, acc, count + 1)
end
end
defp bucket_filter([], _fun, acc, count) do
{ :lists.reverse(acc), count }
end
defp bucket_put([m|_]=bucket, member) when m > member do
{ [member|bucket], 1 }
end
defp bucket_put([member|bucket], member) do
{ [member|bucket], 0 }
end
defp bucket_put([e|bucket], member) do
{ rest, count } = bucket_put(bucket, member)
{ [e|rest], count }
end
defp bucket_put([], member) do
{ [member], 1 }
end
defp bucket_put!([m|_]=bucket, member) when m > member, do: [member|bucket]
defp bucket_put!([member|bucket], member), do: [member|bucket]
defp bucket_put!([e|bucket], member), do: [e|bucket_put!(bucket, member)]
defp bucket_put!([], member), do: [member]
# Deletes a key from the bucket
defp bucket_delete([m,_|_]=bucket, member) when m > member do
{ bucket, 0 }
end
defp bucket_delete([member|bucket], member) do
{ bucket, -1 }
end
defp bucket_delete([e|bucket], member) do
{ rest, count } = bucket_delete(bucket, member)
{ [e|rest], count }
end
defp bucket_delete([], _member) do
{ [], 0 }
end
defp bucket_fold(bucket, acc, fun) do
:lists.foldl(fun, acc, bucket)
end
defp bucket_hash(key) do
:erlang.phash2(key)
end
defp bucket_nth_index(hash, n) do
(hash >>> (@node_shift * n)) &&& @node_bitmap
end
defp bucket_index(hash) do
hash &&& @node_bitmap
end
defp bucket_next(hash) do
hash >>> @node_shift
end
# Trie resizing
defp contract_trie(trie(depth: 0) = set) do
set
end
defp contract_trie(trie(root: root, depth: depth, size: size, contract_on: contract_on, expand_on: expand_on) = set) when size <= contract_on do
new_contract_on = div(contract_on, @node_size)
new_expand_on = div(expand_on, @node_size)
if new_contract_on == 0, do: new_contract_on = @contract_default
if new_expand_on == 0, do: new_expand_on = @expand_default
contract_trie(trie(set, root: node_contract(root, depth),
size: size,
depth: depth - 1,
contract_on: new_contract_on,
expand_on: new_expand_on))
end
defp contract_trie(set) do
set
end
# Node helpers
# Gets a bucket from the node
defp node_bucket(node, 0, hash) do
elem(node, bucket_index(hash))
end
defp node_bucket(node, depth, hash) do
child = elem(node, bucket_index(hash))
node_bucket(child, depth - 1, bucket_next(hash))
end
defp node_put(node, 0, hash, member) do
pos = bucket_index(hash)
{ new, count } = bucket_put(elem(node, pos), member)
{ set_elem(node, pos, new), count }
end
defp node_put(node, depth, hash, member) do
pos = bucket_index(hash)
{ new, count } = node_put(elem(node, pos), depth - 1, bucket_next(hash), member)
{ set_elem(node, pos, new), count }
end
# Deletes a key from the bucket
defp node_delete(node, 0, hash, member) do
pos = bucket_index(hash)
case bucket_delete(elem(node, pos), member) do
{ _, 0 } -> { node, 0 }
{ new, -1 } -> { set_elem(node, pos, new), -1 }
end
end
defp node_delete(node, depth, hash, member) do
pos = bucket_index(hash)
case node_delete(elem(node, pos), depth - 1, bucket_next(hash), member) do
{ _, 0 } -> { node, 0 }
{ new, -1 } -> { set_elem(node, pos, new), -1 }
end
end
defp node_fold(bucket, -1, acc, fun, _) do
bucket_fold(bucket, acc, fun)
end
defp node_fold(node, depth, acc, fun, count) when count >= 1 do
acc = node_fold(:erlang.element(count, node), depth - 1, acc, fun, @node_size)
node_fold(node, depth, acc, fun, count - 1)
end
defp node_fold(_node, _, acc, _fun, 0) do
acc
end
defp node_filter(bucket, -1, fun, _) do
bucket_filter(bucket, fun, [], 0)
end
defp node_filter(node, depth, fun, count) when count >= 1 do
case node_filter(:erlang.element(count, node), depth - 1, fun, @node_size) do
{ _, 0 } ->
node_filter(node, depth, fun, count - 1)
{ new_element, count1 } ->
{ new_node, count2 } = node_filter(:erlang.setelement(count, node, new_element), depth, fun, count - 1)
{ new_node, count1 + count2 }
end
end
defp node_filter(node, _, _fun, 0) do
{ node, 0 }
end
defp node_relocate(node // @node_template, bucket, n) do
:lists.foldl fn member, acc ->
pos = member |> bucket_hash() |> bucket_nth_index(n)
set_elem(acc, pos, bucket_put!(elem(acc, pos), member))
end, node, bucket
end
# Node resizing
defp node_expand({ b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12, b13, b14, b15, b16 }, 0, n) do
{ 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), node_relocate(b9, n),
node_relocate(b10, n), node_relocate(b11, n), node_relocate(b12, n),
node_relocate(b13, n), node_relocate(b14, n), node_relocate(b15, n),
node_relocate(b16, n) }
end
defp node_expand({ b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12, b13, b14, b15, b16 }, depth, n) do
depth = depth - 1
{ node_expand(b1, depth, n), node_expand(b2, depth, n), node_expand(b3, depth, n),
node_expand(b4, depth, n), node_expand(b5, depth, n), node_expand(b6, depth, n),
node_expand(b7, depth, n), node_expand(b8, depth, n), node_expand(b9, depth, n),
node_expand(b10, depth, n), node_expand(b11, depth, n), node_expand(b12, depth, n),
node_expand(b13, depth, n), node_expand(b14, depth, n), node_expand(b15, depth, n),
node_expand(b16, depth, n) }
end
defp node_contract({ b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12, b13, b14, b15, b16 }, depth) when depth > 0 do
depth = depth - 1
{ 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), node_contract(b9, depth),
node_contract(b10, depth), node_contract(b11, depth), node_contract(b12, depth),
node_contract(b13, depth), node_contract(b14, depth), node_contract(b15, depth),
node_contract(b16, depth) }
end
defp node_contract({ b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12, b13, b14, b15, b16 }, 0) do
b1 |> each_contract(b2) |> each_contract(b3) |> each_contract(b4)
|> each_contract(b5) |> each_contract(b6) |> each_contract(b7)
|> each_contract(b8) |> each_contract(b9) |> each_contract(b10)
|> each_contract(b11) |> each_contract(b12) |> each_contract(b13)
|> each_contract(b14) |> each_contract(b15) |> each_contract(b16)
end
defp each_contract([m1|acc], [m2|_]=bucket) when m1 < m2, do: [m1|each_contract(acc, bucket)]
defp each_contract(acc, [m|bucket]), do: [m|each_contract(acc, bucket)]
defp each_contract([], bucket), do: bucket
defp each_contract(acc, []), do: acc
@doc false
def inspect_depth(trie(depth: d)), do: d
@doc false
def inspect_contract(trie(contract_on: c)), do: c
@doc false
def inspect_expand(trie(expand_on: e)), do: e
end
defimpl Enumerable, for: HashSet do
def reduce(set, acc, fun), do: HashSet.reduce(set, acc, fun)
def member?(set, v), do: HashSet.member?(set, v)
def count(set), do: HashSet.size(set)
end
+400
View File
@@ -0,0 +1,400 @@
import Kernel, except: [inspect: 1]
import Inspect.Algebra
defrecord Inspect.Opts, raw: false, limit: :infinity, pretty: false, width: 80
defprotocol Inspect do
@moduledoc """
The `Inspect` protocol is responsible for converting any Elixir
data structure into an algebra document. This document is then
formatted, either in pretty printing format or a regular one.
The `inspect/2` function receives the entity to be inspected
followed by the inspecting options, represented by the record
`Inspect.Opts`.
Inspection is done using the functions available in
`Inspect.Algebra` and by calling `Kernel.inspect/2` recursively
passing the `Inspect.Opts` as an argument. When `Kernel.inspect/2`
receives an `Inspect.Opts` record as the second argument, it returns
the underlying algebra document instead of the formatted string.
Many times, inspecting a structure can be implemented using functions
of the existing entities. For example, here is `HashSet`'s `inspect`
implementation:
defimpl Inspect, for: HashSet do
import Inspect.Algebra
def inspect(dict, opts) do
concat ["#HashSet<", Kernel.inspect(HashSet.to_list(dict), opts), ">"]
end
end
The `concat` function comes from `Inspect.Algebra` and it
concatenates algebra documents together. In the example above,
it is concatenating the string `"HashSet<"` (all strings are
valid algebra documents that keep their formatting when pretty
printed), the document returned by `Kernel.inspect/2` and the
other string `">"`.
Since regular strings are valid entities in an algebra document,
an implementation of inspect may simply return a binary,
although that will devoid it of any pretty-printing.
"""
def inspect(thing, opts)
end
defimpl Inspect, for: Atom do
require Macro
@doc """
Represents the atom as an Elixir term. The atoms `false`, `true`
and `nil` are simply quoted. Modules are properly represented
as modules using the dot notation.
Notice that in Elixir, all operators can be represented using
literal atoms (`:+`, `:-`, etc).
## Examples
iex> inspect(:foo)
":foo"
iex> inspect(nil)
"nil"
iex> inspect(Foo.Bar)
"Foo.Bar"
"""
def inspect(atom, _opts) do
inspect(atom)
end
def inspect(false), do: "false"
def inspect(true), do: "true"
def inspect(nil), do: "nil"
def inspect(:""), do: ":\"\""
def inspect(Elixir), do: "Elixir"
def inspect(atom) do
binary = atom_to_binary(atom)
cond do
valid_atom_identifier?(binary) ->
":" <> binary
valid_ref_identifier?(binary) ->
"Elixir." <> rest = binary
rest
atom in Macro.binary_ops or atom in Macro.unary_ops ->
":" <> binary
true ->
<< ?:, ?", String.escape(binary, ?") :: binary, ?" >>
end
end
# Detect if atom is an atom alias (Elixir.Foo.Bar.Baz)
defp valid_ref_identifier?("Elixir" <> rest) do
valid_ref_piece?(rest)
end
defp valid_ref_identifier?(_), do: false
defp valid_ref_piece?(<<?., h, t :: binary>>) when h in ?A..?Z do
valid_ref_piece? valid_identifier?(t)
end
defp valid_ref_piece?(<<>>), do: true
defp valid_ref_piece?(_), do: false
# Detect if atom
defp valid_atom_identifier?(<<h, t :: binary>>) when h in ?a..?z or h in ?A..?Z or h == ?_ do
case valid_identifier?(t) do
<<>> -> true
<<??>> -> true
<<?!>> -> true
_ -> false
end
end
defp valid_atom_identifier?(_), do: false
defp valid_identifier?(<<h, t :: binary>>)
when h in ?a..?z
when h in ?A..?Z
when h in ?0..?9
when h == ?_ do
valid_identifier? t
end
defp valid_identifier?(other), do: other
end
defimpl Inspect, for: BitString do
@doc %B"""
Represents a string as itself escaping all necessary
characters. Bitstrings and strings that contain non-
printable characters are printed using the bitstring
syntax.
## Examples
iex> inspect("bar")
"\"bar\""
iex> inspect("f\"oo")
"\"f\\\"oo\""
"""
def inspect(thing, opts) when is_binary(thing) do
if String.printable?(thing) do
<< ?", String.escape(thing, ?") :: binary, ?" >>
else
as_bitstring(thing, opts)
end
end
def inspect(thing, opts) do
as_bitstring(thing, opts)
end
## Bitstrings
defp as_bitstring(bitstring, Inspect.Opts[] = opts) do
"<<" <> each_bit(bitstring, opts.limit) <> ">>"
end
defp each_bit(_, 0) do
"..."
end
defp each_bit(<<h, t :: bitstring>>, counter) when t != <<>> do
integer_to_binary(h) <> ", " <> each_bit(t, decrement(counter))
end
defp each_bit(<<h :: size(8)>>, _counter) do
integer_to_binary(h)
end
defp each_bit(<<>>, _counter) do
<<>>
end
defp each_bit(bitstring, _counter) do
size = bit_size(bitstring)
<<h :: size(size)>> = bitstring
integer_to_binary(h) <> "::size(" <> integer_to_binary(size) <> ")"
end
defp decrement(:infinity), do: :infinity
defp decrement(counter), do: counter - 1
end
defimpl Inspect, for: List do
@doc %B"""
Represents a list, checking if it can be printed or not.
If so, a single-quoted representation is returned,
otherwise the brackets syntax is used. Keywords are
printed in keywords syntax.
## Examples
iex> inspect('bar')
"'bar'"
iex> inspect([0|'bar'])
"[0, 98, 97, 114]"
iex> inspect([:foo,:bar])
"[:foo, :bar]"
"""
def inspect([], _opts), do: "[]"
def inspect(thing, Inspect.Opts[] = opts) do
cond do
:io_lib.printable_list(thing) ->
<< ?', String.escape(:unicode.characters_to_binary(thing), ?') :: binary, ?' >>
keyword?(thing) && not opts.raw ->
surround_many("[", thing, "]", opts.limit, keyword(&1, opts))
true ->
surround_many("[", thing, "]", opts.limit, Kernel.inspect(&1, opts))
end
end
defp keyword({key, value}, opts) do
concat(
key_to_binary(key) <> ": ",
Kernel.inspect(value, opts)
)
end
defp key_to_binary(key) do
case Inspect.Atom.inspect(key) do
":" <> right -> right
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 Inspect, for: Tuple do
@doc """
Represents tuples. If the tuple represents a record,
it shows it nicely formatted using the access syntax.
## Examples
iex> inspect({1, 2, 3})
"{1, 2, 3}"
iex> inspect(ArgumentError.new)
"ArgumentError[message: \\\"argument error\\\"]"
"""
def inspect({}, _opts), do: "{}"
def inspect(tuple, opts) do
unless opts.raw do
record_inspect(tuple, opts)
end || surround_many("{", tuple_to_list(tuple), "}", opts.limit, Kernel.inspect(&1, opts))
end
## Helpers
defp record_inspect(record, opts) do
[name|tail] = tuple_to_list(record)
if is_atom(name) && (fields = record_fields(name)) && (length(fields) == size(record) - 1) do
if Enum.first(tail) == :__exception__ do
surround_record(name, tl(fields), tl(tail), opts)
else
surround_record(name, fields, tail, opts)
end
end || surround_many("{", [name|tail], "}", opts.limit, Kernel.inspect(&1, opts))
end
defp record_fields(name) do
case atom_to_binary(name) do
"Elixir." <> _ ->
try do
name.__record__(:fields)
rescue
_ -> nil
end
_ -> nil
end
end
defp surround_record(name, fields, tail, opts) do
fields = lc { field, _ } inlist fields, do: field
concat(
Inspect.Atom.inspect(name, opts),
surround_many("[", Enum.zip(fields, tail), "]", opts.limit, keyword(&1, opts))
)
end
defp keyword({ k, v }, opts) do
concat(
atom_to_binary(k, :utf8) <> ": ",
Kernel.inspect(v, opts)
)
end
end
defimpl Inspect, for: Number do
@doc """
Represents the number as a string.
Floats are represented using the shortened, correctly rounded string
that converts to float when read back with `binary_to_float/1`. This
is done via the Erlang implementation of _Printing Floating-Point
Numbers Quickly and Accurately_ in Proceedings of the SIGPLAN '96
Conference on Programming Language Design and Implementation.
## Examples
iex> inspect(1)
"1"
"""
def inspect(thing, _opts) when is_integer(thing) do
integer_to_binary(thing)
end
def inspect(thing, _opts) do
list_to_binary(:io_lib_format.fwrite_g(thing))
end
end
defimpl Inspect, for: Regex do
@doc %B"""
Represents the Regex using the `%r""` syntax.
## Examples
iex> inspect(%r/foo/m)
"%r\"foo\"m"
"""
def inspect(regex, opts) when size(regex) == 5 do
concat ["%r", Kernel.inspect(Regex.source(regex), opts), Regex.opts(regex)]
end
def inspect(other, opts) do
Kernel.inspect(other, opts.raw(true))
end
end
defimpl Inspect, for: Function do
def inspect(function, _opts) do
fun_info = :erlang.fun_info(function)
if fun_info[:type] == :external and fun_info[:env] == [] do
"function(#{Inspect.Atom.inspect(fun_info[:module])}.#{fun_info[:name]}/#{fun_info[:arity]})"
else
'#Fun' ++ rest = :erlang.fun_to_list(function)
"#Function" <> list_to_binary(rest)
end
end
end
defimpl Inspect, for: PID do
def inspect(pid, _opts) do
"#PID" <> list_to_binary(pid_to_list(pid))
end
end
defimpl Inspect, for: Port do
def inspect(port, _opts) do
list_to_binary :erlang.port_to_list(port)
end
end
defimpl Inspect, for: Reference do
def inspect(ref, _opts) do
'#Ref' ++ rest = :erlang.ref_to_list(ref)
"#Reference" <> list_to_binary(rest)
end
end
defimpl Inspect, for: HashDict do
def inspect(dict, opts) do
concat ["#HashDict<", Inspect.List.inspect(HashDict.to_list(dict), opts), ">"]
end
end
defimpl Inspect, for: HashSet do
def inspect(set, opts) do
concat ["#HashSet<", Inspect.List.inspect(HashSet.to_list(set), opts), ">"]
end
end
+390
View File
@@ -0,0 +1,390 @@
defmodule Inspect.Algebra do
@moduledoc %B"""
A set of functions for creating and manipulating algebra
documents, as described in ["Strictly Pretty" (2000) by Christian Lindig][0].
An algebra document is represented by an `Inspect.Algebra` node
or a regular string.
iex> Inspect.Algebra.empty
:doc_nil
iex> "foo"
"foo"
With the functions in this module, we can concatenate different
elements together and render them:
iex> doc = Inspect.Algebra.concat(Inspect.Algebra.empty, "foo")
iex> Inspect.Algebra.pretty(doc, 80)
"foo"
The functions `nest/2`, `space/2` and `line/2` help you put the
document together into a rigid structure. However, the document
algebra gets interesting when using functions like `break/2`, which
converts the given string into a line break depending on how much space
there is to print. Let's glue two docs together with a break and then
render it:
iex> doc = Inspect.Algebra.glue("a", " ", "b")
iex> Inspect.Algebra.pretty(doc, 80)
"a b"
Notice the break was represented as is, because we haven't reached
a line limit. Once we do, it is replaced by a newline:
iex> doc = Inspect.Algebra.glue(String.duplicate("a", 20), " ", "b")
iex> Inspect.Algebra.pretty(doc, 10)
"aaaaaaaaaaaaaaaaaaaa\nb"
Finally, this module also contains Elixir related functions, a bit
tied to Elixir formatting, namely `surround/3` and `surround_many/5`.
## Implementation details
The original Haskell implementation of the algorithm by [Wadler][1]
relies on lazy evaluation to unfold document groups on two alternatives:
`:flat` (breaks as spaces) and `:break` (breaks as newlines).
Implementing the same logic in a strict language such as Elixir leads
to an exponential growth of possible documents, unless document groups
are encoded explictly as `:flat` or `:break`. Those groups are then reduced
to a simple document, where the layout is already decided, per [Lindig][0].
This implementation slightly changes the semantic of Lindig's algorithm
to allow elements that belong to the same group to be printed together
in the same line, even if they do not fit the line fully. This was achieved
by changing `:break` to mean a possible break and `:flat` to force a flat
structure. Then deciding if a break works as a newline is just a matter
of checking if we have enough space until the next break that is not
inside a group (which is still flat).
Custom pretty printers can be implemented using the documents returned
by this module and by providing their own rendering functions.
[0]: http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.34.2200
[1]: http://homepages.inf.ed.ac.uk/wadler/papers/prettier/prettier.pdf
"""
@surround_separator ","
@tail_separator " |"
@newline "\n"
@nesting 1
@break " "
defp repeat(_, 0), do: ""
defp repeat(s, i), do: :lists.duplicate(i, s)
# Functional interface to `doc` records
@type doc :: :doc_nil | :doc_cons_t | :doc_nest_t | :doc_break_t | :doc_group_t | binary
defrecordp :doc_cons, [left: :doc_nil, right: :doc_nil]
defrecordp :doc_nest, [indent: 1, doc: :doc_nil]
defrecordp :doc_break, [str: " "]
defrecordp :doc_group, [doc: :doc_nil]
@doc """
Returns `:doc_nil` which is a document entity used to represent
nothingness. Takes no arguments.
## Examples
iex> Inspect.Algebra.empty
:doc_nil
"""
@spec empty() :: :doc_nil
def empty, do: :doc_nil
@doc """
Concatenates two document entities. Takes two arguments:
left doc and right doc. Returns a DocCons doc
## Examples
iex> doc = Inspect.Algebra.concat "Tasteless", "Artosis"
iex> Inspect.Algebra.pretty(doc, 80)
"TastelessArtosis"
"""
@spec concat(doc, doc) :: :doc_cons_t
def concat(x, y), do: doc_cons(left: x, right: y)
@doc """
Concatenates a list of documents.
"""
@spec concat([doc]) :: :doc_cons_t
def concat(docs), do: folddoc(docs, concat(&1, &2))
@doc """
Nests document entity `x` positions deep. Nesting will be
appended to the line breaks.
## Examples
iex> doc = Inspect.Algebra.nest(Inspect.Algebra.concat(Inspect.Algebra.break, "6"), 5)
iex> Inspect.Algebra.pretty(doc, 80)
" 6"
"""
@spec nest(doc, non_neg_integer) :: :doc_nest_t
def nest(x, 0), do: x
def nest(x, i) when is_integer(i), do: doc_nest(indent: i, doc: x)
@doc %B"""
Document entity representing a break. This break can
be rendered as a linebreak or as spaces, depending on the
`mode` of the chosen layout or the provided separator.
## Examples
Let's glue two docs together with a break and then render it:
iex> doc = Inspect.Algebra.glue("a", " ", "b")
iex> Inspect.Algebra.pretty(doc, 80)
"a b"
Notice the break was represented as is, because we haven't reached
a line limit. Once we do, it is replaced by a newline:
iex> doc = Inspect.Algebra.glue(String.duplicate("a", 20), " ", "b")
iex> Inspect.Algebra.pretty(doc, 10)
"aaaaaaaaaaaaaaaaaaaa\nb"
"""
@spec break(binary) :: :doc_break_t
def break(s) when is_binary(s), do: doc_break(str: s)
@spec break() :: :doc_break_t
def break(), do: doc_break(str: @break)
@doc """
Inserts a break between two docs. See `break/1` for more info.
"""
@spec glue(doc, doc) :: :doc_cons_t
def glue(x, y), do: concat(x, concat(break, y))
@doc """
Inserts a break, passed as the second argument, between two docs,
the first and the third arguments.
"""
@spec glue(doc, binary, doc) :: :doc_cons_t
def glue(x, g, y) when is_binary(g), do: concat(x, concat(break(g), y))
@doc %B"""
Returns a group containing the specified document.
## Examples
iex> doc = Inspect.Algebra.group(
...> Inspect.Algebra.concat(
...> Inspect.Algebra.group(
...> Inspect.Algebra.concat(
...> "Hello,",
...> Inspect.Algebra.concat(
...> Inspect.Algebra.break,
...> "A"
...> )
...> )
...> ),
...> Inspect.Algebra.concat(
...> Inspect.Algebra.break,
...> "B"
...> )
...> ))
iex> Inspect.Algebra.pretty(doc, 80)
"Hello, A B"
iex> Inspect.Algebra.pretty(doc, 6)
"Hello,\nA B"
"""
@spec group(doc) :: :doc_group_t
def group(d), do: doc_group(doc: d)
@doc """
Inserts a mandatory single space between two document entities.
## Examples
iex> doc = Inspect.Algebra.space "Hughes", "Wadler"
iex> Inspect.Algebra.pretty(doc, 80)
"Hughes Wadler"
"""
@spec space(doc, doc) :: :doc_cons_t
def space(x, y), do: concat(x, concat(" ", y))
@doc %B"""
Inserts a mandatory linebreak between two document entities.
## Examples
iex> doc = Inspect.Algebra.line "Hughes", "Wadler"
iex> Inspect.Algebra.pretty(doc, 80)
"Hughes\nWadler"
"""
@spec line(doc, doc) :: :doc_cons_t
def line(x, y), do: concat(x, concat(@newline, y))
@doc """
Folds a list of document entities into a document entity
using a function that is passed as the first argument.
## Examples
iex> doc = ["A", "B"]
iex> doc = Inspect.Algebra.folddoc(doc, fn(x,y) ->
...> Inspect.Algebra.concat [x, "!", y]
...> end)
iex> Inspect.Algebra.pretty(doc, 80)
"A!B"
"""
@spec folddoc([any], ((doc, [doc]) -> doc)) :: doc
def folddoc([], _), do: empty
def folddoc([doc], _), do: doc
def folddoc([d|ds], f), do: f.(d, folddoc(ds, f))
# Elixir conveniences
@doc %B"""
Surrounds a document with characters.
Puts the document between left and right enclosing and nesting it.
The document is marked as a group, to show the maximum as possible
concisely together.
## Examples
iex> doc = Inspect.Algebra.surround "[", Inspect.Algebra.glue("a", "b"), "]"
iex> Inspect.Algebra.pretty(doc, 3)
"[a\n b]"
"""
@spec surround(binary, doc, binary) :: doc
def surround(left, doc, right) do
group concat [left, nest(doc, @nesting), right]
end
@doc %B"""
Maps and glues a collection of items together using the given separator
and surrounds them. A limit can be passed which, once reached, stops
gluing and outputs "..." instead.
## Examples
iex> doc = Inspect.Algebra.surround_many("[", Enum.to_list(1..5), "]", :infinity, integer_to_binary(&1))
iex> Inspect.Algebra.pretty(doc, 5)
"[1,\n 2,\n 3,\n 4,\n 5]"
iex> doc = Inspect.Algebra.surround_many("[", Enum.to_list(1..5), "]", 3, integer_to_binary(&1))
iex> Inspect.Algebra.pretty(doc, 20)
"[1, 2, 3, ...]"
"""
@spec surround_many(binary, [any], binary, integer | :infinity, (term -> doc)) :: doc
def surround_many(left, [], right, _, _fun) do
concat(left, right)
end
def surround_many(left, docs, right, limit, fun) do
surround(left, surround_many(docs, limit, fun), right)
end
defp surround_many(_, 0, _fun) do
"..."
end
defp surround_many([h], _limit, fun) do
fun.(h)
end
defp surround_many([h|t], limit, fun) when is_list(t) do
glue(
concat(fun.(h), @surround_separator),
surround_many(t, decrement(limit), fun)
)
end
defp surround_many([h|t], _limit, fun) do
glue(
concat(fun.(h), @tail_separator),
fun.(t)
)
end
defp decrement(:infinity), do: :infinity
defp decrement(counter), do: counter - 1
@doc """
The pretty printing function.
Takes the maximum width and a document to print as its arguments
and returns the string representation of the best layout for the
document to fit in the given width.
"""
@spec pretty(doc, non_neg_integer) :: binary
def pretty(d, w) do
sdoc = format w, 0, [{0, default_mode(w), doc_group(doc: d)}]
render(sdoc)
end
defp default_mode(:infinity), do: :flat
defp default_mode(_), do: :break
# Rendering and internal helpers
# Records representing __simple__ documents, already on a fixed layout
# Those are generalized by `sdoc` type.
@type sdoc :: :s_nil | :s_text_t | :s_line_t
defrecordp :s_text, [str: "", sdoc: :s_nil]
defrecordp :s_line, [indent: 1, sdoc: :s_nil] # newline + spaces
# Record representing the document mode to be rendered: __flat__ or __broken__
@type mode :: :flat | :break
@doc false
@spec fits?(integer, [{ integer, mode, doc }]) :: boolean
def fits?(:infinity, _), do: true # no pretty printing
def fits?(w, _) when w < 0, do: false
def fits?(_, []), do: true
def fits?(w, [{_, _, :doc_nil} | t]), do: fits?(w, t)
def fits?(w, [{i, m, doc_cons(left: x, right: y)} | t]), do: fits?(w, [{i, m, x} | [{i, m, y} | t]])
def fits?(w, [{i, m, doc_nest(indent: j, doc: x)} | t]), do: fits?(w, [{i + j, m, x} | t])
def fits?(w, [{_, _, s} | t]) when is_binary(s), do: fits?((w - byte_size s), t)
def fits?(w, [{_, :flat, doc_break(str: s)} | t]), do: fits?((w - byte_size s), t)
def fits?(_, [{_, :break, doc_break(str: _)} | _]), do: true
def fits?(w, [{i, _, doc_group(doc: x)} | t]), do: fits?(w, [{i, :flat, x} | t])
@doc false
@spec format(integer, integer, [{ integer, mode, doc }]) :: atom | tuple
def format(_, _, []), do: :s_nil
def format(w, k, [{_, _, :doc_nil} | t]), do: format(w, k, t)
def format(w, k, [{i, m, doc_cons(left: x, right: y)} | t]), do: format(w, k, [{i, m, x} | [{i, m, y} | t]])
def format(w, k, [{i, m, doc_nest(indent: j, doc: x)} | t]), do: format(w, k, [{i + j, m, x} | t])
def format(w, k, [{_, _, s} | t]) when is_binary(s), do: s_text(str: s, sdoc: format(w, (k + byte_size s), t))
def format(w, k, [{i, m, doc_group(doc: x)} | t]), do: format(w, k, [{i, m, x} | t])
def format(w, k, [{_, :flat, doc_break(str: s)} | t]), do: s_text(str: s, sdoc: format(w, (k + byte_size s), t))
def format(w, k, [{i, :break, doc_break(str: s)} | t]) do
k = k + byte_size(s)
if fits?(w - k, t) do
s_text(str: s, sdoc: format(w, k, t))
else
s_line(indent: i, sdoc: format(w, i, t))
end
end
@doc false
@spec render(sdoc) :: binary
def render(sdoc) do
iolist_to_binary do_render sdoc
end
@spec do_render(sdoc) :: [binary]
defp do_render(:s_nil), do: [""]
defp do_render(s_text(str: s, sdoc: d)), do: [s | do_render(d)]
defp do_render(s_line(indent: i, sdoc: d)) do
prefix = repeat " ", i
[@newline | [prefix | do_render d]]
end
end
+99 -27
View File
@@ -26,7 +26,8 @@ defmodule IO do
import :erlang, only: [group_leader: 0]
@doc """
Reads `count` bytes from the IO device. It returns:
Reads `count` characters from the IO device or until
the end of the line if `:line` is given. It returns:
* `data` - The input characters.
@@ -36,48 +37,53 @@ defmodule IO do
for instance {:error, :estale} if reading from an
NFS file system.
"""
def read(device // group_leader(), count) do
def read(device // group_leader, chars_or_line)
def read(device, :line) do
:io.get_line(map_dev(device), "")
end
def read(device, count) when count >= 0 do
:io.get_chars(map_dev(device), "", count)
end
@doc """
Reads `count` bytes from the IO device as binary,
no unicode conversion happens.
Reads `count` bytes from the IO device or until
the end of the line if `:line` is given. It returns:
Check `read/2` for more information.
* `data` - The input characters.
* :eof - End of file was encountered.
* {:error, reason} - Other (rare) error condition,
for instance {:error, :estale} if reading from an
NFS file system.
"""
def binread(device // group_leader(), count) do
def binread(device // group_leader, chars_or_line)
def binread(device, :line) do
case :file.read_line(map_dev(device)) do
{ :ok, data } -> data
other -> other
end
end
def binread(device, count) when count >= 0 do
case :file.read(map_dev(device), count) do
{ :ok, data } -> data
other -> other
end
end
@doc """
Reads a line from the IO device. It returns:
* `data` - The input characters.
* :eof - End of file was encountered.
* {:error, reason} - Other (rare) error condition,
for instance {:error, :estale} if reading from an
NFS file system.
This function does the same as `gets/2`,
except the prompt is not required as argument.
"""
@doc false
def readline(device // group_leader()) do
IO.write "[WARNING] IO.readline(device) is deprecated, please use IO.read(device, :line) instead\n#{Exception.format_stacktrace}"
:io.get_line(map_dev(device), "")
end
@doc """
Reads a line from the IO device as binary,
no unicode conversion happens.
Check `readline/1` for more information.
"""
@doc false
def binreadline(device // group_leader()) do
IO.write "[WARNING] IO.binreadline(device) is deprecated, please use IO.binread(device, :line) instead\n#{Exception.format_stacktrace}"
case :file.read_line(map_dev(device)) do
{ :ok, data } -> data
other -> other
@@ -129,6 +135,10 @@ defmodule IO do
Inspects and writes the given argument to the device
followed by a new line. A set of options can be given.
It sets by default pretty printing to true and the
width to be the width of the device, with a minimum
of 80 characters.
## Examples
IO.inspect Process.list
@@ -141,7 +151,16 @@ defmodule IO do
@doc """
Inspects the item with options using the given device.
"""
def inspect(device, item, opts) do
def inspect(device, item, opts) when is_list(opts) do
opts = Keyword.put_new(opts, :pretty, true)
unless Keyword.get(opts, :width) do
opts = case :io.columns(device) do
{ :ok, width } -> Keyword.put(opts, :width, width)
{ :error, _ } -> opts
end
end
puts device, Kernel.inspect(item, opts)
item
end
@@ -196,6 +215,59 @@ defmodule IO do
:io.get_line(map_dev(device), to_iodata(prompt))
end
@doc """
Converts the io device into a Stream. The device is
iterated line by line.
This reads the io as utf-8. Check out
`IO.binstream/1` to handle the IO as a raw binary.
## Examples
Here is an example on how we mimic an echo server
from the command line:
Enum.each IO.stream(:stdio), IO.write(&1)
"""
def stream(device) do
stream(map_dev(device), &1, &2)
end
@doc """
Converts the io device into a Stream. The device is
iterated line by line.
This reads the io as a raw binary.
"""
def binstream(device) do
binstream(map_dev(device), &1, &2)
end
@doc false
def stream(device, acc, fun) do
case read(device, :line) do
:eof ->
acc
{ :error, reason } ->
raise File.IteratorError, reason: reason
data ->
stream(device, fun.(data, acc), fun)
end
end
@doc false
def binstream(device, acc, fun) do
case binread(device, :line) do
:eof ->
acc
{ :error, reason } ->
raise File.IteratorError, reason: reason
data ->
binstream(device, fun.(data, acc), fun)
end
end
# Map the Elixir names for standard io and error to Erlang names
defp map_dev(:stdio), do: :standard_io
defp map_dev(:stderr), do: :standard_error
+11 -15
View File
@@ -2,18 +2,14 @@ defmodule IO.ANSI.Sequence do
@moduledoc false
defmacro defsequence(name, code) do
quote do
name = unquote(name)
code = unquote(code)
quote bind_quoted: [name: name, code: code] do
def unquote(name)() do
"\e[#{unquote(code)}m"
end
def name, [], [], do:
quote do: "\e[#{unquote(code)}m"
args =
quote do: [<< unquote(atom_to_binary(name)), rest :: binary >> ]
defp :escape_sequence, args, [], do:
quote do: { "\e[#{unquote(code)}m", rest }
defp escape_sequence(<< unquote(atom_to_binary(name)), rest :: binary >>) do
{ "\e[#{unquote(code)}m", rest }
end
end
end
end
@@ -76,7 +72,7 @@ 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
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
@@ -151,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
@@ -177,9 +173,9 @@ defmodule IO.ANSI do
## Example
iex> IO.ANSI.escape_fragment("Hello %{red,bright,green}yes")
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")
iex> IO.ANSI.escape_fragment("%{reset}bye", true)
"\e[0mbye"
"""
+644 -320
View File
File diff suppressed because it is too large Load Diff
+29 -26
View File
@@ -107,37 +107,37 @@ defmodule Kernel.CLI do
System.halt 0
end
defp process_shared(["-pa",h|t], config) do
defp process_shared(["-pa", h|t], config) do
Enum.each Path.wildcard(Path.expand(h)), Code.prepend_path(&1)
process_shared t, config
end
defp process_shared(["-pz",h|t], config) do
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]
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(["-r",h|t], config) do
process_shared t, config.update_commands [{:require,h}|&1]
defp process_shared(["-r", h|t], config) do
process_shared t, config.update_commands [{:require, h}|&1]
end
defp process_shared(["-pr",h|t], config) do
process_shared t, config.update_commands [{:parallel_require,h}|&1]
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", "--name", "--cookie"] do
defp process_shared([erl, _|t], config) when erl in ["--erl", "--sname", "--name", "--cookie"] do
process_shared t, config
end
@@ -159,8 +159,8 @@ defmodule Kernel.CLI do
process_iex t, config
end
defp process_argv(["-S",h|t], config) do
{ config.update_commands([{:script,h}|&1]), t }
defp process_argv(["-S", h|t], config) do
{ config.update_commands([{:script, h}|&1]), t }
end
defp process_argv([h|t] = list, config) do
@@ -168,7 +168,7 @@ defmodule Kernel.CLI do
"-" <> _ ->
shared_option? list, config, process_argv(&1, &2)
_ ->
{ config.update_commands([{:file,h}|&1]), t }
{ config.update_commands([{:file, h}|&1]), t }
end
end
@@ -182,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
@@ -209,7 +213,7 @@ defmodule Kernel.CLI do
end
defp process_compiler([], config) do
{ config.update_commands([{:compile,config.compile}|&1]), [] }
{ config.update_commands([{:compile, config.compile}|&1]), [] }
end
# Process iex options
@@ -220,16 +224,16 @@ defmodule Kernel.CLI do
# This clause is here so that Kernel.CLI does not error out with "unknown
# option"
defp process_iex(["--dot-iex",_|t], config) do
defp process_iex(["--dot-iex", _|t], config) do
process_iex t, config
end
defp process_iex([opt,_|t], config) when opt in ["--remsh"] do
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 }
defp process_iex(["-S", h|t], config) do
{ config.update_commands([{:script, h}|&1]), t }
end
defp process_iex([h|t] = list, config) do
@@ -237,7 +241,7 @@ defmodule Kernel.CLI do
"-" <> _ ->
shared_option? list, config, process_iex(&1, &2)
_ ->
{ config.update_commands([{:file,h}|&1]), t }
{ config.update_commands([{:file, h}|&1]), t }
end
end
@@ -247,7 +251,7 @@ defmodule Kernel.CLI do
# Process commands
defp process_command({:cookie,h}, _config) do
defp process_command({:cookie, h}, _config) do
if Node.alive? do
Node.set_cookie(binary_to_atom(h))
:ok
@@ -325,7 +329,6 @@ defmodule Kernel.CLI do
Code.compiler_options(config.compiler_options)
Kernel.ParallelCompiler.files_to_path(files, config.output,
fn file -> IO.puts "Compiled #{file}" end)
:ok
else
{ :error, "--compile : No files matched patterns #{Enum.join(patterns, ",")}" }
end
+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
+10 -1
View File
@@ -119,7 +119,16 @@ defmodule Kernel.RecordRewriter do
## Expr
defp optimize_expr({ :call, call_line, { :remote, line, left, right }, args }, module, dict) do
defp optimize_expr({ :call, call_line,
{ :remote, _, { :atom, _, :erlang }, { :atom, _, :setelement } } = remote,
[{ :integer, _, pos } = integer, tuple, value] }, module, dict) when pos > 1 do
{ tuple, dict, res } = optimize_expr(tuple, module, dict)
{ value, dict, _ } = optimize_expr(value, module, dict)
{ { :call, call_line, remote, [integer, tuple, value] }, dict, res }
end
defp optimize_expr({ :call, call_line, { :remote, line, left, right }, args } = call, module, dict) do
{ left, dict, res } = optimize_expr(left, module, dict)
{ right, dict, _ } = optimize_expr(right, module, dict)
{ args, dict } = optimize_args(args, module, dict)
+105 -13
View File
@@ -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
@@ -387,7 +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;
* `:context` - Sets the resolution context;
* `:binding` - Passes a binding to the macro. Whenever a binding is given,
unquote is automatically disabled;
## Macro literals
@@ -397,7 +399,7 @@ 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
@@ -629,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 `:bind_quoted`
option comes to the rescue. By using `:bind_quoted`, we can
automatically disable unquoting while still injecting the
desired variables into the tree:
defmacro defkv(kv) do
quote bind_quoted: [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)
@@ -643,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)
@@ -686,7 +765,7 @@ defmodule Kernel.SpecialForms do
## Examples
values = [2,3,4]
values = [2, 3, 4]
quote do: sum(1, unquote_splicing(values), 5)
#=> { :sum, [], [1, 2, 3, 4, 5] }
@@ -697,7 +776,7 @@ defmodule Kernel.SpecialForms do
@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
@@ -705,35 +784,48 @@ 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}]
Note: Differently from Erlang, Elixir comprehensions filters
never behave as guards when it comes to errors. Errors in
list comprehensions will always be raised. Consider this
Erlang example:
erl> [I || I <- [1,2,3], hd(I)].
[]
In Elixir, it will raise:
iex> lc i inlist [1,2,3], hd(i), do: i
** (ArgumentError) argument error
"""
defmacro lc(args)
@@ -755,7 +847,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)
+68 -68
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:
@@ -50,22 +50,16 @@ defmodule Kernel.Typespec do
Any anonymous function:
(fun(...) -> any)
or
((...) -> any)
or
(... -> any)
Anonymous function with arity of zero:
(fun() -> type)
or
(() -> type)
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
@@ -175,7 +169,7 @@ defmodule Kernel.Typespec do
Module.compile_typespec module, kind, type
if export, do:
Module.compile_typespec module, :export_type, [{ name, length(vars) }]
Module.compile_typespec(module, :export_type, [{ name, length(vars) }])
type
end
@@ -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
@@ -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)
@@ -355,9 +349,9 @@ defmodule Kernel.Typespec do
do_deftype(kind, type, { :term, [line: caller.line], nil }, caller)
end
def deftype(_kind, other, _caller) do
type_spec = Macro.to_binary(other)
raise ArgumentError, message: "invalid type specification #{type_spec}"
def deftype(_kind, other, caller) do
type_spec = Macro.to_string(other)
compile_error caller, "invalid type specification #{type_spec}"
end
defp do_deftype(kind, { name, _, args }, definition, caller) do
@@ -396,9 +390,9 @@ defmodule Kernel.Typespec do
code
end
def defspec(_type, other, _caller) do
spec = Macro.to_binary(other)
raise ArgumentError, message: "invalid function type specification #{spec}"
def defspec(_type, other, caller) do
spec = Macro.to_string(other)
compile_error caller, "invalid function type specification #{spec}"
end
defp guard_to_constraints({ :is_subtype, meta, [{ name, _, _ }, type] }, caller) do
@@ -451,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
@@ -475,12 +469,12 @@ 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
@@ -533,14 +527,14 @@ defmodule Kernel.Typespec do
end
# Handle unions
defp typespec({ :|, meta, [_,_] } = exprs, vars, caller) do
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
@@ -568,11 +562,11 @@ 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
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
@@ -597,9 +591,11 @@ defmodule Kernel.Typespec do
end
# Handle remote calls
defp typespec({{:., meta, [remote, name]}, _, args}, vars, caller) do
defp typespec({{:., meta, [remote, name]}, _, args} = orig, vars, caller) do
remote = Macro.expand remote, caller
unless is_atom(remote), do: raise ArgumentError, message: "invalid remote in typespec"
unless is_atom(remote) do
compile_error(caller, "invalid remote in typespec: #{Macro.to_string(orig)}")
end
remote_type({typespec(remote, vars, caller), meta, typespec(name, vars, caller), args}, vars, caller)
end
@@ -674,8 +670,8 @@ defmodule Kernel.Typespec do
end
defp typespec([h|t] = l, vars, caller) do
union = Enum.reduce(t, validate_kw(h, l), fn(x, acc) ->
{ :|, [], [acc, validate_kw(x, l)] }
union = Enum.reduce(t, validate_kw(h, l, caller), fn(x, acc) ->
{ :|, [], [acc, validate_kw(x, l, caller)] }
end)
typespec({ :list, [], [union] }, vars, caller)
end
@@ -687,6 +683,10 @@ defmodule Kernel.Typespec do
## Helpers
defp compile_error(caller, desc) do
raise CompileError, file: caller.file, line: caller.line, description: desc
end
defp remote_type({remote, meta, name, arguments}, vars, caller) do
arguments = lc arg inlist arguments, do: typespec(arg, vars, caller)
{ :remote_type, line(meta), [ remote, name, arguments ] }
@@ -695,14 +695,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"
defp validate_kw({ key, _ } = t, _, _caller) when is_atom(key), do: t
defp validate_kw(_, original, caller) do
compile_error(caller, "unexpected list #{Macro.to_string 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
@@ -724,11 +724,11 @@ defmodule Kernel.Typespec do
next,
{ :type, _, :tuple, [{ :atom, _, atom }, type] }
] }, acc) do
unpack_typespec_kw(next, [{atom,typespec_to_ast(type)}|acc])
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] }
{ :ok, [{atom, typespec_to_ast(type)}|acc] }
end
defp unpack_typespec_kw(_, _acc) do
+39 -23
View File
@@ -18,8 +18,8 @@ defmodule Keyword do
@type t :: [{key, value}]
@doc """
Creates a Keyword from enum. Differently from `Keyword.new`
which behaves as a dict, `Keyword.from_enum` do not remove
Creates a Keyword from an enum. Unlike `Keyword.new`
which behaves as a dict, `Keyword.from_enum` does not remove
duplicated entries.
"""
@spec from_enum(Enum.t) :: t
@@ -51,12 +51,12 @@ defmodule Keyword do
end
@doc """
Creates a Keyword from an enumerable. Similarly to dicts,
Creates a Keyword from an enumerable. Similar to dicts,
duplicated entries are removed, the latest one prevails.
## Examples
iex> Keyword.new([{:b,1},{:a,2}])
iex> Keyword.new([{:b, 1}, {:a, 2}])
[a: 2, b: 1]
"""
@@ -74,7 +74,7 @@ defmodule Keyword do
## Examples
iex> Keyword.new([:a, :b], fn (x) -> {x,x} end) |> Enum.sort
iex> Keyword.new([:a, :b], fn (x) -> {x, x} end) |> Enum.sort
[a: :a, b: :b]
"""
@@ -87,20 +87,21 @@ defmodule Keyword do
end
@doc """
Gets the value for specific key.
Gets the value for a specific `key`.
If key not exist return default value (nil if no default value)
exists.
If `key` does not exist, return default value (`nil` if no default value).
If duplicated entries exist, the first one is returned.
Use get_values/2 to retrieve all entries.
Use `get_values/2` to retrieve all entries.
## Examples
iex> Keyword.get([a: 1], :a)
1
iex> Keyword.get([a: 1], :b)
nil
iex> Keyword.get([a: 1], :b, 3)
3
@@ -115,8 +116,8 @@ defmodule Keyword do
end
@doc """
Fetchs the value for specific key and return it in a tuple.
If the key does not exist, returns `:error`.
Fetches the value for a specific `key` and returns it in a tuple.
If the `key` does not exist, returns `:error`.
## Examples
@@ -136,8 +137,8 @@ defmodule Keyword do
end
@doc """
Fetches the value for specific key. If key does not exist,
an error is raised.
Fetches the value for specific `key`. If `key` does not exist,
a `KeyError` is raised.
## Examples
@@ -157,7 +158,7 @@ defmodule Keyword do
end
@doc """
Gets all values for a specific key.
Gets all values for a specific `key`.
## Examples
@@ -179,6 +180,9 @@ defmodule Keyword do
iex> Keyword.keys([a: 1, b: 2])
[:a,:b]
iex> Keyword.keys([a: 1, b: 2, a: 3])
[:a,:b,:a]
"""
@spec keys(t) :: [key]
def keys(keywords) do
@@ -186,7 +190,7 @@ defmodule Keyword do
end
@doc """
Returns all values.
Returns all values from the keyword list.
## Examples
@@ -200,8 +204,8 @@ defmodule Keyword do
end
@doc """
Deletes all entries in the keyword list for a specific key.
If the key does not exist, returns the keyword list unchanged.
Deletes all entries in the keyword list for a specific `key`.
If the `key` does not exist, returns the keyword list unchanged.
Use `delete_first` to delete just the first entry in case of
duplicated keys.
@@ -209,6 +213,10 @@ defmodule Keyword do
iex> Keyword.delete([a: 1, b: 2], :a)
[b: 2]
iex> Keyword.delete([a: 1, b: 2, a: 3], :a)
[b: 2]
iex> Keyword.delete([b: 2], :a)
[b: 2]
@@ -219,13 +227,14 @@ defmodule Keyword do
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.
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]
@@ -246,6 +255,9 @@ defmodule Keyword do
iex> Keyword.put([a: 1, b: 2], :a, 3)
[a: 3, b: 2]
iex> Keyword.put([a: 1, b: 2, a: 4], :a, 3)
[a: 3, b: 2]
"""
@spec put(t, key, value) :: t
def put(keywords, key, value) when is_atom(key) do
@@ -257,6 +269,8 @@ defmodule Keyword do
already exists.
## Examples
iex> Keyword.put_new([a: 1], :b, 2)
[b: 2, a: 1]
iex> Keyword.put_new([a: 1, b: 2], :a, 3)
[a: 1, b: 2]
@@ -266,7 +280,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
@@ -326,12 +340,13 @@ defmodule Keyword do
end
@doc """
Returns whether a given key exists in the given keywords.
Returns whether a given `key` exists in the given `keywords`.
## Examples
iex> Keyword.has_key?([a: 1], :a)
true
iex> Keyword.has_key?([a: 1], :b)
false
@@ -342,7 +357,7 @@ defmodule Keyword do
end
@doc """
Updates the key with the given function. If the key does
Updates the `key` with the given function. If the `key` does
not exist, raises `KeyError`.
## Examples
@@ -368,13 +383,14 @@ defmodule Keyword do
end
@doc """
Updates the key with the given function. If the key does
Updates the `key` with the given function. If the `key` does
not exist, inserts the given `initial` value.
## Examples
iex> Keyword.update([a: 1], :a, 13, &1 * 2)
[a: 2]
iex> Keyword.update([a: 1], :b, 11, &1 * 2)
[a: 1, b: 11]
+12 -10
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]
"""
@@ -63,7 +65,7 @@ defmodule List do
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
"""
@@ -241,7 +243,7 @@ defmodule List do
## Examples
iex> List.wrap([1,2,3])
iex> List.wrap([1, 2, 3])
[1,2,3]
"""
+4 -75
View File
@@ -15,7 +15,7 @@ defmodule ListDict do
Creates a new `ListDict` from the given pairs.
"""
def new(pairs) do
Enum.map pairs, fn({ k, v }) -> { k, v } end
Enum.to_list pairs
end
@doc """
@@ -26,38 +26,22 @@ defmodule ListDict do
Enum.map list, transform
end
@doc """
Returns all keys in the dict.
"""
def keys(dict) do
lc { key, _ } inlist dict, do: key
end
@doc """
Returns all values in the dict.
"""
def values(dict) do
lc { _, value } inlist dict, do: value
end
@doc """
Returns the dict size.
"""
def size(dict) do
length(dict)
end
@doc """
Returns true if the dict has the given key.
"""
def has_key?(dict, key) do
:lists.keymember(key, 1, dict)
end
@doc """
Returns the value under key from the given
dict or default if no key is set.
"""
def get(dict, key, default // nil) do
case :lists.keyfind(key, 1, dict) do
{ ^key, value } -> value
@@ -65,10 +49,6 @@ defmodule ListDict 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 }
@@ -76,10 +56,6 @@ defmodule ListDict do
end
end
@doc """
Returns the value under the given key
raises `KeyError` if the key does not exist.
"""
def fetch!(dict, key) do
case :lists.keyfind(key, 1, dict) do
{ ^key, value } -> value
@@ -87,42 +63,25 @@ defmodule ListDict do
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.
"""
def put(dict, key, val) do
[{key, val}|delete(dict, key)]
end
@doc """
Puts the given key-value pair in the dict
if no entry exists yet.
"""
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
@doc """
Deletes the entry under the given key from the dict.
"""
def delete(dict, key) do
lc { k, _ } = tuple inlist dict, key != k, do: tuple
end
@doc """
Merges the given Enumerable into the dict.
"""
def merge(dict, enum, callback // fn(_k, _v1, v2) -> v2 end)
def merge(dict1, dict2, fun) do
@@ -131,42 +90,25 @@ defmodule ListDict do
end
end
@doc """
Splits a dict into two dicts,
one containing entries with key in the keys list,
and another containing entries with key not in keys.
Returns a 2-tuple of the new dicts.
"""
def split(dict, keys) do
acc = { new(), new() }
Enum.reduce dict, acc, fn({ k, v }, { take, drop }) ->
if :lists.member(k, keys) do
{ [{k,v}|take], drop }
{ [{k, v}|take], drop }
else
{ take, [{k,v}|drop] }
{ take, [{k, v}|drop] }
end
end
end
@doc """
Returns a new dict with only the entries
which key is in keys
"""
def take(dict, keys) do
lc { k, _ } = tuple inlist dict, :lists.member(k, keys), do: tuple
end
@doc """
Returns a new dict with only the entries
which key is not in keys
"""
def drop(dict, keys) do
lc { k, _ } = tuple inlist dict, not :lists.member(k, keys), do: tuple
end
@doc """
Updates the key in the dict according to the given function.
"""
def update([{key, value}|dict], key, fun) do
[{key, fun.(value)}|delete(dict, key)]
end
@@ -179,10 +121,6 @@ defmodule ListDict do
raise(KeyError, key: key)
end
@doc """
Updates the key in the dict according to the given function
or uses the given initial value if no entry exists.
"""
def update([{key, value}|dict], key, _initial, fun) do
[{key, fun.(value)}|delete(dict, key)]
end
@@ -195,20 +133,11 @@ defmodule ListDict do
[{key, initial}]
end
@doc """
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.
"""
def to_list(dict), do: dict
end
+213 -126
View File
@@ -1,10 +1,12 @@
import Kernel, except: [to_binary: 1]
import Kernel, except: [to_string: 1]
defmodule Macro do
@moduledoc """
This module provides conveniences for working with macros.
"""
@type t :: { t, t } | { t, Keyword.t, t } | atom | number | binary | list
@doc """
Returns a list of binary operators. This is available
as a macro so it can be used in guard clauses.
@@ -14,10 +16,10 @@ defmodule Macro do
:===, :!==,
:==, :!=, :<=, :>=,
:&&, :||, :<>, :++, :--, :**, ://, :::, :<-, :.., :|>, :=~,
:<, :>,
:<, :>, :->,
:+, :-, :*, :/, :=, :|, :.,
:and, :or, :xor, :when, :in, :inlist, :inbits,
:<<<, :>>>, :|||, :&&&, :^^^, :~~~
:<<<, :>>>, :|||, :&&&, :^^^
]
end
@@ -26,7 +28,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 +44,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 +56,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,14 +68,12 @@ defmodule Macro do
iex> Macro.escape({ :a, :b, :c })
{ :{}, [], [:a, :b, :c] }
"""
def escape(expr) do
:elixir_quote.escape(expr, false) |> elem(0)
end
iex> Macro.escape({ :unquote, [], [1] }, unquote: true)
1
@doc false
def escape_quoted(expr) do
:elixir_quote.escape(expr, true) |> elem(0)
"""
def escape(expr, opts // []) do
:elixir_quote.escape(expr, Keyword.get(opts, :unquote, false)) |> elem(0)
end
@doc %B"""
@@ -151,7 +155,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
@@ -166,112 +170,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: inspect(other, raw: true)
def to_string(other), do: inspect(other, raw: true)
# Block keywords
defmacrop kw_keywords, do: [:do, :catch, :rescue, :after, :else]
@@ -281,56 +297,56 @@ defmodule Macro do
end
defp is_kw_blocks?(_), do: false
defp module_to_binary(atom) when is_atom(atom), do: inspect(atom, raw: true)
defp module_to_binary(other), do: call_to_binary(other)
defp 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
@@ -338,7 +354,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
@@ -351,32 +367,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
@@ -439,46 +441,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
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
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({ :__DIR__, _, atom }, env, _cache) when is_atom(atom), do: :filename.dirname(env.file)
defp expand({ :__ENV__, _, atom }, env, _cache) when is_atom(atom), do: env
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
@@ -488,43 +494,124 @@ 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 %B"""
Receives a AST node and expands it until it no longer represents
a macro. Then it expands all of its children recursively.
The documentation for `Macro.expand_once/2` goes into more detail
on how expansion works. Keep in mind that `Macro.expand_all/2` is
naive as it doesn't mutate the environment. Take this example:
quoted = quote do: __MODULE__
Macro.to_string Macro.expand_all(quoted, __ENV__)
#=> "nil"
The example above, works as expected. Outside of a module,
`__MODULE__` returns nil. Now consider this variation:
quoted = quote do
defmodule Foo, do: __MODULE__
end
Macro.to_string Macro.expand_all(quoted, __ENV__)
#=> "defmodule(Foo) do\n nil\nend"
One would expect `__MODULE__` to be expanded to `Foo`
but that is not the case since all the expansion is done
based on the environment `__ENV__`. This behaviour is on
purpose, as the proper expansion would require passing through
the whole Elixir compiler by actually executing the code, which
is beyond the scope of this function.
"""
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
+4 -2
View File
@@ -21,6 +21,7 @@ defmodule Macro.Env do
* `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
* `vars` - a list keeping all defined varaibles as { var, context }
"""
@type name_arity :: { atom, non_neg_integer }
@@ -32,14 +33,15 @@ defmodule Macro.Env do
@type functions :: [{ module, [name_arity] }]
@type macros :: [{ module, [name_arity] }]
@type context_modules :: [module]
@type vars :: [{ atom, atom }]
fields = [:module, :file, :line, :function, :aliases, :context, :requires,
:functions, :macros, :context_modules, :macro_aliases]
:functions, :macros, :context_modules, :macro_aliases, :vars]
types = quote do: [module: module, file: file, line: line,
function: name_arity, aliases: aliases, requires: requires,
functions: functions, macros: macros, context_modules: context_modules,
macro_aliases: aliases]
macro_aliases: aliases, vars: vars]
Record.deffunctions(fields, __MODULE__)
Record.deftypes(fields, types, __MODULE__)
+52 -46
View File
@@ -1,6 +1,4 @@
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
@@ -192,7 +190,7 @@ defmodule Module do
IO.puts "and guards"
IO.inspect guards
IO.puts "and body"
IO.puts Macro.to_binary(body)
IO.puts Macro.to_string(body)
end
end
@@ -304,7 +302,7 @@ defmodule Module do
"""
def open?(module) do
table = data_table_for(module)
table == ETS.info(table, :name)
table == :ets.info(table, :name)
end
@doc """
@@ -492,12 +490,12 @@ 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 }] ->
ETS.insert(table, {
:ets.insert(table, {
tuple,
line,
kind,
@@ -577,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 """
@@ -597,7 +595,7 @@ 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
case :ets.lookup(table, tuple) do
[{ _, ^kind, _, _, _, _, _ }] -> true
_ -> false
end
@@ -617,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 """
@@ -636,7 +634,7 @@ defmodule Module do
def definitions_in(module, kind) do
assert_not_compiled!(:definitions_in, module)
table = function_table_for(module)
lc { tuple, stored_kind, _, _, _, _, _ } inlist ETS.tab2list(table), stored_kind == kind, do: tuple
lc { tuple, stored_kind, _, _, _, _, _ } inlist :ets.tab2list(table), stored_kind == kind, do: tuple
end
@doc """
@@ -690,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 :lists.member(key, acc) do
case ETS.lookup(table, key) do
[{^key,old}] -> [value|old]
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
@@ -722,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 :lists.member(key, acc), do: [], else: nil
acc = :ets.lookup_element(table, :__acc_attributes, 2)
if :lists.member(key, acc) do
[]
else
warn && :elixir_errors.warn "#{Exception.format_caller} undefined module attribute @#{key}, " <>
"please remove access to @#{key} or explicitly set it to nil before access\n"
nil
end
end
end
@@ -748,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 """
@@ -767,9 +785,7 @@ defmodule Module do
* `:persist` - The attribute will be persisted in the Erlang
Abstract Format. Useful when interfacing with Erlang libraries.
By default, both options are true. Which means that registering
an attribute without passing any options will revert the attribute
behavior to exactly the same expected in :
By default, both options are false.
## Examples
@@ -784,18 +800,18 @@ defmodule Module do
end
"""
def register_attribute(module, new, opts // []) do
def register_attribute(module, new, opts) when is_atom(new) do
assert_not_compiled!(:register_attribute, module)
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] })
if Keyword.get(opts, :persist) do
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] })
if Keyword.get(opts, :accumulate) do
old = :ets.lookup_element(table, :__acc_attributes, 2)
:ets.insert(table, { :__acc_attributes, [new|old] })
end
end
@@ -812,16 +828,6 @@ defmodule Module do
tl(String.split(Binary.Chars.to_binary(module), "."))
end
@doc """
Convert a module name to binary without the Elixir prefix.
"""
def to_binary(Elixir), do: "Elixir"
def to_binary(module) do
"Elixir." <> rest = Binary.Chars.to_binary(module)
rest
end
@doc false
# Used internally to compile documentation. This function
# is private and must be used only internally.
@@ -836,7 +842,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"
:elixir_errors.warn "#{env.file}:#{line} function #{name}/#{arity} is private, @doc's are always discarded for private functions\n"
end
delete_attribute(module, :doc)
@@ -850,12 +856,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
+36 -17
View File
@@ -1,7 +1,11 @@
defmodule OptionParser do
@moduledoc """
This module contains functions to parse command line arguments.
"""
@doc """
Parses the argv and returns one tuple with parsed options
and the arguments.
Parses `argv` and returns a tuple with parsed options
and arguments.
## Examples
@@ -16,26 +20,28 @@ defmodule OptionParser do
## Aliases
A set of aliases can be given as second argument:
A set of aliases can be given as the second argument:
iex> OptionParser.parse(["-d"], aliases: [d: :debug])
{ [debug: true], [] }
## Switches
Extra information about switches can be given as argument too.
This is useful in order to say a switch must behave as a boolean
Extra information about switches can be given as arguments, too.
This is useful when a switch must behave as a boolean
or if duplicated switches should be kept, overriden or accumulated.
The following types are supported:
* `:boolean` - Mark the given switch as boolean. Boolean switches
never consumes the following value unless it is
true or false;
* `:boolean` - Marks the given switch as a boolean. Boolean switches
never consume the following value unless it is
`true` or `false`;
* `:integer` - Parses the switch as an integer;
* `:float` - Parses the switch as a float;
The following extra options are supported:
* `:keep` - Keep duplicated items in the list instead of overriding;
* `:keep` - Keeps duplicated items in the list instead of overriding;
Examples:
@@ -59,16 +65,18 @@ defmodule OptionParser do
end
@doc """
Similar to parse but only parses the head of the argv.
I.e. as soon as it finds a non switch, it stops parsing.
Similar to `parse/2` but only parses the head of `argv`;
as soon as it finds a non-switch, it stops parsing.
Check `parse/2` for more info.
See `parse/2` for more information.
## Example
iex> OptionParser.parse_head(["--source", "lib", "test/enum_test.exs", "--verbose"])
{ [source: "lib"], ["test/enum_test.exs", "--verbose"] }
iex> OptionParser.parse_head(["--verbose", "--source", "lib", "test/enum_test.exs", "--unlock"])
{[verbose: true, source: "lib"], ["test/enum_test.exs", "--unlock"]}
"""
def parse_head(argv, opts // []) when is_list(argv) and is_list(opts) do
parse(argv, opts, false)
@@ -127,10 +135,21 @@ defmodule OptionParser do
end
defp store_option(dict, option, value, kind) do
if is_switch_a? :keep, kind do
[{ option, value }|dict]
else
[{ option, value }|Keyword.delete(dict, option)]
case kind do
:keep ->
[{ option, value }|dict]
:integer ->
case String.to_integer(value) do
{ value, "" } -> [{ option, value }|Keyword.delete(dict, option)]
_ -> dict
end
:float ->
case String.to_float(value) do
{ value, "" } -> [{ option, value }|Keyword.delete(dict, option)]
_ -> dict
end
_ ->
[{ option, value }|Keyword.delete(dict, option)]
end
end
@@ -160,7 +179,7 @@ defmodule OptionParser do
end
defp to_underscore(option) do
bc <<c>> inbits option, do: << if c == ?-, do: ?_, else: c >>
bc <<c>> inbits option, do: << if(c == ?-, do: ?_, else: c) >>
end
defp is_no?("no-" <> _), do: true
+11 -11
View File
@@ -4,7 +4,7 @@ defmodule Path do
retrieving file system paths.
The functions in this module may receive a char list or
a binary as argument and will return a value of the same
a binary as an argument and will return a value of the same
type.
The majority of the functions in this module do not
@@ -18,7 +18,7 @@ defmodule Path do
@type r :: char_list | binary
@doc """
Converts the given path to an absolute one. Differently from
Converts the given path to an absolute one. Unlike
`Path.expand/1`, no attempt is made to resolve `..`, `.` or `~`.
## Unix examples
@@ -45,7 +45,7 @@ defmodule Path do
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
Unlike `Path.expand/2`, no attempt is made to
resolve `..`, `.` or `~`.
## Examples
@@ -67,7 +67,7 @@ defmodule Path do
## Examples
iex> Path.expand("/foo/bar/../bar")
Path.expand("/foo/bar/../bar")
"/foo/bar"
"""
@@ -91,9 +91,9 @@ defmodule Path do
Path.expand("foo/bar/../bar", "baz")
#=> "/quux/baz/foo/bar"
iex> Path.expand("foo/bar/../bar", "/baz")
Path.expand("foo/bar/../bar", "/baz")
"/baz/foo/bar"
iex> Path.expand("/foo/bar/../bar", "/baz")
Path.expand("/foo/bar/../bar", "/baz")
"/foo/bar"
"""
@@ -390,11 +390,11 @@ defmodule Path do
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))
@@ -404,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:
+10 -2
View File
@@ -76,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;
@@ -101,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`.
+21 -17
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
@@ -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
+5 -4
View File
@@ -5,6 +5,9 @@ defrecord Range, [:first, :last] do
end
defprotocol Range.Iterator do
@doc """
Reduces the range based on the type of the first argument.
"""
def reduce(first, range, acc, fun)
@doc """
@@ -61,10 +64,8 @@ defimpl Range.Iterator, for: Number do
end
end
defimpl Binary.Inspect, for: Range do
import Kernel, except: [inspect: 2]
defimpl Inspect, for: Range do
def inspect(Range[first: first, last: last], opts) do
Kernel.inspect(first, opts) <> ".." <> Kernel.inspect(last, opts)
Inspect.Algebra.concat [Kernel.inspect(first, opts), "..", Kernel.inspect(last, opts)]
end
end
+320 -224
View File
@@ -1,155 +1,181 @@
defmodule Record do
@moduledoc %B"""
Functions to define Elixir records
Convenience functions for working with 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`.
A record is a tagged tuple which contains one or more elements
where the first element is an atom. We can manually create a record
by simply defining such a tuple:
## Examples
iex> record = { User, "josé", 25 }
iex> is_record(record, User)
true
defrecord FileInfo, atime: nil, accesses: 0
However, manually constructing tuples can be quite error prone.
In case we need to add a new field to our User, it would require
us to carefully change all places where the tuple is used.
Furthermore, as more and more items are added to the tuple, they
lose semantic value.
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:
This module solves these problems by allowing us to name each
element and encapsulate the generation and manipulation of
such tuples. Much of the functionality provided by this module happens
at compilation time, meaning they don't add any runtime overhead while
considerably improving the quality of our code.
file_info = FileInfo.new(atime: now())
file_info.atime #=> Returns the value of atime
file_info.atime(now()) #=> Updates the value of atime
For these reasons, Records are frequently used in Elixir and
are also very useful when combined with Protocols. This module
provides different mechanisms for working with records and we
are going to explore them in the following sections.
# Update multiple attributes at once:
file_info.update(atime: now(), accesses: 1)
## defrecordp
# Obtain the keywords representation of a record:
file_info.to_keywords #=> [accesses: 1, atime: {1370,7171,911705}]
The simplest way of working with records is via `defrecordp`:
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
defmodule User do
defrecordp :user, name: "José", age: 25
end
Is translated to:
In the example above, `defrecordp` is going to generate a set of
macros named `user` that allows us to create, update and match
on a record. Our record is going to have two fields, a `name` with
default value of "José" and `age` of 25.
def no_access?({ FileInfo, _, _ } = file_info) do
elem(file_info, 1) == 0
Let's see some examples:
# To create records
user() #=> { :user, "José", 25 }
user(age: 26) #=> { :user, "José", 26 }
By using the `user` macro, we no longer need to explicitly create
a tuple with all elements. It also allows us to create and modify
values by name:
# Create a new record
sample_user = user()
# And now change its age to 26
user(sample_user, age: 26)
Since `user` is a macro, all the work happens at compilation time.
This means all operations, like changing the `age` above, work
as a simple tuple operation at runtime:
# This update operation...
user(sample_user, age: 26)
# Literally translates to this one:
set_elem(sample_user, 2, 26)
For this reason, the following operation is not allowed as all
values need to be explicit:
new_values = [age: 26]
user(sample_user, new_values)
As the name says, `defrecordp` is useful when you don't want to
expose the record definition. The `user` macro used above, for
example, is only available inside the `User` module and nowhere else.
You can find more information in `Kernel.defrecordp/2` docs.
In general, though, records are used as part of a module's public
interface. For such use cases, Elixir provides record modules.
## defrecord
By using `defrecord`, a developer can make a Record definition
available everywhere within Elixir code. Let's see an example:
defrecord User, name: "José", age: 25
Notice that, unlike `defrecordp`, `defrecord` expects an
alias as the first argument. This is because `defrecord` is going
to create a module named `User` with all the relevant metadata.
This module can then be imported and we can manipulate the user
as with `defrecordp`:
Record.import User, as: :user
user() #=> User[name: "José", age: 25]
user(age: 26) #=> User[name: "José", age: 26]
Notice that now, since the record definition is accessible, Elixir
shows the record nicely formatted, no longer as a simple tuple. We
can get the raw formatting by passing `raw: true` to `inspect`:
inspect user(), raw: true
{ User, "José", 25 }
Since working with external records is common, Elixir allows
developers to skip importing the record altogether in favor
of a `Module[args]` syntax:
# Skipping a field uses its default value
User[] #=> User[name: "José", age: 25]
User[age: 26] #=> User[name: "José", age: 26]
The macro name is replaced by the module name and the parenthesis
are replaced by brackets. When the shortcut syntax is used, there
is no need to import the record.
Before we sum up the differences between `defrecord` and
`defrecordp`, there is one last functionality introduced by
`defrecord` that we need to discuss.
## Runtime access
All the functionality discussed above happens at compilation time.
This means that both `user(user, age: 26)` and `User[user, age: 26]`
will be transformed into a simple tuple operation at compile time.
However, there are some situations where we want to set or update
fields dynamically. `defrecord` (and `defrecord` only) supports
it out of the box:
defrecord User, name: "José", age: 25
opts = [name: "Hello"]
user = User.new(opts)
#=> User[name: "Hello", age: 25]
user.update(age: 26)
#=> User[name: "Hello", age: 26]
All the calls above happen at runtime. It gives Elixir
records flexibility at the cost of performance since
there is more work happening at runtime.
To sum up, `defrecordp` should be used when you don't want
to expose the record information while `defrecord` should be used
whenever you want to share a record within your code or with other
libraries or whenever you need to dynamically set or update fields.
You can learn more about records in the `Kernel.defrecord/2` docs. Now
let's discuss the usefulness of combining records with protocols.
## Protocols
Developers can extend existing protocols by creating their own
records and implementing the desired protocols. For instance,
imagine that you have created a new representation for storing
date and time, represented by the year, the week of the year
and the week day:
defrecord WeekDate, year: nil, week: nil, week_day: nil
Now we want this date to be represented as a string and this
can be done by implementing the `Binary.Chars` protocol for
our record:
defimpl Binary.Chars, for: WeekDate do
def to_binary(WeekDate[year: year, week: week, week_day: day]) do
"#{year}-W#{week}-#{day}"
end
end
Which provides faster get and set times for record operations.
Now we can explicitly convert our WeekDate:
## Runtime introspection
to_binary WeekDate[year: 2013, week: 26, week_day: 4]
"2013-W26-4"
At runtime, developers can use `__record__` to get information
about the given record:
FileInfo.__record__(:name)
#=> FileInfo
FileInfo.__record__(:fields)
#=> [atime: nil, accesses: 0]
In order to quickly access the index of a field, one can use
the `__index__` function:
FileInfo.__index__(:atime)
#=> 0
FileInfo.__index__(:unknown)
#=> nil
## Compile-time introspection
At the compile time, one can access following information about the record
from within the record module:
* `@record_fields` — a keyword list of record fields with defaults
* `@record_types` — a keyword list of record fields with types
defrecord Foo, bar: nil do
record_type bar: nil | integer
IO.inspect @record_fields
IO.inspect @record_types
end
prints out
[bar: nil]
[bar: {:|,[line: ...],[nil,{:integer,[line: ...],nil}]}]
where the last line is a quoted representation of
[bar: nil | integer]
## Documentation
By default records are not documented and have `@moduledoc` set to false.
## Types
Every record defines a type named `t` that can be accessed in typespecs.
For example, assuming the `Config` record defined above, it could be used
in typespecs as follow:
@spec handle_config(Config.t) :: boolean()
Inside the record definition, a developer can define his own types too:
defrecord Config, counter: 0, failures: [] do
@type kind :: term
record_type counter: integer, failures: [kind]
end
When defining a type, all the fields not mentioned in the type are
assumed to have type `term`.
## Importing records
It is also possible to import a public record (a record, defined using
`defrecord`) as a set of private macros (as if it was defined using `defrecordp`):
Record.import Config, as: :config
See `Record.import/2` and `defrecordp/2` documentation for more information
A protocol can be implemented for any record defined via `defrecord`.
"""
@doc """
@@ -166,68 +192,89 @@ defmodule Record do
end
@doc """
Main entry point for records definition. It defines a module
with the given `name` and the fields specified in `values`.
This is invoked directly by `Kernel.defrecord`, so check it
for more information and documentation.
Imports a 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
defmodule Test do
Record.import File.Stat, as: :file_stat
def size(file_stat(size: size)), do: size
end
"""
def defrecord(name, values, opts) do
defmacro import(module, as: name) do
quote do
module = unquote(module)
fields = if module == __MODULE__ do
@record_fields
else
module.__record__(:fields)
end
Record.defmacros(unquote(name), fields, __ENV__, module)
end
end
@doc false
def defrecord(name, fields, opts) do
block = Keyword.get(opts, :do, nil)
record_check!(fields)
quote do
unquoted_values = unquote(values)
unquoted_fields = unquote(fields)
defmodule unquote(name) do
@moduledoc false
import Elixir.Record.DSL
@record_fields []
@record_types []
# Reassign values to inner scope to
# avoid conflicts in nested records
values = unquoted_values
Elixir.Record.deffunctions(values, __ENV__)
Elixir.Record.deffunctions(unquoted_fields, __ENV__)
value = unquote(block)
Elixir.Record.deftypes(values, @record_types, __ENV__)
Elixir.Record.deftypes(@record_fields, @record_types, __ENV__)
value
end
end
end
@doc """
Import public record definition as a set of private macros (as defined by defrecordp/2)
defp record_check!([{ field, { :::, _, [_, _] }}|_]) when is_atom(field) do
raise ArgumentError, message: "typespecs are not supported inlined with defrecord, " <>
"please use record_type instead"
end
## Usage
defp record_check!([_|t]), do: record_check!(t)
defp record_check!(_), do: :ok
Record.import Record.Module, as: macro_name
@doc false
def defrecordp(name, tag, fields) when is_atom(name) and is_atom(tag) and is_list(fields) do
{ fields, types, def_type } = recordp_split(fields, [], [], false)
type = binary_to_atom(atom_to_binary(name) <> "_t")
tag = tag || name
## Example
defmodule Test do
Record.import File.Stat, as: :file_stat
def size(file_stat(size: size)), do: size
end
"""
defmacro import(module, as: name) do
quote do
Record.defmacros(unquote(name), unquote(module).__record__(:fields), __ENV__, unquote(module))
Record.defmacros(unquote(name), unquote(fields), __ENV__, unquote(tag))
if unquote(def_type) do
@typep unquote(type)() :: { unquote(tag), unquote_splicing(types) }
end
end
end
@doc """
Main entry point for private records definition. It defines
a set of macros with the given `name` and the fields specified
in `values`. This is invoked directly by `Kernel.defrecordp`,
so check it for more information and documentation.
"""
def defrecordp(name, fields) do
quote do
Record.defmacros(unquote(name), unquote(fields), __ENV__)
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 """
@@ -308,25 +355,23 @@ defmodule Record do
{ key, Macro.escape(value) }
end
tag = tag || name
contents = quote do
defmacrop unquote(name)() do
Record.access(unquote(tag) || __MODULE__, unquote(escaped), [], __CALLER__)
Record.access(unquote(tag), unquote(escaped), [], __CALLER__)
end
defmacrop unquote(name)(record) when is_tuple(record) do
Record.to_keywords(unquote(tag) || __MODULE__, unquote(escaped), record)
Record.to_keywords(unquote(tag), unquote(escaped), record)
end
defmacrop unquote(name)(args) do
Record.access(unquote(tag) || __MODULE__, unquote(escaped), args, __CALLER__)
end
defmacrop unquote(name)(record, key) when is_atom(key) do
Record.get(unquote(tag) || __MODULE__, unquote(escaped), record, key)
Record.access(unquote(tag), unquote(escaped), args, __CALLER__)
end
defmacrop unquote(name)(record, args) do
Record.dispatch(unquote(tag) || __MODULE__, unquote(escaped), record, args, __CALLER__)
Record.dispatch(unquote(tag), unquote(escaped), record, args, __CALLER__)
end
end
@@ -368,7 +413,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?
@@ -398,31 +443,56 @@ defmodule Record do
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
# Dispatch the call to either update or to_list depending on the args given.
# Implements to_keywords macro defined by defmacros.
# It returns a quoted expression that represents
# converting record to keywords list.
@doc false
def to_keywords(_atom, fields, record) do
{ var, extra } = cache_var(record)
keywords = Enum.map fields,
fn { key, _default } ->
index = find_index(fields, key, 0)
quote do
{ unquote(key), :erlang.element(unquote(index + 2), unquote(var)) }
end
end
quote do
unquote_splicing(extra)
unquote(keywords)
end
end
# Dispatch the call to either get, update or to_list depending on the args given.
@doc false
def dispatch(atom, fields, record, args, caller) do
if is_keyword(args) do
update(atom, fields, record, args, caller)
else
to_list(atom, fields, record, args)
cond do
is_atom(args) ->
get(atom, fields, record, args)
is_keyword(args) ->
update(atom, fields, record, args, caller)
is_list(args) ->
to_list(atom, fields, record, args)
true ->
raise ArgumentError, message: "expected arguments to be a compile time atom, list or keywords"
end
end
# Implements the update macro defined by defmacros.
# It returns a quoted expression that represents
# the access given by the keywords.
@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 arguments to be compile time keywords"
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) ->
@@ -432,7 +502,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
@@ -440,52 +510,58 @@ defmodule Record do
# Implements the get macro defined by defmacros.
# It returns a quoted expression that represents
# getting the value of a given field.
@doc false
def get(atom, fields, var, key) do
defp get(atom, fields, var, key) do
index = find_index(fields, key, 0)
if index do
quote 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
# Implements to_keywords macro defined by defmacros.
# It returns a quoted expression that represents
# converting record to keywords list.
@doc false
def to_keywords(_atom, fields, record) do
Enum.map fields,
fn { key, _default } ->
index = find_index(fields, key, 0)
quote do
{ unquote(key), :erlang.element(unquote(index + 2), unquote(record)) }
end
end
end
# Implements to_list macro defined by defmacros.
# It returns a quoted expression that represents
# extracting given fields from record.
@doc false
defp to_list(atom, fields, record, keys) do
Enum.map keys,
unless is_list(fields) do
raise ArgumentError, message: "expected arguments to be a compile time list"
end
{ var, extra } = cache_var(record)
list = Enum.map keys,
fn(key) ->
index = find_index(fields, key, 0)
if index do
quote do: :erlang.element(unquote(index + 2), unquote(record))
quote do: :erlang.element(unquote(index + 2), unquote(var))
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
quote do
unquote_splicing(extra)
unquote(list)
end
end
defp cache_var({ var, _, kind } = tuple) when is_atom(var) and is_atom(kind) do
{ tuple, [] }
end
defp cache_var(other) do
quote do
{ x, [x = unquote(other)] }
end
end
## Function generation
# Define __record__/1 and __record__/2 as reflection functions
# that returns the record names and fields.
# Define __record__/1, __record__/2, __record__/3 as reflection
# functions that return the record names and fields.
#
# Note that fields are *not* keywords. They are in the same
# order as given as parameter and reflects the order of the
@@ -497,15 +573,31 @@ defmodule Record do
#
# FileInfo.__record__(:name) #=> FileInfo
# FileInfo.__record__(:fields) #=> [atime: nil, mtime: nil]
# FileInfo.__record__(:index, :atime) #=> 1
# FileInfo.__record__(:index, :mtime) #=> 2
#
defp reflection(values) do
quoted = lc { k, _ } inlist values do
index = find_index(values, k, 0)
quote do
def __record__(:index, unquote(k)), do: unquote(index + 1)
end
end
quote do
@doc false
def __record__(kind, _), do: __record__(kind)
unquote(quoted)
@doc false
def __record__(:name), do: __MODULE__
def __record__(:fields), do: unquote(values)
def __record__(:index, _), do: nil
@doc false
def __record__(:index, arg, _), do: __record__(:index, arg)
@doc false
def __record__(kind, _), do: __record__(kind)
@doc false
def __record__(:name), do: __MODULE__
def __record__(:fields), do: unquote(values)
end
end
@@ -570,7 +662,11 @@ defmodule Record do
index = find_index(values, k, 0)
quote do
@doc false
def __index__(unquote(k)), do: unquote(index + 1)
def __index__(unquote(k)) do
IO.write "[WARNING] #{__MODULE__}.__index__/1 is deprecated, " <>
"please use #{__MODULE__}.__record__(:index, key) instead\n#{Exception.format_stacktrace}"
unquote(index + 1)
end
end
end
quote do
@@ -726,8 +822,8 @@ defmodule Record do
@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
@spec __record__(:fields) :: [{atom, any}]
@spec __record__(:index, atom) :: non_neg_integer | nil
end
end
@@ -766,15 +862,15 @@ defmodule Record do
defp convert_value(atom) when is_atom(atom), do: { atom, nil }
defp convert_value({ atom, other }) when is_atom(atom) and is_function(other), do:
raise ArgumentError, message: "record field default value #{inspect atom} cannot be a function"
raise(ArgumentError, message: "record field default value #{inspect atom} cannot be a function")
defp convert_value({ atom, other }) when is_atom(atom) and (is_reference(other) or is_pid(other) or is_port(other)), do:
raise ArgumentError, message: "record field default value #{inspect atom} cannot be a reference, pid or port"
raise(ArgumentError, message: "record field default value #{inspect atom} cannot be a reference, pid or port")
defp convert_value({ atom, _ } = tuple) when is_atom(atom), do: tuple
defp convert_value({ field, _ }), do:
raise ArgumentError, message: "record field name has to be an atom, got #{inspect field}"
raise(ArgumentError, message: "record field name has to be an atom, got #{inspect field}")
defp find_index([{ k, _ }|_], k, i), do: i
defp find_index([{ _, _ }|t], k, i), do: find_index(t, k, i + 1)
+19 -7
View File
@@ -29,17 +29,18 @@ defmodule Record.Extractor do
# Retrieve the record with the given name from the given file
defp retrieve_record(name, file) do
records = retrieve_from_file(file)
form = read_file(file)
records = retrieve_records(form)
if record = List.keyfind(records, name, 0) do
parse_record(record)
parse_record(record, form)
else
raise ArgumentError, "no record #{name} found at #{to_binary(file)}"
end
end
# Parse the given file and retrieve all existent records.
defp retrieve_from_file(file) do
lc { :attribute, _, :record, record } inlist read_file(file), do: record
defp retrieve_records(form) do
lc { :attribute, _, :record, record } inlist form, do: record
end
# Read a file and return its abstract syntax form that also
@@ -57,12 +58,11 @@ defmodule Record.Extractor do
# Parse a tuple with name and fields and returns a
# list of tuples where the first element is the field
# and the second is its default value.
defp parse_record({ _name, fields }) do
defp parse_record({ _name, fields }, form) do
cons = List.foldr fields, { nil, 0 }, fn f, acc ->
{ :cons, 0, parse_field(f), acc }
end
{ :value, list, _ } = :erl_eval.expr(cons, [])
list
eval_record(cons, form)
end
defp parse_field({ :typed_record_field, record_field, _type }) do
@@ -76,4 +76,16 @@ defmodule Record.Extractor do
defp parse_field({ :record_field, _, key, value }) do
{ :tuple, 0, [key, value] }
end
defp eval_record(cons, form) do
form = form ++
[ { :function, 0, :hello, 0, [
{ :clause, 0, [], [], [ cons ] } ] } ]
{ :function, 0, :hello, 0, [
{ :clause, 0, [], [], [ record_ast ] } ] } = :erl_expand_records.module(form, []) |> List.last
{ :value, record, _ } = :erl_eval.expr(record_ast, [])
record
end
end
+44 -45
View File
@@ -2,7 +2,7 @@ defmodule Regex do
@moduledoc %B"""
Regular expressions for Elixir built on top of the re module
in the Erlang Standard Library. More information can be found
on re documentation: http://www.erlang.org/doc/man/re.html
in the re documentation: http://www.erlang.org/doc/man/re.html
Regular expressions in Elixir can be created using Regex.compile!
or using the special form with `%r`:
@@ -10,32 +10,32 @@ defmodule Regex do
# A simple regular expressions that matches foo anywhere in the string
%r/foo/
# A regular expression with case insensitive options and handle unicode chars
# A regular expression with case insensitive options and handling for unicode chars
%r/foo/iu
The re module provides several options, the one available in Elixir, followed by
The re module provides several options, the ones available in Elixir, followed by
their shortcut in parenthesis, are:
* unicode (u) - enable unicode specific patterns like \p
* caseless (i) - add case insensitivity
* dotall (s) - causes dot to match newlines and also set newline to anycrlf.
The new line setting can be overwritten by setting `(*CR)` or `(*LF)` or
* `unicode` (u) - enable unicode specific patterns like \p
* `caseless` (i) - add case insensitivity
* `dotall` (s) - causes dot to match newlines and also set newline to anycrlf.
The new line setting can be overridden by setting `(*CR)` or `(*LF)` or
`(*CRLF)` or `(*ANY)` according to re documentation
* multiline (m) - causes `^` and `$` to mark the beginning and end of each line.
You need to use `\A` and `\z` to match the end or beginning of the string
* extended (x) - whitespace characters are ignored except when escaped and
* `multiline` (m) - causes `^` and `$` to mark the beginning and end of each line.
Use `\A` and `\z` to match the end or beginning of the string
* `extended` (x) - whitespace characters are ignored except when escaped and
allow `#` to delimit comments
* firstline (f) - forces the unanchored pattern to match before or at the first
* `firstline` (f) - forces the unanchored pattern to match before or at the first
newline, though the matched text may continue over the newline
* ungreedy (r) - invert the "greediness" of the regexp
* groups (g) - compile with info about groups available
* `ungreedy` (r) - invert the "greediness" of the regexp
* `groups` (g) - compile with info about groups available
The options not available are:
* anchored - not available, use `^` or `\A` instead
* dollar_endonly - not available, use `\z` instead
* no_auto_capture - not available, use `?:` instead
* newline - not available, use `(*CR)` or `(*LF)` or `(*CRLF)` or `(*ANYCRLF)`
* `anchored` - not available, use `^` or `\A` instead
* `dollar_endonly` - not available, use `\z` instead
* `no_auto_capture` - not available, use `?:` instead
* `newline` - not available, use `(*CR)` or `(*LF)` or `(*CRLF)` or `(*ANYCRLF)`
or `(*ANY)` at the beginning of the regexp according to the re documentation
Most of the functions in this module accept either a binary or a char list
@@ -43,7 +43,7 @@ defmodule Regex do
a binary, a char list will return a char list).
"""
defrecordp :regex, [:re_pattern, :source, :options, :groups]
defrecordp :regex, Regex, [:re_pattern, :source, :options, :groups]
@type t :: { Regex, term, term, term, term }
defexception CompileError, message: "regex could not be compiled"
@@ -79,20 +79,8 @@ defmodule Regex do
end
end
@doc false
def index(regex(re_pattern: compiled), string) do
IO.puts "Regex.index(re, string) is deprecated. Please use " <>
"Regex.run(re, string, return: :index) instead."
Exception.print_stacktrace
case :re.run(string, compiled, [{ :capture, :first, :index }]) do
:nomatch -> nil
{ :match, [{index,_}] } -> index
end
end
@doc """
Returns a boolean if there was a match or not.
Returns a boolean indicating whether there was a match or not.
## Examples
@@ -108,8 +96,8 @@ defmodule Regex do
@doc """
Runs the regular expression against the given string.
It returns a list with all matches, nil if no match ocurred, or []
if it matched, /g was specified, but nothing was captured.
It returns a list with all matches, `nil` if no match occurred, or `[]`
if it matched, `/g` was specified, but nothing was captured.
## Examples
@@ -140,14 +128,17 @@ defmodule Regex do
end
@doc """
Returns the given captures as a list of tuples.
Returns the given captures as a keyword list or `nil` if no captures are found.
Requires the regex to be compiled with the groups option.
## Examples
iex> Regex.captures(%r/c(?<foo>d)/g, "abcd")
[foo: "d"]
iex> Regex.captures(%r/a(?<foo>b)c(?<bar>d)/g, "abcd")
[bar: "d", foo: "b"]
iex> Regex.captures(%r/a(?<foo>b)c(?<bar>d)/g, "efgh")
nil
"""
def captures(regex(groups: groups) = regex, string, options // []) do
unless captures = Keyword.get(options, :capture) do
@@ -159,7 +150,7 @@ defmodule Regex do
options = Keyword.put(options, :capture, captures)
end
results = run(regex, string, options)
if results, do: Enum.zip captures, results
if results, do: Enum.zip(captures, results)
end
@doc """
@@ -170,7 +161,7 @@ defmodule Regex do
end
@doc """
Returns the regex source as binary.
Returns the regex source as a binary.
## Examples
@@ -196,11 +187,11 @@ defmodule Regex do
end
@doc """
Returns list of named groups in regex.
Returns a list of named groups in the regex.
## Examples
iex> Regex.groups(%r/(?<foo>foo)/g)
iex> Regex.groups(%r/(?<foo>bar)/g)
[:foo]
"""
@@ -236,8 +227,16 @@ defmodule Regex do
end
@doc """
Split the given target in the number of parts specified.
If no ammount of parts is given, it defaults to :infinity.
Split the given target into the number of parts specified.
If no number of parts is given, it defaults to `:infinity`.
## Examples
iex> Regex.split(%r/-/, "a-b-c")
["a","b","c"]
iex> Regex.split(%r/-/, "a-b-c", [parts: 2])
["a","b-c"]
iex> Regex.split(%r/-/, "abc")
["abc"]
"""
def split(regex, string, options // [])
@@ -256,11 +255,11 @@ defmodule Regex do
end
@doc %B"""
Receives a regex, a binary and a replacement and returns a new
Receives a regex, a binary and a replacement, returns a new
binary where the all matches are replaced by replacement.
Inside the replacement, you can either give "&" to access the
whole regular expression or \N, where N is in integer to access
Inside the replacement, you can either give `&` to access the
whole regular expression or `\N`, where `N` is in integer to access
a specific matching parens. You can also set global to false
if you want to replace just the first occurrence.
@@ -326,7 +325,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: []
+233
View File
@@ -0,0 +1,233 @@
defmodule Set do
@moduledoc %B"""
This module specifies the Set API expected to be
implemented by different representations.
It also provides functions that redirect to the
underlying Set, allowing a developer to work with
different Set implementations using one API.
To create a new set, use the `new` functions defined
by each set type:
HashSet.new #=> creates an empty HashSet
For simplicity's sake, in the examples below every time
`new` is used, it implies one of the module-specific
calls like above.
"""
use Behaviour
@type value :: any
@type values :: [ value ]
@type t :: tuple
defcallback delete(t, value) :: t
defcallback difference(t, t) :: t
defcallback disjoint?(t, t) :: boolean
defcallback empty(t) :: t
defcallback equal?(t, t) :: boolean
defcallback intersection(t, t) :: t
defcallback member?(t, value) :: boolean
defcallback put(t, value) :: t
defcallback size(t) :: non_neg_integer
defcallback subset?(t, t) :: boolean
defcallback to_list(t) :: list()
defcallback union(t, t) :: t
defmacrop target(set) do
quote do
if is_tuple(unquote(set)) do
elem(unquote(set), 0)
else
unsupported_set(unquote(set))
end
end
end
@doc """
Deletes `value` from `set`.
## Examples
iex> s = HashSet.new([1, 2, 3])
...> Set.delete(s, 4) |> HashSet.to_list
[1, 2, 3]
iex> s = HashSet.new([1, 2, 3])
...> Set.delete(s, 2) |> HashSet.to_list
[1, 3]
"""
@spec delete(t, value) :: t
def delete(set, value) do
target(set).delete(set, value)
end
@doc """
Returns a set that is `set1` without the members of `set2`.
## Examples
iex> Set.difference(HashSet.new([1,2]), HashSet.new([2,3,4])) |> HashSet.to_list
[1]
"""
@spec difference(t, t) :: t
def difference(set1, set2) do
target(set1).difference(set1, set2)
end
@doc """
Checks if `set1` and `set2` have no members in common.
## Examples
iex> Set.disjoint?(HashSet.new([1, 2]), HashSet.new([3, 4]))
true
iex> Set.disjoint?(HashSet.new([1, 2]), HashSet.new([2, 3]))
false
"""
@spec disjoint?(t, t) :: boolean
def disjoint?(set1, set2) do
target(set1).disjoint?(set1, set2)
end
@doc """
Returns an empty set of the same type as `set`.
"""
@spec empty(t) :: t
def empty(set) do
target(set).empty(set)
end
@doc """
Checks if `set1` and `set2` are equal.
## Examples
iex> Set.equal?(HashSet.new([1, 2]), HashSet.new([2, 1, 1]))
true
iex> Set.equal?(HashSet.new([1, 2]), HashSet.new([3, 4]))
false
"""
@spec equal?(t, t) :: boolean
def equal?(set1, set2) do
target(set1).equal?(set1, set2)
end
@doc """
Returns a set containing only members in common between `set1` and `set2`.
## Examples
iex> Set.intersection(HashSet.new([1,2]), HashSet.new([2,3,4])) |> HashSet.to_list
[2]
iex> Set.intersection(HashSet.new([1,2]), HashSet.new([3,4])) |> HashSet.to_list
[]
"""
@spec intersection(t, t) :: t
def intersection(set1, set2) do
target(set1).intersection(set1, set2)
end
@doc """
Checks if `set` contains `value`.
## Examples
iex> Set.member?(HashSet.new([1, 2, 3]), 2)
true
iex> Set.member?(HashSet.new([1, 2, 3]), 4)
false
"""
@spec member?(t, value) :: boolean
def member?(set, value) do
target(set).member?(set, value)
end
@doc """
Inserts `value` into `set` if it does not already contain it.
## Examples
iex> Set.put(HashSet.new([1, 2, 3]), 3) |> Set.to_list
[1, 2, 3]
iex> Set.put(HashSet.new([1, 2, 3]), 4) |> Set.to_list
[1, 2, 3, 4]
"""
@spec put(t, value) :: t
def put(set, value) do
target(set).put(set, value)
end
@doc """
Returns the number of elements in `set`.
## Examples
iex> Set.size(HashSet.new([1, 2, 3]))
3
"""
@spec size(t) :: non_neg_integer
def size(set) do
target(set).size(set)
end
@doc """
Checks if `set1`'s members are all contained in `set2`.
## Examples
iex> Set.subset?(HashSet.new([1, 2]), HashSet.new([1, 2, 3]))
true
iex> Set.subset?(HashSet.new([1, 2, 3]), HashSet.new([1, 2]))
false
"""
@spec subset?(t, t) :: boolean
def subset?(set1, set2) do
target(set1).subset?(set1, set2)
end
@doc """
Converts `set` to a list.
## Examples
iex> HashSet.to_list(HashSet.new([1, 2, 3]))
[1,2,3]
"""
@spec to_list(t) :: list
def to_list(set) do
target(set).to_list(set)
end
@doc """
Returns a set containing all members of `set1` and `set2`.
## Examples
iex> Set.union(HashSet.new([1,2]), HashSet.new([2,3,4])) |> HashSet.to_list
[1,2,3,4]
"""
@spec union(t, t) :: t
def union(set1, set2) do
target(set1).union(set1, set2)
end
defp unsupported_set(set) do
raise ArgumentError, message: "unsupported set: #{inspect set}"
end
end
+387
View File
@@ -0,0 +1,387 @@
defmodule Stream do
@moduledoc """
Module for creating and composing streams.
Streams are composable, lazy enumerables. Any enumerable that generates
items one by one during enumeration is called a stream. For example,
Elixir's `Range` is a stream:
iex> range = 1..5
1..5
iex> Enum.map range, &1 * 2
[2,4,6,8,10]
In the example above, as we mapped over the range, the elements being
enumerated were created one by one, during enumeration. The `Stream`
module allows us to map the range, without triggering its enumeration:
iex> range = 1..3
iex> stream = Stream.map(range, &1 * 2)
iex> Enum.map(stream, &1 + 1)
[3,5,7]
Notice we started with a range and then we created a stream that is
meant to multiply each item in the range by 2. At this point, no
computation was done yet. Just when `Enum.map/2` is called we
enumerate over each item in the range, multiplying it by 2 and adding 1.
We say the functions in `Stream` are *lazy* and the functions in `Enum`
are *eager*.
Due to their laziness, streams are useful when working with large
(or even infinite) collections. When chaining many operations with `Enum`,
intermediate lists are created, while `Stream` creates a recipe of
computations that are executed at a later moment. Let's see another
example:
1..3 |>
Enum.map(IO.inspect(&1)) |>
Enum.map(&1 * 2) |>
Enum.map(IO.inspect(&1))
1
2
3
2
4
6
#=> [2,4,6]
Notice that we first printed each item in the list, then multiplied each
element by 2 and finally printed each new value. In this example, the list
was iterated three times. Let's see an example with streams:
stream = 1..3 |>
Stream.map(IO.inspect(&1)) |>
Stream.map(&1 * 2) |>
Stream.map(IO.inspect(&1))
Enum.to_list(stream)
1
2
2
4
3
6
#=> [2,4,6]
Although the end result is the same, the order in which the items were
printed changed! With streams, we print the first item and then print
its double. In this example, the list was iterated just once!
That's what we meant when we first said that streams are composable,
lazy enumerables. Notice we could call `Stream.map/2` multiple times,
effectively composing the streams and they are lazy. The computations
are performed only when you call a function from the `Enum` module.
## Creating Streams
There are many functions in Elixir's standard library that return
streams, some examples are:
* `IO.stream/1` - Streams input lines, one by one;
* `URI.query_decoder/1` - Decodes a query string, pair by pair;
This module also allows us to create streams from any enumerable:
iex> stream = Stream.map([1,2,3], &1 * 2)
iex> Enum.map(stream, &1 + 1)
[3,5,7]
By simply passing a list (which is an enumerable) as the first argument
to `Stream.map/2`, we have automatically created a stream that will
multiply the items in the list by 2 on enumeration.
This module also provides other functions for creating streams, such as
`Stream.cycle/1`.
"""
defrecord Lazy, [:enumerable, :fun, :acc]
defimpl Enumerable, for: Lazy do
def reduce(Lazy[] = lazy, acc, fun) do
do_reduce(lazy, acc, fun)
end
def count(Lazy[] = lazy) do
do_reduce(lazy, 0, fn _, acc -> acc + 1 end)
end
def member?(Lazy[] = lazy, value) do
do_reduce(lazy, false, fn(entry, _) ->
if entry === value, do: throw({ :stream_lazy, true }), else: false
end)
end
defp do_reduce(Lazy[enumerable: enumerable, fun: f1, acc: nil], acc, fun) do
do_reduce(enumerable, acc, f1.(fun))
end
defp do_reduce(Lazy[enumerable: enumerable, fun: f1, acc: side], acc, fun) do
do_reduce(enumerable, { acc, side }, f1.(fun)) |> elem(0)
end
defp do_reduce(enumerable, acc, fun) do
Enumerable.reduce(enumerable, acc, fun)
catch
{ :stream_lazy, acc } -> acc
end
end
@type t :: Lazy.t | (acc, (element, acc -> acc) -> acc)
@type acc :: any
@type element :: any
@type index :: non_neg_integer
@type default :: any
@doc """
Creates a stream that cycles through the given enumerable,
infinitely.
## Examples
iex> stream = Stream.cycle([1,2,3])
iex> Enum.take(stream, 5)
[1,2,3,1,2]
"""
@spec cycle(Enumerable.t) :: t
def cycle(enumerable) do
do_cycle(enumerable, &1, &2)
end
defp do_cycle(enumerable, acc, fun) do
acc = Enumerable.reduce(enumerable, acc, fun)
do_cycle(enumerable, acc, fun)
end
@doc """
Lazily drops the next `n` items from the enumerable.
## Examples
iex> stream = Stream.drop(1..10, 5)
iex> Enum.to_list(stream)
[6,7,8,9,10]
"""
@spec drop(Enumerable.t, non_neg_integer) :: t
def drop(enumerable, n) when n >= 0 do
Lazy[enumerable: enumerable,
fun: fn(f1) ->
fn
_entry, { acc, n } when n > 0 ->
{ acc, n - 1 }
entry, { acc, n } ->
{ f1.(entry, acc), n }
end
end,
acc: n]
end
@doc """
Lazily drops elements of the enumerable while the given
function returns true.
## Examples
iex> stream = Stream.drop_while(1..10, &1 <= 5)
iex> Enum.to_list(stream)
[6,7,8,9,10]
"""
@spec drop_while(Enumerable.t, (element -> as_boolean(term))) :: t
def drop_while(enumerable, f) do
Lazy[enumerable: enumerable,
fun: fn(f1) ->
fn
entry, { acc, true } ->
if f.(entry), do: { acc, true }, else: { f1.(entry, acc), false }
entry, { acc, false } ->
{ f1.(entry, acc), false }
end
end,
acc: true]
end
@doc """
Creates a stream that will filter elements according to
the given function on enumeration.
## Examples
iex> stream = Stream.filter([1, 2, 3], fn(x) -> rem(x, 2) == 0 end)
iex> Enum.to_list(stream)
[2]
"""
@spec filter(Enumerable.t, (element -> as_boolean(term))) :: t
def filter(enumerable, f) do
Lazy[enumerable: enumerable,
fun: fn(f1) ->
fn(entry, acc) ->
if f.(entry), do: f1.(entry, acc), else: acc
end
end]
end
@doc """
Emit a sequence of values, starting with `start_value`. Successive
values are generated by calling `next_fun` on the previous value.
## Examples
iex> Stream.iterate(0, &1+1) |> Enum.take(5)
[0,1,2,3,4]
"""
@spec iterate(element, (element -> element)) :: t
def iterate(start_value, next_fun) do
fn acc, fun ->
do_iterate(start_value, next_fun, fun.(start_value, acc), fun)
end
end
defp do_iterate(value, next_fun, acc, fun) do
next = next_fun.(value)
do_iterate(next, next_fun, fun.(next, acc), fun)
end
@doc """
Creates a stream that will apply the given function on
enumeration.
## Examples
iex> stream = Stream.map([1, 2, 3], fn(x) -> x * 2 end)
iex> Enum.to_list(stream)
[2,4,6]
"""
@spec map(Enumerable.t, (element -> any)) :: t
def map(enumerable, f) do
Lazy[enumerable: enumerable,
fun: fn(f1) ->
fn(entry, acc) ->
f1.(f.(entry), acc)
end
end]
end
@doc """
Creates a stream that will reject elements according to
the given function on enumeration.
## Examples
iex> stream = Stream.reject([1, 2, 3], fn(x) -> rem(x, 2) == 0 end)
iex> Enum.to_list(stream)
[1,3]
"""
@spec reject(Enumerable.t, (element -> as_boolean(term))) :: t
def reject(enumerable, f) do
Lazy[enumerable: enumerable,
fun: fn(f1) ->
fn(entry, acc) ->
unless f.(entry), do: f1.(entry, acc), else: acc
end
end]
end
@doc """
Returns a stream generated by calling `generator_fun` repeatedly.
## Examples
iex> Stream.repeatedly(function(:random.uniform/0)) |> Enum.take(3)
[0.4435846174457203, 0.7230402056221108, 0.94581636451987]
"""
@spec repeatedly((() -> element)) :: t
def repeatedly(generator_fun)
when is_function(generator_fun, 0) do
do_repeatedly(generator_fun, &1, &2)
end
defp do_repeatedly(generator_fun, acc, fun) do
do_repeatedly(generator_fun, fun.(generator_fun.(), acc), fun)
end
@doc """
Lazily takes the next `n` items from the enumerable and stops
enumeration.
## Examples
iex> stream = Stream.take(1..100, 5)
iex> Enum.to_list(stream)
[1,2,3,4,5]
iex> stream = Stream.cycle([1, 2, 3]) |> Stream.take(5)
iex> Enum.to_list(stream)
[1,2,3,1,2]
"""
@spec take(Enumerable.t, non_neg_integer) :: t
def take(enumerable, n) when n >= 0 do
Lazy[enumerable: enumerable,
fun: fn(f1) ->
fn
(entry, { acc, n }) when n > 0 ->
{ f1.(entry, acc), n-1 }
(_entry, acc) ->
throw { :stream_lazy, acc }
end
end,
acc: n]
end
@doc """
Lazily takes elements of the enumerable while the given
function returns true.
## Examples
iex> stream = Stream.take_while(1..100, &1 <= 5)
iex> Enum.to_list(stream)
[1,2,3,4,5]
"""
@spec take_while(Enumerable.t, (element -> as_boolean(term))) :: t
def take_while(enumerable, f) do
Lazy[enumerable: enumerable,
fun: fn(f1) ->
fn
entry, { acc, true } ->
if f.(entry), do: { f1.(entry, acc), true }, else: { acc, false }
_entry, acc ->
acc
end
end,
acc: true]
end
@doc """
Creates a stream where each item in the enumerable will
be accompanied by its index.
## Examples
iex> stream = Stream.with_index([1, 2, 3])
iex> Enum.to_list(stream)
[{1,0},{2,1},{3,2}]
"""
@spec with_index(Enumerable.t) :: t
def with_index(enumerable) do
Lazy[enumerable: enumerable,
fun: fn(f1) ->
fn(entry, { acc, counter }) ->
acc = f1.({ entry, counter }, acc)
{ acc, counter + 1}
end
end,
acc: 0]
end
end
+73 -8
View File
@@ -138,6 +138,67 @@ defmodule String do
def printable?(<<>>), do: true
def printable?(_), do: false
@doc %B"""
Escape the given string. It expects one extra paremeter
representing the string surrounds which should also be escaped.
## Examples
iex> String.escape("abc", ?")
"abc"
iex> String.escape("a\nb", ?")
"a\\nb"
"""
def escape(other, char) do
escape(other, char, <<>>)
end
@compile { :inline, escape: 3 }
defp escape(<<>>, _char, binary), do: binary
defp escape(<< char, t :: binary >>, char, binary) do
escape(t, char, << binary :: binary, ?\\, char >>)
end
defp escape(<<?#, ?{, t :: binary>>, char, binary) do
escape(t, char, << binary :: binary, ?\\, ?#, ?{ >>)
end
defp escape(<<?\a, t :: binary>>, char, binary) do
escape(t, char, << binary :: binary, ?\\, ?a >>)
end
defp escape(<<?\b, t :: binary>>, char, binary) do
escape(t, char, << binary :: binary, ?\\, ?b >>)
end
defp escape(<<?\d, t :: binary>>, char, binary) do
escape(t, char, << binary :: binary, ?\\, ?d >>)
end
defp escape(<<?\e, t :: binary>>, char, binary) do
escape(t, char, << binary :: binary, ?\\, ?e >>)
end
defp escape(<<?\f, t :: binary>>, char, binary) do
escape(t, char, << binary :: binary, ?\\, ?f >>)
end
defp escape(<<?\n, t :: binary>>, char, binary) do
escape(t, char, << binary :: binary, ?\\, ?n >>)
end
defp escape(<<?\r, t :: binary>>, char, binary) do
escape(t, char, << binary :: binary, ?\\, ?r >>)
end
defp escape(<<?\\, t :: binary>>, char, binary) do
escape(t, char, << binary :: binary, ?\\, ?\\ >>)
end
defp escape(<<?\t, t :: binary>>, char, binary) do
escape(t, char, << binary :: binary, ?\\, ?t >>)
end
defp escape(<<?\v, t :: binary>>, char, binary) do
escape(t, char, << binary :: binary, ?\\, ?v >>)
end
defp escape(<<h, t :: binary>>, char, binary) do
escape(t, char, << binary :: binary, h >>)
end
@doc """
Splits a string on sub strings at each Unicode whitespace
occurrence with leading and trailing whitespace ignored.
@@ -146,7 +207,7 @@ defmodule String do
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"]
@@ -185,6 +246,8 @@ defmodule String do
@spec split(t, t | [t] | Regex.t, Keyword.t) :: [t]
def split(binary, pattern, options // [])
def split("", _pattern, _options), do: [""]
def split(binary, pattern, options) when is_regex(pattern) do
Regex.split(pattern, binary, global: options[:global])
end
@@ -383,7 +446,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 +462,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
@@ -410,6 +473,8 @@ defmodule String do
## Examples
iex> String.duplicate("abc", 0)
""
iex> String.duplicate("abc", 1)
"abc"
iex> String.duplicate("abc", 2)
@@ -417,7 +482,7 @@ defmodule String do
"""
@spec duplicate(t, pos_integer) :: t
def duplicate(subject, n) when is_integer(n) and n > 0 do
def duplicate(subject, n) when is_integer(n) and n >= 0 do
:binary.copy(subject, n)
end
@@ -773,8 +838,8 @@ defmodule String do
{34.0,""}
iex> String.to_float("34.25")
{34.25,""}
iex> String.to_float("56.5xyz")
{56.5,"xyz"}
iex> String.to_float("56.5xyz")
{56.5,"xyz"}
iex> String.to_float("pi")
:error
@@ -789,7 +854,7 @@ defmodule String do
{int_result, int_remainder} = :string.to_integer(charlist)
case int_result do
:error -> :error
_ -> {float(int_result), list_to_binary(int_remainder)}
_ -> {:erlang.float(int_result), list_to_binary(int_remainder)}
end
_ -> {result, list_to_binary(remainder)}
end
@@ -824,7 +889,7 @@ defmodule String do
end
defp do_starts_with(string, prefix) when is_binary(prefix) do
match?({0,_}, :binary.match(string, prefix))
match?({0, _}, :binary.match(string, prefix))
end
defp do_starts_with(_, _) do
+2 -2
View File
@@ -162,8 +162,8 @@ defmodule System do
defp write_tmp_dir(dir) do
case :file.read_file_info(dir) do
{:ok, info} ->
type_index = File.Stat.__index__ :type
access_index = File.Stat.__index__ :access
type_index = File.Stat.__record__(:index, :type)
access_index = File.Stat.__record__(:index, :access)
case { elem(info, type_index), elem(info, access_index) } do
{ :directory, access } when access in [:read_write, :write] ->
dir
+61 -60
View File
@@ -1,17 +1,52 @@
defmodule URI do
@on_load :preload_parsers
defrecord Info, [scheme: nil, path: nil, query: nil,
fragment: nil, authority: nil,
userinfo: nil, host: nil, port: nil,
specifics: nil]
import Bitwise
@moduledoc """
Utilities for working with and creating URIs.
"""
defrecord Info, [scheme: nil, path: nil, query: nil,
fragment: nil, authority: nil,
userinfo: nil, host: nil, port: nil]
import Bitwise
@ports [
{ "ftp", 21 },
{ "http", 80 },
{ "https", 443 },
{ "ldap", 389 },
{ "sftp", 22 },
{ "tftp", 69 },
]
Enum.each @ports, fn { scheme, port } ->
def normalize_scheme(unquote(scheme)), do: unquote(scheme)
def default_port(unquote(scheme)), do: unquote(port)
end
@doc """
Normalizes the scheme according to the spec by downcasing it.
"""
def normalize_scheme(nil), do: nil
def normalize_scheme(scheme), do: String.downcase(scheme)
@doc """
Returns the default port for a given scheme.
If the scheme is unknown to URI, returns nil.
Any scheme may be registered via `URI.default_port/2`.
"""
def default_port(scheme) when is_binary(scheme) do
{ :ok, dict } = :application.get_env(:elixir, :uri)
Dict.get(dict, scheme)
end
@doc """
Registers a scheme with a default port into Elixir.
"""
def default_port(scheme, port) when is_binary(scheme) and port > 0 do
{ :ok, dict } = :application.get_env(:elixir, :uri)
:application.set_env(:elixir, :uri, Dict.put(dict, scheme, port))
end
@doc """
Takes an enumerable (containing a sequence of two-item tuples)
and returns a string of k=v&k2=v2... where keys and values are
@@ -69,7 +104,7 @@ defmodule URI do
@doc """
Percent (URL) encodes a URI.
"""
def encode(s), do: bc <<c>> inbits s, do: <<percent(c) :: binary>>
def encode(s), do: bc(<<c>> inbits s, do: <<percent(c) :: binary>>)
defp percent(32), do: <<?+>>
defp percent(?-), do: <<?->>
@@ -141,41 +176,17 @@ defmodule URI do
if port, do: authority = authority <> ":" <> integer_to_binary(port)
end
info = URI.Info[
scheme: scheme && String.downcase(scheme), path: path, query: query,
scheme = normalize_scheme(scheme)
if nil?(port) and not nil?(scheme) do
port = default_port(scheme)
end
URI.Info[
scheme: scheme, path: path, query: query,
fragment: fragment, authority: authority,
userinfo: userinfo, host: host, port: port
]
scheme_specific(scheme, info)
end
@doc false
def scheme_module(scheme) do
if scheme do
module =
try do
Module.safe_concat(URI, String.upcase(scheme))
rescue
ArgumentError -> nil
end
if module && Code.ensure_loaded?(module) do
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
end
defp default_port(info, module) do
if info.port, do: info, else: info.port(module.default_port)
end
# Split an authority into its userinfo, host and port parts.
@@ -184,38 +195,28 @@ 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 && String.downcase(host), port }
{ userinfo, host, port }
end
# Regex.run returns empty strings sometimes. We want
# to replace those with nil for consistency.
defp nillify(l) do
lc s inlist l do
if size(s) > 0 do
s
else
nil
end
if size(s) > 0, do: s, else: nil
end
end
# Reference parsers so the parse/1 doesn't fail
# on safe_concat.
defp preload_parsers do
parsers = [URI.FTP, URI.HTTP, URI.HTTPS, URI.LDAP, URI.SFTP, URI.TFTP]
Enum.each parsers, Code.ensure_loaded(&1)
:ok
end
end
defimpl Binary.Chars, for: URI.Info do
def to_binary(uri) do
result = ""
def to_binary(URI.Info[] = uri) do
scheme = uri.scheme
if module = URI.scheme_module(uri.scheme) do
if module.default_port == uri.port, do: uri = uri.port(nil)
if scheme && (port = URI.default_port(scheme)) do
if uri.port == port, do: uri = uri.port(nil)
end
result = ""
if uri.scheme, do: result = result <> uri.scheme <> "://"
if uri.userinfo, do: result = result <> uri.userinfo <> "@"
if uri.host, do: result = result <> uri.host
-5
View File
@@ -1,5 +0,0 @@
defmodule URI.FTP do
@behavior URI.Parser
def default_port(), do: 21
def parse(info), do: info
end
-5
View File
@@ -1,5 +0,0 @@
defmodule URI.HTTP do
@behavior URI.Parser
def default_port(), do: 80
def parse(info), do: info
end
-5
View File
@@ -1,5 +0,0 @@
defmodule URI.HTTPS do
@behavior URI.Parser
def default_port(), do: 443
def parse(info), do: info
end
-7
View File
@@ -1,7 +0,0 @@
defmodule URI.LDAP do
@behavior URI.Parser
def default_port(), do: 389
# TODO: LDAP specific parsing.
def parse(info), do: info
end
-18
View File
@@ -1,18 +0,0 @@
defmodule URI.Parser do
@moduledoc """
Defines the behavior for each URI.Parser.
Check URI.HTTP for a possible implementation.
"""
use Behaviour
@doc """
Responsible for parsing extra URL information.
"""
defcallback parse(uri_info :: URI.Info.t) :: URI.Info.t
@doc """
Responsible for returning the default port.
"""
defcallback default_port() :: integer
end
-5
View File
@@ -1,5 +0,0 @@
defmodule URI.SFTP do
@behavior URI.Parser
def default_port(), do: 22
def parse(info), do: info
end
-5
View File
@@ -1,5 +0,0 @@
defmodule URI.TFTP do
@behavior URI.Parser
def default_port(), do: 69
def parse(info), do: info
end
+4 -4
View File
@@ -18,7 +18,7 @@ defmodule String.Unicode do
data_path = Path.join(__DIR__, "UnicodeData.txt")
{ codes, whitespace } = Enum.reduce File.iterator!(data_path), { [], [] }, fn(line, { cacc, wacc }) ->
{ codes, whitespace } = Enum.reduce File.stream!(data_path), { [], [] }, fn(line, { cacc, wacc }) ->
[ codepoint, _name, _category,
_class, bidi, _decomposition,
_numeric_1, _numeric_2, _numeric_3,
@@ -39,7 +39,7 @@ defmodule String.Unicode do
special_path = Path.join(__DIR__, "SpecialCasing.txt")
codes = Enum.reduce File.iterator!(special_path), codes, fn(line, acc) ->
codes = Enum.reduce File.stream!(special_path), codes, fn(line, acc) ->
[ codepoint, lower, title, upper, _comment ] = :binary.split(line, "; ", [:global])
key = to_binary.(codepoint)
:lists.keystore(key, 1, acc, { key, to_binary.(upper), to_binary.(lower), to_binary.(title) })
@@ -47,7 +47,7 @@ defmodule String.Unicode do
seqs_path = Path.join(__DIR__, "NamedSequences.txt")
seqs = Enum.map File.iterator!(seqs_path), fn(line) ->
seqs = Enum.map File.stream!(seqs_path), fn(line) ->
[ _name, codepoints ] = :binary.split(line, ";", [:global])
codepoints = :binary.split(codepoints, " ", [:global])
codepoints = Enum.map codepoints, Regex.replace(%r/\s+/, &1, "")
@@ -137,7 +137,7 @@ defmodule String.Unicode do
# Split
def split(""), do: ""
def split(""), do: [""]
def split(string) when is_binary(string) do
:lists.reverse do_split(string, "", [])
+1 -1
View File
@@ -24,4 +24,4 @@
{verbose, false}
]}.
{require_otp_vsn,"(R16).*"}.
{require_otp_vsn,"(R16B).*"}.
+13 -34
View File
@@ -2,10 +2,11 @@
-behaviour(application).
-export([main/1, start_cli/0,
scope_for_eval/1, scope_for_eval/2,
eval/2, eval/3, eval/4,
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").
-include_lib("kernel/include/file.hrl").
%% Top level types
-export_type([char_list/0, as_boolean/1]).
@@ -43,6 +44,14 @@ 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) ->
@@ -128,48 +137,18 @@ translate_forms(Tree, Binding, Opts) when is_list(Opts) ->
translate_forms(Tree, Binding, scope_for_eval(Opts));
translate_forms(Tree, Binding, #elixir_scope{} = Scope) ->
elixir_translator:translate(Tree, Scope#elixir_scope{
vars=binding_dict(Binding),
temp_vars=[],
clause_vars=nil,
counter=[]
}).
elixir_translator:translate(Tree, elixir_scope:vars_from_binding(Scope, Binding)).
eval_forms(Tree, Binding, Scope) ->
{ ParseTree, NewScope } = translate_forms(Tree, Binding, Scope),
case ParseTree of
[] -> { nil, Binding, NewScope };
_ ->
{value, Value, NewBinding} = erl_eval:exprs(ParseTree, normalize_binding(Binding)),
{Value, final_binding(NewBinding, NewScope#elixir_scope.vars), NewScope }
{value, Value, NewBinding} = erl_eval:exprs(ParseTree, elixir_scope:binding_for_eval(Binding, nil)),
{Value, elixir_scope:binding_from_vars(NewScope, NewBinding), NewScope }
end.
%% INTERNAL HELPERS
to_binary(Bin) when is_binary(Bin) -> Bin;
to_binary(List) when is_list(List) -> list_to_binary(List).
binding_dict(List) -> binding_dict(List, orddict:new()).
binding_dict([{{H,Kind},_}|T], Dict) -> binding_dict(T, orddict:store({ H, Kind }, H, Dict));
binding_dict([{H,_}|T], Dict) -> binding_dict(T, orddict:store({ H, nil }, H, Dict));
binding_dict([], Dict) -> Dict.
final_binding(Binding, Vars) -> final_binding(Binding, [], Binding, Vars).
final_binding([{Var,_}|T], Acc, Binding, Vars) ->
case lists:member($@, atom_to_list(Var)) of
true ->
final_binding(T, Acc, Binding, Vars);
false ->
RealName = orddict:fetch({ Var, nil }, Vars),
RealValue = proplists:get_value(RealName, Binding, nil),
final_binding(T, [{Var, RealValue}|Acc], Binding, Vars)
end;
final_binding([], Acc, _Binding, _Vars) -> lists:reverse(Acc).
normalize_binding(Binding) ->
Keyword = orddict:from_list(Binding),
case orddict:find('_@MODULE', Keyword) of
{ ok, _ } -> Keyword;
_ -> orddict:store('_@MODULE', nil, Keyword)
end.
+1
View File
@@ -134,6 +134,7 @@ 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) ->
+23 -21
View File
@@ -2,7 +2,7 @@
%% 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]).
-include("elixir.hrl").
@@ -14,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, _ } ->
@@ -25,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).
@@ -77,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.
@@ -95,15 +98,15 @@ 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, TAcc } = each_clause(X, Acc),
{ TX, { merge_clauses_scope(S, TAcc), [TAcc#elixir_scope.clause_vars|CV] } }
end,
@@ -163,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.
@@ -253,6 +256,5 @@ merge_clauses_scope(S1, S2) ->
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
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 -4
View File
@@ -84,12 +84,12 @@ eval_forms(Forms, Line, Vars, S) ->
%% Compile the module by forms based on the scope information
%% executes the callback in case of success. This automatically
%% handles errors and warnings. Used by this module and elixir_module.
module(Forms, S, Callback) ->
module(Forms, File, Callback) ->
Options = case get_opt(debug_info) of
true -> [debug_info];
_ -> []
end,
module(Forms, S#elixir_scope.file, Options, false, Callback).
module(Forms, File, Options, false, Callback).
module(Forms, File, RawOptions, Bootstrap, Callback) when
is_binary(File), is_list(Forms), is_list(RawOptions), is_boolean(Bootstrap), is_function(Callback) ->
@@ -208,9 +208,9 @@ 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/macro.ex",
"lib/elixir/lib/macro/env.ex",
@@ -218,7 +218,8 @@ core_main() ->
"lib/elixir/lib/code.ex",
"lib/elixir/lib/protocol.ex",
"lib/elixir/lib/enum.ex",
"lib/elixir/lib/binary/inspect.ex",
"lib/elixir/lib/inspect/algebra.ex",
"lib/elixir/lib/inspect.ex",
"lib/elixir/lib/binary/chars.ex",
"lib/elixir/lib/io.ex",
"lib/elixir/lib/path.ex",
+31 -18
View File
@@ -100,7 +100,7 @@ store_definition(Kind, Line, CheckClauses, Module, Call, Body, RawS) ->
{ 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
@@ -132,9 +132,10 @@ store_definition(Kind, Line, CheckClauses, Module, Name, Args, Guards, Body, #el
CTable = clauses_table(Module),
compile_super(Module, TS),
check_previous_defaults(Table, Line, Name, Arity, Kind, DefaultsLength, S),
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],
@@ -203,7 +204,7 @@ 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]);
elixir_errors:syntax_error(Line, File, "missing do keyword in ~ts", [Kind]);
translate_clause(Line, Kind, Unpacked, Guards, Body, S) ->
Expr = expr_from_body(Line, Body),
@@ -211,14 +212,6 @@ translate_clause(Line, Kind, Unpacked, Guards, Body, S) ->
{ 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 is_macro(Kind) andalso TS#elixir_scope.caller of
true ->
@@ -226,8 +219,8 @@ translate_clause(Line, Kind, Unpacked, Guards, Body, S) ->
{ '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,
{ [FClause], TS }.
@@ -317,8 +310,8 @@ store_each(Check, Kind, File, Location, Table, CTable, Defaults, {function, Line
FinalLocation = StoredLocation,
FinalDefaults = max(Defaults, StoredDefaults),
check_valid_kind(Line, File, Name, Arity, Kind, StoredKind),
check_valid_defaults(Line, File, Name, Arity, Kind, Defaults, StoredDefaults),
(Check and StoredCheck) andalso check_valid_clause(Line, File, Name, Arity, Kind, 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,
@@ -348,7 +341,18 @@ check_valid_defaults(_Line, _File, _Name, _Arity, _Kind, 0, _) -> [];
check_valid_defaults(Line, File, Name, Arity, Kind, _, 0) ->
elixir_errors:handle_file_warning(File, { Line, ?MODULE, { out_of_order_defaults, { Kind, Name, Arity } } });
check_valid_defaults(Line, File, Name, Arity, Kind, _, _) ->
elixir_errors:handle_file_warning(File, { Line, ?MODULE, { clauses_with_defaults, { Kind, Name, Arity } } }).
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",
@@ -362,8 +366,17 @@ assert_no_aliases_name(_Meta, _Aliases, _Args, _S) ->
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({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]);
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]);
@@ -372,4 +385,4 @@ 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]).
+1 -10
View File
@@ -1,7 +1,7 @@
% 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").
overridable(Module) ->
@@ -25,15 +25,6 @@ ensure_defined(Meta, Module, Tuple, S) ->
_ -> 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) ->
+44 -15
View File
@@ -13,9 +13,9 @@
-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' ].
@@ -23,9 +23,14 @@ 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
@@ -184,7 +189,7 @@ expand_macro_fun(Meta, Fun, Receiver, Name, Args, Module, S) ->
apply(Fun, [SArg|Args])
catch
Kind:Reason ->
Info = { Receiver, Name, length(Args), [{ file, S#elixir_scope.file }, { line, Line }] },
Info = { Receiver, Name, length(Args), location(Line, S) },
erlang:raise(Kind, Reason, munge_stacktrace(Info, erlang:get_stacktrace(), SArg))
end.
@@ -195,10 +200,29 @@ 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) ->
elixir_translator:translate_each(
elixir_quote:linify(?line(Meta), Tree),
S
).
Line = ?line(Meta),
try
elixir_translator:translate_each(
elixir_quote:linify(Line, Tree),
S
)
catch
Kind:Reason ->
erlang:raise(Kind, Reason, [mfa(Line, S)|erlang:get_stacktrace()])
end.
mfa(Line, #elixir_scope{module=nil} = S) ->
{ elixir_compiler, '__FILE__', 2, location(Line, S) };
mfa(Line, #elixir_scope{module=Module,function=nil} = S) ->
{ Module, '__MODULE__', 0, location(Line, S) };
mfa(Line, #elixir_scope{module=Module,function={ Name, Arity }} = S) ->
{ Module, Name, Arity, location(Line, S) }.
location(Line, S) ->
[{ file, S#elixir_scope.file }, { line, Line }].
%% Helpers
@@ -312,13 +336,18 @@ get_optional_macros(Receiver) ->
{ error, _ } -> []
end.
%% Functions imported from Kernel module. Sorted on compilation.
in_elixir_functions() ->
elixir_imported_functions() ->
try
?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.
@@ -352,7 +381,7 @@ in_erlang_functions() ->
{ byte_size, 1 },
% { date, 0 },
{ exit, 1 },
{ float, 1 },
% { float, 1 },
{ float_to_binary, 1 },
{ float_to_list, 1 },
{ hd, 1 },
+23 -8
View File
@@ -1,19 +1,24 @@
% A bunch of helpers to help to deal with errors in Elixir source code.
% This is not exposed in the Elixir language.
-module(elixir_errors).
-export([syntax_error/3, syntax_error/4, inspect/1,
-export([syntax_error/3, syntax_error/4,
compile_error/3, compile_error/4, inspect/1,
form_error/4, parse_error/4, assert_module_scope/3,
assert_no_function_scope/3, assert_function_scope/3,
assert_no_match_scope/3, assert_no_guard_scope/3,
assert_no_match_or_guard_scope/3,
assert_no_match_or_guard_scope/3, warn/1,
handle_file_warning/2, handle_file_warning/3, handle_file_error/2,
deprecation/3, deprecation/4, file_format/3]).
-include("elixir.hrl").
warn(Warning) ->
elixir_code_server:cast(register_warning),
io:put_chars(standard_error, Warning).
%% Handle inspecting for exceptions
inspect(Atom) when is_atom(Atom) ->
'Elixir.Binary.Inspect.Atom':inspect(Atom, []).
'Elixir.Inspect.Atom':inspect(Atom).
%% Raised during macros translation.
@@ -30,6 +35,16 @@ syntax_error(Meta, File, Format, Args) ->
Message = io_lib:format(Format, Args),
raise(Meta, File, 'Elixir.SyntaxError', unicode:characters_to_binary(Message)).
compile_error(Meta, File, Message) when is_list(Message) ->
compile_error(Meta, File, iolist_to_binary(Message));
compile_error(Meta, File, Message) when is_binary(Message) ->
raise(Meta, File, 'Elixir.CompileError', Message).
compile_error(Meta, File, Format, Args) ->
Message = io_lib:format(Format, Args),
raise(Meta, File, 'Elixir.CompileError', unicode:characters_to_binary(Message)).
%% Raised on tokenizing/parsing
-spec parse_error(non_neg_integer() | list(), file:filename(), iolist() | atom(), [] | iolist()) -> no_return().
@@ -93,14 +108,14 @@ handle_file_warning(_, File, {Line,erl_lint,{undefined_behaviour_func,{Fun,Arity
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));
warn(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))
warn(file_format(Line, File, Message))
end;
handle_file_warning(_, _File, {Line,erl_lint,{unused_var,_Var}}) when Line =< 0 ->
@@ -108,16 +123,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));
warn(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));
warn(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)).
warn(file_format(Line, File, Message)).
handle_file_warning(File, Desc) ->
handle_file_warning(false, File, Desc).
+9 -13
View File
@@ -196,18 +196,15 @@ intersection([], _All) -> [].
%% 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 },
@@ -221,10 +218,9 @@ special_form() ->
{'=',2},
{'__op__',2},
{'__op__',3},
{'__ambiguousop__','*'},
{'__scope__',2},
{'__block__','*'},
{'->','2'},
{'->','*'},
{'<<>>','*'},
{'{}','*'},
{'[]','*'},
+12 -8
View File
@@ -65,7 +65,11 @@ translate(Atom, S) when is_atom(Atom) ->
{ { atom, 0, Atom }, S };
translate(Bitstring, S) when is_bitstring(Bitstring) ->
{ elixir_tree_helpers:elixir_to_erl(Bitstring), S }.
{ elixir_tree_helpers:elixir_to_erl(Bitstring), S };
translate(Other, S) ->
elixir_errors:compile_error(0, S#elixir_scope.file, "invalid quoted expression: ~ts",
['Elixir.Kernel':inspect(Other)]).
%% Helpers
@@ -156,8 +160,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);
@@ -198,11 +202,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);
+33 -21
View File
@@ -3,9 +3,11 @@
-module(elixir_macros).
-export([translate/2]).
-import(elixir_translator, [translate_each/2, translate_args/2, translate_apply/7]).
-import(elixir_scope, [umergec/2]).
-import(elixir_errors, [syntax_error/3, syntax_error/4,
assert_no_function_scope/3, assert_module_scope/3, assert_no_match_or_guard_scope/3]).
-import(elixir_scope, [umergec/2, umergea/2]).
-import(elixir_errors, [compile_error/3, compile_error/4,
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(opt_in_types(Kind), Kind == atom orelse Kind == integer orelse Kind == float).
@@ -70,13 +72,15 @@ translate({ function, MetaFA, [{ '/', _, [{F, Meta, C}, A]}] }, S) when is_atom(
end,
case elixir_dispatch:import_function(WrappedMeta, F, A, S) of
false -> syntax_error(WrappedMeta, S#elixir_scope.file, "expected ~ts/~B to be a function, but it is a macro", [F, A]);
false -> compile_error(WrappedMeta, S#elixir_scope.file,
"expected ~ts/~B to be a function, but it is a macro", [F, A]);
Else -> Else
end;
translate({ function, Meta, [_] }, S) ->
translate({ function, Meta, [Arg] }, S) ->
assert_no_match_or_guard_scope(Meta, 'function', S),
syntax_error(Meta, S#elixir_scope.file, "invalid args for function");
syntax_error(Meta, S#elixir_scope.file, "invalid args for function/1: ~ts",
['Elixir.Macro':to_string(Arg)]);
translate({ function, Meta, [_,_,_] = Args }, S) when is_list(Args) ->
assert_no_match_or_guard_scope(Meta, 'function', S),
@@ -119,7 +123,7 @@ 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),
@@ -132,7 +136,7 @@ translate({'@', Meta, [{ Name, _, Args }]}, S) ->
%% Case
translate({'case', Meta, [Expr, KV]}, S) ->
translate({'case', Meta, [Expr, KV]}, S) when is_list(KV) ->
assert_no_match_or_guard_scope(Meta, 'case', S),
Clauses = elixir_clauses:get_pairs(Meta, do, KV, S),
{ TExpr, NS } = translate_each(Expr, S),
@@ -147,27 +151,27 @@ translate({'case', Meta, [Expr, KV]}, S) ->
%% Try
translate({'try', Meta, [Clauses]}, S) ->
translate({'try', Meta, [Clauses]}, S) when is_list(Clauses) ->
assert_no_match_or_guard_scope(Meta, 'try', S),
Do = proplists:get_value('do', Clauses, nil),
{ 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)),
{ TCatch, SC } = elixir_try:clauses(Meta, Catch, umergea(S, SB)),
After = proplists:get_value('after', Clauses, nil),
{ TAfter, SA } = elixir_translator:translate_each(After, umergec(S, SC)),
{ TAfter, SA } = elixir_translator:translate_each(After, umergea(S, SC)),
Else = elixir_clauses:get_pairs(Meta, else, Clauses, S),
{ TElse, SE } = elixir_clauses:match(Meta, Else, umergec(S, SA)),
{ TElse, SE } = elixir_clauses:match(Meta, Else, umergea(S, SA)),
SF = (umergec(S, SE))#elixir_scope{noname=S#elixir_scope.noname},
{ { 'try', ?line(Meta), pack(TDo), TElse, TCatch, pack(TAfter) }, SF };
%% Receive
translate({'receive', Meta, [KV] }, S) ->
translate({'receive', Meta, [KV] }, S) when is_list(KV) ->
assert_no_match_or_guard_scope(Meta, 'receive', S),
Do = elixir_clauses:get_pairs(Meta, do, KV, S, true),
@@ -185,12 +189,12 @@ translate({'receive', Meta, [KV] }, S) ->
%% Definitions
translate({defmodule, Meta, [Ref, KV]}, S) ->
translate({defmodule, Meta, [Ref, KV]}, S) when is_list(KV) ->
{ TRef, _ } = translate_each(Ref, S),
Block = case lists:keyfind(do, 1, KV) of
{ do, DoValue } -> DoValue;
false -> syntax_error(Meta, S#elixir_scope.file, "expected do: argument in defmodule")
false -> syntax_error(Meta, S#elixir_scope.file, "missing do keyword in defmodule")
end,
{ FRef, FS } = case TRef of
@@ -243,7 +247,11 @@ translate({ apply, Meta, [Left, Right, Args] }, S) when is_list(Args) ->
translate({ apply, Meta, Args }, S) ->
{ TArgs, NS } = translate_args(Args, S),
{ ?wrap_call(?line(Meta), erlang, apply, TArgs), NS }.
{ ?wrap_call(?line(Meta), erlang, apply, TArgs), NS };
translate({ Name, Meta, Args }, S) ->
syntax_error(Meta, S#elixir_scope.file,
"invalid arguments for macro ~ts/~B", [Name, length(Args)]).
%% Helpers
@@ -267,6 +275,9 @@ translate_in(Meta, Left, Right, S) ->
end,
{ InGuard, TExpr } = case TRight of
{ nil, _ } ->
Expr = { atom, Line, false },
{ Cache, Expr };
{ cons, _, _, _ } ->
[H|T] = elixir_tree_helpers:cons_to_list(TRight),
Expr = lists:foldr(fun(X, Acc) ->
@@ -299,7 +310,8 @@ translate_in(Meta, Left, Right, S) ->
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")
compile_error(Meta, S#elixir_scope.file, "invalid args for operator in, it expects a "
"compile time list or range on the right side when used in guard expressions")
end
end,
@@ -318,15 +330,15 @@ 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.
%% defmodule :foo
expand_module(Raw, Module, #elixir_scope{module=Nesting}) when is_atom(Raw); Nesting == nil ->
Module;
expand_module(Raw, _Module, _S) when is_atom(Raw) ->
Raw;
%% defmodule Hello
expand_module({ '__aliases__', _, [H] }, _Module, S) ->
+5 -6
View File
@@ -8,7 +8,7 @@
-define(persisted_attr, '__persisted_attributes').
eval_quoted(Module, Quoted, RawBinding, Opts) ->
Binding = binding_for_eval(Module, RawBinding),
Binding = elixir_scope:binding_for_eval(RawBinding, Module),
Scope = scope_for_eval(Module, Opts),
elixir_def:reset_last(Module),
@@ -27,8 +27,6 @@ scope_for_eval(Module, #elixir_scope{} = S) ->
scope_for_eval(Module, Opts) ->
scope_for_eval(Module, elixir:scope_for_eval(Opts)).
binding_for_eval(Module, Binding) -> [{'_@MODULE',Module}|Binding].
%% TABLE METHODS
data_table(Module) ->
@@ -247,7 +245,7 @@ spec_for_macro(Else) -> Else.
%% Loads the form into the code server.
load_form(Line, Forms, S) ->
elixir_compiler:module(Forms, S, fun(Module, Binary) ->
elixir_compiler:module(Forms, S#elixir_scope.file, fun(Module, Binary) ->
EvalS = scope_for_eval(Module, S),
Env = elixir_scope:to_ex_env({ Line, EvalS }),
eval_callbacks(Line, Module, after_compile, [Env, Binary], EvalS),
@@ -332,8 +330,9 @@ else_clause() ->
% HELPERS
eval_callbacks(Line, Module, Name, Args, S) ->
Binding = binding_for_eval(Module, []),
eval_callbacks(Line, Module, Name, Args, RawS) ->
Binding = elixir_scope:binding_for_eval([], Module),
S = elixir_scope:vars_from_binding(RawS, Binding),
Callbacks = lists:reverse(ets:lookup_element(data_table(Module), Name, 2)),
Meta = [{line,Line},{require,false}],
+294 -276
View File
@@ -1,21 +1,24 @@
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 regex_op right_op bin_concat_op
match_op send_op default_op when_op pipe_op in_op inc_op range_op
andand_op oror_op and_op or_op comp_expr_op colon_colon_op three_op at_op
expr container_expr block_expr no_parens_expr identifier_expr max_expr
bracket_expr bracket_at_expr base_expr matched_expr unmatched_expr
op_expr matched_op_expr no_parens_op_expr
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 parens_call
call_args_no_parens call_args_no_parens_strict call_args_parens_not_one
stab stab_eol stab_op stab_expr stab_maybe_expr
kw_eol kw_expr kw_comma kw_base
matched_kw_expr matched_kw_comma matched_kw_base
dot_op dot_ref dot_identifier dot_op_identifier dot_do_identifier
container_comma_expr_base container_comma_expr container_arg container_args
call_args_parens_comma_expr call_args_parens parens_call
call_args_no_parens_one call_args_no_parens_expr call_args_no_parens_comma_expr
call_args_no_parens_all call_args_no_parens_many call_args_no_parens_many_strict
stab stab_eol stab_expr stab_maybe_expr stab_parens_many
kw_eol kw_expr kw_comma kw
call_args_no_parens_kw_expr call_args_no_parens_kw_comma call_args_no_parens_kw
dot_op dot_alias dot_identifier dot_op_identifier dot_do_identifier
dot_paren_identifier dot_punctuated_identifier dot_bracket_identifier
var list bracket_access bit_string tuple
do_block fn_eol do_eol end_eol block_eol block_item block_list
@@ -26,46 +29,43 @@ 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.
%% There are two shift/reduce conflicts coming from call_args_parens.
Expect 2.
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.
Right 180 regex_op.
Right 190 right_op.
Left 200 range_op.
Left 210 three_op.
Left 220 add_op.
Left 230 mult_op.
Right 240 bin_concat_op.
Right 250 two_op.
Nonassoc 300 unary_op.
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
@@ -79,116 +79,132 @@ grammar -> '$empty' : [nil].
% Note expressions are on reverse order
expr_list -> expr : ['$1'].
expr_list -> empty_paren : [nil].
expr_list -> expr_list eol expr : ['$3'|'$1'].
paren_expr -> empty_paren : nil.
paren_expr -> expr : '$1'.
expr -> empty_paren : nil.
expr -> matched_expr : '$1'.
expr -> no_parens_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 -> matched_expr no_parens_op_expr : build_op(element(1, '$2'), '$1', element(2, '$2')).
matched_expr -> unary_op_eol matched_expr : build_unary_op('$1', '$2').
matched_expr -> unary_op_eol no_parens_expr : build_unary_op('$1', '$2').
matched_expr -> at_op_eol matched_expr : build_unary_op('$1', '$2').
matched_expr -> at_op_eol no_parens_expr : build_unary_op('$1', '$2').
matched_expr -> bracket_at_expr : '$1'.
matched_expr -> fn_expr : '$1'.
matched_expr -> identifier_expr : '$1'.
no_parens_expr -> dot_punctuated_identifier call_args_no_parens_many_strict : build_identifier('$1', '$2').
no_parens_expr -> dot_op_identifier call_args_no_parens_many_strict : build_identifier('$1', '$2').
no_parens_expr -> dot_identifier call_args_no_parens_many_strict : build_identifier('$1', '$2').
unmatched_expr -> empty_paren op_expr : build_op(element(1, '$2'), nil, element(2, '$2')).
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 -> regex_op expr : { '$1', '$2' }.
op_expr -> right_op expr : { '$1', '$2' }.
op_expr -> andand_op expr : { '$1', '$2' }.
op_expr -> three_op expr : { '$1', '$2' }.
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' }.
matched_op_expr -> match_op matched_expr : { '$1', '$2' }.
matched_op_expr -> add_op matched_expr : { '$1', '$2' }.
matched_op_expr -> mult_op matched_expr : { '$1', '$2' }.
matched_op_expr -> two_op matched_expr : { '$1', '$2' }.
matched_op_expr -> regex_op matched_expr : { '$1', '$2' }.
matched_op_expr -> right_op matched_expr : { '$1', '$2' }.
matched_op_expr -> andand_op matched_expr : { '$1', '$2' }.
matched_op_expr -> three_op matched_expr : { '$1', '$2' }.
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' }.
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_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_strict do_block : build_identifier('$1', '$2' ++ '$3').
block_expr -> dot_punctuated_identifier call_args_no_parens_all do_block : build_identifier('$1', '$2' ++ '$3').
block_expr -> dot_identifier call_args_no_parens_all 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'.
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' }.
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).
call_expr -> max_expr : '$1'.
no_parens_op_expr -> match_op_eol no_parens_expr : { '$1', '$2' }.
no_parens_op_expr -> add_op_eol no_parens_expr : { '$1', '$2' }.
no_parens_op_expr -> mult_op_eol no_parens_expr : { '$1', '$2' }.
no_parens_op_expr -> exp_op_eol no_parens_expr : { '$1', '$2' }.
no_parens_op_expr -> two_op_eol no_parens_expr : { '$1', '$2' }.
no_parens_op_expr -> and_op_eol no_parens_expr : { '$1', '$2' }.
no_parens_op_expr -> or_op_eol no_parens_expr : { '$1', '$2' }.
no_parens_op_expr -> tail_op_eol no_parens_expr : { '$1', '$2' }.
no_parens_op_expr -> than_op_eol no_parens_expr : { '$1', '$2' }.
no_parens_op_expr -> in_op_eol no_parens_expr : { '$1', '$2' }.
no_parens_op_expr -> inc_op_eol no_parens_expr : { '$1', '$2' }.
no_parens_op_expr -> when_op_eol no_parens_expr : { '$1', '$2' }.
no_parens_op_expr -> range_op_eol no_parens_expr : { '$1', '$2' }.
no_parens_op_expr -> default_op_eol no_parens_expr : { '$1', '$2' }.
no_parens_op_expr -> type_op_eol no_parens_expr : { '$1', '$2' }.
no_parens_op_expr -> comp_op_eol no_parens_expr : { '$1', '$2' }.
no_parens_op_expr -> arrow_op_eol no_parens_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' }.
identifier_expr -> dot_punctuated_identifier call_args_no_parens_one : build_identifier('$1', '$2').
identifier_expr -> dot_op_identifier call_args_no_parens_one : build_identifier('$1', '$2').
identifier_expr -> dot_identifier call_args_no_parens_one : build_identifier('$1', '$2').
identifier_expr -> dot_punctuated_identifier : build_identifier('$1', []).
identifier_expr -> dot_do_identifier : build_identifier('$1', nil).
identifier_expr -> var : build_identifier('$1', nil).
identifier_expr -> max_expr : '$1'.
max_expr -> fn_eol stab end_eol : build_fn('$1', build_stab(lists:reverse('$2'))).
max_expr -> open_paren stab close_paren : build_stab(lists:reverse('$2')).
max_expr -> bracket_expr : '$1'.
max_expr -> parens_call call_args_parens : build_identifier('$1', '$2').
max_expr -> parens_call call_args_parens call_args_parens : build_nested_parens('$1', '$2', '$3').
max_expr -> dot_ref : '$1'.
max_expr -> dot_alias : '$1'.
max_expr -> base_expr : '$1'.
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 -> sigil : build_sigil('$1').
%% Blocks
@@ -217,9 +233,13 @@ 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', unwrap_splice('$1'), '$3').
stab_expr -> call_args_parens_not_one stab_op stab_maybe_expr : build_op('$2', unwrap_splice('$1'), '$3').
stab_expr -> stab_op_eol stab_maybe_expr : build_op('$1', [], '$2').
stab_expr -> call_args_no_parens_all stab_op_eol stab_maybe_expr :
build_op('$2', unwrap_when(unwrap_splice('$1')), '$3').
stab_expr -> stab_parens_many stab_op_eol stab_maybe_expr :
build_op('$2', unwrap_splice('$1'), '$3').
stab_expr -> stab_parens_many when_op 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.
@@ -239,7 +259,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'.
@@ -258,107 +278,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'.
regex_op -> '=~' : '$1'.
regex_op -> '=~' eol : '$1'.
two_op_eol -> two_op : '$1'.
two_op_eol -> two_op eol : '$1'.
right_op -> '|>' : '$1'.
right_op -> '|>' eol : '$1'.
default_op_eol -> default_op : '$1'.
default_op_eol -> default_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'.
type_op_eol -> type_op : '$1'.
type_op_eol -> type_op eol : '$1'.
default_op -> '//' : '$1'.
default_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'.
colon_colon_op -> '::' : '$1'.
colon_colon_op -> '::' eol : '$1'.
match_op_eol -> match_op : '$1'.
match_op_eol -> match_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'.
and_op_eol -> and_op : '$1'.
and_op_eol -> and_op eol : '$1'.
at_op -> '@' : '$1'.
at_op -> '@' eol : '$1'.
or_op_eol -> or_op : '$1'.
or_op_eol -> or_op eol : '$1'.
match_op -> '=' : '$1'.
match_op -> '=' eol : '$1'.
tail_op_eol -> tail_op : '$1'.
tail_op_eol -> tail_op eol : '$1'.
andand_op -> '&&' : '$1'.
andand_op -> '&&' eol : '$1'.
than_op_eol -> than_op : '$1'.
than_op_eol -> than_op eol : '$1'.
oror_op -> '||' : '$1'.
oror_op -> '||' eol : '$1'.
in_op_eol -> in_op : '$1'.
in_op_eol -> in_op eol : '$1'.
and_op -> 'and' : '$1'.
and_op -> 'and' eol : '$1'.
inc_op_eol -> inc_op : '$1'.
inc_op_eol -> inc_op eol : '$1'.
or_op -> 'or' : '$1'.
or_op -> 'or' eol : '$1'.
or_op -> 'xor' : '$1'.
or_op -> 'xor' eol : '$1'.
when_op_eol -> when_op : '$1'.
when_op_eol -> when_op eol : '$1'.
pipe_op -> '|' : '$1'.
pipe_op -> '|' eol : '$1'.
stab_op_eol -> stab_op : '$1'.
stab_op_eol -> stab_op eol : '$1'.
bin_concat_op -> '<>' : '$1'.
bin_concat_op -> '<>' eol : '$1'.
range_op_eol -> range_op : '$1'.
range_op_eol -> range_op eol : '$1'.
in_op -> 'in' : '$1'.
in_op -> 'in' eol : '$1'.
at_op_eol -> at_op : '$1'.
at_op_eol -> at_op eol : '$1'.
inc_op -> 'inlist' : '$1'.
inc_op -> 'inlist' eol : '$1'.
inc_op -> 'inbits' : '$1'.
inc_op -> 'inbits' eol : '$1'.
comp_op_eol -> comp_op : '$1'.
comp_op_eol -> comp_op eol : '$1'.
when_op -> 'when' : '$1'.
when_op -> 'when' 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
@@ -368,7 +350,7 @@ dot_op -> '.' eol : '$1'.
dot_identifier -> identifier : '$1'.
dot_identifier -> matched_expr dot_op identifier : build_dot('$2', '$1', '$3').
dot_ref -> matched_expr dot_op aliases : build_dot_ref('$2', '$1', '$3').
dot_alias -> matched_expr dot_op aliases : build_dot_alias('$2', '$1', '$3').
dot_op_identifier -> op_identifier : '$1'.
dot_op_identifier -> matched_expr dot_op op_identifier : build_dot('$2', '$1', '$3').
@@ -388,85 +370,106 @@ dot_punctuated_identifier -> matched_expr dot_op punctuated_identifier : build_d
parens_call -> dot_paren_identifier : '$1'.
parens_call -> matched_expr dot_call_op : { '.', [{line,?line('$2')}], ['$1'] }. % Fun/local calls
% Function calls
% Function calls with no parentheses
matched_comma_expr -> matched_expr : ['$1'].
matched_comma_expr -> matched_comma_expr ',' matched_expr : ['$3'|'$1'].
call_args_no_parens_expr -> matched_expr : '$1'.
call_args_no_parens_expr -> empty_paren : nil.
call_args_no_parens_expr -> no_parens_expr : throw_no_parens_many_strict('$1').
call_args_no_parens_strict -> call_args_no_parens : '$1'.
call_args_no_parens_strict -> open_paren ')' : throw_no_parens_strict('$1').
call_args_no_parens_strict -> open_paren matched_kw_base close_paren : throw_no_parens_strict('$1').
call_args_no_parens_strict -> open_paren matched_expr ',' call_args_no_parens close_paren : throw_no_parens_strict('$1').
call_args_no_parens_comma_expr -> matched_expr ',' call_args_no_parens_expr : ['$3', '$1'].
call_args_no_parens_comma_expr -> call_args_no_parens_comma_expr ',' call_args_no_parens_expr : ['$3'|'$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_no_parens_all -> call_args_no_parens_one : '$1'.
call_args_no_parens_all -> call_args_no_parens_many : '$1'.
call_args_parens_not_one -> open_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'].
call_args_no_parens_one -> call_args_no_parens_kw : ['$1'].
call_args_no_parens_one -> matched_expr : ['$1'].
call_args_no_parens_one -> no_parens_expr : ['$1'].
base_comma_expr -> paren_expr ',' : ['$1'].
base_comma_expr -> base_comma_expr paren_expr ',' : ['$2'|'$1'].
call_args_no_parens_many -> matched_expr ',' call_args_no_parens_kw : ['$1', '$3'].
call_args_no_parens_many -> call_args_no_parens_comma_expr : lists:reverse('$1').
call_args_no_parens_many -> call_args_no_parens_comma_expr ',' call_args_no_parens_kw : lists:reverse(['$3'|'$1']).
comma_expr -> paren_expr : ['$1'].
comma_expr -> kw_base : ['$1'].
comma_expr -> base_comma_expr : '$1'.
comma_expr -> base_comma_expr paren_expr : ['$2'|'$1'].
comma_expr -> base_comma_expr kw_base : ['$2'|'$1'].
call_args_no_parens_many_strict -> call_args_no_parens_many : '$1'.
call_args_no_parens_many_strict -> empty_paren : throw_no_parens_strict('$1').
call_args_no_parens_many_strict -> open_paren call_args_no_parens_kw close_paren : throw_no_parens_strict('$1').
call_args_no_parens_many_strict -> open_paren call_args_no_parens_many close_paren : throw_no_parens_strict('$1').
optional_comma_expr -> paren_expr : '$1'.
optional_comma_expr -> paren_expr ',' : '$1'.
stab_parens_many -> empty_paren : [].
stab_parens_many -> open_paren call_args_no_parens_kw close_paren : ['$2'].
stab_parens_many -> open_paren call_args_no_parens_many close_paren : '$2'.
call_args -> comma_expr : lists:reverse('$1').
% Containers and function calls with parentheses
call_args_parens -> open_paren ')' : [].
call_args_parens -> open_paren call_args close_paren : '$2'.
container_expr -> empty_paren : nil.
container_expr -> matched_expr : '$1'.
container_expr -> unmatched_expr : '$1'.
container_expr -> no_parens_expr : throw_no_parens_many_strict('$1').
container_comma_expr_base -> container_expr ',' : ['$1'].
container_comma_expr_base -> container_comma_expr_base container_expr ',' : ['$2'|'$1'].
container_comma_expr -> container_expr : ['$1'].
container_comma_expr -> kw : ['$1'].
container_comma_expr -> container_comma_expr_base : '$1'.
container_comma_expr -> container_comma_expr_base container_expr : ['$2'|'$1'].
container_comma_expr -> container_comma_expr_base kw : ['$2'|'$1'].
container_arg -> container_expr : '$1'.
container_arg -> container_expr ',' : '$1'.
container_args -> container_comma_expr : lists:reverse('$1').
call_args_parens_comma_expr -> container_expr : ['$1'].
call_args_parens_comma_expr -> call_args_parens_comma_expr ',' container_expr : ['$3'|'$1'].
call_args_parens -> empty_paren : [].
call_args_parens -> open_paren no_parens_expr close_paren : ['$2'].
call_args_parens -> open_paren kw close_paren : ['$2'].
call_args_parens -> open_paren call_args_parens_comma_expr close_paren : lists:reverse('$2').
call_args_parens -> open_paren call_args_parens_comma_expr ',' kw close_paren : lists:reverse(['$4'|'$2']).
% KV
kw_eol -> kw_identifier : '$1'.
kw_eol -> kw_identifier eol : '$1'.
kw_expr -> kw_eol paren_expr : { ?exprs('$1'),'$2' }.
kw_expr -> kw_eol container_expr : { ?exprs('$1'),'$2' }.
kw_comma -> kw_expr ',' : ['$1'].
kw_comma -> kw_comma kw_expr ',' : ['$2'|'$1'].
kw -> kw_expr : ['$1'].
kw -> kw_comma : lists:reverse('$1').
kw -> kw_comma kw_expr : lists:reverse(['$2'|'$1']).
kw_base -> kw_expr : ['$1'].
kw_base -> kw_comma : lists:reverse('$1').
kw_base -> kw_comma kw_expr : lists:reverse(['$2'|'$1']).
matched_kw_expr -> kw_eol matched_expr : {?exprs('$1'),'$2'}.
matched_kw_expr -> kw_eol empty_paren : {?exprs('$1'),nil}.
matched_kw_comma -> matched_kw_expr : ['$1'].
matched_kw_comma -> matched_kw_expr ',' matched_kw_comma : ['$1'|'$3'].
matched_kw_base -> matched_kw_comma : '$1'.
call_args_no_parens_kw_expr -> kw_eol call_args_no_parens_expr : {?exprs('$1'),'$2'}.
call_args_no_parens_kw_comma -> call_args_no_parens_kw_expr : ['$1'].
call_args_no_parens_kw_comma -> call_args_no_parens_kw_expr ',' call_args_no_parens_kw_comma : ['$1'|'$3'].
call_args_no_parens_kw -> call_args_no_parens_kw_comma : '$1'.
% Lists
bracket_access -> open_bracket ']' : { [], ?line('$1') }.
bracket_access -> open_bracket optional_comma_expr close_bracket : { '$2', ?line('$1') }.
bracket_access -> open_bracket kw_base close_bracket : { '$2', ?line('$1') }.
bracket_access -> open_bracket container_arg close_bracket : { '$2', ?line('$1') }.
bracket_access -> open_bracket kw close_bracket : { '$2', ?line('$1') }.
list -> open_bracket ']' : [].
list -> open_bracket kw_base close_bracket : '$2'.
list -> open_bracket optional_comma_expr close_bracket : ['$2'].
list -> open_bracket paren_expr ',' call_args close_bracket : ['$2'|'$4'].
list -> open_bracket kw close_bracket : '$2'.
list -> open_bracket container_arg close_bracket : ['$2'].
list -> open_bracket container_expr ',' container_args close_bracket : ['$2'|'$4'].
% Tuple
tuple -> open_curly '}' : build_tuple('$1', []).
tuple -> open_curly optional_comma_expr close_curly : build_tuple('$1', ['$2']).
tuple -> open_curly paren_expr ',' call_args close_curly : build_tuple('$1', ['$2'|'$4']).
tuple -> open_curly container_arg close_curly : build_tuple('$1', ['$2']).
tuple -> open_curly container_expr ',' container_args close_curly : build_tuple('$1', ['$2'|'$4']).
% Bitstrings
bit_string -> open_bit '>>' : { '<<>>', [{line,?line('$1')}], [] }.
bit_string -> open_bit call_args close_bit : { '<<>>', [{line,?line('$1')}], '$2' }.
bit_string -> open_bit container_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)).
@@ -479,17 +482,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 };
@@ -503,15 +503,15 @@ build_block([nil]) -> { '__block__', [], [n
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([Expr]) -> Expr;
build_block(Exprs) -> { '__block__', [], Exprs }.
%% Dots
build_dot_ref(Dot, { '__aliases__', _, Left }, { 'aliases', _, Right }) ->
build_dot_alias(Dot, { '__aliases__', _, Left }, { 'aliases', _, Right }) ->
{ '__aliases__', [{line,?line(Dot)}], Left ++ Right };
build_dot_ref(Dot, Other, { 'aliases', _, Right }) ->
build_dot_alias(Dot, Other, { 'aliases', _, Right }) ->
{ '__aliases__', [{line,?line(Dot)}], [Other|Right] }.
build_dot(Dot, Left, Right) ->
@@ -533,8 +533,8 @@ build_identifier({ '.', Meta, _ } = Dot, Args) ->
build_identifier({ Keyword, Line }, Args) when Keyword == fn ->
{ fn, [{line,Line}], Args };
build_identifier({ op_identifier, Line, Identifier }, Args) ->
{ '__ambiguousop__', [{line,Line}], [{ Identifier, [{line,Line}], nil }|Args] };
build_identifier({ op_identifier, Line, Identifier }, [Arg]) ->
{ Identifier, [{ambiguous_op,nil},{line,Line}], [Arg] };
build_identifier({ _, Line, Identifier }, Args) ->
{ Identifier, [{line,Line}], Args }.
@@ -555,13 +555,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 }.
@@ -579,25 +579,25 @@ 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 cause,
%% 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
@@ -607,12 +607,30 @@ unwrap_splice([{ '__block__', [], [{ unquote_splicing, _, _ }] = 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 "
throw(?line(Token), "unexpected parenthesis. If you are making a "
"function call, do not insert spaces in between the function name and the "
"opening parentheses. Syntax error before: ", ")").
"opening parentheses. Syntax error before: ", "'('").
throw_no_parens_many_strict(Token) ->
Line =
case lists:keyfind(line, 1, element(2, Token)) of
{ line, L } -> L;
false -> 0
end,
throw(Line, "unexpected comma. Parentheses are required to solve ambiguity "
"in nested calls. Syntax error before: ", "','").
+1 -1
View File
@@ -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;
+88 -34
View File
@@ -1,6 +1,6 @@
%% Implements Elixir quote.
-module(elixir_quote).
-export([escape/2, erl_escape/3, erl_quote/3, linify/2, unquote/5]).
-export([escape/2, erl_escape/3, erl_quote/4, linify/2, unquote/6, join/5]).
-include("elixir.hrl").
%% Apply the line from site call on quoted contents.
@@ -9,7 +9,7 @@ linify(Line, Exprs) when is_integer(Line) ->
do_linify(Line, { Left, Meta, Right }) ->
NewMeta = case ?line(Meta) of
0 -> lists:keystore(line, 1, Meta, { line, Line });
0 -> keystore(line, Meta, Line);
_ -> Meta
end,
{ do_linify(Line, Left), NewMeta, do_linify(Line, Right) };
@@ -24,10 +24,10 @@ do_linify(_, Else) -> Else.
%% Some expressions cannot be unquoted at compilation time.
%% This function is responsible for doing runtime unquoting.
unquote(_File, Meta, Left, { '__aliases__', _, Args }, nil) ->
unquote(_File, _Line, Meta, Left, { '__aliases__', _, Args }, nil) ->
{ '__aliases__', Meta, [Left|Args] };
unquote(_File, Meta, Left, Right, nil) when is_atom(Right) ->
unquote(_File, _Line, Meta, Left, Right, nil) when is_atom(Right) ->
case atom_to_list(Right) of
"Elixir." ++ _ ->
{ '__aliases__', Meta, [Left, Right] };
@@ -35,22 +35,43 @@ unquote(_File, Meta, Left, Right, nil) when is_atom(Right) ->
{ { '.', Meta, [Left, Right] }, Meta, [] }
end;
unquote(_File, Meta, Left, Right, Args) when is_atom(Right) ->
unquote(_File, _Line, Meta, Left, { Right, _, Context }, nil) when is_atom(Right), is_atom(Context) ->
{ { '.', Meta, [Left, Right] }, Meta, [] };
unquote(_File, _Line, Meta, Left, { Right, _, Args }, nil) when is_atom(Right) ->
{ { '.', Meta, [Left, Right] }, Meta, Args };
unquote(File, Meta, _Left, _Right, _Args) ->
elixir_errors:syntax_error(Meta, File, "expected unquote after dot to return an atom or an alias").
unquote(File, Line, _Meta, _Left, Right, nil) ->
elixir_errors:syntax_error(Line, File, "expected unquote after dot to return an atom, "
"an alias or a quoted call, got: ~ts", ['Elixir.Macro':to_string(Right)]);
unquote(_File, _Line, Meta, Left, Right, Args) when is_atom(Right) ->
{ { '.', Meta, [Left, Right] }, Meta, Args };
unquote(_File, _Line, Meta, Left, { Right, _, Context }, Args) when is_atom(Right), is_atom(Context) ->
{ { '.', Meta, [Left, Right] }, Meta, Args };
unquote(File, Line, _Meta, _Left, Right, _Args) ->
elixir_errors:syntax_error(Line, File, "expected unquote after dot with args to return an atom "
"or a quoted call, got: ~ts", ['Elixir.Macro':to_string(Right)]).
join(_File, _Line, Left, Right, Rest) when is_list(Left), is_list(Right), is_list(Rest) ->
Rest ++ Left ++ Right;
join(_File, _Line, Left, Right, Rest) ->
[H|T] = lists:reverse(Rest ++ Left),
lists:reverse([{ '|', [], [H, Right] }|T]).
%% Escapes the given expression. It is similar to quote, but
%% lines are kept and hygiene mechanisms are disabled.
escape(Expr, Unquote) ->
quote(Expr, #elixir_quote{
quote(Expr, nil, #elixir_quote{
line=keep,
vars_hygiene=false,
aliases_hygiene=false,
imports_hygiene=false,
unquote=Unquote,
mark=false
escape=true
}, nil).
erl_escape(Expr, Unquote, S) ->
@@ -60,11 +81,24 @@ erl_escape(Expr, Unquote, S) ->
%% Quotes an expression and return its quoted Elixir AST.
quote(Expr, Q, S) ->
do_quote(Expr, Q, S).
quote(Expr, nil, Q, S) ->
do_quote(Expr, Q, S);
erl_quote(Expr, Q, S) ->
{ QExpr, TQ } = 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 }.
@@ -79,12 +113,12 @@ do_quote({ unquote, _Meta, [Expr] }, #elixir_quote{unquote=true} = Q, _) ->
%% 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({ Def, Meta, Args }, #elixir_quote{escape=false} = Q, S) when ?defs(Def); Def == defmodule; Def == alias; Def == import ->
NewMeta = keystore(context, Meta, 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({ { '.', _, [_, Def] } = Target, Meta, Args }, #elixir_quote{escape=false} = Q, S) when ?defs(Def); Def == defmodule; Def == alias; Def == import ->
NewMeta = keystore(context, Meta, Q#elixir_quote.context),
do_quote_tuple({ Target, NewMeta, Args }, Q, S);
%% Aliases
@@ -98,7 +132,7 @@ do_quote({ '__aliases__', Meta, [H|T] } = Alias, #elixir_quote{aliases_hygiene=t
Atom when is_atom(Atom) -> Atom;
Aliases when is_list(Aliases) -> false
end,
AliasMeta = lists:keystore(alias, 1, Meta, { alias, Annotation }),
AliasMeta = keystore(alias, Meta, Annotation),
do_quote_tuple({ '__aliases__', AliasMeta, [H|T] }, Q, S);
%% Vars
@@ -124,23 +158,29 @@ 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({ Name, Meta, ArgsOrAtom } = Tuple, #elixir_quote{imports_hygiene=true} = Q, S) when is_atom(Name) ->
do_quote({ Name, Meta, ArgsOrAtom }, #elixir_quote{imports_hygiene=true} = Q, S) when is_atom(Name) ->
Arity = case is_atom(ArgsOrAtom) of
true -> 0;
false -> length(ArgsOrAtom)
end,
case (lists:keyfind(import, 1, Meta) == false) andalso
case (keyfind(import, Meta) == false) andalso
elixir_dispatch:find_import(Meta, Name, Arity, S) of
false -> do_quote_tuple(Tuple, Q, S);
false ->
AmbMeta =
case (Arity == 1) andalso keyfind(ambiguous_op, Meta) of
{ ambiguous_op, nil } -> keystore(ambiguous_op, Meta, Q#elixir_quote.context);
_ -> Meta
end,
do_quote_tuple({ Name, AmbMeta, ArgsOrAtom }, Q, S);
Receiver ->
ImportMeta = lists:keystore(import, 1,
lists:keystore(context, 1, Meta, { context, Q#elixir_quote.context }),
{ import, Receiver }),
ImportMeta = keystore(import,
keystore(context, Meta, Q#elixir_quote.context),
Receiver),
do_quote_tuple({ Name, ImportMeta, ArgsOrAtom }, Q, S)
end;
do_quote({ _, _, _ } = Tuple, Q, S) ->
do_quote({ _, _, _ } = Tuple, #elixir_quote{escape=false} = Q, S) ->
do_quote_tuple(Tuple, Q, S);
%% Literals
@@ -155,7 +195,7 @@ do_quote({ Left, Right }, Q, S) ->
{ TRight, RQ } = do_quote(Right, LQ, S),
{ { TLeft, TRight }, RQ };
do_quote(Tuple, Q, S) when is_tuple(Tuple) ->
do_quote(Tuple, #elixir_quote{escape=true} = Q, S) when is_tuple(Tuple) ->
{ TT, TQ } = do_quote(tuple_to_list(Tuple), Q, S),
{ { '{}', [], TT }, TQ };
@@ -168,16 +208,16 @@ do_quote(Other, Q, _) ->
%% Quote helpers
do_quote_call(Left, Meta, Expr, Args, Q, S) ->
All = [meta(Meta, Q), Left, { unquote, Meta, [Expr] }, Args],
All = [?line(Meta), 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
case (keyfind(import_fa, Meta) == false) andalso
elixir_dispatch:find_import(Meta, F, A, S) of
false -> Meta;
Receiver -> lists:keystore(import_fa, 1, Meta, { import_fa, { Receiver, Q#elixir_quote.context } })
Receiver -> keystore(import_fa, Meta, { Receiver, Q#elixir_quote.context })
end,
do_quote_tuple({ Target, NewMeta, Args }, Q, S).
@@ -189,15 +229,29 @@ do_quote_tuple({ Left, Meta, Right }, Q, S) ->
meta(Meta, #elixir_quote{line=keep}) ->
Meta;
meta(Meta, #elixir_quote{line=nil}) ->
lists:keydelete(line, 1, Meta);
keydelete(line, Meta);
meta(Meta, #elixir_quote{line=Line}) ->
lists:keystore(line, 1, Meta, { line, Line }).
keystore(line, Meta, Line).
keyfind(Key, Meta) ->
lists:keyfind(Key, 1, Meta).
keydelete(Key, Meta) ->
lists:keydelete(Key, 1, Meta).
keystore(Key, Meta, Value) ->
lists:keystore(Key, 1, Meta, { Key, Value }).
%% 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([{ '|', Meta, [{ unquote_splicing, _, [Left] }, Right] }|T], #elixir_quote{unquote=true} = Q, S) ->
%% Process the remaining entries on the list.
%% For [1, 2, 3, unquote_splicing(arg)|tail], this will quote
%% 1, 2 and 3, which could even be unquotes.
{ TT, QT } = do_splice(T, Q, S, [], []),
{ TR, QR } = do_quote(Right, QT, S),
%% Do the joining at runtime when we are aware of the values.
Args = [{ '__FILE__', [], nil }, ?line(Meta), Left, TR, TT],
{ { { '.', Meta, [elixir_quote, join] }, Meta, Args }, QR#elixir_quote{unquoted=true} };
do_splice(List, Q, S) ->
do_splice(List, Q, S, [], []).
+76 -17
View File
@@ -5,13 +5,15 @@
serialize/1, deserialize/1,
serialize_with_vars/2, deserialize_with_vars/2,
to_erl_env/1, to_ex_env/1,
umergev/2, umergec/2, merge_clause_vars/2
vars_from_binding/2, binding_for_eval/2, binding_from_vars/2,
umergev/2, umergec/2, umergea/2, merge_clause_vars/2
]).
-include("elixir.hrl").
translate_var(Meta, Name, Kind, S, Callback) ->
Line = ?line(Meta),
Vars = S#elixir_scope.vars,
Tuple = { Name, Kind },
case Name of
'_' ->
@@ -20,9 +22,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);
@@ -33,11 +35,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;
@@ -76,18 +81,24 @@ 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, ContextModules, MacroAliases }) ->
Requires, Functions, Macros, ContextModules, MacroAliases, Vars }) ->
#elixir_scope{module=Module,file=File,
function=Function,aliases=Aliases,context=Context,
requires=Requires,macros=Macros,functions=Functions,
context_modules=ContextModules,macro_aliases=MacroAliases}.
context_modules=ContextModules,macro_aliases=MacroAliases,
list_vars=Vars}.
to_ex_env({ Line, #elixir_scope{module=Module,file=File,
function=Function,aliases=Aliases,context=Context,
requires=Requires,macros=Macros,functions=Functions,
context_modules=ContextModules,macro_aliases=MacroAliases} }) when is_integer(Line) ->
context_modules=ContextModules,macro_aliases=MacroAliases,
vars=Vars,list_vars=ListVars} }) when is_integer(Line) ->
{ 'Elixir.Macro.Env', Module, File, Line, Function, Aliases,
Context, Requires, Functions, Macros, ContextModules, MacroAliases }.
Context, Requires, Functions, Macros, ContextModules, MacroAliases,
list_vars(ListVars, Vars) }.
list_vars(nil, Vars) -> [K || { K, _ } <- Vars];
list_vars(Other, _) -> Other.
% Provides a tuple with only the scope information we want to serialize.
@@ -130,8 +141,8 @@ deserialize_with_vars({ File, Functions, Requires, Macros,
counter=[{'',length(Vars)}]
}.
% Receives two scopes and return a new scope based on the second
% with their variables merged.
%% Receives two scopes and return a new scope based on the second
%% with their variables merged.
umergev(S1, S2) ->
V1 = S1#elixir_scope.vars,
@@ -143,11 +154,22 @@ umergev(S1, S2) ->
clause_vars=merge_clause_vars(C1, C2)
}.
% Receives two scopes and return the later scope
% keeping the variables from the first (counters,
% imports and everything else are passed forward).
%% Receives two scopes and return the first scope with
%% counters and flags from the later.
umergec(S1, S2) ->
S1#elixir_scope{
counter=S2#elixir_scope.counter,
extra_guards=S2#elixir_scope.extra_guards,
super=S2#elixir_scope.super,
caller=S2#elixir_scope.caller
}.
%% Receives two scopes and return the later scope
%% keeping the variables from the first (counters,
%% imports and everything else are passed forward).
umergea(S1, S2) ->
S2#elixir_scope{
vars=S1#elixir_scope.vars,
temp_vars=S1#elixir_scope.temp_vars,
@@ -179,3 +201,40 @@ var_merger(_Var, K1, K2) ->
var_number([$@|T], _Acc) -> var_number(T, []);
var_number([H|T], Acc) -> var_number(T, [H|Acc]);
var_number([], Acc) -> list_to_integer(lists:reverse(Acc)).
%% Setup the vars in scope from binding
vars_from_binding(Scope, Binding) ->
Scope#elixir_scope{
vars=binding_dict(Binding),
temp_vars=[],
clause_vars=nil,
counter=[]
}.
binding_dict(List) -> binding_dict(List, orddict:new()).
binding_dict([{{H,Kind},_}|T], Dict) -> binding_dict(T, orddict:store({ H, Kind }, H, Dict));
binding_dict([{H,_}|T], Dict) -> binding_dict(T, orddict:store({ H, nil }, H, Dict));
binding_dict([], Dict) -> Dict.
binding_for_eval(Binding, Module) ->
Keyword = orddict:from_list(Binding),
case orddict:find('_@MODULE', Keyword) of
{ ok, _ } -> Keyword;
_ -> orddict:store('_@MODULE', Module, Keyword)
end.
binding_from_vars(#elixir_scope{vars=Vars}, Binding) ->
binding_from_vars(Binding, [], Binding, Vars).
binding_from_vars([{Var,_}|T], Acc, Binding, Vars) ->
case lists:member($@, atom_to_list(Var)) of
true ->
binding_from_vars(T, Acc, Binding, Vars);
false ->
RealName = orddict:fetch({ Var, nil }, Vars),
RealValue = proplists:get_value(RealName, Binding, nil),
binding_from_vars(T, [{Var, RealValue}|Acc], Binding, Vars)
end;
binding_from_vars([], Acc, _Binding, _Vars) -> lists:reverse(Acc).
+198 -120
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
@@ -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 = 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
@@ -282,7 +307,7 @@ 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) ->
tokenize([$:,T|String], Line, Scope, Tokens) when ?is_atom_start(T) ->
{ Rest, Atom } = tokenize_atom([T|String], [], Scope),
tokenize(Rest, Line, Scope, [{ atom, Line, Atom }|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,13 +368,24 @@ 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 == $> ->
@@ -354,21 +397,58 @@ tokenize([T|Rest], Line, Scope, Tokens) when T == $(;
Token = { list_to_atom([T]), Line },
handle_terminator(Rest, Line, Scope, Token, Tokens);
% ## 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);
% ## 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));
@@ -410,13 +490,13 @@ tokenize([H|_] = String, Line, Scope, Tokens) when ?is_downcase(H); H == $_ ->
% Ambiguous unary/binary operators tokens
tokenize([Space, Sign, NotMarker|T], Line, Scope, [{ Identifier, _, _ } = H|Tokens]) when
Sign == $+ orelse Sign == $-,
?is_horizontal_space(Space),
not(?is_space(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
@@ -456,26 +536,23 @@ handle_strings(T, Line, H, #scope{file=File} = Scope, Tokens) ->
{ error, { Line, "invalid interpolation in key ", [$"|T] } }
end;
{ NewLine, Parts, Rest } ->
Token = { string_type(H),Line,unescape_tokens(Parts) },
Token = { string_type(H), Line, unescape_tokens(Parts) },
tokenize(Rest, NewLine, Scope, [Token|Tokens]);
Error ->
interpolation_error(Error, " (for string starting at line ~B)", [Line])
end.
handle_comp_op([$:|Rest], Line, Op, Scope, Tokens) when ?is_space(hd(Rest)) ->
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 ?is_space(hd(Rest)) ->
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 = check_call_identifier(identifier, Line, Op, Rest),
@@ -703,7 +780,7 @@ tokenize_any_identifier(String, Line, Acc, Scope) ->
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) ->
tokenize_kw_or_call_identifier(_Kind, Line, Atom, [$:,H|_]) when ?is_atom_start(H) ->
{ error, { Line, "keyword argument must be followed by space after: ", atom_to_list(Atom) ++ [$:] } };
tokenize_kw_or_call_identifier(Kind, Line, Atom, Rest) ->
@@ -813,15 +890,16 @@ 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;
check_keyword(_, _, _, _) -> nomatch.
@@ -836,19 +914,19 @@ 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;
+2 -2
View File
@@ -24,7 +24,7 @@ setup(Module) ->
end.
cleanup(Module) ->
if_tracker(Module, fun(Pid) -> ?tracker:stop(Pid) end).
if_tracker(Module, fun(Pid) -> unlink(Pid), ?tracker:stop(Pid) end).
record_local(Tuple, Module) when is_atom(Module) ->
if_tracker(Module, fun(Pid) ->
@@ -122,7 +122,7 @@ 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 }) ->
format_error({unused_import, Module}) ->
io_lib:format("unused import ~ts", [elixir_errors:inspect(Module)]);
format_error({unused_args,{Name, Arity}}) ->
+115 -94
View File
@@ -4,10 +4,11 @@
-export([forms/4, 'forms!'/4]).
-export([translate/2, translate_each/2, translate_arg/2,
translate_args/2, translate_apply/7, translate_fn/3]).
-import(elixir_scope, [umergev/2, umergec/2]).
-import(elixir_errors, [syntax_error/3, syntax_error/4, parse_error/4,
assert_function_scope/3, assert_module_scope/3, assert_no_guard_scope/3,
assert_no_match_or_guard_scope/3]).
-import(elixir_scope, [umergev/2, umergec/2, umergea/2]).
-import(elixir_errors, [syntax_error/3, syntax_error/4,
compile_error/3, compile_error/4,
assert_function_scope/3, assert_module_scope/3,
assert_no_guard_scope/3, assert_no_match_or_guard_scope/3]).
-include("elixir.hrl").
forms(String, StartLine, File, Opts) ->
@@ -26,8 +27,10 @@ forms(String, StartLine, File, Opts) ->
'forms!'(String, StartLine, File, Opts) ->
case forms(String, StartLine, File, Opts) of
{ ok, Forms } -> Forms;
{ error, { Line, Error, Token } } -> parse_error(Line, File, Error, Token)
{ ok, Forms } ->
Forms;
{ error, { Line, Error, Token } } ->
elixir_errors:parse_error(Line, File, Error, Token)
end.
translate(Forms, S) ->
@@ -62,9 +65,6 @@ translate_each({ '__block__', Meta, Args }, S) when is_list(Args) ->
{ TArgs, NS } = translate(Args, S),
{ { block, ?line(Meta), TArgs }, NS };
translate_each({ '__scope__', _Meta, [[{erl,true}],[{do,Expr}]] }, S) ->
{ Expr, S };
translate_each({ '__scope__', _Meta, [[{file,File}],[{do,Expr}]] }, S) ->
Old = S#elixir_scope.file,
{ TExpr, TS } = translate_each(Expr, S#elixir_scope{file=File}),
@@ -80,18 +80,6 @@ translate_each({ '__op__', Meta, [Op, Left, Right] }, S) when is_atom(Op) ->
{ [TLeft, TRight], NS } = translate_args([Left, Right], S),
{ { op, ?line(Meta), convert_op(Op), TLeft, TRight }, NS };
translate_each({ '__ambiguousop__', Meta, [Var, H|T] }, S) ->
{ Name, _, Kind } = Var,
case orddict:find({ Name, Kind }, S#elixir_scope.vars) of
error -> translate_each({ Name, Meta, [H|T] }, S);
_ ->
case T of
[] -> translate_each(rellocate_ambiguous_op(H, Var), S);
_ -> syntax_error(Meta, S#elixir_scope.file, "Many arguments given to ~s, but ~s is a variable. Use even spaces to solve ambiguity.", [Name, Name])
end
end;
%% Lexical
translate_each({ alias, Meta, [Ref] }, S) ->
@@ -106,7 +94,7 @@ translate_each({ alias, Meta, [Ref, KV] }, S) ->
{ atom, _, Old } ->
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")
compile_error(Meta, S#elixir_scope.file, "invalid args for alias, expected a compile time atom or alias as argument")
end;
translate_each({ require, Meta, [Ref] }, S) ->
@@ -123,7 +111,7 @@ translate_each({ require, Meta, [Ref, KV] }, S) ->
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")
compile_error(Meta, S#elixir_scope.file, "invalid args for require, expected a compile time atom or alias as argument")
end;
translate_each({ import, Meta, [Left] }, S) ->
@@ -149,7 +137,7 @@ translate_each({ import, Meta, [Left, Right, KV] }, S) ->
Selector = case TSelector of
{ atom, _, SelectorAtom } -> SelectorAtom;
_ -> syntax_error(Meta, S#elixir_scope.file, "invalid selector for import, expected an atom")
_ -> compile_error(Meta, S#elixir_scope.file, "invalid selector for import, expected a compile time atom")
end,
case TRef of
@@ -158,7 +146,7 @@ translate_each({ import, Meta, [Left, Right, KV] }, S) ->
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")
compile_error(Meta, S#elixir_scope.file, "invalid name for import, expected a compile time atom or alias")
end;
%% Pseudo variables
@@ -200,14 +188,14 @@ translate_each({ '__aliases__', Meta, _ } = Alias, S) ->
%% Quoting
translate_each({ Unquote, Meta, [_|_] }, S) when Unquote == unquote; Unquote == unquote_splicing ->
syntax_error(Meta, S#elixir_scope.file, "~p called outside quote", [Unquote]);
compile_error(Meta, S#elixir_scope.file, "~p called outside quote", [Unquote]);
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")
syntax_error(Meta, S#elixir_scope.file, "missing do keyword in quote")
end;
translate_each({ quote, Meta, [_] }, S) ->
@@ -217,10 +205,10 @@ translate_each({ quote, Meta, [KV, Do] }, S) when is_list(Do) ->
Exprs =
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, "missing do keyword in quote")
end,
ValidOpts = [hygiene, context, var_context, location, line, file, unquote],
ValidOpts = [hygiene, context, var_context, location, line, file, unquote, binding, bind_quoted],
{ TKV, ST } = translate_opts(Meta, quote, ValidOpts, KV, S),
Hygiene = case lists:keyfind(hygiene, 1, TKV) of
@@ -234,7 +222,7 @@ translate_each({ quote, Meta, [KV, Do] }, S) when is_list(Do) ->
{ 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");
compile_error(Meta, S#elixir_scope.file, "invalid :context for quote, expected a compile time atom or an alias");
false ->
case S#elixir_scope.module of
nil -> 'Elixir';
@@ -251,39 +239,43 @@ translate_each({ quote, Meta, [KV, Do] }, S) when is_list(Do) ->
false -> { nil, nil }
end,
Line = case lists:keyfind(line, 1, TKV) of
{ line, LineValue } -> LineValue;
false -> DefaultLine
end,
Line = proplists:get_value(line, TKV, DefaultLine),
File = proplists:get_value(file, TKV, DefaultFile),
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,
WExprs = 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")
compile_error(Meta, S#elixir_scope.file, "invalid :file for quote, expected a compile time binary")
end,
{ Binding, DefaultUnquote } = case lists:keyfind(binding, 1, TKV) of
{ binding, B } when is_list(B) ->
elixir_errors:deprecation(Meta, S#elixir_scope.file, ":binding in quote is deprecated in favor of :bind_quoted"),
{ B, false };
false ->
case lists:keyfind(bind_quoted, 1, TKV) of
{ bind_quoted, BQ } -> { BQ, false };
false -> { nil, true }
end
end,
Unquote = case lists:keyfind(unquote, 1, TKV) of
{ unquote, false } -> false;
_ -> true
{ unquote, Bool } when is_boolean(Bool) -> Bool;
false -> DefaultUnquote
end,
Q = #elixir_quote{vars_hygiene=Vars, line=Line, unquote=Unquote,
aliases_hygiene=Aliases, imports_hygiene=Imports, context=Context},
{ TExprs, _TQ, TS } = elixir_quote:erl_quote(WExprs, Q, ST),
{ TExprs, _TQ, TS } = elixir_quote:erl_quote(QExprs, Binding, Q, ST),
{ TExprs, TS };
translate_each({ quote, Meta, [_, _] }, S) ->
@@ -301,7 +293,8 @@ translate_each({ 'var!', Meta, [{Name, _, Atom}, Kind] }, S) when is_atom(Name),
translate_each({ 'var!', Meta, [Name, Kind] }, S) when is_atom(Name) ->
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])
compile_error(Meta, S#elixir_scope.file, "expected var!(~ts) to expand to an existing "
"variable or be a part of a match", [Name])
end);
translate_each({ 'var!', Meta, [_, _] }, S) ->
@@ -320,7 +313,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),
@@ -331,15 +324,11 @@ translate_each({ super, Meta, Args } = Original, S) ->
{ _, Arity } = Function,
{ TArgs, TS } = if
is_atom(Args) andalso Arity == 0 ->
elixir_def_overridable:retrieve_args(Meta, Arity, S);
is_atom(Args) ->
elixir_errors:deprecation(Meta, S#elixir_scope.file, "omitting arguments of super is deprecated, please call super with arguments instead"),
elixir_def_overridable:retrieve_args(Meta, Arity, S);
not is_atom(Args) andalso length(Args) == Arity ->
length(Args) == Arity ->
translate_args(Args, S);
true ->
syntax_error(Meta, S#elixir_scope.file, "super must be called with the same number of arguments as the current function")
syntax_error(Meta, S#elixir_scope.file, "super must be called with the same number of "
"arguments as the current function")
end,
Super = elixir_def_overridable:name(Module, Function),
@@ -356,20 +345,20 @@ 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 ->
syntax_error(Meta, S#elixir_scope.file, "unbound variable ^~ts", [Name])
compile_error(Meta, S#elixir_scope.file, "unbound variable ^~ts", [Name])
end;
translate_each({ '^', Meta, [ { Name, _, Kind } ] }, S) when is_atom(Name), is_atom(Kind) ->
syntax_error(Meta, S#elixir_scope.file,
compile_error(Meta, S#elixir_scope.file,
"cannot use ^~ts outside of match clauses", [Name]);
translate_each({ '^', Meta, [ Expr ] }, S) ->
syntax_error(Meta, S#elixir_scope.file,
"the unary operator ^ can only be used with variables, invalid expression ^~ts", ['Elixir.Macro':to_binary(Expr)]);
"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() ->
@@ -378,7 +367,12 @@ translate_each({ Name, Meta, Kind }, S) when is_atom(Name), is_atom(Kind) ->
%% Local calls
translate_each({ '->', Meta, _Args }, S) ->
syntax_error(Meta, S#elixir_scope.file, "unhandled operator ->");
translate_each({ Atom, Meta, Args } = Original, S) when is_atom(Atom) ->
assert_no_ambiguous_op(Atom, Meta, Args, S),
case elixir_partials:is_sequential(Args) andalso
elixir_dispatch:import_function(Meta, Atom, length(Args), S) of
false ->
@@ -387,13 +381,16 @@ 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)]);
compile_error(Meta, S#elixir_scope.file,
"cannot invoke function ~ts/~B inside match", [Atom, length(Args)]);
guard ->
Arity = length(Args),
File = S#elixir_scope.file,
case Arity of
0 -> syntax_error(Meta, File, "unknown variable ~ts or cannot invoke function ~ts/~B inside guard", [Atom, Atom, Arity]);
_ -> syntax_error(Meta, File, "cannot invoke local ~ts/~B inside guard", [Atom, Arity])
0 -> compile_error(Meta, File, "unknown variable ~ts or cannot invoke "
"function ~ts/~B inside guard", [Atom, Atom, Arity]);
_ -> compile_error(Meta, File, "cannot invoke local ~ts/~B inside guard",
[Atom, Arity])
end;
_ ->
translate_local(Meta, Atom, Args, S)
@@ -426,7 +423,7 @@ translate_each({ { '.', _, [Left, Right] }, Meta, Args } = Original, S) when is_
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",
compile_error(Meta, S#elixir_scope.file, "cannot invoke remote function ~ts.~ts/~B inside ~ts",
[elixir_errors:inspect(Receiver), Right, length(Args), Context]);
_ ->
Callback()
@@ -435,7 +432,7 @@ translate_each({ { '.', _, [Left, Right] }, Meta, Args } = Original, S) when is_
_ ->
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",
compile_error(Meta, S#elixir_scope.file, "cannot invoke remote function ~ts/~B inside ~ts",
[Right, length(Args), Context]);
_ ->
Callback()
@@ -466,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
@@ -478,18 +475,30 @@ translate_each(Literal, S) ->
%% Opts
translate_opts(Meta, Kind, Allowed, Opts, S) ->
{ TOpts, TS } = translate_each(Opts, S),
FOpts = elixir_tree_helpers:erl_to_elixir(TOpts),
validate_opts(Meta, Kind, Allowed, FOpts, TS),
{ FOpts, TS }.
{ 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])
compile_error(Meta, S#elixir_scope.file,
"unsupported option ~ts given to ~s", ['Elixir.Kernel':inspect(Key), Kind])
end || { Key, _ } <- Opts, not lists:member(Key, Allowed)];
validate_opts(Meta, Kind, _Allowed, _Opts, S) ->
syntax_error(Meta, S#elixir_scope.file, "invalid options for ~s, expected a keyword list", [Kind]).
compile_error(Meta, S#elixir_scope.file, "invalid options for ~s, expected a keyword list", [Kind]).
%% Quote
@@ -499,7 +508,7 @@ expand_quote_context(Meta, Alias, Msg, S) ->
{ { atom, _, Atom }, _ } ->
Atom;
_ ->
syntax_error(Meta, S#elixir_scope.file, "~ts, expected a compile time available alias or an atom", [Msg])
compile_error(Meta, S#elixir_scope.file, "~ts, expected a compile time available alias or an atom", [Msg])
end.
%% Require
@@ -526,14 +535,14 @@ translate_alias(Meta, IncludeByDefault, Old, TKV, S) ->
true -> Old
end;
_ ->
syntax_error(Meta, S#elixir_scope.file,
"invalid args for alias, expected an atom or alias in option :as")
compile_error(Meta, S#elixir_scope.file,
"invalid :as for alias, expected a compile time atom or alias")
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)])
false -> compile_error(Meta, S#elixir_scope.file,
"invalid :as for alias, nested alias ~s not allowed", [elixir_errors:inspect(New)])
end,
{ { atom, ?line(Meta), nil }, elixir_aliases:store(Meta, New, Old, S) }.
@@ -552,14 +561,15 @@ no_alias_expansion({ '__aliases__', Meta, [H|T] }) when (H /= 'Elixir') and is_a
no_alias_expansion(Other) ->
Other.
is_atom_tuple({ atom, _, _ }) -> true;
is_atom_tuple(_) -> false.
%% Function
translate_fn(Meta, Clauses, S) ->
Line = ?line(Meta),
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))
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),
@@ -567,13 +577,16 @@ translate_fn(Meta, Clauses, S) ->
case length(lists:usort(Arities)) of
1 ->
{ { 'fun', Line, { clauses, TClauses } }, umergec(S, NS) };
{ { '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,function=nil} = S) ->
compile_error(Meta, S#elixir_scope.file, "function ~ts/~B undefined", [Name, length(Args)]);
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),
@@ -602,14 +615,14 @@ translate_local(Meta, Name, Args, S) ->
translate_arg(Arg, { Acc, S }) ->
{ TArg, TAcc } = translate_each(Arg, Acc),
{ TArg, { umergec(Acc, TAcc), umergev(S, TAcc) } }.
{ TArg, { umergea(Acc, TAcc), umergev(S, TAcc) } }.
translate_args(Args, #elixir_scope{context=match} = S) ->
translate(Args, S);
translate_args(Args, S) ->
{ TArgs, { SC, SV } } = lists:mapfoldl(fun translate_arg/2, {S, S}, Args),
{ TArgs, umergec(SV, SC) }.
{ TArgs, umergea(SV, SC) }.
%% Translate apply
%% Used by both apply and external function invocation macros.
@@ -650,14 +663,20 @@ convert_op('<=') -> '=<';
convert_op('<-') -> '!';
convert_op(Else) -> Else.
is_atom_tuple({ atom, _, _ }) -> true;
is_atom_tuple(_) -> false.
assert_no_ambiguous_op(Name, Meta, [Arg], S) ->
case lists:keyfind(ambiguous_op, 1, Meta) of
{ ambiguous_op, Kind } ->
case orddict:find({ Name, Kind }, S#elixir_scope.vars) of
error -> ok;
_ ->
syntax_error(Meta, S#elixir_scope.file, "\"~ts ~ts\" looks like a function call but "
"there is a variable named \"~ts\", please use explicit parenthesis or even spaces",
[Name, 'Elixir.Macro':to_string(Arg), Name])
end;
_ -> ok
end;
rellocate_ambiguous_op({ Op, Meta, [Expr] }, Var) when Op == '+'; Op == '-' ->
{ Op, Meta, [Var, Expr] };
rellocate_ambiguous_op({ Call, Meta, [H|T] }, Var) ->
{ Call, Meta, [rellocate_ambiguous_op(H, Var)|T] }.
assert_no_ambiguous_op(_Atom, _Meta, _Args, _S) -> ok.
%% Comprehensions
@@ -668,7 +687,7 @@ translate_comprehension(Meta, Kind, Args, S) ->
{ TExpr, SE } = translate_comprehension_do(Meta, Kind, Expr, SC),
{ { Kind, ?line(Meta), TExpr, TCases }, umergec(S, SE) };
_ ->
syntax_error(Meta, S#elixir_scope.file, "keyword argument :do missing for comprehension ~s", [Kind])
syntax_error(Meta, S#elixir_scope.file, "missing do keyword in comprehension ~s", [Kind])
end.
translate_comprehension_do(_Meta, bc, { '<<>>', _, _ } = Expr, S) ->
@@ -694,5 +713,7 @@ translate_comprehension_clause(_Meta, {inlist, Meta, [Left, Right]}, S) ->
{ { generate, ?line(Meta), TLeft, TRight }, SL };
translate_comprehension_clause(Meta, X, S) ->
Line = ?line(Meta),
{ TX, TS } = translate_each(X, S),
elixir_tree_helpers:convert_to_boolean(?line(Meta), TX, true, false, TS).
{ BX, BS } = elixir_tree_helpers:convert_to_boolean(Line, TX, true, false, TS),
{ { match, Line, { var, Line, '_' }, BX }, BS }.
+12 -22
View File
@@ -2,7 +2,7 @@
%% to generate erlang abstract format for basic structures
%% as lists, condition clauses, etc.
-module(elixir_tree_helpers).
-export([elixir_to_erl/1, erl_to_elixir/1, split_last/1,
-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").
@@ -34,8 +34,11 @@ list_to_cons(Line, List, Tail) ->
elixir_to_erl(Tree) when is_tuple(Tree) ->
{ tuple, 0, [elixir_to_erl(X) || X <- tuple_to_list(Tree)] };
elixir_to_erl([]) ->
{ nil, 0 };
elixir_to_erl(Tree) when is_list(Tree) ->
elixir_to_erl_cons(lists:reverse(Tree), { nil, 0 });
elixir_to_erl_cons_1(Tree, []);
elixir_to_erl(Tree) when is_atom(Tree) ->
{ atom, 0, Tree };
@@ -49,26 +52,13 @@ elixir_to_erl(Tree) when is_float(Tree) ->
elixir_to_erl(Tree) when is_binary(Tree) ->
{ bin, 0, [{ bin_element, 0, { string, 0, binary_to_list(Tree) }, default, default }] }.
elixir_to_erl_cons([H|T], Acc) ->
elixir_to_erl_cons(T, { cons, 0, elixir_to_erl(H), Acc });
elixir_to_erl_cons_1([H|T], Acc) -> elixir_to_erl_cons_1(T, [H|Acc]);
elixir_to_erl_cons_1(Other, Acc) -> elixir_to_erl_cons_2(Acc, elixir_to_erl(Other)).
elixir_to_erl_cons([], Acc) -> Acc.
erl_to_elixir({ tuple, _, Args }) ->
list_to_tuple([erl_to_elixir(X) || X <- Args]);
erl_to_elixir({ Kind, _, Value }) when Kind == atom; Kind == integer; Kind == float ->
Value;
erl_to_elixir({ bin, _, [{ bin_element, 0, { string, 0, String }, default, default }] }) ->
list_to_binary(String);
erl_to_elixir({ nil, _ }) -> [];
erl_to_elixir({ cons, _, Left, Right }) -> [erl_to_elixir(Left)|erl_to_elixir(Right)];
erl_to_elixir(Other) ->
{ '__scope__', [], [[{erl,true}],[{do,Other}]] }.
elixir_to_erl_cons_2([H|T], Acc) ->
elixir_to_erl_cons_2(T, { cons, 0, elixir_to_erl(H), Acc });
elixir_to_erl_cons_2([], Acc) ->
Acc.
%% Others
@@ -94,7 +84,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
+20 -20
View File
@@ -6,11 +6,11 @@
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 }];
@@ -23,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),
@@ -44,17 +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;
_ ->
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
@@ -79,11 +79,11 @@ 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;
@@ -92,12 +92,12 @@ normalize_rescue(Meta, Condition, S) ->
{ { atom, _, Atom }, _ } ->
normalize_rescue(Meta, { in, Meta, [{ '_', Meta, nil }, [Atom]] }, S);
_ ->
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")
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),
@@ -114,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
}.
@@ -238,8 +238,8 @@ erlang_rescue_guard_for(Meta, Var, 'Elixir.ErlangError') ->
%% 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]).
@@ -269,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] }.
+18 -14
View File
@@ -2,27 +2,31 @@ ExUnit.start []
defmodule KernelTest do
use ExUnit.Case, async: true
doctest Kernel
doctest Kernel.SpecialForms
doctest Access.List
doctest Binary.Chars.List
doctest Binary.Inspect.Atom
doctest Binary.Inspect.BitString
doctest Binary.Inspect.List
doctest Binary.Inspect.Tuple
doctest Binary.Inspect.Number
doctest Binary.Inspect.Regex
doctest Inspect.Atom
doctest Inspect.BitString
doctest Inspect.List
doctest Inspect.Number
doctest Inspect.Regex
doctest Inspect.Tuple
doctest Bitwise
doctest Code
doctest Dict
doctest Enum
doctest IO.ANSI
doctest Regex
doctest String
doctest Keyword
doctest Kernel
doctest Kernel.SpecialForms
doctest List
doctest Macro
doctest Module
doctest OptionParser
doctest Path
doctest Module
doctest Macro
doctest List
doctest Keyword
doctest Regex
doctest Set
doctest Stream
doctest String
doctest Inspect.Algebra
end
+20 -57
View File
@@ -3,73 +3,36 @@ Code.require_file "test_helper.exs", __DIR__
defmodule AccessTest do
use ExUnit.Case, async: true
# 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])
@mod -13
-13 = @mod
test :nil do
assert nil[:foo] == nil
end
test :list do
assert [foo: :bar][:foo] == :bar
assert [foo: [bar: :baz]][:foo][:bar] == :baz
assert [foo: [bar: :baz]][:fuu][:bar] == nil
end
test :nil do
assert nil[:foo] == nil
end
# Test nil at compilation time does not fail
# and that @config[:foo] has proper precedence.
nil = @config[:foo]
@config [foo: :bar]
:bar = @config[:foo]
@mod :lists
[1,2,3] = @mod.flatten([1,[2],3])
test :atom do
exception = assert_raise RuntimeError, fn ->
foo = :foo
foo[:atom]
end
assert exception.message == "The access protocol can only be invoked for atoms at compilation time, tried to invoke it for :foo"
assert exception.message == "The access protocol can only be invoked for atoms " <>
"at compilation time, tried to invoke it for :foo"
end
end
defmodule Access.RecordTest do
use ExUnit.Case, async: true
defrecord Config, integer: 0
test :keywords do
assert Config[] == { Config, 0 }
assert Config[integer: 1] == { Config, 1 }
end
test :in_match_with_variable do
assert get_var(Config.new) == 0
assert get_var(Config.new(integer: 1)) == 1
end
test :in_match_with_record_match do
assert is_config(Config.new) == true
assert is_config({ Access.AtomTest, 1 }) == false
assert is_config({ Config, 1, 2 }) == false
end
test :in_match_with_field_match do
assert is_zero(Config.new) == true
assert is_zero(Config.new(integer: 1)) == false
end
test :match do
assert Config[integer: 1] = Config.new(integer: 1)
refute Config[integer: 1] = Config.new(integer: 0)
end
defp get_var(Config[integer: integer]) do
integer
end
defp is_zero(Config[integer: 0]), do: true
defp is_zero(Config[integer: _]), do: false
defp is_config(Config[]), do: true
defp is_config(_), do: false
end
+2 -2
View File
@@ -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
+12 -12
View File
@@ -52,7 +52,7 @@ defmodule Binary.Chars.NumberTest do
test :float do
assert to_binary(1.0) == "1.0"
assert to_binary(1.0e10) == "10000000000.0"
assert to_binary(1.0e10) == "1.0e10"
end
end
@@ -60,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
@@ -76,46 +76,46 @@ 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 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 ->
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 ->
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})
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 ->
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 ->
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 ->
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 ->
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
+14 -14
View File
@@ -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,17 @@ 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 "
@@ -148,4 +148,4 @@ bar
defp is_match?(_, _) do
false
end
end
end
+4 -4
View File
@@ -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
+19 -11
View File
@@ -28,6 +28,14 @@ defmodule CodeTest do
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_string("Kernel.if true, do: :ok", [], requires: [Z, Kernel]) == { :ok, [] }
end
@@ -39,7 +47,7 @@ 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
@@ -65,25 +73,25 @@ defmodule CodeTest do
assert :filename.absname(__FILE__) == __FILE__
end
test :string_to_ast do
assert Code.string_to_ast("1 + 2") == { :ok, { :+, [line: 1], [1, 2] } }
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") == { :+, [line: 1], [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

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