Compare commits

...
219 Commits
Author SHA1 Message Date
José Valim 437adc1dd0 Release v0.8.0 2013-01-28 10:03:29 -07:00
José Valim 9e742e50dc Path.join merges second path regardless if it is an absolute or relative path
Closes #790
2013-01-27 18:00:58 -07:00
José Valim 67154735f7 Add Path.relative and Path.type 2013-01-27 15:15:25 -07:00
José Valim ba528f0f92 Add default timeout to elixir_code_server and unify all call points 2013-01-27 11:48:27 -07:00
José Valim fef7ef7233 Ensure consistent use of stab operator in do/end, parens and fn/end, closes #780 2013-01-27 11:00:59 -07:00
José Valim da4654d5f7 Merge pull request #793 from devinus/consolidated-version
Consolidate Elixir's version into a VERSION file
2013-01-25 19:55:48 -08:00
José Valim 1c9b540980 Merge pull request #796 from yrashk/binary-size-shortcut
Implement integer -> size(integer) syntax shortcut for bitsyntax
2013-01-25 18:24:47 -08:00
Yurii Rashkovskii 2bfe0fe358 Implement integer -> size(integer) syntax shortcut for bitsyntax
Closes #795
2013-01-25 17:47:18 -08:00
Devin Torres 6f4398902f Consolidate Elixir's version into a VERSION file 2013-01-25 11:23:49 -06:00
José Valim ded17471fa Revert "Merge pull request #778 from alexrp/master"
There is still some discussion pending on this, see #791

This reverts commit f2d4d1e0b9, reversing
changes made to c524bcdeb2.
2013-01-24 13:43:34 -07:00
José Valim 6a0a768307 Preserving mtime needs to be invoked per task 2013-01-24 11:41:58 -07:00
José Valim 8e22a2c074 Update lib/elixir/lib/hash_dict.ex
Update comments
2013-01-24 10:44:54 -07:00
José Valim 8ced611aee We no longer have a .release task 2013-01-24 08:05:01 -07:00
José Valim 85fca3de2e Properly declare .PHONY targets 2013-01-23 13:55:11 -07:00
José Valim 3d414a84b5 Update rebar.config 2013-01-23 12:28:16 -07:00
José Valim 888ac77664 Call preserving_mtime just once, at the root compile task 2013-01-23 10:24:17 -07:00
José Valim f2d4d1e0b9 Merge pull request #778 from alexrp/master
Fix issue #769.
2013-01-23 08:13:55 -08:00
alexrp 6fc60fa34f Previous commits fix #769. 2013-01-23 16:42:46 +01:00
alexrp b9834a9904 Use System.services_timeout/0 in Mix. 2013-01-23 16:42:34 +01:00
alexrp 4371a1a24c Use System.services_timeout/0 in ExUnit. 2013-01-23 16:42:34 +01:00
alexrp c459d31287 Use System.services_timeout/0 for kernel tests. 2013-01-23 16:42:34 +01:00
alexrp 02dee463e1 Use System.services_timeout/0 for elixir_code_server calls in the kernel. 2013-01-23 16:42:34 +01:00
alexrp 4b49e722f0 Add System.services_timeout/0 function. 2013-01-23 16:42:31 +01:00
alexrp a3df724bb8 Use system_services_timeout for elixir_code_server calls in elixir_compiler. 2013-01-23 10:14:48 +01:00
alexrp beead12c8f Add a system_services_timeout app env var to Elixir. 2013-01-23 10:14:48 +01:00
José Valim c524bcdeb2 Bump to 0.8.0.dev 2013-01-21 21:19:38 -07:00
José Valim abb1ae981a Add hygienic imports to macros 2013-01-21 20:45:49 -07:00
José Valim b3747a28e1 Raise an exception if var!(x) does not expand to a proper var 2013-01-21 19:59:37 -07:00
José Valim f380d0c93f Export elixir:translate_forms/3 2013-01-21 19:50:07 -07:00
José Valim 9b88bd0dca Fix type in deprecation 2013-01-21 19:34:47 -07:00
José Valim 9e37412b7b Provide an endpoint for translating elixir forms 2013-01-21 19:34:29 -07:00
José Valim e64d744be5 Improve var! docs 2013-01-21 19:27:25 -07:00
José Valim 5bde00a9c7 Properly expand var! second argument 2013-01-21 18:50:40 -07:00
José Valim 9a62a43340 Aliases for defmodule also works for raw atoms 2013-01-21 18:40:10 -07:00
José Valim 89cbe93bba The AST now allows metadata to be attached to each node
This means the second item in the AST is no longer an integer
(representing the line), but a keywords list. Code that relies
on the line information from AST or that manually generate AST
nodes need to be properly updated.
2013-01-21 15:42:03 -07:00
José Valim 4633c6449d Update LICENSE 2013-01-21 07:55:39 -07:00
José Valim 313d885589 Properly invoke mix on tests 2013-01-20 20:41:06 -07:00
José Valim b9cea6195f Check if native name encoding is utf8 2013-01-20 20:33:03 -07:00
José Valim bdb4afe99b String.capitalize now considers Unicode's titlecase property 2013-01-20 16:38:36 -07:00
José Valim 6df5dfc08c Ensure File.cwd works with utf-8 file names 2013-01-20 16:08:39 -07:00
José Valim 5643dbd406 Merge pull request #777 from alexrp/master
mix escriptize: Only include .beam and .app files from ebin.
2013-01-20 11:54:42 -08:00
José Valim aa9c93305e Provide iex -S mix instead of mix iex
In order for iex to work reliably, we need it to pass special
parameters to the Erlang VM. Therefore, it cannot be started
via a regular Elixir script, as in `mix iex`.

Therefore, we need to use `iex -S mix` which first boots iex
and then run the given mix commands.

Closes #692
2013-01-20 12:23:55 -07:00
alexrp 41bed0cc35 mix escriptize: Only include .beam and .app files from ebin.
Fixes #768.
2013-01-20 18:39:13 +01:00
José Valim a3d1ee67d7 Use the new Path.relative_to and ensure it won't fail for equals paths 2013-01-19 21:24:44 -07:00
José Valim 204abb6761 Add System.tmp_dir, System.cwd and System.user_home 2013-01-19 21:12:33 -07:00
José Valim 6109768376 Ensure we do not ditch modules with anonymous functions in them 2013-01-19 20:38:54 -07:00
José Valim fbb53287b3 Merge pull request #774 from kimshrier/File_wildcard_deprication_typo_fix
fix deprication message typo
2013-01-19 11:19:43 -08:00
José Valim 689bce1e9a Inspect should represent non valid terms with # 2013-01-19 12:19:28 -07:00
Kim Shrier 29783b72e5 fix deprication message typo 2013-01-19 12:13:35 -07:00
Yurii Rashkovskii 2297fc1c48 Improve Binary.Inspect for Function test 2013-01-19 11:12:47 -08:00
Yurii Rashkovskii 2e10c3d24d When possible, represent functions in a literal way 2013-01-19 11:00:36 -08:00
Yurii Rashkovskii 69afceedd6 Use @moduledoc for the module documentation in Path 2013-01-19 10:21:17 -08:00
José Valim 6740748b7a Compile Path on bootstrap 2013-01-19 10:54:14 -07:00
José Valim 77755f9831 Use the new Path.expand on mix 2013-01-19 10:36:29 -07:00
José Valim cb1b4c5bc5 Add Path.absname and expand ~ on Path.expand 2013-01-19 10:32:33 -07:00
José Valim fda6f0ce09 Move path related functions from File to Path 2013-01-19 09:36:03 -07:00
José Valim 173c0cfb5f Deprecate OrdDict and Binary.Dict 2013-01-18 16:39:24 -07:00
José Valim b13b371a98 Instead of a dynamic array, HashDict uses nested tuples 2013-01-18 10:42:42 -07:00
José Valim e52ac6d170 format_entry should also handle fun, arity, location entries 2013-01-18 06:34:23 -07:00
José Valim d69ed2b1ca More docs fixes to string 2013-01-17 20:04:13 -07:00
José Valim 59c871274a Merge branch 'hash-dict' 2013-01-17 19:34:26 -07:00
José Valim c872cb647d Merge pull request #771 from devinus/cleaner-list-dicts
Introduce List.Dict, DRYing the tuple list as Dict implementation
2013-01-17 18:29:17 -08:00
José Valim 33ab39afb7 Fix a bug, improve wording (ht: @alexrp) 2013-01-17 19:28:42 -07:00
José Valim cb536a4740 Improve docs for the string module 2013-01-17 17:03:48 -07:00
José Valim d603115902 Tidy up HashDict and the Dict protocol 2013-01-17 15:55:21 -07:00
Devin Torres 644b765a3d Introduce List.Dict, DRYing the tuple list as Dict implementation 2013-01-17 14:52:04 -06:00
José Valim e9c8afd65b Implement more of the Dict API into HashDict 2013-01-17 20:34:21 +01:00
José Valim 05a1bdc1e1 Increase large hash dict tests range 2013-01-15 15:57:40 +01:00
José Valim dc7314c9cf Initial work on a faster HashDict (it currently suports get/put/delete/size) 2013-01-15 15:22:49 +01:00
José Valim 4f26d1a964 Support SpecialCasing.txt on Unicode 2013-01-13 23:58:37 +01:00
José Valim e2fde7699c Do not generate lowercase clause if lowercase == codepoint 2013-01-13 23:58:37 +01:00
José Valim 91353ae4a0 Merge pull request #764 from alexrp/master
Mix: Support --no-compile options in deps.get and deps.update.
2013-01-13 13:20:14 -08:00
alexrp ee8e9225f6 Mix: Support --no-compile options in deps.get and deps.update. Fixes #763. 2013-01-13 22:13:25 +01:00
José Valim f2c08ce14f Fix unused variable warning 2013-01-13 20:56:19 +01:00
José Valim 595aeffe61 Better error messages when a dependency compilation fails 2013-01-13 20:51:44 +01:00
José Valim f4595d586f Do not print messages on up to date escript and update CHANGELOG 2013-01-13 20:44:00 +01:00
José Valim 9d72e00444 Consider config_files in escriptize and improve tests 2013-01-13 20:36:46 +01:00
José Valim 5cf9405e6c Merge pull request #761 from alexrp/master
A few 'mix escriptize' fixes.
2013-01-13 11:21:26 -08:00
José Valim 702b24d159 Merge pull request #762 from alexrp/next-codepoint-match-utf8
Use binary matching with the utf8 specifier to implement String.next_codepoint/1.
2013-01-13 11:20:56 -08:00
alexrp e4d9375648 Use binary matching with the utf8 specifier to implement String.next_codepoint/1. 2013-01-13 20:11:55 +01:00
alexrp fbe1a105b6 A few 'mix escriptize' fixes:
* Check timestamps of .beam files before generating escript.
* Don't generate an escript if no .beam files exist.
* Add --force option to disregard timestamps.
* List command line options in docs.
* Correctly specify --no-compile as a switch to OptionParser.

Fixes #760.
2013-01-13 16:26:25 +01:00
José Valim 04e38d0f75 Add a disclosure about setting Code.eval options 2013-01-13 11:11:57 +01:00
José Valim 4bbea0df32 Do not recompile a dependency if nothing changed, closes #759 2013-01-13 10:38:52 +01:00
José Valim 449532e1fb Fix identation, improve error messages 2013-01-12 23:43:07 +01:00
José Valim 17a2ce9766 Ensure requires are sorted on Code.eval 2013-01-11 23:29:44 +01:00
José Valim 0c02095f32 Document var_context option 2013-01-11 22:49:47 +01:00
José Valim 9bdb776980 Remove time calls from Makefile 2013-01-11 10:01:38 +01:00
José Valim 4e651c8aae Expand aliases inside quote 2013-01-11 09:55:38 +01:00
José Valim eaa9714b3a Improve hygiene for variables, related to #722 2013-01-10 14:29:45 +01:00
José Valim bdc5f7ddd2 Also handle < and > inside interpolations 2013-01-10 14:29:45 +01:00
José Valim a55030ea56 Make IEx rely on ensure_compiled and add a fix for when the function has no docs 2013-01-10 14:29:45 +01:00
José Valim ec10b69686 Merge pull request #754 from alexrp/master
mix escriptize: Correctly set :escript_embed_elixir to true by default.
2013-01-08 06:01:50 -08:00
alexrp 1a0a854f6f mix escriptize: Correctly set :escript_embed_elixir to true by default. 2013-01-08 14:55:27 +01:00
José Valim c4421078b6 Allow protocol definitions nested inside modules 2013-01-08 10:33:54 +01:00
José Valim 5d285ae7f8 Add a stacktrace to /> deprecation 2013-01-08 09:38:29 +01:00
José Valim caf2d82e90 Merge pull request #751 from alexrp/pipeline-operator
The pipeline operator is now '|>'.
2013-01-08 00:23:34 -08:00
José Valim d55738228b Merge pull request #753 from alexrp/enum-each-map-iter-index
Support indexed Enum.{each/2, map/2}.
2013-01-08 00:22:16 -08:00
José Valim e376628f8d Merge pull request #752 from alexrp/record-deffunctions-opt-arg
Remove unused optional argument from Record.{deffunctions, deftypes}.
2013-01-08 00:20:35 -08:00
alexrp c30f79018f Add changelog entry about /> -> |>. 2013-01-08 01:13:15 +01:00
alexrp 531e64c66a Add back /> as a deprecated operator.
This commit can simply be reverted when the operator is to be removed.
2013-01-08 01:12:15 +01:00
alexrp f3c89d7812 Enum: Use index type consistently. 2013-01-08 00:38:46 +01:00
alexrp 38410db2c9 Support indexed Enum.each/2.
Fixes #748.
2013-01-08 00:36:24 +01:00
alexrp 1808a3b531 Support indexed Enum.map/2. 2013-01-08 00:36:24 +01:00
alexrp aa44831398 Remove unused optional argument from Record.{deffunctions, deftypes}.
Fixes #744.
2013-01-07 23:47:39 +01:00
alexrp c9908a23dd The pipeline operator is now '|>'.
Fixes #718.
2013-01-07 23:38:55 +01:00
José Valim 3e98bb6fe9 Merge pull request #750 from alexrp/master
mix escriptize: Support escript_path to specify the path on disk to write the escript to.
2013-01-07 04:23:39 -08:00
alexrp ce6922a22c mix escriptize: Support escript_path to specify the path on disk to write the escript to.
Fixes #714.
2013-01-07 13:13:28 +01:00
José Valim 0ee1b72ed6 Provide elixir compiler specific options instead of sharing 2013-01-06 19:57:12 +01:00
José Valim c3b7547af6 Only starts erlang compilation if a file was actually found 2013-01-06 19:35:03 +01:00
José Valim c96a08d39e Merge pull request #747 from datahogs/az-mix-compile-erlang
Initial mix compile.erlang task, issue #625
2013-01-06 10:11:58 -08:00
José Valim 9776f239b4 Provide faster record dispatching for protocols that do not have fallbacks.
Currently, if a protocol must be implemented for records but
not for tuples or any other data type, we simply dispatch to
the given tuple, assuming it is valid.

The downside is that we don't show `Protocol.UndefinedError`
messages if the protocol is invalid, but that can easily be
inflected from the underlying UndefinedFunctionError message.
2013-01-06 16:15:23 +01:00
José Valim d3bac20deb Merge pull request #749 from guedes/master
fixing some docs indentation
2013-01-06 06:16:55 -08:00
José Valim 3334cec732 Add cwd as parameter to Exception.format_entry 2013-01-06 15:11:52 +01:00
Dickson S. Guedes c509e1a94f fixing identation for markdown 2013-01-06 11:37:41 -02:00
José Valim 802aa1e878 Ensure Enum.take(iterator, 0) does not consume any item as well 2013-01-05 20:39:39 +01:00
José Valim 7b9246078c Allow file iterators to be closed, Enum.take should not take items it is not going to use 2013-01-05 20:35:41 +01:00
José Valim dc901c328a Improve docs 2013-01-05 12:59:02 +01:00
José Valim 601ae4d21a Merge pull request #746 from alexrp/master
Add String.valid_codepoint?/1 function.
2013-01-05 01:55:22 -08:00
alexrp e7f029df00 Add String.valid_codepoint?/1. 2013-01-05 04:00:32 +01:00
Zatvor acabf1c873 Initial mix compile.erlang task, issue #625 2013-01-04 21:23:56 +02:00
José Valim a1c202e02e Clean up Mix.project after setting it 2013-01-04 19:25:13 +01:00
José Valim 55055fbfa1 Define tests functions used as a dependency explicitly 2013-01-04 09:02:26 +01:00
José Valim 5bf06e83d6 Merge pull request #745 from devinus/list-as-dict
Extend the Dict protocol to work with tuple lists
2013-01-03 23:52:17 -08:00
Devin Torres 061c0a3a5d Extend the Dict protocol to work with tuple lists 2013-01-03 15:58:35 -06:00
Yurii Rashkovskii f373e67098 Get rid of a minor compile-time warning 2013-01-02 10:07:34 -08:00
José Valim acbb6c0fee Functions defined inside quote(location: :keep) retain the source stacktrace 2013-01-02 10:59:52 +01:00
José Valim 2eef987c50 Make a distinction between file and module body in stacktraces 2013-01-02 10:21:42 +01:00
José Valim 4387ae1a91 Propagate configs from ParallelCompiler in ParallelRequire 2013-01-01 13:14:22 +01:00
José Valim 4644d7a607 Do not rely on the third arg of catch clauses
This is an undocumented Erlang feature and it
must not be part of Elixir public API.
2012-12-31 16:23:24 +01:00
José Valim 03ce57de11 Fix test since we return a four item tuple in the stacktrace 2012-12-31 16:05:32 +01:00
José Valim 5f2d4eb472 Rename BOOTSTRAP to __BOOT__ 2012-12-31 15:59:57 +01:00
José Valim bf14e4b771 Discard spurious cons list from stacktrace 2012-12-31 15:14:58 +01:00
José Valim b9313340b7 Keep \377 and 0377 as the octal syntax, closes #737 2012-12-31 13:46:56 +01:00
José Valim bd931643e2 Update CHANGELOG 2012-12-31 13:37:37 +01:00
José Valim 2bd71f7604 Improve how exceptions are handled in general
* raise expects an explicit stacktrace in order to re-raise
* the Exception module now holds function for both stacktrace
  manipulation and interface with erlang exceptions
* exceptions have their default functions with doc set to false
2012-12-31 13:33:30 +01:00
José Valim 2588bc8ec2 Underscored functions are allowed with passed via only 2012-12-31 11:51:44 +01:00
Yurii Rashkovskii f38b0b8427 Add type specifications to the Node module 2012-12-28 23:30:35 -08:00
Yurii Rashkovskii 254aa8348e Add type specifications to the Process module 2012-12-28 23:16:35 -08:00
Yurii Rashkovskii ec9e3d3bae Improve documentation for Process.get/0 2012-12-28 22:54:20 -08:00
Yurii Rashkovskii 08dbb0eb74 Minor documentation improvement for System.at_exit/1 2012-12-28 22:49:23 -08:00
Yurii Rashkovskii a429cf318b Documentation typo fix in Kernel 2012-12-28 11:18:38 -08:00
José Valim a74cd11a00 Merge pull request #742 from mururu/record_documentation
Fix record documentation
2012-12-27 00:12:35 -08:00
mururu 9e7f8b09f7 Fix record documentation 2012-12-27 17:10:07 +09:00
José Valim cf4842f663 Merge pull request #739 from mururu/escape_sequence
Support "\a"
2012-12-26 21:02:57 -08:00
mururu 5711d74fd9 Support "\a" 2012-12-27 09:18:13 +09:00
José Valim d6e4e2375e Insert new line in between printed docs 2012-12-26 20:14:45 +01:00
José Valim a7dfb25f17 Reduce default assertion timeout to 100 miliseconds 2012-12-26 19:06:08 +01:00
José Valim b6eb45ff90 Add Dict.put_new and Keyword.put_new 2012-12-26 17:39:06 +01:00
José Valim 09f61abe27 Add assert_receive and refute_receive to ExUnit 2012-12-26 17:28:10 +01:00
José Valim ebcffa0fa8 Merge pull request #738 from mururu/fix_indent
Fix indent
2012-12-26 01:30:58 -08:00
mururu a00aaf48e3 Fix indent 2012-12-26 15:42:47 +09:00
José Valim 663faec261 Update CHANGELOG.md 2012-12-24 19:59:32 +01:00
José Valim 2267effae6 Merge pull request #731 from kimshrier/octal_and_hex_escapes_in_characters
add octal and hex escapes for characters
2012-12-24 10:55:20 -08:00
Yurii Rashkovskii 12a856c3c5 Improve message for bad function error
Closes #732
2012-12-24 12:54:23 +01:00
Yurii Rashkovskii 36c9e11064 Remove __info__/1 from __info__(:functions)
Closes #734
2012-12-24 12:51:09 +01:00
Kim Shrier 54d74efa04 Update octal and hex escapes for characters, char lists, and strings to all accept
the same syntax and be case insensitive.  Also relocated the character type macros
into lib/elixir/include/elixir.hrl so that they didn't need to be defined in both
lib/elixir/src/elixir_interpolation.erl and lib/elixir/src/elixir_tokenizer.erl.
Added to the erlang and elixir unit tests to test the new syntax.
2012-12-23 20:05:23 -07:00
Kim Shrier f36db5bd1b Merge remote-tracking branch 'upstream/master' into octal_and_hex_escapes_in_characters 2012-12-23 12:39:48 -07:00
Yurii Rashkovskii 3ffc5e9e84 IEx autocompletion should show all exported functions, not only documented ones
Closes #733
2012-12-23 19:05:34 +00:00
Kim Shrier d23cc079ad add octal and hex escapes for characters 2012-12-22 03:16:12 -07:00
José Valim ea1a79a156 Merge pull request #730 from kimshrier/case_insensitive_hex_interpolation
add code and tests for case insensitive hex interpolation.
2012-12-22 00:39:22 -08:00
José Valim b425969b72 Merge pull request #729 from mururu/atom_interpolation
Modify atom with interpolation tokenizer
2012-12-22 00:38:55 -08:00
Kim Shrier d5613ed380 add code and tests for case insensitive hex interpolation. 2012-12-21 23:32:01 -07:00
mururu 3e2172e646 modify atom with interpolation tokenizer 2012-12-22 14:46:43 +09:00
José Valim cc5443e39a Merge pull request #723 from mururu/string_functions
Implement some String functions
2012-12-20 11:31:37 -08:00
mururu 31e6b414a5 Fix the implementation of String.capitalize/2 2012-12-21 02:48:15 +09:00
mururu 75636bef89 Implement String.slice/3 2012-12-20 21:25:43 +09:00
mururu 3010931045 Implement String.capitalize/1 2012-12-20 21:19:46 +09:00
José Valim 0369d1652f Add ExUnit.ExpectationError and move special formating to ExUnit.CLIFormatter 2012-12-17 10:59:41 +01:00
José Valim c810b4ba82 Fix bootstrap files order 2012-12-17 09:58:52 +01:00
José Valim a0a14ec9a7 Merge pull request #715 from orenbenkiki/master
Step towards improved assertion messages
2012-12-17 00:56:46 -08:00
José Valim cba39b618e Better error message when no test directory exists, closes #716 2012-12-17 09:53:20 +01:00
José Valim 305ad0b9c0 Support else clauses on try 2012-12-16 22:03:49 +01:00
Oren Ben-Kiki 9e29e37bfa Fuller make clean 2012-12-15 00:02:07 +02:00
Oren Ben-Kiki 8e2a30906a Align expected and actual values in assertion failure messages. 2012-12-15 00:01:35 +02:00
José Valim 87bf159fcb Get rid of deps.start
A proper implementation is under discussion and it will
likely be opt-in behaviour.
2012-12-13 10:28:34 +01:00
José Valim 16d0ce6273 Support << "string" :: utf8 >> as in Erlang, closes #700 2012-12-10 09:39:10 +01:00
José Valim e353b1ad15 :flags option was deprecated in favor of :switches to support many types
Closes #710
2012-12-10 09:12:24 +01:00
José Valim 5d85c6a448 Small refactoring on RecordRewriter 2012-12-10 08:55:24 +01:00
Yurii Rashkovskii 486930faa2 Typespec syntax should support [type, ...] shorthand for nonempty_list(type) 2012-12-09 14:19:12 -08:00
José Valim f2e803758f String.first and String.last now return nil 2012-12-09 16:10:24 +01:00
José Valim e6260684a1 By default mark messages in exceptions as binaries 2012-12-09 14:04:45 +01:00
José Valim cc6dcd83e7 Further optimize record calls
* Known records now have their calls translated from `x.function` to
  `X.function(x)`, which is faster and allows tools like dialyzer to
  still work

* `update_*` and `update` now properly keep the inferred record type
2012-12-09 11:27:53 +01:00
José Valim f733c268ae Merge pull request #706 from mururu/fix_string_functions
Fix some String.Unicord functions behavior.
2012-12-09 00:58:05 -08:00
mururu f1323b6b74 String.Unicode.rstrip raise error when an argument is not binary 2012-12-09 17:14:55 +09:00
mururu 89d32f91e1 String.Unicode.lstrip raise error when an argument is not binary 2012-12-09 17:14:48 +09:00
José Valim 7beb864986 Deprecate raw in favor of path, closes #703 2012-12-08 20:51:57 +01:00
José Valim cf634701fa Compiler and test runner now check for the number of schedulers online 2012-12-08 12:31:24 +01:00
José Valim 1105781606 ExUnit supports many manual runs 2012-12-08 12:04:26 +01:00
José Valim 2a74f477fc Improve test to verify ExUnit.Runner is not in the stacktrace 2012-12-08 11:55:13 +01:00
José Valim ccef8413df Unload sync and async cases from ExUnit.Server at once 2012-12-08 11:52:55 +01:00
José Valim 7e1a79e2a4 Use System.cmd instead of Port.open 2012-12-08 10:39:06 +01:00
mururu 3a4612da87 The default formatter of ex_unit should not prints unneeded stacktraces.
Conflicts:

	lib/ex_unit/lib/ex_unit/cli_formatter.ex
2012-12-08 10:37:42 +01:00
José Valim 0886e871cc Improve ExUnit stacktrace to ignore ExUnit internals traces
Closes #704. Thanks to Yuki Ito for the idea and original patch.
2012-12-08 10:21:45 +01:00
José Valim 58cc3fd947 No need to actually rely on ERL_LIBS since we just need elixir beams 2012-12-07 10:42:02 +01:00
José Valim f7a0373bf4 Fix var in Makefile 2012-12-07 10:35:12 +01:00
José Valim cccff482b9 Allow custom mtimes to be given to extract stale 2012-12-07 09:35:03 +01:00
José Valim 828387e762 Improve docs for GenServer.Behaviour 2012-12-06 21:06:06 +01:00
José Valim 299984d9ed Update CHANGELOG 2012-12-06 15:45:34 +01:00
José Valim dfc344988a Mix now starts the current application
--no-start can be given to most commands (iex, test,
run, etc) if one does not desire the app to be started
under a given circumstances

With previous commits, this closes #666
2012-12-06 15:42:44 +01:00
José Valim b88efc43c3 Explicitly support file in quote 2012-12-06 13:34:49 +01:00
José Valim 6b17b85e1f Dependencies' apps are started by default on compilation 2012-12-06 13:34:49 +01:00
José Valim bf89f4057e Merge pull request #701 from alexrp/master
More improvements to bitstring docs.
2012-12-06 04:34:39 -08:00
alexrp e61539a412 More improvements to bitstring docs. 2012-12-06 11:28:40 +01:00
José Valim 17bedb6613 Add a supervisor to Mix, closes #392 2012-12-06 10:33:45 +01:00
José Valim 5a1bc1e333 Improve coverage for Supervisor.Behaviour 2012-12-06 10:02:39 +01:00
José Valim 5507b3b36a Refactor module_test.exs to reduce assertions on load time, closes #696 2012-12-06 09:41:27 +01:00
José Valim 290b0ef894 :... is a valid atom, closes #697 2012-12-06 09:23:24 +01:00
José Valim e7a0c93fc1 Improve assert_raise when message matching fails, closes #694 2012-12-06 09:18:22 +01:00
José Valim 93def0b405 Improve docs for << >>, closes #693 2012-12-06 09:14:56 +01:00
José Valim 4a18102705 Application.Behaviour.start is a private function to Elixir's stdlib 2012-12-05 22:12:29 +01:00
José Valim a6ddf3a736 ExUnit now defines proper application and supervisor callbacks 2012-12-05 22:11:11 +01:00
José Valim 863d9a4584 Introduce Supervisor.Behaviour to define supervisors callbacks 2012-12-05 22:10:39 +01:00
José Valim 94707dc42c Improve coverage for GenServer.Behaviour 2012-12-05 15:55:06 +01:00
José Valim c8c74acdbb Introduce Application.Behaviour to define application module callbacks 2012-12-05 15:55:06 +01:00
José Valim 7cf07813a2 Improve GenServer.Behaviour docs 2012-12-05 15:55:06 +01:00
José Valim 7841f68072 Start an app and its dependencies with --app 2012-12-05 15:55:06 +01:00
Yurii Rashkovskii 7dee0b44d1 Converting typespec to an AST should transform range/2 type back to a .. operator 2012-12-05 02:34:19 -08:00
José Valim a1e9767466 Recompile the project when a dependency is recompiled, closes #687 2012-12-05 10:26:10 +01:00
José Valim 5d27b21ca8 Bump to 0.7.3.dev and get rid of deprecations 2012-12-05 09:46:07 +01:00
205 changed files with 7894 additions and 4089 deletions
+1
View File
@@ -13,3 +13,4 @@
erl_crash.dump
.dialyzer_plt
.dialyzer.base_plt
.*.swp
+1 -1
View File
@@ -1,5 +1,5 @@
language: erlang
script: "make compile && make .release test"
script: "make compile && make test"
notifications:
irc: "irc.freenode.org#elixir-lang"
recipients:
+43 -1
View File
@@ -1,3 +1,45 @@
# v0.8.0 (2013-01-28)
* enhancements
* [Binary] Support `<< "string" :: utf8 >>` as in Erlang
* [Binary] Support `\a` escape character in binaries
* [Binary] Support syntax shortcut for specifying size in bit syntax
* [CLI] Support `--app` option to start an application and its dependencies
* [Dict] Support `put_new` in `Dict` and `Keyword`
* [Dict] Add `List.Dict` and a faster `HashDict` implementation
* [ExUnit] ExUnit now supports multiple runs in the same process
* [ExUnit] Failures in ExUnit now shows a tailored stacktrace
* [ExUnit] Introduce `ExUnit.ExpectationError` to provide better error messages
* [Kernel] Introduce `Application.Behaviour` to define application module callbacks
* [Kernel] Introduce `Supervisor.Behaviour` to define supervisors callbacks
* [Kernel] More optimizations were added to Record handling
* [Kernel] `?\x` and `?\` are now supported ways to retrieve a codepoint
* [Kernel] Octal numbers can now be defined as `0777`
* [Kernel] Improve macros hygiene regarding variables, aliases and imports
* [Mix] Mix now starts the current application before run, iex, test and friends
* [Mix] Mix now provides basic support for compiling `.erl` files
* [Mix] `mix escriptize` only generates escript if necessary and accept `--force` and `--no-compile` as options
* [Path] Introduce `Path` module to hold filesystem paths related functions
* [String] Add `String.capitalize` and `String.slice`
* [System] Add `System.tmp_dir`, `System.cwd` and `System.user_home`
* bug fix
* [Kernel] `import` with `only` accepts functions starting with underscore
* [String] `String.first` and `String.last` return nil for empty binaries
* [String] `String.rstrip` and `String.lstrip` now verify if argument is a binary
* [Typespec] Support `...` inside typespec's lists
* backwards incompatible changes
* [Kernel] The AST now allows metadata to be attached to each node. This means the second item in the AST is no longer an integer (representing the line), but a keywords list. Code that relies on the line information from AST or that manually generate AST nodes need to be properly updated
* deprecations
* [Dict] Deprecate `Binary.Dict` and `OrdDict` in favor of `HashDict` and `List.Dict`
* [File] Deprecate path related functions in favor of the module `Path`
* [Kernel] The `/>` operator has been deprecated in favor of `|>`
* [Mix] `Mix.Project.sources` is deprecated in favor of `Mix.Project.config_files`
* [Mix] `mix iex` is no longer functional, please use `iex -S mix`
* [OptionParser] `:flags` option was deprecated in favor of `:switches` to support many types
# v0.7.2 (2012-12-04)
* enhancements
@@ -157,4 +199,4 @@
# v0.5.0 (2012-05-24)
* First official release
* First official release
+1 -1
View File
@@ -1,4 +1,4 @@
Copyright 2012 Plataformatec.
Copyright 2012-2013 Plataformatec.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
+17 -18
View File
@@ -1,12 +1,11 @@
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 -env ERL_LIBS $ERL_LIBS:lib
VERSION := 0.7.2
RELEASE_FLAG := .release
ERL := erl -I lib/elixir/include -noshell -pa lib/elixir/ebin
VERSION := $(strip $(shell cat VERSION))
INSTALL_PATH := /usr/local
.PHONY: 1
.PHONY: install compile erlang elixir dialyze test clean docs release_docs release_zip release_erl
.NOTPARALLEL: compile
#==> Templates
@@ -15,7 +14,7 @@ define APP_TEMPLATE
$(1): lib/$(1)/ebin/Elixir-$(2).beam lib/$(1)/ebin/$(1).app
lib/$(1)/ebin/$(1).app:
@ cd lib/$(1) && ../../bin/mix compile.app
@ cd lib/$(1) && ../../bin/elixir -e "Mix.Server.start_link(:dev)" -r mix.exs -e "Mix.Task.run('compile.app')"
lib/$(1)/ebin/Elixir-$(2).beam: $(wildcard lib/$(1)/lib/*.ex) $(wildcard lib/$(1)/lib/*/*.ex) $(wildcard lib/$(1)/lib/*/*/*.ex)
@ echo "==> $(1) (compile)"
@@ -23,7 +22,7 @@ lib/$(1)/ebin/Elixir-$(2).beam: $(wildcard lib/$(1)/lib/*.ex) $(wildcard lib/$(1
test_$(1): $(1)
@ echo "==> $(1) (exunit)"
@ cd lib/$(1) && time ../../bin/elixir -r "test/test_helper.exs" -pr "test/**/*_test.exs";
@ cd lib/$(1) && ../../bin/elixir -r "test/test_helper.exs" -pr "test/**/*_test.exs";
endef
#==> Compilation tasks
@@ -82,35 +81,35 @@ install: compile
clean:
@ cd lib/elixir && $(REBAR) clean
rm -rf $(RELEASE_FLAG)
rm -rf ebin
rm -rf lib/*/ebin
rm -rf lib/*/test/tmp
rm -rf lib/mix/test/fixtures/git_repo
rm -rf lib/mix/tmp
rm -rf lib/*/tmp
rm -rf lib/elixir/src/elixir.app.src
rm -rf lib/elixir/src/*_lexer.erl
rm -rf lib/elixir/src/*_parser.erl
rm -rf lib/elixir/test/ebin
#==> Release tasks
$(RELEASE_FLAG): compile
touch $(RELEASE_FLAG)
docs: $(RELEASE_FLAG)
docs:
mkdir -p ebin
rm -rf docs
cp -R -f lib/*/ebin/*.beam ./ebin
bin/elixir ../exdoc/bin/exdoc
rm -rf ebin
release_zip: $(RELEASE_FLAG)
release_zip:
rm -rf v$(VERSION).zip
zip -9 -r v$(VERSION).zip bin CHANGELOG.md LEGAL lib/*/ebin LICENSE README.md rel
zip -9 -r v$(VERSION).zip bin CHANGELOG.md LEGAL lib/*/ebin LICENSE README.md rel VERSION
release_docs: docs
cd ../elixir-lang.github.com && git checkout master
rm -rf ../elixir-lang.github.com/docs/master
mv output ../elixir-lang.github.com/docs/master
release_erl: $(RELEASE_FLAG)
release_erl:
@ rm -rf rel/elixir
@ cd rel && ../rebar generate
@@ -122,20 +121,20 @@ 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
@ time $(ERL) -pa lib/elixir/test/ebin -s test_helper test -s erlang halt;
@ $(ERL) -pa lib/elixir/test/ebin -s test_helper test -s erlang halt;
@ echo
test_elixir: test_kernel test_mix test_ex_unit test_eex test_iex
test_kernel: compile
@ echo "==> kernel (exunit)"
@ cd lib/elixir && time ../../bin/elixir -r "test/elixir/test_helper.exs" -pr "test/elixir/**/*_test.exs";
@ 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
dialyze: $(RELEASE_FLAG) .dialyzer.base_plt
dialyze: .dialyzer.base_plt
@ rm -f .dialyzer_plt
@ cp .dialyzer.base_plt .dialyzer_plt
@ echo "==> Adding Elixir to PLT..."
+2 -7
View File
@@ -22,10 +22,5 @@ This document simply outlines the release process:
## Places where version is mentioned
* src/elixir.app.src
* lib/elixir/src/elixir.app.src
* lib/elixir/lib/system.ex
* rel/reltool.config
* Makefile
* CHANGELOG
* package.exs
* VERSION
* CHANGELOG
+1
View File
@@ -0,0 +1 @@
0.8.0
+2 -1
View File
@@ -9,7 +9,8 @@ if [ $# -eq 0 ]; then
-pr \"file\" Requires the given files/patterns in parallel (*)
-pa \"path\" Prepends the given path to Erlang code path (*)
-pz \"path\" Appends the given path to Erlang code path (*)
--erl \"switches\" Switches to be passed down to erlang
--app \"app\" Start the given app and its dependencies (*)
--erl \"switches\" Switches to be passed down to erlang (*)
--name \"name\" Makes and assigns a name to the distributed node
--sname \"name\" Makes and assigns a short name to the distributed node
--remsh \"name\" Connects to a node using a remote shell (with iex)
+4 -3
View File
@@ -7,13 +7,14 @@ if "%*" == "" (
:documentation
echo Usage: %~nx0 [options] [.exs file] [data]
echo.
echo -v Prints version and exit
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 -pa path Prepend the given path to Erlang code path (*)
echo -pz path Append the given path to Erlang code path (*)
echo --no-halt Do not halt the Erlang VM after execution
echo --app app Start the given app and its dependencies (*)
echo --no-halt Do 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
+3 -17
View File
@@ -1,17 +1,3 @@
#!/bin/sh
readlink_f () {
cd "$(dirname "$1")" > /dev/null
local filename="$(basename "$1")"
if [ -h "$filename" ]; then
readlink_f "$(readlink "$filename")"
else
echo "`pwd -P`/$filename"
fi
}
SELF=$(readlink_f "$0")
SCRIPT_PATH=$(dirname "$SELF")
EXECUTABLE="elixir"
if [ "$1" = "iex" ]; then EXECUTABLE="iex"; fi
exec "$SCRIPT_PATH"/$EXECUTABLE -e "Mix.start; Mix.CLI.run" -- "$@"
#!/usr/bin/env elixir
Mix.start
Mix.CLI.run
+1 -10
View File
@@ -1,10 +1 @@
@echo off
if "%1" == "iex" (
goto iex
) else (
goto elixir
)
:iex
call "%~dp0\iex.bat" -e "Mix.start" -e "Mix.CLI.run" -- %*
:elixir
call "%~dp0\elixir.bat" -e "Mix.start" -e "Mix.CLI.run" -- %*
call "%~dp0\elixir.bat" "%~dp0\mix" %*
+1 -1
View File
@@ -189,7 +189,7 @@ defmodule EEx do
@doc false
def function_from_quoted(module, kind, name, args, source, info) do
args = Enum.map args, fn arg -> { arg, 0, nil } end
args = Enum.map args, fn arg -> { arg, [], nil } end
quote = quote do
unquote(kind).(unquote(name).(unquote_splicing(args)), do: unquote(source))
end
+1 -1
View File
@@ -90,7 +90,7 @@ defmodule EEx.Compiler do
defp maybe_block([]), do: nil
defp maybe_block([h]), do: h
defp maybe_block(other), do: { :__block__, 0, other }
defp maybe_block(other), do: { :__block__, [], other }
# Changes placeholder to real expression
+3 -3
View File
@@ -10,7 +10,7 @@ defmodule EExText.Compiled do
{ :erlang, 1, 2 }.tuple_to_list
EEx.function_from_string :def, :string_sample, "<%= a + b %>", [:a, :b]
filename = File.expand_path("../fixtures/eex_template_with_bindings.eex", __FILE__)
filename = Path.expand("../fixtures/eex_template_with_bindings.eex", __FILE__)
EEx.function_from_file :defp, :private_file_sample, filename, [:bar]
def file_sample(arg), do: private_file_sample(arg)
@@ -271,13 +271,13 @@ foo
end
test "evaluates the source from a given file" do
filename = File.expand_path("../fixtures/eex_template.eex", __FILE__)
filename = Path.expand("../fixtures/eex_template.eex", __FILE__)
result = EEx.eval_file(filename)
assert result == "foo bar.\n"
end
test "evaluates the source from a given file with bindings" do
filename = File.expand_path("../fixtures/eex_template_with_bindings.eex", __FILE__)
filename = Path.expand("../fixtures/eex_template_with_bindings.eex", __FILE__)
result = EEx.eval_file(filename, [bar: 1])
assert result == "foo 1\n"
end
+43 -25
View File
@@ -1,36 +1,54 @@
-define(ELIXIR_WRAP_CALL(Line, Module, Function, Args),
-define(wrap_call(Line, Module, Function, Args),
{ call, Line,
{ remote, Line, { atom, Line, Module }, { atom, Line, Function } },
Args
}).
-define(ELIXIR_ATOM_CONCAT(Atoms), list_to_atom(lists:concat(Atoms))).
-define(ELIXIR_MACRO(Macro), list_to_atom(lists:concat(['MACRO-',Macro]))).
-define(atom_concat(Atoms), list_to_atom(lists:concat(Atoms))).
-define(elixir_macro(Macro), list_to_atom(lists:concat(['MACRO-',Macro]))).
-define(line(Opts), elixir_tree_helpers:get_line(Opts)).
-record(elixir_scope, {
context=nil, %% can be assign, guards or nil
noname=false, %% when true, don't add new names (used by try)
check_clauses=true, %% when true, check def clauses ordering
super=false, %% when true, it means super was invoked
caller=false, %% when true, it means caller was invoked
name_args=false, %% when true, it means arguments should be named
module=nil, %% the current module
function=nil, %% the current function
vars=[], %% a dict of defined variables and their alias
temp_vars=[], %% a dict of all variables defined in a particular assign
clause_vars=nil, %% a dict of all variables defined in a particular clause
extra_guards=nil, %% extra guards from args expansion
counter=[], %% a counter for the variables defined
local=nil, %% the scope to evaluate local functions against
scheduled=[], %% scheduled modules to be loaded
file, %% the current scope filename
aliases, %% an orddict with aliases by new -> old names
requires, %% a set with modules required
macros, %% a list with macros imported by module
functions}). %% a list with functions imported by module
context=nil, %% can be assign, guards or nil
noname=false, %% when true, don't add new names (used by try)
check_requires=true, %% when true, check requires
check_clauses=true, %% when true, check def clauses ordering
super=false, %% when true, it means super was invoked
caller=false, %% when true, it means caller was invoked
name_args=false, %% when true, it means arguments should be named
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
clause_vars=nil, %% a dict of all variables defined in a particular clause
extra_guards=nil, %% extra guards from args expansion
counter=[], %% a counter for the variables defined
local=nil, %% the scope to evaluate local functions against
scheduled=[], %% scheduled modules to be loaded
file, %% the current scope filename
aliases, %% an orddict with aliases by new -> old names
requires, %% a set with modules required
macros, %% a list with macros imported by module
functions}). %% a list with functions imported by module
-record(elixir_quote, {
line=0,
marker=quoted,
var_context=nil,
expand_aliases=true,
expand_imports=true,
unquote=true
}).
}).
%% Used in tokenization and interpolation
-define(is_digit(S), S >= $0 andalso S =< $9).
-define(is_hex(S), ?is_digit(S) orelse (S >= $A andalso S =< $F) orelse (S >= $a andalso S =< $f)).
-define(is_bin(S), S >= $0 andalso S =< $1).
-define(is_octal(S), S >= $0 andalso S =< $7).
-define(is_leading_octal(S), S >= $0 andalso S =< $3).
-define(is_upcase(S), S >= $A andalso S =< $Z).
-define(is_downcase(S), S >= $a andalso S =< $z).
-define(is_word(S), ?is_digit(S) orelse ?is_upcase(S) orelse ?is_downcase(S)).
-define(is_quote(S), S == $" orelse S == $').
-define(is_space(S), S == $\s; S == $\r; S == $\t; S == $\n).
+10 -4
View File
@@ -21,19 +21,25 @@ end
defimpl Access, for: List do
@doc """
Access the given key in a keyword list.
Access the given key in a tuple list.
## Examples
keywords = [a: 1, b: 2]
keywords[:a] #=> 1
star_ratings = [{1.0, "★"}, {1.5, "★☆"}, {2.0, "★★"}]
star_ratings[1.5] #=> "★☆"
"""
def access([], _key), do: nil
def access(list, atom) when is_atom(atom) do
Keyword.get(list, atom)
def access(list, key) do
case :lists.keyfind(key, 1, list) do
{ ^key, value } -> value
false -> nil
end
end
end
defimpl Access, for: Atom do
+105
View File
@@ -0,0 +1,105 @@
defmodule Application.Behaviour do
@moduledoc """
This module is a convenience to define application module callbacks.
In Erlang/OTP, an application is a component that can be started
and stopped as a unit, and which can be re-used in other systems
as well.
The first step to achieve this is to define an application specification.
For example, if your application is named `:my_app`, an app specification
should exist at `ebin/my_app.app`. This file is usually defined by
build tools like Mix.
Then, with the app specification in hands, we must also define an
application module callback that controls how to start and stop
such applications. This module is about defining such callbacks.
There are two callbacks required to be implemented:
1. `start(type, args)` - It must return `{ :ok, pid }` or
`{ :ok, pid, state }`, where `pid` is the process identifier
of the supervisor tree root;
2. `stop(state)` receives the state returned by `start` and should
do any necessary cleaning up. Notice that shutting down the supervisor
is automatically handled by the VM;
When using this module, it simply tags the module behaviour as
`:application` and defines a default `stop/1` callback. The `start/2`
still needs to be defined by the user.
You can learn more about the `:application` module, the application
specification and the application module callbacks below:
http://www.erlang.org/doc/man/application.html
http://www.erlang.org/doc/design_principles/applications.html
http://learnyousomeerlang.com/building-otp-applications
## Example
defmodule MyApp do
use Application.Behaviour
def start(_type, args) do
MyApp.Sup.start_link(args)
end
end
"""
# Starts the given application and all of its dependencies that
# have not been started yet recursively.
#
# ## Supported types
#
# When starting an application, a type can be given:
#
# * `:permanent` - If a permanent application terminates, all other
# applications and the runtime system are also terminated;
# * `:transient` - If a transient application terminates with reason
# `:normal`, this is reported but no other applications are terminated.
# If a transient application terminates abnormally, all other
# applications and the runtime system are also terminated;
# * `:temporary` - If a temporary application terminates, this is reported
# but no other applications are terminated.
#
# The type only applies to the application being started. Its dependencies
# are all started with default type (which is :temporary).
#
# Note that transient mode is of little practical use, since when a
# supervision tree terminates, the reason is set to shutdown, not normal.
#
# ## Examples
#
# Application.Behaviour.start(:my_app)
#
@doc false
def start(app, type // :temporary) do
case :application.start(app, type) do
{ :error, { :not_started, dep } } ->
case start(dep) do
:ok -> start(app, type)
other -> other
end
{ :error, { :already_started, _ } } ->
:ok
other ->
other
end
end
@doc false
defmacro __using__(_) do
quote location: :keep do
@behavior :application
def stop(_state) do
:ok
end
defoverridable [stop: 1]
end
end
end
-5
View File
@@ -45,11 +45,6 @@ defmodule Behaviour do
@doc """
Defines a callback according to the given type specification.
"""
defmacro defcallback(fun, do: return) do
IO.write "[WARNING] defcallback(fun, do: return) is deprecated, please use defcallback(fun :: return) instead\n#{Exception.env_stacktrace(__CALLER__)}"
do_defcallback(fun, return, __CALLER__)
end
defmacro defcallback({ :::, _, [fun, return] }) do
do_defcallback(fun, return, __CALLER__)
end
-88
View File
@@ -1,88 +0,0 @@
defmodule Binary.Dict do
@moduledoc """
This module implements a dictionary that forces the keys to be
converted to binaries on insertion. Currently it is implemented
using an `OrdDict`, but this may change in the future.
Check the `Dict` module for examples and documentation.
"""
use Dict.Common
defmacrop dict(data) do
quote do
{ Binary.Dict, unquote(data) }
end
end
def keys(dict(data)) do
lc { k, _ } inlist data, do: k
end
def values(dict(data)) do
lc { _, v } inlist data, do: v
end
def size(dict(data)) do
length(data)
end
def has_key?(dict(data), key) do
:orddict.is_key to_binary(key), data
end
def get(dict(data), key, default) do
case :orddict.find(to_binary(key), data) do
{:ok, value} -> value
:error -> default
end
end
def get!(dict(data), key) do
case :orddict.find(to_binary(key), data) do
{:ok, value} -> value
:error -> raise(KeyError, key: key)
end
end
def put(dict(data), key, value) do
dict(:orddict.store to_binary(key), value, data)
end
def delete(dict(data), key) do
dict(:orddict.erase to_binary(key), data)
end
def merge(dict(d1), dict(d2), fun) do
dict(:orddict.merge fun, d1, d2)
end
def merge(dict(_) = d1, d2, fun) do
merge(d1, new(d2), fun)
end
def update(dict(data), key, fun) do
dict(:orddict.update to_binary(key), fun, data)
end
def update(dict(data), key, initial, fun) do
dict(:orddict.update to_binary(key), fun, initial, data)
end
def empty(_) do
dict([])
end
def to_list(dict(data)) do
data
end
end
defimpl Enum.Iterator, for: Binary.Dict do
def iterator({ Binary.Dict, data }), do: data
def count({ Binary.Dict, data }), do: length(data)
end
defimpl Access, for: Binary.Dict do
def access(dict, key), do: Binary.Dict.get(dict, key, nil)
end
+45 -18
View File
@@ -11,7 +11,7 @@ defprotocol Binary.Inspect do
printing.
"""
@only [BitString, List, Tuple, Atom, Number, Any]
@only [BitString, List, Tuple, Atom, Number, Function, PID, Port, Reference]
def inspect(thing, opts)
end
@@ -67,11 +67,12 @@ defmodule Binary.Inspect.Utils do
end
defp do_escape(<<h, t :: binary>>, char) when
h == ?# or h == ?\b or
h == ?\d or h == ?\e or
h == ?\f or h == ?\n or
h == ?\r or h == ?\\ or
h == ?\t or h == ?\v do
h == ?# or h == ?\a or
h == ?\b or h == ?\d or
h == ?\e or h == ?\f or
h == ?\n or h == ?\r or
h == ?\\ or h == ?\t or
h == ?\v do
[?\\, escape_map(h) | do_escape(t, char)]
end
@@ -84,6 +85,7 @@ defmodule Binary.Inspect.Utils do
end
defp escape_map(?#), do: ?#
defp escape_map(?\a), do: ?a
defp escape_map(?\b), do: ?b
defp escape_map(?\d), do: ?d
defp escape_map(?\e), do: ?e
@@ -287,7 +289,7 @@ defimpl Binary.Inspect, for: List do
printable?(cs)
end
defp printable?([c|cs]) when c in [?\n, ?\r, ?\t, ?\v, ?\b, ?\f, ?\e] do
defp printable?([c|cs]) when c in [?\n, ?\r, ?\t, ?\v, ?\b, ?\f, ?\e, ?\a] do
printable?(cs)
end
@@ -415,18 +417,43 @@ defimpl Binary.Inspect, for: Regex do
end
end
defimpl Binary.Inspect, for: Any do
defimpl Binary.Inspect, for: Function do
@moduledoc """
For all other terms not implemented, we use the default
Erlang representation.
## Examples
inspect Process.self #=> "<0.35.0>"
Inspect functions, when possible, in a literal form.
"""
def inspect(thing, _) do
iolist_to_binary :io_lib.format('~p', [thing])
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
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
+36 -31
View File
@@ -1,4 +1,10 @@
defmodule Code do
defexception LoadError, file: nil do
def message(exception) do
"could not load #{exception.file}"
end
end
@moduledoc """
The Code module is responsible to manage code compilation,
code evaluation and code loading.
@@ -14,7 +20,7 @@ defmodule Code do
Returns all the loaded files.
"""
def loaded_files do
server_call :loaded
:elixir_code_server.call :loaded
end
@doc """
@@ -24,31 +30,31 @@ defmodule Code do
allowing it to be required again.
"""
def unload_files(files) do
server_cast { :unload_files, files }
:elixir_code_server.cast { :unload_files, files }
end
@doc """
Appends a path to Erlang VM code path.
The path is expanded with `File.expand_path` before added.
The path is expanded with `Path.expand` before added.
"""
def append_path(path) do
:code.add_pathz(File.expand_path to_char_list(path))
:code.add_pathz(Path.expand to_char_list(path))
end
@doc """
Prepends a path to Erlang VM code path.
The path is expanded with `File.expand_path` before added.
The path is expanded with `Path.expand` before added.
"""
def prepend_path(path) do
:code.add_patha(File.expand_path to_char_list(path))
:code.add_patha(Path.expand to_char_list(path))
end
@doc """
Deletes a path from Erlang VM code path.
The path is expanded with `File.expand_path` before deleted.
The path is expanded with `Path.expand` before deleted.
"""
def delete_path(path) do
:code.del_path(File.expand_path to_char_list(path))
:code.del_path(Path.expand to_char_list(path))
end
@doc """
@@ -58,14 +64,23 @@ defmodule Code do
* `:file` - the file to be considered in the evaluation
* `:line` - the line 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:
* `: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
* `:macros` - a list of tuples where the first element is a module
and the second a list of imported macro names and arity
* `:delegate_locals_to` - delegate local calls to the given module,
the default is to not delegate
Notice that setting any ov 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`,
etc.
## Examples
@@ -167,7 +182,7 @@ defmodule Code do
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, forms }
defp unpack_ast(line, forms), do: { :__block__, [line: line], forms }
@doc """
Loads the given `file`. Accepts `relative_to` as an argument
@@ -183,9 +198,9 @@ defmodule Code do
"""
def load_file(file, relative_to // nil) when is_binary(file) do
file = find_file(file, relative_to)
server_call { :acquire, file }
:elixir_code_server.call { :acquire, file }
loaded = :elixir_compiler.file file
server_cast { :loaded, file }
:elixir_code_server.cast { :loaded, file }
loaded
end
@@ -205,14 +220,14 @@ defmodule Code do
def require_file(file, relative_to // nil) when is_binary(file) do
file = find_file(file, relative_to)
case server_call({ :acquire, file }) do
case :elixir_code_server.call({ :acquire, file }) do
:loaded ->
nil
{ :queued, ref } ->
receive do { :elixir_code_server, ^ref, :loaded } -> nil end
:proceed ->
loaded = :elixir_compiler.file file
server_cast { :loaded, file }
:elixir_code_server.cast { :loaded, file }
loaded
end
end
@@ -222,7 +237,7 @@ defmodule Code do
Check compiler_options/1 for more information.
"""
def compiler_options do
server_call :compiler_options
:elixir_code_server.call :compiler_options
end
@doc """
@@ -241,7 +256,7 @@ defmodule Code do
"""
def compiler_options(opts) do
server_call { :compiler_options, opts }
:elixir_code_server.cast { :compiler_options, opts }
end
@doc """
@@ -252,7 +267,7 @@ defmodule Code do
For compiling many files at once, check `Kernel.ParallelCompiler`.
"""
def compile_string(string, file // "nofile") when is_binary(file) do
:elixir_compiler.string :unicode.characters_to_list(string), to_binary(file)
:elixir_compiler.string :unicode.characters_to_list(string), file
end
@doc """
@@ -348,26 +363,16 @@ defmodule Code do
# Finds the file given the relative_to path.
# If the file is found, returns its path in binary, fails otherwise.
defp find_file(file, relative_to) do
file = to_binary(file)
file = if relative_to do
File.expand_path(file, relative_to)
Path.expand(file, relative_to)
else
File.expand_path(file)
Path.expand(file)
end
if File.regular?(file) do
file
else
raise ArgumentError, message: "could not load #{file}"
raise LoadError, file: file
end
end
defp server_call(args) do
:gen_server.call(:elixir_code_server, args)
end
defp server_cast(args) do
:gen_server.cast(:elixir_code_server, args)
end
end
+59 -34
View File
@@ -2,19 +2,13 @@ defmodule Dict do
@moduledoc """
This module specifies the Dict API expected to be
implemented by different dictionaries. It also provides
functions that redirect to the underlying Dict based on
the tuple signature.
The keyword list used throughout Elixir cannot be
manipulated via the Dict module, you must use the
Keyword module instead. This distinction is intentional:
the Dict module is meant to work on structures that work
as storage.
functions that redirect to the underlying Dict, allowing
a developer to work with different Dict implementations
using one API.
To create a new dict, use the `new` functions defined
by each dict type:
OrdDict.new [{:a, 1}, {:b, 2}]
HashDict.new #=> creates an empty HashDict
For simplicity's sake, in the examples below everytime
@@ -24,26 +18,44 @@ defmodule Dict do
it implies that the returned value is actually of the
same dict type as the input one.
Keep in mind that all dicts are also required to
implement both `Access` and `Enum.Iterator` protocols.
"""
use Behaviour
@type key :: any
@type value :: any
@type t :: tuple
@type t :: tuple | list
defcallback delete(t, key) :: t
defcallback empty(t) :: t
defcallback get(t, key) :: value
defcallback get(t, key, value) :: value
defcallback get!(t, key) :: value | no_return
defcallback has_key?(t, key) :: boolean
defcallback keys(t) :: list(key)
defcallback merge(t, t) :: t
defcallback merge(t, t, (key, value, value -> value)) :: t
defcallback put(t, key, value) :: t
defcallback put_new(t, key, value) :: t
defcallback size(t) :: non_neg_integer()
defcallback to_list(t) :: list()
defcallback update(t, key, (value -> value)) :: t
defcallback update(t, key, (value -> value)) :: t | no_return
defcallback update(t, key, value, (value -> value)) :: t
defcallback values(t) :: list(value)
defmacrop target(dict) do
quote do
cond do
is_tuple(unquote(dict)) ->
elem(unquote(dict), 0)
is_list(unquote(dict)) ->
List.Dict
end
end
end
@doc """
Returns a list containing all dict's keys.
The keys are not guaranteed to be sorted, unless
@@ -57,7 +69,7 @@ defmodule Dict do
"""
@spec keys(t) :: [key]
def keys(dict) do
elem(dict, 0).keys(dict)
target(dict).keys(dict)
end
@doc """
@@ -71,7 +83,7 @@ defmodule Dict do
"""
@spec values(t) :: [value]
def values(dict) do
elem(dict, 0).values(dict)
target(dict).values(dict)
end
@doc """
@@ -85,7 +97,7 @@ defmodule Dict do
"""
@spec size(t) :: non_neg_integer
def size(dict) do
elem(dict, 0).size(dict)
target(dict).size(dict)
end
@doc """
@@ -100,7 +112,7 @@ defmodule Dict do
"""
@spec has_key?(t, key) :: boolean
def has_key?(dict, key) do
elem(dict, 0).has_key?(dict, key)
target(dict).has_key?(dict, key)
end
@doc """
@@ -115,10 +127,9 @@ defmodule Dict do
Dict.get d, :b, 3 #=> 3
"""
@spec get(t, key) :: value | nil
@spec get(t, key, value) :: value
def get(dict, key, default // nil) do
elem(dict, 0).get(dict, key, default)
target(dict).get(dict, key, default)
end
@doc """
@@ -134,7 +145,7 @@ defmodule Dict do
"""
@spec get!(t, key) :: value | no_return
def get!(dict, key) do
elem(dict, 0).get!(dict, key)
target(dict).get!(dict, key)
end
@doc """
@@ -150,7 +161,22 @@ defmodule Dict do
"""
@spec put(t, key, value) :: t
def put(dict, key, val) do
elem(dict, 0).put(dict, key, val)
target(dict).put(dict, key, val)
end
@doc """
Puts the given `value` under `key` in `dict` unless `key` already exists.
## Examples
d = new [a: 1, b: 2]
Dict.put_new d, :a, 3
#=> [a: 1, b: 2]
"""
@spec put_new(t, key, value) :: t
def put_new(dict, key, val) do
target(dict).put_new(dict, key, val)
end
@doc """
@@ -168,14 +194,12 @@ defmodule Dict do
"""
@spec delete(t, key) :: t
def delete(dict, key) do
elem(dict, 0).delete(dict, key)
target(dict).delete(dict, key)
end
@doc """
Merges two dicts into one. If the dicts have duplicated entries,
the one given as second argument wins. In case the second argument
is not of the same kind as the first one, it is converted to the
same kind before merging as long as it implements the `Enum` protocol.
Merges the given enum into the dict. In case one of the enum entries
alread exist in the dict, it is given higher preference.
## Examples
@@ -186,13 +210,14 @@ defmodule Dict do
"""
@spec merge(t, t) :: t
def merge(dict1, dict2) do
merge(dict1, dict2, fn(_k, _v1, v2) -> v2 end)
def merge(dict, enum) do
merge(dict, enum, fn(_k, _v1, v2) -> v2 end)
end
@doc """
Merges two dicts into one. If the dicts have duplicated entries, the given
function is invoked to solve conflicts.
Merges the given enum into the dict. In case one of the enum entries
alread exist in the dict, the given function is invoked to solve
conflicts.
## Examples
@@ -205,8 +230,8 @@ defmodule Dict do
"""
@spec merge(t, t, (key, value, value -> value)) :: t
def merge(dict1, dict2, fun) do
elem(dict1, 0).merge(dict1, dict2, fun)
def merge(dict, enum, fun) do
target(dict).merge(dict, enum, fun)
end
@doc """
@@ -222,7 +247,7 @@ defmodule Dict do
"""
@spec update(t, key, (value -> value)) :: t
def update(dict, key, fun) do
elem(dict, 0).update(dict, key, fun)
target(dict).update(dict, key, fun)
end
@doc """
@@ -239,7 +264,7 @@ defmodule Dict do
"""
@spec update(t, key, value, (value -> value)) :: t
def update(dict, key, initial, fun) do
elem(dict, 0).update(dict, key, initial, fun)
target(dict).update(dict, key, initial, fun)
end
@doc """
@@ -247,7 +272,7 @@ defmodule Dict do
"""
@spec empty(t) :: t
def empty(dict) do
elem(dict, 0).empty(dict)
target(dict).empty(dict)
end
@doc """
@@ -256,6 +281,6 @@ defmodule Dict do
"""
@spec to_list(t) :: list
def to_list(dict) do
elem(dict, 0).to_list(dict)
target(dict).to_list(dict)
end
end
-47
View File
@@ -1,47 +0,0 @@
defmodule Dict.Common do
@moduledoc false
defmacro __using__(_) do
quote do
@behavior Dict
@doc """
Creates a new empty dict.
"""
def new do
empty(nil)
end
@doc """
Creates a new dict from a list of pairs.
## Examples
#{inspect(__MODULE__)}.new [{:b,1},{:a,2}]
#=> [a: 1, b: 2]
"""
def new(pairs) do
Enum.reduce pairs, new, fn { k, v }, dict ->
put(dict, k, v)
end
end
@doc """
Creates a new dict from a list of elements with the
help of the transformation function.
## Examples
#{inspect(__MODULE__)}.new ["a", "b"], fn x -> {x, x} end
#=> ["a": "a", "b": "b"]
"""
def new(list, transform) when is_function(transform) do
Enum.reduce list, new(), fn i, dict ->
{ k, v } = transform.(i)
put(dict, k, v)
end
end
end
end
end
+274
View File
@@ -0,0 +1,274 @@
defmodule Dict.Common do
@moduledoc false
defmacro __using__(_) do
quote do
@behavior Dict
@doc """
Creates a new empty dict.
"""
def new do
IO.puts "#{inspect __MODULE__} is deprecated, please use HashDict instead"
Exception.print_stacktrace
empty(nil)
end
@doc """
Creates a new dict from a list of pairs.
## Examples
#{inspect(__MODULE__)}.new [{:b,1},{:a,2}]
#=> [a: 1, b: 2]
"""
def new(pairs) do
IO.puts "#{inspect __MODULE__} is deprecated, please use HashDict instead"
Exception.print_stacktrace
Enum.reduce pairs, new, fn { k, v }, dict ->
put(dict, k, v)
end
end
@doc """
Creates a new dict from a list of elements with the
help of the transformation function.
## Examples
#{inspect(__MODULE__)}.new ["a", "b"], fn x -> {x, x} end
#=> ["a": "a", "b": "b"]
"""
def new(list, transform) when is_function(transform) do
IO.puts "#{inspect __MODULE__} is deprecated, please use HashDict instead"
Exception.print_stacktrace
Enum.reduce list, new(), fn i, dict ->
{ k, v } = transform.(i)
put(dict, k, v)
end
end
end
end
end
defmodule OrdDict do
@moduledoc """
This module implements a dictionary type that stores items
as a list of tuples. It is a simple wrapper around
[Erlang's orddict module](http://www.erlang.org/doc/man/orddict.html)
and exposed via the `Dict` module.
Check the `Dict` module for examples and documentation.
"""
use Dict.Common
defmacrop dict(data) do
quote do
{ OrdDict, unquote(data) }
end
end
@doc false
def keys(dict(data)) do
lc { k, _ } inlist data, do: k
end
@doc false
def values(dict(data)) do
lc { _, v } inlist data, do: v
end
@doc false
def size(dict(data)) do
length(data)
end
@doc false
def has_key?(dict(data), key) do
:orddict.is_key key, data
end
@doc false
def get(dict(data), key, default // nil) do
case :orddict.find(key, data) do
{:ok, value} -> value
:error -> default
end
end
@doc false
def get!(dict(data), key) do
case :orddict.find(key, data) do
{:ok, value} -> value
:error -> raise(KeyError, key: key)
end
end
@doc false
def put(dict(data), key, value) do
dict(:orddict.store key, value, data)
end
@doc false
def put_new(dict(data), key, value) do
dict(:orddict.update key, fn(x) -> x end, value, data)
end
@doc false
def delete(dict(data), key) do
dict(:orddict.erase key, data)
end
@doc false
def merge(d1, d2, fun // fn(_k, _v1, v2) -> v2 end)
def merge(dict(d1), dict(d2), fun) do
dict(:orddict.merge fun, d1, d2)
end
def merge(dict(_) = d1, d2, fun) do
merge(d1, new(d2), fun)
end
@doc false
def update(dict(data), key, fun) do
dict(:orddict.update key, fun, data)
end
@doc false
def update(dict(data), key, initial, fun) do
dict(:orddict.update key, fun, initial, data)
end
@doc false
def empty(_) do
dict([])
end
@doc false
def to_list(dict(data)) do
data
end
end
defimpl Enum.Iterator, for: OrdDict do
def iterator({ OrdDict, data }), do: data
def count({ OrdDict, data }), do: length(data)
end
defimpl Access, for: OrdDict do
def access(dict, key), do: OrdDict.get(dict, key, nil)
end
defmodule Binary.Dict do
@moduledoc """
This module implements a dictionary that forces the keys to be
converted to binaries on insertion. Currently it is implemented
using an `OrdDict`, but this may change in the future.
Check the `Dict` module for examples and documentation.
"""
use Dict.Common
defmacrop dict(data) do
quote do
{ Binary.Dict, unquote(data) }
end
end
@doc false
def keys(dict(data)) do
lc { k, _ } inlist data, do: k
end
@doc false
def values(dict(data)) do
lc { _, v } inlist data, do: v
end
@doc false
def size(dict(data)) do
length(data)
end
@doc false
def has_key?(dict(data), key) do
:orddict.is_key to_binary(key), data
end
@doc false
def get(dict(data), key, default // nil) do
case :orddict.find(to_binary(key), data) do
{:ok, value} -> value
:error -> default
end
end
@doc false
def get!(dict(data), key) do
case :orddict.find(to_binary(key), data) do
{:ok, value} -> value
:error -> raise(KeyError, key: key)
end
end
@doc false
def put(dict(data), key, value) do
dict(:orddict.store to_binary(key), value, data)
end
@doc false
def put_new(dict(data), key, value) do
dict(:orddict.update to_binary(key), fn(x) -> x end, value, data)
end
@doc false
def delete(dict(data), key) do
dict(:orddict.erase to_binary(key), data)
end
@doc false
def merge(d1, d2, fun // fn(_k, _v1, v2) -> v2 end)
def merge(dict(d1), dict(d2), fun) do
dict(:orddict.merge fun, d1, d2)
end
def merge(dict(_) = d1, d2, fun) do
merge(d1, new(d2), fun)
end
@doc false
def update(dict(data), key, fun) do
dict(:orddict.update to_binary(key), fun, data)
end
@doc false
def update(dict(data), key, initial, fun) do
dict(:orddict.update to_binary(key), fun, initial, data)
end
@doc false
def empty(_) do
dict([])
end
@doc false
def to_list(dict(data)) do
data
end
end
defimpl Enum.Iterator, for: Binary.Dict do
def iterator({ Binary.Dict, data }), do: data
def count({ Binary.Dict, data }), do: length(data)
end
defimpl Access, for: Binary.Dict do
def access(dict, key), do: Binary.Dict.get(dict, key, nil)
end
+60 -81
View File
@@ -70,6 +70,7 @@ defmodule Enum do
@type t :: Enum.Iterator.t
@type element :: any
@type index :: non_neg_integer
@doc """
Invokes the given `fun` for each item in the `collection` and returns true if
@@ -160,7 +161,7 @@ defmodule Enum do
Enum.at! [2,4,6], 4 #=> raises Enum.OutOfBoundsError
"""
@spec at!(t, non_neg_integer) :: element | no_return
@spec at!(t, index) :: element | no_return
def at!(collection, n) when is_list(collection) and n >= 0 do
do_at!(collection, n)
end
@@ -259,23 +260,32 @@ defmodule Enum do
@doc """
Invokes the given `fun` for each item in the `collection`.
Returns the `collection` itself.
Returns the `collection` itself. `fun` can take two parameters,
in which case the second parameter will be the iteration index.
## Examples
Enum.each ['some', 'example'], fn(x) -> IO.puts x end
"""
@spec each(t, (element -> any)) :: :ok
@spec each(t, (element -> any) | (element, index -> any)) :: :ok
def each(collection, fun) when is_list(collection) do
:lists.foreach(fun, collection)
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)
end
:ok
end
def each(collection, fun) do
case I.iterator(collection) do
{ iterator, pointer } ->
do_each(pointer, iterator, fun)
cond do
is_function(fun, 1) -> do_each(pointer, iterator, fun)
is_function(fun, 2) -> do_indexed_each(pointer, iterator, fun, 0)
end
:ok
list when is_list(list) ->
each(list, fun)
@@ -430,7 +440,7 @@ defmodule Enum do
#=> 2
"""
@spec find_index(t, (element -> any)) :: non_neg_integer | :nil
@spec find_index(t, (element -> any)) :: index | :nil
def find_index(collection, fun) when is_list(collection) do
do_find_index(collection, 0, fun)
end
@@ -506,6 +516,9 @@ 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.
## Examples
@@ -517,15 +530,21 @@ defmodule Enum do
#=> [a: -1, b: -2]
"""
@spec map(t, (element -> any)) :: list
@spec map(t, (element -> any) | (element, index -> any)) :: list
def map(collection, fun) when is_list(collection) do
lc item inlist collection, do: fun.(item)
cond do
is_function(fun, 1) -> lc item inlist collection, do: fun.(item)
is_function(fun, 2) -> elem(:lists.mapfoldl(fn(h, idx) -> {fun.(h, idx), idx + 1} end, 0, collection), 0)
end
end
def map(collection, fun) do
case I.iterator(collection) do
{ iterator, pointer } ->
do_map(pointer, iterator, fun)
cond do
is_function(fun, 1) -> do_map(pointer, iterator, fun)
is_function(fun, 2) -> do_indexed_map(pointer, iterator, fun, 0)
end
list when is_list(list) ->
map(list, fun)
end
@@ -669,23 +688,6 @@ defmodule Enum do
end
end
@doc false
@spec qsort(t) :: list
def qsort(collection) when is_list(collection) do
IO.write "[WARNING] Enum.qsort is deprecated, please use Enum.sort instead\n#{Exception.formatted_stacktrace}"
do_list_qsort(collection, [])
end
def qsort(collection) do
case I.iterator(collection) do
{ iterator, pointer } ->
IO.write "[WARNING] Enum.qsort is deprecated, please use Enum.sort instead\n#{Exception.formatted_stacktrace}"
do_qsort(pointer, iterator, [])
list when is_list(list) ->
qsort(list)
end
end
@doc """
Sorts the collection using the merge sort algorithm.
@@ -803,11 +805,16 @@ defmodule Enum do
"""
@spec take(t, integer) :: list
def take(collection, count) when is_list(collection) and count >= 0 do
def take(_collection, 0) do
[]
end
def take(collection, count) when is_list(collection) and count > 0 do
do_take(collection, count)
end
def take(collection, count) when count >= 0 do
def take(collection, count) when count > 0 do
case I.iterator(collection) do
{ iterator, pointer } ->
do_take(pointer, iterator, count)
@@ -1143,6 +1150,15 @@ defmodule Enum do
[]
end
defp do_indexed_each({ h, next }, iterator, fun, idx) do
fun.(h, idx)
do_each(iterator.(next), iterator, fun, idx + 1)
end
defp do_each(:stop, _, _, _) do
[]
end
## filter
defp do_filter({ h, next }, iterator, fun) do
@@ -1209,6 +1225,14 @@ defmodule Enum do
[]
end
defp do_indexed_map({ h, next }, iterator, fun, idx) do
[fun.(h, idx) | do_indexed_map(iterator.(next), iterator, fun, idx + 1)]
end
defp do_indexed_map(:stop, _, _, _) do
[]
end
## map join
defp do_map_join([h|t], mapper, joiner, nil) do
@@ -1462,56 +1486,6 @@ defmodule Enum do
{ :lists.reverse(acc), [] }
end
## qsort (lists)
defp do_list_qsort([], acc) do
acc
end
defp do_list_qsort([h|t], acc) do
do_list_qsort_part(h, t, {[], [h], []}, acc)
end
defp do_list_qsort_part(_, [], { l, e, g }, acc) do
do_list_qsort(l, e ++ do_list_qsort(g, acc))
end
defp do_list_qsort_part(x, [h|t], { l, e, g }, acc) do
cond do
h < x ->
do_list_qsort_part(x, t, { [h|l], e, g }, acc)
h > x ->
do_list_qsort_part(x, t, { l, e, [h|g] }, acc)
true ->
do_list_qsort_part(x, t, { l, [h|e], g }, acc)
end
end
## qsort (iterator)
defp do_qsort({ h, next }, iterator, acc) do
do_qsort_part(h, iterator.(next), iterator, {[], [h], []}, acc)
end
defp do_qsort(:stop, _iterator, acc) do
acc
end
defp do_qsort_part(_, :stop, _iterator, { l, e, g }, acc) do
do_list_qsort(l, e ++ do_list_qsort(g, acc))
end
defp do_qsort_part(x, { h, next }, iterator, { l, e, g }, acc) do
cond do
h < x ->
do_qsort_part(x, iterator.(next), iterator, { [h|l], e, g }, acc)
h > x ->
do_qsort_part(x, iterator.(next), iterator, { l, e, [h|g] }, acc)
true ->
do_qsort_part(x, iterator.(next), iterator, { l, [h|e], g }, acc)
end
end
## take
defp do_take([h|t], counter) when counter > 0 do
@@ -1526,10 +1500,14 @@ defmodule Enum do
[]
end
defp do_take({ h, next }, iterator, counter) when counter > 0 do
defp do_take({ h, next }, iterator, counter) when counter > 1 do
[h|do_take(iterator.(next), iterator, counter - 1)]
end
defp do_take({ h, _next }, _iterator, 1) do
[h]
end
defp do_take(_extra, _iterator, 0) do
[]
end
@@ -1627,12 +1605,13 @@ end
defimpl Enum.Iterator, for: Function do
def iterator(function) do
function.()
{ iterator, first } = function.()
{ iterator, iterator.(first) }
end
def count(function) do
{ function, first } = function.()
do_count(first, function, 0)
do_count(function.(first), function, 0)
end
defp do_count({ _, next }, function, acc) do
+112 -68
View File
@@ -1,13 +1,14 @@
defmodule Exception do
@moduledoc """
Several convenience functions to work and pretty print
exceptions and backtraces.
exceptions and stacktraces.
"""
# Normalize an exception converting Erlang exceptions
# to Elixir style exceptions. This is meant to be used
# internally.
@doc false
@doc """
Normalizes an exception converting Erlang exceptions
to Elixir exceptions. Useful when interfacing Erlang
code with Elixir code.
"""
def normalize(exception) when is_exception(exception) do
exception
end
@@ -41,11 +42,11 @@ defmodule Exception do
end
def normalize(:undef) do
UndefinedFunctionError.new from_stacktrace(System.stacktrace)
UndefinedFunctionError.new from_stacktrace(:erlang.get_stacktrace)
end
def normalize(:function_clause) do
FunctionClauseError.new from_stacktrace(System.stacktrace)
FunctionClauseError.new from_stacktrace(:erlang.get_stacktrace)
end
def normalize({ :badarg, payload }) do
@@ -56,6 +57,76 @@ defmodule Exception do
ErlangError.new original: other
end
@doc """
Receives a tuple representing a stacktrace entry and formats it.
The current working directory may be given as argument, which
is used to prettify the stacktrace.
"""
def format_entry(entry, cwd // nil)
# From Macro.Env.stacktrace
def format_entry({ module, :__MODULE__, 0, file_line }, cwd) do
"#{format_file_line(file_line, cwd)}#{inspect module} (module)"
end
# From :elixir_compiler
def format_entry({ _module, :__MODULE__, 2, file_line }, cwd) do
"#{format_file_line(file_line, cwd)}(module)"
end
# From :elixir_compiler
def format_entry({ _module, :__FILE__, 2, file_line }, cwd) do
"#{format_file_line(file_line, cwd)}(file)"
end
def format_entry({module, fun, arity, file_line}, cwd) do
"#{format_file_line(file_line, cwd)}#{format_module_fun_arity(module, fun, arity)}"
end
def format_entry({fun, arity, file_line}, cwd) do
"#{format_file_line(file_line, cwd)}#{format_fun_arity(fun, arity)}"
end
@doc """
Formats the stacktrace.
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.
"""
def format_stacktrace(trace // nil)
def format_stacktrace(trace) when is_tuple(trace) do
IO.puts "Exception.format_stacktrace is deprecated, please use format_entry instead"
print_stacktrace
end
def format_stacktrace(trace) do
trace = trace || try do
throw(:stacktrace)
catch
:stacktrace -> Enum.drop(:erlang.get_stacktrace, 1)
end
case trace do
[] -> "\n"
s -> " " <> Enum.map_join(s, "\n ", format_entry(&1)) <> "\n"
end
end
@doc """
Prints the current stacktrace to standard output.
A stacktrace must be given as argument. If not, this function
calculates the current stacktrace and formats it. As consequence,
the value of `System.stacktrace` is changed.
"""
def print_stacktrace(trace // nil) do
IO.write format_stacktrace(trace)
end
## Helpers
# Check the given module is a valid exception record.
@doc false
def check!(module) do
@@ -64,16 +135,27 @@ defmodule Exception do
end
end
@doc """
Receives a module, fun and arity and returns a string
representing such invocation. Arity may also be a list
of arguments. It follows the same syntax as in stacktraces.
"""
def format_module_fun_arity(module, fun, arity) do
case inspect(fun) do
<< ?:, fun :: binary >> -> :ok
fun -> :ok
# Format fun and arity
@doc false
def format_fun_arity(fun, arity) do
if is_list(arity) do
inspected = lc x inlist arity, do: inspect(x)
"#{inspect fun}(#{Enum.join(inspected, ", ")})"
else
"#{inspect fun}/#{arity}"
end
end
# Receives a module, fun and arity and returns a string
# representing such invocation. Arity may also be a list
# of arguments. It follows the same syntax as in stacktraces.
@doc false
def format_module_fun_arity(module, fun, arity) do
fun =
case inspect(fun) do
<< ?:, erl :: binary >> -> erl
elixir -> elixir
end
if is_list(arity) do
inspected = lc x inlist arity, do: inspect(x)
@@ -83,56 +165,20 @@ defmodule Exception do
end
end
@doc """
Returns the stacktrace as a binary formatted as per `format_stacktrace/1`.
"""
def formatted_stacktrace(trace // nil) do
trace = trace || try do
throw(:stacktrace)
catch
:stacktrace -> Enum.drop(System.stacktrace, 1)
end
case trace do
[] -> ""
s -> " " <> Enum.map_join(s, "\n ", format_stacktrace(&1)) <> "\n"
end
# Format file and line for exception printing.
@doc false
def format_file_line(opts, cwd) do
format_file_line Keyword.get(opts, :file), Keyword.get(opts, :line), cwd
end
@doc """
Returns a formatted stacktrace from the environment.
"""
def env_stacktrace(env) do
rest =
case env.function do
{ name, arity } -> format_module_fun_arity(env.module, name, arity)
nil -> "#{inspect env.module} (body)"
end
" #{format_file_line(env.location)}#{rest}\n"
end
@doc """
Formats each line in the stacktrace.
"""
def format_stacktrace({module, fun, arity, file_line}) do
"#{format_file_line(file_line)}#{format_module_fun_arity(module, fun, arity)}"
end
@doc """
Formats file and line information present in stacktraces.
Expect them to be given in a keyword list.
"""
def format_file_line(file_line) do
format_file_line(Keyword.get(file_line, :file), Keyword.get(file_line, :line))
end
@doc """
Formats the given file and line.
"""
def format_file_line(file, line) do
def format_file_line(file, line, cwd) do
if file do
file = to_binary(file)
if cwd do
file = Path.relative_to(file, cwd)
end
if line && line != 0 do
"#{file}:#{line}: "
else
@@ -143,8 +189,6 @@ defmodule Exception do
end
end
## Helpers
defp from_stacktrace([{ module, function, arity, _ }|_]) do
[module: module, function: function, arity: arity]
end
@@ -159,25 +203,25 @@ defexception SystemLimitError, message: "a system limit has been reached"
defexception SyntaxError, [file: nil, line: nil, description: "syntax error"] do
def message(exception) do
"#{Exception.format_file_line(exception.file, exception.line)}#{exception.description}"
"#{Exception.format_file_line(exception.file, exception.line, nil)}#{exception.description}"
end
end
defexception TokenMissingError, [file: nil, line: nil, description: "expression is incomplete"] do
def message(exception) do
"#{Exception.format_file_line(exception.file, exception.line)}#{exception.description}"
"#{Exception.format_file_line(exception.file, exception.line, nil)}#{exception.description}"
end
end
defexception CompileError, [file: nil, line: nil, description: "compile error"] do
def message(exception) do
"#{Exception.format_file_line(exception.file, exception.line)}#{exception.description}"
"#{Exception.format_file_line(exception.file, exception.line, nil)}#{exception.description}"
end
end
defexception BadFunctionError, [actual: nil] do
def message(exception) do
"bad function: #{inspect(exception.actual)}"
"expected a function, got: #{inspect(exception.actual)}"
end
end
+128 -300
View File
@@ -54,12 +54,11 @@ end
defmodule File do
@moduledoc """
This module contains function to manipulate files,
filenames and the filesystem. 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 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`.
In order to write and read files, one must use the
functions in the IO module. By default, a file is
@@ -106,38 +105,88 @@ defmodule File do
alias :filename, as: FN
alias :filelib, as: FL
@doc """
Expands the path by returning its absolute name and expanding
any `.` and `..` characters.
If the given `path` is a char list, returns a char list.
Otherwise returns a binary.
## Examples
File.expand_path("/foo/bar/../bar") == "/foo/bar"
"""
@doc false
def expand_path(path) do
normalize FN.absname(path)
IO.puts "File.expand_path is deprecated, please use Path.expand instead"
Exception.print_stacktrace
Path.expand(path)
end
@doc """
Expands the path to the relative location and expanding
any `.` and `..` characters. If the path is already an
absolute path, the relative location is ignored.
If the given `path` is a char list, returns a char list.
Otherwise returns a binary.
## Examples
File.expand_path("foo/bar/../bar", "/baz") == "/baz/foo/bar"
File.expand_path("/foo/bar/../bar", "/baz") == "/foo/bar"
"""
@doc false
def expand_path(path, relative_to) do
normalize FN.absname(FN.absname(path, relative_to))
IO.puts "File.expand_path is deprecated, please use Path.expand instead"
Exception.print_stacktrace
Path.expand(path, relative_to)
end
@doc false
def basename(path) do
IO.puts "File.basename is deprecated, please use Path.basename instead"
Exception.print_stacktrace
FN.basename(path)
end
@doc false
def basename(path, extension) do
IO.puts "File.basename is deprecated, please use Path.basename instead"
Exception.print_stacktrace
FN.basename(path, extension)
end
@doc false
def dirname(path) do
IO.puts "File.dirname is deprecated, please use Path.dirname instead"
Exception.print_stacktrace
FN.dirname(path)
end
@doc false
def extname(path) do
IO.puts "File.extname is deprecated, please use Path.extname instead"
Exception.print_stacktrace
FN.extension(path)
end
@doc false
def rootname(path) do
IO.puts "File.rootname is deprecated, please use Path.rootname instead"
Exception.print_stacktrace
FN.rootname(path)
end
@doc false
def rootname(path, extension) do
IO.puts "File.rootname is deprecated, please use Path.rootname instead"
Exception.print_stacktrace
FN.rootname(path, extension)
end
@doc false
def join(paths) do
IO.puts "File.join is deprecated, please use Path.join instead"
Exception.print_stacktrace
FN.join(paths)
end
@doc false
def join(left, right) do
IO.puts "File.join is deprecated, please use Path.join instead"
Exception.print_stacktrace
FN.join(left, right)
end
@doc false
def split(path) do
IO.puts "File.split is deprecated, please use Path.split instead"
Exception.print_stacktrace
FN.split(path)
end
@doc false
def wildcard(glob) do
IO.puts "File.wildcard is deprecated, please use Path.wildcard instead"
Exception.print_stacktrace
Path.wildcard(glob)
end
@doc """
@@ -180,159 +229,6 @@ defmodule File do
match?({ :ok, _ }, F.read_file_info(path))
end
@doc """
Returns the last component of the path or the path
itself if it does not contain any directory separators.
If the given `path` is a char list, returns a char list.
Otherwise returns a binary.
## Examples
File.basename("foo")
#=> "foo"
File.basename("foo/bar")
#=> "bar"
File.basename("/")
#=> ""
"""
def basename(path) do
FN.basename(path)
end
@doc """
Returns the last component of `path` with the `extension`
stripped. This function should be used to remove a specific
extension which might, or might not, be there.
If the given `path` is a char list, returns a char list.
Otherwise returns a binary.
## Examples
File.basename("~/foo/bar.ex", ".ex")
#=> "bar"
File.basename("~/foo/bar.exs", ".ex")
#=> "bar.exs"
File.basename("~/foo/bar.old.ex", ".ex")
#=> "bar.old"
"""
def basename(path, extension) do
FN.basename(path, extension)
end
@doc """
Return the `directory` component of `path`.
If the given `path` is a char list, returns a char list.
Otherwise returns a binary.
## Examples
File.dirname("/foo/bar.ex")
#=> "foo"
"""
def dirname(path) do
FN.dirname(path)
end
@doc """
Return the `extension` of the last component of `path`.
If the given `path` is a char list, returns a char list.
Otherwise returns a binary.
## Examples
File.extname("foo.erl")
#=> ".erl"
File.extname("~/foo/bar")
#=> ""
"""
def extname(path) do
FN.extension(path)
end
@doc """
Returns the `path` with the `extension` stripped.
If the given `path` is a char list, returns a char list.
Otherwise returns a binary.
## Examples
File.rootname("/foo/bar")
#=> "/foo/bar"
File.rootname("/foo/bar.ex")
#=> "/foo/bar"
"""
def rootname(path) do
FN.rootname(path)
end
@doc """
Returns the `path` with the `extension` stripped. This function should be used to
remove a specific extension which might, or might not, be there.
If the given `path` is a char list, returns a char list.
Otherwise returns a binary.
## Examples
File.rootname("/foo/bar.erl", ".erl")
#=> "/foo/bar"
File.rootname("/foo/bar.erl", ".ex")
#=> "/foo/bar.erl"
"""
def rootname(path, extension) do
FN.rootname(path, extension)
end
@doc """
Returns a string with one or more paths components joint by the path separator.
This function should be used to convert a list of strings in a path.
If the given `paths` are a char list, returns a char list.
Otherwise returns a binary.
## Examples
File.join(["~", "foo"])
#=> "~/foo"
File.join(["foo"])
#=> "foo"
File.join(["/", "foo", "bar"])
#=> "/foo/bar"
"""
def join(paths) do
FN.join(paths)
end
@doc """
Join two paths.
If the given paths are a char list, returns a char list.
Otherwise returns a binary.
## Examples
File.join("foo", "bar")
#=> "foo/bar"
"""
def join(left, right) do
FN.join(left, right)
end
@doc """
Tries to create the directory `path`. Missing parent directories are not created.
Returns `:ok` if successful, or `{:error, reason}` if an error occurs.
@@ -372,7 +268,7 @@ defmodule File do
* :enotdir - A component of `path` is not a directory.
"""
def mkdir_p(path) do
FL.ensure_dir(join(path, "."))
FL.ensure_dir(FN.join(path, "."))
end
@doc """
@@ -419,66 +315,6 @@ defmodule File do
end
end
@doc """
Returns a list with the path splitted by the path separator.
If an empty string is given, then it returns the root path.
## Examples
File.split("")
#=> ["/"]
File.split("foo")
#=> ["foo"]
File.split("/foo/bar")
#=> ["/", "foo", "bar"]
"""
def split(path) do
FN.split(path)
end
@doc """
Traverses files and directories according to the given `glob` expression.
The wildcard string looks like an ordinary filename, except that certain
"wildcard characters" are interpreted in a special way. The following
characters are special:
* `?` - Matches one character.
* `*` - Matches any number of characters up to the end of
the filename, the next dot, or the next slash.
* `**` - Two adjacent <c>*</c>'s used as a single pattern will
match all files and zero or more directories and subdirectories.
* `[char1,char2,...]` - Matches any of the characters listed. Two characters
separated by a hyphen will match a range of characters.
* `{item1,item2,...}` - Matches one of the alternatives.
Other characters represent themselves. Only filenames that have exactly
the same character in the same position will match. Note that matching
is case-sensitive; i.e. "a" will not match "A".
## Examples
Imagine you have a directory called `projects` with three Elixir projects
inside of it: `elixir`, `exdoc` and `dynamo`. You can find all `.beam` files
inside their ebin directories all projects as follows:
File.wildcard("projects/*/ebin/**/*.beam")
If you want to search for both `.beam` and `.app` files, you could do:
File.wildcard("projects/*/ebin/**/*.{beam,app}")
"""
def wildcard(glob) when is_binary(glob) do
paths = :elixir_glob.wildcard binary_to_list(glob)
Enum.map paths, list_to_binary(&1)
end
def wildcard(glob) when is_list(glob) do
:elixir_glob.wildcard(glob)
end
@doc """
Returns information about the `path`. If it exists, it
returns a `{ :ok, info }` tuple, where info is as a
@@ -615,7 +451,7 @@ defmodule File do
output =
if dir?(destination) do
mkdir(destination)
join(destination, basename(source))
FN.join(destination, FN.basename(source))
else
destination
end
@@ -690,7 +526,7 @@ defmodule File do
output =
if dir?(destination) || dir?(source) do
mkdir(destination)
join(destination, basename(source))
FN.join(destination, FN.basename(source))
else
destination
end
@@ -726,7 +562,7 @@ defmodule File do
case mkdir(dest) do
success in [:ok, { :error, :eexist }] ->
Enum.reduce(files, [dest|acc], fn(x, acc) ->
do_cp_r(join(src, x), join(dest, x), callback, acc)
do_cp_r(FN.join(src, x), FN.join(dest, x), callback, acc)
end)
reason -> reason
end
@@ -834,7 +670,7 @@ defmodule File do
## Examples
File.rm('foo.txt')
File.rm('file.txt')
#=> :ok
File.rm('tmp_dir/')
@@ -865,7 +701,7 @@ defmodule File do
File.rddir('tmp_dir')
#=> :ok
File.rmdir('foo.txt')
File.rmdir('file.txt')
#=> {:error, :enotdir}
"""
@@ -911,7 +747,7 @@ defmodule File do
{ :ok, files } ->
res =
Enum.reduce files, entry, fn(file, tuple) ->
do_rm_rf(join(path, file), tuple)
do_rm_rf(FN.join(path, file), tuple)
end
case res do
@@ -1037,7 +873,7 @@ defmodule File do
## Examples
File.open!("foo.txt", [:read, :write], fn(file) ->
File.open!("file.txt", [:read, :write], fn(file) ->
IO.readline(file)
end)
@@ -1086,7 +922,7 @@ defmodule File do
"""
def cwd() do
case F.get_cwd do
{ :ok, cwd } -> { :ok, list_to_binary(cwd) }
{ :ok, cwd } -> { :ok, :unicode.characters_to_binary(cwd) }
{ :error, _ } = error -> error
end
end
@@ -1096,7 +932,7 @@ defmodule File do
"""
def cwd!() do
case F.get_cwd do
{ :ok, cwd } -> list_to_binary(cwd)
{ :ok, cwd } -> :unicode.characters_to_binary(cwd)
{ :error, reason } ->
raise File.Error, reason: reason, action: "get current working directory"
end
@@ -1147,6 +983,11 @@ defmodule File do
`close/1` might return an old write error and not even try to close the file.
See `open/2`.
"""
def close(io_device) when is_function(io_device) do
{ iterator, _ } = io_device.()
iterator.(:close)
end
def close(io_device) do
F.close(io_device)
end
@@ -1181,18 +1022,21 @@ defmodule File do
def iterator(device) do
fn ->
function = fn(_) ->
case :io.get_line(device, '') do
:eof ->
close(device)
:stop
{ :error, reason } ->
raise File.IteratorError, reason: reason
data ->
{ data, :ok }
end
function = fn
:ok ->
case :io.get_line(device, '') do
:eof ->
close(device)
:stop
{ :error, reason } ->
raise File.IteratorError, reason: reason
data ->
{ data, :ok }
end
:close ->
close(device)
end
{ function, function.(:start) }
{ function, :ok }
end
end
@@ -1213,7 +1057,7 @@ defmodule File do
cannot be opened.
"""
def iterator!(file, mode // []) do
open!(file, mode) /> iterator
open!(file, mode) |> iterator
end
@doc """
@@ -1229,18 +1073,21 @@ defmodule File do
def biniterator(device) do
fn ->
function = fn(_) ->
case :file.read_line(device) do
:eof ->
close(device)
:stop
{ :error, reason } ->
raise File.IteratorError, reason: reason
{ :ok, data } ->
{ data, :ok }
end
function = fn
:ok ->
case :file.read_line(device) do
:eof ->
close(device)
:stop
{ :error, reason } ->
raise File.IteratorError, reason: reason
{ :ok, data } ->
{ data, :ok }
end
:close ->
close(device)
end
{ function, function.(:start) }
{ function, :ok }
end
end
@@ -1261,30 +1108,11 @@ defmodule File do
cannot be opened.
"""
def biniterator!(file, mode // []) do
open!(file, mode) /> biniterator
open!(file, mode) |> biniterator
end
## Helpers
# Normalize the given path by removing "..".
defp normalize(path), do: normalize(split(path), [])
defp normalize([top|t], [_|acc]) when top in ["..", '..'] do
normalize t, acc
end
defp normalize([top|t], acc) when top in [".", '.'] do
normalize t, acc
end
defp normalize([h|t], acc) do
normalize t, [h|acc]
end
defp normalize([], acc) do
join Enum.reverse(acc)
end
defp open_defaults([:charlist|t], _add_binary) do
open_defaults(t, false)
end
+64 -22
View File
@@ -1,44 +1,86 @@
defmodule GenServer.Behaviour do
@moduledoc """
By using this module, you get default GenServer callbacks
for `init`, `handle_call`, `handle_info`, `handle_cast`,
`terminate` and `code_change`. Since these functions are
defined as overridable, they can be customized and fallback
to the default behaviour by calling `super`.
This module is a convenience to define GenServer callbacks in Elixir.
This module also tags the behavior as :gen_server. For more
information on gen_server, please refer to the Erlang
documentation:
http://www.erlang.org/doc/man/gen_server.html
http://www.erlang.org/doc/design_principles/gen_server_concepts.html
A server is responsible to react to messages received from client
and 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:
defmodule MyServer do
use GenServer.Behaviour
# Callbacks
def handle_call(:peek, _from, [h|_] = state) do
{ :reply, h, state }
def handle_call(:pop, _from, [h|t]) do
{ :reply, h, t }
end
# Default behaviour
def handle_call(request, from, config) do
super(request, from, config)
def handle_call(_request, _from, _config) do
# Call the default implementation from GenServer.Behaviour
super
end
def handle_cast({ :push, item }, state) do
{ :noreply, [item|state] }
def handle_cast({ :push, item }, config) do
{ :noreply, [item|config] }
end
# Default cast behaviour
def handle_cast(request, config) do
super(request, config)
def handle_cast(_request, _config) do
super
end
end
{ :ok, pid } = :gen_server.start_link(MyServer, [:hello], [])
:gen_server.call(pid, :pop)
#=> :hello
:gen_server.cast(pid, { :push, :world })
#=> :ok
:gen_server.call(pid, :pop)
#=> :world
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
`handle_call` or `handle_cast` can return:
{ :reply, reply, new_state }
{ :reply, reply, new_state, timeout }
{ :reply, reply, new_state, :hibernate }
{ :noreply, new_state }
{ :noreply, new_state, timeout }
{ :noreply, new_state, :hibernate }
{ :stop, reason, new_state }
{ :stop, reason, reply, new_state }
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:
* `init(args)` - invoked when the server is started;
* `handle_call(msg, from, state)` - invoked to handle call messages;
* `handle_cast(msg, state)` - invoked to handle cast messages;
* `handle_info(msg, state)` - handle all other messages which are
normally received by processes;
* `terminate(reason, state)` - called when the server is about to
terminate, useful for cleaning up;
* `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
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
"""
@doc false
@@ -51,7 +93,7 @@ defmodule GenServer.Behaviour do
end
def handle_call(_request, _from, state) do
{ :reply, :undef, state }
{ :noreply, state }
end
def handle_info(_msg, state) do
+451 -62
View File
@@ -1,97 +1,478 @@
defmodule HashDict do
@moduledoc """
This module implements a dictionary type based on
hashing of the keys. It is a simple wrapper around
[Erlang's dict module](http://www.erlang.org/doc/man/dict.html)
and exposed via the `Dict` module.
A key-value store.
Check the `Dict` module for examples and documentation.
The `HashDict` is meant to work well with both small and
large set of keys and it is an implementation of the `Dict`
behaviour. For more information about the functions and
their APIs, please consult the `Dict` module.
"""
use Dict.Common
@behaviour Dict
defmacrop dict(data) do
quote do
{ HashDict, unquote(data) }
# A dictionary (key-value) implementation based on dynamic hashing.
#
# This implementation is based on hash tries. We first start with
# a set of 8 buckets and expand when the density is about 5 entries
# per bucket. We use bit shifting to make rehashing faster on
# expansion.
#
# Compared to dict, it provides many enhancements:
#
# 1. HashDict buckets are ordered sets, this gives us faster access
# and modification times
#
# 2. It uses phash2 to calculate the hash (instead of phash)
#
# 3. The dictionary first starts with a single bucket, instead of
# a set of 8 buckets. This allow us to skip hashing altogher
# for small dictionaries, providing faster operations and
# reducing memory consumption
#
# 4. Once we reach 8 elements, the dictionary is promoted to a
# set of buckets
# The ordered record contains a single bucket
@ordered_threshold 8
defrecordp :ordered,
size: 0,
bucket: []
# The bucketed record contains a series of buckets.
@expand_load 5
@contract_load 2
@node_bitmap 0b111
@node_shift 3
@node_size 8
@node_template :erlang.make_tuple(@node_size, [])
defrecordp :trie,
size: 0,
depth: 0,
expand_on: @node_size * @expand_load,
contract_on: @contract_load,
root: @node_template
import Bitwise
# Let's inline common instructions
@compile { :inline, bucket_hash: 1, bucket_index: 1, bucket_nth_index: 2, bucket_next: 1 }
@doc """
Creates a new empty dict.
"""
def new do
ordered()
end
@doc """
Creates a new dict from the given enumerable.
## Examples
HashDict.new [{:b,1},{:a,2}]
#=> HashDict[a: 1, b: 2]
"""
def new(pairs) do
Enum.reduce pairs, ordered(), fn { k, v }, dict ->
put(dict, k, v)
end
end
@doc false
def keys(dict(data)) do
:dict.fetch_keys data
end
@doc """
Creates a new dict from the enumerable with the
help of the transformation function.
@doc false
def values(dict(data)) do
:dict.fold fn _key, value, acc ->
[value|acc]
end, [], data
end
## Examples
@doc false
def size(dict(data)) do
:dict.size data
end
HashDict.new ["a", "b"], fn x -> {x, x} end
#=> HashDict[{ "a", "a" }, { "b", "b" }]
@doc false
def has_key?(dict(data), key) do
:dict.is_key key, data
end
@doc false
def get(dict(data), key, default) do
case :dict.find(key, data) do
{:ok, value} -> value
:error -> default
"""
def new(list, transform) when is_function(transform) do
Enum.reduce list, new(), fn i, dict ->
{ k, v } = transform.(i)
put(dict, k, v)
end
end
@doc false
def get!(dict(data), key) do
case :dict.find(key, data) do
{:ok, value} -> value
:error -> raise(KeyError, key: key)
@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 } ->
dict
{ dict, 1 } ->
raise KeyError, key: key
end
end
@doc false
def put(dict(data), key, value) do
dict(:dict.store key, value, data)
@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 false
def delete(dict(data), key) do
dict(:dict.erase key, data)
@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
end
end
@doc false
def merge(dict(d1), dict(d2), fun) do
dict(:dict.merge fun, d1, d2)
@doc """
Gets the value under key from the dict,
raises KeyError if such key does not exist.
"""
def get!(dict, key) when is_tuple(dict) do
case dict_get(dict, key) do
{ ^key, value } -> value
false -> raise(KeyError, key: key)
end
end
@doc false
def merge(dict(_) = d1, d2, fun) do
merge(d1, new(d2), fun)
@doc """
Checks if the dict has the given key.
"""
def has_key?(dict, key) do
match? { ^key, _ }, dict_get(dict, key)
end
@doc false
def update(dict(data), key, fun) do
dict(:dict.update key, fun, data)
@doc """
Deletes a value from the dict.
"""
def delete(ordered(bucket: bucket, size: size) = dict, key) do
case bucket_delete(bucket, key) do
{ _, 0 } ->
dict
{ new_bucket, -1 } ->
ordered(dict, size: size - 1, bucket: new_bucket)
end
end
@doc false
def update(dict(data), key, initial, fun) do
dict(:dict.update key, fun, initial, data)
def delete(trie(root: root, size: size, depth: depth) = dict, key) do
pos = bucket_hash(key)
case node_delete(root, depth, pos, key) do
{ _, 0 } ->
dict
{ root, -1 } ->
res = if depth > 0 and trie(dict, :contract_on) == size do
root = node_contract(root, depth, depth - 1)
trie(dict,
root: root,
size: size - 1,
depth: depth - 1,
contract_on: div(size, @node_size),
expand_on: div(trie(dict, :expand_on), @node_size))
else
trie(dict, size: size - 1, root: root)
end
end
end
@doc false
@doc """
Returns the dict size.
"""
def size(dict) do
elem(dict, 1)
end
@doc """
Returns an empty dict.
"""
def empty(_) do
dict(:dict.new)
ordered()
end
@doc false
def to_list(dict(data)) do
:dict.to_list data
@doc """
Converts the dict to a list.
"""
def to_list(ordered(bucket: bucket)) do
bucket
end
def to_list(dict) do
dict_fold(dict, [], [&1|&2])
end
@doc """
Get all keys in the dict.
"""
def keys(dict) do
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)
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 ->
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 ->
update(acc, k, v2, callback.(k, &1, v2))
end
end
def merge(dict1, dict2, callback) when is_record(dict1, HashDict) do
Enum.reduce dict2, dict1, fn { k, v2 }, acc ->
update(acc, k, v2, callback.(k, &1, v2))
end
end
## Dict-wide functions
defp dict_get(ordered(bucket: bucket), key) do
:lists.keyfind(key, 1, bucket)
end
defp dict_get(trie(root: root, depth: depth), key) do
:lists.keyfind(key, 1, node_bucket(root, depth, bucket_hash(key)))
end
defp dict_fold(ordered(bucket: bucket), acc, fun) do
bucket_fold(bucket, acc, fun)
end
defp dict_fold(trie(root: root, depth: depth), acc, fun) do
node_fold(root, depth, acc, fun, @node_size)
end
defp dict_put(ordered(size: @ordered_threshold, bucket: bucket), key, value) do
root = node_relocate(bucket, 0)
dict_put(trie(size: @ordered_threshold, root: root), key, value)
end
defp dict_put(ordered(size: size, bucket: bucket) = dict, key, value) do
{ new, count } = bucket_put(bucket, key, value)
{ ordered(dict, size: size + count, bucket: new), count }
end
defp dict_put(trie(root: root, depth: depth, size: size, expand_on: size, contract_on: contract_on) = dict, key, value) do
root = node_expand(root, depth, depth + 1)
dict = trie(dict, root: root, depth: depth + 1,
expand_on: size * @node_size, contract_on: contract_on * @node_size)
dict_put(dict, key, value)
end
defp dict_put(trie(root: root, size: size, depth: depth) = dict, key, value) do
pos = bucket_hash(key)
{ root, count } = node_put(root, depth, pos, key, value)
{ trie(dict, size: size + count, root: root), count }
end
## Bucket helpers
# Puts a value in the bucket
defp bucket_put([{k,_}=e|bucket], key, { :put, value }) when key < k do
{ [{key,value},e|bucket], 1 }
end
defp bucket_put([{k,_}=e|bucket], key, { :update, initial, _fun }) when key < k do
{ [{key,initial},e|bucket], 1 }
end
defp bucket_put([{k,_}=e|bucket], key, value) when key > k do
{ rest, count } = bucket_put(bucket, key, value)
{ [e|rest], count }
end
defp bucket_put([{_,_}|bucket], key, { :put, value }) do
{ [{key,value}|bucket], 0 }
end
defp bucket_put([{_,value}|bucket], key, { :update, _initial, fun }) do
{ [{key,fun.(value)}|bucket], 0 }
end
defp bucket_put([], key, { :put, value }) do
{ [{key,value}], 1 }
end
defp bucket_put([], key, { :update, initial, _fun }) do
{ [{key,initial}], 1 }
end
# Puts a value in the bucket without returning
# the operation value
defp bucket_put!([{k,_}=e|bucket], key, value) when key < k, do: [{key,value},e|bucket]
defp bucket_put!([{k,_}=e|bucket], key, value) when key > k, do: [e|bucket_put!(bucket, key, value)]
defp bucket_put!([{_,_}|bucket], key, value), do: [{key,value}|bucket]
defp bucket_put!([], key, value), do: [{key,value}]
# Deletes a key from the bucket
defp bucket_delete([{k,_}|_] = bucket, key) when key < k do
{ bucket, 0 }
end
defp bucket_delete([{k,_}=e|bucket], key) when key > k do
{ rest, count } = bucket_delete(bucket, key)
{ [e|rest], count }
end
defp bucket_delete([{_,_}|bucket], _key) do
{ bucket, -1 }
end
defp bucket_delete([], _key) do
{ [], 0 }
end
# Folds the bucket
defp bucket_fold(bucket, acc, fun) do
:lists.foldl(fun, acc, bucket)
end
defp bucket_hash(key) do
:erlang.phash2(key)
end
defp bucket_index(hash) do
hash &&& @node_bitmap
end
defp bucket_nth_index(hash, n) do
(hash >>> (@node_shift * n)) &&& @node_bitmap
end
defp bucket_next(hash) do
hash >>> @node_shift
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
# Puts a key-value into a node
defp node_put(node, 0, hash, key, value) do
pos = bucket_index(hash)
{ new, count } = bucket_put(elem(node, pos), key, value)
{ setelem(node, pos, new), count }
end
defp node_put(node, depth, hash, key, value) do
pos = bucket_index(hash)
{ new, count } = node_put(elem(node, pos), depth - 1, bucket_next(hash), key, value)
{ setelem(node, pos, new), count }
end
# Deletes a key from the bucket
defp node_delete(node, 0, hash, key) do
pos = bucket_index(hash)
case bucket_delete(elem(node, pos), key) do
{ _, 0 } -> { node, 0 }
{ new, -1 } -> { setelem(node, pos, new), -1 }
end
end
defp node_delete(node, depth, hash, key) do
pos = bucket_index(hash)
case node_delete(elem(node, pos), depth - 1, bucket_next(hash), key) do
{ _, 0 } -> { node, 0 }
{ new, -1 } -> { setelem(node, pos, new), -1 }
end
end
# Folds a node recursively
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
# Node resizing
defp node_expand({ b1, b2, b3, b4, b5, b6, b7, b8 }, 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) }
end
defp node_expand({ b1, b2, b3, b4, b5, b6, b7, b8 }, 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) }
end
defp node_contract({ b1, b2, b3, b4, b5, b6, b7, b8 }, depth, n) when depth > 1 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) }
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)
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 node_relocate(node // @node_template, bucket, n) do
:lists.foldl fn { key, value }, acc ->
pos = key |> bucket_hash() |> bucket_nth_index(n)
setelem(acc, pos, bucket_put!(elem(acc, pos), key, value))
end, node, bucket
end
end
@@ -102,4 +483,12 @@ end
defimpl Access, for: HashDict do
def access(dict, key), do: HashDict.get(dict, key, nil)
end
end
defimpl Binary.Inspect, for: HashDict do
import Kernel, except: [inspect: 2]
def inspect(dict, opts) do
"#HashDict<" <> Binary.Inspect.inspect(HashDict.to_list(dict), opts) <> ">"
end
end
+1 -2
View File
@@ -120,8 +120,7 @@ defmodule IO do
"""
def puts(device // :stdio, item) do
erl_dev = map_dev(device)
:io.put_chars erl_dev, to_iodata(item)
:io.nl(erl_dev)
:io.put_chars erl_dev, [to_iodata(item), ?\n]
end
@doc """
+106 -53
View File
@@ -282,13 +282,6 @@ defmodule Kernel do
"""
defmacro left !== right
@doc """
When used inside quoting, marks that the variable should not
be hygienezed. Check `Kernel.SpecialForms.quote/1` for more
information.
"""
defmacro var!(var)
@doc """
Returns an integer or float which is the arithmetical absolute value of `number`.
@@ -1506,9 +1499,15 @@ defmodule Kernel do
opts = Keyword.merge(opts, do_block)
opts = Keyword.put(opts, :do, quote do
@moduledoc nil
unquote(Keyword.get opts, :do)
record_type message: binary
@doc false
def exception(args), do: new(args)
@doc false
def exception(args, self), do: update(args, self)
unquote(Keyword.get opts, :do)
end)
fields = [{ :__exception__, :__exception__ }|fields]
@@ -1573,6 +1572,23 @@ defmodule Kernel do
end
end
@doc """
Check if the given argument is a record.
"""
defmacro is_record(thing) do
case __CALLER__.in_guard? do
true ->
quote do
is_tuple(unquote(thing)) and is_atom(:erlang.element(1, unquote(thing)))
end
false ->
quote do
result = unquote(thing)
is_tuple(result) and is_atom(:erlang.element(1, result))
end
end
end
@doc """
Check if the given argument is a regex.
"""
@@ -1674,29 +1690,32 @@ defmodule Kernel do
## Protocols + Records
The real benefit of protocols comes when mixed with records. For instance,
imagine we have a module called `RedBlack` that provides an API to create
and manipulate Red-Black trees. This module represents such trees via a
record named `RedBlack.Tree` and we want this tree to be considered blank
in case it has no items. To achieve this, the developer just needs to
implement the protocol for `RedBlack.Tree`:
The real benefit of protocols comes when mixed with records.
For instance, imagine we have a module called `RedBlack` that
provides an API to create and manipulate Red-Black trees. This
module represents such trees via a record named `RedBlack.Tree`
and we want this tree to be considered blank in case it has no
items. To achieve this, the developer just needs to implement
the protocol for `RedBlack.Tree`:
defimpl Blank, for: RedBlack.Tree do
def blank?(tree), do: RedBlack.empty?(tree)
end
In the example above, we have implemented `blank?` for `RedBlack.Tree`
that simply delegates to `RedBlack.empty?` passing the tree as argument.
This implementation doesn't need to be defined inside the `RedBlack`
tree or inside the record, but anywhere in the code.
In the example above, we have implemented `blank?` for
`RedBlack.Tree` that simply delegates to `RedBlack.empty?` passing
the tree as argument. This implementation doesn't need to be defined
inside the `RedBlack` tree or inside the record, but anywhere in
the code.
Finally, since records are simply tuples, one can add a default protocol
implementation to any record by defining a default implementation for tuples.
Finally, since records are simply tuples, one can add a default
protocol implementation to any record by defining a default
implementation for tuples.
## Types
As in records, defining a protocol automatically defines a type named `t`,
which can be used as:
As in records, defining a protocol automatically defines a type
named `t`, which can be used as:
@spec present?(Blank.t) :: boolean
def present?(blank) do
@@ -1706,8 +1725,8 @@ defmodule Kernel do
The `@spec` above expresses that all types allowed to implement the
given protocol are valid argument types for the given function.
"""
defmacro defprotocol(name, [do: block]) do
Protocol.defprotocol(name, [do: block])
defmacro defprotocol(name, do: block) do
Protocol.defprotocol(name, do: block)
end
@doc """
@@ -1715,7 +1734,9 @@ defmodule Kernel do
`defprotocol/2` for examples.
"""
defmacro defimpl(name, opts, do_block // []) do
Protocol.defimpl(name, Keyword.merge(opts, do_block))
merged = Keyword.merge(opts, do_block)
merged = Keyword.put_new(merged, :for, __CALLER__.module)
Protocol.defimpl(name, merged)
end
@doc """
@@ -1777,7 +1798,7 @@ defmodule Kernel do
end
@doc """
Inspect the given arguments according to the Binary.Inspect protocol.
Inspect the given arguments according to the `Binary.Inspect` protocol.
## Options
@@ -1787,13 +1808,20 @@ defmodule Kernel do
always shown as tuples, defaults to false;
* :limit - the limit of items that are shown in tuples, bitstrings and
lists. Do not apply to strings;
lists. Does not apply to strings;
## Examples
inspect(:foo)
#=> ":foo"
Notice the inspect protocol does not necessarily return a valid Elixir
terms representation. In such cases, the inspected result must start
with `#`. For example, inspecting a function will return:
inspect &1 + &2
#=> #Function<...>
"""
defmacro inspect(arg, opts // []) do
quote do: Binary.Inspect.inspect(unquote(arg), unquote(opts))
@@ -2556,7 +2584,7 @@ defmodule Kernel do
"""
defmacro left <> right do
concats = extract_concatenations({ :<>, 0, [left, right] })
concats = extract_concatenations({ :<>, [], [left, right] })
quote do: << unquote_splicing(concats) >>
end
@@ -2573,7 +2601,7 @@ defmodule Kernel do
"""
defmacro first .. last do
{ :{}, 0, [Elixir.Range, first, last] }
{ :{}, [], [Elixir.Range, first, last] }
end
@doc """
@@ -2684,14 +2712,14 @@ defmodule Kernel do
end
@doc """
`/>` is called the pipeline operator as it is useful
`|>` is called the pipeline operator as it is useful
to write pipeline style expressions. This operator
tntroduces the expression on the left as the first
argument to the expression on the right.
## Examples
[1,[2],3] /> List.flatten /> Enum.map(&1 * 2)
[1,[2],3] |> List.flatten |> Enum.map(&1 * 2)
#=> [2,4,6]
The expression above is simply translated to:
@@ -2701,24 +2729,24 @@ defmodule Kernel do
Please be aware of operator precendence, when using
this operator. For example, the following expression:
String.graphemes "Hello" /> Enum.reverse
String.graphemes "Hello" |> Enum.reverse
Is translated to:
String.graphemes("Hello" /> Enum.reverse)
String.graphemes("Hello" |> Enum.reverse)
Which will result in an error as Enum.Iterator protocol
is not defined for binaries. Adding explicit parenthesis
is recommended:
String.graphemes("Hello") /> Enum.reverse
String.graphemes("Hello") |> Enum.reverse
"""
defmacro left /> right do
defmacro left |> right do
pipeline_op(left, right)
end
defp pipeline_op(left, { :/>, _, [middle, right] }) do
defp pipeline_op(left, { :|>, _, [middle, right] }) do
pipeline_op(pipeline_op(left, middle), right)
end
@@ -2731,18 +2759,26 @@ defmodule Kernel do
end
defp pipeline_op(left, atom) when is_atom(atom) do
{ { :., 0, [left, atom] }, 0, [] }
{ { :., [], [left, atom] }, [], [] }
end
defp pipeline_op(_, other) do
raise ArgumentError, message: "Unsupported expression in pipeline (:/>) operator: #{inspect other}"
raise ArgumentError, message: "Unsupported expression in pipeline |> operator: #{inspect other}"
end
defmacro left /> right do
IO.puts "The /> pipeline operator is deprecated, please use the |> operator instead"
Exception.print_stacktrace __ENV__.stacktrace
pipeline_op(left, right)
end
@doc """
Raises an error.
If the argument is a binary, it raises RuntimeError with the message.
If anything else, becomes a call to raise(argument, []).
If the argument is a binary, it raises `RuntimeError`
using the given argument as message.
If anything else, becomes a call to `raise(argument, [])`.
## Examples
@@ -2774,13 +2810,8 @@ defmodule Kernel do
structure.
Any module defined via `defexception` automatically
defines `exception(args)` that returns a new instance
of the record and a `exception(args, current)` that
updates the current exception.
Re-raising an exception will retrieve the previous
stacktrace so it keps the properties of the original
exception.
defines both `exception(args)` and `exception(args, current)`
that creates a new and updates the given exception.
## Examples
@@ -2788,15 +2819,37 @@ defmodule Kernel do
"""
@spec raise(tuple | atom, list) :: no_return
def raise(exception, args) when is_tuple(exception) and tuple_size(exception) > 1 and
:erlang.element(2, exception) == :__exception__ do
:erlang.raise(:error, exception.exception(args), :erlang.get_stacktrace)
end
def raise(exception, args) do
:erlang.error exception.exception(args)
end
@doc """
Re-raises an exception with the given stacktrace.
## Examples
try do
raise "Oops"
rescue
exception ->
stacktrace = System.stacktrace
if exception.message == "Oops" do
raise exception, [], stacktrace
end
end
Notice that `System.stacktrace` returns the stacktrace
of the last exception. That said, it is common to assign
the stacktrace as the first expression inside a `rescue`
clause as any other exception potentially raised (and
rescued) in between the rescue clause and the raise call
may change the `System.stacktrace` value.
"""
@spec raise(tuple | atom, list, list) :: no_return
def raise(exception, args, stacktrace) do
:erlang.raise :error, exception.exception(args), stacktrace
end
@doc """
Returns true if the `module` is loaded and contains a
public `function` with the given `arity`, otherwise false.
@@ -3127,7 +3180,7 @@ defmodule Kernel do
end
defp wrap_concatenation(other) do
{ :::, 0, [other, { :binary, 0, nil }] }
{ :::, [], [other, { :binary, [], nil }] }
end
# Builds cond clauses by nesting them recursively.
+56 -48
View File
@@ -8,23 +8,23 @@ defmodule Kernel.CLI do
# This is the API invoked by Elixir boot process.
@doc false
def main(options) do
{ config, argv } = process_argv(options, Kernel.CLI.Config.new)
def main(argv) do
argv = lc arg inlist argv, do: list_to_binary(arg)
:gen_server.call(:elixir_code_server, { :argv, argv })
{ config, argv } = process_argv(argv, Kernel.CLI.Config.new)
:elixir_code_server.cast({ :argv, argv })
run fn ->
Enum.map Enum.reverse(config.commands), process_command(&1, config)
:gen_server.cast(:elixir_code_server, :finished)
:elixir_code_server.cast(:finished)
end, config.halt
end
@doc """
Wait until the CLI finishes procesing options.
Wait until the CLI finishes processing options.
"""
def wait_until_finished do
case :gen_server.call(:elixir_code_server, { :wait_until_finished, self }) do
case :elixir_code_server.call({ :wait_until_finished, self }) do
:wait ->
receive do
{ :elixir_code_server, :finished } -> :ok
@@ -53,7 +53,7 @@ defmodule Kernel.CLI do
at_exit(1)
trace = System.stacktrace
IO.puts :stderr, "** (#{inspect exception.__record__(:name)}) #{exception.message}"
IO.puts Exception.formatted_stacktrace(trace)
IO.puts Exception.format_stacktrace(trace)
System.halt(1)
catch
:exit, reason when is_integer(reason) ->
@@ -66,7 +66,7 @@ defmodule Kernel.CLI do
at_exit(1)
trace = System.stacktrace
IO.puts :stderr, "** (#{kind}) #{inspect(reason)}"
IO.puts Exception.formatted_stacktrace(trace)
IO.puts Exception.format_stacktrace(trace)
System.halt(1)
end
end
@@ -74,7 +74,7 @@ defmodule Kernel.CLI do
## Private
defp at_exit(status) do
hooks = :gen_server.call(:elixir_code_server, :flush_at_exit)
hooks = :elixir_code_server.call(:flush_at_exit)
lc hook inlist hooks do
try do
@@ -83,12 +83,12 @@ defmodule Kernel.CLI do
exception ->
trace = System.stacktrace
IO.puts :stderr, "** (#{inspect exception.__record__(:name)}) #{exception.message}"
IO.puts Exception.formatted_stacktrace(trace)
IO.puts Exception.format_stacktrace(trace)
catch
kind, reason ->
trace = System.stacktrace
IO.puts :stderr, "** #{kind} #{inspect(reason)}"
IO.puts Exception.formatted_stacktrace(trace)
IO.puts Exception.format_stacktrace(trace)
end
end
@@ -98,7 +98,7 @@ defmodule Kernel.CLI do
end
defp invalid_option(option) do
IO.puts(:stderr, "Unknown option #{list_to_binary(option)}")
IO.puts(:stderr, "Unknown option #{option}")
System.halt(1)
end
@@ -113,43 +113,44 @@ defmodule Kernel.CLI do
# Process shared options
defp process_shared(['-v'|t], config) do
defp process_shared(["-v"|t], config) do
IO.puts "Elixir #{System.version}"
process_shared t, config
end
defp process_shared(['--no-halt'|t], config) do
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
defp process_shared(["-e",h|t], config) do
process_shared t, config.update_commands [{:eval,h}|&1]
end
defp process_shared(['-pa',h|t], config) do
Enum.each File.wildcard(File.expand_path(h)), Code.prepend_path(&1)
defp process_shared(["-pa",h|t], config) do
Enum.each Path.wildcard(Path.expand(h)), Code.prepend_path(&1)
process_shared t, config
end
defp process_shared(['-pz',h|t], config) do
Enum.each File.wildcard(File.expand_path(h)), Code.append_path(&1)
defp process_shared(["-pz",h|t], config) do
Enum.each Path.wildcard(Path.expand(h)), Code.append_path(&1)
process_shared t, config
end
defp process_shared(['-r',h|t], config) do
h = list_to_binary(h)
config = Enum.reduce File.wildcard(h), config, fn path, config ->
defp process_shared(["-r",h|t], config) do
process_shared t, Enum.reduce(Path.wildcard(h), config, fn path, config ->
config.update_commands [{:require,path}|&1]
end
process_shared t, config
end)
end
defp process_shared(['-pr',h|t], config) do
h = list_to_binary(h)
defp process_shared(["-pr",h|t], config) do
process_shared t, config.update_commands [{:parallel_require,h}|&1]
end
defp process_shared([erl,_|t], config) when erl in ['--erl', '--sname', '--remsh', '--name'] do
defp process_shared([erl,_|t], config) when erl in ["--erl", "--sname", "--remsh", "--name"] do
process_shared t, config
end
@@ -159,19 +160,18 @@ defmodule Kernel.CLI do
# Process init options
defp process_argv(['--'|t], config) do
defp process_argv(["--"|t], config) do
{ config, t }
end
defp process_argv(['--compile'|t], config) do
defp process_argv(["--compile"|t], config) do
process_compiler t, config
end
defp process_argv(['-S',h|t], config) do
defp process_argv(["-S",h|t], config) do
exec = System.find_executable(h)
if exec do
bin = list_to_binary(exec)
{ config.update_commands([{:require,bin}|&1]), t }
{ config.update_commands([{:require,exec}|&1]), t }
else
IO.puts(:stderr, "Could not find executable #{h}")
System.halt(1)
@@ -180,11 +180,10 @@ defmodule Kernel.CLI do
defp process_argv([h|t] = list, config) do
case h do
'-' ++ _ ->
"-" <> _ ->
shared_option? list, config, process_argv(&1, &2)
_ ->
bin = list_to_binary(h)
{ config.update_commands([{:require,bin}|&1]), t }
{ config.update_commands([{:require,h}|&1]), t }
end
end
@@ -194,32 +193,31 @@ defmodule Kernel.CLI do
# Process compiler options
defp process_compiler(['--'|t], config) do
defp process_compiler(["--"|t], config) do
{ config, t }
end
defp process_compiler(['-o',h|t], config) do
process_compiler t, config.output(list_to_binary(h))
defp process_compiler(["-o",h|t], config) do
process_compiler t, config.output(h)
end
defp process_compiler(['--no-docs'|t], config) do
defp process_compiler(["--no-docs"|t], config) do
process_compiler t, config.update_compiler_options([{:docs,false}|&1])
end
defp process_compiler(['--no-debug-info'|t], config) do
defp process_compiler(["--no-debug-info"|t], config) do
process_compiler t, config.update_compiler_options([{:debug_info,false}|&1])
end
defp process_compiler(['--ignore-module-conflict'|t], config) do
defp process_compiler(["--ignore-module-conflict"|t], config) do
process_compiler t, config.update_compiler_options([{:ignore_module_conflict,true}|&1])
end
defp process_compiler([h|t] = list, config) do
case h do
'-' ++ _ ->
"-" <> _ ->
shared_option? list, config, process_compiler(&1, &2)
_ ->
h = list_to_binary(h)
pattern = if File.dir?(h), do: "#{h}/**/*.ex", else: h
process_compiler t, config.update_compile [pattern|&1]
end
@@ -231,8 +229,18 @@ defmodule Kernel.CLI do
# Process commands
defp process_command({:eval, expr}, _config) when is_list(expr) do
:elixir.eval(expr, [])
defp process_command({:eval, expr}, _config) when is_binary(expr) do
Code.eval(expr, [])
end
defp process_command({:app, app}, _config) when is_binary(app) do
case Application.Behaviour.start(binary_to_atom(app)) do
{ :error, reason } ->
IO.puts(:stderr, "Could not start application #{app}: #{inspect reason}")
System.halt(1)
:ok ->
:ok
end
end
defp process_command({:require, file}, _config) when is_binary(file) do
@@ -240,7 +248,7 @@ defmodule Kernel.CLI do
end
defp process_command({:parallel_require, pattern}, _config) when is_binary(pattern) do
files = File.wildcard(pattern)
files = Path.wildcard(pattern)
files = Enum.uniq(files)
files = Enum.filter files, File.regular?(&1)
Kernel.ParallelRequire.files(files)
@@ -249,7 +257,7 @@ defmodule Kernel.CLI do
defp process_command({:compile, patterns}, config) do
File.mkdir_p(config.output)
files = Enum.map patterns, File.wildcard(&1)
files = Enum.map patterns, Path.wildcard(&1)
files = Enum.uniq(List.concat(files))
files = Enum.filter files, File.regular?(&1)
+16 -15
View File
@@ -32,17 +32,18 @@ defmodule Kernel.ParallelCompiler do
defp spawn_compilers(files, path, callback) do
Code.ensure_loaded(Kernel.ErrorHandler)
spawn_compilers(files, path, callback, [], [], [])
schedulers = :erlang.system_info(:schedulers_online)
spawn_compilers(files, path, callback, [], [], schedulers, [])
end
# We already have 4 currently running, don't spawn new ones
defp spawn_compilers(files, output, callback, waiting, queued, result) when
length(queued) - length(waiting) >= 4 do
wait_for_messages(files, output, callback, waiting, queued, result)
defp spawn_compilers(files, output, callback, waiting, queued, schedulers, result) when
length(queued) - length(waiting) >= schedulers do
wait_for_messages(files, output, callback, waiting, queued, schedulers, result)
end
# Spawn a compiler for each file in the list until we reach the limit
defp spawn_compilers([h|t], output, callback, waiting, queued, result) do
defp spawn_compilers([h|t], output, callback, waiting, queued, schedulers, result) do
parent = self()
child = spawn_link fn ->
@@ -63,25 +64,25 @@ defmodule Kernel.ParallelCompiler do
end
end
spawn_compilers(t, output, callback, waiting, [{child,h}|queued], result)
spawn_compilers(t, output, callback, waiting, [{child,h}|queued], schedulers, result)
end
# No more files, nothing waiting, queue is empty, we are done
defp spawn_compilers([], _output, _callback, [], [], result), do: result
defp spawn_compilers([], _output, _callback, [], [], _schedulers, result), do: result
# Queued x, waiting for x: POSSIBLE ERROR! Release processes so we get the failures
defp spawn_compilers([], output, callback, waiting, queued, result) when length(waiting) == length(queued) do
defp spawn_compilers([], output, callback, waiting, queued, schedulers, result) when length(waiting) == length(queued) do
Enum.each queued, fn { child, _ } -> child <- { :release, self() } end
wait_for_messages([], output, callback, waiting, queued, result)
wait_for_messages([], output, callback, waiting, queued, schedulers, result)
end
# No more files, but queue and waiting are not full or do not match
defp spawn_compilers([], output, callback, waiting, queued, result) do
wait_for_messages([], output, callback, waiting, queued, result)
defp spawn_compilers([], output, callback, waiting, queued, schedulers, result) do
wait_for_messages([], output, callback, waiting, queued, schedulers, result)
end
# Wait for messages from child processes
defp wait_for_messages(files, output, callback, waiting, queued, result) do
defp wait_for_messages(files, output, callback, waiting, queued, schedulers, result) do
receive do
{ :compiled, child, file } ->
callback.(file)
@@ -89,14 +90,14 @@ defmodule Kernel.ParallelCompiler do
# Sometimes we may have spurious entries in the waiting
# list because someone invoked try/rescue UndefinedFunctionError
new_waiting = List.keydelete(waiting, child, 0)
spawn_compilers(files, output, callback, new_waiting, new_queued, result)
spawn_compilers(files, output, callback, new_waiting, new_queued, schedulers, result)
{ :module_available, child, module, binary } ->
new_waiting = release_waiting_processes(module, waiting)
new_result = [{module, binary}|result]
wait_for_messages(files, output, callback, new_waiting, queued, new_result)
wait_for_messages(files, output, callback, new_waiting, queued, schedulers, new_result)
{ :waiting, child, on } ->
new_waiting = OrdDict.store(child, on, waiting)
spawn_compilers(files, output, callback, new_waiting, queued, result)
spawn_compilers(files, output, callback, new_waiting, queued, schedulers, result)
{ :failure, child, kind, reason, stacktrace } ->
extra = if match?({^child, module}, List.keyfind(waiting, child, 0)) do
" (undefined module #{inspect module})"
+31 -13
View File
@@ -12,37 +12,55 @@ defmodule Kernel.ParallelRequire do
can be optionally given as argument.
"""
def files(files, callback // default_callback) do
spawn_requires(files, [], callback, [])
schedulers = :erlang.system_info(:schedulers_online)
spawn_requires(files, [], callback, schedulers, [])
end
defp spawn_requires([], [], _callback, result), do: result
defp spawn_requires([], waiting, callback, result), do: wait_for_messages([], waiting, callback, result)
defp spawn_requires([], [], _callback, _schedulers, result), do: result
defp spawn_requires(files, waiting, callback, result) when length(waiting) >= 4 do
wait_for_messages(files, waiting, callback, result)
defp spawn_requires([], waiting, callback, schedulers, result) do
wait_for_messages([], waiting, callback, schedulers, result)
end
defp spawn_requires([h|t], waiting, callback, result) do
defp spawn_requires(files, waiting, callback, schedulers, result) when length(waiting) >= schedulers do
wait_for_messages(files, waiting, callback, schedulers, result)
end
defp spawn_requires([h|t], waiting, callback, schedulers, result) do
parent = self
child = spawn_link fn ->
compiler_pid = :erlang.get(:elixir_compiler_pid)
ensure_compiled = :erlang.get(:elixir_ensure_compiled)
{ :error_handler, handler } = :erlang.process_info(parent, :error_handler)
child = spawn_link fn ->
if compiler_pid != :undefined do
:erlang.put(:elixir_compiler_pid, compiler_pid)
end
if ensure_compiled != :undefined do
:erlang.put(:elixir_ensure_compiled, ensure_compiled)
end
:erlang.process_flag(:error_handler, handler)
try do
new = Code.require_file(h)
callback.(h)
parent <- { :required, self, new }
parent <- { :required, self, new, h }
catch
kind, reason ->
parent <- { :failure, self, kind, reason, System.stacktrace }
end
end
spawn_requires(t, [child|waiting], callback, result)
spawn_requires(t, [child|waiting], callback, schedulers, result)
end
defp wait_for_messages(files, waiting, callback, result) do
defp wait_for_messages(files, waiting, callback, schedulers, result) do
receive do
{ :required, child, new } ->
spawn_requires(files, List.delete(waiting, child), callback, (new || []) ++ result)
{ :required, child, new, file } ->
callback.(file)
spawn_requires(files, List.delete(waiting, child), callback, schedulers, (new || []) ++ result)
{ :failure, _child, kind, reason, stacktrace } ->
:erlang.raise(kind, reason, stacktrace)
end
+28 -16
View File
@@ -54,7 +54,8 @@ defmodule Kernel.RecordRewriter do
defp record_field_info(function) do
case atom_to_list(function) do
'update_' ++ field -> { :update, list_to_atom(field) }
'update' -> nil
'update_' ++ field -> nil
_ -> { :accessor, function }
end
end
@@ -62,13 +63,21 @@ defmodule Kernel.RecordRewriter do
defp optimize_call(line, { record, _ } = res, left, { :atom, _, function }, args) do
case record_fields(record) do
{ fields, optimizable } ->
if List.member?(optimizable, { function, length(args) + 1 }) do
{ kind, field } = record_field_info(function)
if index = Enum.find_index(fields, field == &1) do
optimize_call(line, res, kind, field, index, left, args)
opt_call =
if List.member?(optimizable, { function, length(args) + 1 }) do
case record_field_info(function) do
{ kind, field } ->
if index = Enum.find_index(fields, field == &1) do
optimize_record_accessor_call(line, res, kind, field, index, left, args)
end
nil ->
optimize_record_other_call(line, record, res, function, left, args)
end
end
end
nil -> nil
opt_call || optimize_record_other_call(line, record, nil, function, left, args)
nil ->
nil
end
end
@@ -76,7 +85,7 @@ defmodule Kernel.RecordRewriter do
nil
end
defp optimize_call(line, _res, :accessor, _field, index, left, []) do
defp optimize_record_accessor_call(line, _res, :accessor, _field, index, left, []) do
call = { :call, line,
{ :remote, line, { :atom, 0, :erlang }, { :atom, 0, :element } },
[{ :integer, 0, index + 2 }, left]
@@ -84,7 +93,7 @@ defmodule Kernel.RecordRewriter do
{ call, nil }
end
defp optimize_call(line, res, :accessor, _field, index, left, [arg]) do
defp optimize_record_accessor_call(line, res, :accessor, _field, index, left, [arg]) do
call = { :call, line,
{ :remote, line, { :atom, 0, :erlang }, { :atom, 0, :setelement } },
[{ :integer, 0, index + 2 }, left, arg]
@@ -92,10 +101,10 @@ defmodule Kernel.RecordRewriter do
{ call, res }
end
defp optimize_call(line, res, :update, field, _index, left, args) do
defp optimize_record_other_call(line, record, res, function, left, args) do
call = { :call, line,
{ :remote, line, left, { :atom, 0, :"update_#{field}" } },
args
{ :remote, line, { :atom, line, record }, { :atom, 0, function } },
args ++ [left]
}
{ call, res }
end
@@ -220,15 +229,18 @@ defmodule Kernel.RecordRewriter do
{ { :receive, line, clauses, after_key, after_value }, dict, res }
end
defp optimize_expr({ :try, line, body, [], clauses, try_after }, dict) do
tuples = lc clause inlist clauses, do: optimize_clause(clause, dict)
clauses = lc { clause, _, _ } inlist tuples, do: clause
defp optimize_expr({ :try, line, body, elses, catches, try_after }, dict) do
tuples = lc clause inlist catches, do: optimize_clause(clause, dict)
catches = lc { clause, _, _ } inlist tuples, do: clause
tuples = lc clause inlist elses, do: optimize_clause(clause, dict)
elses = lc { clause, _, _ } inlist tuples, do: clause
{ body, _, res } = optimize_body(body, dict, [])
res = join_result(tuples, res)
{ try_after, _, _ } = optimize_body(try_after, dict, [])
{ { :try, line, body, [], clauses, try_after }, dict, res }
{ { :try, line, body, elses, catches, try_after }, dict, res }
end
defp optimize_expr({ :fun, line, { :function, module, name, arity } }, dict) do
+310 -45
View File
@@ -1,8 +1,12 @@
defmodule Kernel.SpecialForms do
@moduledoc """
In this module we define Elixir special forms. Those are called
special forms because they cannot be overridden by the developer
and sometimes have lexical scope (like `alias`, `import`, etc).
In this module we define Elixir special forms. Special forms
cannot be overriden by the developer and are the basic
building blocks of Elixir code.
Some of those forms are lexical (like `alias`, `import`, etc).
The macros `{}`, `[]` and `<<>>` are also special forms used
to define data structures, respectively tuples, lists and binaries.
This module also documents Elixir's pseudo variables (`__MODULE__`,
`__FILE__`, `__ENV__` and `__CALLER__`). Pseudo variables return
@@ -22,6 +26,7 @@ defmodule Kernel.SpecialForms do
:{}.(1,2,3)
{ 1, 2, 3 }
"""
defmacro :{}.(args)
@@ -32,6 +37,7 @@ defmodule Kernel.SpecialForms do
:[].(1,2,3)
[ 1, 2, 3 ]
"""
defmacro :[].(args)
@@ -42,6 +48,123 @@ defmodule Kernel.SpecialForms do
:<<>>.(1,2,3)
<< 1, 2, 3 >>
## Bitstring types
A bitstring may contain many parts and those may have
specific types. Most of the time, Elixir will figure out
the part's type and won't require any work from you:
<<102, "oo">>
#=> "foo"
Above we have two parts: the first is an integer and the
second is a binary. If we use any other Elixir expression,
Elixir can no longer guess the type:
rest = "oo"
<<102, rest>>
#=> ** (ArgumentError) argument error
When a variable or expression is given as a binary part,
Elixir defaults the type of that part to an unsigned
little-endian integer. In the example above, since we haven't
specified a type, Elixir expected an integer but we passed a
binary, resulting in `ArgumentError`. We can solve this by
explicitly tagging it as a binary:
<<102, rest :: binary>>
The type can be integer, float, binary, bytes, bitstring,
bits, utf8, utf16 or utf32, e.g.:
<<102 :: float, rest :: binary>>
Integer can be any arbitrary precision integer. A float is an
IEEE 754 binary32 or binary64 floating point number. A bitstring
is an arbitrary series of bits. A binary is a special case of
bitstring that has a total size divisible by 8.
The utf8, utf16, and utf32 types are for UTF code points.
The bits type is an alias for bitstring. The bytes type is an
alias for binary.
The signedness can also be given as signed or unsigned. The
signedness only matters for matching. If unspecified, it
defaults to unsigned. Example:
<<-100 :: signed, rest :: binary>> = <<-100, "foo">>
#=> <<156,102,111,111>>
This match would have failed if we did not specify that the
value -100 is signed. If we're matching into a variable instead
of a value, the signedness won't be checked; rather, the number
will simply be interpreted as having the given (or implied)
signedness, e.g.:
<<val, rest :: binary>> = <<-100, "foo">>
val
#=> 156
Here, `val` is interpreted as unsigned.
Signedness is only relevant on integers.
The endianness of a part can be big, little or native (the
latter meaning it will be resolved at VM load time). Passing
many options can be done by giving a list:
<<102 :: [integer, native], rest :: binary>>
Or:
<<102 :: [unsigned, big, integer], rest :: binary>>
And so on.
Endianness only makes sense for integers and some UTF code
point types (utf16 and utf32).
Finally, we can also specify size and unit for each part. The
unit is multiplied by the size to give the effective size of
the part:
<<102, rest :: [size(2), unit(8)]>> = "foo"
"foo"
<<102, rest :: size(16)>> = "foo"
"foo"
<<102, rest :: size(32)>> = "foo"
** (MatchError) no match of right hand side value: "foo"
In the example above, the first two expressions matches
because the string "foo" takes 24 bits and we are matching
against a part of 24 bits as well, 8 of which are taken by
the integer 102 and the remaining 16 bits are specified on
the rest. On the last example, we expect a rest with size 32,
which won't match.
Size and unit are not applicable to utf8, utf16, and utf32.
The default size for integers is 8. For floats, it is 64. For
binaries, it is the size of the binary. Only the last binary
in a binary match can use the default size (all others must
have their size specified explicitly). Bitstrings do not have
a default size.
Size can also be specified using a syntax shortcut. Instead of
writing `size(8)`, one can write just `8` and it will be interpreted
as `size(8)`
<< 1 :: 3 >> == << 1 :: size(3) >> #=> true
The default unit for integers, floats, and bitstrings is 1. For
binaries, it is 8.
For floats, unit * size must result in 32 or 64, corresponding
to binary32 and binary64, respectively.
"""
defmacro :<<>>.(args)
@@ -209,13 +332,13 @@ defmodule Kernel.SpecialForms do
## Examples
quote do: sum(1, 2, 3)
#=> { :sum, 0, [1, 2, 3] }
#=> { :sum, [], [1, 2, 3] }
## Homoiconicity
## Explanation
Elixir is an homoiconic language. Any Elixir program can be
represented using its own data structures. The building block
of Elixir homoiconicity is a tuple with three elements, for example:
Any Elixir code can be represented using Elixir data structures.
The building block of Elixir homoiconicity is a tuple with three
elements, for example:
{ :sum, 1, [1, 2, 3] }
@@ -227,8 +350,20 @@ defmodule Kernel.SpecialForms do
* The second element of the tuple is always an integer
representing the line number;
* The third element of the tuple are the arguments for the
function call. The third argument may be an atom, meaning
that it may be a variable.
function call. The third argument may be an atom, which is
usually a variable (or a local call);
## Options
* `:hygiene` - When false, disables hygiene for variables, aliases and imports;
* `:unquote` - When false, disables unquoting. Useful when you have a quote
inside another quote and want to control which quote is
able to unquote;
* `:location` - When set to `:keep`, keeps the current line and file on quotes.
Read the Stacktrace information section below for more information;
* `:expand_aliases` - When false, do not expand aliases;
* `:expand_imports` - When false, do not expand imports;
* `:var_context` - The context for quoted variables. Defaults to the current module;
## Macro literals
@@ -244,9 +379,21 @@ defmodule Kernel.SpecialForms do
## Hygiene
Elixir macros are hygienic regarding to variables. This means
a variable defined in a macro cannot affect the scope where
the macro is included. Consider the following example:
Elixir macros are hygienic. This means aliases and imports
defined inside the quoted often refer to the context that
defined the macro.
Furthermore, variables inside quote are also hygienic. That
said, a variable defined in a macro cannot affect the scope
where the macro is included.
The option `hygiene` can turn off all the hygiene mechanisms
defined below. However, they can also be changed in a one by
one basis.
### Hygiene in variables
Consider the following example:
defmodule Hygiene do
defmacro no_interference do
@@ -261,10 +408,10 @@ defmodule Kernel.SpecialForms do
a #=> 10
In the example above, `a` returns 10 even if the macro
is apparently setting it to 1 because the variables defined
in the macro does not affect the context the macro is
executed. If you want to set or get a variable, you can do
it with the help of the `var!` macro:
is apparently setting it to 1 because variables defined
in the macro does not affect the context the macro is executed.
If you want to set or get a variable in the user context, you
can do it with the help of the `var!` macro:
defmodule NoHygiene do
defmacro interference do
@@ -278,29 +425,125 @@ defmodule Kernel.SpecialForms do
NoHygiene.interference
a #=> 1
Notice that aliases are not hygienic in Elixir, ambiguity
must be solved by prepending Elixir:
It is important to understand that quoted variables are scoped
to the module they are defined. That said, even if two modules
define the same quoted variable `a`, their values are going
to be independent:
quote do
Elixir.Foo #=> Access the root Foo
Foo #=> Access the Foo alias in the current module
(if any is set), then fallback to Elixir.Foo
defmodule Hygiene1 do
defmacro var1 do
quote do: a = 1
end
end
## Options
defmodule Hygiene2 do
defmacro var2 do
quote do: a = 2
end
end
* `:hygiene` - When false, disables hygiene;
* `:unquote` - When false, disables unquoting. Useful when you have a quote
inside another quote and want to control which quote is able to unquote;
* `:location` - When set to `:keep`, keeps the current line and file on quotes.
Read the Stacktrace information section below for more information;
Calling macros `var1` and `var2` are not going to change their
each other values for `a`. This is useful because quoted
variables from different modules cannot conflict. If you desire
to explicitly access a variable from another module, we can once
again use `var!` macro, but explicitly passing a second argument:
# Access the variable a from Hygiene1
quote do: var!(a, Hygiene1) = 2
Another option is to set the `var_context` option, affecting
all variables in the block:
quote var_context: Hygiene1, do: a = 2
### Hygiene in aliases
Aliases inside quote are expanded by default.
Consider the following example:
defmodule Hygiene do
alias HashDict, as: D
defmacro no_interference do
quote do: D.new
end
end
require Hygiene
Hygiene.no_interference #=> HashDict[]
Notice that, even though the alias `D` is not available
in the context the macro is expanded, the code above works
because `D` was expanded when the quote was generated.
There are two ways to disable this behaviour. By giving
the `expand_aliases` equals to false to quote or by using
the `alias!` macro inside the quote:
defmodule NoHygiene do
defmacro interference do
quote do: alias!(D).new
end
end
require NoHygiene
NoHygiene.interference #=> UndefinedFunctionError
## Hygiene in imports
Similar to aliases, imports in Elixir hygienic. Consider the
following code:
defmodule Hygiene do
defmacrop get_size do
quote do
size("hello")
end
end
def return_size do
import Kernel, except: [size: 1]
get_size
end
end
Hygiene.return_size #=> 5
Notice how `return_size` returns 5 even though the `size/1`
function is not imported.
Elixir is smart enough to delay the resolution to the latest
moment possible. So, if you call `size("hello")` inside quote,
but no `size/1` function is available, it is then expanded on
the caller:
defmodule Lazy do
defmacrop get_size do
import Kernel, except: [size: 1]
quote do
size([a: 1, b: 2])
end
end
def return_size do
import Kernel, except: [size: 1]
import Dict, only: [size: 1]
get_size
end
end
Lazy.return_size #=> 2
As in aliases, imports expansion can be explicitly disabled
via the `expand_imports` option.
## Stacktrace information
One of Elixir goals is to provide proper stacktrace whenever there is an
exception. In order to work properly with macros, the default behavior
in quote is to set the line to 0. When a macro is invoked and the quoted
expressions is expanded, 0 is replaced by the line of the call site.
in quote is to not set a line. When a macro is invoked and the quoted
expressions is expanded, the call site line is inserted.
This is a good behavior for the majority of the cases, except if the macro
is defining new functions. Consider this example:
@@ -313,8 +556,8 @@ defmodule Kernel.SpecialForms do
However, if there is an exception in any of these functions, we want
the stacktrace to point to the `GenServer.Behaviour` and not the line
that calls `use GenServer.Behaviour`. For this reason, there is an
option called `:location` that when set to `:keep` keeps these proper
semantics:
option called `:location` that when set to `:keep` keeps the original
line and file lines instead of setting them to 0:
quote location: :keep do
def handle_call(request, _from, state) do
@@ -324,11 +567,33 @@ defmodule Kernel.SpecialForms do
It is important to warn though that `location: :keep` evaluates the
code as if it was defined inside `GenServer.Behaviour` file, in
particular, the macro `__FILE__` will always point to
`GenServer.Behaviour` file.
particular, the macro `__FILE__` and exceptions happening inside
the quote will always point to `GenServer.Behaviour` file.
"""
defmacro quote(opts, do: contents)
@doc """
When used inside quoting, marks that the variable should
not be hygienized. The argument can be either a variable
node (i.e. a tuple with three elements where the last
one is an atom) or an atom representing the variable name.
Check `quote/2` for more information.
"""
defmacro var!(var)
@doc """
Defines a variable in the given context.
Check `quote/2` for more information.
"""
defmacro var!(var, context)
@doc """
When used inside quoting, marks that the alias should not
be hygienezed. This means the alias will be expanded when
the macro is expanded.
"""
defmacro alias!(alias)
@doc """
Unquotes the given expression from inside a macro.
@@ -343,13 +608,13 @@ defmodule Kernel.SpecialForms do
Which would then return:
{ :sum, 0, [1, { :value, 0, quoted }, 3] }
{ :sum, [], [1, { :value, [], quoted }, 3] }
Which is not the expected result. For this, we use unquote:
value = 13
quote do: sum(1, unquote(value), 3)
#=> { :sum, 0, [1, 13, 3] }
#=> { :sum, [], [1, 13, 3] }
"""
defmacro unquote(expr)
@@ -362,7 +627,7 @@ defmodule Kernel.SpecialForms do
values = [2,3,4]
quote do: sum(1, unquote_splicing(values), 5)
#=> { :sum, 0, [1, 2, 3, 4, 5] }
#=> { :sum, [], [1, 2, 3, 4, 5] }
"""
defmacro unquote_splicing(expr)
@@ -428,7 +693,7 @@ defmodule Kernel.SpecialForms do
and should not be invoked directly:
quote do: (1; 2; 3)
#=> { :__block__, 0, [1,2,3] }
#=> { :__block__, [], [1,2,3] }
"""
defmacro __block__(args)
@@ -451,17 +716,17 @@ defmodule Kernel.SpecialForms do
It is usually compiled to an atom:
quote do: Foo.Bar
{ :__aliases__, 0, [:Foo,:Bar] }
{ :__aliases__, [], [:Foo,:Bar] }
Elixir represents `Foo.Bar` as `__aliases__` so calls can be
unambiguously identified by the operator `:.`. For example:
quote do: Foo.bar
{{:.,0,[{:__aliases__,0,[:Foo]},:bar]},0,[]}
{{:.,[],[{:__aliases__,[],[:Foo]},:bar]},[],[]}
Whenever an expression iterator sees a `:.` as the tuple key,
it can be sure that it represents a call and the second element
of the arguments list is an atom.
it can be sure that it represents a call and the second argument
is the list is an atom.
On the other hand, aliases holds some properties:
@@ -474,7 +739,7 @@ defmodule Kernel.SpecialForms do
4) When the head element of aliases is not an atom, it is expanded at runtime:
quote do: some_var.Foo
{:__aliases__,0,[{:some_var,0,:quoted},:Bar]}
{:__aliases__,[],[{:some_var,[],:quoted},:Bar]}
Since `some_var` is not available at compilation time, the compiler
expands such expression to:
@@ -483,4 +748,4 @@ defmodule Kernel.SpecialForms do
"""
defmacro __aliases__(args)
end
end
+97 -100
View File
@@ -57,13 +57,13 @@ defmodule Kernel.Typespec do
(... -> any)
Function with arity of zero:
(fun() -> type)
or
(() -> type)
Function with some arity:
(fun(type, type) -> type)
or
((type, type) -> type)
@@ -73,7 +73,7 @@ defmodule Kernel.Typespec do
## Notes
Elixir discourages the use of type `string()` as it might be confused
with binaries which are referred to as "strings" in Elixir (as opposed to
with binaries which are referred to as "strings" in Elixir (as opposed to
character lists). In order to use the type that is called `string()` in Erlang,
one has to use the `char_list()` type which is a synonym to `string()`. If yu
use `string()`, you'll get a warning from the compiler.
@@ -96,7 +96,7 @@ defmodule Kernel.Typespec do
"""
defmacro deftype(type) do
quote do
Kernel.Typespec.deftype(:type, (quote line: :keep, do: unquote(type)), __ENV__)
Kernel.Typespec.deftype(:type, unquote(Macro.escape type), __ENV__)
end
end
@@ -111,7 +111,7 @@ defmodule Kernel.Typespec do
"""
defmacro defopaque(type) do
quote do
Kernel.Typespec.deftype(:opaque, (quote line: :keep, do: unquote(type)), __ENV__)
Kernel.Typespec.deftype(:opaque, unquote(Macro.escape type), __ENV__)
end
end
@@ -126,15 +126,7 @@ defmodule Kernel.Typespec do
"""
defmacro deftypep(type) do
quote do
Kernel.Typespec.deftype(:typep, (quote line: :keep, do: unquote(type)), __ENV__)
end
end
@doc false
defmacro defspec(spec, [do: block]) do
IO.write "[WARNING] @spec f(...), do: type is deprecated, use @spec f(...) :: type\n#{Exception.env_stacktrace(__CALLER__)}"
quote do
Kernel.Typespec.defspec(:spec, (quote line: :keep, do: unquote(spec) :: unquote(block)), __ENV__)
Kernel.Typespec.deftype(:typep, unquote(Macro.escape type), __ENV__)
end
end
@@ -149,15 +141,7 @@ defmodule Kernel.Typespec do
"""
defmacro defspec(spec) do
quote do
Kernel.Typespec.defspec(:spec, (quote line: :keep, do: unquote spec), __ENV__)
end
end
@doc false
defmacro defcallback(spec, [do: block]) do
IO.write "[WARNING] @callback f(...), do: type is deprecated, use @callback f(...) :: type\n#{Exception.env_stacktrace(__CALLER__)}"
quote do
Kernel.Typespec.defspec(:callback, (quote line: :keep, do: unquote(spec) :: unquote(block)), __ENV__)
Kernel.Typespec.defspec(:spec, unquote(Macro.escape spec), __ENV__)
end
end
@@ -172,7 +156,7 @@ defmodule Kernel.Typespec do
"""
defmacro defcallback(spec) do
quote do
Kernel.Typespec.defspec(:callback, (quote line: :keep, do: unquote(spec)), __ENV__)
Kernel.Typespec.defspec(:callback, unquote(Macro.escape spec), __ENV__)
end
end
@@ -243,23 +227,23 @@ defmodule Kernel.Typespec 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, [{ name, line, args }, typespec_to_ast(result)] }
{ :::, [line: line], [{ name, [line: line], args }, typespec_to_ast(result)] }
end
def spec_to_ast(name, { :type, line, :fun, [] }) do
{ :::, line, [{ name, line, [] }, quote(do: term)] }
{ :::, [line: line], [{ name, [line: line], [] }, quote(do: term)] }
end
def spec_to_ast(name, { :type, line, :bounded_fun, [{ :type, _, :fun, [{ :type, _, :product, args }, result] }, constraints] }) do
[h|t] =
lc {:type, line, :constraint, [{:atom, _, :is_subtype}, [var, type]]} inlist constraints do
{ :is_subtype, line, [typespec_to_ast(var), typespec_to_ast(type)] }
{ :is_subtype, [line: line], [typespec_to_ast(var), typespec_to_ast(type)] }
end
args = lc arg inlist args, do: typespec_to_ast(arg)
guards = Enum.reduce t, h, fn(x, acc) -> { :and, line, [acc, x] } end
{ :::, line, [{ :when, line, [{ name, line, args }, guards] }, typespec_to_ast(result)] }
{ :::, [line: line], [{ :when, [line: line], [{ name, [line: line], args }, guards] }, typespec_to_ast(result)] }
end
@doc """
@@ -268,7 +252,7 @@ defmodule Kernel.Typespec do
def type_to_ast({ { :record, record }, fields, args }) when is_atom(record) do
fields = lc field inlist fields, do: typespec_to_ast(field)
args = lc arg inlist args, do: typespec_to_ast(arg)
type = { :{}, 0, [record|fields] }
type = { :{}, [], [record|fields] }
quote do: unquote(record)(unquote_splicing(args)) :: unquote(type)
end
@@ -367,7 +351,7 @@ defmodule Kernel.Typespec do
end
def deftype(kind, type, caller) do
do_deftype(kind, type, { :term, caller.line, nil }, caller)
do_deftype(kind, type, { :term, [line: caller.line], nil }, caller)
end
defp do_deftype(kind, { name, _, args }, definition, caller) do
@@ -388,25 +372,26 @@ defmodule Kernel.Typespec do
end
@doc false
def defspec(type, {:::, _, [{ :when, _, [{ name, line, args }, constraints_guard] }, return] }, caller) do
def defspec(type, {:::, _, [{ :when, _, [{ name, meta, args }, constraints_guard] }, return] }, caller) do
if is_atom(args), do: args = []
constraints = guard_to_constraints(constraints_guard, caller)
spec = { :type, line, :fun, fn_args(line, args, return, Keyword.keys(constraints), caller) }
spec = { :type, line, :bounded_fun, [spec, Keyword.values(constraints)] }
spec = { :type, line(meta), :fun, fn_args(meta, args, return, Keyword.keys(constraints), caller) }
spec = { :type, line(meta), :bounded_fun, [spec, Keyword.values(constraints)] }
code = { { name, Kernel.length(args) }, spec }
Module.compile_typespec(caller.module, type, code)
code
end
def defspec(type, {:::, _, [{ name, line, args }, return]}, caller) do
def defspec(type, {:::, _, [{ name, meta, args }, return]}, caller) do
if is_atom(args), do: args = []
spec = { :type, line, :fun, fn_args(line, args, return, [], caller) }
spec = { :type, line(meta), :fun, fn_args(meta, args, return, [], caller) }
code = { { name, Kernel.length(args) }, spec }
Module.compile_typespec(caller.module, type, code)
code
end
defp guard_to_constraints({ :is_subtype, line, [{ name, _, _ }, type] }, caller) do
defp guard_to_constraints({ :is_subtype, meta, [{ name, _, _ }, type] }, caller) do
line = line(meta)
contraints = [{ :atom, line, :is_subtype }, [{:var, line, name}, typespec(type, [], caller)]]
[{ name, { :type, line, :constraint, contraints } }]
end
@@ -423,7 +408,7 @@ defmodule Kernel.Typespec do
defp typespec_to_ast({ :type, line, :tuple, args }) do
args = lc arg inlist args, do: typespec_to_ast(arg)
{ :{}, line, args }
{ :{}, [line: line], args }
end
defp typespec_to_ast({ :type, line, :binary, [arg1, arg2] }) do
@@ -441,31 +426,35 @@ defmodule Kernel.Typespec do
defp typespec_to_ast({ :type, line, :union, args }) do
args = lc arg inlist args, do: typespec_to_ast(arg)
Enum.reduce tl(args), hd(args),
fn(arg, expr) -> { :|, line, [expr, arg] } end
fn(arg, expr) -> { :|, [line: line], [expr, arg] } end
end
defp typespec_to_ast({ :type, line, :fun, [{:type, _, :product, args},result] }) do
args = lc arg inlist args, do: typespec_to_ast(arg)
{ :"->", line, [{args, typespec_to_ast(result)}] }
{ :"->", [line: line], [{args, typespec_to_ast(result)}] }
end
defp typespec_to_ast({ :type, line, :fun, [args, result] }) do
{ :"->", line, [{[typespec_to_ast(args)], typespec_to_ast(result)}] }
{ :"->", [line: line], [{[typespec_to_ast(args)], 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, []} ] })
end
defp typespec_to_ast({ :type, line, :range, [left, right] }) do
{ :"..", [line: line], [typespec_to_ast(left), typespec_to_ast(right)] }
end
defp typespec_to_ast({ :type, line, name, args }) do
args = lc arg inlist args, do: typespec_to_ast(arg)
{ name, line, args }
{ name, [line: line], args }
end
defp typespec_to_ast({ :var, line, var }) do
var =
case atom_to_binary(var) do
<<"_", c :: [binary, size(1)], rest :: binary>> ->
<<"_", 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}")
@@ -480,15 +469,15 @@ defmodule Kernel.Typespec do
defp typespec_to_ast({ :remote_type, line, [mod, name, args] }) do
args = lc arg inlist args, do: typespec_to_ast(arg)
dot = { :., line, [typespec_to_ast(mod), typespec_to_ast(name)] }
{ dot, line, args }
dot = { :., [line: line], [typespec_to_ast(mod), typespec_to_ast(name)] }
{ dot, [line: line], args }
end
defp typespec_to_ast({ :ann_type, line, [var, type] }) do
{ :::, line, [typespec_to_ast(var), typespec_to_ast(type)] }
{ :::, [line: line], [typespec_to_ast(var), typespec_to_ast(type)] }
end
defp typespec_to_ast({ :typed_record_field,
defp typespec_to_ast({ :typed_record_field,
{ :record_field, line, { :atom, line1, name }},
type }) do
typespec_to_ast({ :ann_type, line, [{ :var, line1, name }, type] })
@@ -510,29 +499,36 @@ defmodule Kernel.Typespec do
## From AST conversion
defp line(meta) do
case :lists.keyfind(:line, 1, meta) do
{ :line, line } -> line
false -> 0
end
end
# Handle unions
defp typespec({ :|, line, [_,_] } = exprs, vars, caller) do
defp typespec({ :|, meta, [_,_] } = exprs, vars, caller) do
exprs = :lists.reverse(collect_union(exprs))
union = lc e inlist exprs, do: typespec(e, vars, caller)
{ :type, line, :union, union }
{ :type, line(meta), :union, union }
end
# Handle binaries
defp typespec({:<<>>, line, []}, _,_) do
{:type, line, :binary, [{:integer, line, 0}, {:integer, line, 0}]}
defp typespec({:<<>>, meta, []}, _,_) do
{:type, line(meta), :binary, [{:integer, line(meta), 0}, {:integer, line(meta), 0}]}
end
defp typespec({:<<>>, line, [{:::, _, [{:_, line1, atom}, {:*, _, [{:_, line2, atom}, unit]}]}]}, _, _) when is_atom(atom) do
{:type, line, :binary, [{:integer, line1, 0}, {:integer, line2, unit}]}
defp typespec({:<<>>, meta, [{:::, _, [{:_, meta1, atom}, {:*, _, [{:_, meta2, atom}, unit]}]}]}, _, _) when is_atom(atom) do
{:type, line(meta), :binary, [{:integer, line(meta1), 0}, {:integer, line(meta2), unit}]}
end
defp typespec({:<<>>, line, [{:::, line1, [{:_, line2, atom}, base]}]}, _, _) when is_atom(atom) do
{:type, line, :binary, [{:integer, line1, base}, {:integer, line2, 0}]}
defp typespec({:<<>>, meta, [{:::, meta1, [{:_, meta2, atom}, base]}]}, _, _) when is_atom(atom) do
{:type, line(meta), :binary, [{:integer, line(meta1), base}, {:integer, line(meta2), 0}]}
end
# Handle ranges
defp typespec({:"..", line, args}, vars, caller) do
typespec({:range, line, args}, vars, caller)
defp typespec({:"..", meta, args}, vars, caller) do
typespec({:range, meta, args}, vars, caller)
end
# Handle special forms
@@ -546,82 +542,79 @@ defmodule Kernel.Typespec do
end
# Handle funs
defp typespec({:fun, line, args}, vars, caller) when is_list(args) do
IO.write "[WARNING] fun() type is deprecated, use (... -> type) instead\n#{Exception.env_stacktrace(caller)}"
typespec({:"->", line, [{args, quote do: any}]}, vars, caller)
defp typespec({:->, meta, [{[{:fun, _, arguments}], return}]}, vars, caller) when is_list(arguments) do
typespec({:->, meta, [{arguments, return}]}, vars, caller)
end
defp typespec({:"->", line, [{[{:fun, _, arguments}], return}]}, vars, caller) when is_list(arguments) do
typespec({:"->", line, [{arguments, return}]}, vars, caller)
end
defp typespec({:"->", line, [{arguments, return}]}, vars, caller) when is_list(arguments) do
args = fn_args(line, arguments, return, vars, caller)
{ :type, line, :fun, args }
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
# Handle type operator
defp typespec({:"::", line, [var, expr] }, vars, caller) do
defp typespec({:"::", meta, [var, expr] }, vars, caller) do
left = typespec(var, [elem(var, 0)|vars], caller)
right = typespec(expr, vars, caller)
{ :ann_type, line, [left, right] }
{ :ann_type, line(meta), [left, right] }
end
# Handle unary ops
defp typespec({op, line, [integer]}, _, _) when op in [:+, :-] and is_integer(integer) do
{ :op, line, op, {:integer, line, integer} }
defp typespec({op, meta, [integer]}, _, _) when op in [:+, :-] and is_integer(integer) do
{ :op, line(meta), op, {:integer, line(meta), integer} }
end
# Handle access macro
defp typespec({{:., line, [Kernel, :access]}, line1, [target, args]}, vars, caller) do
access = {{:., line, [Kernel, :access]}, line1,
defp typespec({{:., meta, [Kernel, :access]}, meta1, [target, args]}, vars, caller) do
access = {{:., meta, [Kernel, :access]}, meta1,
[target, args ++ [_: (quote hygiene: false, do: any)]]}
typespec(Macro.expand(access, caller), vars, caller)
end
# Handle remote calls
defp typespec({{:., line, [remote, name]}, _, args}, vars, caller) do
defp typespec({{:., meta, [remote, name]}, _, args}, vars, caller) do
remote = Macro.expand remote, caller
unless is_atom(remote), do: raise ArgumentError, message: "invalid remote in typespec"
remote_type({typespec(remote, vars, caller), line, typespec(name, vars, caller), args}, vars, caller)
remote_type({typespec(remote, vars, caller), meta, typespec(name, vars, caller), args}, vars, caller)
end
# Handle tuples
defp typespec({:tuple, line, atom}, vars, caller) when is_atom(atom) do
typespec({:{}, line, []}, vars, caller)
defp typespec({:tuple, meta, atom}, vars, caller) when is_atom(atom) do
typespec({:{}, meta, []}, vars, caller)
end
defp typespec({:{}, line, []}, _, _) do
{ :type, line, :tuple, :any }
defp typespec({:{}, meta, []}, _, _) do
{ :type, line(meta), :tuple, :any }
end
defp typespec({:{}, line, t}, vars, caller) when is_list(t) do
defp typespec({:{}, meta, t}, vars, caller) when is_list(t) do
args = lc e inlist t, do: typespec(e, vars, caller)
{ :type, line, :tuple, args }
{ :type, line(meta), :tuple, args }
end
# Handle variables or local calls
defp typespec({name, line, atom}, vars, caller) when is_atom(atom) do
defp typespec({name, meta, atom}, vars, caller) when is_atom(atom) do
if List.member?(vars, name) do
{ :var, line, name }
{ :var, line(meta), name }
else
typespec({name, line, []}, vars, caller)
typespec({name, meta, []}, vars, caller)
end
end
# Handle local calls
defp typespec({:string, line, arguments}, vars, caller) do
defp typespec({:string, meta, arguments}, vars, caller) do
IO.write "warning: string() type use is discouraged. For character lists, use " <>
"char_list() type, for strings, String.t()\n#{Exception.env_stacktrace(caller)}"
"char_list() type, for strings, String.t()\n#{Exception.format_stacktrace(caller.stacktrace)}"
arguments = lc arg inlist arguments, do: typespec(arg, vars, caller)
{ :type, line, :string, arguments }
{ :type, line(meta), :string, arguments }
end
defp typespec({:char_list, _line, arguments}, vars, caller) do
defp typespec({:char_list, _meta, arguments}, vars, caller) do
typespec((quote do: :elixir.char_list(unquote_splicing(arguments))), vars, caller)
end
defp typespec({name, line, arguments}, vars, caller) do
defp typespec({name, meta, arguments}, vars, caller) do
arguments = lc arg inlist arguments, do: typespec(arg, vars, caller)
{ :type, line, name, arguments }
{ :type, line(meta), name, arguments }
end
# Handle literals
@@ -634,11 +627,15 @@ defmodule Kernel.Typespec do
end
defp typespec([], vars, caller) do
typespec({ nil, 0, [] }, vars, caller)
typespec({ nil, [], [] }, vars, caller)
end
defp typespec([spec], vars, caller) do
typespec({ :list, 0, [spec] }, vars, caller)
typespec({ :list, [], [spec] }, vars, caller)
end
defp typespec([spec, {:"...", _, quoted}], vars, caller) when is_atom(quoted) do
typespec({ :nonempty_list, [], [spec] }, vars, caller)
end
defp typespec(l, _, _) when is_list(l) do
@@ -652,31 +649,31 @@ defmodule Kernel.Typespec do
## Helpers
defp remote_type({remote, line, name, arguments}, vars, caller) do
defp remote_type({remote, meta, name, arguments}, vars, caller) do
arguments = lc arg inlist arguments, do: typespec(arg, vars, caller)
{ :remote_type, line, [ remote, name, arguments ] }
{ :remote_type, line(meta), [ remote, name, arguments ] }
end
defp collect_union({ :|, _, [a, b] }), do: [b|collect_union(a)]
defp collect_union(v), do: [v]
defp fn_args(line, args, return, vars, caller) do
case [fn_args(line, args, vars, caller), typespec(return, vars, caller)] do
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,[]}] -> []
x -> x
end
end
defp fn_args(line, [{:"...", _, _}], _vars, _caller) do
{ :type, line, :any }
defp fn_args(meta, [{:"...", _, _}], _vars, _caller) do
{ :type, line(meta), :any }
end
defp fn_args(line, args, vars, caller) do
defp fn_args(meta, args, vars, caller) do
args = lc arg inlist args, do: typespec(arg, vars, caller)
{ :type, line, :product, args }
{ :type, line(meta), :product, args }
end
defp variable({name, line, _}) do
{:var, line, name}
defp variable({name, meta, _}) do
{:var, line(meta), name}
end
end
+19 -1
View File
@@ -191,7 +191,7 @@ defmodule Keyword do
end
@doc """
Sets the given `value` under `key`.
Puts the given `value` under `key`.
If a previous value is already stored, all entries are
removed and the value is overriden.
@@ -207,6 +207,24 @@ defmodule Keyword do
[{key, value}|delete(keywords, key)]
end
@doc """
Puts the given `value` under `key` unless the entry `key`
already exists.
## Examples
Keyword.put_new [a: 1, b: 2], :a, 3
#=> [a: 1, b: 2]
"""
@spec put_new(t, key, value) :: t
def put_new(keywords, key, value) when is_atom(key) do
case :lists.keyfind(key, 1, keywords) do
{ ^key, _ } -> keywords
false -> [{key,value}|keywords]
end
end
@doc """
Checks if two keywords are equal. I.e. they contain
the same keys and those keys contain the same values.
+1 -59
View File
@@ -252,51 +252,6 @@ defmodule List do
:lists.keydelete(key, position + 1, list)
end
@doc false
def range(first, last, step // nil)
def range(first, last, step) when is_integer(first) and is_integer(last) and first <= last do
IO.write "[WARNING] List.range is deprecated, please use ranges instead\n#{Exception.formatted_stacktrace}"
case step do
nil ->
:lists.seq(first, last, 1)
x when x < 0 ->
[]
_ ->
:lists.seq(first, last, step)
end
end
def range(first, last, step) when is_integer(first) and is_integer(last) and first > last do
IO.write "[WARNING] List.range is deprecated, please use ranges instead\n#{Exception.formatted_stacktrace}"
case step do
nil ->
:lists.seq(first, last, -1)
x when x > 0 ->
[]
_ ->
:lists.seq(first, last, step)
end
end
@doc false
def sort(list) do
IO.write "[WARNING] List.sort is deprecated, please use Enum.sort instead\n#{Exception.formatted_stacktrace}"
:lists.sort list
end
@doc false
def sort(list, fun) do
IO.write "[WARNING] List.sort is deprecated, please use Enum.sort instead\n#{Exception.formatted_stacktrace}"
:lists.sort fun, list
end
@doc false
def uniq(list) when is_list(list) do
IO.write "[WARNING] List.uniq is deprecated, please use Enum.uniq instead\n#{Exception.formatted_stacktrace}"
do_uniq(list, [])
end
@doc """
Wraps the argument in a list.
If the argument is already a list, returns the list.
@@ -353,20 +308,7 @@ defmodule List do
:lists.map tuple_to_list(&1), zip(list)
end
## Private
# uniq
defp do_uniq([h|t], acc) do
case :lists.member(h, acc) do
true -> do_uniq(t, acc)
false -> [h|do_uniq(t, [h|acc])]
end
end
defp do_uniq([], _acc) do
[]
end
## Helpers
# zip
+155
View File
@@ -0,0 +1,155 @@
defmodule List.Dict do
@moduledoc """
A Dict implementation that works on lists of two-items tuples.
For more information about the functions and their APIs, please
consult the `Dict` module.
"""
@doc """
Returns a new `List.Dict`, i.e. an empty list.
"""
def new, do: []
@doc """
Creates a new `List.Dict` from the given pairs.
"""
def new(pairs) do
Enum.map pairs, fn({ k, v }) -> { k, v } end
end
@doc """
Creates a new `List.Dict` from the given pairs
via the given transformation function.
"""
def new(list, transform) when is_function(transform) 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
false -> default
end
end
@doc """
Returns the value under the given key
raises `KeyError` if the key does not exist.
"""
def get!(dict, key) do
case :lists.keyfind(key, 1, dict) do
{ ^key, value } -> value
false -> raise(KeyError, key: key)
end
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]
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
Enum.reduce dict2, dict1, fn { k, v2 }, acc ->
update(acc, k, v2, fn(v1) -> fun.(k, v1, v2) end)
end
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
def update([{_, _} = e|dict], key, fun) do
[e|update(dict, key, fun)]
end
def update([], key, _fun) 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
def update([{_, _} = e|dict], key, initial, fun) do
[e|update(dict, key, initial, fun)]
end
def update([], key, initial, _fun) do
[{key, initial}]
end
@doc """
Returns an empty `List.Dict`.
"""
def empty(_dict), do: []
@doc """
Converts the dict to a list.
"""
def to_list(dict), do: dict
end
+20 -36
View File
@@ -13,7 +13,7 @@ defmodule Macro do
[
:===, :!==,
:==, :!=, :<=, :>=,
:&&, :||, :<>, :++, :--, :**, ://, :::, :<-, :.., :/>, :=~,
:&&, :||, :<>, :++, :--, :**, ://, :::, :<-, :.., :|>, :=~,
:<, :>,
:+, :-, :*, :/, :=, :|, :.,
:and, :or, :xor, :when, :in, :inlist, :inbits,
@@ -62,14 +62,14 @@ defmodule Macro do
#=> :foo
Macro.escape({ :a, :b, :c })
#=> { :{}, 0, [:a, :b, :c] }
#=> { :{}, [], [:a, :b, :c] }
"""
def escape({ left, right }) do
{ escape(left), escape(right) }
end
def escape(tuple) when is_tuple(tuple) do
{ :{}, 0, escape(tuple_to_list(tuple)) }
{ :{}, [], escape(tuple_to_list(tuple)) }
end
def escape(list) when is_list(list) do
@@ -84,9 +84,9 @@ defmodule Macro do
Check `unescape_binary/2` for information on how to customize
the escaping map.
In this setup, Elixir will escape the following: `\b`, `\d`,
`\e`, `\f`, `\n`, `\r`, `\s`, `\t` and `\v`. Octals are also
escaped according to the latin1 set they represent.
In this setup, Elixir will escape the following: `\a`, `\b`,
`\d`, `\e`, `\f`, `\n`, `\r`, `\s`, `\t` and `\v`. Octals are
also escaped according to the latin1 set they represent.
This function is commonly used on sigil implementations
(like `%r`, `%b` and others).
@@ -114,6 +114,7 @@ defmodule Macro do
representing the number of the characters it wants to unescape.
Here is the default mapping function implemented by Elixir:
def unescape_map(?a), do: ?\a
def unescape_map(?b), do: ?\b
def unescape_map(?d), do: ?\d
def unescape_map(?e), do: ?\e
@@ -265,12 +266,12 @@ defmodule Macro do
# Two-item tuples
def to_binary({ left, right }) do
to_binary({ :{}, 0, [left, right] })
to_binary({ :{}, [], [left, right] })
end
# Lists
def to_binary(list) when is_list(list) do
to_binary({ :[], 0, list })
to_binary({ :[], [], list })
end
# All other structures
@@ -407,7 +408,7 @@ defmodule Macro do
The compilation will fail because `My.Module` when quoted
is not an atom, but a syntax tree as follow:
{:__aliases__, 0, [:My, :Module] }
{:__aliases__, [], [:My, :Module] }
That said, we need to expand the aliases node above to an
atom, so we can retrieve its length. Expanding the node is
@@ -441,28 +442,16 @@ defmodule Macro do
"""
def expand(aliases, env)
# The first case we handle is __aliases__. In case
# aliases just contain one item, we are sure it is
# an atom, so we just expand it based on the aliases
# dict.
def expand({ :__aliases__, _, [h] }, env) when h != Elixir do
expand_alias(h, env)
end
def expand({ :__aliases__, _, _ } = original, env) do
case :elixir_aliases.expand(original, env.aliases) do
atom when is_atom(atom) -> atom
aliases ->
aliases = lc alias inlist aliases, do: expand(alias, env)
# In case aliases contains more than one item, we need
# to loop them checking if they are all atoms or not.
# Macros and pseudo-variables are then expanded.
def expand({ :__aliases__, _, [h|t] } = original, env) do
aliases = case h do
x when is_atom(x) and x != Elixir -> [expand_alias(x, env)|t]
_ -> [h|t]
end
aliases = lc alias inlist aliases, do: expand(alias, env)
case :lists.all(is_atom(&1), aliases) do
true -> :elixir_aliases.concat(aliases)
false -> original
case :lists.all(is_atom(&1), aliases) do
true -> :elixir_aliases.concat(aliases)
false -> original
end
end
end
@@ -523,11 +512,6 @@ defmodule Macro do
:lists.any(match?({ :&, _, [_] }, &1), args)
end
defp expand_alias(h, env) do
atom = list_to_atom('Elixir-' ++ atom_to_list(h))
:elixir_aliases.lookup(atom, env.aliases)
end
@doc """
Recurs the quoted expression checking if all sub terms are
safe (i.e. they represented data structured and don't actually
@@ -550,4 +534,4 @@ defmodule Macro do
def do_safe_term(terms) when is_list(terms), do: Enum.find_value(terms, do_safe_term(&1))
def do_safe_term(terms) when is_tuple(terms), do: { :unsafe, terms }
def do_safe_term(_), do: nil
end
end
+37 -54
View File
@@ -1,4 +1,26 @@
defmodule Macro.Env do
@moduledoc """
A record that holds compile time environment information.
The current environment can be accessed at any time as
`__ENV__`. Inside macros, the caller environment can be
accessed as `__CALLER__`. It contains the following fields:
* `module` - the current module name.
* `file` - the current file name as a binary
* `line` - the current line as an integer
* `function` - a tuple as `{ atom, integer` }, where the first
element is the function name and the seconds its arity. Returns
`nil` if not inside a function
* `aliases` - a list of two item tuples, where the first
item is the aliased name and the second the actual name
* `context` - the context of the environment. It can be nil
(default context), inside a guard or inside an assign
* `requires` - the list of required modules
* `functions` - a list of functions imported from each module
* `macros` - a list of macros imported from each module
"""
@type name_arity :: { atom, non_neg_integer }
@type file :: binary
@type line :: non_neg_integer
@@ -18,60 +40,6 @@ defmodule Macro.Env do
Record.deffunctions(fields, __MODULE__)
Record.deftypes(fields, types, __MODULE__)
@moduledoc """
A record that contains compile time environment information,
It can be accessed at any time by calling __ENV__.
"""
@doc """
Returns the current module name.
"""
def module(record)
@doc """
Returns the current file name as a binary.
"""
def file(record)
@doc """
Returns the current line as an integer.
"""
def line(record)
@doc """
Returns a tuple as { Atom, Integer }, where the first element
is the function name and the seconds its arity. Returns `nil`
if not inside a function.
"""
def function(record)
@doc """
Returns a list of two item tuples, where the first
item is the aliased name and the second the actual name.
"""
def aliases(record)
@doc """
Returns the context of the environment. It can be nil
(default context), inside a guard or inside an assign.
"""
def context(record)
@doc """
Returns the list of required modules.
"""
def requires(record)
@doc """
Returns a list of functions imported from each module.
"""
def functions(record)
@doc """
Returns a list of macros imported from each module.
"""
def macros(record)
@doc """
Returns a keyword list containing the file and line
information as keys.
@@ -91,4 +59,19 @@ defmodule Macro.Env do
inside a match clause.
"""
def in_match?(record), do: context(record) == :assign
@doc """
Returns the environment stacktrace.
"""
def stacktrace(record) do
cond do
nil?(record.module) ->
[{ :elixir_compiler, :__FILE__, 2, location(record) }]
nil?(record.function) ->
[{ module(record), :__MODULE__, 0, location(record) }]
true ->
{ name, arity } = record.function
[{ module(record), name, arity, location(record) }]
end
end
end
+1 -1
View File
@@ -584,7 +584,7 @@ defmodule Module do
atom
end
defp normalize_attribute(:file, env) when is_env(env), do: { binary_to_list(env.file), env.line}
defp normalize_attribute(:file, env) when is_env(env), do: { binary_to_list(env.file), env.line }
defp normalize_attribute(:file, { binary, line }) when is_binary(binary), do: { binary_to_list(binary), line }
defp normalize_attribute(:file, other) when not is_tuple(other), do: normalize_attribute(:file, { other, 1 })
+18 -2
View File
@@ -3,9 +3,12 @@ defmodule Node do
Functions related to Erlang nodes.
"""
@type t :: atom
@doc """
Returns the current node. It returns the same as the built-in node().
"""
@spec self :: t
def self do
:erlang.node()
end
@@ -14,6 +17,7 @@ defmodule Node do
Returns true if the local node is alive; that is, if the node can be
part of a distributed system. Otherwise, it returns false.
"""
@spec alive? :: boolean
def alive? do
:erlang.is_alive()
end
@@ -22,6 +26,7 @@ defmodule Node do
Returns a list of all visible nodes in the system, excluding
the local node. Same as list(visible).
"""
@spec list :: [t]
def list do
:erlang.nodes()
end
@@ -30,9 +35,11 @@ defmodule Node do
Returns a list of nodes according to argument given. The result
returned when the argument is a list, is the list of nodes
satisfying the disjunction(s) of the list elements.
See http://www.erlang.org/doc/man/erlang.html#nodes-1 for more info.
"""
@typep list_arg :: :visible | :hidden | :connected | :this | :known
@spec list(list_arg | [list_arg]) :: [t]
def list(args) do
:erlang.nodes(args)
end
@@ -43,6 +50,7 @@ defmodule Node do
See http://www.erlang.org/doc/man/erlang.html#monitor_node-2 for more info.
"""
@spec monitor(t, boolean) :: true
def monitor(node, flag) do
:erlang.monitor_node(node, flag)
end
@@ -53,6 +61,7 @@ defmodule Node do
See http://www.erlang.org/doc/man/erlang.html#monitor_node-3 for more info.
"""
@spec monitor(t, boolean, [:allow_passive_connect]) :: true
def monitor(node, flag, options) do
:erlang.monitor_node(node, flag, options)
end
@@ -62,9 +71,10 @@ defmodule Node do
the local node has crashed. This BIF is mainly used in the Erlang network
authentication protocols. Returns true if disconnection succeeds, otherwise
false. If the local node is not alive, the function returns ignored.
See http://www.erlang.org/doc/man/erlang.html#disconnect_node-1 for more info.
"""
@spec disconnect(t) :: boolean | :ginored
def disconnect(node) do
:erlang.disconnect_node(node)
end
@@ -76,6 +86,7 @@ defmodule Node do
Check http://www.erlang.org/doc/man/erlang.html#spawn-2 for
the list of available options.
"""
@spec spawn(t, (() -> any)) :: pid
def spawn(node, fun) do
:erlang.spawn(node, fun)
end
@@ -87,6 +98,7 @@ defmodule Node do
Check http://www.erlang.org/doc/man/erlang.html#spawn_opt-3 for
the list of available options.
"""
@spec spawn(t, (() -> any), Process.spawn_opts) :: pid | {pid, reference}
def spawn(node, fun, opts) do
:erlang.spawn_opt(node, fun, opts)
end
@@ -99,6 +111,7 @@ defmodule Node do
Check http://www.erlang.org/doc/man/erlang.html#spawn-4 for
the list of available options.
"""
@spec spawn(t, module, atom, [any]) :: pid
def spawn(node, module, fun, args) do
:erlang.spawn(node, module, fun, args)
end
@@ -111,6 +124,7 @@ defmodule Node do
Check http://www.erlang.org/doc/man/erlang.html#spawn_opt-5 for
the list of available options.
"""
@spec spawn(t, module, atom, [any], Process.spawn_opts) :: pid | {pid, reference}
def spawn(node, module, fun, args, opts) do
:erlang.spawn_opt(node, module, fun, args, opts)
end
@@ -122,6 +136,7 @@ defmodule Node do
(and due to the link, an exit signal with exit reason :noconnection will be
received).
"""
@spec spawn_link(t, (() -> any)) :: pid
def spawn_link(node, fun) do
:erlang.spawn_link(node, fun)
end
@@ -133,6 +148,7 @@ defmodule Node do
pid is returned (and due to the link, an exit signal with exit reason
:noconnection will be received).
"""
@spec spawn_link(t, module, atom, [any]) :: pid
def spawn_link(node, module, fun, args) do
:erlang.spawn_link(node, module, fun, args)
end
+49 -35
View File
@@ -21,30 +21,33 @@ defmodule OptionParser do
OptionParser.parse(["-d"], aliases: [d: :debug])
#=> { [debug: true], [] }
## Flags
## Switches
A set of flags can be given as argument too. Those are considered
boolean and never consume the next value unless it is a boolean:
Extra information about switches can be given as argument too. This is useful
in order to say a switch must behave as a boolean, list, etc. The following
types are supported:
OptionParser.parse(["--unlock path/to/file"], flags: [:unlock])
* `:boolean` - They never consume the next value unless it is true/false;
Examples:
OptionParser.parse(["--unlock path/to/file"], switches: [unlock: :boolean])
#=> { [unlock: true], ["path/to/file"] }
OptionParser.parse(["--unlock false path/to/file"], flags: [:unlock])
OptionParser.parse(["--unlock false path/to/file"], switches: [unlock: :boolean])
#=> { [unlock: false], ["path/to/file"] }
## Negation switches
Any switches starting with `--no-` are always considered to be
booleans and never parse the next value.
booleans and never parse the next value:
OptionParser.parse(["--no-op path/to/file"])
#=> { [no_op: true], ["path/to/file"] }
"""
def parse(argv, opts // []) when is_list(argv) and is_list(opts) do
aliases = opts[:aliases] || []
flags = opts[:flags] || []
parse(argv, aliases, flags, true)
parse(argv, opts, true)
end
@doc """
@@ -60,46 +63,55 @@ defmodule OptionParser do
"""
def parse_head(argv, opts // []) when is_list(argv) and is_list(opts) do
aliases = opts[:aliases] || []
flags = opts[:flags] || []
parse(argv, aliases, flags, false)
parse(argv, opts, false)
end
## Helpers
defp parse(argv, aliases, flags, all) do
parse(argv, aliases, flags, [], [], all)
defp parse(argv, opts, bool) do
if flags = opts[:flags] do
IO.write "OptionParser option :flags is deprecated, please use :switches instead\n#{Exception.format_stacktrace}"
opts = Keyword.put(opts, :switches, lc(k inlist flags, do: { k, :boolean }))
end
aliases = opts[:aliases] || []
switches = opts[:switches] || []
parse(argv, aliases, switches, bool)
end
defp parse(["-" <> option|t], aliases, flags, dict, args, all) do
defp parse(argv, aliases, switches, all) do
parse(argv, aliases, switches, [], [], all)
end
defp parse(["-" <> option|t], aliases, switches, dict, args, all) do
{ option, value } = normalize_option(option, aliases)
if value == nil do
{ value, t } = if is_flag?(flags, option) do
flag_from_tail(t)
else
value_from_tail(t)
end
{ value, t } =
case switch_type(switches, option) do
:boolean -> boolean_from_tail(t)
_ -> value_from_tail(t)
end
end
dict = store_option dict, option, value
parse(t, aliases, flags, dict, args, all)
parse(t, aliases, switches, dict, args, all)
end
defp parse([], _, flags, dict, args, true) do
{ reverse_dict(dict, flags), Enum.reverse(args) }
defp parse([h|t], aliases, switches, dict, args, true) do
parse(t, aliases, switches, dict, [h|args], true)
end
defp parse([h|t], aliases, flags, dict, args, true) do
parse(t, aliases, flags, dict, [h|args], true)
defp parse([], _, switches, dict, args, true) do
{ reverse_dict(dict, switches), Enum.reverse(args) }
end
defp parse(value, _, flags, dict, _args, false) do
{ reverse_dict(dict, flags), value }
defp parse(value, _, switches, dict, _args, false) do
{ reverse_dict(dict, switches), value }
end
defp flag_from_tail([h|t]) when h in ["false", "true"], do: { h, t }
defp flag_from_tail(t) , do: { true, t }
defp boolean_from_tail([h|t]) when h in ["false", "true"], do: { h, t }
defp boolean_from_tail(t) , do: { true, t }
defp value_from_tail(["-" <> _|_] = t), do: { true, t }
defp value_from_tail([h|t]), do: { h, t }
@@ -113,9 +125,9 @@ defmodule OptionParser do
[{ option, value }|dict]
end
defp reverse_dict(dict, flags) do
flags = lc k inlist flags, not Keyword.has_key?(dict, k), do: { k, false }
Enum.reverse flags ++ dict
defp reverse_dict(dict, switches) do
switches = lc { k, :boolean } inlist switches, not Keyword.has_key?(dict, k), do: { k, false }
Enum.reverse switches ++ dict
end
defp normalize_option(<<?-, option :: binary>>, aliases) do
@@ -125,7 +137,7 @@ defmodule OptionParser do
defp normalize_option(option, aliases) do
{ option, value } = split_option(option)
if is_no?(option), do: value = true
atom = option /> to_underscore /> binary_to_atom
atom = option |> to_underscore |> binary_to_atom
{ aliases[atom] || atom, value }
end
@@ -143,5 +155,7 @@ defmodule OptionParser do
defp is_no?("no-" <> _), do: true
defp is_no?(_), do: false
defp is_flag?(flags, option), do: List.member?(flags, option)
end
defp switch_type(switches, option) do
switches[option] || :default
end
end
-103
View File
@@ -1,103 +0,0 @@
defmodule OrdDict do
@moduledoc """
This module implements a dictionary type that stores items
as a list of tuples. It is a simple wrapper around
[Erlang's orddict module](http://www.erlang.org/doc/man/orddict.html)
and exposed via the `Dict` module.
Check the `Dict` module for examples and documentation.
"""
use Dict.Common
defmacrop dict(data) do
quote do
{ OrdDict, unquote(data) }
end
end
@doc false
def keys(dict(data)) do
lc { k, _ } inlist data, do: k
end
@doc false
def values(dict(data)) do
lc { _, v } inlist data, do: v
end
@doc false
def size(dict(data)) do
length(data)
end
@doc false
def has_key?(dict(data), key) do
:orddict.is_key key, data
end
@doc false
def get(dict(data), key, default) do
case :orddict.find(key, data) do
{:ok, value} -> value
:error -> default
end
end
@doc false
def get!(dict(data), key) do
case :orddict.find(key, data) do
{:ok, value} -> value
:error -> raise(KeyError, key: key)
end
end
@doc false
def put(dict(data), key, value) do
dict(:orddict.store key, value, data)
end
@doc false
def delete(dict(data), key) do
dict(:orddict.erase key, data)
end
@doc false
def merge(dict(d1), dict(d2), fun) do
dict(:orddict.merge fun, d1, d2)
end
@doc false
def merge(dict(_) = d1, d2, fun) do
merge(d1, new(d2), fun)
end
@doc false
def update(dict(data), key, fun) do
dict(:orddict.update key, fun, data)
end
@doc false
def update(dict(data), key, initial, fun) do
dict(:orddict.update key, fun, initial, data)
end
@doc false
def empty(_) do
dict([])
end
@doc false
def to_list(dict(data)) do
data
end
end
defimpl Enum.Iterator, for: OrdDict do
def iterator({ OrdDict, data }), do: data
def count({ OrdDict, data }), do: length(data)
end
defimpl Access, for: OrdDict do
def access(dict, key), do: OrdDict.get(dict, key, nil)
end
+516
View File
@@ -0,0 +1,516 @@
defmodule Path do
@moduledoc """
This module provides conveniences for manipulating or
retrieving filesystem paths.
The functions on this module may receive a char list or
a binary as argument and will return the given type.
The majority of the functions in this module do not
interact with the file system, unless some few functions
that needs to query the filesystem to retrieve paths
(like `Path.wildcard` and `Path.expand`).
"""
alias :filename, as: FN
@doc """
Converts the given filename and returns an absolute name.
Differently from `Path.expand/1`, no attempt is made to
resolve `..`, `.` or `~`.
## Unix examples
Path.absname("foo")
#=> "/usr/local/foo"
Path.absname("../x")
#=> "/usr/local/../x"
## Windows
Path.absname("foo").
"D:/usr/local/foo"
Path.absname("../x").
"D:/usr/local/../x"
"""
def absname(path) do
FN.absname(path, get_cwd(path))
end
@doc """
Converts the given filename and returns an absolute name
relative to the given location. If the path is already
an absolute path, the relative path is ignored.
Differently from `Path.expand/2`, no attempt is made to
resolve `..`, `.` or `~`.
## Examples
Path.absname("foo", "bar")
#=> "bar/foo"
Path.absname("../x", "bar")
#=> "bar/../x"
"""
def absname(path, relative_to) do
FN.absname(path, relative_to)
end
@doc """
Expands the path by returning its absolute name and expanding
any `.` and `..` characters.
## Examples
Path.expand("/foo/bar/../bar") == "/foo/bar"
"""
def expand(path) do
normalize FN.absname(path, get_cwd(path))
end
@doc """
Expands the path to the relative location and expanding
any `.` and `..` characters. If the path is already an
absolute path, the relative location is ignored.
## Examples
Path.expand("foo/bar/../bar", "/baz") == "/baz/foo/bar"
Path.expand("/foo/bar/../bar", "/baz") == "/foo/bar"
"""
def expand(path, relative_to) do
normalize FN.absname(FN.absname(path, relative_to), get_cwd(path))
end
@doc """
Returns the path type.
## Unix examples
Path.type("/usr/local/bin") #=> :absolute
Path.type("usr/local/bin") #=> :relative
Path.type("../usr/local/bin") #=> :relative
## Windows examples
Path.type("D:/usr/local/bin") #=> :absolute
Path.type("usr/local/bin") #=> :relative
Path.type("D:bar.ex") #=> :volumerelative
Path.type("/bar/foo.ex") #=> :volumerelative
"""
def type(name) when is_list(name) or is_binary(name) do
case :os.type() do
{ :win32, _ } -> win32_pathtype(name)
_ -> unix_pathtype(name)
end |> elem(0)
end
@doc """
Forces the path to be a relative path.
## Unix examples
Path.relative("/usr/local/bin") #=> "usr/local/bin"
Path.relative("usr/local/bin") #=> "usr/local/bin"
Path.relative("../usr/local/bin") #=> "../usr/local/bin"
## Windows examples
Path.relative("D:/usr/local/bin") #=> "usr/local/bin"
Path.relative("usr/local/bin") #=> "usr/local/bin"
Path.relative("D:bar.ex") #=> "bar.ex"
Path.relative("/bar/foo.ex") #=> "bar/foo.ex"
"""
def relative(name) do
case :os.type() do
{ :win32, _ } -> win32_pathtype(name)
_ -> unix_pathtype(name)
end |> elem(1)
end
defp unix_pathtype(<<?/, relative :: binary>>), do:
{ :absolute, relative }
defp unix_pathtype([?/|relative]), do:
{ :absolute, relative }
defp unix_pathtype([list|rest]) when is_list(list), do:
unix_pathtype(list ++ rest)
defp unix_pathtype([atom|rest]) when is_atom(atom), do:
unix_pathtype(atom_to_list(atom) ++ rest)
defp unix_pathtype(relative), do:
{ :relative, relative }
@slash [?/, ?\\]
defp win32_pathtype([list|rest]) when is_list(list), do:
win32_pathtype(list++rest)
defp win32_pathtype([atom|rest]) when is_atom(atom), do:
win32_pathtype(atom_to_list(atom)++rest)
defp win32_pathtype([char, list|rest]) when is_list(list), do:
win32_pathtype([char|list++rest])
defp win32_pathtype(<<c1, c2, relative :: binary>>) when c1 in @slash and c2 in @slash, do:
{ :absolute, relative }
defp win32_pathtype(<<c, relative :: binary>>) when c in @slash, do:
{ :volumerelative, relative }
defp win32_pathtype(<<_letter, ?:, c, relative :: binary>>) when c in @slash, do:
{ :absolute, relative }
defp win32_pathtype(<<_letter, ?:, relative :: binary>>), do:
{ :volumerelative, relative }
defp win32_pathtype([c1, c2 | relative]) when c1 in @slash and c2 in @slash, do:
{ :absolute, relative }
defp win32_pathtype([c | relative]) when c in @slash, do:
{ :volumerelative, relative }
defp win32_pathtype([c1, c2, list|rest]) when is_list(list), do:
win32_pathtype([c1, c2|list++rest])
defp win32_pathtype([_letter, ?:, c | relative]) when c in @slash, do:
{ :absolute, relative }
defp win32_pathtype([_letter, ?: | relative]), do:
{ :volumerelative, relative }
defp win32_pathtype(relative), do:
{ :relative, relative }
@doc """
Returns the given `path` relative to the given `from` path.
This function does not query the filesystem, so it assumes
no symlinks in between the paths.
In case a direct relative path cannot be found, it returns
the original path.
## Examples
Path.relative_to("/usr/local/foo", "/usr/local") #=> "foo"
Path.relative_to("/usr/local/foo", "/") #=> "foo"
Path.relative_to("/usr/local/foo", "/etc") #=> "/usr/local/foo"
"""
def relative_to(path, from) when is_list(path) and is_binary(from) do
path = filename_string_to_binary(path)
relative_to(FN.split(path), FN.split(from), path)
end
def relative_to(path, from) when is_binary(path) and is_list(from) do
relative_to(FN.split(path), FN.split(filename_string_to_binary(from)), path)
end
def relative_to(path, from) do
relative_to(FN.split(path), FN.split(from), path)
end
defp relative_to([h|t1], [h|t2], original) do
relative_to(t1, t2, original)
end
defp relative_to([_|_] = l1, [], _original) do
FN.join(l1)
end
defp relative_to(_, _, original) do
original
end
@doc """
Returns the last component of the path or the path
itself if it does not contain any directory separators.
## Examples
Path.basename("foo")
#=> "foo"
Path.basename("foo/bar")
#=> "bar"
Path.basename("/")
#=> ""
"""
def basename(path) do
FN.basename(path)
end
@doc """
Returns the last component of `path` with the `extension`
stripped. This function should be used to remove a specific
extension which may, or may not, be there.
## Examples
Path.basename("~/foo/bar.ex", ".ex")
#=> "bar"
Path.basename("~/foo/bar.exs", ".ex")
#=> "bar.exs"
Path.basename("~/foo/bar.old.ex", ".ex")
#=> "bar.old"
"""
def basename(path, extension) do
FN.basename(path, extension)
end
@doc """
Return the `directory` component of `path`.
## Examples
Path.dirname("/foo/bar.ex")
#=> "foo"
"""
def dirname(path) do
FN.dirname(path)
end
@doc """
Return the `extension` of the last component of `path`.
## Examples
Path.extname("foo.erl")
#=> ".erl"
Path.extname("~/foo/bar")
#=> ""
"""
def extname(path) do
FN.extension(path)
end
@doc """
Returns the `path` with the `extension` stripped.
## Examples
Path.rootname("/foo/bar")
#=> "/foo/bar"
Path.rootname("/foo/bar.ex")
#=> "/foo/bar"
"""
def rootname(path) do
FN.rootname(path)
end
@doc """
Returns the `path` with the `extension` stripped. This function should be used to
remove a specific extension which might, or might not, be there.
## Examples
Path.rootname("/foo/bar.erl", ".erl")
#=> "/foo/bar"
Path.rootname("/foo/bar.erl", ".ex")
#=> "/foo/bar.erl"
"""
def rootname(path, extension) do
FN.rootname(path, extension)
end
@doc """
Returns a string with one or more paths components joint by the path separator.
This function should be used to convert a list of strings in a path.
## Examples
Path.join(["~", "foo"])
#=> "~/foo"
Path.join(["foo"])
#=> "foo"
Path.join(["/", "foo", "bar"])
#=> "/foo/bar"
"""
def join([name1, name2|rest]), do:
join([join(name1, name2)|rest])
def join([name]) when is_list(name), do:
binary_to_filename_string(do_join(filename_string_to_binary(name), <<>>, [], major_os_type()))
def join([name]) when is_binary(name), do:
do_join(name, <<>>, [], major_os_type())
@doc """
Joins two paths.
## Examples
Path.join("foo", "bar")
#=> "foo/bar"
"""
def join(left, right) when is_binary(left) and is_binary(right), do:
do_join(left, Path.relative(right), [], major_os_type())
def join(left, right) when is_binary(left) and is_list(right), do:
join(left, filename_string_to_binary(right))
def join(left, right) when is_list(left) and is_binary(right), do:
join(filename_string_to_binary(left), right)
def join(left, right) when is_list(left) and is_list(right), do:
binary_to_filename_string join(filename_string_to_binary(left), filename_string_to_binary(right))
def join(left, right) when is_atom(left), do:
join(atom_to_binary(left), right)
def join(left, right) when is_atom(right), do:
join(left, atom_to_binary(right))
defp major_os_type do
case :os.type do
{ maj, _ } -> maj
maj -> maj
end
end
defp do_join(<<uc_letter, ?:, rest :: binary>>, relativename, [], :win32) when uc_letter in ?A..?Z, do:
do_join(rest, relativename, [?:, uc_letter+?a-?A], :win32)
defp do_join(<<?\\,rest :: binary>>, relativename, result, :win32), do:
do_join(<<?/,rest :: binary>>, relativename, result, :win32)
defp do_join(<<?/,rest :: binary>>, relativename, [?., ?/|result], os_type), do:
do_join(rest, relativename, [?/|result], os_type)
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))
defp do_join(<<>>, relativename, [?:|rest], :win32), do:
do_join(relativename, <<>>, [?:|rest], :win32)
defp do_join(<<>>, relativename, [?/|result], os_type), 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:
do_join(rest, relativename, [char|result], os_type)
defp maybe_remove_dirsep([?/, ?:, letter], :win32), do:
[letter, ?:, ?/]
defp maybe_remove_dirsep([?/], _), do:
[?/]
defp maybe_remove_dirsep([?/|name], _), do:
:lists.reverse(name)
defp maybe_remove_dirsep(name, _), do:
:lists.reverse(name)
@doc """
Returns a list with the path splitted by the path separator.
If an empty string is given, then it returns the root path.
## Examples
Path.split("")
#=> ["/"]
Path.split("foo")
#=> ["foo"]
Path.split("/foo/bar")
#=> ["/", "foo", "bar"]
"""
def split(path) do
FN.split(path)
end
@doc """
Traverses paths according to the given `glob` expression.
The wildcard looks like an ordinary path, except that certain
"wildcard characters" are interpreted in a special way. The
following characters are special:
* `?` - Matches one character.
* `*` - Matches any number of characters up to the end of
the filename, the next dot, or the next slash.
* `**` - Two adjacent <c>*</c>'s used as a single pattern will
match all files and zero or more directories and subdirectories.
* `[char1,char2,...]` - Matches any of the characters listed. Two characters
separated by a hyphen will match a range of characters.
* `{item1,item2,...}` - Matches one of the alternatives.
Other characters represent themselves. Only paths that have
exactly the same character in the same position will match. Note
that matching is case-sensitive; i.e. "a" will not match "A".
## Examples
Imagine you have a directory called `projects` with three Elixir projects
inside of it: `elixir`, `exdoc` and `dynamo`. You can find all `.beam` files
inside their ebin directories all projects as follows:
Path.wildcard("projects/*/ebin/**/*.beam")
If you want to search for both `.beam` and `.app` files, you could do:
Path.wildcard("projects/*/ebin/**/*.{beam,app}")
"""
def wildcard(glob) when is_binary(glob) do
paths = :elixir_glob.wildcard :unicode.characters_to_list(glob)
Enum.map paths, :unicode.characters_to_binary(&1)
end
def wildcard(glob) when is_list(glob) do
Enum.map wildcard(list_to_binary(glob)), binary_to_list(&1)
end
## Helpers
defp get_cwd(path) when is_list(path), do: System.cwd! |> binary_to_list
defp get_cwd(_), do: System.cwd!
defp binary_to_filename_string(binary) do
case :unicode.characters_to_list(binary) do
{ :error, _, _ } ->
:erlang.error(:badarg)
list when is_list(list) ->
list
end
end
defp filename_string_to_binary(list) do
case :unicode.characters_to_binary(:filename.flatten(list), :unicode, :file.native_name_encoding()) do
{ :error, _, _ } ->
:erlang.error(:badarg)
bin when is_binary(bin) ->
bin
end
end
# Normalize the given path by expanding "..", "." and "~".
defp normalize(path), do: do_normalize(FN.split(path))
defp do_normalize(["~"|t]) do
do_normalize t, [System.user_home!]
end
defp do_normalize(['~'|t]) do
do_normalize t, [System.user_home! |> binary_to_list]
end
defp do_normalize(t) do
do_normalize t, []
end
defp do_normalize([top|t], [_|acc]) when top in ["..", '..'] do
do_normalize t, acc
end
defp do_normalize([top|t], acc) when top in [".", '.'] do
do_normalize t, acc
end
defp do_normalize([h|t], acc) do
do_normalize t, [h|acc]
end
defp do_normalize([], acc) do
join Enum.reverse(acc)
end
end
+46 -5
View File
@@ -14,6 +14,7 @@ defmodule Process do
`pid` must refer to a process at the local node.
"""
@spec alive?(pid) :: boolean
def alive?(pid) do
:erlang.is_process_alive(pid)
end
@@ -21,15 +22,15 @@ defmodule Process do
@doc """
Returns the current process.
"""
@spec self :: pid
def self do
:erlang.self()
end
@doc """
Returns all key-values in the dictionary
with no specific ordering (i.e. they are
not a keyword list).
Returns all key-values in the dictionary.
"""
@spec get :: [{term, term}]
def get do
:erlang.get()
end
@@ -37,6 +38,8 @@ defmodule Process do
@doc """
Returns the value for the given key.
"""
@spec get(term) :: term
@spec get(term, default :: term) :: term
def get(key, default // nil) do
case :erlang.get(key) do
:undefined ->
@@ -49,6 +52,7 @@ defmodule Process do
@doc """
Returns all keys that have the given `value`.
"""
@spec get_keys(term) :: [term]
def get_keys(value) do
:erlang.get_keys(value)
end
@@ -56,6 +60,7 @@ defmodule Process do
@doc """
Stores the given key-value in the process dictionary.
"""
@spec put(term, term) :: term | nil
def put(key, value) do
nillify :erlang.put(key, value)
end
@@ -63,6 +68,7 @@ defmodule Process do
@doc """
Deletes all items in the dictionary.
"""
@spec delete :: [{term, term}]
def delete() do
:erlang.erase()
end
@@ -70,6 +76,7 @@ defmodule Process do
@doc """
Deletes the given key from the dictionary.
"""
@spec delete(term) :: term | nil
def delete(key) do
nillify :erlang.erase(key)
end
@@ -97,24 +104,33 @@ defmodule Process do
Process.exit(pid, :kill)
"""
def exit(pid, status) do
:erlang.exit(pid, status)
@spec exit(pid, term) :: true
def exit(pid, reason) do
:erlang.exit(pid, reason)
end
@doc """
Returns the pid of a new process started by the application of `fun`.
It behaves exactly the same as `Kernel.spawn/1`.
"""
@spec spawn((() -> any)) :: pid
def spawn(fun) do
:erlang.spawn(fun)
end
@type spawn_opt :: :link | :monitor | {:priority, :low | :normal | :high} |
{:fullsweep_after, non_neg_integer} |
{:min_heap_size, non_neg_integer} |
{:min_bin_vheap_size, non_neg_integer}
@type spawn_opts :: [spawn_opt]
@doc """
Returns the pid of a new process started by the application of `fun`.
It also accepts extra options, for the list of available options
check http://www.erlang.org/doc/man/erlang.html#spawn_opt-2
"""
@spec spawn((() -> any), spawn_opts) :: pid | {pid, reference}
def spawn(fun, opts) do
:erlang.spawn_opt(fun, opts)
end
@@ -126,6 +142,7 @@ defmodule Process do
It behaves exactly the same as the `Kernel.spawn/3` function.
"""
@spec spawn(module, atom, [any]) :: pid
def spawn(mod, fun, args) do
:erlang.spawn(mod, fun, args)
end
@@ -139,6 +156,7 @@ defmodule Process do
check http://www.erlang.org/doc/man/erlang.html#spawn_opt-4
"""
@spec spawn(module, atom, [any], spawn_opts) :: pid | {pid, reference}
def spawn(mod, fun, args, opts) do
:erlang.spawn_opt(mod, fun, args, opts)
end
@@ -148,6 +166,7 @@ defmodule Process do
A link is created between the calling process and the new
process, atomically.
"""
@spec spawn_link((() -> any)) :: pid
def spawn_link(fun) do
:erlang.spawn_link(fun)
end
@@ -157,6 +176,7 @@ defmodule Process do
`module.function(args)`. A link is created between the calling process
and the new process, atomically. Otherwise works like spawn/3.
"""
@spec spawn_link(module, atom, [any]) :: pid
def spawn_link(mod, fun, args) do
:erlang.spawn_link(mod, fun, args)
end
@@ -165,6 +185,7 @@ defmodule Process do
Returns the pid of a new process started by the application of `fun`
and reference for a monitor created to the new process.
"""
@spec spawn_monitor((() -> any)) :: {pid, reference}
def spawn_monitor(fun) do
:erlang.spawn_monitor(fun)
end
@@ -174,6 +195,7 @@ defmodule Process do
and the process is monitored at the same time. Returns the pid and a
reference for the monitor. Otherwise works like spawn/3.
"""
@spec spawn_monitor(module, atom, [any]) :: {pid, reference}
def spawn_monitor(mod, fun, args) do
:erlang.spawn_monitor(mod, fun, args)
end
@@ -184,6 +206,7 @@ defmodule Process do
See http://www.erlang.org/doc/man/erlang.html#monitor-2 for more info.
"""
@spec monitor(pid | {reg_name :: atom, node :: atom} | reg_name :: atom) :: reference
def monitor(item) do
:erlang.monitor(:process, item)
end
@@ -195,6 +218,8 @@ defmodule Process do
See http://www.erlang.org/doc/man/erlang.html#demonitor-2 for more info.
"""
@spec demonitor(reference) :: true
@spec demonitor(reference, options :: [:flush | :info]) :: boolean
def demonitor(monitor_ref, options // []) do
:erlang.demonitor(monitor_ref, options)
end
@@ -209,6 +234,7 @@ defmodule Process do
See http://www.erlang.org/doc/man/erlang.html#processes-0 for more info.
"""
@spec list :: [pid]
def list do
:erlang.processes()
end
@@ -219,6 +245,7 @@ defmodule Process do
See http://www.erlang.org/doc/man/erlang.html#link-1 for more info.
"""
@spec link(pid | port) :: true
def link(pid) do
:erlang.link(pid)
end
@@ -230,6 +257,7 @@ defmodule Process do
See http://www.erlang.org/doc/man/erlang.html#unlink-1 for more info.
"""
@spec unlink(pid | port) :: true
def unlink(pid) do
:erlang.unlink(pid)
end
@@ -241,6 +269,7 @@ defmodule Process do
See http://www.erlang.org/doc/man/erlang.html#register-2 for more info.
"""
@spec register(pid | port, atom) :: true
def register(pid, name) do
:erlang.register(name, pid)
end
@@ -250,6 +279,7 @@ defmodule Process do
See http://www.erlang.org/doc/man/erlang.html#unregister-1 for more info.
"""
@spec unregister(atom) :: true
def unregister(name) do
:erlang.unregister(name)
end
@@ -260,6 +290,7 @@ defmodule Process do
See http://www.erlang.org/doc/man/erlang.html#whereis-1 for more info.
"""
@spec whereis(atom) :: pid | port | nil
def whereis(name) do
nillify :erlang.whereis(name)
end
@@ -267,6 +298,7 @@ defmodule Process do
@doc """
Returns the pid of the group leader for the process which evaluates the function.
"""
@spec group_leader :: pid
def group_leader do
:erlang.group_leader
end
@@ -275,6 +307,7 @@ defmodule Process do
Sets the group leader of Pid to GroupLeader. Typically, this is used when a processes
started from a certain shell should have another group leader than `:init`.
"""
@spec group_leader(leader :: pid, pid) :: true
def group_leader(leader, pid) do
:erlang.group_leader(leader, pid)
end
@@ -282,16 +315,21 @@ defmodule Process do
@doc """
Returns a list of names which have been registered using register/2.
"""
@spec registered :: [atom]
def registered do
:erlang.registered()
end
@typep process_flag :: :trap_exit | :error_handler | :min_heap_size |
:min_bin_vheap_size | :priority | :save_calls |
:sensitive
@doc """
Sets certain flags for the process which calls this function.
Returns the old value of the flag.
See http://www.erlang.org/doc/man/erlang.html#process_flag-2 for more info.
"""
@spec flag(process_flag, term) :: term
def flag(flag, value) do
:erlang.process_flag(flag, value)
end
@@ -303,6 +341,7 @@ defmodule Process do
See http://www.erlang.org/doc/man/erlang.html#process_flag-3 for more info.
"""
@spec flag(pid, process_flag, term) :: term
def flag(pid, flag, value) do
:erlang.process_flag(pid, flag, value)
end
@@ -313,6 +352,7 @@ defmodule Process do
See http://www.erlang.org/doc/man/erlang.html#process_info-1 for more info.
"""
@spec info(pid) :: Keyword.t
def info(pid) do
:erlang.process_info(pid)
end
@@ -323,6 +363,7 @@ defmodule Process do
See http://www.erlang.org/doc/man/erlang.html#process_info-2 for more info.
"""
@spec info(pid, atom) :: {atom, term}
def info(pid, spec) do
:erlang.process_info(pid, spec)
end
+58 -39
View File
@@ -115,39 +115,53 @@ defmodule Protocol do
# Defines meta information about the protocol and internal callbacks.
@doc false
def meta(functions, conversions, fallback, returns_nil, env) do
any = L.keyfind(Any, 1, conversions) != false
any = L.keyfind(Any, 1, conversions) != false
records = L.keyfind(Record, 1, conversions) != false
meta = quote do
meta = quote location: :keep do
unless Kernel.Typespec.defines_type?(__MODULE__, :t, 0) do
@type t :: unquote(generate_type(conversions, any))
end
@doc false
def __protocol__(:name), do: __MODULE__
def __protocol__(:functions), do: unquote(:lists.sort(functions))
end
impl_for = if L.keyfind(Record, 1, conversions) do
quote do
def __impl_for__(arg) do
case __raw_impl__(arg) do
__MODULE__.Record ->
target = Module.concat(__MODULE__, :erlang.element(1, arg))
try do
target.__impl__
target
catch
:error, :undef, [[{ ^target, :__impl__, [], _ }|_]|_] ->
unquote(fallback)
end
other ->
other
impl_for = cond do
records and fallback ->
quote location: :keep do
def __impl_for__(arg) do
case __raw_impl__(arg) do
__MODULE__.Record ->
target = Module.concat(__MODULE__, :erlang.element(1, arg))
try do
target.__impl__
target
catch
:error, :undef, [[{ ^target, :__impl__, [], _ }|_]|_] ->
unquote(fallback)
end
other ->
other
end
end
end
end
else
quote do
def __impl_for__(arg), do: __raw_impl__(arg)
end
records ->
quote location: :keep do
def __impl_for__(arg) do
case __raw_impl__(arg) do
__MODULE__.Record ->
Module.concat(__MODULE__, :erlang.element(1, arg))
other ->
other
end
end
end
true ->
quote location: :keep do
def __impl_for__(arg), do: __raw_impl__(arg)
end
end
impl_bang = if returns_nil do
@@ -201,7 +215,7 @@ defmodule Protocol do
end
defp generate_type(conversions, false) do
or_function = fn({ _, _, x }, acc) -> { :|, 0, [acc, x] } end
or_function = fn({ _, _, x }, acc) -> { :|, [], [acc, x] } end
{ _, _, first } = hd(conversions)
:lists.foldl(or_function, first, tl(conversions))
end
@@ -234,10 +248,20 @@ defmodule Protocol do
end
end
# We don't have a fallback, so we assume what was given
# as a record is indeed a record
defp each_impl_for({ _, :is_record, _ }, _conversions, nil) do
quote do
defp __raw_impl__(arg) when is_record(arg) do
__MODULE__.Record
end
end
end
# Specially handle records in the case we have fallbacks.
defp each_impl_for({ _, :is_record, _ }, conversions, fallback) do
quote do
defp __raw_impl__(arg) when is_tuple(arg) and is_atom(:erlang.element(1, arg)) do
defp __raw_impl__(arg) when is_record(arg) do
first = :erlang.element(1, arg)
case unquote(is_builtin?(conversions)) do
true -> unquote(fallback)
@@ -283,7 +307,7 @@ defmodule Protocol.DSL do
# interpolation to generate the arguments because of compile
# dependencies, so we use the <<>> instead.
args = lc i inlist :lists.seq(1, arity) do
{ binary_to_atom(<<?x, i + 64>>), 0, :quoted }
{ binary_to_atom(<<?x, i + 64>>), [], __MODULE__ }
end
{ conversions, fallback, returns_nil } = conversions_for(module)
@@ -299,7 +323,7 @@ defmodule Protocol.DSL do
body =
quote do
case __raw_impl__(xA), do: unquote({ :->, 0, clauses })
case __raw_impl__(xA), do: unquote({ :->, [], clauses })
end
{ args, body }
@@ -334,6 +358,13 @@ defmodule Protocol.DSL do
end
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
end
defp record_clause(name, args, fallback) do
arity = length(args)
@@ -345,23 +376,11 @@ defmodule Protocol.DSL do
catch
:error, :undef, [[{ ^target, name, args, _ }|_]|_] when
name == unquote(name) and length(args) == unquote(arity) ->
unquote(catch_clause(args, fallback))
apply unquote(fallback), name, [unquote_splicing(args)]
end) }
end
end
defp catch_clause(args, fallback) do
if fallback do
quote do
apply unquote(fallback), name, [unquote_splicing(args)]
end
else
quote do
raise Protocol.UndefinedError, protocol: __MODULE__, structure: xA
end
end
end
defmacro def(expression) do
case expression do
{ _, _, args } when args == [] or is_atom(args) ->
+21 -9
View File
@@ -32,19 +32,31 @@ defimpl Enum.Iterator, for: Range do
end
defimpl Range.Iterator, for: Number do
def iterator(first, Range[first: f, last: last]) when is_integer(first) and is_integer(last) and last < f do
fn(current) ->
if current < last, do: :stop, else: { current, current - 1 }
end
end
def iterator(first, Range[last: last]) when is_integer(first) and is_integer(last) do
def iterator(first, Range[last: last]) when is_integer(first) and is_integer(last) and last >= first do
fn(current) ->
if current > last, do: :stop, else: { current, current + 1 }
end
end
def count(first, Range[last: last]) when is_integer(first) and is_integer(last) do
def iterator(first, Range[last: last]) when is_integer(first) and is_integer(last) do
fn(current) ->
if current < last, do: :stop, else: { current, current - 1 }
end
end
def count(first, Range[last: last]) when is_integer(first) and is_integer(last) and last >= first do
last - first + 1
end
end
def count(first, Range[last: last]) when is_integer(first) and is_integer(last) do
first - last + 1
end
end
defimpl Binary.Inspect, for: Range do
import Kernel, except: [inspect: 2]
def inspect(Range[first: first, last: last], opts) do
Binary.Inspect.inspect(first, opts) <> ".." <> Binary.Inspect.inspect(last, opts)
end
end
+40 -87
View File
@@ -33,9 +33,9 @@ defmodule Record do
values = unquote(values)
opts = unquote(opts)
Record.deffunctions(values, opts, __ENV__)
Record.deffunctions(values, __ENV__)
unquote(block)
Record.deftypes(values, @record_type, opts, __ENV__)
Record.deftypes(values, @record_type, __ENV__)
end
end
end
@@ -61,11 +61,11 @@ defmodule Record do
defmodule CustomRecord do
Record.deffunctions [:name, :age], __ENV__
Record.deftypes [:name, :age], __ENV__
Record.deftypes [:name, :age], [name: :binary, age: :integer], __ENV__
end
"""
def deffunctions(values, _opts // [], env) do
def deffunctions(values, env) do
values = lc value inlist values, do: convert_value(value)
escaped = Macro.escape(values)
@@ -74,9 +74,10 @@ defmodule Record do
initializer(escaped),
indexes(escaped),
conversions(values),
record_optimizable(),
updater(values),
extensions(values, 1, [], Record.Extensions),
accessors(values),
accessors(values, 1),
switch_recorder()
]
contents = [quote(do: @__record__ unquote(escaped))|contents]
@@ -92,7 +93,7 @@ defmodule Record do
@doc """
Defines types and specs for the record.
"""
def deftypes(values, types, _opts // [], env) do
def deftypes(values, types, env) do
types = types || []
values = lc value inlist values do
{ name, default } = convert_value(value)
@@ -195,7 +196,7 @@ defmodule Record do
in_match = caller.in_match?
has_underscore_value = Keyword.has_key?(keyword, :_)
underscore_value = Keyword.get(keyword, :_, { :_, 0, nil })
underscore_value = Keyword.get(keyword, :_, { :_, [], nil })
keyword = Keyword.delete keyword, :_
iterator = fn({field, default}, each_keyword) ->
@@ -215,7 +216,7 @@ defmodule Record do
{ match, remaining } = :lists.mapfoldl(iterator, keyword, fields)
case remaining do
[] -> { :{}, caller.line, [atom|match] }
[] -> { :{}, [line: caller.line], [atom|match] }
_ ->
keys = lc { key, _ } inlist remaining, do: key
raise "record #{inspect atom} does not have the keys: #{inspect keys}"
@@ -320,7 +321,10 @@ defmodule Record do
#
defp reflection(values) do
quote do
@doc false
def __record__(kind, _), do: __record__(kind)
@doc false
def __record__(:name), do: __MODULE__
def __record__(:fields), do: unquote(values)
end
@@ -356,7 +360,10 @@ defmodule Record do
end
quote do
@doc false
def new(), do: new([])
@doc false
def new([]), do: { __MODULE__, unquote_splicing(defaults) }
def new(opts) when is_list(opts), do: { __MODULE__, unquote_splicing(selective) }
def new(tuple) when is_tuple(tuple), do: :erlang.setelement(1, tuple, __MODULE__)
@@ -388,7 +395,11 @@ defmodule Record do
end
quote do
unquote(quoted)
@doc false
def __index__(_), do: nil
@doc false
def __index__(key, _), do: __index__(key)
end
end
@@ -408,6 +419,7 @@ defmodule Record do
end
quote do
@doc false
def to_keywords(record) do
unquote(:orddict.from_list(sorted))
end
@@ -444,15 +456,6 @@ defmodule Record do
# setelem(record, 2, callback.(elem(record, 2)))
# end
#
defp accessors(values) do
[ quote do
@record_optimized true
@record_optimizable []
@before_compile { unquote(__MODULE__), :__before_compile__ }
@on_definition { unquote(__MODULE__), :__on_definition__ }
end | accessors(values, 1) ]
end
defp accessors([{ :__exception__, _ }|t], 1) do
accessors(t, 2)
end
@@ -461,14 +464,17 @@ defmodule Record do
update = binary_to_atom "update_" <> atom_to_binary(key)
contents = quote do
@doc false
def unquote(key).(record) do
:erlang.element(unquote(i + 1), record)
end
@doc false
def unquote(key).(value, record) do
:erlang.setelement(unquote(i + 1), record, value)
end
@doc false
def unquote(update).(function, record) do
:erlang.setelement(unquote(i + 1), record,
function.(:erlang.element(unquote(i + 1), record)))
@@ -479,7 +485,7 @@ defmodule Record do
end
defp accessors([], _i) do
[quote do: @record_optimized false]
[]
end
# Define an updater method that receives a
@@ -493,9 +499,10 @@ defmodule Record do
end
end
contents = { :{}, 0, [(quote do: __MODULE__)|fields] }
contents = { :{}, [], [(quote do: __MODULE__)|fields] }
quote do
@doc false
def update([], record) do
record
end
@@ -506,13 +513,18 @@ defmodule Record do
end
end
# Defines extra functions from the definition.
defp extensions([{ key, default }|t], i, acc, extensions) do
functions = extensions.functions_for(key, default, i)
extensions(t, i + 1, [functions | acc], extensions)
defp record_optimizable do
quote do
@record_optimized true
@record_optimizable []
@before_compile { unquote(__MODULE__), :__before_compile__ }
@on_definition { unquote(__MODULE__), :__on_definition__ }
end
end
defp extensions([], _i, acc, _), do: acc
defp switch_recorder do
quote do: @record_optimized false
end
## Types/specs generation
@@ -592,69 +604,10 @@ defmodule Record.DSL do
Expects a keyword list.
"""
defmacro record_type(opts) when is_list(opts) do
escaped = lc { k, v } inlist opts, do: { k, Macro.escape(v) }
quote do
@record_type quote do: unquote(opts)
@record_type Keyword.merge(@record_type || [], unquote(escaped))
end
end
end
defmodule Record.Extensions do
@moduledoc false
# Function definition
def functions_for(key, default, i) do
extension_for(key, default, i)
end
defp extension_for(key, default, i) when is_list(default) do
prepend = prefix("prepend_", key)
merge = prefix("merge_", key)
quote do
def unquote(prepend).(value, record) do
IO.write "[WARNING] record default-based generated function #{unquote(prepend)} is deprecated\n#{Exception.formatted_stacktrace}"
current = :erlang.element(unquote(i + 1), record)
:erlang.setelement(unquote(i + 1), record, value ++ current)
end
def unquote(merge).(value, record) do
IO.write "[WARNING] record default-based generated function #{unquote(merge)} is deprecated\n#{Exception.formatted_stacktrace}"
current = :erlang.element(unquote(i + 1), record)
:erlang.setelement(unquote(i + 1), record, Keyword.merge(current, value))
end
end
end
defp extension_for(key, default, i) when is_number(default) do
increment = prefix("increment_", key)
quote do
def unquote(increment).(value // 1, record) do
IO.write "[WARNING] record default-based generated function #{unquote(increment)} is deprecated\n#{Exception.formatted_stacktrace}"
current = :erlang.element(unquote(i + 1), record)
:erlang.setelement(unquote(i + 1), record, current + value)
end
end
end
defp extension_for(key, default, i) when is_boolean(default) do
toggle = prefix("toggle_", key)
quote do
def unquote(toggle).(record) do
IO.write "[WARNING] record default-based generated function #{unquote(toggle)} is deprecated\n#{Exception.formatted_stacktrace}"
current = :erlang.element(unquote(i + 1), record)
:erlang.setelement(unquote(i + 1), record, not current)
end
end
end
defp extension_for(_, _, _), do: nil
# Helpers
defp prefix(prefix, key) do
binary_to_atom prefix <> atom_to_binary(key)
end
end
+1
View File
@@ -280,6 +280,7 @@ defmodule Regex do
def unescape_map(?r), do: ?\r
def unescape_map(?t), do: ?\t
def unescape_map(?v), do: ?\v
def unescape_map(?a), do: ?\a
def unescape_map(_), do: false
# Private Helpers
+196 -94
View File
@@ -1,17 +1,79 @@
defmodule String do
@moduledoc """
A string in Elixir is a utf-8 binary. This module
contains function to work with utf-8 data, its
codepoints and graphemes.
@moduledoc %B"""
A string in Elixir is a UTF-8 encoded binary.
Notice that graphemes is a superset of UTF-8 codepoints
which also contains named sequences as defined per
http://www.unicode.org/reports/tr34/. In short, graphemes
also contain multiple characters that are "perceived as
a single character" by readers.
The functions in this module act according to the
Unicode Standard, version 6.2.0. A codepoint is a
Unicode Character, which may be represented by one
or more bytes. For example, the character "é" is
represented with two bytes:
For working with raw binaries, use Erlang's :binary
string = "é"
#=> "é"
size(string)
#=> 2
Furthermore, this module also presents the concept of
graphemes, which are multiple characters that may be
"perceived as a single character" by readers. For example,
the same "é" character written above could be represented
by the letter "e" followed by the accent ́:
string = "\x{0065}\x{0301}"
#=> "é"
size(string)
#=> 3
Although the example above is made of two characters, it is
perceived by users as one.
Graphemes can also be two characters that are interpreted
as one by some languages. For example, some languages may
consider "ch" as a grapheme. However, since this information
depends on the locale, it is not taken into account by this
module.
In general, the functions in this module rely on the Unicode
Standard, but does not contain any of the locale specific
behaviour.
## Integer codepoints
Although codepoints could be represented as integers, this
module represents all codepoints as binaries. For example:
String.codepoints "josé" #=> ["j", "o", "s", "é"]
There are a couple of ways to retrieve a character integer
codepoint. One may use the `?` special macro:
?j #=> 106
?é #=> 233
Or also via pattern matching:
<< eacute :: utf8 >> = "é"
eacute #=> 233
As we have seen above, codepoints can be inserted into
a string by their hexadecimal code:
string = "jos\x{0065}\x{0301}"
#=> "josé"
## Self-synchronization
The UTF-8 encoding is self-synchronizing. This means that
if malformed data (i.e., data that is not possible according
to the definition of the encoding) is encountered, only one
codepoint needs to be rejected.
This module relies on this behaviour to ignore such invalid
characters. For example, `String.length` is going to return
a correct result even if an invalid codepoint is fed into it.
In the future, bang version of such functions may be
provided which will rather raise on such invalid data.
"""
@type t :: binary
@@ -28,64 +90,12 @@ defmodule String do
"""
@spec printable?(t) :: boolean
# Allow basic ascii chars
def printable?(<<c, t :: binary>>) when c in ?\s..?~ do
printable?(t)
end
# From 16#A0 to 16#BF
def printable?(<<194, c, t :: binary>>) when c in 160..191 do
printable?(t)
end
# From 16#C0 to 16#7FF
def printable?(<<m, o1, t :: binary>>) when m in 195..223 and o1 in 128..191 do
printable?(t)
end
# From 16#800 to 16#CFFF
def printable?(<<m, o1, o2, t :: binary>>) when m in 224..236 and
o1 >= 128 and o1 < 192 and o2 >= 128 and o2 < 192 do
printable?(t)
end
# From 16#D000 to 16#D7FF
def printable?(<<237, o1, o2, t :: binary>>) when
o1 >= 128 and o1 < 160 and o2 >= 128 and o2 < 192 do
printable?(t)
end
# Reject 16#FFFF and 16#FFFE
def printable?(<<239, 191, o>>) when o == 190 or o == 191 do
false
end
# From 16#E000 to 16#EFFF
def printable?(<<m, o1, o2, t :: binary>>) when (m == 238 or m == 239) and
o1 in 128..191 and o2 in 128..191 do
printable?(t)
end
# From 16#F000 to 16#FFFD
def printable?(<<239, o1, o2, t :: binary>>) when
o1 in 128..191 and o2 in 128..191 do
printable?(t)
end
# From 16#10000 to 16#3FFFF
def printable?(<<240, o1, o2, o3, t :: binary>>) when
o1 in 144..191 and o2 in 128..191 and o3 in 128..191 do
printable?(t)
end
# Reject 16#110000 onwards
def printable?(<<244, o1, _, _, _ :: binary>>) when o1 >= 144 do
false
end
# From 16#4000 to 16#10FFFF
def printable?(<<m, o1, o2, o3, t :: binary>>) when m in 241..244 and
o1 in 128..191 and o2 in 128..191 and o3 in 128..191 do
def printable?(<< h :: utf8, t :: binary >>)
when h in ?\040..?\176
when h in 0xA0..0xD7FF
when h in 0xE000..0xFFFD
when h in 0x10000..0x10FFFF do
printable?(t)
end
@@ -96,6 +106,7 @@ defmodule String do
def printable?(<<?\b, t :: binary>>), do: printable?(t)
def printable?(<<?\f, t :: binary>>), do: printable?(t)
def printable?(<<?\e, t :: binary>>), do: printable?(t)
def printable?(<<?\a, t :: binary>>), do: printable?(t)
def printable?(<<>>), do: true
def printable?(_), do: false
@@ -105,9 +116,10 @@ defmodule String do
returning a list of these sub string. The pattern can
be a string, a list of strings or a regular expression.
The string is split into two parts by default, unless
`global` option is true. If a pattern is not specified,
the string is split on whitespace occurrences.
The string is split into as many parts as possible by
default, unless the `global` option is set to false.
If a pattern is not specified, the string is split on
whitespace occurrences.
It returns a list with the original string if the pattern
can't be matched.
@@ -128,6 +140,7 @@ defmodule String do
@spec split(t) :: [t]
@spec split(t, t | [t] | Regex.t) :: [t]
@spec split(t, t | [t] | Regex.t, Keyword.t) :: [t]
def split(binary, pattern // " ", options // [])
def split(binary, pattern, options) when is_regex(pattern) do
@@ -142,10 +155,6 @@ defmodule String do
@doc """
Convert all characters on the given string to upcase.
This function relies on the simple uppercase mapping
available in Unicode 6.2.0, check http://unicode.org/reports/tr44/
for more information.
## Examples
String.upcase("abcd") #=> "ABCD"
@@ -159,10 +168,6 @@ defmodule String do
@doc """
Convert all characters on the given string to downcase.
This function relies on the simple lowercase mapping
available in Unicode 6.2.0, check http://unicode.org/reports/tr44/
for more information.
## Examples
String.downcase("ABCD") #=> "abcd"
@@ -173,6 +178,28 @@ defmodule String do
@spec downcase(t) :: t
defdelegate downcase(binary), to: String.Unicode
@doc """
Converts the first character in the given string to
titlecase and the remaining to downcase.
This relies on the titlecase information provided
by the Unicode Standard. Note this function makes
no attempt in capitalizing all words in the string
(usually known as titlecase).
## Examples
String.capitalize("abcd") #=> "Abcd"
String.capitalize("fin") #=> "Fin"
String.capitalize("josé") #=> "José"
"""
@spec capitalize(t) :: t
def capitalize(string) when is_binary(string) do
{ char, rest } = String.Unicode.titlecase_once(string)
char <> downcase(rest)
end
@doc """
Returns a string where trailing whitespace characters
and new line have been removed.
@@ -282,13 +309,12 @@ defmodule String do
@doc """
Returns a new binary based on `subject` by replacing the parts
matching `pattern` for `replacement`. If `options` is specified
with `[global: true]`, then it will replace all matches, otherwise
it will replace just the first one.
matching `pattern` for `replacement`. By default, it replaces
all entries, except if the `global` option is set to false.
For the replaced part must be used in `replacement`, then the
position or the positions where it is to be inserted must be specified by using
the option `insert_replaced`.
If the replaced part must be used in `replacement`, then the
position or the positions where it is to be inserted must be
specified by using the option `insert_replaced`.
## Examples
@@ -332,7 +358,7 @@ defmodule String do
end
@doc """
Returns a list with codepoints from an utf8 string.
Returns all codepoints in the string.
## Examples
@@ -349,21 +375,45 @@ defmodule String do
The result is a tuple with the codepoint and the
remaining of the string or `:no_codepoint` in case
the String reached its end.
the string reached its end.
As the other functions in the String module, this
function does not check for the validity of the codepoint.
That said, if an invalid codepoint is found, it will
be returned by this function.
## Examples
String.next_codepoint("josé") #=> { "j", "osé" }
"""
@spec next_codepoint(t) :: codepoint | :no_codepoint
@spec next_codepoint(t) :: {codepoint, t} | :no_codepoint
defdelegate next_codepoint(string), to: String.Unicode
@doc """
Returns unicode graphemes in the string
@doc %B"""
Checks whether `str` is a valid codepoint.
Note that the empty string is considered invalid, as are
strings containing multiple codepoints.
## Examples
String.graphemes("Ā̀stute") # => ["Ā̀","s","t","u","t","e"]
String.valid_codepoint?("a") #=> true
String.valid_codepoint?("ø") #=> true
String.valid_codepoint?("\xffff") #=> false
String.valid_codepoint?("asdf") #=> false
"""
@spec valid_codepoint?(codepoint) :: boolean
def valid_codepoint?(<<_ :: utf8>>), do: true
def valid_codepoint?(_), do: false
@doc """
Returns unicode graphemes in the string.
## Examples
String.graphemes("Ā̀stute") # => ["Ā̀","s","t","u","t","e"]
"""
@spec graphemes(t) :: [grapheme]
@@ -393,11 +443,11 @@ defmodule String do
String.first("եոգլի") #=> "ե"
"""
@spec first(t) :: grapheme
@spec first(t) :: grapheme | nil
def first(string) do
case next_grapheme(string) do
{ char, _ } -> char
:no_grapheme -> ""
:no_grapheme -> nil
end
end
@@ -410,9 +460,9 @@ defmodule String do
String.last("եոգլի") #=> "ի"
"""
@spec last(t) :: grapheme
@spec last(t) :: grapheme | nil
def last(string) do
do_last(next_grapheme(string), "")
do_last(next_grapheme(string), nil)
end
defp do_last({char, rest}, _) do
@@ -455,6 +505,7 @@ defmodule String do
"""
@spec at(t, integer) :: grapheme | nil
def at(string, position) when position >= 0 do
do_at(next_grapheme(string), position, 0)
end
@@ -476,4 +527,55 @@ defmodule String do
end
defp do_at(:no_grapheme, _, _), do: nil
@doc """
Returns a substring starting at the offset given by the first, and
a length given by the second.
If the offset is greater than string length, than it returns nil.
## Examples
String.slice("elixir", 1, 3) #=> "lix"
String.slice("elixir", 1, 10) #=> "lixir"
String.slice("elixir", 10, 3) #=> nil
String.slice("elixir", -4, 4) #=> "ixi"
String.slice("elixir", -10, 3) #=> nil
"""
@spec slice(t, integer, integer) :: grapheme | nil
def slice(string, start, len) when start >= 0 do
do_slice(next_grapheme(string), start, start + len - 1, 0, "")
end
def slice(string, start, len) when start < 0 do
real_start_pos = do_length(next_grapheme(string)) - abs(start)
case real_start_pos >= 0 do
true -> do_slice(next_grapheme(string), real_start_pos, real_start_pos + len - 1, 0, "")
false -> nil
end
end
defp do_slice(_, start_pos, last_pos, _, _) when start_pos > last_pos do
nil
end
defp do_slice({_, rest}, start_pos, last_pos, current_pos, acc) when current_pos < start_pos do
do_slice(next_grapheme(rest), start_pos, last_pos, current_pos + 1, acc)
end
defp do_slice({char, rest}, start_pos, last_pos, current_pos, acc) when current_pos >= start_pos and current_pos < last_pos do
do_slice(next_grapheme(rest), start_pos, last_pos, current_pos + 1, acc <> char)
end
defp do_slice({char, _}, start_pos, last_pos, current_pos, acc) when current_pos >= start_pos and current_pos == last_pos do
acc <> char
end
defp do_slice(:no_grapheme, _, _, _, acc) do
case acc do
"" -> nil
_ -> acc
end
end
end
+183
View File
@@ -0,0 +1,183 @@
defmodule Supervisor.Behaviour do
@moduledoc """
This module is a convenience to define Supervisor
callbacks in Elixir. By using this module, you get
the module behaviour automatically tagged as
`:supervisor` and some helper functions are imported
to make defining supervisors easier.
For more information on supervisors, please check the
remaining functions defined in this module or refer to
the following:
http://www.erlang.org/doc/man/supervisor.html
http://www.erlang.org/doc/design_principles/sup_princ.html
http://learnyousomeerlang.com/supervisors
## Example
defmodule ExUnit.Sup do
use Supervisor.Behaviour
def init(user_options) do
tree = [ worker(ExUnit.Runner, [user_options]) ]
supervise(tree, strategy: :one_for_one)
end
end
{ :ok, pid } = :supervisor.start_link(MyServer, [])
"""
@doc false
defmacro __using__(_) do
quote location: :keep do
@behaviour :supervisor
import unquote(__MODULE__)
end
end
@doc """
Receives a list of children (worker or supervisors) to
supervise and a set of options. Returns a tuple containing
the supervisor specification.
## Examples
supervise children, strategy: :one_for_one
## Options
* `:strategy` - the restart strategy option It can be either
`:one_for_one`, `:rest_for_one`, `:one_for_all` and
`:simple_one_for_one`;
* `:max_restarts` - the maximum amount of restarts allowed in
a time frame. Defaults to 5;
* `:max_seconds` - the time frame in which max_restarts applies.
Defaults to 5;
The `:strategy` option is required and by default maximum 5 restarts
are allowed in 5 seconds.
## Strategies
* `:one_for_one` - If a child process terminates, only that
process is restarted;
* `:one_for_all` - If a child process terminates, all other child
processes are terminated and then all child processes, including
the terminated one, are restarted;
* `:rest_for_one` - If a child process terminates, the "rest" of
the child processes, i.e. the child processes after the terminated
process in start order, are terminated. Then the terminated child
process and the rest of the child processes are restarted;
* `:simple_one_for_one` - Similar to `:one_for_one` but suits better
when dynamically attaching children;
"""
def supervise(children, options) do
unless strategy = options[:strategy] do
raise ArgumentError, message: "expected :strategy option to be given to supervise"
end
maxR = Keyword.get(options, :max_restarts, 5)
maxS = Keyword.get(options, :max_seconds, 5)
{ :ok, { { strategy, maxR, maxS }, children } }
end
@child_doc """
## Options
* `:id` - a name used to identify the child specification
internally by the supervisor. Defaults to the module name;
* `:function` - the function to invoke on the child to start it.
Defaults to `:start_link`;
* `:restart` - defines when the child process should restart.
Defaults to `:permanent`;
* `:shutdown` - defines how a child process should be terminated.
Defaults to `5000`;
* `:modules` - it should be a list with one element `[module]`,
where module is the name of the callback module only if the
child process is a supervisor, gen_server or gen_fsm. If the
child process is a gen_event, modules should be `:dynamic`.
Defaults to a list with the given module;
## Restart values
The following restart values are supported:
* `:permanent` - the child process is always restarted;
* `:temporary` - the child process is never restarted (not even
when the supervisor's strategy is `:rest_for_one` or `:one_for_all`);
* `:transient` - the child process is restarted only if it
terminates abnormally, i.e. with another exit reason than
`:normal`, `:shutdown` or `{ :shutdown, term }`;
## Shutdown values
The following shutdown values are supported:
* `:brutal_kill` - the child process is unconditionally terminated
using `exit(child, :kill)`;
* `:infinity` - if the child process is a supervisor, it is a mechanism
to give the subtree enough time to shutdown. It can also be used with
workers with care;
* Finally, it can also be any integer meaning that the supervisor tells
the child process to terminate by calling `exit(child, :shutdown)` and
then waits for an exit signal back. If no exit signal is received within
the specified time (in miliseconds), the child process is unconditionally
terminated using `exit(child, :kill)`;
"""
@doc """
Defines the given `module` as a worker which will be started
with the given arguments.
worker ExUnit.Runner, [], restart: :permanent
By default, the function `:start_link` is invoked on the given module.
#{@child_doc}
"""
def worker(module, args, options // []) do
child(:worker, module, args, options)
end
@doc """
Defines the given `module` as a supervisor which will be started
with the given arguments.
supervisor ExUnit.Runner, [], restart: :permanent
By default, the function `:start_link` is invoked on the given module.
#{@child_doc}
"""
def supervisor(module, args, options // []) do
child(:supervisor, module, args, options)
end
defp child(type, module, args, options) do
id = Keyword.get(options, :id, module)
modules = Keyword.get(options, :modules, [module])
function = Keyword.get(options, :function, :start_link)
restart = Keyword.get(options, :restart, :permanent)
shutdown = Keyword.get(options, :shutdown, 5000)
{ id, { module, function, args },
restart, shutdown, type, modules }
end
end
+114 -16
View File
@@ -1,14 +1,28 @@
defmodule System do
defexception NoHomeError,
message: "could not find the user home, please set the HOME environment variable"
defexception NoTmpDirError,
message: "could not get a writable temporary directory, please set the TMPDIR environment variable"
defexception NoAccessCwdError,
message: "could not get a current working directory, the current location is not accessible"
@moduledoc """
The System module provides access to some variables used or
maintained by the VM and to functions that interact strongly
with the VM or the host system.
"""
# Read and strip the version from the `VERSION` file.
defmacrop get_version do
Regex.replace %r/^\s+|\s+$/, File.read!("VERSION"), ""
end
# Tries to run `git describe --always --tags`. In case of success
# returns the most recent tag, otherwise returns an empty string.
defmacrop get_describe do
dotgit = File.join(File.cwd!, ".git")
dotgit = Path.join(File.cwd!, ".git")
if :os.find_executable('git') && File.exists?(dotgit) do
data = :os.cmd('git describe --always --tags')
Regex.replace %r/\n/, to_binary(data), ""
@@ -26,7 +40,7 @@ defmodule System do
Returns Elixir's version as binary.
"""
@spec version() :: String.t
def version, do: "0.7.2"
def version, do: get_version
@doc """
Returns a keywords list with version, git tag info and date.
@@ -41,19 +55,107 @@ defmodule System do
"""
@spec argv() :: [String.t]
def argv do
:gen_server.call(:elixir_code_server, :argv)
:elixir_code_server.call :argv
end
@doc """
Returns the current working directory or nil if one
is not available.
"""
def cwd do
case :file.get_cwd do
{ :ok, list } -> :unicode.characters_to_binary(list)
_ -> nil
end
end
@doc """
Returns the current working directory or raises `System.NoAccessCwdError`.
"""
def cwd! do
cwd || raise NoAccessCwdError
end
@doc """
Returns the user home (platform independent).
It returns nil if no user home is set.
"""
def user_home do
get_unix_home || get_windows_home
end
@doc """
Same as `user_home` but raises `System.NoHomeError`
instead of returning nil if no user home is set.
"""
def user_home! do
user_home || raise NoHomeError
end
defp get_unix_home do
get_env("HOME")
end
defp get_windows_home do
get_env("USERPROFILE") || (
hd = get_env("HOMEDRIVE")
hp = get_env("HOMEPATH")
hd && hp && hd <> hp
)
end
@doc %B"""
Returns a writable temporary directory.
It searches for directories in the following order:
1. The directory named by the TMPDIR environment variable
2. The directory named by the TEMP environment variable
3. The directory named by the TMP environment variable
4. `C:\TMP` on Windows or `/tmp` on Unix
5. As a last resort, the current working directory
Returns nil if none of the above are writable.
"""
def tmp_dir do
write_env_tmp_dir('TMPDIR') ||
write_env_tmp_dir('TEMP') ||
write_env_tmp_dir('TMP') ||
write_tmp_dir("/tmp") ||
((cwd = cwd()) && write_tmp_dir(cwd))
end
@doc """
Same as `tmp_dir` but raises `System.NoTmpDirError`
instead of returning nil if no temp dir is set.
"""
def tmp_dir! do
tmp_dir || raise NoTmpDirError
end
defp write_env_tmp_dir(env) do
case System.get_env(env) do
nil -> nil
tmp -> write_tmp_dir tmp
end
end
defp write_tmp_dir(dir) do
case File.stat(dir) do
{ :ok, File.Stat[type: :directory, access: access] } when access in [:read_write, :write] -> dir
{ :error, _ } -> nil
end
end
@doc """
Registers a function that will be invoked
at the end of program execution. Useful for
invoking a hook on scripted mode.
invoking a hook in a "script" mode.
The function must expect the exit status code
as argument.
"""
def at_exit(fun) when is_function(fun, 1) do
server_call { :at_exit, fun }
:elixir_code_server.cast { :at_exit, fun }
end
@doc """
@@ -146,7 +248,11 @@ defmodule System do
end
@doc """
Get the stacktrace.
Gets Elixir's stacktrace.
Notice the Erlang VM (and therefore this function) does not
return the current stacktrace but rather the stacktrace of the
latest exception.
"""
def stacktrace do
filter_stacktrace :erlang.get_stacktrace
@@ -205,14 +311,6 @@ defmodule System do
## Helpers
# Filter stacktrace by removing internal BOOTSTRAP calls.
defp filter_stacktrace([{ Kernel, :raise, _, _ }|t]), do: filter_stacktrace(t)
defp filter_stacktrace([{ _mod, :BOOTSTRAP, _, info }|t]),
do: filter_stacktrace([{ Kernel, :defmodule, 2, info }|t])
defp filter_stacktrace([h|t]), do: [h|filter_stacktrace(t)]
defp filter_stacktrace([]), do: []
defp server_call(args) do
:gen_server.call(:elixir_code_server, args)
end
defp filter_stacktrace([{ Kernel, :raise, _, _ }|t]), do: t
defp filter_stacktrace(t), do: t
end
+1
View File
@@ -1,2 +1,3 @@
defmodule Tuple do
@moduledoc false
end
+8 -8
View File
@@ -28,7 +28,7 @@ defmodule URI do
Use decoder/1 if you want to customize or iterate each value manually.
"""
def decode_query(q, dict // OrdDict.new) do
def decode_query(q, dict // HashDict.new) when is_binary(q) do
Enum.reduce query_decoder(q), dict, fn({ k, v }, acc) -> Dict.put(acc, k, v) end
end
@@ -36,8 +36,8 @@ defmodule URI do
Returns an iterator function over the query string that decodes
the query string in steps.
"""
def query_decoder(q) do
fn -> { do_decoder(&1), do_decoder(to_binary(q)) } end
def query_decoder(q) when is_binary(q) do
fn -> { do_decoder(&1), q } end
end
defp do_decoder("") do
@@ -123,10 +123,10 @@ defmodule URI do
for that particular scheme. Take a look at URI.HTTPS for an
example of one of these extension modules.
"""
def parse(s) do
def parse(s) when is_binary(s) do
# From http://tools.ietf.org/html/rfc3986#appendix-B
regex = %r/^(([^:\/?#]+):)?(\/\/([^\/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?/
parts = nillify(Regex.run(regex, to_binary(s)))
parts = nillify(Regex.run(regex, s))
destructure [_, _, scheme, _, authority, path, _, query, _, fragment], parts
{ userinfo, host, port } = split_authority(authority)
@@ -144,12 +144,12 @@ defmodule URI do
if scheme do
module =
try do
Module.safe_concat(URI, :string.to_upper(binary_to_list(scheme)))
Module.safe_concat(URI, String.upcase(scheme))
rescue
ArgumentError -> nil
end
if module && match?({:module,^module}, Code.ensure_loaded(module)) do
if module && Code.ensure_loaded?(module) do
module.parse(default_port(info, module))
else
info
@@ -168,7 +168,7 @@ defmodule URI do
s = s || ""
components = Regex.run %r/(^(.*)@)?([^:]*)(:(\d*))?/, s
destructure [_, _, userinfo, host, _, port], nillify(components)
port = if port, do: list_to_integer(binary_to_list(port))
port = if port, do: binary_to_integer(port)
{ userinfo, host, port }
end
+103
View File
@@ -0,0 +1,103 @@
00DF; 00DF; 0053 0073; 0053 0053; # LATIN SMALL LETTER SHARP S
0130; 0069 0307; 0130; 0130; # LATIN CAPITAL LETTER I WITH DOT ABOVE
FB00; FB00; 0046 0066; 0046 0046; # LATIN SMALL LIGATURE FF
FB01; FB01; 0046 0069; 0046 0049; # LATIN SMALL LIGATURE FI
FB02; FB02; 0046 006C; 0046 004C; # LATIN SMALL LIGATURE FL
FB03; FB03; 0046 0066 0069; 0046 0046 0049; # LATIN SMALL LIGATURE FFI
FB04; FB04; 0046 0066 006C; 0046 0046 004C; # LATIN SMALL LIGATURE FFL
FB05; FB05; 0053 0074; 0053 0054; # LATIN SMALL LIGATURE LONG S T
FB06; FB06; 0053 0074; 0053 0054; # LATIN SMALL LIGATURE ST
0587; 0587; 0535 0582; 0535 0552; # ARMENIAN SMALL LIGATURE ECH YIWN
FB13; FB13; 0544 0576; 0544 0546; # ARMENIAN SMALL LIGATURE MEN NOW
FB14; FB14; 0544 0565; 0544 0535; # ARMENIAN SMALL LIGATURE MEN ECH
FB15; FB15; 0544 056B; 0544 053B; # ARMENIAN SMALL LIGATURE MEN INI
FB16; FB16; 054E 0576; 054E 0546; # ARMENIAN SMALL LIGATURE VEW NOW
FB17; FB17; 0544 056D; 0544 053D; # ARMENIAN SMALL LIGATURE MEN XEH
0149; 0149; 02BC 004E; 02BC 004E; # LATIN SMALL LETTER N PRECEDED BY APOSTROPHE
0390; 0390; 0399 0308 0301; 0399 0308 0301; # GREEK SMALL LETTER IOTA WITH DIALYTIKA AND TONOS
03B0; 03B0; 03A5 0308 0301; 03A5 0308 0301; # GREEK SMALL LETTER UPSILON WITH DIALYTIKA AND TONOS
01F0; 01F0; 004A 030C; 004A 030C; # LATIN SMALL LETTER J WITH CARON
1E96; 1E96; 0048 0331; 0048 0331; # LATIN SMALL LETTER H WITH LINE BELOW
1E97; 1E97; 0054 0308; 0054 0308; # LATIN SMALL LETTER T WITH DIAERESIS
1E98; 1E98; 0057 030A; 0057 030A; # LATIN SMALL LETTER W WITH RING ABOVE
1E99; 1E99; 0059 030A; 0059 030A; # LATIN SMALL LETTER Y WITH RING ABOVE
1E9A; 1E9A; 0041 02BE; 0041 02BE; # LATIN SMALL LETTER A WITH RIGHT HALF RING
1F50; 1F50; 03A5 0313; 03A5 0313; # GREEK SMALL LETTER UPSILON WITH PSILI
1F52; 1F52; 03A5 0313 0300; 03A5 0313 0300; # GREEK SMALL LETTER UPSILON WITH PSILI AND VARIA
1F54; 1F54; 03A5 0313 0301; 03A5 0313 0301; # GREEK SMALL LETTER UPSILON WITH PSILI AND OXIA
1F56; 1F56; 03A5 0313 0342; 03A5 0313 0342; # GREEK SMALL LETTER UPSILON WITH PSILI AND PERISPOMENI
1FB6; 1FB6; 0391 0342; 0391 0342; # GREEK SMALL LETTER ALPHA WITH PERISPOMENI
1FC6; 1FC6; 0397 0342; 0397 0342; # GREEK SMALL LETTER ETA WITH PERISPOMENI
1FD2; 1FD2; 0399 0308 0300; 0399 0308 0300; # GREEK SMALL LETTER IOTA WITH DIALYTIKA AND VARIA
1FD3; 1FD3; 0399 0308 0301; 0399 0308 0301; # GREEK SMALL LETTER IOTA WITH DIALYTIKA AND OXIA
1FD6; 1FD6; 0399 0342; 0399 0342; # GREEK SMALL LETTER IOTA WITH PERISPOMENI
1FD7; 1FD7; 0399 0308 0342; 0399 0308 0342; # GREEK SMALL LETTER IOTA WITH DIALYTIKA AND PERISPOMENI
1FE2; 1FE2; 03A5 0308 0300; 03A5 0308 0300; # GREEK SMALL LETTER UPSILON WITH DIALYTIKA AND VARIA
1FE3; 1FE3; 03A5 0308 0301; 03A5 0308 0301; # GREEK SMALL LETTER UPSILON WITH DIALYTIKA AND OXIA
1FE4; 1FE4; 03A1 0313; 03A1 0313; # GREEK SMALL LETTER RHO WITH PSILI
1FE6; 1FE6; 03A5 0342; 03A5 0342; # GREEK SMALL LETTER UPSILON WITH PERISPOMENI
1FE7; 1FE7; 03A5 0308 0342; 03A5 0308 0342; # GREEK SMALL LETTER UPSILON WITH DIALYTIKA AND PERISPOMENI
1FF6; 1FF6; 03A9 0342; 03A9 0342; # GREEK SMALL LETTER OMEGA WITH PERISPOMENI
1F80; 1F80; 1F88; 1F08 0399; # GREEK SMALL LETTER ALPHA WITH PSILI AND YPOGEGRAMMENI
1F81; 1F81; 1F89; 1F09 0399; # GREEK SMALL LETTER ALPHA WITH DASIA AND YPOGEGRAMMENI
1F82; 1F82; 1F8A; 1F0A 0399; # GREEK SMALL LETTER ALPHA WITH PSILI AND VARIA AND YPOGEGRAMMENI
1F83; 1F83; 1F8B; 1F0B 0399; # GREEK SMALL LETTER ALPHA WITH DASIA AND VARIA AND YPOGEGRAMMENI
1F84; 1F84; 1F8C; 1F0C 0399; # GREEK SMALL LETTER ALPHA WITH PSILI AND OXIA AND YPOGEGRAMMENI
1F85; 1F85; 1F8D; 1F0D 0399; # GREEK SMALL LETTER ALPHA WITH DASIA AND OXIA AND YPOGEGRAMMENI
1F86; 1F86; 1F8E; 1F0E 0399; # GREEK SMALL LETTER ALPHA WITH PSILI AND PERISPOMENI AND YPOGEGRAMMENI
1F87; 1F87; 1F8F; 1F0F 0399; # GREEK SMALL LETTER ALPHA WITH DASIA AND PERISPOMENI AND YPOGEGRAMMENI
1F88; 1F80; 1F88; 1F08 0399; # GREEK CAPITAL LETTER ALPHA WITH PSILI AND PROSGEGRAMMENI
1F89; 1F81; 1F89; 1F09 0399; # GREEK CAPITAL LETTER ALPHA WITH DASIA AND PROSGEGRAMMENI
1F8A; 1F82; 1F8A; 1F0A 0399; # GREEK CAPITAL LETTER ALPHA WITH PSILI AND VARIA AND PROSGEGRAMMENI
1F8B; 1F83; 1F8B; 1F0B 0399; # GREEK CAPITAL LETTER ALPHA WITH DASIA AND VARIA AND PROSGEGRAMMENI
1F8C; 1F84; 1F8C; 1F0C 0399; # GREEK CAPITAL LETTER ALPHA WITH PSILI AND OXIA AND PROSGEGRAMMENI
1F8D; 1F85; 1F8D; 1F0D 0399; # GREEK CAPITAL LETTER ALPHA WITH DASIA AND OXIA AND PROSGEGRAMMENI
1F8E; 1F86; 1F8E; 1F0E 0399; # GREEK CAPITAL LETTER ALPHA WITH PSILI AND PERISPOMENI AND PROSGEGRAMMENI
1F8F; 1F87; 1F8F; 1F0F 0399; # GREEK CAPITAL LETTER ALPHA WITH DASIA AND PERISPOMENI AND PROSGEGRAMMENI
1F90; 1F90; 1F98; 1F28 0399; # GREEK SMALL LETTER ETA WITH PSILI AND YPOGEGRAMMENI
1F91; 1F91; 1F99; 1F29 0399; # GREEK SMALL LETTER ETA WITH DASIA AND YPOGEGRAMMENI
1F92; 1F92; 1F9A; 1F2A 0399; # GREEK SMALL LETTER ETA WITH PSILI AND VARIA AND YPOGEGRAMMENI
1F93; 1F93; 1F9B; 1F2B 0399; # GREEK SMALL LETTER ETA WITH DASIA AND VARIA AND YPOGEGRAMMENI
1F94; 1F94; 1F9C; 1F2C 0399; # GREEK SMALL LETTER ETA WITH PSILI AND OXIA AND YPOGEGRAMMENI
1F95; 1F95; 1F9D; 1F2D 0399; # GREEK SMALL LETTER ETA WITH DASIA AND OXIA AND YPOGEGRAMMENI
1F96; 1F96; 1F9E; 1F2E 0399; # GREEK SMALL LETTER ETA WITH PSILI AND PERISPOMENI AND YPOGEGRAMMENI
1F97; 1F97; 1F9F; 1F2F 0399; # GREEK SMALL LETTER ETA WITH DASIA AND PERISPOMENI AND YPOGEGRAMMENI
1F98; 1F90; 1F98; 1F28 0399; # GREEK CAPITAL LETTER ETA WITH PSILI AND PROSGEGRAMMENI
1F99; 1F91; 1F99; 1F29 0399; # GREEK CAPITAL LETTER ETA WITH DASIA AND PROSGEGRAMMENI
1F9A; 1F92; 1F9A; 1F2A 0399; # GREEK CAPITAL LETTER ETA WITH PSILI AND VARIA AND PROSGEGRAMMENI
1F9B; 1F93; 1F9B; 1F2B 0399; # GREEK CAPITAL LETTER ETA WITH DASIA AND VARIA AND PROSGEGRAMMENI
1F9C; 1F94; 1F9C; 1F2C 0399; # GREEK CAPITAL LETTER ETA WITH PSILI AND OXIA AND PROSGEGRAMMENI
1F9D; 1F95; 1F9D; 1F2D 0399; # GREEK CAPITAL LETTER ETA WITH DASIA AND OXIA AND PROSGEGRAMMENI
1F9E; 1F96; 1F9E; 1F2E 0399; # GREEK CAPITAL LETTER ETA WITH PSILI AND PERISPOMENI AND PROSGEGRAMMENI
1F9F; 1F97; 1F9F; 1F2F 0399; # GREEK CAPITAL LETTER ETA WITH DASIA AND PERISPOMENI AND PROSGEGRAMMENI
1FA0; 1FA0; 1FA8; 1F68 0399; # GREEK SMALL LETTER OMEGA WITH PSILI AND YPOGEGRAMMENI
1FA1; 1FA1; 1FA9; 1F69 0399; # GREEK SMALL LETTER OMEGA WITH DASIA AND YPOGEGRAMMENI
1FA2; 1FA2; 1FAA; 1F6A 0399; # GREEK SMALL LETTER OMEGA WITH PSILI AND VARIA AND YPOGEGRAMMENI
1FA3; 1FA3; 1FAB; 1F6B 0399; # GREEK SMALL LETTER OMEGA WITH DASIA AND VARIA AND YPOGEGRAMMENI
1FA4; 1FA4; 1FAC; 1F6C 0399; # GREEK SMALL LETTER OMEGA WITH PSILI AND OXIA AND YPOGEGRAMMENI
1FA5; 1FA5; 1FAD; 1F6D 0399; # GREEK SMALL LETTER OMEGA WITH DASIA AND OXIA AND YPOGEGRAMMENI
1FA6; 1FA6; 1FAE; 1F6E 0399; # GREEK SMALL LETTER OMEGA WITH PSILI AND PERISPOMENI AND YPOGEGRAMMENI
1FA7; 1FA7; 1FAF; 1F6F 0399; # GREEK SMALL LETTER OMEGA WITH DASIA AND PERISPOMENI AND YPOGEGRAMMENI
1FA8; 1FA0; 1FA8; 1F68 0399; # GREEK CAPITAL LETTER OMEGA WITH PSILI AND PROSGEGRAMMENI
1FA9; 1FA1; 1FA9; 1F69 0399; # GREEK CAPITAL LETTER OMEGA WITH DASIA AND PROSGEGRAMMENI
1FAA; 1FA2; 1FAA; 1F6A 0399; # GREEK CAPITAL LETTER OMEGA WITH PSILI AND VARIA AND PROSGEGRAMMENI
1FAB; 1FA3; 1FAB; 1F6B 0399; # GREEK CAPITAL LETTER OMEGA WITH DASIA AND VARIA AND PROSGEGRAMMENI
1FAC; 1FA4; 1FAC; 1F6C 0399; # GREEK CAPITAL LETTER OMEGA WITH PSILI AND OXIA AND PROSGEGRAMMENI
1FAD; 1FA5; 1FAD; 1F6D 0399; # GREEK CAPITAL LETTER OMEGA WITH DASIA AND OXIA AND PROSGEGRAMMENI
1FAE; 1FA6; 1FAE; 1F6E 0399; # GREEK CAPITAL LETTER OMEGA WITH PSILI AND PERISPOMENI AND PROSGEGRAMMENI
1FAF; 1FA7; 1FAF; 1F6F 0399; # GREEK CAPITAL LETTER OMEGA WITH DASIA AND PERISPOMENI AND PROSGEGRAMMENI
1FB3; 1FB3; 1FBC; 0391 0399; # GREEK SMALL LETTER ALPHA WITH YPOGEGRAMMENI
1FBC; 1FB3; 1FBC; 0391 0399; # GREEK CAPITAL LETTER ALPHA WITH PROSGEGRAMMENI
1FC3; 1FC3; 1FCC; 0397 0399; # GREEK SMALL LETTER ETA WITH YPOGEGRAMMENI
1FCC; 1FC3; 1FCC; 0397 0399; # GREEK CAPITAL LETTER ETA WITH PROSGEGRAMMENI
1FF3; 1FF3; 1FFC; 03A9 0399; # GREEK SMALL LETTER OMEGA WITH YPOGEGRAMMENI
1FFC; 1FF3; 1FFC; 03A9 0399; # GREEK CAPITAL LETTER OMEGA WITH PROSGEGRAMMENI
1FB2; 1FB2; 1FBA 0345; 1FBA 0399; # GREEK SMALL LETTER ALPHA WITH VARIA AND YPOGEGRAMMENI
1FB4; 1FB4; 0386 0345; 0386 0399; # GREEK SMALL LETTER ALPHA WITH OXIA AND YPOGEGRAMMENI
1FC2; 1FC2; 1FCA 0345; 1FCA 0399; # GREEK SMALL LETTER ETA WITH VARIA AND YPOGEGRAMMENI
1FC4; 1FC4; 0389 0345; 0389 0399; # GREEK SMALL LETTER ETA WITH OXIA AND YPOGEGRAMMENI
1FF2; 1FF2; 1FFA 0345; 1FFA 0399; # GREEK SMALL LETTER OMEGA WITH VARIA AND YPOGEGRAMMENI
1FF4; 1FF4; 038F 0345; 038F 0399; # GREEK SMALL LETTER OMEGA WITH OXIA AND YPOGEGRAMMENI
1FB7; 1FB7; 0391 0342 0345; 0391 0342 0399; # GREEK SMALL LETTER ALPHA WITH PERISPOMENI AND YPOGEGRAMMENI
1FC7; 1FC7; 0397 0342 0345; 0397 0342 0399; # GREEK SMALL LETTER ETA WITH PERISPOMENI AND YPOGEGRAMMENI
1FF7; 1FF7; 03A9 0342 0345; 03A9 0342 0399; # GREEK SMALL LETTER OMEGA WITH PERISPOMENI AND YPOGEGRAMMENI
+57 -35
View File
@@ -6,22 +6,30 @@ defmodule String.Unicode do
def version, do: {6,2,0}
to_binary = fn(codepoint) ->
:unicode.characters_to_binary([binary_to_integer(codepoint, 16)])
to_binary = fn
"" ->
nil
codepoints ->
codepoints = :binary.split(codepoints, " ", [:global])
Enum.reduce codepoints, "", fn(codepoint, acc) ->
acc <> << binary_to_integer(codepoint, 16) :: utf8 >>
end
end
data_path = File.expand_path("../UnicodeData.txt", __FILE__)
data_path = Path.expand("../UnicodeData.txt", __FILE__)
{ codes, whitespace } = Enum.reduce File.iterator!(data_path), { [], [] }, fn(line, { cacc, wacc }) ->
[ codepoint, _name, _category,
_class, bidi, _decomposition,
_numeric_1, _numeric_2, _numeric_3,
_bidi_mirror, _unicode_1, _iso,
upper, lower, _title ] = :binary.split(line, ";", [:global])
upper, lower, title ] = :binary.split(line, ";", [:global])
title = :binary.part(title, 0, size(title) - 1)
cond do
upper != "" or lower != "" ->
{ [{ to_binary.(codepoint), upper, lower } | cacc], wacc }
upper != "" or lower != "" or title != "" ->
{ [{ to_binary.(codepoint), to_binary.(upper), to_binary.(lower), to_binary.(title) } | cacc], wacc }
bidi in ["B", "S", "WS"] ->
{ cacc, [to_binary.(codepoint) | wacc] }
true ->
@@ -29,7 +37,15 @@ defmodule String.Unicode do
end
end
seqs_path = File.expand_path("../NamedSequences.txt", __FILE__)
special_path = Path.expand("../SpecialCasing.txt", __FILE__)
codes = Enum.reduce File.iterator!(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) })
end
seqs_path = Path.expand("../NamedSequences.txt", __FILE__)
seqs = Enum.map File.iterator!(seqs_path), fn(line) ->
[ _name, codepoints ] = :binary.split(line, ";", [:global])
@@ -41,10 +57,9 @@ defmodule String.Unicode do
# Downcase
lc { codepoint, _upper, lower } inlist codes, lower != "" do
lower = to_binary.(lower)
args = quote do: [unquote(codepoint) <> t]
code = quote do: unquote(lower) <> downcase(t)
lc { codepoint, _upper, lower, _title } inlist codes, lower && lower != codepoint do
args = quote do: [unquote(codepoint) <> t]
code = quote do: unquote(lower) <> downcase(t)
def :downcase, args, [], do: code
end
@@ -58,10 +73,9 @@ defmodule String.Unicode do
# Upcase
lc { codepoint, upper, _lower } inlist codes, upper != "" do
upper = to_binary.(upper)
args = quote do: [unquote(codepoint) <> t]
code = quote do: unquote(upper) <> upcase(t)
lc { codepoint, upper, _lower, _title } inlist codes, upper && upper != codepoint do
args = quote do: [unquote(codepoint) <> t]
code = quote do: unquote(upper) <> upcase(t)
def :upcase, args, [], do: code
end
@@ -73,6 +87,22 @@ defmodule String.Unicode do
<< >>
end
# Titlecase once
lc { codepoint, _upper, _lower, title } inlist codes, title && title != codepoint do
args = quote do: [unquote(codepoint) <> t]
code = quote do: { unquote(title), t }
def :titlecase_once, args, [], do: code
end
def titlecase_once(<< h, t :: binary >>) do
{ <<h>>, t }
end
def titlecase_once(<< >>) do
{ <<>>, <<>> }
end
# Strip
def lstrip(""), do: ""
@@ -83,11 +113,11 @@ defmodule String.Unicode do
def :lstrip, args, [], do: exprs
end
def lstrip(other), do: other
def lstrip(other) when is_binary(other), do: other
def rstrip(""), do: ""
def rstrip(string) do
def rstrip(string) when is_binary(string) do
do_rstrip(string, "")
end
@@ -136,25 +166,17 @@ defmodule String.Unicode do
# Codepoints
def next_codepoint(<<194, char, rest :: binary>>)
when char in 161..191,
do: { <<194, char>>, rest }
def next_codepoint(<< cp :: utf8, rest :: binary >>) do
{ <<cp :: utf8>>, rest }
end
def next_codepoint(<<first, char, rest :: binary>>)
when first in 195..223 and char in 128..191,
do: { <<first, char>>, rest }
def next_codepoint(<< cp, rest :: binary >>) do
{ <<cp>>, rest }
end
def next_codepoint(<<first, second, char, rest :: binary>>)
when first == 224 and second in 160..191 and char in 128..191,
do: { <<first, second, char>>, rest }
def next_codepoint(<<first, second, char, rest :: binary>>)
when first in 225..239 and second in 128..191 and char in 128..191,
do: { <<first, second, char>>, rest }
def next_codepoint(<<other, rest :: binary>>), do: { <<other>>, rest }
def next_codepoint(<<>>), do: :no_codepoint
def next_codepoint(<<>>) do
:no_codepoint
end
def codepoints(binary) when is_binary(binary) do
do_codepoints(next_codepoint(binary))
@@ -167,4 +189,4 @@ defmodule String.Unicode do
defp do_codepoints(:no_codepoint) do
[]
end
end
end
+12 -7
View File
@@ -3,7 +3,7 @@
-export([main/1, start_cli/0,
scope_for_eval/1, eval/2, eval/3, eval/4,
eval_quoted/2, eval_quoted/3, eval_quoted/4,
eval_forms/3]).
eval_forms/3, translate_forms/3]).
-include("elixir.hrl").
-compile({parse_transform, elixir_transform}).
@@ -61,7 +61,7 @@ scope_for_eval(Opts) ->
end,
case lists:keyfind(requires, 1, Opts) of
{ requires, Requires } -> Requires;
{ requires, List } -> Requires = ordsets:from_list(List);
false -> Requires = elixir_dispatch:default_requires()
end,
@@ -107,20 +107,25 @@ eval_quoted(Tree, Binding, Opts) ->
end,
eval_quoted(Tree, Binding, Line, scope_for_eval(Opts)).
eval_quoted(Tree, Binding, Line, #elixir_scope{} = S) ->
eval_quoted(Tree, Binding, Line, #elixir_scope{} = S) when is_integer(Line) ->
eval_forms(elixir_quote:linify(Line, Tree), Binding, S).
%% Handle forms evaluation internally, it is an
%% internal API not meant for external usage.
eval_forms(Tree, Binding, RawScope) ->
Scope = RawScope#elixir_scope{
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=[]
},
{ ParseTree, NewScope } = elixir_translator:translate(Tree, Scope),
}).
eval_forms(Tree, Binding, Scope) ->
{ ParseTree, NewScope } = translate_forms(Tree, Binding, Scope),
case ParseTree of
[] -> { nil, Binding, NewScope };
_ ->
+40 -13
View File
@@ -1,10 +1,29 @@
-module(elixir_aliases).
-export([first/1, last/1, concat/1, safe_concat/1, lookup/2,
format_error/1, ensure_loaded/3]).
-export([nesting/2, last/1, concat/1, safe_concat/1, lookup/2,
format_error/1, ensure_loaded/3, expand/2]).
-include("elixir.hrl").
-compile({parse_transform, elixir_transform}).
%% Expand an alias returning an atom if possible.
%% Otherwise returns the list of aliases args.
expand({ '__aliases__', _Line, [H] }, Aliases) when H /= 'Elixir' ->
expand_alias(H, Aliases);
expand({ '__aliases__', _Line, [H|T] }, Aliases) when is_atom(H) ->
concat(case H of
'Elixir' -> [H|T];
_ -> [expand_alias(H, Aliases)|T]
end);
expand({ '__aliases__', _Line, List }, _Aliases) ->
List.
expand_alias(H, Aliases) ->
lookup(list_to_atom("Elixir-" ++ atom_to_list(H)), Aliases).
%% Ensure a module is loaded before its usage.
ensure_loaded(_Line, 'Elixir.Kernel', _S) ->
ok;
@@ -20,17 +39,6 @@ ensure_loaded(Line, Ref, S) ->
elixir_errors:form_error(Line, S#elixir_scope.file, ?MODULE, { Kind, Ref })
end.
%% Receives an atom and returns the first alias.
first(Atom) ->
First = first(atom_to_list(Atom), []),
list_to_atom("Elixir-" ++ First).
first("Elixir-" ++ Rest, []) -> first(Rest, []);
first([$-|_], Acc) -> lists:reverse(Acc);
first([H|T], Acc) -> first(T, [H|Acc]);
first([], Acc) -> lists:reverse(Acc).
%% Receives an atom and returns the last alias.
last(Atom) ->
@@ -41,6 +49,25 @@ last([$-|_], Acc) -> Acc;
last([H|T], Acc) -> last(T, [H|Acc]);
last([], Acc) -> Acc.
%% Returns the nesting between two aliases.
nesting(nil, _Full) -> false;
nesting(Prefix, Full) ->
PrefixList = list_nesting(Prefix),
FullList = list_nesting(Full),
(PrefixList /= []) andalso
(PrefixList /= FullList) andalso
lists:prefix(PrefixList, FullList) andalso
binary_to_atom(<<"Elixir-", (hd(FullList -- PrefixList))/binary>>, utf8).
list_nesting(Atom) ->
case binary:split(atom_to_binary(Atom, utf8), <<$->>, [global]) of
[<<"Elixir">>|T] -> T;
_ -> []
end.
%% Receives a list of atoms representing modules
%% and concatenate them.
+62 -55
View File
@@ -9,17 +9,17 @@
%% Get pairs from a clause.
get_pairs(Line, Key, Clauses, S) ->
get_pairs(Line, Key, Clauses, S, false).
get_pairs(Meta, Key, Clauses, S) ->
get_pairs(Meta, Key, Clauses, S, false).
get_pairs(Line, Key, Clauses, S, AllowNil) ->
get_pairs(Meta, Key, Clauses, S, AllowNil) ->
case lists:keyfind(Key, 1, Clauses) of
{ Key, { '->', _, Pairs } } ->
[{ Key, Left, Right } || { Left, Right } <- Pairs];
{ Key, nil } when AllowNil ->
[];
{ Key, _ } ->
elixir_errors:syntax_error(Line, S#elixir_scope.file, "expected pairs with -> for key ~s", [Key]);
elixir_errors:syntax_error(Meta, S#elixir_scope.file, "expected pairs with -> for key ~s", [Key]);
_ ->
[]
end.
@@ -39,7 +39,7 @@ assigns_block(Line, Fun, BareArgs, Exprs, S) ->
{ Args, Guards } = extract_guards(BareArgs),
assigns_block(Line, Fun, Args, Exprs, Guards, S).
assigns_block(Line, Fun, Args, Exprs, Guards, S) ->
assigns_block(Line, Fun, Args, Exprs, Guards, S) when is_integer(Line) ->
{ TArgs, SA } = assigns(Fun, Args, S#elixir_scope{extra_guards=[]}),
{ TExprs, SE } = elixir_translator:translate(Exprs, SA#elixir_scope{extra_guards=nil}),
@@ -88,56 +88,58 @@ extract_last_guards(Args) ->
% Function for translating macros with match style like case and receive.
match(Line, Clauses, #elixir_scope{clause_vars=C1} = S) ->
{ TC, TS } = do_match(Line, Clauses, S#elixir_scope{clause_vars=orddict:new()}),
match(Meta, Clauses, #elixir_scope{clause_vars=C1} = S) ->
{ TC, TS } = do_match(Meta, Clauses, S#elixir_scope{clause_vars=orddict:new()}),
C2 = TS#elixir_scope.clause_vars,
{ TC, TS#elixir_scope{clause_vars=elixir_scope:merge_clause_vars(C1, C2)} }.
do_match(Line, DecoupledClauses, S) ->
case DecoupledClauses of
[DecoupledClause] ->
{ TDecoupledClause, TS } = each_clause(Line, DecoupledClause, S),
{ [TDecoupledClause], TS };
_ ->
% Transform tree just passing the variables counter forward
% and storing variables defined inside each clause.
Transformer = fun(X, {Acc, CV}) ->
{ TX, TAcc } = each_clause(Line, X, Acc),
{ TX, { umergec(S, TAcc), [TAcc#elixir_scope.clause_vars|CV] } }
end,
do_match(_Meta, [], S) ->
{ [], S };
{ TClauses, { TS, RawCV } } = lists:mapfoldl(Transformer, {S, []}, DecoupledClauses),
do_match(Meta, [DecoupledClause], S) ->
{ TDecoupledClause, TS } = each_clause(Meta, DecoupledClause, S),
{ [TDecoupledClause], TS };
% Now get all the variables defined inside each clause
CV = lists:reverse(RawCV),
do_match(Meta, DecoupledClauses, S) ->
% Transform tree just passing the variables counter forward
% and storing variables defined inside each clause.
Transformer = fun(X, {Acc, CV}) ->
{ TX, TAcc } = each_clause(Meta, X, Acc),
{ TX, { umergec(S, TAcc), [TAcc#elixir_scope.clause_vars|CV] } }
end,
AllVars = lists:umerge([orddict:fetch_keys(X) || X <- CV]),
SharedVars = ordsets:intersection(CV),
{ TClauses, { TS, RawCV } } = lists:mapfoldl(Transformer, {S, []}, DecoupledClauses),
case AllVars of
[] -> { TClauses, TS };
_ ->
% Create a new scope that contains a list of all variables
% defined inside all the clauses. It returns this new scope and
% a list of tuples where the first element is the variable name,
% the second one is the new pointer to the variable and the third
% is the old pointer.
{ FinalVars, FS } = lists:mapfoldl(fun(X, Acc) -> normalize_vars(X, SharedVars, Acc) end, TS, AllVars),
% Now get all the variables defined inside each clause
CV = lists:reverse(RawCV),
% Defines a tuple that will be used as left side of the match operator
LeftVars = [{var, Line, NewValue} || { _, _, NewValue, _ } <- FinalVars],
AllVars = lists:umerge([orddict:fetch_keys(X) || X <- CV]),
SharedVars = ordsets:intersection(CV),
% Expand all clauses by adding a match operation at the end
% that assigns variables missing in one clause to the others.
expand_clauses(Line, TClauses, CV, LeftVars, FinalVars, [], FS)
end
case AllVars of
[] -> { TClauses, TS };
_ ->
% Create a new scope that contains a list of all variables
% defined inside all the clauses. It returns this new scope and
% a list of tuples where the first element is the variable name,
% the second one is the new pointer to the variable and the third
% is the old pointer.
{ FinalVars, FS } = lists:mapfoldl(fun(X, Acc) -> normalize_vars(X, SharedVars, Acc) end, TS, AllVars),
% Defines a tuple that will be used as left side of the match operator
Line = ?line(Meta),
LeftVars = [{var, Line, NewValue} || { _, _, NewValue, _ } <- FinalVars],
% Expand all clauses by adding a match operation at the end
% that assigns variables missing in one clause to the others.
expand_clauses(Meta, TClauses, CV, LeftVars, FinalVars, [], FS)
end.
expand_clauses(Line, [Clause|T], [ClauseVars|V], LeftVars, FinalVars, Acc, S) ->
expand_clauses(Meta, [Clause|T], [ClauseVars|V], LeftVars, FinalVars, Acc, S) ->
RightVars = [normalize_clause_var(Var, Kind, OldValue, ClauseVars) ||
{ Var, Kind, _, OldValue } <- FinalVars],
AssignExpr = generate_match(Line, LeftVars, RightVars),
AssignExpr = generate_match(Meta, LeftVars, RightVars),
ClauseExprs = element(5, Clause),
[Final|RawClauseExprs] = lists:reverse(ClauseExprs),
@@ -150,6 +152,7 @@ expand_clauses(Line, [Clause|T], [ClauseVars|V], LeftVars, FinalVars, Acc, S) ->
{ match, _, { var, _, UserVarName } = UserVar, _ } when UserVarName /= '_' ->
{ [UserVar,AssignExpr,Final|RawClauseExprs], S };
_ ->
Line = ?line(Meta),
{ StorageVar, SS } = elixir_scope:build_erl_var(Line, S),
StorageExpr = { match, Line, StorageVar, Final },
{ [StorageVar,AssignExpr,StorageExpr|RawClauseExprs], SS }
@@ -159,26 +162,29 @@ expand_clauses(Line, [Clause|T], [ClauseVars|V], LeftVars, FinalVars, Acc, S) ->
end,
FinalClause = setelement(5, Clause, lists:reverse(FinalClauseExprs)),
expand_clauses(Line, T, V, LeftVars, FinalVars, [FinalClause|Acc], FS);
expand_clauses(Meta, T, V, LeftVars, FinalVars, [FinalClause|Acc], FS);
expand_clauses(_Line, [], [], _LeftVars, _FinalVars, Acc, S) ->
expand_clauses(_Meta, [], [], _LeftVars, _FinalVars, Acc, S) ->
{ lists:reverse(Acc), S }.
% Handle each key/value clause pair and translate them accordingly.
each_clause(Line, { do, [Condition], Expr }, S) ->
assigns_block(Line, fun elixir_translator:translate_each/2, Condition, [Expr], S);
each_clause(Meta, { do, [Condition], Expr }, S) ->
assigns_block(?line(Meta), fun elixir_translator:translate_each/2, Condition, [Expr], S);
each_clause(Line, { 'after', [Condition], Expr }, S) ->
each_clause(Meta, { else, [Condition], Expr }, S) ->
assigns_block(?line(Meta), fun elixir_translator:translate_each/2, Condition, [Expr], S);
each_clause(Meta, { 'after', [Condition], Expr }, S) ->
{ TCondition, SC } = elixir_translator:translate_each(Condition, S),
{ TBody, SB } = elixir_translator:translate([Expr], SC),
{ { clause, Line, [TCondition], [], TBody }, SB };
{ { clause, ?line(Meta), [TCondition], [], TBody }, SB };
each_clause(Line, { Key, [_|_], _ }, S) when Key == do; Key == 'after' ->
elixir_errors:syntax_error(Line, S#elixir_scope.file, "too many arguments given for ~s", [Key]);
each_clause(Meta, { Key, [_|_], _ }, S) when Key == do; Key == 'after' ->
elixir_errors:syntax_error(Meta, S#elixir_scope.file, "too many arguments given for ~s", [Key]);
each_clause(Line, { Key, _, _ }, S) ->
elixir_errors:syntax_error(Line, S#elixir_scope.file, "invalid key ~s", [Key]).
each_clause(Meta, { Key, _, _ }, S) ->
elixir_errors:syntax_error(Meta, S#elixir_scope.file, "invalid key ~s", [Key]).
% Check if the given expression is a match tuple.
% This is a small optimization to allow us to change
@@ -212,7 +218,7 @@ normalize_vars({ Var, Kind } = Key, Shared, #elixir_scope{vars=Vars,clause_vars=
{ ok, SharedValue } ->
{ SharedValue, S };
error ->
{ { _, _, ErlValue }, ErlS } = case (Kind == quoted) or (S#elixir_scope.noname) of
{ { _, _, ErlValue }, ErlS } = case (Kind /= nil) or (S#elixir_scope.noname) of
true -> elixir_scope:build_erl_var(0, S);
false -> elixir_scope:build_erl_var(0, Var, "_@" ++ atom_to_list(Var), S)
end,
@@ -242,10 +248,11 @@ normalize_clause_var(Var, Kind, OldValue, ClauseVars) ->
%% generate_match
generate_match(Line, [Left], [Right]) ->
{ match, Line, Left, Right };
generate_match(Meta, [Left], [Right]) ->
{ match, ?line(Meta), Left, Right };
generate_match(Line, LeftVars, RightVars) ->
generate_match(Meta, LeftVars, RightVars) ->
Line = ?line(Meta),
{ match, Line, { tuple, Line, LeftVars }, { tuple, Line, RightVars } }.
%% Listify
+23 -11
View File
@@ -1,4 +1,5 @@
-module(elixir_code_server).
-export([call/1, cast/1]).
-export([start_link/0, init/1, handle_call/3, handle_cast/2,
handle_info/2, terminate/2, code_change/3]).
-behavior(gen_server).
@@ -12,8 +13,19 @@
waiting=[]
}).
call(Args) ->
gen_server:call(?MODULE, Args, get_timeout()).
cast(Args) ->
gen_server:cast(?MODULE, Args).
get_timeout() ->
30000.
%% Callbacks
start_link() ->
{ ok, _ } = gen_server:start_link({local, elixir_code_server}, ?MODULE, [], []).
{ ok, _ } = gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
init(_args) ->
{ ok, #elixir_code_server{} }.
@@ -38,16 +50,6 @@ handle_call({ acquire, Path }, From, Config) ->
{ reply, proceed, Config#elixir_code_server{loaded=Queued} }
end;
handle_call({ at_exit, AtExit }, _From, Config) ->
{ reply, ok, Config#elixir_code_server{at_exit=[AtExit|Config#elixir_code_server.at_exit]} };
handle_call({ argv, Argv }, _From, Config) ->
{ reply, ok, Config#elixir_code_server{argv=Argv} };
handle_call({ compiler_options, Options }, _From, Config) ->
Final = orddict:merge(fun(_,_,V) -> V end, Config#elixir_code_server.compiler_options, Options),
{ reply, ok, Config#elixir_code_server{compiler_options=Final} };
handle_call(loaded, _From, Config) ->
{ reply, [F || { F, true } <- Config#elixir_code_server.loaded], Config };
@@ -75,6 +77,16 @@ handle_call(retrieve_module_name, _From, Config) ->
handle_call(_Request, _From, Config) ->
{ reply, undef, Config }.
handle_cast({ at_exit, AtExit }, Config) ->
{ noreply, Config#elixir_code_server{at_exit=[AtExit|Config#elixir_code_server.at_exit]} };
handle_cast({ argv, Argv }, Config) ->
{ noreply, Config#elixir_code_server{argv=Argv} };
handle_cast({ compiler_options, Options }, Config) ->
Final = orddict:merge(fun(_,_,V) -> V end, Config#elixir_code_server.compiler_options, Options),
{ noreply, Config#elixir_code_server{compiler_options=Final} };
handle_cast(finished, Config) ->
Waiting = Config#elixir_code_server.waiting,
[Pid ! { elixir_code_server, finished } || Pid <- lists:reverse(Waiting)],
+28 -16
View File
@@ -17,7 +17,7 @@ get_opt(Key, Dict) ->
end.
get_opts() ->
gen_server:call(elixir_code_server, compiler_options).
elixir_code_server:call(compiler_options).
%% Compiles the given string.
@@ -52,19 +52,28 @@ file_to_path(File, Path) when is_binary(File), is_binary(Path) ->
eval_forms(Forms, Line, Vars, S) ->
{ Module, I } = retrieve_module_name(),
{ Exprs, FS } = elixir_translator:translate(Forms, S),
ModuleForm = module_form(Exprs, Line, S#elixir_scope.file, Module, Vars),
Fun = module_form_fun(S#elixir_scope.module),
Form = module_form(Fun, Exprs, Line, S#elixir_scope.file, Module, Vars),
Args = [X || { _, _, _, X } <- Vars],
%% Pass { native, false } to speed up bootstrap
%% process when native is set to true
{ module(ModuleForm, S#elixir_scope.file, [{native,false}], true, fun(_, _) ->
Res = Module:'BOOTSTRAP'(S#elixir_scope.module, Args),
{ module(Form, S#elixir_scope.file, [{native,false}], true, fun(_, Binary) ->
Res = Module:Fun(S#elixir_scope.module, Args),
code:delete(Module),
case code:soft_purge(Module) of
true -> return_module_name(I);
false -> ok
%% If we have labeled locals, anonymous functions
%% were created and therefore we cannot ditch the
%% module
case beam_lib:chunks(Binary, [labeled_locals]) of
{ ok, { _, [{ labeled_locals, []}] } } ->
code:purge(Module),
return_module_name(I);
_ ->
ok
end,
Res
end), FS }.
@@ -98,7 +107,7 @@ module(Forms, File, Options, Bootstrap, Callback) when
core() ->
application:start(elixir),
gen_server:call(elixir_code_server, { compiler_options, [{docs,false},{internal,true}] }),
elixir_code_server:cast({ compiler_options, [{docs,false},{internal,true}] }),
[core_file(File) || File <- core_main()].
%% HELPERS
@@ -107,9 +116,11 @@ no_auto_import() ->
Bifs = [{ Name, Arity } || { Name, Arity } <- erlang:module_info(exports), erl_internal:bif(Name, Arity)],
{ attribute, 0, compile, { no_auto_import, Bifs } }.
module_form(Exprs, Line, File, Module, Vars) when
is_binary(File), is_list(Exprs), is_integer(Line), is_atom(Module) ->
module_form_fun(nil) -> '__FILE__';
module_form_fun(_) -> '__MODULE__'.
module_form(Fun, Exprs, Line, File, Module, Vars) when
is_binary(File), is_integer(Line), is_list(Exprs), is_atom(Module) ->
Cons = lists:foldr(fun({ _, _, Var, _ }, Acc) ->
{ cons, Line, { var, Line, Var }, Acc }
end, { nil, Line }, Vars),
@@ -119,8 +130,8 @@ module_form(Exprs, Line, File, Module, Vars) when
[
{ attribute, Line, file, { binary_to_list(File), 1 } },
{ attribute, Line, module, Module },
{ attribute, Line, export, [{ 'BOOTSTRAP', 2 }] },
{ function, Line, 'BOOTSTRAP', length(Args), [
{ attribute, Line, export, [{ Fun, 2 }] },
{ function, Line, Fun, length(Args), [
{ clause, Line, Args, [], Exprs }
] }
].
@@ -128,10 +139,10 @@ module_form(Exprs, Line, File, Module, Vars) when
%% Generate module names from code server.
retrieve_module_name() ->
gen_server:call(elixir_code_server, retrieve_module_name).
elixir_code_server:call(retrieve_module_name).
return_module_name(I) ->
gen_server:cast(elixir_code_server, { return_module_name, I }).
elixir_code_server:cast({ return_module_name, I }).
%% Receives a module Binary and outputs it in the given path.
@@ -158,21 +169,22 @@ core_main() ->
"lib/elixir/lib/kernel.ex",
"lib/elixir/lib/keyword.ex",
"lib/elixir/lib/list.ex",
"lib/elixir/lib/kernel/typespec.ex",
"lib/elixir/lib/kernel/typespec.ex",
"lib/elixir/lib/module.ex",
"lib/elixir/lib/record.ex",
"lib/elixir/lib/record/extractor.ex",
"lib/elixir/lib/macro.ex",
"lib/elixir/lib/macro/env.ex",
"lib/elixir/lib/exception.ex",
"lib/elixir/lib/code.ex",
"lib/elixir/lib/protocol.ex",
"lib/elixir/lib/enum.ex",
"lib/elixir/lib/exception.ex",
"lib/elixir/lib/binary/inspect.ex",
"lib/elixir/lib/binary/chars.ex",
"lib/elixir/lib/list/chars.ex",
"lib/elixir/lib/io.ex",
"lib/elixir/lib/file.ex",
"lib/elixir/lib/path.ex",
"lib/elixir/lib/access.ex",
"lib/elixir/lib/regex.ex",
"lib/elixir/lib/system.ex",
+31 -30
View File
@@ -7,14 +7,14 @@
wrap_definition/7,
store_definition/8,
store_each/7,
unwrap_stored_definitions/1,
unwrap_stored_definitions/2,
format_error/1]).
-include("elixir.hrl").
-compile({parse_transform, elixir_transform}).
%% Table management functions. Called internally.
table(Module) -> ?ELIXIR_ATOM_CONCAT([f, Module]).
table(Module) -> ?atom_concat([f, Module]).
build_table(Module) ->
FunctionTable = table(Module),
@@ -44,7 +44,8 @@ reset_last(Module) ->
%%
%% If we just analyzed the compiled structure (i.e. the function availables
%% before evaluating the function body), we would see both definitions.
wrap_definition(Kind, Line, Name, Args, Guards, Expr, S) ->
wrap_definition(Kind, Meta, Name, Args, Guards, Expr, S) ->
Line = ?line(Meta),
MetaS = elixir_scope:serialize(S),
Invoke = [
@@ -58,7 +59,7 @@ wrap_definition(Kind, Line, Name, Args, Guards, Expr, S) ->
MetaS
],
?ELIXIR_WRAP_CALL(Line, ?MODULE, store_definition, Invoke).
?wrap_call(Line, ?MODULE, store_definition, Invoke).
% Invoked by the wrap definition with the function abstract tree.
% Each function is then added to the function table.
@@ -74,7 +75,7 @@ store_definition(Kind, Line, Module, Name, Args, Guards, Body, RawS) ->
Expr = def_body(Line, Body),
CO = elixir_compiler:get_opts(),
Location = retrieve_file(Module, CO),
Location = retrieve_file(Line, Module, S, CO),
run_on_definition_callbacks(Kind, Line, Module, Name, Args, Guards, Body, S, CO),
{ Function, Defaults, TS } = translate_definition(Kind, Line, Name, Args, Guards, Expr, S),
@@ -99,7 +100,7 @@ store_definition(Kind, Line, Module, Name, Args, Guards, Body, RawS) ->
def_body(_Line, nil) -> nil;
def_body(_Line, [{ do, Expr }]) -> Expr;
def_body(Line, Else) -> { 'try', Line, [Else] }.
def_body(Line, Else) -> { 'try', [{line,Line}], [Else] }.
%% @on_definition
@@ -113,14 +114,14 @@ run_on_definition_callbacks(Kind, Line, Module, Name, Args, Guards, Expr, S, CO)
[Mod:Fun(Env, Kind, Name, Args, Guards, Expr) || { Mod, Fun } <- Callbacks]
end.
%% Retrieve @file
%% Retrieve @file or fallback to default
retrieve_file(Module, CO) ->
retrieve_file(Line, Module, S, CO) ->
case elixir_compiler:get_opt(internal, CO) of
true -> [];
true -> { binary_to_list(S#elixir_scope.file), Line };
_ ->
case 'Elixir.Module':get_attribute(Module, file) of
nil -> [];
nil -> { binary_to_list(S#elixir_scope.file), Line };
Else ->
'Elixir.Module':delete_attribute(Module, file),
Else
@@ -137,7 +138,7 @@ compile_super(_Module, _S) -> ok.
%% Translate the given call and expression given
%% and then store it in memory.
translate_definition(Kind, Line, Name, RawArgs, RawGuards, RawExpr, S) ->
translate_definition(Kind, Line, Name, RawArgs, RawGuards, RawExpr, S) when is_integer(Line) ->
Args = elixir_quote:linify(Line, RawArgs),
Guards = elixir_quote:linify(Line, RawGuards),
Expr = elixir_quote:linify(Line, RawExpr),
@@ -148,7 +149,7 @@ translate_definition(Kind, Line, Name, RawArgs, RawGuards, RawExpr, S) ->
%% not affect the arity of the stored function, but the clause
%% already contains it.
ExtendedArgs = case IsMacro of
true -> [{ '_@CALLER', Line, nil }|Args];
true -> [{ '_@CALLER', [{line,Line}], nil }|Args];
false -> Args
end,
@@ -170,7 +171,7 @@ translate_definition(Kind, Line, Name, RawArgs, RawGuards, RawExpr, S) ->
true ->
FBody = { 'match', Line,
{ 'var', Line, '__CALLER__' },
?ELIXIR_WRAP_CALL(Line, elixir_scope, to_ex_env, [{ var, Line, '_@CALLER' }])
?wrap_call(Line, elixir_scope, to_ex_env, [{ var, Line, '_@CALLER' }])
},
setelement(5, NClause, [FBody|element(5, NClause)]);
false -> NClause
@@ -186,53 +187,53 @@ is_macro(_) -> false.
% Unwrap the functions stored in the functions table.
% It returns a list of all functions to be exported, plus the macros,
% and the body of all functions.
unwrap_stored_definitions(Module) ->
unwrap_stored_definitions(File, Module) ->
Table = table(Module),
ets:delete(Table, last),
unwrap_stored_definition(ets:tab2list(Table), [], [], [], [], [], {[],[]}).
unwrap_stored_definition(ets:tab2list(Table), File, [], [], [], [], [], {[],[]}).
unwrap_stored_definition([Fun|T], Exports, Private, Def, Defmacro, Defmacrop, Functions) when element(2, Fun) == def ->
unwrap_stored_definition([Fun|T], File, Exports, Private, Def, Defmacro, Defmacrop, Functions) when element(2, Fun) == def ->
Tuple = element(1, Fun),
unwrap_stored_definition(
T, [Tuple|Exports], Private, [Tuple|Def], Defmacro, Defmacrop,
function_for_stored_definition(Fun, Functions)
T, File, [Tuple|Exports], Private, [Tuple|Def], Defmacro, Defmacrop,
function_for_stored_definition(Fun, File, Functions)
);
unwrap_stored_definition([Fun|T], Exports, Private, Def, Defmacro, Defmacrop, Functions) when element(2, Fun) == defmacro ->
unwrap_stored_definition([Fun|T], File, Exports, Private, Def, Defmacro, Defmacrop, Functions) when element(2, Fun) == defmacro ->
{ Name, Arity } = Tuple = element(1, Fun),
Macro = { ?ELIXIR_MACRO(Name), Arity + 1 },
Macro = { ?elixir_macro(Name), Arity + 1 },
unwrap_stored_definition(
T, [Macro|Exports], Private, Def, [Tuple|Defmacro], Defmacrop,
function_for_stored_definition(setelement(1, Fun, Macro), Functions)
T, File, [Macro|Exports], Private, Def, [Tuple|Defmacro], Defmacrop,
function_for_stored_definition(setelement(1, Fun, Macro), File, Functions)
);
unwrap_stored_definition([Fun|T], Exports, Private, Def, Defmacro, Defmacrop, Functions) when element(2, Fun) == defp ->
unwrap_stored_definition([Fun|T], File, Exports, Private, Def, Defmacro, Defmacrop, Functions) when element(2, Fun) == defp ->
unwrap_stored_definition(
T, Exports, [element(1, Fun)|Private], Def, Defmacro, Defmacrop,
function_for_stored_definition(Fun, Functions)
T, File, Exports, [element(1, Fun)|Private], Def, Defmacro, Defmacrop,
function_for_stored_definition(Fun, File, Functions)
);
unwrap_stored_definition([Fun|T], Exports, Private, Def, Defmacro, Defmacrop, Functions) when element(2, Fun) == defmacrop ->
unwrap_stored_definition([Fun|T], File, Exports, Private, Def, Defmacro, Defmacrop, Functions) when element(2, Fun) == defmacrop ->
Tuple = element(1, Fun),
unwrap_stored_definition(
T, Exports, [Tuple|Private], Def, Defmacro,
T, File, Exports, [Tuple|Private], Def, Defmacro,
[{ Tuple, element(3, Fun), element(5, Fun) }|Defmacrop], Functions
);
unwrap_stored_definition([], Exports, Private, Def, Defmacro, Defmacrop, {Functions,Tail}) ->
unwrap_stored_definition([], _File, Exports, Private, Def, Defmacro, Defmacrop, {Functions,Tail}) ->
{ Exports, Private, ordsets:from_list(Def), ordsets:from_list(Defmacro),
ordsets:from_list(Defmacrop), lists:reverse(Tail ++ Functions) }.
%% Helpers
function_for_stored_definition({{Name, Arity}, _, Line, _, _, [], _, Clauses}, {Functions,Tail}) ->
function_for_stored_definition({{Name, Arity}, _, Line, _, _, { File, _ }, _, Clauses}, File, {Functions,Tail}) ->
{
[{ function, Line, Name, Arity, lists:reverse(Clauses) }|Functions],
Tail
};
function_for_stored_definition({{Name, Arity}, _, Line, _, _, Location, _, Clauses}, {Functions,Tail}) ->
function_for_stored_definition({{Name, Arity}, _, Line, _, _, Location, _, Clauses}, _File, {Functions,Tail}) ->
{
Functions,
[
+3 -3
View File
@@ -38,7 +38,7 @@ extract_defaults([{'//', _, [_Expr, Default]}|T], Line, Counter, NewArgs, NewInv
extract_defaults(T, Line, Counter, NewArgs, [Default|NewInvoke]);
extract_defaults([_|T], Line, Counter, NewArgs, NewInvoke) ->
H = { ?ELIXIR_ATOM_CONCAT(["_@D", Counter]), Line, nil },
H = { ?atom_concat(["_@D", Counter]), Line, nil },
extract_defaults(T, Line, Counter + 1, [H|NewArgs], [H|NewInvoke]);
extract_defaults([], _Line, _Counter, NewArgs, NewInvoke) ->
@@ -49,10 +49,10 @@ extract_defaults([], _Line, _Counter, NewArgs, NewInvoke) ->
build_match([], _Line, Acc) -> Acc;
build_match([_|T], Line, Acc) ->
Var = { ?ELIXIR_ATOM_CONCAT(["_@D", length(T)]), Line, nil },
Var = { ?atom_concat(["_@D", length(T)]), Line, nil },
build_match(T, Line, [Var|Acc]).
% Given the invoked function name based on the kind
name_for_kind(Kind, Name) when Kind == defmacro; Kind == defmacrop -> ?ELIXIR_MACRO(Name);
name_for_kind(Kind, Name) when Kind == defmacro; Kind == defmacrop -> ?elixir_macro(Name);
name_for_kind(_Kind, Name) -> Name.
+7 -6
View File
@@ -20,15 +20,16 @@ is_defined(Module, Tuple) ->
_ -> false
end.
ensure_defined(Line, Module, Tuple, S) ->
ensure_defined(Meta, Module, Tuple, S) ->
case is_defined(Module, Tuple) of
true -> [];
_ -> elixir_errors:form_error(Line, S#elixir_scope.file, ?MODULE, { no_super, Module, Tuple })
_ -> elixir_errors:form_error(Meta, S#elixir_scope.file, ?MODULE, { no_super, Module, Tuple })
end.
%% Retrieve args defined for the given arity.
retrieve_args(Line, Arity, S) ->
retrieve_args(Meta, Arity, S) ->
Line = ?line(Meta),
{
[ { var, Line, super_arg(X) } || X <- lists:seq(1, Arity) ],
S#elixir_scope{name_args=true}
@@ -36,7 +37,7 @@ retrieve_args(Line, Arity, S) ->
%% Assign pseudo variables to the given vars.
assign_args(Line, Args, S) ->
assign_args(Line, Args, S) when is_integer(Line) ->
{ FArgs, _ } = lists:mapfoldl(fun(X, Acc) -> assign_args(Line, X, Acc, S) end, 1, Args),
FArgs.
@@ -45,7 +46,7 @@ assign_args(Line, X, Acc, _) ->
{ Match, Acc + 1 }.
super_arg(Counter) ->
?ELIXIR_ATOM_CONCAT(['_@S', Counter]).
?atom_concat(['_@S', Counter]).
%% Gets the name based on the function and stored overridables
@@ -54,7 +55,7 @@ name(Module, Function) ->
name(_Module, { Name, _ } = Function, Overridable) ->
{ Count, _, _ } = orddict:fetch(Function, Overridable),
?ELIXIR_ATOM_CONCAT([Name, " (overridable ", Count, ")"]).
?atom_concat([Name, " (overridable ", Count, ")"]).
%% Store
+51 -38
View File
@@ -5,7 +5,7 @@
-export([default_macros/0, default_functions/0, default_requires/0,
dispatch_require/6, dispatch_import/5,
require_function/5, import_function/4,
expand_import/8, expand_require/8,
expand_import/8, expand_require/8, find_import/4,
format_error/1, in_erlang_functions/0, in_erlang_macros/0]).
-include("elixir.hrl").
-compile({parse_transform, elixir_transform}).
@@ -20,47 +20,58 @@ default_macros() ->
default_requires() ->
[ ?BUILTIN, 'Elixir.Kernel.Typespec' ].
%% Function retrieval
import_function(Line, Name, Arity, S) ->
find_import(_Meta, Name, Arity, S) ->
Tuple = { Name, Arity },
case find_dispatch(Tuple, S#elixir_scope.functions) of
false ->
case find_dispatch(Tuple, S#elixir_scope.macros) of
false -> { { 'fun', Line, { function, Name, Arity } }, S };
false -> false;
Receiver -> Receiver
end;
Receiver -> Receiver
end.
%% Function retrieval
import_function(Meta, Name, Arity, S) ->
Tuple = { Name, Arity },
case find_dispatch(Tuple, S#elixir_scope.functions) of
false ->
case find_dispatch(Tuple, S#elixir_scope.macros) of
false -> { { 'fun', ?line(Meta), { function, Name, Arity } }, S };
_ -> false
end;
Receiver ->
elixir_import:record(import, Tuple, Receiver, S#elixir_scope.module),
remote_function(Line, Receiver, Name, Arity, S)
remote_function(Meta, Receiver, Name, Arity, S)
end.
require_function(Line, Receiver, Name, Arity, S) ->
require_function(Meta, Receiver, Name, Arity, S) ->
Tuple = { Name, Arity },
case is_element(Tuple, get_optional_macros(Receiver)) of
true -> false;
false -> remote_function(Line, Receiver, Name, Arity, S)
false -> remote_function(Meta, Receiver, Name, Arity, S)
end.
%% Function dispatch
dispatch_import(Line, Name, Args, S, Callback) ->
dispatch_import(Meta, Name, Args, S, Callback) ->
Module = S#elixir_scope.module,
Arity = length(Args),
Tuple = { Name, Arity },
case find_dispatch(Tuple, S#elixir_scope.functions) of
false ->
case expand_import(Line, Tuple, Args, Module, S#elixir_scope.function,
case expand_import(Meta, Tuple, Args, Module, S#elixir_scope.function,
S#elixir_scope.requires, S#elixir_scope.macros, S) of
{ error, noexpansion } ->
Callback();
{ error, internal } ->
elixir_import:record(import, Tuple, ?BUILTIN, Module),
elixir_macros:translate({ Name, Line, Args }, S);
elixir_macros:translate({ Name, Meta, Args }, S);
{ ok, _Receiver, Tree } ->
translate_expansion(Line, Tree, S)
translate_expansion(Meta, Tree, S)
end;
Receiver ->
elixir_import:record(import, Tuple, Receiver, Module),
@@ -68,32 +79,32 @@ dispatch_import(Line, Name, Args, S, Callback) ->
true -> erlang;
false -> Receiver
end,
elixir_translator:translate_each({ { '.', Line, [Endpoint, Name] }, Line, Args }, S)
elixir_translator:translate_each({ { '.', Meta, [Endpoint, Name] }, Meta, Args }, S)
end.
dispatch_require(Line, Receiver, Name, Args, S, Callback) ->
dispatch_require(Meta, Receiver, Name, Args, S, Callback) ->
Module = S#elixir_scope.module,
Arity = length(Args),
Tuple = { Name, Arity },
case (Receiver == Module) andalso is_element(Tuple, in_erlang_functions()) of
case (Receiver == ?BUILTIN) andalso is_element(Tuple, in_erlang_functions()) of
true ->
elixir_translator:translate_each({ { '.', Line, [erlang, Name] }, Line, Args }, S);
elixir_translator:translate_each({ { '.', Meta, [erlang, Name] }, Meta, Args }, S);
false ->
case expand_require(Line, Receiver, Tuple, Args, Module,
case expand_require(Meta, Receiver, Tuple, Args, Module,
S#elixir_scope.function, S#elixir_scope.requires, S) of
{ error, noexpansion } ->
Callback();
{ error, internal } ->
elixir_macros:translate({ Name, Line, Args }, S);
elixir_macros:translate({ Name, Meta, Args }, S);
{ ok, Tree } ->
translate_expansion(Line, Tree, S)
translate_expansion(Meta, Tree, S)
end
end.
%% Macros expansion
expand_import(Line, { Name, Arity } = Tuple, Args, Module, Function, Requires, Macros, SEnv) ->
expand_import(Meta, { Name, Arity } = Tuple, Args, Module, Function, Requires, Macros, SEnv) ->
case find_dispatch(Tuple, Macros) of
false ->
Fun = (Function /= Tuple) andalso
@@ -102,55 +113,56 @@ expand_import(Line, { Name, Arity } = Tuple, Args, Module, Function, Requires, M
false -> { error, noexpansion };
_ ->
elixir_import:record(import, Tuple, Module, Module),
{ ok, Module, expand_macro_fun(Line, Fun, Module, Name, Args, Module, Requires, SEnv) }
{ ok, Module, expand_macro_fun(Meta, Fun, Module, Name, Args, Module, Requires, SEnv) }
end;
?BUILTIN ->
case is_element(Tuple, in_erlang_macros()) of
true -> { error, internal };
false ->
elixir_import:record(import, Tuple, ?BUILTIN, Module),
{ ok, ?BUILTIN, expand_macro_named(Line, ?BUILTIN, Name, Arity, Args, Module, Requires, SEnv) }
{ ok, ?BUILTIN, expand_macro_named(Meta, ?BUILTIN, Name, Arity, Args, Module, Requires, SEnv) }
end;
Receiver ->
elixir_import:record(import, Tuple, Receiver, Module),
{ ok, Receiver, expand_macro_named(Line, Receiver, Name, Arity, Args, Module, Requires, SEnv) }
{ ok, Receiver, expand_macro_named(Meta, Receiver, Name, Arity, Args, Module, Requires, SEnv) }
end.
expand_require(Line, ?BUILTIN, { Name, Arity } = Tuple, Args, Module, _Function, Requires, SEnv) ->
expand_require(Meta, ?BUILTIN, { Name, Arity } = Tuple, Args, Module, _Function, Requires, SEnv) ->
case is_element(Tuple, in_erlang_macros()) of
true -> { error, internal };
false ->
case is_element(Tuple, in_elixir_macros()) of
true -> { ok, expand_macro_named(Line, ?BUILTIN, Name, Arity, Args, Module, Requires, SEnv) };
true -> { ok, expand_macro_named(Meta, ?BUILTIN, Name, Arity, Args, Module, Requires, SEnv) };
false -> { error, noexpansion }
end
end;
expand_require(Line, Receiver, { Name, Arity } = Tuple, Args, Module, Function, Requires, SEnv) ->
expand_require(Meta, Receiver, { Name, Arity } = Tuple, Args, Module, Function, Requires, SEnv) ->
Fun = (Module == Receiver) andalso (Function /= Tuple) andalso
elixir_def_local:macro_for(Tuple, false, Module),
case Fun of
false ->
case is_element(Tuple, get_optional_macros(Receiver)) of
true -> { ok, expand_macro_named(Line, Receiver, Name, Arity, Args, Module, Requires, SEnv) };
true -> { ok, expand_macro_named(Meta, Receiver, Name, Arity, Args, Module, Requires, SEnv) };
false -> { error, noexpansion }
end;
_ ->
elixir_import:record(import, Tuple, Receiver, Module),
{ ok, expand_macro_fun(Line, Fun, Receiver, Name, Args, Module, Requires, SEnv) }
{ ok, expand_macro_fun(Meta, Fun, Receiver, Name, Args, Module, Requires, SEnv) }
end.
%% Expansion helpers
expand_macro_fun(Line, Fun, Receiver, Name, Args, Module, Requires, SEnv) ->
expand_macro_fun(Meta, Fun, Receiver, Name, Args, Module, Requires, SEnv) ->
case (Receiver == Module) or is_element(Receiver, Requires) of
true -> ok;
false ->
Tuple = { unrequired_module, { Receiver, Name, length(Args), Requires } },
elixir_errors:form_error(Line, elixir_scope:filename(SEnv), ?MODULE, Tuple)
elixir_errors:form_error(Meta, elixir_scope:filename(SEnv), ?MODULE, Tuple)
end,
Line = ?line(Meta),
SArg = {Line,SEnv},
try
@@ -161,15 +173,15 @@ expand_macro_fun(Line, Fun, Receiver, Name, Args, Module, Requires, SEnv) ->
erlang:raise(Kind, Reason, munge_stacktrace(Info, erlang:get_stacktrace(), SArg))
end.
expand_macro_named(Line, Receiver, Name, Arity, Args, Module, Requires, SEnv) ->
ProperName = ?ELIXIR_MACRO(Name),
expand_macro_named(Meta, Receiver, Name, Arity, Args, Module, Requires, SEnv) ->
ProperName = ?elixir_macro(Name),
ProperArity = Arity + 1,
Fun = fun Receiver:ProperName/ProperArity,
expand_macro_fun(Line, Fun, Receiver, Name, Args, Module, Requires, SEnv).
expand_macro_fun(Meta, Fun, Receiver, Name, Args, Module, Requires, SEnv).
translate_expansion(Line, Tree, S) ->
translate_expansion(Meta, Tree, S) ->
{ TR, TS } = elixir_translator:translate_each(
elixir_quote:linify(Line, Tree),
elixir_quote:linify(?line(Meta), Tree),
S#elixir_scope{check_clauses=false}
),
{ TR, TS#elixir_scope{check_clauses=S#elixir_scope.check_clauses} }.
@@ -205,7 +217,9 @@ format_error({ unrequired_module,{Receiver, Name, Arity, Required }}) ->
%% INTROSPECTION
remote_function(Line, Receiver, Name, Arity, S) ->
remote_function(Meta, Receiver, Name, Arity, S) ->
Line = ?line(Meta),
Final =
case Receiver == ?BUILTIN andalso is_element({ Name, Arity }, in_erlang_functions()) of
true -> erlang;
@@ -236,7 +250,7 @@ get_optional_macros(Receiver) ->
in_elixir_functions() ->
try
?BUILTIN:'__info__'(functions) -- [{'__info__',1}]
?BUILTIN:'__info__'(functions)
catch
error:undef -> []
end.
@@ -372,6 +386,5 @@ in_erlang_macros() ->
{'or',2},
{'receive',1},
{'try',1},
{'var!',1},
{'xor',2}
].
+39 -36
View File
@@ -26,34 +26,34 @@ to_dot(L) -> L.
%% Raised during macros translation.
-spec syntax_error(non_neg_integer(), file:filename(), binary() | string()) -> no_return().
-spec syntax_error(non_neg_integer(), file:filename(), binary() | string(), list()) -> no_return().
-spec syntax_error(non_neg_integer() | list(), file:filename(), binary() | string()) -> no_return().
-spec syntax_error(non_neg_integer() | list(), file:filename(), binary() | string(), list()) -> no_return().
syntax_error(Line, File, Message) when is_list(Message) ->
syntax_error(Line, File, iolist_to_binary(Message));
syntax_error(Meta, File, Message) when is_list(Message) ->
syntax_error(Meta, File, iolist_to_binary(Message));
syntax_error(Line, File, Message) when is_binary(Message) ->
raise(Line, File, 'Elixir.SyntaxError', Message).
syntax_error(Meta, File, Message) when is_binary(Message) ->
raise(Meta, File, 'Elixir.SyntaxError', Message).
syntax_error(Line, File, Format, Args) ->
syntax_error(Meta, File, Format, Args) ->
Message = io_lib:format(Format, Args),
raise(Line, File, 'Elixir.SyntaxError', iolist_to_binary(Message)).
raise(Meta, File, 'Elixir.SyntaxError', iolist_to_binary(Message)).
%% Raised on tokenizing/parsing
-spec parse_error(non_neg_integer(), file:filename(), iolist() | atom(), [] | iolist()) -> no_return().
-spec parse_error(non_neg_integer() | list(), file:filename(), iolist() | atom(), [] | iolist()) -> no_return().
parse_error(Line, File, Error, []) ->
parse_error(Meta, File, Error, []) ->
Message = case Error of
"syntax error before: " -> <<"syntax error: expression is incomplete">>;
_ -> iolist_to_binary(Error)
end,
raise(Line, File, 'Elixir.TokenMissingError', Message);
raise(Meta, File, 'Elixir.TokenMissingError', Message);
parse_error(Line, File, "syntax error before: ", "'end'") ->
raise(Line, File, 'Elixir.SyntaxError', <<"unexpected token: end">>);
parse_error(Meta, File, "syntax error before: ", "'end'") ->
raise(Meta, File, 'Elixir.SyntaxError', <<"unexpected token: end">>);
parse_error(Line, File, Error, Token) ->
parse_error(Meta, File, Error, Token) ->
BinError = if
is_atom(Error) -> atom_to_binary(Error, utf8);
true -> iolist_to_binary(Error)
@@ -65,15 +65,15 @@ parse_error(Line, File, Error, Token) ->
end,
Message = <<BinError / binary, BinToken / binary >>,
raise(Line, File, 'Elixir.SyntaxError', Message).
raise(Meta, File, 'Elixir.SyntaxError', Message).
%% Raised during compilation
-spec form_error(non_neg_integer(), file:filename(), module(), any()) -> no_return().
form_error(Line, File, Module, Desc) ->
form_error(Meta, File, Module, Desc) ->
Message = iolist_to_binary(format_error(Module, Desc)),
raise(Line, File, 'Elixir.CompileError', Message).
raise(Meta, File, 'Elixir.CompileError', Message).
%% Shows a deprecation message
@@ -132,33 +132,36 @@ handle_file_error(File, {Line,Module,Desc}) ->
%% Assertions
assert_no_function_scope(_Line, _Kind, #elixir_scope{function=nil}) -> [];
assert_no_function_scope(Line, Kind, S) ->
syntax_error(Line, S#elixir_scope.file, "cannot invoke ~s inside a function", [Kind]).
assert_no_function_scope(_Meta, _Kind, #elixir_scope{function=nil}) -> [];
assert_no_function_scope(Meta, Kind, S) ->
syntax_error(Meta, S#elixir_scope.file, "cannot invoke ~s inside a function", [Kind]).
assert_no_assign_or_guard_scope(Line, Kind, S) ->
assert_no_assign_scope(Line, Kind, S),
assert_no_guard_scope(Line, Kind, S).
assert_no_assign_or_guard_scope(Meta, Kind, S) ->
assert_no_assign_scope(Meta, Kind, S),
assert_no_guard_scope(Meta, Kind, S).
assert_no_assign_scope(Line, Kind, #elixir_scope{context=assign} = S) ->
syntax_error(Line, S#elixir_scope.file, "cannot invoke ~s inside assign", [Kind]);
assert_no_assign_scope(_Line, _Kind, _S) -> [].
assert_no_assign_scope(Meta, Kind, #elixir_scope{context=assign} = S) ->
syntax_error(Meta, S#elixir_scope.file, "cannot invoke ~s inside assign", [Kind]);
assert_no_assign_scope(_Meta, _Kind, _S) -> [].
assert_no_guard_scope(Line, Kind, #elixir_scope{context=guard} = S) ->
syntax_error(Line, S#elixir_scope.file, "cannot invoke ~s inside guard", [Kind]);
assert_no_guard_scope(_Line, _Kind, _S) -> [].
assert_no_guard_scope(Meta, Kind, #elixir_scope{context=guard} = S) ->
syntax_error(Meta, S#elixir_scope.file, "cannot invoke ~s inside guard", [Kind]);
assert_no_guard_scope(_Meta, _Kind, _S) -> [].
assert_module_scope(Line, Kind, #elixir_scope{module=nil,file=File}) ->
syntax_error(Line, File, "cannot invoke ~s outside module", [Kind]);
assert_module_scope(_Line, _Kind, #elixir_scope{module=Module}) -> Module.
assert_module_scope(Meta, Kind, #elixir_scope{module=nil,file=File}) ->
syntax_error(Meta, File, "cannot invoke ~s outside module", [Kind]);
assert_module_scope(_Meta, _Kind, #elixir_scope{module=Module}) -> Module.
assert_function_scope(Line, Kind, #elixir_scope{function=nil,file=File}) ->
syntax_error(Line, File, "cannot invoke ~s outside function", [Kind]);
assert_function_scope(_Line, _Kind, #elixir_scope{function=Function}) -> Function.
assert_function_scope(Meta, Kind, #elixir_scope{function=nil,file=File}) ->
syntax_error(Meta, File, "cannot invoke ~s outside function", [Kind]);
assert_function_scope(_Meta, _Kind, #elixir_scope{function=Function}) -> Function.
%% Helpers
raise(Line, File, Kind, Message) ->
raise(Meta, File, Kind, Message) when is_list(Meta) ->
raise(?line(Meta), File, Kind, Message);
raise(Line, File, Kind, Message) when is_integer(Line) ->
Stacktrace = erlang:get_stacktrace(),
Exception = Kind:new([{description, Message}, {file, iolist_to_binary(File)}, {line, Line}]),
erlang:raise(error, Exception, Stacktrace).
+49 -45
View File
@@ -6,7 +6,7 @@
build_table/1, delete_table/1, record/4]).
-include("elixir.hrl").
table(Module) -> ?ELIXIR_ATOM_CONCAT([i, Module]).
table(Module) -> ?atom_concat([i, Module]).
build_table(Module) ->
ets:new(table(Module), [set, named_table, public]).
@@ -34,14 +34,14 @@ recorded_locals(Module) ->
%% Update the scope to consider the imports for aliases
%% based on the given options and selector.
import(Line, Ref, Opts, Selector, S) ->
import(Meta, Ref, Opts, Selector, S) ->
IncludeAll = (Selector == all) or (Selector == default),
SF = case IncludeAll or (Selector == functions) of
false -> S;
true ->
FunctionsFun = fun() -> remove_underscored(Selector, get_functions(Ref)) end,
Functions = calculate(Line, Ref, Opts,
FunctionsFun = fun(K) -> remove_underscored(K andalso Selector, get_functions(Ref)) end,
Functions = calculate(Meta, Ref, Opts,
S#elixir_scope.functions, FunctionsFun, S),
S#elixir_scope{functions=Functions}
end,
@@ -49,13 +49,13 @@ import(Line, Ref, Opts, Selector, S) ->
SM = case IncludeAll or (Selector == macros) of
false -> SF;
true ->
MacrosFun = fun() ->
MacrosFun = fun(K) ->
case IncludeAll of
true -> remove_underscored(Selector, get_optional_macros(Ref));
false -> get_macros(Line, Ref, SF)
true -> remove_underscored(K andalso Selector, get_optional_macros(Ref));
false -> get_macros(Meta, Ref, SF)
end
end,
Macros = calculate(Line, Ref, Opts,
Macros = calculate(Meta, Ref, Opts,
SF#elixir_scope.macros, MacrosFun, SF),
SF#elixir_scope{macros=Macros}
end,
@@ -66,27 +66,28 @@ import(Line, Ref, Opts, Selector, S) ->
%% Calculates the imports based on only and except
calculate(Line, Key, Opts, Old, AvailableFun, S) ->
calculate(Meta, Key, Opts, Old, AvailableFun, S) ->
File = S#elixir_scope.file,
All = keydelete(Key, Old),
New = case keyfind(only, Opts) of
{ only, RawOnly } ->
Only = expand_fun_arity(Line, only, RawOnly, S),
Only = expand_fun_arity(Meta, only, RawOnly, S),
case Only -- get_exports(Key) of
[{Name,Arity}|_] ->
Tuple = { invalid_import, { Key, Name, Arity } },
elixir_errors:form_error(Line, File, ?MODULE, Tuple);
_ -> intersection(Only, AvailableFun())
elixir_errors:form_error(Meta, File, ?MODULE, Tuple);
_ ->
intersection(Only, AvailableFun(false))
end;
false ->
case keyfind(except, Opts) of
false -> AvailableFun() ;
{ except, [] } -> AvailableFun();
false -> AvailableFun(true);
{ except, [] } -> AvailableFun(true);
{ except, RawExcept } ->
Except = expand_fun_arity(Line, except, RawExcept, S),
Except = expand_fun_arity(Meta, except, RawExcept, S),
case keyfind(Key, Old) of
false -> AvailableFun() -- Except;
false -> AvailableFun(true) -- Except;
{Key,ToRemove} -> ToRemove -- Except
end
end
@@ -99,33 +100,33 @@ calculate(Line, Key, Opts, Old, AvailableFun, S) ->
case Final of
[] -> All;
_ ->
ensure_no_conflicts(Line, File, Final, keydelete(Key, S#elixir_scope.macros)),
ensure_no_conflicts(Line, File, Final, keydelete(Key, S#elixir_scope.functions)),
ensure_no_in_erlang_macro_conflict(Line, File, Key, Final, internal_conflict),
ensure_no_conflicts(Meta, File, Final, keydelete(Key, S#elixir_scope.macros)),
ensure_no_conflicts(Meta, File, Final, keydelete(Key, S#elixir_scope.functions)),
ensure_no_in_erlang_macro_conflict(Meta, File, Key, Final, internal_conflict),
[{ Key, Final }|All]
end.
%% Ensure we are expanding macros and stuff
expand_fun_arity(Line, Kind, Value, S) ->
expand_fun_arity(Meta, Kind, Value, S) ->
{ TValue, _S } = elixir_translator:translate_each(Value, S),
cons_to_keywords(Line, Kind, TValue, S).
cons_to_keywords(Meta, Kind, TValue, S).
cons_to_keywords(Line, Kind, { cons, _, Left, { nil, _ } }, S) ->
[tuple_to_fun_arity(Line, Kind, Left, S)];
cons_to_keywords(Meta, Kind, { cons, _, Left, { nil, _ } }, S) ->
[tuple_to_fun_arity(Meta, Kind, Left, S)];
cons_to_keywords(Line, Kind, { cons, _, Left, Right }, S) ->
[tuple_to_fun_arity(Line, Kind, Left, S)|cons_to_keywords(Line, Kind, Right, S)];
cons_to_keywords(Meta, Kind, { cons, _, Left, Right }, S) ->
[tuple_to_fun_arity(Meta, Kind, Left, S)|cons_to_keywords(Meta, Kind, Right, S)];
cons_to_keywords(Line, Kind, _, S) ->
elixir_errors:syntax_error(Line, S#elixir_scope.file,
cons_to_keywords(Meta, Kind, _, S) ->
elixir_errors:syntax_error(Meta, S#elixir_scope.file,
"invalid value for :~s, expected a list with functions and arities", [Kind]).
tuple_to_fun_arity(_Line, _Kind, { tuple, _, [{ atom, _, Atom }, { integer, _, Integer }] }, _S) ->
tuple_to_fun_arity(_Meta, _Kind, { tuple, _, [{ atom, _, Atom }, { integer, _, Integer }] }, _S) ->
{ Atom, Integer };
tuple_to_fun_arity(Line, Kind, _, S) ->
elixir_errors:syntax_error(Line, S#elixir_scope.file,
tuple_to_fun_arity(Meta, Kind, _, S) ->
elixir_errors:syntax_error(Meta, S#elixir_scope.file,
"invalid value for :~s, expected a list with functions and arities", [Kind]).
%% Retrieve functions and macros from modules
@@ -144,13 +145,13 @@ get_functions(Module) ->
error:undef -> Module:module_info(exports)
end.
get_macros(Line, Module, S) ->
get_macros(Meta, Module, S) ->
try
Module:'__info__'(macros)
catch
error:undef ->
Tuple = { no_macros, Module },
elixir_errors:form_error(Line, S#elixir_scope.file, ?MODULE, Tuple)
elixir_errors:form_error(Meta, S#elixir_scope.file, ?MODULE, Tuple)
end.
get_optional_macros(Module) ->
@@ -170,13 +171,13 @@ get_optional_macros(Module) ->
%% Elixir "implemented in Erlang" macro. Checking if a local
%% conflicts with an import is automatically done by Erlang.
ensure_no_local_conflict(Line, File, Module, AllDefined) ->
ensure_no_in_erlang_macro_conflict(Line, File, Module, AllDefined, local_conflict).
ensure_no_local_conflict(Meta, File, Module, AllDefined) ->
ensure_no_in_erlang_macro_conflict(Meta, File, Module, AllDefined, local_conflict).
%% Find conlicts in the given list of functions with
%% the recorded set of imports.
ensure_no_import_conflict(Line, File, Module, AllDefined) ->
ensure_no_import_conflict(Meta, File, Module, AllDefined) ->
Table = table(Module),
Matches = [X || X <- AllDefined, ets:member(Table, X)],
@@ -184,7 +185,7 @@ ensure_no_import_conflict(Line, File, Module, AllDefined) ->
[{Name,Arity}|_] ->
Key = ets:lookup_element(Table, {Name, Arity }, 2),
Tuple = { import_conflict, { Key, Name, Arity } },
elixir_errors:form_error(Line, File, ?MODULE, Tuple);
elixir_errors:form_error(Meta, File, ?MODULE, Tuple);
[] ->
ok
end.
@@ -192,7 +193,7 @@ ensure_no_import_conflict(Line, File, Module, AllDefined) ->
%% Ensure the given functions don't clash with any
%% of Elixir non overridable macros.
ensure_no_in_erlang_macro_conflict(Line, File, Key, [{Name,Arity}|T], Reason) ->
ensure_no_in_erlang_macro_conflict(Meta, File, Key, [{Name,Arity}|T], Reason) ->
Values = lists:filter(fun({X,Y}) ->
(Name == X) andalso ((Y == '*') orelse (Y == Arity))
end, non_overridable_macros()),
@@ -200,27 +201,27 @@ ensure_no_in_erlang_macro_conflict(Line, File, Key, [{Name,Arity}|T], Reason) ->
case Values /= [] of
true ->
Tuple = { Reason, { Key, Name, Arity } },
elixir_errors:form_error(Line, File, ?MODULE, Tuple);
false -> ensure_no_in_erlang_macro_conflict(Line, File, Key, T, Reason)
elixir_errors:form_error(Meta, File, ?MODULE, Tuple);
false -> ensure_no_in_erlang_macro_conflict(Meta, File, Key, T, Reason)
end;
ensure_no_in_erlang_macro_conflict(_Line, _File, _Key, [], _) -> ok.
ensure_no_in_erlang_macro_conflict(_Meta, _File, _Key, [], _) -> ok.
%% Find conlicts in the given list of functions with the set of imports.
%% Used internally to ensure a newly imported fun or macro does not
%% conflict with an already imported set.
ensure_no_conflicts(Line, File, Functions, [{Key,Value}|T]) ->
ensure_no_conflicts(Meta, File, Functions, [{Key,Value}|T]) ->
Filtered = lists:filter(fun(X) -> lists:member(X, Functions) end, Value),
case Filtered of
[{Name,Arity}|_] ->
Tuple = { already_imported, { Key, Name, Arity } },
elixir_errors:form_error(Line, File, ?MODULE, Tuple);
elixir_errors:form_error(Meta, File, ?MODULE, Tuple);
[] ->
ensure_no_conflicts(Line, File, Functions, T)
ensure_no_conflicts(Meta, File, Functions, T)
end;
ensure_no_conflicts(_Line, _File, _Functions, _S) -> ok.
ensure_no_conflicts(_Meta, _File, _Functions, _S) -> ok.
%% ERROR HANDLING
@@ -317,5 +318,8 @@ non_overridable_macros() ->
{'super','*'},
{'super?',0},
{'bc','*'},
{'lc','*'}
{'lc','*'},
{'var!',1},
{'var!',2},
{'alias!',1}
].
+23 -18
View File
@@ -39,14 +39,17 @@ extract(Line, File, true, [$}|Rest], Buffer, [$}], Output, Last) ->
NewOutput = build_interpol(i, Line, File, Buffer, Output),
extract(Line, File, true, Rest, [], [], NewOutput, Last);
%% Check for available separators "", {}, [] and () inside interpolation
%% Check for available separators inside interpolation
extract(Line, File, Interpol, [C|Rest], Buffer, [C|Search], Output, Last) when C == $); C == $]; C == $}; C == $"; C == $' ->
extract(Line, File, Interpol, [C|Rest], Buffer, [C|Search], Output, Last) when C == $); C == $]; C == $}; C == $>; C == $"; C == $' ->
extract(Line, File, Interpol, Rest, [C|Buffer], Search, Output, Last);
extract(Line, File, Interpol, [C|Rest], Buffer, [_|_] = Search, Output, Last) when C == $"; C == $' ->
extract(Line, File, Interpol, Rest, [C|Buffer], [C|Search], Output, Last);
extract(Line, File, Interpol, [$<|Rest], Buffer, [_|_] = Search, Output, Last) ->
extract(Line, File, Interpol, Rest, [$<|Buffer], [$>|Search], Output, Last);
extract(Line, File, Interpol, [${|Rest], Buffer, [_|_] = Search, Output, Last) ->
extract(Line, File, Interpol, Rest, [${|Buffer], [$}|Search], Output, Last);
@@ -74,9 +77,6 @@ unescape_token(Other, _Map) -> Other.
% Unescape chars. For instance, "\" "n" (two chars) needs to be converted to "\n" (one char).
-define(is_octal(S), S >= $0 andalso S =< $7).
-define(is_hex(S), (S >= $0 andalso S =< $9) orelse (S >= $A andalso S =< $F)).
unescape_chars(String) ->
unescape_chars(String, fun unescape_map/1).
@@ -85,7 +85,7 @@ unescape_chars(String, Map) ->
Hex = Map($x) /= false,
unescape_chars(String, Map, Octals, Hex, <<>>).
unescape_chars(<<$\\,A,B,C,Rest/binary>>, Map, true, Hex, Acc) when ?is_octal(A), ?is_octal(B), ?is_octal(C) ->
unescape_chars(<<$\\,A,B,C,Rest/binary>>, Map, true, Hex, Acc) when ?is_octal(A), A =< $3, ?is_octal(B), ?is_octal(C) ->
append_escaped(Rest, Map, [A,B,C], true, Hex, Acc, 8);
unescape_chars(<<$\\,A,B,Rest/binary>>, Map, true, Hex, Acc) when ?is_octal(A), ?is_octal(B) ->
@@ -94,25 +94,28 @@ unescape_chars(<<$\\,A,B,Rest/binary>>, Map, true, Hex, Acc) when ?is_octal(A),
unescape_chars(<<$\\,A,Rest/binary>>, Map, true, Hex, Acc) when ?is_octal(A) ->
append_escaped(Rest, Map, [A], true, Hex, Acc, 8);
unescape_chars(<<$\\,$x,A,B,Rest/binary>>, Map, Octal, true, Acc) when ?is_hex(A), ?is_hex(B) ->
unescape_chars(<<$\\,P,A,B,Rest/binary>>, Map, Octal, true, Acc) when (P == $x orelse P == $X), ?is_hex(A), ?is_hex(B) ->
append_escaped(Rest, Map, [A,B], Octal, true, Acc, 16);
unescape_chars(<<$\\,$x,${,A,$},Rest/binary>>, Map, Octal, true, Acc) when ?is_hex(A) ->
unescape_chars(<<$\\,P,A,Rest/binary>>, Map, Octal, true, Acc) when (P == $x orelse P == $X), ?is_hex(A) ->
append_escaped(Rest, Map, [A], Octal, true, Acc, 16);
unescape_chars(<<$\\,$x,${,A,B,$},Rest/binary>>, Map, Octal, true, Acc) when ?is_hex(A), ?is_hex(B) ->
unescape_chars(<<$\\,P,${,A,$},Rest/binary>>, Map, Octal, true, Acc) when (P == $x orelse P == $X), ?is_hex(A) ->
append_escaped(Rest, Map, [A], Octal, true, Acc, 16);
unescape_chars(<<$\\,P,${,A,B,$},Rest/binary>>, Map, Octal, true, Acc) when (P == $x orelse P == $X), ?is_hex(A), ?is_hex(B) ->
append_escaped(Rest, Map, [A,B], Octal, true, Acc, 16);
unescape_chars(<<$\\,$x,${,A,B,C,$},Rest/binary>>, Map, Octal, true, Acc) when ?is_hex(A), ?is_hex(B), ?is_hex(C) ->
unescape_chars(<<$\\,P,${,A,B,C,$},Rest/binary>>, Map, Octal, true, Acc) when (P == $x orelse P == $X), ?is_hex(A), ?is_hex(B), ?is_hex(C) ->
append_escaped(Rest, Map, [A,B,C], Octal, true, Acc, 16);
unescape_chars(<<$\\,$x,${,A,B,C,D,$},Rest/binary>>, Map, Octal, true, Acc) when ?is_hex(A), ?is_hex(B), ?is_hex(C), ?is_hex(D) ->
unescape_chars(<<$\\,P,${,A,B,C,D,$},Rest/binary>>, Map, Octal, true, Acc) when (P == $x orelse P == $X), ?is_hex(A), ?is_hex(B), ?is_hex(C), ?is_hex(D) ->
append_escaped(Rest, Map, [A,B,C,D], Octal, true, Acc, 16);
unescape_chars(<<$\\,$x,${,A,B,C,D,E,$},Rest/binary>>, Map, Octal, true, Acc) when ?is_hex(A), ?is_hex(B), ?is_hex(C), ?is_hex(D), ?is_hex(E) ->
unescape_chars(<<$\\,P,${,A,B,C,D,E,$},Rest/binary>>, Map, Octal, true, Acc) when (P == $x orelse P == $X), ?is_hex(A), ?is_hex(B), ?is_hex(C), ?is_hex(D), ?is_hex(E) ->
append_escaped(Rest, Map, [A,B,C,D,E], Octal, true, Acc, 16);
unescape_chars(<<$\\,$x,${,A,B,C,D,E,F,$},Rest/binary>>, Map, Octal, true, Acc) when ?is_hex(A), ?is_hex(B), ?is_hex(C), ?is_hex(D), ?is_hex(E), ?is_hex(F) ->
unescape_chars(<<$\\,P,${,A,B,C,D,E,F,$},Rest/binary>>, Map, Octal, true, Acc) when (P == $x orelse P == $X), ?is_hex(A), ?is_hex(B), ?is_hex(C), ?is_hex(D), ?is_hex(E), ?is_hex(F) ->
append_escaped(Rest, Map, [A,B,C,D,E,F], Octal, true, Acc, 16);
unescape_chars(<<$\\,Escaped,Rest/binary>>, Map, Octals, Hex, Acc) ->
@@ -127,11 +130,12 @@ unescape_chars(<<Char, Rest/binary>>, Map, Octals, Hex, Acc) ->
unescape_chars(<<>>, _Map, _Octals, _Hex, Acc) -> Acc.
append_escaped(Rest, Map, List, Octal, Hex, Acc, Base) ->
Binary = unicode:characters_to_binary([list_to_integer(List, Base)]),
unescape_chars(Rest, Map, Octal, Hex, <<Acc/binary, Binary/binary>>).
Codepoint = list_to_integer(List, Base),
unescape_chars(Rest, Map, Octal, Hex, <<Acc/binary, Codepoint/utf8>>).
% Unescape Helpers
unescape_map($a) -> 7;
unescape_map($b) -> $\b;
unescape_map($d) -> $\d;
unescape_map($e) -> $\e;
@@ -165,12 +169,13 @@ wrap_interpol(_Line, Form) when is_binary(Form) ->
Form;
wrap_interpol(Line, Form) ->
{ '::', Line, [{ { '.', Line, ['Elixir.Binary.Chars', to_binary] }, Line, [Form]}, { binary, Line, nil }]}.
Meta = [{line,Line}],
{ '::', Meta, [{ { '.', Meta, ['Elixir.Binary.Chars', to_binary] }, Meta, [Form]}, { binary, Meta, nil }]}.
forms(String, StartLine, File) ->
case elixir_translator:forms(String, StartLine, File, []) of
{ ok, [] } -> nil;
{ ok, [Forms] } when not is_list(Forms) -> Forms;
{ ok, Forms } -> { '__block__', StartLine, Forms };
{ ok, Forms } -> { '__block__', [{line,StartLine}], Forms };
{ error, Tuple } -> throw({ interpolation_error, Tuple })
end.
end.
+84 -56
View File
@@ -5,31 +5,31 @@
-include("elixir.hrl").
-compile({parse_transform, elixir_transform}).
translate({ '<<>>', Line, Args } = Original, S) when is_list(Args) ->
translate({ '<<>>', Meta, Args } = Original, S) when is_list(Args) ->
case elixir_partials:handle(Original, S, allow_tail) of
error ->
case S#elixir_scope.context of
assign ->
build_bitstr(fun elixir_translator:translate_each/2, Args, Line, S);
build_bitstr(fun elixir_translator:translate_each/2, Args, Meta, S);
_ ->
{ TArgs, { SC, SV } } = build_bitstr(fun elixir_translator:translate_arg/2, Args, Line, { S, S }),
{ TArgs, { SC, SV } } = build_bitstr(fun elixir_translator:translate_arg/2, Args, Meta, { S, S }),
{ TArgs, umergec(SV, SC) }
end;
Else -> Else
end;
translate({ '{}', Line, Args } = Original, S) when is_list(Args) ->
translate({ '{}', Meta, Args } = Original, S) when is_list(Args) ->
case elixir_partials:handle(Original, S) of
error ->
{ TArgs, SE } = translate_args(Args, S),
{ {tuple, Line, TArgs}, SE };
{ { tuple, ?line(Meta), TArgs }, SE };
Else -> Else
end;
translate({ '[]', _Line, [] }, S) ->
translate({ '[]', _Meta, [] }, S) ->
{ { nil, 0 }, S };
translate({ '[]', _Line, Args } = Original, S) when is_list(Args) ->
translate({ '[]', _Meta, Args } = Original, S) when is_list(Args) ->
case elixir_partials:handle(Original, S, allow_tail) of
error ->
[RTail|RArgs] = lists:reverse(Args),
@@ -60,10 +60,10 @@ translate({ '[]', _Line, Args } = Original, S) when is_list(Args) ->
end;
translate({ Left, Right }, S) ->
translate({ '{}', 0, [Left, Right]}, S);
translate({ '{}', [], [Left, Right]}, S);
translate(Args, S) when is_list(Args) ->
translate({ '[]', 0, Args }, S);
translate({ '[]', [], Args }, S);
translate(Number, S) when is_integer(Number) ->
{ { integer, 0, Number }, S };
@@ -76,87 +76,115 @@ translate(Atom, S) when is_atom(Atom) ->
translate(Bitstring, S) when is_bitstring(Bitstring) ->
{ elixir_tree_helpers:abstract_syntax(Bitstring), S }.
%% Helpers
% Build a bitstring taking into accounts the following types:
%
% * If a bitstring or a list is given, we just append its items
% * If '|' is given, extract the bitstring information
% * If '::' is given, extract the bitstring information
% * All the other types are simply translated and handled with Erlang's default
%
build_bitstr(Fun, Exprs, Line, S) ->
{ Final, FinalS } = build_bitstr_each(Fun, Exprs, Line, S, []),
{ { bin, Line, lists:reverse(Final) }, FinalS }.
build_bitstr(Fun, Exprs, Meta, S) ->
{ Final, FinalS } = build_bitstr_each(Fun, Exprs, Meta, S, []),
{ { bin, ?line(Meta), lists:reverse(Final) }, FinalS }.
build_bitstr_each(_Fun, [], _Line, S, Acc) ->
build_bitstr_each(_Fun, [], _Meta, S, Acc) ->
{ Acc, S };
build_bitstr_each(Fun, [H|T], Line, S, Acc) when is_list(H) ->
{ NewAcc, NewS } = build_bitstr_each(Fun, H, Line, S, Acc),
build_bitstr_each(Fun, T, Line, NewS, NewAcc);
build_bitstr_each(Fun, [H|T], Line, S, Acc) when is_bitstring(H) ->
{ bin, _, Elements } = elixir_tree_helpers:abstract_syntax(H),
NewAcc = lists:foldl(fun(Element, FinalAcc) -> [Element|FinalAcc] end, Acc, Elements),
build_bitstr_each(Fun, T, Line, S, NewAcc);
build_bitstr_each(Fun, [{'::',_,[H,V]}|T], Line, S, Acc) ->
{ Expr, NS } = Fun(H, S),
build_bitstr_each(Fun, [{'::',_,[H,V]}|T], Meta, S, Acc) ->
%% Variables defined outside the binary can be accounted
%% on subparts, however we can't assign new variables.
case NS of
{ ES, _ } -> []; %% translate_arg, no assigns
_ -> ES = NS#elixir_scope{context=nil} %% translate_each, revert assigns
case S of
{ ES, _ } -> []; %% translate_arg, no assigns
_ -> ES = S#elixir_scope{context=nil} %% translate_each, revert assigns
end,
{ Size, Types } = extract_bit_values(Line, V, ES),
build_bitstr_each(Fun, T, Line, NS, [{ bin_element, Line, Expr, Size, Types }|Acc]);
{ Size, Types } = extract_bit_values(Meta, V, ES),
build_bitstr_each(Fun, T, Meta, S, Acc, H, Size, Types);
build_bitstr_each(Fun, [H|T], Line, S, Acc) ->
build_bitstr_each(Fun, [H|T], Meta, S, Acc) ->
build_bitstr_each(Fun, T, Meta, S, Acc, H, default, default).
build_bitstr_each(Fun, T, Meta, S, Acc, H, Size, Types) when is_list(H) ->
case is_default_or_utf(Types) of
true ->
{ NewAcc, NewS } = lists:foldl(fun(L, { LA, LS }) ->
{ FL, FS } = Fun(L, LS),
{ [{ bin_element, ?line(Meta), FL, Size, Types }|LA], FS }
end, { Acc, S }, H),
build_bitstr_each(Fun, T, Meta, NewS, NewAcc);
false ->
build_bitstr_default(Fun, T, Meta, S, Acc, H, Size, Types)
end;
build_bitstr_each(Fun, T, Meta, S, Acc, H, Size, Types) when is_bitstring(H) ->
case is_default_or_utf(Types) of
true ->
Line = ?line(Meta),
{ bin, _, Elements } = elixir_tree_helpers:abstract_syntax(H),
NewAcc = lists:foldl(fun({ bin_element, _, Expr, _, _ }, FinalAcc) ->
[{ bin_element, Line, Expr, Size, Types }|FinalAcc]
end, Acc, Elements),
build_bitstr_each(Fun, T, Meta, S, NewAcc);
false ->
build_bitstr_default(Fun, T, Meta, S, Acc, H, Size, Types)
end;
build_bitstr_each(Fun, T, Meta, S, Acc, H, Size, Types) ->
build_bitstr_default(Fun, T, Meta, S, Acc, H, Size, Types).
build_bitstr_default(Fun, T, Meta, S, Acc, H, Size, Types) ->
{ Expr, NS } = Fun(H, S),
build_bitstr_each(Fun, T, Line, NS, [{ bin_element, Line, Expr, default, default }|Acc]).
build_bitstr_each(Fun, T, Meta, NS, [{ bin_element, ?line(Meta), Expr, Size, Types }|Acc]).
is_default_or_utf(default) -> true;
is_default_or_utf([UTF|_]) when UTF == utf8; UTF == utf16; UTF == utf32 -> true;
is_default_or_utf([_|T]) -> is_default_or_utf(T);
is_default_or_utf([]) -> false.
%% Extra bitstring specifiers
extract_bit_values(Line, V, S) when is_list(V) ->
extract_bit_values(Line, V, default, [], S);
extract_bit_values(Meta, V, S) when is_list(V) ->
extract_bit_values(Meta, V, default, [], S);
extract_bit_values(Line, V, S) ->
extract_bit_values(Line, [V], S).
extract_bit_values(Meta, V, S) ->
extract_bit_values(Meta, [V], S).
extract_bit_values(Line, [{ Value, _CallLine, Atom }|T] = All, Size, Types, S)
extract_bit_values(Meta, [{ Value, _Meta, Atom }|T] = All, Size, Types, S)
when is_atom(Value) andalso (is_atom(Atom) orelse Atom == []) ->
case extract_bit_type(Value, Types) of
{ error, unknown } ->
handle_unknown_specifier(Line, All, Size, Types, S);
handle_unknown_specifier(Meta, All, Size, Types, S);
NewTypes when is_list(NewTypes) ->
extract_bit_values(Line, T, Size, NewTypes, S)
extract_bit_values(Meta, T, Size, NewTypes, S)
end;
extract_bit_values(Line, [{ Value, CallLine, [_] = Args }|T] = All, Size, Types, S) when is_atom(Value) ->
extract_bit_values(Meta, [{ Value, CallMeta, [_] = Args }|T] = All, Size, Types, S) when is_atom(Value) ->
{ TArgs, _ } = elixir_translator:translate_args(Args, S),
case extract_bit_type_or_size(Value, TArgs, Size, Types) of
{ error, unknown } ->
handle_unknown_specifier(Line, All, Size, Types, S);
handle_unknown_specifier(Meta, All, Size, Types, S);
{ error, Msg } ->
elixir_errors:syntax_error(CallLine, S#elixir_scope.file, Msg);
elixir_errors:syntax_error(CallMeta, S#elixir_scope.file, Msg);
{ NewSize, NewTypes } ->
extract_bit_values(Line, T, NewSize, NewTypes, S)
extract_bit_values(Meta, T, NewSize, NewTypes, S)
end;
extract_bit_values(Line, [{ '|', _, [Left, Right] }], Size, Types, S) ->
Expanded = 'Elixir.Macro':expand(Right, elixir_scope:to_ex_env({ Line, S })),
extract_bit_values(Line, [Left|join_expansion(Expanded,[])], Size, Types, S);
extract_bit_values(Meta, [Size|T], default, Types, S) when is_integer(Size) andalso Size >= 0 ->
extract_bit_values(Meta, T, {integer, Meta, Size}, Types, S);
extract_bit_values(Line, [_|_] = All, Size, Types, S) ->
handle_unknown_specifier(Line, All, 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);
extract_bit_values(_Line, [], Size, [], _S) ->
extract_bit_values(Meta, [_|_] = All, Size, Types, S) ->
handle_unknown_specifier(Meta, All, Size, Types, S);
extract_bit_values(_Meta, [], Size, [], _S) ->
{ Size, default };
extract_bit_values(_Line, [], Size, Types, _S) ->
extract_bit_values(_Meta, [], Size, Types, _S) ->
{ Size, lists:reverse(Types) }.
extract_bit_type(Value, Types) when
@@ -188,12 +216,12 @@ extract_bit_type_or_size(unit, _Args, _Other, _Types) ->
extract_bit_type_or_size(_Value, _Args, _Other, _Types) ->
{ error, unknown }.
handle_unknown_specifier(Line, [H|T], Size, Types, S) ->
case 'Elixir.Macro':expand(H, elixir_scope:to_ex_env({ Line, S })) of
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(Line, S#elixir_scope.file, "unknown bitstring specifier ~s", ['Elixir.Macro':to_binary(H)]);
elixir_errors:syntax_error(Meta, S#elixir_scope.file, "unknown bitstring specifier ~s", ['Elixir.Macro':to_binary(H)]);
E ->
extract_bit_values(Line, join_expansion(E,T), Size, Types, S)
extract_bit_values(Meta, join_expansion(E,T), Size, Types, S)
end.
join_expansion({ '__block__', _, [Expanded] }, Tail) -> join_expansion(Expanded, Tail);
+113 -121
View File
@@ -15,127 +15,127 @@
%% Operators
translate({ '+', _Line, [Expr] }, S) when is_number(Expr) ->
translate({ '+', _Meta, [Expr] }, S) when is_number(Expr) ->
translate_each(Expr, S);
translate({ '-', _Line, [Expr] }, S) when is_number(Expr) ->
translate({ '-', _Meta, [Expr] }, S) when is_number(Expr) ->
translate_each(-1 * Expr, S);
translate({ Op, Line, Exprs }, S) when is_list(Exprs),
translate({ Op, Meta, Exprs }, S) when is_list(Exprs),
Op == '<-' orelse Op == '--' ->
assert_no_assign_or_guard_scope(Line, Op, S),
translate_each({ '__op__', Line, [Op|Exprs] }, S);
assert_no_assign_or_guard_scope(Meta, Op, S),
translate_each({ '__op__', Meta, [Op|Exprs] }, S);
translate({ Op, Line, Exprs }, S) when is_list(Exprs),
translate({ Op, Meta, Exprs }, S) when is_list(Exprs),
Op == '+' orelse Op == '-' orelse Op == '*' orelse Op == '/' orelse
Op == '++' orelse Op == 'not' orelse Op == 'and' orelse Op == 'or' orelse
Op == 'xor' orelse Op == '<' orelse Op == '>' orelse Op == '<=' orelse
Op == '>=' orelse Op == '==' orelse Op == '!=' orelse Op == '===' orelse
Op == '!==' ->
translate_each({ '__op__', Line, [Op|Exprs] }, S);
translate_each({ '__op__', Meta, [Op|Exprs] }, S);
translate({ '!', Line, [{ '!', _, [Expr] }] }, S) ->
translate({ '!', Meta, [{ '!', _, [Expr] }] }, S) ->
{ TExpr, SE } = translate_each(Expr, S),
elixir_tree_helpers:convert_to_boolean(Line, TExpr, true, S#elixir_scope.context == guard, SE);
elixir_tree_helpers:convert_to_boolean(?line(Meta), TExpr, true, S#elixir_scope.context == guard, SE);
translate({ '!', Line, [Expr] }, S) ->
translate({ '!', Meta, [Expr] }, S) ->
{ TExpr, SE } = translate_each(Expr, S),
elixir_tree_helpers:convert_to_boolean(Line, TExpr, false, S#elixir_scope.context == guard, SE);
elixir_tree_helpers:convert_to_boolean(?line(Meta), TExpr, false, S#elixir_scope.context == guard, SE);
translate({ in, Line, [Left, Right] }, #elixir_scope{extra_guards=nil} = S) ->
{ _, TExpr, TS } = translate_in(Line, Left, Right, S),
translate({ in, Meta, [Left, Right] }, #elixir_scope{extra_guards=nil} = S) ->
{ _, TExpr, TS } = translate_in(Meta, Left, Right, S),
{ TExpr, TS };
translate({ in, Line, [Left, Right] }, #elixir_scope{extra_guards=Extra} = S) ->
{ TVar, TExpr, TS } = translate_in(Line, Left, Right, S),
translate({ in, Meta, [Left, Right] }, #elixir_scope{extra_guards=Extra} = S) ->
{ TVar, TExpr, TS } = translate_in(Meta, Left, Right, S),
{ TVar, TS#elixir_scope{extra_guards=[TExpr|Extra]} };
%% Functions
translate({ function, Line, [[{do,{ '->',_,Pairs}}]] }, S) ->
assert_no_assign_or_guard_scope(Line, 'function', S),
elixir_translator:translate_fn(Line, Pairs, S);
translate({ function, Meta, [[{do,{ '->',_,Pairs}}]] }, S) ->
assert_no_assign_or_guard_scope(Meta, 'function', S),
elixir_translator:translate_fn(Meta, Pairs, S);
translate({ function, Line, [{ '/', _, [{{ '.', _ ,[M, F] }, _ , [] }, A]}] }, S) ->
translate({ function, Line, [M, F, A] }, S);
translate({ function, Meta, [{ '/', _, [{{ '.', _ ,[M, F] }, _ , [] }, A]}] }, S) ->
translate({ function, Meta, [M, F, A] }, S);
translate({ function, Line, [{ '/', _, [{F, _, Q}, A]}] }, S) when is_atom(Q) ->
translate({ function, Line, [F, A] }, S);
translate({ function, Meta, [{ '/', _, [{F, _, Q}, A]}] }, S) when is_atom(Q) ->
translate({ function, Meta, [F, A] }, S);
translate({ function, Line, [_] }, S) ->
assert_no_assign_or_guard_scope(Line, 'function', S),
syntax_error(Line, S#elixir_scope.file, "invalid args for function");
translate({ function, Meta, [_] }, S) ->
assert_no_assign_or_guard_scope(Meta, 'function', S),
syntax_error(Meta, S#elixir_scope.file, "invalid args for function");
translate({ function, Line, [_, _] = Args }, S) ->
assert_no_assign_or_guard_scope(Line, 'function', S),
translate({ function, Meta, [_, _] = Args }, S) ->
assert_no_assign_or_guard_scope(Meta, 'function', S),
case translate_args(Args, S) of
{ [{atom,_,Name}, {integer,_,Arity}], SA } ->
case elixir_dispatch:import_function(Line, Name, Arity, SA) of
false -> syntax_error(Line, S#elixir_scope.file, "cannot convert a macro to a function");
case elixir_dispatch:import_function(Meta, Name, Arity, SA) of
false -> syntax_error(Meta, S#elixir_scope.file, "cannot convert a macro to a function");
Else -> Else
end;
_ ->
syntax_error(Line, S#elixir_scope.file, "cannot dynamically retrieve local function. use function(module, fun, arity) instead")
syntax_error(Meta, S#elixir_scope.file, "cannot dynamically retrieve local function. use function(module, fun, arity) instead")
end;
translate({ function, Line, [_,_,_] = Args }, S) when is_list(Args) ->
assert_no_assign_or_guard_scope(Line, 'function', S),
translate({ function, Meta, [_,_,_] = Args }, S) when is_list(Args) ->
assert_no_assign_or_guard_scope(Meta, 'function', S),
{ [A,B,C], SA } = translate_args(Args, S),
{ { 'fun', Line, { function, A, B, C } }, SA };
{ { 'fun', ?line(Meta), { function, A, B, C } }, SA };
%% @
translate({'@', Line, [{ Name, _, Args }]}, S) when is_list(Args) andalso (Name == typep orelse Name == type orelse Name == spec orelse Name == callback orelse Name == opaque) ->
translate({'@', Meta, [{ Name, _, [Arg] }]}, S) when Name == typep; Name == type; Name == spec; Name == callback; Name == opaque ->
case elixir_compiler:get_opt(internal) of
true -> { { nil, Line }, S };
true -> { { nil, ?line(Meta) }, S };
false ->
Call = { { '.', Line, ['Elixir.Kernel.Typespec', spec_to_macro(Name)] }, Line, Args },
Call = { { '.', Meta, ['Elixir.Kernel.Typespec', spec_to_macro(Name)] }, Meta, [Arg] },
translate_each(Call, S)
end;
translate({'@', Line, [{ Name, _, Args }]}, S) ->
assert_module_scope(Line, '@', S),
translate({'@', Meta, [{ Name, _, Args }]}, S) ->
assert_module_scope(Meta, '@', S),
case is_reserved_data(Name) andalso elixir_compiler:get_opt(internal) of
true ->
{ { nil, Line }, S };
{ { nil, ?line(Meta) }, S };
_ ->
case Args of
[Arg] ->
case S#elixir_scope.function of
nil ->
translate_each({
{ '.', Line, ['Elixir.Module', put_attribute] },
Line,
[ { '__MODULE__', Line, false }, Name, Arg ]
{ '.', Meta, ['Elixir.Module', put_attribute] },
Meta,
[ { '__MODULE__', Meta, false }, Name, Arg ]
}, S);
_ ->
syntax_error(Line, S#elixir_scope.file,
syntax_error(Meta, S#elixir_scope.file,
"cannot dynamically set attribute @~s inside a function", [Name])
end;
_ when is_atom(Args) or (Args == []) ->
case S#elixir_scope.function of
nil ->
translate_each({
{ '.', Line, ['Elixir.Module', get_attribute] },
Line,
[ { '__MODULE__', Line, false }, Name ]
{ '.', Meta, ['Elixir.Module', get_attribute] },
Meta,
[ { '__MODULE__', Meta, false }, Name ]
}, S);
_ ->
Contents = 'Elixir.Module':get_attribute(S#elixir_scope.module, Name),
{ elixir_tree_helpers:abstract_syntax(Contents), S }
end;
_ ->
syntax_error(Line, S#elixir_scope.file, "expected 0 or 1 argument for @~s, got: ~p", [Name, length(Args)])
syntax_error(Meta, S#elixir_scope.file, "expected 0 or 1 argument for @~s, got: ~p", [Name, length(Args)])
end
end;
%% Case
translate({'case', Line, [Expr, KV]}, S) ->
assert_no_assign_or_guard_scope(Line, 'case', S),
Clauses = elixir_clauses:get_pairs(Line, do, KV, S),
translate({'case', Meta, [Expr, KV]}, S) ->
assert_no_assign_or_guard_scope(Meta, 'case', S),
Clauses = elixir_clauses:get_pairs(Meta, do, KV, S),
{ TExpr, NS } = translate_each(Expr, S),
RClauses = case elixir_tree_helpers:returns_boolean(TExpr) of
@@ -143,132 +143,124 @@ translate({'case', Line, [Expr, KV]}, S) ->
false -> Clauses
end,
{ TClauses, TS } = elixir_clauses:match(Line, RClauses, NS),
{ { 'case', Line, TExpr, TClauses }, TS };
{ TClauses, TS } = elixir_clauses:match(Meta, RClauses, NS),
{ { 'case', ?line(Meta), TExpr, TClauses }, TS };
%% Try
translate({'try', Line, [Clauses]}, RawS) ->
translate({'try', Meta, [Clauses]}, RawS) ->
S = RawS#elixir_scope{noname=true},
assert_no_assign_or_guard_scope(Line, 'try', S),
assert_no_assign_or_guard_scope(Meta, 'try', S),
Do = proplists:get_value('do', Clauses, []),
{ TDo, SB } = elixir_translator:translate([Do], S),
Do = proplists:get_value('do', Clauses, nil),
{ TDo, SB } = elixir_translator:translate_each(Do, S),
Catch = [Tuple || { X, _ } = Tuple <- Clauses, X == 'rescue' orelse X == 'catch'],
{ TCatch, SC } = elixir_try:clauses(Line, Catch, umergec(S, SB)),
{ TCatch, SC } = elixir_try:clauses(Meta, Catch, umergec(S, SB)),
{ TAfter, SA } = case lists:keyfind('after', 1, Clauses) of
{ 'after', After } -> elixir_translator:translate([After], umergec(S, SC));
false -> { [], SC }
end,
After = proplists:get_value('after', Clauses, nil),
{ TAfter, SA } = elixir_translator:translate_each(After, umergec(S, SC)),
{ { 'try', Line, unpack(TDo), [], TCatch, unpack(TAfter) }, umergec(RawS, SA) };
Else = elixir_clauses:get_pairs(Meta, else, Clauses, S),
{ TElse, SE } = elixir_clauses:match(Meta, Else, umergec(S, SA)),
{ { 'try', ?line(Meta), pack(TDo), TElse, TCatch, pack(TAfter) }, umergec(RawS, SE) };
%% Receive
translate({'receive', Line, [KV] }, S) ->
assert_no_assign_or_guard_scope(Line, 'receive', S),
Do = elixir_clauses:get_pairs(Line, do, KV, S, true),
translate({'receive', Meta, [KV] }, S) ->
assert_no_assign_or_guard_scope(Meta, 'receive', S),
Do = elixir_clauses:get_pairs(Meta, do, KV, S, true),
case lists:keyfind('after', 1, KV) of
false ->
{ TClauses, SC } = elixir_clauses:match(Line, Do, S),
{ { 'receive', Line, TClauses }, SC };
{ TClauses, SC } = elixir_clauses:match(Meta, Do, S),
{ { 'receive', ?line(Meta), TClauses }, SC };
_ ->
After = elixir_clauses:get_pairs(Line, 'after', KV, S),
{ TClauses, SC } = elixir_clauses:match(Line, Do ++ After, S),
After = elixir_clauses:get_pairs(Meta, 'after', KV, S),
{ TClauses, SC } = elixir_clauses:match(Meta, Do ++ After, S),
{ FClauses, TAfter } = elixir_tree_helpers:split_last(TClauses),
{ _, _, [FExpr], _, FAfter } = TAfter,
{ { 'receive', Line, FClauses, FExpr, FAfter }, SC }
{ { 'receive', ?line(Meta), FClauses, FExpr, FAfter }, SC }
end;
%% Definitions
translate({defmodule, Line, [Ref, KV]}, S) ->
translate({defmodule, Meta, [Ref, KV]}, S) ->
{ TRef, _ } = translate_each(Ref, S),
Block = case lists:keyfind(do, 1, KV) of
{ do, DoValue } -> DoValue;
false -> syntax_error(Line, S#elixir_scope.file, "expected do: argument in defmodule")
false -> syntax_error(Meta, S#elixir_scope.file, "expected do: argument in defmodule")
end,
{ FRef, FS } = case TRef of
{ atom, _, Module } ->
NewModule = module_ref(Ref, Module, S#elixir_scope.module),
FullModule = module_ref(Ref, Module, S#elixir_scope.module),
RS = case Module == NewModule of
true -> S;
false ->
element(2, translate_each({
alias, Line, [NewModule, [{as, elixir_aliases:first(Module)}]]
}, S))
RS = case elixir_aliases:nesting(S#elixir_scope.module, FullModule) of
false -> S;
Alias -> element(2, translate_each({ alias, Meta, [FullModule, [{ as, Alias }]] }, S))
end,
{
{ atom, Line, NewModule },
RS#elixir_scope{scheduled=[NewModule|S#elixir_scope.scheduled]}
{ atom, Meta, FullModule },
RS#elixir_scope{scheduled=[FullModule|S#elixir_scope.scheduled]}
};
_ ->
{ TRef, S }
end,
{ elixir_module:translate(Line, FRef, Block, S#elixir_scope{check_clauses=true}), FS };
{ elixir_module:translate(Meta, FRef, Block, S#elixir_scope{check_clauses=true}), FS };
translate({Kind, Line, [Call]}, S) when ?FUNS(Kind) ->
translate({Kind, Line, [Call, nil]}, S);
translate({Kind, Meta, [Call]}, S) when ?FUNS(Kind) ->
translate({Kind, Meta, [Call, nil]}, S);
translate({Kind, Line, [Call, Expr]}, S) when ?FUNS(Kind) ->
assert_module_scope(Line, Kind, S),
assert_no_function_scope(Line, Kind, S),
translate({Kind, Meta, [Call, Expr]}, S) when ?FUNS(Kind) ->
assert_module_scope(Meta, Kind, S),
assert_no_function_scope(Meta, Kind, S),
{ TCall, Guards } = elixir_clauses:extract_guards(Call),
{ Name, Args } = case elixir_clauses:extract_args(TCall) of
error -> syntax_error(Line, S#elixir_scope.file,
error -> syntax_error(Meta, S#elixir_scope.file,
"invalid syntax in ~s ~s", [Kind, 'Elixir.Macro':to_binary(TCall)]);
Tuple -> Tuple
end,
assert_no_aliases_name(Line, Name, Args, S),
assert_no_aliases_name(Meta, Name, Args, S),
TName = elixir_tree_helpers:abstract_syntax(Name),
TArgs = elixir_tree_helpers:abstract_syntax(Args),
TGuards = elixir_tree_helpers:abstract_syntax(Guards),
TExpr = elixir_tree_helpers:abstract_syntax(Expr),
TName = elixir_tree_helpers:abstract_syntax(Name),
TArgs = elixir_tree_helpers:abstract_syntax(Args),
TGuards = elixir_tree_helpers:abstract_syntax(Guards),
TExpr = elixir_tree_helpers:abstract_syntax(Expr),
{ elixir_def:wrap_definition(Kind, Line, TName, TArgs, TGuards, TExpr, S), S };
{ elixir_def:wrap_definition(Kind, Meta, TName, TArgs, TGuards, TExpr, S), S };
translate({Kind, Line, [Name, Args, Guards, Expr]}, S) when ?FUNS(Kind) ->
assert_module_scope(Line, Kind, S),
assert_no_function_scope(Line, Kind, S),
translate({Kind, Meta, [Name, Args, Guards, Expr]}, S) when ?FUNS(Kind) ->
assert_module_scope(Meta, Kind, S),
assert_no_function_scope(Meta, Kind, S),
{ TName, NS } = translate_each(Name, S),
{ TArgs, AS } = translate_each(Args, NS),
{ TGuards, GS } = translate_each(Guards, AS),
{ TExpr, ES } = translate_each(Expr, GS),
{ elixir_def:wrap_definition(Kind, Line, TName, TArgs, TGuards, TExpr, ES), ES };
{ elixir_def:wrap_definition(Kind, Meta, TName, TArgs, TGuards, TExpr, ES), ES };
%% Apply - Optimize apply by checking what doesn't need to be dispatched dynamically
translate({ apply, Line, [Left, Right, Args] }, S) when is_list(Args) ->
translate({ apply, Meta, [Left, Right, Args] }, S) when is_list(Args) ->
{ TLeft, SL } = translate_each(Left, S),
{ TRight, SR } = translate_each(Right, umergec(S, SL)),
translate_apply(Line, TLeft, TRight, Args, S, SL, SR);
translate_apply(Meta, TLeft, TRight, Args, S, SL, SR);
translate({ apply, Line, Args }, S) ->
translate({ apply, Meta, Args }, S) ->
{ TArgs, NS } = translate_args(Args, S),
{ ?ELIXIR_WRAP_CALL(Line, erlang, apply, TArgs), NS };
%% Handle forced variables
translate({ 'var!', _, [{Name, Line, Atom}] }, S) when is_atom(Name), is_atom(Atom) ->
elixir_scope:translate_var(Line, Name, nil, S);
translate({ 'var!', Line, [_] }, S) ->
syntax_error(Line, S#elixir_scope.file, "invalid args for var!").
{ ?wrap_call(?line(Meta), erlang, apply, TArgs), NS }.
%% Helpers
translate_in(Line, Left, Right, S) ->
translate_in(Meta, Left, Right, S) ->
Line = ?line(Meta),
{ TLeft, SL } = case Left of
{ '_', _, Atom } when is_atom(Atom) ->
elixir_scope:build_erl_var(Line, S);
@@ -311,7 +303,7 @@ translate_in(Line, Left, Right, S) ->
decreasing_compare(Line, Var, Start, End) } }
end;
_ ->
syntax_error(Line, S#elixir_scope.file, "invalid args for operator in")
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")
end,
case Cache of
@@ -335,13 +327,13 @@ rewrite_case_clauses([{do,[{in,_,[{'_',_,_},[false,nil]]}],False},{do,[{'_',_,_}
rewrite_case_clauses(Clauses) ->
Clauses.
module_ref(_Raw, Module, nil) ->
module_ref(Raw, Module, Nesting) when is_atom(Raw); Nesting == nil ->
Module;
module_ref({ '__aliases__', _, ['Elixir'|_]}, Module, _Nesting) ->
module_ref({ '__aliases__', _, ['Elixir'|_] }, Module, _Nesting) ->
Module;
module_ref(_F, Module, Nesting) ->
module_ref(_Raw, Module, Nesting) ->
elixir_aliases:concat([Nesting, Module]).
is_reserved_data(moduledoc) -> true;
@@ -354,13 +346,13 @@ spec_to_macro(opaque) -> defopaque;
spec_to_macro(spec) -> defspec;
spec_to_macro(callback) -> defcallback.
% Unpack a list of expressions from a block.
unpack([{ '__block__', _, Exprs }]) -> Exprs;
unpack(Exprs) -> Exprs.
% Pack a list of expressions from a block.
pack({ 'block', _, Exprs }) -> Exprs;
pack(Expr) -> [Expr].
assert_no_aliases_name(Line, '__aliases__', [Atom], #elixir_scope{file=File}) when is_atom(Atom) ->
assert_no_aliases_name(Meta, '__aliases__', [Atom], #elixir_scope{file=File}) when is_atom(Atom) ->
Message = "function names should start with lowercase characters or underscore, invalid name ~s",
syntax_error(Line, File, Message, [atom_to_binary(Atom, utf8)]);
syntax_error(Meta, File, Message, [atom_to_binary(Atom, utf8)]);
assert_no_aliases_name(_Line, _Aliases, _Args, _S) ->
assert_no_aliases_name(_Meta, _Aliases, _Args, _S) ->
ok.
+11 -9
View File
@@ -31,7 +31,7 @@ data_table(Module) ->
Module.
docs_table(Module) ->
?ELIXIR_ATOM_CONCAT([o, Module]).
?atom_concat([o, Module]).
%% TRANSFORMATION FUNCTIONS
@@ -39,25 +39,27 @@ docs_table(Module) ->
%% The abstract form for extra arguments may be given and they
%% will be passed to the invoked function.
translate(Line, Ref, Block, S) ->
translate(Meta, Ref, Block, S) ->
Line = ?line(Meta),
MetaBlock = elixir_tree_helpers:abstract_syntax(Block),
{ MetaS, Vars } = elixir_scope:serialize_with_vars(Line, S),
Args = [{integer, Line, Line}, Ref, MetaBlock, Vars, MetaS],
?ELIXIR_WRAP_CALL(Line, ?MODULE, compile, Args).
?wrap_call(Line, ?MODULE, compile, Args).
%% The compilation hook.
compile(Line, Module, Block, Vars, #elixir_scope{} = S) when is_atom(Module) ->
C = elixir_compiler:get_opts(),
File = S#elixir_scope.file,
FileList = binary_to_list(File),
check_module_availability(Line, File, Module, C),
build(Line, File, Module),
try
Result = eval_form(Line, Module, Block, Vars, S),
{ Export, Private, Def, Defmacro, Defmacrop, Functions } = elixir_def:unwrap_stored_definitions(Module),
{ Export, Private, Def, Defmacro, Defmacrop, Functions } = elixir_def:unwrap_stored_definitions(FileList, Module),
{ All, Forms0 } = functions_form(Line, File, Module, Export, Private, Def, Defmacro, Defmacrop, Functions, C),
Forms1 = specs_form(Line, Module, Defmacro, Defmacrop, Forms0, C),
@@ -67,7 +69,7 @@ compile(Line, Module, Block, Vars, #elixir_scope{} = S) when is_atom(Module) ->
elixir_import:ensure_no_import_conflict(Line, File, Module, All),
Final = [
{ attribute, Line, file, { binary_to_list(File), Line } },
{ attribute, Line, file, { FileList, Line } },
{ attribute, Line, module, Module } | Forms2
],
@@ -219,7 +221,7 @@ translate_spec({ Spec, Rest }, Defmacro, Defmacrop) ->
case ordsets:is_element(Spec, Defmacro) of
true ->
{ Name, Arity } = Spec,
{ { ?ELIXIR_MACRO(Name), Arity + 1 }, spec_for_macro(Rest) };
{ { ?elixir_macro(Name), Arity + 1 }, spec_for_macro(Rest) };
false ->
{ Spec, Rest }
end
@@ -284,8 +286,7 @@ add_info_function(Line, File, Module, Export, Functions, Def, Defmacro, C) ->
end.
functions_clause(Def) ->
All = ordsets:add_element({'__info__',1}, Def),
{ clause, 0, [{ atom, 0, functions }], [], [elixir_tree_helpers:abstract_syntax(All)] }.
{ clause, 0, [{ atom, 0, functions }], [], [elixir_tree_helpers:abstract_syntax(Def)] }.
macros_clause(Module, Def, Defmacro) ->
All = handle_builtin_macros(Module, Def, Defmacro),
@@ -324,9 +325,10 @@ eval_callbacks(Line, Module, Name, Args, RawS) ->
Binding = binding_for_eval(Module, []),
Callbacks = lists:reverse(ets:lookup_element(data_table(Module), Name, 2)),
Requires = S#elixir_scope.requires,
Meta = [{line,Line}],
lists:foreach(fun({M,F}) ->
Expr = { { '.', Line, [M,F] }, Line, Args },
Expr = { { '.', Meta, [M,F] }, Meta, Args },
Scope = case ordsets:is_element(M, Requires) of
true -> S;
false -> S#elixir_scope{requires=ordsets:add_element(M, Requires)}
+66 -55
View File
@@ -1,12 +1,9 @@
% Grammar for the Elixir language done with yecc
% Copyright (C) 2011 Jose Valim
Nonterminals
grammar expr_list
expr paren_expr block_expr fn_expr bracket_expr call_expr bracket_at_expr max_expr
base_expr matched_expr matched_op_expr unmatched_expr op_expr
add_op mult_op unary_op two_op pipeline_op bin_concat_op
match_op send_op default_op when_op pipe_op in_op inc_op stab_op range_op
match_op send_op default_op when_op pipe_op in_op inc_op range_op
andand_op oror_op and_op or_op comp_expr_op colon_colon_op three_op at_op
open_paren close_paren empty_paren
open_bracket close_bracket
@@ -14,7 +11,7 @@ Nonterminals
open_bit close_bit
base_comma_expr comma_expr optional_comma_expr matched_comma_expr
call_args call_args_parens call_args_parens_not_one call_args_no_parens parens_call
stab_expr stab_expr_list
stab 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
@@ -26,14 +23,14 @@ Nonterminals
Terminals
identifier kw_identifier punctuated_identifier
bracket_identifier paren_identifier do_identifier block_identifier
fn 'end' '__aliases__'
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 ',' '&' '|' '.' '^' '@' '<-' '<>' '->' '/>' '=~'
eol ',' '&' '|' '.' '^' '@' '<-' '<>' '->' '|>' '/>' '=~'
'&&' '||' '!' '...' '..'
'<<<' '>>>' '&&&' '|||' '^^^' '~~~'
.
@@ -41,7 +38,7 @@ Terminals
Rootsymbol grammar.
Left 5 do.
Right 10 '->'.
Right 10 stab_op.
Left 20 ','. % Solve nested call_args conflicts
Right 30 colon_colon_op.
Right 40 when_op.
@@ -144,13 +141,12 @@ 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_identifier('$1', '$2'), ?line('$1'), '$3' ++ '$4' }.
block_expr -> parens_call call_args_parens call_args_parens do_block : build_nested_parens('$1', '$2', '$3' ++ '$4').
block_expr -> dot_punctuated_identifier call_args_no_parens do_block : build_identifier('$1', '$2' ++ '$3').
block_expr -> dot_do_identifier do_block : build_identifier('$1', '$2').
block_expr -> dot_identifier call_args_no_parens do_block : build_identifier('$1', '$2' ++ '$3').
fn_expr -> fn_eol stab_expr_list end_eol : build_fn('$1', build_stab(lists:reverse('$2'))).
fn_expr -> fn_eol '->' grammar 'end' : build_fn('$1', { '->', ?line('$2'), [{ [], build_block('$3') }] }).
fn_expr -> fn_eol stab end_eol : build_fn('$1', build_stab(lists:reverse('$2'))).
fn_expr -> call_expr : '$1'.
call_expr -> dot_punctuated_identifier call_args_no_parens : build_identifier('$1', '$2').
@@ -163,10 +159,10 @@ call_expr -> max_expr : '$1'.
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_identifier('$1', '$2'), ?line('$1'), '$3' }.
max_expr -> parens_call call_args_parens call_args_parens : build_nested_parens('$1', '$2', '$3').
max_expr -> dot_ref : '$1'.
max_expr -> base_expr : '$1'.
max_expr -> open_paren stab_expr_list close_paren : build_stab(lists:reverse('$2')).
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').
@@ -176,27 +172,27 @@ bracket_at_expr -> at_op max_expr bracket_access : build_access(build_unary_op('
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('$1'), ?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 -> '__aliases__' : '$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 -> '&' : '$1'.
base_expr -> '...' : { ?op('$1'), ?line('$1'), [] }.
base_expr -> '&' : { '&', [{line,?line('$1')}], ?exprs('$1') }.
base_expr -> '...' : { ?op('$1'), [{line,?line('$1')}], [] }.
base_expr -> sigil : build_sigil('$1').
%% Blocks
do_block -> do_eol 'end' : [[{do,nil}]].
do_block -> do_eol stab_expr_list end_eol : [[{ do, build_stab(lists:reverse('$2')) }]].
do_block -> do_eol stab end_eol : [[{ do, build_stab(lists:reverse('$2')) }]].
do_block -> do_eol block_list 'end' : [[{ do, nil }|'$2']].
do_block -> do_eol stab_expr_list eol block_list 'end' : [[{ do, build_stab(lists:reverse('$2')) }|'$4']].
do_block -> do_eol stab eol block_list 'end' : [[{ do, build_stab(lists:reverse('$2')) }|'$4']].
fn_eol -> 'fn' : '$1'.
fn_eol -> 'fn' eol : '$1'.
@@ -210,14 +206,18 @@ end_eol -> eol 'end' : '$2'.
block_eol -> block_identifier : '$1'.
block_eol -> block_identifier eol : '$1'.
stab_expr_list -> stab_expr : ['$1'].
stab_expr_list -> stab_expr_list eol stab_expr : ['$3'|'$1'].
stab -> stab_expr : ['$1'].
stab -> stab eol stab_expr : ['$3'|'$1'].
stab_expr -> expr : '$1'.
stab_expr -> call_args_no_parens stab_op expr : build_op('$2', '$1', '$3').
stab_expr -> call_args_parens_not_one stab_op expr : build_op('$2', '$1', '$3').
stab_expr -> stab_op stab_maybe_expr : build_op('$1', [], '$2').
stab_expr -> call_args_no_parens stab_op stab_maybe_expr : build_op('$2', '$1', '$3').
stab_expr -> call_args_parens_not_one stab_op stab_maybe_expr : build_op('$2', '$1', '$3').
block_item -> block_eol stab_expr_list eol : { ?exprs('$1'), build_stab(lists:reverse('$2')) }.
stab_maybe_expr -> 'expr' : '$1'.
stab_maybe_expr -> '$empty' : nil.
block_item -> block_eol stab eol : { ?exprs('$1'), build_stab(lists:reverse('$2')) }.
block_item -> block_eol : { ?exprs('$1'), nil }.
block_list -> block_item : ['$1'].
@@ -270,6 +270,8 @@ two_op -> '**' eol : '$1'.
pipeline_op -> '=~' : '$1'.
pipeline_op -> '=~' eol : '$1'.
pipeline_op -> '|>' : '$1'.
pipeline_op -> '|>' eol : '$1'.
pipeline_op -> '/>' : '$1'.
pipeline_op -> '/>' eol : '$1'.
@@ -360,7 +362,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_ref -> matched_expr dot_op aliases : build_dot_ref('$2', '$1', '$3').
dot_op_identifier -> op_identifier : '$1'.
dot_op_identifier -> matched_expr dot_op op_identifier : build_dot('$2', '$1', '$3').
@@ -378,7 +380,7 @@ dot_punctuated_identifier -> punctuated_identifier : '$1'.
dot_punctuated_identifier -> matched_expr dot_op punctuated_identifier : build_dot('$2', '$1', '$3').
parens_call -> dot_paren_identifier : '$1'.
parens_call -> matched_expr dot_call_op : { '.', ?line('$2'), ['$1'] }. % Fun/local calls
parens_call -> matched_expr dot_call_op : { '.', [{line,?line('$2')}], ['$1'] }. % Fun/local calls
% Function calls
@@ -448,8 +450,8 @@ tuple -> open_curly paren_expr ',' call_args close_curly : build_tuple('$1', ['
% Bitstrings
bit_string -> open_bit '>>' : { '<<>>', ?line('$1'), [] }.
bit_string -> open_bit call_args close_bit : { '<<>>', ?line('$1'), '$2' }.
bit_string -> open_bit '>>' : { '<<>>', [{line,?line('$1')}], [] }.
bit_string -> open_bit call_args close_bit : { '<<>>', [{line,?line('$1')}], '$2' }.
Erlang code.
@@ -467,58 +469,62 @@ Erlang code.
%% Operators
build_op({ _, _, _ } = Op, Left, Right) ->
{ ?exprs(Op), ?line(Op), [Left, Right] };
{ ?exprs(Op), [{line,?line(Op)}], [Left, Right] };
build_op({ BOp, Line }, { UOp, _, [Left] }, Right) when ?rearrange_bop(BOp), ?rearrange_uop(UOp) ->
{ UOp, Line, [{ BOp, Line, [Left, Right] }] };
{ UOp, [{line,Line}], [{ BOp, [{line,Line}], [Left, Right] }] };
build_op(Op, Left, Right) ->
{ ?op(Op), ?line(Op), [Left, Right] }.
{ ?op(Op), [{line,?line(Op)}], [Left, Right] }.
build_unary_op(Op, Expr) ->
{ ?op(Op), ?line(Op), [Expr] }.
{ ?op(Op), [{line,?line(Op)}], [Expr] }.
build_tuple(_Marker, [Left, Right]) ->
{ Left, Right };
build_tuple(Marker, Args) ->
{ '{}', ?line(Marker), Args }.
{ '{}', [{line,?line(Marker)}], Args }.
%% Blocks
build_block([nil]) -> { '__block__', 0, [nil] };
build_block([{Op,_,[_]}]=Exprs) when ?rearrange_uop(Op) -> { '__block__', 0, Exprs };
build_block([nil]) -> { '__block__', [], [nil] };
build_block([{Op,_,[_]}]=Exprs) when ?rearrange_uop(Op) -> { '__block__', [], Exprs };
build_block([Expr]) when not is_list(Expr) -> Expr;
build_block(Exprs) -> { '__block__', 0, Exprs }.
build_block(Exprs) -> { '__block__', [], Exprs }.
%% Dots
build_dot_ref(Dot, { '__aliases__', _, Left }, { '__aliases__', _, Right }) ->
{ '__aliases__', ?line(Dot), Left ++ Right };
build_dot_ref(Dot, { '__aliases__', _, Left }, { 'aliases', _, Right }) ->
{ '__aliases__', [{line,?line(Dot)}], Left ++ Right };
build_dot_ref(Dot, Other, { '__aliases__', _, Right }) ->
{ '__aliases__', ?line(Dot), [Other|Right] }.
build_dot_ref(Dot, Other, { 'aliases', _, Right }) ->
{ '__aliases__', [{line,?line(Dot)}], [Other|Right] }.
build_dot(Dot, Left, Right) ->
{ '.', ?line(Dot), [Left, extract_identifier(Right)] }.
{ '.', [{line,?line(Dot)}], [Left, extract_identifier(Right)] }.
%% Identifiers
build_identifier({ '.', Line, _ } = Dot, Args) ->
build_nested_parens(Dot, Args1, Args2) ->
Identifier = build_identifier(Dot, Args1),
{ Identifier, ?line(Identifier), Args2 }.
build_identifier({ '.', Meta, _ } = Dot, Args) ->
FArgs = case Args of
nil -> [];
_ -> Args
end,
{ Dot, Line, FArgs };
{ Dot, Meta, FArgs };
build_identifier({ Keyword, Line }, Args) when Keyword == fn ->
{ fn, Line, Args };
{ fn, [{line,Line}], Args };
build_identifier({ op_identifier, Line, Identifier }, Args) ->
{ '__ambiguousop__', Line, [{ Identifier, Line, nil }|Args] };
{ '__ambiguousop__', [{line,Line}], [{ Identifier, [{line,Line}], nil }|Args] };
build_identifier({ _, Line, Identifier }, Args) ->
{ Identifier, Line, Args }.
{ Identifier, [{line,Line}], Args }.
extract_identifier({ Kind, _, Identifier }) when
Kind == identifier; Kind == punctuated_identifier; Kind == bracket_identifier;
@@ -530,32 +536,37 @@ extract_identifier(Other) -> Other.
%% Fn
build_fn(Op, Stab) ->
{ fn, ?line(Op), [[{ do, Stab }]] }.
{ fn, [{line,?line(Op)}], [[{ do, Stab }]] }.
%% Access
build_access(Expr, Access) ->
Line = ?line(Access),
{ { '.', Line, ['Elixir-Kernel', access] }, ?line(Access), [ Expr, ?op(Access) ] }.
Meta = [{line,?line(Access)}],
{ { '.', Meta, ['Elixir-Kernel', access] }, Meta, [ Expr, ?op(Access) ] }.
%% Interpolation aware
build_sigil({ sigil, Line, Sigil, Parts, Modifiers }) ->
{ list_to_atom([$_,$_,Sigil,$_,$_]), Line, [ { '<<>>', Line, Parts }, Modifiers ] }.
Meta = [{line,Line}],
{ list_to_atom([$_,$_,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, Args }.
build_bin_string({ bin_string, Line, Args }) -> { '<<>>', [{line,Line}], Args }.
build_list_string({ list_string, _Line, [H] }) when is_binary(H) -> binary_to_list(H);
build_list_string({ list_string, Line, Args }) -> { { '.', Line, [erlang, binary_to_list] }, Line, [{ '<<>>', Line, Args}] }.
build_list_string({ list_string, Line, Args }) ->
Meta = [{line,Line}],
{ { '.', Meta, [erlang, binary_to_list] }, Meta, [{ '<<>>', Meta, Args}] }.
build_atom({ atom, _Line, Atom }) when is_atom(Atom) -> Atom;
build_atom({ atom, Line, Args }) -> { { '.', Line, [erlang, binary_to_atom] }, Line, [{ '<<>>', Line, Args }, utf8] }.
build_atom({ atom, Line, Args }) ->
Meta = [{line,Line}],
{ { '.', Meta, [erlang, binary_to_atom] }, Meta, [{ '<<>>', Meta, Args }, utf8] }.
%% Keywords
build_stab([{ '->', Line, [Left, Right] }|T]) ->
{ '->', Line, build_stab(T, Left, [Right], []) };
build_stab([{ '->', Meta, [Left, Right] }|T]) ->
{ '->', Meta, build_stab(T, Left, [Right], []) };
build_stab(Else) ->
build_block(Else).
+14 -14
View File
@@ -6,28 +6,28 @@
handle(Original, S) ->
handle(Original, S, default).
handle({ _, Line, Args } = Original, S, Opt) when is_list(Args), S#elixir_scope.context /= assign ->
handle({ _, Meta, Args } = Original, S, Opt) when is_list(Args), S#elixir_scope.context /= assign ->
case convert(Args, S, Opt) of
{ Call, Def, SC } when Def /= [] ->
Final = validate(Line, Def, SC),
Final = validate(Meta, Def, SC),
Block = setelement(3, Original, Call),
elixir_translator:translate_fn(Line, [{ Final, Block }], SC);
elixir_translator:translate_fn(Meta, [{ Final, Block }], SC);
_ -> error
end;
handle(_Original, _S, _Opt) ->
error.
validate(Line, Def, S) ->
validate(Line, lists:sort(Def), 1, S).
validate(Meta, Def, S) ->
validate(Meta, lists:sort(Def), 1, S).
validate(Line, [{ Pos, Item }|T], Pos, S) ->
[Item|validate(Line, T, Pos + 1, S)];
validate(Meta, [{ Pos, Item }|T], Pos, S) ->
[Item|validate(Meta, T, Pos + 1, S)];
validate(Line, [{ Pos, _ }|_], Expected, S) ->
elixir_errors:syntax_error(Line, S#elixir_scope.file, "partial variable &~w cannot be defined without &~w", [Pos, Expected]);
validate(Meta, [{ Pos, _ }|_], Expected, S) ->
elixir_errors:syntax_error(Meta, S#elixir_scope.file, "partial variable &~w cannot be defined without &~w", [Pos, Expected]);
validate(_Line, [], _Pos, _S) ->
validate(_Meta, [], _Pos, _S) ->
[].
%% This function receives arguments and then checks
@@ -37,14 +37,14 @@ validate(_Line, [], _Pos, _S) ->
%% function definition and the third one is the new scope.
convert(List, S, Opt) -> convert(List, S, Opt, [], []).
convert([{'|', Line, [_, _] = Args}|T], S, allow_tail, CallAcc, DefAcc) ->
convert([{'|', Meta, [_, _] = Args}|T], S, allow_tail, CallAcc, DefAcc) ->
{ NewArgs, NewDef, NewS } = convert(Args, S, allow_tail, [], DefAcc),
convert(T, NewS, allow_tail, [{ '|', Line, NewArgs}|CallAcc], NewDef);
convert(T, NewS, allow_tail, [{ '|', Meta, NewArgs}|CallAcc], NewDef);
convert([{'&', Line, [Pos]}|T], S, Opt, CallAcc, DefAcc) ->
convert([{'&', Meta, [Pos]}|T], S, Opt, CallAcc, DefAcc) ->
case lists:keyfind(Pos, 1, DefAcc) of
false ->
{ Var, SC } = elixir_scope:build_ex_var(Line, S),
{ Var, SC } = elixir_scope:build_ex_var(?line(Meta), S),
convert(T, SC, Opt, [Var|CallAcc], [{Pos,Var}|DefAcc]);
{Pos,Var} ->
convert(T, S, Opt, [Var|CallAcc], DefAcc)
+96 -56
View File
@@ -3,75 +3,100 @@
-include("elixir.hrl").
%% Apply the line from site call on quoted contents.
linify(Line, Exprs) when is_integer(Line) ->
do_linify(Line, Exprs).
linify(Line, { Left, 0, Right }) ->
{ linify(Line, Left), Line, linify(Line, Right) };
do_linify(Line, { Left, Meta, Right }) ->
NewMeta = case ?line(Meta) of
0 -> lists:keystore(line, 1, Meta, { line, Line });
_ -> Meta
end,
{ do_linify(Line, Left), NewMeta, do_linify(Line, Right) };
linify(Line, Tuple) when is_tuple(Tuple) ->
list_to_tuple(linify(Line, tuple_to_list(Tuple)));
do_linify(Line, { Left, Right }) ->
{ do_linify(Line, Left), do_linify(Line, Right) };
linify(Line, List) when is_list(List) ->
[linify(Line, X) || X <- List];
do_linify(Line, List) when is_list(List) ->
[do_linify(Line, X) || X <- List];
linify(_, Else) -> Else.
do_linify(_, Else) -> Else.
%% Join quoted checks for quote arguments at runtime
%% in order to properly insert them into the tree
join_quoted(Line, Left, { '__aliases__', _, Args }, nil, _File) ->
{ '__aliases__', Line, [Left|Args] };
join_quoted(Meta, Left, { '__aliases__', _, Args }, nil, _File) ->
{ '__aliases__', Meta, [Left|Args] };
join_quoted(Line, Left, Right, nil, _File) when is_atom(Right) ->
join_quoted(Meta, Left, Right, nil, _File) when is_atom(Right) ->
case atom_to_list(Right) of
"Elixir-" ++ _ ->
{ '__aliases__', Line, [Left, Right] };
{ '__aliases__', Meta, [Left, Right] };
_ ->
{ { '.', Line, [Left, Right] }, Line, [] }
{ { '.', Meta, [Left, Right] }, Meta, [] }
end;
join_quoted(Line, Left, Right, Args, _File) when is_atom(Right) ->
{ { '.', Line, [Left, Right] }, Line, Args };
join_quoted(Meta, Left, Right, Args, _File) when is_atom(Right) ->
{ { '.', Meta, [Left, Right] }, Meta, Args };
join_quoted(Line, _Left, _Right, _Args, File) ->
elixir_errors:syntax_error(Line, File, "expected unquote after dot to return an atom or an alias").
join_quoted(Meta, _Left, _Right, _Args, File) ->
elixir_errors:syntax_error(Meta, File, "expected unquote after dot to return an atom or an alias").
%% Translation
quote({ 'unquote_splicing', Line, _ } = Expr, #elixir_quote{unquote=true} = Q, S) ->
do_quote({ '__block__', Line, [Expr] }, Q, S);
quote({ 'unquote_splicing', Meta, _ } = Expr, #elixir_quote{unquote=true} = Q, S) ->
do_quote({ '__block__', Meta, [Expr] }, Q, S);
quote(Else, Q, S) ->
do_quote(Else, Q, S).
do_quote({ unquote, _Line, [Expr] }, #elixir_quote{unquote=true}, S) ->
do_quote({ unquote, _Meta, [Expr] }, #elixir_quote{unquote=true}, S) ->
elixir_translator:translate_each(Expr, S);
do_quote({ { { '.', Line, [Left, unquote] }, _, [Expr] }, _, Args }, #elixir_quote{unquote=true} = Q, S) ->
All = [Left, { unquote, Line, [Expr] }, Args, S#elixir_scope.file],
{ TAll, TS } = lists:mapfoldl(fun(X, Acc) -> do_quote(X, Q, Acc) end, S, All),
{ ?ELIXIR_WRAP_CALL(Line, elixir_quote, join_quoted, [line(Line, Q)|TAll]), TS };
do_quote({ 'alias!', _Meta, [Expr] }, Q, S) ->
do_quote(Expr, Q#elixir_quote{expand_aliases=false}, S);
do_quote({ { '.', Line, [Left, unquote] }, _, [Expr] }, #elixir_quote{unquote=true} = Q, S) ->
All = [Left, { unquote, Line, [Expr] }, nil, S#elixir_scope.file],
{ TAll, TS } = lists:mapfoldl(fun(X, Acc) -> do_quote(X, Q, Acc) end, S, All),
{ ?ELIXIR_WRAP_CALL(Line, elixir_quote, join_quoted, [line(Line, Q)|TAll]), TS };
do_quote({ '__aliases__', Meta, [H|_] = Aliases }, #elixir_quote{expand_aliases=true} = Q, S) when is_atom(H) and H /= 'Elixir' ->
Line = ?line(Meta),
{ TAliases, SA } = do_quote(['Elixir'|Aliases], Q, S),
do_quote({ Left, Line, nil }, Q, S) when is_atom(Left) ->
{ { tuple, Line, [
{ atom, Line, '__aliases__' },
meta(Meta, Q),
TAliases
] }, SA };
do_quote({ { { '.', Meta, [Left, unquote] }, _, [Expr] }, _, Args }, #elixir_quote{unquote=true} = Q, S) ->
All = [Left, { unquote, Meta, [Expr] }, Args, S#elixir_scope.file],
{ TAll, TS } = lists:mapfoldl(fun(X, Acc) -> do_quote(X, Q, Acc) end, S, All),
{ ?wrap_call(?line(Meta), elixir_quote, join_quoted, [meta(Meta, Q)|TAll]), TS };
do_quote({ { '.', Meta, [Left, unquote] }, _, [Expr] }, #elixir_quote{unquote=true} = Q, S) ->
All = [Left, { unquote, Meta, [Expr] }, nil, S#elixir_scope.file],
{ TAll, TS } = lists:mapfoldl(fun(X, Acc) -> do_quote(X, Q, Acc) end, S, All),
{ ?wrap_call(?line(Meta), elixir_quote, join_quoted, [meta(Meta, Q)|TAll]), TS };
do_quote({ Left, Meta, nil }, Q, S) when is_atom(Left) ->
Line = ?line(Meta),
Tuple = { tuple, Line, [
{ atom, Line, Left },
line(Line, Q),
{ atom, Line, Q#elixir_quote.marker }
meta(Meta, Q),
{ atom, Line, Q#elixir_quote.var_context }
] },
{ Tuple, S };
do_quote({ Left, Line, Right }, Q, S) ->
{ TLeft, LS } = do_quote(Left, Q, S),
{ TRight, RS } = do_quote(Right, Q, LS),
do_quote({ Name, Meta, ArgsOrAtom } = Tuple, #elixir_quote{expand_imports=true} = Q, S) when is_atom(Name) ->
Arity = case is_atom(ArgsOrAtom) of
true -> 0;
false -> length(ArgsOrAtom)
end,
%% We need to remove line numbers from quoted exprs
%% otherwise the line number quotes in the macro will
%% get mixed with the unquoted contents
Tuple = { tuple, Line, [TLeft, line(Line, Q), TRight] },
{ Tuple, RS };
case (lists:keyfind(import, 1, Meta) == false) andalso
elixir_dispatch:find_import(Meta, Name, Arity, S) of
false -> do_quote_tuple(Tuple, Q, S);
Receiver -> do_quote_tuple({ Name, [{import,Receiver}|Meta], ArgsOrAtom }, Q, S)
end;
do_quote({ _, _, _ } = Tuple, Q, S) ->
do_quote_tuple(Tuple, Q, S);
% Handle two item tuples but still allow them to be spliced.
do_quote({ Left, Right }, Q, S) when
@@ -79,26 +104,36 @@ do_quote({ Left, Right }, Q, S) when
not is_tuple(Right) orelse (element(1, Right) /= unquote_splicing) ->
{ TLeft, LS } = do_quote(Left, Q, S),
{ TRight, RS } = do_quote(Right, Q, LS),
{ { tuple, int_line(Q), [TLeft, TRight] }, RS };
{ { tuple, line(Q), [TLeft, TRight] }, RS };
do_quote({ Left, Right }, Q, S) ->
do_quote({ '{}', int_line(Q), [Left, Right] }, Q, S);
do_quote({ '{}', [{line,line(Q)}], [Left, Right] }, Q, S);
do_quote(List, Q, S) when is_list(List) ->
splice(List, Q, [], [], S);
do_quote(Number, Q, S) when is_integer(Number) ->
{ { integer, int_line(Q), Number }, S };
{ { integer, line(Q), Number }, S };
do_quote(Number, Q, S) when is_float(Number) ->
{ { float, int_line(Q), Number }, S };
{ { float, line(Q), Number }, S };
do_quote(Atom, Q, S) when is_atom(Atom) ->
{ { atom, int_line(Q), Atom }, S };
{ { atom, line(Q), Atom }, S };
do_quote(Bitstring, _Q, S) when is_bitstring(Bitstring) ->
{ elixir_tree_helpers:abstract_syntax(Bitstring), S }.
do_quote_tuple({ Left, Meta, Right }, Q, S) ->
{ TLeft, LS } = do_quote(Left, Q, S),
{ TRight, RS } = do_quote(Right, Q, LS),
%% We need to remove line numbers from quoted exprs
%% otherwise the line number quotes in the macro will
%% get mixed with the unquoted contents
Tuple = { tuple, ?line(Meta), [TLeft, meta(Meta, Q), TRight] },
{ Tuple, RS }.
% Loop through the list finding each unquote_splicing entry.
splice([{ unquote_splicing, _, [Args] }|T], #elixir_quote{unquote=true} = Q, Buffer, Acc, S) ->
@@ -106,10 +141,10 @@ splice([{ unquote_splicing, _, [Args] }|T], #elixir_quote{unquote=true} = Q, Buf
{ TArgs, TS } = elixir_translator:translate_each(Args, NewS),
splice(T, Q, [], [TArgs|NewAcc], TS);
splice([{ '|', Line, [{ unquote_splicing, _, [_] } = Left, Right] }], #elixir_quote{unquote=true} = Q, Buffer, Acc, S) ->
splice([{ '|', Meta, [{ unquote_splicing, _, [_] } = Left, Right] }], #elixir_quote{unquote=true} = Q, Buffer, Acc, S) ->
{ TLeft, SL } = splice([Left], Q, Buffer, Acc, S),
{ TRight, SR } = do_quote(Right, Q, SL),
{ { op, Line, '++', TLeft, TRight }, SR };
{ { op, ?line(Meta), '++', TLeft, TRight }, SR };
splice([H|T], Q, Buffer, Acc, S) ->
splice(T, Q, [H|Buffer], Acc, S);
@@ -118,12 +153,12 @@ splice([], Q, Buffer, Acc, S) ->
{ NewAcc, NewS } = from_buffer_to_acc(Buffer, Q, Acc, S),
case NewAcc of
[] ->
{ { nil, int_line(Q) }, NewS };
{ { nil, line(Q) }, NewS };
[List] ->
{ List, NewS };
_ ->
List = elixir_tree_helpers:build_simple_reverse_list(int_line(Q), NewAcc),
{ ?ELIXIR_WRAP_CALL(int_line(Q), lists, append, [List]), NewS }
List = elixir_tree_helpers:build_simple_reverse_list(line(Q), NewAcc),
{ ?wrap_call(line(Q), lists, append, [List]), NewS }
end.
from_buffer_to_acc([], _Q, Acc, S) ->
@@ -131,14 +166,19 @@ from_buffer_to_acc([], _Q, Acc, S) ->
from_buffer_to_acc(Buffer, Q, Acc, S) ->
{ New, NewS } = elixir_tree_helpers:build_reverse_list(
fun(X, AccS) -> do_quote(X, Q, AccS) end, Buffer, int_line(Q), S),
fun(X, AccS) -> do_quote(X, Q, AccS) end, Buffer, line(Q), S),
{ [New|Acc], NewS }.
line(Line, #elixir_quote{line=keep}) -> { integer, Line, Line };
line(_Line, #elixir_quote{line=Line}) -> Line.
meta(Meta, #elixir_quote{line=keep}) ->
elixir_tree_helpers:abstract_syntax(Meta);
meta(Meta, #elixir_quote{line=nil}) ->
elixir_tree_helpers:abstract_syntax(lists:keydelete(line, 1, Meta));
meta(Meta, #elixir_quote{line=Line} = Q) ->
{ cons,
line(Q),
{ tuple, line(Q), [{ atom, line(Q), line }, Line] },
elixir_tree_helpers:abstract_syntax(lists:keydelete(line, 1, Meta))
}.
int_line(Q) ->
case line(0, Q) of
{ integer, _, Line } -> Line;
_ -> 0
end.
line(#elixir_quote{line={ integer, _, Line }}) -> Line;
line(_) -> 0.
+18 -12
View File
@@ -1,7 +1,7 @@
%% Convenience functions used to manipulate scope
%% and its variables.
-module(elixir_scope).
-export([translate_var/4,
-export([translate_var/5,
build_erl_var/2, build_ex_var/2,
build_erl_var/3, build_ex_var/3,
build_erl_var/4, build_ex_var/4,
@@ -13,7 +13,8 @@
-include("elixir.hrl").
-compile({parse_transform, elixir_transform}).
translate_var(Line, Name, Kind, S) ->
translate_var(Meta, Name, Kind, S, Callback) ->
Line = ?line(Meta),
Vars = S#elixir_scope.vars,
case Name of
@@ -28,7 +29,7 @@ translate_var(Line, Name, Kind, S) ->
{ { var, Line, orddict:fetch({ Name, Kind }, Vars) }, S };
{ Else, _ } ->
{ NewVar, NS } = if
Kind == quoted -> build_erl_var(Line, S);
Kind /= nil -> build_erl_var(Line, S);
Else -> build_erl_var(Line, Name, S);
S#elixir_scope.noname -> build_erl_var(Line, Name, S);
true -> { { var, Line, Name }, S }
@@ -47,7 +48,7 @@ translate_var(Line, Name, Kind, S) ->
_ ->
case orddict:find({ Name, Kind }, Vars) of
{ ok, VarName } -> { { var, Line, VarName }, S };
error -> elixir_translator:translate_each({ Name, Line, [] }, S)
error -> Callback()
end
end
end.
@@ -64,14 +65,19 @@ build_var_counter(Key, #elixir_scope{counter=Counter} = S) ->
New = orddict:update_counter(Key, 1, Counter),
{ orddict:fetch(Key, New), S#elixir_scope{counter=New} }.
build_erl_var(Line, Key, Name, S) ->
build_erl_var(Line, Key, Name, S) when is_integer(Line) ->
{ Counter, NS } = build_var_counter(Key, S),
Var = { var, Line, ?ELIXIR_ATOM_CONCAT([Name, "@", Counter]) },
Var = { var, Line, ?atom_concat([Name, "@", Counter]) },
{ Var, NS }.
build_ex_var(Line, Key, Name, S) ->
build_ex_var(Line, Key, Name, S) when is_integer(Line) ->
Context = case S#elixir_scope.module of
nil -> 'Elixir';
Mod -> Mod
end,
{ Counter, NS } = build_var_counter(Key, S),
Var = { ?ELIXIR_ATOM_CONCAT([Name, "@", Counter]), Line, quoted },
Var = { ?atom_concat([Name, "@", Counter]), [{line,Line}], Context },
{ Var, NS }.
% Handle Macro.Env conversion
@@ -79,12 +85,12 @@ build_ex_var(Line, Key, Name, S) ->
to_erl_env(Scope) ->
elixir_tree_helpers:abstract_syntax(to_ex_env(Scope)).
to_ex_env({ Line, Tuple }) when element(1, Tuple) == 'Elixir.Macro.Env' ->
to_ex_env({ Line, Tuple }) when element(1, Tuple) == 'Elixir.Macro.Env', is_integer(Line) ->
setelement(4, Tuple, Line);
to_ex_env({ Line, #elixir_scope{module=Module,file=File,
function=Function,aliases=Aliases,context=Context,
requires=Requires,macros=Macros,functions=Functions} }) ->
requires=Requires,macros=Macros,functions=Functions} }) when is_integer(Line) ->
{ 'Elixir.Macro.Env', Module, File, Line, Function, Aliases, Context, Requires, Functions, Macros }.
filename(#elixir_scope{file=File}) -> File;
@@ -98,12 +104,12 @@ serialize(S) ->
S#elixir_scope.requires, S#elixir_scope.macros, S#elixir_scope.aliases, S#elixir_scope.scheduled }
).
serialize_with_vars(Line, S) ->
serialize_with_vars(Line, S) when is_integer(Line) ->
{ Vars, _ } = orddict:fold(fun({ Key, Kind }, Value, { Acc, Counter }) ->
{ { cons, Line, { tuple, Line, [
{ atom, Line, Key },
{ atom, Line, Kind },
{ atom, Line, ?ELIXIR_ATOM_CONCAT(["_@", Counter]) },
{ atom, Line, ?atom_concat(["_@", Counter]) },
{ var, Line, Value }
] }, Acc }, Counter + 1 }
end, { { nil, Line }, 0 }, S#elixir_scope.vars),
+57 -21
View File
@@ -10,16 +10,6 @@
existing_atoms_only=false
}).
-define(is_digit(S), S >= $0 andalso S =< $9).
-define(is_hex(S), ?is_digit(S) orelse (S >= $A andalso S =< $F) orelse (S >= $a andalso S =< $f)).
-define(is_bin(S), S >= $0 andalso S =< $1).
-define(is_octal(S), S >= $0 andalso S =< $7).
-define(is_upcase(S), S >= $A andalso S =< $Z).
-define(is_downcase(S), S >= $a andalso S =< $z).
-define(is_word(S), ?is_digit(S) orelse ?is_upcase(S) orelse ?is_downcase(S)).
-define(is_quote(S), S == $" orelse S == $').
-define(is_space(S), S == $\s; S == $\r; S == $\t; S == $\n).
-define(container2(T1, T2),
T1 == ${, T2 == $};
T1 == $[, T2 == $]
@@ -58,6 +48,7 @@
T1 == $<, T2 == $-;
T1 == $-, T2 == $>;
T1 == $., T2 == $.;
T1 == $|, T2 == $>;
T1 == $/, T2 == $>;
T1 == $=, T2 == $~
).
@@ -125,14 +116,14 @@ tokenize([$0,X,H|T], Line, Scope, Tokens) when (X == $x orelse X == $X), ?is_hex
{ Rest, Number } = tokenize_hex([H|T], []),
tokenize(Rest, Line, Scope, [{ number, Line, Number }|Tokens]);
tokenize([$0,O,H|T], Line, Scope, Tokens) when (O == $o orelse O == $O), ?is_octal(H) ->
{ Rest, Number } = tokenize_octal([H|T], []),
tokenize(Rest, Line, Scope, [{ number, Line, Number }|Tokens]);
tokenize([$0,B,H|T], Line, Scope, Tokens) when (B == $b orelse B == $B), ?is_bin(H) ->
{ Rest, Number } = tokenize_bin([H|T], []),
tokenize(Rest, Line, Scope, [{ number, Line, Number }|Tokens]);
tokenize([$0,H|T], Line, Scope, Tokens) when ?is_octal(H) ->
{ Rest, Number } = tokenize_octal([H|T], []),
tokenize(Rest, Line, Scope, [{ number, Line, Number }|Tokens]);
% Comments
tokenize([$#|String], Line, Scope, Tokens) ->
@@ -162,6 +153,50 @@ tokenize([$%,S,H|T], Line, #scope{file=File} = Scope, Tokens) when not(?is_word(
% Char tokens
tokenize([$?,$\\,P,${,A,B,C,D,E,F,$}|T], Line, Scope, Tokens) when (P == $x orelse P == $X), ?is_hex(A), ?is_hex(B), ?is_hex(C), ?is_hex(D), ?is_hex(E), ?is_hex(F) ->
[Char] = unicode:characters_to_list(elixir_interpolation:unescape_chars(list_to_binary([$\\,P,${,A,B,C,D,E,F,$}]))),
tokenize(T, Line, Scope, [{ number, Line, Char }|Tokens]);
tokenize([$?,$\\,P,${,A,B,C,D,E,$}|T], Line, Scope, Tokens) when (P == $x orelse P == $X), ?is_hex(A), ?is_hex(B), ?is_hex(C), ?is_hex(D), ?is_hex(E) ->
[Char] = unicode:characters_to_list(elixir_interpolation:unescape_chars(list_to_binary([$\\,P,${,A,B,C,D,E,$}]))),
tokenize(T, Line, Scope, [{ number, Line, Char }|Tokens]);
tokenize([$?,$\\,P,${,A,B,C,D,$}|T], Line, Scope, Tokens) when (P == $x orelse P == $X), ?is_hex(A), ?is_hex(B), ?is_hex(C), ?is_hex(D) ->
[Char] = unicode:characters_to_list(elixir_interpolation:unescape_chars(list_to_binary([$\\,P,${,A,B,C,D,$}]))),
tokenize(T, Line, Scope, [{ number, Line, Char }|Tokens]);
tokenize([$?,$\\,P,${,A,B,C,$}|T], Line, Scope, Tokens) when (P == $x orelse P == $X), ?is_hex(A), ?is_hex(B), ?is_hex(C) ->
[Char] = unicode:characters_to_list(elixir_interpolation:unescape_chars(list_to_binary([$\\,P,${,A,B,C,$}]))),
tokenize(T, Line, Scope, [{ number, Line, Char }|Tokens]);
tokenize([$?,$\\,P,${,A,B,$}|T], Line, Scope, Tokens) when (P == $x orelse P == $X), ?is_hex(A), ?is_hex(B) ->
[Char] = unicode:characters_to_list(elixir_interpolation:unescape_chars(list_to_binary([$\\,P,${,A,B,$}]))),
tokenize(T, Line, Scope, [{ number, Line, Char }|Tokens]);
tokenize([$?,$\\,P,${,A,$}|T], Line, Scope, Tokens) when (P == $x orelse P == $X), ?is_hex(A) ->
[Char] = unicode:characters_to_list(elixir_interpolation:unescape_chars(list_to_binary([$\\,P,${,A,$}]))),
tokenize(T, Line, Scope, [{ number, Line, Char }|Tokens]);
tokenize([$?,$\\,P,A,B|T], Line, Scope, Tokens) when (P == $x orelse P == $X), ?is_hex(A), ?is_hex(B) ->
[Char] = unicode:characters_to_list(elixir_interpolation:unescape_chars(list_to_binary([$\\,P,A,B]))),
tokenize(T, Line, Scope, [{ number, Line, Char }|Tokens]);
tokenize([$?,$\\,P,A|T], Line, Scope, Tokens) when (P == $x orelse P == $X), ?is_hex(A) ->
[Char] = unicode:characters_to_list(elixir_interpolation:unescape_chars(list_to_binary([$\\,P,A]))),
tokenize(T, Line, Scope, [{ number, Line, Char }|Tokens]);
tokenize([$?,$\\,A,B,C|T], Line, Scope, Tokens) when ?is_octal(A), A =< $3,?is_octal(B), ?is_octal(C) ->
[Char] = unicode:characters_to_list(elixir_interpolation:unescape_chars(list_to_binary([$\\,A,B,C]))),
tokenize(T, Line, Scope, [{ number, Line, Char }|Tokens]);
tokenize([$?,$\\,A,B|T], Line, Scope, Tokens) when ?is_octal(A), ?is_octal(B) ->
[Char] = unicode:characters_to_list(elixir_interpolation:unescape_chars(list_to_binary([$\\,A,B]))),
tokenize(T, Line, Scope, [{ number, Line, Char }|Tokens]);
tokenize([$?,$\\,A|T], Line, Scope, Tokens) when ?is_octal(A) ->
[Char] = unicode:characters_to_list(elixir_interpolation:unescape_chars(list_to_binary([$\\,A]))),
tokenize(T, Line, Scope, [{ number, Line, Char }|Tokens]);
tokenize([$?,$\\,H|T], Line, Scope, Tokens) ->
Char = elixir_interpolation:unescape_map(H),
tokenize(T, Line, Scope, [{ number, Line, Char }|Tokens]);
@@ -169,14 +204,12 @@ tokenize([$?,$\\,H|T], Line, Scope, Tokens) ->
tokenize([$?,Char|T], Line, Scope, Tokens) ->
tokenize(T, Line, Scope, [{ number, Line, Char }|Tokens]);
% Dot identifier
% Dot identifier/operators
tokenize("..." ++ Rest, Line, Scope, Tokens) ->
Token = tokenize_call_identifier(identifier, Line, '...', Rest),
tokenize(Rest, Line, Scope, [Token|Tokens]);
% Dot operators
% ## Containers
tokenize(".<<>>" ++ Rest, Line, Scope, Tokens) ->
handle_call_identifier(Rest, Line, '<<>>', Scope, Tokens);
@@ -237,7 +270,7 @@ tokenize([$:,H|T], Line, #scope{file=File} = Scope, Tokens) when ?is_quote(H) ->
case elixir_interpolation:extract(Line, File, true, T, H) of
{ NewLine, Parts, Rest } ->
Token = case unescape_tokens(Parts) of
[Part] -> { atom, Line, unsafe_to_atom(Part, Scope) };
[Part] when is_list(Part) or is_binary(Part) -> { atom, Line, unsafe_to_atom(Part, Scope) };
Unescaped -> { atom, Line, Unescaped }
end,
tokenize(Rest, NewLine, Scope, [Token|Tokens]);
@@ -245,7 +278,10 @@ tokenize([$:,H|T], Line, #scope{file=File} = Scope, Tokens) when ?is_quote(H) ->
interpolation_error(Error, " (for atom starting at line ~B)", [Line])
end;
% Atom operators
% Atom identifiers/operators
tokenize(":..." ++ Rest, Line, Scope, Tokens) ->
tokenize(Rest, Line, Scope, [{ atom, Line, '...' }|Tokens]);
% ## Containers
tokenize(":<<>>" ++ Rest, Line, Scope, Tokens) ->
@@ -350,7 +386,7 @@ tokenize([H|_] = String, Line, Scope, Tokens) when ?is_upcase(H) ->
[$:|T] when hd(T) /= $: ->
tokenize(T, Line, Scope, [{ kw_identifier, Line, Atom }|Tokens]);
_ ->
tokenize(Rest, Line, Scope, [{ '__aliases__', Line, [Atom] }|Tokens])
tokenize(Rest, Line, Scope, [{ aliases, Line, [Atom] }|Tokens])
end;
% Identifier
@@ -809,4 +845,4 @@ keyword('else') -> block;
keyword('rescue') -> block;
keyword('catch') -> block;
keyword(_) -> false.
keyword(_) -> false.
+257 -176
View File
@@ -38,11 +38,11 @@ translate(Forms, S) ->
%% Assignment operator
translate_each({'=', Line, [Left, Right]}, S) ->
assert_no_guard_scope(Line, '=', S),
translate_each({'=', Meta, [Left, Right]}, S) ->
assert_no_guard_scope(Meta, '=', S),
{ TRight, SR } = translate_each(Right, S),
{ TLeft, SL } = elixir_clauses:assigns(fun translate_each/2, Left, SR),
{ { match, Line, TLeft, TRight }, SL };
{ { match, ?line(Meta), TLeft, TRight }, SL };
%% Containers
@@ -51,51 +51,51 @@ translate_each({ C, _, _ } = Original, S) when C == '[]'; C == '{}'; C == '<<>>'
%% Blocks and scope rewriters
translate_each({ '__block__', Line, [] }, S) ->
{ { atom, Line, nil }, S };
translate_each({ '__block__', Meta, [] }, S) ->
{ { atom, ?line(Meta), nil }, S };
translate_each({ '__block__', _Line, [Arg] }, S) ->
translate_each(Arg, S);
translate_each({ '__block__', Line, Args }, S) when is_list(Args) ->
translate_each({ '__block__', Meta, Args }, S) when is_list(Args) ->
{ TArgs, NS } = translate(Args, S),
{ { block, Line, TArgs }, NS };
{ { block, ?line(Meta), TArgs }, NS };
translate_each({ '__scope__', _Line, [[{file,File}],[{do,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}),
{ TExpr, TS#elixir_scope{file=Old} };
%% Erlang op
translate_each({ '__op__', Line, [Op, Expr] }, S) when is_atom(Op) ->
translate_each({ '__op__', Meta, [Op, Expr] }, S) when is_atom(Op) ->
{ TExpr, NS } = translate_each(Expr, S),
{ { op, Line, convert_op(Op), TExpr }, NS };
{ { op, ?line(Meta), convert_op(Op), TExpr }, NS };
translate_each({ '__op__', Line, [Op, Left, Right] }, S) when is_atom(Op) ->
translate_each({ '__op__', Meta, [Op, Left, Right] }, S) when is_atom(Op) ->
{ [TLeft, TRight], NS } = translate_args([Left, Right], S),
{ { op, Line, convert_op(Op), TLeft, TRight }, NS };
{ { op, ?line(Meta), convert_op(Op), TLeft, TRight }, NS };
translate_each({ '__ambiguousop__', Line, [Var, H|T] }, S) ->
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, Line, [H|T] }, S);
error -> translate_each({ Name, Meta, [H|T] }, S);
_ ->
case T of
[] -> translate_each(rellocate_ambiguous_op(H, Var), S);
_ -> syntax_error(Line, S#elixir_scope.file, "Many arguments given to ~s, but ~s is a variable. Use even spaces to solve ambiguity.", [Name, Name])
_ -> 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, Line, [Ref] }, S) ->
translate_each({ alias, Line, [Ref,[]] }, S);
translate_each({ alias, Meta, [Ref] }, S) ->
translate_each({ alias, Meta, [Ref,[]] }, S);
translate_each({ alias, Line, [Ref, KV] }, S) ->
assert_no_assign_or_guard_scope(Line, alias, S),
validate_opts(Line, alias, [as], KV, S),
translate_each({ alias, Meta, [Ref, KV] }, S) ->
assert_no_assign_or_guard_scope(Meta, alias, S),
validate_opts(Meta, alias, [as], KV, S),
{ TRef, SR } = translate_each(Ref, S),
@@ -110,7 +110,7 @@ translate_each({ alias, Line, [Ref, KV] }, S) ->
{ TOther, SA } = translate_each(Other, SR#elixir_scope{aliases=[]}),
case TOther of
{ atom, _, Atom } -> { Atom, SA };
_ -> syntax_error(Line, S#elixir_scope.file,
_ -> syntax_error(Meta, S#elixir_scope.file,
"invalid args for alias, expected an atom or alias as argument")
end
end,
@@ -118,28 +118,28 @@ translate_each({ alias, Line, [Ref, KV] }, S) ->
%% Avoid creating aliases if first == last
%% unecessarily polluting the aliases dict
case New == Old of
true -> { { nil, Line }, SF };
true -> { { nil, ?line(Meta) }, SF };
false ->
case string:tokens(atom_to_list(New), "-") of
[_,_] -> [];
_ -> syntax_error(Line, S#elixir_scope.file,
_ -> syntax_error(Meta, S#elixir_scope.file,
"invalid args for alias, cannot create nested alias ~s", [elixir_errors:inspect(New)])
end,
{ { nil, Line }, SF#elixir_scope{
{ { nil, ?line(Meta) }, SF#elixir_scope{
aliases=orddict:store(New, Old, S#elixir_scope.aliases)
} }
end;
_ ->
syntax_error(Line, S#elixir_scope.file, "invalid args for alias, expected an atom or alias as argument")
syntax_error(Meta, S#elixir_scope.file, "invalid args for alias, expected an atom or alias as argument")
end;
translate_each({ require, Line, [Ref] }, S) ->
translate_each({ require, Line, [Ref, []] }, S);
translate_each({ require, Meta, [Ref] }, S) ->
translate_each({ require, Meta, [Ref, []] }, S);
translate_each({ require, Line, [Ref, KV] }, S) ->
assert_no_assign_or_guard_scope(Line, 'require', S),
validate_opts(Line, require, [as], KV, S),
translate_each({ require, Meta, [Ref, KV] }, S) ->
assert_no_assign_or_guard_scope(Meta, 'require', S),
validate_opts(Meta, require, [as], KV, S),
{ TRef, SR } = translate_each(Ref, S),
@@ -150,225 +150,291 @@ translate_each({ require, Line, [Ref, KV] }, S) ->
case TRef of
{ atom, _, Old } ->
elixir_aliases:ensure_loaded(Line, Old, SR),
elixir_aliases:ensure_loaded(Meta, Old, SR),
SF = SR#elixir_scope{
requires=ordsets:add_element(Old, S#elixir_scope.requires)
},
translate_each({ alias, Line, [Ref, [{ as, As }]] }, SF);
translate_each({ alias, Meta, [Ref, [{ as, As }]] }, SF);
_ ->
syntax_error(Line, S#elixir_scope.file, "invalid args for require, expected an atom or alias as argument")
syntax_error(Meta, S#elixir_scope.file, "invalid args for require, expected an atom or alias as argument")
end;
translate_each({ import, Line, [Left] }, S) ->
translate_each({ import, Line, [Left, []]}, S);
translate_each({ import, Meta, [Left] }, S) ->
translate_each({ import, Meta, [Left, []]}, S);
translate_each({ import, Line, [Left,Opts] }, S) when is_list(Opts) ->
translate_each({ import, Line, [default, Left, Opts]}, S);
translate_each({ import, Meta, [Left,Opts] }, S) when is_list(Opts) ->
translate_each({ import, Meta, [default, Left, Opts]}, S);
translate_each({ import, Line, [Selector, Left] }, S) ->
translate_each({ import, Line, [Selector, Left, []]}, S);
translate_each({ import, Meta, [Selector, Left] }, S) ->
translate_each({ import, Meta, [Selector, Left, []]}, S);
translate_each({ import, Line, [Left, Right, Opts] }, S) ->
assert_no_assign_or_guard_scope(Line, 'import', S),
translate_each({ import, Meta, [Left, Right, Opts] }, S) ->
assert_no_assign_or_guard_scope(Meta, 'import', S),
{ TSelector, SL } = translate_each(Left, S),
{ TRef, SR } = translate_each(Right, SL),
Selector = case TSelector of
{ atom, _, SelectorAtom } -> SelectorAtom;
_ -> syntax_error(Line, S#elixir_scope.file, "invalid selector for import, expected an atom")
_ -> syntax_error(Meta, S#elixir_scope.file, "invalid selector for import, expected an atom")
end,
Ref = case TRef of
{ atom, _, RefAtom } -> RefAtom;
_ -> syntax_error(Line, S#elixir_scope.file, "invalid name for import, expected an atom or alias")
_ -> syntax_error(Meta, S#elixir_scope.file, "invalid name for import, expected an atom or alias")
end,
validate_opts(Line, import, [as, only, except], Opts, S),
validate_opts(Meta, import, [as, only, except], Opts, S),
As = case lists:keyfind(as, 1, Opts) of
false -> false;
{ as, Value } -> Value
end,
elixir_aliases:ensure_loaded(Line, Ref, SR),
SF = elixir_import:import(Line, Ref, Opts, Selector, SR),
translate_each({ require, Line, [Ref, [{ as, As }]] }, SF);
elixir_aliases:ensure_loaded(Meta, Ref, SR),
SF = elixir_import:import(Meta, Ref, Opts, Selector, SR),
translate_each({ require, Meta, [Ref, [{ as, As }]] }, SF);
%% Pseudo variables
translate_each({ '__MODULE__', Line, Atom }, S) when is_atom(Atom) ->
{ { atom, Line, S#elixir_scope.module }, S };
translate_each({ '__MODULE__', Meta, Atom }, S) when is_atom(Atom) ->
{ { atom, ?line(Meta), S#elixir_scope.module }, S };
translate_each({ '__FILE__', _Line, Atom }, S) when is_atom(Atom) ->
translate_each({ '__FILE__', _Meta, Atom }, S) when is_atom(Atom) ->
translate_each(S#elixir_scope.file, S);
translate_each({ '__ENV__', Line, Atom }, S) when is_atom(Atom) ->
{ elixir_scope:to_erl_env({ Line, S }), S };
translate_each({ '__ENV__', Meta, Atom }, S) when is_atom(Atom) ->
{ elixir_scope:to_erl_env({ ?line(Meta), S }), S };
translate_each({ '__CALLER__', Line, Atom }, S) when is_atom(Atom) ->
{ { var, Line, '__CALLER__' }, S#elixir_scope{caller=true} };
translate_each({ '__CALLER__', Meta, Atom }, S) when is_atom(Atom) ->
{ { var, ?line(Meta), '__CALLER__' }, S#elixir_scope{caller=true} };
%% Aliases
translate_each({ '__aliases__', Line, [H] }, S) when H /= 'Elixir' ->
Atom = list_to_atom("Elixir-" ++ atom_to_list(H)),
{ { atom, Line, elixir_aliases:lookup(Atom, S#elixir_scope.aliases) }, S };
translate_each({ '__aliases__', Meta, _ } = Alias, S) ->
case elixir_aliases:expand(Alias, S#elixir_scope.aliases) of
Atom when is_atom(Atom) -> { { atom, ?line(Meta), Atom}, S };
Aliases ->
{ TAliases, SA } = translate_args(Aliases, S),
translate_each({ '__aliases__', Line, [H|T] }, S) ->
Aliases = if
is_atom(H) andalso (H /= 'Elixir') ->
Atom = list_to_atom("Elixir-" ++ atom_to_list(H)),
[elixir_aliases:lookup(Atom, S#elixir_scope.aliases)|T];
true ->
[H|T]
end,
{ TAliases, SA } = translate_args(Aliases, S),
case lists:all(fun is_atom_tuple/1, TAliases) of
true ->
Atoms = [Atom || { atom, _, Atom } <- TAliases],
{ { atom, Line, elixir_aliases:concat(Atoms) }, SA };
false ->
Args = [elixir_tree_helpers:build_simple_list(Line, TAliases)],
{ ?ELIXIR_WRAP_CALL(Line, elixir_aliases, concat, Args), SA }
case lists:all(fun is_atom_tuple/1, TAliases) of
true ->
Atoms = [Atom || { atom, _, Atom } <- TAliases],
{ { atom, ?line(Meta), elixir_aliases:concat(Atoms) }, SA };
false ->
Args = [elixir_tree_helpers:build_simple_list(?line(Meta), TAliases)],
{ ?wrap_call(?line(Meta), elixir_aliases, concat, Args), SA }
end
end;
%% Quoting
translate_each({ Unquote, Line, _Args }, S) when Unquote == unquote; Unquote == unquote_splicing ->
syntax_error(Line, S#elixir_scope.file, "~p called outside quote", [Unquote]);
translate_each({ Unquote, Meta, _Args }, S) when Unquote == unquote; Unquote == unquote_splicing ->
syntax_error(Meta, S#elixir_scope.file, "~p called outside quote", [Unquote]);
translate_each({ quote, Line, [Left, Right] }, S) ->
translate_each({ quote, Line, [orddict:from_list(Left ++ Right)] }, S);
translate_each({ quote, Meta, [Left, Right] }, S) ->
translate_each({ quote, Meta, [orddict:from_list(Left ++ Right)] }, S);
translate_each({ quote, GivenLine, [T] }, S) when is_list(T) ->
translate_each({ quote, Meta, [T] }, S) when is_list(T) ->
Exprs =
case lists:keyfind(do, 1, T) of
{do, E} -> E;
case lists:keyfind(do, 1, T) of
{ do, E } -> E;
false ->
syntax_error(Meta, S#elixir_scope.file, "invalid args for quote")
end,
Context = case lists:keyfind(var_context, 1, T) of
{ var_context, VarContext } ->
expand_var_context(Meta, VarContext,
"invalid argument given for var_context in quote", S);
false ->
syntax_error(GivenLine, S#elixir_scope.file, "invalid args for quote")
case lists:keyfind(hygiene, 1, T) of
{ hygiene, false } -> nil;
_ ->
case S#elixir_scope.module of
nil -> 'Elixir';
Mod -> Mod
end
end
end,
Marker = case lists:keyfind(hygiene, 1, T) of
{ hygiene, false } -> nil;
_ -> quoted
GetExpansion = fun(Key) ->
case lists:keyfind(Key, 1, T) of
{ Key, Bool } when is_boolean(Bool) ->
Bool;
false ->
case lists:keyfind(hygiene, 1, T) of
{ hygiene, Bool } when is_boolean(Bool) -> Bool;
false -> true
end
end
end,
{ DefaultLine, WrappedExprs } = case lists:keyfind(location, 1, T) of
{ location, keep } ->
Scoped = { '__scope__', GivenLine, [[{file,S#elixir_scope.file}],[{do,Exprs}]] },
{ keep, Scoped };
_ ->
{ 0, Exprs}
Aliases = GetExpansion(expand_aliases),
Imports = GetExpansion(expand_imports),
{ DefaultLine, DefaultFile } = case lists:keyfind(location, 1, T) of
{ location, keep } -> { keep, keep };
false -> { nil, nil }
end,
Line = case lists:keyfind(line, 1, T) of
{ line, Value } -> Value;
_ -> DefaultLine
{ line, LineValue } -> LineValue;
false -> DefaultLine
end,
{ TLine, SL } = case Line of
keep -> { keep, S };
nil -> { nil, S };
_ -> translate_each(Line, S)
end,
File = case lists:keyfind(file, 1, T) of
{ file, FileValue } -> FileValue;
false -> DefaultFile
end,
TFile = case File of
keep -> S#elixir_scope.file;
_ -> File
end,
TExprs = if
is_binary(TFile) ->
{ '__scope__', Meta, [[{file,TFile}],[{do,Exprs}]] };
File == nil ->
Exprs;
true ->
syntax_error(Meta, S#elixir_scope.file, "invalid args for quote, expected :file to be a binary")
end,
Unquote = case lists:keyfind(unquote, 1, T) of
{ unquote, false } -> false;
_ -> true
end,
elixir_quote:quote(WrappedExprs, #elixir_quote{marker=Marker, line=TLine, unquote=Unquote}, SL);
Q = #elixir_quote{var_context=Context, line=TLine, unquote=Unquote,
expand_aliases=Aliases, expand_imports=Imports},
translate_each({ quote, GivenLine, [_] }, S) ->
syntax_error(GivenLine, S#elixir_scope.file, "invalid args for quote");
elixir_quote:quote(TExprs, Q, SL);
translate_each({ quote, Meta, [_] }, S) ->
syntax_error(Meta, S#elixir_scope.file, "invalid args for quote");
translate_each({ 'alias!', _Meta, [Arg] }, S) ->
translate_each(Arg, S);
translate_each({ 'var!', Meta, [Arg] }, S) ->
translate_each({ 'var!', Meta, [Arg, nil] }, S);
translate_each({ 'var!', Meta, [{Name, _, Atom}, Kind] }, S) when is_atom(Name), is_atom(Atom) ->
translate_each({ 'var!', Meta, [Name, Kind] }, S);
translate_each({ 'var!', Meta, [Name, Kind] }, S) when is_atom(Name) ->
Expanded = expand_var_context(Meta, Kind, "invalid second argument for var!", S),
elixir_scope:translate_var(Meta, Name, Expanded, S, fun() ->
syntax_error(Meta, S#elixir_scope.file, "expected var!(~ts) to expand to an existing variable or be a part of a match", [Name])
end);
translate_each({ 'var!', Meta, [_, _] }, S) ->
syntax_error(Meta, S#elixir_scope.file, "invalid first argument for var!, expected a var or an atom");
%% Functions
translate_each({ fn, Line, [[{do, { '->', _, Pairs }}]] }, S) ->
assert_no_assign_or_guard_scope(Line, 'fn', S),
translate_fn(Line, Pairs, S);
translate_each({ fn, Meta, [[{do, { '->', _, Pairs }}]] }, S) ->
assert_no_assign_or_guard_scope(Meta, 'fn', S),
translate_fn(Meta, Pairs, S);
%% Comprehensions
translate_each({ Kind, Line, Args }, S) when is_list(Args), (Kind == lc) orelse (Kind == bc) ->
translate_comprehension(Line, Kind, Args, S);
translate_each({ Kind, Meta, Args }, S) when is_list(Args), (Kind == lc) orelse (Kind == bc) ->
translate_comprehension(Meta, Kind, Args, S);
%% Super (exceptionally supports partial application)
translate_each({ super, Line, Args } = Original, S) ->
translate_each({ super, Meta, Args } = Original, S) ->
case elixir_partials:handle(Original, S) of
error ->
assert_no_assign_or_guard_scope(Line, super, S),
Module = assert_module_scope(Line, super, S),
Function = assert_function_scope(Line, super, S),
elixir_def_overridable:ensure_defined(Line, Module, Function, S),
assert_no_assign_or_guard_scope(Meta, super, S),
Module = assert_module_scope(Meta, super, S),
Function = assert_function_scope(Meta, super, S),
elixir_def_overridable:ensure_defined(Meta, Module, Function, S),
{ _, Arity } = Function,
{ TArgs, TS } = if
is_atom(Args) ->
elixir_def_overridable:retrieve_args(Line, Arity, S);
elixir_def_overridable:retrieve_args(Meta, Arity, S);
length(Args) == Arity ->
translate_args(Args, S);
true ->
syntax_error(Line, 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),
{ { call, Line, { atom, Line, Super }, TArgs }, TS#elixir_scope{super=true} };
{ { call, ?line(Meta), { atom, ?line(Meta), Super }, TArgs }, TS#elixir_scope{super=true} };
Else -> Else
end;
translate_each({ 'super?', Line, [] }, S) ->
Module = assert_module_scope(Line, 'super?', S),
Function = assert_function_scope(Line, 'super?', S),
translate_each({ 'super?', Meta, [] }, S) ->
Module = assert_module_scope(Meta, 'super?', S),
Function = assert_function_scope(Meta, 'super?', S),
Bool = elixir_def_overridable:is_defined(Module, Function),
{ { atom, Line, Bool }, S };
{ { atom, ?line(Meta), Bool }, S };
%% Variables
translate_each({ '^', Line, [ { Name, _, Kind } ] }, S) when is_list(Kind) ->
syntax_error(Line, S#elixir_scope.file, "cannot use ^ with expression at ^~s, ^ must be used only with variables", [Name]);
translate_each({ '^', Meta, [ { Name, _, Kind } ] }, S) when is_list(Kind) ->
syntax_error(Meta, S#elixir_scope.file, "cannot use ^ with expression at ^~s, ^ must be used only with variables", [Name]);
translate_each({ '^', Line, [ { Name, _, Kind } ] }, #elixir_scope{context=assign} = S) when is_atom(Kind) ->
translate_each({ '^', Meta, [ { Name, _, Kind } ] }, #elixir_scope{context=assign} = S) when is_atom(Kind) ->
case orddict:find({ Name, Kind }, S#elixir_scope.vars) of
{ ok, Value } ->
{ { var, Line, Value }, S };
{ { var, Meta, Value }, S };
error ->
syntax_error(Line, S#elixir_scope.file, "unbound variable ^~s", [Name])
syntax_error(Meta, S#elixir_scope.file, "unbound variable ^~s", [Name])
end;
translate_each({ '^', Line, [ { Name, _, Kind } ] }, S) when is_atom(Kind) ->
syntax_error(Line, S#elixir_scope.file,
translate_each({ '^', Meta, [ { Name, _, Kind } ] }, S) when is_atom(Kind) ->
syntax_error(Meta, S#elixir_scope.file,
"cannot access variable ^~s outside of assignment", [Name]);
translate_each({ Name, Line, Kind }, S) when is_atom(Name), (Kind == nil orelse Kind == quoted) ->
elixir_scope:translate_var(Line, Name, Kind, S);
translate_each({ Name, Meta, Kind }, S) when is_atom(Name), is_atom(Kind) ->
elixir_scope:translate_var(Meta, Name, Kind, S, fun() ->
translate_each({ Name, Meta, [] }, S)
end);
%% Local calls
translate_each({ Atom, Line, Args } = Original, S) when is_atom(Atom) ->
translate_each({ Atom, Meta, Args } = Original, S) when is_atom(Atom) ->
case elixir_partials:is_sequential(Args) andalso
elixir_dispatch:import_function(Line, Atom, length(Args), S) of
elixir_dispatch:import_function(Meta, Atom, length(Args), S) of
false ->
case elixir_partials:handle(Original, S) of
error ->
Callback = fun() ->
case S#elixir_scope.context of
guard ->
Arity = length(Args),
File = S#elixir_scope.file,
case Arity of
0 -> syntax_error(Line, File, "unknown variable ~s or cannot invoke local ~s/~B inside guard", [Atom, Atom, Arity]);
_ -> syntax_error(Line, File, "cannot invoke local ~s/~B inside guard", [Atom, Arity])
end;
_ -> translate_local(Line, Atom, Args, S)
end
end,
elixir_dispatch:dispatch_import(Line, Atom, Args, S, Callback);
case lists:keyfind(import, 1, Meta) of
{ import, Receiver } ->
{ TRes, TS } = translate_each({ { '.', Meta, [Receiver, Atom] }, Meta, Args },
S#elixir_scope{check_requires=false}),
{ TRes, TS#elixir_scope{check_requires=S#elixir_scope.check_requires} };
false ->
Callback = fun() ->
case S#elixir_scope.context of
guard ->
Arity = length(Args),
File = S#elixir_scope.file,
case Arity of
0 -> syntax_error(Meta, File, "unknown variable ~s or cannot invoke local ~s/~B inside guard", [Atom, Atom, Arity]);
_ -> syntax_error(Meta, File, "cannot invoke local ~s/~B inside guard", [Atom, Arity])
end;
_ ->
translate_local(Meta, Atom, Args, S)
end
end,
elixir_dispatch:dispatch_import(Meta, Atom, Args, S, Callback)
end;
Else -> Else
end;
Else -> Else
@@ -376,22 +442,22 @@ translate_each({ Atom, Line, Args } = Original, S) when is_atom(Atom) ->
%% Remote calls
translate_each({ { '.', _, [Left, Right] }, Line, Args } = Original, S) when is_atom(Right) ->
translate_each({ { '.', _, [Left, Right] }, Meta, Args } = Original, S) when is_atom(Right) ->
{ TLeft, SL } = translate_each(Left, S),
Fun = (element(1, TLeft) == atom) andalso elixir_partials:is_sequential(Args) andalso
elixir_dispatch:require_function(Line, element(3, TLeft), Right, length(Args), SL),
elixir_dispatch:require_function(Meta, element(3, TLeft), Right, length(Args), SL),
case Fun of
false ->
case elixir_partials:handle(Original, S) of
error ->
{ TRight, SR } = translate_each(Right, umergec(S, SL)),
Callback = fun() -> translate_apply(Line, TLeft, TRight, Args, S, SL, SR) end,
Callback = fun() -> translate_apply(Meta, TLeft, TRight, Args, S, SL, SR) end,
case TLeft of
{ atom, _, Receiver } ->
elixir_dispatch:dispatch_require(Line, Receiver, Right, Args, umergev(SL, SR), Callback);
elixir_dispatch:dispatch_require(Meta, Receiver, Right, Args, umergev(SL, SR), Callback);
_ ->
Callback()
end;
@@ -402,24 +468,24 @@ translate_each({ { '.', _, [Left, Right] }, Line, Args } = Original, S) when is_
%% Anonymous function calls
translate_each({ { '.', _, [Expr] }, Line, Args } = Original, S) ->
translate_each({ { '.', _, [Expr] }, Meta, Args } = Original, S) ->
{ TExpr, SE } = translate_each(Expr, S),
case TExpr of
{ atom, _, Atom } ->
translate_each({ Atom, Line, Args }, S);
translate_each({ Atom, Meta, Args }, S);
_ ->
case elixir_partials:handle(Original, S) of
error ->
{ TArgs, SA } = translate_args(Args, umergec(S, SE)),
{ {call, Line, TExpr, TArgs}, umergev(SE, SA) };
{ {call, ?line(Meta), TExpr, TArgs}, umergev(SE, SA) };
Else -> Else
end
end;
%% Invalid calls
translate_each({ Invalid, Line, _Args }, S) ->
syntax_error(Line, S#elixir_scope.file, "unexpected parenthesis after ~ts",
translate_each({ Invalid, Meta, _Args }, S) ->
syntax_error(Meta, S#elixir_scope.file, "unexpected parenthesis after ~ts",
['Elixir.Macro':to_binary(Invalid)]);
%% Literals
@@ -429,15 +495,17 @@ translate_each(Literal, S) ->
%% Helpers
validate_opts(Line, Kind, Allowed, Opts, S) when is_list(Opts) ->
validate_opts(Meta, Kind, Allowed, Opts, S) when is_list(Opts) ->
[begin
syntax_error(Line, S#elixir_scope.file, "unsupported option ~s given to ~s", [Key, Kind])
syntax_error(Meta, S#elixir_scope.file, "unsupported option ~s given to ~s", [Key, Kind])
end || { Key, _ } <- Opts, not lists:member(Key, Allowed)];
validate_opts(Line, Kind, _Allowed, _Opts, S) ->
syntax_error(Line, S#elixir_scope.file, "invalid options for ~s, expected a keyword list", [Kind]).
validate_opts(Meta, Kind, _Allowed, _Opts, S) ->
syntax_error(Meta, S#elixir_scope.file, "invalid options for ~s, expected a keyword list", [Kind]).
translate_fn(Meta, Clauses, S) ->
Line = ?line(Meta),
translate_fn(Line, Clauses, S) ->
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))
@@ -450,14 +518,16 @@ translate_fn(Line, Clauses, S) ->
1 ->
{ { 'fun', Line, { clauses, TClauses } }, umergec(S, NS) };
_ ->
syntax_error(Line, S#elixir_scope.file, "cannot mix clauses with different arities in function definition")
syntax_error(Meta, S#elixir_scope.file, "cannot mix clauses with different arities in function definition")
end.
translate_local(Line, Name, Args, #elixir_scope{local=nil} = S) ->
translate_local(Meta, Name, Args, #elixir_scope{local=nil} = S) ->
Line = ?line(Meta),
{ TArgs, NS } = translate_args(Args, S),
{ { call, Line, { atom, Line, Name }, TArgs }, NS };
translate_local(Line, Name, Args, S) ->
translate_local(Meta, Name, Args, S) ->
Line = ?line(Meta),
{ TArgs, NS } = translate_args(Args, S),
Remote = { remote, Line,
{ atom, Line, S#elixir_scope.local },
@@ -465,6 +535,15 @@ translate_local(Line, Name, Args, S) ->
},
{ { call, Line, Remote, TArgs }, NS }.
expand_var_context(_Meta, Atom, _Msg, _S) when is_atom(Atom) -> Atom;
expand_var_context(Meta, Alias, Msg, S) ->
case translate_each(Alias, S) of
{ { atom, _, Atom }, _ } ->
Atom;
_ ->
syntax_error(Meta, S#elixir_scope.file, "~ts, expected a compile time available alias or an atom", [Msg])
end.
%% Translate args
%% Variables in arguments are not propagated from one
@@ -496,7 +575,9 @@ translate_args(Args, S) ->
%% Translate apply
%% Used by both apply and external function invocation macros.
translate_apply(Line, TLeft, TRight, Args, S, SL, SR) ->
translate_apply(Meta, TLeft, TRight, Args, S, SL, SR) ->
Line = ?line(Meta),
Optimize = case (Args == []) orelse lists:last(Args) of
{ '|', _, _ } -> false;
_ ->
@@ -514,7 +595,7 @@ translate_apply(Line, TLeft, TRight, Args, S, SL, SR) ->
false ->
{ TArgs, SA } = translate_each(Args, umergec(S, SR)),
FS = umergev(SL, umergev(SR,SA)),
{ ?ELIXIR_WRAP_CALL(Line, erlang, apply, [TLeft, TRight, TArgs]), FS }
{ ?wrap_call(Line, erlang, apply, [TLeft, TRight, TArgs]), FS }
end.
%% __op__ helpers
@@ -533,43 +614,43 @@ convert_op(Else) -> Else.
is_atom_tuple({ atom, _, _ }) -> true;
is_atom_tuple(_) -> false.
rellocate_ambiguous_op({ Op, Line, [Expr] }, Var) when Op == '+'; Op == '-' ->
{ Op, Line, [Var, Expr] };
rellocate_ambiguous_op({ Op, Meta, [Expr] }, Var) when Op == '+'; Op == '-' ->
{ Op, Meta, [Var, Expr] };
rellocate_ambiguous_op({ Call, Line, [H|T] }, Var) ->
{ Call, Line, [rellocate_ambiguous_op(H, Var)|T] }.
rellocate_ambiguous_op({ Call, Meta, [H|T] }, Var) ->
{ Call, Meta, [rellocate_ambiguous_op(H, Var)|T] }.
%% Comprehensions
translate_comprehension(Line, Kind, Args, S) ->
translate_comprehension(Meta, Kind, Args, S) ->
case elixir_tree_helpers:split_last(Args) of
{ Cases, [{do,Expr}] } ->
{ TCases, SC } = lists:mapfoldl(fun(C, Acc) -> translate_comprehension_clause(Line, C, Acc) end, S, Cases),
{ TExpr, SE } = translate_comprehension_do(Line, Kind, Expr, SC),
{ { Kind, Line, TExpr, TCases }, umergec(S, SE) };
{ TCases, SC } = lists:mapfoldl(fun(C, Acc) -> translate_comprehension_clause(Meta, C, Acc) end, S, Cases),
{ TExpr, SE } = translate_comprehension_do(Meta, Kind, Expr, SC),
{ { Kind, ?line(Meta), TExpr, TCases }, umergec(S, SE) };
_ ->
syntax_error(Line, S#elixir_scope.file, "keyword argument :do missing for comprehension ~s", [Kind])
syntax_error(Meta, S#elixir_scope.file, "keyword argument :do missing for comprehension ~s", [Kind])
end.
translate_comprehension_do(_Line, bc, { '<<>>', _, _ } = Expr, S) ->
translate_comprehension_do(_Meta, bc, { '<<>>', _, _ } = Expr, S) ->
translate_each(Expr, S);
translate_comprehension_do(Line, bc, _Expr, S) ->
syntax_error(Line, S#elixir_scope.file, "a bit comprehension expects a bit string << >> to be returned");
translate_comprehension_do(Meta, bc, _Expr, S) ->
syntax_error(Meta, S#elixir_scope.file, "a bit comprehension expects a bit string << >> to be returned");
translate_comprehension_do(_Line, _Kind, Expr, S) ->
translate_comprehension_do(_Meta, _Kind, Expr, S) ->
translate_each(Expr, S).
translate_comprehension_clause(_Line, {inbits, Line, [Left, Right]}, S) ->
translate_comprehension_clause(_Meta, {inbits, Meta, [Left, Right]}, S) ->
{ TRight, SR } = translate_each(Right, S),
{ TLeft, SL } = elixir_clauses:assigns(fun elixir_translator:translate_each/2, Left, SR),
{ { b_generate, Line, TLeft, TRight }, SL };
{ { b_generate, ?line(Meta), TLeft, TRight }, SL };
translate_comprehension_clause(_Line, {inlist, Line, [Left, Right]}, S) ->
translate_comprehension_clause(_Meta, {inlist, Meta, [Left, Right]}, S) ->
{ TRight, SR } = translate_each(Right, S),
{ TLeft, SL } = elixir_clauses:assigns(fun elixir_translator:translate_each/2, Left, SR),
{ { generate, Line, TLeft, TRight }, SL };
{ { generate, ?line(Meta), TLeft, TRight }, SL };
translate_comprehension_clause(Line, X, S) ->
translate_comprehension_clause(Meta, X, S) ->
{ TX, TS } = translate_each(X, S),
elixir_tree_helpers:convert_to_boolean(Line, TX, true, false, TS).
elixir_tree_helpers:convert_to_boolean(?line(Meta), TX, true, false, TS).
+10 -4
View File
@@ -5,11 +5,17 @@
-module(elixir_tree_helpers).
-compile({parse_transform, elixir_transform}).
-export([abstract_syntax/1, split_last/1, cons_to_list/1,
convert_to_boolean/5, returns_boolean/1,
convert_to_boolean/5, returns_boolean/1, get_line/1,
build_list/4, build_list/5, build_simple_list/2,
build_reverse_list/4, build_reverse_list/5, build_simple_reverse_list/2]).
-include("elixir.hrl").
get_line(Opts) ->
case lists:keyfind(line, 1, Opts) of
{ line, Line } when is_integer(Line) -> Line;
false -> 0
end.
split_last([]) -> { [], [] };
split_last(List) -> split_last(List, []).
split_last([H], Acc) -> { lists:reverse(Acc), H };
@@ -37,14 +43,14 @@ cons_to_list({ cons, _, Left, Right }) ->
build_list(Fun, Exprs, Line, S) ->
build_list(Fun, Exprs, Line, S, {nil, Line}).
build_list(Fun, Exprs, Line, S, Tail) ->
build_list(Fun, Exprs, Line, S, Tail) when is_integer(Line) ->
build_list_each(Fun, lists:reverse(Exprs), Line, S, Tail).
% Same as build_list, but the list given is in reverse other.
build_reverse_list(Fun, Exprs, Line, S) ->
build_list_each(Fun, Exprs, Line, S, {nil,Line}).
build_reverse_list(Fun, Exprs, Line, S, Tail) ->
build_reverse_list(Fun, Exprs, Line, S, Tail) when is_integer(Line) ->
build_list_each(Fun, Exprs, Line, S, Tail).
% Builds a simple list, without translatation, just by generating the cons-cell.
@@ -97,7 +103,7 @@ returns_boolean({ 'case', _, _, Clauses }) ->
returns_boolean(_) -> false.
convert_to_boolean(Line, Expr, Bool, InGuard, S) ->
convert_to_boolean(Line, Expr, Bool, InGuard, S) when is_integer(Line) ->
case { returns_boolean(Expr), Bool } of
{ true, true } -> { Expr, S };
{ true, false } -> { { op, Line, 'not', Expr }, S };
+122 -123
View File
@@ -4,102 +4,101 @@
-include("elixir.hrl").
-compile({parse_transform, elixir_transform}).
clauses(Line, Clauses, S) ->
Catch = elixir_clauses:get_pairs(Line, 'catch', Clauses, S),
Rescue = elixir_clauses:get_pairs(Line, rescue, Clauses, S),
Transformer = fun(X, Acc) -> each_clause(Line, X, umergec(S, Acc)) end,
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,
lists:mapfoldl(Transformer, S, Rescue ++ Catch).
each_clause(Line, { 'catch', Raw, Expr }, S) ->
each_clause(Meta, { 'catch', Raw, Expr }, S) ->
{ Args, Guards } = elixir_clauses:extract_last_guards(Raw),
Final = case Args of
[X] -> [throw, X, { '_', Line, nil }];
[X,Y] -> [X, Y, { '_', Line, nil }];
[X] -> [throw, X, { '_', Meta, nil }];
[X,Y] -> [X, Y, { '_', Meta, nil }];
[_,_,_] -> Args;
_ ->
elixir_errors:syntax_error(Line, S#elixir_scope.file, "too many arguments given for catch")
elixir_errors:syntax_error(Meta, S#elixir_scope.file, "too many arguments given for catch")
end,
Condition = { '{}', Line, Final },
elixir_clauses:assigns_block(Line, fun elixir_translator:translate_each/2, Condition, [Expr], Guards, S);
Condition = { '{}', Meta, Final },
elixir_clauses:assigns_block(?line(Meta), fun elixir_translator:translate_each/2, Condition, [Expr], Guards, S);
each_clause(Line, { rescue, [Condition], Expr }, S) ->
case normalize_rescue(Line, Condition, S) of
each_clause(Meta, { rescue, [Condition|T], Expr }, S) ->
case normalize_rescue(Meta, Condition, S) of
{ Left, Right } ->
case Left of
{ '_', _, _ } ->
{ ClauseVar, CS } = elixir_scope:build_ex_var(Line, S),
{ Clause, _ } = rescue_guards(Line, ClauseVar, Right, S),
each_clause(Line, { 'catch', [error, Clause], Expr }, CS);
{ 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);
_ ->
{ Clause, Safe } = rescue_guards(Line, Left, Right, S),
{ Clause, Safe } = rescue_guards(Meta, Left, Right, S),
case Safe of
true ->
each_clause(Line, { 'catch', [error, Clause], Expr }, S);
each_clause(Meta, { 'catch', [error, Clause|T], Expr }, S);
false ->
{ ClauseVar, CS } = elixir_scope:build_ex_var(Line, S),
{ FinalClause, _ } = rescue_guards(Line, ClauseVar, Right, S),
Match = { '=', Line, [
{ ClauseVar, CS } = elixir_scope:build_ex_var(?line(Meta), S),
{ FinalClause, _ } = rescue_guards(Meta, ClauseVar, Right, S),
Match = { '=', Meta, [
Left,
{ { '.', Line, ['Elixir.Exception', normalize] }, Line, [ClauseVar] }
{ { '.', Meta, ['Elixir.Exception', normalize] }, Meta, [ClauseVar] }
] },
FinalExpr = prepend_to_block(Line, Match, Expr),
each_clause(Line, { 'catch', [error, FinalClause], FinalExpr }, CS)
FinalExpr = prepend_to_block(Meta, Match, Expr),
each_clause(Meta, { 'catch', [error, FinalClause|T], FinalExpr }, CS)
end
end;
_ ->
Result = each_clause(Line, { 'catch', [error, Condition], Expr }, S),
validate_rescue_access(Line, Condition, S),
Result
validate_rescue_access(Meta, Condition, S),
each_clause(Meta, { 'catch', [error, Condition|T], Expr }, S)
end;
each_clause(Line, {rescue,_,_}, S) ->
elixir_errors:syntax_error(Line, S#elixir_scope.file, "too many arguments given for rescue");
each_clause(Meta, {rescue,_,_}, S) ->
elixir_errors:syntax_error(Meta, S#elixir_scope.file, "too many arguments given for rescue");
each_clause(Line, {Key,_,_}, S) ->
elixir_errors:syntax_error(Line, S#elixir_scope.file, "invalid key ~s in try", [Key]).
each_clause(Meta, {Key,_,_}, S) ->
elixir_errors:syntax_error(Meta, S#elixir_scope.file, "invalid key ~s in try", [Key]).
%% Helpers
%% rescue [Error] -> _ in [Error]
normalize_rescue(Line, List, S) when is_list(List) ->
normalize_rescue(Line, { in, Line, [{ '_', Line, nil }, List] }, S);
normalize_rescue(Meta, List, S) when is_list(List) ->
normalize_rescue(Meta, { in, Meta, [{ '_', Meta, nil }, List] }, S);
%% rescue var -> var in _
normalize_rescue(_, { Name, Line, Atom } = Rescue, S) when is_atom(Name), is_atom(Atom), Name /= '_' ->
normalize_rescue(Line, { in, Line, [Rescue, { '_', Line, nil }] }, S);
normalize_rescue(_, { Name, Meta, Atom } = Rescue, S) when is_atom(Name), is_atom(Atom), Name /= '_' ->
normalize_rescue(Meta, { in, Meta, [Rescue, { '_', Meta, nil }] }, S);
%% rescue var in [Exprs]
normalize_rescue(_, { in, Line, [Left, Right] }, S) ->
normalize_rescue(_, { in, Meta, [Left, Right] }, S) ->
case Right of
{ '_', _, _ } ->
{ Left, nil };
_ when is_list(Right) ->
is_valid_rescue_list(Right, S) andalso { Left, Right };
_ ->
Expanded = 'Elixir.Macro':expand(Right, elixir_scope:to_ex_env({ Line, S })),
Expanded = 'Elixir.Macro':expand(Right, elixir_scope:to_ex_env({ ?line(Meta), S })),
case is_valid_rescue_list(Expanded, S) of
true -> { Left, Expanded };
false ->
elixir_errors:syntax_error(Line, S#elixir_scope.file, "invalid use of operator \"in\" in rescue inside try")
elixir_errors:syntax_error(Meta, S#elixir_scope.file, "invalid use of operator \"in\" in rescue inside try")
end
end;
normalize_rescue(_, { '=', Line, [{ '__aliases__', _, _ } = Alias, { Name, _, Atom } = Var] }, S)
normalize_rescue(_, { '=', Meta, [{ '__aliases__', _, _ } = Alias, { Name, _, Atom } = Var] }, S)
when is_atom(Name) and is_atom(Atom) ->
elixir_errors:handle_file_warning(S#elixir_scope.file, { Line, ?MODULE, { rescue_no_match, Var, Alias } }),
elixir_errors:handle_file_warning(S#elixir_scope.file, { Meta, ?MODULE, { rescue_no_match, Var, Alias } }),
false;
normalize_rescue(_, { '=', Line, [{ Name, _, Atom } = Var, { '__aliases__', _, _ } = Alias] }, S)
normalize_rescue(_, { '=', Meta, [{ Name, _, Atom } = Var, { '__aliases__', _, _ } = Alias] }, S)
when is_atom(Name) and is_atom(Atom) ->
elixir_errors:handle_file_warning(S#elixir_scope.file, { Line, ?MODULE, { rescue_no_match, Var, Alias } }),
elixir_errors:handle_file_warning(S#elixir_scope.file, { Meta, ?MODULE, { rescue_no_match, Var, Alias } }),
false;
normalize_rescue(Line, Condition, S) ->
normalize_rescue(Meta, Condition, S) ->
case elixir_translator:translate_each(Condition, S#elixir_scope{context=assign}) of
{ { atom, _, Atom }, _ } ->
normalize_rescue(Line, { in, Line, [{ '_', Line, nil }, [Atom]] }, S);
normalize_rescue(Meta, { in, Meta, [{ '_', Meta, nil }, [Atom]] }, S);
_ ->
false
end.
@@ -107,39 +106,39 @@ normalize_rescue(Line, Condition, S) ->
%% Convert rescue clauses into guards.
rescue_guards(_, Var, nil, _) -> { Var, false };
rescue_guards(Line, Var, Guards, S) ->
{ RawElixir, RawErlang } = rescue_each_var(Line, Var, Guards),
{ Elixir, Erlang, Safe } = rescue_each_ref(Line, Var, Guards, RawElixir, RawErlang, RawErlang == [], S),
rescue_guards(Meta, Var, Guards, S) ->
{ RawElixir, RawErlang } = rescue_each_var(Meta, Var, Guards),
{ Elixir, Erlang, Safe } = rescue_each_ref(Meta, Var, Guards, RawElixir, RawErlang, RawErlang == [], S),
Final = case Elixir == [] of
true -> Erlang;
false ->
IsTuple = { is_tuple, Line, [Var] },
IsException = { '==', Line, [
{ { '.', Line, [erlang, element] }, Line, [2, Var] }, '__exception__'
IsTuple = { is_tuple, Meta, [Var] },
IsException = { '==', Meta, [
{ { '.', Meta, [erlang, element] }, Meta, [2, Var] }, '__exception__'
] },
OrElse = join(Line, 'or', Elixir),
[join(Line, 'and', [IsTuple, IsException, OrElse])|Erlang]
OrElse = join(Meta, 'or', Elixir),
[join(Meta, 'and', [IsTuple, IsException, OrElse])|Erlang]
end,
{
{ 'when', Line, [Var, reverse_join(Line, 'when', Final)] },
{ 'when', Meta, [Var, reverse_join(Meta, 'when', Final)] },
Safe
}.
%% Handle variables in the right side of rescue.
rescue_each_var(Line, ClauseVar, Guards) ->
rescue_each_var(Meta, ClauseVar, Guards) ->
Vars = [Var || Var <- Guards, is_var(Var)],
case Vars == [] of
true -> { [], [] };
false ->
Elixir = [exception_compare(Line, ClauseVar, Var) || Var <- Vars],
Elixir = [exception_compare(Meta, ClauseVar, Var) || Var <- Vars],
Erlang = lists:map(fun(Rescue) ->
Compares = [{ '==', Line, [Rescue, Var] } || Var <- Vars],
{ 'and', Line, [
erlang_rescue_guard_for(Line, ClauseVar, Rescue),
join(Line, 'or', Compares)
Compares = [{ '==', Meta, [Rescue, Var] } || Var <- Vars],
{ 'and', Meta, [
erlang_rescue_guard_for(Meta, ClauseVar, Rescue),
join(Meta, 'or', Compares)
] }
end, erlang_rescues()),
{ Elixir, Erlang }
@@ -150,30 +149,30 @@ rescue_each_var(Line, ClauseVar, Guards) ->
%% method for optimization.
%% Ignore variables
rescue_each_ref(Line, Var, [{ Name, _, Atom }|T], Elixir, Erlang, Safe, S) when is_atom(Name), is_atom(Atom) ->
rescue_each_ref(Line, Var, T, Elixir, Erlang, Safe, S);
rescue_each_ref(Meta, Var, [{ Name, _, Atom }|T], Elixir, Erlang, Safe, S) when is_atom(Name), is_atom(Atom) ->
rescue_each_ref(Meta, Var, T, Elixir, Erlang, Safe, S);
rescue_each_ref(Line, Var, [H|T], Elixir, Erlang, _Safe, S) when
rescue_each_ref(Meta, Var, [H|T], Elixir, Erlang, _Safe, S) when
H == 'Elixir.UndefinedFunctionError'; H == 'Elixir.ErlangError';
H == 'Elixir.ArgumentError'; H == 'Elixir.ArithmeticError';
H == 'Elixir.BadArityError'; H == 'Elixir.BadFunctionError';
H == 'Elixir.MatchError'; H == 'Elixir.CaseClauseError';
H == 'Elixir.FunctionClauseError'; H == 'Elixir.SystemLimitError' ->
Expr = { 'or', Line, [
erlang_rescue_guard_for(Line, Var, H),
exception_compare(Line, Var, H)
Expr = { 'or', Meta, [
erlang_rescue_guard_for(Meta, Var, H),
exception_compare(Meta, Var, H)
] },
rescue_each_ref(Line, Var, T, Elixir, [Expr|Erlang], false, S);
rescue_each_ref(Meta, Var, T, Elixir, [Expr|Erlang], false, S);
rescue_each_ref(Line, Var, [H|T], Elixir, Erlang, Safe, S) when is_atom(H) ->
rescue_each_ref(Line, Var, T, [exception_compare(Line, Var, H)|Elixir], Erlang, Safe, S);
rescue_each_ref(Meta, Var, [H|T], Elixir, Erlang, Safe, S) when is_atom(H) ->
rescue_each_ref(Meta, Var, T, [exception_compare(Meta, Var, H)|Elixir], Erlang, Safe, S);
rescue_each_ref(Line, Var, [H|T], Elixir, Erlang, Safe, S) ->
rescue_each_ref(Meta, Var, [H|T], Elixir, Erlang, Safe, S) ->
case elixir_translator:translate_each(H, S) of
{ { atom, _, Atom }, _ } ->
rescue_each_ref(Line, Var, [Atom|T], Elixir, Erlang, Safe, S);
rescue_each_ref(Meta, Var, [Atom|T], Elixir, Erlang, Safe, S);
_ ->
rescue_each_ref(Line, Var, T, [exception_compare(Line, Var, H)|Elixir], Erlang, Safe, S)
rescue_each_ref(Meta, Var, T, [exception_compare(Meta, Var, H)|Elixir], Erlang, Safe, S)
end;
rescue_each_ref(_, _, [], Elixir, Erlang, Safe, _) ->
@@ -188,73 +187,73 @@ erlang_rescues() ->
'Elixir.SystemLimitError', 'Elixir.ErlangError'
].
erlang_rescue_guard_for(Line, Var, List) when is_list(List) ->
join(Line, 'or', [erlang_rescue_guard_for(Line, Var, X) || X <- List]);
erlang_rescue_guard_for(Meta, Var, List) when is_list(List) ->
join(Meta, 'or', [erlang_rescue_guard_for(Meta, Var, X) || X <- List]);
erlang_rescue_guard_for(Line, Var, 'Elixir.UndefinedFunctionError') ->
{ '==', Line, [Var, undef] };
erlang_rescue_guard_for(Meta, Var, 'Elixir.UndefinedFunctionError') ->
{ '==', Meta, [Var, undef] };
erlang_rescue_guard_for(Line, Var, 'Elixir.FunctionClauseError') ->
{ '==', Line, [Var, function_clause] };
erlang_rescue_guard_for(Meta, Var, 'Elixir.FunctionClauseError') ->
{ '==', Meta, [Var, function_clause] };
erlang_rescue_guard_for(Line, Var, 'Elixir.SystemLimitError') ->
{ '==', Line, [Var, system_limit] };
erlang_rescue_guard_for(Meta, Var, 'Elixir.SystemLimitError') ->
{ '==', Meta, [Var, system_limit] };
erlang_rescue_guard_for(Line, Var, 'Elixir.ArithmeticError') ->
{ '==', Line, [Var, badarith] };
erlang_rescue_guard_for(Meta, Var, 'Elixir.ArithmeticError') ->
{ '==', Meta, [Var, badarith] };
erlang_rescue_guard_for(Line, Var, 'Elixir.BadArityError') ->
{ 'and', Line, [
{ is_tuple, Line, [Var] },
exception_compare(Line, Var, badarity)
erlang_rescue_guard_for(Meta, Var, 'Elixir.BadArityError') ->
{ 'and', Meta, [
{ is_tuple, Meta, [Var] },
exception_compare(Meta, Var, badarity)
] };
erlang_rescue_guard_for(Line, Var, 'Elixir.BadFunctionError') ->
{ 'and', Line, [
{ is_tuple, Line, [Var] },
exception_compare(Line, Var, badfun)
erlang_rescue_guard_for(Meta, Var, 'Elixir.BadFunctionError') ->
{ 'and', Meta, [
{ is_tuple, Meta, [Var] },
exception_compare(Meta, Var, badfun)
] };
erlang_rescue_guard_for(Line, Var, 'Elixir.MatchError') ->
{ 'and', Line, [
{ is_tuple, Line, [Var] },
exception_compare(Line, Var, badmatch)
erlang_rescue_guard_for(Meta, Var, 'Elixir.MatchError') ->
{ 'and', Meta, [
{ is_tuple, Meta, [Var] },
exception_compare(Meta, Var, badmatch)
] };
erlang_rescue_guard_for(Line, Var, 'Elixir.CaseClauseError') ->
{ 'and', Line, [
{ is_tuple, Line, [Var] },
exception_compare(Line, Var, case_clause)
erlang_rescue_guard_for(Meta, Var, 'Elixir.CaseClauseError') ->
{ 'and', Meta, [
{ is_tuple, Meta, [Var] },
exception_compare(Meta, Var, case_clause)
] };
erlang_rescue_guard_for(Line, Var, 'Elixir.ArgumentError') ->
{ 'or', Line, [
{ '==', Line, [Var, badarg] },
{ 'and', Line, [
{ is_tuple, Line, [Var] },
exception_compare(Line, Var, badarg)
erlang_rescue_guard_for(Meta, Var, 'Elixir.ArgumentError') ->
{ 'or', Meta, [
{ '==', Meta, [Var, badarg] },
{ 'and', Meta, [
{ is_tuple, Meta, [Var] },
exception_compare(Meta, Var, badarg)
] }
] };
erlang_rescue_guard_for(Line, Var, 'Elixir.ErlangError') ->
IsNotTuple = { 'not', Line, [{ is_tuple, Line, [Var] }] },
IsException = { '!=', Line, [
{ { '.', Line, [ erlang, element ] }, Line, [2, Var] }, '__exception__'
erlang_rescue_guard_for(Meta, Var, 'Elixir.ErlangError') ->
IsNotTuple = { 'not', Meta, [{ is_tuple, Meta, [Var] }] },
IsException = { '!=', Meta, [
{ { '.', Meta, [ erlang, element ] }, Meta, [2, Var] }, '__exception__'
] },
{ 'or', Line, [IsNotTuple, IsException] }.
{ 'or', Meta, [IsNotTuple, IsException] }.
%% Validate rescue access
validate_rescue_access(Line, { '=', _, [Left, Right] }, S) ->
validate_rescue_access(Line, Left, S),
validate_rescue_access(Line, Right, S);
validate_rescue_access(Meta, { '=', _, [Left, Right] }, S) ->
validate_rescue_access(Meta, Left, S),
validate_rescue_access(Meta, Right, S);
validate_rescue_access(Line, { { '.', _, ['Elixir.Kernel', 'access'] }, _, [Element, _] }, S) ->
validate_rescue_access(Meta, { { '.', _, ['Elixir.Kernel', 'access'] }, _, [Element, _] }, S) ->
case elixir_translator:translate_each(Element, S) of
{ { atom, _, Atom }, _ } ->
case lists:member(Atom, erlang_rescues()) of
false -> [];
true -> elixir_errors:syntax_error(Line, S#elixir_scope.file, "cannot (yet) pattern match against erlang exceptions")
true -> elixir_errors:syntax_error(Meta, S#elixir_scope.file, "cannot (yet) pattern match against erlang exceptions")
end;
_ -> []
end;
@@ -279,20 +278,20 @@ is_valid_rescue_list(Right, S) when is_list(Right) ->
{ TRight, _ } = elixir_translator:translate(Right, S),
lists:all(fun is_erl_var_or_atom/1, TRight).
exception_compare(Line, Var, Expr) ->
{ '==', Line, [
{ { '.', Line, [ erlang, element ] }, Line, [1, Var] },
exception_compare(Meta, Var, Expr) ->
{ '==', Meta, [
{ { '.', Meta, [ erlang, element ] }, Meta, [1, Var] },
Expr
] }.
join(Line, Kind, [H|T]) ->
lists:foldl(fun(X, Acc) -> { Kind, Line, [Acc, X] } end, H, T).
join(Meta, Kind, [H|T]) ->
lists:foldl(fun(X, Acc) -> { Kind, Meta, [Acc, X] } end, H, T).
reverse_join(Line, Kind, [H|T]) ->
lists:foldl(fun(X, Acc) -> { Kind, Line, [X, Acc] } end, H, T).
reverse_join(Meta, Kind, [H|T]) ->
lists:foldl(fun(X, Acc) -> { Kind, Meta, [X, Acc] } end, H, T).
prepend_to_block(_Line, Expr, { '__block__', Line, Args }) ->
{ '__block__', Line, [Expr|Args] };
prepend_to_block(_Meta, Expr, { '__block__', Meta, Args }) ->
{ '__block__', Meta, [Expr|Args] };
prepend_to_block(Line, Expr, Args) ->
{ '__block__', Line, [Expr, Args] }.
prepend_to_block(Meta, Expr, Args) ->
{ '__block__', Meta, [Expr, Args] }.
+26 -7
View File
@@ -61,6 +61,7 @@ defmodule Binary.Inspect.BitStringTest do
test :escape do
assert inspect("f\no") == "\"f\\no\""
assert inspect("f\\o") == "\"f\\\\o\""
assert inspect("f\ao") == "\"f\\ao\""
end
test :utf8 do
@@ -184,17 +185,35 @@ defmodule Binary.Inspect.ListTest do
end
end
defmodule Binary.Inspect.AnyTest do
defmodule Binary.Inspect.OthersTest do
use ExUnit.Case, async: true
test :funs do
bin = inspect(fn(x) -> x + 1 end)
assert '#Fun<' ++ _ = binary_to_list(bin)
def f do
fn() -> :ok end
end
end
defmodule Binary.Inspect.RegexTest do
use ExUnit.Case, async: true
test :external_elixir_funs do
bin = inspect(function(Enum.map/2))
assert bin == "function(Enum.map/2)"
end
test :external_erlang_funs do
bin = inspect(function(:lists.map/2))
assert bin == "function(:lists.map/2)"
end
test :other_funs do
assert "#Function<" <> _ = inspect(fn(x) -> x + 1 end)
assert "#Function<" <> _ = inspect(f)
end
test :pids do
assert "#PID<" <> _ = inspect(self)
end
test :references do
assert "#Reference<" <> _ = inspect(make_ref)
end
test :regex do
"%r\"foo\"m" = inspect(%r(foo)m)
+27
View File
@@ -47,16 +47,24 @@ bar
end
test :octals do
assert "\1" == <<1>>
assert "\12" == "\n"
assert "\123" == "S"
assert "\123" == "S"
assert "\377" == "ÿ"
assert "\128" == "\n8"
assert "\18" == <<1,?8>>
end
test :hex do
assert "\xa" == "\n"
assert "\xE9" == "é"
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>>
end
@@ -76,6 +84,20 @@ bar
assert (<< &2, &1 >>).(2, 1) == << 1, 2 >>
end
test :bitsyntax_with_utf do
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 <<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 >>
end
test :bitsyntax_translation do
refb = "sample"
sec_data = "another"
@@ -85,6 +107,11 @@ bar
sec_data :: binary >>
end
test :bitsyntax_size_shorcut do
assert << 1 :: 3 >> == << 1 :: size(3) >>
assert << 1 :: [unit(8), 3] >> == << 1 :: [unit(8), size(3)] >>
end
defmacrop signed_16 do
quote do
[big, signed, integer, unit(16)]
+17
View File
@@ -22,8 +22,25 @@ bar '''
end
test :octals do
assert '\1' == [1]
assert '\12' == '\n'
assert '\123' == 'S'
assert '\123' == 'S'
assert '\377' == 'ÿ'
assert '\128' == '\n8'
assert '\18' == [1, ?8]
end
test :hex do
assert '\xa' == '\n'
assert '\xE9' == 'é'
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]
end
end
+8 -3
View File
@@ -23,6 +23,10 @@ defmodule CodeTest do
assert Code.eval("one", [], delegate_locals_to: __MODULE__) == { 1, [] }
end
test :eval_with_requires do
assert Code.eval("Kernel.if true, do: :ok", [], requires: [Z, Kernel]) == { :ok, [] }
end
test :eval_quoted do
assert Code.eval_quoted(quote(do: 1 + 2)) == { 3, [] }
assert CodeTest.Sample.eval_quoted_info() == { CodeTest.Sample, "sample.ex", 13 }
@@ -44,7 +48,7 @@ defmodule CodeTest do
end
test :path_manipulation do
path = File.expand_path("../binary", __FILE__)
path = Path.expand("../binary", __FILE__)
Code.prepend_path path
assert binary_to_list(path) inlist :code.get_path
@@ -57,7 +61,8 @@ defmodule CodeTest do
end
test :string_to_ast do
assert { :ok, quote line: 1, do: 1 + 2 } = Code.string_to_ast("1 + 2")
assert Code.string_to_ast("1 + 2") == { :ok, quote hygiene: false, line: 1, do: 1 + 2 }
assert Code.string_to_ast("1 + 2; 3 + 4") == { :ok, quote hygiene: false, line: 1, do: (1 + 2; 3 + 4) }
assert { :error, _ } = Code.string_to_ast("a.1")
end
@@ -67,7 +72,7 @@ defmodule CodeTest do
end
test :string_to_ast! do
assert Code.string_to_ast!("1 + 2") == quote line: 1, do: 1 + 2
assert Code.string_to_ast!("1 + 2") == quote hygiene: false, line: 1, do: 1 + 2
assert_raise SyntaxError, fn ->
Code.string_to_ast!("a.1")
+33 -36
View File
@@ -5,6 +5,15 @@ defmodule DictTest.Common do
quote location: :keep do
use ExUnit.Case, async: true
# Most underlying Dict implementations have no key order guarantees,
# sort them before we compare:
defmacrop dicts_equal(actual, expected) do
quote do
cmp = fn { k1, _ }, { k2, _ } -> k1 < k2 end
Enum.sort(expected, cmp) == Enum.sort(actual, cmp)
end
end
test :access do
dict = new_dict [{"first_key", 1}, {"second_key", 2}]
assert dict["first_key"] == 1
@@ -48,6 +57,11 @@ defmodule DictTest.Common do
assert 2 == Dict.get dict, "second_key"
end
test :put_new do
dict = Dict.put_new(new_dict, "first_key", {1})
assert 1 == Dict.get dict, "first_key"
end
test :keys do
assert ["first_key", "second_key"] == Enum.sort Dict.keys new_dict
assert [] == Dict.keys empty_dict
@@ -78,24 +92,25 @@ defmodule DictTest.Common do
dict1 = new_dict Enum.zip ["a", "b", "c"], [1, 2, 3]
dict2 = new_dict Enum.zip ["a", "c", "d"], [3, :a, 0]
final = new_dict Enum.zip ["a", "b", "c", "d"], [3, 2, :a, 0]
assert Dict.merge(dict1, dict2) == final
actual = Dict.merge(dict1, dict2)
expected = new_dict Enum.zip ["a", "b", "c", "d"], [3, 2, :a, 0]
assert dicts_equal actual, expected
end
test :merge_with_enum do
dict1 = new_dict Enum.zip ["a", "b", "c"], [1, 2, 3]
dict2 = Enum.zip ["a", "c", "d"], [3, :a, 0]
final = new_dict(Enum.zip ["a", "b", "c", "d"], [3, 2, :a, 0])
assert Dict.merge(dict1, dict2) == final
actual = Dict.merge(dict1, dict2)
expected = new_dict(Enum.zip ["a", "b", "c", "d"], [3, 2, :a, 0])
assert dicts_equal actual, expected
end
test :merge_with_function do
dict1 = new_dict Enum.zip ["a", "b"], [1, 2]
dict2 = new_dict Enum.zip ["a", "d"], [3, 4]
result = Dict.merge dict1, dict2, fn _k, v1, v2 ->
v1 + v2
end
assert new_dict(Enum.zip ["a", "b", "d"], [4, 2, 4]) == result
actual = Dict.merge dict1, dict2, fn _k, v1, v2 -> v1 + v2 end
expected = new_dict(Enum.zip ["a", "b", "d"], [4, 2, 4])
assert dicts_equal actual, expected
end
test :has_key do
@@ -123,39 +138,21 @@ defmodule DictTest.Common do
defp empty_dict, do: unquote(module).new
defp new_dict(list // [{"first_key", 1}, {"second_key", 2}])
defp new_dict(list), do: unquote(module).new list
defp new_dict(list, transform), do: unquote(module).new list, transform
defp new_dict(list // [{"first_key", 1}, {"second_key", 2}]) do
unquote(module).new list
end
defp new_dict(list, transform) do
unquote(module).new list, transform
end
end
end
end
defmodule DictTest do
defmodule Dict.HashDictTest do
use DictTest.Common, HashDict
test :new do
assert :dict.new == elem(new_dict([]), 1)
end
end
defmodule OrdDictTest do
use DictTest.Common, OrdDict
test :new do
assert [] == elem(new_dict([]), 1)
end
end
defmodule Binary.DictTest do
use DictTest.Common, Binary.Dict
test :new do
assert [] == elem(new_dict([]), 1)
end
test :merge_mixed do
merged = Dict.merge(new_dict, new_dict([first_key: 13]))
assert merged[:first_key] == 13
assert merged["first_key"] == 13
end
defmodule Dict.ListTest do
use DictTest.Common, List.Dict
end
+49 -212
View File
@@ -1,18 +1,5 @@
Code.require_file "../test_helper.exs", __FILE__
defmodule EnumTest do
use ExUnit.Case, async: true
test :reverse do
assert Enum.reverse(URI.query_decoder("foo=bar&baz=bat")) ==
[{ "baz", "bat" }, { "foo", "bar" }]
end
test :count do
assert Enum.count(URI.query_decoder("foo=bar&baz=bat")) == 2
end
end
defmodule EnumTest.List do
use ExUnit.Case, async: true
@@ -99,8 +86,14 @@ defmodule EnumTest.List do
assert Enum.each([1,2,3], fn(x) -> Process.put(:enum_test_each, x * 2) end) == :ok
assert Process.get(:enum_test_each) == 6
assert Enum.each([], fn(x, idx) -> {x, idx} end) == :ok
assert Enum.each([1, 2, 3], fn(_, idx) -> Process.put(:enum_test_indexed_each, idx) end) == :ok
assert Process.get(:enum_test_indexed_each) == 2
after
Process.delete(:enum_test_each)
Process.delete(:enum_test_indexed_each)
end
end
@@ -157,6 +150,9 @@ defmodule EnumTest.List do
test :map do
assert Enum.map([], fn x -> x * 2 end) == []
assert Enum.map([1,2,3], fn x -> x * 2 end) == [2,4,6]
assert Enum.map([], fn x, idx -> x * idx end) == []
assert Enum.map([1, 2, 3], fn _, idx -> idx end) == [0, 1, 2]
end
test :map_reduce do
@@ -211,6 +207,32 @@ defmodule EnumTest.List do
assert Enum.take([], 3) == []
end
test :take_does_not_consume_next_without_a_need do
import PathHelpers
iterator = File.iterator!(fixture_path("one-liner.txt"))
try do
assert Enum.take(iterator, 1) == ["ONE"]
assert Enum.take(iterator, 5) == []
after
File.close(iterator)
end
end
test :take_with_no_item_works_as_no_op do
import PathHelpers
iterator = File.iterator!(fixture_path("one-liner.txt"))
try do
assert Enum.take(iterator, 0) == []
assert Enum.take(iterator, 0) == []
assert Enum.take(iterator, 0) == []
assert Enum.take(iterator, 0) == []
after
File.close(iterator)
end
end
test :take_while do
assert Enum.take_while([1,2,3], fn(x) -> x > 3 end) == []
assert Enum.take_while([1,2,3], fn(x) -> x <= 1 end) == [1]
@@ -232,204 +254,6 @@ defmodule EnumTest.List do
end
end
defmodule EnumTest.Dict.Common do
defmacro __using__(module) do
quote do
use ExUnit.Case, async: true
test :all? do
dict = unquote(module).new [{2,2}, {3,4}, {4,6}]
assert Enum.all?(dict, fn({_, v}) -> rem(v, 2) == 0 end)
refute Enum.all?(dict, fn({k, _}) -> rem(k, 2) == 0 end)
assert Enum.all?(unquote(module).new)
end
test :any? do
dict = unquote(module).new [{2,2}, {3,4}, {4,6}]
refute Enum.any?(dict, fn({_, v}) -> rem(v, 2) == 1 end)
assert Enum.any?(dict, fn({k, _}) -> rem(k, 2) == 1 end)
refute Enum.any?(unquote(module).new)
end
test :count do
dict = unquote(module).new [{2,2}, {3,4}, {4,6}]
assert Enum.count(dict) == 3
assert Enum.count(unquote(module).new) == 0
end
test :count_fun do
dict = unquote(module).new [{2,2}, {3,4}, {4,6}]
assert Enum.count(dict, fn({x,_}) -> rem(x, 2) == 0 end) == 2
assert Enum.count(unquote(module).new, fn(x) -> rem(x, 2) == 0 end) == 0
end
test :find do
dict = unquote(module).new [a: 1, b: 2, c: 3]
assert nil == Enum.find(dict, fn({_, v}) -> v == 0 end)
assert :ok == Enum.find(dict, :ok, fn({_, v}) -> v == 0 end)
assert {:a, 1} == Enum.find(dict, fn({_, v}) -> rem(v, 2) == 1 end)
assert {:b, 2} == Enum.find(dict, fn({_, v}) -> rem(v, 2) == 0 end)
end
test :find_value do
dict = unquote(module).new [a: 1, b: 2, c: 3]
assert nil == Enum.find_value(dict, fn({_, v}) -> v == 0 end)
assert :ok == Enum.find_value(dict, :ok, fn({_, v}) -> v == 0 end)
assert Enum.find_value(dict, fn({_, v}) -> rem(v, 2) == 1 end)
end
test :empty? do
assert Enum.empty?(unquote(module).new)
refute Enum.empty?(unquote(module).new [a: 1])
end
test :each do
try do
empty_dict = unquote(module).new
assert Enum.each(empty_dict, fn(x) -> x end) == :ok
dict = unquote(module).new [{"one",1}, {"two",2}, {"three",3}]
assert Enum.each(dict, fn({k, v}) -> Process.put(k, v * 2) end) == :ok
assert Process.get("one")
assert Process.get("two")
assert Process.get("three")
after
Process.delete("one")
Process.delete("two")
Process.delete("three")
end
end
test :filter do
all = [a: 1, b: 2, c: 3, d: 4]
odd = [a: 1, c: 3]
even = [b: 2, d: 4]
dict = unquote(module).new(all)
assert Enum.filter(dict, fn({_, v}) -> rem(v, 2) == 1 end) == odd
assert Enum.filter(dict, fn({_, v}) -> rem(v, 2) == 0 end) == even
assert Enum.filter(dict, fn(x) -> x end) == all
end
test :filter_map do
odd_dict = unquote(module).new [a: 1, b: 2, c: 3]
even_dict = unquote(module).new [a: 2, b: 4, c: 6]
assert Enum.filter_map(odd_dict,
fn({_, v}) -> rem(v, 2) == 0 end,
fn({k, v}) -> { k, v * 2 } end) == [b: 4]
assert Enum.filter_map(even_dict,
fn({_, v}) -> rem(v, 2) == 0 end,
fn({k, v}) -> { k, v * 2 } end) == [a: 4, b: 8, c: 12]
end
test :join do
dict = unquote(module).new [a: 1, b: 2, c: 3]
assert_raise Protocol.UndefinedError, fn ->
Enum.join dict, ","
end
assert_raise Protocol.UndefinedError, fn ->
Enum.join dict, ','
end
end
test :reduce do
dict = unquote(module).new [a: 1, b: 2, c: 3]
assert 1 == Enum.reduce(unquote(module).new, 1, fn(x, acc) -> x + acc end)
assert 7 == Enum.reduce(dict, 1, fn({_, v}, acc) -> v + acc end)
end
test :map do
dict = unquote(module).new [a: 1, b: 2, c: 3]
assert Enum.map(dict, fn {k, v} -> { k, v * 2 } end) == [a: 2, b: 4, c: 6]
assert Enum.map(unquote(module).new, fn x -> x * 2 end) == []
end
test :map_reduce do
dict = unquote(module).new [a: 1, b: 2, c: 3]
double = [a: 2, b: 4, c: 6]
assert Enum.map_reduce(dict, 1, fn({k, v}, acc) -> { {k, v * 2}, v + acc } end) == { double, 7 }
assert Enum.map_reduce(unquote(module).new, 1, fn(x, acc) -> { x * 2, x + acc } end) == { [], 1 }
end
test :partition do
all = [a: 1, b: 2, c: 3, d: 4, e: 5, f: 6, g: 7]
below_4 = [a: 1, b: 2, c: 3]
above_4 = [d: 4, e: 5, f: 6, g: 7]
dict = unquote(module).new all
assert { below_4, above_4 } == Enum.partition(dict, fn({_k, v}) -> v < 4 end)
assert { all, [] } == Enum.partition(dict, fn({_k, v}) -> v < 10 end)
end
test :sort do
dict = unquote(module).new [{:b,2},{:c,3},{:a,1},{:d,4}]
assert Enum.sort(dict) == [a: 1, b: 2, c: 3, d: 4]
end
end
end
end
defmodule EnumTest.OrdDict do
use EnumTest.Dict.Common, OrdDict
test :drop do
dict = OrdDict.new [a: 1, b: 2, c: 3]
assert Enum.drop(dict, 2) == [c: 3]
assert Enum.drop(dict, 3) == []
assert Enum.drop(dict, 4) == []
end
test :drop_while do
dict = OrdDict.new [a: 1, b: 2, c: 3]
assert Enum.drop_while(dict, fn({_k, v}) -> v < 3 end) == [c: 3]
end
test :first do
dict = OrdDict.new []
assert Enum.first(dict) == nil
dict = OrdDict.new [a: 1, b: 2, c: 3]
assert Enum.first(dict) == {:a, 1}
end
test :split do
dict = OrdDict.new [a: 1, b: 2, c: 3]
assert Enum.split(dict, 1) == { [a: 1], [b: 2, c: 3] }
assert Enum.split(dict, 0) == { [], [a: 1, b: 2, c: 3] }
assert Enum.split(dict, 3) == { [a: 1, b: 2, c: 3], [] }
assert Enum.split(dict, 4) == { [a: 1, b: 2, c: 3], [] }
assert Enum.split(dict, -1) == { [a: 1, b: 2], [c: 3] }
assert Enum.split(dict, -2) == { [a: 1], [b: 2, c: 3] }
assert Enum.split(dict, -3) == { [], [a: 1, b: 2, c: 3] }
assert Enum.split(dict, -10) == { [], [a: 1, b: 2, c: 3] }
end
test :split_while do
dict = OrdDict.new [a: 1, b: 3, c: 2, d: 4]
assert Enum.split_while(dict, fn({_k, v}) -> rem(v, 2) == 1 end) == { [a: 1, b: 3], [c: 2, d: 4] }
end
test :take do
dict = OrdDict.new [a: 1, b: 2, c: 3, d: 4]
assert Enum.take(dict, 2) == [a: 1, b: 2]
assert Enum.take(dict, 0) == []
assert Enum.take(dict, 4) == [a: 1, b: 2, c: 3, d: 4]
assert Enum.take(dict, 5) == [a: 1, b: 2, c: 3, d: 4]
end
test :take_while do
dict = OrdDict.new [a: 1, b: 3, c: 2, d: 4]
assert Enum.take_while(dict, fn({_k, v}) -> rem(v, 2) == 1 end) == [a: 1, b: 3]
assert Enum.take_while(dict, fn({_k, v}) -> v < 10 end) == [a: 1, b: 3, c: 2, d: 4]
assert Enum.take_while(dict, fn({_k, v}) -> v < 1 end) == []
end
end
defmodule EnumTest.Range do
use ExUnit.Case, async: true
@@ -724,4 +548,17 @@ defmodule EnumTest.Range do
assert Enum.zip(1..4, 1..2) == [{1, 1}, {2, 2}, {3, nil}, {4, nil}]
assert Enum.zip(1..2, 1..4) == [{1, 1}, {2, 2}]
end
end
end
defmodule EnumTest.Others do
use ExUnit.Case, async: true
test :reverse do
assert Enum.reverse(URI.query_decoder("foo=bar&baz=bat")) ==
[{ "baz", "bat" }, { "foo", "bar" }]
end
test :count do
assert Enum.count(URI.query_decoder("foo=bar&baz=bat")) == 2
end
end
+20 -18
View File
@@ -8,30 +8,32 @@ defmodule Kernel.ExceptionTest do
refute is_exception({ :foo, :bar })
end
test :format_environment do
env = __ENV__.file("foo").line(12)
assert Exception.env_stacktrace(env) == " foo:12: Kernel.ExceptionTest.test_format_environment/0\n"
env = env.function(nil)
assert Exception.env_stacktrace(env) == " foo:12: Kernel.ExceptionTest (body)\n"
test :format_entry_with_no_file_or_line do
assert Exception.format_entry({Foo, :bar, [1, 2, 3], []}) == "Foo.bar(1, 2, 3)"
assert Exception.format_entry({Foo, :bar, [], []}) == "Foo.bar()"
assert Exception.format_entry({Foo, :bar, 1, []}) == "Foo.bar/1"
end
test :format_stacktrace_with_no_file_or_line do
assert Exception.format_stacktrace({Foo, :bar, [1, 2, 3], []}) == "Foo.bar(1, 2, 3)"
assert Exception.format_stacktrace({Foo, :bar, [], []}) == "Foo.bar()"
assert Exception.format_stacktrace({Foo, :bar, 1, []}) == "Foo.bar/1"
test :format_entry_with_file_and_line do
assert Exception.format_entry({Foo, :bar, [], [file: 'file.ex', line: 10]}) == "file.ex:10: Foo.bar()"
assert Exception.format_entry({Foo, :bar, [1, 2, 3], [file: 'file.ex', line: 10]}) == "file.ex:10: Foo.bar(1, 2, 3)"
assert Exception.format_entry({Foo, :bar, 1, [file: 'file.ex', line: 10]}) == "file.ex:10: Foo.bar/1"
end
test :format_stacktrace_with_file_and_line do
assert Exception.format_stacktrace({Foo, :bar, [], [file: 'file.ex', line: 10]}) == "file.ex:10: Foo.bar()"
assert Exception.format_stacktrace({Foo, :bar, [1, 2, 3], [file: 'file.ex', line: 10]}) == "file.ex:10: Foo.bar(1, 2, 3)"
assert Exception.format_stacktrace({Foo, :bar, 1, [file: 'file.ex', line: 10]}) == "file.ex:10: Foo.bar/1"
test :format_entry_with_file_and_line_and_cwd do
assert Exception.format_entry({Foo, :bar, [], [file: '/foo/file.ex', line: 10]}, "/foo") == "file.ex:10: Foo.bar()"
end
test :format_stacktrace_with_file_no_line do
assert Exception.format_stacktrace({Foo, :bar, [], [file: 'file.ex']}) == "file.ex: Foo.bar()"
assert Exception.format_stacktrace({Foo, :bar, [], [file: 'file.ex', line: 0]}) == "file.ex: Foo.bar()"
assert Exception.format_stacktrace({Foo, :bar, [1, 2, 3], [file: 'file.ex']}) == "file.ex: Foo.bar(1, 2, 3)"
assert Exception.format_stacktrace({Foo, :bar, 1, [file: 'file.ex']}) == "file.ex: Foo.bar/1"
test :format_entry_with_file_no_line do
assert Exception.format_entry({Foo, :bar, [], [file: 'file.ex']}) == "file.ex: Foo.bar()"
assert Exception.format_entry({Foo, :bar, [], [file: 'file.ex', line: 0]}) == "file.ex: Foo.bar()"
assert Exception.format_entry({Foo, :bar, [1, 2, 3], [file: 'file.ex']}) == "file.ex: Foo.bar(1, 2, 3)"
assert Exception.format_entry({Foo, :bar, 1, [file: 'file.ex']}) == "file.ex: Foo.bar/1"
end
test :format_entry_with_fun do
assert Exception.format_entry({fn(x) -> x end, [1], []}) =~ %r"\(1\)"
assert Exception.format_entry({fn(x, y) -> { x, y } end, 2, []}) =~ %r"/2"
end
test :format_module_function_arity do
+92 -195
View File
@@ -8,22 +8,20 @@ defmodule FileTest do
defmodule Cp do
use ExUnit.Case
##
## cp_r/c is managed in setup/teardown because if it is stored in
## the repository, reltool can't build a release
##
# cp_r/c is managed in setup/teardown because if it is stored in
# the repository, reltool can't build a release
def setup do
src = fixture_path("cp_r")
:file.make_symlink 'certainly/invalid', File.join([src, "c"])
:file.make_symlink 'certainly/invalid', Path.join([src, "c"])
end
def teardown do
src = fixture_path("cp_r")
File.rm File.join([src, "c"])
File.rm Path.join([src, "c"])
end
test :cp_with_src_file_and_dest_file do
src = fixture_path("foo.txt")
src = fixture_path("file.txt")
dest = tmp_path("sample.txt")
File.touch(dest)
@@ -38,9 +36,9 @@ defmodule FileTest do
end
test :cp_with_src_file_and_dest_dir do
src = fixture_path("foo.txt")
src = fixture_path("file.txt")
dest = tmp_path("tmp")
final = File.join(dest, "foo.txt")
final = Path.join(dest, "file.txt")
File.mkdir(dest)
@@ -54,7 +52,7 @@ defmodule FileTest do
end
test :cp_with_src_file_and_dest_unknown do
src = fixture_path("foo.txt")
src = fixture_path("file.txt")
dest = tmp_path("tmp.file")
try do
@@ -73,7 +71,7 @@ defmodule FileTest do
end
test :cp_with_conflict do
src = fixture_path("foo.txt")
src = fixture_path("file.txt")
dest = tmp_path("tmp.file")
File.write!(dest, "hello")
@@ -88,7 +86,7 @@ defmodule FileTest do
end
test :cp_with_conflict_with_function do
src = fixture_path("foo.txt")
src = fixture_path("file.txt")
dest = tmp_path("tmp.file")
File.write!(dest, "hello")
@@ -107,7 +105,7 @@ defmodule FileTest do
end
test :cp_with_src_file_and_dest_file! do
src = fixture_path("foo.txt")
src = fixture_path("file.txt")
dest = tmp_path("sample.txt")
File.touch(dest)
@@ -130,10 +128,8 @@ defmodule FileTest do
end
end
#####
test :cp_r_with_src_file_and_dest_file do
src = fixture_path("foo.txt")
src = fixture_path("file.txt")
dest = tmp_path("sample.txt")
File.touch(dest)
@@ -148,9 +144,9 @@ defmodule FileTest do
end
test :cp_r_with_src_file_and_dest_dir do
src = fixture_path("foo.txt")
src = fixture_path("file.txt")
dest = tmp_path("tmp")
final = File.join(dest, "foo.txt")
final = Path.join(dest, "file.txt")
File.mkdir(dest)
@@ -164,7 +160,7 @@ defmodule FileTest do
end
test :cp_r_with_src_file_and_dest_unknown do
src = fixture_path("foo.txt")
src = fixture_path("file.txt")
dest = tmp_path("tmp.file")
try do
@@ -269,7 +265,7 @@ defmodule FileTest do
try do
File.mkdir(dest)
File.write!(File.join(dest, "a"), "hello")
File.write!(Path.join(dest, "a"), "hello")
assert File.cp_r(src, dest) == { :error, :enotdir }
after
File.rm_rf dest
@@ -277,8 +273,8 @@ defmodule FileTest do
end
test :cp_r_with_src_dir_and_dest_dir_using_lists do
src = fixture_path("cp_r/.") /> to_char_list
dest = tmp_path("tmp") /> to_char_list
src = fixture_path("cp_r/.") |> to_char_list
dest = tmp_path("tmp") |> to_char_list
File.mkdir(dest)
@@ -375,128 +371,11 @@ defmodule FileTest do
File.cp! src, dest, fn(_,_) -> true end
File.Stat[mode: src_mode] = File.stat! src
File.Stat[mode: dest_mode] = File.stat! dest
assert src_mode == dest_mode
assert src_mode == dest_mode
end
end
defmodule Paths do
use ExUnit.Case, async: true
test :expand_path_with_binary do
assert File.expand_path("/foo/bar") == "/foo/bar"
assert File.expand_path("/foo/bar/") == "/foo/bar"
assert File.expand_path("/foo/bar/.") == "/foo/bar"
assert File.expand_path("/foo/bar/../bar") == "/foo/bar"
assert File.expand_path("bar", "/foo") == "/foo/bar"
assert File.expand_path("bar/", "/foo") == "/foo/bar"
assert File.expand_path("bar/.", "/foo") == "/foo/bar"
assert File.expand_path("bar/../bar", "/foo") == "/foo/bar"
assert File.expand_path("../bar/../bar", "/foo/../foo/../foo") == "/bar"
full = File.expand_path("foo/bar")
assert File.expand_path("bar/../bar", "foo") == full
end
test :expand_path_with_list do
assert File.expand_path('/foo/bar') == '/foo/bar'
assert File.expand_path('/foo/bar/') == '/foo/bar'
assert File.expand_path('/foo/bar/.') == '/foo/bar'
assert File.expand_path('/foo/bar/../bar') == '/foo/bar'
end
test :rootname_with_binary do
assert File.rootname("~/foo/bar.ex", ".ex") == "~/foo/bar"
assert File.rootname("~/foo/bar.exs", ".ex") == "~/foo/bar.exs"
assert File.rootname("~/foo/bar.old.ex", ".ex") == "~/foo/bar.old"
end
test :rootname_with_list do
assert File.rootname('~/foo/bar.ex', '.ex') == '~/foo/bar'
assert File.rootname('~/foo/bar.exs', '.ex') == '~/foo/bar.exs'
assert File.rootname('~/foo/bar.old.ex', '.ex') == '~/foo/bar.old'
end
test :extname_with_binary do
assert File.extname("foo.erl") == ".erl"
assert File.extname("~/foo/bar") == ""
end
test :extname_with_list do
assert File.extname('foo.erl') == '.erl'
assert File.extname('~/foo/bar') == ''
end
test :dirname_with_binary do
assert File.dirname("/foo/bar.ex") == "/foo"
assert File.dirname("~/foo/bar.ex") == "~/foo"
assert File.dirname("/foo/bar/baz/") == "/foo/bar/baz"
end
test :dirname_with_list do
assert File.dirname('/foo/bar.ex') == '/foo'
assert File.dirname('~/foo/bar.ex') == '~/foo'
assert File.dirname('/foo/bar/baz/') == '/foo/bar/baz'
end
test :basename_with_binary do
assert File.basename("foo") == "foo"
assert File.basename("/foo/bar") == "bar"
assert File.basename("/") == ""
assert File.basename("~/foo/bar.ex", ".ex") == "bar"
assert File.basename("~/foo/bar.exs", ".ex") == "bar.exs"
assert File.basename("~/for/bar.old.ex", ".ex") == "bar.old"
end
test :basename_with_list do
assert File.basename('foo') == 'foo'
assert File.basename('/foo/bar') == 'bar'
assert File.basename('/') == ''
assert File.basename('~/foo/bar.ex', '.ex') == 'bar'
assert File.basename('~/foo/bar.exs', '.ex') == 'bar.exs'
assert File.basename('~/for/bar.old.ex', '.ex') == 'bar.old'
end
test :join_with_binary do
assert File.join([""]) == ""
assert File.join(["foo"]) == "foo"
assert File.join(["/", "foo", "bar"]) == "/foo/bar"
assert File.join(["~", "foo", "bar"]) == "~/foo/bar"
end
test :join_with_list do
assert File.join(['']) == ''
assert File.join(['foo']) == 'foo'
assert File.join(['/', 'foo', 'bar']) == '/foo/bar'
assert File.join(['~', 'foo', 'bar']) == '~/foo/bar'
end
test :join_two_with_binary do
assert File.join("/foo", "bar") == "/foo/bar"
assert File.join("~", "foo") == "~/foo"
end
test :join_two_with_list do
assert File.join('/foo', 'bar') == '/foo/bar'
assert File.join('~', 'foo') == '~/foo'
end
test :split_with_binary do
assert File.split("") == ["/"]
assert File.split("foo") == ["foo"]
assert File.split("/foo/bar") == ["/", "foo", "bar"]
end
test :split_with_list do
assert File.split('') == ''
assert File.split('foo') == ['foo']
assert File.split('/foo/bar') == ['/', 'foo', 'bar']
end
end
defmodule Queries do
use ExUnit.Case
@@ -509,7 +388,7 @@ defmodule FileTest do
test :exists do
assert File.exists?(__FILE__)
assert File.exists?(fixture_path)
assert File.exists?(fixture_path("foo.txt"))
assert File.exists?(fixture_path("file.txt"))
refute File.exists?(fixture_path("missing.txt"))
refute File.exists?("_missing.txt")
@@ -520,21 +399,21 @@ defmodule FileTest do
use ExUnit.Case
test :read_with_binary do
assert { :ok, "FOO\n" } = File.read(fixture_path("foo.txt"))
assert { :ok, "FOO\n" } = File.read(fixture_path("file.txt"))
assert { :error, :enoent } = File.read(fixture_path("missing.txt"))
end
test :read_with_list do
assert { :ok, "FOO\n" } = File.read(File.expand_path('../fixtures/foo.txt', __FILE__))
assert { :error, :enoent } = File.read(File.expand_path('../fixtures/missing.txt', __FILE__))
assert { :ok, "FOO\n" } = File.read(Path.expand('../fixtures/file.txt', __FILE__))
assert { :error, :enoent } = File.read(Path.expand('../fixtures/missing.txt', __FILE__))
end
test :read_with_utf8 do
assert { :ok, "Русский\n日\n" } = File.read(File.expand_path('../fixtures/utf8.txt', __FILE__))
assert { :ok, "Русский\n日\n" } = File.read(Path.expand('../fixtures/utf8.txt', __FILE__))
end
test :read! do
assert File.read!(fixture_path("foo.txt")) == "FOO\n"
assert File.read!(fixture_path("file.txt")) == "FOO\n"
expected_message = "could not read file fixtures/missing.txt: no such file or directory"
assert_raise File.Error, expected_message, fn ->
@@ -577,13 +456,13 @@ defmodule FileTest do
end
test :open_file_without_modes do
{ :ok, file } = File.open(fixture_path("foo.txt"))
{ :ok, file } = File.open(fixture_path("file.txt"))
assert IO.gets(file, "") == "FOO\n"
assert File.close(file) == :ok
end
test :open_file_with_charlist do
{ :ok, file } = File.open(fixture_path("foo.txt"), [:charlist])
{ :ok, file } = File.open(fixture_path("file.txt"), [:charlist])
assert IO.gets(file, "") == 'FOO\n'
assert File.close(file) == :ok
end
@@ -595,7 +474,7 @@ defmodule FileTest do
end
test :open_readonly_by_default do
{ :ok, file } = File.open(fixture_path("foo.txt"))
{ :ok, file } = File.open(fixture_path("file.txt"))
assert_raise ArgumentError, fn -> IO.write(file, "foo") end
assert File.close(file) == :ok
end
@@ -641,7 +520,7 @@ defmodule FileTest do
end
test :open_a_file_with_function do
file = fixture_path("foo.txt")
file = fixture_path("file.txt")
assert File.open(file, IO.readline(&1)) == { :ok, "FOO\n" }
end
@@ -653,7 +532,7 @@ defmodule FileTest do
end
test :open_a_file_with_function! do
file = fixture_path("foo.txt")
file = fixture_path("file.txt")
assert File.open!(file, IO.readline(&1)) == "FOO\n"
end
end
@@ -673,7 +552,7 @@ defmodule FileTest do
end
test :mkdir_with_list do
fixture = tmp_path("tmp_test") /> to_char_list
fixture = tmp_path("tmp_test") |> to_char_list
try do
refute File.exists?(fixture)
assert File.mkdir(fixture) == :ok
@@ -684,8 +563,8 @@ defmodule FileTest do
end
test :mkdir_with_invalid_path do
fixture = fixture_path("foo.txt")
invalid = File.join fixture, "test"
fixture = fixture_path("file.txt")
invalid = Path.join fixture, "test"
assert File.exists?(fixture)
assert File.mkdir(invalid) == { :error, :enotdir }
refute File.exists?(invalid)
@@ -703,8 +582,8 @@ defmodule FileTest do
end
test :mkdir_with_invalid_path! do
fixture = fixture_path("foo.txt")
invalid = File.join fixture, "test"
fixture = fixture_path("file.txt")
invalid = Path.join fixture, "test"
assert File.exists?(fixture)
assert_raise File.Error, "could not make directory #{invalid}: not a directory", fn ->
File.mkdir!(invalid)
@@ -724,7 +603,7 @@ defmodule FileTest do
test :mkdir_p_with_nested_directory_and_binary do
base = tmp_path("tmp_test")
fixture = File.join(base, "test")
fixture = Path.join(base, "test")
refute File.exists?(base)
try do
@@ -737,8 +616,8 @@ defmodule FileTest do
end
test :mkdir_p_with_nested_directory_and_list do
base = tmp_path("tmp_test") /> to_char_list
fixture = File.join(base, "test")
base = tmp_path("tmp_test") |> to_char_list
fixture = Path.join(base, "test")
refute File.exists?(base)
try do
@@ -752,7 +631,7 @@ defmodule FileTest do
test :mkdir_p_with_nested_directory_and_existing_parent do
base = tmp_path("tmp_test")
fixture = File.join(base, "test")
fixture = Path.join(base, "test")
File.mkdir(base)
@@ -766,8 +645,8 @@ defmodule FileTest do
end
test :mkdir_p_with_invalid_path do
assert File.exists?(fixture_path("foo.txt"))
invalid = File.join fixture_path("foo.txt"), "test/foo"
assert File.exists?(fixture_path("file.txt"))
invalid = Path.join fixture_path("file.txt"), "test/foo"
assert File.mkdir(invalid) == { :error, :enotdir }
refute File.exists?(invalid)
end
@@ -784,8 +663,8 @@ defmodule FileTest do
end
test :mkdir_p_with_invalid_path! do
fixture = fixture_path("foo.txt")
invalid = File.join fixture, "test"
fixture = fixture_path("file.txt")
invalid = Path.join fixture, "test"
assert File.exists?(fixture)
assert_raise File.Error, "could not make directory (with -p) #{invalid}: not a directory", fn ->
File.mkdir_p!(invalid)
@@ -796,18 +675,16 @@ defmodule FileTest do
defmodule Rm do
use ExUnit.Case
##
## cp_r/c is managed in setup/teardown because if it is stored in
## the repository, reltool can't build a release
##
# cp_r/c is managed in setup/teardown because if it is stored in
# the repository, reltool can't build a release
def setup do
src = fixture_path("cp_r")
:file.make_symlink 'certainly/invalid', File.join([src, "c"])
:file.make_symlink 'certainly/invalid', Path.join([src, "c"])
end
def teardown do
src = fixture_path("cp_r")
File.rm File.join([src, "c"])
File.rm Path.join([src, "c"])
end
test :rm_file do
@@ -849,7 +726,7 @@ defmodule FileTest do
end
test :rmdir_with_file do
assert File.rmdir(fixture_path("foo.txt")) == { :error, :enotdir }
assert File.rmdir(fixture_path("file.txt")) == { :error, :enotdir }
end
test :rmdir! do
@@ -861,7 +738,7 @@ defmodule FileTest do
end
test :rmdir_with_file! do
fixture = fixture_path("foo.txt")
fixture = fixture_path("file.txt")
assert_raise File.Error, "could not remove directory #{fixture}: not a directory", fn ->
File.rmdir!(fixture)
end
@@ -897,12 +774,12 @@ defmodule FileTest do
{ :ok, files } = File.rm_rf(fixture)
assert length(files) == 1
assert File.exists?(fixture_path("foo.txt"))
assert File.exists?(fixture_path("file.txt"))
refute File.exists?(fixture)
end
test :rm_rf_with_char_list do
fixture = tmp_path("tmp") /> to_char_list
fixture = tmp_path("tmp") |> to_char_list
File.mkdir(fixture)
File.cp_r!(fixture_path("cp_r/."), fixture)
@@ -913,7 +790,7 @@ defmodule FileTest do
{ :ok, files } = File.rm_rf(fixture)
assert length(files) == 8
assert fixture inlist files
assert (tmp_path("tmp/a/1.txt") /> to_char_list) inlist files
assert (tmp_path("tmp/a/1.txt") |> to_char_list) inlist files
refute File.exists?(tmp_path("tmp/a/1.txt"))
refute File.exists?(tmp_path("tmp/a/a/2.txt"))
@@ -933,7 +810,7 @@ defmodule FileTest do
end
test :rm_rf_with_invalid do
fixture = fixture_path "foo.txt/path"
fixture = fixture_path "file.txt/path"
assert File.rm_rf(fixture) == { :error, :enotdir }
end
@@ -958,7 +835,7 @@ defmodule FileTest do
end
test :rm_rf_with_invalid! do
fixture = fixture_path "foo.txt/path"
fixture = fixture_path "file.txt/path"
assert_raise File.Error, "could not remove files and directories recursively from #{fixture}: not a directory", fn ->
File.rm_rf!(fixture)
end
@@ -985,7 +862,7 @@ defmodule FileTest do
end
test :iterator do
src = File.open! fixture_path("foo.txt")
src = File.open! fixture_path("file.txt")
dest = tmp_path("tmp_test.txt")
try do
@@ -1002,7 +879,7 @@ defmodule FileTest do
end
test :iterator_with_path do
src = fixture_path("foo.txt")
src = fixture_path("file.txt")
dest = tmp_path("tmp_test.txt")
try do
@@ -1019,7 +896,7 @@ defmodule FileTest do
end
test :iterator! do
src = fixture_path("foo.txt")
src = fixture_path("file.txt")
dest = tmp_path("tmp_test.txt")
try do
@@ -1035,8 +912,14 @@ defmodule FileTest do
end
end
test :iterator_can_be_closed do
src = fixture_path("file.txt")
iterator = File.iterator!(src)
assert File.close(iterator) == :ok
end
test :biniterator do
src = File.open! fixture_path("foo.txt")
src = File.open! fixture_path("file.txt")
dest = tmp_path("tmp_test.txt")
try do
@@ -1053,7 +936,7 @@ defmodule FileTest do
end
test :biniterator_with_path do
src = fixture_path("foo.txt")
src = fixture_path("file.txt")
dest = tmp_path("tmp_test.txt")
try do
@@ -1070,7 +953,7 @@ defmodule FileTest do
end
test :biniterator! do
src = fixture_path("foo.txt")
src = fixture_path("file.txt")
dest = tmp_path("tmp_test.txt")
try do
@@ -1086,8 +969,14 @@ defmodule FileTest do
end
end
test :biniterator_can_be_closed do
src = fixture_path("file.txt")
iterator = File.biniterator!(src)
assert File.close(iterator) == :ok
end
test :copy do
src = fixture_path("foo.txt")
src = fixture_path("file.txt")
dest = tmp_path("tmp_test.txt")
try do
refute File.exists?(dest)
@@ -1099,7 +988,7 @@ defmodule FileTest do
end
test :copy_with_bytes_count do
src = fixture_path("foo.txt")
src = fixture_path("file.txt")
dest = tmp_path("tmp_test.txt")
try do
refute File.exists?(dest)
@@ -1117,7 +1006,7 @@ defmodule FileTest do
end
test :copy! do
src = fixture_path("foo.txt")
src = fixture_path("file.txt")
dest = tmp_path("tmp_test.txt")
try do
refute File.exists?(dest)
@@ -1129,7 +1018,7 @@ defmodule FileTest do
end
test :copy_with_bytes_count! do
src = fixture_path("foo.txt")
src = fixture_path("file.txt")
dest = tmp_path("tmp_test.txt")
try do
refute File.exists?(dest)
@@ -1152,26 +1041,34 @@ defmodule FileTest do
{ :ok, current } = File.cwd
try do
assert File.cd(fixture_path) == :ok
assert File.exists?("foo.txt")
assert File.exists?("file.txt")
after
File.cd!(current)
end
end
test :cwd_and_cd_with_utf8 do
if :file.native_name_encoding == :utf8 do
File.cd!(fixture_path("héllò"), fn ->
assert Path.basename(File.cwd!) == "héllò"
end)
end
end
test :invalid_cd do
assert File.cd(fixture_path("foo.txt")) == { :error, :enotdir }
assert File.cd(fixture_path("file.txt")) == { :error, :enotdir }
end
test :invalid_cd! do
message = "could not set current working directory to #{fixture_path("foo.txt")}: not a directory"
message = "could not set current working directory to #{fixture_path("file.txt")}: not a directory"
assert_raise File.Error, message, fn ->
File.cd!(fixture_path("foo.txt"))
File.cd!(fixture_path("file.txt"))
end
end
test :cd_with_function do
assert File.cd!(fixture_path, fn ->
assert File.exists?("foo.txt")
assert File.exists?("file.txt")
:cd_result
end) == :cd_result
end
@@ -1207,7 +1104,7 @@ defmodule FileTest do
end
test :touch_with_failure do
fixture = fixture_path("foo.txt/bar")
fixture = fixture_path("file.txt/bar")
assert File.touch(fixture) == { :error, :enotdir }
end
@@ -1216,7 +1113,7 @@ defmodule FileTest do
end
test :touch_with_failure! do
fixture = fixture_path("foo.txt/bar")
fixture = fixture_path("file.txt/bar")
assert_raise File.Error, "could not touch #{fixture}: not a directory", fn ->
File.touch!(fixture)
end
@@ -1229,4 +1126,4 @@ defmodule FileTest do
defp last_year({ { year, month, day },time }) do
{ { year - 1, month, day }, time }
end
end
end
@@ -0,0 +1 @@
ONE
@@ -0,0 +1,38 @@
Code.require_file "../../test_helper.exs", __FILE__
defmodule GenServer.BehaviourTest do
use ExUnit.Case, async: true
defmodule Sample do
use GenServer.Behaviour
# Callbacks
def handle_call(:pop, _from, [h|t]) do
{ :reply, h, t }
end
def handle_call(:terminate, _from, config) do
{ :stop, :normal, :ok, config }
end
def handle_call(_request, _from, _config) do
super
end
def handle_cast({ :push, item }, config) do
{ :noreply, [item|config] }
end
def handle_cast(request, config) do
super(request, config)
end
end
test :using do
assert { :ok, pid } = :gen_server.start_link(Sample, [:hello], [])
assert :gen_server.call(pid, :pop) == :hello
assert :gen_server.cast(pid, { :push, :world }) == :ok
assert :gen_server.call(pid, :pop) == :world
end
end
+253
View File
@@ -0,0 +1,253 @@
Code.require_file "../test_helper.exs", __FILE__
defmodule HashDictTest do
use ExUnit.Case, async: true
test :smoke_small_range_test do
smoke_test(1..8)
smoke_test(8..1)
end
test :smoke_medium_range_test do
smoke_test(1..80)
smoke_test(80..1)
end
test :smoke_large_range_test do
smoke_test(1..1200)
smoke_test(1200..1)
end
test :get! do
dict = filled_dict(8)
assert HashDict.get!(dict, 1) == 1
assert_raise KeyError, fn ->
HashDict.get!(dict, 11)
end
end
test :empty do
assert HashDict.empty filled_dict(8) == HashDict.new
assert HashDict.empty filled_dict(20) == HashDict.new
assert HashDict.empty filled_dict(120) == HashDict.new
end
test :has_key? do
dict = filled_dict(8)
assert HashDict.has_key? dict, 4
refute HashDict.has_key? dict, 16
dict = filled_dict(20)
assert HashDict.has_key? dict, 10
refute HashDict.has_key? dict, 30
dict = filled_dict(120)
assert HashDict.has_key? dict, 60
refute HashDict.has_key? dict, 240
end
test :put_new do
dict = filled_dict(8)
dict = HashDict.put_new(dict, 1, 11)
assert HashDict.get(dict, 1) == 1
assert HashDict.size(dict) == 8
dict = HashDict.put_new(dict, 11, 13)
assert HashDict.get(dict, 11) == 13
assert HashDict.size(dict) == 9
end
test :update do
dict = filled_dict(8)
dict = HashDict.update(dict, 1, &1 * 2)
assert HashDict.get(dict, 1) == 2
assert HashDict.size(dict) == 8
assert_raise KeyError, fn ->
HashDict.update(dict, 11, &1 * 2)
end
dict = HashDict.update(dict, 3, 3, &1 * 2)
assert HashDict.get(dict, 3) == 6
assert HashDict.size(dict) == 8
dict = HashDict.update(dict, 11, 13, &1 * 2)
assert HashDict.get(dict, 11) == 13
assert HashDict.size(dict) == 9
end
test :to_list do
list = filled_dict(8) |> HashDict.to_list
assert length(list) == 8
assert { 1, 1 } inlist list
list = filled_dict(20) |> HashDict.to_list
assert length(list) == 20
assert { 1, 1 } inlist list
list = filled_dict(120) |> HashDict.to_list
assert length(list) == 120
assert { 1, 1 } inlist list
end
test :keys do
list = filled_dict(8) |> HashDict.keys
assert length(list) == 8
assert 1 inlist list
list = filled_dict(20) |> HashDict.keys
assert length(list) == 20
assert 1 inlist list
list = filled_dict(120) |> HashDict.keys
assert length(list) == 120
assert 1 inlist list
end
test :values do
list = filled_dict(8) |> HashDict.values
assert length(list) == 8
assert 1 inlist list
list = filled_dict(20) |> HashDict.values
assert length(list) == 20
assert 1 inlist list
list = filled_dict(120) |> HashDict.values
assert length(list) == 120
assert 1 inlist list
end
test :enum do
assert Enum.count(filled_dict(8)) == 8
assert Enum.map(filled_dict(3), fn({ k, v }) -> k + v end) == [2,4,6]
end
test :access do
assert filled_dict(8)[1] == 1
assert filled_dict(8)[5] == 5
assert filled_dict(8)[9] == nil
end
test :inspect do
assert inspect(filled_dict(8)) =~ %r"#HashDict<"
end
test :small_range_merge do
dict1 = filled_dict(8)
dict2 = Enum.reduce 6..10, HashDict.new, fn(i, d) -> HashDict.put(d, i, i * 2) end
dict = HashDict.merge(dict1, dict2)
assert HashDict.get(dict, 3) == 3
assert HashDict.get(dict, 6) == 12
assert HashDict.get(dict, 10) == 20
assert HashDict.size(dict) == 10
dict = HashDict.merge(dict2, dict1)
assert HashDict.get(dict, 3) == 3
assert HashDict.get(dict, 6) == 6
assert HashDict.get(dict, 10) == 20
assert HashDict.size(dict) == 10
dict = HashDict.merge(dict1, dict2, fn(k, v1, v2) -> 3*k + 2*v1 + v2 end)
assert HashDict.get(dict, 3) == 3
assert HashDict.get(dict, 6) == 6 * 7
assert HashDict.get(dict, 10) == 20
assert HashDict.size(dict) == 10
list = Enum.map 6..10, fn x -> { x, x * 2 } end
dict = HashDict.merge(dict1, list)
assert HashDict.get(dict, 3) == 3
assert HashDict.get(dict, 6) == 12
assert HashDict.get(dict, 10) == 20
assert HashDict.size(dict) == 10
end
test :medium_range_merge do
dict1 = filled_dict(20)
dict2 = Enum.reduce 18..22, HashDict.new, fn(i, d) -> HashDict.put(d, i, i * 2) end
dict = HashDict.merge(dict1, dict2)
assert HashDict.get(dict, 16) == 16
assert HashDict.get(dict, 18) == 36
assert HashDict.get(dict, 22) == 44
assert HashDict.size(dict) == 22
dict = HashDict.merge(dict2, dict1)
assert HashDict.get(dict, 16) == 16
assert HashDict.get(dict, 18) == 18
assert HashDict.get(dict, 22) == 44
assert HashDict.size(dict) == 22
dict = HashDict.merge(dict1, dict2, fn(k, v1, v2) -> 3*k + 2*v1 + v2 end)
assert HashDict.get(dict, 16) == 16
assert HashDict.get(dict, 18) == 18 * 7
assert HashDict.get(dict, 22) == 44
assert HashDict.size(dict) == 22
list = Enum.map 18..22, fn x -> { x, x * 2 } end
dict = HashDict.merge(dict1, list)
assert HashDict.get(dict, 16) == 16
assert HashDict.get(dict, 18) == 36
assert HashDict.get(dict, 22) == 44
assert HashDict.size(dict) == 22
end
test :large_range_merge do
dict1 = filled_dict(120)
dict2 = Enum.reduce 118..122, HashDict.new, fn(i, d) -> HashDict.put(d, i, i * 2) end
dict = HashDict.merge(dict1, dict2)
assert HashDict.get(dict, 116) == 116
assert HashDict.get(dict, 118) == 236
assert HashDict.get(dict, 122) == 244
assert HashDict.size(dict) == 122
dict = HashDict.merge(dict2, dict1)
assert HashDict.get(dict, 116) == 116
assert HashDict.get(dict, 118) == 118
assert HashDict.get(dict, 122) == 244
assert HashDict.size(dict) == 122
dict = HashDict.merge(dict1, dict2, fn(k, v1, v2) -> 3*k + 2*v1 + v2 end)
assert HashDict.get(dict, 116) == 116
assert HashDict.get(dict, 118) == 118 * 7
assert HashDict.get(dict, 122) == 244
assert HashDict.size(dict) == 122
list = Enum.map 118..122, fn x -> { x, x * 2 } end
dict = HashDict.merge(dict1, list)
assert HashDict.get(dict, 116) == 116
assert HashDict.get(dict, 118) == 236
assert HashDict.get(dict, 122) == 244
assert HashDict.size(dict) == 122
end
defp smoke_test(range) do
{ dict, _ } = Enum.reduce range, { HashDict.new, 1 }, fn(x, { acc, i }) ->
acc = HashDict.put(acc, x, x)
assert HashDict.size(acc) == i
{ acc, i + 1 }
end
Enum.each range, fn(x) ->
assert HashDict.get(dict, x) == x
end
{ dict, _ } = Enum.reduce range, { dict, Enum.count(range) }, fn(x, { acc, i }) ->
assert HashDict.size(acc) == i
acc = HashDict.delete(acc, x)
assert HashDict.size(acc) == i - 1
assert HashDict.get(acc, x) == nil
{ acc, i - 1 }
end
dict
end
defp filled_dict(range) do
Enum.reduce 1..range, HashDict.new, HashDict.put(&2, &1, &1)
end
end
+11 -11
View File
@@ -4,25 +4,25 @@ defmodule IOTest do
use ExUnit.Case, async: true
test :read_with_count do
{ :ok, file } = File.open(File.expand_path('../fixtures/foo.txt', __FILE__), [:charlist])
{ :ok, file } = File.open(Path.expand('../fixtures/file.txt', __FILE__), [:charlist])
assert 'FOO' == IO.read(file, 3)
assert File.close(file) == :ok
end
test :read_with_utf8_and_binary do
{ :ok, file } = File.open(File.expand_path('../fixtures/utf8.txt', __FILE__), [:utf8])
{ :ok, file } = File.open(Path.expand('../fixtures/utf8.txt', __FILE__), [:utf8])
assert "Русский" == IO.read(file, 7)
assert File.close(file) == :ok
end
test :binread do
{ :ok, file } = File.open(File.expand_path('../fixtures/utf8.txt', __FILE__))
{ :ok, file } = File.open(Path.expand('../fixtures/utf8.txt', __FILE__))
assert "Русский" == IO.binread(file, 14)
assert File.close(file) == :ok
end
test :getb do
{ :ok, file } = File.open(File.expand_path('../fixtures/foo.txt', __FILE__))
{ :ok, file } = File.open(Path.expand('../fixtures/file.txt', __FILE__))
assert "F" == IO.getb(file, "")
assert "O" == IO.getb(file, "")
assert "O" == IO.getb(file, "")
@@ -32,47 +32,47 @@ defmodule IOTest do
end
test :getb_with_count do
{ :ok, file } = File.open(File.expand_path('../fixtures/foo.txt', __FILE__), [:charlist])
{ :ok, file } = File.open(Path.expand('../fixtures/file.txt', __FILE__), [:charlist])
assert 'FOO' == IO.getb(file, "", 3)
assert File.close(file) == :ok
end
test :getb_with_utf8_and_binary do
{ :ok, file } = File.open(File.expand_path('../fixtures/utf8.txt', __FILE__), [:utf8])
{ :ok, file } = File.open(Path.expand('../fixtures/utf8.txt', __FILE__), [:utf8])
assert "Русский" == IO.getb(file, "", 7)
assert File.close(file) == :ok
end
test :gets do
{ :ok, file } = File.open(File.expand_path('../fixtures/foo.txt', __FILE__), [:charlist])
{ :ok, file } = File.open(Path.expand('../fixtures/file.txt', __FILE__), [:charlist])
assert 'FOO\n' == IO.gets(file, "")
assert :eof == IO.gets(file, "")
assert File.close(file) == :ok
end
test :gets_with_utf8_and_binary do
{ :ok, file } = File.open(File.expand_path('../fixtures/utf8.txt', __FILE__), [:utf8])
{ :ok, file } = File.open(Path.expand('../fixtures/utf8.txt', __FILE__), [:utf8])
assert "Русский\n" == IO.gets(file, "")
assert "日\n" == IO.gets(file, "")
assert File.close(file) == :ok
end
test :readline do
{ :ok, file } = File.open(File.expand_path('../fixtures/foo.txt', __FILE__))
{ :ok, file } = File.open(Path.expand('../fixtures/file.txt', __FILE__))
assert "FOO\n" == IO.readline(file)
assert :eof == IO.readline(file)
assert File.close(file) == :ok
end
test :readline_with_utf8_and_binary do
{ :ok, file } = File.open(File.expand_path('../fixtures/utf8.txt', __FILE__), [:utf8])
{ :ok, file } = File.open(Path.expand('../fixtures/utf8.txt', __FILE__), [:utf8])
assert "Русский\n" == IO.readline(file)
assert "日\n" == IO.readline(file)
assert File.close(file) == :ok
end
test :binreadline do
{ :ok, file } = File.open(File.expand_path('../fixtures/utf8.txt', __FILE__))
{ :ok, file } = File.open(Path.expand('../fixtures/utf8.txt', __FILE__))
assert "Русский\n" == IO.binreadline(file)
assert "日\n" == IO.binreadline(file)
assert File.close(file) == :ok
+6 -6
View File
@@ -17,17 +17,17 @@ defmodule Kernel.CLI.OptionParsingTest do
import PathHelpers
test :path do
root = fixture_path("../../..") /> to_char_list
root = fixture_path("../../..") |> to_char_list
list = elixir('-e "IO.inspect :code.get_path" -pa "#{root}/*" -pz "#{root}/lib/*"')
{ path, _ } = Code.eval list, []
# pa
assert File.expand_path('ebin', root) inlist path
assert File.expand_path('lib', root) inlist path
assert File.expand_path('src', root) inlist path
assert Path.expand('ebin', root) inlist path
assert Path.expand('lib', root) inlist path
assert Path.expand('src', root) inlist path
# pz
assert File.expand_path('lib/list', root) inlist path
assert Path.expand('lib/list', root) inlist path
end
end
@@ -36,7 +36,7 @@ defmodule Kernel.CLI.AtExitTest do
import PathHelpers
test :at_exit do
assert elixir(fixture_path("at_exit.exs") /> to_char_list) ==
assert elixir(fixture_path("at_exit.exs") |> to_char_list) ==
'goodbye cruel world with status 0\n'
end
end
+4 -4
View File
@@ -4,14 +4,14 @@ defmodule Kernel.DocTest do
use ExUnit.Case
test :compiled_docs do
path = File.expand_path("../../fixtures/compiled_with_docs.ex", __FILE__)
path = Path.expand("../../fixtures/compiled_with_docs.ex", __FILE__)
try do
Code.load_file path
expected = [
{{:example,1},5,:def,[{:var,6,nil}],"Some example"},
{{:nodoc,1},8,:def,[{:"//",8,[{:var,8,nil},0]}],nil}
{{:example,1},5,:def,[{:var,[line: 6],nil}],"Some example"},
{{:nodoc,1},8,:def,[{:"//",[line: 8],[{:var,[line: 8],nil},0]}],nil}
]
assert CompiledWithDocs.__info__(:docs) == expected
@@ -23,7 +23,7 @@ defmodule Kernel.DocTest do
end
test :compiled_without_docs do
path = File.expand_path("../../fixtures/compiled_with_docs.ex", __FILE__)
path = Path.expand("../../fixtures/compiled_with_docs.ex", __FILE__)
try do
Code.compiler_options(docs: false)
@@ -184,6 +184,11 @@ defmodule Kernel.ErrorsTest do
assert "nofile:1: unit in bitstring expects an integer as argument" == format_rescue '<<1 :: unit(x)>>'
end
test :invalid_var! do
assert "nofile:1: expected var!(y) to expand to an existing variable or be a part of a match",
format_rescue 'var!(x)'
end
test :invalid_alias do
assert "nofile:1: invalid args for alias, cannot create nested alias Sample.Lists" ==
format_rescue 'alias :lists, as: Sample.Lists'
+26 -39
View File
@@ -9,51 +9,34 @@ end
defmodule Kernel.ImportOnlyTest do
use ExUnit.Case, async: true
require Kernel.ImportAvailable
import :lists, only: Kernel.ImportAvailable.flatten
test :import_with_only do
require Kernel.ImportAvailable
import :lists, only: Kernel.ImportAvailable.flatten
assert flatten([1,[2],3]) == [1,2,3]
end
test :import_erlang do
test :import_all do
import :lists
assert flatten([1,[2],3]) == [1,2,3]
end
test :import_except_none do
import :lists, except: []
assert flatten([1,[2],3]) == [1,2,3]
end
test :import_with_except_erlang do
import :lists, except: [each: 2]
assert flatten([1,[2],3]) == [1,2,3]
end
end
defmodule Kernel.ImportAllTest do
defmodule Kernel.DoubleImportTest do
use ExUnit.Case, async: true
import :lists
test :import_erlang do
assert flatten([1,[2],3]) == [1,2,3]
end
end
defmodule Kernel.ImportExceptNoneTest do
use ExUnit.Case, async: true
import :lists, except: []
test :import_erlang do
assert flatten([1,[2],3]) == [1,2,3]
end
end
defmodule Kernel.ImportExceptTest do
use ExUnit.Case, async: true
import :lists, except: [each: 2]
test :import_erlang do
assert flatten([1,[2],3]) == [1,2,3]
end
end
defmodule Kernel.ImportTwiceWithExceptTest do
use ExUnit.Case, async: true
import :lists, except: [flatten: 1]
import :lists, except: [each: 2]
test :import_erlang do
test :import_double_except do
import :lists, except: [flatten: 1]
import :lists, except: [each: 2]
assert flatten([1,[2],3]) == [1,[2],3]
end
@@ -75,10 +58,14 @@ defmodule Kernel.ExplicitUnderscored do
def __underscore__(x), do: x * 2
end
defmodule Kernel.ImportUnderscoreTest do
use ExUnit.Case, async: true
test :includes_only_underscore do
import Kernel.Underscored, only: [__underscore__: 1]
assert __underscore__(3) == 3
end
import :all, Kernel.ExplicitUnderscored
test :does_not_include_underscored do

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